/* Work around rename bugs in some systems.
- Copyright (C) 2001, 2002, 2003, 2005, 2006, 2009 Free Software
- Foundation, Inc.
+ Copyright (C) 2001-2003, 2005-2006, 2009-2013 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
existing files. */
# include <errno.h>
+# include <stdbool.h>
+# include <stdlib.h>
+# include <sys/stat.h>
+# include <unistd.h>
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
+# include "dirname.h"
+
/* Rename the file SRC to DST. This replacement is necessary on
Windows, on which the system rename function will not replace
an existing DST. */
rpl_rename (char const *src, char const *dst)
{
int error;
+ size_t src_len = strlen (src);
+ size_t dst_len = strlen (dst);
+ char *src_base = last_component (src);
+ char *dst_base = last_component (dst);
+ bool src_slash;
+ bool dst_slash;
+ bool dst_exists;
+ struct stat src_st;
+ struct stat dst_st;
+
+ /* Filter out dot as last component. */
+ if (!src_len || !dst_len)
+ {
+ errno = ENOENT;
+ return -1;
+ }
+ 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;
+ }
+ }
+
+ /* Presence of a trailing slash requires directory semantics. If
+ the source does not exist, or if the destination cannot be turned
+ into a directory, give up now. Otherwise, strip trailing slashes
+ before calling rename. There are no symlinks on mingw, so stat
+ works instead of lstat. */
+ src_slash = ISSLASH (src[src_len - 1]);
+ dst_slash = ISSLASH (dst[dst_len - 1]);
+ if (stat (src, &src_st))
+ return -1;
+ if (stat (dst, &dst_st))
+ {
+ if (errno != ENOENT || (!S_ISDIR (src_st.st_mode) && dst_slash))
+ return -1;
+ dst_exists = false;
+ }
+ else
+ {
+ if (S_ISDIR (dst_st.st_mode) != S_ISDIR (src_st.st_mode))
+ {
+ errno = S_ISDIR (dst_st.st_mode) ? EISDIR : ENOTDIR;
+ return -1;
+ }
+ dst_exists = true;
+ }
+
+ /* There are no symlinks, so if a file existed with a trailing
+ slash, it must be a directory, and we don't have to worry about
+ stripping strip trailing slash. However, mingw refuses to
+ replace an existing empty directory, so we have to help it out.
+ And canonicalize_file_name is not yet ported to mingw; however,
+ for directories, getcwd works as a viable alternative. Ensure
+ that we can get back to where we started before using it; later
+ attempts to return are fatal. Note that we can end up losing a
+ directory if rename then fails, but it was empty, so not much
+ damage was done. */
+ if (dst_exists && S_ISDIR (dst_st.st_mode))
+ {
+ char *cwd = getcwd (NULL, 0);
+ char *src_temp;
+ char *dst_temp;
+ if (!cwd || chdir (cwd))
+ return -1;
+ if (IS_ABSOLUTE_FILE_NAME (src))
+ {
+ dst_temp = chdir (dst) ? NULL : getcwd (NULL, 0);
+ src_temp = chdir (src) ? NULL : getcwd (NULL, 0);
+ }
+ else
+ {
+ src_temp = chdir (src) ? NULL : getcwd (NULL, 0);
+ if (!IS_ABSOLUTE_FILE_NAME (dst) && chdir (cwd))
+ abort ();
+ dst_temp = chdir (dst) ? NULL : getcwd (NULL, 0);
+ }
+ if (chdir (cwd))
+ abort ();
+ free (cwd);
+ if (!src_temp || !dst_temp)
+ {
+ free (src_temp);
+ free (dst_temp);
+ errno = ENOMEM;
+ return -1;
+ }
+ src_len = strlen (src_temp);
+ if (strncmp (src_temp, dst_temp, src_len) == 0
+ && (ISSLASH (dst_temp[src_len]) || dst_temp[src_len] == '\0'))
+ {
+ error = dst_temp[src_len];
+ free (src_temp);
+ free (dst_temp);
+ if (error)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ return 0;
+ }
+ if (rmdir (dst))
+ {
+ error = errno;
+ free (src_temp);
+ free (dst_temp);
+ errno = error;
+ return -1;
+ }
+ free (src_temp);
+ free (dst_temp);
+ }
- /* MoveFileEx works if SRC is a directory without any flags,
- but fails with MOVEFILE_REPLACE_EXISTING, so try without
- flags first. */
+ /* MoveFileEx works if SRC is a directory without any flags, but
+ fails with MOVEFILE_REPLACE_EXISTING, so try without flags first.
+ Thankfully, MoveFileEx handles hard links correctly, even though
+ rename() does not. */
if (MoveFileEx (src, dst, 0))
return 0;
/* Retry with MOVEFILE_REPLACE_EXISTING if the move failed
- * due to the destination already existing. */
+ due to the destination already existing. */
error = GetLastError ();
if (error == ERROR_FILE_EXISTS || error == ERROR_ALREADY_EXISTS)
{
break;
# ifndef ERROR_FILE_TOO_LARGE
-/* This value is documented but not defined in all versions of windows.h. */
+/* This value is documented but not defined in all versions of windows.h. */
# define ERROR_FILE_TOO_LARGE 223
# endif
case ERROR_FILE_TOO_LARGE:
# include <stdlib.h>
# include <string.h>
# include <sys/stat.h>
+# include <unistd.h>
# include "dirname.h"
# include "same-inode.h"
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;
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
{
if (errno != ENOENT || (!S_ISDIR (src_st.st_mode) && dst_slash))
return -1;
+ dst_exists = false;
}
else
{
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
+ {
+ errno = S_ISDIR (dst_st.st_mode) ? EISDIR : ENOTDIR;
+ return -1;
+ }
+# if RENAME_HARD_LINK_BUG
if (SAME_INODE (src_st, dst_st))
- return 0;
-# endif /* RENAME_DEST_EXISTS_BUG */
+ return 0;
+# 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
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. Note that we can end up losing
+ a directory if rename then fails, but it was empty, so not much
+ damage was done. */
+ if (dst_exists && S_ISDIR (dst_st.st_mode))
+ {
+ if (src_st.st_dev != dst_st.st_dev)
+ {
+ rename_errno = EXDEV;
+ goto out;
+ }
+ 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;