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.
This commit is contained in:
Paul Eggert
2026-07-29 23:12:46 -07:00
parent e54e505a92
commit 9371d36cc7
2 changed files with 68 additions and 52 deletions
+8 -5
View File
@@ -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 ();
+60 -47
View 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.
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;
}
}