[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: |
Sat, 19 Sep 2009 14:04:54 -0600 |
User-agent: |
Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.23) Gecko/20090812 Thunderbird/2.0.0.23 Mnenhy/0.7.6.666 |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
According to Eric Blake on 9/17/2009 5:24 PM:
> Actually, on review, I already see a reason to rebase - test-unlink.c tries
> to
> unlink symlink-to-dir/ without first checking whether that is safe if done as
> root.
>
> Eric Blake (6):
> stat: new module, for mingw bug
> stat: fix Solaris 9 bug
> stat: add as dependency to other modules
> lstat: fix Solaris 9 bug
> unlink: new module, for Solaris 9 bug
> remove: new module, for mingw and Solaris 9 bugs
Here's the polished series; now pushed. I've tested on Linux, cygwin 1.5,
cygwin 1.7, mingw, OpenBSD, Solaris 8, 9, 10.
Eric Blake (12):
[1/12] stat: new module, for mingw bug
Depending on the current directory, either stat(".",buf) or stat("./",buf)
would fail on mingw. I did not find a way to use an object-like macro
while still keeping struct stat alive, so I had to use a function-like
macro (and some of the earlier iterations of my later patches fell foul
when I tried to pass &stat as a function pointer, cleaned up by instead
passing via a function pointer to a wrapper function). Because mingw
lacks lstat, this means that lstat is also an object-like macro in some
instances.
[2/12] stat: fix Solaris 9 bug
stat("file/",buf) mistakenly succeeded. This patch required some
refactoring in stat.c, since the two bugs are distinct enough that
rpl_stat should not have to deal with both bugs at once.
[3/12] stat: add as dependency to other modules
Hopefully I got the right set of modules. I left out modules like openat
if they already depended on lstat (since lstat now depends on stat), I
also left out modules that used stat on well-controlled non-directory
strings and thus did not trigger the bugs from [1/12] or [2/12].
[4/12] lstat: fix Solaris 9 bug
lstat("file/",buf) mistakenly succeeded. But since I just fixed the same
bug in stat, the fix to lstat.c is actually fewer lines of code.
[5/12] unlink: new module, for Solaris 9 bug
unlink("file/") mistakenly succeeded. unlink is conceptually harder to
fix than stat - in the former, just try it, then fix any mistakes; in the
latter, you can't try it unless you can prove it will do the right thing.
I intentionally did not replace unlink on Solaris 10, which means there
are still platforms where unlink("link-to-dir/") as root will unlink "dir"
and leave "link-to-dir" dangling (per POSIX), in contrast to the GNU
behavior of failing with ENOTDIR. But I also intentionally did not make
the Solaris 9 replacement try to match Solaris 10 behavior (the cost of a
series of readlinks just to do this corner case, which will fail for all
but non-root, is atrocious). It took me a couple of iterations before
this final version to be satisfied that the new test should be safe to run
as root (that is, the test does not try to unlink a directory if it can't
prove that such an action would fail).
[6/12] remove: new module, for mingw and Solaris 9 bugs
mingw obeys C89 but not POSIX, so directories are not handled. Solaris 9
remove("file/") mistakenly succeeded (because of the unlink bug). The fix
to both issues looks deceptively simple, thanks to the prerequisite patches.
[7/12] test-fstatat: new test, to expose Solaris 9 bugs
Share the lstat and stat tests ([1/12], [2/12], [4/12]) with fstatat;
proves that Solaris 9 has a bug in fstatat.
[8/12] test-unlinkat: enhance test, to expose Solaris 9 bug
Share the unlink tests ([5/12]) with unlinkat; rmdir tests were already
shared but needed a bit of refactoring to handle symlink avoidance on
mingw. Proves that Solaris 9 has a bug in unlinkat.
[9/12] openat: fix fstatat bugs on Solaris 9
fstatat(fd,"file/",buf,flag) mistakenly succeeded. This reuses the same
fixes as in [2/12] and [4/12], but does not need to worry about the fixes
in [1/12] since mingw lacks fstatat.
[10/12] openat: fix unlinkat bugs on Solaris 9
unlinkat(fd,"file/",0) mistakenly succeeded. This reuses the same fix as
in [5/12].
[11/12] openat: move fstatat and unlinkat into correct files
Code motion, with no semantic change. It was easier to put fstatat and
unlinkat in the same file, whether we were compiling the /proc/self/fd
version for systems without *at, or compiling the rpl_ version for Solaris
9; it also makes it easier to provide rpl_openat.
[12/12] openat: fix openat bugs on Solaris 9
openat(fd,"file/",O_RDONLY) mistakenly succeeded. This borrows the
trailing slash fixes from lib/open.c, but does not worry about /dev/null
or fchdir fixes since Solaris 9 already has fchdir. Finally fixes
test-openat-safer, which fails as part of the coreutils 7.6 testsuite on
Solaris 9. I would still like to make test-open, test-open-safer, and
test-openat share more code paths with test-openat-safer, but that can
come at a later date when I implement O_CLOEXEC.
Several of these bug fixes are visible to the command line using
coreutils, for example by using stat(1) or unlink(1), so this can probably
be documented in a coreutils NEWS item the same as was done in 7.6 for
link(1). Also, at least the stat(2) bug is visible from find(1):
pre:
$ touch file
$ find file/
file/
post, or on working platform:
$ touch file
$ find file/
find: `file/': Not a directory
and I suspect (although I haven't tried to prove) that something like:
$ touch file
$ ln -s file/ link
$ find -L ...
might expose openat() failures through fts().
- --
Don't work too hard, make some time for fun as well!
Eric Blake address@hidden
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Public key at home.comcast.net/~ericblake/eblake.gpg
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/
iEYEARECAAYFAkq1OWUACgkQ84KuGfSFAYCMMwCfU71WiDUD+sWKB6Z99UGSSpCV
kCoAn02AnbHamU+Phi4eZQxbzv+ebbJp
=0AUC
-----END PGP SIGNATURE-----
>From 5794d89bd24981d2974f1504a04bf09b7efaf251 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Tue, 15 Sep 2009 07:11:40 -0600
Subject: [PATCH 01/12] stat: new module, for mingw bug
Depending on the current directory, either stat(".",buf) or
stat("./",buf) would fail on mingw.
* modules/stat: New file.
* lib/stat.c: Likewise.
* m4/stat.m4 (gl_FUNC_STAT): Likewise.
* m4/sys_stat_h.m4 (gl_SYS_STAT_H_DEFAULTS): Add witnesses.
* modules/sys_stat (Makefile.am): Use them.
* lib/sys_stat.in.h (stat): Declare replacement.
* lib/openat.c (fstatat): Deal with lstat and stat being function
macros.
* modules/openat (Depends-on): Add inline.
* MODULES.html.sh (systems lacking POSIX:2008): Mention module.
* doc/posix-functions/stat.texi (stat): Likewise.
* modules/stat-tests: New test.
* tests/test-stat.c: Likewise.
Signed-off-by: Eric Blake <address@hidden>
---
ChangeLog | 17 +++++++++
MODULES.html.sh | 1 +
doc/posix-functions/stat.texi | 14 +++++++-
lib/openat.c | 20 ++++++++++-
lib/stat.c | 78 +++++++++++++++++++++++++++++++++++++++
lib/sys_stat.in.h | 16 ++++++++
m4/stat.m4 | 31 ++++++++++++++++
m4/sys_stat_h.m4 | 4 ++-
modules/openat | 1 +
modules/stat | 26 +++++++++++++
modules/stat-tests | 12 ++++++
modules/sys_stat | 2 +
tests/test-stat.c | 81 +++++++++++++++++++++++++++++++++++++++++
13 files changed, 300 insertions(+), 3 deletions(-)
create mode 100644 lib/stat.c
create mode 100644 m4/stat.m4
create mode 100644 modules/stat
create mode 100644 modules/stat-tests
create mode 100644 tests/test-stat.c
diff --git a/ChangeLog b/ChangeLog
index fab0733..fde8d07 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,20 @@
+2009-09-19 Eric Blake <address@hidden>
+
+ stat: new module, for mingw bug
+ * modules/stat: New file.
+ * lib/stat.c: Likewise.
+ * m4/stat.m4 (gl_FUNC_STAT): Likewise.
+ * m4/sys_stat_h.m4 (gl_SYS_STAT_H_DEFAULTS): Add witnesses.
+ * modules/sys_stat (Makefile.am): Use them.
+ * lib/sys_stat.in.h (stat): Declare replacement.
+ * lib/openat.c (fstatat): Deal with lstat and stat being function
+ macros.
+ * modules/openat (Depends-on): Add inline.
+ * MODULES.html.sh (systems lacking POSIX:2008): Mention module.
+ * doc/posix-functions/stat.texi (stat): Likewise.
+ * modules/stat-tests: New test.
+ * tests/test-stat.c: Likewise.
+
2009-09-19 Jim Meyering <address@hidden>
syntax-check: detect unnecessary inclusion of canonicalize.h
diff --git a/MODULES.html.sh b/MODULES.html.sh
index 76741b3..fbea183 100755
--- a/MODULES.html.sh
+++ b/MODULES.html.sh
@@ -2331,6 +2331,7 @@ func_all_modules ()
func_module socket
func_module spawn
func_module sprintf-posix
+ func_module stat
func_module strdup-posix
func_module string
func_module strings
diff --git a/doc/posix-functions/stat.texi b/doc/posix-functions/stat.texi
index 97992e4..a7011ed 100644
--- a/doc/posix-functions/stat.texi
+++ b/doc/posix-functions/stat.texi
@@ -4,10 +4,14 @@ stat
POSIX specification:
@url{http://www.opengroup.org/onlinepubs/9699919799/functions/stat.html}
-Gnulib module: ---
+Gnulib module: stat
Portability problems fixed by Gnulib:
@itemize
address@hidden
+On some platforms, @code{stat(".",buf)} and @code{stat("./",buf)} give
+different results:
+mingw.
@end itemize
Portability problems not fixed by Gnulib:
@@ -19,4 +23,12 @@ stat
@item
Cygwin's @code{stat} function sometimes sets @code{errno} to @code{EACCES} when
@code{ENOENT} would be more appropriate.
address@hidden
+On Windows platforms (excluding Cygwin), @code{st_ino} is always 0.
address@hidden
+Because of the definition of @code{struct stat}, it is not possible to
+portably replace @code{stat} via an object-like macro. Therefore,
+expressions such as @code{(islnk ? lstat : stat) (name, buf)} are not
+portable, and should instead be written @code{islnk ? lstat (name,
+buf) : stat (name, buf)}.
@end itemize
diff --git a/lib/openat.c b/lib/openat.c
index d22d830..a0d0ab4 100644
--- a/lib/openat.c
+++ b/lib/openat.c
@@ -157,6 +157,24 @@ openat_needs_fchdir (void)
return needs_fchdir;
}
+/* On mingw, the gnulib <sys/stat.h> defines `stat' as a function-like
+ macro; but using it in AT_FUNC_F2 causes compilation failure
+ because the preprocessor sees a use of a macro that requires two
+ arguments but is only given one. Hence, we need an inline
+ forwarder to get past the preprocessor. */
+static inline int
+stat_func (char const *name, struct stat *st)
+{
+ return stat (name, st);
+}
+
+/* Likewise, if there is no native `lstat', then the gnulib
+ <sys/stat.h> defined it as stat, which also needs adjustment. */
+#if !HAVE_LSTAT
+# undef lstat
+# define lstat stat_func
+#endif
+
/* Replacement for Solaris' function by the same name.
<http://www.google.com/search?q=fstatat+site:docs.sun.com>
First, try to simulate it via l?stat ("/proc/self/fd/FD/FILE").
@@ -167,7 +185,7 @@ openat_needs_fchdir (void)
#define AT_FUNC_NAME fstatat
#define AT_FUNC_F1 lstat
-#define AT_FUNC_F2 stat
+#define AT_FUNC_F2 stat_func
#define AT_FUNC_USE_F1_COND AT_SYMLINK_NOFOLLOW
#define AT_FUNC_POST_FILE_PARAM_DECLS , struct stat *st, int flag
#define AT_FUNC_POST_FILE_ARGS , st
diff --git a/lib/stat.c b/lib/stat.c
new file mode 100644
index 0000000..c3400d5
--- /dev/null
+++ b/lib/stat.c
@@ -0,0 +1,78 @@
+/* Work around platform bugs in stat.
+ Copyright (C) 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* written by Eric Blake */
+
+#include <config.h>
+
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <string.h>
+
+#undef stat
+
+/* For now, mingw is the only known platform where stat(".") and
+ stat("./") give different results. Mingw stat has other bugs (such
+ as st_ino always being 0 on success) which this wrapper does not
+ work around. But at least this implementation provides the ability
+ to emulate fchdir correctly. */
+
+int
+rpl_stat (char const *name, struct stat *st)
+{
+ int result = stat (name, st);
+ if (result == -1 && errno == ENOENT)
+ {
+ /* Due to mingw's oddities, there are some directories (like
+ c:\) where stat() only succeeds with a trailing slash, and
+ other directories (like c:\windows) where stat() only
+ succeeds without a trailing slash. But we want the two to be
+ synonymous, since chdir() manages either style. Likewise, Mingw also
+ reports ENOENT for names longer than PATH_MAX, when we want
+ ENAMETOOLONG, and for stat("file/"), when we want ENOTDIR.
+ Fortunately, mingw PATH_MAX is small enough for stack
+ allocation. */
+ char fixed_name[PATH_MAX + 1] = {0};
+ size_t len = strlen (name);
+ bool check_dir = false;
+ if (PATH_MAX <= len)
+ errno = ENAMETOOLONG;
+ else if (len)
+ {
+ strcpy (fixed_name, name);
+ if (ISSLASH (fixed_name[len - 1]))
+ {
+ check_dir = true;
+ while (len && ISSLASH (fixed_name[len - 1]))
+ fixed_name[--len] = '\0';
+ if (!len)
+ fixed_name[0] = '/';
+ }
+ else
+ fixed_name[len++] = '/';
+ result = stat (fixed_name, st);
+ if (result == 0 && check_dir && !S_ISDIR (st->st_mode))
+ {
+ result = -1;
+ errno = ENOTDIR;
+ }
+ }
+ }
+ return result;
+}
diff --git a/lib/sys_stat.in.h b/lib/sys_stat.in.h
index 869cb0f..cc43822 100644
--- a/lib/sys_stat.in.h
+++ b/lib/sys_stat.in.h
@@ -302,6 +302,22 @@ extern int rpl_lstat (const char *name, struct stat *buf);
lstat (p, b))
#endif
+#if @GNULIB_STAT@
+# if @REPLACE_STAT@
+/* We can't use the object-like #define stat rpl_stat, because of
+ struct stat. This means that rpl_stat will not be used if the user
+ does (stat)(a,b). Oh well. */
+# undef stat
+# define stat(name, st) rpl_stat (name, st)
+extern int stat (const char *name, struct stat *buf);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef stat
+# define stat(p,b) \
+ (GL_LINK_WARNING ("stat is unportable - " \
+ "use gnulib module stat for portability"), \
+ stat (p, b))
+#endif
#if @GNULIB_FCHMODAT@
# if address@hidden@
diff --git a/m4/stat.m4 b/m4/stat.m4
new file mode 100644
index 0000000..9cc0b4a
--- /dev/null
+++ b/m4/stat.m4
@@ -0,0 +1,31 @@
+# serial 1
+
+# Copyright (C) 2009 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_FUNC_STAT],
+[
+ AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
+ AC_REQUIRE([gl_AC_DOS])
+ AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS])
+ dnl mingw is the only known platform where stat(".") and stat("./") differ
+ AC_CACHE_CHECK([whether stat handles trailing slashes],
+ [gl_cv_func_stat_works],
+ [AC_RUN_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[#include <sys/stat.h>
+]], [[struct stat st; return stat (".", &st) != stat ("./", &st);]])],
+ [gl_cv_func_stat_works=yes], [gl_cv_func_stat_works=no],
+ [case $host_os in
+ mingw*) gl_cv_func_stat_works="guessing no";;
+ *) gl_cv_func_stat_works="guessing yes";;
+ esac])])
+ case $gl_cv_func_stat_works in
+ *yes) ;;
+ *) REPLACE_STAT=1
+ AC_LIBOBJ([stat]);;
+ esac
+])
diff --git a/m4/sys_stat_h.m4 b/m4/sys_stat_h.m4
index 4c01807..df7b238 100644
--- a/m4/sys_stat_h.m4
+++ b/m4/sys_stat_h.m4
@@ -1,4 +1,4 @@
-# sys_stat_h.m4 serial 15 -*- Autoconf -*-
+# sys_stat_h.m4 serial 16 -*- Autoconf -*-
dnl Copyright (C) 2006-2009 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
@@ -56,6 +56,7 @@ AC_DEFUN([gl_SYS_STAT_H_DEFAULTS],
GNULIB_MKDIRAT=0; AC_SUBST([GNULIB_MKDIRAT])
GNULIB_MKFIFOAT=0; AC_SUBST([GNULIB_MKFIFOAT])
GNULIB_MKNODAT=0; AC_SUBST([GNULIB_MKNODAT])
+ GNULIB_STAT=0; AC_SUBST([GNULIB_STAT])
dnl Assume proper GNU behavior unless another module says otherwise.
HAVE_FCHMODAT=1; AC_SUBST([HAVE_FCHMODAT])
HAVE_FSTATAT=1; AC_SUBST([HAVE_FSTATAT])
@@ -67,4 +68,5 @@ AC_DEFUN([gl_SYS_STAT_H_DEFAULTS],
REPLACE_FSTATAT=0; AC_SUBST([REPLACE_FSTATAT])
REPLACE_LSTAT=0; AC_SUBST([REPLACE_LSTAT])
REPLACE_MKDIR=0; AC_SUBST([REPLACE_MKDIR])
+ REPLACE_STAT=0; AC_SUBST([REPLACE_STAT])
])
diff --git a/modules/openat b/modules/openat
index 276882b..27e5c50 100644
--- a/modules/openat
+++ b/modules/openat
@@ -22,6 +22,7 @@ fchdir
fcntl-h
fdopendir
gettext-h
+inline
intprops
lchown
lstat
diff --git a/modules/stat b/modules/stat
new file mode 100644
index 0000000..9729b84
--- /dev/null
+++ b/modules/stat
@@ -0,0 +1,26 @@
+Description:
+stat(): query file information
+
+Files:
+lib/stat.c
+m4/dos.m4
+m4/stat.m4
+
+Depends-on:
+stdbool
+sys_stat
+
+configure.ac:
+gl_FUNC_STAT
+gl_SYS_STAT_MODULE_INDICATOR([stat])
+
+Makefile.am:
+
+Include:
+<sys/stat.h>
+
+License:
+LGPLv2+
+
+Maintainer:
+Eric Blake
diff --git a/modules/stat-tests b/modules/stat-tests
new file mode 100644
index 0000000..3d31b52
--- /dev/null
+++ b/modules/stat-tests
@@ -0,0 +1,12 @@
+Files:
+tests/test-stat.c
+
+Depends-on:
+pathmax
+same-inode
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-stat
+check_PROGRAMS += test-stat
diff --git a/modules/sys_stat b/modules/sys_stat
index b2be2ed..a4abafe 100644
--- a/modules/sys_stat
+++ b/modules/sys_stat
@@ -33,6 +33,7 @@ sys/stat.h: sys_stat.in.h
-e 's|@''GNULIB_MKDIRAT''@|$(GNULIB_MKDIRAT)|g' \
-e 's|@''GNULIB_MKFIFOAT''@|$(GNULIB_MKFIFOAT)|g' \
-e 's|@''GNULIB_MKNODAT''@|$(GNULIB_MKNODAT)|g' \
+ -e 's|@''GNULIB_STAT''@|$(GNULIB_STAT)|g' \
-e 's|@''HAVE_FCHMODAT''@|$(HAVE_FCHMODAT)|g' \
-e 's|@''HAVE_FSTATAT''@|$(HAVE_FSTATAT)|g' \
-e 's|@''HAVE_LCHMOD''@|$(HAVE_LCHMOD)|g' \
@@ -44,6 +45,7 @@ sys/stat.h: sys_stat.in.h
-e 's|@''REPLACE_FSTATAT''@|$(REPLACE_FSTATAT)|g' \
-e 's|@''REPLACE_LSTAT''@|$(REPLACE_LSTAT)|g' \
-e 's|@''REPLACE_MKDIR''@|$(REPLACE_MKDIR)|g' \
+ -e 's|@''REPLACE_STAT''@|$(REPLACE_STAT)|g' \
-e '/definition of GL_LINK_WARNING/r $(LINK_WARNING_H)' \
< $(srcdir)/sys_stat.in.h; \
} > address@hidden && \
diff --git a/tests/test-stat.c b/tests/test-stat.c
new file mode 100644
index 0000000..337c819
--- /dev/null
+++ b/tests/test-stat.c
@@ -0,0 +1,81 @@
+/* Tests of stat.
+ Copyright (C) 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Eric Blake <address@hidden>, 2009. */
+
+#include <config.h>
+
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "pathmax.h"
+#include "same-inode.h"
+
+#define ASSERT(expr) \
+ do \
+ { \
+ if (!(expr)) \
+ { \
+ fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
+ fflush (stderr); \
+ abort (); \
+ } \
+ } \
+ while (0)
+
+#define BASE "test-stat.t"
+
+int
+main ()
+{
+ struct stat st1;
+ struct stat st2;
+ char cwd[PATH_MAX];
+
+ ASSERT (getcwd (cwd, PATH_MAX) == cwd);
+ ASSERT (stat (".", &st1) == 0);
+ ASSERT (stat ("./", &st2) == 0);
+ ASSERT (SAME_INODE (st1, st2));
+ ASSERT (stat (cwd, &st2) == 0);
+ ASSERT (SAME_INODE (st1, st2));
+ ASSERT (stat ("/", &st1) == 0);
+ ASSERT (stat ("///", &st2) == 0);
+ ASSERT (SAME_INODE (st1, st2));
+
+ errno = 0;
+ ASSERT (stat ("", &st1) == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (stat ("nosuch", &st1) == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (stat ("nosuch/", &st1) == -1);
+ ASSERT (errno == ENOENT);
+
+ ASSERT (close (creat (BASE "file", 0600)) == 0);
+ ASSERT (stat (BASE "file", &st1) == 0);
+ errno = 0;
+ ASSERT (stat (BASE "file/", &st1) == -1);
+ ASSERT (errno == ENOTDIR);
+ ASSERT (unlink (BASE "file") == 0);
+
+ return 0;
+}
--
1.6.5.rc1
>From c9d72f69bd201a1ab31464d91f234ea1817fe0e1 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Tue, 15 Sep 2009 17:08:39 -0600
Subject: [PATCH 02/12] stat: fix Solaris 9 bug
stat("file/",buf) mistakenly succeeded.
* m4/stat.m4 (gl_FUNC_STAT): Detect Solaris 9 bug with trailing
slash.
* lib/stat.c (rpl_stat): Work around it.
* doc/posix-functions/stat.texi (stat): Update documentation.
Signed-off-by: Eric Blake <address@hidden>
---
ChangeLog | 6 +++++
doc/posix-functions/stat.texi | 4 +++
lib/stat.c | 44 ++++++++++++++++++++++++++++++++--------
m4/stat.m4 | 38 ++++++++++++++++++++++++++---------
4 files changed, 73 insertions(+), 19 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index fde8d07..d0aa2c4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
2009-09-19 Eric Blake <address@hidden>
+ stat: fix Solaris 9 bug
+ * m4/stat.m4 (gl_FUNC_STAT): Detect Solaris 9 bug with trailing
+ slash.
+ * lib/stat.c (rpl_stat): Work around it.
+ * doc/posix-functions/stat.texi (stat): Update documentation.
+
stat: new module, for mingw bug
* modules/stat: New file.
* lib/stat.c: Likewise.
diff --git a/doc/posix-functions/stat.texi b/doc/posix-functions/stat.texi
index a7011ed..5fdb683 100644
--- a/doc/posix-functions/stat.texi
+++ b/doc/posix-functions/stat.texi
@@ -9,6 +9,10 @@ stat
Portability problems fixed by Gnulib:
@itemize
@item
+On some platforms, @code{stat("file/",buf)} succeeds instead of
+failing with @code{ENOTDIR}.
+Solaris 9.
address@hidden
On some platforms, @code{stat(".",buf)} and @code{stat("./",buf)} give
different results:
mingw.
diff --git a/lib/stat.c b/lib/stat.c
index c3400d5..8aa7709 100644
--- a/lib/stat.c
+++ b/lib/stat.c
@@ -18,6 +18,19 @@
#include <config.h>
+/* Get the original definition of stat. It might be defined as a macro. */
+#define __need_system_sys_stat_h
+#include <sys/types.h>
+#include <sys/stat.h>
+#undef __need_system_sys_stat_h
+
+static inline int
+orig_stat (const char *filename, struct stat *buf)
+{
+ return stat (filename, buf);
+}
+
+/* Specification. */
#include <sys/stat.h>
#include <errno.h>
@@ -25,18 +38,30 @@
#include <stdbool.h>
#include <string.h>
-#undef stat
-
-/* For now, mingw is the only known platform where stat(".") and
- stat("./") give different results. Mingw stat has other bugs (such
- as st_ino always being 0 on success) which this wrapper does not
- work around. But at least this implementation provides the ability
- to emulate fchdir correctly. */
+/* Store information about NAME into ST. Work around bugs with
+ trailing slashes. Mingw has other bugs (such as st_ino always
+ being 0 on success) which this wrapper does not work around. But
+ at least this implementation provides the ability to emulate fchdir
+ correctly. */
int
rpl_stat (char const *name, struct stat *st)
{
- int result = stat (name, st);
+ int result = orig_stat (name, st);
+#if REPLACE_FUNC_STAT_FILE
+ /* Solaris 9 mistakenly succeeds when given a non-directory with a
+ trailing slash. */
+ if (result == 0 && !S_ISDIR (st->st_mode))
+ {
+ size_t len = strlen (name);
+ if (ISSLASH (name[len - 1]))
+ {
+ errno = ENOTDIR;
+ return -1;
+ }
+ }
+#endif /* REPLACE_FUNC_STAT_FILE */
+#if REPLACE_FUNC_STAT_DIR
if (result == -1 && errno == ENOENT)
{
/* Due to mingw's oddities, there are some directories (like
@@ -66,7 +91,7 @@ rpl_stat (char const *name, struct stat *st)
}
else
fixed_name[len++] = '/';
- result = stat (fixed_name, st);
+ result = orig_stat (fixed_name, st);
if (result == 0 && check_dir && !S_ISDIR (st->st_mode))
{
result = -1;
@@ -74,5 +99,6 @@ rpl_stat (char const *name, struct stat *st)
}
}
}
+#endif /* REPLACE_FUNC_STAT_DIR */
return result;
}
diff --git a/m4/stat.m4 b/m4/stat.m4
index 9cc0b4a..0a10de1 100644
--- a/m4/stat.m4
+++ b/m4/stat.m4
@@ -1,4 +1,4 @@
-# serial 1
+# serial 2
# Copyright (C) 2009 Free Software Foundation, Inc.
#
@@ -12,20 +12,38 @@ AC_DEFUN([gl_FUNC_STAT],
AC_REQUIRE([gl_AC_DOS])
AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS])
dnl mingw is the only known platform where stat(".") and stat("./") differ
- AC_CACHE_CHECK([whether stat handles trailing slashes],
- [gl_cv_func_stat_works],
+ AC_CACHE_CHECK([whether stat handles trailing slashes on directories],
+ [gl_cv_func_stat_dir_slash],
[AC_RUN_IFELSE(
[AC_LANG_PROGRAM(
[[#include <sys/stat.h>
]], [[struct stat st; return stat (".", &st) != stat ("./", &st);]])],
- [gl_cv_func_stat_works=yes], [gl_cv_func_stat_works=no],
+ [gl_cv_func_stat_dir_slash=yes], [gl_cv_func_stat_dir_slash=no],
[case $host_os in
- mingw*) gl_cv_func_stat_works="guessing no";;
- *) gl_cv_func_stat_works="guessing yes";;
+ mingw*) gl_cv_func_stat_dir_slash="guessing no";;
+ *) gl_cv_func_stat_dir_slash="guessing yes";;
esac])])
- case $gl_cv_func_stat_works in
- *yes) ;;
- *) REPLACE_STAT=1
- AC_LIBOBJ([stat]);;
+ dnl Solaris 9 mistakenly succeeds on stat("file/")
+ AC_CACHE_CHECK([whether stat handles trailing slashes on files],
+ [gl_cv_func_stat_file_slash],
+ [touch conftest.tmp
+ AC_RUN_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[#include <sys/stat.h>
+]], [[struct stat st; return !stat ("conftest.tmp/", &st);]])],
+ [gl_cv_func_stat_file_slash=yes], [gl_cv_func_stat_file_slash=no],
+ [gl_cv_func_stat_file_slash="guessing no"])])
+ case $gl_cv_func_stat_dir_slash in
+ *no) REPLACE_STAT=1
+ AC_DEFINE([REPLACE_FUNC_STAT_DIR], [1], [Define to 1 if stat needs
+ help when passed a directory name with a trailing slash]);;
+ esac
+ case $gl_cv_func_stat_file_slash in
+ *no) REPLACE_STAT=1
+ AC_DEFINE([REPLACE_FUNC_STAT_FILE], [1], [Define to 1 if stat needs
+ help when passed a file name with a trailing slash]);;
esac
+ if test $REPLACE_STAT = 1; then
+ AC_LIBOBJ([stat])
+ fi
])
--
1.6.5.rc1
>From ba1652925190702bec29e2c5129ee857576853d6 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Tue, 15 Sep 2009 14:43:14 -0600
Subject: [PATCH 03/12] stat: add as dependency to other modules
Modules that use stat but are not in this list don't trip any of
the bugs that the stat module fixes.
* modules/chown (Depends-on): Add stat.
* modules/euidaccess (Depends-on): Likewise.
* modules/fchdir (Depends-on): Likewise.
* modules/isdir (Depends-on): Likewise.
* modules/link (Depends-on): Likewise.
* modules/lstat (Depends-on): Likewise.
* modules/mkdir-p (Depends-on): Likewise.
* modules/modechange (Depends-on): Likewise.
* modules/open (Depends-on): Likewise.
* modules/readlink (Depends-on): Likewise.
* modules/same (Depends-on): Likewise.
Signed-off-by: Eric Blake <address@hidden>
---
ChangeLog | 13 +++++++++++++
modules/chown | 1 +
modules/euidaccess | 1 +
modules/fchdir | 1 +
modules/isdir | 2 +-
modules/link | 1 +
modules/lstat | 1 +
modules/mkdir-p | 1 +
modules/modechange | 1 +
modules/open | 1 +
modules/readlink | 1 +
modules/same | 1 +
12 files changed, 24 insertions(+), 1 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index d0aa2c4..1002722 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,18 @@
2009-09-19 Eric Blake <address@hidden>
+ stat: add as dependency to other modules
+ * modules/chown (Depends-on): Add stat.
+ * modules/euidaccess (Depends-on): Likewise.
+ * modules/fchdir (Depends-on): Likewise.
+ * modules/isdir (Depends-on): Likewise.
+ * modules/link (Depends-on): Likewise.
+ * modules/lstat (Depends-on): Likewise.
+ * modules/mkdir-p (Depends-on): Likewise.
+ * modules/modechange (Depends-on): Likewise.
+ * modules/open (Depends-on): Likewise.
+ * modules/readlink (Depends-on): Likewise.
+ * modules/same (Depends-on): Likewise.
+
stat: fix Solaris 9 bug
* m4/stat.m4 (gl_FUNC_STAT): Detect Solaris 9 bug with trailing
slash.
diff --git a/modules/chown b/modules/chown
index cf99210..88d0cd4 100644
--- a/modules/chown
+++ b/modules/chown
@@ -9,6 +9,7 @@ m4/chown.m4
Depends-on:
open
unistd
+stat
sys_stat
configure.ac:
diff --git a/modules/euidaccess b/modules/euidaccess
index 47d9716..bf329c6 100644
--- a/modules/euidaccess
+++ b/modules/euidaccess
@@ -9,6 +9,7 @@ Depends-on:
unistd
extensions
group-member
+stat
sys_stat
configure.ac:
diff --git a/modules/fchdir b/modules/fchdir
index b35e385..69ac3c3 100644
--- a/modules/fchdir
+++ b/modules/fchdir
@@ -16,6 +16,7 @@ include_next
malloc-posix
open
realloc-posix
+stat
stdbool
strdup-posix
sys_stat
diff --git a/modules/isdir b/modules/isdir
index 09f5956..001546e 100644
--- a/modules/isdir
+++ b/modules/isdir
@@ -6,6 +6,7 @@ lib/isdir.c
m4/isdir.m4
Depends-on:
+stat
configure.ac:
gl_ISDIR
@@ -20,4 +21,3 @@ GPL
Maintainer:
Jim Meyering
-
diff --git a/modules/link b/modules/link
index 9492950..6a006c3 100644
--- a/modules/link
+++ b/modules/link
@@ -6,6 +6,7 @@ lib/link.c
m4/link.m4
Depends-on:
+stat
strdup-posix
sys_stat
unistd
diff --git a/modules/lstat b/modules/lstat
index b0c8b44..e4eabb5 100644
--- a/modules/lstat
+++ b/modules/lstat
@@ -6,6 +6,7 @@ lib/lstat.c
m4/lstat.m4
Depends-on:
+stat
sys_stat
configure.ac:
diff --git a/modules/mkdir-p b/modules/mkdir-p
index 20e3a23..3b0abdf 100644
--- a/modules/mkdir-p
+++ b/modules/mkdir-p
@@ -17,6 +17,7 @@ lchown
mkancesdirs
quote
savewd
+stat
stat-macros
stdbool
sys_stat
diff --git a/modules/modechange b/modules/modechange
index 80d8b70..e58589a 100644
--- a/modules/modechange
+++ b/modules/modechange
@@ -8,6 +8,7 @@ lib/modechange.c
m4/modechange.m4
Depends-on:
+stat
stat-macros
stdbool
sys_stat
diff --git a/modules/open b/modules/open
index 601e064..0b8b303 100644
--- a/modules/open
+++ b/modules/open
@@ -8,6 +8,7 @@ m4/mode_t.m4
Depends-on:
fcntl-h
+stat
configure.ac:
gl_FUNC_OPEN
diff --git a/modules/readlink b/modules/readlink
index 8e2a1de..eb32ef5 100644
--- a/modules/readlink
+++ b/modules/readlink
@@ -6,6 +6,7 @@ lib/readlink.c
m4/readlink.m4
Depends-on:
+stat
unistd
configure.ac:
diff --git a/modules/same b/modules/same
index b17d735..a6fc64f 100644
--- a/modules/same
+++ b/modules/same
@@ -12,6 +12,7 @@ xalloc
error
dirname
same-inode
+stat
stdbool
memcmp
--
1.6.5.rc1
>From b4caad347f49b6fc8ec0b656e494dcd90f031b7c Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Thu, 17 Sep 2009 15:55:24 -0600
Subject: [PATCH 04/12] lstat: fix Solaris 9 bug
lstat("file/",buf) mistakenly succeeded.
* lib/lstat.c (lstat): Also check for trailing slash on
non-symlink, non-directories. Use stat module to simplify logic.
* doc/posix-functions/lstat.texi (lstat): Document it.
* modules/lstat-tests (Depends-on): Add errno, same-inode.
(configure.ac): Check for symlink.
* tests/test-lstat.c (main): Add more tests.
Signed-off-by: Eric Blake <address@hidden>
---
ChangeLog | 8 +++
doc/posix-functions/lstat.texi | 17 +++++-
lib/lstat.c | 41 +++++++-------
modules/lstat-tests | 3 +
tests/test-lstat.c | 119 +++++++++++++++++++++++++++++++++++++--
5 files changed, 159 insertions(+), 29 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index 1002722..70c18c1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,13 @@
2009-09-19 Eric Blake <address@hidden>
+ lstat: fix Solaris 9 bug
+ * lib/lstat.c (lstat): Also check for trailing slash on
+ non-symlink, non-directories. Use stat module to simplify logic.
+ * doc/posix-functions/lstat.texi (lstat): Document it.
+ * modules/lstat-tests (Depends-on): Add errno, same-inode.
+ (configure.ac): Check for symlink.
+ * tests/test-lstat.c (main): Add more tests.
+
stat: add as dependency to other modules
* modules/chown (Depends-on): Add stat.
* modules/euidaccess (Depends-on): Likewise.
diff --git a/doc/posix-functions/lstat.texi b/doc/posix-functions/lstat.texi
index 7a82d87..dbe31f8 100644
--- a/doc/posix-functions/lstat.texi
+++ b/doc/posix-functions/lstat.texi
@@ -9,8 +9,13 @@ lstat
Portability problems fixed by Gnulib:
@itemize
@item
-When the argument ends in a slash, some platforms don't dereference the
-argument.
+For symlinks, when the argument ends in a slash, some platforms don't
+dereference the argument:
+Solaris 9.
address@hidden
+On some platforms, @code{lstat("file/",buf)} succeeds instead of
+failing with @code{ENOTDIR}.
+Solaris 9.
@item
On Windows platforms (excluding Cygwin), symlinks are not supported, so
@code{lstat} does not exist.
@@ -22,4 +27,12 @@ lstat
On platforms where @code{off_t} is a 32-bit type, @code{lstat} may not
correctly report the size of files or block devices larger than 2 GB. The fix
is to use the @code{AC_SYS_LARGEFILE} macro.
address@hidden
+On Windows platforms (excluding Cygwin), @code{st_ino} is always 0.
address@hidden
+Because of the definition of @code{struct stat}, it is not possible to
+portably replace @code{stat} via an object-like macro. Therefore,
+expressions such as @code{(islnk ? lstat : stat) (name, buf)} are not
+portable, and should instead be written @code{islnk ? lstat (name,
+buf) : stat (name, buf)}.
@end itemize
diff --git a/lib/lstat.c b/lib/lstat.c
index 3e07270..a05f674 100644
--- a/lib/lstat.c
+++ b/lib/lstat.c
@@ -1,6 +1,7 @@
/* Work around a bug of lstat on some systems
- Copyright (C) 1997-1999, 2000-2006, 2008 Free Software Foundation, Inc.
+ Copyright (C) 1997-1999, 2000-2006, 2008-2009 Free Software
+ Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -19,7 +20,7 @@
#include <config.h>
-/* Get the original definition of open. It might be defined as a macro. */
+/* Get the original definition of lstat. It might be defined as a macro. */
#define __need_system_sys_stat_h
#include <sys/types.h>
#include <sys/stat.h>
@@ -56,27 +57,27 @@ rpl_lstat (const char *file, struct stat *sbuf)
size_t len;
int lstat_result = orig_lstat (file, sbuf);
- if (lstat_result != 0 || !S_ISLNK (sbuf->st_mode))
+ if (lstat_result != 0)
return lstat_result;
+ /* This replacement file can blindly check against '/' rather than
+ using the ISSLASH macro, because all platforms with '\\' either
+ lack symlinks (mingw) or have working lstat (cygwin) and thus do
+ not compile this file. 0 len should have already been filtered
+ out above, with a failure return of ENOENT. */
len = strlen (file);
- if (len == 0 || file[len - 1] != '/')
+ if (file[len - 1] != '/' || S_ISDIR (sbuf->st_mode))
return 0;
- /* FILE refers to a symbolic link and the name ends with a slash.
- Call stat() to get info about the link's referent. */
-
- /* If stat fails, then we do the same. */
- if (stat (file, sbuf) != 0)
- return -1;
-
- /* If FILE references a directory, return 0. */
- if (S_ISDIR (sbuf->st_mode))
- return 0;
-
- /* Here, we know stat succeeded and FILE references a non-directory.
- But it was specified via a name including a trailing slash.
- Fail with errno set to ENOTDIR to indicate the contradiction. */
- errno = ENOTDIR;
- return -1;
+ /* At this point, a trailing slash is only permitted on
+ symlink-to-dir; but it should have found information on the
+ directory, not the symlink. Call stat() to get info about the
+ link's referent. Our replacement stat guarantees valid results,
+ even if the symlink is not pointing to a directory. */
+ if (!S_ISLNK (sbuf->st_mode))
+ {
+ errno = ENOTDIR;
+ return -1;
+ }
+ return stat (file, sbuf);
}
diff --git a/modules/lstat-tests b/modules/lstat-tests
index f0fe3f6..2368692 100644
--- a/modules/lstat-tests
+++ b/modules/lstat-tests
@@ -2,8 +2,11 @@ Files:
tests/test-lstat.c
Depends-on:
+errno
+same-inode
configure.ac:
+AC_CHECK_FUNCS_ONCE([symlink])
Makefile.am:
TESTS += test-lstat
diff --git a/tests/test-lstat.c b/tests/test-lstat.c
index 1a0a0f2..8182738 100644
--- a/tests/test-lstat.c
+++ b/tests/test-lstat.c
@@ -1,5 +1,5 @@
/* Test of lstat() function.
- Copyright (C) 2008 Free Software Foundation, Inc.
+ Copyright (C) 2008, 2009 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -14,24 +14,129 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
-/* Written by Simon Josefsson, 2008. */
+/* Written by Simon Josefsson, 2008; and Eric Blake, 2009. */
#include <config.h>
#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "same-inode.h"
+
+#if !HAVE_SYMLINK
+# define symlink(a,b) (-1)
+#endif
+
+#define ASSERT(expr) \
+ do \
+ { \
+ if (!(expr)) \
+ { \
+ fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
+ fflush (stderr); \
+ abort (); \
+ } \
+ } \
+ while (0)
+
+#define BASE "test-lstat.t"
int
-main (int argc, char **argv)
+main ()
{
- struct stat buf;
+ struct stat st1;
+ struct stat st2;
+
+ /* Remove any leftovers from a previous partial run. */
+ ASSERT (system ("rm -rf " BASE "*") == 0);
+
+ /* Test for common directories. */
+ ASSERT (lstat (".", &st1) == 0);
+ ASSERT (lstat ("./", &st2) == 0);
+ ASSERT (SAME_INODE (st1, st2));
+ ASSERT (S_ISDIR (st1.st_mode));
+ ASSERT (S_ISDIR (st2.st_mode));
+ ASSERT (lstat ("/", &st1) == 0);
+ ASSERT (lstat ("///", &st2) == 0);
+ ASSERT (SAME_INODE (st1, st2));
+ ASSERT (S_ISDIR (st1.st_mode));
+ ASSERT (S_ISDIR (st2.st_mode));
+ ASSERT (lstat ("..", &st1) == 0);
+ ASSERT (S_ISDIR (st1.st_mode));
+
+ /* Test for error conditions. */
+ errno = 0;
+ ASSERT (lstat ("", &st1) == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (lstat ("nosuch", &st1) == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (lstat ("nosuch/", &st1) == -1);
+ ASSERT (errno == ENOENT);
- if (lstat ("/", &buf) != 0)
+ ASSERT (close (creat (BASE "file", 0600)) == 0);
+ ASSERT (lstat (BASE "file", &st1) == 0);
+ ASSERT (S_ISREG (st1.st_mode));
+ errno = 0;
+ ASSERT (lstat (BASE "file/", &st1) == -1);
+ ASSERT (errno == ENOTDIR);
+
+ /* Now for some symlink tests, where supported. We set up:
+ link1 -> directory
+ link2 -> file
+ link3 -> dangling
+ link4 -> loop
+ then test behavior both with and without trailing slash.
+ */
+ if (symlink (".", BASE "link1") != 0)
{
- perror ("lstat");
- return 1;
+ ASSERT (unlink (BASE "file") == 0);
+ fputs ("skipping test: symlinks not supported on this filesystem\n",
+ stderr);
+ return 77;
}
+ ASSERT (symlink (BASE "file", BASE "link2") == 0);
+ ASSERT (symlink (BASE "nosuch", BASE "link3") == 0);
+ ASSERT (symlink (BASE "link4", BASE "link4") == 0);
+
+ ASSERT (lstat (BASE "link1", &st1) == 0);
+ ASSERT (S_ISLNK (st1.st_mode));
+ ASSERT (lstat (BASE "link1/", &st1) == 0);
+ ASSERT (stat (BASE "link1", &st2) == 0);
+ ASSERT (S_ISDIR (st1.st_mode));
+ ASSERT (S_ISDIR (st2.st_mode));
+ ASSERT (SAME_INODE (st1, st2));
+
+ ASSERT (lstat (BASE "link2", &st1) == 0);
+ ASSERT (S_ISLNK (st1.st_mode));
+ errno = 0;
+ ASSERT (lstat (BASE "link2/", &st1) == -1);
+ ASSERT (errno == ENOTDIR);
+
+ ASSERT (lstat (BASE "link3", &st1) == 0);
+ ASSERT (S_ISLNK (st1.st_mode));
+ errno = 0;
+ ASSERT (lstat (BASE "link3/", &st1) == -1);
+ ASSERT (errno == ENOENT);
+
+ ASSERT (lstat (BASE "link4", &st1) == 0);
+ ASSERT (S_ISLNK (st1.st_mode));
+ errno = 0;
+ ASSERT (lstat (BASE "link4/", &st1) == -1);
+ ASSERT (errno == ELOOP);
+
+ /* Cleanup. */
+ ASSERT (unlink (BASE "file") == 0);
+ ASSERT (unlink (BASE "link1") == 0);
+ ASSERT (unlink (BASE "link2") == 0);
+ ASSERT (unlink (BASE "link3") == 0);
+ ASSERT (unlink (BASE "link4") == 0);
return 0;
}
--
1.6.5.rc1
>From 4a60ba548bf42233384fa86fd97cf0091514c553 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Wed, 16 Sep 2009 17:13:20 -0600
Subject: [PATCH 05/12] unlink: new module, for Solaris 9 bug
unlink("file/") mistakenly succeeded. This patch favors, but
does not enforce, GNU semantics that unlink("link-to-dir/")
flat-out fails rather than attempting to unlink "dir".
* modules/unlink: New file.
* lib/unlink.c: Likewise.
* m4/unlink.m4 (gl_FUNC_UNLINK): Likewise.
* m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add witnesses.
* modules/unistd (Makefile.am): Use them.
* lib/unistd.in.h (stat): Declare replacement.
* MODULES.html.sh (systems lacking POSIX:2008): Mention module.
* doc/posix-functions/unlink.texi (unlink): Likewise.
* modules/unlink-tests: New test.
* tests/test-unlink.c: Likewise.
Signed-off-by: Eric Blake <address@hidden>
---
ChangeLog | 12 ++++
MODULES.html.sh | 1 +
doc/posix-functions/unlink.texi | 15 +++++-
lib/unistd.in.h | 15 +++++
lib/unlink.c | 85 ++++++++++++++++++++++++++++++
m4/unistd_h.m4 | 4 +-
m4/unlink.m4 | 27 ++++++++++
modules/unistd | 2 +
modules/unlink | 26 +++++++++
modules/unlink-tests | 12 ++++
tests/test-unlink.c | 110 +++++++++++++++++++++++++++++++++++++++
11 files changed, 307 insertions(+), 2 deletions(-)
create mode 100644 lib/unlink.c
create mode 100644 m4/unlink.m4
create mode 100644 modules/unlink
create mode 100644 modules/unlink-tests
create mode 100644 tests/test-unlink.c
diff --git a/ChangeLog b/ChangeLog
index 70c18c1..64bdd7c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,17 @@
2009-09-19 Eric Blake <address@hidden>
+ unlink: new module, for Solaris 9 bug
+ * modules/unlink: New file.
+ * lib/unlink.c: Likewise.
+ * m4/unlink.m4 (gl_FUNC_UNLINK): Likewise.
+ * m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add witnesses.
+ * modules/unistd (Makefile.am): Use them.
+ * lib/unistd.in.h (stat): Declare replacement.
+ * MODULES.html.sh (systems lacking POSIX:2008): Mention module.
+ * doc/posix-functions/unlink.texi (unlink): Likewise.
+ * modules/unlink-tests: New test.
+ * tests/test-unlink.c: Likewise.
+
lstat: fix Solaris 9 bug
* lib/lstat.c (lstat): Also check for trailing slash on
non-symlink, non-directories. Use stat module to simplify logic.
diff --git a/MODULES.html.sh b/MODULES.html.sh
index fbea183..f8a2fb4 100755
--- a/MODULES.html.sh
+++ b/MODULES.html.sh
@@ -2359,6 +2359,7 @@ func_all_modules ()
func_module sys_wait
func_module tsearch
func_module unistd
+ func_module unlink
func_module utime
func_module vasnprintf-posix
func_module vasprintf-posix
diff --git a/doc/posix-functions/unlink.texi b/doc/posix-functions/unlink.texi
index 2f94ab1..1249c42 100644
--- a/doc/posix-functions/unlink.texi
+++ b/doc/posix-functions/unlink.texi
@@ -4,15 +4,28 @@ unlink
POSIX specification:
@url{http://www.opengroup.org/onlinepubs/9699919799/functions/unlink.html}
-Gnulib module: ---
+Gnulib module: unlink
Portability problems fixed by Gnulib:
@itemize
address@hidden
+Some systems mistakenly succeed on @code{unlink("file/")}:
+Solaris 9.
@end itemize
Portability problems not fixed by Gnulib:
@itemize
@item
+Some systems allow a superuser to unlink directories, even though this
+can cause file system corruption. The error given if a process is not
+permitted to unlink directories varies across implementations; it is
+not always the POSIX value of @code{EPERM}. Meanwhile, if a process
+has the ability to unlink directories, POSIX requires that
address@hidden("symlink-to-dir/")} remove @file{dir} and leave
address@hidden dangling; this behavior is counter-intuitive.
+The gnulib module unlinkdir can help determine whether code must be
+cautious of unlinking directories.
address@hidden
Removing an open file is non-portable: On Unix this allows the programs that
have the file already open to continue working with it; the file's storage
is only freed when the no process has the file open any more. On Windows,
diff --git a/lib/unistd.in.h b/lib/unistd.in.h
index 68c3155..6fa2831 100644
--- a/lib/unistd.in.h
+++ b/lib/unistd.in.h
@@ -164,6 +164,21 @@ extern int fchownat (int fd, char const *file, uid_t
owner, gid_t group, int fla
#endif
+#if @GNULIB_UNLINK@
+# if @REPLACE_UNLINK@
+# undef unlink
+# define unlink rpl_unlink
+extern int unlink (char const *file);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef unlink
+# define unlink(n) \
+ (GL_LINK_WARNING ("unlink is not portable - " \
+ "use gnulib module unlink for portability"), \
+ unlink (n))
+#endif
+
+
#if @GNULIB_UNLINKAT@
# if address@hidden@
extern int unlinkat (int fd, char const *file, int flag);
diff --git a/lib/unlink.c b/lib/unlink.c
new file mode 100644
index 0000000..cd8f4b1
--- /dev/null
+++ b/lib/unlink.c
@@ -0,0 +1,85 @@
+/* Work around unlink bugs.
+
+ Copyright (C) 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#undef unlink
+
+/* Remove file NAME.
+ Return 0 if successful, -1 if not. */
+
+int
+rpl_unlink (char const *name)
+{
+ /* Work around Solaris 9 bug where unlink("file/") succeeds. */
+ size_t len = strlen (name);
+ int result = 0;
+ if (len && ISSLASH (name[len - 1]))
+ {
+ /* We can't unlink(2) something if it doesn't exist. If it does
+ exist, then it resolved to a directory, due to the trailing
+ slash, and POSIX requires that the unlink attempt to remove
+ that directory (which would leave the symlink dangling).
+ Unfortunately, Solaris 9 is one of the platforms where the
+ root user can unlink directories, and we don't want to
+ cripple this behavior on real directories, even if it is
+ seldom needed (at any rate, it's nicer to let coreutils'
+ unlink(1) give the correct errno for non-root users). But we
+ don't know whether name was an actual directory, or a symlink
+ to a directory; and due to the bug of ignoring trailing
+ slash, Solaris 9 would end up successfully unlinking the
+ symlink instead of the directory. Technically, we could use
+ realpath to find the canonical directory name to attempt
+ deletion on. But that is a lot of work for a corner case; so
+ we instead just use an lstat on the shortened name, and
+ reject symlinks with trailing slashes. The root user of
+ unlink(1) will just have to live with the rule that they
+ can't delete a directory via a symlink. */
+ struct stat st;
+ result = lstat (name, &st);
+ if (result == 0)
+ {
+ /* Trailing NUL will overwrite the trailing slash. */
+ char *short_name = malloc (len);
+ if (!short_name)
+ {
+ errno = EPERM;
+ return -1;
+ }
+ memcpy (short_name, name, len);
+ while (len && ISSLASH (short_name[len - 1]))
+ short_name[--len] = '\0';
+ if (len && (lstat (short_name, &st) || S_ISLNK (st.st_mode)))
+ {
+ free (short_name);
+ errno = EPERM;
+ return -1;
+ }
+ free (short_name);
+ }
+ }
+ if (!result)
+ result = unlink (name);
+ return result;
+}
diff --git a/m4/unistd_h.m4 b/m4/unistd_h.m4
index 2d5edc7..d21cafc 100644
--- a/m4/unistd_h.m4
+++ b/m4/unistd_h.m4
@@ -1,4 +1,4 @@
-# unistd_h.m4 serial 27
+# unistd_h.m4 serial 28
dnl Copyright (C) 2006-2009 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
@@ -61,6 +61,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS],
GNULIB_SYMLINKAT=0; AC_SUBST([GNULIB_SYMLINKAT])
GNULIB_UNISTD_H_GETOPT=0; AC_SUBST([GNULIB_UNISTD_H_GETOPT])
GNULIB_UNISTD_H_SIGPIPE=0; AC_SUBST([GNULIB_UNISTD_H_SIGPIPE])
+ GNULIB_UNLINK=0; AC_SUBST([GNULIB_UNLINK])
GNULIB_UNLINKAT=0; AC_SUBST([GNULIB_UNLINKAT])
GNULIB_WRITE=0; AC_SUBST([GNULIB_WRITE])
dnl Assume proper GNU behavior unless another module says otherwise.
@@ -99,6 +100,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS],
REPLACE_LINK=0; AC_SUBST([REPLACE_LINK])
REPLACE_LSEEK=0; AC_SUBST([REPLACE_LSEEK])
REPLACE_RMDIR=0; AC_SUBST([REPLACE_RMDIR])
+ REPLACE_UNLINK=0; AC_SUBST([REPLACE_UNLINK])
REPLACE_WRITE=0; AC_SUBST([REPLACE_WRITE])
UNISTD_H_HAVE_WINSOCK2_H=0; AC_SUBST([UNISTD_H_HAVE_WINSOCK2_H])
UNISTD_H_HAVE_WINSOCK2_H_AND_USE_SOCKETS=0;
diff --git a/m4/unlink.m4 b/m4/unlink.m4
new file mode 100644
index 0000000..626c3ad
--- /dev/null
+++ b/m4/unlink.m4
@@ -0,0 +1,27 @@
+# unlink.m4 serial 1
+dnl Copyright (C) 2009 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_FUNC_UNLINK],
+[
+ AC_REQUIRE([gl_AC_DOS])
+ AC_REQUIRE([gl_UNISTD_H_DEFAULTS])
+ dnl Detect Solaris 9 bug.
+ AC_CACHE_CHECK([whether unlink honors trailing slashes],
+ [gl_cv_func_unlink_works],
+ [touch conftest.file
+ AC_RUN_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[#include <stdio.h>
+ #include <errno.h>
+]], [[return !unlink ("conftest.file/") || errno != ENOTDIR;]])],
+ [gl_cv_func_unlink_works=yes], [gl_cv_func_unlink_works=no],
+ [gl_cv_func_unlink_works="guessing no"])
+ rm -f conftest.file])
+ if test x"$gl_cv_func_unlink_works" != xyes; then
+ REPLACE_UNLINK=1
+ AC_LIBOBJ([unlink])
+ fi
+])
diff --git a/modules/unistd b/modules/unistd
index 5b3546c..336381a 100644
--- a/modules/unistd
+++ b/modules/unistd
@@ -54,6 +54,7 @@ unistd.h: unistd.in.h
-e 's|@''GNULIB_SYMLINKAT''@|$(GNULIB_SYMLINKAT)|g' \
-e 's|@''GNULIB_UNISTD_H_GETOPT''@|$(GNULIB_UNISTD_H_GETOPT)|g' \
-e 's|@''GNULIB_UNISTD_H_SIGPIPE''@|$(GNULIB_UNISTD_H_SIGPIPE)|g'
\
+ -e 's|@''GNULIB_UNLINK''@|$(GNULIB_UNLINK)|g' \
-e 's|@''GNULIB_UNLINKAT''@|$(GNULIB_UNLINKAT)|g' \
-e 's|@''GNULIB_WRITE''@|$(GNULIB_WRITE)|g' \
-e 's|@''HAVE_DUP2''@|$(HAVE_DUP2)|g' \
@@ -91,6 +92,7 @@ unistd.h: unistd.in.h
-e 's|@''REPLACE_LINK''@|$(REPLACE_LINK)|g' \
-e 's|@''REPLACE_LSEEK''@|$(REPLACE_LSEEK)|g' \
-e 's|@''REPLACE_RMDIR''@|$(REPLACE_RMDIR)|g' \
+ -e 's|@''REPLACE_UNLINK''@|$(REPLACE_UNLINK)|g' \
-e 's|@''REPLACE_WRITE''@|$(REPLACE_WRITE)|g' \
-e
's|@''UNISTD_H_HAVE_WINSOCK2_H''@|$(UNISTD_H_HAVE_WINSOCK2_H)|g' \
-e
's|@''UNISTD_H_HAVE_WINSOCK2_H_AND_USE_SOCKETS''@|$(UNISTD_H_HAVE_WINSOCK2_H_AND_USE_SOCKETS)|g'
\
diff --git a/modules/unlink b/modules/unlink
new file mode 100644
index 0000000..224efa3
--- /dev/null
+++ b/modules/unlink
@@ -0,0 +1,26 @@
+Description:
+unlink(): remove a file.
+
+Files:
+lib/unlink.c
+m4/dos.m4
+m4/unlink.m4
+
+Depends-on:
+lstat
+unistd
+
+configure.ac:
+gl_FUNC_UNLINK
+gl_UNISTD_MODULE_INDICATOR([unlink])
+
+Makefile.am:
+
+Include:
+<unistd.h>
+
+License:
+LGPL
+
+Maintainer:
+Eric Blake
diff --git a/modules/unlink-tests b/modules/unlink-tests
new file mode 100644
index 0000000..b527b61
--- /dev/null
+++ b/modules/unlink-tests
@@ -0,0 +1,12 @@
+Files:
+tests/test-unlink.c
+
+Depends-on:
+unlinkdir
+
+configure.ac:
+AC_CHECK_FUNCS_ONCE([symlink])
+
+Makefile.am:
+TESTS += test-unlink
+check_PROGRAMS += test-unlink
diff --git a/tests/test-unlink.c b/tests/test-unlink.c
new file mode 100644
index 0000000..5aaa595
--- /dev/null
+++ b/tests/test-unlink.c
@@ -0,0 +1,110 @@
+/* Tests of unlink.
+ Copyright (C) 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Eric Blake <address@hidden>, 2009. */
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "unlinkdir.h"
+
+#if !HAVE_SYMLINK
+# define symlink(a,b) (-1)
+#endif
+
+#define ASSERT(expr) \
+ do \
+ { \
+ if (!(expr)) \
+ { \
+ fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
+ fflush (stderr); \
+ abort (); \
+ } \
+ } \
+ while (0)
+
+#define BASE "test-unlink.t"
+
+int
+main ()
+{
+ /* Remove any leftovers from a previous partial run. */
+ ASSERT (system ("rm -rf " BASE "*") == 0);
+
+ /* Setup. */
+ ASSERT (mkdir (BASE "dir", 0700) == 0);
+ ASSERT (close (creat (BASE "dir/file", 0600)) == 0);
+
+ /* Basic error conditions. */
+ errno = 0;
+ ASSERT (unlink ("") == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (unlink (BASE "nosuch") == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (unlink (BASE "nosuch/") == -1);
+ ASSERT (errno == ENOENT);
+ /* Resulting errno after directories is rather varied across
+ implementations (EPERM, EINVAL, EACCES, EBUSY, EISDIR, ENOTSUP);
+ however, we must be careful to not attempt unlink on a directory
+ unless we know it must fail. */
+ if (cannot_unlink_dir ())
+ {
+ ASSERT (unlink (".") == -1);
+ ASSERT (unlink ("..") == -1);
+ ASSERT (unlink ("/") == -1);
+ ASSERT (unlink (BASE "dir") == -1);
+ ASSERT (mkdir (BASE "dir1", 0700) == 0);
+ ASSERT (unlink (BASE "dir1") == -1);
+ ASSERT (rmdir (BASE "dir1") == 0);
+ }
+ errno = 0;
+ ASSERT (unlink (BASE "dir/file/") == -1);
+ ASSERT (errno == ENOTDIR);
+
+ /* Test symlink behavior. Specifying trailing slash will attempt
+ unlink of a directory, so only attempt it if we know it must
+ fail. */
+ if (symlink (BASE "dir", BASE "link") != 0)
+ {
+ ASSERT (unlink (BASE "dir/file") == 0);
+ ASSERT (rmdir (BASE "dir") == 0);
+ fputs ("skipping test: symlinks not supported on this filesystem\n",
+ stderr);
+ return 77;
+ }
+ if (cannot_unlink_dir ())
+ ASSERT (unlink (BASE "link/") == -1);
+ ASSERT (unlink (BASE "link") == 0);
+ ASSERT (symlink (BASE "dir/file", BASE "link") == 0);
+ /* Order here proves unlink of a symlink does not follow through to
+ the file. */
+ ASSERT (unlink (BASE "link") == 0);
+ ASSERT (unlink (BASE "dir/file") == 0);
+ ASSERT (rmdir (BASE "dir") == 0);
+
+ return 0;
+}
--
1.6.5.rc1
>From 6ca71ffe395184749d849f5bba4771d6b2fbb7d6 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Mon, 14 Sep 2009 16:57:55 -0600
Subject: [PATCH 06/12] remove: new module, for mingw and Solaris 9 bugs
Mingw obeys C89, but not POSIX, by not handling directories.
Solaris remove("file/") mistakenly succeeded.
* modules/remove: New file.
* lib/remove.c: Likewise.
* m4/remove.m4 (gl_FUNC_REMOVE): Likewise.
* m4/stdio_h.m4 (gl_STDIO_H_DEFAULTS): Add witnesses.
* modules/stdio (Makefile.am): Use them.
* lib/stdio.in.h (remove): Declare replacement.
* MODULES.html.sh (systems lacking POSIX:2008): Mention module.
* doc/posix-functions/remove.texi (remove): Likewise.
* modules/remove-tests: New test.
* tests/test-remove.c: Likewise.
Signed-off-by: Eric Blake <address@hidden>
---
ChangeLog | 12 ++++
MODULES.html.sh | 1 +
doc/posix-functions/remove.texi | 13 ++++-
lib/remove.c | 43 ++++++++++++++
lib/stdio.in.h | 14 +++++
m4/remove.m4 | 40 +++++++++++++
m4/stdio_h.m4 | 4 +-
modules/remove | 27 +++++++++
modules/remove-tests | 12 ++++
modules/stdio | 2 +
tests/test-remove.c | 122 +++++++++++++++++++++++++++++++++++++++
11 files changed, 288 insertions(+), 2 deletions(-)
create mode 100644 lib/remove.c
create mode 100644 m4/remove.m4
create mode 100644 modules/remove
create mode 100644 modules/remove-tests
create mode 100644 tests/test-remove.c
diff --git a/ChangeLog b/ChangeLog
index 64bdd7c..48defa3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,17 @@
2009-09-19 Eric Blake <address@hidden>
+ remove: new module, for mingw and Solaris 9 bugs
+ * modules/remove: New file.
+ * lib/remove.c: Likewise.
+ * m4/remove.m4 (gl_FUNC_REMOVE): Likewise.
+ * m4/stdio_h.m4 (gl_STDIO_H_DEFAULTS): Add witnesses.
+ * modules/stdio (Makefile.am): Use them.
+ * lib/stdio.in.h (remove): Declare replacement.
+ * MODULES.html.sh (systems lacking POSIX:2008): Mention module.
+ * doc/posix-functions/remove.texi (remove): Likewise.
+ * modules/remove-tests: New test.
+ * tests/test-remove.c: Likewise.
+
unlink: new module, for Solaris 9 bug
* modules/unlink: New file.
* lib/unlink.c: Likewise.
diff --git a/MODULES.html.sh b/MODULES.html.sh
index f8a2fb4..9104d54 100755
--- a/MODULES.html.sh
+++ b/MODULES.html.sh
@@ -2319,6 +2319,7 @@ func_all_modules ()
func_module realloc-posix
func_module recv
func_module recvfrom
+ func_module remove
func_module sched
func_module select
func_module send
diff --git a/doc/posix-functions/remove.texi b/doc/posix-functions/remove.texi
index 3995006..b3e3d1b 100644
--- a/doc/posix-functions/remove.texi
+++ b/doc/posix-functions/remove.texi
@@ -4,10 +4,21 @@ remove
POSIX specification:
@url{http://www.opengroup.org/onlinepubs/9699919799/functions/remove.html}
-Gnulib module: ---
+Gnulib module: remove
Portability problems fixed by Gnulib:
@itemize
address@hidden
+This function fails to reject trailing slashes on non-directories on
+some platforms:
+Solaris 9.
address@hidden
+This function mistakenly removes a directory with
address@hidden("dir/./")} on some platforms:
+Cygwin 1.5.x.
address@hidden
+This function does not remove empty directories on some platforms:
+mingw.
@end itemize
Portability problems not fixed by Gnulib:
diff --git a/lib/remove.c b/lib/remove.c
new file mode 100644
index 0000000..49863e5
--- /dev/null
+++ b/lib/remove.c
@@ -0,0 +1,43 @@
+/* Remove a file or directory.
+ Copyright (C) 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* written by Eric Blake */
+
+#include <config.h>
+
+#include <stdio.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+#undef remove
+
+/* Remove NAME from the file system. This works around C89 platforms
+ that don't handle directories like POSIX requires; it also works
+ around Solaris 9 bugs with trailing slash. */
+int
+rpl_remove (char const *name)
+{
+ /* It is faster to just try rmdir, and fall back on unlink, than it
+ is to use lstat to see what we are about to remove. Technically,
+ it is more likely that we want unlink, not rmdir, but we cannot
+ guarantee the safety of unlink on directories. Trailing slash
+ bugs are handled by our rmdir and unlink wrappers. */
+ int result = rmdir (name);
+ if (result && errno == ENOTDIR)
+ result = unlink (name);
+ return result;
+}
diff --git a/lib/stdio.in.h b/lib/stdio.in.h
index 27cd305..35109c3 100644
--- a/lib/stdio.in.h
+++ b/lib/stdio.in.h
@@ -416,6 +416,20 @@ extern int putchar (int c);
extern int puts (const char *string);
#endif
+#if @GNULIB_REMOVE@
+# if @REPLACE_REMOVE@
+# undef remove
+# define remove rpl_remove
+extern int remove (const char *name);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef remove
+# define remove(n) \
+ (GL_LINK_WARNING ("remove cannot handle directories on some platforms - " \
+ "use gnulib module remove for more portability"), \
+ remove (n))
+#endif
+
#if @GNULIB_RENAME@
# if @REPLACE_RENAME@
# undef rename
diff --git a/m4/remove.m4 b/m4/remove.m4
new file mode 100644
index 0000000..7bb5a0c
--- /dev/null
+++ b/m4/remove.m4
@@ -0,0 +1,40 @@
+# remove.m4 serial 1
+dnl Copyright (C) 2009 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_FUNC_REMOVE],
+[
+ AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
+ AC_REQUIRE([gl_AC_DOS])
+ AC_REQUIRE([gl_STDIO_H_DEFAULTS])
+ AC_REQUIRE([gl_FUNC_RMDIR])
+ AC_REQUIRE([gl_FUNC_UNLINK])
+ if test "$gl_cv_func_rmdir_works:$gl_cv_func_unlink_works" != yes:yes; then
+ dnl If either underlying syscall is broken, then remove likely has
+ dnl the same bug; blindly use our replacement.
+ REPLACE_REMOVE=1
+ AC_LIBOBJ([remove])
+ else
+ dnl C89 requires remove(), but only POSIX requires it to handle
+ dnl directories. On mingw, directories fails with EPERM.
+ AC_CACHE_CHECK([whether remove handles directories],
+ [gl_cv_func_remove_dir_works],
+ [mkdir conftest.dir
+ AC_RUN_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[#include <stdio.h>
+]], [[return remove ("conftest.dir");]])],
+ [gl_cv_func_remove_dir_works=yes], [gl_cv_func_remove_dir_works=no],
+ [case $host_os in
+ mingw*) gl_cv_func_remove_dir_works="guessing no";;
+ *) gl_cv_func_remove_dir_works="guessing yes";;
+ esac])
+ rm -rf conftest.dir])
+ case $gl_cv_func_remove_dir_works in
+ *no*) REPLACE_REMOVE=1
+ AC_LIBOBJ([remove]);;
+ esac
+ fi
+])
diff --git a/m4/stdio_h.m4 b/m4/stdio_h.m4
index ac5e20a..01af04d 100644
--- a/m4/stdio_h.m4
+++ b/m4/stdio_h.m4
@@ -1,4 +1,4 @@
-# stdio_h.m4 serial 18
+# stdio_h.m4 serial 19
dnl Copyright (C) 2007-2009 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
@@ -67,6 +67,7 @@ AC_DEFUN([gl_STDIO_H_DEFAULTS],
GNULIB_PUTC=0; AC_SUBST([GNULIB_PUTC])
GNULIB_PUTCHAR=0; AC_SUBST([GNULIB_PUTCHAR])
GNULIB_PUTS=0; AC_SUBST([GNULIB_PUTS])
+ GNULIB_REMOVE=0; AC_SUBST([GNULIB_REMOVE])
GNULIB_RENAME=0; AC_SUBST([GNULIB_RENAME])
GNULIB_SNPRINTF=0; AC_SUBST([GNULIB_SNPRINTF])
GNULIB_SPRINTF_POSIX=0; AC_SUBST([GNULIB_SPRINTF_POSIX])
@@ -107,6 +108,7 @@ AC_DEFUN([gl_STDIO_H_DEFAULTS],
REPLACE_PERROR=0; AC_SUBST([REPLACE_PERROR])
REPLACE_POPEN=0; AC_SUBST([REPLACE_POPEN])
REPLACE_PRINTF=0; AC_SUBST([REPLACE_PRINTF])
+ REPLACE_REMOVE=0; AC_SUBST([REPLACE_REMOVE])
REPLACE_RENAME=0; AC_SUBST([REPLACE_RENAME])
REPLACE_SNPRINTF=0; AC_SUBST([REPLACE_SNPRINTF])
REPLACE_SPRINTF=0; AC_SUBST([REPLACE_SPRINTF])
diff --git a/modules/remove b/modules/remove
new file mode 100644
index 0000000..c2a305e
--- /dev/null
+++ b/modules/remove
@@ -0,0 +1,27 @@
+Description:
+remove(): remove a file or directory
+
+Files:
+lib/remove.c
+m4/dos.m4
+m4/remove.m4
+
+Depends-on:
+rmdir
+stdio
+unlink
+
+configure.ac:
+gl_FUNC_REMOVE
+gl_STDIO_MODULE_INDICATOR([remove])
+
+Makefile.am:
+
+Include:
+<stdio.h>
+
+License:
+LGPL
+
+Maintainer:
+Eric Blake
diff --git a/modules/remove-tests b/modules/remove-tests
new file mode 100644
index 0000000..fdc26c2
--- /dev/null
+++ b/modules/remove-tests
@@ -0,0 +1,12 @@
+Files:
+tests/test-remove.c
+
+Depends-on:
+sys_stat
+
+configure.ac:
+AC_CHECK_FUNCS_ONCE([symlink])
+
+Makefile.am:
+TESTS += test-remove
+check_PROGRAMS += test-remove
diff --git a/modules/stdio b/modules/stdio
index 8f242cc..22b83f7 100644
--- a/modules/stdio
+++ b/modules/stdio
@@ -52,6 +52,7 @@ stdio.h: stdio.in.h
-e 's|@''GNULIB_PUTC''@|$(GNULIB_PUTC)|g' \
-e 's|@''GNULIB_PUTCHAR''@|$(GNULIB_PUTCHAR)|g' \
-e 's|@''GNULIB_PUTS''@|$(GNULIB_PUTS)|g' \
+ -e 's|@''GNULIB_REMOVE''@|$(GNULIB_REMOVE)|g' \
-e 's|@''GNULIB_RENAME''@|$(GNULIB_RENAME)|g' \
-e 's|@''GNULIB_SNPRINTF''@|$(GNULIB_SNPRINTF)|g' \
-e 's|@''GNULIB_SPRINTF_POSIX''@|$(GNULIB_SPRINTF_POSIX)|g' \
@@ -89,6 +90,7 @@ stdio.h: stdio.in.h
-e 's|@''REPLACE_PERROR''@|$(REPLACE_PERROR)|g' \
-e 's|@''REPLACE_POPEN''@|$(REPLACE_POPEN)|g' \
-e 's|@''REPLACE_PRINTF''@|$(REPLACE_PRINTF)|g' \
+ -e 's|@''REPLACE_REMOVE''@|$(REPLACE_REMOVE)|g' \
-e 's|@''REPLACE_RENAME''@|$(REPLACE_RENAME)|g' \
-e 's|@''REPLACE_SNPRINTF''@|$(REPLACE_SNPRINTF)|g' \
-e 's|@''REPLACE_SPRINTF''@|$(REPLACE_SPRINTF)|g' \
diff --git a/tests/test-remove.c b/tests/test-remove.c
new file mode 100644
index 0000000..787cde2
--- /dev/null
+++ b/tests/test-remove.c
@@ -0,0 +1,122 @@
+/* Tests of remove.
+ Copyright (C) 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Eric Blake <address@hidden>, 2009. */
+
+#include <config.h>
+
+#include <stdio.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#if !HAVE_SYMLINK
+# define symlink(a,b) (-1)
+#endif
+
+#define ASSERT(expr) \
+ do \
+ { \
+ if (!(expr)) \
+ { \
+ fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
+ fflush (stderr); \
+ abort (); \
+ } \
+ } \
+ while (0)
+
+#define BASE "test-remove.t"
+
+int
+main ()
+{
+ /* Remove any leftovers from a previous partial run. */
+ ASSERT (system ("rm -rf " BASE "*") == 0);
+
+ /* Setup. */
+ ASSERT (mkdir (BASE "dir", 0700) == 0);
+ ASSERT (close (creat (BASE "dir/file", 0600)) == 0);
+
+ /* Basic error conditions. */
+ errno = 0;
+ ASSERT (remove ("") == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (remove ("nosuch") == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (remove ("nosuch/") == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (remove (".") == -1);
+ ASSERT (errno == EINVAL || errno == EBUSY);
+ /* Resulting errno after ".." or "/" is too varied to test; it is
+ reasonable to see any of EINVAL, EEXIST, ENOTEMPTY, EACCES. */
+ ASSERT (remove ("..") == -1);
+ ASSERT (remove ("/") == -1);
+ ASSERT (remove ("///") == -1);
+ errno = 0;
+ ASSERT (remove (BASE "dir/file/") == -1);
+ ASSERT (errno == ENOTDIR);
+
+ /* Non-empty directory. */
+ errno = 0;
+ ASSERT (remove (BASE "dir") == -1);
+ ASSERT (errno == EEXIST || errno == ENOTEMPTY);
+
+ /* Non-directory. */
+ ASSERT (remove (BASE "dir/file") == 0);
+
+ /* Empty directory. */
+ errno = 0;
+ ASSERT (remove (BASE "dir/./") == -1);
+ ASSERT (errno == EINVAL || errno == EBUSY);
+ ASSERT (remove (BASE "dir") == 0);
+
+ /* Test symlink behavior. Specifying trailing slash should remove
+ referent directory, or cause ENOTDIR failure, but not touch
+ symlink. */
+ if (symlink (BASE "dir", BASE "link") != 0)
+ {
+ fputs ("skipping test: symlinks not supported on this filesystem\n",
+ stderr);
+ return 77;
+ }
+ ASSERT (mkdir (BASE "dir", 0700) == 0);
+ errno = 0;
+ if (remove (BASE "link/") == 0)
+ {
+ struct stat st;
+ errno = 0;
+ ASSERT (stat (BASE "link", &st) == -1);
+ ASSERT (errno == ENOENT);
+ }
+ else
+ ASSERT (remove (BASE "dir") == 0);
+ {
+ struct stat st;
+ ASSERT (lstat (BASE "link", &st) == 0);
+ ASSERT (S_ISLNK (st.st_mode));
+ }
+ ASSERT (remove (BASE "link") == 0);
+
+ return 0;
+}
--
1.6.5.rc1
>From 3a5041b1c62c2b7c829f47bec2f1789c59844b6c Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Fri, 18 Sep 2009 18:06:31 -0600
Subject: [PATCH 07/12] test-fstatat: new test, to expose Solaris 9 bugs
Share the stat and lstat tests with fstatat.
* tests/test-stat.c (main): Factor guts...
* tests/test-stat.h (test_stat_func): ...into new file.
* tests/test-lstat.c (main): Factor guts...
* tests/test-lstat.h (test_lstat_func): ...into new file.
* tests/test-fstatat.c: New file.
* modules/stat-tests (Files): Add test-stat.h.
* modules/lstat-tests (Files): Add test-lstat.h.
(Depends-on): Add stdbool.
* modules/openat-tests (Depends-on): Add pathmax.
(Files): Add test-lstat.h, test-stat.h, test-fstatat.c.
(Makefile.am): Run new test.
Signed-off-by: Eric Blake <address@hidden>
---
ChangeLog | 13 +++++
modules/lstat-tests | 2 +
modules/openat-tests | 9 +++-
modules/stat-tests | 1 +
tests/test-fstatat.c | 89 +++++++++++++++++++++++++++++++++++++
tests/test-lstat.c | 102 +++++-------------------------------------
tests/test-lstat.h | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++
tests/test-stat.c | 43 +++++-------------
tests/test-stat.h | 59 +++++++++++++++++++++++++
9 files changed, 313 insertions(+), 124 deletions(-)
create mode 100644 tests/test-fstatat.c
create mode 100644 tests/test-lstat.h
create mode 100644 tests/test-stat.h
diff --git a/ChangeLog b/ChangeLog
index 48defa3..99833ab 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,18 @@
2009-09-19 Eric Blake <address@hidden>
+ test-fstatat: new test, to expose Solaris 9 bugs
+ * tests/test-stat.c (main): Factor guts...
+ * tests/test-stat.h (test_stat_func): ...into new file.
+ * tests/test-lstat.c (main): Factor guts...
+ * tests/test-lstat.h (test_lstat_func): ...into new file.
+ * tests/test-fstatat.c: New file.
+ * modules/stat-tests (Files): Add test-stat.h.
+ * modules/lstat-tests (Files): Add test-lstat.h.
+ (Depends-on): Add stdbool.
+ * modules/openat-tests (Depends-on): Add pathmax.
+ (Files): Add test-lstat.h, test-stat.h, test-fstatat.c.
+ (Makefile.am): Run new test.
+
remove: new module, for mingw and Solaris 9 bugs
* modules/remove: New file.
* lib/remove.c: Likewise.
diff --git a/modules/lstat-tests b/modules/lstat-tests
index 2368692..acd2bcd 100644
--- a/modules/lstat-tests
+++ b/modules/lstat-tests
@@ -1,9 +1,11 @@
Files:
+tests/test-lstat.h
tests/test-lstat.c
Depends-on:
errno
same-inode
+stdbool
configure.ac:
AC_CHECK_FUNCS_ONCE([symlink])
diff --git a/modules/openat-tests b/modules/openat-tests
index 54d0c61..42ae6e8 100644
--- a/modules/openat-tests
+++ b/modules/openat-tests
@@ -1,15 +1,20 @@
Files:
+tests/test-lstat.h
tests/test-rmdir.h
+tests/test-stat.h
+tests/test-fstatat.c
tests/test-openat.c
tests/test-unlinkat.c
Depends-on:
+pathmax
configure.ac:
AC_CHECK_FUNCS_ONCE([symlink])
Makefile.am:
-TESTS += test-openat test-unlinkat
-check_PROGRAMS += test-openat test-unlinkat
+TESTS += test-fstatat test-openat test-unlinkat
+check_PROGRAMS += test-fstatat test-openat test-unlinkat
+test_fstatat_LDADD = $(LDADD) @LIBINTL@
test_openat_LDADD = $(LDADD) @LIBINTL@
test_unlinkat_LDADD = $(LDADD) @LIBINTL@
diff --git a/modules/stat-tests b/modules/stat-tests
index 3d31b52..93444aa 100644
--- a/modules/stat-tests
+++ b/modules/stat-tests
@@ -1,4 +1,5 @@
Files:
+tests/test-stat.h
tests/test-stat.c
Depends-on:
diff --git a/tests/test-fstatat.c b/tests/test-fstatat.c
new file mode 100644
index 0000000..a70721a
--- /dev/null
+++ b/tests/test-fstatat.c
@@ -0,0 +1,89 @@
+/* Tests of fstatat.
+ Copyright (C) 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Eric Blake <address@hidden>, 2009. */
+
+#include <config.h>
+
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "openat.h"
+#include "pathmax.h"
+#include "same-inode.h"
+
+#if !HAVE_SYMLINK
+# define symlink(a,b) (-1)
+#endif
+
+#define ASSERT(expr) \
+ do \
+ { \
+ if (!(expr)) \
+ { \
+ fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
+ fflush (stderr); \
+ abort (); \
+ } \
+ } \
+ while (0)
+
+#define BASE "test-fstatat.t"
+
+#include "test-lstat.h"
+#include "test-stat.h"
+
+static int dfd = AT_FDCWD;
+
+/* Wrapper around fstatat to test stat behavior. */
+static int
+do_stat (char const *name, struct stat *st)
+{
+ return statat (dfd, name, st);
+}
+
+/* Wrapper around fstatat to test lstat behavior. */
+static int
+do_lstat (char const *name, struct stat *st)
+{
+ return lstatat (dfd, name, st);
+}
+
+int
+main ()
+{
+ int result;
+ ASSERT (test_stat_func (do_stat) == 0);
+ result = test_lstat_func (do_lstat, false);
+ dfd = open (".", O_RDONLY);
+ ASSERT (0 <= dfd);
+ ASSERT (test_stat_func (do_stat) == 0);
+ ASSERT (test_lstat_func (do_lstat, false) == result);
+ ASSERT (close (dfd) == 0);
+
+ /* FIXME - add additional tests of dfd not at current directory. */
+
+ if (result == 77)
+ fputs ("skipping test: symlinks not supported on this filesystem\n",
+ stderr);
+ return result;
+}
diff --git a/tests/test-lstat.c b/tests/test-lstat.c
index 8182738..cb9963d 100644
--- a/tests/test-lstat.c
+++ b/tests/test-lstat.c
@@ -22,6 +22,7 @@
#include <fcntl.h>
#include <errno.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
@@ -46,97 +47,18 @@
#define BASE "test-lstat.t"
+#include "test-lstat.h"
+
+/* Wrapper around lstat, which works even if lstat is a function-like
+ macro, where test_lstat_func(lstat) would do the wrong thing. */
+static int
+do_lstat (char const *name, struct stat *st)
+{
+ return lstat (name, st);
+}
+
int
main ()
{
- struct stat st1;
- struct stat st2;
-
- /* Remove any leftovers from a previous partial run. */
- ASSERT (system ("rm -rf " BASE "*") == 0);
-
- /* Test for common directories. */
- ASSERT (lstat (".", &st1) == 0);
- ASSERT (lstat ("./", &st2) == 0);
- ASSERT (SAME_INODE (st1, st2));
- ASSERT (S_ISDIR (st1.st_mode));
- ASSERT (S_ISDIR (st2.st_mode));
- ASSERT (lstat ("/", &st1) == 0);
- ASSERT (lstat ("///", &st2) == 0);
- ASSERT (SAME_INODE (st1, st2));
- ASSERT (S_ISDIR (st1.st_mode));
- ASSERT (S_ISDIR (st2.st_mode));
- ASSERT (lstat ("..", &st1) == 0);
- ASSERT (S_ISDIR (st1.st_mode));
-
- /* Test for error conditions. */
- errno = 0;
- ASSERT (lstat ("", &st1) == -1);
- ASSERT (errno == ENOENT);
- errno = 0;
- ASSERT (lstat ("nosuch", &st1) == -1);
- ASSERT (errno == ENOENT);
- errno = 0;
- ASSERT (lstat ("nosuch/", &st1) == -1);
- ASSERT (errno == ENOENT);
-
- ASSERT (close (creat (BASE "file", 0600)) == 0);
- ASSERT (lstat (BASE "file", &st1) == 0);
- ASSERT (S_ISREG (st1.st_mode));
- errno = 0;
- ASSERT (lstat (BASE "file/", &st1) == -1);
- ASSERT (errno == ENOTDIR);
-
- /* Now for some symlink tests, where supported. We set up:
- link1 -> directory
- link2 -> file
- link3 -> dangling
- link4 -> loop
- then test behavior both with and without trailing slash.
- */
- if (symlink (".", BASE "link1") != 0)
- {
- ASSERT (unlink (BASE "file") == 0);
- fputs ("skipping test: symlinks not supported on this filesystem\n",
- stderr);
- return 77;
- }
- ASSERT (symlink (BASE "file", BASE "link2") == 0);
- ASSERT (symlink (BASE "nosuch", BASE "link3") == 0);
- ASSERT (symlink (BASE "link4", BASE "link4") == 0);
-
- ASSERT (lstat (BASE "link1", &st1) == 0);
- ASSERT (S_ISLNK (st1.st_mode));
- ASSERT (lstat (BASE "link1/", &st1) == 0);
- ASSERT (stat (BASE "link1", &st2) == 0);
- ASSERT (S_ISDIR (st1.st_mode));
- ASSERT (S_ISDIR (st2.st_mode));
- ASSERT (SAME_INODE (st1, st2));
-
- ASSERT (lstat (BASE "link2", &st1) == 0);
- ASSERT (S_ISLNK (st1.st_mode));
- errno = 0;
- ASSERT (lstat (BASE "link2/", &st1) == -1);
- ASSERT (errno == ENOTDIR);
-
- ASSERT (lstat (BASE "link3", &st1) == 0);
- ASSERT (S_ISLNK (st1.st_mode));
- errno = 0;
- ASSERT (lstat (BASE "link3/", &st1) == -1);
- ASSERT (errno == ENOENT);
-
- ASSERT (lstat (BASE "link4", &st1) == 0);
- ASSERT (S_ISLNK (st1.st_mode));
- errno = 0;
- ASSERT (lstat (BASE "link4/", &st1) == -1);
- ASSERT (errno == ELOOP);
-
- /* Cleanup. */
- ASSERT (unlink (BASE "file") == 0);
- ASSERT (unlink (BASE "link1") == 0);
- ASSERT (unlink (BASE "link2") == 0);
- ASSERT (unlink (BASE "link3") == 0);
- ASSERT (unlink (BASE "link4") == 0);
-
- return 0;
+ return test_lstat_func (do_lstat, true);
}
diff --git a/tests/test-lstat.h b/tests/test-lstat.h
new file mode 100644
index 0000000..68e4f74
--- /dev/null
+++ b/tests/test-lstat.h
@@ -0,0 +1,119 @@
+/* Test of lstat() function.
+ Copyright (C) 2008, 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Simon Josefsson, 2008; and Eric Blake, 2009. */
+
+/* This file is designed to test both lstat(n,buf) and
+ fstatat(AT_FDCWD,n,buf,AT_SYMLINK_NOFOLLOW). FUNC is the function
+ to test. Assumes that BASE and ASSERT are already defined, and
+ that appropriate headers are already included. If PRINT, warn
+ before skipping symlink tests with status 77. */
+
+static int
+test_lstat_func (int (*func) (char const *, struct stat *), bool print)
+{
+ struct stat st1;
+ struct stat st2;
+
+ /* Remove any leftovers from a previous partial run. */
+ ASSERT (system ("rm -rf " BASE "*") == 0);
+
+ /* Test for common directories. */
+ ASSERT (func (".", &st1) == 0);
+ ASSERT (func ("./", &st2) == 0);
+ ASSERT (SAME_INODE (st1, st2));
+ ASSERT (S_ISDIR (st1.st_mode));
+ ASSERT (S_ISDIR (st2.st_mode));
+ ASSERT (func ("/", &st1) == 0);
+ ASSERT (func ("///", &st2) == 0);
+ ASSERT (SAME_INODE (st1, st2));
+ ASSERT (S_ISDIR (st1.st_mode));
+ ASSERT (S_ISDIR (st2.st_mode));
+ ASSERT (func ("..", &st1) == 0);
+ ASSERT (S_ISDIR (st1.st_mode));
+
+ /* Test for error conditions. */
+ errno = 0;
+ ASSERT (func ("", &st1) == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (func ("nosuch", &st1) == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (func ("nosuch/", &st1) == -1);
+ ASSERT (errno == ENOENT);
+
+ ASSERT (close (creat (BASE "file", 0600)) == 0);
+ ASSERT (func (BASE "file", &st1) == 0);
+ ASSERT (S_ISREG (st1.st_mode));
+ errno = 0;
+ ASSERT (func (BASE "file/", &st1) == -1);
+ ASSERT (errno == ENOTDIR);
+
+ /* Now for some symlink tests, where supported. We set up:
+ link1 -> directory
+ link2 -> file
+ link3 -> dangling
+ link4 -> loop
+ then test behavior both with and without trailing slash.
+ */
+ if (symlink (".", BASE "link1") != 0)
+ {
+ ASSERT (unlink (BASE "file") == 0);
+ if (print)
+ fputs ("skipping test: symlinks not supported on this filesystem\n",
+ stderr);
+ return 77;
+ }
+ ASSERT (symlink (BASE "file", BASE "link2") == 0);
+ ASSERT (symlink (BASE "nosuch", BASE "link3") == 0);
+ ASSERT (symlink (BASE "link4", BASE "link4") == 0);
+
+ ASSERT (func (BASE "link1", &st1) == 0);
+ ASSERT (S_ISLNK (st1.st_mode));
+ ASSERT (func (BASE "link1/", &st1) == 0);
+ ASSERT (stat (BASE "link1", &st2) == 0);
+ ASSERT (S_ISDIR (st1.st_mode));
+ ASSERT (S_ISDIR (st2.st_mode));
+ ASSERT (SAME_INODE (st1, st2));
+
+ ASSERT (func (BASE "link2", &st1) == 0);
+ ASSERT (S_ISLNK (st1.st_mode));
+ errno = 0;
+ ASSERT (func (BASE "link2/", &st1) == -1);
+ ASSERT (errno == ENOTDIR);
+
+ ASSERT (func (BASE "link3", &st1) == 0);
+ ASSERT (S_ISLNK (st1.st_mode));
+ errno = 0;
+ ASSERT (func (BASE "link3/", &st1) == -1);
+ ASSERT (errno == ENOENT);
+
+ ASSERT (func (BASE "link4", &st1) == 0);
+ ASSERT (S_ISLNK (st1.st_mode));
+ errno = 0;
+ ASSERT (func (BASE "link4/", &st1) == -1);
+ ASSERT (errno == ELOOP);
+
+ /* Cleanup. */
+ ASSERT (unlink (BASE "file") == 0);
+ ASSERT (unlink (BASE "link1") == 0);
+ ASSERT (unlink (BASE "link2") == 0);
+ ASSERT (unlink (BASE "link3") == 0);
+ ASSERT (unlink (BASE "link4") == 0);
+
+ return 0;
+}
diff --git a/tests/test-stat.c b/tests/test-stat.c
index 337c819..f6777e8 100644
--- a/tests/test-stat.c
+++ b/tests/test-stat.c
@@ -43,39 +43,18 @@
#define BASE "test-stat.t"
+#include "test-stat.h"
+
+/* Wrapper around stat, which works even if stat is a function-like
+ macro, where test_stat_func(stat) would do the wrong thing. */
+static int
+do_stat (char const *name, struct stat *st)
+{
+ return stat (name, st);
+}
+
int
main ()
{
- struct stat st1;
- struct stat st2;
- char cwd[PATH_MAX];
-
- ASSERT (getcwd (cwd, PATH_MAX) == cwd);
- ASSERT (stat (".", &st1) == 0);
- ASSERT (stat ("./", &st2) == 0);
- ASSERT (SAME_INODE (st1, st2));
- ASSERT (stat (cwd, &st2) == 0);
- ASSERT (SAME_INODE (st1, st2));
- ASSERT (stat ("/", &st1) == 0);
- ASSERT (stat ("///", &st2) == 0);
- ASSERT (SAME_INODE (st1, st2));
-
- errno = 0;
- ASSERT (stat ("", &st1) == -1);
- ASSERT (errno == ENOENT);
- errno = 0;
- ASSERT (stat ("nosuch", &st1) == -1);
- ASSERT (errno == ENOENT);
- errno = 0;
- ASSERT (stat ("nosuch/", &st1) == -1);
- ASSERT (errno == ENOENT);
-
- ASSERT (close (creat (BASE "file", 0600)) == 0);
- ASSERT (stat (BASE "file", &st1) == 0);
- errno = 0;
- ASSERT (stat (BASE "file/", &st1) == -1);
- ASSERT (errno == ENOTDIR);
- ASSERT (unlink (BASE "file") == 0);
-
- return 0;
+ return test_stat_func (do_stat);
}
diff --git a/tests/test-stat.h b/tests/test-stat.h
new file mode 100644
index 0000000..17bb43f
--- /dev/null
+++ b/tests/test-stat.h
@@ -0,0 +1,59 @@
+/* Tests of stat.
+ Copyright (C) 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Eric Blake <address@hidden>, 2009. */
+
+/* This file is designed to test both stat(n,buf) and
+ fstatat(AT_FDCWD,n,buf,0). FUNC is the function to test. Assumes
+ that BASE and ASSERT are already defined, and that appropriate
+ headers are already included. */
+
+static int
+test_stat_func (int (*func) (char const *, struct stat *))
+{
+ struct stat st1;
+ struct stat st2;
+ char cwd[PATH_MAX];
+
+ ASSERT (getcwd (cwd, PATH_MAX) == cwd);
+ ASSERT (func (".", &st1) == 0);
+ ASSERT (func ("./", &st2) == 0);
+ ASSERT (SAME_INODE (st1, st2));
+ ASSERT (func (cwd, &st2) == 0);
+ ASSERT (SAME_INODE (st1, st2));
+ ASSERT (func ("/", &st1) == 0);
+ ASSERT (func ("///", &st2) == 0);
+ ASSERT (SAME_INODE (st1, st2));
+
+ errno = 0;
+ ASSERT (func ("", &st1) == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (func ("nosuch", &st1) == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (func ("nosuch/", &st1) == -1);
+ ASSERT (errno == ENOENT);
+
+ ASSERT (close (creat (BASE "file", 0600)) == 0);
+ ASSERT (func (BASE "file", &st1) == 0);
+ errno = 0;
+ ASSERT (func (BASE "file/", &st1) == -1);
+ ASSERT (errno == ENOTDIR);
+ ASSERT (unlink (BASE "file") == 0);
+
+ return 0;
+}
--
1.6.5.rc1
>From ff4e8f6b53d7b8c53856ba8ba2adaad3251e6a88 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Thu, 17 Sep 2009 22:16:56 -0600
Subject: [PATCH 08/12] test-unlinkat: enhance test, to expose Solaris 9 bug
Share the unlink tests with unlinkat.
* tests/test-unlink.c (main): Factor guts...
* tests/test-unlink.h (test_rmdir_func): ...into new file.
* tests/test-rmdir.h (test_rmdir_func): Add parameter.
* tests/test-rmdir.c (main): Adjust caller.
* tests/test-unlinkat.c (main): Likewise. Add unlink tests.
(unlinker): New helper function.
(rmdirat): Enhance check.
* modules/rmdir-tests (Depends-on): Add stdbool.
* modules/unlink-tests (Depends-on): Likewise.
(Files): Add test-unlink.h.
* modules/openat-tests (Files): Likewise.
(Depends-on): Add unlinkdir.
Signed-off-by: Eric Blake <address@hidden>
---
ChangeLog | 14 ++++++++
modules/openat-tests | 2 +
modules/rmdir-tests | 1 +
modules/unlink-tests | 2 +
tests/test-rmdir.c | 3 +-
tests/test-rmdir.h | 10 +++--
tests/test-unlink.c | 61 ++--------------------------------
tests/test-unlink.h | 86 +++++++++++++++++++++++++++++++++++++++++++++++++
tests/test-unlinkat.c | 34 +++++++++++++++++--
9 files changed, 148 insertions(+), 65 deletions(-)
create mode 100644 tests/test-unlink.h
diff --git a/ChangeLog b/ChangeLog
index 99833ab..3c54062 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,19 @@
2009-09-19 Eric Blake <address@hidden>
+ test-unlinkat: enhance test, to expose Solaris 9 bug
+ * tests/test-unlink.c (main): Factor guts...
+ * tests/test-unlink.h (test_rmdir_func): ...into new file.
+ * tests/test-rmdir.h (test_rmdir_func): Add parameter.
+ * tests/test-rmdir.c (main): Adjust caller.
+ * tests/test-unlinkat.c (main): Likewise. Add unlink tests.
+ (unlinker): New helper function.
+ (rmdirat): Enhance check.
+ * modules/rmdir-tests (Depends-on): Add stdbool.
+ * modules/unlink-tests (Depends-on): Likewise.
+ (Files): Add test-unlink.h.
+ * modules/openat-tests (Files): Likewise.
+ (Depends-on): Add unlinkdir.
+
test-fstatat: new test, to expose Solaris 9 bugs
* tests/test-stat.c (main): Factor guts...
* tests/test-stat.h (test_stat_func): ...into new file.
diff --git a/modules/openat-tests b/modules/openat-tests
index 42ae6e8..7134f2c 100644
--- a/modules/openat-tests
+++ b/modules/openat-tests
@@ -2,12 +2,14 @@ Files:
tests/test-lstat.h
tests/test-rmdir.h
tests/test-stat.h
+tests/test-unlink.h
tests/test-fstatat.c
tests/test-openat.c
tests/test-unlinkat.c
Depends-on:
pathmax
+unlinkdir
configure.ac:
AC_CHECK_FUNCS_ONCE([symlink])
diff --git a/modules/rmdir-tests b/modules/rmdir-tests
index 2a68120..fca8a77 100644
--- a/modules/rmdir-tests
+++ b/modules/rmdir-tests
@@ -3,6 +3,7 @@ tests/test-rmdir.h
tests/test-rmdir.c
Depends-on:
+stdbool
configure.ac:
AC_CHECK_FUNCS_ONCE([symlink])
diff --git a/modules/unlink-tests b/modules/unlink-tests
index b527b61..f0930f0 100644
--- a/modules/unlink-tests
+++ b/modules/unlink-tests
@@ -1,7 +1,9 @@
Files:
+tests/test-unlink.h
tests/test-unlink.c
Depends-on:
+stdbool
unlinkdir
configure.ac:
diff --git a/tests/test-rmdir.c b/tests/test-rmdir.c
index 6d55ea9..d7e4da3 100644
--- a/tests/test-rmdir.c
+++ b/tests/test-rmdir.c
@@ -22,6 +22,7 @@
#include <fcntl.h>
#include <errno.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
@@ -49,5 +50,5 @@
int
main ()
{
- return test_rmdir_func (rmdir);
+ return test_rmdir_func (rmdir, true);
}
diff --git a/tests/test-rmdir.h b/tests/test-rmdir.h
index 2470240..bb7b344 100644
--- a/tests/test-rmdir.h
+++ b/tests/test-rmdir.h
@@ -19,10 +19,11 @@
/* This file is designed to test both rmdir(n) and
unlinkat(AT_FDCWD,n,AT_REMOVEDIR). FUNC is the function to test.
Assumes that BASE and ASSERT are already defined, and that
- appropriate headers are already included. */
+ appropriate headers are already included. If PRINT, then warn
+ before returning status 77 when symlinks are unsupported. */
static int
-test_rmdir_func (int (*func) (char const *name))
+test_rmdir_func (int (*func) (char const *name), bool print)
{
/* Remove any leftovers from a previous partial run. */
ASSERT (system ("rm -rf " BASE "*") == 0);
@@ -78,8 +79,9 @@ test_rmdir_func (int (*func) (char const *name))
but not enough to penalize POSIX systems with an rpl_rmdir. */
if (symlink (BASE "dir", BASE "link") != 0)
{
- fputs ("skipping test: symlinks not supported on this filesystem\n",
- stderr);
+ if (print)
+ fputs ("skipping test: symlinks not supported on this filesystem\n",
+ stderr);
return 77;
}
ASSERT (mkdir (BASE "dir", 0700) == 0);
diff --git a/tests/test-unlink.c b/tests/test-unlink.c
index 5aaa595..f5df9b6 100644
--- a/tests/test-unlink.c
+++ b/tests/test-unlink.c
@@ -22,6 +22,7 @@
#include <fcntl.h>
#include <errno.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -47,64 +48,10 @@
#define BASE "test-unlink.t"
+#include "test-unlink.h"
+
int
main ()
{
- /* Remove any leftovers from a previous partial run. */
- ASSERT (system ("rm -rf " BASE "*") == 0);
-
- /* Setup. */
- ASSERT (mkdir (BASE "dir", 0700) == 0);
- ASSERT (close (creat (BASE "dir/file", 0600)) == 0);
-
- /* Basic error conditions. */
- errno = 0;
- ASSERT (unlink ("") == -1);
- ASSERT (errno == ENOENT);
- errno = 0;
- ASSERT (unlink (BASE "nosuch") == -1);
- ASSERT (errno == ENOENT);
- errno = 0;
- ASSERT (unlink (BASE "nosuch/") == -1);
- ASSERT (errno == ENOENT);
- /* Resulting errno after directories is rather varied across
- implementations (EPERM, EINVAL, EACCES, EBUSY, EISDIR, ENOTSUP);
- however, we must be careful to not attempt unlink on a directory
- unless we know it must fail. */
- if (cannot_unlink_dir ())
- {
- ASSERT (unlink (".") == -1);
- ASSERT (unlink ("..") == -1);
- ASSERT (unlink ("/") == -1);
- ASSERT (unlink (BASE "dir") == -1);
- ASSERT (mkdir (BASE "dir1", 0700) == 0);
- ASSERT (unlink (BASE "dir1") == -1);
- ASSERT (rmdir (BASE "dir1") == 0);
- }
- errno = 0;
- ASSERT (unlink (BASE "dir/file/") == -1);
- ASSERT (errno == ENOTDIR);
-
- /* Test symlink behavior. Specifying trailing slash will attempt
- unlink of a directory, so only attempt it if we know it must
- fail. */
- if (symlink (BASE "dir", BASE "link") != 0)
- {
- ASSERT (unlink (BASE "dir/file") == 0);
- ASSERT (rmdir (BASE "dir") == 0);
- fputs ("skipping test: symlinks not supported on this filesystem\n",
- stderr);
- return 77;
- }
- if (cannot_unlink_dir ())
- ASSERT (unlink (BASE "link/") == -1);
- ASSERT (unlink (BASE "link") == 0);
- ASSERT (symlink (BASE "dir/file", BASE "link") == 0);
- /* Order here proves unlink of a symlink does not follow through to
- the file. */
- ASSERT (unlink (BASE "link") == 0);
- ASSERT (unlink (BASE "dir/file") == 0);
- ASSERT (rmdir (BASE "dir") == 0);
-
- return 0;
+ return test_unlink_func (unlink, true);
}
diff --git a/tests/test-unlink.h b/tests/test-unlink.h
new file mode 100644
index 0000000..80cce94
--- /dev/null
+++ b/tests/test-unlink.h
@@ -0,0 +1,86 @@
+/* Tests of unlink.
+ Copyright (C) 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Eric Blake <address@hidden>, 2009. */
+
+/* This file is designed to test both unlink(n) and
+ unlinkat(AT_FDCWD,n,0). FUNC is the function to test. Assumes
+ that BASE and ASSERT are already defined, and that appropriate
+ headers are already included. If PRINT, then warn before returning
+ status 77 when symlinks are unsupported. */
+
+static int
+test_unlink_func (int (*func) (char const *name), bool print)
+{
+ /* Remove any leftovers from a previous partial run. */
+ ASSERT (system ("rm -rf " BASE "*") == 0);
+
+ /* Setup. */
+ ASSERT (mkdir (BASE "dir", 0700) == 0);
+ ASSERT (close (creat (BASE "dir/file", 0600)) == 0);
+
+ /* Basic error conditions. */
+ errno = 0;
+ ASSERT (func ("") == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (func (BASE "nosuch") == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (func (BASE "nosuch/") == -1);
+ ASSERT (errno == ENOENT);
+ /* Resulting errno after directories is rather varied across
+ implementations (EPERM, EINVAL, EACCES, EBUSY, EISDIR, ENOTSUP);
+ however, we must be careful to not attempt unlink on a directory
+ unless we know it must fail. */
+ if (cannot_unlink_dir ())
+ {
+ ASSERT (func (".") == -1);
+ ASSERT (func ("..") == -1);
+ ASSERT (func ("/") == -1);
+ ASSERT (func (BASE "dir") == -1);
+ ASSERT (mkdir (BASE "dir1", 0700) == 0);
+ ASSERT (func (BASE "dir1") == -1);
+ ASSERT (rmdir (BASE "dir1") == 0);
+ }
+ errno = 0;
+ ASSERT (func (BASE "dir/file/") == -1);
+ ASSERT (errno == ENOTDIR);
+
+ /* Test symlink behavior. Specifying trailing slash will attempt
+ unlink of a directory, so only attempt it if we know it must
+ fail. */
+ if (symlink (BASE "dir", BASE "link") != 0)
+ {
+ ASSERT (func (BASE "dir/file") == 0);
+ ASSERT (rmdir (BASE "dir") == 0);
+ if (print)
+ fputs ("skipping test: symlinks not supported on this filesystem\n",
+ stderr);
+ return 77;
+ }
+ if (cannot_unlink_dir ())
+ ASSERT (func (BASE "link/") == -1);
+ ASSERT (func (BASE "link") == 0);
+ ASSERT (symlink (BASE "dir/file", BASE "link") == 0);
+ /* Order here proves unlink of a symlink does not follow through to
+ the file. */
+ ASSERT (func (BASE "link") == 0);
+ ASSERT (func (BASE "dir/file") == 0);
+ ASSERT (rmdir (BASE "dir") == 0);
+
+ return 0;
+}
diff --git a/tests/test-unlinkat.c b/tests/test-unlinkat.c
index fb87a19..8e0a1cd 100644
--- a/tests/test-unlinkat.c
+++ b/tests/test-unlinkat.c
@@ -22,10 +22,13 @@
#include <fcntl.h>
#include <errno.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
+#include "unlinkdir.h"
+
#if !HAVE_SYMLINK
# define symlink(a,b) (-1)
#endif
@@ -45,17 +48,42 @@
#define BASE "test-unlinkat.t"
#include "test-rmdir.h"
+#include "test-unlink.h"
+
+static int dfd = AT_FDCWD;
/* Wrapper around unlinkat to test rmdir behavior. */
static int
rmdirat (char const *name)
{
- return unlinkat (AT_FDCWD, name, AT_REMOVEDIR);
+ return unlinkat (dfd, name, AT_REMOVEDIR);
+}
+
+/* Wrapper around unlinkat to test unlink behavior. */
+static int
+unlinker (char const *name)
+{
+ return unlinkat (dfd, name, 0);
}
int
main ()
{
- /* FIXME: Add tests of unlinkat(,0), and of fd instead of AT_FDCWD. */
- return test_rmdir_func (rmdirat);
+ /* FIXME: Add tests of fd other than ".". */
+ int result1;
+ int result2;
+ result1 = test_rmdir_func (rmdirat, false);
+ result2 = test_unlink_func (unlinker, false);
+ ASSERT (result1 == result2);
+ dfd = open (".", O_RDONLY);
+ ASSERT (0 <= dfd);
+ result2 = test_rmdir_func (rmdirat, false);
+ ASSERT (result1 == result2);
+ result2 = test_unlink_func (unlinker, false);
+ ASSERT (result1 == result2);
+ ASSERT (close (dfd) == 0);
+ if (result1 == 77)
+ fputs ("skipping test: symlinks not supported on this filesystem\n",
+ stderr);
+ return result1;
}
--
1.6.5.rc1
>From ff971b940b619067d76bb7a893d0e567636f4fc2 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Fri, 18 Sep 2009 19:38:46 -0600
Subject: [PATCH 09/12] openat: fix fstatat bugs on Solaris 9
fstatat(fd,"file/",buf,flag) mistakenly succeeded.
* lib/fstatat.c (rpl_fstatat): Copy recent fixes from lstat and
stat.
* doc/posix-functions/fstatat.texi (fstatat): Document this.
Signed-off-by: Eric Blake <address@hidden>
---
ChangeLog | 5 +++++
doc/posix-functions/fstatat.texi | 14 ++++++++++++++
lib/fstatat.c | 34 ++++++++++++++++++++--------------
3 files changed, 39 insertions(+), 14 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index 3c54062..e79f9dd 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
2009-09-19 Eric Blake <address@hidden>
+ openat: fix fstatat bugs on Solaris 9
+ * lib/fstatat.c (rpl_fstatat): Copy recent fixes from lstat and
+ stat.
+ * doc/posix-functions/fstatat.texi (fstatat): Document this.
+
test-unlinkat: enhance test, to expose Solaris 9 bug
* tests/test-unlink.c (main): Factor guts...
* tests/test-unlink.h (test_rmdir_func): ...into new file.
diff --git a/doc/posix-functions/fstatat.texi b/doc/posix-functions/fstatat.texi
index 4b9fcbc..d1c4c83 100644
--- a/doc/posix-functions/fstatat.texi
+++ b/doc/posix-functions/fstatat.texi
@@ -13,8 +13,22 @@ fstatat
glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX
5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Cygwin 1.5.x, mingw, Interix 3.5, BeOS.
But the replacement function is not safe to be used in libraries and is not
multithread-safe.
address@hidden
+On some platforms, @code{fstatat(fd,"file/",buf,flag)} succeeds instead of
+failing with @code{ENOTDIR}.
+Solaris 9.
address@hidden
+For symlinks, when the argument ends in a slash, some platforms don't
+dereference the argument:
+Solaris 9.
@end itemize
Portability problems not fixed by Gnulib:
@itemize
address@hidden
+On platforms where @code{off_t} is a 32-bit type, @code{fstatat} may
+not correctly report the size of files or block devices larger than 2
+GB. The fix is to use the @code{AC_SYS_LARGEFILE} macro.
address@hidden
+On Windows platforms (excluding Cygwin), @code{st_ino} is always 0.
@end itemize
diff --git a/lib/fstatat.c b/lib/fstatat.c
index 9b0c1af..59d7422 100644
--- a/lib/fstatat.c
+++ b/lib/fstatat.c
@@ -28,31 +28,37 @@
#undef fstatat
/* fstatat should always follow symbolic links that end in /, but on
- Solaris 9 it doesn't if AT_SYMLINK_NOFOLLOW is specified. This is
- the same problem that lstat.c addresses, so solve it in a similar
- way. */
+ Solaris 9 it doesn't if AT_SYMLINK_NOFOLLOW is specified.
+ Likewise, trailing slash on a non-directory should be an error.
+ These are the same problems that lstat.c and stat.c address, so
+ solve it in a similar way. */
int
rpl_fstatat (int fd, char const *file, struct stat *st, int flag)
{
int result = fstatat (fd, file, st, flag);
+ size_t len;
- if (result == 0 && (flag & AT_SYMLINK_NOFOLLOW) && S_ISLNK (st->st_mode)
- && file[strlen (file) - 1] == '/')
+ if (result != 0)
+ return result;
+ len = strlen (file);
+ if (flag & AT_SYMLINK_NOFOLLOW)
{
- /* FILE refers to a symbolic link and the name ends with a slash.
- Get info about the link's referent. */
- result = fstatat (fd, file, st, flag & ~AT_SYMLINK_NOFOLLOW);
- if (result == 0 && ! S_ISDIR (st->st_mode))
+ /* Fix lstat behavior. */
+ if (file[len - 1] != '/' || S_ISDIR (st->st_mode))
+ return 0;
+ if (!S_ISLNK (st->st_mode))
{
- /* fstatat succeeded and FILE references a non-directory.
- But it was specified via a name including a trailing
- slash. Fail with errno set to ENOTDIR to indicate the
- contradiction. */
errno = ENOTDIR;
return -1;
}
+ result = fstatat (fd, file, st, flag & ~AT_SYMLINK_NOFOLLOW);
+ }
+ /* Fix stat behavior. */
+ if (result == 0 && !S_ISDIR (st->st_mode) && file[len - 1] == '/')
+ {
+ errno = ENOTDIR;
+ return -1;
}
-
return result;
}
--
1.6.5.rc1
>From 22d7496bac7d391faff10755b17889ecd2f604d4 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Fri, 18 Sep 2009 19:39:06 -0600
Subject: [PATCH 10/12] openat: fix unlinkat bugs on Solaris 9
unlinkat(fd,"file/",0) mistakenly succeeded.
* lib/unlinkat.c (unlinkat): New file.
* modules/openat (Depends-on): Add unlink.
(Files): Distribute it.
* m4/openat.m4 (gl_FUNC_OPENAT): Mark unlinkat for replacement if
trailing slash behavior is broken.
* m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add witness.
* modules/unistd (Makefile.am): Substitute it.
* lib/unistd.in.h (unlinkat): Declare replacement.
* doc/posix-functions/unlinkat.texi (unlinkat): Document this.
Signed-off-by: Eric Blake <address@hidden>
---
ChangeLog | 11 +++++
doc/posix-functions/unlinkat.texi | 27 +++++++++++++
lib/unistd.in.h | 6 ++-
lib/unlinkat.c | 77 +++++++++++++++++++++++++++++++++++++
m4/openat.m4 | 6 ++-
m4/unistd_h.m4 | 3 +-
modules/openat | 2 +
modules/unistd | 1 +
8 files changed, 130 insertions(+), 3 deletions(-)
create mode 100644 lib/unlinkat.c
diff --git a/ChangeLog b/ChangeLog
index e79f9dd..99e0840 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,16 @@
2009-09-19 Eric Blake <address@hidden>
+ openat: fix unlinkat bugs on Solaris 9
+ * lib/unlinkat.c (unlinkat): New file.
+ * modules/openat (Depends-on): Add unlink.
+ (Files): Distribute it.
+ * m4/openat.m4 (gl_FUNC_OPENAT): Mark unlinkat for replacement if
+ trailing slash behavior is broken.
+ * m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add witness.
+ * modules/unistd (Makefile.am): Substitute it.
+ * lib/unistd.in.h (unlinkat): Declare replacement.
+ * doc/posix-functions/unlinkat.texi (unlinkat): Document this.
+
openat: fix fstatat bugs on Solaris 9
* lib/fstatat.c (rpl_fstatat): Copy recent fixes from lstat and
stat.
diff --git a/doc/posix-functions/unlinkat.texi
b/doc/posix-functions/unlinkat.texi
index 99c4b3e..dfff9b9 100644
--- a/doc/posix-functions/unlinkat.texi
+++ b/doc/posix-functions/unlinkat.texi
@@ -13,8 +13,35 @@ unlinkat
glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX
5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Cygwin 1.5.x, mingw, Interix 3.5, BeOS.
But the replacement function is not safe to be used in libraries and is not
multithread-safe.
address@hidden
+Some systems mistakenly succeed on @code{unlinkat(fd,"file/",flag)}:
+Solaris 9.
@end itemize
Portability problems not fixed by Gnulib:
@itemize
address@hidden
+When @code{unlinkat(fd,name,AT_REMOVEDIR)} fails because the specified
+directory is not empty, the @code{errno} value is system dependent.
address@hidden
+POSIX requires that @code{unlinkdir(fd,"link-to-empty/",AT_REMOVEDIR)}
+remove @file{empty} and leave @file{link-to-empty} as a dangling
+symlink. This is counter-intuitive, so some systems fail with
address@hidden instead:
+glibc
address@hidden
+Some systems allow a superuser to unlink directories, even though this
+can cause file system corruption. The error given if a process is not
+permitted to unlink directories varies across implementations; it is
+not always the POSIX value of @code{EPERM}. Meanwhile, if a process
+has the ability to unlink directories, POSIX requires that
address@hidden(fd,"symlink-to-dir/",0)} remove @file{dir} and leave
address@hidden dangling; this behavior is counter-intuitive.
+The gnulib module unlinkdir can help determine whether code must be
+cautious of unlinking directories.
address@hidden
+Removing an open file is non-portable: On Unix this allows the programs that
+have the file already open to continue working with it; the file's storage
+is only freed when the no process has the file open any more. On Windows,
+the attempt to remove an open file fails.
@end itemize
diff --git a/lib/unistd.in.h b/lib/unistd.in.h
index 6fa2831..322593d 100644
--- a/lib/unistd.in.h
+++ b/lib/unistd.in.h
@@ -180,7 +180,11 @@ extern int unlink (char const *file);
#if @GNULIB_UNLINKAT@
-# if address@hidden@
+# if @REPLACE_UNLINKAT@
+# undef unlinkat
+# define unlinkat rpl_unlinkat
+# endif
+# if address@hidden@ || @REPLACE_UNLINKAT@
extern int unlinkat (int fd, char const *file, int flag);
# endif
#elif defined GNULIB_POSIXCHECK
diff --git a/lib/unlinkat.c b/lib/unlinkat.c
new file mode 100644
index 0000000..bf5d5b8
--- /dev/null
+++ b/lib/unlinkat.c
@@ -0,0 +1,77 @@
+/* Work around unlinkat bugs on Solaris 9.
+
+ Copyright (C) 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Eric Blake. */
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "openat.h"
+
+#undef unlinkat
+
+/* unlinkat without AT_REMOVEDIR does not honor trailing / on Solaris
+ 9. Solve it in a similar manner to unlink. */
+
+int
+rpl_unlinkat (int fd, char const *name, int flag)
+{
+ size_t len;
+ int result = 0;
+ /* rmdir behavior has no problems with trailing slash. */
+ if (flag & AT_REMOVEDIR)
+ return unlinkat (fd, name, flag);
+
+ len = strlen (name);
+ if (len && ISSLASH (name[len - 1]))
+ {
+ /* See the lengthy comment in unlink.c why we disobey the POSIX
+ rule of letting unlink("link-to-dir/") attempt to unlink a
+ directory. */
+ struct stat st;
+ result = lstatat (fd, name, &st);
+ if (result == 0)
+ {
+ /* Trailing NUL will overwrite the trailing slash. */
+ char *short_name = malloc (len);
+ if (!short_name)
+ {
+ errno = EPERM;
+ return -1;
+ }
+ memcpy (short_name, name, len);
+ while (len && ISSLASH (short_name[len - 1]))
+ short_name[--len] = '\0';
+ if (len && (lstatat (fd, short_name, &st) || S_ISLNK (st.st_mode)))
+ {
+ free (short_name);
+ errno = EPERM;
+ return -1;
+ }
+ free (short_name);
+ }
+ }
+ if (!result)
+ result = unlinkat (fd, name, flag);
+ return result;
+}
diff --git a/m4/openat.m4 b/m4/openat.m4
index 445e952..e02a11c 100644
--- a/m4/openat.m4
+++ b/m4/openat.m4
@@ -1,4 +1,4 @@
-# serial 21
+# serial 22
# See if we need to use our replacement for Solaris' openat et al functions.
dnl Copyright (C) 2004-2009 Free Software Foundation, Inc.
@@ -30,8 +30,12 @@ AC_DEFUN([gl_FUNC_OPENAT],
case $ac_cv_func_openat+$ac_cv_func_lstat_dereferences_slashed_symlink in
yes+yes) ;;
yes+*)
+ # Solaris 9 has *at functions, but uniformly mishandles trailing
+ # slash in all of them.
AC_LIBOBJ([fstatat])
REPLACE_FSTATAT=1
+ AC_LIBOBJ([unlinkat])
+ REPLACE_UNLINKAT=1
;;
*)
HAVE_OPENAT=0
diff --git a/m4/unistd_h.m4 b/m4/unistd_h.m4
index d21cafc..648fd86 100644
--- a/m4/unistd_h.m4
+++ b/m4/unistd_h.m4
@@ -1,4 +1,4 @@
-# unistd_h.m4 serial 28
+# unistd_h.m4 serial 29
dnl Copyright (C) 2006-2009 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
@@ -101,6 +101,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS],
REPLACE_LSEEK=0; AC_SUBST([REPLACE_LSEEK])
REPLACE_RMDIR=0; AC_SUBST([REPLACE_RMDIR])
REPLACE_UNLINK=0; AC_SUBST([REPLACE_UNLINK])
+ REPLACE_UNLINKAT=0; AC_SUBST([REPLACE_UNLINKAT])
REPLACE_WRITE=0; AC_SUBST([REPLACE_WRITE])
UNISTD_H_HAVE_WINSOCK2_H=0; AC_SUBST([UNISTD_H_HAVE_WINSOCK2_H])
UNISTD_H_HAVE_WINSOCK2_H_AND_USE_SOCKETS=0;
diff --git a/modules/openat b/modules/openat
index 27e5c50..8cb1345 100644
--- a/modules/openat
+++ b/modules/openat
@@ -11,6 +11,7 @@ lib/openat.c
lib/openat.h
lib/openat-priv.h
lib/openat-proc.c
+lib/unlinkat.c
m4/openat.m4
m4/mode_t.m4
@@ -33,6 +34,7 @@ save-cwd
stdbool
sys_stat
unistd
+unlink
configure.ac:
gl_FUNC_OPENAT
diff --git a/modules/unistd b/modules/unistd
index 336381a..6cc6cda 100644
--- a/modules/unistd
+++ b/modules/unistd
@@ -93,6 +93,7 @@ unistd.h: unistd.in.h
-e 's|@''REPLACE_LSEEK''@|$(REPLACE_LSEEK)|g' \
-e 's|@''REPLACE_RMDIR''@|$(REPLACE_RMDIR)|g' \
-e 's|@''REPLACE_UNLINK''@|$(REPLACE_UNLINK)|g' \
+ -e 's|@''REPLACE_UNLINKAT''@|$(REPLACE_UNLINKAT)|g' \
-e 's|@''REPLACE_WRITE''@|$(REPLACE_WRITE)|g' \
-e
's|@''UNISTD_H_HAVE_WINSOCK2_H''@|$(UNISTD_H_HAVE_WINSOCK2_H)|g' \
-e
's|@''UNISTD_H_HAVE_WINSOCK2_H_AND_USE_SOCKETS''@|$(UNISTD_H_HAVE_WINSOCK2_H_AND_USE_SOCKETS)|g'
\
--
1.6.5.rc1
>From 82bf7d1b42dc970e704f9347862594445f4a22dd Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Sat, 19 Sep 2009 05:50:30 -0600
Subject: [PATCH 11/12] openat: move fstatat and unlinkat into correct files
Code motion, should be no semantic changes.
* m4/openat.m4 (gl_FUNC_OPENAT): Adjust which files will be
compiled.
* lib/openat.c (fstatat, unlinkat): Move...
* lib/fstatat.c (fstatat): ...into correct files.
* lib/unlinkat.c (unlinkat): Likewise.
Signed-off-by: Eric Blake <address@hidden>
---
ChangeLog | 7 ++++++
lib/fstatat.c | 48 ++++++++++++++++++++++++++++++++++++++++++-
lib/openat.c | 62 --------------------------------------------------------
lib/unlinkat.c | 30 ++++++++++++++++++++++++++-
m4/openat.m4 | 4 +-
5 files changed, 85 insertions(+), 66 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index 99e0840..1b9f09a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,12 @@
2009-09-19 Eric Blake <address@hidden>
+ openat: move fstatat and unlinkat into correct files
+ * m4/openat.m4 (gl_FUNC_OPENAT): Adjust which files will be
+ compiled.
+ * lib/openat.c (fstatat, unlinkat): Move...
+ * lib/fstatat.c (fstatat): ...into correct files.
+ * lib/unlinkat.c (unlinkat): Likewise.
+
openat: fix unlinkat bugs on Solaris 9
* lib/unlinkat.c (unlinkat): New file.
* modules/openat (Depends-on): Add unlink.
diff --git a/lib/fstatat.c b/lib/fstatat.c
index 59d7422..1c6c2d3 100644
--- a/lib/fstatat.c
+++ b/lib/fstatat.c
@@ -25,7 +25,9 @@
#include <fcntl.h>
#include <string.h>
-#undef fstatat
+#if HAVE_FSTATAT
+
+# undef fstatat
/* fstatat should always follow symbolic links that end in /, but on
Solaris 9 it doesn't if AT_SYMLINK_NOFOLLOW is specified.
@@ -62,3 +64,47 @@ rpl_fstatat (int fd, char const *file, struct stat *st, int
flag)
}
return result;
}
+
+#else /* !HAVE_FSTATAT */
+
+/* On mingw, the gnulib <sys/stat.h> defines `stat' as a function-like
+ macro; but using it in AT_FUNC_F2 causes compilation failure
+ because the preprocessor sees a use of a macro that requires two
+ arguments but is only given one. Hence, we need an inline
+ forwarder to get past the preprocessor. */
+static inline int
+stat_func (char const *name, struct stat *st)
+{
+ return stat (name, st);
+}
+
+/* Likewise, if there is no native `lstat', then the gnulib
+ <sys/stat.h> defined it as stat, which also needs adjustment. */
+# if !HAVE_LSTAT
+# undef lstat
+# define lstat stat_func
+# endif
+
+/* Replacement for Solaris' function by the same name.
+ <http://www.google.com/search?q=fstatat+site:docs.sun.com>
+ First, try to simulate it via l?stat ("/proc/self/fd/FD/FILE").
+ Failing that, simulate it via save_cwd/fchdir/(stat|lstat)/restore_cwd.
+ If either the save_cwd or the restore_cwd fails (relatively unlikely),
+ then give a diagnostic and exit nonzero.
+ Otherwise, this function works just like Solaris' fstatat. */
+
+# define AT_FUNC_NAME fstatat
+# define AT_FUNC_F1 lstat
+# define AT_FUNC_F2 stat_func
+# define AT_FUNC_USE_F1_COND AT_SYMLINK_NOFOLLOW
+# define AT_FUNC_POST_FILE_PARAM_DECLS , struct stat *st, int flag
+# define AT_FUNC_POST_FILE_ARGS , st
+# include "at-func.c"
+# undef AT_FUNC_NAME
+# undef AT_FUNC_F1
+# undef AT_FUNC_F2
+# undef AT_FUNC_USE_F1_COND
+# undef AT_FUNC_POST_FILE_PARAM_DECLS
+# undef AT_FUNC_POST_FILE_ARGS
+
+#endif /* !HAVE_FSTATAT */
diff --git a/lib/openat.c b/lib/openat.c
index a0d0ab4..2a194e8 100644
--- a/lib/openat.c
+++ b/lib/openat.c
@@ -156,65 +156,3 @@ openat_needs_fchdir (void)
return needs_fchdir;
}
-
-/* On mingw, the gnulib <sys/stat.h> defines `stat' as a function-like
- macro; but using it in AT_FUNC_F2 causes compilation failure
- because the preprocessor sees a use of a macro that requires two
- arguments but is only given one. Hence, we need an inline
- forwarder to get past the preprocessor. */
-static inline int
-stat_func (char const *name, struct stat *st)
-{
- return stat (name, st);
-}
-
-/* Likewise, if there is no native `lstat', then the gnulib
- <sys/stat.h> defined it as stat, which also needs adjustment. */
-#if !HAVE_LSTAT
-# undef lstat
-# define lstat stat_func
-#endif
-
-/* Replacement for Solaris' function by the same name.
- <http://www.google.com/search?q=fstatat+site:docs.sun.com>
- First, try to simulate it via l?stat ("/proc/self/fd/FD/FILE").
- Failing that, simulate it via save_cwd/fchdir/(stat|lstat)/restore_cwd.
- If either the save_cwd or the restore_cwd fails (relatively unlikely),
- then give a diagnostic and exit nonzero.
- Otherwise, this function works just like Solaris' fstatat. */
-
-#define AT_FUNC_NAME fstatat
-#define AT_FUNC_F1 lstat
-#define AT_FUNC_F2 stat_func
-#define AT_FUNC_USE_F1_COND AT_SYMLINK_NOFOLLOW
-#define AT_FUNC_POST_FILE_PARAM_DECLS , struct stat *st, int flag
-#define AT_FUNC_POST_FILE_ARGS , st
-#include "at-func.c"
-#undef AT_FUNC_NAME
-#undef AT_FUNC_F1
-#undef AT_FUNC_F2
-#undef AT_FUNC_USE_F1_COND
-#undef AT_FUNC_POST_FILE_PARAM_DECLS
-#undef AT_FUNC_POST_FILE_ARGS
-
-/* Replacement for Solaris' function by the same name.
- <http://www.google.com/search?q=unlinkat+site:docs.sun.com>
- First, try to simulate it via (unlink|rmdir) ("/proc/self/fd/FD/FILE").
- Failing that, simulate it via save_cwd/fchdir/(unlink|rmdir)/restore_cwd.
- If either the save_cwd or the restore_cwd fails (relatively unlikely),
- then give a diagnostic and exit nonzero.
- Otherwise, this function works just like Solaris' unlinkat. */
-
-#define AT_FUNC_NAME unlinkat
-#define AT_FUNC_F1 rmdir
-#define AT_FUNC_F2 unlink
-#define AT_FUNC_USE_F1_COND AT_REMOVEDIR
-#define AT_FUNC_POST_FILE_PARAM_DECLS , int flag
-#define AT_FUNC_POST_FILE_ARGS /* empty */
-#include "at-func.c"
-#undef AT_FUNC_NAME
-#undef AT_FUNC_F1
-#undef AT_FUNC_F2
-#undef AT_FUNC_USE_F1_COND
-#undef AT_FUNC_POST_FILE_PARAM_DECLS
-#undef AT_FUNC_POST_FILE_ARGS
diff --git a/lib/unlinkat.c b/lib/unlinkat.c
index bf5d5b8..7302252 100644
--- a/lib/unlinkat.c
+++ b/lib/unlinkat.c
@@ -28,7 +28,9 @@
#include "openat.h"
-#undef unlinkat
+#if HAVE_UNLINKAT
+
+# undef unlinkat
/* unlinkat without AT_REMOVEDIR does not honor trailing / on Solaris
9. Solve it in a similar manner to unlink. */
@@ -75,3 +77,29 @@ rpl_unlinkat (int fd, char const *name, int flag)
result = unlinkat (fd, name, flag);
return result;
}
+
+#else /* !HAVE_UNLINKAT */
+
+/* Replacement for Solaris' function by the same name.
+ <http://www.google.com/search?q=unlinkat+site:docs.sun.com>
+ First, try to simulate it via (unlink|rmdir) ("/proc/self/fd/FD/FILE").
+ Failing that, simulate it via save_cwd/fchdir/(unlink|rmdir)/restore_cwd.
+ If either the save_cwd or the restore_cwd fails (relatively unlikely),
+ then give a diagnostic and exit nonzero.
+ Otherwise, this function works just like Solaris' unlinkat. */
+
+# define AT_FUNC_NAME unlinkat
+# define AT_FUNC_F1 rmdir
+# define AT_FUNC_F2 unlink
+# define AT_FUNC_USE_F1_COND AT_REMOVEDIR
+# define AT_FUNC_POST_FILE_PARAM_DECLS , int flag
+# define AT_FUNC_POST_FILE_ARGS /* empty */
+# include "at-func.c"
+# undef AT_FUNC_NAME
+# undef AT_FUNC_F1
+# undef AT_FUNC_F2
+# undef AT_FUNC_USE_F1_COND
+# undef AT_FUNC_POST_FILE_PARAM_DECLS
+# undef AT_FUNC_POST_FILE_ARGS
+
+#endif /* !HAVE_UNLINKAT */
diff --git a/m4/openat.m4 b/m4/openat.m4
index e02a11c..b824393 100644
--- a/m4/openat.m4
+++ b/m4/openat.m4
@@ -1,4 +1,4 @@
-# serial 22
+# serial 23
# See if we need to use our replacement for Solaris' openat et al functions.
dnl Copyright (C) 2004-2009 Free Software Foundation, Inc.
@@ -25,7 +25,7 @@ AC_DEFUN([gl_FUNC_OPENAT],
AC_LIBOBJ([openat-proc])
AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
AC_CHECK_FUNCS_ONCE([lchmod])
- AC_REPLACE_FUNCS([fchmodat mkdirat openat])
+ AC_REPLACE_FUNCS([fchmodat fstatat mkdirat openat unlinkat])
AC_REQUIRE([AC_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK])
case $ac_cv_func_openat+$ac_cv_func_lstat_dereferences_slashed_symlink in
yes+yes) ;;
--
1.6.5.rc1
>From 4c45e93c58de6532275c22a9153ecdfe516928ff Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Sat, 19 Sep 2009 11:16:58 -0600
Subject: [PATCH 12/12] openat: fix openat bugs on Solaris 9
openat(fd,"file/",O_RDONLY) mistakenly succeeded.
* lib/openat.c (rpl_openat): Work around Solaris 9 bug.
* m4/openat.m4 (gl_FUNC_OPENAT): Also replace openat on Solaris.
* modules/openat (Depends-on): Add open.
* m4/fcntl_h.m4 (gl_FCNTL_H_DEFAULTS): Provide new default.
* modules/fcntl-h (Makefile.am): Substitute it.
* lib/fcntl.in.h (openat): Declare replacement.
* doc/posix-functions/openat.texi (openat): Document this.
Signed-off-by: Eric Blake <address@hidden>
---
ChangeLog | 9 ++++
doc/posix-functions/openat.texi | 5 ++
lib/fcntl.in.h | 4 +-
lib/openat.c | 97 +++++++++++++++++++++++++++++++++++++++
m4/fcntl_h.m4 | 11 ++--
m4/openat.m4 | 4 +-
modules/fcntl-h | 1 +
modules/openat | 1 +
8 files changed, 125 insertions(+), 7 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index 1b9f09a..94b8465 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,14 @@
2009-09-19 Eric Blake <address@hidden>
+ openat: fix openat bugs on Solaris 9
+ * lib/openat.c (rpl_openat): Work around Solaris 9 bug.
+ * m4/openat.m4 (gl_FUNC_OPENAT): Also replace openat on Solaris.
+ * modules/openat (Depends-on): Add open.
+ * m4/fcntl_h.m4 (gl_FCNTL_H_DEFAULTS): Provide new default.
+ * modules/fcntl-h (Makefile.am): Substitute it.
+ * lib/fcntl.in.h (openat): Declare replacement.
+ * doc/posix-functions/openat.texi (openat): Document this.
+
openat: move fstatat and unlinkat into correct files
* m4/openat.m4 (gl_FUNC_OPENAT): Adjust which files will be
compiled.
diff --git a/doc/posix-functions/openat.texi b/doc/posix-functions/openat.texi
index 5a99e0c..2bfe611 100644
--- a/doc/posix-functions/openat.texi
+++ b/doc/posix-functions/openat.texi
@@ -13,6 +13,11 @@ openat
glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX
5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Cygwin 1.5.x, mingw, Interix 3.5, BeOS.
But the replacement function is not safe to be used in libraries and is not
multithread-safe.
address@hidden
+This function does not fail when the file name argument ends in a slash
+and (without the slash) names a nonexistent file or a file that is not a
+directory, on some platforms:
+Solaris 9.
@end itemize
Portability problems not fixed by Gnulib:
diff --git a/lib/fcntl.in.h b/lib/fcntl.in.h
index cadb6a1..0ae8213 100644
--- a/lib/fcntl.in.h
+++ b/lib/fcntl.in.h
@@ -62,9 +62,11 @@ extern int open (const char *filename, int flags, ...);
#endif
#if @GNULIB_OPENAT@
-# if address@hidden@
+# if @REPLACE_OPENAT@
# undef openat
# define openat rpl_openat
+# endif
+# if address@hidden@ || @REPLACE_OPENAT@
int openat (int fd, char const *file, int flags, /* mode_t mode */ ...);
# endif
#elif defined GNULIB_POSIXCHECK
diff --git a/lib/openat.c b/lib/openat.c
index 2a194e8..7e46a26 100644
--- a/lib/openat.c
+++ b/lib/openat.c
@@ -22,12 +22,107 @@
#include <stdarg.h>
#include <stddef.h>
+#include <string.h>
#include <sys/stat.h>
#include "dirname.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */
#include "openat-priv.h"
#include "save-cwd.h"
+#if HAVE_OPENAT
+
+# undef openat
+
+/* Like openat, but work around Solaris 9 bugs with trailing slash. */
+int
+rpl_openat (int dfd, char const *filename, int flags, ...)
+{
+ mode_t mode;
+ int fd;
+
+ mode = 0;
+ if (flags & O_CREAT)
+ {
+ va_list arg;
+ va_start (arg, flags);
+
+ /* We have to use PROMOTED_MODE_T instead of mode_t, otherwise GCC 4
+ creates crashing code when 'mode_t' is smaller than 'int'. */
+ mode = va_arg (arg, PROMOTED_MODE_T);
+
+ va_end (arg);
+ }
+
+#if OPEN_TRAILING_SLASH_BUG
+ /* If the filename ends in a slash and one of O_CREAT, O_WRONLY, O_RDWR
+ is specified, then fail.
+ Rationale: POSIX <http://www.opengroup.org/susv3/basedefs/xbd_chap04.html>
+ says that
+ "A pathname that contains at least one non-slash character and that
+ ends with one or more trailing slashes shall be resolved as if a
+ single dot character ( '.' ) were appended to the pathname."
+ and
+ "The special filename dot shall refer to the directory specified by
+ its predecessor."
+ If the named file already exists as a directory, then
+ - if O_CREAT is specified, open() must fail because of the semantics
+ of O_CREAT,
+ - if O_WRONLY or O_RDWR is specified, open() must fail because POSIX
+ <http://www.opengroup.org/susv3/functions/open.html> says that it
+ fails with errno = EISDIR in this case.
+ If the named file does not exist or does not name a directory, then
+ - if O_CREAT is specified, open() must fail since open() cannot create
+ directories,
+ - if O_WRONLY or O_RDWR is specified, open() must fail because the
+ file does not contain a '.' directory. */
+ if (flags & (O_CREAT | O_WRONLY | O_RDWR))
+ {
+ size_t len = strlen (filename);
+ if (len > 0 && filename[len - 1] == '/')
+ {
+ errno = EISDIR;
+ return -1;
+ }
+ }
+#endif
+
+ fd = openat (dfd, filename, flags, mode);
+
+#if OPEN_TRAILING_SLASH_BUG
+ /* If the filename ends in a slash and fd does not refer to a directory,
+ then fail.
+ Rationale: POSIX <http://www.opengroup.org/susv3/basedefs/xbd_chap04.html>
+ says that
+ "A pathname that contains at least one non-slash character and that
+ ends with one or more trailing slashes shall be resolved as if a
+ single dot character ( '.' ) were appended to the pathname."
+ and
+ "The special filename dot shall refer to the directory specified by
+ its predecessor."
+ If the named file without the slash is not a directory, open() must fail
+ with ENOTDIR. */
+ if (fd >= 0)
+ {
+ size_t len = strlen (filename);
+ if (len > 0 && filename[len - 1] == '/')
+ {
+ struct stat statbuf;
+
+ if (fstat (fd, &statbuf) >= 0 && !S_ISDIR (statbuf.st_mode))
+ {
+ close (fd);
+ errno = ENOTDIR;
+ return -1;
+ }
+ }
+ }
+#endif
+
+ return fd;
+}
+
+#else /* !HAVE_OPENAT */
+
/* Replacement for Solaris' openat function.
<http://www.google.com/search?q=openat+site:docs.sun.com>
First, try to simulate it via open ("/proc/self/fd/FD/FILE").
@@ -156,3 +251,5 @@ openat_needs_fchdir (void)
return needs_fchdir;
}
+
+#endif /* !HAVE_OPENAT */
diff --git a/m4/fcntl_h.m4 b/m4/fcntl_h.m4
index 7100d19..5eed088 100644
--- a/m4/fcntl_h.m4
+++ b/m4/fcntl_h.m4
@@ -1,4 +1,4 @@
-# serial 3
+# serial 4
# Configure fcntl.h.
dnl Copyright (C) 2006, 2007, 2009 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
@@ -91,9 +91,10 @@ AC_DEFUN([gl_FCNTL_MODULE_INDICATOR],
AC_DEFUN([gl_FCNTL_H_DEFAULTS],
[
- GNULIB_OPEN=0; AC_SUBST([GNULIB_OPEN])
- GNULIB_OPENAT=0; AC_SUBST([GNULIB_OPENAT])
+ GNULIB_OPEN=0; AC_SUBST([GNULIB_OPEN])
+ GNULIB_OPENAT=0; AC_SUBST([GNULIB_OPENAT])
dnl Assume proper GNU behavior unless another module says otherwise.
- HAVE_OPENAT=1; AC_SUBST([HAVE_OPENAT])
- REPLACE_OPEN=0; AC_SUBST([REPLACE_OPEN])
+ HAVE_OPENAT=1; AC_SUBST([HAVE_OPENAT])
+ REPLACE_OPEN=0; AC_SUBST([REPLACE_OPEN])
+ REPLACE_OPENAT=0; AC_SUBST([REPLACE_OPENAT])
])
diff --git a/m4/openat.m4 b/m4/openat.m4
index b824393..42df3ee 100644
--- a/m4/openat.m4
+++ b/m4/openat.m4
@@ -1,4 +1,4 @@
-# serial 23
+# serial 24
# See if we need to use our replacement for Solaris' openat et al functions.
dnl Copyright (C) 2004-2009 Free Software Foundation, Inc.
@@ -32,6 +32,8 @@ AC_DEFUN([gl_FUNC_OPENAT],
yes+*)
# Solaris 9 has *at functions, but uniformly mishandles trailing
# slash in all of them.
+ AC_LIBOBJ([openat])
+ REPLACE_OPENAT=1
AC_LIBOBJ([fstatat])
REPLACE_FSTATAT=1
AC_LIBOBJ([unlinkat])
diff --git a/modules/fcntl-h b/modules/fcntl-h
index 2b811d1..ea76181 100644
--- a/modules/fcntl-h
+++ b/modules/fcntl-h
@@ -28,6 +28,7 @@ fcntl.h: fcntl.in.h
-e 's|@''GNULIB_OPEN''@|$(GNULIB_OPEN)|g' \
-e 's|@''GNULIB_OPENAT''@|$(GNULIB_OPENAT)|g' \
-e 's|@''REPLACE_OPEN''@|$(REPLACE_OPEN)|g' \
+ -e 's|@''REPLACE_OPENAT''@|$(REPLACE_OPENAT)|g' \
-e 's|@''HAVE_OPENAT''@|$(HAVE_OPENAT)|g' \
-e '/definition of GL_LINK_WARNING/r $(LINK_WARNING_H)' \
< $(srcdir)/fcntl.in.h; \
diff --git a/modules/openat b/modules/openat
index 8cb1345..150853f 100644
--- a/modules/openat
+++ b/modules/openat
@@ -27,6 +27,7 @@ inline
intprops
lchown
lstat
+open
openat-die
rmdir
same-inode
--
1.6.5.rc1