Basic files and required changes to configure(.ac) to create a stub version of the robocopy utility
Signed-off-by: Florian Eder others.meder@gmail.com --- v7: Removed cygwin flag in makefile and added terminating newline to stub output, changed return value of stub to 16 ("Error, No Files Copied") --- 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 d1edbdc684a..fd62dd6e078 100755 --- a/configure +++ b/configure @@ -20989,6 +20989,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 @@ -22522,4 +22523,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 da6ecf5a7a8..22d3cd10eeb 100644 --- a/configure.ac +++ b/configure.ac @@ -3872,6 +3872,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..f5d9dff6e4d --- /dev/null +++ b/programs/robocopy/Makefile.in @@ -0,0 +1,8 @@ +MODULE = robocopy.exe + +EXTRADLLFLAGS = -mconsole -municode + +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..320de52f2d5 --- /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\n"); + return 16; +} 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 conformance tests create test source directories with files and sub folders, 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 --- v7: Added conformance tests for the behaiviour added in the following patches, merged patch 2 and 6 from v6 --- configure | 1 + configure.ac | 1 + programs/robocopy/tests/Makefile.in | 4 + programs/robocopy/tests/robocopy.c | 411 ++++++++++++++++++++++++++++ 4 files changed, 417 insertions(+) create mode 100644 programs/robocopy/tests/Makefile.in create mode 100644 programs/robocopy/tests/robocopy.c
diff --git a/configure b/configure index fd62dd6e078..49e43ebebdb 100755 --- a/configure +++ b/configure @@ -20990,6 +20990,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 22d3cd10eeb..fa7aae50f66 100644 --- a/configure.ac +++ b/configure.ac @@ -3873,6 +3873,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..76279f476f9 --- /dev/null +++ b/programs/robocopy/tests/robocopy.c @@ -0,0 +1,411 @@ +/* + * 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> +#include <wchar.h> +#include <wine/test.h> + +WCHAR temp_path[MAX_PATH]; + +static BOOL run_cmd(const WCHAR *cmd, DWORD *exit_code) +{ + STARTUPINFOW startup_info; + PROCESS_INFORMATION process_info; + WCHAR cmd_copy[256]; + + memset(&startup_info, 0, sizeof(STARTUPINFOW)); + startup_info.dwFlags = STARTF_USESTDHANDLES; + startup_info.hStdInput = INVALID_HANDLE_VALUE; + startup_info.hStdOutput = INVALID_HANDLE_VALUE; + startup_info.hStdError = INVALID_HANDLE_VALUE; + + swprintf(cmd_copy, ARRAY_SIZE(cmd_copy), L"%s", cmd); + + if (!CreateProcessW(NULL, cmd_copy, NULL, NULL, TRUE, 0, NULL, NULL, &startup_info, &process_info)) + return FALSE; + + if (WaitForSingleObject(process_info.hProcess, 30000) == WAIT_TIMEOUT) + return FALSE; + + GetExitCodeProcess(process_info.hProcess, exit_code); + + CloseHandle(process_info.hThread); + CloseHandle(process_info.hProcess); + + return TRUE; +} + +static void create_test_file(const WCHAR *relative_path, size_t size) +{ + HANDLE handle; + WCHAR path[MAX_PATH]; + swprintf(path, ARRAY_SIZE(path), L"%s%s", temp_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", debugstr_w(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", debugstr_w(path), GetLastError()); + } + CloseHandle(handle); +} + +static void create_test_folder(const WCHAR *relative_path) +{ + WCHAR path[MAX_PATH]; + swprintf(path, ARRAY_SIZE(path), L"%s%s", temp_path, relative_path); + + CreateDirectoryW(path, NULL); +} + +static void create_test_source_folder(void) +{ + create_test_folder(L"source"); + create_test_folder(L"source\folderA"); + create_test_folder(L"source\folderB"); + create_test_folder(L"source\folderC"); + create_test_folder(L"source\folderA\folderD"); + create_test_folder(L"source\folderA\folderE"); + create_test_file(L"source\fileA.a", 4000); + create_test_file(L"source\fileB.b", 8000); + create_test_file(L"source\folderA\fileC.c", 60); + create_test_file(L"source\folderA\fileD.d", 80); + create_test_file(L"source\folderA\folderD\fileE.e", 10000); + create_test_file(L"source\folderB\fileF.f", 10000); + create_test_file(L"source\folderB\fileG.g", 200); +} + +static void check_file(const WCHAR *relative_path, BOOL should_exist) +{ + WCHAR path[MAX_PATH]; + swprintf(path, ARRAY_SIZE(path), L"%s%s", temp_path, relative_path); + + ok (DeleteFileW(path) == should_exist, "file %s expected exist to be %d, but is %d\n", debugstr_w(relative_path), should_exist, !should_exist); + if (!should_exist) + ok(GetLastError() == ERROR_FILE_NOT_FOUND || GetLastError() == ERROR_PATH_NOT_FOUND, + "file %s DeleteFileW returned error %d, should not exist\n", + debugstr_w(relative_path), + GetLastError()); +} + +static void check_folder(const WCHAR *relative_path, BOOL should_exist) +{ + WCHAR path[MAX_PATH]; + swprintf(path, ARRAY_SIZE(path), L"%s%s", temp_path, relative_path); + + ok (RemoveDirectoryW(path) == should_exist, "folder %s expected exist to be %d, but is %d\n", debugstr_w(relative_path), should_exist, !should_exist); + if (!should_exist) + ok(GetLastError() == ERROR_FILE_NOT_FOUND || GetLastError() == ERROR_PATH_NOT_FOUND, + "folder %s RemoveDirectoryW returned error %d, should not exist\n", + debugstr_w(relative_path), + GetLastError()); +} + +static void check_basic_copy_1_test(void) +{ + check_file(L"source\fileA.a", TRUE); + check_file(L"source\fileB.b", TRUE); + check_file(L"source\folderA\fileC.c", TRUE); + check_file(L"source\folderA\fileD.d", TRUE); + check_file(L"source\folderA\folderD\fileE.e", TRUE); + check_file(L"source\folderB\fileF.f", TRUE); + check_file(L"source\folderB\fileG.g", TRUE); + check_folder(L"source\folderA\folderD", TRUE); + check_folder(L"source\folderA\folderE", TRUE); + check_folder(L"source\folderA", TRUE); + check_folder(L"source\folderB", TRUE); + check_folder(L"source\folderC", TRUE); + check_folder(L"source", TRUE); + + todo_wine check_file(L"destination\fileA.a", TRUE); + todo_wine check_file(L"destination\fileB.b", TRUE); + check_file(L"destination\folderA\fileC.c", FALSE); + check_file(L"destination\folderA\fileD.d", FALSE); + check_file(L"destination\folderA\folderD\fileE.e", FALSE); + check_file(L"destination\folderB\fileF.f", FALSE); + check_file(L"destination\folderB\fileG.g", FALSE); + check_folder(L"destination\folderA\folderD", FALSE); + check_folder(L"destination\folderA\folderE", FALSE); + check_folder(L"destination\folderA", FALSE); + check_folder(L"destination\folderB", FALSE); + check_folder(L"destination\folderC", FALSE); + todo_wine check_folder(L"destination", TRUE); +} + +static void check_basic_copy_2_test(void) +{ + check_file(L"source\fileA.a", TRUE); + check_file(L"source\fileB.b", TRUE); + check_file(L"source\folderA\fileC.c", TRUE); + check_file(L"source\folderA\fileD.d", TRUE); + check_file(L"source\folderA\folderD\fileE.e", TRUE); + check_file(L"source\folderB\fileF.f", TRUE); + check_file(L"source\folderB\fileG.g", TRUE); + check_folder(L"source\folderA\folderD", TRUE); + check_folder(L"source\folderA\folderE", TRUE); + check_folder(L"source\folderA", TRUE); + check_folder(L"source\folderB", TRUE); + check_folder(L"source\folderC", TRUE); + check_folder(L"source", TRUE); + + check_file(L"destination\fileA.a", TRUE); + todo_wine check_file(L"destination\fileB.b", TRUE); + check_file(L"destination\folderA\fileC.c", FALSE); + check_file(L"destination\folderA\fileD.d", FALSE); + check_file(L"destination\folderA\folderD\fileE.e", FALSE); + check_file(L"destination\folderB\fileF.f", FALSE); + check_file(L"destination\folderB\fileG.g", FALSE); + check_folder(L"destination\folderA\folderD", FALSE); + check_folder(L"destination\folderA\folderE", FALSE); + check_folder(L"destination\folderA", FALSE); + check_folder(L"destination\folderB", FALSE); + check_folder(L"destination\folderC", FALSE); + check_folder(L"destination", TRUE); +} + +static void check_no_copy_test(void) +{ + check_file(L"source\fileA.a", TRUE); + check_file(L"source\fileB.b", TRUE); + check_file(L"source\folderA\fileC.c", TRUE); + check_file(L"source\folderA\fileD.d", TRUE); + check_file(L"source\folderA\folderD\fileE.e", TRUE); + check_file(L"source\folderB\fileF.f", TRUE); + check_file(L"source\folderB\fileG.g", TRUE); + check_folder(L"source\folderA\folderD", TRUE); + check_folder(L"source\folderA\folderE", TRUE); + check_folder(L"source\folderA", TRUE); + check_folder(L"source\folderB", TRUE); + check_folder(L"source\folderC", TRUE); + check_folder(L"source", TRUE); + + check_file(L"destination\fileA.a", FALSE); + check_file(L"destination\fileB.b", FALSE); + check_file(L"destination\folderA\fileC.c", FALSE); + check_file(L"destination\folderA\fileD.d", FALSE); + check_file(L"destination\folderA\folderD\fileE.e", FALSE); + check_file(L"destination\folderB\fileF.f", FALSE); + check_file(L"destination\folderB\fileG.g", FALSE); + check_folder(L"destination\folderA\folderD", FALSE); + check_folder(L"destination\folderA\folderE", FALSE); + check_folder(L"destination\folderA", FALSE); + check_folder(L"destination\folderB", FALSE); + check_folder(L"destination\folderC", FALSE); + check_folder(L"destination", FALSE); +} + +static void check_wildcard_1_test(void) +{ + check_file(L"source\fileA.a", TRUE); + check_file(L"source\fileB.b", TRUE); + check_file(L"source\folderA\fileC.c", TRUE); + check_file(L"source\folderA\fileD.d", TRUE); + check_file(L"source\folderA\folderD\fileE.e", TRUE); + check_file(L"source\folderB\fileF.f", TRUE); + check_file(L"source\folderB\fileG.g", TRUE); + check_folder(L"source\folderA\folderD", TRUE); + check_folder(L"source\folderA\folderE", TRUE); + check_folder(L"source\folderA", TRUE); + check_folder(L"source\folderB", TRUE); + check_folder(L"source\folderC", TRUE); + check_folder(L"source", TRUE); + + todo_wine check_file(L"destination\fileA.a", TRUE); + check_file(L"destination\fileB.b", FALSE); + check_file(L"destination\folderA\fileC.c", FALSE); + check_file(L"destination\folderA\fileD.d", FALSE); + check_file(L"destination\folderA\folderD\fileE.e", FALSE); + check_file(L"destination\folderB\fileF.f", FALSE); + check_file(L"destination\folderB\fileG.g", FALSE); + check_folder(L"destination\folderA\folderD", FALSE); + check_folder(L"destination\folderA\folderE", FALSE); + check_folder(L"destination\folderA", FALSE); + check_folder(L"destination\folderB", FALSE); + check_folder(L"destination\folderC", FALSE); + todo_wine check_folder(L"destination", TRUE); +} + +START_TEST(robocopy) +{ + DWORD exit_code; + WCHAR temp_cmd[512]; + int i; + const WCHAR *invalid_syntax_tests[] = + { + L"robocopy", + L"robocopy invalid_folder", + L"robocopy -flag invalid_folder", + L"robocopy invalid_folder destination", + L"robocopy -?", + L"robocopy invalid_folder -?", + L"robocopy invalid_folder /?", + }; + + /* robocopy is only available from Vista onwards, abort test if not available */ + if (!run_cmd(L"robocopy", &exit_code)) return; + + ok(GetTempPathW(ARRAY_SIZE(temp_path), temp_path) != 0, "couldn't get temp folder path\n"); + wcscat(temp_path, L"robocopy_test\"); + ok(CreateDirectoryW(temp_path, NULL), "couldn't create temp test folder %s, error %d\n", debugstr_w(temp_path), GetLastError()); + ok(SetCurrentDirectoryW(temp_path), "couldn't set CWD to temp folder %s\n", debugstr_w(temp_path)); + + for (i = 0; i < ARRAY_SIZE(invalid_syntax_tests); i++) + { + run_cmd(invalid_syntax_tests[i], &exit_code); + ok(exit_code == 16, "unexpected exit code %d from command %s\n", exit_code, debugstr_w(invalid_syntax_tests[i])); + } + + winetest_push_context("basic copy test 1"); + create_test_source_folder(); + run_cmd(L"robocopy source destination /r:1 /w:0", &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_1_test(); + winetest_pop_context(); + + winetest_push_context("basic copy test 2"); + create_test_source_folder(); + run_cmd(L"robocopy ./source third_folder/../destination /r:1 /w:0", &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_1_test(); + winetest_pop_context(); + + winetest_push_context("basic copy test 3"); + create_test_source_folder(); + create_test_folder(L"destination"); + create_test_file(L"destination\fileA.a", 9000); + run_cmd(L"robocopy ./source source/../destination /r:1 /w:0", &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_2_test(); + winetest_pop_context(); + + winetest_push_context("basic copy test 4"); + create_test_source_folder(); + swprintf(temp_cmd, ARRAY_SIZE(temp_cmd), + L"robocopy %s\source %s\destination /r:1 /w:0", + temp_path, temp_path); + run_cmd(temp_cmd, &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_1_test(); + winetest_pop_context(); + + winetest_push_context("basic copy test 5"); + create_test_source_folder(); + swprintf(temp_cmd, ARRAY_SIZE(temp_cmd), + L"robocopy %s\source destination /r:1 /w:0", + temp_path); + run_cmd(temp_cmd, &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_1_test(); + winetest_pop_context(); + + winetest_push_context("basic copy test 6"); + create_test_source_folder(); + swprintf(temp_cmd, ARRAY_SIZE(temp_cmd), + L"robocopy source %s\destination /r:1 /w:0", + temp_path); + run_cmd(temp_cmd, &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_1_test(); + winetest_pop_context(); + + winetest_push_context("basic copy test 7"); + create_test_source_folder(); + swprintf(temp_cmd, ARRAY_SIZE(temp_cmd), + L"robocopy %s\third_folder\..\source %s\third_folder\..\destination /r:1 /w:0", + temp_path, temp_path); + run_cmd(temp_cmd, &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_1_test(); + winetest_pop_context(); + + winetest_push_context("invalid copy test 1"); + create_test_source_folder(); + run_cmd(L"robocopy source source /r:1 /w:0", &exit_code); + todo_wine ok(exit_code == 0, "unexpected exit code %d\n", exit_code); + check_no_copy_test(); + winetest_pop_context(); + + winetest_push_context("invalid copy test 2"); + create_test_source_folder(); + run_cmd(L"robocopy invalid_folder destination /r:1 /w:0", &exit_code); + ok(exit_code == 16, "unexpected exit code %d\n", exit_code); + check_no_copy_test(); + winetest_pop_context(); + + winetest_push_context("wildcard test 1"); + create_test_source_folder(); + run_cmd(L"robocopy source destination file?.? /r:1 /w:0", &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_1_test(); + winetest_pop_context(); + + winetest_push_context("wildcard test 2"); + create_test_source_folder(); + run_cmd(L"robocopy source destination file* /r:1 /w:0", &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_1_test(); + winetest_pop_context(); + + winetest_push_context("wildcard test 3"); + create_test_source_folder(); + run_cmd(L"robocopy source destination *le?.? /r:1 /w:0", &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_1_test(); + winetest_pop_context(); + + winetest_push_context("wildcard test 4"); + create_test_source_folder(); + run_cmd(L"robocopy source destination *A.? /r:1 /w:0", &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_wildcard_1_test(); + winetest_pop_context(); + + winetest_push_context("wildcard test 5"); + create_test_source_folder(); + run_cmd(L"robocopy sour?e destination /r:1 /w:0", &exit_code); + ok(exit_code == 16, "unexpected exit code %d\n", exit_code); + check_no_copy_test(); + winetest_pop_context(); + + winetest_push_context("wildcard test 6"); + create_test_source_folder(); + run_cmd(L"robocopy s* destination /r:1 /w:0", &exit_code); + ok(exit_code == 16, "unexpected exit code %d\n", exit_code); + check_no_copy_test(); + winetest_pop_context(); + + winetest_push_context("flag parser test 1"); + create_test_source_folder(); + run_cmd(L"robocopy /r:1 source destination /r:1 /w:0", &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_1_test(); + winetest_pop_context(); + + winetest_push_context("flag parser test 2"); + create_test_source_folder(); + run_cmd(L"robocopy source /r:1 destination /r:1 /w:0", &exit_code); + todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + check_basic_copy_1_test(); + winetest_pop_context(); + + SetCurrentDirectoryW(L".."); + ok(RemoveDirectoryW(temp_path), "could not remove temp path %s, error %d\n", debugstr_w(temp_path), GetLastError()); +}
On 10/16/21 6:13 AM, Florian Eder wrote:
+static void check_file(const WCHAR *relative_path, BOOL should_exist) +{
- WCHAR path[MAX_PATH];
- swprintf(path, ARRAY_SIZE(path), L"%s%s", temp_path, relative_path);
- ok (DeleteFileW(path) == should_exist, "file %s expected exist to be %d, but is %d\n", debugstr_w(relative_path), should_exist, !should_exist);
Do we need to pass an absolute path to DeleteFile() and RemoveDirectory(), or can we just pass the relative path?
- if (!should_exist)
ok(GetLastError() == ERROR_FILE_NOT_FOUND || GetLastError() == ERROR_PATH_NOT_FOUND,
"file %s DeleteFileW returned error %d, should not exist\n",
debugstr_w(relative_path),
GetLastError());
+}
+static void check_folder(const WCHAR *relative_path, BOOL should_exist) +{
- WCHAR path[MAX_PATH];
- swprintf(path, ARRAY_SIZE(path), L"%s%s", temp_path, relative_path);
- ok (RemoveDirectoryW(path) == should_exist, "folder %s expected exist to be %d, but is %d\n", debugstr_w(relative_path), should_exist, !should_exist);
- if (!should_exist)
ok(GetLastError() == ERROR_FILE_NOT_FOUND || GetLastError() == ERROR_PATH_NOT_FOUND,
"folder %s RemoveDirectoryW returned error %d, should not exist\n",
debugstr_w(relative_path),
GetLastError());
+}
+static void check_basic_copy_1_test(void) +{
- check_file(L"source\fileA.a", TRUE);
- check_file(L"source\fileB.b", TRUE);
- check_file(L"source\folderA\fileC.c", TRUE);
- check_file(L"source\folderA\fileD.d", TRUE);
- check_file(L"source\folderA\folderD\fileE.e", TRUE);
- check_file(L"source\folderB\fileF.f", TRUE);
- check_file(L"source\folderB\fileG.g", TRUE);
- check_folder(L"source\folderA\folderD", TRUE);
- check_folder(L"source\folderA\folderE", TRUE);
- check_folder(L"source\folderA", TRUE);
- check_folder(L"source\folderB", TRUE);
- check_folder(L"source\folderC", TRUE);
- check_folder(L"source", TRUE);
All of these so far are copy operations, so this block ends up getting duplicated. Could that use a helper?
- todo_wine check_file(L"destination\fileA.a", TRUE);
- todo_wine check_file(L"destination\fileB.b", TRUE);
- check_file(L"destination\folderA\fileC.c", FALSE);
- check_file(L"destination\folderA\fileD.d", FALSE);
- check_file(L"destination\folderA\folderD\fileE.e", FALSE);
- check_file(L"destination\folderB\fileF.f", FALSE);
- check_file(L"destination\folderB\fileG.g", FALSE);
- check_folder(L"destination\folderA\folderD", FALSE);
- check_folder(L"destination\folderA\folderE", FALSE);
- check_folder(L"destination\folderA", FALSE);
- check_folder(L"destination\folderB", FALSE);
- check_folder(L"destination\folderC", FALSE);
- todo_wine check_folder(L"destination", TRUE);
+}
...
+START_TEST(robocopy) +{
- DWORD exit_code;
- WCHAR temp_cmd[512];
- int i;
- const WCHAR *invalid_syntax_tests[] =
This should be static const as well (i.e. "static const WCHAR *const").
- {
L"robocopy",
L"robocopy invalid_folder",
L"robocopy -flag invalid_folder",
L"robocopy invalid_folder destination",
L"robocopy -?",
L"robocopy invalid_folder -?",
L"robocopy invalid_folder /?",
- };
- /* robocopy is only available from Vista onwards, abort test if not available */
- if (!run_cmd(L"robocopy", &exit_code)) return;
- ok(GetTempPathW(ARRAY_SIZE(temp_path), temp_path) != 0, "couldn't get temp folder path\n");
- wcscat(temp_path, L"robocopy_test\");
- ok(CreateDirectoryW(temp_path, NULL), "couldn't create temp test folder %s, error %d\n", debugstr_w(temp_path), GetLastError());
- ok(SetCurrentDirectoryW(temp_path), "couldn't set CWD to temp folder %s\n", debugstr_w(temp_path));
- for (i = 0; i < ARRAY_SIZE(invalid_syntax_tests); i++)
- {
run_cmd(invalid_syntax_tests[i], &exit_code);
ok(exit_code == 16, "unexpected exit code %d from command %s\n", exit_code, debugstr_w(invalid_syntax_tests[i]));
- }
- winetest_push_context("basic copy test 1");
- create_test_source_folder();
- run_cmd(L"robocopy source destination /r:1 /w:0", &exit_code);
- todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
- check_basic_copy_1_test();
- winetest_pop_context();
There's a lot of tests like this one that have the same result; should we put those in a loop as well? You won't be able to catch them all this way, of course (e.g. not the absolute paths), but many of the wildcard and flag parsing tests could be deduplicated.
A couple more tests I'd like to see that occur to me:
* non-wildcard path spec,
* multiple path specs,
* absolute path spec.
Parses path arguments as source, destination and files to include, 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 --- v7: Merged patches 3 and 5, removed all messaging / error outputs and made copy function non-recursive for now, until the /S switch is implemented --- programs/robocopy/Makefile.in | 1 + programs/robocopy/main.c | 196 ++++++++++++++++++++++++++++- programs/robocopy/tests/robocopy.c | 38 +++--- 3 files changed, 214 insertions(+), 21 deletions(-)
diff --git a/programs/robocopy/Makefile.in b/programs/robocopy/Makefile.in index f5d9dff6e4d..d1f5db665df 100644 --- a/programs/robocopy/Makefile.in +++ b/programs/robocopy/Makefile.in @@ -1,6 +1,7 @@ MODULE = robocopy.exe
EXTRADLLFLAGS = -mconsole -municode +IMPORTS = kernelbase shlwapi
C_SRCS = \ main.c diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 320de52f2d5..6e19170712d 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -21,9 +21,201 @@ WINE_DEFAULT_DEBUG_CHANNEL(robocopy);
#define WIN32_LEAN_AND_MEAN #include <windows.h> +#include <stdlib.h> +#include <pathcch.h> +#include <shlwapi.h> +#include <wine/list.h> + +struct path_array +{ + UINT size; + WCHAR *array[1]; +}; + +struct path +{ + struct list entry; + WCHAR *name; +}; + +struct robocopy_options +{ + WCHAR *destination; + WCHAR *source; + struct path_array *files; +}; + +#define ROBOCOPY_SUCCESS_FILES_COPIED 1 +#define ROBOCOPY_ERROR_NO_FILES_COPIED 16 + +struct robocopy_options options; + +static WCHAR *get_absolute_path(const WCHAR *path) +{ + DWORD size; + WCHAR *absolute_path; + + size = GetFullPathNameW(path, 0, NULL, NULL) + 2; + absolute_path = calloc(size, sizeof(WCHAR)); + GetFullPathNameW(path, size, absolute_path, NULL); + PathCchAddBackslashEx(absolute_path, size, NULL, NULL); + return absolute_path; +} + +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)); + pointer = wcschr(path, L'\'); + while (pointer != NULL) + { + if (!lstrcpynW(current_folder, path, pointer - path + 2)) return FALSE; + if (!CreateDirectoryW(current_folder, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) return FALSE; + pointer = wcschr(pointer + 1, L'\'); + } + return TRUE; +} + +static void append_matching_directory_content(struct list *paths, WCHAR *search_path, WCHAR *root, struct path_array *file_names) +{ + WIN32_FIND_DATAW entry_data; + HANDLE handle; + WCHAR *current_absolute_path; + struct path *new_path; + + handle = FindFirstFileExW(search_path, FindExInfoStandard, &entry_data, FindExSearchNameMatch, NULL, 0); + if (handle != INVALID_HANDLE_VALUE) + { + do + { + if (!wcscmp(L".", entry_data.cFileName) || !wcscmp(L"..", entry_data.cFileName)) continue; + + PathAllocCombine(root, entry_data.cFileName, + PATHCCH_ALLOW_LONG_PATHS | PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS, + ¤t_absolute_path); + + if ((!PathIsDirectoryW(current_absolute_path) && path_in_array(entry_data.cFileName, file_names)) || + (PathIsDirectoryW(current_absolute_path))) + { + new_path = calloc(1, sizeof(struct path)); + new_path->name = wcsdup(entry_data.cFileName); + list_add_tail(paths, &new_path->entry); + } + } + while (FindNextFileW(handle, &entry_data)); + } +} + +static void get_file_paths_in_folder(WCHAR *root, struct list *paths) +{ + struct path *new_path; + WCHAR *current_search_path; + + list_init(paths); + new_path = calloc(1, sizeof(struct path)); + new_path->name = calloc(2, sizeof(WCHAR)); + list_add_tail(paths, &new_path->entry); + + PathAllocCombine(root, L"*", + PATHCCH_ALLOW_LONG_PATHS | PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS, + ¤t_search_path); + append_matching_directory_content(paths, current_search_path, root, options.files); +} + +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)) 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_ALLOW_LONG_PATHS | PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS, + ¤t_absolute_path); + + PathAllocCombine(options.destination, current_path->name, + PATHCCH_ALLOW_LONG_PATHS | PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS, + &target_path); + + if (!PathIsDirectoryW(current_absolute_path)) + { + create_directory_path(target_path); + CopyFileW(current_absolute_path, target_path, FALSE); + } + } + return TRUE; +} + +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] == '/') && !wcschr(argv[i] + 1, '/')) + WINE_FIXME("encountered an unknown robocopy flag: %s\n", debugstr_w(argv[i])); + else + { + 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[]) { - WINE_FIXME("robocopy stub\n"); - return 16; + 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++; + } + + if (!options.destination || !options.source) + return ROBOCOPY_ERROR_NO_FILES_COPIED; + + if (!perform_copy()) + return ROBOCOPY_ERROR_NO_FILES_COPIED; + + return ROBOCOPY_SUCCESS_FILES_COPIED; } diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c index 76279f476f9..cfa7fa96f5b 100644 --- a/programs/robocopy/tests/robocopy.c +++ b/programs/robocopy/tests/robocopy.c @@ -135,8 +135,8 @@ static void check_basic_copy_1_test(void) check_folder(L"source\folderC", TRUE); check_folder(L"source", TRUE);
- todo_wine check_file(L"destination\fileA.a", TRUE); - todo_wine check_file(L"destination\fileB.b", TRUE); + check_file(L"destination\fileA.a", TRUE); + check_file(L"destination\fileB.b", TRUE); check_file(L"destination\folderA\fileC.c", FALSE); check_file(L"destination\folderA\fileD.d", FALSE); check_file(L"destination\folderA\folderD\fileE.e", FALSE); @@ -147,7 +147,7 @@ static void check_basic_copy_1_test(void) check_folder(L"destination\folderA", FALSE); check_folder(L"destination\folderB", FALSE); check_folder(L"destination\folderC", FALSE); - todo_wine check_folder(L"destination", TRUE); + check_folder(L"destination", TRUE); }
static void check_basic_copy_2_test(void) @@ -167,7 +167,7 @@ static void check_basic_copy_2_test(void) check_folder(L"source", TRUE);
check_file(L"destination\fileA.a", TRUE); - todo_wine check_file(L"destination\fileB.b", TRUE); + check_file(L"destination\fileB.b", TRUE); check_file(L"destination\folderA\fileC.c", FALSE); check_file(L"destination\folderA\fileD.d", FALSE); check_file(L"destination\folderA\folderD\fileE.e", FALSE); @@ -228,7 +228,7 @@ static void check_wildcard_1_test(void) check_folder(L"source\folderC", TRUE); check_folder(L"source", TRUE);
- todo_wine check_file(L"destination\fileA.a", TRUE); + check_file(L"destination\fileA.a", TRUE); check_file(L"destination\fileB.b", FALSE); check_file(L"destination\folderA\fileC.c", FALSE); check_file(L"destination\folderA\fileD.d", FALSE); @@ -240,7 +240,7 @@ static void check_wildcard_1_test(void) check_folder(L"destination\folderA", FALSE); check_folder(L"destination\folderB", FALSE); check_folder(L"destination\folderC", FALSE); - todo_wine check_folder(L"destination", TRUE); + check_folder(L"destination", TRUE); }
START_TEST(robocopy) @@ -276,14 +276,14 @@ START_TEST(robocopy) winetest_push_context("basic copy test 1"); create_test_source_folder(); run_cmd(L"robocopy source destination /r:1 /w:0", &exit_code); - todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + ok(exit_code == 1, "unexpected exit code %d\n", exit_code); check_basic_copy_1_test(); winetest_pop_context();
winetest_push_context("basic copy test 2"); create_test_source_folder(); run_cmd(L"robocopy ./source third_folder/../destination /r:1 /w:0", &exit_code); - todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + ok(exit_code == 1, "unexpected exit code %d\n", exit_code); check_basic_copy_1_test(); winetest_pop_context();
@@ -292,7 +292,7 @@ START_TEST(robocopy) create_test_folder(L"destination"); create_test_file(L"destination\fileA.a", 9000); run_cmd(L"robocopy ./source source/../destination /r:1 /w:0", &exit_code); - todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + ok(exit_code == 1, "unexpected exit code %d\n", exit_code); check_basic_copy_2_test(); winetest_pop_context();
@@ -302,7 +302,7 @@ START_TEST(robocopy) L"robocopy %s\source %s\destination /r:1 /w:0", temp_path, temp_path); run_cmd(temp_cmd, &exit_code); - todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + ok(exit_code == 1, "unexpected exit code %d\n", exit_code); check_basic_copy_1_test(); winetest_pop_context();
@@ -312,7 +312,7 @@ START_TEST(robocopy) L"robocopy %s\source destination /r:1 /w:0", temp_path); run_cmd(temp_cmd, &exit_code); - todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + ok(exit_code == 1, "unexpected exit code %d\n", exit_code); check_basic_copy_1_test(); winetest_pop_context();
@@ -322,7 +322,7 @@ START_TEST(robocopy) L"robocopy source %s\destination /r:1 /w:0", temp_path); run_cmd(temp_cmd, &exit_code); - todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + ok(exit_code == 1, "unexpected exit code %d\n", exit_code); check_basic_copy_1_test(); winetest_pop_context();
@@ -332,7 +332,7 @@ START_TEST(robocopy) L"robocopy %s\third_folder\..\source %s\third_folder\..\destination /r:1 /w:0", temp_path, temp_path); run_cmd(temp_cmd, &exit_code); - todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + ok(exit_code == 1, "unexpected exit code %d\n", exit_code); check_basic_copy_1_test(); winetest_pop_context();
@@ -353,28 +353,28 @@ START_TEST(robocopy) winetest_push_context("wildcard test 1"); create_test_source_folder(); run_cmd(L"robocopy source destination file?.? /r:1 /w:0", &exit_code); - todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + ok(exit_code == 1, "unexpected exit code %d\n", exit_code); check_basic_copy_1_test(); winetest_pop_context();
winetest_push_context("wildcard test 2"); create_test_source_folder(); run_cmd(L"robocopy source destination file* /r:1 /w:0", &exit_code); - todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + ok(exit_code == 1, "unexpected exit code %d\n", exit_code); check_basic_copy_1_test(); winetest_pop_context();
winetest_push_context("wildcard test 3"); create_test_source_folder(); run_cmd(L"robocopy source destination *le?.? /r:1 /w:0", &exit_code); - todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + ok(exit_code == 1, "unexpected exit code %d\n", exit_code); check_basic_copy_1_test(); winetest_pop_context();
winetest_push_context("wildcard test 4"); create_test_source_folder(); run_cmd(L"robocopy source destination *A.? /r:1 /w:0", &exit_code); - todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + ok(exit_code == 1, "unexpected exit code %d\n", exit_code); check_wildcard_1_test(); winetest_pop_context();
@@ -395,14 +395,14 @@ START_TEST(robocopy) winetest_push_context("flag parser test 1"); create_test_source_folder(); run_cmd(L"robocopy /r:1 source destination /r:1 /w:0", &exit_code); - todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + ok(exit_code == 1, "unexpected exit code %d\n", exit_code); check_basic_copy_1_test(); winetest_pop_context();
winetest_push_context("flag parser test 2"); create_test_source_folder(); run_cmd(L"robocopy source /r:1 destination /r:1 /w:0", &exit_code); - todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code); + ok(exit_code == 1, "unexpected exit code %d\n", exit_code); check_basic_copy_1_test(); winetest_pop_context();
On 10/16/21 6:13 AM, Florian Eder wrote:
Parses path arguments as source, destination and files to include, 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
v7: Merged patches 3 and 5, removed all messaging / error outputs and made copy function non-recursive for now, until the /S switch is implemented
programs/robocopy/Makefile.in | 1 + programs/robocopy/main.c | 196 ++++++++++++++++++++++++++++- programs/robocopy/tests/robocopy.c | 38 +++--- 3 files changed, 214 insertions(+), 21 deletions(-)
diff --git a/programs/robocopy/Makefile.in b/programs/robocopy/Makefile.in index f5d9dff6e4d..d1f5db665df 100644 --- a/programs/robocopy/Makefile.in +++ b/programs/robocopy/Makefile.in @@ -1,6 +1,7 @@ MODULE = robocopy.exe
EXTRADLLFLAGS = -mconsole -municode +IMPORTS = kernelbase shlwapi
C_SRCS = \ main.c diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 320de52f2d5..6e19170712d 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -21,9 +21,201 @@ WINE_DEFAULT_DEBUG_CHANNEL(robocopy);
#define WIN32_LEAN_AND_MEAN #include <windows.h> +#include <stdlib.h> +#include <pathcch.h> +#include <shlwapi.h> +#include <wine/list.h>
+struct path_array +{
- UINT size;
- WCHAR *array[1];
+};
+struct path +{
- struct list entry;
- WCHAR *name;
+};
+struct robocopy_options +{
- WCHAR *destination;
- WCHAR *source;
- struct path_array *files;
+};
+#define ROBOCOPY_SUCCESS_FILES_COPIED 1 +#define ROBOCOPY_ERROR_NO_FILES_COPIED 16
+struct robocopy_options options;
+static WCHAR *get_absolute_path(const WCHAR *path) +{
- DWORD size;
- WCHAR *absolute_path;
- size = GetFullPathNameW(path, 0, NULL, NULL) + 2;
- absolute_path = calloc(size, sizeof(WCHAR));
- GetFullPathNameW(path, size, absolute_path, NULL);
- PathCchAddBackslashEx(absolute_path, size, NULL, NULL);
- return absolute_path;
+}
Do we need to convert paths to absolute before copying them?
(Do we even need to convert paths to absolute before writing output, and if so, should that happen in this patch?)
+static BOOL path_in_array(WCHAR *name, struct path_array *names)
Both of these arguments could be const. There's a few other cases below.
+{
- 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));
- pointer = wcschr(path, L'\');
- while (pointer != NULL)
- {
if (!lstrcpynW(current_folder, path, pointer - path + 2)) return FALSE;
if (!CreateDirectoryW(current_folder, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) return FALSE;
pointer = wcschr(pointer + 1, L'\\');
- }
- return TRUE;
+}
+static void append_matching_directory_content(struct list *paths, WCHAR *search_path, WCHAR *root, struct path_array *file_names) +{
- WIN32_FIND_DATAW entry_data;
- HANDLE handle;
- WCHAR *current_absolute_path;
- struct path *new_path;
- handle = FindFirstFileExW(search_path, FindExInfoStandard, &entry_data, FindExSearchNameMatch, NULL, 0);
- if (handle != INVALID_HANDLE_VALUE)
You could return early here and save a level of indentation.
- {
do
{
if (!wcscmp(L".", entry_data.cFileName) || !wcscmp(L"..", entry_data.cFileName)) continue;
PathAllocCombine(root, entry_data.cFileName,
PATHCCH_ALLOW_LONG_PATHS | PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS,
¤t_absolute_path);
Have you considered adding a helper for this PathAllocCombine() call? It's only one function, granted, but it's done many times here, and it does take up several lines.
if ((!PathIsDirectoryW(current_absolute_path) && path_in_array(entry_data.cFileName, file_names)) ||
(PathIsDirectoryW(current_absolute_path)))
This could be simplified to "PathIsDirectory || path_in_array(...)".
{
new_path = calloc(1, sizeof(struct path));
new_path->name = wcsdup(entry_data.cFileName);
list_add_tail(paths, &new_path->entry);
}
}
while (FindNextFileW(handle, &entry_data));
- }
+}
Adds output methods to output copied files and errors
Signed-off-by: Florian Eder others.meder@gmail.com --- v7: More or less equal to patch 3 and parts of patch 4 from v6 --- programs/robocopy/main.c | 87 +++++++++++++++++++++++++++++++++-- programs/robocopy/robocopy.h | 24 ++++++++++ programs/robocopy/robocopy.rc | 13 ++++++ 3 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 programs/robocopy/robocopy.h
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 6e19170712d..ca3388d7ad0 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -26,6 +26,8 @@ WINE_DEFAULT_DEBUG_CHANNEL(robocopy); #include <shlwapi.h> #include <wine/list.h>
+#include "robocopy.h" + struct path_array { UINT size; @@ -50,6 +52,62 @@ struct robocopy_options
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, (WCHAR *)&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 void output_error(UINT format_string_id, HRESULT error_code, WCHAR* path) +{ + WCHAR *error_string; + + FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER, + NULL, error_code, 0, (WCHAR *)&error_string, 0, NULL); + + output_message(format_string(format_string_id), path, error_string); + + LocalFree(error_string); +} + static WCHAR *get_absolute_path(const WCHAR *path) { DWORD size; @@ -80,7 +138,16 @@ static BOOL create_directory_path(WCHAR *path) while (pointer != NULL) { if (!lstrcpynW(current_folder, path, pointer - path + 2)) return FALSE; - if (!CreateDirectoryW(current_folder, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) return FALSE; + if (!CreateDirectoryW(current_folder, NULL)) + { + if (GetLastError() != ERROR_ALREADY_EXISTS) + { + output_error(STRING_ERROR_WRITE_DIRECTORY, GetLastError(), current_folder); + return FALSE; + } + } + else + output_message(format_string(STRING_CREATE_DIRECTORY), current_folder); pointer = wcschr(pointer + 1, L'\'); } return TRUE; @@ -140,7 +207,11 @@ static BOOL perform_copy(void)
list_init(&paths_source);
- if (!PathIsDirectoryW(options.source)) return FALSE; + if (!PathIsDirectoryW(options.source)) + { + output_error(STRING_ERROR_READ_DIRECTORY, ERROR_FILE_NOT_FOUND, options.source); + return FALSE; + }
create_directory_path(options.destination);
@@ -158,10 +229,18 @@ static BOOL perform_copy(void) PATHCCH_ALLOW_LONG_PATHS | PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS, &target_path);
- if (!PathIsDirectoryW(current_absolute_path)) + if (PathIsDirectoryW(current_absolute_path)) + { + if (!create_directory_path(target_path)) + output_error(STRING_ERROR_WRITE_DIRECTORY, GetLastError(), target_path); + } + else { create_directory_path(target_path); - CopyFileW(current_absolute_path, target_path, FALSE); + if (!CopyFileW(current_absolute_path, target_path, FALSE)) + output_error(STRING_ERROR_WRITE_FILE, GetLastError(), target_path); + else + output_message(format_string(STRING_CREATE_FILE), target_path); } } return TRUE; diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h new file mode 100644 index 00000000000..4add81dd208 --- /dev/null +++ b/programs/robocopy/robocopy.h @@ -0,0 +1,24 @@ +/* + * 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 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 edae5b71d86..92dc8796d8c 100644 --- a/programs/robocopy/robocopy.rc +++ b/programs/robocopy/robocopy.rc @@ -17,9 +17,22 @@ */
#include <windef.h> +#include "robocopy.h"
#pragma makedep po
+LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT + +STRINGTABLE +{ + STRING_MISSING_DESTINATION_OR_SOURCE, "No destination or source specified, can't copy anything\n" + STRING_ERROR_READ_DIRECTORY, "Error occurred reading directory "%1":\n%2\n" + STRING_ERROR_WRITE_DIRECTORY, "Error occurred writing directory "%1":\n%2\n" + STRING_ERROR_WRITE_FILE, "Error occurred writing file "%1":\n%2\n" + STRING_CREATE_DIRECTORY, " Created Dir: %1\n" + STRING_CREATE_FILE, " Copied File: %1\n" +} + LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
#define WINE_FILEDESCRIPTION_STR "Wine Robocopy"