rename: fix cygwin 1.5.x bugs
authorEric Blake <ebb9@byu.net>
Tue, 29 Sep 2009 22:42:59 +0000 (16:42 -0600)
committerEric Blake <ebb9@byu.net>
Fri, 2 Oct 2009 12:07:57 +0000 (06:07 -0600)
On cygwin 1.5.x, rename("dir","file") mistakenly succeeded.
rename("hard1","hard2") mistakenly reduced the hard link count,
such that "hard1" disappears once "hard2" is unlinked.

* m4/rename.m4 (gl_FUNC_RENAME): Detect cygwin bugs.
* lib/rename.c (rpl_rename): Work around them.
* modules/rename (Depends-on): Add same-inode.

ChangeLog
lib/rename.c
m4/rename.m4
modules/rename

index 8c38c9c..7a90694 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2009-10-02  Eric Blake  <ebb9@byu.net>
 
+       rename: fix cygwin 1.5.x bugs
+       * m4/rename.m4 (gl_FUNC_RENAME): Detect cygwin bugs.
+       * lib/rename.c (rpl_rename): Work around them.
+       * modules/rename (Depends-on): Add same-inode.
+
        rename: fix Solaris 10 bug
        * m4/rename.m4 (gl_FUNC_RENAME): Detect Solaris bug.
        * lib/rename.c (rpl_rename): Don't cripple POSIX behavior if this
index 22310be..68ea8e8 100644 (file)
@@ -134,17 +134,14 @@ rpl_rename (char const *src, char const *dst)
 
 #else /* ! W32 platform */
 
-# if RENAME_DEST_EXISTS_BUG
-#  error Please report your platform and this message to bug-gnulib@gnu.org.
-# elif RENAME_TRAILING_SLASH_SOURCE_BUG || RENAME_TRAILING_SLASH_DEST_BUG
-
-#  include <errno.h>
-#  include <stdio.h>
-#  include <stdlib.h>
-#  include <string.h>
-#  include <sys/stat.h>
+# include <errno.h>
+# include <stdio.h>
+# include <stdlib.h>
+# include <string.h>
+# include <sys/stat.h>
 
-#  include "dirname.h"
+# include "dirname.h"
+# include "same-inode.h"
 
 /* Rename the file SRC to DST, fixing any trailing slash bugs.  */
 
@@ -165,10 +162,41 @@ rpl_rename (char const *src, char const *dst)
   if (!src_len || !dst_len)
     return rename (src, dst); /* Let strace see the ENOENT failure.  */
 
+# if RENAME_DEST_EXISTS_BUG
+  {
+    char *src_base = last_component (src);
+    char *dst_base = last_component (dst);
+    if (*src_base == '.')
+      {
+        size_t len = base_len (src_base);
+        if (len == 1 || (len == 2 && src_base[1] == '.'))
+          {
+            errno = EINVAL;
+            return -1;
+          }
+      }
+    if (*dst_base == '.')
+      {
+        size_t len = base_len (dst_base);
+        if (len == 1 || (len == 2 && dst_base[1] == '.'))
+          {
+            errno = EINVAL;
+            return -1;
+          }
+      }
+  }
+# endif /* RENAME_DEST_EXISTS_BUG */
+
   src_slash = src[src_len - 1] == '/';
   dst_slash = dst[dst_len - 1] == '/';
+
+# if !RENAME_DEST_EXISTS_BUG
+  /* If there are no trailing slashes, then trust the native
+     implementation unless we also suspect issues with hard link
+     detection.  */
   if (!src_slash && !dst_slash)
     return rename (src, dst);
+# endif /* !RENAME_DEST_EXISTS_BUG */
 
   /* Presence of a trailing slash requires directory semantics.  If
      the source does not exist, or if the destination cannot be turned
@@ -178,26 +206,29 @@ rpl_rename (char const *src, char const *dst)
     return -1;
   if (lstat (dst, &dst_st))
     {
-      if (errno != ENOENT || !S_ISDIR (src_st.st_mode))
+      if (errno != ENOENT || (!S_ISDIR (src_st.st_mode) && dst_slash))
         return -1;
     }
-  else if (!S_ISDIR (dst_st.st_mode))
-    {
-      errno = ENOTDIR;
-      return -1;
-    }
-  else if (!S_ISDIR (src_st.st_mode))
+  else
     {
-      errno = EISDIR;
-      return -1;
+      if (S_ISDIR (dst_st.st_mode) != S_ISDIR (src_st.st_mode))
+       {
+         errno = S_ISDIR (dst_st.st_mode) ? EISDIR : ENOTDIR;
+         return -1;
+       }
+# if RENAME_DEST_EXISTS_BUG
+      if (SAME_INODE (src_st, dst_st))
+       return 0;
+# endif /* RENAME_DEST_EXISTS_BUG */
     }
 
-#  if RENAME_TRAILING_SLASH_SOURCE_BUG
+# if RENAME_TRAILING_SLASH_SOURCE_BUG || RENAME_DEST_EXISTS_BUG
   /* If the only bug was that a trailing slash was allowed on a
      non-existing file destination, as in Solaris 10, then we've
      already covered that situation.  But if there is any problem with
      a trailing slash on an existing source or destination, as in
-     Solaris 9, then we must strip the offending slash and check that
+     Solaris 9, or if a directory can overwrite a symlink, as on
+     Cygwin 1.5, then we must strip the offending slash and check that
      we have not encountered a symlink instead of a directory.
 
      Stripping a trailing slash interferes with POSIX semantics, where
@@ -248,7 +279,7 @@ rpl_rename (char const *src, char const *dst)
       else if (S_ISLNK (dst_st.st_mode))
         goto out;
     }
-#  endif /* RENAME_TRAILING_SLASH_SOURCE_BUG */
+# endif /* RENAME_TRAILING_SLASH_SOURCE_BUG || RENAME_DEST_EXISTS_BUG */
 
   ret_val = rename (src_temp, dst_temp);
   rename_errno = errno;
@@ -260,5 +291,4 @@ rpl_rename (char const *src, char const *dst)
   errno = rename_errno;
   return ret_val;
 }
-# endif /* RENAME_TRAILING_SLASH_*_BUG */
 #endif /* ! W32 platform */
index 04921fa..c458f59 100644 (file)
@@ -1,4 +1,4 @@
-# serial 17
+# serial 18
 
 # Copyright (C) 2001, 2003, 2005, 2006, 2009 Free Software Foundation, Inc.
 # This file is free software; the Free Software Foundation
@@ -72,14 +72,27 @@ AC_DEFUN([gl_FUNC_RENAME],
        argument, such as on Solaris 9 or cygwin 1.5.])
   fi
 
-  AC_CACHE_CHECK([whether rename is broken when the destination exists],
-    [gl_cv_func_rename_dest_exists_bug],
-    [case "$host_os" in
-      mingw*) gl_cv_func_rename_dest_exists_bug=yes ;;
-      *) gl_cv_func_rename_dest_exists_bug=no ;;
-    esac
+  dnl Cygwin 1.5.x mistakenly allows rename("dir","file").
+  dnl mingw mistakenly forbids rename("dir1","dir2").
+  dnl These bugs require stripping trailing slash to avoid corrupting
+  dnl symlinks with a trailing slash.
+  AC_CACHE_CHECK([whether rename manages existing destinations correctly],
+    [gl_cv_func_rename_dest_works],
+    [rm -rf conftest.f conftest.d1 conftest.d2
+    touch conftest.f && mkdir conftest.d1 conftest.d2 ||
+      AC_MSG_ERROR([cannot create temporary files])
+    AC_RUN_IFELSE([AC_LANG_PROGRAM([[
+#       include <stdio.h>
+#       include <stdlib.h>
+]], [if (rename ("conftest.d1", "conftest.d2") != 0) return 1;
+     if (rename ("conftest.d2", "conftest.f") == 0) return 2;])],
+      [gl_cv_func_rename_dest_works=yes],
+      [gl_cv_func_rename_dest_works=no],
+      dnl When crosscompiling, assume rename is broken.
+      [gl_cv_func_rename_dest_works="guessing no"])
+    rm -rf conftest.f conftest.f1 conftest.d1 conftest.d2
   ])
-  if test $gl_cv_func_rename_dest_exists_bug = yes; then
+  if test "x$gl_cv_func_rename_dest_works" != xyes; then
     AC_LIBOBJ([rename])
     REPLACE_RENAME=1
     AC_DEFINE([RENAME_DEST_EXISTS_BUG], [1],
index 649c2fa..09baa83 100644 (file)
@@ -8,6 +8,7 @@ m4/rename.m4
 Depends-on:
 dirname
 lstat
+same-inode
 stdbool
 stdio
 strdup