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>
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.
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
#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
--- /dev/null
+/* 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;
+}
-# 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.
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
-# 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,
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;
lib/openat.h
lib/openat-priv.h
lib/openat-proc.c
+lib/unlinkat.c
m4/openat.m4
m4/mode_t.m4
stdbool
sys_stat
unistd
+unlink
configure.ac:
gl_FUNC_OPENAT
-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' \