From 9371d36cc707a099e6867199f18bbaa7795b6150 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Wed, 29 Jul 2026 23:10:27 -0700 Subject: [PATCH] tar: reuse FD when extracting hard links * 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 | 13 +++--- src/misc.c | 107 ++++++++++++++++++++++++++++---------------------- 2 files changed, 68 insertions(+), 52 deletions(-) diff --git a/src/extract.c b/src/extract.c index 5febf6de..2c9cfca2 100644 --- a/src/extract.c +++ b/src/extract.c @@ -2147,14 +2147,14 @@ extract_finish (void) 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); } - else if (f1.fd != BADFD) + else if (f.fd != BADFD) { int e = errno; @@ -2163,8 +2163,11 @@ rename_directory (char *src, char *dst) 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); - 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 (); diff --git a/src/misc.c b/src/misc.c index e4d5cbf6..ad9d8335 100644 --- a/src/misc.c +++ b/src/misc.c @@ -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. - 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. @@ -1321,63 +1323,74 @@ fdbase_opendir (char const *file_name, bool alternate, int child_oflags) 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 { - /* 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); - c->fd = subfd; + c->chdir_current = chdir_current; + c->fd = fd = newfd; 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; } } -- 2.47.3