link: fix platform bugs
authorEric Blake <ebb9@byu.net>
Wed, 9 Sep 2009 21:25:26 +0000 (15:25 -0600)
committerEric Blake <ebb9@byu.net>
Thu, 10 Sep 2009 02:57:05 +0000 (20:57 -0600)
* m4/link.m4 (gl_FUNC_LINK): Detect Solaris and Cygwin bugs.
* lib/link.c (link): Work around them.  Fix related mingw bug.
* m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add REPLACE_LINK.
* modules/unistd (Makefile.am): Substitute it.
* lib/unistd.in.h (link): Declare replacement.
* doc/posix-functions/link.texi (link): Document this.
* modules/link (Depends-on): Add strdup-posix, sys_stat.

Signed-off-by: Eric Blake <ebb9@byu.net>
ChangeLog
doc/posix-functions/link.texi
lib/link.c
lib/unistd.in.h
m4/link.m4
m4/unistd_h.m4
modules/link
modules/unistd

index 8bfb08d..957a1f6 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,14 @@
 2009-09-09  Eric Blake  <ebb9@byu.net>
 
+       link: fix platform bugs
+       * m4/link.m4 (gl_FUNC_LINK): Detect Solaris and Cygwin bugs.
+       * lib/link.c (link): Work around them.  Fix related mingw bug.
+       * m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add REPLACE_LINK.
+       * modules/unistd (Makefile.am): Substitute it.
+       * lib/unistd.in.h (link): Declare replacement.
+       * doc/posix-functions/link.texi (link): Document this.
+       * modules/link (Depends-on): Add strdup-posix, sys_stat.
+
        test-link: consolidate into single C program, test more cases
        * tests/test-link.sh: Delete.
        * tests/test-link.c: Test more error conditions.  Exposes bugs on
index ace07cd..c785371 100644 (file)
@@ -11,6 +11,10 @@ Portability problems fixed by Gnulib:
 @item
 This function is missing on some platforms:
 mingw.
+@item
+This function fails to reject trailing slashes on non-directories on
+some platforms:
+Solaris, Cygwin 1.5.x.
 @end itemize
 
 Portability problems not fixed by Gnulib:
index 2b5a97f..72b8600 100644 (file)
 
 #include <config.h>
 
-#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
-
-#define WIN32_LEAN_AND_MEAN
 #include <unistd.h>
-#include <windows.h>
 
 #include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#if !HAVE_LINK
+# if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+
+#  define WIN32_LEAN_AND_MEAN
+#  include <windows.h>
 
 /* CreateHardLink was introduced only in Windows 2000.  */
 typedef BOOL (WINAPI * CreateHardLinkFuncType) (LPCTSTR lpFileName,
@@ -46,8 +51,11 @@ initialize (void)
 }
 
 int
-link (const char *path1, const char *path2)
+link (const char *file1, const char *file2)
 {
+  char *dir;
+  size_t len1 = strlen (file1);
+  size_t len2 = strlen (file2);
   if (!initialized)
     initialize ();
   if (CreateHardLinkFunc == NULL)
@@ -56,7 +64,39 @@ link (const char *path1, const char *path2)
       errno = EPERM;
       return -1;
     }
-  if (CreateHardLinkFunc (path2, path1, NULL) == 0)
+  /* Reject trailing slashes on non-directories; mingw does not
+     support hard-linking directories.  */
+  if ((len1 && (file1[len1 - 1] == '/' || file1[len1 - 1] == '\\'))
+      || (len2 && (file2[len2 - 1] == '/' || file2[len2 - 1] == '\\')))
+    {
+      struct stat st;
+      if (stat (file1, &st) == 0 && S_ISDIR (st.st_mode))
+        errno = EPERM;
+      else
+        errno = ENOTDIR;
+      return -1;
+    }
+  /* CreateHardLink("b/.","a",NULL) creates file "b", so we must check
+     that dirname(file2) exists.  */
+  dir = strdup (file2);
+  if (!dir)
+    return -1;
+  {
+    struct stat st;
+    char *p = strchr (dir, '\0');
+    while (dir < p && (*--p != '/' && *p != '\\'));
+    *p = '\0';
+    if (p != dir && stat (dir, &st) == -1)
+      {
+        int saved_errno = errno;
+        free (dir);
+        errno = saved_errno;
+        return -1;
+      }
+    free (dir);
+  }
+  /* Now create the link.  */
+  if (CreateHardLinkFunc (file2, file1, NULL) == 0)
     {
       /* It is not documented which errors CreateHardLink() can produce.
        * The following conversions are based on tests on a Windows XP SP2
@@ -102,8 +142,60 @@ link (const char *path1, const char *path2)
   return 0;
 }
 
-#else /* !Windows */
+# else /* !Windows */
+
+#  error "This platform lacks a link function, and Gnulib doesn't provide a replacement. This is a bug in Gnulib."
+
+# endif /* !Windows */
+#else /* HAVE_LINK */
 
-#error "This platform lacks a link function, and Gnulib doesn't provide a replacement. This is a bug in Gnulib."
+# undef link
 
-#endif /* !Windows */
+/* Create a hard link from FILE1 to FILE2, working around platform bugs.  */
+int
+rpl_link (char const *file1, char const *file2)
+{
+  /* Reject trailing slashes on non-directories.  */
+  size_t len1 = strlen (file1);
+  size_t len2 = strlen (file2);
+  if ((len1 && file1[len1 - 1] == '/')
+      || (len2 && file2[len2 - 1] == '/'))
+    {
+      /* Let link() decide whether hard-linking directories is legal.
+         If stat() fails, link() will probably fail for the same
+         reason; so we only have to worry about successful stat() and
+         non-directory.  */
+      struct stat st;
+      if (stat (file1, &st) == 0 && !S_ISDIR (st.st_mode))
+        {
+          errno = ENOTDIR;
+          return -1;
+        }
+    }
+  else
+    {
+      /* Fix Cygwin 1.5.x bug where link("a","b/.") creates file "b".  */
+      char *dir = strdup (file2);
+      struct stat st;
+      char *p;
+      if (!dir)
+        return -1;
+      /* We already know file2 does not end in slash.  Strip off the
+         basename, then check that the dirname exists.  */
+      p = strrchr (dir, '/');
+      if (p)
+        {
+          *p = '\0';
+          if (stat (dir, &st) == -1)
+            {
+              int saved_errno = errno;
+              free (dir);
+              errno = saved_errno;
+              return -1;
+            }
+        }
+      free (dir);
+    }
+  return link (file1, file2);
+}
+#endif /* HAVE_LINK */
index 902b025..f191412 100644 (file)
@@ -595,11 +595,14 @@ extern int lchown (char const *file, uid_t owner, gid_t group);
 
 
 #if @GNULIB_LINK@
+# if @REPLACE_LINK@
+#  define link rpl_link
+# endif
 /* Create a new hard link for an existing file.
    Return 0 if successful, otherwise -1 and errno set.
    See POSIX:2001 specification
    <http://www.opengroup.org/susv3xsh/link.html>.  */
-# if !@HAVE_LINK@
+# if !@HAVE_LINK@ || @REPLACE_LINK@
 extern int link (const char *path1, const char *path2);
 # endif
 #elif defined GNULIB_POSIXCHECK
index 349d537..fc071cd 100644 (file)
@@ -1,4 +1,4 @@
-# link.m4 serial 1
+# link.m4 serial 2
 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,
@@ -11,9 +11,20 @@ AC_DEFUN([gl_FUNC_LINK],
   if test $ac_cv_func_link = no; then
     HAVE_LINK=0
     AC_LIBOBJ([link])
-    gl_PREREQ_LINK
+  else
+    AC_CACHE_CHECK([whether link handles trailing slash correctly],
+      [gl_cv_func_link_works],
+      [touch conftest.a
+       AC_RUN_IFELSE(
+         [AC_LANG_PROGRAM(
+           [[#include <unistd.h>
+]], [[return !link ("conftest.a", "conftest.b/");]])],
+         [gl_cv_func_link_works=yes], [gl_cv_func_link_works=no],
+         [gl_cv_func_link_works="guessing no"])
+       rm -f conftest.a conftest.b])
+    if test $gl_cv_func_link_works != yes; then
+      REPLACE_LINK=1
+      AC_LIBOBJ([link])
+    fi
   fi
 ])
-
-# Prerequisites of lib/link.c.
-AC_DEFUN([gl_PREREQ_LINK], [:])
index 84f0755..7347e38 100644 (file)
@@ -1,4 +1,4 @@
-# unistd_h.m4 serial 24
+# unistd_h.m4 serial 25
 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,
@@ -94,6 +94,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS],
   REPLACE_GETCWD=0;       AC_SUBST([REPLACE_GETCWD])
   REPLACE_GETPAGESIZE=0;  AC_SUBST([REPLACE_GETPAGESIZE])
   REPLACE_LCHOWN=0;       AC_SUBST([REPLACE_LCHOWN])
+  REPLACE_LINK=0;         AC_SUBST([REPLACE_LINK])
   REPLACE_LSEEK=0;        AC_SUBST([REPLACE_LSEEK])
   REPLACE_WRITE=0;        AC_SUBST([REPLACE_WRITE])
   UNISTD_H_HAVE_WINSOCK2_H=0; AC_SUBST([UNISTD_H_HAVE_WINSOCK2_H])
index 31d1af4..9492950 100644 (file)
@@ -6,6 +6,8 @@ lib/link.c
 m4/link.m4
 
 Depends-on:
+strdup-posix
+sys_stat
 unistd
 
 configure.ac:
@@ -21,4 +23,4 @@ License:
 LGPLv2+
 
 Maintainer:
-Martin Lambers
+Martin Lambers, Eric Blake
index 1f8b29e..37ecaaa 100644 (file)
@@ -86,6 +86,7 @@ unistd.h: unistd.in.h
              -e 's|@''REPLACE_GETCWD''@|$(REPLACE_GETCWD)|g' \
              -e 's|@''REPLACE_GETPAGESIZE''@|$(REPLACE_GETPAGESIZE)|g' \
              -e 's|@''REPLACE_LCHOWN''@|$(REPLACE_LCHOWN)|g' \
+             -e 's|@''REPLACE_LINK''@|$(REPLACE_LINK)|g' \
              -e 's|@''REPLACE_LSEEK''@|$(REPLACE_LSEEK)|g' \
              -e 's|@''REPLACE_WRITE''@|$(REPLACE_WRITE)|g' \
              -e 's|@''UNISTD_H_HAVE_WINSOCK2_H''@|$(UNISTD_H_HAVE_WINSOCK2_H)|g' \