tar: open directories more efficiently

Without this patch, tar would open the parent of a directory
for search, then open the directory itself for search.
It's more efficient to just open the directory for search.
* src/create.c (create_archive, restore_parent_fd, chdir_do):
Use open_searchdir instead of opening by hand.
* src/misc.c (chdir_do): Use fdbase_opendir and fdbase_close
instead of openat and close, so that the fd is cached properly.
(chdirable): New function.
(fdbase_clear): Use it to avoid closing chdirable FDs.
(fdbase_close): New function.
(open_subdir): New arg OFLAGS.  All callers changed.
(fdbase_opendir): New arg CHILD_OFLAGS.  All callers changed.
Prefer AT_FDCWD for absolute file names, so that we need not
worry about chdir_fd == BADFD in that case.
Do not allow empty file names.
When replacing an old directory, do not close its fd if it
is chdirable.
(open_searchdir): New function.
* src/tar.c (decode_options): Do not worry about one_top_level_dir
and IS_ABSOLUTE_FILE_NAME (one_top_level_dir), as fdbase_opendir
now prefers AT_FDCWD for absolute file names.
(tar_stat_close): Use fdbase_close instead of close,
so that the fd is cached properly.
This commit is contained in:
Paul Eggert
2026-07-29 23:12:46 -07:00
parent 941f62b24d
commit e54e505a92
4 changed files with 87 additions and 54 deletions
+2
View File
@@ -787,6 +787,8 @@ void chdir_do (idx_t dir, bool create);
struct chdir_id { int err; dev_t st_dev; ino_t st_ino; } chdir_id (void);
struct fdbase fdbase (char const *);
struct fdbase fdbase1 (char const *);
int open_searchdir (char const *);
int fdbase_close (int);
void fdbase_clear (void);
idx_t chdir_count (void);
+2 -7
View File
@@ -1342,10 +1342,7 @@ create_archive (void)
{
if (! st.orig_file_name)
{
struct fdbase f = fdbase (p->name);
int fd = (f.fd == BADFD ? -1
: openat (f.fd, f.base,
open_searchdir_how.flags));
int fd = open_searchdir (p->name);
if (fd < 0)
{
file_removed_diag (p->name, !p->parent,
@@ -1584,9 +1581,7 @@ restore_parent_fd (struct tar_stat_info const *st)
if (parentfd < 0)
{
struct fdbase f = fdbase (parent->orig_file_name);
int origfd = (f.fd == BADFD ? -1
: openat (f.fd, f.base, open_searchdir_how.flags));
int origfd = open_searchdir (parent->orig_file_name);
if (0 <= origfd)
{
if (fstat (origfd, &parentstat) < 0
+82 -45
View File
@@ -32,6 +32,7 @@
static void namebuf_add_dir (namebuf_t, char const *);
static char *namebuf_finish (namebuf_t);
static const char *tar_getcdpath (idx_t);
static struct fdbase fdbase_opendir (char const *, bool, int);
char const *
quote_n_colon (int n, char const *arg)
@@ -1104,17 +1105,11 @@ chdir_do (idx_t i, bool create)
{
if (! fd || create)
{
int dfd;
if (IS_ABSOLUTE_FILE_NAME (curr->name))
dfd = AT_FDCWD;
else
{
chdir_do ((i - 1) & ~+one_top_level, false);
dfd = chdir_fd;
}
if (! IS_ABSOLUTE_FILE_NAME (curr->name))
chdir_do ((i - 1) & ~+one_top_level, false);
fd = openat (dfd, curr->name,
open_searchdir_how.flags & ~O_NOFOLLOW);
int oflags = open_searchdir_how.flags & ~O_NOFOLLOW;
fd = fdbase_opendir (curr->name, false, oflags).fd;
if (fd < 0)
{
if (errno == ENOENT)
@@ -1124,8 +1119,7 @@ chdir_do (idx_t i, bool create)
if (!create_dir (curr->name))
fatal_exit ();
/* Directory likely exists now; retry. */
fd = openat (dfd, curr->name,
open_searchdir_how.flags & ~O_NOFOLLOW);
fd = fdbase_opendir (curr->name, false, oflags).fd;
}
else if (i & one_top_level)
{
@@ -1150,7 +1144,7 @@ chdir_do (idx_t i, bool create)
else
{
struct wd *stale = &wd[wdcache[CHDIR_CACHE_SIZE - 1]];
if (close (stale->fd) < 0)
if (fdbase_close (stale->fd) < 0)
close_diag (stale->name);
stale->fd = 0;
wdcache[CHDIR_CACHE_SIZE - 1] = i;
@@ -1219,6 +1213,22 @@ static struct fdbase_cache
int fd;
} fdbase_cache[2];
/* Return true if positive FD is for a directory searched because of a
-C or a --one-top-dir option. */
static bool
chdirable (int fd)
{
/* Optimize for most common case. */
if (fd == chdir_fd)
return true;
for (idx_t i = 0; i < wdcache_count; i++)
if (fd == wd[wdcache[i]].fd)
return true;
return false;
}
/* 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,
@@ -1232,41 +1242,61 @@ fdbase_clear (void)
struct fdbase_cache *c = &fdbase_cache[i];
if (c->subdirlen)
{
if (0 <= c->fd)
if (0 <= c->fd && !chdirable (c->fd))
close (c->fd);
c->subdirlen = 0;
}
}
}
/* Starting from the directory FD, open a subdirectory SUBDIR for search.
If extracting or diffing and --absolute-names (-P) is not in effect,
do not let the subdirectory escape FD, i.e., the subdirectory must
be at or under FD in the directory hierarchy. */
static int
open_subdir (int fd, char const *subdir)
/* Close the file descriptor FD,
and remove from the fdbase cache any entry corresponding to FD. */
int
fdbase_close (int fd)
{
return openat2 (fd, subdir, &open_searchdir_how, sizeof open_searchdir_how);
for (int i = 0; i < 2; i++)
{
struct fdbase_cache *c = &fdbase_cache[i];
if (c->subdirlen && c->fd == fd)
c->subdirlen = 0;
}
return close (fd);
}
/* Return an fd open to FILE_NAME's parent directory,
along with the base name of FILE_NAME.
/* Starting from the directory FD, open a subdirectory SUBDIR for search.
If OFLAGS, open with OFLAGS. Otherwise, open_searchdir_how
determines whether SUBDIR can escape FD, i.e., whether it must
be at or under FD in the directory hierarchy. */
static int
open_subdir (int fd, char const *subdir, int oflags)
{
return
(oflags
? openat (fd, subdir, oflags)
: openat2 (fd, subdir, &open_searchdir_how, sizeof open_searchdir_how));
}
/* 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 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.
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)
fdbase_opendir (char const *file_name, bool alternate, int child_oflags)
{
char const *name = file_name;
int dfd = IS_ABSOLUTE_FILE_NAME (file_name) ? AT_FDCWD : chdir_fd;
if (chdir_fd == BADFD && ! IS_ABSOLUTE_FILE_NAME (file_name))
if (dfd == BADFD)
{
/* BADFD is a sentinel value meaning that the chdir directory
needs to be created lazily, therefore if we encounter it, the
directory does not exist yet. */
errno = ENOENT;
return (struct fdbase) { .fd = chdir_fd, .base = name };
return (struct fdbase) { .fd = BADFD, .base = name };
}
/* Skip past leading "./"s,
but not past the last "./" if that ends the name. */
@@ -1280,11 +1310,16 @@ fdbase_opendir (char const *file_name, bool alternate)
}
/* For files immediately under CHDIR_FD, and for root directories,
just use CHDIR_FD and NAME. */
just use CHDIR_FD and NAME. Empty NAME is invalid, though. */
char const *base = last_component (name);
idx_t subdirlen = base - name;
idx_t subdirlen = base + (child_oflags ? strlen (base) : 0) - name;
if (!subdirlen | !*base)
return (struct fdbase) { .fd = chdir_fd, .base = name };
{
if (*name)
return (struct fdbase) { .fd = dfd, .base = name };
errno = EINVAL;
return (struct fdbase) { .fd = BADFD, .base = name };
}
struct fdbase_cache *c = &fdbase_cache[alternate];
int fd = c->fd;
@@ -1308,7 +1343,7 @@ fdbase_opendir (char const *file_name, bool 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]);
int subfd = open_subdir (fd, &subdir[c->subdirlen], child_oflags);
if (subfd < 0)
{
/* Keep the old directory cached and report open failure,
@@ -1324,28 +1359,24 @@ fdbase_opendir (char const *file_name, bool alternate)
else
{
/* Replace the old directory with the new one. */
close (fd);
if (!chdirable (fd))
close (fd);
c->fd = subfd;
c->subdirlen = subdirlen;
return (struct fdbase) { .fd = subfd, .base = base };
}
}
/* Remove any old directory info,
and add new info if the new directory can be opened. */
if (0 < c->subdirlen)
close (fd);
fd = open_subdir (chdir_fd, c->subdir);
if (fd < 0)
{
if (BADFD != -1 && fd < 0)
fd = BADFD;
c->subdirlen = 0;
}
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;
c->fd = fd = newfd;
c->subdirlen = subdirlen;
}
}
@@ -1356,13 +1387,19 @@ fdbase_opendir (char const *file_name, bool alternate)
struct fdbase
fdbase (char const *name)
{
return fdbase_opendir (name, false);
return fdbase_opendir (name, false, 0);
}
struct fdbase
fdbase1 (char const *name)
{
return fdbase_opendir (name, true);
return fdbase_opendir (name, true, 0);
}
int
open_searchdir (char const *name)
{
return fdbase_opendir (name, false, open_searchdir_how.flags).fd;
}
+1 -2
View File
@@ -2716,7 +2716,6 @@ decode_options (int argc, char **argv)
open_searchdir_how.flags = (search_flags | nofollow_flag
| O_BINARY | O_CLOEXEC | O_DIRECTORY);
if (!absolute_names_option && !dereference_option
&& ! (one_top_level_dir && IS_ABSOLUTE_FILE_NAME (one_top_level_dir))
&& (subcommand_option == EXTRACT_SUBCOMMAND
|| subcommand_option == DIFF_SUBCOMMAND))
open_searchdir_how.resolve = RESOLVE_BENEATH;
@@ -2965,7 +2964,7 @@ bool
tar_stat_close (struct tar_stat_info *st)
{
int status = (st->dirstream ? closedir (st->dirstream)
: 0 < st->fd ? close (st->fd)
: 0 < st->fd ? fdbase_close (st->fd)
: 0);
st->dirstream = NULL;
st->fd = 0;