[Top][All Lists]
[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