diff lib/link.c @ 11981:7e5f9267c62e

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. Signed-off-by: Eric Blake <ebb9@byu.net>
author Eric Blake <ebb9@byu.net>
date Wed, 09 Sep 2009 15:25:26 -0600
parents 2921cdaa1164
children 00e8f52bdef4
line wrap: on
line diff
--- a/lib/link.c
+++ b/lib/link.c
@@ -18,13 +18,18 @@
 
 #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 @@
 }
 
 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 @@
       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 @@
   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 */
+
+# undef link
 
-#error "This platform lacks a link function, and Gnulib doesn't provide a replacement. This is a bug in Gnulib."
-
-#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 */