bug-gnulib
[Top][All Lists]
Advanced

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

Re: mkfifoat, renameat


From: Eric Blake
Subject: Re: mkfifoat, renameat
Date: Wed, 9 Sep 2009 23:31:16 +0000 (UTC)
User-agent: Loom/3.14 (http://gmane.org/)

Eric Blake <ebb9 <at> byu.net> writes:

> Part of the problem on mingw is that the fchdir module relies on
> canonicalize doing the right thing, but mingw's paths (with drive letters
> and \, rather than leading / for absolute) throws canonicalize for a loop;
> and cross-compilation tries to replace getcwd, even though mingw's getcwd
> always works.  At that point, the populated dirs[fd].name mapping have
> invalid entries, which hurt attempts to do fstat(dfd).  It doesn't help
> that mingw stat() reports st_ino == 0 for every directory, rendering
> SAME_INODE useless.  Oh well, I'll have to spend more time porting those
> pieces to mingw.

Among other things, testing shows that mingw supports getcwd(NULL,0), so the 
cross-compilation guess needs to be improved there.  stat() has the annoying 
property that a trailing slash changes semantics (but / and \\ are 
interchangeable), and in an inconsistent manner (you can't always stat the 
result of getcwd!):

getcwd reports: c:\
stat("c:",&st) => fails
stat("c:/",&st) => passes

getcwd reports: c:\dir
stat("c:/dir",&st) => passes
stat("c:/dir/",&st) => fails

getcwd reports: \\machine\share
stat("\\\\machine\\share",&st) => fails
stat("\\\\machine\\share\\",&st) => passes

getcwd reports: \\machine\share\dir
stat("\\\\machine\\share\\dir",&st) => passes
stat("\\\\machine\\share\\dir\\",&st) => fails

And it doesn't help that canonicalize is not consistently using ISSLASH macros 
to munge '/' and '\\' into a canonical form.  Yep, definitely some groundwork 
to lay here before linkat and renameat will work on mingw.

Meanwhile, I noticed that the link module has a bug on mingw, where link
("a","b/.") created the regular file "b" rather than failing with ENOENT (I 
noticed it because cygwin 1.5 had the same bug, and I noticed that while 
enhancing the link module to work around Solaris 8 handling of trailing / in 
link).  I'll probably be applying this soon...


From: Eric Blake <address@hidden>
Date: Wed, 9 Sep 2009 11:06:44 -0600
Subject: [PATCH 1/2] 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
at least Cygwin and Solaris.
* modules/link-tests (Files): Remove unused file.
(Depends-on): Add errno, sys_stat.
(Makefile.am): Simplify.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog          |    8 ++++
 modules/link-tests |    6 ++--
 tests/test-link.c  |  103 +++++++++++++++++++++++++++++++++++++++++++++++++---
 tests/test-link.sh |   37 -------------------
 4 files changed, 109 insertions(+), 45 deletions(-)
 delete mode 100755 tests/test-link.sh

diff --git a/ChangeLog b/ChangeLog
index 08fe19a..8372ebe 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,13 @@
 2009-09-09  Eric Blake  <address@hidden>

+       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
+       at least Cygwin and Solaris.
+       * modules/link-tests (Files): Remove unused file.
+       (Depends-on): Add errno, sys_stat.
+       (Makefile.am): Simplify.
+
        dirname: add library-safe mdir_name
        * lib/dirname.h (mdir_name): New prototype.
        * lib/dirname.c (dir_name): Move guts...
diff --git a/modules/link-tests b/modules/link-tests
index c1ec52c..ca61deb 100644
--- a/modules/link-tests
+++ b/modules/link-tests
@@ -1,12 +1,12 @@
 Files:
-tests/test-link.sh
 tests/test-link.c

 Depends-on:
+errno
+sys_stat

 configure.ac:

 Makefile.am:
-TESTS += test-link.sh
-TESTS_ENVIRONMENT += EXEEXT='@EXEEXT@'
+TESTS += test-link
 check_PROGRAMS += test-link
diff --git a/tests/test-link.c b/tests/test-link.c
index 250a821..fbc794a 100644
--- a/tests/test-link.c
+++ b/tests/test-link.c
@@ -19,8 +19,12 @@
 #include <unistd.h>

 #include <errno.h>
+#include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>

 #define ASSERT(expr) \
   do                                                                         \
@@ -34,30 +38,119 @@
     }                                                                        \
   while (0)

+#define BASE "test-link.t"
+
 int
 main (int argc, char **argv)
 {
+  int fd;
   int ret;

-  ASSERT (argc == 3);
+  /* Remove any garbage left from previous partial runs.  */
+  ASSERT (system ("rm -rf " BASE "*") == 0);
+
+  /* Create first file.  */
+  fd = open (BASE "a", O_CREAT | O_EXCL | O_WRONLY, 0600);
+  ASSERT (0 <= fd);
+  ASSERT (write (fd, "hello", 5) == 5);
+  ASSERT (close (fd) == 0);

-  ret = link (argv[1], argv[2]);
-  if (ret < 0)
+  /* Not all file systems support link.  Mingw doesn't have reliable
+     st_nlink on hard links, but our implementation does fail with
+     EPERM on poor file systems, and we can detect the inferior stat()
+     via st_ino.  Cygwin 1.5.x copies rather than links files on those
+     file systems, but there, st_nlink and st_ino are reliable.  */
+  ret = link (BASE "a", BASE "b");
+  if (!ret)
+  {
+    struct stat st;
+    ASSERT (stat (BASE "b", &st) == 0);
+    if (st.st_ino && st.st_nlink != 2)
+      {
+        ASSERT (unlink (BASE "b") == 0);
+        errno = EPERM;
+        ret = -1;
+      }
+  }
+  if (ret == -1)
     {
       /* If the device does not support hard links, errno is
         EPERM on Linux, EOPNOTSUPP on FreeBSD.  */
       switch (errno)
        {
        case EPERM:
-#ifdef EOPNOTSUPP
        case EOPNOTSUPP:
-#endif
+          fputs ("skipping test: "
+                 "hard links are not supported on this file system\n", stderr);
+          ASSERT (unlink (BASE "a") == 0);
          return 77;
        default:
          perror ("link");
          return 1;
        }
     }
+  ASSERT (ret == 0);
+
+  /* Now, for some behavior tests.  Modify the contents of 'b', and
+     ensure that 'a' can see it, both while 'b' exists and after.  */
+  fd = open (BASE "b", O_APPEND | O_WRONLY);
+  ASSERT (0 <= fd);
+  ASSERT (write (fd, "world", 5) == 5);
+  ASSERT (close (fd) == 0);
+  {
+    char buf[11] = { 0 };
+    fd = open (BASE "a", O_RDONLY);
+    ASSERT (0 <= fd);
+    ASSERT (read (fd, buf, 10) == 10);
+    ASSERT (strcmp (buf, "helloworld") == 0);
+    ASSERT (close (fd) == 0);
+    ASSERT (unlink (BASE "b") == 0);
+    fd = open (BASE "a", O_RDONLY);
+    ASSERT (0 <= fd);
+    ASSERT (read (fd, buf, 10) == 10);
+    ASSERT (strcmp (buf, "helloworld") == 0);
+    ASSERT (close (fd) == 0);
+  }
+
+  /* Test for various error conditions.  Assumes hard links to
+     directories are not permitted.  */
+  ASSERT (mkdir (BASE "d", 0700) == 0);
+  errno = 0;
+  ASSERT (link (BASE "a", ".") == -1);
+  ASSERT (errno == EEXIST || errno == EINVAL);
+  errno = 0;
+  ASSERT (link (BASE "a", BASE "a") == -1);
+  ASSERT (errno == EEXIST);
+  ASSERT (link (BASE "a", BASE "b") == 0);
+  errno = 0;
+  ASSERT (link (BASE "a", BASE "b") == -1);
+  ASSERT (errno == EEXIST);
+  errno = 0;
+  ASSERT (link (BASE "a", BASE "d") == -1);
+  ASSERT (errno == EEXIST);
+  errno = 0;
+  ASSERT (link (BASE "c", BASE "e") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (link (BASE "a", BASE "c/.") == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (link (BASE "a/", BASE "c") == -1);
+  ASSERT (errno == ENOTDIR);
+  errno = 0;
+  ASSERT (link (BASE "a", BASE "c/") == -1);
+  ASSERT (errno == ENOTDIR);
+  errno = 0;
+  ASSERT (link (BASE "d", BASE "c") == -1);
+  ASSERT (errno == EPERM || errno == EACCES);
+
+  /* Clean up.  */
+  ASSERT (unlink (BASE "a") == 0);
+  ASSERT (unlink (BASE "b") == 0);
+  errno = 0;
+  ASSERT (unlink (BASE "c") == -1);
+  ASSERT (errno == ENOENT);
+  ASSERT (rmdir (BASE "d") == 0);

   return 0;
 }
diff --git a/tests/test-link.sh b/tests/test-link.sh
deleted file mode 100755
index 1519f9d..0000000
--- a/tests/test-link.sh
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/bin/sh
-
-tmpfiles="test-link-a.txt test-link-b.txt test-link-c.txt"
-trap 'rm -fr $tmpfiles' 1 2 3 15
-
-# Create a file.
-echo "hello" > test-link-a.txt || exit 1
-
-# Use link() to create a new name for it.
-./test-link${EXEEXT} test-link-a.txt test-link-b.txt
-case $? in
-  0) ;;
-  77)
-    echo "Skipping test: hard links are not supported on this file system"
-    rm -fr $tmpfiles
-    exit 77
-    ;;
-  *) exit 1 ;;
-esac
-cmp test-link-a.txt test-link-b.txt || exit 1
-
-# Modify the contents of the first file.
-echo "world" >> test-link-a.txt || exit 1
-cmp test-link-a.txt test-link-b.txt || exit 1
-
-# Modify the contents of the second file.
-echo "some text" >> test-link-b.txt || exit 1
-cmp test-link-a.txt test-link-b.txt || exit 1
-
-# Delete the first file, then verity the second file still has the same
-# contents.
-cp test-link-a.txt test-link-c.txt || exit 1
-rm test-link-a.txt || exit 1
-cmp test-link-b.txt test-link-c.txt || exit 1
-
-rm -fr $tmpfiles
-exit 0
-- 
1.6.3.2


>From 9101691fafa16a95b3fb0a95abe9c43da26fa907 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Wed, 9 Sep 2009 15:25:26 -0600
Subject: [PATCH 2/2] 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 <address@hidden>
---
 ChangeLog                     |    9 +++
 doc/posix-functions/link.texi |    4 ++
 lib/link.c                    |  110 +++++++++++++++++++++++++++++++++++++---
 lib/unistd.in.h               |    5 ++-
 m4/link.m4                    |   21 ++++++--
 m4/unistd_h.m4                |    3 +-
 modules/link                  |    4 +-
 modules/unistd                |    1 +
 8 files changed, 140 insertions(+), 17 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 8372ebe..d35aba7 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,14 @@
 2009-09-09  Eric Blake  <address@hidden>

+       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
index ace07cd..c785371 100644
--- a/doc/posix-functions/link.texi
+++ b/doc/posix-functions/link.texi
@@ -11,6 +11,10 @@ link
 @item
 This function is missing on some platforms:
 mingw.
address@hidden
+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
index 2b5a97f..72b8600 100644
--- 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 @@ 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 */
diff --git a/lib/unistd.in.h b/lib/unistd.in.h
index 902b025..f191412 100644
--- a/lib/unistd.in.h
+++ b/lib/unistd.in.h
@@ -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 address@hidden@
+# if address@hidden@ || @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
index 349d537..fc071cd 100644
--- 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 @@ 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], [:])
diff --git a/m4/unistd_h.m4 b/m4/unistd_h.m4
index 84f0755..7347e38 100644
--- 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 @@ 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])
diff --git a/modules/link b/modules/link
index 31d1af4..9492950 100644
--- a/modules/link
+++ b/modules/link
@@ -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
diff --git a/modules/unistd b/modules/unistd
index 1f8b29e..37ecaaa 100644
--- a/modules/unistd
+++ b/modules/unistd
@@ -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' \
-- 
1.6.3.2







reply via email to

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