bug-gnulib
[Top][All Lists]
Advanced

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

symlink/readlink and trailing slash


From: Eric Blake
Subject: symlink/readlink and trailing slash
Date: Mon, 21 Sep 2009 20:53:06 +0000 (UTC)
User-agent: Loom/3.14 (http://gmane.org/)

That Solaris 9 bug of ignoring trailing slash is pervasive.  I'm looking at 
committing this series next, once I run it through tests on more machines.

Eric Blake (6):
      [1/7] unistd: sort replacement declarations
Similar to prior cleanups, makes adding symlink easier

      [2/7] symlink: new module, for Solaris 9 bug
symlink("a","b/") mistakenly creates "b" on Solaris 9.  Plus, this allows calls 
to symlink to at least compile on mingw (although it doesn't magically work 
around the fact that the OS flat out doesn't have symlinks ;)

      [3/7] symlink: use throughout gnulib
symlink was mostly used in tests, which can be simplified by assuming symlink 
exists.  symlinkat continues to provide a direct ENOSYS failure on mingw, 
rather than wasting time going through save_cwd/fchdir/fail/restore_cwd.

      [4/7] readlink: use correct return type
readlink.c predates our unistd.in.h replacement header, which now guarantees 
ssize_t exists.  I don't know if this will have any impact on progreloc.c, 
since canonicalize-lgpl uses readlink (so we may need to add a declaration of 
readlink if !HAVE_READLINK in a couple of places).

      [5/7] readlink: fix Solaris 9 bug
readlink("link-to-dir/",buf,len) mistakenly read the contents of link-to-dir, 
rather than failing with EINVAL because the resolved directory dir/ is not a 
symlink.

      [6/7] test-symlinkat: enhance test
Solaris 9 lacks symlinkat and readlinkat (so no *at wrappers needed to work 
around these particular trailing slash bugs).  But enhancing the testsuite to 
make sure of this is always a good idea.

      [7/7] readlink: document portability issue with symlink length
Documentation improvement.  Hmm.  I guess this means test-readlink and test-
symlinkat will now fail on AIX and HP-UX (I have access to neither, so I'm just 
going off the comment in areadlink.c rather than a confirmed failure), because 
those platforms fail with ERANGE rather than truncate when the requested length 
is too small for the symlink contents.  Should we make readlink have GNU 
behavior on these platforms (returning truncated string instead of ERANGE 
failure, and making areadlink/areadlink-with-size simpler as a result), or 
should I go ahead and relax the testsuite to not expect success with a too-
small buffer?

>From 424cb13d06a8236ba10f9538b0f00734340ee00f Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Sat, 19 Sep 2009 20:37:50 -0600
Subject: [PATCH 1/7] unistd: sort replacement declarations

* lib/unistd.in.h: Sort declarations.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog       |    3 +
 lib/unistd.in.h |  177 ++++++++++++++++++++++++++++---------------------------
 2 files changed, 94 insertions(+), 86 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 7c6f358..9c0ce38 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,8 @@
 2009-09-21  Eric Blake  <address@hidden>

+       unistd: sort replacement declarations
+       * lib/unistd.in.h: Sort declarations.
+
        fts: avoid compiler warning
        * lib/fts.c (dirent_inode_sort_may_be_useful)
        (leaf_optimization_applies) [!__linux__]: Mark unused parameters.
diff --git a/lib/unistd.in.h b/lib/unistd.in.h
index 322593d..12b791b 100644
--- a/lib/unistd.in.h
+++ b/lib/unistd.in.h
@@ -106,7 +106,7 @@
 # define STDERR_FILENO 2
 #endif

-/* Ensure *_OK functions exist.  */
+/* Ensure *_OK macros exist.  */
 #ifndef F_OK
 # define F_OK 0
 # define X_OK 1
@@ -147,91 +147,6 @@ extern int chown (const char *file, uid_t uid, gid_t gid);
 #endif


-#if @GNULIB_FCHOWNAT@
-# if @REPLACE_FCHOWNAT@
-#  undef fchownat
-#  define fchownat rpl_fchownat
-# endif
-# if address@hidden@ || @REPLACE_FCHOWNAT@
-extern int fchownat (int fd, char const *file, uid_t owner, gid_t group, int 
flag);
-# endif
-#elif defined GNULIB_POSIXCHECK
-# undef fchownat
-# define fchownat(d,n,o,g,f)                       \
-    (GL_LINK_WARNING ("fchownat is not portable - " \
-                      "use gnulib module openat for portability"), \
-     fchownat (d, n, o, g, f))
-#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 @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
-# undef unlinkat
-# define unlinkat(d,n,f)                         \
-    (GL_LINK_WARNING ("unlinkat is not portable - " \
-                      "use gnulib module openat for portability"), \
-     unlinkat (d, n, f))
-#endif
-
-
-#if @GNULIB_FACCESSAT@
-# if address@hidden@
-int faccessat (int fd, char const *file, int mode, int flag);
-# endif
-#elif defined GNULIB_POSIXCHECK
-# undef faccessat
-# define faccessat(d,n,m,f)                        \
-    (GL_LINK_WARNING ("faccessat is not portable - " \
-                      "use gnulib module faccessat for portability"), \
-     faccessat (d, n, m, f))
-#endif
-
-#if @GNULIB_SYMLINKAT@
-# if address@hidden@
-int symlinkat (char const *contents, int fd, char const *file);
-# endif
-#elif defined GNULIB_POSIXCHECK
-# undef symlinkat
-# define symlinkat(c,d,n)                           \
-    (GL_LINK_WARNING ("symlinkat is not portable - " \
-                      "use gnulib module symlinkat for portability"), \
-     symlinkat (c, d, n))
-#endif
-
-#if @GNULIB_READLINKAT@
-# if address@hidden@
-ssize_t readlinkat (int fd, char const *file, char *buf, size_t len);
-# endif
-#elif defined GNULIB_POSIXCHECK
-# undef readlinkat
-# define readlinkat(d,n,b,l)                        \
-    (GL_LINK_WARNING ("readlinkat is not portable - " \
-                      "use gnulib module symlinkat for portability"), \
-     readlinkat (d, n, b, l))
-#endif
-
 #if @GNULIB_CLOSE@
 # if @REPLACE_CLOSE@
 /* Automatically included by modules that need a replacement for close.  */
@@ -250,11 +165,13 @@ extern int close (int);
      close (f))
 #endif

+
 #if @REPLACE_DUP@
 # define dup rpl_dup
 extern int dup (int);
 #endif

+
 #if @GNULIB_DUP2@
 # if @REPLACE_DUP2@
 #  define dup2 rpl_dup2
@@ -333,6 +250,19 @@ extern int euidaccess (const char *filename, int mode);
 #endif


+#if @GNULIB_FACCESSAT@
+# if address@hidden@
+int faccessat (int fd, char const *file, int mode, int flag);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef faccessat
+# define faccessat(d,n,m,f)                        \
+    (GL_LINK_WARNING ("faccessat is not portable - " \
+                      "use gnulib module faccessat for portability"), \
+     faccessat (d, n, m, f))
+#endif
+
+
 #if @GNULIB_FCHDIR@
 # if @REPLACE_FCHDIR@
 /* Change the process' current working directory to the directory on which
@@ -358,6 +288,23 @@ extern const char *_gl_directory_name (int fd);
 #endif


+#if @GNULIB_FCHOWNAT@
+# if @REPLACE_FCHOWNAT@
+#  undef fchownat
+#  define fchownat rpl_fchownat
+# endif
+# if address@hidden@ || @REPLACE_FCHOWNAT@
+extern int fchownat (int fd, char const *file, uid_t owner, gid_t group, int 
flag);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef fchownat
+# define fchownat(d,n,o,g,f)                       \
+    (GL_LINK_WARNING ("fchownat is not portable - " \
+                      "use gnulib module openat for portability"), \
+     fchownat (d, n, o, g, f))
+#endif
+
+
 #if @GNULIB_FSYNC@
 /* Synchronize changes to a file.
    Return 0 if successful, otherwise -1 and errno set.
@@ -691,6 +638,19 @@ extern int readlink (const char *file, char *buf, size_t 
bufsize);
 #endif


+#if @GNULIB_READLINKAT@
+# if address@hidden@
+ssize_t readlinkat (int fd, char const *file, char *buf, size_t len);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef readlinkat
+# define readlinkat(d,n,b,l)                        \
+    (GL_LINK_WARNING ("readlinkat is not portable - " \
+                      "use gnulib module symlinkat for portability"), \
+     readlinkat (d, n, b, l))
+#endif
+
+
 #if @GNULIB_RMDIR@
 # if @REPLACE_RMDIR@
 #  define rmdir rpl_rmdir
@@ -723,6 +683,51 @@ extern unsigned int sleep (unsigned int n);
 #endif


+#if @GNULIB_SYMLINKAT@
+# if address@hidden@
+int symlinkat (char const *contents, int fd, char const *file);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef symlinkat
+# define symlinkat(c,d,n)                           \
+    (GL_LINK_WARNING ("symlinkat is not portable - " \
+                      "use gnulib module symlinkat for portability"), \
+     symlinkat (c, d, n))
+#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 @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
+# undef unlinkat
+# define unlinkat(d,n,f)                         \
+    (GL_LINK_WARNING ("unlinkat is not portable - " \
+                      "use gnulib module openat for portability"), \
+     unlinkat (d, n, f))
+#endif
+
+
 #if @GNULIB_WRITE@ && @REPLACE_WRITE@ && @GNULIB_UNISTD_H_SIGPIPE@
 /* Write up to COUNT bytes starting at BUF to file descriptor FD.
    See the POSIX:2001 specification
-- 
1.6.4.2


>From 168b103c0d345373709ea34ba1a1d33f3e805424 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Sat, 19 Sep 2009 21:20:42 -0600
Subject: [PATCH 2/7] symlink: new module, for Solaris 9 bug

symlink("a","link/") mistakenly succeeds.

* modules/symlink: New file.
* m4/symlink.m4 (gl_FUNC_SYMLINK): Likewise.
* lib/symlink.c: Likewise.
* m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add defaults.
* modules/unistd (Makefile.am): Substitute them.
* lib/unistd.in.h (symlink): Declare replacement.
* MODULES.html.sh (File system functions): Mention it.
* doc/posix-functions/symlink.texi (symlink): Likewise.
* modules/symlink-tests: New test.
* tests/test-symlink.c: Likewise.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                        |   12 +++++
 MODULES.html.sh                  |    1 +
 doc/posix-functions/symlink.texi |   13 ++++-
 lib/symlink.c                    |   57 ++++++++++++++++++++++
 lib/unistd.in.h                  |   17 +++++++
 m4/symlink.m4                    |   35 ++++++++++++++
 m4/unistd_h.m4                   |    3 +
 modules/symlink                  |   25 ++++++++++
 modules/symlink-tests            |   10 ++++
 modules/unistd                   |    3 +
 tests/test-symlink.c             |   97 ++++++++++++++++++++++++++++++++++++++
 11 files changed, 270 insertions(+), 3 deletions(-)
 create mode 100644 lib/symlink.c
 create mode 100644 m4/symlink.m4
 create mode 100644 modules/symlink
 create mode 100644 modules/symlink-tests
 create mode 100644 tests/test-symlink.c

diff --git a/ChangeLog b/ChangeLog
index 9c0ce38..30a12ef 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,17 @@
 2009-09-21  Eric Blake  <address@hidden>

+       symlink: new module, for Solaris 9 bug
+       * modules/symlink: New file.
+       * m4/symlink.m4 (gl_FUNC_SYMLINK): Likewise.
+       * lib/symlink.c: Likewise.
+       * m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add defaults.
+       * modules/unistd (Makefile.am): Substitute them.
+       * lib/unistd.in.h (symlink): Declare replacement.
+       * MODULES.html.sh (File system functions): Mention it.
+       * doc/posix-functions/symlink.texi (symlink): Likewise.
+       * modules/symlink-tests: New test.
+       * tests/test-symlink.c: Likewise.
+
        unistd: sort replacement declarations
        * lib/unistd.in.h: Sort declarations.

diff --git a/MODULES.html.sh b/MODULES.html.sh
index 9104d54..ad00ac8 100755
--- a/MODULES.html.sh
+++ b/MODULES.html.sh
@@ -2482,6 +2482,7 @@ func_all_modules ()
   func_module savewd
   func_module stat-macros
   func_module stat-time
+  func_module symlink
   func_module symlinkat
   func_module tmpdir
   func_module unlinkdir
diff --git a/doc/posix-functions/symlink.texi b/doc/posix-functions/symlink.texi
index e947d2f..de7c0aa 100644
--- a/doc/posix-functions/symlink.texi
+++ b/doc/posix-functions/symlink.texi
@@ -4,15 +4,22 @@ symlink

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

-Gnulib module: ---
+Gnulib module: symlink

 Portability problems fixed by Gnulib:
 @itemize
address@hidden
+On some systems, @code{symlink(value,"name/")} mistakenly creates a
+symlink:
+Solaris 9
address@hidden
+This function is missing on some platforms; however, the replacement
+always fails with @code{EPERM}:
+mingw.
 @end itemize

 Portability problems not fixed by Gnulib:
 @itemize
 @item
-This function is missing on some platforms:
-mingw.
+Some file systems do not support symbolic links.
 @end itemize
diff --git a/lib/symlink.c b/lib/symlink.c
new file mode 100644
index 0000000..9c81884
--- /dev/null
+++ b/lib/symlink.c
@@ -0,0 +1,57 @@
+/* Stub for symlink().
+   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>
+
+/* Specification.  */
+#include <unistd.h>
+
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+
+
+#if HAVE_SYMLINK
+
+# undef symlink
+
+/* Create a symlink, but reject trailing slash.  */
+int
+rpl_symlink (char const *contents, char const *name)
+{
+  size_t len = strlen (name);
+  if (name[len - 1] == '/')
+    {
+      struct stat st;
+      if (lstat (name, &st) == 0)
+       errno = EEXIST;
+      return -1;
+    }
+  return symlink (contents, name);
+}
+
+#else /* !HAVE_SYMLINK */
+
+/* The system does not support symlinks.  */
+int
+symlink (char const *contents _UNUSED_PARAMETER_,
+        char const *name _UNUSED_PARAMETER_)
+{
+  errno = ENOSYS;
+  return -1;
+}
+
+#endif /* !HAVE_SYMLINK */
diff --git a/lib/unistd.in.h b/lib/unistd.in.h
index 12b791b..76d987e 100644
--- a/lib/unistd.in.h
+++ b/lib/unistd.in.h
@@ -683,6 +683,23 @@ extern unsigned int sleep (unsigned int n);
 #endif


+#if @GNULIB_SYMLINK@
+# if @REPLACE_SYMLINK@
+#  undef symlink
+#  define symlink rpl_symlink
+# endif
+# if address@hidden@ || @REPLACE_SYMLINK@
+int symlink (char const *contents, char const *file);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef symlink
+# define symlink(c,n)                       \
+    (GL_LINK_WARNING ("symlink is not portable - " \
+                      "use gnulib module symlink for portability"), \
+     symlink (c, n))
+#endif
+
+
 #if @GNULIB_SYMLINKAT@
 # if address@hidden@
 int symlinkat (char const *contents, int fd, char const *file);
diff --git a/m4/symlink.m4 b/m4/symlink.m4
new file mode 100644
index 0000000..abea045
--- /dev/null
+++ b/m4/symlink.m4
@@ -0,0 +1,35 @@
+# serial 1
+# See if we need to provide symlink replacement.
+
+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.
+
+# Written by Eric Blake.
+
+AC_DEFUN([gl_FUNC_SYMLINK],
+[
+  AC_REQUIRE([gl_UNISTD_H_DEFAULTS])
+  AC_CHECK_FUNCS_ONCE([symlink])
+  dnl The best we can do on mingw is provide a dummy that always fails, so
+  dnl that compilation can proceed with fewer ifdefs.  On Solaris 9, we
+  dnl want to fix a bug with trailing slash handling.
+  if test $ac_cv_func_symlink = no; then
+    HAVE_SYMLINK=0
+    AC_LIBOBJ([symlink])
+  else
+    AC_CACHE_CHECK([whether symlink handles trailing slash correctly],
+      [gl_cv_func_symlink_works],
+      [AC_RUN_IFELSE(
+         [AC_LANG_PROGRAM(
+           [[#include <unistd.h>
+]], [[return !symlink ("a", "conftest.link/");]])],
+         [gl_cv_func_symlink_works=yes], [gl_cv_func_symlink_works=no],
+         [gl_cv_func_symlink_works="guessing no"])])
+    if test "$gl_cv_func_symlink_works" != yes; then
+      REPLACE_SYMLINK=1
+      AC_LIBOBJ([symlink])
+    fi
+  fi
+])
diff --git a/m4/unistd_h.m4 b/m4/unistd_h.m4
index 648fd86..0a263d2 100644
--- a/m4/unistd_h.m4
+++ b/m4/unistd_h.m4
@@ -58,6 +58,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS],
   GNULIB_READLINKAT=0;       AC_SUBST([GNULIB_READLINKAT])
   GNULIB_RMDIR=0;            AC_SUBST([GNULIB_RMDIR])
   GNULIB_SLEEP=0;            AC_SUBST([GNULIB_SLEEP])
+  GNULIB_SYMLINK=0;          AC_SUBST([GNULIB_SYMLINK])
   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])
@@ -82,6 +83,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS],
   HAVE_READLINK=1;        AC_SUBST([HAVE_READLINK])
   HAVE_READLINKAT=1;      AC_SUBST([HAVE_READLINKAT])
   HAVE_SLEEP=1;           AC_SUBST([HAVE_SLEEP])
+  HAVE_SYMLINK=1;         AC_SUBST([HAVE_SYMLINK])
   HAVE_SYMLINKAT=1;       AC_SUBST([HAVE_SYMLINKAT])
   HAVE_DECL_ENVIRON=1;    AC_SUBST([HAVE_DECL_ENVIRON])
   HAVE_DECL_GETLOGIN_R=1; AC_SUBST([HAVE_DECL_GETLOGIN_R])
@@ -100,6 +102,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_SYMLINK=0;      AC_SUBST([REPLACE_SYMLINK])
   REPLACE_UNLINK=0;       AC_SUBST([REPLACE_UNLINK])
   REPLACE_UNLINKAT=0;     AC_SUBST([REPLACE_UNLINKAT])
   REPLACE_WRITE=0;        AC_SUBST([REPLACE_WRITE])
diff --git a/modules/symlink b/modules/symlink
new file mode 100644
index 0000000..4276036
--- /dev/null
+++ b/modules/symlink
@@ -0,0 +1,25 @@
+Description:
+symlink(): create a symlink, if possible
+
+Files:
+lib/symlink.c
+m4/symlink.m4
+
+Depends-on:
+lstat
+unistd
+
+configure.ac:
+gl_FUNC_SYMLINK
+gl_UNISTD_MODULE_INDICATOR([symlink])
+
+Makefile.am:
+
+Include:
+<unistd.h>
+
+License:
+LGPL
+
+Maintainer:
+Eric Blake
diff --git a/modules/symlink-tests b/modules/symlink-tests
new file mode 100644
index 0000000..aa67405
--- /dev/null
+++ b/modules/symlink-tests
@@ -0,0 +1,10 @@
+Files:
+tests/test-symlink.c
+
+Depends-on:
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-symlink
+check_PROGRAMS += test-symlink
diff --git a/modules/unistd b/modules/unistd
index 6cc6cda..d2568b2 100644
--- a/modules/unistd
+++ b/modules/unistd
@@ -51,6 +51,7 @@ unistd.h: unistd.in.h
              -e 's|@''GNULIB_READLINKAT''@|$(GNULIB_READLINKAT)|g' \
              -e 's|@''GNULIB_RMDIR''@|$(GNULIB_RMDIR)|g' \
              -e 's|@''GNULIB_SLEEP''@|$(GNULIB_SLEEP)|g' \
+             -e 's|@''GNULIB_SYMLINK''@|$(GNULIB_SYMLINK)|g' \
              -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' 
\
@@ -74,6 +75,7 @@ unistd.h: unistd.in.h
              -e 's|@''HAVE_READLINK''@|$(HAVE_READLINK)|g' \
              -e 's|@''HAVE_READLINKAT''@|$(HAVE_READLINKAT)|g' \
              -e 's|@''HAVE_SLEEP''@|$(HAVE_SLEEP)|g' \
+             -e 's|@''HAVE_SYMLINK''@|$(HAVE_SYMLINK)|g' \
              -e 's|@''HAVE_SYMLINKAT''@|$(HAVE_SYMLINKAT)|g' \
              -e 's|@''HAVE_UNLINKAT''@|$(HAVE_UNLINKAT)|g' \
              -e 's|@''HAVE_DECL_ENVIRON''@|$(HAVE_DECL_ENVIRON)|g' \
@@ -92,6 +94,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_SYMLINK''@|$(REPLACE_SYMLINK)|g' \
              -e 's|@''REPLACE_UNLINK''@|$(REPLACE_UNLINK)|g' \
              -e 's|@''REPLACE_UNLINKAT''@|$(REPLACE_UNLINKAT)|g' \
              -e 's|@''REPLACE_WRITE''@|$(REPLACE_WRITE)|g' \
diff --git a/tests/test-symlink.c b/tests/test-symlink.c
new file mode 100644
index 0000000..9f9dda1
--- /dev/null
+++ b/tests/test-symlink.c
@@ -0,0 +1,97 @@
+/* Tests of symlink.
+   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 <sys/stat.h>
+
+#define ASSERT(expr) \
+  do                                                                         \
+    {                                                                        \
+      if (!(expr))                                                           \
+       {                                                                    \
+         fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__);  \
+         fflush (stderr);                                                   \
+         abort ();                                                          \
+       }                                                                    \
+    }                                                                        \
+  while (0)
+
+#define BASE "test-symlink.t"
+
+int
+main ()
+{
+  /* Remove any leftovers from a previous partial run.  */
+  ASSERT (system ("rm -rf " BASE "*") == 0);
+
+  if (symlink ("nowhere", BASE "link1"))
+    {
+      fputs ("skipping test: symlinks not supported on this filesystem\n",
+            stderr);
+      return 77;
+    }
+
+  /* Some systems allow the creation of 0-length symlinks as a synonym
+     for "."; but most reject it.  */
+  errno = 0;
+  if (symlink ("", BASE "link2") == -1)
+    ASSERT (errno == ENOENT || errno == EINVAL);
+  else
+    ASSERT (unlink (BASE "link2") == 0);
+
+  /* Sanity checks of failures.  */
+  errno = 0;
+  ASSERT (symlink ("nowhere", "") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (symlink ("nowhere", ".") == -1);
+  ASSERT (errno == EEXIST || errno == EINVAL);
+  errno = 0;
+  ASSERT (symlink ("somewhere", BASE "link1") == -1);
+  ASSERT (errno == EEXIST);
+  errno = 0;
+  ASSERT (symlink ("nowhere", BASE "link2/") == -1);
+  ASSERT (errno == ENOTDIR || errno == ENOENT);
+  ASSERT (mkdir (BASE "dir", 0700) == 0);
+  errno = 0;
+  ASSERT (symlink ("nowhere", BASE "dir") == -1);
+  ASSERT (errno == EEXIST);
+  errno = 0;
+  ASSERT (symlink ("nowhere", BASE "dir/") == -1);
+  ASSERT (errno == EEXIST);
+  ASSERT (close (creat (BASE "file", 0600)) == 0);
+  errno = 0;
+  ASSERT (symlink ("nowhere", BASE "file") == -1);
+  ASSERT (errno == EEXIST);
+  errno = 0;
+  ASSERT (symlink ("nowhere", BASE "file/") == -1);
+  ASSERT (errno == EEXIST || errno == ENOTDIR);
+
+  ASSERT (rmdir (BASE "dir") == 0);
+  ASSERT (unlink (BASE "file") == 0);
+  ASSERT (unlink (BASE "link1") == 0);
+
+  return 0;
+}
-- 
1.6.4.2


>From 60356950f9fa783107b20024f0649b0ef628e1fa Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Sat, 19 Sep 2009 21:58:11 -0600
Subject: [PATCH 3/7] symlink: use throughout gnulib

* m4/symlinkat.m4 (gl_FUNC_SYMLINKAT): Omit symlink check.
* lib/symlinkat.c (symlinkat) [!HAVE_SYMLINK]: Document why
symlink is not used.
* modules/symlinkat (Depends-on): Add symlink.
* modules/canonicalize-lgpl-tests (Depends-on): Likewise.
* modules/canonicalize-tests (Depends-on): Likewise.
* modules/lstat-tests (Depends-on): Likewise.
* modules/openat-tests (Depends-on): Likewise.
* modules/remove-tests (Depends-on): Likewise.
* modules/rmdir-tests (Depends-on): Likewise.
* modules/unlink-tests (Depends-on): Likewise.
* tests/test-canonicalize-lgpl.c (symlink): Delete stub.
* tests/test-canonicalize.c (symlink): Likewise.
* tests/test-fstatat.c (symlink): Likewise.
* tests/test-lstat.c (symlink): Likewise.
* tests/test-remove.c (symlink): Likewise.
* tests/test-rmdir.c (symlink): Likewise.
* tests/test-unlink.c (symlink): Likewise.
* tests/test-unlinkat.c (symlink): Likewise.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                       |   21 +++++++++++++++++++++
 lib/symlinkat.c                 |    3 ++-
 m4/symlinkat.m4                 |    4 ++--
 modules/canonicalize-lgpl-tests |    2 +-
 modules/canonicalize-tests      |    2 +-
 modules/lstat-tests             |    2 +-
 modules/openat-tests            |    2 +-
 modules/remove-tests            |    2 +-
 modules/rmdir-tests             |    2 +-
 modules/symlinkat               |    1 +
 modules/unlink-tests            |    2 +-
 tests/test-canonicalize-lgpl.c  |    4 ----
 tests/test-canonicalize.c       |    4 ----
 tests/test-fstatat.c            |    4 ----
 tests/test-lstat.c              |    4 ----
 tests/test-remove.c             |    4 ----
 tests/test-rmdir.c              |    4 ----
 tests/test-unlink.c             |    4 ----
 tests/test-unlinkat.c           |    4 ----
 19 files changed, 33 insertions(+), 42 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 30a12ef..8390e19 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,26 @@
 2009-09-21  Eric Blake  <address@hidden>

+       symlink: use throughout gnulib
+       * m4/symlinkat.m4 (gl_FUNC_SYMLINKAT): Omit symlink check.
+       * lib/symlinkat.c (symlinkat) [!HAVE_SYMLINK]: Document why
+       symlink is not used.
+       * modules/symlinkat (Depends-on): Add symlink.
+       * modules/canonicalize-lgpl-tests (Depends-on): Likewise.
+       * modules/canonicalize-tests (Depends-on): Likewise.
+       * modules/lstat-tests (Depends-on): Likewise.
+       * modules/openat-tests (Depends-on): Likewise.
+       * modules/remove-tests (Depends-on): Likewise.
+       * modules/rmdir-tests (Depends-on): Likewise.
+       * modules/unlink-tests (Depends-on): Likewise.
+       * tests/test-canonicalize-lgpl.c (symlink): Delete stub.
+       * tests/test-canonicalize.c (symlink): Likewise.
+       * tests/test-fstatat.c (symlink): Likewise.
+       * tests/test-lstat.c (symlink): Likewise.
+       * tests/test-remove.c (symlink): Likewise.
+       * tests/test-rmdir.c (symlink): Likewise.
+       * tests/test-unlink.c (symlink): Likewise.
+       * tests/test-unlinkat.c (symlink): Likewise.
+
        symlink: new module, for Solaris 9 bug
        * modules/symlink: New file.
        * m4/symlink.m4 (gl_FUNC_SYMLINK): Likewise.
diff --git a/lib/symlinkat.c b/lib/symlinkat.c
index fd129ea..d9032b9 100644
--- a/lib/symlinkat.c
+++ b/lib/symlinkat.c
@@ -21,7 +21,8 @@
 #include <unistd.h>

 #if !HAVE_SYMLINK
-/* Mingw lacks symlink, so this wrapper is trivial.  */
+/* Mingw lacks symlink, and it is more efficient to provide a trivial
+   wrapper than to go through at-func.c to call rpl_symlink.  */

 # include <errno.h>

diff --git a/m4/symlinkat.m4 b/m4/symlinkat.m4
index 93980d5..27fa344 100644
--- a/m4/symlinkat.m4
+++ b/m4/symlinkat.m4
@@ -1,4 +1,4 @@
-# serial 2
+# serial 3
 # See if we need to provide symlinkat/readlinkat replacement.

 dnl Copyright (C) 2009 Free Software Foundation, Inc.
@@ -13,7 +13,7 @@ AC_DEFUN([gl_FUNC_SYMLINKAT],
   AC_REQUIRE([gl_FUNC_OPENAT])
   AC_REQUIRE([gl_UNISTD_H_DEFAULTS])
   AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
-  AC_CHECK_FUNCS_ONCE([symlink symlinkat readlinkat])
+  AC_CHECK_FUNCS_ONCE([symlinkat readlinkat])
   if test $ac_cv_func_symlinkat = no; then
     # No known system has readlinkat but not symlinkat
     HAVE_SYMLINKAT=0
diff --git a/modules/canonicalize-lgpl-tests b/modules/canonicalize-lgpl-tests
index cf96c58..39c3484 100644
--- a/modules/canonicalize-lgpl-tests
+++ b/modules/canonicalize-lgpl-tests
@@ -3,9 +3,9 @@ tests/test-canonicalize-lgpl.c

 Depends-on:
 same-inode
+symlink

 configure.ac:
-AC_CHECK_FUNCS_ONCE([symlink])

 Makefile.am:
 TESTS += test-canonicalize-lgpl
diff --git a/modules/canonicalize-tests b/modules/canonicalize-tests
index 202d639..b10b61d 100644
--- a/modules/canonicalize-tests
+++ b/modules/canonicalize-tests
@@ -4,9 +4,9 @@ tests/test-canonicalize.c
 Depends-on:
 progname
 same-inode
+symlink

 configure.ac:
-AC_CHECK_FUNCS_ONCE([symlink])

 Makefile.am:
 TESTS += test-canonicalize
diff --git a/modules/lstat-tests b/modules/lstat-tests
index acd2bcd..1a34c34 100644
--- a/modules/lstat-tests
+++ b/modules/lstat-tests
@@ -6,9 +6,9 @@ Depends-on:
 errno
 same-inode
 stdbool
+symlink

 configure.ac:
-AC_CHECK_FUNCS_ONCE([symlink])

 Makefile.am:
 TESTS += test-lstat
diff --git a/modules/openat-tests b/modules/openat-tests
index 7134f2c..0d0ff8c 100644
--- a/modules/openat-tests
+++ b/modules/openat-tests
@@ -9,10 +9,10 @@ tests/test-unlinkat.c

 Depends-on:
 pathmax
+symlink
 unlinkdir

 configure.ac:
-AC_CHECK_FUNCS_ONCE([symlink])

 Makefile.am:
 TESTS += test-fstatat test-openat test-unlinkat
diff --git a/modules/remove-tests b/modules/remove-tests
index fdc26c2..665ebe4 100644
--- a/modules/remove-tests
+++ b/modules/remove-tests
@@ -2,10 +2,10 @@ Files:
 tests/test-remove.c

 Depends-on:
+symlink
 sys_stat

 configure.ac:
-AC_CHECK_FUNCS_ONCE([symlink])

 Makefile.am:
 TESTS += test-remove
diff --git a/modules/rmdir-tests b/modules/rmdir-tests
index fca8a77..0fa5efe 100644
--- a/modules/rmdir-tests
+++ b/modules/rmdir-tests
@@ -4,9 +4,9 @@ tests/test-rmdir.c

 Depends-on:
 stdbool
+symlink

 configure.ac:
-AC_CHECK_FUNCS_ONCE([symlink])

 Makefile.am:
 TESTS += test-rmdir
diff --git a/modules/symlinkat b/modules/symlinkat
index 2da3ba4..1a065fd 100644
--- a/modules/symlinkat
+++ b/modules/symlinkat
@@ -10,6 +10,7 @@ extensions
 fcntl-h
 openat
 readlink
+symlink
 unistd

 configure.ac:
diff --git a/modules/unlink-tests b/modules/unlink-tests
index f0930f0..b9be842 100644
--- a/modules/unlink-tests
+++ b/modules/unlink-tests
@@ -4,10 +4,10 @@ tests/test-unlink.c

 Depends-on:
 stdbool
+symlink
 unlinkdir

 configure.ac:
-AC_CHECK_FUNCS_ONCE([symlink])

 Makefile.am:
 TESTS += test-unlink
diff --git a/tests/test-canonicalize-lgpl.c b/tests/test-canonicalize-lgpl.c
index 39025fa..6efd714 100644
--- a/tests/test-canonicalize-lgpl.c
+++ b/tests/test-canonicalize-lgpl.c
@@ -29,10 +29,6 @@

 #include "same-inode.h"

-#if !HAVE_SYMLINK
-# define symlink(a,b) (-1)
-#endif /* !HAVE_SYMLINK */
-
 #define ASSERT(expr) \
   do                                                                        \
     {                                                                       \
diff --git a/tests/test-canonicalize.c b/tests/test-canonicalize.c
index 463297f..6589877 100644
--- a/tests/test-canonicalize.c
+++ b/tests/test-canonicalize.c
@@ -30,10 +30,6 @@

 #include "same-inode.h"

-#if !HAVE_SYMLINK
-# define symlink(a,b) (-1)
-#endif
-
 #define ASSERT(expr) \
   do                                                                        \
     {                                                                       \
diff --git a/tests/test-fstatat.c b/tests/test-fstatat.c
index a70721a..28a71fa 100644
--- a/tests/test-fstatat.c
+++ b/tests/test-fstatat.c
@@ -31,10 +31,6 @@
 #include "pathmax.h"
 #include "same-inode.h"

-#if !HAVE_SYMLINK
-# define symlink(a,b) (-1)
-#endif
-
 #define ASSERT(expr) \
   do                                                                         \
     {                                                                        \
diff --git a/tests/test-lstat.c b/tests/test-lstat.c
index cb9963d..89edb1f 100644
--- a/tests/test-lstat.c
+++ b/tests/test-lstat.c
@@ -29,10 +29,6 @@

 #include "same-inode.h"

-#if !HAVE_SYMLINK
-# define symlink(a,b) (-1)
-#endif
-
 #define ASSERT(expr) \
   do                                                                         \
     {                                                                        \
diff --git a/tests/test-remove.c b/tests/test-remove.c
index 787cde2..99edb0c 100644
--- a/tests/test-remove.c
+++ b/tests/test-remove.c
@@ -27,10 +27,6 @@
 #include <sys/stat.h>
 #include <unistd.h>

-#if !HAVE_SYMLINK
-# define symlink(a,b) (-1)
-#endif
-
 #define ASSERT(expr) \
   do                                                                         \
     {                                                                        \
diff --git a/tests/test-rmdir.c b/tests/test-rmdir.c
index d7e4da3..9d8eb5a 100644
--- a/tests/test-rmdir.c
+++ b/tests/test-rmdir.c
@@ -27,10 +27,6 @@
 #include <stdlib.h>
 #include <sys/stat.h>

-#if !HAVE_SYMLINK
-# define symlink(a,b) (-1)
-#endif
-
 #define ASSERT(expr) \
   do                                                                         \
     {                                                                        \
diff --git a/tests/test-unlink.c b/tests/test-unlink.c
index f5df9b6..0c8dc6f 100644
--- a/tests/test-unlink.c
+++ b/tests/test-unlink.c
@@ -30,10 +30,6 @@

 #include "unlinkdir.h"

-#if !HAVE_SYMLINK
-# define symlink(a,b) (-1)
-#endif
-
 #define ASSERT(expr) \
   do                                                                         \
     {                                                                        \
diff --git a/tests/test-unlinkat.c b/tests/test-unlinkat.c
index 8e0a1cd..785ede4 100644
--- a/tests/test-unlinkat.c
+++ b/tests/test-unlinkat.c
@@ -29,10 +29,6 @@

 #include "unlinkdir.h"

-#if !HAVE_SYMLINK
-# define symlink(a,b) (-1)
-#endif
-
 #define ASSERT(expr) \
   do                                                                         \
     {                                                                        \
-- 
1.6.4.2


>From 012edba5df851abfaec47f9405204fb80946dd0f Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Mon, 21 Sep 2009 11:11:46 -0600
Subject: [PATCH 4/7] readlink: use correct return type

* lib/unistd.in.h (readlink): Use ssize_t.
* lib/readlink.c (readlink): Likewise.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog       |    4 ++++
 lib/readlink.c  |    8 ++------
 lib/unistd.in.h |    6 ++++--
 3 files changed, 10 insertions(+), 8 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 8390e19..5d5a372 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2009-09-21  Eric Blake  <address@hidden>

+       readlink: use correct return type
+       * lib/unistd.in.h (readlink): Use ssize_t.
+       * lib/readlink.c (readlink): Likewise.
+
        symlink: use throughout gnulib
        * m4/symlinkat.m4 (gl_FUNC_SYMLINKAT): Omit symlink check.
        * lib/symlinkat.c (symlinkat) [!HAVE_SYMLINK]: Document why
diff --git a/lib/readlink.c b/lib/readlink.c
index 5cccdc9..81c5ba8 100644
--- a/lib/readlink.c
+++ b/lib/readlink.c
@@ -1,5 +1,5 @@
 /* Stub for readlink().
-   Copyright (C) 2003-2007 Free Software Foundation, Inc.
+   Copyright (C) 2003-2007, 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
@@ -29,11 +29,7 @@
 /* readlink() substitute for systems that don't have a readlink() function,
    such as DJGPP 2.03 and mingw32.  */

-/* The official POSIX return type of readlink() is ssize_t, but since here
-   we have no declaration in a public header file, we use 'int' as return
-   type.  */
-
-int
+ssize_t
 readlink (const char *path, char *buf, size_t bufsize)
 {
   struct stat statbuf;
diff --git a/lib/unistd.in.h b/lib/unistd.in.h
index 76d987e..0226713 100644
--- a/lib/unistd.in.h
+++ b/lib/unistd.in.h
@@ -41,7 +41,9 @@
 /* mingw, BeOS, Haiku declare environ in <stdlib.h>, not in <unistd.h>.  */
 #include <stdlib.h>

-#if @GNULIB_WRITE@ && @REPLACE_WRITE@ && @GNULIB_UNISTD_H_SIGPIPE@
+#if ((@GNULIB_WRITE@ && @REPLACE_WRITE@ && @GNULIB_UNISTD_H_SIGPIPE@)   \
+     || (@GNULIB_READLINK@ && address@hidden@)                         \
+     || (@GNULIB_READLINKAT@ && address@hidden@))
 /* Get ssize_t.  */
 # include <sys/types.h>
 #endif
@@ -627,7 +629,7 @@ extern int pipe2 (int fd[2], int flags);
    See the POSIX:2001 specification
    <http://www.opengroup.org/susv3xsh/readlink.html>.  */
 # if address@hidden@
-extern int readlink (const char *file, char *buf, size_t bufsize);
+extern ssize_t readlink (const char *file, char *buf, size_t bufsize);
 # endif
 #elif defined GNULIB_POSIXCHECK
 # undef readlink
-- 
1.6.4.2


>From cf481742f8a311519be15930d665ca5af86cce6b Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Mon, 21 Sep 2009 12:11:46 -0600
Subject: [PATCH 5/7] readlink: fix Solaris 9 bug

readlink("link/",buf,len) mistakenly succeeds.

* lib/readlink.c (rpl_readlink): Work around trailing slash bug.
* m4/readlink.m4 (gl_FUNC_READLINK): Detect the bug.
* m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add witness.
* modules/unistd (Makefile.am): Substitute it.
* lib/unistd.in.h (readlink): Declare replacement.
* modules/readlink-tests: New test.
* tests/test-readlink.c: Likewise.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                         |   10 +++
 doc/posix-functions/readlink.texi |    3 +
 lib/readlink.c                    |   38 ++++++++++--
 lib/unistd.in.h                   |    7 ++-
 m4/readlink.m4                    |   19 ++++++-
 m4/unistd_h.m4                    |    1 +
 modules/readlink-tests            |   11 ++++
 modules/unistd                    |    1 +
 tests/test-readlink.c             |  115 +++++++++++++++++++++++++++++++++++++
 9 files changed, 196 insertions(+), 9 deletions(-)
 create mode 100644 modules/readlink-tests
 create mode 100644 tests/test-readlink.c

diff --git a/ChangeLog b/ChangeLog
index 5d5a372..9702b35 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,15 @@
 2009-09-21  Eric Blake  <address@hidden>

+       readlink: fix Solaris 9 bug
+       * lib/readlink.c (rpl_readlink): Work around trailing slash bug.
+       * m4/readlink.m4 (gl_FUNC_READLINK): Detect the bug.
+       * m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add witness.
+       * modules/unistd (Makefile.am): Substitute it.
+       * lib/unistd.in.h (readlink): Declare replacement.
+       * doc/posix-functions/readlink.texi (readlink): Document this.
+       * modules/readlink-tests: New test.
+       * tests/test-readlink.c: Likewise.
+
        readlink: use correct return type
        * lib/unistd.in.h (readlink): Use ssize_t.
        * lib/readlink.c (readlink): Likewise.
diff --git a/doc/posix-functions/readlink.texi b/doc/posix-
functions/readlink.texi
index e2a2a1e..db23db0 100644
--- a/doc/posix-functions/readlink.texi
+++ b/doc/posix-functions/readlink.texi
@@ -9,6 +9,9 @@ readlink
 Portability problems fixed by Gnulib:
 @itemize
 @item
+Some platforms mistakenly succeed on @code{readlink("link/",buf,len)}:
+Solaris 9.
address@hidden
 This function is missing on some platforms:
 mingw.
 @end itemize
diff --git a/lib/readlink.c b/lib/readlink.c
index 81c5ba8..457d20e 100644
--- a/lib/readlink.c
+++ b/lib/readlink.c
@@ -20,9 +20,8 @@
 #include <unistd.h>

 #include <errno.h>
-#include <sys/types.h>
+#include <string.h>
 #include <sys/stat.h>
-#include <stddef.h>

 #if !HAVE_READLINK

@@ -30,16 +29,43 @@
    such as DJGPP 2.03 and mingw32.  */

 ssize_t
-readlink (const char *path, char *buf, size_t bufsize)
+readlink (const char *name, char *buf _UNUSED_PARAMETER_,
+          size_t bufsize _UNUSED_PARAMETER_)
 {
   struct stat statbuf;

   /* In general we should use lstat() here, not stat().  But on platforms
-     without symbolic links lstat() - if it exists - would be equivalent to
+     without symbolic links, lstat() - if it exists - would be equivalent to
      stat(), therefore we can use stat().  This saves us a configure check.  */
-  if (stat (path, &statbuf) >= 0)
+  if (stat (name, &statbuf) >= 0)
     errno = EINVAL;
   return -1;
 }

-#endif
+#else /* HAVE_READLINK */
+
+# undef readlink
+
+/* readlink() substitute for systems that don't reject trailing slash,
+   such as Solaris 9.  */
+
+ssize_t
+rpl_readlink (const char *name, char *buf, size_t bufsize)
+{
+  size_t len = strlen (name);
+  if (name[len - 1] == '/')
+    {
+      /* Even if name without the slash is a symlink to a directory,
+         both lstat() and stat() must resolve to that directory rather
+         than the symlink.  We can therefore safely use stat() to
+         distinguish between EINVAL and ENOTDIR/ENOENT, to avoid the
+         extra overhead in rpl_lstat().  */
+      struct stat st;
+      if (stat (name, &st) == 0)
+        errno = EINVAL;
+      return -1;
+    }
+  return readlink (name, buf, bufsize);
+}
+
+#endif /* HAVE_READLINK */
diff --git a/lib/unistd.in.h b/lib/unistd.in.h
index 0226713..600f224 100644
--- a/lib/unistd.in.h
+++ b/lib/unistd.in.h
@@ -42,7 +42,7 @@
 #include <stdlib.h>

 #if ((@GNULIB_WRITE@ && @REPLACE_WRITE@ && @GNULIB_UNISTD_H_SIGPIPE@)   \
-     || (@GNULIB_READLINK@ && address@hidden@)                         \
+     || (@GNULIB_READLINK@ && (address@hidden@ || @REPLACE_READLINK@)) \
      || (@GNULIB_READLINKAT@ && address@hidden@))
 /* Get ssize_t.  */
 # include <sys/types.h>
@@ -623,12 +623,15 @@ extern int pipe2 (int fd[2], int flags);


 #if @GNULIB_READLINK@
+# if @REPLACE_READLINK@
+#  define readlink rpl_readlink
+# endif
 /* Read the contents of the symbolic link FILE and place the first BUFSIZE
    bytes of it into BUF.  Return the number of bytes placed into BUF if
    successful, otherwise -1 and errno set.
    See the POSIX:2001 specification
    <http://www.opengroup.org/susv3xsh/readlink.html>.  */
-# if address@hidden@
+# if address@hidden@ || @REPLACE_READLINK@
 extern ssize_t readlink (const char *file, char *buf, size_t bufsize);
 # endif
 #elif defined GNULIB_POSIXCHECK
diff --git a/m4/readlink.m4 b/m4/readlink.m4
index ff3f1f5..d01a7f9 100644
--- a/m4/readlink.m4
+++ b/m4/readlink.m4
@@ -1,4 +1,4 @@
-# readlink.m4 serial 5
+# readlink.m4 serial 6
 dnl Copyright (C) 2003, 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,
@@ -12,6 +12,23 @@ AC_DEFUN([gl_FUNC_READLINK],
     HAVE_READLINK=0
     AC_LIBOBJ([readlink])
     gl_PREREQ_READLINK
+  else
+    AC_CACHE_CHECK([whether readlink handles trailing slash correctly],
+      [gl_cv_func_readlink_works],
+      [# We have readlink, so assume ln -s works.
+       ln -s conftest.no-such conftest.link
+       AC_RUN_IFELSE(
+         [AC_LANG_PROGRAM(
+           [[#include <unistd.h>
+]], [[char buf[20];
+      return readlink ("conftest.link/", buf, sizeof buf) != -1;]])],
+         [gl_cv_func_readlink_works=yes], [gl_cv_func_readlink_works=no],
+         [gl_cv_func_readlink_works="guessing no"])
+      rm -f conftest.link])
+    if test "$gl_cv_func_readlink_works" != yes; then
+      REPLACE_READLINK=1
+      AC_LIBOBJ([readlink])
+    fi
   fi
 ])

diff --git a/m4/unistd_h.m4 b/m4/unistd_h.m4
index 0a263d2..2334582 100644
--- a/m4/unistd_h.m4
+++ b/m4/unistd_h.m4
@@ -101,6 +101,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS],
   REPLACE_LCHOWN=0;       AC_SUBST([REPLACE_LCHOWN])
   REPLACE_LINK=0;         AC_SUBST([REPLACE_LINK])
   REPLACE_LSEEK=0;        AC_SUBST([REPLACE_LSEEK])
+  REPLACE_READLINK=0;     AC_SUBST([REPLACE_READLINK])
   REPLACE_RMDIR=0;        AC_SUBST([REPLACE_RMDIR])
   REPLACE_SYMLINK=0;      AC_SUBST([REPLACE_SYMLINK])
   REPLACE_UNLINK=0;       AC_SUBST([REPLACE_UNLINK])
diff --git a/modules/readlink-tests b/modules/readlink-tests
new file mode 100644
index 0000000..84994d5
--- /dev/null
+++ b/modules/readlink-tests
@@ -0,0 +1,11 @@
+Files:
+tests/test-readlink.c
+
+Depends-on:
+symlink
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-readlink
+check_PROGRAMS += test-readlink
diff --git a/modules/unistd b/modules/unistd
index d2568b2..875efb0 100644
--- a/modules/unistd
+++ b/modules/unistd
@@ -93,6 +93,7 @@ unistd.h: unistd.in.h
              -e 's|@''REPLACE_LCHOWN''@|$(REPLACE_LCHOWN)|g' \
              -e 's|@''REPLACE_LINK''@|$(REPLACE_LINK)|g' \
              -e 's|@''REPLACE_LSEEK''@|$(REPLACE_LSEEK)|g' \
+             -e 's|@''REPLACE_READLINK''@|$(REPLACE_READLINK)|g' \
              -e 's|@''REPLACE_RMDIR''@|$(REPLACE_RMDIR)|g' \
              -e 's|@''REPLACE_SYMLINK''@|$(REPLACE_SYMLINK)|g' \
              -e 's|@''REPLACE_UNLINK''@|$(REPLACE_UNLINK)|g' \
diff --git a/tests/test-readlink.c b/tests/test-readlink.c
new file mode 100644
index 0000000..0baf861
--- /dev/null
+++ b/tests/test-readlink.c
@@ -0,0 +1,115 @@
+/* Tests of readlink.
+   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>
+
+#define ASSERT(expr) \
+  do                                                                         \
+    {                                                                        \
+      if (!(expr))                                                           \
+       {                                                                    \
+         fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__);  \
+         fflush (stderr);                                                   \
+         abort ();                                                          \
+       }                                                                    \
+    }                                                                        \
+  while (0)
+
+#define BASE "test-readlink.t"
+
+int
+main ()
+{
+  char buf[80];
+
+  /* Remove any leftovers from a previous partial run.  */
+  ASSERT (system ("rm -rf " BASE "*") == 0);
+
+  /* Sanity checks of failures.  Mingw lacks symlink, but readlink can
+     still distinguish between various errors.  */
+  memset (buf, 0xff, sizeof buf);
+  errno = 0;
+  ASSERT (readlink ("no_such", buf, sizeof buf) == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (readlink ("no_such/", buf, sizeof buf) == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (readlink ("", buf, sizeof buf) == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (readlink (".", buf, sizeof buf) == -1);
+  ASSERT (errno == EINVAL);
+  errno = 0;
+  ASSERT (readlink ("./", buf, sizeof buf) == -1);
+  ASSERT (errno == EINVAL);
+  ASSERT (close (creat (BASE "file", 0600)) == 0);
+  errno = 0;
+  ASSERT (readlink (BASE "file", buf, sizeof buf) == -1);
+  ASSERT (errno == EINVAL);
+  errno = 0;
+  ASSERT (readlink (BASE "file/", buf, sizeof buf) == -1);
+  ASSERT (errno == ENOTDIR);
+  ASSERT (unlink (BASE "file") == 0);
+
+  /* Now test actual symlinks.  */
+  if (symlink (BASE "dir", BASE "link"))
+    {
+      fputs ("skipping test: symlinks not supported on this filesystem\n",
+            stderr);
+      return 77;
+    }
+  ASSERT (mkdir (BASE "dir", 0700) == 0);
+  errno = 0;
+  ASSERT (readlink (BASE "link/", buf, sizeof buf) == -1);
+  ASSERT (errno == EINVAL);
+  {
+    /* Up till now, no readlink has been successful, so buf should be
+       unchanged.  */
+    int i;
+    for (i = 0; i < sizeof buf; i++)
+      ASSERT (buf[i] == (char) 0xff);
+  }
+  {
+    size_t len = strlen (BASE "dir");
+    ASSERT (readlink (BASE "link", buf, 0) == 0);
+    ASSERT (buf[0] == (char) 0xff);
+    ASSERT (readlink (BASE "link", buf, 1) == 1);
+    ASSERT (readlink (BASE "link", buf, 1) == 1);
+    ASSERT (buf[0] == BASE[0]);
+    ASSERT (buf[1] == (char) 0xff);
+    ASSERT (readlink (BASE "link", buf, sizeof buf) == len);
+    ASSERT (strncmp (buf, BASE "dir", len) == 0);
+    /* POSIX says rest of buf is unspecified; but in practice, it is
+       either left alone, or NUL-terminated.  */
+    ASSERT (buf[len] == '\0' || buf[len] == (char) 0xff);
+  }
+  ASSERT (rmdir (BASE "dir") == 0);
+  ASSERT (unlink (BASE "link") == 0);
+
+  return 0;
+}
-- 
1.6.4.2


>From 28fe4a12a488f721d6e2d291a92ba545b66c7785 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Mon, 21 Sep 2009 14:07:44 -0600
Subject: [PATCH 6/7] test-symlinkat: enhance test

* tests/test-readlink.c (main): Move guts...
* tests/test-readlink.h (test_readlink): ...into new file.
* tests/test-symlink.c (main): Move guts...
* tests/test-symlink.h (test_symlink): ...into new file.
* tests/test-symlinkat.c (main): Use new files for further
coverage.
(do_symlink, do_readlink): New helper functions.
* modules/symlink-tests (Files): Ship new file.
(Depends-on): Add stdbool.
* modules/readlink-tests (Files): Ship new file.
(Depends-on): Add stdbool.
* modules/symlinkat-tests (Files): Use new files.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog               |   14 +++++++
 modules/readlink-tests  |    2 +
 modules/symlink-tests   |    2 +
 modules/symlinkat-tests |    2 +
 tests/test-readlink.c   |   69 ++--------------------------------
 tests/test-readlink.h   |   94 +++++++++++++++++++++++++++++++++++++++++++++++
 tests/test-symlink.c    |   52 ++------------------------
 tests/test-symlink.h    |   77 ++++++++++++++++++++++++++++++++++++++
 tests/test-symlinkat.c  |   91 +++++++++++++++++++++++----------------------
 9 files changed, 246 insertions(+), 157 deletions(-)
 create mode 100644 tests/test-readlink.h
 create mode 100644 tests/test-symlink.h

diff --git a/ChangeLog b/ChangeLog
index 9702b35..a16b694 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,19 @@
 2009-09-21  Eric Blake  <address@hidden>

+       test-symlinkat: enhance test
+       * tests/test-readlink.c (main): Move guts...
+       * tests/test-readlink.h (test_readlink): ...into new file.
+       * tests/test-symlink.c (main): Move guts...
+       * tests/test-symlink.h (test_symlink): ...into new file.
+       * tests/test-symlinkat.c (main): Use new files for further
+       coverage.
+       (do_symlink, do_readlink): New helper functions.
+       * modules/symlink-tests (Files): Ship new file.
+       (Depends-on): Add stdbool.
+       * modules/readlink-tests (Files): Ship new file.
+       (Depends-on): Add stdbool.
+       * modules/symlinkat-tests (Files): Use new files.
+
        readlink: fix Solaris 9 bug
        * lib/readlink.c (rpl_readlink): Work around trailing slash bug.
        * m4/readlink.m4 (gl_FUNC_READLINK): Detect the bug.
diff --git a/modules/readlink-tests b/modules/readlink-tests
index 84994d5..84cc326 100644
--- a/modules/readlink-tests
+++ b/modules/readlink-tests
@@ -1,7 +1,9 @@
 Files:
+tests/test-readlink.h
 tests/test-readlink.c

 Depends-on:
+stdbool
 symlink

 configure.ac:
diff --git a/modules/symlink-tests b/modules/symlink-tests
index aa67405..9858ade 100644
--- a/modules/symlink-tests
+++ b/modules/symlink-tests
@@ -1,7 +1,9 @@
 Files:
+tests/test-symlink.h
 tests/test-symlink.c

 Depends-on:
+stdbool

 configure.ac:

diff --git a/modules/symlinkat-tests b/modules/symlinkat-tests
index 9150568..a0dab9f 100644
--- a/modules/symlinkat-tests
+++ b/modules/symlinkat-tests
@@ -1,4 +1,6 @@
 Files:
+tests/test-readlink.h
+tests/test-symlink.h
 tests/test-symlinkat.c

 Depends-on:
diff --git a/tests/test-readlink.c b/tests/test-readlink.c
index 0baf861..1fdc2da 100644
--- a/tests/test-readlink.c
+++ b/tests/test-readlink.c
@@ -22,6 +22,7 @@

 #include <fcntl.h>
 #include <errno.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -41,75 +42,13 @@

 #define BASE "test-readlink.t"

+#include "test-readlink.h"
+
 int
 main ()
 {
-  char buf[80];
-
   /* Remove any leftovers from a previous partial run.  */
   ASSERT (system ("rm -rf " BASE "*") == 0);

-  /* Sanity checks of failures.  Mingw lacks symlink, but readlink can
-     still distinguish between various errors.  */
-  memset (buf, 0xff, sizeof buf);
-  errno = 0;
-  ASSERT (readlink ("no_such", buf, sizeof buf) == -1);
-  ASSERT (errno == ENOENT);
-  errno = 0;
-  ASSERT (readlink ("no_such/", buf, sizeof buf) == -1);
-  ASSERT (errno == ENOENT);
-  errno = 0;
-  ASSERT (readlink ("", buf, sizeof buf) == -1);
-  ASSERT (errno == ENOENT);
-  errno = 0;
-  ASSERT (readlink (".", buf, sizeof buf) == -1);
-  ASSERT (errno == EINVAL);
-  errno = 0;
-  ASSERT (readlink ("./", buf, sizeof buf) == -1);
-  ASSERT (errno == EINVAL);
-  ASSERT (close (creat (BASE "file", 0600)) == 0);
-  errno = 0;
-  ASSERT (readlink (BASE "file", buf, sizeof buf) == -1);
-  ASSERT (errno == EINVAL);
-  errno = 0;
-  ASSERT (readlink (BASE "file/", buf, sizeof buf) == -1);
-  ASSERT (errno == ENOTDIR);
-  ASSERT (unlink (BASE "file") == 0);
-
-  /* Now test actual symlinks.  */
-  if (symlink (BASE "dir", BASE "link"))
-    {
-      fputs ("skipping test: symlinks not supported on this filesystem\n",
-            stderr);
-      return 77;
-    }
-  ASSERT (mkdir (BASE "dir", 0700) == 0);
-  errno = 0;
-  ASSERT (readlink (BASE "link/", buf, sizeof buf) == -1);
-  ASSERT (errno == EINVAL);
-  {
-    /* Up till now, no readlink has been successful, so buf should be
-       unchanged.  */
-    int i;
-    for (i = 0; i < sizeof buf; i++)
-      ASSERT (buf[i] == (char) 0xff);
-  }
-  {
-    size_t len = strlen (BASE "dir");
-    ASSERT (readlink (BASE "link", buf, 0) == 0);
-    ASSERT (buf[0] == (char) 0xff);
-    ASSERT (readlink (BASE "link", buf, 1) == 1);
-    ASSERT (readlink (BASE "link", buf, 1) == 1);
-    ASSERT (buf[0] == BASE[0]);
-    ASSERT (buf[1] == (char) 0xff);
-    ASSERT (readlink (BASE "link", buf, sizeof buf) == len);
-    ASSERT (strncmp (buf, BASE "dir", len) == 0);
-    /* POSIX says rest of buf is unspecified; but in practice, it is
-       either left alone, or NUL-terminated.  */
-    ASSERT (buf[len] == '\0' || buf[len] == (char) 0xff);
-  }
-  ASSERT (rmdir (BASE "dir") == 0);
-  ASSERT (unlink (BASE "link") == 0);
-
-  return 0;
+  return test_readlink (readlink, true);
 }
diff --git a/tests/test-readlink.h b/tests/test-readlink.h
new file mode 100644
index 0000000..e85fd36
--- /dev/null
+++ b/tests/test-readlink.h
@@ -0,0 +1,94 @@
+/* Tests of readlink.
+   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 readlink(a,b,c) and
+   readlinkat(AT_FDCWD,a,b,c).  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_readlink (ssize_t (*func) (char const *, char *, size_t), bool print)
+{
+  char buf[80];
+
+  /* Sanity checks of failures.  Mingw lacks symlink, but readlink can
+     still distinguish between various errors.  */
+  memset (buf, 0xff, sizeof buf);
+  errno = 0;
+  ASSERT (func ("no_such", buf, sizeof buf) == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (func ("no_such/", buf, sizeof buf) == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (func ("", buf, sizeof buf) == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (func (".", buf, sizeof buf) == -1);
+  ASSERT (errno == EINVAL);
+  errno = 0;
+  ASSERT (func ("./", buf, sizeof buf) == -1);
+  ASSERT (errno == EINVAL);
+  ASSERT (close (creat (BASE "file", 0600)) == 0);
+  errno = 0;
+  ASSERT (func (BASE "file", buf, sizeof buf) == -1);
+  ASSERT (errno == EINVAL);
+  errno = 0;
+  ASSERT (func (BASE "file/", buf, sizeof buf) == -1);
+  ASSERT (errno == ENOTDIR);
+  ASSERT (unlink (BASE "file") == 0);
+
+  /* Now test actual symlinks.  */
+  if (symlink (BASE "dir", BASE "link"))
+    {
+      if (print)
+        fputs ("skipping test: symlinks not supported on this filesystem\n",
+               stderr);
+      return 77;
+    }
+  ASSERT (mkdir (BASE "dir", 0700) == 0);
+  errno = 0;
+  ASSERT (func (BASE "link/", buf, sizeof buf) == -1);
+  ASSERT (errno == EINVAL);
+  {
+    /* Up till now, no readlink has been successful, so buf should be
+       unchanged.  */
+    int i;
+    for (i = 0; i < sizeof buf; i++)
+      ASSERT (buf[i] == (char) 0xff);
+  }
+  {
+    size_t len = strlen (BASE "dir");
+    ASSERT (func (BASE "link", buf, 0) == 0);
+    ASSERT (buf[0] == (char) 0xff);
+    ASSERT (func (BASE "link", buf, 1) == 1);
+    ASSERT (func (BASE "link", buf, 1) == 1);
+    ASSERT (buf[0] == BASE[0]);
+    ASSERT (buf[1] == (char) 0xff);
+    ASSERT (func (BASE "link", buf, sizeof buf) == len);
+    ASSERT (strncmp (buf, BASE "dir", len) == 0);
+    /* POSIX says rest of buf is unspecified; but in practice, it is
+       either left alone, or NUL-terminated.  */
+    ASSERT (buf[len] == '\0' || buf[len] == (char) 0xff);
+  }
+  ASSERT (rmdir (BASE "dir") == 0);
+  ASSERT (unlink (BASE "link") == 0);
+
+  return 0;
+}
diff --git a/tests/test-symlink.c b/tests/test-symlink.c
index 9f9dda1..9f39090 100644
--- a/tests/test-symlink.c
+++ b/tests/test-symlink.c
@@ -22,6 +22,7 @@

 #include <fcntl.h>
 #include <errno.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/stat.h>
@@ -40,58 +41,13 @@

 #define BASE "test-symlink.t"

+#include "test-symlink.h"
+
 int
 main ()
 {
   /* Remove any leftovers from a previous partial run.  */
   ASSERT (system ("rm -rf " BASE "*") == 0);

-  if (symlink ("nowhere", BASE "link1"))
-    {
-      fputs ("skipping test: symlinks not supported on this filesystem\n",
-            stderr);
-      return 77;
-    }
-
-  /* Some systems allow the creation of 0-length symlinks as a synonym
-     for "."; but most reject it.  */
-  errno = 0;
-  if (symlink ("", BASE "link2") == -1)
-    ASSERT (errno == ENOENT || errno == EINVAL);
-  else
-    ASSERT (unlink (BASE "link2") == 0);
-
-  /* Sanity checks of failures.  */
-  errno = 0;
-  ASSERT (symlink ("nowhere", "") == -1);
-  ASSERT (errno == ENOENT);
-  errno = 0;
-  ASSERT (symlink ("nowhere", ".") == -1);
-  ASSERT (errno == EEXIST || errno == EINVAL);
-  errno = 0;
-  ASSERT (symlink ("somewhere", BASE "link1") == -1);
-  ASSERT (errno == EEXIST);
-  errno = 0;
-  ASSERT (symlink ("nowhere", BASE "link2/") == -1);
-  ASSERT (errno == ENOTDIR || errno == ENOENT);
-  ASSERT (mkdir (BASE "dir", 0700) == 0);
-  errno = 0;
-  ASSERT (symlink ("nowhere", BASE "dir") == -1);
-  ASSERT (errno == EEXIST);
-  errno = 0;
-  ASSERT (symlink ("nowhere", BASE "dir/") == -1);
-  ASSERT (errno == EEXIST);
-  ASSERT (close (creat (BASE "file", 0600)) == 0);
-  errno = 0;
-  ASSERT (symlink ("nowhere", BASE "file") == -1);
-  ASSERT (errno == EEXIST);
-  errno = 0;
-  ASSERT (symlink ("nowhere", BASE "file/") == -1);
-  ASSERT (errno == EEXIST || errno == ENOTDIR);
-
-  ASSERT (rmdir (BASE "dir") == 0);
-  ASSERT (unlink (BASE "file") == 0);
-  ASSERT (unlink (BASE "link1") == 0);
-
-  return 0;
+  return test_symlink (symlink, true);
 }
diff --git a/tests/test-symlink.h b/tests/test-symlink.h
new file mode 100644
index 0000000..ef51564
--- /dev/null
+++ b/tests/test-symlink.h
@@ -0,0 +1,77 @@
+/* Tests of symlink.
+   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 symlink(a,b) and
+   symlinkat(a,AT_FDCWD,b).  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_symlink (int (*func) (char const *, char const *), bool print)
+{
+  if (func ("nowhere", BASE "link1"))
+    {
+      if (print)
+        fputs ("skipping test: symlinks not supported on this filesystem\n",
+               stderr);
+      return 77;
+    }
+
+  /* Some systems allow the creation of 0-length symlinks as a synonym
+     for "."; but most reject it.  */
+  errno = 0;
+  if (func ("", BASE "link2") == -1)
+    ASSERT (errno == ENOENT || errno == EINVAL);
+  else
+    ASSERT (unlink (BASE "link2") == 0);
+
+  /* Sanity checks of failures.  */
+  errno = 0;
+  ASSERT (func ("nowhere", "") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (func ("nowhere", ".") == -1);
+  ASSERT (errno == EEXIST || errno == EINVAL);
+  errno = 0;
+  ASSERT (func ("somewhere", BASE "link1") == -1);
+  ASSERT (errno == EEXIST);
+  errno = 0;
+  ASSERT (func ("nowhere", BASE "link2/") == -1);
+  ASSERT (errno == ENOTDIR || errno == ENOENT);
+  ASSERT (mkdir (BASE "dir", 0700) == 0);
+  errno = 0;
+  ASSERT (func ("nowhere", BASE "dir") == -1);
+  ASSERT (errno == EEXIST);
+  errno = 0;
+  ASSERT (func ("nowhere", BASE "dir/") == -1);
+  ASSERT (errno == EEXIST);
+  ASSERT (close (creat (BASE "file", 0600)) == 0);
+  errno = 0;
+  ASSERT (func ("nowhere", BASE "file") == -1);
+  ASSERT (errno == EEXIST);
+  errno = 0;
+  ASSERT (func ("nowhere", BASE "file/") == -1);
+  ASSERT (errno == EEXIST || errno == ENOTDIR);
+
+  ASSERT (rmdir (BASE "dir") == 0);
+  ASSERT (unlink (BASE "file") == 0);
+  ASSERT (unlink (BASE "link1") == 0);
+
+  return 0;
+}
diff --git a/tests/test-symlinkat.c b/tests/test-symlinkat.c
index 7942d27..4a6fe0a 100644
--- a/tests/test-symlinkat.c
+++ b/tests/test-symlinkat.c
@@ -22,6 +22,7 @@

 #include <fcntl.h>
 #include <errno.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -43,78 +44,80 @@
     }                                                                        \
   while (0)

+#define BASE "test-symlinkat.t"
+
+#include "test-readlink.h"
+#include "test-symlink.h"
+
+static int dfd = AT_FDCWD;
+
+static int
+do_symlink (char const *contents, char const *name)
+{
+  return symlinkat (contents, dfd, name);
+}
+
+static ssize_t
+do_readlink (char const *name, char *buf, size_t len)
+{
+  return readlinkat (dfd, name, buf, len);
+}
+
 int
 main ()
 {
   char buf[80];
+  int result;

-  /* Create handle for future use.  */
-  int dfd = openat (AT_FDCWD, ".", O_RDONLY);
+  /* Remove any leftovers from a previous partial run.  */
+  ASSERT (system ("rm -rf " BASE "*") == 0);
+
+  /* Perform same checks as counterpart functions.  */
+  result = test_readlink (do_readlink, false);
+  ASSERT (test_symlink (do_symlink, false) == result);
+  dfd = openat (AT_FDCWD, ".", O_RDONLY);
   ASSERT (0 <= dfd);
+  ASSERT (test_readlink (do_readlink, false) == result);
+  ASSERT (test_symlink (do_symlink, false) == result);

-  /* Sanity checks of failures.  Mingw lacks symlinkat, but readlinkat
-     can still distinguish between various errors.  */
-  errno = 0;
-  ASSERT (readlinkat (AT_FDCWD, "no_such", buf, sizeof buf) == -1);
-  ASSERT (errno == ENOENT);
-  errno = 0;
-  ASSERT (readlinkat (dfd, "no_such", buf, sizeof buf) == -1);
-  ASSERT (errno == ENOENT);
-  errno = 0;
-  ASSERT (readlinkat (AT_FDCWD, "", buf, sizeof buf) == -1);
-  ASSERT (errno == ENOENT);
-  errno = 0;
-  ASSERT (readlinkat (dfd, "", buf, sizeof buf) == -1);
-  ASSERT (errno == ENOENT);
-  errno = 0;
-  ASSERT (readlinkat (AT_FDCWD, ".", buf, sizeof buf) == -1);
-  ASSERT (errno == EINVAL);
-  errno = 0;
-  ASSERT (readlinkat (dfd, ".", buf, sizeof buf) == -1);
-  ASSERT (errno == EINVAL);
-  errno = 0;
-  ASSERT (symlinkat ("who cares", AT_FDCWD, "") == -1);
-  ASSERT (errno == ENOENT || errno == ENOSYS);
-  errno = 0;
-  ASSERT (symlinkat ("who cares", dfd, "") == -1);
-  ASSERT (errno == ENOENT || errno == ENOSYS);
-
-  /* Skip everything else on mingw.  */
+  /* Now perform some cross-directory checks.  Skip everything else on
+     mingw.  */
   if (HAVE_SYMLINK)
     {
-      const char *linkname = "test-symlinkat.link";
       const char *contents = "don't matter!";
-      int exp = strlen (contents);
+      ssize_t exp = strlen (contents);

       /* Create link while cwd is '.', then read it in '..'.  */
-      ASSERT (symlinkat (contents, AT_FDCWD, linkname) == 0);
+      ASSERT (symlinkat (contents, AT_FDCWD, BASE "link") == 0);
       errno = 0;
-      ASSERT (symlinkat (contents, dfd, linkname) == -1);
+      ASSERT (symlinkat (contents, dfd, BASE "link") == -1);
       ASSERT (errno == EEXIST);
       ASSERT (chdir ("..") == 0);
       errno = 0;
-      ASSERT (readlinkat (AT_FDCWD, linkname, buf, sizeof buf) == -1);
+      ASSERT (readlinkat (AT_FDCWD, BASE "link", buf, sizeof buf) == -1);
       ASSERT (errno == ENOENT);
-      ASSERT (readlinkat (dfd, linkname, buf, sizeof buf) == exp);
+      ASSERT (readlinkat (dfd, BASE "link", buf, sizeof buf) == exp);
       ASSERT (strncmp (contents, buf, exp) == 0);
-      ASSERT (unlinkat (dfd, linkname, 0) == 0);
+      ASSERT (unlinkat (dfd, BASE "link", 0) == 0);

       /* Create link while cwd is '..', then read it in '.'.  */
-      ASSERT (symlinkat (contents, dfd, linkname) == 0);
+      ASSERT (symlinkat (contents, dfd, BASE "link") == 0);
       ASSERT (fchdir (dfd) == 0);
       errno = 0;
-      ASSERT (symlinkat (contents, AT_FDCWD, linkname) == -1);
+      ASSERT (symlinkat (contents, AT_FDCWD, BASE "link") == -1);
       ASSERT (errno == EEXIST);
       buf[0] = '\0';
-      ASSERT (readlinkat (AT_FDCWD, linkname, buf, sizeof buf) == exp);
+      ASSERT (readlinkat (AT_FDCWD, BASE "link", buf, sizeof buf) == exp);
       ASSERT (strncmp (contents, buf, exp) == 0);
       buf[0] = '\0';
-      ASSERT (readlinkat (dfd, linkname, buf, sizeof buf) == exp);
+      ASSERT (readlinkat (dfd, BASE "link", buf, sizeof buf) == exp);
       ASSERT (strncmp (contents, buf, exp) == 0);
-      ASSERT (unlink (linkname) == 0);
+      ASSERT (unlink (BASE "link") == 0);
     }

   ASSERT (close (dfd) == 0);
-
-  return 0;
+  if (result == 77)
+    fputs ("skipping test: symlinks not supported on this filesystem\n",
+          stderr);
+  return result;
 }
-- 
1.6.4.2


>From 6b287b6f8a98f0f3a5030be272522b34f221a544 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Mon, 21 Sep 2009 14:40:20 -0600
Subject: [PATCH 7/7] readlink: document portability issue with symlink length

* doc/posix-functions/lstat.texi (lstat): Mention that some file
systems have bogus st_size on symlinks, and the
areadlink-with-size module.
* doc/posix-functions/fstatat.texi (fstatat): Likewise.
* doc/posix-functions/readlink.texi (readlink): Mention the
areadlink module.
* doc/posix-functions/readlinkat.texi (readlinkat): Likewise.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                           |    9 +++++++++
 doc/posix-functions/fstatat.texi    |    4 ++++
 doc/posix-functions/lstat.texi      |    4 ++++
 doc/posix-functions/readlink.texi   |    5 +++++
 doc/posix-functions/readlinkat.texi |    5 +++++
 5 files changed, 27 insertions(+), 0 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index a16b694..0329be0 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,14 @@
 2009-09-21  Eric Blake  <address@hidden>

+       readlink: document portability issue with symlink length
+       * doc/posix-functions/lstat.texi (lstat): Mention that some file
+       systems have bogus st_size on symlinks, and the
+       areadlink-with-size module.
+       * doc/posix-functions/fstatat.texi (fstatat): Likewise.
+       * doc/posix-functions/readlink.texi (readlink): Mention the
+       areadlink module.
+       * doc/posix-functions/readlinkat.texi (readlinkat): Likewise.
+
        test-symlinkat: enhance test
        * tests/test-readlink.c (main): Move guts...
        * tests/test-readlink.h (test_readlink): ...into new file.
diff --git a/doc/posix-functions/fstatat.texi b/doc/posix-functions/fstatat.texi
index d1c4c83..b96fc4d 100644
--- a/doc/posix-functions/fstatat.texi
+++ b/doc/posix-functions/fstatat.texi
@@ -31,4 +31,8 @@ fstatat
 GB.  The fix is to use the @code{AC_SYS_LARGEFILE} macro.
 @item
 On Windows platforms (excluding Cygwin), @code{st_ino} is always 0.
address@hidden
+On some filesystems, @code{st_size} contains bogus information for
+symlinks; use the gnulib module areadlink-with-size for a better way
+to get symlink contents.
 @end itemize
diff --git a/doc/posix-functions/lstat.texi b/doc/posix-functions/lstat.texi
index dbe31f8..328b896 100644
--- a/doc/posix-functions/lstat.texi
+++ b/doc/posix-functions/lstat.texi
@@ -35,4 +35,8 @@ lstat
 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)}.
address@hidden
+On some filesystems, @code{st_size} contains bogus information for
+symlinks; use the gnulib module areadlink-with-size for a better way
+to get symlink contents.
 @end itemize
diff --git a/doc/posix-functions/readlink.texi b/doc/posix-
functions/readlink.texi
index db23db0..a19f002 100644
--- a/doc/posix-functions/readlink.texi
+++ b/doc/posix-functions/readlink.texi
@@ -27,4 +27,9 @@ readlink
 When @code{readlink} is called on a file that is not a symbolic link:
 Irix may set @code{errno} to @code{ENXIO} instead of @code{EINVAL}.  Cygwin
 may set errno to @code{EACCES} instead of @code{EINVAL}.
address@hidden
+Symlink contents do not always have a trailing null byte, and there is
+no indication if symlink contents were truncated if the return value
+matches the length.  Use the gnulib module areadlink for improved
+ability to read symlink contents.
 @end itemize
diff --git a/doc/posix-functions/readlinkat.texi b/doc/posix-
functions/readlinkat.texi
index b64b443..af5858b 100644
--- a/doc/posix-functions/readlinkat.texi
+++ b/doc/posix-functions/readlinkat.texi
@@ -28,4 +28,9 @@ readlinkat
 When @code{readlink} is called on a file that is not a symbolic link:
 Irix may set @code{errno} to @code{ENXIO} instead of @code{EINVAL}.  Cygwin
 may set errno to @code{EACCES} instead of @code{EINVAL}.
address@hidden
+Symlink contents do not always have a trailing null byte, and there is
+no indication if symlink contents were truncated if the return value
+matches the length.  Use the gnulib module areadlink for improved
+ability to read symlink contents.
 @end itemize
-- 
1.6.4.2







reply via email to

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