]> git.ipfire.org Git - thirdparty/tar.git/commitdiff
tar: reuse FD when extracting hard links master
authorPaul Eggert <eggert@cs.ucla.edu>
Thu, 30 Jul 2026 06:10:27 +0000 (23:10 -0700)
committerPaul Eggert <eggert@cs.ucla.edu>
Thu, 30 Jul 2026 06:12:46 +0000 (23:12 -0700)
* src/extract.c (rename_directory): Redo to accommodate
new semantics of fdbase1, and to use fds more intelligently.
* src/misc.c (fdbase_opendir): If ALTERNATE, it is now OK to use,
though not update, the main cache.  This makes it possible for the
caller to do linkat (N, "A", N, "B") instead of the
linkat (N, "A", N + 1, "B") it previously did, and this
saves some open and close calls on directories.

src/extract.c
src/misc.c

index 5febf6de6faf60ca0f0ce4fb8f4e399d8ed01112..2c9cfca2bca949d84384c0d0ee7ab335f8e3da71 100644 (file)
@@ -2147,14 +2147,14 @@ extract_finish (void)
 bool
 rename_directory (char *src, char *dst)
 {
 bool
 rename_directory (char *src, char *dst)
 {
-  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)
+  struct fdbase f = fdbase (src);
+  struct fdbase f1 = f.fd == BADFD ? f : fdbase1 (dst);
+  if (f1.fd != BADFD && renameat (f.fd, f.base, f1.fd, f1.base) == 0)
     {
       fdbase_clear ();
       fixup_delayed_set_stat (src, dst);
     }
     {
       fdbase_clear ();
       fixup_delayed_set_stat (src, dst);
     }
-  else if (f1.fd != BADFD)
+  else if (f.fd != BADFD)
     {
       int e = errno;
 
     {
       int e = errno;
 
@@ -2163,8 +2163,11 @@ rename_directory (char *src, char *dst)
        case ENOENT:
          if (0 <= make_directories (dst, MAKEDIR_PARENT))
            {
        case ENOENT:
          if (0 <= make_directories (dst, MAKEDIR_PARENT))
            {
+             /* make_directories likely cached DST and evicted SRC,
+                so try to reuse the cached DST and then reget SRC.  */
              f = fdbase (dst);
              f = fdbase (dst);
-             if (f.fd != BADFD
+             f1 = f.fd == BADFD ? f : fdbase1 (src);
+             if (f1.fd != BADFD
                  && renameat (f1.fd, f1.base, f.fd, f.base) == 0)
                {
                  fdbase_clear ();
                  && renameat (f1.fd, f1.base, f.fd, f.base) == 0)
                {
                  fdbase_clear ();
index e4d5cbf61c80a80f04925b0a60ebff16d1ae69bf..ad9d8335cc46905a7c619b03a7960ccaad0f2b12 100644 (file)
@@ -1278,7 +1278,9 @@ open_subdir (int fd, char const *subdir, int oflags)
 
 /* Return an fd open to a directory related to FILE_NAME
    along with the corresponding base name.
 
 /* Return an fd open to a directory related to FILE_NAME
    along with the corresponding base name.
-   Use the alternate cache if ALTERNATE, the main cache otherwise.
+   If ALTERNATE, use either the main or the alternate cache but update
+   only the alternate cache; otherwise, use and update only the main cache;
+   this means a call with ALTERNATE cannot invalidate a call without.
    If FILE_NAME is relative, it is relative to chdir_fd.
    If CHILD_OFLAGS, open FILE_NAME itself, posssibly letting it escape from
    chdir_fd; otherwise, open FILE_NAME's parent but do not let it escape.
    If FILE_NAME is relative, it is relative to chdir_fd.
    If CHILD_OFLAGS, open FILE_NAME itself, posssibly letting it escape from
    chdir_fd; otherwise, open FILE_NAME's parent but do not let it escape.
@@ -1321,63 +1323,74 @@ fdbase_opendir (char const *file_name, bool alternate, int child_oflags)
       return (struct fdbase) { .fd = BADFD, .base = name };
     }
 
       return (struct fdbase) { .fd = BADFD, .base = name };
     }
 
-  struct fdbase_cache *c = &fdbase_cache[alternate];
-  int fd = c->fd;
-  bool submatch = (0 < c->subdirlen && c->subdirlen <= subdirlen
-                  && c->chdir_current == chdir_current
-                  && !ISSLASH (name[c->subdirlen])
-                  && memeq (c->subdir, name, c->subdirlen));
+  int fd;
 
 
-  if (! (submatch && c->subdirlen == subdirlen))
+  /* Try to reuse fdbase_cache[0] or (if ALTERNATE) fdbase_cache[1].  */
+  for (struct fdbase_cache *c = fdbase_cache; ; c++)
     {
     {
-      /* Copy the new directory's name into the cache.  */
-      char *subdir = c->subdir;
-      if (c->subdiralloc <= subdirlen)
-       c->subdir = subdir = xpalloc (subdir, &c->subdiralloc,
-                                     subdirlen - c->subdiralloc + 1, -1, 1);
-      char *p = mempcpy (subdir, name, subdirlen);
-      *p = '\0';
-
-      if (submatch && c->subdirlen < subdirlen
-         && !ISSLASH (subdir[c->subdirlen]))
+      fd = c->fd;
+      bool submatch = (0 < c->subdirlen && c->subdirlen <= subdirlen
+                      && c->chdir_current == chdir_current
+                      && !ISSLASH (name[c->subdirlen])
+                      && memeq (c->subdir, name, c->subdirlen));
+      if (submatch && c->subdirlen == subdirlen)
+       break;
+      if (c == fdbase_cache + alternate)
        {
        {
-         /* The new directory is a subdirectory of the old,
-            so open relative to FD rather than to chdir_fd.  */
-         int subfd = open_subdir (fd, &subdir[c->subdirlen], child_oflags);
-         if (subfd < 0)
+         /* Cannot reuse, so evict and reget fdbase_cache[ALTERNATE].
+            Start by copying the new directory's name into the cache.  */
+         char *subdir = c->subdir;
+         if (c->subdiralloc <= subdirlen)
+           c->subdir = subdir = xpalloc (subdir, &c->subdiralloc,
+                                         subdirlen - c->subdiralloc + 1,
+                                         -1, 1);
+         char *p = mempcpy (subdir, name, subdirlen);
+         *p = '\0';
+
+         if (submatch && c->subdirlen < subdirlen
+             && !ISSLASH (subdir[c->subdirlen]))
            {
            {
-             /* Keep the old directory cached and report open failure,
-                unless EMFILE/ENFILE means it's possible that falling
-                through to close the old directory would mean we
-                could successfully retry from the chdir_fd level.
-                When reporting failure, there is no need to
-                null-terminate the old directory, since the code does
-                not assume null termination.  */
-             if (errno != EMFILE && errno != ENFILE)
-               return (struct fdbase) { .fd = BADFD, .base = base };
+             /* The new directory is a subdirectory of the old,
+                so open relative to FD rather than to chdir_fd.  */
+             int subfd = open_subdir (fd, &subdir[c->subdirlen],
+                                      child_oflags);
+             if (subfd < 0)
+               {
+                 /* Keep the old directory cached and report open failure,
+                    unless EMFILE/ENFILE means it's possible that falling
+                    through to close the old directory would mean we
+                    could successfully retry from the chdir_fd level.
+                    When reporting failure, there is no need to
+                    null-terminate the old directory, since the code does
+                    not assume null termination.  */
+                 if (errno != EMFILE && errno != ENFILE)
+                   return (struct fdbase) { .fd = BADFD, .base = base };
+               }
+             else
+               {
+                 /* Replace the old directory with the new one.  */
+                 if (!chdirable (fd))
+                   close (fd);
+                 c->fd = subfd;
+                 c->subdirlen = subdirlen;
+                 return (struct fdbase) { .fd = subfd, .base = base };
+               }
            }
            }
+
+         int newfd = open_subdir (chdir_fd, c->subdir, child_oflags);
+         if (newfd < 0)
+           fd = BADFD == -1 ? newfd : BADFD;
          else
            {
          else
            {
-             /* Replace the old directory with the new one.  */
-             if (!chdirable (fd))
+             /* Remove any old directory info, and add new info.  */
+             if (0 < c->subdirlen && !chdirable (fd))
                close (fd);
                close (fd);
-             c->fd = subfd;
+             c->chdir_current = chdir_current;
+             c->fd = fd = newfd;
              c->subdirlen = subdirlen;
              c->subdirlen = subdirlen;
-             return (struct fdbase) { .fd = subfd, .base = base };
            }
            }
-       }
 
 
-      int newfd = open_subdir (chdir_fd, c->subdir, child_oflags);
-      if (newfd < 0)
-       fd = BADFD == -1 ? newfd : BADFD;
-      else
-       {
-         /* Remove any old directory info, and add new info.  */
-         if (0 < c->subdirlen && !chdirable (fd))
-           close (fd);
-         c->chdir_current = chdir_current;
-         c->fd = fd = newfd;
-         c->subdirlen = subdirlen;
+         break;
        }
     }
 
        }
     }