tar: refactor fdbase_opendir

* src/misc.c (fdbase_opendir): Rename locals to more clearly
distinguish new subdirectory name from old.
Cache c->subdir and c->subdirlen to help the compiler warn
us statically if we mess up.
Move the large part of an if that ends in a break to be
out of the loop, to make things a bit clearer and reduce indentation.
Distinguish whether the new name is a subdirectory of the old,
from whether the new name extends the old; they are not the same
thing as the new and old names can have different chdir_fd values.
If the new name extends the old, don’t overwrite the old name;
just store into the old name’s extension.
Although these changes improve performance with long names,
the primary goal is to clarify the code, not performance.
This commit is contained in:
Paul Eggert
2026-08-20 15:46:21 -07:00
parent e5aeda0699
commit 0713d3502a
+67 -59
View File
@@ -1314,8 +1314,8 @@ fdbase_opendir (char const *file_name, bool alternate, int child_oflags)
/* For files immediately under CHDIR_FD, and for root directories,
just use CHDIR_FD and NAME. Empty NAME is invalid, though. */
char const *base = last_component (name);
idx_t subdirlen = base + (child_oflags ? strlen (base) : 0) - name;
if (!subdirlen | !*base)
idx_t newdirlen = base + (child_oflags ? strlen (base) : 0) - name;
if (!newdirlen | !*base)
{
if (*name)
return (struct fdbase) { .fd = dfd, .base = name };
@@ -1324,67 +1324,75 @@ fdbase_opendir (char const *file_name, bool alternate, int child_oflags)
}
/* Try to reuse fdbase_cache[0] or (if ALTERNATE) fdbase_cache[1]. */
for (struct fdbase_cache *c = fdbase_cache; ; c++)
int fd;
idx_t subdirlen;
bool chdirmatch;
bool submatch;
struct fdbase_cache *c;
for (c = fdbase_cache; ; c++)
{
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));
if (submatch && c->subdirlen == subdirlen)
fd = c->fd;
subdirlen = c->subdirlen;
chdirmatch = c->chdir_current == chdir_current;
submatch = (0 < subdirlen && subdirlen <= newdirlen
&& !ISSLASH (name[subdirlen])
&& memeq (c->subdir, name, subdirlen));
if (chdirmatch & submatch && subdirlen == newdirlen)
return (struct fdbase) { .fd = fd, .base = base };
if (c == fdbase_cache + alternate)
{
/* Cannot reuse, so evict and reget fdbase_cache[ALTERNATE].
Start by copying the new directory's name into the cache,
but put it after any existing name if the new name is
not a subdirectory of the old one.
If the new name works it will be copied over the existing name;
if not, the existing name will survive.
Null-terminate the new name for open_subdir;
there is no need to null-terminate the old name. */
bool new_is_subdir_of_old = submatch && c->subdirlen < subdirlen;
idx_t new_subdir_offset = new_is_subdir_of_old ? 0 : c->subdirlen;
idx_t bothsize = new_subdir_offset + subdirlen + 1;
if (c->subdiralloc < bothsize)
c->subdir = xpalloc (c->subdir, &c->subdiralloc,
bothsize - c->subdiralloc, -1, 1);
char *subdir = c->subdir + new_subdir_offset;
char *subdirend = mempcpy (subdir, name, subdirlen);
*subdirend = '\0';
if (new_is_subdir_of_old)
{
/* 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)
return (struct fdbase) { .fd = BADFD, .base = base };
/* Replace the old directory info. */
if (!chdirable (fd))
close (fd);
c->fd = subfd;
c->subdirlen = subdirlen;
return (struct fdbase) { .fd = subfd, .base = base };
}
int newfd = open_subdir (chdir_fd, subdir, child_oflags);
if (newfd < 0)
return (struct fdbase) { .fd = BADFD, .base = base };
/* Replace the old directory info (if any). */
if (0 < c->subdirlen && !chdirable (fd))
close (fd);
c->chdir_current = chdir_current;
c->fd = newfd;
c->subdirlen = subdirlen;
memmove (c->subdir, subdir, subdirlen);
return (struct fdbase) { .fd = newfd, .base = base };
}
break;
}
/* Cannot reuse, so evict and reget fdbase_cache[ALTERNATE].
Start by copying the new directory's name into the cache,
but put it after any existing name if the new name is
not a subdirectory of the old one.
If the new name works it will be copied over the existing name;
if not, the existing name will survive.
Null-terminate the new name for open_subdir;
there is no need to null-terminate the old name. */
/* Whether the old directory name prefixes the new name.
If also CHDIRMATCH, the old is an ancestor of the new. */
bool old_prefixes_new = submatch && subdirlen < newdirlen;
/* The old length if reusing old name inside the new; otherwise, 0.
Although the code would be correct if this were always 0,
it would be slower if names are long. */
idx_t reuselen = old_prefixes_new ? subdirlen : 0;
/* Offset in the buffer of the new name.
Zero if the old name prefixes the new, as the new is an extension.
Otherwise, just past the old name. */
idx_t newdir_offset = old_prefixes_new ? 0 : subdirlen;
idx_t bothsize = newdir_offset + newdirlen + 1;
if (c->subdiralloc < bothsize)
c->subdir = xpalloc (c->subdir, &c->subdiralloc,
bothsize - c->subdiralloc, -1, 1);
char *subdir = c->subdir;
char *newdir = subdir + newdir_offset;
char *newdirend = mempcpy (newdir + reuselen, name + reuselen,
newdirlen - reuselen);
*newdirend = '\0';
/* If the new directory descends from the old, for speed
open descendant to FD rather than to CHDIR_FD. */
bool descendant = old_prefixes_new & chdirmatch;
int newfd = open_subdir (descendant ? fd : chdir_fd,
&newdir[descendant ? subdirlen : 0], child_oflags);
if (newfd < 0)
return (struct fdbase) { .fd = BADFD, .base = base };
/* Replace the old directory info (if any). */
if (0 < subdirlen && !chdirable (fd))
close (fd);
c->chdir_current = chdir_current;
c->fd = newfd;
c->subdirlen = newdirlen;
if (subdir != newdir)
memmove (subdir, newdir, newdirlen);
return (struct fdbase) { .fd = newfd, .base = base };
}
struct fdbase