From: Florian Eder Subject: [PATCH v2 5/6] robocopy: Add basic copy logic and error handling Message-Id: <20210915215700.424685-5-others.meder@gmail.com> Date: Wed, 15 Sep 2021 21:56:59 +0000 In-Reply-To: <20210915215700.424685-1-others.meder@gmail.com> References: <20210915215700.424685-1-others.meder@gmail.com> Reads all files in the source folder that match any of the files to include and copies them to the destination, creating necessary folders in the process Signed-off-by: Florian Eder --- Combined patches 5, 6 and 11 from V1 to one patch, replaced open coded function with PathAllocCombine, removed some obvious comments and removed pointless check whether some directory is a file or directory in get_file_paths_in_folder as any error thrown by FindFirstFile is caught anyway Still does not have any kind of max depth, so the complete source directory tree will be copied Import of shlwapi is required for PathIsDirectoryW, which is AFAIAA not exported by kernelbase :-/ --- programs/robocopy/Makefile.in | 2 +- programs/robocopy/main.c | 166 +++++++++++++++++++++++++++++++++- programs/robocopy/robocopy.h | 17 ++++ programs/robocopy/robocopy.rc | 6 ++ 4 files changed, 188 insertions(+), 3 deletions(-) diff --git a/programs/robocopy/Makefile.in b/programs/robocopy/Makefile.in index 0f4f5c76119..1c640599b4b 100644 --- a/programs/robocopy/Makefile.in +++ b/programs/robocopy/Makefile.in @@ -1,5 +1,5 @@ MODULE = robocopy.exe -IMPORTS = kernelbase +IMPORTS = kernelbase shlwapi EXTRADLLFLAGS = -mconsole -municode -mno-cygwin diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index fc16fa1c2a2..16d1ba16445 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -23,6 +23,8 @@ WINE_DEFAULT_DEBUG_CHANNEL(robocopy); #include #include #include +#include +#include #include "robocopy.h" struct robocopy_options options; @@ -71,6 +73,21 @@ static void output_message(const WCHAR *format_string, ...) LocalFree(string); } +static void output_error(UINT format_string_id, HRESULT error_code, WCHAR* path) +{ + WCHAR *error_string, error_code_long[64], error_code_short[64]; + + FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER, + NULL, error_code, 0, (LPWSTR)&error_string, 0, NULL); + + swprintf(error_code_long, ARRAY_SIZE(error_code_long), L"0x%08x", error_code); + swprintf(error_code_short, ARRAY_SIZE(error_code_short), L"%u", error_code); + + output_message(format_string(format_string_id), L"", error_code_short, error_code_long, path, error_string); + + LocalFree(error_string); +} + static WCHAR *strip_path_prefix(WCHAR* path) { /* returns a path without the \\?\ prefix */ @@ -145,6 +162,134 @@ static void parse_arguments(int argc, WCHAR *argv[]) } } +static BOOL matches_array_entry(WCHAR *name, struct path_array *names_to_match) +{ + int i; + for (i = 0; i < names_to_match->size; i++) + { + if (PathMatchSpecW(name, names_to_match->array[i])) return TRUE; + } + return FALSE; +} + +static BOOL create_directory_path(WCHAR *path) +{ + WCHAR *pointer, *current_folder; + current_folder = calloc(wcslen(path) + 1, sizeof(WCHAR)); + /* ignore the "\\?\" prefix, so that those backslashes are not matched */ + pointer = wcschr(strip_path_prefix(path), L'\\'); + while (pointer != NULL) + { + if (!lstrcpynW(current_folder, path, pointer - path + 2)) return FALSE; + /* try to create the folder, ignoring any failure due to ERROR_ALREADY_EXISTS */ + if (!CreateDirectoryW(current_folder, NULL)) + { + if (GetLastError() != ERROR_ALREADY_EXISTS) + { + output_error(STRING_ERROR_WRITE_DIRECTORY, GetLastError(), strip_path_prefix(current_folder)); + return FALSE; + } + } + else + output_message(format_string(STRING_CREATE_DIRECTORY), strip_path_prefix(current_folder)); + pointer = wcschr(pointer + 1, L'\\'); + } + return TRUE; +} + +static void get_file_paths_in_folder(WCHAR *directory_path, struct list *paths) +{ + HANDLE temp_handle; + struct path *new_path, *current_path; + WIN32_FIND_DATAW entry_data; + WCHAR *parent_absolute_path, *current_relative_path, *current_absolute_path, *current_search_path; + + list_init(paths); + + /* initialize list with a empty relative path */ + new_path = calloc(1, sizeof(struct path)); + new_path->name = calloc(2, sizeof(WCHAR)); + list_add_tail(paths, &new_path->entry); + + LIST_FOR_EACH_ENTRY(current_path, paths, struct path, entry) + { + /* append relative path to the (prefix) directory path */ + PathAllocCombine(directory_path, current_path->name, PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH, &parent_absolute_path); + + /* append * to recieve every file / subdirectory in this directory */ + PathAllocCombine(parent_absolute_path, L"*", PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH, ¤t_search_path); + /* walk through all files / directories in this directory */ + temp_handle = FindFirstFileExW(current_search_path, FindExInfoStandard, &entry_data, FindExSearchNameMatch, NULL, 0); + if (temp_handle != INVALID_HANDLE_VALUE) + { + do + { + /* Ignore . and .. entries */ + if (!wcscmp(L".", entry_data.cFileName) || !wcscmp(L"..", entry_data.cFileName)) continue; + + PathAllocCombine(current_path->name, entry_data.cFileName, PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH, ¤t_relative_path); + PathAllocCombine(directory_path, current_relative_path, PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH, ¤t_absolute_path); + + /* If this entry is a matching file or empty directory, add it to the list of results */ + if ((!PathIsDirectoryW(current_absolute_path) && matches_array_entry(entry_data.cFileName, options.files)) || + (PathIsDirectoryW(current_absolute_path))) + { + new_path = calloc(1, sizeof(struct path)); + new_path->name = wcsdup(current_relative_path); + list_add_tail(paths, &new_path->entry); + } + } + while (FindNextFileW(temp_handle, &entry_data) != 0); + } + } +} + +static BOOL perform_copy(void) +{ + struct list paths_source; + struct path *current_path; + WCHAR *current_absolute_path, *target_path; + + list_init(&paths_source); + + if (!PathIsDirectoryW(options.source)) + { + output_error(STRING_ERROR_READ_DIRECTORY, ERROR_FILE_NOT_FOUND, strip_path_prefix(options.source)); + return FALSE; + } + + create_directory_path(options.destination); + + /* get files in the source folder */ + get_file_paths_in_folder(options.source, &paths_source); + + /* walk through files in the source folder */ + LIST_FOR_EACH_ENTRY(current_path, &paths_source, struct path, entry) + { + PathAllocCombine(options.source, current_path->name, PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH, ¤t_absolute_path); + + /* append the relative source path to the destination to get the target path */ + PathAllocCombine(options.destination, current_path->name, PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH, &target_path); + + if (PathIsDirectoryW(current_absolute_path)) + { + if (!create_directory_path(target_path)) + output_error(STRING_ERROR_WRITE_DIRECTORY, GetLastError(), strip_path_prefix(target_path)); + } + else + { + create_directory_path(target_path); + if (!CopyFileW(current_absolute_path, target_path, FALSE)) + output_error(STRING_ERROR_WRITE_FILE, GetLastError(), strip_path_prefix(target_path)); + else + { + output_message(format_string(STRING_CREATE_FILE), strip_path_prefix(target_path)); + } + } + } + return TRUE; +} + static void print_header(void) { UINT i; @@ -166,8 +311,25 @@ int __cdecl wmain(int argc, WCHAR *argv[]) { parse_arguments(argc, argv); + /* If no file filters are set, set *.* to include all files */ + if (options.files->size == 0) + { + options.files->array[options.files->size] = calloc(64, sizeof(WCHAR)); + wcscpy(options.files->array[0], L"*.*"); + options.files->size++; + } + print_header(); - WINE_FIXME("robocopy stub"); - return 0; + /* Break if Source or Destination not set */ + if (!options.destination || !options.source) + { + output_message(format_string(STRING_MISSING_DESTINATION_OR_SOURCE)); + return ROBOCOPY_ERROR_NO_FILES_COPIED; + } + + if (!perform_copy()) + return ROBOCOPY_ERROR_NO_FILES_COPIED; + + return ROBOCOPY_NO_ERROR_FILES_COPIED; } diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index f5c2ac56fcf..86be775da01 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -18,6 +18,13 @@ #define WIN32_LEAN_AND_MEAN #include +#include + +struct path +{ + struct list entry; + WCHAR *name; +}; struct path_array { @@ -32,8 +39,18 @@ struct robocopy_options struct path_array *files; }; +/* Exit codes */ +#define ROBOCOPY_NO_ERROR_FILES_COPIED 1 +#define ROBOCOPY_ERROR_NO_FILES_COPIED 16 + /* Resource strings */ #define STRING_HEADER 1000 #define STRING_SOURCE 1003 #define STRING_DESTINATION 1004 #define STRING_FILES 1005 +#define STRING_MISSING_DESTINATION_OR_SOURCE 1010 +#define STRING_ERROR_READ_DIRECTORY 1011 +#define STRING_ERROR_WRITE_DIRECTORY 1012 +#define STRING_ERROR_WRITE_FILE 1014 +#define STRING_CREATE_DIRECTORY 1019 +#define STRING_CREATE_FILE 1022 diff --git a/programs/robocopy/robocopy.rc b/programs/robocopy/robocopy.rc index 3a2ddc9474f..fd74722ae63 100644 --- a/programs/robocopy/robocopy.rc +++ b/programs/robocopy/robocopy.rc @@ -29,6 +29,12 @@ STRINGTABLE STRING_SOURCE, " Source: %1\n" STRING_DESTINATION, " Destination: %1\n\n" STRING_FILES, " Files: %1\n" + STRING_MISSING_DESTINATION_OR_SOURCE, "No destination or source specified, can't copy anything\n" + STRING_ERROR_READ_DIRECTORY, "[%1] Error %2 (%3) occurred reading directory \"%4\":\n%5\n" + STRING_ERROR_WRITE_DIRECTORY, "[%1] Error %2 (%3) occurred writing directory \"%4\":\n%5\n" + STRING_ERROR_WRITE_FILE, "[%1] Error %2 (%3) occurred writing file \"%4\":\n%5\n" + STRING_CREATE_DIRECTORY, " Created Dir: %1\n" + STRING_CREATE_FILE, " Copied File: %1\n" } LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -- 2.32.0