From 0d54f471901494f8fc5b638fbcc0768a42caddc7 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Thu, 1 Oct 2009 16:46:08 -0600 Subject: [PATCH] rename: fix mingw bugs Copy various workarounds from cygwin 1.5: rename("dir/.","name"), rename("dir","file"), rename("dir1","dir2"). Amazingly, even though mingw stat() has no way to identify hard linked files, and even though rename("hard1","hard2") destroys the hard link, the lower-level MoveFileEx does the right thing! * lib/rename.c (rpl_rename) [W32]: Fix trailing slash and directory overwrite bugs. Signed-off-by: Eric Blake --- ChangeLog | 4 ++ lib/rename.c | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 136 insertions(+), 5 deletions(-) diff --git a/ChangeLog b/ChangeLog index d388c41a2..b2a45bf57 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2009-10-02 Eric Blake + rename: fix mingw bugs + * lib/rename.c (rpl_rename) [W32]: Fix trailing slash and + directory overwrite bugs. + rename: fix another cygwin 1.5 bug * m4/rename.m4 (gl_FUNC_RENAME): Split cygwin bugs into two checks. diff --git a/lib/rename.c b/lib/rename.c index b6a5dabee..abd1ec51a 100644 --- a/lib/rename.c +++ b/lib/rename.c @@ -30,10 +30,16 @@ existing files. */ # include +# include +# include +# include +# include # define WIN32_LEAN_AND_MEAN # include +# 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. */ @@ -41,15 +47,136 @@ int 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. */ + if (dst_exists && S_ISDIR (dst_st.st_mode)) + { + char *cwd = getcwd (NULL, 0); + char *src_temp; + char *dst_temp; + if (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); + dst_temp = chdir (dst) ? NULL : getcwd (NULL, 0); + } + chdir (cwd); + 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) { @@ -117,7 +244,7 @@ rpl_rename (char const *src, char const *dst) 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: -- 2.11.0