changeset 17787:461b92d0b2eb

linkat: wrap to handle symlinks on OS X 10.10 * m4/linkat.m4 (gl_FUNC_LINKAT): linkat() is available on Yosemite but not usable because it doesn't support creating hardlinks to symlinks. Therefore add a generic test for this capability and fallback to our emulation if linkat() fails with ENOTSUP.
author Pádraig Brady <P@draigBrady.com>
date Fri, 31 Oct 2014 18:12:15 +0000
parents 49eed2643636
children 6eb23dad34b3
files ChangeLog doc/posix-functions/linkat.texi lib/linkat.c m4/linkat.m4 modules/linkat
diffstat 5 files changed, 66 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2014-11-03  Pádraig Brady  <P@draigBrady.com>
+
+	linkat: wrap to handle symlinks on OS X 10.10
+	* m4/linkat.m4 (gl_FUNC_LINKAT): linkat() is available on Yosemite
+	but not usable because it doesn't support creating hardlinks
+	to symlinks.  Therefore add a generic test for this capability
+	and fallback to our emulation if linkat() fails with ENOTSUP.
+
 2014-11-02  Paul Eggert  <eggert@cs.ucla.edu>
 
 	open, openat: document nonstandard FreeBSD, NetBSD O_NOFOLLOW errno
--- a/doc/posix-functions/linkat.texi
+++ b/doc/posix-functions/linkat.texi
@@ -10,10 +10,13 @@
 @itemize
 @item
 This function is missing on some platforms:
-glibc 2.3.6, Mac OS X 10.5, FreeBSD 6.0, NetBSD 5.0, OpenBSD 3.8, Minix 3.1.8,
+glibc 2.3.6, Mac OS X < 10.10, FreeBSD 6.0, NetBSD 5.0, OpenBSD 3.8, Minix 3.1.8,
 AIX 5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 10, Cygwin 1.5.x, mingw, MSVC 9, Interix 3.5, BeOS.
 But the replacement function is not safe to be used in libraries and is not multithread-safe.
 @item
+This function fails to directly hardlink symlinks on some platforms:
+Mac OS X 10.10.
+@item
 This function fails to reject trailing slashes on non-directories on
 some platforms:
 AIX 7.1, Solaris 11 2011-11.
--- a/lib/linkat.c
+++ b/lib/linkat.c
@@ -43,7 +43,7 @@
 # endif
 #endif
 
-#if !HAVE_LINKAT
+#if !HAVE_LINKAT || LINKAT_SYMLINK_NOTSUP
 
 /* Create a link.  If FILE1 is a symlink, either create a hardlink to
    that symlink, or fake it by creating an identical symlink.  */
@@ -195,6 +195,10 @@
 
 # endif
 
+#endif /* !HAVE_LINKAT || LINKAT_SYMLINK_NOTSUP  */
+
+#if !HAVE_LINKAT
+
 /* Create a link to FILE1, in the directory open on descriptor FD1, to FILE2,
    in the directory open on descriptor FD2.  If FILE1 is a symlink, FLAG
    controls whether to dereference FILE1 first.  If possible, do it without
@@ -321,7 +325,17 @@
 # endif
 
   if (!flag)
-    return linkat (fd1, file1, fd2, file2, flag);
+    {
+      int result = linkat (fd1, file1, fd2, file2, flag);
+# if LINKAT_SYMLINK_NOTSUP
+      /* OS X 10.10 has linkat() but it doesn't support
+         hardlinks to symlinks.  Fallback to our emulation
+         in that case.  */
+      if (result == -1 && (errno == ENOTSUP || errno == EOPNOTSUPP))
+        return at_func2 (fd1, file1, fd2, file2, link_immediate);
+# endif
+      return result;
+    }
 
   /* Cache the information on whether the system call really works.  */
   {
--- a/m4/linkat.m4
+++ b/m4/linkat.m4
@@ -20,6 +20,27 @@
   if test $ac_cv_func_linkat = no; then
     HAVE_LINKAT=0
   else
+    dnl OS X Yosemite has linkat() but it's not sufficient
+    dnl to our needs since it doesn't support creating
+    dnl hardlinks to symlinks.  Therefore check for that
+    dnl capability before considering using the system version.
+    AC_CACHE_CHECK([whether linkat() can link symlinks],
+      [gl_cv_func_linkat_nofollow],
+      [rm -rf conftest.l1 conftest.l2
+       ln -s target conftest.l1
+       AC_RUN_IFELSE([AC_LANG_PROGRAM(
+                        [[#include <fcntl.h>
+                          #include <unistd.h>
+                        ]],
+                        [return linkat (AT_FDCWD, "conftest.l1", AT_FDCWD,
+                                            "conftest.l2", 0);
+                        ])],
+         [gl_cv_func_linkat_nofollow=yes
+          LINKAT_SYMLINK_NOTSUP=0],
+         [gl_cv_func_linkat_nofollow=no
+          LINKAT_SYMLINK_NOTSUP=1])
+       rm -rf conftest.l1 conftest.l2])
+
     AC_CACHE_CHECK([whether linkat(,AT_SYMLINK_FOLLOW) works],
       [gl_cv_func_linkat_follow],
       [rm -rf conftest.f1 conftest.f2
@@ -37,6 +58,7 @@
          [gl_cv_func_linkat_follow=yes],
          [gl_cv_func_linkat_follow="need runtime check"])
        rm -rf conftest.f1 conftest.f2])
+
     AC_CACHE_CHECK([whether linkat handles trailing slash correctly],
       [gl_cv_func_linkat_slash],
       [rm -rf conftest.a conftest.b conftest.c conftest.d
@@ -85,11 +107,15 @@
       *yes) gl_linkat_slash_bug=0 ;;
       *)    gl_linkat_slash_bug=1 ;;
     esac
+
     if test "$gl_cv_func_linkat_follow" != yes \
+       || test "$gl_cv_func_linkat_nofollow" != yes \
        || test $gl_linkat_slash_bug = 1; then
       REPLACE_LINKAT=1
       AC_DEFINE_UNQUOTED([LINKAT_TRAILING_SLASH_BUG], [$gl_linkat_slash_bug],
         [Define to 1 if linkat fails to recognize a trailing slash.])
+      AC_DEFINE_UNQUOTED([LINKAT_SYMLINK_NOTSUP], [$LINKAT_SYMLINK_NOTSUP],
+        [Define to 1 if linkat can create hardlinks to symlinks])
     fi
   fi
 ])
--- a/modules/linkat
+++ b/modules/linkat
@@ -14,18 +14,18 @@
 fcntl-h          [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
 filenamecat-lgpl [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
 link-follow      [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
-areadlink        [test $HAVE_LINKAT = 0]
-at-internal      [test $HAVE_LINKAT = 0]
-dosname          [test $HAVE_LINKAT = 0]
-fstat            [test $HAVE_LINKAT = 0]
-getcwd-lgpl      [test $HAVE_LINKAT = 0]
-openat-h         [test $HAVE_LINKAT = 0]
-openat-die       [test $HAVE_LINKAT = 0]
-link             [test $HAVE_LINKAT = 0]
-lstat            [test $HAVE_LINKAT = 0]
-same-inode       [test $HAVE_LINKAT = 0]
-save-cwd         [test $HAVE_LINKAT = 0]
-symlink          [test $HAVE_LINKAT = 0]
+areadlink        [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
+at-internal      [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
+dosname          [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
+fstat            [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
+getcwd-lgpl      [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
+openat-h         [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
+openat-die       [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
+link             [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
+lstat            [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
+same-inode       [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
+save-cwd         [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
+symlink          [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
 areadlinkat      [test $REPLACE_LINKAT = 1]
 fstatat          [test $REPLACE_LINKAT = 1]
 
@@ -33,8 +33,6 @@
 gl_FUNC_LINKAT
 if test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1; then
   AC_LIBOBJ([linkat])
-fi
-if test $HAVE_LINKAT = 0; then
   AC_LIBOBJ([at-func2])
 fi
 gl_UNISTD_MODULE_INDICATOR([linkat])