Basic files and required changes to configure(.ac) to create a stub version of the robocopy utility
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=43653 Signed-off-by: Florian Eder others.meder@gmail.com --- v3: Identical to the patch sent in v2 --- configure | 2 +- configure.ac | 1 + programs/robocopy/Makefile.in | 8 ++++++++ programs/robocopy/main.c | 29 +++++++++++++++++++++++++++++ programs/robocopy/robocopy.rc | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 programs/robocopy/Makefile.in create mode 100644 programs/robocopy/main.c create mode 100644 programs/robocopy/robocopy.rc
diff --git a/configure b/configure index 00a5be6a504..a5767af3c60 100755 --- a/configure +++ b/configure @@ -21234,6 +21234,7 @@ wine_fn_config_makefile programs/regedit/tests enable_tests wine_fn_config_makefile programs/regini enable_regini wine_fn_config_makefile programs/regsvcs enable_regsvcs wine_fn_config_makefile programs/regsvr32 enable_regsvr32 +wine_fn_config_makefile programs/robocopy enable_robocopy wine_fn_config_makefile programs/rpcss enable_rpcss wine_fn_config_makefile programs/rundll.exe16 enable_win16 wine_fn_config_makefile programs/rundll32 enable_rundll32 @@ -22767,4 +22768,3 @@ IFS="$ac_save_IFS" $as_echo " $as_me: Finished. Do '${ac_make}' to compile Wine. " >&6 - diff --git a/configure.ac b/configure.ac index dea5fe61bf3..97c46565db3 100644 --- a/configure.ac +++ b/configure.ac @@ -3958,6 +3958,7 @@ WINE_CONFIG_MAKEFILE(programs/regedit/tests) WINE_CONFIG_MAKEFILE(programs/regini) WINE_CONFIG_MAKEFILE(programs/regsvcs) WINE_CONFIG_MAKEFILE(programs/regsvr32) +WINE_CONFIG_MAKEFILE(programs/robocopy) WINE_CONFIG_MAKEFILE(programs/rpcss) WINE_CONFIG_MAKEFILE(programs/rundll.exe16,enable_win16) WINE_CONFIG_MAKEFILE(programs/rundll32) diff --git a/programs/robocopy/Makefile.in b/programs/robocopy/Makefile.in new file mode 100644 index 00000000000..5a48be3725b --- /dev/null +++ b/programs/robocopy/Makefile.in @@ -0,0 +1,8 @@ +MODULE = robocopy.exe + +EXTRADLLFLAGS = -mconsole -municode -mno-cygwin + +C_SRCS = \ + main.c + +RC_SRCS = robocopy.rc diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c new file mode 100644 index 00000000000..00dacc8ab6c --- /dev/null +++ b/programs/robocopy/main.c @@ -0,0 +1,29 @@ +/* + * Copyright 2021 Florian Eder + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "wine/debug.h" +WINE_DEFAULT_DEBUG_CHANNEL(robocopy); + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +int __cdecl wmain(int argc, WCHAR *argv[]) +{ + WINE_FIXME("robocopy stub"); + return 0; +} diff --git a/programs/robocopy/robocopy.rc b/programs/robocopy/robocopy.rc new file mode 100644 index 00000000000..edae5b71d86 --- /dev/null +++ b/programs/robocopy/robocopy.rc @@ -0,0 +1,34 @@ +/* + * Copyright 2021 Florian Eder + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <windef.h> + +#pragma makedep po + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL + +#define WINE_FILEDESCRIPTION_STR "Wine Robocopy" +#define WINE_FILENAME_STR "robocopy.exe" +#define WINE_FILETYPE VFT_APP +#define WINE_FILEVERSION 5,1,10,1027 +#define WINE_FILEVERSION_STR "5.1.10.1027" + +#define WINE_PRODUCTVERSION 5,1,10,1027 +#define WINE_PRODUCTVERSION_STR "XP027" + +#include "wine/wine_common_ver.rc"
Basic files and required changes to configure(.ac) to create a scaffolding for the conformance tests for the robocopy utility
Signed-off-by: Florian Eder others.meder@gmail.com --- v3: Identical to patch sent in v2 --- configure | 1 + configure.ac | 1 + programs/robocopy/tests/Makefile.in | 4 ++ programs/robocopy/tests/robocopy.c | 66 +++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+) create mode 100644 programs/robocopy/tests/Makefile.in create mode 100644 programs/robocopy/tests/robocopy.c
diff --git a/configure b/configure index a5767af3c60..d63321522f4 100755 --- a/configure +++ b/configure @@ -21235,6 +21235,7 @@ wine_fn_config_makefile programs/regini enable_regini wine_fn_config_makefile programs/regsvcs enable_regsvcs wine_fn_config_makefile programs/regsvr32 enable_regsvr32 wine_fn_config_makefile programs/robocopy enable_robocopy +wine_fn_config_makefile programs/robocopy/tests enable_tests wine_fn_config_makefile programs/rpcss enable_rpcss wine_fn_config_makefile programs/rundll.exe16 enable_win16 wine_fn_config_makefile programs/rundll32 enable_rundll32 diff --git a/configure.ac b/configure.ac index 97c46565db3..6d89ceea089 100644 --- a/configure.ac +++ b/configure.ac @@ -3959,6 +3959,7 @@ WINE_CONFIG_MAKEFILE(programs/regini) WINE_CONFIG_MAKEFILE(programs/regsvcs) WINE_CONFIG_MAKEFILE(programs/regsvr32) WINE_CONFIG_MAKEFILE(programs/robocopy) +WINE_CONFIG_MAKEFILE(programs/robocopy/tests) WINE_CONFIG_MAKEFILE(programs/rpcss) WINE_CONFIG_MAKEFILE(programs/rundll.exe16,enable_win16) WINE_CONFIG_MAKEFILE(programs/rundll32) diff --git a/programs/robocopy/tests/Makefile.in b/programs/robocopy/tests/Makefile.in new file mode 100644 index 00000000000..d86e70f4c43 --- /dev/null +++ b/programs/robocopy/tests/Makefile.in @@ -0,0 +1,4 @@ +TESTDLL = robocopy.exe + +C_SRCS = \ + robocopy.c diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c new file mode 100644 index 00000000000..02798663ba5 --- /dev/null +++ b/programs/robocopy/tests/robocopy.c @@ -0,0 +1,66 @@ +/* + * Copyright 2021 Florian Eder + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <wine/test.h> +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <wchar.h> + +static DWORD execute_robocopy(const WCHAR *cmd_line) +{ + STARTUPINFOW startup_info; + PROCESS_INFORMATION process_info; + DWORD exit_code; + WCHAR cmd_line_copy[2048]; + + memset(&startup_info, 0, sizeof(STARTUPINFOW)); + startup_info.dwFlags = STARTF_USESTDHANDLES; + + /* CreateProcess must not be called with static strings */ + wcscpy(cmd_line_copy, cmd_line); + + if (!CreateProcessW(NULL, cmd_line_copy, NULL, NULL, TRUE, 0, NULL, NULL, &startup_info, &process_info)) + return -1; + + if (WaitForSingleObject(process_info.hProcess, 30000) == WAIT_TIMEOUT) + return -1; + + GetExitCodeProcess(process_info.hProcess, &exit_code); + + CloseHandle(process_info.hThread); + CloseHandle(process_info.hProcess); + + return exit_code; +} + +START_TEST(robocopy) +{ + WCHAR previous_cwd_path[4096], temp_path[4096]; + + ok(GetFullPathNameW(L".", ARRAY_SIZE(previous_cwd_path), previous_cwd_path, NULL) != 0, "couldn't get CWD path"); + ok(GetTempPathW(ARRAY_SIZE(temp_path), temp_path) != 0, "couldn't get temp folder path"); + + /* robocopy is only available from Vista onwards, abort test if not available */ + if (execute_robocopy(L"robocopy.exe") == -1) return; + + ok(SetCurrentDirectoryW(temp_path), "couldn't set CWD to temp folder "%S"", temp_path); + + /* TODO: conformance tests here */ + + ok(SetCurrentDirectoryW(previous_cwd_path), "couldn't set CWD to previous CWD folder "%S"", previous_cwd_path); +}
Signed-off-by: Zebediah Figura zfigura@codeweavers.com
Florian Eder others.meder@gmail.com writes:
- WCHAR previous_cwd_path[4096], temp_path[4096];
- ok(GetFullPathNameW(L".", ARRAY_SIZE(previous_cwd_path), previous_cwd_path, NULL) != 0, "couldn't get CWD path");
- ok(GetTempPathW(ARRAY_SIZE(temp_path), temp_path) != 0, "couldn't get temp folder path");
- /* robocopy is only available from Vista onwards, abort test if not available */
- if (execute_robocopy(L"robocopy.exe") == -1) return;
- ok(SetCurrentDirectoryW(temp_path), "couldn't set CWD to temp folder "%S"", temp_path);
- /* TODO: conformance tests here */
- ok(SetCurrentDirectoryW(previous_cwd_path), "couldn't set CWD to previous CWD folder "%S"", previous_cwd_path);
Restoring the current directory on exit is not useful.
Parses (relative / absolute) path arguments as source, destination and files to include
Signed-off-by: Florian Eder others.meder@gmail.com --- v3: Replaced manual string allocation / copying with wcsdup, used debugstr_w in WINE_FIXME and made get_absolute_path static with a const string argument --- programs/robocopy/Makefile.in | 1 + programs/robocopy/main.c | 73 +++++++++++++++++++++++++++++++++++ programs/robocopy/robocopy.h | 33 ++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 programs/robocopy/robocopy.h
diff --git a/programs/robocopy/Makefile.in b/programs/robocopy/Makefile.in index 5a48be3725b..0f4f5c76119 100644 --- a/programs/robocopy/Makefile.in +++ b/programs/robocopy/Makefile.in @@ -1,4 +1,5 @@ MODULE = robocopy.exe +IMPORTS = kernelbase
EXTRADLLFLAGS = -mconsole -municode -mno-cygwin
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 00dacc8ab6c..2f63e4dbbc4 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -21,9 +21,82 @@ WINE_DEFAULT_DEBUG_CHANNEL(robocopy);
#define WIN32_LEAN_AND_MEAN #include <windows.h> +#include <stdlib.h> +#include <pathcch.h> +#include "robocopy.h" + +struct robocopy_options options; + +static WCHAR *get_absolute_path(const WCHAR *path) +{ + DWORD size; + WCHAR *absolute_path; + + /* allocate absolute path + potential backslash + null WCHAR */ + size = GetFullPathNameW(path, 0, NULL, NULL) + 2; + if (!wcsnicmp(path, L"\\?\", 4)) + { + /* already prefixed with \?\ */ + absolute_path = calloc(size, sizeof(WCHAR)); + GetFullPathNameW(path, size, absolute_path, NULL); + PathCchAddBackslashEx(absolute_path, size, NULL, NULL); + } + else + { + /* not prefixed with \?, we must add it in front of the path */ + absolute_path = calloc(size + 4, sizeof(WCHAR)); + wcscpy(absolute_path, L"\\?\"); + GetFullPathNameW(path, size, &(absolute_path[4]), NULL); + PathCchAddBackslashEx(absolute_path, size + 4, NULL, NULL); + } + return absolute_path; +} + +static void parse_arguments(int argc, WCHAR *argv[]) +{ + int i; + + memset(&options, 0, sizeof(options)); + options.files = calloc(1, offsetof(struct path_array, array[argc])); + + for (i = 1; i < argc; i++) + { + /* + * Robocopy switches contain one (and only one) backslash at the start + * /xf => valid flag + * /r:1 => valid flag + * /r:1aö => valid flag + * /r:1aö/ => not a valid flag, is interpreted as a filename + */ + if ((argv[i][0] == L'/') && (wcschr(argv[i] + 1, L'/') == NULL)) + WINE_FIXME("encountered an unknown robocopy flag: %s\n", debugstr_w(argv[i])); + else + { + /* + *(Probably) not a flag, we can parse it as the source / the destination / a filename + * Priority: Source > Destination > (more than one) File + */ + if (!options.source) + { + options.source = get_absolute_path(argv[i]); + } + else if (!options.destination) + { + options.destination = get_absolute_path(argv[i]); + } + else + { + options.files->array[options.files->size] = wcsdup(argv[i]); + options.files->size++; + } + } + } +}
int __cdecl wmain(int argc, WCHAR *argv[]) { + parse_arguments(argc, argv); + WINE_FIXME("robocopy stub"); return 0; } diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h new file mode 100644 index 00000000000..f62eb92c663 --- /dev/null +++ b/programs/robocopy/robocopy.h @@ -0,0 +1,33 @@ +/* + * Copyright 2021 Florian Eder + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +struct path_array +{ + UINT size; + WCHAR *array[1]; +}; + +struct robocopy_options +{ + WCHAR *destination; + WCHAR *source; + struct path_array *files; +};
Signed-off-by: Zebediah Figura zfigura@codeweavers.com
Florian Eder others.meder@gmail.com writes:
+static WCHAR *get_absolute_path(const WCHAR *path) +{
- DWORD size;
- WCHAR *absolute_path;
- /* allocate absolute path + potential backslash + null WCHAR */
- size = GetFullPathNameW(path, 0, NULL, NULL) + 2;
- if (!wcsnicmp(path, L"\\?\", 4))
- {
/* already prefixed with \\?\ */
absolute_path = calloc(size, sizeof(WCHAR));
GetFullPathNameW(path, size, absolute_path, NULL);
PathCchAddBackslashEx(absolute_path, size, NULL, NULL);
- }
- else
- {
/* not prefixed with \\?\, we must add it in front of the path */
absolute_path = calloc(size + 4, sizeof(WCHAR));
wcscpy(absolute_path, L"\\\\?\\");
GetFullPathNameW(path, size, &(absolute_path[4]), NULL);
PathCchAddBackslashEx(absolute_path, size + 4, NULL, NULL);
- }
Why do you want to add \?\ to all paths?
Adds output method and prints a header and the source / destination / files to include back to the user
Signed-off-by: Florian Eder others.meder@gmail.com --- v3: Print the first file not on the same line as the actual "Files:" string, but below it with a fixed offset --- programs/robocopy/main.c | 70 +++++++++++++++++++++++++++++++++++ programs/robocopy/robocopy.h | 6 +++ programs/robocopy/robocopy.rc | 11 ++++++ 3 files changed, 87 insertions(+)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 2f63e4dbbc4..21527c12c8b 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -27,6 +27,57 @@ WINE_DEFAULT_DEBUG_CHANNEL(robocopy);
struct robocopy_options options;
+static const WCHAR *format_string(UINT format_string_id) +{ + WCHAR format_string[2048]; + if (!LoadStringW(GetModuleHandleW(NULL), format_string_id, format_string, ARRAY_SIZE(format_string))) + { + WINE_ERR("invalid string loaded"); + return L""; + } + return wcsdup(format_string); +} + +static void output_message(const WCHAR *format_string, ...) +{ + __ms_va_list va_args; + WCHAR *string; + DWORD length, bytes_written; + + __ms_va_start(va_args, format_string); + length = FormatMessageW(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, + format_string, 0, 0, (LPWSTR)&string, 0, &va_args); + __ms_va_end(va_args); + if (!length) + { + WINE_ERR("string formation failed"); + return; + } + + /* If WriteConsole fails, the output is being redirected to a file */ + if (!WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), string, wcslen(string), &bytes_written, NULL)) + { + CHAR *string_multibyte; + DWORD length_multibyte; + + length_multibyte = WideCharToMultiByte(GetConsoleOutputCP(), 0, string, wcslen(string), NULL, 0, NULL, NULL); + string_multibyte = malloc(length_multibyte); + + WideCharToMultiByte(GetConsoleOutputCP(), 0, string, wcslen(string), string_multibyte, length_multibyte, NULL, NULL); + WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), string_multibyte, length_multibyte, &bytes_written, NULL); + free(string_multibyte); + } + + LocalFree(string); +} + +static WCHAR *strip_path_prefix(WCHAR* path) +{ + /* returns a path without the \?\ prefix */ + if (wcslen(path) <= 4) return path; + return &(path[4]); +} + static WCHAR *get_absolute_path(const WCHAR *path) { DWORD size; @@ -93,10 +144,29 @@ static void parse_arguments(int argc, WCHAR *argv[]) } }
+static void print_header(void) +{ + UINT i; + + output_message(format_string(STRING_HEADER)); + + if (!options.source) output_message(format_string(STRING_SOURCE), L"-"); + else output_message(format_string(STRING_SOURCE), strip_path_prefix(options.source)); + + if (!options.destination) output_message(format_string(STRING_DESTINATION), L"-"); + else output_message(format_string(STRING_DESTINATION), strip_path_prefix(options.destination)); + + output_message(format_string(STRING_FILES)); + for (i = 0; i < options.files->size; i++) + output_message(L" %1\n", options.files->array[i]); +} + int __cdecl wmain(int argc, WCHAR *argv[]) { parse_arguments(argc, argv);
+ print_header(); + WINE_FIXME("robocopy stub"); return 0; } diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index f62eb92c663..f5c2ac56fcf 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -31,3 +31,9 @@ struct robocopy_options WCHAR *source; struct path_array *files; }; + +/* Resource strings */ +#define STRING_HEADER 1000 +#define STRING_SOURCE 1003 +#define STRING_DESTINATION 1004 +#define STRING_FILES 1005 diff --git a/programs/robocopy/robocopy.rc b/programs/robocopy/robocopy.rc index edae5b71d86..044bab77774 100644 --- a/programs/robocopy/robocopy.rc +++ b/programs/robocopy/robocopy.rc @@ -17,9 +17,20 @@ */
#include <windef.h> +#include "robocopy.h"
#pragma makedep po
+LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT + +STRINGTABLE +{ + STRING_HEADER, "Robocopy for Wine\n\n" + STRING_SOURCE, " Source: %1\n" + STRING_DESTINATION, " Destination: %1\n\n" + STRING_FILES, " Files:\n" +} + LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
#define WINE_FILEDESCRIPTION_STR "Wine Robocopy"
Signed-off-by: Zebediah Figura zfigura@codeweavers.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 others.meder@gmail.com --- v3: wcsdup instead of manual string allocation & copying and name changes to matches_array_entry according to code review --- programs/robocopy/Makefile.in | 2 +- programs/robocopy/main.c | 165 +++++++++++++++++++++++++++++++++- programs/robocopy/robocopy.h | 17 ++++ programs/robocopy/robocopy.rc | 6 ++ 4 files changed, 187 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 21527c12c8b..02dfc2eea0b 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -23,6 +23,8 @@ WINE_DEFAULT_DEBUG_CHANNEL(robocopy); #include <windows.h> #include <stdlib.h> #include <pathcch.h> +#include <shlwapi.h> +#include <wine/list.h> #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 */ @@ -144,6 +161,134 @@ static void parse_arguments(int argc, WCHAR *argv[]) } }
+static BOOL path_in_array(WCHAR *name, struct path_array *names) +{ + int i; + for (i = 0; i < names->size; i++) + { + if (PathMatchSpecW(name, names->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) && path_in_array(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; @@ -165,8 +310,24 @@ 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] = wcsdup(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 <windows.h> +#include <wine/list.h> + +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 044bab77774..1ce605f432d 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:\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
Signed-off-by: Zebediah Figura zfigura@codeweavers.com
Basic conformance tests create a test source directory with files and execute robocopy on it, checking whether the resulting destination directory, the remaining source directory and the exit code is as expected
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/tests/robocopy.c | 189 ++++++++++++++++++++++++++++- 1 file changed, 187 insertions(+), 2 deletions(-)
diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c index 02798663ba5..60ce0aeecca 100644 --- a/programs/robocopy/tests/robocopy.c +++ b/programs/robocopy/tests/robocopy.c @@ -48,9 +48,153 @@ static DWORD execute_robocopy(const WCHAR *cmd_line) return exit_code; }
+static void create_test_file(const WCHAR *relative_path, size_t size, LONGLONG fixed_filetime, long filetime_offset) +{ + HANDLE handle; + WCHAR path[1024]; + wcscpy(path, L"\\?\"); + GetTempPathW(ARRAY_SIZE(path) - 4, &(path[4])); + wcscat(path, relative_path); + handle = CreateFileW(path, FILE_GENERIC_WRITE | FILE_GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL); + ok(handle != INVALID_HANDLE_VALUE, "creation of %S failed (0x%08x)\n", path, GetLastError()); + if (size != 0) + { + BYTE *data; + DWORD bytes_written; + data = calloc(size, sizeof(BYTE)); + ok(WriteFile(handle, data, size, &bytes_written, NULL), "writing to %S failed (%d)\n", path, GetLastError()); + } + if (fixed_filetime != 0) + { + FILETIME time; + LARGE_INTEGER time_as_integer; + time_as_integer.QuadPart = fixed_filetime; + time.dwHighDateTime = time_as_integer.HighPart; + time.dwLowDateTime = time_as_integer.LowPart; + ok(SetFileTime(handle, &time, &time, &time), "filetime manipulation of %S failed (%d)\n", path, GetLastError()); + } + if (filetime_offset != 0) + { + FILETIME time, modified_time, access_time; + LARGE_INTEGER time_as_integer; + GetFileTime(handle, &time, &modified_time, &access_time); + /* FILETIME is no union with LONGLONG / LONG64, casting could be unsafe */ + time_as_integer.HighPart = time.dwHighDateTime; + time_as_integer.LowPart = time.dwLowDateTime; + /* 1000 * 1000 * 60 * 60 * 24 = 86400000000ns per day */ + time_as_integer.QuadPart += 864000000000LL * filetime_offset; + time.dwHighDateTime = time_as_integer.HighPart; + time.dwLowDateTime = time_as_integer.LowPart; + ok(SetFileTime(handle, &time, &time, &time), "filetime manipulation of %S failed (%d)\n", path, GetLastError()); + } + CloseHandle(handle); +} + +static void create_test_folder(const WCHAR *relative_path) +{ + WCHAR path[1024]; + wcscpy(path, L"\\?\"); + GetTempPathW(ARRAY_SIZE(path) - 4, &(path[4])); + wcscat(path, relative_path); + CreateDirectoryW(path, NULL); +} + +static void create_test_source_folder(void) +{ + create_test_folder(L"robocopy_source"); + create_test_folder(L"robocopy_source\folderA"); + create_test_folder(L"robocopy_source\folderB"); + create_test_folder(L"robocopy_source\folderC"); + create_test_folder(L"robocopy_source\folderA\folderD"); + create_test_folder(L"robocopy_source\folderA\folderE"); + create_test_file(L"robocopy_source\fileA.a", 4000, 0, -10); + create_test_file(L"robocopy_source\fileB.b", 8000, 0, -2); + create_test_file(L"robocopy_source\folderA\fileC.c", 60, 0, -2); + create_test_file(L"robocopy_source\folderA\fileD.d", 80, 0, 0); + create_test_file(L"robocopy_source\folderA\folderD\fileE.e", 10000, 0, -10); + create_test_file(L"robocopy_source\folderB\fileF.f", 10000, 132223104000000000, 0); + create_test_file(L"robocopy_source\folderB\fileG.g", 200, 132223104000000000, 0); +} + +static void check_file_and_delete(const WCHAR *relative_path, BOOL should_exist) +{ + WCHAR path[1024]; + wcscpy(path, L"\\?\"); + GetTempPathW(ARRAY_SIZE(path) - 4, &(path[4])); + wcscat(path, relative_path); + if (!DeleteFileW(path)) + { + if (GetLastError() == ERROR_FILE_NOT_FOUND) + ok(!should_exist, "file "%S" does not exist, but should exist\n", relative_path); + else if (GetLastError() == ERROR_PATH_NOT_FOUND) + ok(!should_exist, "file "%S" and the parent directory do not exist, but should exist\n", relative_path); + else + ok(FALSE, "file "%S" DeleteFileW returned error %d\n", relative_path, GetLastError()); + } + else + { + ok(should_exist, "file "%S" should not exist, but does exist\n", relative_path); + } +} + +static void check_folder_and_delete(const WCHAR *relative_path, BOOL should_exist) +{ + WCHAR path[1024]; + wcscpy(path, L"\\?\"); + GetTempPathW(ARRAY_SIZE(path) - 4, &(path[4])); + wcscat(path, relative_path); + if (!RemoveDirectoryW(path)) + { + if (GetLastError() == ERROR_FILE_NOT_FOUND) + ok(!should_exist, "directory "%S" does not exist, but should exist\n", relative_path); + else if (GetLastError() == ERROR_PATH_NOT_FOUND) + ok(!should_exist, "directory "%S" and the parent directory do not exist, but should exist\n", relative_path); + else if (GetLastError() == ERROR_DIR_NOT_EMPTY) + ok(FALSE, "directory "%S" is unexpectedly not empty\n", relative_path); + else + ok(FALSE, "directory "%S" DeleteFileW returned error %d\n", relative_path, GetLastError()); + } + else + { + ok(should_exist, "directory "%S" should not exist, but does exist\n", relative_path); + } +} + +static void check_basic_copy_test(void) +{ + check_file_and_delete(L"robocopy_source\fileA.a", TRUE); + check_file_and_delete(L"robocopy_source\fileB.b", TRUE); + check_file_and_delete(L"robocopy_source\folderA\fileC.c", TRUE); + check_file_and_delete(L"robocopy_source\folderA\fileD.d", TRUE); + check_file_and_delete(L"robocopy_source\folderA\folderD\fileE.e", TRUE); + check_file_and_delete(L"robocopy_source\folderB\fileF.f", TRUE); + check_file_and_delete(L"robocopy_source\folderB\fileG.g", TRUE); + check_folder_and_delete(L"robocopy_source\folderA\folderD", TRUE); + check_folder_and_delete(L"robocopy_source\folderA\folderE", TRUE); + check_folder_and_delete(L"robocopy_source\folderA", TRUE); + check_folder_and_delete(L"robocopy_source\folderB", TRUE); + check_folder_and_delete(L"robocopy_source\folderC", TRUE); + check_folder_and_delete(L"robocopy_source", TRUE); + + check_file_and_delete(L"robocopy_destination\fileA.a", TRUE); + check_file_and_delete(L"robocopy_destination\fileB.b", TRUE); + todo_wine check_file_and_delete(L"robocopy_destination\folderA\fileC.c", FALSE); + todo_wine check_file_and_delete(L"robocopy_destination\folderA\fileD.d", FALSE); + todo_wine check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", FALSE); + todo_wine check_file_and_delete(L"robocopy_destination\folderB\fileF.f", FALSE); + todo_wine check_file_and_delete(L"robocopy_destination\folderB\fileG.g", FALSE); + todo_wine check_folder_and_delete(L"robocopy_destination\folderA\folderD", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", FALSE); + todo_wine check_folder_and_delete(L"robocopy_destination\folderA", FALSE); + todo_wine check_folder_and_delete(L"robocopy_destination\folderB", FALSE); + check_folder_and_delete(L"robocopy_destination\folderC", FALSE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + START_TEST(robocopy) { - WCHAR previous_cwd_path[4096], temp_path[4096]; + WCHAR temp_command_line[2048], previous_cwd_path[4096], temp_path[4096]; + DWORD exit_code;
ok(GetFullPathNameW(L".", ARRAY_SIZE(previous_cwd_path), previous_cwd_path, NULL) != 0, "couldn't get CWD path"); ok(GetTempPathW(ARRAY_SIZE(temp_path), temp_path) != 0, "couldn't get temp folder path"); @@ -60,7 +204,48 @@ START_TEST(robocopy)
ok(SetCurrentDirectoryW(temp_path), "couldn't set CWD to temp folder "%S"", temp_path);
- /* TODO: conformance tests here */ + winetest_push_context("basic copy test 1"); + create_test_source_folder(); + exit_code = execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /r:1 /w:0"); + ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_test(); + winetest_pop_context(); + + winetest_push_context("basic copy test 2"); + create_test_source_folder(); + exit_code = execute_robocopy(L"robocopy.exe ./robocopy_source third_folder/../robocopy_destination /r:1 /w:0"); + ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_test(); + winetest_pop_context(); + + winetest_push_context("basic copy test 3"); + create_test_source_folder(); + create_test_folder(L"robocopy_destination"); + create_test_file(L"robocopy_destination\fileA.a", 9000, 0, -10); + exit_code = execute_robocopy(L"robocopy.exe ./robocopy_source robocopy_source/../robocopy_destination /r:1 /w:0"); + ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_test(); + winetest_pop_context(); + + winetest_push_context("basic copy test 4"); + create_test_source_folder(); + swprintf(temp_command_line, ARRAY_SIZE(temp_command_line), + L"robocopy.exe %s\robocopy_source %s\robocopy_destination /r:1 /w:0", + temp_path, temp_path); + exit_code = execute_robocopy(temp_command_line); + ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_test(); + winetest_pop_context(); + + winetest_push_context("basic copy test 5"); + create_test_source_folder(); + swprintf(temp_command_line, ARRAY_SIZE(temp_command_line), + L"robocopy.exe %s\third_folder\..\robocopy_source %s\third_folder\..\robocopy_destination /r:1 /w:0", + temp_path, temp_path); + exit_code = execute_robocopy(temp_command_line); + ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_test(); + winetest_pop_context();
ok(SetCurrentDirectoryW(previous_cwd_path), "couldn't set CWD to previous CWD folder "%S"", previous_cwd_path); }
Signed-off-by: Zebediah Figura zfigura@codeweavers.com