changeset 12045:2bd4d1ff29e9

openat: fix unlinkat bugs on Solaris 9 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>
author Eric Blake <ebb9@byu.net>
date Fri, 18 Sep 2009 19:39:06 -0600
parents 1b85d0321310
children fb58acddad72
files ChangeLog doc/posix-functions/unlinkat.texi lib/unistd.in.h lib/unlinkat.c m4/openat.m4 m4/unistd_h.m4 modules/openat modules/unistd
diffstat 8 files changed, 130 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- 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.
--- a/doc/posix-functions/unlinkat.texi
+++ b/doc/posix-functions/unlinkat.texi
@@ -13,8 +13,35 @@
 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
--- a/lib/unistd.in.h
+++ b/lib/unistd.in.h
@@ -180,7 +180,11 @@
 
 
 #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
new file mode 100644
--- /dev/null
+++ b/lib/unlinkat.c
@@ -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;
+}
--- a/m4/openat.m4
+++ b/m4/openat.m4
@@ -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 @@
   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
--- a/m4/unistd_h.m4
+++ b/m4/unistd_h.m4
@@ -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 @@
   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;
--- a/modules/openat
+++ b/modules/openat
@@ -11,6 +11,7 @@
 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 @@
 stdbool
 sys_stat
 unistd
+unlink
 
 configure.ac:
 gl_FUNC_OPENAT
--- a/modules/unistd
+++ b/modules/unistd
@@ -93,6 +93,7 @@
 	      -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' \