openat: detect Solaris fchownat bug
[pspp] / lib / canonicalize.c
index 403093055b1563036e49c708cac1dc5db9e487f8..65e73a703b286e62af10d1920d5644c77ff0df91 100644 (file)
 #include "xalloc.h"
 #include "xgetcwd.h"
 
-#if !(HAVE_CANONICALIZE_FILE_NAME || GNULIB_CANONICALIZE_LGPL)
+#ifndef DOUBLE_SLASH_IS_DISTINCT_ROOT
+# define DOUBLE_SLASH_IS_DISTINCT_ROOT 0
+#endif
+
+#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
    separators ('/') or symlinks.  All components must exist.
@@ -121,6 +126,8 @@ 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] == '/')
+       *dest++ = '/';
     }
 
   for (start = name; *start; start = end)
@@ -142,6 +149,9 @@ canonicalize_filename_mode (const char *name, canonicalize_mode_t can_mode)
          /* Back up to previous component, ignore if at root already.  */
          if (dest > rname + 1)
            while ((--dest)[-1] != '/');
+         if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1
+             && *dest == '/')
+           dest++;
        }
       else
        {
@@ -174,8 +184,12 @@ canonicalize_filename_mode (const char *name, canonicalize_mode_t can_mode)
              saved_errno = errno;
              if (can_mode == CAN_EXISTING)
                goto error;
-             if (can_mode == CAN_ALL_BUT_LAST && *end)
-               goto error;
+             if (can_mode == CAN_ALL_BUT_LAST)
+               {
+                 if (end[strspn (end, "/")] || saved_errno != ENOENT)
+                   goto error;
+                 continue;
+               }
              st.st_mode = 0;
            }
 
@@ -225,11 +239,21 @@ canonicalize_filename_mode (const char *name, canonicalize_mode_t can_mode)
              name = end = memcpy (extra_buf, buf, n);
 
              if (buf[0] == '/')
-               dest = rname + 1;       /* It's an absolute symlink */
+               {
+                 dest = rname + 1;     /* It's an absolute symlink */
+                 if (DOUBLE_SLASH_IS_DISTINCT_ROOT && buf[1] == '/')
+                   *dest++ = '/';
+               }
              else
-               /* Back up to previous component, ignore if at root already: */
-               if (dest > rname + 1)
-                 while ((--dest)[-1] != '/');
+               {
+                 /* Back up to previous component, ignore if at root
+                    already: */
+                 if (dest > rname + 1)
+                   while ((--dest)[-1] != '/');
+                 if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1
+                     && *dest == '/')
+                   dest++;
+               }
 
              free (buf);
            }
@@ -245,6 +269,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 == '/')
+    dest++;
   *dest = '\0';
 
   free (extra_buf);