]> Dogcows Code - chaz/tar/blobdiff - src/names.c
(names_done): New function.
[chaz/tar] / src / names.c
index 72b928003cf8f516f64232e6cc7a47c84e5e2007..41d3476ec6cb05428c5e719e4edfe9155cbd5638 100644 (file)
@@ -1,5 +1,7 @@
 /* Various processing of names.
-   Copyright 1988, 92, 94, 96, 97, 98, 99, 2000 Free Software Foundation, Inc.
+
+   Copyright (C) 1988, 1992, 1994, 1996, 1997, 1998, 1999, 2000, 2001,
+   2003 Free Software Foundation, Inc.
 
    This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by the
@@ -41,15 +43,15 @@ struct group *getgrgid ();
    This code should also be modified for non-UNIX systems to do something
    reasonable.  */
 
-static char cached_uname[UNAME_FIELD_SIZE];
-static char cached_gname[GNAME_FIELD_SIZE];
+static char *cached_uname;
+static char *cached_gname;
 
 static uid_t cached_uid;       /* valid only if cached_uname is not empty */
 static gid_t cached_gid;       /* valid only if cached_gname is not empty */
 
 /* These variables are valid only if nonempty.  */
-static char cached_no_such_uname[UNAME_FIELD_SIZE];
-static char cached_no_such_gname[GNAME_FIELD_SIZE];
+static char *cached_no_such_uname;
+static char *cached_no_such_gname;
 
 /* These variables are valid only if nonzero.  It's not worth optimizing
    the case for weird systems where 0 is not a valid uid or gid.  */
@@ -58,87 +60,87 @@ static gid_t cached_no_such_gid;
 
 /* Given UID, find the corresponding UNAME.  */
 void
-uid_to_uname (uid_t uid, char uname[UNAME_FIELD_SIZE])
+uid_to_uname (uid_t uid, char **uname)
 {
   struct passwd *passwd;
 
   if (uid != 0 && uid == cached_no_such_uid)
     {
-      *uname = '\0';
+      *uname = strdup ("");
       return;
     }
 
-  if (!cached_uname[0] || uid != cached_uid)
+  if (!cached_uname || uid != cached_uid)
     {
       passwd = getpwuid (uid);
       if (passwd)
        {
          cached_uid = uid;
-         strncpy (cached_uname, passwd->pw_name, UNAME_FIELD_SIZE);
+         assign_string (&cached_uname, passwd->pw_name);
        }
       else
        {
          cached_no_such_uid = uid;
-         *uname = '\0';
+         *uname = strdup ("");
          return;
        }
     }
-  strncpy (uname, cached_uname, UNAME_FIELD_SIZE);
+  *uname = strdup (cached_uname);
 }
 
 /* Given GID, find the corresponding GNAME.  */
 void
-gid_to_gname (gid_t gid, char gname[GNAME_FIELD_SIZE])
+gid_to_gname (gid_t gid, char **gname)
 {
   struct group *group;
 
   if (gid != 0 && gid == cached_no_such_gid)
     {
-      *gname = '\0';
+      *gname = strdup ("");
       return;
     }
 
-  if (!cached_gname[0] || gid != cached_gid)
+  if (!cached_gname || gid != cached_gid)
     {
       group = getgrgid (gid);
       if (group)
        {
          cached_gid = gid;
-         strncpy (cached_gname, group->gr_name, GNAME_FIELD_SIZE);
+         assign_string (&cached_gname, group->gr_name);
        }
       else
        {
          cached_no_such_gid = gid;
-         *gname = '\0';
+         *gname = strdup ("");
          return;
        }
     }
-  strncpy (gname, cached_gname, GNAME_FIELD_SIZE);
+  *gname = strdup (cached_gname);
 }
 
 /* Given UNAME, set the corresponding UID and return 1, or else, return 0.  */
 int
-uname_to_uid (char uname[UNAME_FIELD_SIZE], uid_t *uidp)
+uname_to_uid (char *uname, uid_t *uidp)
 {
   struct passwd *passwd;
 
-  if (cached_no_such_uname[0]
-      && strncmp (uname, cached_no_such_uname, UNAME_FIELD_SIZE) == 0)
+  if (cached_no_such_uname
+      && strcmp (uname, cached_no_such_uname) == 0)
     return 0;
 
-  if (!cached_uname[0]
+  if (!cached_uname
       || uname[0] != cached_uname[0]
-      || strncmp (uname, cached_uname, UNAME_FIELD_SIZE) != 0)
+      || strcmp (uname, cached_uname) != 0)
     {
       passwd = getpwnam (uname);
       if (passwd)
        {
          cached_uid = passwd->pw_uid;
-         strncpy (cached_uname, uname, UNAME_FIELD_SIZE);
+         assign_string (&cached_uname, passwd->pw_name);
        }
       else
        {
-         strncpy (cached_no_such_uname, uname, UNAME_FIELD_SIZE);
+         assign_string (&cached_no_such_uname, uname);
          return 0;
        }
     }
@@ -148,33 +150,34 @@ uname_to_uid (char uname[UNAME_FIELD_SIZE], uid_t *uidp)
 
 /* Given GNAME, set the corresponding GID and return 1, or else, return 0.  */
 int
-gname_to_gid (char gname[GNAME_FIELD_SIZE], gid_t *gidp)
+gname_to_gid (char *gname, gid_t *gidp)
 {
   struct group *group;
 
-  if (cached_no_such_gname[0]
-      && strncmp (gname, cached_no_such_gname, GNAME_FIELD_SIZE) == 0)
+  if (cached_no_such_gname
+      && strcmp (gname, cached_no_such_gname) == 0)
     return 0;
 
-  if (!cached_gname[0]
+  if (!cached_gname
       || gname[0] != cached_gname[0]
-      || strncmp (gname, cached_gname, GNAME_FIELD_SIZE) != 0)
+      || strcmp (gname, cached_gname) != 0)
     {
       group = getgrnam (gname);
       if (group)
        {
          cached_gid = group->gr_gid;
-         strncpy (cached_gname, gname, GNAME_FIELD_SIZE);
+         assign_string (&cached_gname, gname);
        }
       else
        {
-         strncpy (cached_no_such_gname, gname, GNAME_FIELD_SIZE);
+         assign_string (&cached_no_such_gname, gname);
          return 0;
        }
     }
   *gidp = cached_gid;
   return 1;
 }
+
 \f
 /* Names from the command call.  */
 
@@ -268,7 +271,9 @@ read_name_from_file (void)
     {
       if (counter == name_buffer_length)
        {
-         name_buffer_length += NAME_FIELD_SIZE;
+         if (name_buffer_length * 2 < name_buffer_length)
+           xalloc_die ();
+         name_buffer_length *= 2;
          name_buffer = xrealloc (name_buffer, name_buffer_length + 2);
        }
       name_buffer[counter++] = character;
@@ -279,7 +284,9 @@ read_name_from_file (void)
 
   if (counter == name_buffer_length)
     {
-      name_buffer_length += NAME_FIELD_SIZE;
+      if (name_buffer_length * 2 < name_buffer_length)
+       xalloc_die ();
+      name_buffer_length *= 2;
       name_buffer = xrealloc (name_buffer, name_buffer_length + 2);
     }
   name_buffer[counter] = '\0';
@@ -317,11 +324,20 @@ name_next (int change_dirs)
        }
       else
        {
+         size_t source_len;
          source = name_array[name_index++];
-         if (strlen (source) > name_buffer_length)
+         source_len = strlen (source);
+         if (name_buffer_length < source_len)
            {
+             do
+               {
+                 name_buffer_length *= 2;
+                 if (! name_buffer_length)
+                   xalloc_die ();
+               }
+             while (name_buffer_length < source_len);
+
              free (name_buffer);
-             name_buffer_length = strlen (source);
              name_buffer = xmalloc (name_buffer_length + 2);
            }
          strcpy (name_buffer, source);
@@ -330,7 +346,7 @@ name_next (int change_dirs)
       /* Zap trailing slashes.  */
 
       cursor = name_buffer + strlen (name_buffer) - 1;
-      while (cursor > name_buffer && *cursor == '/')
+      while (cursor > name_buffer && ISSLASH (*cursor))
        *cursor-- = '\0';
 
       if (chdir_flag)
@@ -380,7 +396,7 @@ name_gather (void)
 {
   /* Buffer able to hold a single name.  */
   static struct name *buffer;
-  static size_t allocated_length;
+  static size_t allocated_size;
 
   char const *name;
 
@@ -388,12 +404,12 @@ name_gather (void)
     {
       static int change_dir;
 
-      if (allocated_length == 0)
+      if (allocated_size == 0)
        {
-         allocated_length = sizeof (struct name) + NAME_FIELD_SIZE;
-         buffer = xmalloc (allocated_length);
+         allocated_size = offsetof (struct name, name) + NAME_FIELD_SIZE + 1;
+         buffer = xmalloc (allocated_size);
          /* FIXME: This memset is overkill, and ugly...  */
-         memset (buffer, 0, allocated_length);
+         memset (buffer, 0, allocated_size);
        }
 
       while ((name = name_next (0)) && strcmp (name, "-C") == 0)
@@ -406,14 +422,23 @@ name_gather (void)
 
       if (name)
        {
+         size_t needed_size;
          buffer->length = strlen (name);
-         if (sizeof (struct name) + buffer->length >= allocated_length)
+         needed_size = offsetof (struct name, name) + buffer->length + 1;
+         if (allocated_size < needed_size)
            {
-             allocated_length = sizeof (struct name) + buffer->length;
-             buffer = xrealloc (buffer, allocated_length);
+             do
+               {
+                 allocated_size *= 2;
+                 if (! allocated_size)
+                   xalloc_die ();
+               }
+             while (allocated_size < needed_size);
+
+             buffer = xrealloc (buffer, allocated_size);
            }
          buffer->change_dir = change_dir;
-         memcpy (buffer->name, name, buffer->length + 1);
+         strcpy (buffer->name, name);
          buffer->next = 0;
          buffer->found = 0;
 
@@ -452,23 +477,26 @@ name_gather (void)
 struct name *
 addname (char const *string, int change_dir)
 {
-  struct name *name;
-  size_t length;
-
-  length = string ? strlen (string) : 0;
-  name = xmalloc (sizeof (struct name) + length);
-  memset (name, 0, sizeof (struct name) + length);
-  name->next = 0;
+  size_t length = string ? strlen (string) : 0;
+  struct name *name = xmalloc (offsetof (struct name, name) + length + 1);
 
   if (string)
     {
       name->fake = 0;
-      name->length = length;
-      memcpy (name->name, string, length + 1);
+      strcpy (name->name, string);
     }
   else
-    name->fake = 1;
+    {
+      name->fake = 1;
+
+      /* FIXME: This initialization (and the byte of memory that it
+        initializes) is probably not needed, but we are currently in
+        bug-fix mode so we'll leave it in for now.  */
+      name->name[0] = 0;
+    }
 
+  name->next = 0;
+  name->length = length;
   name->found = 0;
   name->regexp = 0;            /* assume not a regular expression */
   name->firstch = 1;           /* assume first char is literal */
@@ -487,6 +515,32 @@ addname (char const *string, int change_dir)
   return name;
 }
 
+/* Find a match for PATH (whose string length is LENGTH) in the name
+   list.  */
+static struct name *
+namelist_match (char const *path, size_t length)
+{
+  struct name *p;
+
+  for (p = namelist; p; p = p->next)
+    {
+      /* If first chars don't match, quick skip.  */
+
+      if (p->firstch && p->name[0] != path[0])
+       continue;
+
+      if (p->regexp
+         ? fnmatch (p->name, path, recursion_option) == 0
+         : (p->length <= length
+            && (path[p->length] == '\0'
+                || (ISSLASH (path[p->length]) && recursion_option))
+            && memcmp (path, p->name, p->length) == 0))
+       return p;
+    }
+
+  return 0;
+}
+
 /* Return true if and only if name PATH (from an archive) matches any
    name from the namelist.  */
 int
@@ -509,32 +563,20 @@ name_match (const char *path)
          return ! files_from_option;
        }
 
-      for (; cursor; cursor = cursor->next)
+      cursor = namelist_match (path, length);
+      if (cursor)
        {
-         /* If first chars don't match, quick skip.  */
-
-         if (cursor->firstch && cursor->name[0] != path[0])
-           continue;
-
-         if (cursor->regexp
-             ? fnmatch (cursor->name, path, FNM_LEADING_DIR) == 0
-             : (cursor->length <= length
-                && (path[cursor->length] == '\0'
-                    || path[cursor->length] == '/')
-                && memcmp (path, cursor->name, cursor->length) == 0))
+         cursor->found = 1; /* remember it matched */
+         if (starting_file_option)
            {
-             cursor->found = 1; /* remember it matched */
-             if (starting_file_option)
-               {
-                 free (namelist);
-                 namelist = 0;
-                 nametail = &namelist;
-               }
-             chdir_do (cursor->change_dir);
-  
-             /* We got a match.  */
-             return 1;
+             free (namelist);
+             namelist = 0;
+             nametail = &namelist;
            }
+         chdir_do (cursor->change_dir);
+
+         /* We got a match.  */
+         return 1;
        }
 
       /* Filename from archive not found in namelist.  If we have the whole
@@ -553,29 +595,29 @@ name_match (const char *path)
     }
 }
 
+/* Returns true if all names from the namelist were processed */
+bool
+names_done ()
+{
+  struct name const *cursor;
+  for (cursor = namelist; cursor; cursor = cursor->next)
+    if (!cursor->found && !cursor->fake)
+      return false;
+  return true;
+}
+
 /* Print the names of things in the namelist that were not matched.  */
 void
 names_notfound (void)
 {
-  struct name *cursor;
-  struct name *next;
+  struct name const *cursor;
 
-  for (cursor = namelist; cursor; cursor = next)
-    {
-      next = cursor->next;
-      if (!cursor->found && !cursor->fake)
-       ERROR ((0, 0, _("%s: Not found in archive"),
-               quotearg_colon (cursor->name)));
-
-      /* We could free the list, but the process is about to die anyway, so
-        save some CPU time.  Amigas and other similarly broken software
-        will need to waste the time, though.  */
+  for (cursor = namelist; cursor; cursor = cursor->next)
+    if (!cursor->found && !cursor->fake)
+      ERROR ((0, 0, _("%s: Not found in archive"),
+             quotearg_colon (cursor->name)));
 
-#ifdef amiga
-      if (!same_order_option)
-       free (cursor);
-#endif
-    }
+  /* Don't bother freeing the name list; we're about to exit.  */
   namelist = 0;
   nametail = &namelist;
 
@@ -677,15 +719,14 @@ compare_names (struct name const *n1, struct name const *n2)
 }
 \f
 /* Add all the dirs under NAME, which names a directory, to the namelist.
-   DIRSIZE is the size of the directory, or -1 if not known.
    If any of the files is a directory, recurse on the subdirectory.
    DEVICE is the device not to leave, if the -l option is specified.  */
 
 static void
-add_hierarchy_to_namelist (struct name *name, off_t dirsize, dev_t device)
+add_hierarchy_to_namelist (struct name *name, dev_t device)
 {
   char *path = name->name;
-  char *buffer = get_directory_contents (path, dirsize, device);
+  char *buffer = get_directory_contents (path, device);
 
   if (! buffer)
     name->dir_contents = "\0\0\0\0";
@@ -703,7 +744,7 @@ add_hierarchy_to_namelist (struct name *name, off_t dirsize, dev_t device)
 
       name->dir_contents = buffer;
       strcpy (name_buffer, path);
-      if (name_buffer[name_length - 1] != '/')
+      if (! ISSLASH (name_buffer[name_length - 1]))
        {
          name_buffer[name_length++] = '/';
          name_buffer[name_length] = '\0';
@@ -714,15 +755,21 @@ add_hierarchy_to_namelist (struct name *name, off_t dirsize, dev_t device)
          string_length = strlen (string);
          if (*string == 'D')
            {
-             if (name_length + string_length >= allocated_length)
+             if (allocated_length <= name_length + string_length)
                {
-                 while (name_length + string_length >= allocated_length)
-                   allocated_length += NAME_FIELD_SIZE;
+                 do
+                   {
+                     allocated_length *= 2;
+                     if (! allocated_length)
+                       xalloc_die ();
+                   }
+                 while (allocated_length <= name_length + string_length);
+
                  name_buffer = xrealloc (name_buffer, allocated_length + 1);
                }
              strcpy (name_buffer + name_length, string + 1);
              add_hierarchy_to_namelist (addname (name_buffer, change_dir),
-                                        -1, device);
+                                        device);
            }
        }
 
@@ -763,13 +810,16 @@ collect_and_sort_names (void)
 
       if (deref_stat (dereference_option, name->name, &statbuf) != 0)
        {
-         stat_error (name->name);
+         if (ignore_failed_read_option)
+           stat_warn (name->name);
+         else
+           stat_error (name->name);
          continue;
        }
       if (S_ISDIR (statbuf.st_mode))
        {
          name->found = 1;
-         add_hierarchy_to_namelist (name, statbuf.st_size, statbuf.st_dev);
+         add_hierarchy_to_namelist (name, statbuf.st_dev);
        }
     }
 
@@ -793,33 +843,16 @@ name_scan (const char *path)
 
   while (1)
     {
-      struct name *cursor = namelist;
-
-      if (!cursor)
-       return 0;
-
-      for (; cursor; cursor = cursor->next)
-       {
-         /* If first chars don't match, quick skip.  */
-
-         if (cursor->firstch && cursor->name[0] != path[0])
-           continue;
-
-         if (cursor->regexp
-             ? fnmatch (cursor->name, path, FNM_LEADING_DIR) == 0
-             : (cursor->length <= length
-                && (path[cursor->length] == '\0'
-                    || path[cursor->length] == '/')
-                && memcmp (path, cursor->name, cursor->length) == 0))
-           return cursor;      /* we got a match */
-       }
+      struct name *cursor = namelist_match (path, length);
+      if (cursor)
+       return cursor;
 
       /* Filename from archive not found in namelist.  If we have the whole
         namelist here, just return 0.  Otherwise, read the next name in and
         compare it.  If this was the last name, namelist->found will remain
         on.  If not, we loop to compare the newly read name.  */
 
-      if (same_order_option && namelist->found)
+      if (same_order_option && namelist && namelist->found)
        {
          name_gather ();       /* read one more */
          if (namelist->found)
@@ -868,7 +901,7 @@ new_name (const char *path, const char *name)
 {
   size_t pathlen = strlen (path);
   size_t namesize = strlen (name) + 1;
-  int slash = pathlen && path[pathlen - 1] != '/';
+  int slash = pathlen && ! ISSLASH (path[pathlen - 1]);
   char *buffer = xmalloc (pathlen + slash + namesize);
   memcpy (buffer, path, pathlen);
   buffer[pathlen] = '/';
@@ -876,59 +909,192 @@ new_name (const char *path, const char *name)
   return buffer;
 }
 
-/* Return nonzero if file NAME is excluded.  Exclude a name if its
-   prefix matches a pattern that contains slashes, or if one of its
-   components matches a pattern that contains no slashes.  */
-int
+/* Return nonzero if file NAME is excluded.  */
+bool
 excluded_name (char const *name)
 {
-  char const *p;
-  name += FILESYSTEM_PREFIX_LEN (name);
-
-  if (excluded_filename (excluded_with_slash, name,
-                        FNM_FILE_NAME | FNM_LEADING_DIR))
-    return 1;
-
-  for (p = name; *p; p++)
-    if (((p == name || ISSLASH (p[-1])) && !ISSLASH (p[0]))
-       && excluded_filename (excluded_without_slash, p,
-                             FNM_FILE_NAME | FNM_LEADING_DIR))
-      return 1;
-
-  return 0;
+  return excluded_filename (excluded, name + FILESYSTEM_PREFIX_LEN (name));
 }
 \f
-/* Names to avoid dumping.  */
-static Hash_table *avoided_name_table;
+/* Hash tables of strings.  */
 
-/* Calculate the hash of an avoided name.  */
+/* Calculate the hash of a string.  */
 static unsigned
-hash_avoided_name (void const *name, unsigned n_buckets)
+hash_string_hasher (void const *name, unsigned n_buckets)
 {
   return hash_string (name, n_buckets);
 }
 
-/* Compare two avoided names for equality.  */
+/* Compare two strings for equality.  */
 static bool
-compare_avoided_names (void const *name1, void const *name2)
+hash_string_compare (void const *name1, void const *name2)
 {
   return strcmp (name1, name2) == 0;
 }
 
+/* Return zero if TABLE contains a copy of STRING; otherwise, insert a
+   copy of STRING to TABLE and return 1.  */
+static bool
+hash_string_insert (Hash_table **table, char const *string)
+{
+  Hash_table *t = *table;
+  char *s = xstrdup (string);
+  char *e;
+
+  if (! ((t
+         || (*table = t = hash_initialize (0, 0, hash_string_hasher,
+                                           hash_string_compare, 0)))
+        && (e = hash_insert (t, s))))
+    xalloc_die ();
+
+  if (e == s)
+    return 1;
+  else
+    {
+      free (s);
+      return 0;
+    }
+}
+
+/* Return 1 if TABLE contains STRING.  */
+static bool
+hash_string_lookup (Hash_table const *table, char const *string)
+{
+  return table && hash_lookup (table, string);
+}
+\f
+/* Names to avoid dumping.  */
+static Hash_table *avoided_name_table;
+
 /* Remember to not archive NAME.  */
 void
 add_avoided_name (char const *name)
 {
-  if (! ((avoided_name_table
-         || (avoided_name_table = hash_initialize (0, 0, hash_avoided_name,
-                                                   compare_avoided_names, 0)))
-        && hash_insert (avoided_name_table, xstrdup (name))))
-    xalloc_die ();
+  hash_string_insert (&avoided_name_table, name);
 }
 
 /* Should NAME be avoided when archiving?  */
-int
+bool
 is_avoided_name (char const *name)
 {
-  return avoided_name_table && hash_lookup (avoided_name_table, name);
+  return hash_string_lookup (avoided_name_table, name);
+}
+\f
+/* Return a safer suffix of FILE_NAME, or "." if it has no safer
+   suffix.  Check for fully specified file names and other atrocities.
+   Warn the user if we do not return NAME.  If LINK_TARGET is 1,
+   FILE_NAME is the target of a hard link, not a member name.  */
+
+char *
+safer_name_suffix (char const *file_name, bool link_target)
+{
+  char const *p;
+
+  if (absolute_names_option)
+    p = file_name;
+  else
+    {
+      /* Skip file system prefixes, leading pathnames that contain
+        "..", and leading slashes.  */
+
+      size_t prefix_len = FILESYSTEM_PREFIX_LEN (file_name);
+
+      for (p = file_name + prefix_len; *p; )
+       {
+         if (p[0] == '.' && p[1] == '.' && (ISSLASH (p[2]) || !p[2]))
+           prefix_len = p + 2 - file_name;
+
+         do
+           {
+             char c = *p++;
+             if (ISSLASH (c))
+               break;
+           }
+         while (*p);
+       }
+
+      for (p = file_name + prefix_len; ISSLASH (*p); p++)
+       continue;
+      prefix_len = p - file_name;
+
+      if (prefix_len)
+       {
+         static Hash_table *prefix_table[2];
+         char *prefix = alloca (prefix_len + 1);
+         memcpy (prefix, file_name, prefix_len);
+         prefix[prefix_len] = '\0';
+
+         if (hash_string_insert (&prefix_table[link_target], prefix))
+           {
+             static char const *const diagnostic[] =
+             {
+               N_("Removing leading `%s' from member names"),
+               N_("Removing leading `%s' from hard link targets")
+             };
+             WARN ((0, 0, _(diagnostic[link_target]), prefix));
+           }
+       }
+    }
+
+  if (! *p)
+    {
+      if (p == file_name)
+       {
+         static char const *const diagnostic[] =
+         {
+           N_("Substituting `.' for empty member name"),
+           N_("Substituting `.' for empty hard link target")
+         };
+         WARN ((0, 0, _(diagnostic[link_target])));
+       }
+
+      p = ".";
+    }
+
+  return (char *) p;
+}
+\f
+/* Return the size of the prefix of FILE_NAME that is removed after
+   stripping NUM leading path name components.  NUM must be
+   positive.  */
+
+size_t
+stripped_prefix_len (char const *file_name, size_t num)
+{
+  char const *p = file_name + FILESYSTEM_PREFIX_LEN (file_name);
+  while (ISSLASH (*p))
+    p++;
+  while (*p)
+    {
+      bool slash = ISSLASH (*p);
+      p++;
+      if (slash)
+       {
+         if (--num == 0)
+           return p - file_name;
+         while (ISSLASH (*p))
+           p++;
+       }
+    }
+  return -1;
+}
+\f
+/* Return nonzero if NAME contains ".." as a path name component.  */
+bool
+contains_dot_dot (char const *name)
+{
+  char const *p = name + FILESYSTEM_PREFIX_LEN (name);
+
+  for (;; p++)
+    {
+      if (p[0] == '.' && p[1] == '.' && (ISSLASH (p[2]) || !p[2]))
+       return 1;
+
+      do
+       {
+         if (! *p++)
+           return 0;
+       }
+      while (! ISSLASH (*p));
+    }
 }
This page took 0.046375 seconds and 4 git commands to generate.