Tue Jun 18 17:56:44 1996 Roland McGrath <roland@delasyd.gnu.ai.mit.edu>
authorRoland McGrath <roland@gnu.org>
Tue, 18 Jun 1996 22:01:27 +0000 (22:01 +0000)
committerRoland McGrath <roland@gnu.org>
Tue, 18 Jun 1996 22:01:27 +0000 (22:01 +0000)
* stdlib/test-canon.c: New test program contributed by David Mosberger.
* stdlib/Makefile (tests): Add test-canon.
* stdlib/canonicalize.c: Rewritten by David Mosberger.

stdlib/Makefile
stdlib/canonicalize.c
stdlib/test-canon.c [new file with mode: 0644]

index 46d7aa1..aa07208 100644 (file)
@@ -45,7 +45,8 @@ routines      :=                                                            \
        rpmatch strfmon
 
 distribute     := exit.h grouping.h
-tests          := tst-strtol tst-strtod testmb testrand testsort testdiv
+tests          := tst-strtol tst-strtod testmb testrand testsort testdiv \
+                  test-canon
 
 
 # Several mpn functions from GNU MP are used by the strtod function.
index 8008a28..a65b7f1 100644 (file)
@@ -17,174 +17,152 @@ License along with the GNU C Library; see the file COPYING.LIB.  If
 not, write to the Free Software Foundation, Inc., 675 Mass Ave,
 Cambridge, MA 02139, USA.  */
 
+#include <assert.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 #include <limits.h>
+#include <sys/param.h>
 #include <sys/stat.h>
 #include <errno.h>
 
-/* Return the canonical absolute name of file NAME.  The last file name
-   component need not exist, and may be a symlink to a nonexistent file.
-   If RESOLVED is null, the result is malloc'd; otherwise, if the canonical
-   name is PATH_MAX chars or more, returns null with `errno' set to
-   ENAMETOOLONG; if the name fits in fewer than PATH_MAX chars, returns the
-   name in RESOLVED.  */
+/* Return the canonical absolute name of file NAME.  A canonical name
+   does not contain any `.', `..' components nor any repeated path
+   separators ('/') or symlinks.  All path components must exist.  If
+   RESOLVED is null, the result is malloc'd; otherwise, if the
+   canonical name is PATH_MAX chars or more, returns null with `errno'
+   set to ENAMETOOLONG; if the name fits in fewer than PATH_MAX chars,
+   returns the name in RESOLVED.  If the name cannot be resolved and
+   RESOLVED is non-NULL, it contains the path of the first component
+   that cannot be resolved.  If the path can be resolved, RESOLVED
+   holds the same value as the value returned.  */
 
 static char *
 canonicalize (const char *name, char *resolved)
 {
-  struct stat st;
-  const char *p;
+  char *rpath, *dest, *extra_buf = NULL;
+  const char *start, *end, *rpath_limit;
   long int path_max;
-  char *result, *dir, *end;
-  size_t namelen;
+  int num_links = 0;
 
-  if (! resolved)
-    path_max = 0;
-  else
-    {
 #ifdef PATH_MAX
-      path_max = PATH_MAX;
+  path_max = PATH_MAX;
 #else
-      path_max = pathconf (name, _PC_PATH_MAX);
-      if (path_max <= 0)
-       path_max = 1024;
+  path_max = pathconf (name, _PC_PATH_MAX);
+  if (path_max <= 0)
+    path_max = 1024;
 #endif
-    }
 
-  p = strrchr (name, '/');
-  if (!p)
-    {
-      dir = (char *) ".";
-      p = name;
-    }
-  else
-    {
-      if (p++ == name)
-       dir = (char *) "/";
-      else
-       {
-         dir = __alloca (p - name);
-         memcpy (dir, name, p - name - 1);
-         dir[p - name] = '\0';
-       }
-    }
+  rpath = resolved;
+  rpath_limit = rpath + path_max;
+  if (!resolved)
+    rpath = malloc (path_max);
 
-  result = __canonicalize_directory_name_internal (dir, resolved, path_max);
-  if (!result)
-    return NULL;
+  strcpy (rpath, "/");
+  if (name[0] != '/' && !getcwd (rpath, path_max))
+    goto error;
+  dest = rpath + strlen (rpath);
 
-  /* Reconstruct the file name in the canonicalized directory.  */
-  namelen = strlen (name);
-  end = strchr (result, '\0');
-  if (resolved)
+  for (start = end = name; *start; start = end)
     {
-      /* Make sure the name is not too long.  */
-      if (end - result + namelen > path_max)
+      struct stat st;
+      int n;
+
+      /* skip sequence of multiple path-separators: */
+      while (*start == '/') ++start;
+
+      /* find end of path component: */
+      for (end = start; *end && *end != '/'; ++end);
+      
+      if (end - start == 0)
+       break;
+      else if (strncmp (start, ".", end - start) == 0)
+       /* nothing */;
+      else if (strncmp (start, "..", end - start) == 0) {
+       /* back up to previous component, ignore if at root already: */
+       if (dest > rpath + 1)
+         while ((--dest)[-1] != '/');
+      } else
        {
-         errno = ENAMETOOLONG;
-         return NULL;
-       }
-    }
-  else
-    {
-      /* The name is dynamically allocated.  Extend it.  */
-      char *new = realloc (result, end - result + namelen + 1);
-      if (! new)
-       {
-         free (result);
-         return NULL;
-       }
-      end = new + (end - result);
-      result = new;
-    }
-  memcpy (end, name, namelen + 1);
+         size_t new_size;
 
-  while (__lstat (result, &st) == 0 && S_ISLNK (st.st_mode))
-    {
-      /* The file is a symlink.  Read its contents.  */
-      ssize_t n;
-    read_link_contents:
-      n = readlink (result, end,
-                   resolved ? result + path_max - end : namelen + 1);
-      if (n < 0)
-       /* Error reading the link contents.  */
-       return NULL;
-
-      if (end[0] == '/')
-       {
-         /* Absolute symlink.  */
-         if (resolved ? (end + n < result + path_max) : (n < namelen + 1))
-           {
-             /* It fit in our buffer, so we have the whole thing.  */
-             memcpy (result, end, n);
-             result[n] = '\0';
-           }
-         else if (resolved)
+         if (dest[-1] != '/')
+           *dest++ = '/';
+
+         if (dest + (end - start) >= rpath_limit)
            {
-             /* It didn't fit in the remainder of the buffer.  Either it
-                fits in the entire buffer, or it doesn't.  Copy back the
-                unresolved name onto the canonical directory and try once
-                more.  */
-             memcpy (end, name, namelen + 1);
-             n = readlink (result, result, path_max);
-             if (n < 0)
-               return NULL;
-             if (n == path_max)
+             if (resolved)
                {
                  errno = ENAMETOOLONG;
-                 return NULL;
+                 goto error;
                }
-             result[n] = '\0';
+             new_size = rpath_limit - rpath;
+             if (end - start + 1 > path_max)
+               new_size += end - start + 1;
+             else
+               new_size += path_max;
+             rpath = realloc (rpath, new_size);
+             rpath_limit = rpath + new_size;
+             if (!rpath)
+               return NULL;
            }
-         else
-           /* Try again with a bigger buffer.  */
-           goto extend_buffer;
 
-         /* Check the resolved name for being a symlink too.  */
-         continue;
-       }
+         memcpy (dest, start, end - start);
+         dest += end - start;
+         *dest = '\0';
+         
+         if (__lstat (rpath, &st) < 0)
+           goto error;
 
-      if (resolved)
-       {
-         if (end + n == result + path_max)
+         if (S_ISLNK (st.st_mode))
            {
-             /* The link contents we read fill the buffer completely.
-                There may be even more to read, and there is certainly no
-                space for the null terminator.  */
-             errno = ENAMETOOLONG;
-             return NULL;
-           }
-       }
-      else if (n == namelen + 1)
-      extend_buffer:
-       {
-         /* The name buffer is dynamically allocated.  Extend it.  */
-         char *new;
+             char * buf = __alloca(path_max);
 
-         /* Copy back the unresolved name onto the canonical directory.  */
-         memcpy (end, name, namelen + 1);
+             if (++num_links > MAXSYMLINKS)
+               {
+                 errno = ELOOP;
+                 goto error;
+               }
 
-         /* Make more space for readlink.  */
-         namelen *= 2;
-         new = realloc (result, end - result + namelen + 1);
-         if (! new)
-           {
-             free (result);
-             return NULL;
-           }
-         end = new + (end - result);
-         result = new;
+             n = readlink (rpath, buf, path_max);
+             if (n < 0)
+               goto error;
+             buf[n] = '\0';
 
-         goto read_link_contents;
-       }
+             if (!extra_buf)
+               extra_buf = __alloca (path_max);
 
-      /* Terminate the string; readlink does not.  */
-      end[n] = '\0';
-    }
+             if (n + strlen (end) >= path_max)
+               {
+                 errno = ENAMETOOLONG;
+                 goto error;
+               }
 
-  return result;
+             /* careful here, end may be a pointer into extra_buf... */
+             strcat (buf, end);
+             strcpy (extra_buf, buf);
+             name = end = extra_buf;
+
+             if (buf[0] == '/')
+               dest = rpath + 1;       /* it's an absolute symlink */
+             else
+               /* back up to previous component, ignore if at root already: */
+               if (dest > rpath + 1)
+                 while ((--dest)[-1] != '/');
+           }
+         else
+           num_links = 0;
+       }
+    }
+  if (dest > rpath + 1 && dest[-1] == '/')
+    --dest;
+  *dest = '\0';
+  return rpath;
+
+error:
+  if (!resolved)
+    free (rpath);
+  return NULL;
 }
 
 weak_alias (canonicalize, realpath)
diff --git a/stdlib/test-canon.c b/stdlib/test-canon.c
new file mode 100644 (file)
index 0000000..6f0171d
--- /dev/null
@@ -0,0 +1,181 @@
+/* Test program for returning the canonical absolute name of a given file.
+Copyright (C) 1996 Free Software Foundation, Inc.
+This file is part of the GNU C Library.
+Contributed by David Mosberger <davidm@azstarnet.com>.
+
+The GNU C Library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+The GNU C Library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with the GNU C Library; see the file COPYING.LIB.  If
+not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+Cambridge, MA 02139, USA.  */
+
+/* This file must be run from within a directory called "stdlib".  */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/param.h>
+
+static char    cwd[1024];
+static size_t  cwd_len;
+
+struct {
+  const char * name;
+  const char * value;
+} symlinks[] = {
+  {"SYMLINK_LOOP",     "SYMLINK_LOOP"},
+  {"SYMLINK_1",                "."},
+  {"SYMLINK_2",                "//////./../../etc"},
+  {"SYMLINK_3",                "SYMLINK_1"},
+  {"SYMLINK_4",                "SYMLINK_2"},
+  {"SYMLINK_5",                "doesNotExist"},
+};
+
+struct {
+  const char * in, * out, * resolved;
+  int errno;
+} tests[] = {
+  /*  0 */
+  {"/",                                        "/"},
+  {"/////////////////////////////////",        "/"},
+  {"/.././.././.././..///",            "/"},
+  {"/etc",                             "/etc"},
+  {"/etc/../etc",                      "/etc"},
+  /*  5 */
+  {"/doesNotExist/../etc",             0, "/doesNotExist", ENOENT},
+  {"./././././././././.",              "."},
+  {"/etc/.//doesNotExist",             0, "/etc/doesNotExist", ENOENT},
+  {"./doesExist",                      "./doesExist"},
+  {"./doesExist/",                     "./doesExist"},
+  /* 10 */
+  {"./doesExist/../doesExist",         "./doesExist"},
+  {"foobar",                           0, "./foobar", ENOENT},
+  {".",                                        "."},
+  {"./foobar",                         0, "./foobar", ENOENT},
+  {"SYMLINK_LOOP",                     0, "./SYMLINK_LOOP", ELOOP},
+  /* 15 */
+  {"./SYMLINK_LOOP",                   0, "./SYMLINK_LOOP", ELOOP},
+  {"SYMLINK_1",                                "."},
+  {"SYMLINK_1/foobar",                 0, "./foobar", ENOENT},
+  {"SYMLINK_2",                                "/etc"},
+  {"SYMLINK_3",                                "."},
+  /* 20 */
+  {"SYMLINK_4",                                "/etc"},
+  {"../stdlib/SYMLINK_1",              "."},
+  {"../stdlib/SYMLINK_2",              "/etc"},
+  {"../stdlib/SYMLINK_3",              "."},
+  {"../stdlib/SYMLINK_4",              "/etc"},
+  /* 25 */
+  {"./SYMLINK_5",                      0, "./doesNotExist", ENOENT},
+  {"SYMLINK_5",                                0, "./doesNotExist", ENOENT},
+  {"SYMLINK_5/foobar",                 0, "./doesNotExist", ENOENT},
+  {"doesExist/../../stdlib/doesExist", "./doesExist"},
+  {"doesExist/.././../stdlib/.",       "."}
+};
+
+
+int
+check_path (const char * result, const char * expected)
+{
+  int good;
+
+  if (!result)
+    return (expected == NULL);
+
+  if (!expected)
+    return 0;
+
+  if (expected[0] == '.' && (expected[1] == '/' || expected[1] == '\0'))
+    good = (strncmp (result, cwd, cwd_len) == 0
+           && strcmp (result + cwd_len, expected + 1) == 0);
+  else
+    good = (strcmp (expected, result) == 0);
+
+  return good;
+}
+
+
+void
+main (int argc, char ** argv)
+{
+  char * result;
+  int fd, i, errors = 0;
+  char buf[PATH_MAX];
+
+  getcwd (cwd, sizeof(buf));
+  cwd_len = strlen (cwd);
+
+  for (i = 0; i < sizeof (symlinks) / sizeof (symlinks[0]); ++i)
+    symlink (symlinks[i].value, symlinks[i].name);
+
+  fd = open("doesExist", O_CREAT | O_EXCL, 0777);
+
+  for (i = 0; i < sizeof (tests) / sizeof (tests[0]); ++i)
+    {
+      buf[0] = '\0';
+      result = realpath (tests[i].in, buf);
+
+      if (!check_path (result, tests[i].out))
+       {
+         printf ("%s: flunked test %d (expected `%s', got `%s')\n",
+                 argv[0], i, tests[i].out ? tests[i].out : "NULL",
+                 result ? result : "NULL");
+         ++errors;
+         continue;
+       }
+
+      if (!check_path (buf, tests[i].out ? tests[i].out : tests[i].resolved))
+       {
+         printf ("%s: flunked test %d (expected resolved `%s', got `%s')\n",
+                 argv[0], i, tests[i].out ? tests[i].out : tests[i].resolved,
+                 buf);
+         ++errors;
+         continue;
+       }
+
+      if (!tests[i].out && errno != tests[i].errno)
+       {
+         printf ("%s: flunked test %d (expected errno %d, got %d)\n",
+                 argv[0], i, tests[i].errno, errno);
+         ++errors;
+         continue;
+       }
+    }
+
+  getcwd (buf, sizeof(buf));
+  if (strcmp (buf, cwd))
+    {
+      printf ("%s: current working directory changed from %s to %s\n",
+             argv[0], cwd, buf);
+      ++errors;
+    }
+
+  if (fd >= 0)
+    unlink("doesExist");
+
+  for (i = 0; i < sizeof (symlinks) / sizeof (symlinks[0]); ++i)
+    unlink (symlinks[i].name);
+
+  if (errors == 0)
+    {
+      puts ("No errors.");
+      exit (EXIT_SUCCESS);
+    }
+  else
+    {
+      printf ("%d errors.\n", errors);
+      exit (EXIT_FAILURE);
+    }
+}