openat: fix unlinkat bugs on Solaris 9
authorEric Blake <ebb9@byu.net>
Sat, 19 Sep 2009 01:39:06 +0000 (19:39 -0600)
committerEric Blake <ebb9@byu.net>
Sat, 19 Sep 2009 19:42:31 +0000 (13:42 -0600)
unlinkat(fd,"file/",0) mistakenly succeeded.

* lib/unlinkat.c (unlinkat): New file.
* modules/openat (Depends-on): Add unlink.
(Files): Distribute it.
* m4/openat.m4 (gl_FUNC_OPENAT): Mark unlinkat for replacement if
trailing slash behavior is broken.
* m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add witness.
* modules/unistd (Makefile.am): Substitute it.
* lib/unistd.in.h (unlinkat): Declare replacement.
* doc/posix-functions/unlinkat.texi (unlinkat): Document this.

Signed-off-by: Eric Blake <ebb9@byu.net>
ChangeLog
doc/posix-functions/unlinkat.texi
lib/unistd.in.h
lib/unlinkat.c [new file with mode: 0644]
m4/openat.m4
m4/unistd_h.m4
modules/openat
modules/unistd

index e79f9dd..99e0840 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,16 @@
 2009-09-19  Eric Blake  <ebb9@byu.net>
 
+       openat: fix unlinkat bugs on Solaris 9
+       * lib/unlinkat.c (unlinkat): New file.
+       * modules/openat (Depends-on): Add unlink.
+       (Files): Distribute it.
+       * m4/openat.m4 (gl_FUNC_OPENAT): Mark unlinkat for replacement if
+       trailing slash behavior is broken.
+       * m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add witness.
+       * modules/unistd (Makefile.am): Substitute it.
+       * lib/unistd.in.h (unlinkat): Declare replacement.
+       * doc/posix-functions/unlinkat.texi (unlinkat): Document this.
+
        openat: fix fstatat bugs on Solaris 9
        * lib/fstatat.c (rpl_fstatat): Copy recent fixes from lstat and
        stat.
index 99c4b3e..dfff9b9 100644 (file)
@@ -13,8 +13,35 @@ This function is missing on some platforms:
 glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX
 5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Cygwin 1.5.x, mingw, Interix 3.5, BeOS.
 But the replacement function is not safe to be used in libraries and is not multithread-safe.
+@item
+Some systems mistakenly succeed on @code{unlinkat(fd,"file/",flag)}:
+Solaris 9.
 @end itemize
 
 Portability problems not fixed by Gnulib:
 @itemize
+@item
+When @code{unlinkat(fd,name,AT_REMOVEDIR)} fails because the specified
+directory is not empty, the @code{errno} value is system dependent.
+@item
+POSIX requires that @code{unlinkdir(fd,"link-to-empty/",AT_REMOVEDIR)}
+remove @file{empty} and leave @file{link-to-empty} as a dangling
+symlink.  This is counter-intuitive, so some systems fail with
+@code{ENOTDIR} instead:
+glibc
+@item
+Some systems allow a superuser to unlink directories, even though this
+can cause file system corruption.  The error given if a process is not
+permitted to unlink directories varies across implementations; it is
+not always the POSIX value of @code{EPERM}.  Meanwhile, if a process
+has the ability to unlink directories, POSIX requires that
+@code{unlinkat(fd,"symlink-to-dir/",0)} remove @file{dir} and leave
+@file{symlink-to-dir} dangling; this behavior is counter-intuitive.
+The gnulib module unlinkdir can help determine whether code must be
+cautious of unlinking directories.
+@item
+Removing an open file is non-portable: On Unix this allows the programs that
+have the file already open to continue working with it; the file's storage
+is only freed when the no process has the file open any more.  On Windows,
+the attempt to remove an open file fails.
 @end itemize
index 6fa2831..322593d 100644 (file)
@@ -180,7 +180,11 @@ extern int unlink (char const *file);
 
 
 #if @GNULIB_UNLINKAT@
-# if !@HAVE_UNLINKAT@
+# if @REPLACE_UNLINKAT@
+#  undef unlinkat
+#  define unlinkat rpl_unlinkat
+# endif
+# if !@HAVE_UNLINKAT@ || @REPLACE_UNLINKAT@
 extern int unlinkat (int fd, char const *file, int flag);
 # endif
 #elif defined GNULIB_POSIXCHECK
diff --git a/lib/unlinkat.c b/lib/unlinkat.c
new file mode 100644 (file)
index 0000000..bf5d5b8
--- /dev/null
@@ -0,0 +1,77 @@
+/* Work around unlinkat bugs on Solaris 9.
+
+   Copyright (C) 2009 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
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by Eric Blake.  */
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "openat.h"
+
+#undef unlinkat
+
+/* unlinkat without AT_REMOVEDIR does not honor trailing / on Solaris
+   9.  Solve it in a similar manner to unlink.  */
+
+int
+rpl_unlinkat (int fd, char const *name, int flag)
+{
+  size_t len;
+  int result = 0;
+  /* rmdir behavior has no problems with trailing slash.  */
+  if (flag & AT_REMOVEDIR)
+    return unlinkat (fd, name, flag);
+
+  len = strlen (name);
+  if (len && ISSLASH (name[len - 1]))
+    {
+      /* See the lengthy comment in unlink.c why we disobey the POSIX
+        rule of letting unlink("link-to-dir/") attempt to unlink a
+        directory.  */
+      struct stat st;
+      result = lstatat (fd, 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 && (lstatat (fd, short_name, &st) || S_ISLNK (st.st_mode)))
+           {
+             free (short_name);
+             errno = EPERM;
+             return -1;
+           }
+         free (short_name);
+       }
+    }
+  if (!result)
+    result = unlinkat (fd, name, flag);
+  return result;
+}
index 445e952..e02a11c 100644 (file)
@@ -1,4 +1,4 @@
-# serial 21
+# serial 22
 # See if we need to use our replacement for Solaris' openat et al functions.
 
 dnl Copyright (C) 2004-2009 Free Software Foundation, Inc.
@@ -30,8 +30,12 @@ AC_DEFUN([gl_FUNC_OPENAT],
   case $ac_cv_func_openat+$ac_cv_func_lstat_dereferences_slashed_symlink in
   yes+yes) ;;
   yes+*)
+    # Solaris 9 has *at functions, but uniformly mishandles trailing
+    # slash in all of them.
     AC_LIBOBJ([fstatat])
     REPLACE_FSTATAT=1
+    AC_LIBOBJ([unlinkat])
+    REPLACE_UNLINKAT=1
     ;;
   *)
     HAVE_OPENAT=0
index d21cafc..648fd86 100644 (file)
@@ -1,4 +1,4 @@
-# unistd_h.m4 serial 28
+# unistd_h.m4 serial 29
 dnl Copyright (C) 2006-2009 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -101,6 +101,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS],
   REPLACE_LSEEK=0;        AC_SUBST([REPLACE_LSEEK])
   REPLACE_RMDIR=0;        AC_SUBST([REPLACE_RMDIR])
   REPLACE_UNLINK=0;       AC_SUBST([REPLACE_UNLINK])
+  REPLACE_UNLINKAT=0;     AC_SUBST([REPLACE_UNLINKAT])
   REPLACE_WRITE=0;        AC_SUBST([REPLACE_WRITE])
   UNISTD_H_HAVE_WINSOCK2_H=0; AC_SUBST([UNISTD_H_HAVE_WINSOCK2_H])
   UNISTD_H_HAVE_WINSOCK2_H_AND_USE_SOCKETS=0;
index 27e5c50..8cb1345 100644 (file)
@@ -11,6 +11,7 @@ lib/openat.c
 lib/openat.h
 lib/openat-priv.h
 lib/openat-proc.c
+lib/unlinkat.c
 m4/openat.m4
 m4/mode_t.m4
 
@@ -33,6 +34,7 @@ save-cwd
 stdbool
 sys_stat
 unistd
+unlink
 
 configure.ac:
 gl_FUNC_OPENAT
index 336381a..6cc6cda 100644 (file)
@@ -93,6 +93,7 @@ unistd.h: unistd.in.h
              -e 's|@''REPLACE_LSEEK''@|$(REPLACE_LSEEK)|g' \
              -e 's|@''REPLACE_RMDIR''@|$(REPLACE_RMDIR)|g' \
              -e 's|@''REPLACE_UNLINK''@|$(REPLACE_UNLINK)|g' \
+             -e 's|@''REPLACE_UNLINKAT''@|$(REPLACE_UNLINKAT)|g' \
              -e 's|@''REPLACE_WRITE''@|$(REPLACE_WRITE)|g' \
              -e 's|@''UNISTD_H_HAVE_WINSOCK2_H''@|$(UNISTD_H_HAVE_WINSOCK2_H)|g' \
              -e 's|@''UNISTD_H_HAVE_WINSOCK2_H_AND_USE_SOCKETS''@|$(UNISTD_H_HAVE_WINSOCK2_H_AND_USE_SOCKETS)|g' \