bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: improve unlink on Solaris


From: Eric Blake
Subject: Re: improve unlink on Solaris
Date: Sat, 19 Sep 2009 14:04:54 -0600
User-agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.23) Gecko/20090812 Thunderbird/2.0.0.23 Mnenhy/0.7.6.666

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

According to Eric Blake on 9/17/2009 5:24 PM:
> Actually, on review, I already see a reason to rebase - test-unlink.c tries 
> to 
> unlink symlink-to-dir/ without first checking whether that is safe if done as 
> root.
> 
> Eric Blake (6):
>       stat: new module, for mingw bug
>       stat: fix Solaris 9 bug
>       stat: add as dependency to other modules
>       lstat: fix Solaris 9 bug
>       unlink: new module, for Solaris 9 bug
>       remove: new module, for mingw and Solaris 9 bugs

Here's the polished series; now pushed.  I've tested on Linux, cygwin 1.5,
cygwin 1.7, mingw, OpenBSD, Solaris 8, 9, 10.

Eric Blake (12):
      [1/12] stat: new module, for mingw bug
Depending on the current directory, either stat(".",buf) or stat("./",buf)
would fail on mingw.  I did not find a way to use an object-like macro
while still keeping struct stat alive, so I had to use a function-like
macro (and some of the earlier iterations of my later patches fell foul
when I tried to pass &stat as a function pointer, cleaned up by instead
passing via a function pointer to a wrapper function).  Because mingw
lacks lstat, this means that lstat is also an object-like macro in some
instances.

      [2/12] stat: fix Solaris 9 bug
stat("file/",buf) mistakenly succeeded.  This patch required some
refactoring in stat.c, since the two bugs are distinct enough that
rpl_stat should not have to deal with both bugs at once.

      [3/12] stat: add as dependency to other modules
Hopefully I got the right set of modules.  I left out modules like openat
if they already depended on lstat (since lstat now depends on stat), I
also left out modules that used stat on well-controlled non-directory
strings and thus did not trigger the bugs from [1/12] or [2/12].

      [4/12] lstat: fix Solaris 9 bug
lstat("file/",buf) mistakenly succeeded.  But since I just fixed the same
bug in stat, the fix to lstat.c is actually fewer lines of code.

      [5/12] unlink: new module, for Solaris 9 bug
unlink("file/") mistakenly succeeded.  unlink is conceptually harder to
fix than stat - in the former, just try it, then fix any mistakes; in the
latter, you can't try it unless you can prove it will do the right thing.
 I intentionally did not replace unlink on Solaris 10, which means there
are still platforms where unlink("link-to-dir/") as root will unlink "dir"
and leave "link-to-dir" dangling (per POSIX), in contrast to the GNU
behavior of failing with ENOTDIR.  But I also intentionally did not make
the Solaris 9 replacement try to match Solaris 10 behavior (the cost of a
series of readlinks just to do this corner case, which will fail for all
but non-root, is atrocious).  It took me a couple of iterations before
this final version to be satisfied that the new test should be safe to run
as root (that is, the test does not try to unlink a directory if it can't
prove that such an action would fail).

      [6/12] remove: new module, for mingw and Solaris 9 bugs
mingw obeys C89 but not POSIX, so directories are not handled.  Solaris 9
remove("file/") mistakenly succeeded (because of the unlink bug).  The fix
to both issues looks deceptively simple, thanks to the prerequisite patches.

      [7/12] test-fstatat: new test, to expose Solaris 9 bugs
Share the lstat and stat tests ([1/12], [2/12], [4/12]) with fstatat;
proves that Solaris 9 has a bug in fstatat.

      [8/12] test-unlinkat: enhance test, to expose Solaris 9 bug
Share the unlink tests ([5/12]) with unlinkat; rmdir tests were already
shared but needed a bit of refactoring to handle symlink avoidance on
mingw.  Proves that Solaris 9 has a bug in unlinkat.

      [9/12] openat: fix fstatat bugs on Solaris 9
fstatat(fd,"file/",buf,flag) mistakenly succeeded.  This reuses the same
fixes as in [2/12] and [4/12], but does not need to worry about the fixes
in [1/12] since mingw lacks fstatat.

      [10/12] openat: fix unlinkat bugs on Solaris 9
unlinkat(fd,"file/",0) mistakenly succeeded.  This reuses the same fix as
in [5/12].

      [11/12] openat: move fstatat and unlinkat into correct files
Code motion, with no semantic change.  It was easier to put fstatat and
unlinkat in the same file, whether we were compiling the /proc/self/fd
version for systems without *at, or compiling the rpl_ version for Solaris
9; it also makes it easier to provide rpl_openat.

      [12/12] openat: fix openat bugs on Solaris 9
openat(fd,"file/",O_RDONLY) mistakenly succeeded.  This borrows the
trailing slash fixes from lib/open.c, but does not worry about /dev/null
or fchdir fixes since Solaris 9 already has fchdir.  Finally fixes
test-openat-safer, which fails as part of the coreutils 7.6 testsuite on
Solaris 9.  I would still like to make test-open, test-open-safer, and
test-openat share more code paths with test-openat-safer, but that can
come at a later date when I implement O_CLOEXEC.

Several of these bug fixes are visible to the command line using
coreutils, for example by using stat(1) or unlink(1), so this can probably
be documented in a coreutils NEWS item the same as was done in 7.6 for
link(1).  Also, at least the stat(2) bug is visible from find(1):

pre:
$ touch file
$ find file/
file/

post, or on working platform:
$ touch file
$ find file/
find: `file/': Not a directory

and I suspect (although I haven't tried to prove) that something like:

$ touch file
$ ln -s file/ link
$ find -L ...

might expose openat() failures through fts().

- --
Don't work too hard, make some time for fun as well!

Eric Blake             address@hidden
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Public key at home.comcast.net/~ericblake/eblake.gpg
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAkq1OWUACgkQ84KuGfSFAYCMMwCfU71WiDUD+sWKB6Z99UGSSpCV
kCoAn02AnbHamU+Phi4eZQxbzv+ebbJp
=0AUC
-----END PGP SIGNATURE-----
>From 5794d89bd24981d2974f1504a04bf09b7efaf251 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Tue, 15 Sep 2009 07:11:40 -0600
Subject: [PATCH 01/12] stat: new module, for mingw bug

Depending on the current directory, either stat(".",buf) or
stat("./",buf) would fail on mingw.

* modules/stat: New file.
* lib/stat.c: Likewise.
* m4/stat.m4 (gl_FUNC_STAT): Likewise.
* m4/sys_stat_h.m4 (gl_SYS_STAT_H_DEFAULTS): Add witnesses.
* modules/sys_stat (Makefile.am): Use them.
* lib/sys_stat.in.h (stat): Declare replacement.
* lib/openat.c (fstatat): Deal with lstat and stat being function
macros.
* modules/openat (Depends-on): Add inline.
* MODULES.html.sh (systems lacking POSIX:2008): Mention module.
* doc/posix-functions/stat.texi (stat): Likewise.
* modules/stat-tests: New test.
* tests/test-stat.c: Likewise.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                     |   17 +++++++++
 MODULES.html.sh               |    1 +
 doc/posix-functions/stat.texi |   14 +++++++-
 lib/openat.c                  |   20 ++++++++++-
 lib/stat.c                    |   78 +++++++++++++++++++++++++++++++++++++++
 lib/sys_stat.in.h             |   16 ++++++++
 m4/stat.m4                    |   31 ++++++++++++++++
 m4/sys_stat_h.m4              |    4 ++-
 modules/openat                |    1 +
 modules/stat                  |   26 +++++++++++++
 modules/stat-tests            |   12 ++++++
 modules/sys_stat              |    2 +
 tests/test-stat.c             |   81 +++++++++++++++++++++++++++++++++++++++++
 13 files changed, 300 insertions(+), 3 deletions(-)
 create mode 100644 lib/stat.c
 create mode 100644 m4/stat.m4
 create mode 100644 modules/stat
 create mode 100644 modules/stat-tests
 create mode 100644 tests/test-stat.c

diff --git a/ChangeLog b/ChangeLog
index fab0733..fde8d07 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,20 @@
+2009-09-19  Eric Blake  <address@hidden>
+
+       stat: new module, for mingw bug
+       * modules/stat: New file.
+       * lib/stat.c: Likewise.
+       * m4/stat.m4 (gl_FUNC_STAT): Likewise.
+       * m4/sys_stat_h.m4 (gl_SYS_STAT_H_DEFAULTS): Add witnesses.
+       * modules/sys_stat (Makefile.am): Use them.
+       * lib/sys_stat.in.h (stat): Declare replacement.
+       * lib/openat.c (fstatat): Deal with lstat and stat being function
+       macros.
+       * modules/openat (Depends-on): Add inline.
+       * MODULES.html.sh (systems lacking POSIX:2008): Mention module.
+       * doc/posix-functions/stat.texi (stat): Likewise.
+       * modules/stat-tests: New test.
+       * tests/test-stat.c: Likewise.
+
 2009-09-19  Jim Meyering  <address@hidden>

        syntax-check: detect unnecessary inclusion of canonicalize.h
diff --git a/MODULES.html.sh b/MODULES.html.sh
index 76741b3..fbea183 100755
--- a/MODULES.html.sh
+++ b/MODULES.html.sh
@@ -2331,6 +2331,7 @@ func_all_modules ()
   func_module socket
   func_module spawn
   func_module sprintf-posix
+  func_module stat
   func_module strdup-posix
   func_module string
   func_module strings
diff --git a/doc/posix-functions/stat.texi b/doc/posix-functions/stat.texi
index 97992e4..a7011ed 100644
--- a/doc/posix-functions/stat.texi
+++ b/doc/posix-functions/stat.texi
@@ -4,10 +4,14 @@ stat

 POSIX specification: 
@url{http://www.opengroup.org/onlinepubs/9699919799/functions/stat.html}

-Gnulib module: ---
+Gnulib module: stat

 Portability problems fixed by Gnulib:
 @itemize
address@hidden
+On some platforms, @code{stat(".",buf)} and @code{stat("./",buf)} give
+different results:
+mingw.
 @end itemize

 Portability problems not fixed by Gnulib:
@@ -19,4 +23,12 @@ stat
 @item
 Cygwin's @code{stat} function sometimes sets @code{errno} to @code{EACCES} when
 @code{ENOENT} would be more appropriate.
address@hidden
+On Windows platforms (excluding Cygwin), @code{st_ino} is always 0.
address@hidden
+Because of the definition of @code{struct stat}, it is not possible to
+portably replace @code{stat} via an object-like macro.  Therefore,
+expressions such as @code{(islnk ? lstat : stat) (name, buf)} are not
+portable, and should instead be written @code{islnk ? lstat (name,
+buf) : stat (name, buf)}.
 @end itemize
diff --git a/lib/openat.c b/lib/openat.c
index d22d830..a0d0ab4 100644
--- a/lib/openat.c
+++ b/lib/openat.c
@@ -157,6 +157,24 @@ openat_needs_fchdir (void)
   return needs_fchdir;
 }

+/* On mingw, the gnulib <sys/stat.h> defines `stat' as a function-like
+   macro; but using it in AT_FUNC_F2 causes compilation failure
+   because the preprocessor sees a use of a macro that requires two
+   arguments but is only given one.  Hence, we need an inline
+   forwarder to get past the preprocessor.  */
+static inline int
+stat_func (char const *name, struct stat *st)
+{
+  return stat (name, st);
+}
+
+/* Likewise, if there is no native `lstat', then the gnulib
+   <sys/stat.h> defined it as stat, which also needs adjustment.  */
+#if !HAVE_LSTAT
+# undef lstat
+# define lstat stat_func
+#endif
+
 /* Replacement for Solaris' function by the same name.
    <http://www.google.com/search?q=fstatat+site:docs.sun.com>
    First, try to simulate it via l?stat ("/proc/self/fd/FD/FILE").
@@ -167,7 +185,7 @@ openat_needs_fchdir (void)

 #define AT_FUNC_NAME fstatat
 #define AT_FUNC_F1 lstat
-#define AT_FUNC_F2 stat
+#define AT_FUNC_F2 stat_func
 #define AT_FUNC_USE_F1_COND AT_SYMLINK_NOFOLLOW
 #define AT_FUNC_POST_FILE_PARAM_DECLS , struct stat *st, int flag
 #define AT_FUNC_POST_FILE_ARGS        , st
diff --git a/lib/stat.c b/lib/stat.c
new file mode 100644
index 0000000..c3400d5
--- /dev/null
+++ b/lib/stat.c
@@ -0,0 +1,78 @@
+/* Work around platform bugs in stat.
+   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 <sys/stat.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <string.h>
+
+#undef stat
+
+/* For now, mingw is the only known platform where stat(".") and
+   stat("./") give different results.  Mingw stat has other bugs (such
+   as st_ino always being 0 on success) which this wrapper does not
+   work around.  But at least this implementation provides the ability
+   to emulate fchdir correctly.  */
+
+int
+rpl_stat (char const *name, struct stat *st)
+{
+  int result = stat (name, st);
+  if (result == -1 && errno == ENOENT)
+    {
+      /* Due to mingw's oddities, there are some directories (like
+         c:\) where stat() only succeeds with a trailing slash, and
+         other directories (like c:\windows) where stat() only
+         succeeds without a trailing slash.  But we want the two to be
+         synonymous, since chdir() manages either style.  Likewise, Mingw also
+         reports ENOENT for names longer than PATH_MAX, when we want
+         ENAMETOOLONG, and for stat("file/"), when we want ENOTDIR.
+         Fortunately, mingw PATH_MAX is small enough for stack
+         allocation.  */
+      char fixed_name[PATH_MAX + 1] = {0};
+      size_t len = strlen (name);
+      bool check_dir = false;
+      if (PATH_MAX <= len)
+        errno = ENAMETOOLONG;
+      else if (len)
+        {
+          strcpy (fixed_name, name);
+          if (ISSLASH (fixed_name[len - 1]))
+            {
+              check_dir = true;
+              while (len && ISSLASH (fixed_name[len - 1]))
+                fixed_name[--len] = '\0';
+              if (!len)
+                fixed_name[0] = '/';
+            }
+          else
+            fixed_name[len++] = '/';
+          result = stat (fixed_name, st);
+          if (result == 0 && check_dir && !S_ISDIR (st->st_mode))
+            {
+              result = -1;
+              errno = ENOTDIR;
+            }
+        }
+    }
+  return result;
+}
diff --git a/lib/sys_stat.in.h b/lib/sys_stat.in.h
index 869cb0f..cc43822 100644
--- a/lib/sys_stat.in.h
+++ b/lib/sys_stat.in.h
@@ -302,6 +302,22 @@ extern int rpl_lstat (const char *name, struct stat *buf);
    lstat (p, b))
 #endif

+#if @GNULIB_STAT@
+# if @REPLACE_STAT@
+/* We can't use the object-like #define stat rpl_stat, because of
+   struct stat.  This means that rpl_stat will not be used if the user
+   does (stat)(a,b).  Oh well.  */
+#  undef stat
+#  define stat(name, st) rpl_stat (name, st)
+extern int stat (const char *name, struct stat *buf);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef stat
+# define stat(p,b)                                                     \
+  (GL_LINK_WARNING ("stat is unportable - "                            \
+                   "use gnulib module stat for portability"),          \
+   stat (p, b))
+#endif

 #if @GNULIB_FCHMODAT@
 # if address@hidden@
diff --git a/m4/stat.m4 b/m4/stat.m4
new file mode 100644
index 0000000..9cc0b4a
--- /dev/null
+++ b/m4/stat.m4
@@ -0,0 +1,31 @@
+# serial 1
+
+# Copyright (C) 2009 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_FUNC_STAT],
+[
+  AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
+  AC_REQUIRE([gl_AC_DOS])
+  AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS])
+  dnl mingw is the only known platform where stat(".") and stat("./") differ
+  AC_CACHE_CHECK([whether stat handles trailing slashes],
+      [gl_cv_func_stat_works],
+      [AC_RUN_IFELSE(
+         [AC_LANG_PROGRAM(
+           [[#include <sys/stat.h>
+]], [[struct stat st; return stat (".", &st) != stat ("./", &st);]])],
+         [gl_cv_func_stat_works=yes], [gl_cv_func_stat_works=no],
+         [case $host_os in
+            mingw*) gl_cv_func_stat_works="guessing no";;
+            *) gl_cv_func_stat_works="guessing yes";;
+          esac])])
+  case $gl_cv_func_stat_works in
+    *yes) ;;
+    *) REPLACE_STAT=1
+       AC_LIBOBJ([stat]);;
+  esac
+])
diff --git a/m4/sys_stat_h.m4 b/m4/sys_stat_h.m4
index 4c01807..df7b238 100644
--- a/m4/sys_stat_h.m4
+++ b/m4/sys_stat_h.m4
@@ -1,4 +1,4 @@
-# sys_stat_h.m4 serial 15   -*- Autoconf -*-
+# sys_stat_h.m4 serial 16   -*- Autoconf -*-
 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,
@@ -56,6 +56,7 @@ AC_DEFUN([gl_SYS_STAT_H_DEFAULTS],
   GNULIB_MKDIRAT=0;   AC_SUBST([GNULIB_MKDIRAT])
   GNULIB_MKFIFOAT=0;  AC_SUBST([GNULIB_MKFIFOAT])
   GNULIB_MKNODAT=0;   AC_SUBST([GNULIB_MKNODAT])
+  GNULIB_STAT=0;      AC_SUBST([GNULIB_STAT])
   dnl Assume proper GNU behavior unless another module says otherwise.
   HAVE_FCHMODAT=1;    AC_SUBST([HAVE_FCHMODAT])
   HAVE_FSTATAT=1;     AC_SUBST([HAVE_FSTATAT])
@@ -67,4 +68,5 @@ AC_DEFUN([gl_SYS_STAT_H_DEFAULTS],
   REPLACE_FSTATAT=0;  AC_SUBST([REPLACE_FSTATAT])
   REPLACE_LSTAT=0;    AC_SUBST([REPLACE_LSTAT])
   REPLACE_MKDIR=0;    AC_SUBST([REPLACE_MKDIR])
+  REPLACE_STAT=0;     AC_SUBST([REPLACE_STAT])
 ])
diff --git a/modules/openat b/modules/openat
index 276882b..27e5c50 100644
--- a/modules/openat
+++ b/modules/openat
@@ -22,6 +22,7 @@ fchdir
 fcntl-h
 fdopendir
 gettext-h
+inline
 intprops
 lchown
 lstat
diff --git a/modules/stat b/modules/stat
new file mode 100644
index 0000000..9729b84
--- /dev/null
+++ b/modules/stat
@@ -0,0 +1,26 @@
+Description:
+stat(): query file information
+
+Files:
+lib/stat.c
+m4/dos.m4
+m4/stat.m4
+
+Depends-on:
+stdbool
+sys_stat
+
+configure.ac:
+gl_FUNC_STAT
+gl_SYS_STAT_MODULE_INDICATOR([stat])
+
+Makefile.am:
+
+Include:
+<sys/stat.h>
+
+License:
+LGPLv2+
+
+Maintainer:
+Eric Blake
diff --git a/modules/stat-tests b/modules/stat-tests
new file mode 100644
index 0000000..3d31b52
--- /dev/null
+++ b/modules/stat-tests
@@ -0,0 +1,12 @@
+Files:
+tests/test-stat.c
+
+Depends-on:
+pathmax
+same-inode
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-stat
+check_PROGRAMS += test-stat
diff --git a/modules/sys_stat b/modules/sys_stat
index b2be2ed..a4abafe 100644
--- a/modules/sys_stat
+++ b/modules/sys_stat
@@ -33,6 +33,7 @@ sys/stat.h: sys_stat.in.h
              -e 's|@''GNULIB_MKDIRAT''@|$(GNULIB_MKDIRAT)|g' \
              -e 's|@''GNULIB_MKFIFOAT''@|$(GNULIB_MKFIFOAT)|g' \
              -e 's|@''GNULIB_MKNODAT''@|$(GNULIB_MKNODAT)|g' \
+             -e 's|@''GNULIB_STAT''@|$(GNULIB_STAT)|g' \
              -e 's|@''HAVE_FCHMODAT''@|$(HAVE_FCHMODAT)|g' \
              -e 's|@''HAVE_FSTATAT''@|$(HAVE_FSTATAT)|g' \
              -e 's|@''HAVE_LCHMOD''@|$(HAVE_LCHMOD)|g' \
@@ -44,6 +45,7 @@ sys/stat.h: sys_stat.in.h
              -e 's|@''REPLACE_FSTATAT''@|$(REPLACE_FSTATAT)|g' \
              -e 's|@''REPLACE_LSTAT''@|$(REPLACE_LSTAT)|g' \
              -e 's|@''REPLACE_MKDIR''@|$(REPLACE_MKDIR)|g' \
+             -e 's|@''REPLACE_STAT''@|$(REPLACE_STAT)|g' \
              -e '/definition of GL_LINK_WARNING/r $(LINK_WARNING_H)' \
              < $(srcdir)/sys_stat.in.h; \
        } > address@hidden && \
diff --git a/tests/test-stat.c b/tests/test-stat.c
new file mode 100644
index 0000000..337c819
--- /dev/null
+++ b/tests/test-stat.c
@@ -0,0 +1,81 @@
+/* Tests of stat.
+   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 <address@hidden>, 2009.  */
+
+#include <config.h>
+
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "pathmax.h"
+#include "same-inode.h"
+
+#define ASSERT(expr) \
+  do                                                                         \
+    {                                                                        \
+      if (!(expr))                                                           \
+       {                                                                    \
+         fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__);  \
+         fflush (stderr);                                                   \
+         abort ();                                                          \
+       }                                                                    \
+    }                                                                        \
+  while (0)
+
+#define BASE "test-stat.t"
+
+int
+main ()
+{
+  struct stat st1;
+  struct stat st2;
+  char cwd[PATH_MAX];
+
+  ASSERT (getcwd (cwd, PATH_MAX) == cwd);
+  ASSERT (stat (".", &st1) == 0);
+  ASSERT (stat ("./", &st2) == 0);
+  ASSERT (SAME_INODE (st1, st2));
+  ASSERT (stat (cwd, &st2) == 0);
+  ASSERT (SAME_INODE (st1, st2));
+  ASSERT (stat ("/", &st1) == 0);
+  ASSERT (stat ("///", &st2) == 0);
+  ASSERT (SAME_INODE (st1, st2));
+
+  errno = 0;
+  ASSERT (stat ("", &st1) == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (stat ("nosuch", &st1) == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (stat ("nosuch/", &st1) == -1);
+  ASSERT (errno == ENOENT);
+
+  ASSERT (close (creat (BASE "file", 0600)) == 0);
+  ASSERT (stat (BASE "file", &st1) == 0);
+  errno = 0;
+  ASSERT (stat (BASE "file/", &st1) == -1);
+  ASSERT (errno == ENOTDIR);
+  ASSERT (unlink (BASE "file") == 0);
+
+  return 0;
+}
-- 
1.6.5.rc1


>From c9d72f69bd201a1ab31464d91f234ea1817fe0e1 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Tue, 15 Sep 2009 17:08:39 -0600
Subject: [PATCH 02/12] stat: fix Solaris 9 bug

stat("file/",buf) mistakenly succeeded.

* m4/stat.m4 (gl_FUNC_STAT): Detect Solaris 9 bug with trailing
slash.
* lib/stat.c (rpl_stat): Work around it.
* doc/posix-functions/stat.texi (stat): Update documentation.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                     |    6 +++++
 doc/posix-functions/stat.texi |    4 +++
 lib/stat.c                    |   44 ++++++++++++++++++++++++++++++++--------
 m4/stat.m4                    |   38 ++++++++++++++++++++++++++---------
 4 files changed, 73 insertions(+), 19 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index fde8d07..d0aa2c4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2009-09-19  Eric Blake  <address@hidden>

+       stat: fix Solaris 9 bug
+       * m4/stat.m4 (gl_FUNC_STAT): Detect Solaris 9 bug with trailing
+       slash.
+       * lib/stat.c (rpl_stat): Work around it.
+       * doc/posix-functions/stat.texi (stat): Update documentation.
+
        stat: new module, for mingw bug
        * modules/stat: New file.
        * lib/stat.c: Likewise.
diff --git a/doc/posix-functions/stat.texi b/doc/posix-functions/stat.texi
index a7011ed..5fdb683 100644
--- a/doc/posix-functions/stat.texi
+++ b/doc/posix-functions/stat.texi
@@ -9,6 +9,10 @@ stat
 Portability problems fixed by Gnulib:
 @itemize
 @item
+On some platforms, @code{stat("file/",buf)} succeeds instead of
+failing with @code{ENOTDIR}.
+Solaris 9.
address@hidden
 On some platforms, @code{stat(".",buf)} and @code{stat("./",buf)} give
 different results:
 mingw.
diff --git a/lib/stat.c b/lib/stat.c
index c3400d5..8aa7709 100644
--- a/lib/stat.c
+++ b/lib/stat.c
@@ -18,6 +18,19 @@

 #include <config.h>

+/* Get the original definition of stat.  It might be defined as a macro.  */
+#define __need_system_sys_stat_h
+#include <sys/types.h>
+#include <sys/stat.h>
+#undef __need_system_sys_stat_h
+
+static inline int
+orig_stat (const char *filename, struct stat *buf)
+{
+  return stat (filename, buf);
+}
+
+/* Specification.  */
 #include <sys/stat.h>

 #include <errno.h>
@@ -25,18 +38,30 @@
 #include <stdbool.h>
 #include <string.h>

-#undef stat
-
-/* For now, mingw is the only known platform where stat(".") and
-   stat("./") give different results.  Mingw stat has other bugs (such
-   as st_ino always being 0 on success) which this wrapper does not
-   work around.  But at least this implementation provides the ability
-   to emulate fchdir correctly.  */
+/* Store information about NAME into ST.  Work around bugs with
+   trailing slashes.  Mingw has other bugs (such as st_ino always
+   being 0 on success) which this wrapper does not work around.  But
+   at least this implementation provides the ability to emulate fchdir
+   correctly.  */

 int
 rpl_stat (char const *name, struct stat *st)
 {
-  int result = stat (name, st);
+  int result = orig_stat (name, st);
+#if REPLACE_FUNC_STAT_FILE
+  /* Solaris 9 mistakenly succeeds when given a non-directory with a
+     trailing slash.  */
+  if (result == 0 && !S_ISDIR (st->st_mode))
+    {
+      size_t len = strlen (name);
+      if (ISSLASH (name[len - 1]))
+       {
+         errno = ENOTDIR;
+         return -1;
+       }
+    }
+#endif /* REPLACE_FUNC_STAT_FILE */
+#if REPLACE_FUNC_STAT_DIR
   if (result == -1 && errno == ENOENT)
     {
       /* Due to mingw's oddities, there are some directories (like
@@ -66,7 +91,7 @@ rpl_stat (char const *name, struct stat *st)
             }
           else
             fixed_name[len++] = '/';
-          result = stat (fixed_name, st);
+          result = orig_stat (fixed_name, st);
           if (result == 0 && check_dir && !S_ISDIR (st->st_mode))
             {
               result = -1;
@@ -74,5 +99,6 @@ rpl_stat (char const *name, struct stat *st)
             }
         }
     }
+#endif /* REPLACE_FUNC_STAT_DIR */
   return result;
 }
diff --git a/m4/stat.m4 b/m4/stat.m4
index 9cc0b4a..0a10de1 100644
--- a/m4/stat.m4
+++ b/m4/stat.m4
@@ -1,4 +1,4 @@
-# serial 1
+# serial 2

 # Copyright (C) 2009 Free Software Foundation, Inc.
 #
@@ -12,20 +12,38 @@ AC_DEFUN([gl_FUNC_STAT],
   AC_REQUIRE([gl_AC_DOS])
   AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS])
   dnl mingw is the only known platform where stat(".") and stat("./") differ
-  AC_CACHE_CHECK([whether stat handles trailing slashes],
-      [gl_cv_func_stat_works],
+  AC_CACHE_CHECK([whether stat handles trailing slashes on directories],
+      [gl_cv_func_stat_dir_slash],
       [AC_RUN_IFELSE(
          [AC_LANG_PROGRAM(
            [[#include <sys/stat.h>
 ]], [[struct stat st; return stat (".", &st) != stat ("./", &st);]])],
-         [gl_cv_func_stat_works=yes], [gl_cv_func_stat_works=no],
+         [gl_cv_func_stat_dir_slash=yes], [gl_cv_func_stat_dir_slash=no],
          [case $host_os in
-            mingw*) gl_cv_func_stat_works="guessing no";;
-            *) gl_cv_func_stat_works="guessing yes";;
+            mingw*) gl_cv_func_stat_dir_slash="guessing no";;
+            *) gl_cv_func_stat_dir_slash="guessing yes";;
           esac])])
-  case $gl_cv_func_stat_works in
-    *yes) ;;
-    *) REPLACE_STAT=1
-       AC_LIBOBJ([stat]);;
+  dnl Solaris 9 mistakenly succeeds on stat("file/")
+  AC_CACHE_CHECK([whether stat handles trailing slashes on files],
+      [gl_cv_func_stat_file_slash],
+      [touch conftest.tmp
+       AC_RUN_IFELSE(
+         [AC_LANG_PROGRAM(
+           [[#include <sys/stat.h>
+]], [[struct stat st; return !stat ("conftest.tmp/", &st);]])],
+         [gl_cv_func_stat_file_slash=yes], [gl_cv_func_stat_file_slash=no],
+         [gl_cv_func_stat_file_slash="guessing no"])])
+  case $gl_cv_func_stat_dir_slash in
+    *no) REPLACE_STAT=1
+      AC_DEFINE([REPLACE_FUNC_STAT_DIR], [1], [Define to 1 if stat needs
+        help when passed a directory name with a trailing slash]);;
+  esac
+  case $gl_cv_func_stat_file_slash in
+    *no) REPLACE_STAT=1
+      AC_DEFINE([REPLACE_FUNC_STAT_FILE], [1], [Define to 1 if stat needs
+        help when passed a file name with a trailing slash]);;
   esac
+  if test $REPLACE_STAT = 1; then
+    AC_LIBOBJ([stat])
+  fi
 ])
-- 
1.6.5.rc1


>From ba1652925190702bec29e2c5129ee857576853d6 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Tue, 15 Sep 2009 14:43:14 -0600
Subject: [PATCH 03/12] stat: add as dependency to other modules

Modules that use stat but are not in this list don't trip any of
the bugs that the stat module fixes.

* modules/chown (Depends-on): Add stat.
* modules/euidaccess (Depends-on): Likewise.
* modules/fchdir (Depends-on): Likewise.
* modules/isdir (Depends-on): Likewise.
* modules/link (Depends-on): Likewise.
* modules/lstat (Depends-on): Likewise.
* modules/mkdir-p (Depends-on): Likewise.
* modules/modechange (Depends-on): Likewise.
* modules/open (Depends-on): Likewise.
* modules/readlink (Depends-on): Likewise.
* modules/same (Depends-on): Likewise.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog          |   13 +++++++++++++
 modules/chown      |    1 +
 modules/euidaccess |    1 +
 modules/fchdir     |    1 +
 modules/isdir      |    2 +-
 modules/link       |    1 +
 modules/lstat      |    1 +
 modules/mkdir-p    |    1 +
 modules/modechange |    1 +
 modules/open       |    1 +
 modules/readlink   |    1 +
 modules/same       |    1 +
 12 files changed, 24 insertions(+), 1 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index d0aa2c4..1002722 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,18 @@
 2009-09-19  Eric Blake  <address@hidden>

+       stat: add as dependency to other modules
+       * modules/chown (Depends-on): Add stat.
+       * modules/euidaccess (Depends-on): Likewise.
+       * modules/fchdir (Depends-on): Likewise.
+       * modules/isdir (Depends-on): Likewise.
+       * modules/link (Depends-on): Likewise.
+       * modules/lstat (Depends-on): Likewise.
+       * modules/mkdir-p (Depends-on): Likewise.
+       * modules/modechange (Depends-on): Likewise.
+       * modules/open (Depends-on): Likewise.
+       * modules/readlink (Depends-on): Likewise.
+       * modules/same (Depends-on): Likewise.
+
        stat: fix Solaris 9 bug
        * m4/stat.m4 (gl_FUNC_STAT): Detect Solaris 9 bug with trailing
        slash.
diff --git a/modules/chown b/modules/chown
index cf99210..88d0cd4 100644
--- a/modules/chown
+++ b/modules/chown
@@ -9,6 +9,7 @@ m4/chown.m4
 Depends-on:
 open
 unistd
+stat
 sys_stat

 configure.ac:
diff --git a/modules/euidaccess b/modules/euidaccess
index 47d9716..bf329c6 100644
--- a/modules/euidaccess
+++ b/modules/euidaccess
@@ -9,6 +9,7 @@ Depends-on:
 unistd
 extensions
 group-member
+stat
 sys_stat

 configure.ac:
diff --git a/modules/fchdir b/modules/fchdir
index b35e385..69ac3c3 100644
--- a/modules/fchdir
+++ b/modules/fchdir
@@ -16,6 +16,7 @@ include_next
 malloc-posix
 open
 realloc-posix
+stat
 stdbool
 strdup-posix
 sys_stat
diff --git a/modules/isdir b/modules/isdir
index 09f5956..001546e 100644
--- a/modules/isdir
+++ b/modules/isdir
@@ -6,6 +6,7 @@ lib/isdir.c
 m4/isdir.m4

 Depends-on:
+stat

 configure.ac:
 gl_ISDIR
@@ -20,4 +21,3 @@ GPL

 Maintainer:
 Jim Meyering
-
diff --git a/modules/link b/modules/link
index 9492950..6a006c3 100644
--- a/modules/link
+++ b/modules/link
@@ -6,6 +6,7 @@ lib/link.c
 m4/link.m4

 Depends-on:
+stat
 strdup-posix
 sys_stat
 unistd
diff --git a/modules/lstat b/modules/lstat
index b0c8b44..e4eabb5 100644
--- a/modules/lstat
+++ b/modules/lstat
@@ -6,6 +6,7 @@ lib/lstat.c
 m4/lstat.m4

 Depends-on:
+stat
 sys_stat

 configure.ac:
diff --git a/modules/mkdir-p b/modules/mkdir-p
index 20e3a23..3b0abdf 100644
--- a/modules/mkdir-p
+++ b/modules/mkdir-p
@@ -17,6 +17,7 @@ lchown
 mkancesdirs
 quote
 savewd
+stat
 stat-macros
 stdbool
 sys_stat
diff --git a/modules/modechange b/modules/modechange
index 80d8b70..e58589a 100644
--- a/modules/modechange
+++ b/modules/modechange
@@ -8,6 +8,7 @@ lib/modechange.c
 m4/modechange.m4

 Depends-on:
+stat
 stat-macros
 stdbool
 sys_stat
diff --git a/modules/open b/modules/open
index 601e064..0b8b303 100644
--- a/modules/open
+++ b/modules/open
@@ -8,6 +8,7 @@ m4/mode_t.m4

 Depends-on:
 fcntl-h
+stat

 configure.ac:
 gl_FUNC_OPEN
diff --git a/modules/readlink b/modules/readlink
index 8e2a1de..eb32ef5 100644
--- a/modules/readlink
+++ b/modules/readlink
@@ -6,6 +6,7 @@ lib/readlink.c
 m4/readlink.m4

 Depends-on:
+stat
 unistd

 configure.ac:
diff --git a/modules/same b/modules/same
index b17d735..a6fc64f 100644
--- a/modules/same
+++ b/modules/same
@@ -12,6 +12,7 @@ xalloc
 error
 dirname
 same-inode
+stat
 stdbool
 memcmp

-- 
1.6.5.rc1


>From b4caad347f49b6fc8ec0b656e494dcd90f031b7c Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Thu, 17 Sep 2009 15:55:24 -0600
Subject: [PATCH 04/12] lstat: fix Solaris 9 bug

lstat("file/",buf) mistakenly succeeded.

* lib/lstat.c (lstat): Also check for trailing slash on
non-symlink, non-directories.  Use stat module to simplify logic.
* doc/posix-functions/lstat.texi (lstat): Document it.
* modules/lstat-tests (Depends-on): Add errno, same-inode.
(configure.ac): Check for symlink.
* tests/test-lstat.c (main): Add more tests.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                      |    8 +++
 doc/posix-functions/lstat.texi |   17 +++++-
 lib/lstat.c                    |   41 +++++++-------
 modules/lstat-tests            |    3 +
 tests/test-lstat.c             |  119 +++++++++++++++++++++++++++++++++++++--
 5 files changed, 159 insertions(+), 29 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 1002722..70c18c1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,13 @@
 2009-09-19  Eric Blake  <address@hidden>

+       lstat: fix Solaris 9 bug
+       * lib/lstat.c (lstat): Also check for trailing slash on
+       non-symlink, non-directories.  Use stat module to simplify logic.
+       * doc/posix-functions/lstat.texi (lstat): Document it.
+       * modules/lstat-tests (Depends-on): Add errno, same-inode.
+       (configure.ac): Check for symlink.
+       * tests/test-lstat.c (main): Add more tests.
+
        stat: add as dependency to other modules
        * modules/chown (Depends-on): Add stat.
        * modules/euidaccess (Depends-on): Likewise.
diff --git a/doc/posix-functions/lstat.texi b/doc/posix-functions/lstat.texi
index 7a82d87..dbe31f8 100644
--- a/doc/posix-functions/lstat.texi
+++ b/doc/posix-functions/lstat.texi
@@ -9,8 +9,13 @@ lstat
 Portability problems fixed by Gnulib:
 @itemize
 @item
-When the argument ends in a slash, some platforms don't dereference the
-argument.
+For symlinks, when the argument ends in a slash, some platforms don't
+dereference the argument:
+Solaris 9.
address@hidden
+On some platforms, @code{lstat("file/",buf)} succeeds instead of
+failing with @code{ENOTDIR}.
+Solaris 9.
 @item
 On Windows platforms (excluding Cygwin), symlinks are not supported, so
 @code{lstat} does not exist.
@@ -22,4 +27,12 @@ lstat
 On platforms where @code{off_t} is a 32-bit type, @code{lstat} may not
 correctly report the size of files or block devices larger than 2 GB.  The fix
 is to use the @code{AC_SYS_LARGEFILE} macro.
address@hidden
+On Windows platforms (excluding Cygwin), @code{st_ino} is always 0.
address@hidden
+Because of the definition of @code{struct stat}, it is not possible to
+portably replace @code{stat} via an object-like macro.  Therefore,
+expressions such as @code{(islnk ? lstat : stat) (name, buf)} are not
+portable, and should instead be written @code{islnk ? lstat (name,
+buf) : stat (name, buf)}.
 @end itemize
diff --git a/lib/lstat.c b/lib/lstat.c
index 3e07270..a05f674 100644
--- a/lib/lstat.c
+++ b/lib/lstat.c
@@ -1,6 +1,7 @@
 /* Work around a bug of lstat on some systems

-   Copyright (C) 1997-1999, 2000-2006, 2008 Free Software Foundation, Inc.
+   Copyright (C) 1997-1999, 2000-2006, 2008-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
@@ -19,7 +20,7 @@

 #include <config.h>

-/* Get the original definition of open.  It might be defined as a macro.  */
+/* Get the original definition of lstat.  It might be defined as a macro.  */
 #define __need_system_sys_stat_h
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -56,27 +57,27 @@ rpl_lstat (const char *file, struct stat *sbuf)
   size_t len;
   int lstat_result = orig_lstat (file, sbuf);

-  if (lstat_result != 0 || !S_ISLNK (sbuf->st_mode))
+  if (lstat_result != 0)
     return lstat_result;

+  /* This replacement file can blindly check against '/' rather than
+     using the ISSLASH macro, because all platforms with '\\' either
+     lack symlinks (mingw) or have working lstat (cygwin) and thus do
+     not compile this file.  0 len should have already been filtered
+     out above, with a failure return of ENOENT.  */
   len = strlen (file);
-  if (len == 0 || file[len - 1] != '/')
+  if (file[len - 1] != '/' || S_ISDIR (sbuf->st_mode))
     return 0;

-  /* FILE refers to a symbolic link and the name ends with a slash.
-     Call stat() to get info about the link's referent.  */
-
-  /* If stat fails, then we do the same.  */
-  if (stat (file, sbuf) != 0)
-    return -1;
-
-  /* If FILE references a directory, return 0.  */
-  if (S_ISDIR (sbuf->st_mode))
-    return 0;
-
-  /* Here, we know stat succeeded and FILE references a non-directory.
-     But it was specified via a name including a trailing slash.
-     Fail with errno set to ENOTDIR to indicate the contradiction.  */
-  errno = ENOTDIR;
-  return -1;
+  /* At this point, a trailing slash is only permitted on
+     symlink-to-dir; but it should have found information on the
+     directory, not the symlink.  Call stat() to get info about the
+     link's referent.  Our replacement stat guarantees valid results,
+     even if the symlink is not pointing to a directory.  */
+  if (!S_ISLNK (sbuf->st_mode))
+    {
+      errno = ENOTDIR;
+      return -1;
+    }
+  return stat (file, sbuf);
 }
diff --git a/modules/lstat-tests b/modules/lstat-tests
index f0fe3f6..2368692 100644
--- a/modules/lstat-tests
+++ b/modules/lstat-tests
@@ -2,8 +2,11 @@ Files:
 tests/test-lstat.c

 Depends-on:
+errno
+same-inode

 configure.ac:
+AC_CHECK_FUNCS_ONCE([symlink])

 Makefile.am:
 TESTS += test-lstat
diff --git a/tests/test-lstat.c b/tests/test-lstat.c
index 1a0a0f2..8182738 100644
--- a/tests/test-lstat.c
+++ b/tests/test-lstat.c
@@ -1,5 +1,5 @@
 /* Test of lstat() function.
-   Copyright (C) 2008 Free Software Foundation, Inc.
+   Copyright (C) 2008, 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
@@ -14,24 +14,129 @@
    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 Simon Josefsson, 2008.  */
+/* Written by Simon Josefsson, 2008; and Eric Blake, 2009.  */

 #include <config.h>

 #include <sys/stat.h>

+#include <fcntl.h>
+#include <errno.h>
 #include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "same-inode.h"
+
+#if !HAVE_SYMLINK
+# define symlink(a,b) (-1)
+#endif
+
+#define ASSERT(expr) \
+  do                                                                         \
+    {                                                                        \
+      if (!(expr))                                                           \
+       {                                                                    \
+         fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__);  \
+         fflush (stderr);                                                   \
+         abort ();                                                          \
+       }                                                                    \
+    }                                                                        \
+  while (0)
+
+#define BASE "test-lstat.t"

 int
-main (int argc, char **argv)
+main ()
 {
-  struct stat buf;
+  struct stat st1;
+  struct stat st2;
+
+  /* Remove any leftovers from a previous partial run.  */
+  ASSERT (system ("rm -rf " BASE "*") == 0);
+
+  /* Test for common directories.  */
+  ASSERT (lstat (".", &st1) == 0);
+  ASSERT (lstat ("./", &st2) == 0);
+  ASSERT (SAME_INODE (st1, st2));
+  ASSERT (S_ISDIR (st1.st_mode));
+  ASSERT (S_ISDIR (st2.st_mode));
+  ASSERT (lstat ("/", &st1) == 0);
+  ASSERT (lstat ("///", &st2) == 0);
+  ASSERT (SAME_INODE (st1, st2));
+  ASSERT (S_ISDIR (st1.st_mode));
+  ASSERT (S_ISDIR (st2.st_mode));
+  ASSERT (lstat ("..", &st1) == 0);
+  ASSERT (S_ISDIR (st1.st_mode));
+
+  /* Test for error conditions.  */
+  errno = 0;
+  ASSERT (lstat ("", &st1) == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (lstat ("nosuch", &st1) == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (lstat ("nosuch/", &st1) == -1);
+  ASSERT (errno == ENOENT);

-  if (lstat ("/", &buf) != 0)
+  ASSERT (close (creat (BASE "file", 0600)) == 0);
+  ASSERT (lstat (BASE "file", &st1) == 0);
+  ASSERT (S_ISREG (st1.st_mode));
+  errno = 0;
+  ASSERT (lstat (BASE "file/", &st1) == -1);
+  ASSERT (errno == ENOTDIR);
+
+  /* Now for some symlink tests, where supported.  We set up:
+     link1 -> directory
+     link2 -> file
+     link3 -> dangling
+     link4 -> loop
+     then test behavior both with and without trailing slash.
+  */
+  if (symlink (".", BASE "link1") != 0)
     {
-      perror ("lstat");
-      return 1;
+      ASSERT (unlink (BASE "file") == 0);
+      fputs ("skipping test: symlinks not supported on this filesystem\n",
+             stderr);
+      return 77;
     }
+  ASSERT (symlink (BASE "file", BASE "link2") == 0);
+  ASSERT (symlink (BASE "nosuch", BASE "link3") == 0);
+  ASSERT (symlink (BASE "link4", BASE "link4") == 0);
+
+  ASSERT (lstat (BASE "link1", &st1) == 0);
+  ASSERT (S_ISLNK (st1.st_mode));
+  ASSERT (lstat (BASE "link1/", &st1) == 0);
+  ASSERT (stat (BASE "link1", &st2) == 0);
+  ASSERT (S_ISDIR (st1.st_mode));
+  ASSERT (S_ISDIR (st2.st_mode));
+  ASSERT (SAME_INODE (st1, st2));
+
+  ASSERT (lstat (BASE "link2", &st1) == 0);
+  ASSERT (S_ISLNK (st1.st_mode));
+  errno = 0;
+  ASSERT (lstat (BASE "link2/", &st1) == -1);
+  ASSERT (errno == ENOTDIR);
+
+  ASSERT (lstat (BASE "link3", &st1) == 0);
+  ASSERT (S_ISLNK (st1.st_mode));
+  errno = 0;
+  ASSERT (lstat (BASE "link3/", &st1) == -1);
+  ASSERT (errno == ENOENT);
+
+  ASSERT (lstat (BASE "link4", &st1) == 0);
+  ASSERT (S_ISLNK (st1.st_mode));
+  errno = 0;
+  ASSERT (lstat (BASE "link4/", &st1) == -1);
+  ASSERT (errno == ELOOP);
+
+  /* Cleanup.  */
+  ASSERT (unlink (BASE "file") == 0);
+  ASSERT (unlink (BASE "link1") == 0);
+  ASSERT (unlink (BASE "link2") == 0);
+  ASSERT (unlink (BASE "link3") == 0);
+  ASSERT (unlink (BASE "link4") == 0);

   return 0;
 }
-- 
1.6.5.rc1


>From 4a60ba548bf42233384fa86fd97cf0091514c553 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Wed, 16 Sep 2009 17:13:20 -0600
Subject: [PATCH 05/12] unlink: new module, for Solaris 9 bug

unlink("file/") mistakenly succeeded.  This patch favors, but
does not enforce, GNU semantics that unlink("link-to-dir/")
flat-out fails rather than attempting to unlink "dir".

* modules/unlink: New file.
* lib/unlink.c: Likewise.
* m4/unlink.m4 (gl_FUNC_UNLINK): Likewise.
* m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add witnesses.
* modules/unistd (Makefile.am): Use them.
* lib/unistd.in.h (stat): Declare replacement.
* MODULES.html.sh (systems lacking POSIX:2008): Mention module.
* doc/posix-functions/unlink.texi (unlink): Likewise.
* modules/unlink-tests: New test.
* tests/test-unlink.c: Likewise.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                       |   12 ++++
 MODULES.html.sh                 |    1 +
 doc/posix-functions/unlink.texi |   15 +++++-
 lib/unistd.in.h                 |   15 +++++
 lib/unlink.c                    |   85 ++++++++++++++++++++++++++++++
 m4/unistd_h.m4                  |    4 +-
 m4/unlink.m4                    |   27 ++++++++++
 modules/unistd                  |    2 +
 modules/unlink                  |   26 +++++++++
 modules/unlink-tests            |   12 ++++
 tests/test-unlink.c             |  110 +++++++++++++++++++++++++++++++++++++++
 11 files changed, 307 insertions(+), 2 deletions(-)
 create mode 100644 lib/unlink.c
 create mode 100644 m4/unlink.m4
 create mode 100644 modules/unlink
 create mode 100644 modules/unlink-tests
 create mode 100644 tests/test-unlink.c

diff --git a/ChangeLog b/ChangeLog
index 70c18c1..64bdd7c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,17 @@
 2009-09-19  Eric Blake  <address@hidden>

+       unlink: new module, for Solaris 9 bug
+       * modules/unlink: New file.
+       * lib/unlink.c: Likewise.
+       * m4/unlink.m4 (gl_FUNC_UNLINK): Likewise.
+       * m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add witnesses.
+       * modules/unistd (Makefile.am): Use them.
+       * lib/unistd.in.h (stat): Declare replacement.
+       * MODULES.html.sh (systems lacking POSIX:2008): Mention module.
+       * doc/posix-functions/unlink.texi (unlink): Likewise.
+       * modules/unlink-tests: New test.
+       * tests/test-unlink.c: Likewise.
+
        lstat: fix Solaris 9 bug
        * lib/lstat.c (lstat): Also check for trailing slash on
        non-symlink, non-directories.  Use stat module to simplify logic.
diff --git a/MODULES.html.sh b/MODULES.html.sh
index fbea183..f8a2fb4 100755
--- a/MODULES.html.sh
+++ b/MODULES.html.sh
@@ -2359,6 +2359,7 @@ func_all_modules ()
   func_module sys_wait
   func_module tsearch
   func_module unistd
+  func_module unlink
   func_module utime
   func_module vasnprintf-posix
   func_module vasprintf-posix
diff --git a/doc/posix-functions/unlink.texi b/doc/posix-functions/unlink.texi
index 2f94ab1..1249c42 100644
--- a/doc/posix-functions/unlink.texi
+++ b/doc/posix-functions/unlink.texi
@@ -4,15 +4,28 @@ unlink

 POSIX specification: 
@url{http://www.opengroup.org/onlinepubs/9699919799/functions/unlink.html}

-Gnulib module: ---
+Gnulib module: unlink

 Portability problems fixed by Gnulib:
 @itemize
address@hidden
+Some systems mistakenly succeed on @code{unlink("file/")}:
+Solaris 9.
 @end itemize

 Portability problems not fixed by Gnulib:
 @itemize
 @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
address@hidden("symlink-to-dir/")} remove @file{dir} and leave
address@hidden dangling; this behavior is counter-intuitive.
+The gnulib module unlinkdir can help determine whether code must be
+cautious of unlinking directories.
address@hidden
 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,
diff --git a/lib/unistd.in.h b/lib/unistd.in.h
index 68c3155..6fa2831 100644
--- a/lib/unistd.in.h
+++ b/lib/unistd.in.h
@@ -164,6 +164,21 @@ extern int fchownat (int fd, char const *file, uid_t 
owner, gid_t group, int fla
 #endif


+#if @GNULIB_UNLINK@
+# if @REPLACE_UNLINK@
+#  undef unlink
+#  define unlink rpl_unlink
+extern int unlink (char const *file);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef unlink
+# define unlink(n)                         \
+    (GL_LINK_WARNING ("unlink is not portable - " \
+                      "use gnulib module unlink for portability"), \
+     unlink (n))
+#endif
+
+
 #if @GNULIB_UNLINKAT@
 # if address@hidden@
 extern int unlinkat (int fd, char const *file, int flag);
diff --git a/lib/unlink.c b/lib/unlink.c
new file mode 100644
index 0000000..cd8f4b1
--- /dev/null
+++ b/lib/unlink.c
@@ -0,0 +1,85 @@
+/* Work around unlink bugs.
+
+   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/>.  */
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#undef unlink
+
+/* Remove file NAME.
+   Return 0 if successful, -1 if not.  */
+
+int
+rpl_unlink (char const *name)
+{
+  /* Work around Solaris 9 bug where unlink("file/") succeeds.  */
+  size_t len = strlen (name);
+  int result = 0;
+  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.  */
+      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);
+       }
+    }
+  if (!result)
+    result = unlink (name);
+  return result;
+}
diff --git a/m4/unistd_h.m4 b/m4/unistd_h.m4
index 2d5edc7..d21cafc 100644
--- a/m4/unistd_h.m4
+++ b/m4/unistd_h.m4
@@ -1,4 +1,4 @@
-# unistd_h.m4 serial 27
+# unistd_h.m4 serial 28
 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,
@@ -61,6 +61,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS],
   GNULIB_SYMLINKAT=0;        AC_SUBST([GNULIB_SYMLINKAT])
   GNULIB_UNISTD_H_GETOPT=0;  AC_SUBST([GNULIB_UNISTD_H_GETOPT])
   GNULIB_UNISTD_H_SIGPIPE=0; AC_SUBST([GNULIB_UNISTD_H_SIGPIPE])
+  GNULIB_UNLINK=0;           AC_SUBST([GNULIB_UNLINK])
   GNULIB_UNLINKAT=0;         AC_SUBST([GNULIB_UNLINKAT])
   GNULIB_WRITE=0;            AC_SUBST([GNULIB_WRITE])
   dnl Assume proper GNU behavior unless another module says otherwise.
@@ -99,6 +100,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS],
   REPLACE_LINK=0;         AC_SUBST([REPLACE_LINK])
   REPLACE_LSEEK=0;        AC_SUBST([REPLACE_LSEEK])
   REPLACE_RMDIR=0;        AC_SUBST([REPLACE_RMDIR])
+  REPLACE_UNLINK=0;       AC_SUBST([REPLACE_UNLINK])
   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;
diff --git a/m4/unlink.m4 b/m4/unlink.m4
new file mode 100644
index 0000000..626c3ad
--- /dev/null
+++ b/m4/unlink.m4
@@ -0,0 +1,27 @@
+# unlink.m4 serial 1
+dnl Copyright (C) 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,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_FUNC_UNLINK],
+[
+  AC_REQUIRE([gl_AC_DOS])
+  AC_REQUIRE([gl_UNISTD_H_DEFAULTS])
+  dnl Detect Solaris 9 bug.
+  AC_CACHE_CHECK([whether unlink honors trailing slashes],
+    [gl_cv_func_unlink_works],
+    [touch conftest.file
+     AC_RUN_IFELSE(
+       [AC_LANG_PROGRAM(
+         [[#include <stdio.h>
+           #include <errno.h>
+]], [[return !unlink ("conftest.file/") || errno != ENOTDIR;]])],
+      [gl_cv_func_unlink_works=yes], [gl_cv_func_unlink_works=no],
+      [gl_cv_func_unlink_works="guessing no"])
+     rm -f conftest.file])
+  if test x"$gl_cv_func_unlink_works" != xyes; then
+    REPLACE_UNLINK=1
+    AC_LIBOBJ([unlink])
+  fi
+])
diff --git a/modules/unistd b/modules/unistd
index 5b3546c..336381a 100644
--- a/modules/unistd
+++ b/modules/unistd
@@ -54,6 +54,7 @@ unistd.h: unistd.in.h
              -e 's|@''GNULIB_SYMLINKAT''@|$(GNULIB_SYMLINKAT)|g' \
              -e 's|@''GNULIB_UNISTD_H_GETOPT''@|$(GNULIB_UNISTD_H_GETOPT)|g' \
              -e 's|@''GNULIB_UNISTD_H_SIGPIPE''@|$(GNULIB_UNISTD_H_SIGPIPE)|g' 
\
+             -e 's|@''GNULIB_UNLINK''@|$(GNULIB_UNLINK)|g' \
              -e 's|@''GNULIB_UNLINKAT''@|$(GNULIB_UNLINKAT)|g' \
              -e 's|@''GNULIB_WRITE''@|$(GNULIB_WRITE)|g' \
              -e 's|@''HAVE_DUP2''@|$(HAVE_DUP2)|g' \
@@ -91,6 +92,7 @@ unistd.h: unistd.in.h
              -e 's|@''REPLACE_LINK''@|$(REPLACE_LINK)|g' \
              -e 's|@''REPLACE_LSEEK''@|$(REPLACE_LSEEK)|g' \
              -e 's|@''REPLACE_RMDIR''@|$(REPLACE_RMDIR)|g' \
+             -e 's|@''REPLACE_UNLINK''@|$(REPLACE_UNLINK)|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'
 \
diff --git a/modules/unlink b/modules/unlink
new file mode 100644
index 0000000..224efa3
--- /dev/null
+++ b/modules/unlink
@@ -0,0 +1,26 @@
+Description:
+unlink(): remove a file.
+
+Files:
+lib/unlink.c
+m4/dos.m4
+m4/unlink.m4
+
+Depends-on:
+lstat
+unistd
+
+configure.ac:
+gl_FUNC_UNLINK
+gl_UNISTD_MODULE_INDICATOR([unlink])
+
+Makefile.am:
+
+Include:
+<unistd.h>
+
+License:
+LGPL
+
+Maintainer:
+Eric Blake
diff --git a/modules/unlink-tests b/modules/unlink-tests
new file mode 100644
index 0000000..b527b61
--- /dev/null
+++ b/modules/unlink-tests
@@ -0,0 +1,12 @@
+Files:
+tests/test-unlink.c
+
+Depends-on:
+unlinkdir
+
+configure.ac:
+AC_CHECK_FUNCS_ONCE([symlink])
+
+Makefile.am:
+TESTS += test-unlink
+check_PROGRAMS += test-unlink
diff --git a/tests/test-unlink.c b/tests/test-unlink.c
new file mode 100644
index 0000000..5aaa595
--- /dev/null
+++ b/tests/test-unlink.c
@@ -0,0 +1,110 @@
+/* Tests of unlink.
+   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 <address@hidden>, 2009.  */
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "unlinkdir.h"
+
+#if !HAVE_SYMLINK
+# define symlink(a,b) (-1)
+#endif
+
+#define ASSERT(expr) \
+  do                                                                         \
+    {                                                                        \
+      if (!(expr))                                                           \
+       {                                                                    \
+         fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__);  \
+         fflush (stderr);                                                   \
+         abort ();                                                          \
+       }                                                                    \
+    }                                                                        \
+  while (0)
+
+#define BASE "test-unlink.t"
+
+int
+main ()
+{
+  /* Remove any leftovers from a previous partial run.  */
+  ASSERT (system ("rm -rf " BASE "*") == 0);
+
+  /* Setup.  */
+  ASSERT (mkdir (BASE "dir", 0700) == 0);
+  ASSERT (close (creat (BASE "dir/file", 0600)) == 0);
+
+  /* Basic error conditions.  */
+  errno = 0;
+  ASSERT (unlink ("") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (unlink (BASE "nosuch") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (unlink (BASE "nosuch/") == -1);
+  ASSERT (errno == ENOENT);
+  /* Resulting errno after directories is rather varied across
+     implementations (EPERM, EINVAL, EACCES, EBUSY, EISDIR, ENOTSUP);
+     however, we must be careful to not attempt unlink on a directory
+     unless we know it must fail.  */
+  if (cannot_unlink_dir ())
+    {
+      ASSERT (unlink (".") == -1);
+      ASSERT (unlink ("..") == -1);
+      ASSERT (unlink ("/") == -1);
+      ASSERT (unlink (BASE "dir") == -1);
+      ASSERT (mkdir (BASE "dir1", 0700) == 0);
+      ASSERT (unlink (BASE "dir1") == -1);
+      ASSERT (rmdir (BASE "dir1") == 0);
+    }
+  errno = 0;
+  ASSERT (unlink (BASE "dir/file/") == -1);
+  ASSERT (errno == ENOTDIR);
+
+  /* Test symlink behavior.  Specifying trailing slash will attempt
+     unlink of a directory, so only attempt it if we know it must
+     fail.  */
+  if (symlink (BASE "dir", BASE "link") != 0)
+    {
+      ASSERT (unlink (BASE "dir/file") == 0);
+      ASSERT (rmdir (BASE "dir") == 0);
+      fputs ("skipping test: symlinks not supported on this filesystem\n",
+             stderr);
+      return 77;
+    }
+  if (cannot_unlink_dir ())
+    ASSERT (unlink (BASE "link/") == -1);
+  ASSERT (unlink (BASE "link") == 0);
+  ASSERT (symlink (BASE "dir/file", BASE "link") == 0);
+  /* Order here proves unlink of a symlink does not follow through to
+     the file.  */
+  ASSERT (unlink (BASE "link") == 0);
+  ASSERT (unlink (BASE "dir/file") == 0);
+  ASSERT (rmdir (BASE "dir") == 0);
+
+  return 0;
+}
-- 
1.6.5.rc1


>From 6ca71ffe395184749d849f5bba4771d6b2fbb7d6 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Mon, 14 Sep 2009 16:57:55 -0600
Subject: [PATCH 06/12] remove: new module, for mingw and Solaris 9 bugs

Mingw obeys C89, but not POSIX, by not handling directories.
Solaris remove("file/") mistakenly succeeded.

* modules/remove: New file.
* lib/remove.c: Likewise.
* m4/remove.m4 (gl_FUNC_REMOVE): Likewise.
* m4/stdio_h.m4 (gl_STDIO_H_DEFAULTS): Add witnesses.
* modules/stdio (Makefile.am): Use them.
* lib/stdio.in.h (remove): Declare replacement.
* MODULES.html.sh (systems lacking POSIX:2008): Mention module.
* doc/posix-functions/remove.texi (remove): Likewise.
* modules/remove-tests: New test.
* tests/test-remove.c: Likewise.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                       |   12 ++++
 MODULES.html.sh                 |    1 +
 doc/posix-functions/remove.texi |   13 ++++-
 lib/remove.c                    |   43 ++++++++++++++
 lib/stdio.in.h                  |   14 +++++
 m4/remove.m4                    |   40 +++++++++++++
 m4/stdio_h.m4                   |    4 +-
 modules/remove                  |   27 +++++++++
 modules/remove-tests            |   12 ++++
 modules/stdio                   |    2 +
 tests/test-remove.c             |  122 +++++++++++++++++++++++++++++++++++++++
 11 files changed, 288 insertions(+), 2 deletions(-)
 create mode 100644 lib/remove.c
 create mode 100644 m4/remove.m4
 create mode 100644 modules/remove
 create mode 100644 modules/remove-tests
 create mode 100644 tests/test-remove.c

diff --git a/ChangeLog b/ChangeLog
index 64bdd7c..48defa3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,17 @@
 2009-09-19  Eric Blake  <address@hidden>

+       remove: new module, for mingw and Solaris 9 bugs
+       * modules/remove: New file.
+       * lib/remove.c: Likewise.
+       * m4/remove.m4 (gl_FUNC_REMOVE): Likewise.
+       * m4/stdio_h.m4 (gl_STDIO_H_DEFAULTS): Add witnesses.
+       * modules/stdio (Makefile.am): Use them.
+       * lib/stdio.in.h (remove): Declare replacement.
+       * MODULES.html.sh (systems lacking POSIX:2008): Mention module.
+       * doc/posix-functions/remove.texi (remove): Likewise.
+       * modules/remove-tests: New test.
+       * tests/test-remove.c: Likewise.
+
        unlink: new module, for Solaris 9 bug
        * modules/unlink: New file.
        * lib/unlink.c: Likewise.
diff --git a/MODULES.html.sh b/MODULES.html.sh
index f8a2fb4..9104d54 100755
--- a/MODULES.html.sh
+++ b/MODULES.html.sh
@@ -2319,6 +2319,7 @@ func_all_modules ()
   func_module realloc-posix
   func_module recv
   func_module recvfrom
+  func_module remove
   func_module sched
   func_module select
   func_module send
diff --git a/doc/posix-functions/remove.texi b/doc/posix-functions/remove.texi
index 3995006..b3e3d1b 100644
--- a/doc/posix-functions/remove.texi
+++ b/doc/posix-functions/remove.texi
@@ -4,10 +4,21 @@ remove

 POSIX specification: 
@url{http://www.opengroup.org/onlinepubs/9699919799/functions/remove.html}

-Gnulib module: ---
+Gnulib module: remove

 Portability problems fixed by Gnulib:
 @itemize
address@hidden
+This function fails to reject trailing slashes on non-directories on
+some platforms:
+Solaris 9.
address@hidden
+This function mistakenly removes a directory with
address@hidden("dir/./")} on some platforms:
+Cygwin 1.5.x.
address@hidden
+This function does not remove empty directories on some platforms:
+mingw.
 @end itemize

 Portability problems not fixed by Gnulib:
diff --git a/lib/remove.c b/lib/remove.c
new file mode 100644
index 0000000..49863e5
--- /dev/null
+++ b/lib/remove.c
@@ -0,0 +1,43 @@
+/* Remove a file or directory.
+   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 <stdio.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+#undef remove
+
+/* Remove NAME from the file system.  This works around C89 platforms
+   that don't handle directories like POSIX requires; it also works
+   around Solaris 9 bugs with trailing slash.  */
+int
+rpl_remove (char const *name)
+{
+  /* It is faster to just try rmdir, and fall back on unlink, than it
+     is to use lstat to see what we are about to remove.  Technically,
+     it is more likely that we want unlink, not rmdir, but we cannot
+     guarantee the safety of unlink on directories.  Trailing slash
+     bugs are handled by our rmdir and unlink wrappers.  */
+  int result = rmdir (name);
+  if (result && errno == ENOTDIR)
+    result = unlink (name);
+  return result;
+}
diff --git a/lib/stdio.in.h b/lib/stdio.in.h
index 27cd305..35109c3 100644
--- a/lib/stdio.in.h
+++ b/lib/stdio.in.h
@@ -416,6 +416,20 @@ extern int putchar (int c);
 extern int puts (const char *string);
 #endif

+#if @GNULIB_REMOVE@
+# if @REPLACE_REMOVE@
+#  undef remove
+#  define remove rpl_remove
+extern int remove (const char *name);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef remove
+# define remove(n)                                        \
+   (GL_LINK_WARNING ("remove cannot handle directories on some platforms - " \
+                     "use gnulib module remove for more portability"), \
+    remove (n))
+#endif
+
 #if @GNULIB_RENAME@
 # if @REPLACE_RENAME@
 #  undef rename
diff --git a/m4/remove.m4 b/m4/remove.m4
new file mode 100644
index 0000000..7bb5a0c
--- /dev/null
+++ b/m4/remove.m4
@@ -0,0 +1,40 @@
+# remove.m4 serial 1
+dnl Copyright (C) 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,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_FUNC_REMOVE],
+[
+  AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
+  AC_REQUIRE([gl_AC_DOS])
+  AC_REQUIRE([gl_STDIO_H_DEFAULTS])
+  AC_REQUIRE([gl_FUNC_RMDIR])
+  AC_REQUIRE([gl_FUNC_UNLINK])
+  if test "$gl_cv_func_rmdir_works:$gl_cv_func_unlink_works" != yes:yes; then
+    dnl If either underlying syscall is broken, then remove likely has
+    dnl the same bug; blindly use our replacement.
+    REPLACE_REMOVE=1
+    AC_LIBOBJ([remove])
+  else
+    dnl C89 requires remove(), but only POSIX requires it to handle
+    dnl directories.  On mingw, directories fails with EPERM.
+    AC_CACHE_CHECK([whether remove handles directories],
+      [gl_cv_func_remove_dir_works],
+      [mkdir conftest.dir
+       AC_RUN_IFELSE(
+         [AC_LANG_PROGRAM(
+           [[#include <stdio.h>
+]], [[return remove ("conftest.dir");]])],
+         [gl_cv_func_remove_dir_works=yes], [gl_cv_func_remove_dir_works=no],
+         [case $host_os in
+            mingw*) gl_cv_func_remove_dir_works="guessing no";;
+            *) gl_cv_func_remove_dir_works="guessing yes";;
+          esac])
+       rm -rf conftest.dir])
+    case $gl_cv_func_remove_dir_works in
+      *no*) REPLACE_REMOVE=1
+        AC_LIBOBJ([remove]);;
+    esac
+  fi
+])
diff --git a/m4/stdio_h.m4 b/m4/stdio_h.m4
index ac5e20a..01af04d 100644
--- a/m4/stdio_h.m4
+++ b/m4/stdio_h.m4
@@ -1,4 +1,4 @@
-# stdio_h.m4 serial 18
+# stdio_h.m4 serial 19
 dnl Copyright (C) 2007-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,
@@ -67,6 +67,7 @@ AC_DEFUN([gl_STDIO_H_DEFAULTS],
   GNULIB_PUTC=0;                 AC_SUBST([GNULIB_PUTC])
   GNULIB_PUTCHAR=0;              AC_SUBST([GNULIB_PUTCHAR])
   GNULIB_PUTS=0;                 AC_SUBST([GNULIB_PUTS])
+  GNULIB_REMOVE=0;               AC_SUBST([GNULIB_REMOVE])
   GNULIB_RENAME=0;               AC_SUBST([GNULIB_RENAME])
   GNULIB_SNPRINTF=0;             AC_SUBST([GNULIB_SNPRINTF])
   GNULIB_SPRINTF_POSIX=0;        AC_SUBST([GNULIB_SPRINTF_POSIX])
@@ -107,6 +108,7 @@ AC_DEFUN([gl_STDIO_H_DEFAULTS],
   REPLACE_PERROR=0;              AC_SUBST([REPLACE_PERROR])
   REPLACE_POPEN=0;               AC_SUBST([REPLACE_POPEN])
   REPLACE_PRINTF=0;              AC_SUBST([REPLACE_PRINTF])
+  REPLACE_REMOVE=0;              AC_SUBST([REPLACE_REMOVE])
   REPLACE_RENAME=0;              AC_SUBST([REPLACE_RENAME])
   REPLACE_SNPRINTF=0;            AC_SUBST([REPLACE_SNPRINTF])
   REPLACE_SPRINTF=0;             AC_SUBST([REPLACE_SPRINTF])
diff --git a/modules/remove b/modules/remove
new file mode 100644
index 0000000..c2a305e
--- /dev/null
+++ b/modules/remove
@@ -0,0 +1,27 @@
+Description:
+remove(): remove a file or directory
+
+Files:
+lib/remove.c
+m4/dos.m4
+m4/remove.m4
+
+Depends-on:
+rmdir
+stdio
+unlink
+
+configure.ac:
+gl_FUNC_REMOVE
+gl_STDIO_MODULE_INDICATOR([remove])
+
+Makefile.am:
+
+Include:
+<stdio.h>
+
+License:
+LGPL
+
+Maintainer:
+Eric Blake
diff --git a/modules/remove-tests b/modules/remove-tests
new file mode 100644
index 0000000..fdc26c2
--- /dev/null
+++ b/modules/remove-tests
@@ -0,0 +1,12 @@
+Files:
+tests/test-remove.c
+
+Depends-on:
+sys_stat
+
+configure.ac:
+AC_CHECK_FUNCS_ONCE([symlink])
+
+Makefile.am:
+TESTS += test-remove
+check_PROGRAMS += test-remove
diff --git a/modules/stdio b/modules/stdio
index 8f242cc..22b83f7 100644
--- a/modules/stdio
+++ b/modules/stdio
@@ -52,6 +52,7 @@ stdio.h: stdio.in.h
              -e 's|@''GNULIB_PUTC''@|$(GNULIB_PUTC)|g' \
              -e 's|@''GNULIB_PUTCHAR''@|$(GNULIB_PUTCHAR)|g' \
              -e 's|@''GNULIB_PUTS''@|$(GNULIB_PUTS)|g' \
+             -e 's|@''GNULIB_REMOVE''@|$(GNULIB_REMOVE)|g' \
              -e 's|@''GNULIB_RENAME''@|$(GNULIB_RENAME)|g' \
              -e 's|@''GNULIB_SNPRINTF''@|$(GNULIB_SNPRINTF)|g' \
              -e 's|@''GNULIB_SPRINTF_POSIX''@|$(GNULIB_SPRINTF_POSIX)|g' \
@@ -89,6 +90,7 @@ stdio.h: stdio.in.h
              -e 's|@''REPLACE_PERROR''@|$(REPLACE_PERROR)|g' \
              -e 's|@''REPLACE_POPEN''@|$(REPLACE_POPEN)|g' \
              -e 's|@''REPLACE_PRINTF''@|$(REPLACE_PRINTF)|g' \
+             -e 's|@''REPLACE_REMOVE''@|$(REPLACE_REMOVE)|g' \
              -e 's|@''REPLACE_RENAME''@|$(REPLACE_RENAME)|g' \
              -e 's|@''REPLACE_SNPRINTF''@|$(REPLACE_SNPRINTF)|g' \
              -e 's|@''REPLACE_SPRINTF''@|$(REPLACE_SPRINTF)|g' \
diff --git a/tests/test-remove.c b/tests/test-remove.c
new file mode 100644
index 0000000..787cde2
--- /dev/null
+++ b/tests/test-remove.c
@@ -0,0 +1,122 @@
+/* Tests of remove.
+   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 <address@hidden>, 2009.  */
+
+#include <config.h>
+
+#include <stdio.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#if !HAVE_SYMLINK
+# define symlink(a,b) (-1)
+#endif
+
+#define ASSERT(expr) \
+  do                                                                         \
+    {                                                                        \
+      if (!(expr))                                                           \
+       {                                                                    \
+         fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__);  \
+         fflush (stderr);                                                   \
+         abort ();                                                          \
+       }                                                                    \
+    }                                                                        \
+  while (0)
+
+#define BASE "test-remove.t"
+
+int
+main ()
+{
+  /* Remove any leftovers from a previous partial run.  */
+  ASSERT (system ("rm -rf " BASE "*") == 0);
+
+  /* Setup.  */
+  ASSERT (mkdir (BASE "dir", 0700) == 0);
+  ASSERT (close (creat (BASE "dir/file", 0600)) == 0);
+
+  /* Basic error conditions.  */
+  errno = 0;
+  ASSERT (remove ("") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (remove ("nosuch") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (remove ("nosuch/") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (remove (".") == -1);
+  ASSERT (errno == EINVAL || errno == EBUSY);
+  /* Resulting errno after ".." or "/" is too varied to test; it is
+     reasonable to see any of EINVAL, EEXIST, ENOTEMPTY, EACCES.  */
+  ASSERT (remove ("..") == -1);
+  ASSERT (remove ("/") == -1);
+  ASSERT (remove ("///") == -1);
+  errno = 0;
+  ASSERT (remove (BASE "dir/file/") == -1);
+  ASSERT (errno == ENOTDIR);
+
+  /* Non-empty directory.  */
+  errno = 0;
+  ASSERT (remove (BASE "dir") == -1);
+  ASSERT (errno == EEXIST || errno == ENOTEMPTY);
+
+  /* Non-directory.  */
+  ASSERT (remove (BASE "dir/file") == 0);
+
+  /* Empty directory.  */
+  errno = 0;
+  ASSERT (remove (BASE "dir/./") == -1);
+  ASSERT (errno == EINVAL || errno == EBUSY);
+  ASSERT (remove (BASE "dir") == 0);
+
+  /* Test symlink behavior.  Specifying trailing slash should remove
+     referent directory, or cause ENOTDIR failure, but not touch
+     symlink.  */
+  if (symlink (BASE "dir", BASE "link") != 0)
+    {
+      fputs ("skipping test: symlinks not supported on this filesystem\n",
+             stderr);
+      return 77;
+    }
+  ASSERT (mkdir (BASE "dir", 0700) == 0);
+  errno = 0;
+  if (remove (BASE "link/") == 0)
+    {
+      struct stat st;
+      errno = 0;
+      ASSERT (stat (BASE "link", &st) == -1);
+      ASSERT (errno == ENOENT);
+    }
+  else
+    ASSERT (remove (BASE "dir") == 0);
+  {
+    struct stat st;
+    ASSERT (lstat (BASE "link", &st) == 0);
+    ASSERT (S_ISLNK (st.st_mode));
+  }
+  ASSERT (remove (BASE "link") == 0);
+
+  return 0;
+}
-- 
1.6.5.rc1


>From 3a5041b1c62c2b7c829f47bec2f1789c59844b6c Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Fri, 18 Sep 2009 18:06:31 -0600
Subject: [PATCH 07/12] test-fstatat: new test, to expose Solaris 9 bugs

Share the stat and lstat tests with fstatat.

* tests/test-stat.c (main): Factor guts...
* tests/test-stat.h (test_stat_func): ...into new file.
* tests/test-lstat.c (main): Factor guts...
* tests/test-lstat.h (test_lstat_func): ...into new file.
* tests/test-fstatat.c: New file.
* modules/stat-tests (Files): Add test-stat.h.
* modules/lstat-tests (Files): Add test-lstat.h.
(Depends-on): Add stdbool.
* modules/openat-tests (Depends-on): Add pathmax.
(Files): Add test-lstat.h, test-stat.h, test-fstatat.c.
(Makefile.am): Run new test.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog            |   13 +++++
 modules/lstat-tests  |    2 +
 modules/openat-tests |    9 +++-
 modules/stat-tests   |    1 +
 tests/test-fstatat.c |   89 +++++++++++++++++++++++++++++++++++++
 tests/test-lstat.c   |  102 +++++-------------------------------------
 tests/test-lstat.h   |  119 ++++++++++++++++++++++++++++++++++++++++++++++++++
 tests/test-stat.c    |   43 +++++-------------
 tests/test-stat.h    |   59 +++++++++++++++++++++++++
 9 files changed, 313 insertions(+), 124 deletions(-)
 create mode 100644 tests/test-fstatat.c
 create mode 100644 tests/test-lstat.h
 create mode 100644 tests/test-stat.h

diff --git a/ChangeLog b/ChangeLog
index 48defa3..99833ab 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,18 @@
 2009-09-19  Eric Blake  <address@hidden>

+       test-fstatat: new test, to expose Solaris 9 bugs
+       * tests/test-stat.c (main): Factor guts...
+       * tests/test-stat.h (test_stat_func): ...into new file.
+       * tests/test-lstat.c (main): Factor guts...
+       * tests/test-lstat.h (test_lstat_func): ...into new file.
+       * tests/test-fstatat.c: New file.
+       * modules/stat-tests (Files): Add test-stat.h.
+       * modules/lstat-tests (Files): Add test-lstat.h.
+       (Depends-on): Add stdbool.
+       * modules/openat-tests (Depends-on): Add pathmax.
+       (Files): Add test-lstat.h, test-stat.h, test-fstatat.c.
+       (Makefile.am): Run new test.
+
        remove: new module, for mingw and Solaris 9 bugs
        * modules/remove: New file.
        * lib/remove.c: Likewise.
diff --git a/modules/lstat-tests b/modules/lstat-tests
index 2368692..acd2bcd 100644
--- a/modules/lstat-tests
+++ b/modules/lstat-tests
@@ -1,9 +1,11 @@
 Files:
+tests/test-lstat.h
 tests/test-lstat.c

 Depends-on:
 errno
 same-inode
+stdbool

 configure.ac:
 AC_CHECK_FUNCS_ONCE([symlink])
diff --git a/modules/openat-tests b/modules/openat-tests
index 54d0c61..42ae6e8 100644
--- a/modules/openat-tests
+++ b/modules/openat-tests
@@ -1,15 +1,20 @@
 Files:
+tests/test-lstat.h
 tests/test-rmdir.h
+tests/test-stat.h
+tests/test-fstatat.c
 tests/test-openat.c
 tests/test-unlinkat.c

 Depends-on:
+pathmax

 configure.ac:
 AC_CHECK_FUNCS_ONCE([symlink])

 Makefile.am:
-TESTS += test-openat test-unlinkat
-check_PROGRAMS += test-openat test-unlinkat
+TESTS += test-fstatat test-openat test-unlinkat
+check_PROGRAMS += test-fstatat test-openat test-unlinkat
+test_fstatat_LDADD = $(LDADD) @LIBINTL@
 test_openat_LDADD = $(LDADD) @LIBINTL@
 test_unlinkat_LDADD = $(LDADD) @LIBINTL@
diff --git a/modules/stat-tests b/modules/stat-tests
index 3d31b52..93444aa 100644
--- a/modules/stat-tests
+++ b/modules/stat-tests
@@ -1,4 +1,5 @@
 Files:
+tests/test-stat.h
 tests/test-stat.c

 Depends-on:
diff --git a/tests/test-fstatat.c b/tests/test-fstatat.c
new file mode 100644
index 0000000..a70721a
--- /dev/null
+++ b/tests/test-fstatat.c
@@ -0,0 +1,89 @@
+/* Tests of fstatat.
+   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 <address@hidden>, 2009.  */
+
+#include <config.h>
+
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "openat.h"
+#include "pathmax.h"
+#include "same-inode.h"
+
+#if !HAVE_SYMLINK
+# define symlink(a,b) (-1)
+#endif
+
+#define ASSERT(expr) \
+  do                                                                         \
+    {                                                                        \
+      if (!(expr))                                                           \
+       {                                                                    \
+         fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__);  \
+         fflush (stderr);                                                   \
+         abort ();                                                          \
+       }                                                                    \
+    }                                                                        \
+  while (0)
+
+#define BASE "test-fstatat.t"
+
+#include "test-lstat.h"
+#include "test-stat.h"
+
+static int dfd = AT_FDCWD;
+
+/* Wrapper around fstatat to test stat behavior.  */
+static int
+do_stat (char const *name, struct stat *st)
+{
+  return statat (dfd, name, st);
+}
+
+/* Wrapper around fstatat to test lstat behavior.  */
+static int
+do_lstat (char const *name, struct stat *st)
+{
+  return lstatat (dfd, name, st);
+}
+
+int
+main ()
+{
+  int result;
+  ASSERT (test_stat_func (do_stat) == 0);
+  result = test_lstat_func (do_lstat, false);
+  dfd = open (".", O_RDONLY);
+  ASSERT (0 <= dfd);
+  ASSERT (test_stat_func (do_stat) == 0);
+  ASSERT (test_lstat_func (do_lstat, false) == result);
+  ASSERT (close (dfd) == 0);
+
+  /* FIXME - add additional tests of dfd not at current directory.  */
+
+  if (result == 77)
+    fputs ("skipping test: symlinks not supported on this filesystem\n",
+          stderr);
+  return result;
+}
diff --git a/tests/test-lstat.c b/tests/test-lstat.c
index 8182738..cb9963d 100644
--- a/tests/test-lstat.c
+++ b/tests/test-lstat.c
@@ -22,6 +22,7 @@

 #include <fcntl.h>
 #include <errno.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -46,97 +47,18 @@

 #define BASE "test-lstat.t"

+#include "test-lstat.h"
+
+/* Wrapper around lstat, which works even if lstat is a function-like
+   macro, where test_lstat_func(lstat) would do the wrong thing.  */
+static int
+do_lstat (char const *name, struct stat *st)
+{
+  return lstat (name, st);
+}
+
 int
 main ()
 {
-  struct stat st1;
-  struct stat st2;
-
-  /* Remove any leftovers from a previous partial run.  */
-  ASSERT (system ("rm -rf " BASE "*") == 0);
-
-  /* Test for common directories.  */
-  ASSERT (lstat (".", &st1) == 0);
-  ASSERT (lstat ("./", &st2) == 0);
-  ASSERT (SAME_INODE (st1, st2));
-  ASSERT (S_ISDIR (st1.st_mode));
-  ASSERT (S_ISDIR (st2.st_mode));
-  ASSERT (lstat ("/", &st1) == 0);
-  ASSERT (lstat ("///", &st2) == 0);
-  ASSERT (SAME_INODE (st1, st2));
-  ASSERT (S_ISDIR (st1.st_mode));
-  ASSERT (S_ISDIR (st2.st_mode));
-  ASSERT (lstat ("..", &st1) == 0);
-  ASSERT (S_ISDIR (st1.st_mode));
-
-  /* Test for error conditions.  */
-  errno = 0;
-  ASSERT (lstat ("", &st1) == -1);
-  ASSERT (errno == ENOENT);
-  errno = 0;
-  ASSERT (lstat ("nosuch", &st1) == -1);
-  ASSERT (errno == ENOENT);
-  errno = 0;
-  ASSERT (lstat ("nosuch/", &st1) == -1);
-  ASSERT (errno == ENOENT);
-
-  ASSERT (close (creat (BASE "file", 0600)) == 0);
-  ASSERT (lstat (BASE "file", &st1) == 0);
-  ASSERT (S_ISREG (st1.st_mode));
-  errno = 0;
-  ASSERT (lstat (BASE "file/", &st1) == -1);
-  ASSERT (errno == ENOTDIR);
-
-  /* Now for some symlink tests, where supported.  We set up:
-     link1 -> directory
-     link2 -> file
-     link3 -> dangling
-     link4 -> loop
-     then test behavior both with and without trailing slash.
-  */
-  if (symlink (".", BASE "link1") != 0)
-    {
-      ASSERT (unlink (BASE "file") == 0);
-      fputs ("skipping test: symlinks not supported on this filesystem\n",
-             stderr);
-      return 77;
-    }
-  ASSERT (symlink (BASE "file", BASE "link2") == 0);
-  ASSERT (symlink (BASE "nosuch", BASE "link3") == 0);
-  ASSERT (symlink (BASE "link4", BASE "link4") == 0);
-
-  ASSERT (lstat (BASE "link1", &st1) == 0);
-  ASSERT (S_ISLNK (st1.st_mode));
-  ASSERT (lstat (BASE "link1/", &st1) == 0);
-  ASSERT (stat (BASE "link1", &st2) == 0);
-  ASSERT (S_ISDIR (st1.st_mode));
-  ASSERT (S_ISDIR (st2.st_mode));
-  ASSERT (SAME_INODE (st1, st2));
-
-  ASSERT (lstat (BASE "link2", &st1) == 0);
-  ASSERT (S_ISLNK (st1.st_mode));
-  errno = 0;
-  ASSERT (lstat (BASE "link2/", &st1) == -1);
-  ASSERT (errno == ENOTDIR);
-
-  ASSERT (lstat (BASE "link3", &st1) == 0);
-  ASSERT (S_ISLNK (st1.st_mode));
-  errno = 0;
-  ASSERT (lstat (BASE "link3/", &st1) == -1);
-  ASSERT (errno == ENOENT);
-
-  ASSERT (lstat (BASE "link4", &st1) == 0);
-  ASSERT (S_ISLNK (st1.st_mode));
-  errno = 0;
-  ASSERT (lstat (BASE "link4/", &st1) == -1);
-  ASSERT (errno == ELOOP);
-
-  /* Cleanup.  */
-  ASSERT (unlink (BASE "file") == 0);
-  ASSERT (unlink (BASE "link1") == 0);
-  ASSERT (unlink (BASE "link2") == 0);
-  ASSERT (unlink (BASE "link3") == 0);
-  ASSERT (unlink (BASE "link4") == 0);
-
-  return 0;
+  return test_lstat_func (do_lstat, true);
 }
diff --git a/tests/test-lstat.h b/tests/test-lstat.h
new file mode 100644
index 0000000..68e4f74
--- /dev/null
+++ b/tests/test-lstat.h
@@ -0,0 +1,119 @@
+/* Test of lstat() function.
+   Copyright (C) 2008, 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 Simon Josefsson, 2008; and Eric Blake, 2009.  */
+
+/* This file is designed to test both lstat(n,buf) and
+   fstatat(AT_FDCWD,n,buf,AT_SYMLINK_NOFOLLOW).  FUNC is the function
+   to test.  Assumes that BASE and ASSERT are already defined, and
+   that appropriate headers are already included.  If PRINT, warn
+   before skipping symlink tests with status 77.  */
+
+static int
+test_lstat_func (int (*func) (char const *, struct stat *), bool print)
+{
+  struct stat st1;
+  struct stat st2;
+
+  /* Remove any leftovers from a previous partial run.  */
+  ASSERT (system ("rm -rf " BASE "*") == 0);
+
+  /* Test for common directories.  */
+  ASSERT (func (".", &st1) == 0);
+  ASSERT (func ("./", &st2) == 0);
+  ASSERT (SAME_INODE (st1, st2));
+  ASSERT (S_ISDIR (st1.st_mode));
+  ASSERT (S_ISDIR (st2.st_mode));
+  ASSERT (func ("/", &st1) == 0);
+  ASSERT (func ("///", &st2) == 0);
+  ASSERT (SAME_INODE (st1, st2));
+  ASSERT (S_ISDIR (st1.st_mode));
+  ASSERT (S_ISDIR (st2.st_mode));
+  ASSERT (func ("..", &st1) == 0);
+  ASSERT (S_ISDIR (st1.st_mode));
+
+  /* Test for error conditions.  */
+  errno = 0;
+  ASSERT (func ("", &st1) == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (func ("nosuch", &st1) == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (func ("nosuch/", &st1) == -1);
+  ASSERT (errno == ENOENT);
+
+  ASSERT (close (creat (BASE "file", 0600)) == 0);
+  ASSERT (func (BASE "file", &st1) == 0);
+  ASSERT (S_ISREG (st1.st_mode));
+  errno = 0;
+  ASSERT (func (BASE "file/", &st1) == -1);
+  ASSERT (errno == ENOTDIR);
+
+  /* Now for some symlink tests, where supported.  We set up:
+     link1 -> directory
+     link2 -> file
+     link3 -> dangling
+     link4 -> loop
+     then test behavior both with and without trailing slash.
+  */
+  if (symlink (".", BASE "link1") != 0)
+    {
+      ASSERT (unlink (BASE "file") == 0);
+      if (print)
+       fputs ("skipping test: symlinks not supported on this filesystem\n",
+              stderr);
+      return 77;
+    }
+  ASSERT (symlink (BASE "file", BASE "link2") == 0);
+  ASSERT (symlink (BASE "nosuch", BASE "link3") == 0);
+  ASSERT (symlink (BASE "link4", BASE "link4") == 0);
+
+  ASSERT (func (BASE "link1", &st1) == 0);
+  ASSERT (S_ISLNK (st1.st_mode));
+  ASSERT (func (BASE "link1/", &st1) == 0);
+  ASSERT (stat (BASE "link1", &st2) == 0);
+  ASSERT (S_ISDIR (st1.st_mode));
+  ASSERT (S_ISDIR (st2.st_mode));
+  ASSERT (SAME_INODE (st1, st2));
+
+  ASSERT (func (BASE "link2", &st1) == 0);
+  ASSERT (S_ISLNK (st1.st_mode));
+  errno = 0;
+  ASSERT (func (BASE "link2/", &st1) == -1);
+  ASSERT (errno == ENOTDIR);
+
+  ASSERT (func (BASE "link3", &st1) == 0);
+  ASSERT (S_ISLNK (st1.st_mode));
+  errno = 0;
+  ASSERT (func (BASE "link3/", &st1) == -1);
+  ASSERT (errno == ENOENT);
+
+  ASSERT (func (BASE "link4", &st1) == 0);
+  ASSERT (S_ISLNK (st1.st_mode));
+  errno = 0;
+  ASSERT (func (BASE "link4/", &st1) == -1);
+  ASSERT (errno == ELOOP);
+
+  /* Cleanup.  */
+  ASSERT (unlink (BASE "file") == 0);
+  ASSERT (unlink (BASE "link1") == 0);
+  ASSERT (unlink (BASE "link2") == 0);
+  ASSERT (unlink (BASE "link3") == 0);
+  ASSERT (unlink (BASE "link4") == 0);
+
+  return 0;
+}
diff --git a/tests/test-stat.c b/tests/test-stat.c
index 337c819..f6777e8 100644
--- a/tests/test-stat.c
+++ b/tests/test-stat.c
@@ -43,39 +43,18 @@

 #define BASE "test-stat.t"

+#include "test-stat.h"
+
+/* Wrapper around stat, which works even if stat is a function-like
+   macro, where test_stat_func(stat) would do the wrong thing.  */
+static int
+do_stat (char const *name, struct stat *st)
+{
+  return stat (name, st);
+}
+
 int
 main ()
 {
-  struct stat st1;
-  struct stat st2;
-  char cwd[PATH_MAX];
-
-  ASSERT (getcwd (cwd, PATH_MAX) == cwd);
-  ASSERT (stat (".", &st1) == 0);
-  ASSERT (stat ("./", &st2) == 0);
-  ASSERT (SAME_INODE (st1, st2));
-  ASSERT (stat (cwd, &st2) == 0);
-  ASSERT (SAME_INODE (st1, st2));
-  ASSERT (stat ("/", &st1) == 0);
-  ASSERT (stat ("///", &st2) == 0);
-  ASSERT (SAME_INODE (st1, st2));
-
-  errno = 0;
-  ASSERT (stat ("", &st1) == -1);
-  ASSERT (errno == ENOENT);
-  errno = 0;
-  ASSERT (stat ("nosuch", &st1) == -1);
-  ASSERT (errno == ENOENT);
-  errno = 0;
-  ASSERT (stat ("nosuch/", &st1) == -1);
-  ASSERT (errno == ENOENT);
-
-  ASSERT (close (creat (BASE "file", 0600)) == 0);
-  ASSERT (stat (BASE "file", &st1) == 0);
-  errno = 0;
-  ASSERT (stat (BASE "file/", &st1) == -1);
-  ASSERT (errno == ENOTDIR);
-  ASSERT (unlink (BASE "file") == 0);
-
-  return 0;
+  return test_stat_func (do_stat);
 }
diff --git a/tests/test-stat.h b/tests/test-stat.h
new file mode 100644
index 0000000..17bb43f
--- /dev/null
+++ b/tests/test-stat.h
@@ -0,0 +1,59 @@
+/* Tests of stat.
+   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 <address@hidden>, 2009.  */
+
+/* This file is designed to test both stat(n,buf) and
+   fstatat(AT_FDCWD,n,buf,0).  FUNC is the function to test.  Assumes
+   that BASE and ASSERT are already defined, and that appropriate
+   headers are already included.  */
+
+static int
+test_stat_func (int (*func) (char const *, struct stat *))
+{
+  struct stat st1;
+  struct stat st2;
+  char cwd[PATH_MAX];
+
+  ASSERT (getcwd (cwd, PATH_MAX) == cwd);
+  ASSERT (func (".", &st1) == 0);
+  ASSERT (func ("./", &st2) == 0);
+  ASSERT (SAME_INODE (st1, st2));
+  ASSERT (func (cwd, &st2) == 0);
+  ASSERT (SAME_INODE (st1, st2));
+  ASSERT (func ("/", &st1) == 0);
+  ASSERT (func ("///", &st2) == 0);
+  ASSERT (SAME_INODE (st1, st2));
+
+  errno = 0;
+  ASSERT (func ("", &st1) == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (func ("nosuch", &st1) == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (func ("nosuch/", &st1) == -1);
+  ASSERT (errno == ENOENT);
+
+  ASSERT (close (creat (BASE "file", 0600)) == 0);
+  ASSERT (func (BASE "file", &st1) == 0);
+  errno = 0;
+  ASSERT (func (BASE "file/", &st1) == -1);
+  ASSERT (errno == ENOTDIR);
+  ASSERT (unlink (BASE "file") == 0);
+
+  return 0;
+}
-- 
1.6.5.rc1


>From ff4e8f6b53d7b8c53856ba8ba2adaad3251e6a88 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Thu, 17 Sep 2009 22:16:56 -0600
Subject: [PATCH 08/12] test-unlinkat: enhance test, to expose Solaris 9 bug

Share the unlink tests with unlinkat.

* tests/test-unlink.c (main): Factor guts...
* tests/test-unlink.h (test_rmdir_func): ...into new file.
* tests/test-rmdir.h (test_rmdir_func): Add parameter.
* tests/test-rmdir.c (main): Adjust caller.
* tests/test-unlinkat.c (main): Likewise.  Add unlink tests.
(unlinker): New helper function.
(rmdirat): Enhance check.
* modules/rmdir-tests (Depends-on): Add stdbool.
* modules/unlink-tests (Depends-on): Likewise.
(Files): Add test-unlink.h.
* modules/openat-tests (Files): Likewise.
(Depends-on): Add unlinkdir.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog             |   14 ++++++++
 modules/openat-tests  |    2 +
 modules/rmdir-tests   |    1 +
 modules/unlink-tests  |    2 +
 tests/test-rmdir.c    |    3 +-
 tests/test-rmdir.h    |   10 +++--
 tests/test-unlink.c   |   61 ++--------------------------------
 tests/test-unlink.h   |   86 +++++++++++++++++++++++++++++++++++++++++++++++++
 tests/test-unlinkat.c |   34 +++++++++++++++++--
 9 files changed, 148 insertions(+), 65 deletions(-)
 create mode 100644 tests/test-unlink.h

diff --git a/ChangeLog b/ChangeLog
index 99833ab..3c54062 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,19 @@
 2009-09-19  Eric Blake  <address@hidden>

+       test-unlinkat: enhance test, to expose Solaris 9 bug
+       * tests/test-unlink.c (main): Factor guts...
+       * tests/test-unlink.h (test_rmdir_func): ...into new file.
+       * tests/test-rmdir.h (test_rmdir_func): Add parameter.
+       * tests/test-rmdir.c (main): Adjust caller.
+       * tests/test-unlinkat.c (main): Likewise.  Add unlink tests.
+       (unlinker): New helper function.
+       (rmdirat): Enhance check.
+       * modules/rmdir-tests (Depends-on): Add stdbool.
+       * modules/unlink-tests (Depends-on): Likewise.
+       (Files): Add test-unlink.h.
+       * modules/openat-tests (Files): Likewise.
+       (Depends-on): Add unlinkdir.
+
        test-fstatat: new test, to expose Solaris 9 bugs
        * tests/test-stat.c (main): Factor guts...
        * tests/test-stat.h (test_stat_func): ...into new file.
diff --git a/modules/openat-tests b/modules/openat-tests
index 42ae6e8..7134f2c 100644
--- a/modules/openat-tests
+++ b/modules/openat-tests
@@ -2,12 +2,14 @@ Files:
 tests/test-lstat.h
 tests/test-rmdir.h
 tests/test-stat.h
+tests/test-unlink.h
 tests/test-fstatat.c
 tests/test-openat.c
 tests/test-unlinkat.c

 Depends-on:
 pathmax
+unlinkdir

 configure.ac:
 AC_CHECK_FUNCS_ONCE([symlink])
diff --git a/modules/rmdir-tests b/modules/rmdir-tests
index 2a68120..fca8a77 100644
--- a/modules/rmdir-tests
+++ b/modules/rmdir-tests
@@ -3,6 +3,7 @@ tests/test-rmdir.h
 tests/test-rmdir.c

 Depends-on:
+stdbool

 configure.ac:
 AC_CHECK_FUNCS_ONCE([symlink])
diff --git a/modules/unlink-tests b/modules/unlink-tests
index b527b61..f0930f0 100644
--- a/modules/unlink-tests
+++ b/modules/unlink-tests
@@ -1,7 +1,9 @@
 Files:
+tests/test-unlink.h
 tests/test-unlink.c

 Depends-on:
+stdbool
 unlinkdir

 configure.ac:
diff --git a/tests/test-rmdir.c b/tests/test-rmdir.c
index 6d55ea9..d7e4da3 100644
--- a/tests/test-rmdir.c
+++ b/tests/test-rmdir.c
@@ -22,6 +22,7 @@

 #include <fcntl.h>
 #include <errno.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/stat.h>
@@ -49,5 +50,5 @@
 int
 main ()
 {
-  return test_rmdir_func (rmdir);
+  return test_rmdir_func (rmdir, true);
 }
diff --git a/tests/test-rmdir.h b/tests/test-rmdir.h
index 2470240..bb7b344 100644
--- a/tests/test-rmdir.h
+++ b/tests/test-rmdir.h
@@ -19,10 +19,11 @@
 /* This file is designed to test both rmdir(n) and
    unlinkat(AT_FDCWD,n,AT_REMOVEDIR).  FUNC is the function to test.
    Assumes that BASE and ASSERT are already defined, and that
-   appropriate headers are already included.  */
+   appropriate headers are already included.  If PRINT, then warn
+   before returning status 77 when symlinks are unsupported.  */

 static int
-test_rmdir_func (int (*func) (char const *name))
+test_rmdir_func (int (*func) (char const *name), bool print)
 {
   /* Remove any leftovers from a previous partial run.  */
   ASSERT (system ("rm -rf " BASE "*") == 0);
@@ -78,8 +79,9 @@ test_rmdir_func (int (*func) (char const *name))
      but not enough to penalize POSIX systems with an rpl_rmdir.  */
   if (symlink (BASE "dir", BASE "link") != 0)
     {
-      fputs ("skipping test: symlinks not supported on this filesystem\n",
-             stderr);
+      if (print)
+       fputs ("skipping test: symlinks not supported on this filesystem\n",
+              stderr);
       return 77;
     }
   ASSERT (mkdir (BASE "dir", 0700) == 0);
diff --git a/tests/test-unlink.c b/tests/test-unlink.c
index 5aaa595..f5df9b6 100644
--- a/tests/test-unlink.c
+++ b/tests/test-unlink.c
@@ -22,6 +22,7 @@

 #include <fcntl.h>
 #include <errno.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -47,64 +48,10 @@

 #define BASE "test-unlink.t"

+#include "test-unlink.h"
+
 int
 main ()
 {
-  /* Remove any leftovers from a previous partial run.  */
-  ASSERT (system ("rm -rf " BASE "*") == 0);
-
-  /* Setup.  */
-  ASSERT (mkdir (BASE "dir", 0700) == 0);
-  ASSERT (close (creat (BASE "dir/file", 0600)) == 0);
-
-  /* Basic error conditions.  */
-  errno = 0;
-  ASSERT (unlink ("") == -1);
-  ASSERT (errno == ENOENT);
-  errno = 0;
-  ASSERT (unlink (BASE "nosuch") == -1);
-  ASSERT (errno == ENOENT);
-  errno = 0;
-  ASSERT (unlink (BASE "nosuch/") == -1);
-  ASSERT (errno == ENOENT);
-  /* Resulting errno after directories is rather varied across
-     implementations (EPERM, EINVAL, EACCES, EBUSY, EISDIR, ENOTSUP);
-     however, we must be careful to not attempt unlink on a directory
-     unless we know it must fail.  */
-  if (cannot_unlink_dir ())
-    {
-      ASSERT (unlink (".") == -1);
-      ASSERT (unlink ("..") == -1);
-      ASSERT (unlink ("/") == -1);
-      ASSERT (unlink (BASE "dir") == -1);
-      ASSERT (mkdir (BASE "dir1", 0700) == 0);
-      ASSERT (unlink (BASE "dir1") == -1);
-      ASSERT (rmdir (BASE "dir1") == 0);
-    }
-  errno = 0;
-  ASSERT (unlink (BASE "dir/file/") == -1);
-  ASSERT (errno == ENOTDIR);
-
-  /* Test symlink behavior.  Specifying trailing slash will attempt
-     unlink of a directory, so only attempt it if we know it must
-     fail.  */
-  if (symlink (BASE "dir", BASE "link") != 0)
-    {
-      ASSERT (unlink (BASE "dir/file") == 0);
-      ASSERT (rmdir (BASE "dir") == 0);
-      fputs ("skipping test: symlinks not supported on this filesystem\n",
-             stderr);
-      return 77;
-    }
-  if (cannot_unlink_dir ())
-    ASSERT (unlink (BASE "link/") == -1);
-  ASSERT (unlink (BASE "link") == 0);
-  ASSERT (symlink (BASE "dir/file", BASE "link") == 0);
-  /* Order here proves unlink of a symlink does not follow through to
-     the file.  */
-  ASSERT (unlink (BASE "link") == 0);
-  ASSERT (unlink (BASE "dir/file") == 0);
-  ASSERT (rmdir (BASE "dir") == 0);
-
-  return 0;
+  return test_unlink_func (unlink, true);
 }
diff --git a/tests/test-unlink.h b/tests/test-unlink.h
new file mode 100644
index 0000000..80cce94
--- /dev/null
+++ b/tests/test-unlink.h
@@ -0,0 +1,86 @@
+/* Tests of unlink.
+   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 <address@hidden>, 2009.  */
+
+/* This file is designed to test both unlink(n) and
+   unlinkat(AT_FDCWD,n,0).  FUNC is the function to test.  Assumes
+   that BASE and ASSERT are already defined, and that appropriate
+   headers are already included.  If PRINT, then warn before returning
+   status 77 when symlinks are unsupported.  */
+
+static int
+test_unlink_func (int (*func) (char const *name), bool print)
+{
+  /* Remove any leftovers from a previous partial run.  */
+  ASSERT (system ("rm -rf " BASE "*") == 0);
+
+  /* Setup.  */
+  ASSERT (mkdir (BASE "dir", 0700) == 0);
+  ASSERT (close (creat (BASE "dir/file", 0600)) == 0);
+
+  /* Basic error conditions.  */
+  errno = 0;
+  ASSERT (func ("") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (func (BASE "nosuch") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (func (BASE "nosuch/") == -1);
+  ASSERT (errno == ENOENT);
+  /* Resulting errno after directories is rather varied across
+     implementations (EPERM, EINVAL, EACCES, EBUSY, EISDIR, ENOTSUP);
+     however, we must be careful to not attempt unlink on a directory
+     unless we know it must fail.  */
+  if (cannot_unlink_dir ())
+    {
+      ASSERT (func (".") == -1);
+      ASSERT (func ("..") == -1);
+      ASSERT (func ("/") == -1);
+      ASSERT (func (BASE "dir") == -1);
+      ASSERT (mkdir (BASE "dir1", 0700) == 0);
+      ASSERT (func (BASE "dir1") == -1);
+      ASSERT (rmdir (BASE "dir1") == 0);
+    }
+  errno = 0;
+  ASSERT (func (BASE "dir/file/") == -1);
+  ASSERT (errno == ENOTDIR);
+
+  /* Test symlink behavior.  Specifying trailing slash will attempt
+     unlink of a directory, so only attempt it if we know it must
+     fail.  */
+  if (symlink (BASE "dir", BASE "link") != 0)
+    {
+      ASSERT (func (BASE "dir/file") == 0);
+      ASSERT (rmdir (BASE "dir") == 0);
+      if (print)
+       fputs ("skipping test: symlinks not supported on this filesystem\n",
+              stderr);
+      return 77;
+    }
+  if (cannot_unlink_dir ())
+    ASSERT (func (BASE "link/") == -1);
+  ASSERT (func (BASE "link") == 0);
+  ASSERT (symlink (BASE "dir/file", BASE "link") == 0);
+  /* Order here proves unlink of a symlink does not follow through to
+     the file.  */
+  ASSERT (func (BASE "link") == 0);
+  ASSERT (func (BASE "dir/file") == 0);
+  ASSERT (rmdir (BASE "dir") == 0);
+
+  return 0;
+}
diff --git a/tests/test-unlinkat.c b/tests/test-unlinkat.c
index fb87a19..8e0a1cd 100644
--- a/tests/test-unlinkat.c
+++ b/tests/test-unlinkat.c
@@ -22,10 +22,13 @@

 #include <fcntl.h>
 #include <errno.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/stat.h>

+#include "unlinkdir.h"
+
 #if !HAVE_SYMLINK
 # define symlink(a,b) (-1)
 #endif
@@ -45,17 +48,42 @@
 #define BASE "test-unlinkat.t"

 #include "test-rmdir.h"
+#include "test-unlink.h"
+
+static int dfd = AT_FDCWD;

 /* Wrapper around unlinkat to test rmdir behavior.  */
 static int
 rmdirat (char const *name)
 {
-  return unlinkat (AT_FDCWD, name, AT_REMOVEDIR);
+  return unlinkat (dfd, name, AT_REMOVEDIR);
+}
+
+/* Wrapper around unlinkat to test unlink behavior.  */
+static int
+unlinker (char const *name)
+{
+  return unlinkat (dfd, name, 0);
 }

 int
 main ()
 {
-  /* FIXME: Add tests of unlinkat(,0), and of fd instead of AT_FDCWD.  */
-  return test_rmdir_func (rmdirat);
+  /* FIXME: Add tests of fd other than ".".  */
+  int result1;
+  int result2;
+  result1 = test_rmdir_func (rmdirat, false);
+  result2 = test_unlink_func (unlinker, false);
+  ASSERT (result1 == result2);
+  dfd = open (".", O_RDONLY);
+  ASSERT (0 <= dfd);
+  result2 = test_rmdir_func (rmdirat, false);
+  ASSERT (result1 == result2);
+  result2 = test_unlink_func (unlinker, false);
+  ASSERT (result1 == result2);
+  ASSERT (close (dfd) == 0);
+  if (result1 == 77)
+    fputs ("skipping test: symlinks not supported on this filesystem\n",
+          stderr);
+  return result1;
 }
-- 
1.6.5.rc1


>From ff971b940b619067d76bb7a893d0e567636f4fc2 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Fri, 18 Sep 2009 19:38:46 -0600
Subject: [PATCH 09/12] openat: fix fstatat bugs on Solaris 9

fstatat(fd,"file/",buf,flag) mistakenly succeeded.

* lib/fstatat.c (rpl_fstatat): Copy recent fixes from lstat and
stat.
* doc/posix-functions/fstatat.texi (fstatat): Document this.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                        |    5 +++++
 doc/posix-functions/fstatat.texi |   14 ++++++++++++++
 lib/fstatat.c                    |   34 ++++++++++++++++++++--------------
 3 files changed, 39 insertions(+), 14 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 3c54062..e79f9dd 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2009-09-19  Eric Blake  <address@hidden>

+       openat: fix fstatat bugs on Solaris 9
+       * lib/fstatat.c (rpl_fstatat): Copy recent fixes from lstat and
+       stat.
+       * doc/posix-functions/fstatat.texi (fstatat): Document this.
+
        test-unlinkat: enhance test, to expose Solaris 9 bug
        * tests/test-unlink.c (main): Factor guts...
        * tests/test-unlink.h (test_rmdir_func): ...into new file.
diff --git a/doc/posix-functions/fstatat.texi b/doc/posix-functions/fstatat.texi
index 4b9fcbc..d1c4c83 100644
--- a/doc/posix-functions/fstatat.texi
+++ b/doc/posix-functions/fstatat.texi
@@ -13,8 +13,22 @@ fstatat
 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.
address@hidden
+On some platforms, @code{fstatat(fd,"file/",buf,flag)} succeeds instead of
+failing with @code{ENOTDIR}.
+Solaris 9.
address@hidden
+For symlinks, when the argument ends in a slash, some platforms don't
+dereference the argument:
+Solaris 9.
 @end itemize

 Portability problems not fixed by Gnulib:
 @itemize
address@hidden
+On platforms where @code{off_t} is a 32-bit type, @code{fstatat} may
+not correctly report the size of files or block devices larger than 2
+GB.  The fix is to use the @code{AC_SYS_LARGEFILE} macro.
address@hidden
+On Windows platforms (excluding Cygwin), @code{st_ino} is always 0.
 @end itemize
diff --git a/lib/fstatat.c b/lib/fstatat.c
index 9b0c1af..59d7422 100644
--- a/lib/fstatat.c
+++ b/lib/fstatat.c
@@ -28,31 +28,37 @@
 #undef fstatat

 /* fstatat should always follow symbolic links that end in /, but on
-   Solaris 9 it doesn't if AT_SYMLINK_NOFOLLOW is specified.  This is
-   the same problem that lstat.c addresses, so solve it in a similar
-   way.  */
+   Solaris 9 it doesn't if AT_SYMLINK_NOFOLLOW is specified.
+   Likewise, trailing slash on a non-directory should be an error.
+   These are the same problems that lstat.c and stat.c address, so
+   solve it in a similar way.  */

 int
 rpl_fstatat (int fd, char const *file, struct stat *st, int flag)
 {
   int result = fstatat (fd, file, st, flag);
+  size_t len;

-  if (result == 0 && (flag & AT_SYMLINK_NOFOLLOW) && S_ISLNK (st->st_mode)
-      && file[strlen (file) - 1] == '/')
+  if (result != 0)
+    return result;
+  len = strlen (file);
+  if (flag & AT_SYMLINK_NOFOLLOW)
     {
-      /* FILE refers to a symbolic link and the name ends with a slash.
-        Get info about the link's referent.  */
-      result = fstatat (fd, file, st, flag & ~AT_SYMLINK_NOFOLLOW);
-      if (result == 0 && ! S_ISDIR (st->st_mode))
+      /* Fix lstat behavior.  */
+      if (file[len - 1] != '/' || S_ISDIR (st->st_mode))
+       return 0;
+      if (!S_ISLNK (st->st_mode))
        {
-         /* fstatat succeeded and FILE references a non-directory.
-            But it was specified via a name including a trailing
-            slash.  Fail with errno set to ENOTDIR to indicate the
-            contradiction.  */
          errno = ENOTDIR;
          return -1;
        }
+      result = fstatat (fd, file, st, flag & ~AT_SYMLINK_NOFOLLOW);
+    }
+  /* Fix stat behavior.  */
+  if (result == 0 && !S_ISDIR (st->st_mode) && file[len - 1] == '/')
+    {
+      errno = ENOTDIR;
+      return -1;
     }
-
   return result;
 }
-- 
1.6.5.rc1


>From 22d7496bac7d391faff10755b17889ecd2f604d4 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Fri, 18 Sep 2009 19:39:06 -0600
Subject: [PATCH 10/12] 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 <address@hidden>
---
 ChangeLog                         |   11 +++++
 doc/posix-functions/unlinkat.texi |   27 +++++++++++++
 lib/unistd.in.h                   |    6 ++-
 lib/unlinkat.c                    |   77 +++++++++++++++++++++++++++++++++++++
 m4/openat.m4                      |    6 ++-
 m4/unistd_h.m4                    |    3 +-
 modules/openat                    |    2 +
 modules/unistd                    |    1 +
 8 files changed, 130 insertions(+), 3 deletions(-)
 create mode 100644 lib/unlinkat.c

diff --git a/ChangeLog b/ChangeLog
index e79f9dd..99e0840 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,16 @@
 2009-09-19  Eric Blake  <address@hidden>

+       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.
diff --git a/doc/posix-functions/unlinkat.texi 
b/doc/posix-functions/unlinkat.texi
index 99c4b3e..dfff9b9 100644
--- a/doc/posix-functions/unlinkat.texi
+++ b/doc/posix-functions/unlinkat.texi
@@ -13,8 +13,35 @@ unlinkat
 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.
address@hidden
+Some systems mistakenly succeed on @code{unlinkat(fd,"file/",flag)}:
+Solaris 9.
 @end itemize

 Portability problems not fixed by Gnulib:
 @itemize
address@hidden
+When @code{unlinkat(fd,name,AT_REMOVEDIR)} fails because the specified
+directory is not empty, the @code{errno} value is system dependent.
address@hidden
+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
address@hidden instead:
+glibc
address@hidden
+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
address@hidden(fd,"symlink-to-dir/",0)} remove @file{dir} and leave
address@hidden dangling; this behavior is counter-intuitive.
+The gnulib module unlinkdir can help determine whether code must be
+cautious of unlinking directories.
address@hidden
+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
diff --git a/lib/unistd.in.h b/lib/unistd.in.h
index 6fa2831..322593d 100644
--- a/lib/unistd.in.h
+++ b/lib/unistd.in.h
@@ -180,7 +180,11 @@ extern int unlink (char const *file);


 #if @GNULIB_UNLINKAT@
-# if address@hidden@
+# if @REPLACE_UNLINKAT@
+#  undef unlinkat
+#  define unlinkat rpl_unlinkat
+# endif
+# if address@hidden@ || @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
index 0000000..bf5d5b8
--- /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;
+}
diff --git a/m4/openat.m4 b/m4/openat.m4
index 445e952..e02a11c 100644
--- 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 @@ 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
diff --git a/m4/unistd_h.m4 b/m4/unistd_h.m4
index d21cafc..648fd86 100644
--- 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 @@ 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;
diff --git a/modules/openat b/modules/openat
index 27e5c50..8cb1345 100644
--- a/modules/openat
+++ b/modules/openat
@@ -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
diff --git a/modules/unistd b/modules/unistd
index 336381a..6cc6cda 100644
--- a/modules/unistd
+++ b/modules/unistd
@@ -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'
 \
-- 
1.6.5.rc1


>From 82bf7d1b42dc970e704f9347862594445f4a22dd Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Sat, 19 Sep 2009 05:50:30 -0600
Subject: [PATCH 11/12] openat: move fstatat and unlinkat into correct files

Code motion, should be no semantic changes.

* m4/openat.m4 (gl_FUNC_OPENAT): Adjust which files will be
compiled.
* lib/openat.c (fstatat, unlinkat): Move...
* lib/fstatat.c (fstatat): ...into correct files.
* lib/unlinkat.c (unlinkat): Likewise.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog      |    7 ++++++
 lib/fstatat.c  |   48 ++++++++++++++++++++++++++++++++++++++++++-
 lib/openat.c   |   62 --------------------------------------------------------
 lib/unlinkat.c |   30 ++++++++++++++++++++++++++-
 m4/openat.m4   |    4 +-
 5 files changed, 85 insertions(+), 66 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 99e0840..1b9f09a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,12 @@
 2009-09-19  Eric Blake  <address@hidden>

+       openat: move fstatat and unlinkat into correct files
+       * m4/openat.m4 (gl_FUNC_OPENAT): Adjust which files will be
+       compiled.
+       * lib/openat.c (fstatat, unlinkat): Move...
+       * lib/fstatat.c (fstatat): ...into correct files.
+       * lib/unlinkat.c (unlinkat): Likewise.
+
        openat: fix unlinkat bugs on Solaris 9
        * lib/unlinkat.c (unlinkat): New file.
        * modules/openat (Depends-on): Add unlink.
diff --git a/lib/fstatat.c b/lib/fstatat.c
index 59d7422..1c6c2d3 100644
--- a/lib/fstatat.c
+++ b/lib/fstatat.c
@@ -25,7 +25,9 @@
 #include <fcntl.h>
 #include <string.h>

-#undef fstatat
+#if HAVE_FSTATAT
+
+# undef fstatat

 /* fstatat should always follow symbolic links that end in /, but on
    Solaris 9 it doesn't if AT_SYMLINK_NOFOLLOW is specified.
@@ -62,3 +64,47 @@ rpl_fstatat (int fd, char const *file, struct stat *st, int 
flag)
     }
   return result;
 }
+
+#else /* !HAVE_FSTATAT */
+
+/* On mingw, the gnulib <sys/stat.h> defines `stat' as a function-like
+   macro; but using it in AT_FUNC_F2 causes compilation failure
+   because the preprocessor sees a use of a macro that requires two
+   arguments but is only given one.  Hence, we need an inline
+   forwarder to get past the preprocessor.  */
+static inline int
+stat_func (char const *name, struct stat *st)
+{
+  return stat (name, st);
+}
+
+/* Likewise, if there is no native `lstat', then the gnulib
+   <sys/stat.h> defined it as stat, which also needs adjustment.  */
+# if !HAVE_LSTAT
+#  undef lstat
+#  define lstat stat_func
+# endif
+
+/* Replacement for Solaris' function by the same name.
+   <http://www.google.com/search?q=fstatat+site:docs.sun.com>
+   First, try to simulate it via l?stat ("/proc/self/fd/FD/FILE").
+   Failing that, simulate it via save_cwd/fchdir/(stat|lstat)/restore_cwd.
+   If either the save_cwd or the restore_cwd fails (relatively unlikely),
+   then give a diagnostic and exit nonzero.
+   Otherwise, this function works just like Solaris' fstatat.  */
+
+# define AT_FUNC_NAME fstatat
+# define AT_FUNC_F1 lstat
+# define AT_FUNC_F2 stat_func
+# define AT_FUNC_USE_F1_COND AT_SYMLINK_NOFOLLOW
+# define AT_FUNC_POST_FILE_PARAM_DECLS , struct stat *st, int flag
+# define AT_FUNC_POST_FILE_ARGS        , st
+# include "at-func.c"
+# undef AT_FUNC_NAME
+# undef AT_FUNC_F1
+# undef AT_FUNC_F2
+# undef AT_FUNC_USE_F1_COND
+# undef AT_FUNC_POST_FILE_PARAM_DECLS
+# undef AT_FUNC_POST_FILE_ARGS
+
+#endif /* !HAVE_FSTATAT */
diff --git a/lib/openat.c b/lib/openat.c
index a0d0ab4..2a194e8 100644
--- a/lib/openat.c
+++ b/lib/openat.c
@@ -156,65 +156,3 @@ openat_needs_fchdir (void)

   return needs_fchdir;
 }
-
-/* On mingw, the gnulib <sys/stat.h> defines `stat' as a function-like
-   macro; but using it in AT_FUNC_F2 causes compilation failure
-   because the preprocessor sees a use of a macro that requires two
-   arguments but is only given one.  Hence, we need an inline
-   forwarder to get past the preprocessor.  */
-static inline int
-stat_func (char const *name, struct stat *st)
-{
-  return stat (name, st);
-}
-
-/* Likewise, if there is no native `lstat', then the gnulib
-   <sys/stat.h> defined it as stat, which also needs adjustment.  */
-#if !HAVE_LSTAT
-# undef lstat
-# define lstat stat_func
-#endif
-
-/* Replacement for Solaris' function by the same name.
-   <http://www.google.com/search?q=fstatat+site:docs.sun.com>
-   First, try to simulate it via l?stat ("/proc/self/fd/FD/FILE").
-   Failing that, simulate it via save_cwd/fchdir/(stat|lstat)/restore_cwd.
-   If either the save_cwd or the restore_cwd fails (relatively unlikely),
-   then give a diagnostic and exit nonzero.
-   Otherwise, this function works just like Solaris' fstatat.  */
-
-#define AT_FUNC_NAME fstatat
-#define AT_FUNC_F1 lstat
-#define AT_FUNC_F2 stat_func
-#define AT_FUNC_USE_F1_COND AT_SYMLINK_NOFOLLOW
-#define AT_FUNC_POST_FILE_PARAM_DECLS , struct stat *st, int flag
-#define AT_FUNC_POST_FILE_ARGS        , st
-#include "at-func.c"
-#undef AT_FUNC_NAME
-#undef AT_FUNC_F1
-#undef AT_FUNC_F2
-#undef AT_FUNC_USE_F1_COND
-#undef AT_FUNC_POST_FILE_PARAM_DECLS
-#undef AT_FUNC_POST_FILE_ARGS
-
-/* Replacement for Solaris' function by the same name.
-   <http://www.google.com/search?q=unlinkat+site:docs.sun.com>
-   First, try to simulate it via (unlink|rmdir) ("/proc/self/fd/FD/FILE").
-   Failing that, simulate it via save_cwd/fchdir/(unlink|rmdir)/restore_cwd.
-   If either the save_cwd or the restore_cwd fails (relatively unlikely),
-   then give a diagnostic and exit nonzero.
-   Otherwise, this function works just like Solaris' unlinkat.  */
-
-#define AT_FUNC_NAME unlinkat
-#define AT_FUNC_F1 rmdir
-#define AT_FUNC_F2 unlink
-#define AT_FUNC_USE_F1_COND AT_REMOVEDIR
-#define AT_FUNC_POST_FILE_PARAM_DECLS , int flag
-#define AT_FUNC_POST_FILE_ARGS        /* empty */
-#include "at-func.c"
-#undef AT_FUNC_NAME
-#undef AT_FUNC_F1
-#undef AT_FUNC_F2
-#undef AT_FUNC_USE_F1_COND
-#undef AT_FUNC_POST_FILE_PARAM_DECLS
-#undef AT_FUNC_POST_FILE_ARGS
diff --git a/lib/unlinkat.c b/lib/unlinkat.c
index bf5d5b8..7302252 100644
--- a/lib/unlinkat.c
+++ b/lib/unlinkat.c
@@ -28,7 +28,9 @@

 #include "openat.h"

-#undef unlinkat
+#if HAVE_UNLINKAT
+
+# undef unlinkat

 /* unlinkat without AT_REMOVEDIR does not honor trailing / on Solaris
    9.  Solve it in a similar manner to unlink.  */
@@ -75,3 +77,29 @@ rpl_unlinkat (int fd, char const *name, int flag)
     result = unlinkat (fd, name, flag);
   return result;
 }
+
+#else /* !HAVE_UNLINKAT */
+
+/* Replacement for Solaris' function by the same name.
+   <http://www.google.com/search?q=unlinkat+site:docs.sun.com>
+   First, try to simulate it via (unlink|rmdir) ("/proc/self/fd/FD/FILE").
+   Failing that, simulate it via save_cwd/fchdir/(unlink|rmdir)/restore_cwd.
+   If either the save_cwd or the restore_cwd fails (relatively unlikely),
+   then give a diagnostic and exit nonzero.
+   Otherwise, this function works just like Solaris' unlinkat.  */
+
+# define AT_FUNC_NAME unlinkat
+# define AT_FUNC_F1 rmdir
+# define AT_FUNC_F2 unlink
+# define AT_FUNC_USE_F1_COND AT_REMOVEDIR
+# define AT_FUNC_POST_FILE_PARAM_DECLS , int flag
+# define AT_FUNC_POST_FILE_ARGS        /* empty */
+# include "at-func.c"
+# undef AT_FUNC_NAME
+# undef AT_FUNC_F1
+# undef AT_FUNC_F2
+# undef AT_FUNC_USE_F1_COND
+# undef AT_FUNC_POST_FILE_PARAM_DECLS
+# undef AT_FUNC_POST_FILE_ARGS
+
+#endif /* !HAVE_UNLINKAT */
diff --git a/m4/openat.m4 b/m4/openat.m4
index e02a11c..b824393 100644
--- a/m4/openat.m4
+++ b/m4/openat.m4
@@ -1,4 +1,4 @@
-# serial 22
+# serial 23
 # See if we need to use our replacement for Solaris' openat et al functions.

 dnl Copyright (C) 2004-2009 Free Software Foundation, Inc.
@@ -25,7 +25,7 @@ AC_DEFUN([gl_FUNC_OPENAT],
   AC_LIBOBJ([openat-proc])
   AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
   AC_CHECK_FUNCS_ONCE([lchmod])
-  AC_REPLACE_FUNCS([fchmodat mkdirat openat])
+  AC_REPLACE_FUNCS([fchmodat fstatat mkdirat openat unlinkat])
   AC_REQUIRE([AC_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK])
   case $ac_cv_func_openat+$ac_cv_func_lstat_dereferences_slashed_symlink in
   yes+yes) ;;
-- 
1.6.5.rc1


>From 4c45e93c58de6532275c22a9153ecdfe516928ff Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Sat, 19 Sep 2009 11:16:58 -0600
Subject: [PATCH 12/12] openat: fix openat bugs on Solaris 9

openat(fd,"file/",O_RDONLY) mistakenly succeeded.

* lib/openat.c (rpl_openat): Work around Solaris 9 bug.
* m4/openat.m4 (gl_FUNC_OPENAT): Also replace openat on Solaris.
* modules/openat (Depends-on): Add open.
* m4/fcntl_h.m4 (gl_FCNTL_H_DEFAULTS): Provide new default.
* modules/fcntl-h (Makefile.am): Substitute it.
* lib/fcntl.in.h (openat): Declare replacement.
* doc/posix-functions/openat.texi (openat): Document this.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                       |    9 ++++
 doc/posix-functions/openat.texi |    5 ++
 lib/fcntl.in.h                  |    4 +-
 lib/openat.c                    |   97 +++++++++++++++++++++++++++++++++++++++
 m4/fcntl_h.m4                   |   11 ++--
 m4/openat.m4                    |    4 +-
 modules/fcntl-h                 |    1 +
 modules/openat                  |    1 +
 8 files changed, 125 insertions(+), 7 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 1b9f09a..94b8465 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,14 @@
 2009-09-19  Eric Blake  <address@hidden>

+       openat: fix openat bugs on Solaris 9
+       * lib/openat.c (rpl_openat): Work around Solaris 9 bug.
+       * m4/openat.m4 (gl_FUNC_OPENAT): Also replace openat on Solaris.
+       * modules/openat (Depends-on): Add open.
+       * m4/fcntl_h.m4 (gl_FCNTL_H_DEFAULTS): Provide new default.
+       * modules/fcntl-h (Makefile.am): Substitute it.
+       * lib/fcntl.in.h (openat): Declare replacement.
+       * doc/posix-functions/openat.texi (openat): Document this.
+
        openat: move fstatat and unlinkat into correct files
        * m4/openat.m4 (gl_FUNC_OPENAT): Adjust which files will be
        compiled.
diff --git a/doc/posix-functions/openat.texi b/doc/posix-functions/openat.texi
index 5a99e0c..2bfe611 100644
--- a/doc/posix-functions/openat.texi
+++ b/doc/posix-functions/openat.texi
@@ -13,6 +13,11 @@ openat
 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.
address@hidden
+This function does not fail when the file name argument ends in a slash
+and (without the slash) names a nonexistent file or a file that is not a
+directory, on some platforms:
+Solaris 9.
 @end itemize

 Portability problems not fixed by Gnulib:
diff --git a/lib/fcntl.in.h b/lib/fcntl.in.h
index cadb6a1..0ae8213 100644
--- a/lib/fcntl.in.h
+++ b/lib/fcntl.in.h
@@ -62,9 +62,11 @@ extern int open (const char *filename, int flags, ...);
 #endif

 #if @GNULIB_OPENAT@
-# if address@hidden@
+# if @REPLACE_OPENAT@
 #  undef openat
 #  define openat rpl_openat
+# endif
+# if address@hidden@ || @REPLACE_OPENAT@
 int openat (int fd, char const *file, int flags, /* mode_t mode */ ...);
 # endif
 #elif defined GNULIB_POSIXCHECK
diff --git a/lib/openat.c b/lib/openat.c
index 2a194e8..7e46a26 100644
--- a/lib/openat.c
+++ b/lib/openat.c
@@ -22,12 +22,107 @@

 #include <stdarg.h>
 #include <stddef.h>
+#include <string.h>
 #include <sys/stat.h>

 #include "dirname.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */
 #include "openat-priv.h"
 #include "save-cwd.h"

+#if HAVE_OPENAT
+
+# undef openat
+
+/* Like openat, but work around Solaris 9 bugs with trailing slash.  */
+int
+rpl_openat (int dfd, char const *filename, int flags, ...)
+{
+  mode_t mode;
+  int fd;
+
+  mode = 0;
+  if (flags & O_CREAT)
+    {
+      va_list arg;
+      va_start (arg, flags);
+
+      /* We have to use PROMOTED_MODE_T instead of mode_t, otherwise GCC 4
+        creates crashing code when 'mode_t' is smaller than 'int'.  */
+      mode = va_arg (arg, PROMOTED_MODE_T);
+
+      va_end (arg);
+    }
+
+#if OPEN_TRAILING_SLASH_BUG
+  /* If the filename ends in a slash and one of O_CREAT, O_WRONLY, O_RDWR
+     is specified, then fail.
+     Rationale: POSIX <http://www.opengroup.org/susv3/basedefs/xbd_chap04.html>
+     says that
+       "A pathname that contains at least one non-slash character and that
+        ends with one or more trailing slashes shall be resolved as if a
+        single dot character ( '.' ) were appended to the pathname."
+     and
+       "The special filename dot shall refer to the directory specified by
+        its predecessor."
+     If the named file already exists as a directory, then
+       - if O_CREAT is specified, open() must fail because of the semantics
+         of O_CREAT,
+       - if O_WRONLY or O_RDWR is specified, open() must fail because POSIX
+         <http://www.opengroup.org/susv3/functions/open.html> says that it
+         fails with errno = EISDIR in this case.
+     If the named file does not exist or does not name a directory, then
+       - if O_CREAT is specified, open() must fail since open() cannot create
+         directories,
+       - if O_WRONLY or O_RDWR is specified, open() must fail because the
+         file does not contain a '.' directory.  */
+  if (flags & (O_CREAT | O_WRONLY | O_RDWR))
+    {
+      size_t len = strlen (filename);
+      if (len > 0 && filename[len - 1] == '/')
+       {
+         errno = EISDIR;
+         return -1;
+       }
+    }
+#endif
+
+  fd = openat (dfd, filename, flags, mode);
+
+#if OPEN_TRAILING_SLASH_BUG
+  /* If the filename ends in a slash and fd does not refer to a directory,
+     then fail.
+     Rationale: POSIX <http://www.opengroup.org/susv3/basedefs/xbd_chap04.html>
+     says that
+       "A pathname that contains at least one non-slash character and that
+        ends with one or more trailing slashes shall be resolved as if a
+        single dot character ( '.' ) were appended to the pathname."
+     and
+       "The special filename dot shall refer to the directory specified by
+        its predecessor."
+     If the named file without the slash is not a directory, open() must fail
+     with ENOTDIR.  */
+  if (fd >= 0)
+    {
+      size_t len = strlen (filename);
+      if (len > 0 && filename[len - 1] == '/')
+       {
+         struct stat statbuf;
+
+         if (fstat (fd, &statbuf) >= 0 && !S_ISDIR (statbuf.st_mode))
+           {
+             close (fd);
+             errno = ENOTDIR;
+             return -1;
+           }
+       }
+    }
+#endif
+
+  return fd;
+}
+
+#else /* !HAVE_OPENAT */
+
 /* Replacement for Solaris' openat function.
    <http://www.google.com/search?q=openat+site:docs.sun.com>
    First, try to simulate it via open ("/proc/self/fd/FD/FILE").
@@ -156,3 +251,5 @@ openat_needs_fchdir (void)

   return needs_fchdir;
 }
+
+#endif /* !HAVE_OPENAT */
diff --git a/m4/fcntl_h.m4 b/m4/fcntl_h.m4
index 7100d19..5eed088 100644
--- a/m4/fcntl_h.m4
+++ b/m4/fcntl_h.m4
@@ -1,4 +1,4 @@
-# serial 3
+# serial 4
 # Configure fcntl.h.
 dnl Copyright (C) 2006, 2007, 2009 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
@@ -91,9 +91,10 @@ AC_DEFUN([gl_FCNTL_MODULE_INDICATOR],

 AC_DEFUN([gl_FCNTL_H_DEFAULTS],
 [
-  GNULIB_OPEN=0;   AC_SUBST([GNULIB_OPEN])
-  GNULIB_OPENAT=0; AC_SUBST([GNULIB_OPENAT])
+  GNULIB_OPEN=0;    AC_SUBST([GNULIB_OPEN])
+  GNULIB_OPENAT=0;  AC_SUBST([GNULIB_OPENAT])
   dnl Assume proper GNU behavior unless another module says otherwise.
-  HAVE_OPENAT=1;  AC_SUBST([HAVE_OPENAT])
-  REPLACE_OPEN=0; AC_SUBST([REPLACE_OPEN])
+  HAVE_OPENAT=1;    AC_SUBST([HAVE_OPENAT])
+  REPLACE_OPEN=0;   AC_SUBST([REPLACE_OPEN])
+  REPLACE_OPENAT=0; AC_SUBST([REPLACE_OPENAT])
 ])
diff --git a/m4/openat.m4 b/m4/openat.m4
index b824393..42df3ee 100644
--- a/m4/openat.m4
+++ b/m4/openat.m4
@@ -1,4 +1,4 @@
-# serial 23
+# serial 24
 # See if we need to use our replacement for Solaris' openat et al functions.

 dnl Copyright (C) 2004-2009 Free Software Foundation, Inc.
@@ -32,6 +32,8 @@ AC_DEFUN([gl_FUNC_OPENAT],
   yes+*)
     # Solaris 9 has *at functions, but uniformly mishandles trailing
     # slash in all of them.
+    AC_LIBOBJ([openat])
+    REPLACE_OPENAT=1
     AC_LIBOBJ([fstatat])
     REPLACE_FSTATAT=1
     AC_LIBOBJ([unlinkat])
diff --git a/modules/fcntl-h b/modules/fcntl-h
index 2b811d1..ea76181 100644
--- a/modules/fcntl-h
+++ b/modules/fcntl-h
@@ -28,6 +28,7 @@ fcntl.h: fcntl.in.h
              -e 's|@''GNULIB_OPEN''@|$(GNULIB_OPEN)|g' \
              -e 's|@''GNULIB_OPENAT''@|$(GNULIB_OPENAT)|g' \
              -e 's|@''REPLACE_OPEN''@|$(REPLACE_OPEN)|g' \
+             -e 's|@''REPLACE_OPENAT''@|$(REPLACE_OPENAT)|g' \
              -e 's|@''HAVE_OPENAT''@|$(HAVE_OPENAT)|g' \
              -e '/definition of GL_LINK_WARNING/r $(LINK_WARNING_H)' \
              < $(srcdir)/fcntl.in.h; \
diff --git a/modules/openat b/modules/openat
index 8cb1345..150853f 100644
--- a/modules/openat
+++ b/modules/openat
@@ -27,6 +27,7 @@ inline
 intprops
 lchown
 lstat
+open
 openat-die
 rmdir
 same-inode
-- 
1.6.5.rc1


reply via email to

[Prev in Thread] Current Thread [Next in Thread]