From ec4440cf5ee9b885957a774354894b62713258c5 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Sat, 14 Dec 2013 10:29:42 +0200 Subject: [PATCH] Fix copy-file on MS-Windows with file names outside of current locale. src/fileio.c (Fcopy_file) [WINDOWSNT]: Move most of the Windows-specific code to w32.c. Change error message text to match that of Posix platforms. src/w32.c (w32_copy_file): New function, most of the code copied and reworked from Fcopy_file. Improve error handling. Plug memory leak when errors are thrown. Support file names outside of the current codepage. (Bug#7100) --- src/ChangeLog | 11 +++++ src/fileio.c | 60 +++++++-------------------- src/w32.c | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/w32.h | 1 + 4 files changed, 137 insertions(+), 46 deletions(-) diff --git a/src/ChangeLog b/src/ChangeLog index 4bd5191d5c6..df145600556 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,3 +1,14 @@ +2013-12-14 Eli Zaretskii + + * fileio.c (Fcopy_file) [WINDOWSNT]: Move most of the + Windows-specific code to w32.c. Change error message text to + match that of Posix platforms. + + * w32.c (w32_copy_file): New function, most of the code copied and + reworked from Fcopy_file. Improve error handling. Plug memory + leak when errors are thrown. Support file names outside of the + current codepage. (Bug#7100) + 2013-12-13 Paul Eggert * lread.c (load_path_default): Prototype. diff --git a/src/fileio.c b/src/fileio.c index f6c31ebf1b9..deb913cbdac 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -1959,7 +1959,7 @@ entries (depending on how Emacs was built). */) int conlength = 0; #endif #ifdef WINDOWSNT - acl_t acl = NULL; + int result; #endif encoded_file = encoded_newname = Qnil; @@ -1996,52 +1996,20 @@ entries (depending on how Emacs was built). */) out_st.st_mode = 0; #ifdef WINDOWSNT - if (!NILP (preserve_extended_attributes)) - { - acl = acl_get_file (SDATA (encoded_file), ACL_TYPE_ACCESS); - if (acl == NULL && acl_errno_valid (errno)) - report_file_error ("Getting ACL", file); - } - if (!CopyFile (SDATA (encoded_file), - SDATA (encoded_newname), - FALSE)) - { - /* CopyFile doesn't set errno when it fails. By far the most - "popular" reason is that the target is read-only. */ - report_file_errno ("Copying file", list2 (file, newname), - GetLastError () == 5 ? EACCES : EPERM); - } - /* CopyFile retains the timestamp by default. */ - else if (NILP (keep_time)) - { - struct timespec now; - DWORD attributes; - char * filename; - - filename = SDATA (encoded_newname); - - /* Ensure file is writable while its modified time is set. */ - attributes = GetFileAttributes (filename); - SetFileAttributes (filename, attributes & ~FILE_ATTRIBUTE_READONLY); - now = current_timespec (); - if (set_file_times (-1, filename, now, now)) - { - /* Restore original attributes. */ - SetFileAttributes (filename, attributes); - xsignal2 (Qfile_date_error, - build_string ("Cannot set file date"), newname); - } - /* Restore original attributes. */ - SetFileAttributes (filename, attributes); - } - if (acl != NULL) + result = w32_copy_file (SSDATA (encoded_file), SSDATA (encoded_newname), + !NILP (keep_time), !NILP (preserve_uid_gid), + !NILP (preserve_extended_attributes)); + switch (result) { - bool fail = - acl_set_file (SDATA (encoded_newname), ACL_TYPE_ACCESS, acl) != 0; - if (fail && acl_errno_valid (errno)) - report_file_error ("Setting ACL", newname); - - acl_free (acl); + case -1: + report_file_error ("Copying file", list2 (file, newname)); + case -2: + report_file_error ("Copying permissions from", file); + case -3: + xsignal2 (Qfile_date_error, + build_string ("Resetting file times"), newname); + case -4: + report_file_error ("Copying permissions to", newname); } #else /* not WINDOWSNT */ immediate_quit = 1; diff --git a/src/w32.c b/src/w32.c index e5488642118..e4678637cbb 100644 --- a/src/w32.c +++ b/src/w32.c @@ -140,6 +140,7 @@ typedef struct _PROCESS_MEMORY_COUNTERS_EX { #include #include +#include /* This is not in MinGW's sddl.h (but they are in MSVC headers), so we define them by hand if not already defined. */ @@ -6001,6 +6002,116 @@ careadlinkat (int fd, char const *filename, return NULL; } +int +w32_copy_file (const char *from, const char *to, + int keep_time, int preserve_ownership, int copy_acls) +{ + acl_t acl = NULL; + BOOL copy_result; + wchar_t from_w[MAX_PATH], to_w[MAX_PATH]; + char from_a[MAX_PATH], to_a[MAX_PATH]; + + /* We ignore preserve_ownership for now. */ + preserve_ownership = preserve_ownership; + + if (copy_acls) + { + acl = acl_get_file (from, ACL_TYPE_ACCESS); + if (acl == NULL && acl_errno_valid (errno)) + return -2; + } + if (w32_unicode_filenames) + { + filename_to_utf16 (from, from_w); + filename_to_utf16 (to, to_w); + copy_result = CopyFileW (from_w, to_w, FALSE); + } + else + { + filename_to_ansi (from, from_a); + filename_to_ansi (to, to_a); + copy_result = CopyFileA (from_a, to_a, FALSE); + } + if (!copy_result) + { + /* CopyFile doesn't set errno when it fails. By far the most + "popular" reason is that the target is read-only. */ + DWORD err = GetLastError (); + + switch (err) + { + case ERROR_FILE_NOT_FOUND: + errno = ENOENT; + break; + case ERROR_ACCESS_DENIED: + errno = EACCES; + break; + case ERROR_ENCRYPTION_FAILED: + errno = EIO; + break; + default: + errno = EPERM; + break; + } + + if (acl) + acl_free (acl); + return -1; + } + /* CopyFile retains the timestamp by default. However, see + "Community Additions" for CopyFile: it sounds like that is not + entirely true. Testing on Windows XP confirms that modified time + is copied, but creation and last-access times are not. + FIXME? */ + else if (!keep_time) + { + struct timespec now; + DWORD attributes; + + if (w32_unicode_filenames) + { + /* Ensure file is writable while its times are set. */ + attributes = GetFileAttributesW (to_w); + SetFileAttributesW (to_w, attributes & ~FILE_ATTRIBUTE_READONLY); + now = current_timespec (); + if (set_file_times (-1, to, now, now)) + { + /* Restore original attributes. */ + SetFileAttributesW (to_w, attributes); + if (acl) + acl_free (acl); + return -3; + } + /* Restore original attributes. */ + SetFileAttributesW (to_w, attributes); + } + else + { + attributes = GetFileAttributesA (to_a); + SetFileAttributesA (to_a, attributes & ~FILE_ATTRIBUTE_READONLY); + now = current_timespec (); + if (set_file_times (-1, to, now, now)) + { + SetFileAttributesA (to_a, attributes); + if (acl) + acl_free (acl); + return -3; + } + SetFileAttributesA (to_a, attributes); + } + } + if (acl != NULL) + { + bool fail = + acl_set_file (to, ACL_TYPE_ACCESS, acl) != 0; + acl_free (acl); + if (fail && acl_errno_valid (errno)) + return -4; + } + + return 0; +} + /* Support for browsing other processes and their attributes. See process.c for the Lisp bindings. */ diff --git a/src/w32.h b/src/w32.h index cca95855a78..74460a50440 100644 --- a/src/w32.h +++ b/src/w32.h @@ -185,6 +185,7 @@ extern int filename_to_ansi (const char *, char *); extern int filename_from_utf16 (const wchar_t *, char *); extern int filename_to_utf16 (const char *, wchar_t *); extern Lisp_Object ansi_encode_filename (Lisp_Object); +extern int w32_copy_file (const char *, const char *, int, int, int); extern BOOL init_winsock (int load_now); extern void srandom (int); -- 2.39.2