maint: update copyright
[gnulib.git] / lib / unlink.c
index cd8f4b1..a234be4 100644 (file)
@@ -1,6 +1,6 @@
 /* Work around unlink bugs.
 
-   Copyright (C) 2009 Free Software Foundation, Inc.
+   Copyright (C) 2009-2014 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
@@ -24,6 +24,8 @@
 #include <string.h>
 #include <sys/stat.h>
 
+#include "dosname.h"
+
 #undef unlink
 
 /* Remove file NAME.
@@ -38,48 +40,58 @@ rpl_unlink (char const *name)
   if (len && ISSLASH (name[len - 1]))
     {
       /* We can't unlink(2) something if it doesn't exist.  If it does
-        exist, then it resolved to a directory, due to the trailing
-        slash, and POSIX requires that the unlink attempt to remove
-        that directory (which would leave the symlink dangling).
-        Unfortunately, Solaris 9 is one of the platforms where the
-        root user can unlink directories, and we don't want to
-        cripple this behavior on real directories, even if it is
-        seldom needed (at any rate, it's nicer to let coreutils'
-        unlink(1) give the correct errno for non-root users).  But we
-        don't know whether name was an actual directory, or a symlink
-        to a directory; and due to the bug of ignoring trailing
-        slash, Solaris 9 would end up successfully unlinking the
-        symlink instead of the directory.  Technically, we could use
-        realpath to find the canonical directory name to attempt
-        deletion on.  But that is a lot of work for a corner case; so
-        we instead just use an lstat on the shortened name, and
-        reject symlinks with trailing slashes.  The root user of
-        unlink(1) will just have to live with the rule that they
-        can't delete a directory via a symlink.  */
+         exist, then it resolved to a directory, due to the trailing
+         slash, and POSIX requires that the unlink attempt to remove
+         that directory (which would leave the symlink dangling).
+         Unfortunately, Solaris 9 is one of the platforms where the
+         root user can unlink directories, and we don't want to
+         cripple this behavior on real directories, even if it is
+         seldom needed (at any rate, it's nicer to let coreutils'
+         unlink(1) give the correct errno for non-root users).  But we
+         don't know whether name was an actual directory, or a symlink
+         to a directory; and due to the bug of ignoring trailing
+         slash, Solaris 9 would end up successfully unlinking the
+         symlink instead of the directory.  Technically, we could use
+         realpath to find the canonical directory name to attempt
+         deletion on.  But that is a lot of work for a corner case; so
+         we instead just use an lstat on the shortened name, and
+         reject symlinks with trailing slashes.  The root user of
+         unlink(1) will just have to live with the rule that they
+         can't delete a directory via a symlink.  */
       struct stat st;
       result = lstat (name, &st);
       if (result == 0)
-       {
-         /* Trailing NUL will overwrite the trailing slash.  */
-         char *short_name = malloc (len);
-         if (!short_name)
-           {
-             errno = EPERM;
-             return -1;
-           }
-         memcpy (short_name, name, len);
-         while (len && ISSLASH (short_name[len - 1]))
-           short_name[--len] = '\0';
-         if (len && (lstat (short_name, &st) || S_ISLNK (st.st_mode)))
-           {
-             free (short_name);
-             errno = EPERM;
-             return -1;
-           }
-         free (short_name);
-       }
+        {
+          /* Trailing NUL will overwrite the trailing slash.  */
+          char *short_name = malloc (len);
+          if (!short_name)
+            {
+              errno = EPERM;
+              return -1;
+            }
+          memcpy (short_name, name, len);
+          while (len && ISSLASH (short_name[len - 1]))
+            short_name[--len] = '\0';
+          if (len && (lstat (short_name, &st) || S_ISLNK (st.st_mode)))
+            {
+              free (short_name);
+              errno = EPERM;
+              return -1;
+            }
+          free (short_name);
+        }
     }
   if (!result)
-    result = unlink (name);
+    {
+#if UNLINK_PARENT_BUG
+      if (len >= 2 && name[len - 1] == '.' && name[len - 2] == '.'
+          && (len == 2 || ISSLASH (name[len - 3])))
+        {
+          errno = EISDIR; /* could also use EPERM */
+          return -1;
+        }
+#endif
+      result = unlink (name);
+    }
   return result;
 }