canonicalize: fix // handling
[gnulib.git] / lib / canonicalize.c
index a329a33..ed094b7 100644 (file)
@@ -1,5 +1,5 @@
 /* Return the canonical absolute name of a given file.
-   Copyright (C) 1996-2011 Free Software Foundation, Inc.
+   Copyright (C) 1996-2012 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
 #include "xalloc.h"
 #include "xgetcwd.h"
 
+#define MULTIPLE_BITS_SET(i) (((i) & ((i) - 1)) != 0)
+
+/* In this file, we cannot handle file names longer than PATH_MAX.
+   On systems with no file name length limit, use a fallback.  */
+#ifndef PATH_MAX
+# define PATH_MAX 8192
+#endif
+
 #ifndef DOUBLE_SLASH_IS_DISTINCT_ROOT
 # define DOUBLE_SLASH_IS_DISTINCT_ROOT 0
 #endif
@@ -38,7 +46,7 @@
 #if !((HAVE_CANONICALIZE_FILE_NAME && FUNC_REALPATH_WORKS)      \
       || GNULIB_CANONICALIZE_LGPL)
 /* Return the canonical absolute name of file NAME.  A canonical name
-   does not contain any `.', `..' components nor any repeated file name
+   does not contain any ".", ".." components nor any repeated file name
    separators ('/') or symlinks.  All components must exist.
    The result is malloc'd.  */
 
@@ -75,9 +83,10 @@ seen_triple (Hash_table **ht, char const *filename, struct stat const *st)
 
 /* Return the canonical absolute name of file NAME, while treating
    missing elements according to CAN_MODE.  A canonical name
-   does not contain any `.', `..' components nor any repeated file name
-   separators ('/') or symlinks.  Whether components must exist
-   or not depends on canonicalize mode.  The result is malloc'd.  */
+   does not contain any ".", ".." components nor any repeated file name
+   separators ('/') or, depending on other CAN_MODE flags, symlinks.
+   Whether components must exist or not depends on canonicalize mode.
+   The result is malloc'd.  */
 
 char *
 canonicalize_filename_mode (const char *name, canonicalize_mode_t can_mode)
@@ -89,6 +98,16 @@ canonicalize_filename_mode (const char *name, canonicalize_mode_t can_mode)
   size_t extra_len = 0;
   Hash_table *ht = NULL;
   int saved_errno;
+  int can_flags = can_mode & ~CAN_MODE_MASK;
+  can_mode &= CAN_MODE_MASK;
+  bool logical = can_flags & CAN_NOLINKS;
+  /* Perhaps in future we might support CAN_NOALLOC with CAN_NOLINKS.  */
+
+  if (MULTIPLE_BITS_SET (can_mode))
+    {
+      errno = EINVAL;
+      return NULL;
+    }
 
   if (name == NULL)
     {
@@ -126,7 +145,7 @@ canonicalize_filename_mode (const char *name, canonicalize_mode_t can_mode)
       rname_limit = rname + PATH_MAX;
       rname[0] = '/';
       dest = rname + 1;
-      if (DOUBLE_SLASH_IS_DISTINCT_ROOT && name[1] == '/')
+      if (DOUBLE_SLASH_IS_DISTINCT_ROOT && name[1] == '/' && name[2] != '/')
         *dest++ = '/';
     }
 
@@ -150,7 +169,7 @@ canonicalize_filename_mode (const char *name, canonicalize_mode_t can_mode)
           if (dest > rname + 1)
             while ((--dest)[-1] != '/');
           if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1
-              && *dest == '/')
+              && *dest == '/' && dest[1] != '/')
             dest++;
         }
       else
@@ -179,7 +198,14 @@ canonicalize_filename_mode (const char *name, canonicalize_mode_t can_mode)
           dest += end - start;
           *dest = '\0';
 
-          if (lstat (rname, &st) != 0)
+          if (logical && (can_mode == CAN_MISSING))
+            {
+              /* Avoid the stat in this case as it's inconsequential.
+                 i.e. we're neither resolving symlinks or testing
+                 component existence.  */
+              st.st_mode = 0;
+            }
+          else if ((logical ? stat (rname, &st) : lstat (rname, &st)) != 0)
             {
               saved_errno = errno;
               if (can_mode == CAN_EXISTING)
@@ -241,7 +267,8 @@ canonicalize_filename_mode (const char *name, canonicalize_mode_t can_mode)
               if (buf[0] == '/')
                 {
                   dest = rname + 1;     /* It's an absolute symlink */
-                  if (DOUBLE_SLASH_IS_DISTINCT_ROOT && buf[1] == '/')
+                  if (DOUBLE_SLASH_IS_DISTINCT_ROOT
+                      && buf[1] == '/' && buf[2] != '/')
                     *dest++ = '/';
                 }
               else
@@ -251,7 +278,7 @@ canonicalize_filename_mode (const char *name, canonicalize_mode_t can_mode)
                   if (dest > rname + 1)
                     while ((--dest)[-1] != '/');
                   if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1
-                      && *dest == '/')
+                      && *dest == '/' && dest[1] != '/')
                     dest++;
                 }
 
@@ -269,7 +296,8 @@ canonicalize_filename_mode (const char *name, canonicalize_mode_t can_mode)
     }
   if (dest > rname + 1 && dest[-1] == '/')
     --dest;
-  if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1 && *dest == '/')
+  if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1
+      && *dest == '/' && dest[1] != '/')
     dest++;
   *dest = '\0';
   if (rname_limit != dest + 1)