]> git.ipfire.org Git - thirdparty/tar.git/commitdiff
Cache parent directories
authorPaul Eggert <eggert@cs.ucla.edu>
Thu, 13 Nov 2025 01:33:11 +0000 (17:33 -0800)
committerPaul Eggert <eggert@cs.ucla.edu>
Sat, 15 Nov 2025 23:10:48 +0000 (15:10 -0800)
Although this might help (or hurt) performance, the main
motivation is to make it easier in future commits
to prevent tarballs from escaping the extraction directory.
* src/common.h: (BADFD): New constant.
(struct fdbase): New type.
* src/create.c (dump_file0): Use parent->fd instead of caching
it into a local, as the latter approach is now awkward.
* src/extract.c (extract_link): Don’t save errno unless needed.
* src/misc.c (safer_rmdir): New arg F.  All callers changed.
(maybe_backup_file): Construct full after_backup_name, now
that find_backup_file_name no longer does that for us.
(chdir_fd): Now static not extern, as other modules now use fdbase.
(fdbase_cache): New static var.
(fdbase_clear): New function.  Call it whenever removing
or renaming directories or symlinks to directories.
(fdbase_opendir): New static function.
(fdbase, fdbase1): New functions.  Call them whenever the
code formerly passed chdir_fd to a syscall.

src/common.h
src/compare.c
src/create.c
src/extract.c
src/misc.c
src/names.c
src/unlink.c
src/update.c
src/xattrs.c

index 6296eacd7b42929f66868c160446f1c6d6ed0e4b..92032d22fa07c38107e30fd4eb700a5ae93b2dc4 100644 (file)
@@ -752,11 +752,17 @@ int deref_stat (char const *name, struct stat *buf);
 idx_t blocking_read (int fd, void *buf, idx_t count);
 idx_t blocking_write (int fd, void const *buf, idx_t count);
 
+/* Not valid as the first argument to openat.
+   It is a negative integer not equal to AT_FDCWD.  */
+enum { BADFD = AT_FDCWD == -1 ? -2 : -1 };
+
 extern idx_t chdir_current;
-extern int chdir_fd;
 idx_t chdir_arg (char const *dir);
 void chdir_do (idx_t dir);
 struct chdir_id { int err; dev_t st_dev; ino_t st_ino; } chdir_id (void);
+struct fdbase { int fd; char const *base; } fdbase (char const *);
+struct fdbase fdbase1 (char const *);
+void fdbase_clear (void);
 idx_t chdir_count (void);
 
 void close_diag (char const *name);
index 71d51cc8636165582eab203ebd1561ba4847bf61..3135b92e3796d494cdb80fca3ed1003b14ae001e 100644 (file)
@@ -213,7 +213,9 @@ diff_file (void)
        }
       else
        {
-         diff_handle = openat (chdir_fd, file_name, open_read_flags);
+         struct fdbase f = fdbase (file_name);
+         diff_handle = (f.fd == BADFD ? -1
+                        : openat (f.fd, f.base, open_read_flags));
 
          if (diff_handle < 0)
            {
@@ -232,8 +234,7 @@ diff_file (void)
                  && stat_data.st_size != 0)
                {
                  struct timespec atime = get_stat_atime (&stat_data);
-                 if (set_file_atime (diff_handle, chdir_fd, file_name, atime)
-                     < 0)
+                 if (set_file_atime (diff_handle, f.fd, f.base, atime) < 0)
                    utime_error (file_name);
                }
 
@@ -265,9 +266,9 @@ diff_symlink (void)
   char buf[1024];
   idx_t len = strlen (current_stat_info.link_name);
   char *linkbuf = len < sizeof buf ? buf : xmalloc (len + 1);
-
-  ssize_t status = readlinkat (chdir_fd, current_stat_info.file_name,
-                              linkbuf, len + 1);
+  struct fdbase f = fdbase (current_stat_info.file_name);
+  ssize_t status = (f.fd == BADFD ? -1
+                   : readlinkat (f.fd, f.base, linkbuf, len + 1));
 
   if (status < 0)
     {
@@ -426,7 +427,8 @@ diff_multivol (void)
     }
 
 
-  int fd = openat (chdir_fd, current_stat_info.file_name, open_read_flags);
+  struct fdbase f = fdbase (current_stat_info.file_name);
+  int fd = f.fd == BADFD ? -1 : openat (f.fd, f.base, open_read_flags);
 
   if (fd < 0)
     {
index 274d760ab3061d870229fbf78a251eb4233dfaed..aa5acb63841a46eb186a9ef349858192a09cc699 100644 (file)
@@ -815,12 +815,15 @@ start_header (struct tar_stat_info *st)
        break;
 
       case COMMAND_MTIME:
-       if (!sys_exec_setmtime_script (set_mtime_command,
-                                      chdir_fd,
-                                      st->orig_file_name,
-                                      set_mtime_format,
-                                      &mtime))
-         mtime = st->mtime;
+       {
+         struct fdbase f = fdbase (st->orig_file_name);
+         if (f.fd == BADFD
+             || !sys_exec_setmtime_script (set_mtime_command,
+                                           f.fd, f.base,
+                                           set_mtime_format,
+                                           &mtime))
+           mtime = st->mtime;
+       }
        break;
       }
 
@@ -1338,8 +1341,10 @@ create_archive (void)
                    {
                      if (! st.orig_file_name)
                        {
-                         int fd = openat (chdir_fd, p->name,
-                                          open_searchdir_flags);
+                         struct fdbase f = fdbase (p->name);
+                         int fd = (f.fd == BADFD ? -1
+                                   : openat (f.fd, f.base,
+                                             open_searchdir_flags));
                          if (fd < 0)
                            {
                              file_removed_diag (p->name, !p->parent,
@@ -1539,9 +1544,18 @@ subfile_open (struct tar_stat_info const *dir, char const *file, int flags)
       gettext ("");
     }
 
-  while ((fd = openat (dir ? dir->fd : chdir_fd, file, flags)) < 0
-        && open_failure_recover (dir))
-    continue;
+  do
+    {
+      if (dir)
+       fd = openat (dir->fd, file, flags);
+      else
+       {
+         struct fdbase f = fdbase (file);
+         fd = f.fd == BADFD ? -1 : openat (f.fd, f.base, flags);
+       }
+    }
+  while (fd < 0 && open_failure_recover (dir));
+
   return fd;
 }
 
@@ -1569,8 +1583,9 @@ restore_parent_fd (struct tar_stat_info const *st)
 
       if (parentfd < 0)
        {
-         int origfd = openat (chdir_fd, parent->orig_file_name,
-                              open_searchdir_flags);
+         struct fdbase f = fdbase (parent->orig_file_name);
+         int origfd = (f.fd == BADFD ? -1
+                       : openat (f.fd, f.base, open_searchdir_flags));
          if (0 <= origfd)
            {
              if (fstat (parentfd, &parentstat) < 0
@@ -1605,7 +1620,6 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
   bool is_dir;
   struct tar_stat_info const *parent = st->parent;
   bool top_level = ! parent;
-  int parentfd = top_level ? chdir_fd : parent->fd;
   void (*diag) (char const *) = 0;
 
   if (interactive_option && !confirm ("add", p))
@@ -1618,12 +1632,15 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
   if (!transform_name (&st->file_name, XFORM_REGFILE))
     return NULL;
 
-  if (parentfd < 0 && ! top_level)
+  struct fdbase f = (top_level ? fdbase (name)
+                    : (struct fdbase) { .fd = parent->fd, .base = name });
+  if (!top_level && parent->fd < 0)
     {
-      errno = - parentfd;
+      errno = - parent->fd;
       diag = open_diag;
     }
-  else if (fstatat (parentfd, name, &st->stat, fstatat_flags) < 0)
+  else if (f.fd == BADFD
+          || fstatat (f.fd, f.base, &st->stat, fstatat_flags) < 0)
     diag = stat_diag;
   else if (file_dumpable_p (&st->stat))
     {
@@ -1695,9 +1712,9 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
       bool ok;
       struct stat st2;
 
-      xattrs_acls_get (parentfd, name, st, !is_dir);
-      xattrs_selinux_get (parentfd, name, st, fd);
-      xattrs_xattrs_get (parentfd, name, st, fd);
+      xattrs_acls_get (f.fd, f.base, st, !is_dir);
+      xattrs_selinux_get (f.fd, f.base, st, fd);
+      xattrs_xattrs_get (f.fd, f.base, st, fd);
 
       if (is_dir)
        {
@@ -1715,7 +1732,8 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
          ok = dump_dir (st);
 
          fd = st->fd;
-         parentfd = top_level ? chdir_fd : parent->fd;
+         f = (top_level ? fdbase (name)
+              : (struct fdbase) { .fd = parent->fd, .base = name });
        }
       else
        {
@@ -1756,9 +1774,9 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
            }
          else if (fd == 0)
            {
-             if (parentfd < 0 && ! top_level)
+             if (!top_level && parent->fd < 0)
                {
-                 errno = - parentfd;
+                 errno = - parent->fd;
                  ok = false;
                }
            }
@@ -1809,7 +1827,7 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
            }
          else if (atime_preserve_option == replace_atime_preserve
                   && timespec_cmp (st->atime, get_stat_atime (&st2)) != 0
-                  && set_file_atime (fd, parentfd, name, st->atime) < 0)
+                  && set_file_atime (fd, f.fd, f.base, st->atime) < 0)
            utime_error (p);
        }
 
@@ -1821,7 +1839,7 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
     }
   else if (S_ISLNK (st->stat.st_mode))
     {
-      st->link_name = areadlinkat_with_size (parentfd, name, st->stat.st_size);
+      st->link_name = areadlinkat_with_size (f.fd, f.base, st->stat.st_size);
       if (!st->link_name)
        {
          if (errno == ENOMEM)
@@ -1835,8 +1853,8 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
          < strlen (st->link_name))
        write_long_link (st);
 
-      xattrs_selinux_get (parentfd, name, st, 0);
-      xattrs_xattrs_get (parentfd, name, st, 0);
+      xattrs_selinux_get (f.fd, f.base, st, 0);
+      xattrs_xattrs_get (f.fd, f.base, st, 0);
 
       block_ordinal = current_block_ordinal ();
       st->stat.st_size = 0;    /* force 0 size on symlink */
@@ -1857,23 +1875,23 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
   else if (S_ISCHR (st->stat.st_mode))
     {
       type = CHRTYPE;
-      xattrs_acls_get (parentfd, name, st, true);
-      xattrs_selinux_get (parentfd, name, st, 0);
-      xattrs_xattrs_get (parentfd, name, st, 0);
+      xattrs_acls_get (f.fd, f.base, st, true);
+      xattrs_selinux_get (f.fd, f.base, st, 0);
+      xattrs_xattrs_get (f.fd, f.base, st, 0);
     }
   else if (S_ISBLK (st->stat.st_mode))
     {
       type = BLKTYPE;
-      xattrs_acls_get (parentfd, name, st, true);
-      xattrs_selinux_get (parentfd, name, st, 0);
-      xattrs_xattrs_get (parentfd, name, st, 0);
+      xattrs_acls_get (f.fd, f.base, st, true);
+      xattrs_selinux_get (f.fd, f.base, st, 0);
+      xattrs_xattrs_get (f.fd, f.base, st, 0);
     }
   else if (S_ISFIFO (st->stat.st_mode))
     {
       type = FIFOTYPE;
-      xattrs_acls_get (parentfd, name, st, true);
-      xattrs_selinux_get (parentfd, name, st, 0);
-      xattrs_xattrs_get (parentfd, name, st, 0);
+      xattrs_acls_get (f.fd, f.base, st, true);
+      xattrs_selinux_get (f.fd, f.base, st, 0);
+      xattrs_xattrs_get (f.fd, f.base, st, 0);
     }
   else if (S_ISSOCK (st->stat.st_mode))
     {
index 179a2d7b63856b71fddd10a112a22a16e2853d01..7b11abbd1ad672eaf840e097f20ff1633548a4a3 100644 (file)
@@ -266,7 +266,8 @@ fd_i_chmod (int fd, char const *file, mode_t mode, int atflag)
       if (result == 0 || implemented (errno))
        return result;
     }
-  return fchmodat (chdir_fd, file, mode, atflag);
+  struct fdbase f = fdbase (file);
+  return f.fd == BADFD ? -1 : fchmodat (f.fd, f.base, mode, atflag);
 }
 
 /* A version of fd_i_chmod which gracefully handles several common error
@@ -315,16 +316,18 @@ fd_chown (int fd, char const *file, uid_t uid, gid_t gid, int atflag)
       if (result == 0 || implemented (errno))
        return result;
     }
-  return fchownat (chdir_fd, file, uid, gid, atflag);
+  struct fdbase f = fdbase (file);
+  return f.fd == BADFD ? -1 : fchownat (f.fd, f.base, uid, gid, atflag);
 }
 
 /* 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));
+  if (0 <= fd)
+    return fstat (fd, st);
+  struct fdbase f = fdbase (file);
+  return f.fd == BADFD ? -1 : fstatat (f.fd, f.base, st, atflag);
 }
 
 /* Set the mode for FILE_NAME to MODE.
@@ -421,7 +424,15 @@ set_stat (char const *file_name,
        ts[0].tv_nsec = UTIME_OMIT;
       ts[1] = st->mtime;
 
-      if (fdutimensat (fd, chdir_fd, file_name, ts, atflag) == 0)
+      int r;
+      if (0 <= fd)
+       r = futimens (fd, ts);
+      if (fd < 0 || (r < 0 && errno == ENOSYS))
+       {
+         struct fdbase f = fdbase (file_name);
+         r = f.fd == BADFD ? -1 : utimensat (f.fd, f.base, ts, atflag);
+       }
+      if (r == 0)
        {
          if (incremental_option)
            check_time (file_name, ts[0]);
@@ -546,9 +557,9 @@ delay_set_stat (char const *file_name, struct tar_stat_info const *st,
       if (data->interdir)
        {
          struct stat real_st;
-         if (fstatat (chdir_fd, data->file_name,
-                      &real_st, data->atflag)
-             < 0)
+         struct fdbase f = fdbase (data->file_name);
+         if (f.fd == BADFD
+             || fstatat (f.fd, f.base, &real_st, data->atflag) < 0)
            {
              stat_error (data->file_name);
            }
@@ -660,7 +671,8 @@ repair_delayed_set_stat (char const *dir,
   for (data = delayed_set_stat_head; data; data = data->next)
     {
       struct stat st;
-      if (fstatat (chdir_fd, data->file_name, &st, data->atflag) < 0)
+      struct fdbase f = fdbase (data->file_name);
+      if (f.fd == BADFD || fstatat (f.fd, f.base, &st, data->atflag) < 0)
        {
          stat_error (data->file_name);
          return;
@@ -774,7 +786,8 @@ make_directories (char *file_name, bool *interdir_made)
       *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);
+      struct fdbase f = fdbase (file_name);
+      status = f.fd == BADFD ? -1 : mkdirat (f.fd, f.base, mode);
 
       if (status == 0)
        {
@@ -817,7 +830,8 @@ make_directories (char *file_name, bool *interdir_made)
      process may have created it, so check whether it exists now.  */
   *parent_end = '\0';
   struct stat st;
-  int stat_status = fstatat (chdir_fd, file_name, &st, 0);
+  struct fdbase f = fdbase (file_name);
+  int stat_status = f.fd == BADFD ? -1 : fstatat (f.fd, f.base, &st, 0);
   if (! (stat_status < 0 || S_ISDIR (st.st_mode)))
     stat_status = -1;
   if (stat_status < 0)
@@ -973,7 +987,8 @@ set_xattr (MAYBE_UNUSED char const *file_name,
 #ifdef HAVE_XATTRS
   if (xattrs_option && st->xattr_map.xm_size)
     {
-      int r = mknodat (chdir_fd, file_name, mode | S_IFREG, 0);
+      struct fdbase f = fdbase (file_name);
+      int r = f.fd == BADFD ? -1 : mknodat (f.fd, f.base, mode | S_IFREG, 0);
       if (r < 0)
        return r;
       xattrs_xattrs_set (st, file_name, typeflag, false);
@@ -1017,7 +1032,8 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links)
 
       if (check_for_renamed_directories)
        {
-         if (fstatat (chdir_fd, data->file_name, &st, data->atflag) < 0)
+         struct fdbase f = fdbase (data->file_name);
+         if (f.fd == BADFD || fstatat (f.fd, f.base, &st, data->atflag) < 0)
            {
              stat_error (data->file_name);
              skip_this_one = 1;
@@ -1066,8 +1082,9 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links)
 static bool
 is_directory_link (char const *file_name, struct stat *st)
 {
-  return (issymlinkat (chdir_fd, file_name)
-         && fstatat (chdir_fd, file_name, st, 0) == 0
+  struct fdbase f = fdbase (file_name);
+  return (f.fd != BADFD && issymlinkat (f.fd, f.base)
+         && fstatat (f.fd, f.base, st, 0) == 0
          && S_ISDIR (st->st_mode));
 }
 \f
@@ -1126,7 +1143,8 @@ extract_dir (char *file_name, char typeflag)
 
   for (;;)
     {
-      status = mkdirat (chdir_fd, file_name, mode);
+      struct fdbase f = fdbase (file_name);
+      status = f.fd == BADFD ? -1 : mkdirat (f.fd, f.base, mode);
       if (status == 0)
        {
          current_mode = mode & ~ current_umask;
@@ -1235,18 +1253,20 @@ open_output_file (char const *file_name, char typeflag, mode_t mode,
        }
     }
 
+  struct fdbase f = fdbase (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
-      && issymlinkat (chdir_fd, file_name))
+      && f.fd != BADFD && issymlinkat (f.fd, f.base))
     {
       errno = ELOOP;
       return -1;
     }
 
-  fd = openat (chdir_fd, file_name, openflag, mode);
+  fd = f.fd == BADFD ? -1 : openat (f.fd, f.base, openflag, mode);
   if (0 <= fd)
     {
       if (openflag & O_EXCL)
@@ -1410,7 +1430,8 @@ find_delayed_link_source (char const *name)
   if (!delayed_link_table)
     return false;
 
-  if (fstatat (chdir_fd, name, &st, AT_SYMLINK_NOFOLLOW) < 0)
+  struct fdbase f = fdbase (name);
+  if (f.fd == BADFD || fstatat (f.fd, f.base, &st, AT_SYMLINK_NOFOLLOW) < 0)
     {
       if (errno != ENOENT)
        stat_error (name);
@@ -1436,8 +1457,16 @@ 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)
+  for (;;)
     {
+      struct fdbase f = fdbase (file_name);
+      if (f.fd != BADFD)
+       {
+         fd = openat (f.fd, f.base, O_WRONLY | O_CREAT | O_EXCL, 0);
+         if (0 <= fd)
+           break;
+       }
+
       if (errno == EEXIST && find_delayed_link_source (file_name))
        {
          /* The placeholder file has already been created.  This means
@@ -1459,7 +1488,7 @@ create_placeholder_file (char *file_name, bool is_symlink, bool *interdir_made)
          open_error (file_name);
          return false;
        }
-      }
+    }
 
   if (fstat (fd, &st) < 0)
     {
@@ -1536,15 +1565,23 @@ extract_link (char *file_name, MAYBE_UNUSED char typeflag)
 
   do
     {
-      struct stat st1, st2;
-      int e;
-      int status = linkat (chdir_fd, link_name, chdir_fd, file_name, 0);
-      e = errno;
+      struct stat st, st1;
+      int status;
+
+      struct fdbase f = fdbase (file_name), f1;
+      if (f.fd == BADFD)
+       status = -1;
+      else
+       {
+         f1 = fdbase1 (link_name);
+         status = (f1.fd == BADFD ? -1
+                   : linkat (f1.fd, f1.base, f.fd, f.base, 0));
+       }
 
       if (status == 0)
        {
          if (delayed_link_table
-             && fstatat (chdir_fd, link_name, &st1, AT_SYMLINK_NOFOLLOW) == 0)
+             && fstatat (f1.fd, f1.base, &st1, AT_SYMLINK_NOFOLLOW) == 0)
            {
              struct delayed_link dl1;
              dl1.st_ino = st1.st_ino;
@@ -1564,14 +1601,14 @@ extract_link (char *file_name, MAYBE_UNUSED char typeflag)
 
          return true;
        }
-      else if ((e == EEXIST && streq (link_name, file_name))
-              || ((fstatat (chdir_fd, link_name, &st1, AT_SYMLINK_NOFOLLOW)
-                   == 0)
-                  && (fstatat (chdir_fd, file_name, &st2, AT_SYMLINK_NOFOLLOW)
-                      == 0)
-                  && psame_inode (&st1, &st2)))
-       return true;
 
+      int e = errno;
+      if ((e == EEXIST && streq (link_name, file_name))
+         || (f.fd != BADFD && f1.fd != BADFD
+             && fstatat (f1.fd, f1.base, &st1, AT_SYMLINK_NOFOLLOW) == 0
+             && fstatat (f.fd, f.base, &st, AT_SYMLINK_NOFOLLOW) == 0
+             && psame_inode (&st1, &st)))
+       return true;
       errno = e;
     }
   while ((rc = maybe_recoverable (file_name, false, &interdir_made))
@@ -1597,7 +1634,10 @@ extract_symlink (char *file_name, MAYBE_UNUSED char typeflag)
          || 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)
+  for (struct fdbase f;
+       ((f = fdbase (file_name)).fd == BADFD
+       || symlinkat (current_stat_info.link_name, f.fd, f.base) < 0);
+       )
     switch (maybe_recoverable (file_name, false, &interdir_made))
       {
       case RECOVER_OK:
@@ -1636,8 +1676,10 @@ extract_node (char *file_name, char typeflag)
   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)
+  for (struct fdbase f;
+       ((f = fdbase (file_name)).fd == BADFD
+       || mknodat (f.fd, f.base, mode, current_stat_info.stat.st_rdev) < 0);
+       )
     switch (maybe_recoverable (file_name, false, &interdir_made))
       {
       case RECOVER_OK:
@@ -1666,7 +1708,10 @@ extract_fifo (char *file_name, char typeflag)
   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)
+  for (struct fdbase f;
+       ((f = fdbase (file_name)).fd == BADFD
+       || mkfifoat (f.fd, f.base, mode) < 0);
+       )
     switch (maybe_recoverable (file_name, false, &interdir_made))
       {
       case RECOVER_OK:
@@ -1882,7 +1927,7 @@ static void
 apply_delayed_link (struct delayed_link *ds)
 {
   struct string_list *sources = ds->sources;
-  char const *valid_source = 0;
+  char const *valid_source = NULL;
 
   chdir_do (ds->change_dir);
 
@@ -1894,24 +1939,29 @@ apply_delayed_link (struct delayed_link *ds)
       /* 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 (fstatat (chdir_fd, source, &st, AT_SYMLINK_NOFOLLOW) == 0
+      struct fdbase f = fdbase (source);
+      if (f.fd != BADFD && fstatat (f.fd, f.base, &st, AT_SYMLINK_NOFOLLOW) == 0
          && SAME_INODE (st, *ds)
          && BIRTHTIME_EQ (get_stat_birthtime (&st), ds->birthtime))
        {
          /* Unlink the placeholder, then create a hard link if possible,
             a symbolic link otherwise.  */
-         if (unlinkat (chdir_fd, source, 0) < 0)
+         struct fdbase f1;
+         if (unlinkat (f.fd, f.base, 0) < 0)
            unlink_error (source);
          else if (valid_source
-                  && (linkat (chdir_fd, valid_source, chdir_fd, source, 0)
-                      == 0))
+                  && ((f1 = f.fd == BADFD ? f : fdbase1 (valid_source)).fd
+                      != BADFD)
+                  && linkat (f1.fd, f1.base, f.fd, f.base, 0) == 0)
            ;
          else if (!ds->is_symlink)
            {
-             if (linkat (chdir_fd, ds->target, chdir_fd, source, 0) < 0)
+             f1 = f.fd == BADFD ? f : fdbase1 (ds->target);
+             if (f1.fd == BADFD
+                 || linkat (f1.fd, f1.base, f.fd, f.base, 0) < 0)
                link_error (ds->target, source);
            }
-         else if (symlinkat (ds->target, chdir_fd, source) < 0)
+         else if (symlinkat (ds->target, f.fd, f.base) < 0)
            symlink_error (ds->target, source);
          else
            {
@@ -1996,9 +2046,14 @@ extract_finish (void)
 bool
 rename_directory (char *src, char *dst)
 {
-  if (renameat (chdir_fd, src, chdir_fd, dst) == 0)
-    fixup_delayed_set_stat (src, dst);
-  else
+  struct fdbase f1 = fdbase1 (src);
+  struct fdbase f = f1.fd == BADFD ? f1 : fdbase (dst);
+  if (f.fd != BADFD && renameat (f1.fd, f1.base, f.fd, f.base) == 0)
+    {
+      fdbase_clear ();
+      fixup_delayed_set_stat (src, dst);
+    }
+  else if (f1.fd != BADFD)
     {
       int e = errno;
 
@@ -2007,8 +2062,13 @@ rename_directory (char *src, char *dst)
        case ENOENT:
          if (make_directories (dst, NULL) == 0)
            {
-             if (renameat (chdir_fd, src, chdir_fd, dst) == 0)
-               return true;
+             f = fdbase (dst);
+             if (f.fd != BADFD
+                 && renameat (f1.fd, f1.base, f.fd, f.base) == 0)
+               {
+                 fdbase_clear ();
+                 return true;
+               }
              e = errno;
            }
          break;
index 779814128332ef56e6cdca7960486a920abb88bc..7939ce930e3882e8cef81ba98fcfda3d37eac581 100644 (file)
@@ -656,21 +656,25 @@ must_be_dot_or_slash (char const *file_name)
     }
 }
 
-/* Act like rmdir (FILE_NAME) relative to CHDIR_FD.
+/* Act like rmdir (FILENAME) relative to chdir_fd, i.e., like rmdir (F).
    However, reject attempts to remove a root directory
    even on systems that allow such a thing.
    Also, do not try to change the removed directory's status later.  */
 static int
-safer_rmdir (const char *file_name)
+safer_rmdir (const char *file_name, struct fdbase f)
 {
-  if (!file_name[slashlen (file_name)])
+  if (f.fd == BADFD)
+    return -1; /* Preserve errno.  */
+
+  if (IS_ABSOLUTE_FILE_NAME (f.base))
     {
-      errno = file_name[0] ? EBUSY : ENOENT;
+      errno = EBUSY;
       return -1;
     }
 
-  if (unlinkat (chdir_fd, file_name, AT_REMOVEDIR) == 0)
+  if (f.fd != BADFD && unlinkat (f.fd, f.base, AT_REMOVEDIR) == 0)
     {
+      fdbase_clear ();
       remove_delayed_set_stat (file_name);
       return 0;
     }
@@ -692,10 +696,15 @@ remove_any_file (const char *file_name, enum remove_option option)
      non-directory.  */
   bool try_unlink_first = cannot_unlink_dir ();
 
+  struct fdbase f = fdbase (file_name);
+
   if (try_unlink_first)
     {
-      if (unlinkat (chdir_fd, file_name, 0) == 0)
-       return 1;
+      if (f.fd != BADFD && unlinkat (f.fd, f.base, 0) == 0)
+       {
+         fdbase_clear ();
+         return 1;
+       }
 
       /* POSIX 1003.1-2001 requires EPERM when attempting to unlink a
         directory without appropriate privileges, but many Linux
@@ -704,13 +713,16 @@ remove_any_file (const char *file_name, enum remove_option option)
        return 0;
     }
 
-  if (safer_rmdir (file_name) == 0)
+  if (safer_rmdir (file_name, f) == 0)
     return 1;
 
   switch (errno)
     {
     case ENOTDIR:
-      return !try_unlink_first && unlinkat (chdir_fd, file_name, 0) == 0;
+      if (try_unlink_first || f.fd == BADFD || unlinkat (f.fd, f.base, 0) < 0)
+       return 0;
+      fdbase_clear ();
+      return 1;
 
     case 0:
     case EEXIST:
@@ -751,7 +763,7 @@ remove_any_file (const char *file_name, enum remove_option option)
              }
 
            free (directory);
-           return safer_rmdir (file_name) == 0;
+           return safer_rmdir (file_name, fdbase (file_name)) == 0;
          }
        }
       break;
@@ -801,13 +813,27 @@ maybe_backup_file (const char *file_name, bool this_is_the_archive)
       && (S_ISBLK (file_stat.st_mode) || S_ISCHR (file_stat.st_mode)))
     return true;
 
-  after_backup_name = find_backup_file_name (chdir_fd, file_name, backup_type);
+  struct fdbase f = fdbase (file_name);
+  if (f.fd == BADFD)
+    {
+      open_error (file_name);
+      return false;
+    }
+  idx_t subdirlen = f.base - file_name;
+  after_backup_name = find_backup_file_name (f.fd, f.base, backup_type);
   if (! after_backup_name)
     xalloc_die ();
-
-  if (renameat (chdir_fd, before_backup_name, chdir_fd, after_backup_name)
-      == 0)
+  idx_t after_backup_namelen = strlen (after_backup_name);
+  after_backup_name = xrealloc (after_backup_name,
+                               subdirlen + after_backup_namelen + 1);
+  memmove (after_backup_name + subdirlen, after_backup_name,
+          after_backup_namelen + 1);
+  memcpy (after_backup_name, file_name, subdirlen);
+
+  if (renameat (f.fd, f.base, f.fd, &after_backup_name[subdirlen]) == 0)
     {
+      if (S_ISLNK (file_stat.st_mode))
+       fdbase_clear ();
       if (verbose_option)
        fprintf (stdlis, _("Renaming %s to %s\n"),
                 quote_n (0, before_backup_name),
@@ -833,8 +859,11 @@ undo_last_backup (void)
 {
   if (after_backup_name)
     {
-      if (renameat (chdir_fd, after_backup_name, chdir_fd, before_backup_name)
-         < 0)
+      struct fdbase f = fdbase (before_backup_name);
+      if (f.fd == BADFD
+         || (renameat (f.fd, &after_backup_name[f.base - before_backup_name],
+                       f.fd, f.base)
+             < 0))
        {
          int e = errno;
          paxerror (e, _("%s: Cannot rename to %s"),
@@ -855,7 +884,8 @@ undo_last_backup (void)
 int
 deref_stat (char const *name, struct stat *buf)
 {
-  return fstatat (chdir_fd, name, buf, fstatat_flags);
+  struct fdbase f = fdbase (name);
+  return f.fd == BADFD ? -1 : fstatat (f.fd, f.base, buf, fstatat_flags);
 }
 
 /* Read from FD into the buffer BUF with COUNT bytes.  Attempt to fill
@@ -1020,7 +1050,7 @@ idx_t chdir_current;
    similar locations for fstatat, etc.  This is an open file
    descriptor, or AT_FDCWD if the working directory is current.  It is
    valid until the next invocation of chdir_do.  */
-int chdir_fd = AT_FDCWD;
+static int chdir_fd = AT_FDCWD;
 
 /* Change to directory I, in a virtual way.  This does not actually
    invoke chdir; it merely sets chdir_fd to an int suitable as the
@@ -1100,6 +1130,113 @@ chdir_id (void)
     }
   return curr->id;
 }
+
+/* Caches of recent calls to fdbase and fdbase1.  */
+static struct fdbase_cache
+{
+  /* Length of subdirectory name.  If zero, no subdir is cached here:
+     SUBDIR (if nonnull) is merely a buffer available for use later,
+     and CHDIR_CURRENT and FD are irrelevant.  */
+  idx_t subdirlen;
+
+  /* Index of ancestor of this subdirectory.  */
+  idx_t chdir_current;
+
+  /* Buffer containing name of subdirectory relative to the ancestor.  */
+  char *subdir;
+
+  /* Number of bytes allocated for SUBDIR.  */
+  idx_t subdiralloc;
+
+  /* FD of subdirectory.  */
+  int fd;
+} fdbase_cache[2];
+
+/* Clear the fdbase cache.  Call this after any action that might
+   invalidate the cache.  Such actions include removing or renaming
+   directories or symlinks to directories.  Call this if in doubt,
+   e.g., if it is not known whether a removed directory entry is a
+   symlink to a directory.  */
+void
+fdbase_clear (void)
+{
+  for (int i = 0; i < 2; i++)
+    {
+      struct fdbase_cache *c = &fdbase_cache[i];
+      if (c->subdirlen)
+       {
+         if (0 <= c->fd)
+           close (c->fd);
+         c->subdirlen = 0;
+       }
+    }
+}
+
+/* Return an fd open to FILE_NAME's parent directory,
+   along with the base name of FILE_NAME.
+   Use the alternate cache if ALTERNATE, the main cache otherwise.
+   If FILE_NAME is relative, it is relative to chdir_fd.
+   Return AT_FDCWD if FILE_NAME is relative to the working directory.
+   Return BADFD (setting errno) on failure.  */
+static struct fdbase
+fdbase_opendir (char const *file_name, bool alternate)
+{
+  char const *name = file_name;
+
+  /* Skip past leading "./"s,
+     but not past the last "./" if that ends the name.  */
+  idx_t dslen = dotslashlen (name);
+  if (dslen)
+    {
+      name += dslen;
+      if (!*name)
+       for (name--; *--name != '.'; )
+         continue;
+    }
+
+  /* For files immediately under CHDIR_FD, and for root directories,
+     just use CHDIR_FD and NAME.  */
+  char const *base = last_component (name);
+  idx_t subdirlen = base - name;
+  if (!subdirlen | !*base)
+    return (struct fdbase) { .fd = chdir_fd, .base = name };
+
+  struct fdbase_cache *c = &fdbase_cache[alternate];
+
+  if (! (c->chdir_current == chdir_current
+        && c->subdirlen == subdirlen
+        && memeq (c->subdir, name, subdirlen)))
+    {
+      if (c->subdirlen && 0 <= c->fd)
+       close (c->fd);
+
+      c->chdir_current = chdir_current;
+      if (c->subdiralloc <= subdirlen)
+       c->subdir = xpalloc (c->subdir, &c->subdiralloc,
+                            subdirlen - c->subdiralloc + 1, -1, 1);
+      char *p = mempcpy (c->subdir, name, subdirlen);
+      *p = '\0';
+      c->fd = openat (chdir_fd, c->subdir, open_searchdir_flags);
+      c->subdirlen = c->fd < 0 ? 0 : subdirlen;
+      if (BADFD != -1 && c->fd < 0)
+       c->fd = BADFD;
+    }
+
+  return (struct fdbase) { .fd = c->fd, .base = base };
+}
+
+struct fdbase
+fdbase (char const *name)
+{
+  return fdbase_opendir (name, false);
+}
+
+struct fdbase
+fdbase1 (char const *name)
+{
+  return fdbase_opendir (name, true);
+}
+
 \f
 const char *
 tar_dirname (void)
@@ -1353,7 +1490,9 @@ tar_savedir (const char *name, bool must_exist)
 {
   char *ret = NULL;
   DIR *dir = NULL;
-  int fd = openat (chdir_fd, name, open_read_flags | O_DIRECTORY);
+  struct fdbase f = fdbase (name);
+  int fd = (f.fd == BADFD ? -1
+           : openat (f.fd, f.base, open_read_flags | O_DIRECTORY));
   if (fd < 0)
     {
       if (!must_exist && errno == ENOENT)
index c29d3aa34a9baaadd25fc1e2561f733566305ac6..4d8b6ee7216735a3d335a93fd75d3440e1a8b55b 100644 (file)
@@ -1799,8 +1799,9 @@ collect_and_sort_names (void)
        }
       if (S_ISDIR (st.stat.st_mode))
        {
-         int dir_fd = openat (chdir_fd, name->name,
-                              open_read_flags | O_DIRECTORY);
+         struct fdbase f = fdbase (name->name);
+         int dir_fd = (f.fd == BADFD ? -1
+                       : openat (f.fd, f.base, open_read_flags | O_DIRECTORY));
          if (dir_fd < 0)
            open_diag (name->name);
          else
index a1f754a4885e2f26474e0d0ec3fe49f730f279a9..d042d2ed34c500a8b24f6a8a03b5aa6b4085121d 100644 (file)
@@ -106,7 +106,10 @@ flush_deferred_unlinks (bool force)
              else
                fname = p->file_name;
 
-             if (unlinkat (chdir_fd, fname, AT_REMOVEDIR) < 0)
+             struct fdbase f = fdbase (fname);
+             if (f.fd != BADFD && unlinkat (f.fd, f.base, AT_REMOVEDIR) == 0)
+               fdbase_clear ();
+             else
                {
                  switch (errno)
                    {
@@ -132,7 +135,10 @@ flush_deferred_unlinks (bool force)
            }
          else
            {
-             if (unlinkat (chdir_fd, p->file_name, 0) < 0 && errno != ENOENT)
+             struct fdbase f = fdbase (p->file_name);
+             if (f.fd != BADFD && unlinkat (f.fd, f.base, 0) == 0)
+               fdbase_clear ();
+             else if (errno != ENOENT)
                unlink_error (p->file_name);
            }
          dunlink_reclaim (p);
@@ -166,11 +172,12 @@ flush_deferred_unlinks (bool force)
          else
            fname = p->file_name;
 
-         if (unlinkat (chdir_fd, fname, AT_REMOVEDIR) < 0)
-           {
-             if (errno != ENOENT)
-               rmdir_error (fname);
-           }
+         struct fdbase f = fdbase (fname);
+         if (f.fd != BADFD && unlinkat (f.fd, f.base, AT_REMOVEDIR) == 0)
+           fdbase_clear ();
+         else if (errno != ENOENT)
+           rmdir_error (fname);
+
          dunlink_reclaim (p);
          p = next;
        }
index b693b838dd65926c6986900331209ab54790325e..fbae4f550f1079ed8ad4ca05fc5f5368dd7fe67c 100644 (file)
@@ -45,7 +45,8 @@ static bool acting_as_filter;
 static void
 append_file (char *file_name)
 {
-  int handle = openat (chdir_fd, file_name, O_RDONLY | O_BINARY);
+  struct fdbase f = fdbase (file_name);
+  int handle = f.fd == BADFD ? -1 : openat (f.fd, f.base, O_RDONLY | O_BINARY);
 
   if (handle < 0)
     {
index 399eb7fbbd0c90b941ae7029c05d1badf4be50de..26e23ef7f1554d855ed3786e2f987ad491d3accd 100644 (file)
@@ -293,7 +293,8 @@ xattrs__acls_set (struct tar_stat_info const *st,
       /* No "default" IEEE 1003.1e ACL set for directory.  At this moment,
          FILE_NAME may already have inherited default acls from parent
          directory;  clean them up. */
-      if (acl_delete_def_file_at (chdir_fd, file_name) < 0)
+      struct fdbase f1 = fdbase (file_name);
+      if (f1.fd == BADFD || acl_delete_def_file_at (f1.fd, f1.base) < 0)
        warnopt (WARN_XATTR_WRITE, errno,
                  _("acl_delete_def_file_at: Cannot drop default POSIX ACLs "
                    "for file '%s'"),
@@ -309,7 +310,8 @@ xattrs__acls_set (struct tar_stat_info const *st,
       return;
     }
 
-  if (acl_set_file_at (chdir_fd, file_name, type, acl) < 0)
+  struct fdbase f = fdbase (file_name);
+  if (f.fd == BADFD || acl_set_file_at (f.fd, f.base, type, acl) < 0)
     /* warn even if filesystem does not support acls */
     warnopt (WARN_XATTR_WRITE, errno,
             _ ("acl_set_file_at: Cannot set POSIX ACLs for file '%s'"),
@@ -599,13 +601,16 @@ xattrs__fd_set (char const *file_name, char typeflag,
     {
       const char *sysname = "setxattrat";
       int ret;
+      struct fdbase f = fdbase (file_name);
 
-      if (typeflag != SYMTYPE)
-        ret = setxattrat (chdir_fd, file_name, attr, ptr, len, 0);
+      if (f.fd == BADFD)
+       ret = -1;
+      else if (typeflag != SYMTYPE)
+       ret = setxattrat (f.fd, f.base, attr, ptr, len, 0);
       else
         {
           sysname = "lsetxattr";
-          ret = lsetxattrat (chdir_fd, file_name, attr, ptr, len, 0);
+         ret = lsetxattrat (f.fd, f.base, attr, ptr, len, 0);
         }
 
       if (ret < 0)
@@ -662,14 +667,17 @@ xattrs_selinux_set (MAYBE_UNUSED struct tar_stat_info const *st,
       if (!st->cntx_name)
         return;
 
-      if (typeflag != SYMTYPE)
+      struct fdbase f = fdbase (file_name);
+      if (f.fd == BADFD)
+       ret = -1;
+      else if (typeflag != SYMTYPE)
         {
-          ret = setfileconat (chdir_fd, file_name, st->cntx_name);
+         ret = setfileconat (f.fd, f.base, st->cntx_name);
           sysname = "setfileconat";
         }
       else
         {
-          ret = lsetfileconat (chdir_fd, file_name, st->cntx_name);
+         ret = lsetfileconat (f.fd, f.base, st->cntx_name);
           sysname = "lsetfileconat";
         }