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: Thu, 17 Sep 2009 23:24:47 +0000 (UTC)
User-agent: Loom/3.14 (http://gmane.org/)

Jim Meyering <jim <at> meyering.net> writes:

> > I'm working on a patch to fix unlink("file/") on Solaris 9.  But as a
> > prerequisite (to avoid anyone corrupting their file system if running the 
unit
> > test as root, and accidentally unlinking an empty directory), I noticed that
> > mingw failed to compile unlinkdir.c due to a missing geteuid.  This works
> > around it:
> >
> Looks good.  Thanks!
> 
> $s/.*/for which no one can unlink a directory./

That wording fix is queued into my next push, as are the canonicalize 
improvements.  Here's the current state of my series to fix stat, lstat, 
unlink, and remove; the tests now pass on Linux and cygwin 1.7 (no help 
needed), Solaris 8 (all four functions ignore trailing slash), mingw (stat 
mishandles trailing slash on directories, and remove ignores directories), and 
cygwin 1.5 (remove mishandles trailing . on directories).  It does not (yet) 
address additional trailing slash bugs in fstatat and unlinkat on Solaris 9, so 
I'll probably hammer on the series a bit more before pushing to savannah in 
case I end up rebasing for any reason.

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


>From 4cd46c95415b7f989a1257b3bdea6d9aa54549f2 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Tue, 15 Sep 2009 07:11:40 -0600
Subject: [PATCH 1/6] 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.

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             |   79 +++++++++++++++++++++++++++++++++++++++++
 13 files changed, 298 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 70b592d..62f1c40 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,22 @@
 2009-09-17  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-17  Eric Blake  <address@hidden>
+
        canonicalize: in CAN_ALL_BUT_LAST, allow trailing slash
        * lib/canonicalize.c (canonicalize_filename_mode): Skip trailing
        slashes when checking if last component is missing.
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 e1471b8..e9771d4 100644
--- a/lib/openat.c
+++ b/lib/openat.c
@@ -158,6 +158,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").
@@ -168,7 +186,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..07aa323
--- /dev/null
+++ b/lib/stat.c
@@ -0,0 +1,78 @@
+/* Work around some limitations of mingw 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..145edb5
--- /dev/null
+++ b/tests/test-stat.c
@@ -0,0 +1,79 @@
+/* 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)
+
+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 ("test-stat.tmp", 0600)) == 0);
+  ASSERT (stat ("test-stat.tmp", &st1) == 0);
+  errno = 0;
+  ASSERT (stat ("test-stat.tmp/", &st1) == -1);
+  ASSERT (errno == ENOTDIR);
+  ASSERT (unlink ("test-stat.tmp") == 0);
+
+  return 0;
+}
-- 
1.6.4.2


>From 9837778ddd8eb803dd5d01f46fe835578af3efe4 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Tue, 15 Sep 2009 17:08:39 -0600
Subject: [PATCH 2/6] 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.

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

diff --git a/ChangeLog b/ChangeLog
index 62f1c40..20b59ab 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2009-09-17  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 07aa323..954d22c 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,27 @@
 #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.  */
+  size_t len = strlen (name);
+  if (result == 0 && !S_ISDIR (st->st_mode) && ISSLASH (name[len - 1]))
+    {
+      result = -1;
+      errno = ENOTDIR;
+    }
+#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 +88,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 +96,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.4.2


>From f2f75434d513ca7bda02cc062c25ed940587ff2d Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Tue, 15 Sep 2009 14:43:14 -0600
Subject: [PATCH 3/6] 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 20b59ab..3f4b02e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,18 @@
 2009-09-17  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.4.2


>From be7e86ff364d0673f3745b893f746841a6538cc8 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Thu, 17 Sep 2009 15:55:24 -0600
Subject: [PATCH 4/6] 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.

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 3f4b02e..6711e55 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,13 @@
 2009-09-17  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.4.2


>From efb0af184a9c3a5a7173746de0d97e43b8a7b4f9 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Wed, 16 Sep 2009 17:13:20 -0600
Subject: [PATCH 5/6] 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.

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                    |   46 ++++++++++++++++
 m4/unistd_h.m4                  |    4 +-
 m4/unlink.m4                    |   27 ++++++++++
 modules/unistd                  |    2 +
 modules/unlink                  |   26 +++++++++
 modules/unlink-tests            |   12 ++++
 tests/test-unlink.c             |  109 +++++++++++++++++++++++++++++++++++++++
 11 files changed, 267 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 6711e55..204a1a3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,17 @@
 2009-09-17  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..59e1e3b
--- /dev/null
+++ b/lib/unlink.c
@@ -0,0 +1,46 @@
+/* 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 <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 something if it doesn't exist.  */
+      struct stat st;
+      result = lstat (name, &st);
+    }
+  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_A
ND_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..beb7a69
--- /dev/null
+++ b/tests/test-unlink.c
@@ -0,0 +1,109 @@
+/* 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 attempt unlink
+     of a full directory, which must fail (for any number of
+     reasons).  */
+  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;
+    }
+  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.4.2


>From 4405362094fedee63a0e83257fe281a923df314c Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Mon, 14 Sep 2009 16:57:55 -0600
Subject: [PATCH 6/6] 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.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                       |   12 ++++
 MODULES.html.sh                 |    1 +
 doc/posix-functions/remove.texi |   13 ++++-
 lib/remove.c                    |   42 +++++++++++++
 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             |  127 +++++++++++++++++++++++++++++++++++++++
 11 files changed, 292 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 204a1a3..8ae5910 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,17 @@
 2009-09-17  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..88a7b1c
--- /dev/null
+++ b/lib/remove.c
@@ -0,0 +1,42 @@
+/* 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>
+
+#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..113588d
--- /dev/null
+++ b/tests/test-remove.c
@@ -0,0 +1,127 @@
+/* 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)
+
+int
+main ()
+{
+  /* Remove any leftovers from a previous partial run.  */
+  ASSERT (system ("rm -rf test-remove.dir test-remove.link") == 0);
+
+  /* Setup.  */
+  ASSERT (mkdir ("test-remove.dir", 0700) == 0);
+  ASSERT (close (creat ("test-remove.dir/file", 0600)) == 0);
+
+  /* Basic error conditions.  */
+  errno = 0;
+  ASSERT (remove ("") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (remove ("test-remove.nosuch") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (remove ("test-remove.nosuch/") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (remove (".") == -1);
+  ASSERT (errno == EINVAL || errno == EBUSY);
+  ASSERT (remove ("..") == -1);
+  /* Resulting errno after ".." or "/" is too varied to test; it is
+     reasonable to see any of EINVAL, EEXIST, ENOTEMPTY, EACCES.  */
+  {
+    int saved_errno;
+    errno = 0;
+    ASSERT (remove ("/") == -1);
+    saved_errno = errno;
+    errno = 0;
+    ASSERT (remove ("///") == -1);
+    ASSERT (errno == saved_errno);
+  }
+  errno = 0;
+  ASSERT (remove ("test-remove.dir/file/") == -1);
+  ASSERT (errno == ENOTDIR);
+
+  /* Non-empty directory.  */
+  errno = 0;
+  ASSERT (remove ("test-remove.dir") == -1);
+  ASSERT (errno == EEXIST || errno == ENOTEMPTY);
+
+  /* Non-directory.  */
+  ASSERT (remove ("test-remove.dir/file") == 0);
+
+  /* Empty directory.  */
+  errno = 0;
+  ASSERT (remove ("test-remove.dir/./") == -1);
+  ASSERT (errno == EINVAL);
+  ASSERT (remove ("test-remove.dir") == 0);
+
+  /* Test symlink behavior.  Specifying trailing slash should remove
+     referent directory, or cause ENOTDIR failure, but not touch
+     symlink.  */
+  if (symlink ("test-remove.dir", "test-remove.link") != 0)
+    {
+      fputs ("skipping test: symlinks not supported on this filesystem\n",
+             stderr);
+      return 77;
+    }
+  ASSERT (mkdir ("test-remove.dir", 0700) == 0);
+  errno = 0;
+  if (remove ("test-remove.link/") == 0)
+    {
+      struct stat st;
+      errno = 0;
+      ASSERT (stat ("test-remove.link", &st) == -1);
+      ASSERT (errno == ENOENT);
+    }
+  else
+    ASSERT (remove ("test-remove.dir") == 0);
+  {
+    struct stat st;
+    ASSERT (lstat ("test-remove.link", &st) == 0);
+    ASSERT (S_ISLNK (st.st_mode));
+  }
+  ASSERT (remove ("test-remove.link") == 0);
+
+  return 0;
+}
-- 
1.6.4.2







reply via email to

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