]> Dogcows Code - chaz/tar/blobdiff - src/extract.c
Update copyright years.
[chaz/tar] / src / extract.c
index e62671e985fb32076623a5c7a06f6c273c4d9e0a..2cc1f7b383a121fb5a6d864e96bf54f8af4a9d95 100644 (file)
 /* Extract files from a tar archive.
 
-   Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1998, 1999, 2000,
-   2001, 2003 Free Software Foundation, Inc.
+   Copyright 1988, 1992-1994, 1996-2001, 2003-2007, 2010, 2012-2014 Free
+   Software Foundation, Inc.
 
-   Written by John Gilmore, on 1985-11-19.
+   This file is part of GNU tar.
 
-   This program is free software; you can redistribute it and/or modify it
-   under the terms of the GNU General Public License as published by the
-   Free Software Foundation; either version 2, or (at your option) any later
-   version.
+   GNU tar is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
 
-   This program is distributed in the hope that it will be useful, but
-   WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
-   Public License for more details.
+   GNU tar is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
 
-   You should have received a copy of the GNU General Public License along
-   with this program; if not, write to the Free Software Foundation, Inc.,
-   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-#include "system.h"
+   Written by John Gilmore, on 1985-11-19.  */
+
+#include <system.h>
 #include <quotearg.h>
 #include <errno.h>
-
-#if HAVE_UTIME_H
-# include <utime.h>
-#else
-struct utimbuf
-  {
-    long actime;
-    long modtime;
-  };
-#endif
+#include <priv-set.h>
+#include <root-uid.h>
+#include <utimens.h>
 
 #include "common.h"
 
-bool we_are_root;              /* true if our effective uid == 0 */
+static bool we_are_root;       /* true if our effective uid == 0 */
 static mode_t newdir_umask;    /* umask when creating new directories */
 static mode_t current_umask;   /* current umask (which is set to 0 if -p) */
 
-/* Status of the permissions of a file that we are extracting.  */
-enum permstatus
-{
-  /* This file may have existed already; its permissions are unknown.  */
-  UNKNOWN_PERMSTATUS,
+#define ALL_MODE_BITS ((mode_t) ~ (mode_t) 0)
 
-  /* This file was created using the permissions from the archive.  */
-  ARCHIVED_PERMSTATUS,
+#if ! HAVE_FCHMOD && ! defined fchmod
+# define fchmod(fd, mode) (errno = ENOSYS, -1)
+#endif
+#if ! HAVE_FCHOWN && ! defined fchown
+# define fchown(fd, uid, gid) (errno = ENOSYS, -1)
+#endif
 
-  /* This is an intermediate directory; the archive did not specify
-     its permissions.  */
-  INTERDIR_PERMSTATUS
-};
+/* Return true if an error number ERR means the system call is
+   supported in this case.  */
+static bool
+implemented (int err)
+{
+  return ! (err == ENOSYS
+           || err == ENOTSUP
+           || (EOPNOTSUPP != ENOTSUP && err == EOPNOTSUPP));
+}
 
 /* List of directories whose statuses we need to extract after we've
    finished extracting their subsidiary files.  If you consider each
    contiguous subsequence of elements of the form [D]?[^D]*, where [D]
-   represents an element where AFTER_SYMLINKS is nonzero and [^D]
-   represents an element where AFTER_SYMLINKS is zero, then the head
+   represents an element where AFTER_LINKS is nonzero and [^D]
+   represents an element where AFTER_LINKS is zero, then the head
    of the subsequence has the longest name, and each non-head element
    in the prefix is an ancestor (in the directory hierarchy) of the
    preceding element.  */
 
 struct delayed_set_stat
   {
+    /* Next directory in list.  */
     struct delayed_set_stat *next;
-    struct stat stat_info;
+
+    /* Metadata for this directory.  */
+    dev_t dev;
+    ino_t ino;
+    mode_t mode; /* The desired mode is MODE & ~ current_umask.  */
+    uid_t uid;
+    gid_t gid;
+    struct timespec atime;
+    struct timespec mtime;
+
+    /* An estimate of the directory's current mode, along with a mask
+       specifying which bits of this estimate are known to be correct.
+       If CURRENT_MODE_MASK is zero, CURRENT_MODE's value doesn't
+       matter.  */
+    mode_t current_mode;
+    mode_t current_mode_mask;
+
+    /* This directory is an intermediate directory that was created
+       as an ancestor of some other directory; it was not mentioned
+       in the archive, so do not set its uid, gid, atime, or mtime,
+       and don't alter its mode outside of MODE_RWX.  */
+    bool interdir;
+
+    /* Whether symbolic links should be followed when accessing the
+       directory.  */
+    int atflag;
+
+    /* Do not set the status of this directory until after delayed
+       links are created.  */
+    bool after_links;
+
+    /* Directory that the name is relative to.  */
+    int change_dir;
+
+    /* extended attributes*/
+    char *cntx_name;
+    char *acls_a_ptr;
+    size_t acls_a_len;
+    char *acls_d_ptr;
+    size_t acls_d_len;
+    size_t xattr_map_size;
+    struct xattr_array *xattr_map;
+    /* Length and contents of name.  */
     size_t file_name_len;
-    mode_t invert_permissions;
-    enum permstatus permstatus;
-    bool after_symlinks;
     char file_name[1];
   };
 
 static struct delayed_set_stat *delayed_set_stat_head;
 
-/* List of symbolic links whose creation we have delayed.  */
-struct delayed_symlink
+/* List of links whose creation we have delayed.  */
+struct delayed_link
   {
-    /* The next delayed symbolic link in the list.  */
-    struct delayed_symlink *next;
-
-    /* The device, inode number and last-modified time of the placeholder.  */
+    /* The next delayed link in the list.  */
+    struct delayed_link *next;
+
+    /* The device, inode number and birthtime of the placeholder.
+       birthtime.tv_nsec is negative if the birthtime is not available.
+       Don't use mtime as this would allow for false matches if some
+       other process removes the placeholder.  Don't use ctime as
+       this would cause race conditions and other screwups, e.g.,
+       when restoring hard-linked symlinks.  */
     dev_t dev;
     ino_t ino;
-    time_t mtime;
+    struct timespec birthtime;
+
+    /* True if the link is symbolic.  */
+    bool is_symlink;
 
-    /* The desired owner and group of the symbolic link.  */
+    /* The desired metadata, valid only the link is symbolic.  */
+    mode_t mode;
     uid_t uid;
     gid_t gid;
+    struct timespec atime;
+    struct timespec mtime;
 
-    /* A list of sources for this symlink.  The sources are all to be
+    /* The directory that the sources and target are relative to.  */
+    int change_dir;
+
+    /* A list of sources for this link.  The sources are all to be
        hard-linked together.  */
     struct string_list *sources;
 
+    /* SELinux context */
+    char *cntx_name;
+
+    /* ACLs */
+    char *acls_a_ptr;
+    size_t acls_a_len;
+    char *acls_d_ptr;
+    size_t acls_d_len;
+
+    size_t xattr_map_size;
+    struct xattr_array *xattr_map;
+
     /* The desired target of the desired link.  */
     char target[1];
   };
 
-static struct delayed_symlink *delayed_symlink_head;
+static struct delayed_link *delayed_link_head;
 
 struct string_list
   {
@@ -110,10 +175,9 @@ struct string_list
 void
 extr_init (void)
 {
-  we_are_root = geteuid () == 0;
+  we_are_root = geteuid () == ROOT_UID;
   same_permissions_option += we_are_root;
   same_owner_option += we_are_root;
-  xalloc_fail_func = extract_finish;
 
   /* Option -p clears the kernel umask, so it does not affect proper
      restoration of file permissions.  New intermediate directories will
@@ -127,206 +191,339 @@ extr_init (void)
       umask (newdir_umask);    /* restore the kernel umask */
       current_umask = newdir_umask;
     }
+
+  /* If the user wants to guarantee that everything is under one directory,
+     determine its name now and let it be created later.  */
+  if (one_top_level_option && !one_top_level_dir)
+    {
+      char *base = base_name (archive_name_array[0]);
+
+      one_top_level_dir = strip_compression_suffix (base);
+      free (base);
+      
+      if (!one_top_level_dir)
+       USAGE_ERROR ((0, 0, _("Cannot deduce top-level directory name; please set it explicitly with --one-top-level=DIR")));
+    }
 }
 
-/* If restoring permissions, restore the mode for FILE_NAME from
-   information given in *STAT_INFO (where *CUR_INFO gives
-   the current status if CUR_INFO is nonzero); otherwise invert the
-   INVERT_PERMISSIONS bits from the file's current permissions.
-   PERMSTATUS specifies the status of the file's permissions.
-   TYPEFLAG specifies the type of the file.  */
-static void
-set_mode (char const *file_name,
-         struct stat const *stat_info,
-         struct stat const *cur_info,
-         mode_t invert_permissions, enum permstatus permstatus,
-         char typeflag)
+/* Use fchmod if possible, fchmodat otherwise.  */
+static int
+fd_chmod (int fd, char const *file, mode_t mode, int atflag)
 {
-  mode_t mode;
-
-  if (0 < same_permissions_option
-      && permstatus != INTERDIR_PERMSTATUS)
+  if (0 <= fd)
     {
-      mode = stat_info->st_mode;
-
-      /* If we created the file and it has a usual mode, then its mode
-        is normally set correctly already.  But on many hosts, some
-        directories inherit the setgid bits from their parents, so we
-        we must set directories' modes explicitly.  */
-      if (permstatus == ARCHIVED_PERMSTATUS
-         && ! (mode & ~ MODE_RWX)
-         && typeflag != DIRTYPE
-         && typeflag != GNUTYPE_DUMPDIR)
-       return;
+      int result = fchmod (fd, mode);
+      if (result == 0 || implemented (errno))
+       return result;
     }
-  else if (! invert_permissions)
-    return;
-  else
+  return fchmodat (chdir_fd, file, mode, atflag);
+}
+
+/* Use fchown if possible, fchownat otherwise.  */
+static int
+fd_chown (int fd, char const *file, uid_t uid, gid_t gid, int atflag)
+{
+  if (0 <= fd)
     {
-      /* We must inspect a directory's current permissions, since the
-        directory may have inherited its setgid bit from its parent.
+      int result = fchown (fd, uid, gid);
+      if (result == 0 || implemented (errno))
+       return result;
+    }
+  return fchownat (chdir_fd, file, uid, gid, atflag);
+}
 
-        INVERT_PERMISSIONS happens to be nonzero only for directories
-        that we created, so there's no point optimizing this code for
-        other cases.  */
-      struct stat st;
-      if (! cur_info)
+/* Use fstat if possible, fstatat otherwise.  */
+static int
+fd_stat (int fd, char const *file, struct stat *st, int atflag)
+{
+  return (0 <= fd
+         ? fstat (fd, st)
+         : fstatat (chdir_fd, file, st, atflag));
+}
+
+/* Set the mode for FILE_NAME to MODE.
+   MODE_MASK specifies the bits of MODE that we care about;
+   thus if MODE_MASK is zero, do nothing.
+   If FD is nonnegative, it is a file descriptor for the file.
+   CURRENT_MODE and CURRENT_MODE_MASK specify information known about
+   the file's current mode, using the style of struct delayed_set_stat.
+   TYPEFLAG specifies the type of the file.
+   ATFLAG specifies the flag to use when statting the file.  */
+static void
+set_mode (char const *file_name,
+         mode_t mode, mode_t mode_mask, int fd,
+         mode_t current_mode, mode_t current_mode_mask,
+         char typeflag, int atflag)
+{
+  if (((current_mode ^ mode) | ~ current_mode_mask) & mode_mask)
+    {
+      if (MODE_ALL & ~ mode_mask & ~ current_mode_mask)
        {
-         if (stat (file_name, &st) != 0)
+         struct stat st;
+         if (fd_stat (fd, file_name, &st, atflag) != 0)
            {
              stat_error (file_name);
              return;
            }
-         cur_info = &st;
+         current_mode = st.st_mode;
        }
-      mode = cur_info->st_mode ^ invert_permissions;
-    }
 
-  if (chmod (file_name, mode) != 0)
-    chmod_error_details (file_name, mode);
+      current_mode &= MODE_ALL;
+      mode = (current_mode & ~ mode_mask) | (mode & mode_mask);
+
+      if (current_mode != mode)
+       {
+         int chmod_errno =
+           fd_chmod (fd, file_name, mode, atflag) == 0 ? 0 : errno;
+
+         /* On Solaris, chmod may fail if we don't have PRIV_ALL, because
+            setuid-root files would otherwise be a backdoor.  See
+            http://opensolaris.org/jive/thread.jspa?threadID=95826
+            (2009-09-03).  */
+         if (chmod_errno == EPERM && (mode & S_ISUID)
+             && priv_set_restore_linkdir () == 0)
+           {
+             chmod_errno =
+               fd_chmod (fd, file_name, mode, atflag) == 0 ? 0 : errno;
+             priv_set_remove_linkdir ();
+           }
+
+         /* Linux fchmodat does not support AT_SYMLINK_NOFOLLOW, and
+            returns ENOTSUP even when operating on non-symlinks, try
+            again with the flag disabled if it does not appear to be
+            supported and if the file is not a symlink.  This
+            introduces a race, alas.  */
+         if (atflag && typeflag != SYMTYPE && ! implemented (chmod_errno))
+           chmod_errno = fd_chmod (fd, file_name, mode, 0) == 0 ? 0 : errno;
+
+         if (chmod_errno
+             && (typeflag != SYMTYPE || implemented (chmod_errno)))
+           {
+             errno = chmod_errno;
+             chmod_error_details (file_name, mode);
+           }
+       }
+    }
 }
 
 /* Check time after successfully setting FILE_NAME's time stamp to T.  */
 static void
-check_time (char const *file_name, time_t t)
+check_time (char const *file_name, struct timespec t)
 {
-  time_t now;
-  if (t <= 0)
-    WARN ((0, 0, _("%s: implausibly old time stamp %s"),
-          file_name, tartime (t)));
-  else if (start_time < t && (now = time (0)) < t)
-    WARN ((0, 0, _("%s: time stamp %s is %lu s in the future"),
-          file_name, tartime (t), (unsigned long) (t - now)));
+  if (t.tv_sec < 0)
+    WARNOPT (WARN_TIMESTAMP,
+            (0, 0, _("%s: implausibly old time stamp %s"),
+             file_name, tartime (t, true)));
+  else if (timespec_cmp (volume_start_time, t) < 0)
+    {
+      struct timespec now;
+      gettime (&now);
+      if (timespec_cmp (now, t) < 0)
+       {
+         char buf[TIMESPEC_STRSIZE_BOUND];
+         struct timespec diff;
+         diff.tv_sec = t.tv_sec - now.tv_sec;
+         diff.tv_nsec = t.tv_nsec - now.tv_nsec;
+         if (diff.tv_nsec < 0)
+           {
+             diff.tv_nsec += BILLION;
+             diff.tv_sec--;
+           }
+         WARNOPT (WARN_TIMESTAMP,
+                  (0, 0, _("%s: time stamp %s is %s s in the future"),
+                   file_name, tartime (t, true), code_timespec (diff, buf)));
+       }
+    }
 }
 
 /* Restore stat attributes (owner, group, mode and times) for
-   FILE_NAME, using information given in *STAT_INFO.
-   If CUR_INFO is nonzero, *CUR_INFO is the
-   file's currernt status.
-   If not restoring permissions, invert the
-   INVERT_PERMISSIONS bits from the file's current permissions.
-   PERMSTATUS specifies the status of the file's permissions.
-   TYPEFLAG specifies the type of the file.  */
-
-/* FIXME: About proper restoration of symbolic link attributes, we still do
-   not have it right.  Pretesters' reports tell us we need further study and
-   probably more configuration.  For now, just use lchown if it exists, and
-   punt for the rest.  Sigh!  */
+   FILE_NAME, using information given in *ST.
+   If FD is nonnegative, it is a file descriptor for the file.
+   CURRENT_MODE and CURRENT_MODE_MASK specify information known about
+   the file's current mode, using the style of struct delayed_set_stat.
+   TYPEFLAG specifies the type of the file.
+   If INTERDIR, this is an intermediate directory.
+   ATFLAG specifies the flag to use when statting the file.  */
 
 static void
 set_stat (char const *file_name,
-         struct stat const *stat_info,
-         struct stat const *cur_info,
-         mode_t invert_permissions, enum permstatus permstatus,
-         char typeflag)
+         struct tar_stat_info const *st,
+         int fd, mode_t current_mode, mode_t current_mode_mask,
+         char typeflag, bool interdir, int atflag)
 {
-  struct utimbuf utimbuf;
+  /* Do the utime before the chmod because some versions of utime are
+     broken and trash the modes of the file.  */
 
-  if (typeflag != SYMTYPE)
+  if (! touch_option && ! interdir)
     {
-      /* We do the utime before the chmod because some versions of utime are
-        broken and trash the modes of the file.  */
+      struct timespec ts[2];
+      if (incremental_option)
+       ts[0] = st->atime;
+      else
+       ts[0].tv_nsec = UTIME_OMIT;
+      ts[1] = st->mtime;
 
-      if (! touch_option && permstatus != INTERDIR_PERMSTATUS)
+      if (fdutimensat (fd, chdir_fd, file_name, ts, atflag) == 0)
        {
-         /* We set the accessed time to `now', which is really the time we
-            started extracting files, unless incremental_option is used, in
-            which case .st_atime is used.  */
-
-         /* FIXME: incremental_option should set ctime too, but how?  */
-
          if (incremental_option)
-           utimbuf.actime = stat_info->st_atime;
-         else
-           utimbuf.actime = start_time;
-
-         utimbuf.modtime = stat_info->st_mtime;
-
-         if (utime (file_name, &utimbuf) < 0)
-           utime_error (file_name);
-         else
-           {
-             check_time (file_name, utimbuf.actime);
-             check_time (file_name, utimbuf.modtime);
-           }
+           check_time (file_name, ts[0]);
+         check_time (file_name, ts[1]);
        }
+      else if (typeflag != SYMTYPE || implemented (errno))
+       utime_error (file_name);
+    }
 
+  if (0 < same_owner_option && ! interdir)
+    {
       /* Some systems allow non-root users to give files away.  Once this
-        done, it is not possible anymore to change file permissions, so we
-        have to set permissions prior to possibly giving files away.  */
-
-      set_mode (file_name, stat_info, cur_info,
-               invert_permissions, permstatus, typeflag);
+        done, it is not possible anymore to change file permissions.
+        However, setting file permissions now would be incorrect, since
+        they would apply to the wrong user, and there would be a race
+        condition.  So, don't use systems that allow non-root users to
+        give files away.  */
+      uid_t uid = st->stat.st_uid;
+      gid_t gid = st->stat.st_gid;
+
+      if (fd_chown (fd, file_name, uid, gid, atflag) == 0)
+       {
+         /* Changing the owner can clear st_mode bits in some cases.  */
+         if ((current_mode | ~ current_mode_mask) & S_IXUGO)
+           current_mode_mask &= ~ (current_mode & (S_ISUID | S_ISGID));
+       }
+      else if (typeflag != SYMTYPE || implemented (errno))
+       chown_error_details (file_name, uid, gid);
     }
 
-  if (0 < same_owner_option && permstatus != INTERDIR_PERMSTATUS)
+  set_mode (file_name,
+           st->stat.st_mode & ~ current_umask,
+           0 < same_permissions_option && ! interdir ? MODE_ALL : MODE_RWX,
+           fd, current_mode, current_mode_mask, typeflag, atflag);
+
+  /* these three calls must be done *after* fd_chown() call because fd_chown
+     causes that linux capabilities becomes cleared. */
+  xattrs_xattrs_set (st, file_name, typeflag, 1);
+  xattrs_acls_set (st, file_name, typeflag);
+  xattrs_selinux_set (st, file_name, typeflag);
+}
+
+/* For each entry H in the leading prefix of entries in HEAD that do
+   not have after_links marked, mark H and fill in its dev and ino
+   members.  Assume HEAD && ! HEAD->after_links.  */
+static void
+mark_after_links (struct delayed_set_stat *head)
+{
+  struct delayed_set_stat *h = head;
+
+  do
     {
-      /* When lchown exists, it should be used to change the attributes of
-        the symbolic link itself.  In this case, a mere chown would change
-        the attributes of the file the symbolic link is pointing to, and
-        should be avoided.  */
+      struct stat st;
+      h->after_links = 1;
 
-      if (typeflag == SYMTYPE)
-       {
-#if HAVE_LCHOWN
-         if (lchown (file_name, stat_info->st_uid, stat_info->st_gid) < 0)
-           chown_error_details (file_name,
-                                stat_info->st_uid, stat_info->st_gid);
-#endif
-       }
+      if (deref_stat (h->file_name, &st) != 0)
+       stat_error (h->file_name);
       else
        {
-         if (chown (file_name, stat_info->st_uid, stat_info->st_gid) < 0)
-           chown_error_details (file_name,
-                                stat_info->st_uid, stat_info->st_gid);
-
-         /* On a few systems, and in particular, those allowing to give files
-            away, changing the owner or group destroys the suid or sgid bits.
-            So let's attempt setting these bits once more.  */
-         if (stat_info->st_mode & (S_ISUID | S_ISGID | S_ISVTX))
-           set_mode (file_name, stat_info, 0,
-                     invert_permissions, permstatus, typeflag);
+         h->dev = st.st_dev;
+         h->ino = st.st_ino;
        }
     }
+  while ((h = h->next) && ! h->after_links);
 }
 
 /* Remember to restore stat attributes (owner, group, mode and times)
-   for the directory FILE_NAME, using information given in *STAT_INFO,
+   for the directory FILE_NAME, using information given in *ST,
    once we stop extracting files into that directory.
-   If not restoring permissions, remember to invert the
-   INVERT_PERMISSIONS bits from the file's current permissions.
-   PERMSTATUS specifies the status of the file's permissions.  */
+
+   If ST is null, merely create a placeholder node for an intermediate
+   directory that was created by make_directories.
+
+   NOTICE: this works only if the archive has usual member order, i.e.
+   directory, then the files in that directory. Incremental archive have
+   somewhat reversed order: first go subdirectories, then all other
+   members. To help cope with this case the variable
+   delay_directory_restore_option is set by prepare_to_extract.
+
+   If an archive was explicitely created so that its member order is
+   reversed, some directory timestamps can be restored incorrectly,
+   e.g.:
+       tar --no-recursion -cf archive dir dir/file1 foo dir/file2
+*/
 static void
-delay_set_stat (char const *file_name, struct stat const *stat_info,
-               mode_t invert_permissions, enum permstatus permstatus)
+delay_set_stat (char const *file_name, struct tar_stat_info const *st,
+               mode_t current_mode, mode_t current_mode_mask,
+               mode_t mode, int atflag)
 {
   size_t file_name_len = strlen (file_name);
   struct delayed_set_stat *data =
     xmalloc (offsetof (struct delayed_set_stat, file_name)
             + file_name_len + 1);
+  data->next = delayed_set_stat_head;
+  data->mode = mode;
+  if (st)
+    {
+      data->dev = st->stat.st_dev;
+      data->ino = st->stat.st_ino;
+      data->uid = st->stat.st_uid;
+      data->gid = st->stat.st_gid;
+      data->atime = st->atime;
+      data->mtime = st->mtime;
+    }
   data->file_name_len = file_name_len;
+  data->current_mode = current_mode;
+  data->current_mode_mask = current_mode_mask;
+  data->interdir = ! st;
+  data->atflag = atflag;
+  data->after_links = 0;
+  data->change_dir = chdir_current;
+  data->cntx_name = NULL;
+  if (st)
+    assign_string (&data->cntx_name, st->cntx_name);
+  if (st && st->acls_a_ptr)
+    {
+      data->acls_a_ptr = xmemdup (st->acls_a_ptr, st->acls_a_len + 1);
+      data->acls_a_len = st->acls_a_len;
+    }
+  else
+    {
+      data->acls_a_ptr = NULL;
+      data->acls_a_len = 0;
+    }
+  if (st && st->acls_d_ptr)
+    {
+      data->acls_d_ptr = xmemdup (st->acls_d_ptr, st->acls_d_len + 1);
+      data->acls_d_len = st->acls_d_len;
+    }
+  else
+    {
+      data->acls_d_ptr = NULL;
+      data->acls_d_len = 0;
+    }
+  if (st)
+    xheader_xattr_copy (st, &data->xattr_map, &data->xattr_map_size);
+  else
+    {
+      data->xattr_map = NULL;
+      data->xattr_map_size = 0;
+    }
   strcpy (data->file_name, file_name);
-  data->invert_permissions = invert_permissions;
-  data->permstatus = permstatus;
-  data->after_symlinks = 0;
-  data->stat_info = *stat_info;
-  data->next = delayed_set_stat_head;
   delayed_set_stat_head = data;
+  if (must_be_dot_or_slash (file_name))
+    mark_after_links (data);
 }
 
 /* Update the delayed_set_stat info for an intermediate directory
-   created on the path to DIR_NAME.  The intermediate directory turned
+   created within the file name of DIR.  The intermediate directory turned
    out to be the same as this directory, e.g. due to ".." or symbolic
    links.  *DIR_STAT_INFO is the status of the directory.  */
 static void
-repair_delayed_set_stat (char const *dir_name,
+repair_delayed_set_stat (char const *dir,
                         struct stat const *dir_stat_info)
 {
   struct delayed_set_stat *data;
   for (data = delayed_set_stat_head;  data;  data = data->next)
     {
       struct stat st;
-      if (stat (data->file_name, &st) != 0)
+      if (fstatat (chdir_fd, data->file_name, &st, data->atflag) != 0)
        {
          stat_error (data->file_name);
          return;
@@ -335,35 +532,41 @@ repair_delayed_set_stat (char const *dir_name,
       if (st.st_dev == dir_stat_info->st_dev
          && st.st_ino == dir_stat_info->st_ino)
        {
-         data->stat_info = current_stat_info.stat;
-         data->invert_permissions =
-           (MODE_RWX & (current_stat_info.stat.st_mode ^ st.st_mode));
-         data->permstatus = ARCHIVED_PERMSTATUS;
+         data->dev = current_stat_info.stat.st_dev;
+         data->ino = current_stat_info.stat.st_ino;
+         data->mode = current_stat_info.stat.st_mode;
+         data->uid = current_stat_info.stat.st_uid;
+         data->gid = current_stat_info.stat.st_gid;
+         data->atime = current_stat_info.atime;
+         data->mtime = current_stat_info.mtime;
+         data->current_mode = st.st_mode;
+         data->current_mode_mask = ALL_MODE_BITS;
+         data->interdir = false;
          return;
        }
     }
 
   ERROR ((0, 0, _("%s: Unexpected inconsistency when making directory"),
-         quotearg_colon (dir_name)));
+         quotearg_colon (dir)));
 }
 
-/* After a file/link/symlink/directory creation has failed, see if
+/* After a file/link/directory creation has failed, see if
    it's because some required directory was not present, and if so,
-   create all required directories.  Return non-zero if a directory
-   was created.  */
+   create all required directories.  Return zero if all the required
+   directories were created, nonzero (issuing a diagnostic) otherwise.
+   Set *INTERDIR_MADE if at least one directory was created.  */
 static int
-make_directories (char *file_name)
+make_directories (char *file_name, bool *interdir_made)
 {
-  char *cursor0 = file_name + FILESYSTEM_PREFIX_LEN (file_name);
-  char *cursor;                        /* points into path */
-  int did_something = 0;       /* did we do anything yet? */
-  int mode;
-  int invert_permissions;
-  int status;
+  char *cursor0 = file_name + FILE_SYSTEM_PREFIX_LEN (file_name);
+  char *cursor;                        /* points into the file name */
 
-  
   for (cursor = cursor0; *cursor; cursor++)
     {
+      mode_t mode;
+      mode_t desired_mode;
+      int status;
+
       if (! ISSLASH (*cursor))
        continue;
 
@@ -372,7 +575,7 @@ make_directories (char *file_name)
       if (cursor == cursor0 || ISSLASH (cursor[-1]))
        continue;
 
-      /* Avoid mkdir where last part of path is "." or "..".  */
+      /* Avoid mkdir where last part of file name is "." or "..".  */
 
       if (cursor[-1] == '.'
          && (cursor == cursor0 + 1 || ISSLASH (cursor[-2])
@@ -380,119 +583,216 @@ make_directories (char *file_name)
                  && (cursor == cursor0 + 2 || ISSLASH (cursor[-3])))))
        continue;
 
-      *cursor = '\0';          /* truncate the path there */
-      mode = MODE_RWX & ~ newdir_umask;
-      invert_permissions = we_are_root ? 0 : MODE_WXUSR & ~ mode;
-      status = mkdir (file_name, mode ^ invert_permissions);
+      *cursor = '\0';          /* truncate the name there */
+      desired_mode = MODE_RWX & ~ newdir_umask;
+      mode = desired_mode | (we_are_root ? 0 : MODE_WXUSR);
+      status = mkdirat (chdir_fd, file_name, mode);
 
       if (status == 0)
        {
          /* Create a struct delayed_set_stat even if
-            invert_permissions is zero, because
+            mode == desired_mode, because
             repair_delayed_set_stat may need to update the struct.  */
          delay_set_stat (file_name,
-                         &current_stat_info.stat /* ignored */,
-                         invert_permissions, INTERDIR_PERMSTATUS);
+                         0, mode & ~ current_umask, MODE_RWX,
+                         desired_mode, AT_SYMLINK_NOFOLLOW);
 
-         print_for_mkdir (file_name, cursor - file_name, mode);
-         did_something = 1;
-
-         *cursor = '/';
-         continue;
+         print_for_mkdir (file_name, cursor - file_name, desired_mode);
+         *interdir_made = true;
+       }
+      else if (errno == EEXIST)
+       status = 0;
+      else
+       {
+         /* Check whether the desired file exists.  Even when the
+            file exists, mkdir can fail with some errno value E other
+            than EEXIST, so long as E describes an error condition
+            that also applies.  */
+         int e = errno;
+         struct stat st;
+         status = fstatat (chdir_fd, file_name, &st, 0);
+         if (status)
+           {
+             errno = e;
+             mkdir_error (file_name);
+           }
        }
 
       *cursor = '/';
-
-      if (errno == EEXIST)
-       continue;               /* Directory already exists.  */
-      else if ((errno == ENOSYS /* Automounted dirs on Solaris return
-                                  this. Reported by Warren Hyde
-                                  <Warren.Hyde@motorola.com> */
-              || ERRNO_IS_EACCES)  /* Turbo C mkdir gives a funny errno.  */
-              && access (file_name, W_OK) == 0)
-       continue;
-
-      /* Some other error in the mkdir.  We return to the caller.  */
-      break;
+      if (status)
+       return status;
     }
 
-  return did_something;                /* tell them to retry if we made one */
+  return 0;
 }
 
-/* Prepare to extract a file.
-   Return zero if extraction should not proceed.  */
-
-static int
-prepare_to_extract (char const *file_name, bool directory)
+/* Return true if FILE_NAME (with status *STP, if STP) is not a
+   directory, and has a time stamp newer than (or equal to) that of
+   TAR_STAT.  */
+static bool
+file_newer_p (const char *file_name, struct stat const *stp,
+             struct tar_stat_info *tar_stat)
 {
-  if (to_stdout_option)
-    return 0;
+  struct stat st;
 
-  if (old_files_option == UNLINK_FIRST_OLD_FILES
-      && !remove_any_file (file_name, recursive_unlink_option)
-      && errno && errno != ENOENT)
+  if (!stp)
     {
-      unlink_error (file_name);
-      return 0;
+      if (deref_stat (file_name, &st) != 0)
+       {
+         if (errno != ENOENT)
+           {
+             stat_warn (file_name);
+             /* Be safer: if the file exists, assume it is newer.  */
+             return true;
+           }
+         return false;
+       }
+      stp = &st;
     }
 
-  return 1;
+  return (! S_ISDIR (stp->st_mode)
+         && tar_timespec_cmp (tar_stat->mtime, get_stat_mtime (stp)) <= 0);
 }
 
+#define RECOVER_NO 0
+#define RECOVER_OK 1
+#define RECOVER_SKIP 2
+
 /* Attempt repairing what went wrong with the extraction.  Delete an
    already existing file or create missing intermediate directories.
-   Return nonzero if we somewhat increased our chances at a successful
-   extraction.  errno is properly restored on zero return.  */
+   Return RECOVER_OK if we somewhat increased our chances at a successful
+   extraction, RECOVER_NO if there are no chances, and RECOVER_SKIP if the
+   caller should skip extraction of that member.  The value of errno is
+   properly restored on returning RECOVER_NO.
+
+   If REGULAR, the caller was trying to extract onto a regular file.
+
+   Set *INTERDIR_MADE if an intermediate directory is made as part of
+   the recovery process.  */
+
 static int
-maybe_recoverable (char *file_name, int *interdir_made)
+maybe_recoverable (char *file_name, bool regular, bool *interdir_made)
 {
+  int e = errno;
+  struct stat st;
+  struct stat const *stp = 0;
+
   if (*interdir_made)
-    return 0;
+    return RECOVER_NO;
 
-  switch (errno)
+  switch (e)
     {
+    case ELOOP:
+
+      /* With open ("symlink", O_NOFOLLOW|...), POSIX says errno == ELOOP,
+        but some operating systems do not conform to the standard.  */
+#ifdef EFTYPE
+      /* NetBSD uses errno == EFTYPE; see <http://gnats.netbsd.org/43154>.  */
+    case EFTYPE:
+#endif
+      /* FreeBSD 8.1 uses errno == EMLINK.  */
+    case EMLINK:
+      /* Tru64 5.1B uses errno == ENOTSUP.  */
+    case ENOTSUP:
+
+      if (! regular
+         || old_files_option != OVERWRITE_OLD_FILES || dereference_option)
+       break;
+      if (strchr (file_name, '/'))
+       {
+         if (deref_stat (file_name, &st) != 0)
+           break;
+         stp = &st;
+       }
+
+      /* The caller tried to open a symbolic link with O_NOFOLLOW.
+        Fall through, treating it as an already-existing file.  */
+
     case EEXIST:
       /* Remove an old file, if the options allow this.  */
 
       switch (old_files_option)
        {
-       default:
-         return 0;
+       case SKIP_OLD_FILES:
+         WARNOPT (WARN_EXISTING_FILE,
+                  (0, 0, _("%s: skipping existing file"), file_name));
+         return RECOVER_SKIP;
+
+       case KEEP_OLD_FILES:
+         return RECOVER_NO;
+
+       case KEEP_NEWER_FILES:
+         if (file_newer_p (file_name, stp, &current_stat_info))
+           break;
+         /* FALL THROUGH */
 
        case DEFAULT_OLD_FILES:
        case NO_OVERWRITE_DIR_OLD_FILES:
        case OVERWRITE_OLD_FILES:
-         {
-           int r = remove_any_file (file_name, 0);
-           errno = EEXIST;
-           return r;
-         }
+         if (0 < remove_any_file (file_name, ORDINARY_REMOVE_OPTION))
+           return RECOVER_OK;
+         break;
+
+       case UNLINK_FIRST_OLD_FILES:
+         break;
        }
 
     case ENOENT:
       /* Attempt creating missing intermediate directories.  */
-      if (! make_directories (file_name))
-       {
-         errno = ENOENT;
-         return 0;
-       }
-      *interdir_made = 1;
-      return 1;
+      if (make_directories (file_name, interdir_made) == 0 && *interdir_made)
+       return RECOVER_OK;
+      break;
 
     default:
       /* Just say we can't do anything about it...  */
+      break;
+    }
 
-      return 0;
+  errno = e;
+  return RECOVER_NO;
+}
+
+/* Restore stat extended attributes (xattr) for FILE_NAME, using information
+   given in *ST.  Restore before extraction because they may affect file layout
+   (e.g. on Lustre distributed parallel filesystem - setting info about how many
+   servers is this file striped over, stripe size, mirror copies, etc.
+   in advance dramatically improves the following  performance of reading and
+   writing a file).  If not restoring permissions, invert the INVERT_PERMISSIONS
+   bits from the file's current permissions.  TYPEFLAG specifies the type of the
+   file.  FILE_CREATED indicates set_xattr has created the file */
+static int
+set_xattr (char const *file_name, struct tar_stat_info const *st,
+           mode_t invert_permissions, char typeflag, int *file_created)
+{
+  int status = 0;
+
+#ifdef HAVE_XATTRS
+  bool interdir_made = false;
+
+  if ((xattrs_option > 0) && st->xattr_map_size)
+    {
+      mode_t mode = current_stat_info.stat.st_mode & MODE_RWX & ~ current_umask;
+
+      do
+        status = mknodat (chdir_fd, file_name, mode ^ invert_permissions, 0);
+      while (status && maybe_recoverable ((char *)file_name, false,
+                                          &interdir_made));
+
+      xattrs_xattrs_set (st, file_name, typeflag, 0);
+      *file_created = 1;
     }
+#endif
+
+  return(status);
 }
 
 /* Fix the statuses of all directories whose statuses need fixing, and
-   which are not ancestors of FILE_NAME.  If AFTER_SYMLINKS is
+   which are not ancestors of FILE_NAME.  If AFTER_LINKS is
    nonzero, do this for all such directories; otherwise, stop at the
    first directory that is marked to be fixed up only after delayed
-   symlinks are applied.  */
+   links are applied.  */
 static void
-apply_nonancestor_delayed_set_stat (char const *file_name, bool after_symlinks)
+apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links)
 {
   size_t file_name_len = strlen (file_name);
   bool check_for_renamed_directories = 0;
@@ -502,11 +802,12 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_symlinks)
       struct delayed_set_stat *data = delayed_set_stat_head;
       bool skip_this_one = 0;
       struct stat st;
-      struct stat const *cur_info = 0;
+      mode_t current_mode = data->current_mode;
+      mode_t current_mode_mask = data->current_mode_mask;
 
-      check_for_renamed_directories |= data->after_symlinks;
+      check_for_renamed_directories |= data->after_links;
 
-      if (after_symlinks < data->after_symlinks
+      if (after_links < data->after_links
          || (data->file_name_len < file_name_len
              && file_name[data->file_name_len]
              && (ISSLASH (file_name[data->file_name_len])
@@ -514,598 +815,902 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_symlinks)
              && memcmp (file_name, data->file_name, data->file_name_len) == 0))
        break;
 
+      chdir_do (data->change_dir);
+
       if (check_for_renamed_directories)
        {
-         cur_info = &st;
-         if (stat (data->file_name, &st) != 0)
+         if (fstatat (chdir_fd, data->file_name, &st, data->atflag) != 0)
            {
              stat_error (data->file_name);
              skip_this_one = 1;
            }
-         else if (! (st.st_dev == data->stat_info.st_dev
-                     && (st.st_ino == data->stat_info.st_ino)))
+         else
            {
-             ERROR ((0, 0,
-                     _("%s: Directory renamed before its status could be extracted"),
-                     quotearg_colon (data->file_name)));
-             skip_this_one = 1;
+             current_mode = st.st_mode;
+             current_mode_mask = ALL_MODE_BITS;
+             if (! (st.st_dev == data->dev && st.st_ino == data->ino))
+               {
+                 ERROR ((0, 0,
+                         _("%s: Directory renamed before its status could be extracted"),
+                         quotearg_colon (data->file_name)));
+                 skip_this_one = 1;
+               }
            }
        }
 
       if (! skip_this_one)
-       set_stat (data->file_name, &data->stat_info, cur_info,
-                 data->invert_permissions, data->permstatus, DIRTYPE);
+       {
+         struct tar_stat_info sb;
+         sb.stat.st_mode = data->mode;
+         sb.stat.st_uid = data->uid;
+         sb.stat.st_gid = data->gid;
+         sb.atime = data->atime;
+         sb.mtime = data->mtime;
+         sb.cntx_name = data->cntx_name;
+         sb.acls_a_ptr = data->acls_a_ptr;
+         sb.acls_a_len = data->acls_a_len;
+         sb.acls_d_ptr = data->acls_d_ptr;
+         sb.acls_d_len = data->acls_d_len;
+         sb.xattr_map = data->xattr_map;
+         sb.xattr_map_size = data->xattr_map_size;
+         set_stat (data->file_name, &sb,
+                   -1, current_mode, current_mode_mask,
+                   DIRTYPE, data->interdir, data->atflag);
+       }
 
       delayed_set_stat_head = data->next;
+      xheader_xattr_free (data->xattr_map, data->xattr_map_size);
+      free (data->cntx_name);
+      free (data->acls_a_ptr);
+      free (data->acls_d_ptr);
       free (data);
     }
 }
 
-/* Extract a file from the archive.  */
-void
-extract_archive (void)
+\f
+static bool
+is_directory_link (const char *file_name)
+{
+  struct stat st;
+  int e = errno;
+  int res;
+  
+  res = (fstatat (chdir_fd, file_name, &st, AT_SYMLINK_NOFOLLOW) == 0 &&
+        S_ISLNK (st.st_mode) &&
+        fstatat (chdir_fd, file_name, &st, 0) == 0 &&
+        S_ISDIR (st.st_mode));
+  errno = e;
+  return res;
+}
+\f
+/* Extractor functions for various member types */
+
+static int
+extract_dir (char *file_name, int typeflag)
 {
-  union block *data_block;
-  int fd;
   int status;
-  size_t count;
-  size_t written;
-  int openflag;
   mode_t mode;
-  off_t size;
-  off_t file_size;
-  int interdir_made = 0;
-  char typeflag;
-  char *file_name;
-
-  set_next_block_after (current_header);
-  decode_header (current_header, &current_stat_info, &current_format, 1);
+  mode_t current_mode = 0;
+  mode_t current_mode_mask = 0;
+  int atflag = 0;
+  bool interdir_made = false;
 
-  if (interactive_option && !confirm ("extract", current_stat_info.file_name))
+  /* Save 'root device' to avoid purging mount points. */
+  if (one_file_system_option && root_device == 0)
     {
-      skip_member ();
-      return;
-    }
-
-  /* Print the block from current_header and current_stat.  */
-
-  if (verbose_option)
-    print_header (&current_stat_info, -1);
+      struct stat st;
 
-  file_name = safer_name_suffix (current_stat_info.file_name, false);
-  if (strip_path_elements)
-    {
-      size_t prefix_len = stripped_prefix_len (file_name, strip_path_elements);
-      if (prefix_len == (size_t) -1)
-       {
-         skip_member ();
-         return;
-       }
-      file_name += prefix_len;
+      if (fstatat (chdir_fd, ".", &st, 0) != 0)
+       stat_diag (".");
+      else
+       root_device = st.st_dev;
     }
-  
-  apply_nonancestor_delayed_set_stat (file_name, 0);
-
-  /* Take a safety backup of a previously existing file.  */
-
-  if (backup_option && !to_stdout_option)
-    if (!maybe_backup_file (file_name, 0))
-      {
-       int e = errno;
-       ERROR ((0, e, _("%s: Was unable to backup this file"),
-               quotearg_colon (file_name)));
-       skip_member ();
-       return;
-      }
-
-  /* Extract the archive entry according to its type.  */
 
-  typeflag = current_header->header.typeflag;
-  /*KLUDGE */
-  if (current_format == POSIX_FORMAT
-      && current_stat_info.archive_file_size != current_stat_info.stat.st_size)
-         typeflag = GNUTYPE_SPARSE;
-  
-  switch (typeflag)
+  if (incremental_option)
+    /* Read the entry and delete files that aren't listed in the archive.  */
+    purge_directory (file_name);
+  else if (typeflag == GNUTYPE_DUMPDIR)
+    skip_member ();
+
+  /* If ownership or permissions will be restored later, create the
+     directory with restrictive permissions at first, so that in the
+     meantime processes owned by other users do not inadvertently
+     create files under this directory that inherit the wrong owner,
+     group, or permissions from the directory.  If not root, though,
+     make the directory writeable and searchable at first, so that
+     files can be created under it.  */
+  mode = ((current_stat_info.stat.st_mode
+          & (0 < same_owner_option || 0 < same_permissions_option
+             ? S_IRWXU
+             : MODE_RWX))
+         | (we_are_root ? 0 : MODE_WXUSR));
+
+  for (;;)
     {
-    case GNUTYPE_SPARSE:
-      /* Fall through.  */
-
-    case AREGTYPE:
-    case REGTYPE:
-    case CONTTYPE:
-
-      /* Appears to be a file.  But BSD tar uses the convention that a slash
-        suffix means a directory.  */
-
-      if (current_stat_info.had_trailing_slash)
-       goto really_dir;
-
-      /* FIXME: deal with protection issues.  */
-
-    again_file:
-      openflag = (O_WRONLY | O_BINARY | O_CREAT
-                 | (old_files_option == OVERWRITE_OLD_FILES
-                    ? O_TRUNC
-                    : O_EXCL));
-      mode = current_stat_info.stat.st_mode & MODE_RWX & ~ current_umask;
-
-      if (to_stdout_option)
-       {
-         fd = STDOUT_FILENO;
-         goto extract_file;
-       }
-
-      if (! prepare_to_extract (file_name, 0))
+      status = mkdirat (chdir_fd, file_name, mode);
+      if (status == 0)
        {
-         skip_member ();
-         if (backup_option)
-           undo_last_backup ();
+         current_mode = mode & ~ current_umask;
+         current_mode_mask = MODE_RWX;
+         atflag = AT_SYMLINK_NOFOLLOW;
          break;
        }
 
-#if O_CTG
-      /* Contiguous files (on the Masscomp) have to specify the size in
-        the open call that creates them.  */
-
-      if (typeflag == CONTTYPE)
-       fd = open (file_name, openflag | O_CTG, mode, current_stat_info.stat.st_size);
-      else
-       fd = open (file_name, openflag, mode);
-
-#else /* not O_CTG */
-      if (typeflag == CONTTYPE)
+      if (errno == EEXIST
+         && (interdir_made
+             || keep_directory_symlink_option
+             || old_files_option == DEFAULT_OLD_FILES
+             || old_files_option == OVERWRITE_OLD_FILES))
        {
-         static int conttype_diagnosed;
+         struct stat st;
 
-         if (!conttype_diagnosed)
+         if (keep_directory_symlink_option && is_directory_link (file_name))
+           return 0;
+         
+         if (deref_stat (file_name, &st) == 0)
            {
-             conttype_diagnosed = 1;
-             WARN ((0, 0, _("Extracting contiguous files as regular files")));
+             current_mode = st.st_mode;
+             current_mode_mask = ALL_MODE_BITS;
+
+             if (S_ISDIR (current_mode))
+               {
+                 if (interdir_made)
+                   {
+                     repair_delayed_set_stat (file_name, &st);
+                     return 0;
+                   }
+                 break;
+               }
            }
+         errno = EEXIST;
        }
-      fd = open (file_name, openflag, mode);
-
-#endif /* not O_CTG */
 
-      if (fd < 0)
+      switch (maybe_recoverable (file_name, false, &interdir_made))
        {
-         if (maybe_recoverable (file_name, &interdir_made))
-           goto again_file;
+       case RECOVER_OK:
+         continue;
 
-         open_error (file_name);
-         skip_member ();
-         if (backup_option)
-           undo_last_backup ();
+       case RECOVER_SKIP:
          break;
-       }
 
-    extract_file:
-      if (typeflag == GNUTYPE_SPARSE)
-       {
-         sparse_extract_file (fd, &current_stat_info, &size);
+       case RECOVER_NO:
+         if (errno != EEXIST)
+           {
+             mkdir_error (file_name);
+             return 1;
+           }
+         break;
        }
-      else
-       for (size = current_stat_info.stat.st_size; size > 0; )
-         {
-           if (multi_volume_option)
-             {
-               assign_string (&save_name, current_stat_info.file_name);
-               save_totsize = current_stat_info.stat.st_size;
-               save_sizeleft = size;
-             }
-
-           /* Locate data, determine max length writeable, write it,
-              block that we have used the data, then check if the write
-              worked.  */
-
-           data_block = find_next_block ();
-           if (! data_block)
-             {
-               ERROR ((0, 0, _("Unexpected EOF in archive")));
-               break;          /* FIXME: What happens, then?  */
-             }
-
-           written = available_space_after (data_block);
-
-           if (written > size)
-             written = size;
-           errno = 0;
-           count = full_write (fd, data_block->buffer, written);
-           size -= count;
-
-           set_next_block_after ((union block *)
-                                 (data_block->buffer + written - 1));
-           if (count != written)
-             {
-               write_error_details (file_name, count, written);
-               break;
-             }
-         }
-
-      skip_file (size);
+      break;
+    }
 
-      if (multi_volume_option)
-       assign_string (&save_name, 0);
+  if (status == 0
+      || old_files_option == DEFAULT_OLD_FILES
+      || old_files_option == OVERWRITE_OLD_FILES)
+    delay_set_stat (file_name, &current_stat_info,
+                   current_mode, current_mode_mask,
+                   current_stat_info.stat.st_mode, atflag);
+  return status;
+}
 
-      /* If writing to stdout, don't try to do anything to the filename;
-        it doesn't exist, or we don't want to touch it anyway.  */
 
-      if (to_stdout_option)
-       break;
 
-      status = close (fd);
-      if (status < 0)
-       {
-         close_error (file_name);
-         if (backup_option)
-           undo_last_backup ();
-       }
+static int
+open_output_file (char const *file_name, int typeflag, mode_t mode,
+                  int file_created, mode_t *current_mode,
+                  mode_t *current_mode_mask)
+{
+  int fd;
+  bool overwriting_old_files = old_files_option == OVERWRITE_OLD_FILES;
+  int openflag = (O_WRONLY | O_BINARY | O_CLOEXEC | O_NOCTTY | O_NONBLOCK
+                 | O_CREAT
+                 | (overwriting_old_files
+                    ? O_TRUNC | (dereference_option ? 0 : O_NOFOLLOW)
+                    : O_EXCL));
 
-      set_stat (file_name, &current_stat_info.stat, 0, 0,
-               (old_files_option == OVERWRITE_OLD_FILES
-                ? UNKNOWN_PERMSTATUS
-                : ARCHIVED_PERMSTATUS),
-               typeflag);
-      break;
+  /* File might be created in set_xattr. So clear O_EXCL to avoid open() fail */
+  if (file_created)
+    openflag = openflag & ~O_EXCL;
 
-    case SYMTYPE:
-#ifdef HAVE_SYMLINK
-      if (! prepare_to_extract (file_name, 0))
-       break;
+  if (typeflag == CONTTYPE)
+    {
+      static int conttype_diagnosed;
 
-      if (absolute_names_option
-         || ! (ISSLASH (current_stat_info.link_name
-                        [FILESYSTEM_PREFIX_LEN (current_stat_info.link_name)])
-               || contains_dot_dot (current_stat_info.link_name)))
+      if (!conttype_diagnosed)
        {
-         while (status = symlink (current_stat_info.link_name, file_name),
-                status != 0)
-           if (!maybe_recoverable (file_name, &interdir_made))
-             break;
+         conttype_diagnosed = 1;
+         WARNOPT (WARN_CONTIGUOUS_CAST,
+                  (0, 0, _("Extracting contiguous files as regular files")));
+       }
+    }
 
-         if (status == 0)
-           set_stat (file_name, &current_stat_info.stat, 0, 0, 0, SYMTYPE);
-         else
-           symlink_error (current_stat_info.link_name, file_name);
+  /* If O_NOFOLLOW is needed but does not work, check for a symlink
+     separately.  There's a race condition, but that cannot be avoided
+     on hosts lacking O_NOFOLLOW.  */
+  if (! HAVE_WORKING_O_NOFOLLOW
+      && overwriting_old_files && ! dereference_option)
+    {
+      struct stat st;
+      if (fstatat (chdir_fd, file_name, &st, AT_SYMLINK_NOFOLLOW) == 0
+         && S_ISLNK (st.st_mode))
+       {
+         errno = ELOOP;
+         return -1;
        }
-      else
+    }
+
+  fd = openat (chdir_fd, file_name, openflag, mode);
+  if (0 <= fd)
+    {
+      if (overwriting_old_files)
        {
-         /* This symbolic link is potentially dangerous.  Don't
-            create it now; instead, create a placeholder file, which
-            will be replaced after other extraction is done.  */
          struct stat st;
-
-         while (fd = open (file_name, O_WRONLY | O_CREAT | O_EXCL, 0),
-                fd < 0)
-           if (! maybe_recoverable (file_name, &interdir_made))
-             break;
-
-         status = -1;
-         if (fd < 0)
-           open_error (file_name);
-         else if (fstat (fd, &st) != 0)
+         if (fstat (fd, &st) != 0)
            {
-             stat_error (file_name);
+             int e = errno;
              close (fd);
+             errno = e;
+             return -1;
            }
-         else if (close (fd) != 0)
-           close_error (file_name);
-         else
+         if (! S_ISREG (st.st_mode))
            {
-             struct delayed_set_stat *h;
-             struct delayed_symlink *p =
-               xmalloc (offsetof (struct delayed_symlink, target)
-                        + strlen (current_stat_info.link_name) + 1);
-             p->next = delayed_symlink_head;
-             delayed_symlink_head = p;
-             p->dev = st.st_dev;
-             p->ino = st.st_ino;
-             p->mtime = st.st_mtime;
-             p->uid = current_stat_info.stat.st_uid;
-             p->gid = current_stat_info.stat.st_gid;
-             p->sources = xmalloc (offsetof (struct string_list, string)
-                                   + strlen (file_name) + 1);
-             p->sources->next = 0;
-             strcpy (p->sources->string, file_name);
-             strcpy (p->target, current_stat_info.link_name);
-
-             h = delayed_set_stat_head;
-             if (h && ! h->after_symlinks
-                 && strncmp (file_name, h->file_name, h->file_name_len) == 0
-                 && ISSLASH (file_name[h->file_name_len])
-                 && (base_name (file_name)
-                     == file_name + h->file_name_len + 1))
-               {
-                 do
-                   {
-                     h->after_symlinks = 1;
-
-                     if (stat (h->file_name, &st) != 0)
-                       stat_error (h->file_name);
-                     else
-                       {
-                         h->stat_info.st_dev = st.st_dev;
-                         h->stat_info.st_ino = st.st_ino;
-                       }
-                   }
-                 while ((h = h->next) && ! h->after_symlinks);
-               }
+             close (fd);
+             errno = EEXIST;
+             return -1;
+           }
+         *current_mode = st.st_mode;
+         *current_mode_mask = ALL_MODE_BITS;
+       }
+      else
+       {
+         *current_mode = mode & ~ current_umask;
+         *current_mode_mask = MODE_RWX;
+       }
+    }
+
+  return fd;
+}
+
+static int
+extract_file (char *file_name, int typeflag)
+{
+  int fd;
+  off_t size;
+  union block *data_block;
+  int status;
+  size_t count;
+  size_t written;
+  bool interdir_made = false;
+  mode_t mode = (current_stat_info.stat.st_mode & MODE_RWX
+                & ~ (0 < same_owner_option ? S_IRWXG | S_IRWXO : 0));
+  mode_t invert_permissions = 0 < same_owner_option ? mode & (S_IRWXG | S_IRWXO)
+                                                    : 0;
+  mode_t current_mode = 0;
+  mode_t current_mode_mask = 0;
 
-             status = 0;
+  if (to_stdout_option)
+    fd = STDOUT_FILENO;
+  else if (to_command_option)
+    {
+      fd = sys_exec_command (file_name, 'f', &current_stat_info);
+      if (fd < 0)
+       {
+         skip_member ();
+         return 0;
+       }
+    }
+  else
+    {
+      int file_created = 0;
+      if (set_xattr (file_name, &current_stat_info, invert_permissions,
+                     typeflag, &file_created))
+        {
+          skip_member ();
+          open_error (file_name);
+          return 1;
+        }
+
+      while ((fd = open_output_file (file_name, typeflag, mode,
+                                     file_created, &current_mode,
+                                     &current_mode_mask))
+            < 0)
+       {
+         int recover = maybe_recoverable (file_name, true, &interdir_made);
+         if (recover != RECOVER_OK)
+           {
+             skip_member ();
+             if (recover == RECOVER_SKIP)
+               return 0;
+             open_error (file_name);
+             return 1;
            }
        }
-  
-      if (status != 0 && backup_option)
-       undo_last_backup ();
-      break;
+    }
 
-#else
+  mv_begin_read (&current_stat_info);
+  if (current_stat_info.is_sparse)
+    sparse_extract_file (fd, &current_stat_info, &size);
+  else
+    for (size = current_stat_info.stat.st_size; size > 0; )
       {
-       static int warned_once;
+       mv_size_left (size);
 
-       if (!warned_once)
+       /* Locate data, determine max length writeable, write it,
+          block that we have used the data, then check if the write
+          worked.  */
+
+       data_block = find_next_block ();
+       if (! data_block)
          {
-           warned_once = 1;
-           WARN ((0, 0,
-                  _("Attempting extraction of symbolic links as hard links")));
+           ERROR ((0, 0, _("Unexpected EOF in archive")));
+           break;              /* FIXME: What happens, then?  */
          }
-      }
-      typeflag = LNKTYPE;
-      /* Fall through.  */
-#endif
-
-    case LNKTYPE:
-      if (! prepare_to_extract (file_name, 0))
-       break;
 
-    again_link:
-      {
-       char const *link_name = safer_name_suffix (current_stat_info.link_name,
-                                                  true);
-       struct stat st1, st2;
-       int e;
+       written = available_space_after (data_block);
 
-       /* MSDOS does not implement links.  However, djgpp's link() actually
-          copies the file.  */
-       status = link (link_name, file_name);
+       if (written > size)
+         written = size;
+       errno = 0;
+       count = blocking_write (fd, data_block->buffer, written);
+       size -= written;
 
-       if (status == 0)
+       set_next_block_after ((union block *)
+                             (data_block->buffer + written - 1));
+       if (count != written)
          {
-           struct delayed_symlink *ds = delayed_symlink_head;
-           if (ds && stat (link_name, &st1) == 0)
-             for (; ds; ds = ds->next)
-               if (ds->dev == st1.st_dev
-                   && ds->ino == st1.st_ino
-                   && ds->mtime == st1.st_mtime)
-                 {
-                   struct string_list *p = 
-                     xmalloc (offsetof (struct string_list, string)
-                              + strlen (file_name) + 1);
-                   strcpy (p->string, file_name);
-                   p->next = ds->sources;
-                   ds->sources = p;
-                   break;
-                 }
+           if (!to_command_option)
+             write_error_details (file_name, count, written);
+           /* FIXME: shouldn't we restore from backup? */
            break;
          }
-       if (maybe_recoverable (file_name, &interdir_made))
-         goto again_link;
+      }
 
-       if (incremental_option && errno == EEXIST)
-         break;
-       e = errno;
-       if (stat (link_name, &st1) == 0
-           && stat (file_name, &st2) == 0
-           && st1.st_dev == st2.st_dev
-           && st1.st_ino == st2.st_ino)
-         break;
+  skip_file (size);
 
-       link_error (link_name, file_name);
-       if (backup_option)
-         undo_last_backup ();
-      }
-      break;
+  mv_end ();
 
-#if S_IFCHR
-    case CHRTYPE:
-      current_stat_info.stat.st_mode |= S_IFCHR;
-      goto make_node;
-#endif
+  /* If writing to stdout, don't try to do anything to the filename;
+     it doesn't exist, or we don't want to touch it anyway.  */
 
-#if S_IFBLK
-    case BLKTYPE:
-      current_stat_info.stat.st_mode |= S_IFBLK;
-#endif
+  if (to_stdout_option)
+    return 0;
 
-#if S_IFCHR || S_IFBLK
-    make_node:
-      if (! prepare_to_extract (file_name, 0))
-       break;
+  if (! to_command_option)
+    set_stat (file_name, &current_stat_info, fd,
+             current_mode, current_mode_mask, typeflag, false,
+             (old_files_option == OVERWRITE_OLD_FILES
+              ? 0 : AT_SYMLINK_NOFOLLOW));
 
-      status = mknod (file_name, current_stat_info.stat.st_mode,
-                     current_stat_info.stat.st_rdev);
-      if (status != 0)
-       {
-         if (maybe_recoverable (file_name, &interdir_made))
-           goto make_node;
-         mknod_error (file_name);
-         if (backup_option)
-           undo_last_backup ();
-         break;
-       };
-      set_stat (file_name, &current_stat_info.stat, 0, 0,
-               ARCHIVED_PERMSTATUS, typeflag);
-      break;
-#endif
+  status = close (fd);
+  if (status < 0)
+    close_error (file_name);
 
-#if HAVE_MKFIFO || defined mkfifo
-    case FIFOTYPE:
-      if (! prepare_to_extract (file_name, 0))
-       break;
+  if (to_command_option)
+    sys_wait_command ();
 
-      while (status = mkfifo (file_name, current_stat_info.stat.st_mode),
-            status != 0)
-       if (!maybe_recoverable (file_name, &interdir_made))
-         break;
+  return status;
+}
 
-      if (status == 0)
-       set_stat (file_name, &current_stat_info.stat, NULL, 0,
-                 ARCHIVED_PERMSTATUS, typeflag);
-      else
+/* Create a placeholder file with name FILE_NAME, which will be
+   replaced after other extraction is done by a symbolic link if
+   IS_SYMLINK is true, and by a hard link otherwise.  Set
+   *INTERDIR_MADE if an intermediate directory is made in the
+   process.  */
+
+static int
+create_placeholder_file (char *file_name, bool is_symlink, bool *interdir_made)
+{
+  int fd;
+  struct stat st;
+
+  while ((fd = openat (chdir_fd, file_name, O_WRONLY | O_CREAT | O_EXCL, 0)) < 0)
+    {
+      switch (maybe_recoverable (file_name, false, interdir_made))
        {
-         mkfifo_error (file_name);
-         if (backup_option)
-           undo_last_backup ();
+       case RECOVER_OK:
+         continue;
+
+       case RECOVER_SKIP:
+         return 0;
+
+       case RECOVER_NO:
+         open_error (file_name);
+         return -1;
        }
-      break;
-#endif
+      }
 
-    case DIRTYPE:
-    case GNUTYPE_DUMPDIR:
-    really_dir:
-      if (incremental_option)
+  if (fstat (fd, &st) != 0)
+    {
+      stat_error (file_name);
+      close (fd);
+    }
+  else if (close (fd) != 0)
+    close_error (file_name);
+  else
+    {
+      struct delayed_set_stat *h;
+      struct delayed_link *p =
+       xmalloc (offsetof (struct delayed_link, target)
+                + strlen (current_stat_info.link_name)
+                + 1);
+      p->next = delayed_link_head;
+      delayed_link_head = p;
+      p->dev = st.st_dev;
+      p->ino = st.st_ino;
+      p->birthtime = get_stat_birthtime (&st);
+      p->is_symlink = is_symlink;
+      if (is_symlink)
        {
-         /* Read the entry and delete files that aren't listed in the
-            archive.  */
-
-         gnu_restore (file_name);
+         p->mode = current_stat_info.stat.st_mode;
+         p->uid = current_stat_info.stat.st_uid;
+         p->gid = current_stat_info.stat.st_gid;
+         p->atime = current_stat_info.atime;
+         p->mtime = current_stat_info.mtime;
        }
-      else if (typeflag == GNUTYPE_DUMPDIR)
-       skip_member ();
+      p->change_dir = chdir_current;
+      p->sources = xmalloc (offsetof (struct string_list, string)
+                           + strlen (file_name) + 1);
+      p->sources->next = 0;
+      strcpy (p->sources->string, file_name);
+      p->cntx_name = NULL;
+      assign_string (&p->cntx_name, current_stat_info.cntx_name);
+      p->acls_a_ptr = NULL;
+      p->acls_a_len = 0;
+      p->acls_d_ptr = NULL;
+      p->acls_d_len = 0;
+      xheader_xattr_copy (&current_stat_info, &p->xattr_map, &p->xattr_map_size);
+      strcpy (p->target, current_stat_info.link_name);
+
+      h = delayed_set_stat_head;
+      if (h && ! h->after_links
+         && strncmp (file_name, h->file_name, h->file_name_len) == 0
+         && ISSLASH (file_name[h->file_name_len])
+         && (last_component (file_name) == file_name + h->file_name_len + 1))
+       mark_after_links (h);
 
-      mode = ((current_stat_info.stat.st_mode
-              | (we_are_root ? 0 : MODE_WXUSR))
-             & MODE_RWX);
+      return 0;
+    }
 
-      status = prepare_to_extract (file_name, 1);
-      if (status == 0)
-       break;
-      if (status < 0)
-       goto directory_exists;
+  return -1;
+}
 
-    again_dir:
-      status = mkdir (file_name, mode);
+static int
+extract_link (char *file_name, int typeflag)
+{
+  bool interdir_made = false;
+  char const *link_name;
+  int rc;
+
+  link_name = current_stat_info.link_name;
+
+  if (! absolute_names_option && contains_dot_dot (link_name))
+    return create_placeholder_file (file_name, false, &interdir_made);
+
+  do
+    {
+      struct stat st1, st2;
+      int e;
+      int status = linkat (chdir_fd, link_name, chdir_fd, file_name, 0);
+      e = errno;
 
-      if (status != 0)
+      if (status == 0)
        {
-         if (errno == EEXIST
-             && (interdir_made
-                 || old_files_option == DEFAULT_OLD_FILES
-                 || old_files_option == OVERWRITE_OLD_FILES))
-           {
-             struct stat st;
-             if (stat (file_name, &st) == 0)
+         struct delayed_link *ds = delayed_link_head;
+         if (ds
+             && fstatat (chdir_fd, link_name, &st1, AT_SYMLINK_NOFOLLOW) == 0)
+           for (; ds; ds = ds->next)
+             if (ds->change_dir == chdir_current
+                 && ds->dev == st1.st_dev
+                 && ds->ino == st1.st_ino
+                 && (timespec_cmp (ds->birthtime, get_stat_birthtime (&st1))
+                     == 0))
                {
-                 if (interdir_made)
-                   {
-                     repair_delayed_set_stat (file_name, &st);
-                     break;
-                   }
-                 if (S_ISDIR (st.st_mode))
-                   {
-                     mode = st.st_mode & ~ current_umask;
-                     goto directory_exists;
-                   }
+                 struct string_list *p =  xmalloc (offsetof (struct string_list, string)
+                                                   + strlen (file_name) + 1);
+                 strcpy (p->string, file_name);
+                 p->next = ds->sources;
+                 ds->sources = p;
+                 break;
                }
-             errno = EEXIST;
-           }
-       
-         if (maybe_recoverable (file_name, &interdir_made))
-           goto again_dir;
+         return 0;
+       }
+      else if ((e == EEXIST && strcmp (link_name, file_name) == 0)
+              || ((fstatat (chdir_fd, link_name, &st1, AT_SYMLINK_NOFOLLOW)
+                   == 0)
+                  && (fstatat (chdir_fd, file_name, &st2, AT_SYMLINK_NOFOLLOW)
+                      == 0)
+                  && st1.st_dev == st2.st_dev
+                  && st1.st_ino == st2.st_ino))
+       return 0;
+
+      errno = e;
+    }
+  while ((rc = maybe_recoverable (file_name, false, &interdir_made))
+        == RECOVER_OK);
 
-         if (errno != EEXIST)
-           {
-             mkdir_error (file_name);
-             if (backup_option)
-               undo_last_backup ();
-             break;
-           }
+  if (rc == RECOVER_SKIP)
+    return 0;
+  if (!(incremental_option && errno == EEXIST))
+    {
+      link_error (link_name, file_name);
+      return 1;
+    }
+  return 0;
+}
+
+static int
+extract_symlink (char *file_name, int typeflag)
+{
+#ifdef HAVE_SYMLINK
+  bool interdir_made = false;
+
+  if (! absolute_names_option
+      && (IS_ABSOLUTE_FILE_NAME (current_stat_info.link_name)
+         || contains_dot_dot (current_stat_info.link_name)))
+    return create_placeholder_file (file_name, true, &interdir_made);
+
+  while (symlinkat (current_stat_info.link_name, chdir_fd, file_name) != 0)
+    switch (maybe_recoverable (file_name, false, &interdir_made))
+      {
+      case RECOVER_OK:
+       continue;
+
+      case RECOVER_SKIP:
+       return 0;
+
+      case RECOVER_NO:
+       symlink_error (current_stat_info.link_name, file_name);
+       return -1;
+      }
+
+  set_stat (file_name, &current_stat_info, -1, 0, 0,
+           SYMTYPE, false, AT_SYMLINK_NOFOLLOW);
+  return 0;
+
+#else
+  static int warned_once;
+
+  if (!warned_once)
+    {
+      warned_once = 1;
+      WARNOPT (WARN_SYMLINK_CAST,
+              (0, 0,
+               _("Attempting extraction of symbolic links as hard links")));
+    }
+  return extract_link (file_name, typeflag);
+#endif
+}
+
+#if S_IFCHR || S_IFBLK
+static int
+extract_node (char *file_name, int typeflag)
+{
+  bool interdir_made = false;
+  mode_t mode = (current_stat_info.stat.st_mode & (MODE_RWX | S_IFBLK | S_IFCHR)
+                & ~ (0 < same_owner_option ? S_IRWXG | S_IRWXO : 0));
+
+  while (mknodat (chdir_fd, file_name, mode, current_stat_info.stat.st_rdev)
+        != 0)
+    switch (maybe_recoverable (file_name, false, &interdir_made))
+      {
+      case RECOVER_OK:
+       continue;
+
+      case RECOVER_SKIP:
+       return 0;
+
+      case RECOVER_NO:
+       mknod_error (file_name);
+       return -1;
+      }
+
+  set_stat (file_name, &current_stat_info, -1,
+           mode & ~ current_umask, MODE_RWX,
+           typeflag, false, AT_SYMLINK_NOFOLLOW);
+  return 0;
+}
+#endif
+
+#if HAVE_MKFIFO || defined mkfifo
+static int
+extract_fifo (char *file_name, int typeflag)
+{
+  bool interdir_made = false;
+  mode_t mode = (current_stat_info.stat.st_mode & MODE_RWX
+                & ~ (0 < same_owner_option ? S_IRWXG | S_IRWXO : 0));
+
+  while (mkfifoat (chdir_fd, file_name, mode) != 0)
+    switch (maybe_recoverable (file_name, false, &interdir_made))
+      {
+      case RECOVER_OK:
+       continue;
+
+      case RECOVER_SKIP:
+       return 0;
+
+      case RECOVER_NO:
+       mkfifo_error (file_name);
+       return -1;
+      }
+
+  set_stat (file_name, &current_stat_info, -1,
+           mode & ~ current_umask, MODE_RWX,
+           typeflag, false, AT_SYMLINK_NOFOLLOW);
+  return 0;
+}
+#endif
+
+static int
+extract_volhdr (char *file_name, int typeflag)
+{
+  skip_member ();
+  return 0;
+}
+
+static int
+extract_failure (char *file_name, int typeflag)
+{
+  return 1;
+}
+
+static int
+extract_skip (char *file_name, int typeflag)
+{
+  skip_member ();
+  return 0;
+}
+
+typedef int (*tar_extractor_t) (char *file_name, int typeflag);
+
+\f
+
+/* Prepare to extract a file. Find extractor function.
+   Return zero if extraction should not proceed.  */
+
+static int
+prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun)
+{
+  int rc = 1;
+
+  if (EXTRACT_OVER_PIPE)
+    rc = 0;
+
+  /* Select the extractor */
+  switch (typeflag)
+    {
+    case GNUTYPE_SPARSE:
+      *fun = extract_file;
+      rc = 1;
+      break;
+
+    case AREGTYPE:
+    case REGTYPE:
+    case CONTTYPE:
+      /* Appears to be a file.  But BSD tar uses the convention that a slash
+        suffix means a directory.  */
+      if (current_stat_info.had_trailing_slash)
+       *fun = extract_dir;
+      else
+       {
+         *fun = extract_file;
+         rc = 1;
        }
+      break;
 
-    directory_exists:
-      if (status == 0
-         || old_files_option == DEFAULT_OLD_FILES
-         || old_files_option == OVERWRITE_OLD_FILES)
-       delay_set_stat (file_name, &current_stat_info.stat,
-                       MODE_RWX & (mode ^ current_stat_info.stat.st_mode),
-                       (status == 0
-                        ? ARCHIVED_PERMSTATUS
-                        : UNKNOWN_PERMSTATUS));
+    case SYMTYPE:
+      *fun = extract_symlink;
       break;
 
-    case GNUTYPE_VOLHDR:
-      if (verbose_option)
-       fprintf (stdlis, _("Reading %s\n"), quote (current_stat_info.file_name));
+    case LNKTYPE:
+      *fun = extract_link;
+      break;
+
+#if S_IFCHR
+    case CHRTYPE:
+      current_stat_info.stat.st_mode |= S_IFCHR;
+      *fun = extract_node;
+      break;
+#endif
+
+#if S_IFBLK
+    case BLKTYPE:
+      current_stat_info.stat.st_mode |= S_IFBLK;
+      *fun = extract_node;
+      break;
+#endif
+
+#if HAVE_MKFIFO || defined mkfifo
+    case FIFOTYPE:
+      *fun = extract_fifo;
       break;
+#endif
 
-    case GNUTYPE_NAMES:
-      extract_mangle ();
+    case DIRTYPE:
+    case GNUTYPE_DUMPDIR:
+      *fun = extract_dir;
+      if (current_stat_info.is_dumpdir)
+       delay_directory_restore_option = true;
+      break;
+
+    case GNUTYPE_VOLHDR:
+      *fun = extract_volhdr;
       break;
 
     case GNUTYPE_MULTIVOL:
       ERROR ((0, 0,
              _("%s: Cannot extract -- file is continued from another volume"),
              quotearg_colon (current_stat_info.file_name)));
-      skip_member ();
-      if (backup_option)
-       undo_last_backup ();
+      *fun = extract_skip;
       break;
 
     case GNUTYPE_LONGNAME:
     case GNUTYPE_LONGLINK:
-      ERROR ((0, 0, _("Visible long name error")));
-      skip_member ();
-      if (backup_option)
-       undo_last_backup ();
+      ERROR ((0, 0, _("Unexpected long name header")));
+      *fun = extract_failure;
+      break;
+
+    default:
+      WARNOPT (WARN_UNKNOWN_CAST,
+              (0, 0,
+               _("%s: Unknown file type '%c', extracted as normal file"),
+               quotearg_colon (file_name), typeflag));
+      *fun = extract_file;
+    }
+
+  /* Determine whether the extraction should proceed */
+  if (rc == 0)
+    return 0;
+
+  switch (old_files_option)
+    {
+    case UNLINK_FIRST_OLD_FILES:
+      if (!remove_any_file (file_name,
+                            recursive_unlink_option ? RECURSIVE_REMOVE_OPTION
+                                                      : ORDINARY_REMOVE_OPTION)
+         && errno && errno != ENOENT)
+       {
+         unlink_error (file_name);
+         return 0;
+       }
+      break;
+
+    case KEEP_NEWER_FILES:
+      if (file_newer_p (file_name, 0, &current_stat_info))
+       {
+         WARNOPT (WARN_IGNORE_NEWER,
+                  (0, 0, _("Current %s is newer or same age"),
+                   quote (file_name)));
+         return 0;
+       }
       break;
 
     default:
-      WARN ((0, 0,
-            _("%s: Unknown file type '%c', extracted as normal file"),
-            quotearg_colon (file_name), typeflag));
-      goto again_file;
+      break;
+    }
+
+  return 1;
+}
+
+/* Extract a file from the archive.  */
+void
+extract_archive (void)
+{
+  char typeflag;
+  tar_extractor_t fun;
+
+  fatal_exit_hook = extract_finish;
+
+  set_next_block_after (current_header);
+
+  if (!current_stat_info.file_name[0]
+      || (interactive_option
+         && !confirm ("extract", current_stat_info.file_name)))
+    {
+      skip_member ();
+      return;
+    }
+
+  /* Print the block from current_header and current_stat.  */
+  if (verbose_option)
+    print_header (&current_stat_info, current_header, -1);
+
+  /* Restore stats for all non-ancestor directories, unless
+     it is an incremental archive.
+     (see NOTICE in the comment to delay_set_stat above) */
+  if (!delay_directory_restore_option)
+    {
+      int dir = chdir_current;
+      apply_nonancestor_delayed_set_stat (current_stat_info.file_name, 0);
+      chdir_do (dir);
+    }
+
+  /* Take a safety backup of a previously existing file.  */
+
+  if (backup_option)
+    if (!maybe_backup_file (current_stat_info.file_name, 0))
+      {
+       int e = errno;
+       ERROR ((0, e, _("%s: Was unable to backup this file"),
+               quotearg_colon (current_stat_info.file_name)));
+       skip_member ();
+       return;
+      }
+
+  /* Extract the archive entry according to its type.  */
+  /* KLUDGE */
+  typeflag = sparse_member_p (&current_stat_info) ?
+                  GNUTYPE_SPARSE : current_header->header.typeflag;
+
+  if (prepare_to_extract (current_stat_info.file_name, typeflag, &fun))
+    {
+      if (fun && (*fun) (current_stat_info.file_name, typeflag)
+         && backup_option)
+       undo_last_backup ();
     }
+  else
+    skip_member ();
+
 }
 
-/* Extract the symbolic links whose final extraction were delayed.  */
+/* Extract the links whose final extraction were delayed.  */
 static void
-apply_delayed_symlinks (void)
+apply_delayed_links (void)
 {
-  struct delayed_symlink *ds;
+  struct delayed_link *ds;
 
-  for (ds = delayed_symlink_head; ds; )
+  for (ds = delayed_link_head; ds; )
     {
       struct string_list *sources = ds->sources;
       char const *valid_source = 0;
 
+      chdir_do (ds->change_dir);
+
       for (sources = ds->sources; sources; sources = sources->next)
        {
          char const *source = sources->string;
          struct stat st;
 
          /* Make sure the placeholder file is still there.  If not,
-            don't create a symlink, as the placeholder was probably
+            don't create a link, as the placeholder was probably
             removed by a later extraction.  */
-         if (lstat (source, &st) == 0
+         if (fstatat (chdir_fd, source, &st, AT_SYMLINK_NOFOLLOW) == 0
              && st.st_dev == ds->dev
              && st.st_ino == ds->ino
-             && st.st_mtime == ds->mtime)
+             && timespec_cmp (get_stat_birthtime (&st), ds->birthtime) == 0)
            {
              /* Unlink the placeholder, then create a hard link if possible,
                 a symbolic link otherwise.  */
-             if (unlink (source) != 0)
+             if (unlinkat (chdir_fd, source, 0) != 0)
                unlink_error (source);
-             else if (valid_source && link (valid_source, source) == 0)
+             else if (valid_source
+                      && (linkat (chdir_fd, valid_source, chdir_fd, source, 0)
+                          == 0))
                ;
-             else if (symlink (ds->target, source) != 0)
+             else if (!ds->is_symlink)
+               {
+                 if (linkat (chdir_fd, ds->target, chdir_fd, source, 0) != 0)
+                   link_error (ds->target, source);
+               }
+             else if (symlinkat (ds->target, chdir_fd, source) != 0)
                symlink_error (ds->target, source);
              else
                {
+                 struct tar_stat_info st1;
+                 st1.stat.st_mode = ds->mode;
+                 st1.stat.st_uid = ds->uid;
+                 st1.stat.st_gid = ds->gid;
+                 st1.atime = ds->atime;
+                 st1.mtime = ds->mtime;
+                  st1.cntx_name = ds->cntx_name;
+                  st1.acls_a_ptr = ds->acls_a_ptr;
+                  st1.acls_a_len = ds->acls_a_len;
+                  st1.acls_d_ptr = ds->acls_d_ptr;
+                  st1.acls_d_len = ds->acls_d_len;
+                  st1.xattr_map = ds->xattr_map;
+                  st1.xattr_map_size = ds->xattr_map_size;
+                 set_stat (source, &st1, -1, 0, 0, SYMTYPE,
+                           false, AT_SYMLINK_NOFOLLOW);
                  valid_source = source;
-                 st.st_uid = ds->uid;
-                 st.st_gid = ds->gid;
-                 set_stat (source, &st, 0, 0, 0, SYMTYPE);
                }
            }
        }
@@ -1117,14 +1722,17 @@ apply_delayed_symlinks (void)
          sources = next;
        }
 
+   xheader_xattr_free (ds->xattr_map, ds->xattr_map_size);
+   free (ds->cntx_name);
+
       {
-       struct delayed_symlink *next = ds->next;
+       struct delayed_link *next = ds->next;
        free (ds);
        ds = next;
       }
     }
 
-  delayed_symlink_head = 0;
+  delayed_link_head = 0;
 }
 
 /* Finish the extraction of an archive.  */
@@ -1134,19 +1742,45 @@ extract_finish (void)
   /* First, fix the status of ordinary directories that need fixing.  */
   apply_nonancestor_delayed_set_stat ("", 0);
 
-  /* Then, apply delayed symlinks, so that they don't affect delayed
+  /* Then, apply delayed links, so that they don't affect delayed
      directory status-setting for ordinary directories.  */
-  apply_delayed_symlinks ();
+  apply_delayed_links ();
 
   /* Finally, fix the status of directories that are ancestors
-     of delayed symlinks.  */
+     of delayed links.  */
   apply_nonancestor_delayed_set_stat ("", 1);
 }
 
-void
-fatal_exit (void)
+bool
+rename_directory (char *src, char *dst)
 {
-  extract_finish ();
-  error (TAREXIT_FAILURE, 0, _("Error is not recoverable: exiting now"));
-  abort ();
+  if (renameat (chdir_fd, src, chdir_fd, dst) != 0)
+    {
+      int e = errno;
+      bool interdir_made;
+
+      switch (e)
+       {
+       case ENOENT:
+         if (make_directories (dst, &interdir_made) == 0)
+           {
+             if (renameat (chdir_fd, src, chdir_fd, dst) == 0)
+               return true;
+             e = errno;
+           }
+         break;
+
+       case EXDEV:
+         /* FIXME: Fall back to recursive copying */
+
+       default:
+         break;
+       }
+
+      ERROR ((0, e, _("Cannot rename %s to %s"),
+             quote_n (0, src),
+             quote_n (1, dst)));
+      return false;
+    }
+  return true;
 }
This page took 0.075536 seconds and 4 git commands to generate.