about summary refs log tree commit diff
path: root/stdlib/canonicalize.c
diff options
context:
space:
mode:
Diffstat (limited to 'stdlib/canonicalize.c')
-rw-r--r--stdlib/canonicalize.c244
1 files changed, 111 insertions, 133 deletions
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)