# HG changeset patch # User Eric Blake # Date 1252531526 21600 # Node ID 7e5f9267c62ec4697b725b2003148f38d68c6dc1 # Parent 0811c19e147fa7c128994c035e3dbc4bcf73bfee 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 diff --git a/ChangeLog b/ChangeLog --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,14 @@ 2009-09-09 Eric Blake + 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 diff --git a/doc/posix-functions/link.texi b/doc/posix-functions/link.texi --- a/doc/posix-functions/link.texi +++ b/doc/posix-functions/link.texi @@ -11,6 +11,10 @@ @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: diff --git a/lib/link.c b/lib/link.c --- a/lib/link.c +++ b/lib/link.c @@ -18,13 +18,18 @@ #include -#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ - -#define WIN32_LEAN_AND_MEAN #include -#include #include +#include +#include +#include + +#if !HAVE_LINK +# if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ + +# define WIN32_LEAN_AND_MEAN +# include /* 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 */ diff --git a/lib/unistd.in.h b/lib/unistd.in.h --- a/lib/unistd.in.h +++ b/lib/unistd.in.h @@ -595,11 +595,14 @@ #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 . */ -# if !@HAVE_LINK@ +# if !@HAVE_LINK@ || @REPLACE_LINK@ extern int link (const char *path1, const char *path2); # endif #elif defined GNULIB_POSIXCHECK diff --git a/m4/link.m4 b/m4/link.m4 --- a/m4/link.m4 +++ b/m4/link.m4 @@ -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 @@ 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 +]], [[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], [:]) diff --git a/m4/unistd_h.m4 b/m4/unistd_h.m4 --- a/m4/unistd_h.m4 +++ b/m4/unistd_h.m4 @@ -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 @@ 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]) diff --git a/modules/link b/modules/link --- a/modules/link +++ b/modules/link @@ -6,6 +6,8 @@ m4/link.m4 Depends-on: +strdup-posix +sys_stat unistd configure.ac: @@ -21,4 +23,4 @@ LGPLv2+ Maintainer: -Martin Lambers +Martin Lambers, Eric Blake diff --git a/modules/unistd b/modules/unistd --- a/modules/unistd +++ b/modules/unistd @@ -86,6 +86,7 @@ -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' \