rename: fix another cygwin 1.5 bug
authorEric Blake <ebb9@byu.net>
Thu, 1 Oct 2009 17:57:47 +0000 (11:57 -0600)
committerEric Blake <ebb9@byu.net>
Fri, 2 Oct 2009 12:26:37 +0000 (06:26 -0600)
Cygwin 1.5 sometimes, but not always, failed on rename("dir1","dir2")
when "dir2" exists.  Beef up the m4 tests to more reliably
detect at least one of cygwin's bugs, in spite of not knowing
why the rename only failed sporadically (thankfully, cygwin 1.7
does not have the bug).  Meanwhile, although NetBSD needs to
share the hard link workarounds, it does not need the trailing
dot and directory workarounds, so split the m4 test into two
separate feature checks.

* m4/rename.m4 (gl_FUNC_RENAME): Split cygwin bugs into two
checks.
* lib/rename.c (rpl_rename): Don't penalize NetBSD with
unnecessary cygwin workarounds.  Also work around bug with moving
full directory onto an empty one.
* modules/rename (Depends-on): Add canonicalize-lgpl, rmdir.

Signed-off-by: Eric Blake <ebb9@byu.net>
ChangeLog
lib/rename.c
m4/rename.m4
modules/rename

index 717c6f38c82948da3dd7028b353a024dfedcd53d..d388c41a287accb9ab980b5dbdfcd5aa95473617 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,13 @@
 2009-10-02  Eric Blake  <ebb9@byu.net>
 
+       rename: fix another cygwin 1.5 bug
+       * m4/rename.m4 (gl_FUNC_RENAME): Split cygwin bugs into two
+       checks.
+       * lib/rename.c (rpl_rename): Don't penalize NetBSD with
+       unnecessary cygwin workarounds.  Also work around bug with moving
+       full directory onto an empty one.
+       * modules/rename (Depends-on): Add canonicalize-lgpl, rmdir.
+
        rename-dest-slash: merge into rename module
        * modules/rename-dest-slash (Status): Mark obsolete.
        (Depends-on): Add rename.
index a59328e5d240fd1ed9ffc6efcb59eb1380ba3798..b6a5dabee25ab8f58d63bb768d4dbaf056b93397 100644 (file)
@@ -139,6 +139,7 @@ rpl_rename (char const *src, char const *dst)
 # include <stdlib.h>
 # include <string.h>
 # include <sys/stat.h>
+# include <unistd.h>
 
 # include "dirname.h"
 # include "same-inode.h"
@@ -154,6 +155,7 @@ rpl_rename (char const *src, char const *dst)
   char *dst_temp = (char *) dst;
   bool src_slash;
   bool dst_slash;
+  bool dst_exists;
   int ret_val = -1;
   int rename_errno = ENOTDIR;
   struct stat src_st;
@@ -190,13 +192,13 @@ rpl_rename (char const *src, char const *dst)
   src_slash = src[src_len - 1] == '/';
   dst_slash = dst[dst_len - 1] == '/';
 
-# if !RENAME_DEST_EXISTS_BUG
+# if !RENAME_HARD_LINK_BUG && !RENAME_DEST_EXISTS_BUG
   /* If there are no trailing slashes, then trust the native
      implementation unless we also suspect issues with hard link
-     detection.  */
+     detection or file/directory conflicts.  */
   if (!src_slash && !dst_slash)
     return rename (src, dst);
-# endif /* !RENAME_DEST_EXISTS_BUG */
+# endif /* !RENAME_HARD_LINK_BUG && !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
@@ -208,6 +210,7 @@ rpl_rename (char const *src, char const *dst)
     {
       if (errno != ENOENT || (!S_ISDIR (src_st.st_mode) && dst_slash))
         return -1;
+      dst_exists = false;
     }
   else
     {
@@ -216,13 +219,15 @@ rpl_rename (char const *src, char const *dst)
          errno = S_ISDIR (dst_st.st_mode) ? EISDIR : ENOTDIR;
          return -1;
        }
-# if RENAME_DEST_EXISTS_BUG
+# if RENAME_HARD_LINK_BUG
       if (SAME_INODE (src_st, dst_st))
        return 0;
-# endif /* RENAME_DEST_EXISTS_BUG */
+# endif /* RENAME_HARD_LINK_BUG */
+      dst_exists = true;
     }
 
-# if RENAME_TRAILING_SLASH_SOURCE_BUG || RENAME_DEST_EXISTS_BUG
+# if (RENAME_TRAILING_SLASH_SOURCE_BUG || RENAME_DEST_EXISTS_BUG        \
+      || RENAME_HARD_LINK_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
@@ -281,7 +286,42 @@ rpl_rename (char const *src, char const *dst)
       else if (S_ISLNK (dst_st.st_mode))
         goto out;
     }
-# endif /* RENAME_TRAILING_SLASH_SOURCE_BUG || RENAME_DEST_EXISTS_BUG */
+# endif /* RENAME_TRAILING_SLASH_SOURCE_BUG || RENAME_DEST_EXISTS_BUG
+           || RENAME_HARD_LINK_BUG */
+
+# if RENAME_DEST_EXISTS_BUG
+  /* Cygwin 1.5 sometimes behaves oddly when moving a non-empty
+     directory on top of an empty one (the old directory name can
+     reappear if the new directory tree is removed).  Work around this
+     by removing the target first, but don't remove the target if it
+     is a subdirectory of the source.  */
+  if (dst_exists && S_ISDIR (dst_st.st_mode))
+    {
+      if (src_temp != src)
+        free (src_temp);
+      src_temp = canonicalize_file_name (src);
+      if (dst_temp != dst)
+        free (dst_temp);
+      dst_temp = canonicalize_file_name (dst);
+      if (!src_temp || !dst_temp)
+        {
+          rename_errno = ENOMEM;
+          goto out;
+        }
+      src_len = strlen (src_temp);
+      if (strncmp (src_temp, dst_temp, src_len) == 0
+          && dst_temp[src_len] == '/')
+        {
+          rename_errno = EINVAL;
+          goto out;
+        }
+      if (rmdir (dst))
+        {
+          rename_errno = errno;
+          goto out;
+        }
+    }
+# endif /* RENAME_DEST_EXISTS_BUG */
 
   ret_val = rename (src_temp, dst_temp);
   rename_errno = errno;
index fc0d715b3406e5a795da8fd7a58ceb702b30c4d2..27c9944c6d73678058bea1e8be775464c9d08cfb 100644 (file)
@@ -1,4 +1,4 @@
-# serial 19
+# serial 20
 
 # Copyright (C) 2001, 2003, 2005, 2006, 2009 Free Software Foundation, Inc.
 # This file is free software; the Free Software Foundation
@@ -76,7 +76,39 @@ AC_DEFUN([gl_FUNC_RENAME],
        argument, such as on Solaris 9 or cygwin 1.5.])
   fi
 
-  dnl NetBSD 1.6 mistakenly reduces hard link count on rename("h1","h2").
+  dnl NetBSD 1.6 and cygwin 1.5.x mistakenly reduce hard link count
+  dnl on rename("h1","h2").
+  dnl This bug requires stat'ting targets prior to attempting rename.
+  AC_CACHE_CHECK([whether rename manages hard links correctly],
+    [gl_cv_func_rename_link_works],
+    [rm -rf conftest.f conftest.f1
+    if touch conftest.f && ln conftest.f conftest.f1 &&
+        set x `ls -i conftest.f conftest.f1` && test "$2" = "$4"; then
+      AC_RUN_IFELSE([AC_LANG_PROGRAM([[
+#       include <stdio.h>
+#       include <stdlib.h>
+#       include <unistd.h>
+]], [if (rename ("conftest.f", "conftest.f1")) return 1;
+     if (unlink ("conftest.f1")) return 2;
+     if (rename ("conftest.f", "conftest.f")) return 3;
+     if (rename ("conftest.f1", "conftest.f1") == 0) return 4;])],
+        [gl_cv_func_rename_link_works=yes],
+        [gl_cv_func_rename_link_works=no],
+        dnl When crosscompiling, assume rename is broken.
+        [gl_cv_func_rename_link_works="guessing no"])
+    else
+      gl_cv_func_rename_link_works="guessing no"
+    fi
+    rm -rf conftest.f conftest.f1
+  ])
+  if test "x$gl_cv_func_rename_link_works" != xyes; then
+    AC_LIBOBJ([rename])
+    REPLACE_RENAME=1
+    AC_DEFINE([RENAME_HARD_LINK_BUG], [1],
+      [Define if rename fails to leave hard links alone, as on NetBSD 1.6
+       or Cygwin 1.5.])
+  fi
+
   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
@@ -86,29 +118,22 @@ AC_DEFUN([gl_FUNC_RENAME],
     [rm -rf conftest.f conftest.d1 conftest.d2
     touch conftest.f && mkdir conftest.d1 conftest.d2 ||
       AC_MSG_ERROR([cannot create temporary files])
-    if ln conftest.f conftest.f1 && set x `ls -i conftest.f conftest.f1` &&
-        test "$2" = "$4"; then
-      AC_RUN_IFELSE([AC_LANG_PROGRAM([[
+    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;
-     if (rename ("conftest.f", "conftest.f1")
-         || rename ("conftest.f", "conftest.f")) return 3;])],
-        [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"])
-    else
-      gl_cv_func_rename_dest_works="guessing no"
-    fi
-    rm -rf conftest.f conftest.f1 conftest.d1 conftest.d2
+     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.d1 conftest.d2
   ])
   if test "x$gl_cv_func_rename_dest_works" != xyes; then
     AC_LIBOBJ([rename])
     REPLACE_RENAME=1
     AC_DEFINE([RENAME_DEST_EXISTS_BUG], [1],
       [Define if rename does not work when the destination file exists,
-       as with NetBSD 1.6 on hard links, or Windows on directories.])
+       as on Cygwin 1.5 or Windows.])
   fi
 ])
index 09baa839f1451a14a4f56c367412274123daddad..fbd081c4f238f1336f9f58fd8b65e3dc9493ae03 100644 (file)
@@ -6,8 +6,10 @@ lib/rename.c
 m4/rename.m4
 
 Depends-on:
+canonicalize-lgpl
 dirname
 lstat
+rmdir
 same-inode
 stdbool
 stdio