Merge branch 'vendor/LIBARCHIVE'
authorPeter Avalos <pavalos@dragonflybsd.org>
Wed, 11 Jul 2012 03:39:24 +0000 (20:39 -0700)
committerPeter Avalos <pavalos@dragonflybsd.org>
Wed, 11 Jul 2012 03:39:24 +0000 (20:39 -0700)
1  2 
contrib/libarchive/libarchive/archive_read_disk_posix.c

@@@ -1,6 -1,6 +1,6 @@@
  /*-
   * Copyright (c) 2003-2009 Tim Kientzle
-  * Copyright (c) 2010,2011 Michihiro NAKAJIMA
+  * Copyright (c) 2010-2012 Michihiro NAKAJIMA
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
@@@ -52,6 -52,19 +52,19 @@@ __FBSDID("$FreeBSD$")
  #ifdef HAVE_LINUX_MAGIC_H
  #include <linux/magic.h>
  #endif
+ #ifdef HAVE_LINUX_FS_H
+ #include <linux/fs.h>
+ #endif
+ /*
+  * Some Linux distributions have both linux/ext2_fs.h and ext2fs/ext2_fs.h.
+  * As the include guards don't agree, the order of include is important.
+  */
+ #ifdef HAVE_LINUX_EXT2_FS_H
+ #include <linux/ext2_fs.h>      /* for Linux file flags */
+ #endif
+ #if defined(HAVE_EXT2FS_EXT2_FS_H) && !defined(__CYGWIN__)
+ #include <ext2fs/ext2_fs.h>     /* Linux file flags, broken on Cygwin */
+ #endif
  #ifdef HAVE_DIRECT_H
  #include <direct.h>
  #endif
@@@ -76,6 -89,9 +89,9 @@@
  #ifdef HAVE_UNISTD_H
  #include <unistd.h>
  #endif
+ #ifdef HAVE_SYS_IOCTL_H
+ #include <sys/ioctl.h>
+ #endif
  
  #include "archive.h"
  #include "archive_string.h"
@@@ -222,6 -238,7 +238,7 @@@ struct tree 
        char                     symlink_mode;
        struct filesystem       *current_filesystem;
        struct filesystem       *filesystem_table;
+       int                      initial_filesystem_id;
        int                      current_filesystem_id;
        int                      max_filesystem_id;
        int                      allocated_filesytem;
  #define       onWorkingDir    64 /* We are on the working dir where we are
                            * reading directory entry at this time. */
  #define       needsRestoreTimes 128
+ #define       onInitialDir    256 /* We are on the initial dir. */
  
  static int
  tree_dir_next_posix(struct tree *t);
@@@ -342,6 -360,7 +360,7 @@@ static const char *trivial_lookup_uname
  static int    setup_sparse(struct archive_read_disk *, struct archive_entry *);
  static int    close_and_restore_time(int fd, struct tree *,
                    struct restore_time *);
+ static int    open_on_current_dir(struct tree *, const char *, int);
  
  
  static struct archive_vtable *
@@@ -430,16 -449,19 +449,19 @@@ archive_read_disk_new(void
  {
        struct archive_read_disk *a;
  
-       a = (struct archive_read_disk *)malloc(sizeof(*a));
+       a = (struct archive_read_disk *)calloc(1, sizeof(*a));
        if (a == NULL)
                return (NULL);
-       memset(a, 0, sizeof(*a));
        a->archive.magic = ARCHIVE_READ_DISK_MAGIC;
        a->archive.state = ARCHIVE_STATE_NEW;
        a->archive.vtable = archive_read_disk_vtable();
        a->lookup_uname = trivial_lookup_uname;
        a->lookup_gname = trivial_lookup_gname;
-       a->entry_wd_fd = -1;
+       a->enable_copyfile = 1;
+       a->traverse_mount_points = 1;
+       a->open_on_current_dir = open_on_current_dir;
+       a->tree_current_dir_fd = tree_current_dir_fd;
+       a->tree_enter_working_dir = tree_enter_working_dir;
        return (&a->archive);
  }
  
@@@ -555,6 -577,37 +577,37 @@@ archive_read_disk_set_atime_restored(st
  #endif
  }
  
+ int
+ archive_read_disk_set_behavior(struct archive *_a, int flags)
+ {
+       struct archive_read_disk *a = (struct archive_read_disk *)_a;
+       int r = ARCHIVE_OK;
+       archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC,
+           ARCHIVE_STATE_ANY, "archive_read_disk_honor_nodump");
+       if (flags & ARCHIVE_READDISK_RESTORE_ATIME)
+               r = archive_read_disk_set_atime_restored(_a);
+       else {
+               a->restore_time = 0;
+               if (a->tree != NULL)
+                       a->tree->flags &= ~needsRestoreTimes;
+       }
+       if (flags & ARCHIVE_READDISK_HONOR_NODUMP)
+               a->honor_nodump = 1;
+       else
+               a->honor_nodump = 0;
+       if (flags & ARCHIVE_READDISK_MAC_COPYFILE)
+               a->enable_copyfile = 1;
+       else
+               a->enable_copyfile = 0;
+       if (flags & ARCHIVE_READDISK_NO_TRAVERSE_MOUNTS)
+               a->traverse_mount_points = 0;
+       else
+               a->traverse_mount_points = 1;
+       return (r);
+ }
  /*
   * Trivial implementations of gname/uname lookup functions.
   * These are normally overridden by the client, but these stub
@@@ -685,13 -738,8 +738,8 @@@ _archive_read_data_block(struct archiv
                        flags |= O_NOATIME;
                do {
  #endif
- #ifdef HAVE_OPENAT
-                       t->entry_fd = openat(tree_current_dir_fd(t),
+                       t->entry_fd = open_on_current_dir(t,
                            tree_current_access_path(t), flags);
- #else
-                       tree_enter_working_dir(t);
-                       t->entry_fd = open(tree_current_access_path(t), flags);
- #endif
  #if defined(O_NOATIME)
                        /*
                         * When we did open the file with O_NOATIME flag,
        t->entry_buff_size = t->current_filesystem->buff_size;
  
        buffbytes = t->entry_buff_size;
-       if (buffbytes > t->current_sparse->length)
+       if ((int64_t)buffbytes > t->current_sparse->length)
                buffbytes = t->current_sparse->length;
  
        /*
@@@ -802,29 -850,17 +850,17 @@@ abort_read_data
  }
  
  static int
- _archive_read_next_header2(struct archive *_a, struct archive_entry *entry)
+ next_entry(struct archive_read_disk *a, struct tree *t,
+     struct archive_entry *entry)
  {
-       struct archive_read_disk *a = (struct archive_read_disk *)_a;
-       struct tree *t;
        const struct stat *st; /* info to use for this entry */
        const struct stat *lst;/* lstat() information */
-       int descend, fd = -1, r;
-       archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC,
-           ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA,
-           "archive_read_next_header2");
+       const char *name;
+       int descend, r;
  
-       t = a->tree;
-       if (t->entry_fd >= 0) {
-               close_and_restore_time(t->entry_fd, t, &t->restore_time);
-               t->entry_fd = -1;
-       }
- #if !(defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_FDOPENDIR))
-       /* Restore working directory. */
-       tree_enter_working_dir(t);
- #endif
        st = NULL;
        lst = NULL;
+       t->descend = 0;
        do {
                switch (tree_next(t)) {
                case TREE_ERROR_FATAL:
                }       
        } while (lst == NULL);
  
+ #ifdef __APPLE__
+       if (a->enable_copyfile) {
+               /* If we're using copyfile(), ignore "._XXX" files. */
+               const char *bname = strrchr(tree_current_path(t), '/');
+               if (bname == NULL)
+                       bname = tree_current_path(t);
+               else
+                       ++bname;
+               if (bname[0] == '.' && bname[1] == '_')
+                       return (ARCHIVE_RETRY);
+       }
+ #endif
+       archive_entry_copy_pathname(entry, tree_current_path(t));
+       /*
+        * Perform path matching.
+        */
+       if (a->matching) {
+               r = archive_match_path_excluded(a->matching, entry);
+               if (r < 0) {
+                       archive_set_error(&(a->archive), errno,
+                           "Faild : %s", archive_error_string(a->matching));
+                       return (r);
+               }
+               if (r) {
+                       if (a->excluded_cb_func)
+                               a->excluded_cb_func(&(a->archive),
+                                   a->excluded_cb_data, entry);
+                       return (ARCHIVE_RETRY);
+               }
+       }
        /*
         * Distinguish 'L'/'P'/'H' symlink following.
         */
                tree_enter_initial_dir(t);
                return (ARCHIVE_FATAL);
        }
+       if (t->initial_filesystem_id == -1)
+               t->initial_filesystem_id = t->current_filesystem_id;
+       if (!a->traverse_mount_points) {
+               if (t->initial_filesystem_id != t->current_filesystem_id)
+                       return (ARCHIVE_RETRY);
+       }
        t->descend = descend;
  
-       archive_entry_set_pathname(entry, tree_current_path(t));
-       archive_entry_copy_sourcepath(entry, tree_current_access_path(t));
+       /*
+        * Honor nodump flag.
+        * If the file is marked with nodump flag, do not return this entry.
+        */
+       if (a->honor_nodump) {
+ #if defined(HAVE_STRUCT_STAT_ST_FLAGS) && defined(UF_NODUMP)
+               if (st->st_flags & UF_NODUMP)
+                       return (ARCHIVE_RETRY);
+ #elif defined(EXT2_IOC_GETFLAGS) && defined(EXT2_NODUMP_FL) &&\
+       defined(HAVE_WORKING_EXT2_IOC_GETFLAGS)
+               if (S_ISREG(st->st_mode) || S_ISDIR(st->st_mode)) {
+                       unsigned long stflags;
+                       t->entry_fd = open_on_current_dir(t,
+                           tree_current_access_path(t), O_RDONLY | O_NONBLOCK);
+                       if (t->entry_fd >= 0) {
+                               r = ioctl(t->entry_fd, EXT2_IOC_GETFLAGS,
+                                       &stflags);
+                               if (r == 0 && (stflags & EXT2_NODUMP_FL) != 0)
+                                       return (ARCHIVE_RETRY);
+                       }
+               }
+ #endif
+       }
        archive_entry_copy_stat(entry, st);
  
-       /* Save the times to be restored. */
+       /* Save the times to be restored. This must be in before
+        * calling archive_read_disk_descend() or any chance of it,
+        * especially, invokng a callback. */
        t->restore_time.mtime = archive_entry_mtime(entry);
        t->restore_time.mtime_nsec = archive_entry_mtime_nsec(entry);
        t->restore_time.atime = archive_entry_atime(entry);
        t->restore_time.filetype = archive_entry_filetype(entry);
        t->restore_time.noatime = t->current_filesystem->noatime;
  
- #if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_FDOPENDIR)
        /*
-        * Open the current file to freely gather its metadata anywhere in
-        * working directory.
-        * Note: A symbolic link file cannot be opened with O_NOFOLLOW.
+        * Perform time matching.
         */
-       if (a->follow_symlinks || archive_entry_filetype(entry) != AE_IFLNK)
-               fd = openat(tree_current_dir_fd(t), tree_current_access_path(t),
-                   O_RDONLY | O_NONBLOCK);
-       /* Restore working directory if openat() operation failed or
-        * the file is a symbolic link. */
-       if (fd < 0)
-               tree_enter_working_dir(t);
+       if (a->matching) {
+               r = archive_match_time_excluded(a->matching, entry);
+               if (r < 0) {
+                       archive_set_error(&(a->archive), errno,
+                           "Faild : %s", archive_error_string(a->matching));
+                       return (r);
+               }
+               if (r) {
+                       if (a->excluded_cb_func)
+                               a->excluded_cb_func(&(a->archive),
+                                   a->excluded_cb_data, entry);
+                       return (ARCHIVE_RETRY);
+               }
+       }
  
-       /* The current direcotry fd is needed at
-        * archive_read_disk_entry_from_file() function to read link data
-        * with readlinkat(). */
-       a->entry_wd_fd = tree_current_dir_fd(t);
- #endif
+       /* Lookup uname/gname */
+       name = archive_read_disk_uname(&(a->archive), archive_entry_uid(entry));
+       if (name != NULL)
+               archive_entry_copy_uname(entry, name);
+       name = archive_read_disk_gname(&(a->archive), archive_entry_gid(entry));
+       if (name != NULL)
+               archive_entry_copy_gname(entry, name);
+       /*
+        * Perform owner matching.
+        */
+       if (a->matching) {
+               r = archive_match_owner_excluded(a->matching, entry);
+               if (r < 0) {
+                       archive_set_error(&(a->archive), errno,
+                           "Faild : %s", archive_error_string(a->matching));
+                       return (r);
+               }
+               if (r) {
+                       if (a->excluded_cb_func)
+                               a->excluded_cb_func(&(a->archive),
+                                   a->excluded_cb_data, entry);
+                       return (ARCHIVE_RETRY);
+               }
+       }
+       /*
+        * Invoke a meta data filter callback.
+        */
+       if (a->metadata_filter_func) {
+               if (!a->metadata_filter_func(&(a->archive),
+                   a->metadata_filter_data, entry))
+                       return (ARCHIVE_RETRY);
+       }
  
        /*
         * Populate the archive_entry with metadata from the disk.
         */
-       r = archive_read_disk_entry_from_file(&(a->archive), entry, fd, st);
+       archive_entry_copy_sourcepath(entry, tree_current_access_path(t));
+       r = archive_read_disk_entry_from_file(&(a->archive), entry,
+               t->entry_fd, st);
  
-       /* Close the file descriptor used for reding the current file
-        * metadata at archive_read_disk_entry_from_file(). */
-       if (fd >= 0)
-               close(fd);
+       return (r);
+ }
+ static int
+ _archive_read_next_header2(struct archive *_a, struct archive_entry *entry)
+ {
+       struct archive_read_disk *a = (struct archive_read_disk *)_a;
+       struct tree *t;
+       int r;
+       archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC,
+           ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA,
+           "archive_read_next_header2");
+       t = a->tree;
+       if (t->entry_fd >= 0) {
+               close_and_restore_time(t->entry_fd, t, &t->restore_time);
+               t->entry_fd = -1;
+       }
+       for (;;) {
+               r = next_entry(a, t, entry);
+               if (t->entry_fd >= 0) {
+                       close(t->entry_fd);
+                       t->entry_fd = -1;
+               }
+               if (r == ARCHIVE_RETRY) {
+                       archive_entry_clear(entry);
+                       continue;
+               }
+               break;
+       }
  
        /* Return to the initial directory. */
        tree_enter_initial_dir(t);
-       archive_entry_copy_sourcepath(entry, tree_current_path(t));
  
        /*
         * EOF and FATAL are persistent at this layer.  By
                break;
        case ARCHIVE_OK:
        case ARCHIVE_WARN:
+               /* Overwrite the sourcepath based on the initial directory. */
+               archive_entry_copy_sourcepath(entry, tree_current_path(t));
                t->entry_total = 0;
                if (archive_entry_filetype(entry) == AE_IFREG) {
                        t->nlink = archive_entry_nlink(entry);
@@@ -1018,6 -1182,48 +1182,48 @@@ setup_sparse(struct archive_read_disk *
        return (ARCHIVE_OK);
  }
  
+ int
+ archive_read_disk_set_matching(struct archive *_a, struct archive *_ma,
+     void (*_excluded_func)(struct archive *, void *, struct archive_entry *),
+     void *_client_data)
+ {
+       struct archive_read_disk *a = (struct archive_read_disk *)_a;
+       archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC,
+           ARCHIVE_STATE_ANY, "archive_read_disk_set_matching");
+       a->matching = _ma;
+       a->excluded_cb_func = _excluded_func;
+       a->excluded_cb_data = _client_data;
+       return (ARCHIVE_OK);
+ }
+ int
+ archive_read_disk_set_metadata_filter_callback(struct archive *_a,
+     int (*_metadata_filter_func)(struct archive *, void *,
+     struct archive_entry *), void *_client_data)
+ {
+       struct archive_read_disk *a = (struct archive_read_disk *)_a;
+       archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, ARCHIVE_STATE_ANY,
+           "archive_read_disk_set_metadata_filter_callback");
+       a->metadata_filter_func = _metadata_filter_func;
+       a->metadata_filter_data = _client_data;
+       return (ARCHIVE_OK);
+ }
+ int
+ archive_read_disk_can_descend(struct archive *_a)
+ {
+       struct archive_read_disk *a = (struct archive_read_disk *)_a;
+       struct tree *t = a->tree;
+       archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC,
+           ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA,
+           "archive_read_disk_can_descend");
+       return (t->visit_type == TREE_REGULAR && t->descend);
+ }
  /*
   * Called by the client to mark the directory just returned from
   * tree_next() as needing to be visited.
@@@ -1028,14 -1234,12 +1234,12 @@@ archive_read_disk_descend(struct archiv
        struct archive_read_disk *a = (struct archive_read_disk *)_a;
        struct tree *t = a->tree;
  
-       archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, ARCHIVE_STATE_DATA,
+       archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC,
+           ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA,
            "archive_read_disk_descend");
  
-       if (t->visit_type != TREE_REGULAR || !t->descend) {
-               archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
-                   "Ignored the request descending the current object");
-               return (ARCHIVE_WARN);
-       }
+       if (t->visit_type != TREE_REGULAR || !t->descend)
+               return (ARCHIVE_OK);
  
        if (tree_current_is_physical_dir(t)) {
                tree_push(t, t->basename, t->current_filesystem_id,
@@@ -1079,8 -1283,12 +1283,12 @@@ archive_read_disk_open_w(struct archiv
        archive_string_init(&path);
        if (archive_string_append_from_wcs(&path, pathname,
            wcslen(pathname)) != 0) {
-               archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
-                   "Can't convert a path to a char string");
+               if (errno == ENOMEM)
+                       archive_set_error(&a->archive, ENOMEM,
+                           "Can't allocate memory");
+               else
+                       archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+                           "Can't convert a path to a char string");
                a->archive.state = ARCHIVE_STATE_FATAL;
                ret = ARCHIVE_FATAL;
        } else
@@@ -1258,7 -1466,7 +1466,7 @@@ setup_current_filesystem(struct archive
        struct tree *t = a->tree;
        struct statfs sfs;
  #if defined(HAVE_GETVFSBYNAME) && defined(VFCF_SYNTHETIC)
 -      struct xvfsconf vfc;
 +      struct vfsconf vfc;
  #endif
        int r, xr = 0;
  #if !defined(HAVE_STRUCT_STATFS_F_NAMEMAX)
        t->current_filesystem->synthetic = -1;
        t->current_filesystem->remote = -1;
        if (tree_current_is_symblic_link_target(t)) {
- #if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_FDOPENDIR)
+ #if defined(HAVE_OPENAT)
                /*
                 * Get file system statistics on any directory
                 * where current is.
                        xr = get_xfer_size(t, fd, NULL);
                close(fd);
  #else
+               if (tree_enter_working_dir(t) != 0) {
+                       archive_set_error(&a->archive, errno, "fchdir failed");
+                       return (ARCHIVE_FAILED);
+               }
                r = statfs(tree_current_access_path(t), &sfs);
                if (r == 0)
                        xr = get_xfer_size(t, -1, tree_current_access_path(t));
        t->current_filesystem->name_max = sfs.f_namemax;
  #else
        /* Mac OS X does not have f_namemax in struct statfs. */
-       if (tree_current_is_symblic_link_target(t))
+       if (tree_current_is_symblic_link_target(t)) {
+               if (tree_enter_working_dir(t) != 0) {
+                       archive_set_error(&a->archive, errno, "fchdir failed");
+                       return (ARCHIVE_FAILED);
+               }
                nm = pathconf(tree_current_access_path(t), _PC_NAME_MAX);
-       else
+       else
                nm = fpathconf(tree_current_dir_fd(t), _PC_NAME_MAX);
        if (nm == -1)
                t->current_filesystem->name_max = NAME_MAX;
@@@ -1360,6 -1576,10 +1576,10 @@@ setup_current_filesystem(struct archive
        int r, xr = 0;
  
        t->current_filesystem->synthetic = -1;
+       if (tree_enter_working_dir(t) != 0) {
+               archive_set_error(&a->archive, errno, "fchdir failed");
+               return (ARCHIVE_FAILED);
+       }
        if (tree_current_is_symblic_link_target(t)) {
                r = statvfs(tree_current_access_path(t), &sfs);
                if (r == 0)
                 * for pathconf() function. */
                t->current_filesystem->xfer_align = sfs.f_frsize;
                t->current_filesystem->max_xfer_size = -1;
+ #if defined(HAVE_STRUCT_STATVFS_F_IOSIZE)
                t->current_filesystem->min_xfer_size = sfs.f_iosize;
                t->current_filesystem->incr_xfer_size = sfs.f_iosize;
+ #else
+               t->current_filesystem->min_xfer_size = sfs.f_bsize;
+               t->current_filesystem->incr_xfer_size = sfs.f_bsize;
+ #endif
        }
        if (sfs.f_flag & ST_LOCAL)
                t->current_filesystem->remote = 0;
        else
                t->current_filesystem->remote = 1;
  
+ #if defined(ST_NOATIME)
        if (sfs.f_flag & ST_NOATIME)
                t->current_filesystem->noatime = 1;
        else
+ #endif
                t->current_filesystem->noatime = 0;
  
        /* Set maximum filename length. */
@@@ -1427,7 -1654,7 +1654,7 @@@ setup_current_filesystem(struct archive
        int r, vr = 0, xr = 0;
  
        if (tree_current_is_symblic_link_target(t)) {
- #if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_FDOPENDIR)
+ #if defined(HAVE_OPENAT)
                /*
                 * Get file system statistics on any directory
                 * where current is.
                        xr = get_xfer_size(t, fd, NULL);
                close(fd);
  #else
+               if (tree_enter_working_dir(t) != 0) {
+                       archive_set_error(&a->archive, errno, "fchdir failed");
+                       return (ARCHIVE_FAILED);
+               }
                vr = statvfs(tree_current_access_path(t), &svfs);
                r = statfs(tree_current_access_path(t), &sfs);
                if (r == 0)
                r = fstatfs(tree_current_dir_fd(t), &sfs);
                if (r == 0)
                        xr = get_xfer_size(t, tree_current_dir_fd(t), NULL);
- #elif defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_FDOPENDIR)
- #error "Unexpected case. Please tell us about this error."
  #else
+               if (tree_enter_working_dir(t) != 0) {
+                       archive_set_error(&a->archive, errno, "fchdir failed");
+                       return (ARCHIVE_FAILED);
+               }
                vr = statvfs(".", &svfs);
                r = statfs(".", &sfs);
                if (r == 0)
@@@ -1529,7 -1762,7 +1762,7 @@@ setup_current_filesystem(struct archive
        t->current_filesystem->synthetic = -1;/* Not supported */
        t->current_filesystem->remote = -1;/* Not supported */
        if (tree_current_is_symblic_link_target(t)) {
- #if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_FDOPENDIR)
+ #if defined(HAVE_OPENAT)
                /*
                 * Get file system statistics on any directory
                 * where current is.
                        xr = get_xfer_size(t, fd, NULL);
                close(fd);
  #else
+               if (tree_enter_working_dir(t) != 0) {
+                       archive_set_error(&a->archive, errno, "fchdir failed");
+                       return (ARCHIVE_FAILED);
+               }
                r = statvfs(tree_current_access_path(t), &sfs);
                if (r == 0)
                        xr = get_xfer_size(t, -1, tree_current_access_path(t));
                r = fstatvfs(tree_current_dir_fd(t), &sfs);
                if (r == 0)
                        xr = get_xfer_size(t, tree_current_dir_fd(t), NULL);
- #elif defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_FDOPENDIR)
- #error "Unexpected case. Please tell us about this error."
  #else
+               if (tree_enter_working_dir(t) != 0) {
+                       archive_set_error(&a->archive, errno, "fchdir failed");
+                       return (ARCHIVE_FAILED);
+               }
                r = statvfs(".", &sfs);
                if (r == 0)
                        xr = get_xfer_size(t, -1, ".");
@@@ -1615,9 -1854,13 +1854,13 @@@ setup_current_filesystem(struct archive
  #if defined(HAVE_READDIR_R)
        /* Set maximum filename length. */
  #  if defined(_PC_NAME_MAX)
-       if (tree_current_is_symblic_link_target(t))
+       if (tree_current_is_symblic_link_target(t)) {
+               if (tree_enter_working_dir(t) != 0) {
+                       archive_set_error(&a->archive, errno, "fchdir failed");
+                       return (ARCHIVE_FAILED);
+               }
                nm = pathconf(tree_current_access_path(t), _PC_NAME_MAX);
-       else
+       else
                nm = fpathconf(tree_current_dir_fd(t), _PC_NAME_MAX);
        if (nm == -1)
  #  endif /* _PC_NAME_MAX */
@@@ -1697,6 -1940,18 +1940,18 @@@ close_and_restore_time(int fd, struct t
        return (0);
  }
  
+ static int
+ open_on_current_dir(struct tree *t, const char *path, int flags)
+ {
+ #ifdef HAVE_OPENAT
+       return (openat(tree_current_dir_fd(t), path, flags));
+ #else
+       if (tree_enter_working_dir(t) != 0)
+               return (-1);
+       return (open(path, flags));
+ #endif
+ }
  /*
   * Add a directory path to the current stack.
   */
@@@ -1778,6 -2033,7 +2033,7 @@@ static struct tree 
  tree_reopen(struct tree *t, const char *path, int restore_time)
  {
        t->flags = (restore_time)?needsRestoreTimes:0;
+       t->flags |= onInitialDir;
        t->visit_type = 0;
        t->tree_errno = 0;
        t->dirname_length = 0;
        t->entry_fd = -1;
        t->entry_eof = 0;
        t->entry_remaining_bytes = 0;
+       t->initial_filesystem_id = -1;
  
        /* First item is set up a lot like a symlink traversal. */
        tree_push(t, path, 0, 0, 0, NULL);
  static int
  tree_descent(struct tree *t)
  {
-       int r = 0;
+       int flag, new_fd, r = 0;
  
- #if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_FDOPENDIR)
-       int new_fd;
        t->dirname_length = archive_strlen(&t->path);
-       new_fd = openat(t->working_dir_fd, t->stack->name.s, O_RDONLY);
+       flag = O_RDONLY;
+ #if defined(O_DIRECTORY)
+       flag |= O_DIRECTORY;
+ #endif
+       new_fd = open_on_current_dir(t, t->stack->name.s, flag);
        if (new_fd < 0) {
                t->tree_errno = errno;
                r = TREE_ERROR_DIR;
                                t->maxOpenCount = t->openCount;
                } else
                        close(t->working_dir_fd);
+               /* Renew the current working directory. */
                t->working_dir_fd = new_fd;
+               t->flags &= ~onWorkingDir;
        }
- #else
-       /* If it is a link, set up fd for the ascent. */
-       if (t->stack->flags & isDirLink)
-               t->stack->symlink_parent_fd = t->working_dir_fd;
-       else {
-               close(t->working_dir_fd);
-               t->openCount--;
-       }
-       t->working_dir_fd = -1;
-       t->dirname_length = archive_strlen(&t->path);
-       if (chdir(t->stack->name.s) != 0)
-       {
-               t->tree_errno = errno;
-               r = TREE_ERROR_DIR;
-       } else {
-               t->depth++;
-               t->working_dir_fd = open(".", O_RDONLY);
-               t->openCount++;
-               if (t->openCount > t->maxOpenCount)
-                       t->maxOpenCount = t->openCount;
-       }
- #endif
        return (r);
  }
  
@@@ -1856,37 -2095,21 +2095,21 @@@ static in
  tree_ascend(struct tree *t)
  {
        struct tree_entry *te;
-       int r = 0, prev_dir_fd;
+       int new_fd, r = 0, prev_dir_fd;
  
        te = t->stack;
        prev_dir_fd = t->working_dir_fd;
- #if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_FDOPENDIR)
        if (te->flags & isDirLink)
-               t->working_dir_fd = te->symlink_parent_fd;
-       else {
-               int new_fd = openat(t->working_dir_fd, "..", O_RDONLY);
-               if (new_fd < 0) {
-                       t->tree_errno = errno;
-                       r = TREE_ERROR_FATAL;
-               } else
-                       t->working_dir_fd = new_fd;
-       }
- #else
-       if (te->flags & isDirLink) {
-               if (fchdir(te->symlink_parent_fd) != 0) {
-                       t->tree_errno = errno;
-                       r = TREE_ERROR_FATAL;
-               } else
-                       t->working_dir_fd = te->symlink_parent_fd;
+               new_fd = te->symlink_parent_fd;
+       else
+               new_fd = open_on_current_dir(t, "..", O_RDONLY);
+       if (new_fd < 0) {
+               t->tree_errno = errno;
+               r = TREE_ERROR_FATAL;
        } else {
-               if (chdir("..") != 0) {
-                       t->tree_errno = errno;
-                       r = TREE_ERROR_FATAL;
-               } else
-                       t->working_dir_fd = open(".", O_RDONLY);
-       }
- #endif
-       if (r == 0) {
+               /* Renew the current working directory. */
+               t->working_dir_fd = new_fd;
+               t->flags &= ~onWorkingDir;
                /* Current directory has been changed, we should
                 * close an fd of previous working directory. */
                close_and_restore_time(prev_dir_fd, t, &te->restore_time);
@@@ -1907,10 -2130,12 +2130,12 @@@ tree_enter_initial_dir(struct tree *t
  {
        int r = 0;
  
-       if (t->flags & onWorkingDir) {
+       if ((t->flags & onInitialDir) == 0) {
                r = fchdir(t->initial_dir_fd);
-               if (r == 0)
+               if (r == 0) {
                        t->flags &= ~onWorkingDir;
+                       t->flags |= onInitialDir;
+               }
        }
        return (r);
  }
@@@ -1930,8 -2155,10 +2155,10 @@@ tree_enter_working_dir(struct tree *t
         */
        if (t->depth > 0 && (t->flags & onWorkingDir) == 0) {
                r = fchdir(t->working_dir_fd);
-               if (r == 0)
+               if (r == 0) {
+                       t->flags &= ~onInitialDir;
                        t->flags |= onWorkingDir;
+               }
        }
        return (r);
  }
@@@ -2040,7 -2267,8 +2267,8 @@@ tree_dir_next_posix(struct tree *t
  #if defined(HAVE_FDOPENDIR)
                if ((t->d = fdopendir(dup(t->working_dir_fd))) == NULL) {
  #else
-               if ((t->d = opendir(".")) == NULL) {
+               if (tree_enter_working_dir(t) != 0 ||
+                   (t->d = opendir(".")) == NULL) {
  #endif
                        r = tree_ascend(t); /* Undo "chdir" */
                        tree_pop(t);
@@@ -2111,6 -2339,8 +2339,8 @@@ tree_current_stat(struct tree *t
                if (fstatat(tree_current_dir_fd(t),
                    tree_current_access_path(t), &t->st, 0) != 0)
  #else
+               if (tree_enter_working_dir(t) != 0)
+                       return NULL;
                if (stat(tree_current_access_path(t), &t->st) != 0)
  #endif
                        return NULL;
@@@ -2131,6 -2361,8 +2361,8 @@@ tree_current_lstat(struct tree *t
                    tree_current_access_path(t), &t->lst,
                    AT_SYMLINK_NOFOLLOW) != 0)
  #else
+               if (tree_enter_working_dir(t) != 0)
+                       return NULL;
                if (lstat(tree_current_access_path(t), &t->lst) != 0)
  #endif
                        return NULL;
@@@ -2152,7 -2384,10 +2384,10 @@@ tree_current_is_dir(struct tree *t
         */
        if (t->flags & hasLstat) {
                /* If lstat() says it's a dir, it must be a dir. */
-               if (S_ISDIR(tree_current_lstat(t)->st_mode))
+               st = tree_current_lstat(t);
+               if (st == NULL)
+                       return 0;
+               if (S_ISDIR(st->st_mode))
                        return 1;
                /* Not a dir; might be a link to a dir. */
                /* If it's not a link, then it's not a link to a dir. */
@@@ -2186,9 -2421,13 +2421,13 @@@ tree_current_is_physical_dir(struct tre
         * If stat() says it isn't a dir, then it's not a dir.
         * If stat() data is cached, this check is free, so do it first.
         */
-       if ((t->flags & hasStat)
-           && (!S_ISDIR(tree_current_stat(t)->st_mode)))
-               return 0;
+       if (t->flags & hasStat) {
+               st = tree_current_stat(t);
+               if (st == NULL)
+                       return (0);
+               if (!S_ISDIR(st->st_mode))
+                       return (0);
+       }
  
        /*
         * Either stat() said it was a dir (in which case, we have
@@@ -2214,7 -2453,8 +2453,8 @@@ tree_target_is_same_as_parent(struct tr
        struct tree_entry *te;
  
        for (te = t->current->parent; te != NULL; te = te->parent) {
-               if (te->dev == st->st_dev && te->ino == st->st_ino)
+               if (te->dev == (int64_t)st->st_dev &&
+                   te->ino == (int64_t)st->st_ino)
                        return (1);
        }
        return (0);
@@@ -2231,7 -2471,8 +2471,8 @@@ tree_current_is_symblic_link_target(str
  
        lst = tree_current_lstat(t);
        st = tree_current_stat(t);
-       return (st != NULL && st->st_dev == t->current_filesystem->dev &&
+       return (st != NULL && lst != NULL &&
+           (int64_t)st->st_dev == t->current_filesystem->dev &&
            st->st_dev != lst->st_dev);
  }