]> Dogcows Code - chaz/tar/blobdiff - src/extract.c
Update copyright years.
[chaz/tar] / src / extract.c
index 148fdfca02933d288e5469e96aba7edfe2f8e4f4..2cc1f7b383a121fb5a6d864e96bf54f8af4a9d95 100644 (file)
@@ -1,29 +1,31 @@
 /* Extract files from a tar archive.
 
-   Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1998, 1999, 2000,
-   2001, 2003, 2004, 2005, 2006 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.,
-   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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/>.
+
+   Written by John Gilmore, on 1985-11-19.  */
 
 #include <system.h>
 #include <quotearg.h>
-#include <utimens.h>
 #include <errno.h>
-#include <xgetcwd.h>
+#include <priv-set.h>
+#include <root-uid.h>
+#include <utimens.h>
 
 #include "common.h"
 
@@ -31,19 +33,24 @@ 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
@@ -56,18 +63,52 @@ enum permstatus
 
 struct delayed_set_stat
   {
+    /* Next directory in list.  */
     struct delayed_set_stat *next;
+
+    /* Metadata for this directory.  */
     dev_t dev;
     ino_t ino;
-    mode_t mode;
+    mode_t mode; /* The desired mode is MODE & ~ current_umask.  */
     uid_t uid;
     gid_t gid;
     struct timespec atime;
     struct timespec mtime;
-    size_t file_name_len;
-    mode_t invert_permissions;
-    enum permstatus permstatus;
+
+    /* 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;
     char file_name[1];
   };
 
@@ -79,22 +120,45 @@ struct delayed_link
     /* The next delayed link in the list.  */
     struct delayed_link *next;
 
-    /* The device, inode number and last-modified time of the placeholder.  */
+    /* 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;
-    struct timespec mtime;
+    struct timespec birthtime;
 
     /* True if the link is symbolic.  */
     bool is_symlink;
 
-    /* The desired owner and group of the link, if it is a symlink.  */
+    /* The desired metadata, valid only the link is symbolic.  */
+    mode_t mode;
     uid_t uid;
     gid_t gid;
+    struct timespec atime;
+    struct timespec mtime;
+
+    /* 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];
   };
@@ -111,7 +175,7 @@ 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;
 
@@ -127,72 +191,129 @@ 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, struct timespec t)
 {
-  if (t.tv_sec <= 0)
-    WARN ((0, 0, _("%s: implausibly old time stamp %s"),
-          file_name, tartime (t, true)));
+  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;
@@ -208,107 +329,113 @@ check_time (char const *file_name, struct timespec t)
              diff.tv_nsec += BILLION;
              diff.tv_sec--;
            }
-         WARN ((0, 0, _("%s: time stamp %s is %s s in the future"),
-                file_name, tartime (t, true), code_timespec (diff, buf)));
+         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 *ST.
-   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!  */
+   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 tar_stat_info const *st,
-         struct stat const *cur_info,
-         mode_t invert_permissions, enum permstatus permstatus,
-         char typeflag)
+         int fd, mode_t current_mode, mode_t current_mode_mask,
+         char typeflag, bool interdir, int atflag)
 {
-  if (typeflag != SYMTYPE)
+  /* Do the utime before the chmod because some versions of utime are
+     broken and trash the modes of the file.  */
+
+  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?  */
-
-         struct timespec ts[2];
          if (incremental_option)
-           ts[0] = st->atime;
-         else
-           ts[0] = start_time;
-         ts[1] = st->mtime;
-
-         if (utimens (file_name, ts) != 0)
-           utime_error (file_name);
-         else
-           {
-             check_time (file_name, ts[0]);
-             check_time (file_name, ts[1]);
-           }
+           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, &st->stat, 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, st->stat.st_uid, st->stat.st_gid) < 0)
-           chown_error_details (file_name,
-                                st->stat.st_uid, st->stat.st_gid);
-#endif
-       }
+      if (deref_stat (h->file_name, &st) != 0)
+       stat_error (h->file_name);
       else
        {
-         if (chown (file_name, st->stat.st_uid, st->stat.st_gid) < 0)
-           chown_error_details (file_name,
-                                st->stat.st_uid, st->stat.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 (st->stat.st_mode & (S_ISUID | S_ISGID | S_ISVTX))
-           set_mode (file_name, &st->stat, 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 *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
@@ -323,26 +450,65 @@ set_stat (char const *file_name,
 */
 static void
 delay_set_stat (char const *file_name, struct tar_stat_info const *st,
-               mode_t invert_permissions, enum permstatus permstatus)
+               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->dev = st->stat.st_dev;
-  data->ino = st->stat.st_ino;
-  data->mode = st->stat.st_mode;
-  data->uid = st->stat.st_uid;
-  data->gid = st->stat.st_gid;
-  data->atime = st->atime;
-  data->mtime = st->mtime;
+  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->invert_permissions = invert_permissions;
-  data->permstatus = permstatus;
+  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);
   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
@@ -357,7 +523,7 @@ repair_delayed_set_stat (char const *dir,
   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;
@@ -373,9 +539,9 @@ repair_delayed_set_stat (char const *dir,
          data->gid = current_stat_info.stat.st_gid;
          data->atime = current_stat_info.atime;
          data->mtime = current_stat_info.mtime;
-         data->invert_permissions =
-           (MODE_RWX & (current_stat_info.stat.st_mode ^ st.st_mode));
-         data->permstatus = ARCHIVED_PERMSTATUS;
+         data->current_mode = st.st_mode;
+         data->current_mode_mask = ALL_MODE_BITS;
+         data->interdir = false;
          return;
        }
     }
@@ -386,20 +552,21 @@ repair_delayed_set_stat (char const *dir,
 
 /* 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 + FILE_SYSTEM_PREFIX_LEN (file_name);
   char *cursor;                        /* points into the file name */
-  int did_something = 0;       /* did we do anything yet? */
-  int mode;
-  int invert_permissions;
-  int status;
 
   for (cursor = cursor0; *cursor; cursor++)
     {
+      mode_t mode;
+      mode_t desired_mode;
+      int status;
+
       if (! ISSLASH (*cursor))
        continue;
 
@@ -417,101 +584,154 @@ make_directories (char *file_name)
        continue;
 
       *cursor = '\0';          /* truncate the name there */
-      mode = MODE_RWX & ~ newdir_umask;
-      invert_permissions = we_are_root ? 0 : MODE_WXUSR & ~ mode;
-      status = mkdir (file_name, mode ^ invert_permissions);
+      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,
-                         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;
 }
 
+/* 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 tar_stat_info *tar_stat)
+file_newer_p (const char *file_name, struct stat const *stp,
+             struct tar_stat_info *tar_stat)
 {
   struct stat st;
 
-  if (stat (file_name, &st))
-    {
-      stat_warn (file_name);
-      /* Be on the safe side: if the file does exist assume it is newer */
-      return errno != ENOENT;
-    }
-  if (!S_ISDIR (st.st_mode)
-      && tar_timespec_cmp (tar_stat->mtime, get_stat_mtime (&st)) <= 0)
+  if (!stp)
     {
-      return true;
+      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 false;
+
+  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)
        {
+       case SKIP_OLD_FILES:
+         WARNOPT (WARN_EXISTING_FILE,
+                  (0, 0, _("%s: skipping existing file"), file_name));
+         return RECOVER_SKIP;
+
        case KEEP_OLD_FILES:
-         return 0;
+         return RECOVER_NO;
 
        case KEEP_NEWER_FILES:
-         if (file_newer_p (file_name, &current_stat_info))
-           {
-             errno = e;
-             return 0;
-           }
+         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, ORDINARY_REMOVE_OPTION);
-           errno = EEXIST;
-           return r;
-         }
+         if (0 < remove_any_file (file_name, ORDINARY_REMOVE_OPTION))
+           return RECOVER_OK;
+         break;
 
        case UNLINK_FIRST_OLD_FILES:
          break;
@@ -519,19 +739,51 @@ maybe_recoverable (char *file_name, int *interdir_made)
 
     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
@@ -550,7 +802,8 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links)
       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_links;
 
@@ -562,42 +815,74 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links)
              && 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->dev && st.st_ino == data->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)
        {
-         struct tar_stat_info st;
-         st.stat.st_mode = data->mode;
-         st.stat.st_uid = data->uid;
-         st.stat.st_gid = data->gid;
-         st.atime = data->atime;
-         st.mtime = data->mtime;
-         set_stat (data->file_name, &st, 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);
     }
 }
 
 \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
@@ -605,19 +890,20 @@ extract_dir (char *file_name, int typeflag)
 {
   int status;
   mode_t mode;
-  int interdir_made = 0;
+  mode_t current_mode = 0;
+  mode_t current_mode_mask = 0;
+  int atflag = 0;
+  bool interdir_made = false;
 
   /* Save 'root device' to avoid purging mount points. */
   if (one_file_system_option && root_device == 0)
     {
       struct stat st;
-      char *dir = xgetcwd ();
 
-      if (deref_stat (true, dir, &st))
-       stat_diag (dir);
+      if (fstatat (chdir_fd, ".", &st, 0) != 0)
+       stat_diag (".");
       else
        root_device = st.st_dev;
-      free (dir);
     }
 
   if (incremental_option)
@@ -626,40 +912,74 @@ extract_dir (char *file_name, int typeflag)
   else if (typeflag == GNUTYPE_DUMPDIR)
     skip_member ();
 
-  mode = (current_stat_info.stat.st_mode |
-          (we_are_root ? 0 : MODE_WXUSR)) & MODE_RWX;
-
-  while ((status = mkdir (file_name, mode)))
+  /* 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 (;;)
     {
+      status = mkdirat (chdir_fd, file_name, mode);
+      if (status == 0)
+       {
+         current_mode = mode & ~ current_umask;
+         current_mode_mask = MODE_RWX;
+         atflag = AT_SYMLINK_NOFOLLOW;
+         break;
+       }
+
       if (errno == EEXIST
          && (interdir_made
+             || keep_directory_symlink_option
              || old_files_option == DEFAULT_OLD_FILES
              || old_files_option == OVERWRITE_OLD_FILES))
        {
          struct stat st;
-         if (stat (file_name, &st) == 0)
+
+         if (keep_directory_symlink_option && is_directory_link (file_name))
+           return 0;
+         
+         if (deref_stat (file_name, &st) == 0)
            {
-             if (interdir_made)
-               {
-                 repair_delayed_set_stat (file_name, &st);
-                 return 0;
-               }
-             if (S_ISDIR (st.st_mode))
+             current_mode = st.st_mode;
+             current_mode_mask = ALL_MODE_BITS;
+
+             if (S_ISDIR (current_mode))
                {
-                 mode = st.st_mode;
+                 if (interdir_made)
+                   {
+                     repair_delayed_set_stat (file_name, &st);
+                     return 0;
+                   }
                  break;
                }
            }
          errno = EEXIST;
        }
 
-      if (maybe_recoverable (file_name, &interdir_made))
-       continue;
-
-      if (errno != EEXIST)
+      switch (maybe_recoverable (file_name, false, &interdir_made))
        {
-         mkdir_error (file_name);
-         return 1;
+       case RECOVER_OK:
+         continue;
+
+       case RECOVER_SKIP:
+         break;
+
+       case RECOVER_NO:
+         if (errno != EEXIST)
+           {
+             mkdir_error (file_name);
+             return 1;
+           }
+         break;
        }
       break;
     }
@@ -667,40 +987,31 @@ extract_dir (char *file_name, int typeflag)
   if (status == 0
       || old_files_option == DEFAULT_OLD_FILES
       || old_files_option == OVERWRITE_OLD_FILES)
-    {
-      if (status == 0)
-       delay_set_stat (file_name, &current_stat_info,
-                       MODE_RWX & (mode ^ current_stat_info.stat.st_mode),
-                       ARCHIVED_PERMSTATUS);
-      else /* For an already existing directory, invert_perms must be 0 */
-       delay_set_stat (file_name, &current_stat_info,
-                       0,
-                       UNKNOWN_PERMSTATUS);
-    }
+    delay_set_stat (file_name, &current_stat_info,
+                   current_mode, current_mode_mask,
+                   current_stat_info.stat.st_mode, atflag);
   return status;
 }
 
 
+
 static int
-open_output_file (char *file_name, int typeflag)
+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;
-  int openflag = (O_WRONLY | O_BINARY | O_CREAT
-                 | (old_files_option == OVERWRITE_OLD_FILES
-                    ? O_TRUNC
+  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));
-  mode_t mode = current_stat_info.stat.st_mode & MODE_RWX & ~ current_umask;
 
-#if O_CTG
-  /* Contiguous files (on the Masscomp) have to specify the size in
-     the open call that creates them.  */
+  /* File might be created in set_xattr. So clear O_EXCL to avoid open() fail */
+  if (file_created)
+    openflag = openflag & ~O_EXCL;
 
-  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)
     {
       static int conttype_diagnosed;
@@ -708,12 +1019,54 @@ open_output_file (char *file_name, int typeflag)
       if (!conttype_diagnosed)
        {
          conttype_diagnosed = 1;
-         WARN ((0, 0, _("Extracting contiguous files as regular files")));
+         WARNOPT (WARN_CONTIGUOUS_CAST,
+                  (0, 0, _("Extracting contiguous files as regular files")));
+       }
+    }
+
+  /* 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;
        }
     }
-  fd = open (file_name, openflag, mode);
 
-#endif /* not O_CTG */
+  fd = openat (chdir_fd, file_name, openflag, mode);
+  if (0 <= fd)
+    {
+      if (overwriting_old_files)
+       {
+         struct stat st;
+         if (fstat (fd, &st) != 0)
+           {
+             int e = errno;
+             close (fd);
+             errno = e;
+             return -1;
+           }
+         if (! S_ISREG (st.st_mode))
+           {
+             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;
 }
@@ -727,9 +1080,13 @@ extract_file (char *file_name, int typeflag)
   int status;
   size_t count;
   size_t written;
-  int interdir_made = 0;
-
-  /* FIXME: deal with protection issues.  */
+  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;
 
   if (to_stdout_option)
     fd = STDOUT_FILENO;
@@ -744,18 +1101,33 @@ extract_file (char *file_name, int typeflag)
     }
   else
     {
-      do
-       fd = open_output_file (file_name, typeflag);
-      while (fd < 0 && maybe_recoverable (file_name, &interdir_made));
-
-      if (fd < 0)
+      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)
        {
-         open_error (file_name);
-         return 1;
+         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;
+           }
        }
     }
 
-  mv_begin (&current_stat_info);
+  mv_begin_read (&current_stat_info);
   if (current_stat_info.is_sparse)
     sparse_extract_file (fd, &current_stat_info, &size);
   else
@@ -779,7 +1151,7 @@ extract_file (char *file_name, int typeflag)
        if (written > size)
          written = size;
        errno = 0;
-       count = full_write (fd, data_block->buffer, written);
+       count = blocking_write (fd, data_block->buffer, written);
        size -= written;
 
        set_next_block_after ((union block *)
@@ -803,17 +1175,18 @@ extract_file (char *file_name, int typeflag)
   if (to_stdout_option)
     return 0;
 
+  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 = close (fd);
   if (status < 0)
     close_error (file_name);
 
   if (to_command_option)
     sys_wait_command ();
-  else
-    set_stat (file_name, &current_stat_info, NULL, 0,
-             (old_files_option == OVERWRITE_OLD_FILES ?
-              UNKNOWN_PERMSTATUS : ARCHIVED_PERMSTATUS),
-             typeflag);
 
   return status;
 }
@@ -825,18 +1198,28 @@ extract_file (char *file_name, int typeflag)
    process.  */
 
 static int
-create_placeholder_file (char *file_name, bool is_symlink, int *interdir_made)
+create_placeholder_file (char *file_name, bool is_symlink, bool *interdir_made)
 {
   int fd;
   struct stat st;
 
-  while ((fd = open (file_name, O_WRONLY | O_CREAT | O_EXCL, 0)) < 0)
-    if (! maybe_recoverable (file_name, interdir_made))
-      break;
+  while ((fd = openat (chdir_fd, file_name, O_WRONLY | O_CREAT | O_EXCL, 0)) < 0)
+    {
+      switch (maybe_recoverable (file_name, false, interdir_made))
+       {
+       case RECOVER_OK:
+         continue;
+
+       case RECOVER_SKIP:
+         return 0;
+
+       case RECOVER_NO:
+         open_error (file_name);
+         return -1;
+       }
+      }
 
-  if (fd < 0)
-    open_error (file_name);
-  else if (fstat (fd, &st) != 0)
+  if (fstat (fd, &st) != 0)
     {
       stat_error (file_name);
       close (fd);
@@ -854,17 +1237,28 @@ create_placeholder_file (char *file_name, bool is_symlink, int *interdir_made)
       delayed_link_head = p;
       p->dev = st.st_dev;
       p->ino = st.st_ino;
-      p->mtime = get_stat_mtime (&st);
+      p->birthtime = get_stat_birthtime (&st);
       p->is_symlink = is_symlink;
       if (is_symlink)
        {
+         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;
        }
+      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;
@@ -872,21 +1266,7 @@ create_placeholder_file (char *file_name, bool is_symlink, int *interdir_made)
          && 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))
-       {
-         do
-           {
-             h->after_links = 1;
-
-             if (stat (h->file_name, &st) != 0)
-               stat_error (h->file_name);
-             else
-               {
-                 h->dev = st.st_dev;
-                 h->ino = st.st_ino;
-               }
-           }
-         while ((h = h->next) && ! h->after_links);
-       }
+       mark_after_links (h);
 
       return 0;
     }
@@ -897,9 +1277,11 @@ create_placeholder_file (char *file_name, bool is_symlink, int *interdir_made)
 static int
 extract_link (char *file_name, int typeflag)
 {
-  char const *link_name = safer_name_suffix (current_stat_info.link_name,
-                                             true, absolute_names_option);
-  int interdir_made = 0;
+  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);
@@ -908,17 +1290,20 @@ extract_link (char *file_name, int typeflag)
     {
       struct stat st1, st2;
       int e;
-      int status = link (link_name, file_name);
+      int status = linkat (chdir_fd, link_name, chdir_fd, file_name, 0);
       e = errno;
 
       if (status == 0)
        {
          struct delayed_link *ds = delayed_link_head;
-         if (ds && lstat (link_name, &st1) == 0)
+         if (ds
+             && fstatat (chdir_fd, link_name, &st1, AT_SYMLINK_NOFOLLOW) == 0)
            for (; ds; ds = ds->next)
-             if (ds->dev == st1.st_dev
+             if (ds->change_dir == chdir_current
+                 && ds->dev == st1.st_dev
                  && ds->ino == st1.st_ino
-                 && timespec_cmp (ds->mtime, get_stat_mtime (&st1)) == 0)
+                 && (timespec_cmp (ds->birthtime, get_stat_birthtime (&st1))
+                     == 0))
                {
                  struct string_list *p =  xmalloc (offsetof (struct string_list, string)
                                                    + strlen (file_name) + 1);
@@ -930,16 +1315,21 @@ extract_link (char *file_name, int typeflag)
          return 0;
        }
       else if ((e == EEXIST && strcmp (link_name, file_name) == 0)
-              || (lstat (link_name, &st1) == 0
-                  && lstat (file_name, &st2) == 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 (maybe_recoverable (file_name, &interdir_made));
+  while ((rc = maybe_recoverable (file_name, false, &interdir_made))
+        == RECOVER_OK);
 
+  if (rc == RECOVER_SKIP)
+    return 0;
   if (!(incremental_option && errno == EEXIST))
     {
       link_error (link_name, file_name);
@@ -952,23 +1342,30 @@ static int
 extract_symlink (char *file_name, int typeflag)
 {
 #ifdef HAVE_SYMLINK
-  int status;
-  int interdir_made = 0;
+  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 ((status = symlink (current_stat_info.link_name, file_name)))
-    if (!maybe_recoverable (file_name, &interdir_made))
-      break;
+  while (symlinkat (current_stat_info.link_name, chdir_fd, file_name) != 0)
+    switch (maybe_recoverable (file_name, false, &interdir_made))
+      {
+      case RECOVER_OK:
+       continue;
 
-  if (status == 0)
-    set_stat (file_name, &current_stat_info, NULL, 0, 0, SYMTYPE);
-  else
-    symlink_error (current_stat_info.link_name, file_name);
-  return status;
+      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;
@@ -976,7 +1373,9 @@ extract_symlink (char *file_name, int typeflag)
   if (!warned_once)
     {
       warned_once = 1;
-      WARN ((0, 0, _("Attempting extraction of symbolic links as hard links")));
+      WARNOPT (WARN_SYMLINK_CAST,
+              (0, 0,
+               _("Attempting extraction of symbolic links as hard links")));
     }
   return extract_link (file_name, typeflag);
 #endif
@@ -986,20 +1385,29 @@ extract_symlink (char *file_name, int typeflag)
 static int
 extract_node (char *file_name, int typeflag)
 {
-  int status;
-  int interdir_made = 0;
+  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));
 
-  do
-    status = mknod (file_name, current_stat_info.stat.st_mode,
-                   current_stat_info.stat.st_rdev);
-  while (status && maybe_recoverable (file_name, &interdir_made));
+  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;
 
-  if (status != 0)
-    mknod_error (file_name);
-  else
-    set_stat (file_name, &current_stat_info, NULL, 0,
-             ARCHIVED_PERMSTATUS, typeflag);
-  return status;
+      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
 
@@ -1007,34 +1415,34 @@ extract_node (char *file_name, int typeflag)
 static int
 extract_fifo (char *file_name, int typeflag)
 {
-  int status;
-  int interdir_made = 0;
+  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 ((status = mkfifo (file_name, current_stat_info.stat.st_mode)))
-    if (!maybe_recoverable (file_name, &interdir_made))
-      break;
+  while (mkfifoat (chdir_fd, file_name, mode) != 0)
+    switch (maybe_recoverable (file_name, false, &interdir_made))
+      {
+      case RECOVER_OK:
+       continue;
 
-  if (status == 0)
-    set_stat (file_name, &current_stat_info, NULL, 0,
-             ARCHIVED_PERMSTATUS, typeflag);
-  else
-    mkfifo_error (file_name);
-  return status;
-}
-#endif
+      case RECOVER_SKIP:
+       return 0;
 
-static int
-extract_mangle_wrapper (char *file_name, int typeflag)
-{
-  extract_mangle ();
+      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)
 {
-  if (verbose_option)
-    fprintf (stdlis, _("Reading %s\n"), quote (current_stat_info.file_name));
   skip_member ();
   return 0;
 }
@@ -1045,6 +1453,13 @@ 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
@@ -1121,15 +1536,11 @@ prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun)
       *fun = extract_volhdr;
       break;
 
-    case GNUTYPE_NAMES:
-      *fun = extract_mangle_wrapper;
-      break;
-
     case GNUTYPE_MULTIVOL:
       ERROR ((0, 0,
              _("%s: Cannot extract -- file is continued from another volume"),
              quotearg_colon (current_stat_info.file_name)));
-      *fun = extract_failure;
+      *fun = extract_skip;
       break;
 
     case GNUTYPE_LONGNAME:
@@ -1139,9 +1550,10 @@ prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun)
       break;
 
     default:
-      WARN ((0, 0,
-            _("%s: Unknown file type `%c', extracted as normal file"),
-            quotearg_colon (file_name), typeflag));
+      WARNOPT (WARN_UNKNOWN_CAST,
+              (0, 0,
+               _("%s: Unknown file type '%c', extracted as normal file"),
+               quotearg_colon (file_name), typeflag));
       *fun = extract_file;
     }
 
@@ -1163,10 +1575,11 @@ prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun)
       break;
 
     case KEEP_NEWER_FILES:
-      if (file_newer_p (file_name, &current_stat_info))
+      if (file_newer_p (file_name, 0, &current_stat_info))
        {
-         WARN ((0, 0, _("Current %s is newer or same age"),
-                quote (file_name)));
+         WARNOPT (WARN_IGNORE_NEWER,
+                  (0, 0, _("Current %s is newer or same age"),
+                   quote (file_name)));
          return 0;
        }
       break;
@@ -1185,8 +1598,10 @@ extract_archive (void)
   char typeflag;
   tar_extractor_t fun;
 
+  fatal_exit_hook = extract_finish;
+
   set_next_block_after (current_header);
-  decode_header (current_header, &current_stat_info, &current_format, 1);
+
   if (!current_stat_info.file_name[0]
       || (interactive_option
          && !confirm ("extract", current_stat_info.file_name)))
@@ -1197,13 +1612,17 @@ extract_archive (void)
 
   /* Print the block from current_header and current_stat.  */
   if (verbose_option)
-    print_header (&current_stat_info, -1);
+    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)
-    apply_nonancestor_delayed_set_stat (current_stat_info.file_name, 0);
+    {
+      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.  */
 
@@ -1233,7 +1652,7 @@ extract_archive (void)
 
 }
 
-/* Extract the symbolic links whose final extraction were delayed.  */
+/* Extract the links whose final extraction were delayed.  */
 static void
 apply_delayed_links (void)
 {
@@ -1244,6 +1663,8 @@ apply_delayed_links (void)
       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;
@@ -1252,30 +1673,43 @@ apply_delayed_links (void)
          /* Make sure the placeholder file is still there.  If not,
             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
-             && timespec_cmp (get_stat_mtime (&st), ds->mtime) == 0)
+             && 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 (!ds->is_symlink)
                {
-                 if (link (ds->target, source) != 0)
+                 if (linkat (chdir_fd, ds->target, chdir_fd, source, 0) != 0)
                    link_error (ds->target, source);
                }
-             else if (symlink (ds->target, source) != 0)
+             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;
-                 set_stat (source, &st1, NULL, 0, 0, SYMTYPE);
+                 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;
                }
            }
@@ -1288,6 +1722,9 @@ apply_delayed_links (void)
          sources = next;
        }
 
+   xheader_xattr_free (ds->xattr_map, ds->xattr_map_size);
+   free (ds->cntx_name);
+
       {
        struct delayed_link *next = ds->next;
        free (ds);
@@ -1317,24 +1754,25 @@ extract_finish (void)
 bool
 rename_directory (char *src, char *dst)
 {
-  if (rename (src, dst))
+  if (renameat (chdir_fd, src, chdir_fd, dst) != 0)
     {
       int e = errno;
+      bool interdir_made;
 
       switch (e)
        {
        case ENOENT:
-         if (make_directories (dst))
+         if (make_directories (dst, &interdir_made) == 0)
            {
-             if (rename (src, dst) == 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;
        }
@@ -1346,18 +1784,3 @@ rename_directory (char *src, char *dst)
     }
   return true;
 }
-      
-void
-fatal_exit (void)
-{
-  extract_finish ();
-  error (TAREXIT_FAILURE, 0, _("Error is not recoverable: exiting now"));
-  abort ();
-}
-
-void
-xalloc_die (void)
-{
-  error (0, 0, "%s", _("memory exhausted"));
-  fatal_exit ();
-}
This page took 0.066712 seconds and 4 git commands to generate.