]> git.ipfire.org Git - thirdparty/tar.git/commitdiff
Refactor to avoid duplication in "./" scanning
authorPaul Eggert <eggert@cs.ucla.edu>
Sat, 8 Nov 2025 00:26:32 +0000 (16:26 -0800)
committerPaul Eggert <eggert@cs.ucla.edu>
Sat, 15 Nov 2025 23:10:48 +0000 (15:10 -0800)
* 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.

src/common.h
src/exclist.c
src/misc.c

index 1d31659d92ba349b13217d77e73ecbe685144df2..2cc89822accf357c17c50364b4e0cc6dbb479603 100644 (file)
@@ -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,
index 4c5544f6a284c40d9fb8e5c94c1890116cea2fb9..2d03b1105c8a902ad7b27b2418e2fe50a66c7e3c 100644 (file)
@@ -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;
 
index dcc81ef6aac884ce8d39eaca0d64b3e79294e41c..607f5daf7e4ed530e3110e8472182963c48ec9bc 100644 (file)
@@ -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;
     }