mirror of
https://git.savannah.gnu.org/git/tar.git
synced 2026-04-26 03:20:40 +00:00
Refactor to avoid duplication in "./" scanning
* src/exclist.c (excluded_name): * src/misc.c (normalize_filename_x, must_be_dot_or_slash) (chdir_arg): Use dotslash or dotslashlen instead of doing things by hand. * src/misc.c (slashlen, dotslashlen): New functions. (safer_rmdir): Do not worry about unlinkat with AT_REMOVEDIR succeeding on ".", as POSIX prohibits it, and it does not succeed on any known platform. This simplifies the file name test. Continue to worry about "/" though, as POSIX does allow it to be removed.
This commit is contained in:
@@ -661,6 +661,7 @@ void assign_string_n (char **string, const char *value, idx_t n);
|
||||
#define ASSIGN_STRING_N(s,v) assign_string_n (s, v, sizeof (v))
|
||||
void unquote_string (char *str);
|
||||
char *zap_slashes (char *name);
|
||||
idx_t dotslashlen (char const *);
|
||||
char *normalize_filename (idx_t, char const *);
|
||||
void normalize_filename_x (char *name);
|
||||
void replace_prefix (char **pname, const char *samp, idx_t slen,
|
||||
|
||||
@@ -170,12 +170,7 @@ excluded_name (char const *name, struct tar_stat_info *st)
|
||||
break;
|
||||
|
||||
if (!rname)
|
||||
{
|
||||
rname = name;
|
||||
/* Skip leading ./ */
|
||||
while (*rname == '.' && ISSLASH (rname[1]))
|
||||
rname += 2;
|
||||
}
|
||||
rname = name + dotslashlen (name);
|
||||
if ((result = excluded_file_name (ep->excluded, rname)))
|
||||
break;
|
||||
|
||||
|
||||
71
src/misc.c
71
src/misc.c
@@ -265,6 +265,30 @@ zap_slashes (char *name)
|
||||
return name;
|
||||
}
|
||||
|
||||
/* The number of file name slashes at the start of the string F. */
|
||||
static idx_t
|
||||
slashlen (char const *f)
|
||||
{
|
||||
idx_t i = 0;
|
||||
while (ISSLASH (f[i]))
|
||||
i++;
|
||||
return i;
|
||||
}
|
||||
|
||||
/* The length of the longest initial prefix of F that consists
|
||||
entirely of a sequence of '.'s each followed by one or more slashes.
|
||||
This prefix acts like the working directory, i.e., the file name F
|
||||
acts like "." if 0 < dotslashlen (F) == strlen (F), and acts like
|
||||
&F[dotslashlen (F)] otherwise. */
|
||||
idx_t
|
||||
dotslashlen (char const *f)
|
||||
{
|
||||
idx_t i = 0;
|
||||
while (f[i] == '.' && ISSLASH (f[i + 1]))
|
||||
i += 2 + slashlen (&f[i + 2]);
|
||||
return i;
|
||||
}
|
||||
|
||||
/* Normalize FILE_NAME by removing redundant slashes and "."
|
||||
components, including redundant trailing slashes.
|
||||
Leave ".." alone, as it may be significant in the presence
|
||||
@@ -275,8 +299,6 @@ void
|
||||
normalize_filename_x (char *file_name)
|
||||
{
|
||||
char *name = file_name + FILE_SYSTEM_PREFIX_LEN (file_name);
|
||||
char *p;
|
||||
char const *q;
|
||||
char c;
|
||||
|
||||
/* Don't squeeze leading "//" to "/", on hosts where they're distinct. */
|
||||
@@ -284,16 +306,20 @@ normalize_filename_x (char *file_name)
|
||||
&& ISSLASH (*name) && ISSLASH (name[1]) && ! ISSLASH (name[2]));
|
||||
|
||||
/* Omit redundant leading "." components. */
|
||||
for (q = p = name; (*p = *q) == '.' && ISSLASH (q[1]); p += !*q)
|
||||
for (q += 2; ISSLASH (*q); q++)
|
||||
continue;
|
||||
char *p = name;
|
||||
char const *q = name + dotslashlen (name);
|
||||
|
||||
if (p < q && !*q)
|
||||
q = "."; /* NAME is nonempty and equivalent to ".". */
|
||||
|
||||
/* Copy components from Q to P, omitting redundant slashes and
|
||||
internal "." components. */
|
||||
while ((*p++ = c = *q++) != '\0')
|
||||
if (ISSLASH (c))
|
||||
while (ISSLASH (q[*q == '.']))
|
||||
q += (*q == '.') + 1;
|
||||
{
|
||||
q += slashlen (q);
|
||||
q += dotslashlen (q);
|
||||
}
|
||||
|
||||
/* Omit redundant trailing "." component and slash. */
|
||||
if (2 < p - name)
|
||||
@@ -602,7 +628,8 @@ decode_timespec (char const *arg, char **arg_lim, bool parse_fraction)
|
||||
static char *before_backup_name;
|
||||
static char *after_backup_name;
|
||||
|
||||
/* Return 1 if FILE_NAME is obviously "." or "/". */
|
||||
/* Return 1 if FILE_NAME must identify a working or root directory.
|
||||
FILE_NAME should not be empty. */
|
||||
bool
|
||||
must_be_dot_or_slash (char const *file_name)
|
||||
{
|
||||
@@ -610,6 +637,7 @@ must_be_dot_or_slash (char const *file_name)
|
||||
|
||||
if (ISSLASH (file_name[0]))
|
||||
{
|
||||
/* It must be a root directory if all components are "." or "..". */
|
||||
for (;;)
|
||||
if (ISSLASH (file_name[1]))
|
||||
file_name++;
|
||||
@@ -621,26 +649,23 @@ must_be_dot_or_slash (char const *file_name)
|
||||
}
|
||||
else
|
||||
{
|
||||
while (file_name[0] == '.' && ISSLASH (file_name[1]))
|
||||
{
|
||||
file_name += 2;
|
||||
while (ISSLASH (*file_name))
|
||||
file_name++;
|
||||
}
|
||||
|
||||
return ! file_name[0] || (file_name[0] == '.' && ! file_name[1]);
|
||||
/* It must be a working directory if it is "." or "",
|
||||
after skipping ^(\./+)* ERE. */
|
||||
file_name += dotslashlen (file_name);
|
||||
return ! file_name[file_name[0] == '.'];
|
||||
}
|
||||
}
|
||||
|
||||
/* Some implementations of rmdir let you remove '.' or '/'.
|
||||
Report an error with errno set to zero for obvious cases of this;
|
||||
otherwise call rmdir. */
|
||||
/* Act like rmdir (FILE_NAME) relative to CHDIR_FD.
|
||||
However, reject attempts to remove a root directory
|
||||
even on systems that allow such a thing.
|
||||
Also, do not try to change the removed directory's status later. */
|
||||
static int
|
||||
safer_rmdir (const char *file_name)
|
||||
{
|
||||
if (must_be_dot_or_slash (file_name))
|
||||
if (!file_name[slashlen (file_name)])
|
||||
{
|
||||
errno = 0;
|
||||
errno = file_name[0] ? EBUSY : ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -976,9 +1001,7 @@ chdir_arg (char const *dir)
|
||||
or the working directory as a prefix. */
|
||||
if (dir[0])
|
||||
{
|
||||
while (dir[0] == '.' && ISSLASH (dir[1]))
|
||||
for (dir += 2; ISSLASH (*dir); dir++)
|
||||
continue;
|
||||
dir += dotslashlen (dir);
|
||||
if (! dir[dir[0] == '.'])
|
||||
return wd_count - 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user