about summary refs log tree commit diff
path: root/stdlib
diff options
context:
space:
mode:
Diffstat (limited to 'stdlib')
-rw-r--r--stdlib/Makefile3
-rw-r--r--stdlib/canonicalize.c244
-rw-r--r--stdlib/test-canon.c181
3 files changed, 294 insertions, 134 deletions
diff --git a/stdlib/Makefile b/stdlib/Makefile
index 46d7aa13a5..aa072082cc 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -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.
diff --git a/stdlib/canonicalize.c b/stdlib/canonicalize.c
index 8008a28cc2..a65b7f187c 100644
--- a/stdlib/canonicalize.c
+++ b/stdlib/canonicalize.c
@@ -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
index 0000000000..6f0171d200
--- /dev/null
+++ b/stdlib/test-canon.c
@@ -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);
+    }
+}