# HG changeset patch # User Eric Blake # Date 1254264179 21600 # Node ID 724dd32f13f79dcdaa192abdd020aaa4aeca74f7 # Parent 67458384fb3fa5e681c60222d11dcc770b0b4e98 rename: fix cygwin 1.5.x bugs 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. diff --git a/ChangeLog b/ChangeLog --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,10 @@ 2009-10-02 Eric Blake + 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 diff --git a/lib/rename.c b/lib/rename.c --- a/lib/rename.c +++ b/lib/rename.c @@ -134,17 +134,14 @@ #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 +# include +# include +# include +# include -# include -# include -# include -# include -# include - -# 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 @@ 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 @@ 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)) + else { - errno = ENOTDIR; - return -1; - } - else if (!S_ISDIR (src_st.st_mode)) - { - 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 @@ 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 @@ errno = rename_errno; return ret_val; } -# endif /* RENAME_TRAILING_SLASH_*_BUG */ #endif /* ! W32 platform */ diff --git a/m4/rename.m4 b/m4/rename.m4 --- a/m4/rename.m4 +++ b/m4/rename.m4 @@ -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 @@ 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 +# include +]], [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], diff --git a/modules/rename b/modules/rename --- a/modules/rename +++ b/modules/rename @@ -8,6 +8,7 @@ Depends-on: dirname lstat +same-inode stdbool stdio strdup