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 --- Please feel free to give any kind of feedback regarding the patches :-) --- configure | 2 +- configure.ac | 1 + programs/robocopy/Makefile.in | 9 +++++++++ programs/robocopy/main.c | 29 +++++++++++++++++++++++++++++ programs/robocopy/robocopy.h | 19 +++++++++++++++++++ programs/robocopy/robocopy.rc | 35 +++++++++++++++++++++++++++++++++++ 6 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 programs/robocopy/Makefile.in create mode 100644 programs/robocopy/main.c create mode 100644 programs/robocopy/robocopy.h create mode 100644 programs/robocopy/robocopy.rc
diff --git a/configure b/configure index 99283e0ef18..9e2fa9ef009 100755 --- a/configure +++ b/configure @@ -21264,6 +21264,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 @@ -22797,4 +22798,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 26f74985924..88b9426260f 100644 --- a/configure.ac +++ b/configure.ac @@ -3963,6 +3963,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..5463edcb8b3 --- /dev/null +++ b/programs/robocopy/Makefile.in @@ -0,0 +1,9 @@ +MODULE = robocopy.exe +IMPORTS = + +EXTRADLLFLAGS = -mconsole -municode -mno-cygwin + +C_SRCS = \ + main.c + +RC_SRCS = robocopy.rc \ No newline at end of file diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c new file mode 100644 index 00000000000..a184235e488 --- /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; +} \ No newline at end of file diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h new file mode 100644 index 00000000000..33e84b82ea0 --- /dev/null +++ b/programs/robocopy/robocopy.h @@ -0,0 +1,19 @@ +/* + * 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 \ No newline at end of file diff --git a/programs/robocopy/robocopy.rc b/programs/robocopy/robocopy.rc new file mode 100644 index 00000000000..c7acd5ad161 --- /dev/null +++ b/programs/robocopy/robocopy.rc @@ -0,0 +1,35 @@ +/* + * 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> +#include "robocopy.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 --- configure | 1 + configure.ac | 1 + programs/robocopy/tests/Makefile.in | 5 ++ programs/robocopy/tests/robocopy.c | 80 +++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+) create mode 100644 programs/robocopy/tests/Makefile.in create mode 100644 programs/robocopy/tests/robocopy.c
diff --git a/configure b/configure index 9e2fa9ef009..297b1783575 100755 --- a/configure +++ b/configure @@ -21265,6 +21265,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 88b9426260f..c8aa29b6628 100644 --- a/configure.ac +++ b/configure.ac @@ -3964,6 +3964,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..146246e4b1a --- /dev/null +++ b/programs/robocopy/tests/Makefile.in @@ -0,0 +1,5 @@ +TESTDLL = robocopy.exe +IMPORTS = user32 + +C_SRCS = \ + robocopy.c diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c new file mode 100644 index 00000000000..ca3d8d7da42 --- /dev/null +++ b/programs/robocopy/tests/robocopy.c @@ -0,0 +1,80 @@ +/* + * 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 *command_line, DWORD expected_exit_code) +{ + STARTUPINFOW startup_information; + PROCESS_INFORMATION process_information; + DWORD return_value; + DWORD process_exit_code; + WCHAR command_line_copy[2048]; + + memset(&startup_information, 0, sizeof(STARTUPINFOW)); + startup_information.dwFlags = STARTF_USESTDHANDLES; + + /* CreateProcess must not be called with static strings */ + wcscpy(command_line_copy, command_line); + + if (!CreateProcessW(NULL, command_line_copy, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &startup_information, &process_information)) + { + ok(expected_exit_code == -1, "process execution of "%S" failed with error %d\n", command_line_copy, GetLastError()); + return -1; + } + + return_value = WaitForSingleObject(process_information.hProcess, 30000); + if (return_value == WAIT_TIMEOUT) + { + TerminateProcess(process_information.hProcess, 1); + ok(FALSE, "process (executing "%S") timed out\n", command_line_copy); + return -1; + } + + GetExitCodeProcess(process_information.hProcess, &process_exit_code); + + CloseHandle(process_information.hThread); + CloseHandle(process_information.hProcess); + + /* also accept any exit code if expected exit code is -1 */ + ok((expected_exit_code == process_exit_code) || (expected_exit_code == -1), + "process exit code was %d, but expected %d\n", process_exit_code, expected_exit_code); + return process_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) == -1) return; + + /* set CWD to temp folder */ + ok(SetCurrentDirectoryW(temp_path), "couldn't set CWD to temp folder "%S"", temp_path); + + /* TODO: conformance tests here */ + + /* Reset CWD to previous folder */ + ok(SetCurrentDirectoryW(previous_cwd_path), "couldn't set CWD to previous CWD folder "%S"", previous_cwd_path); +} \ No newline at end of file
On 9/6/21 9:54 AM, Florian Eder wrote:
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
configure | 1 + configure.ac | 1 + programs/robocopy/tests/Makefile.in | 5 ++ programs/robocopy/tests/robocopy.c | 80 +++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+) create mode 100644 programs/robocopy/tests/Makefile.in create mode 100644 programs/robocopy/tests/robocopy.c
diff --git a/configure b/configure index 9e2fa9ef009..297b1783575 100755 --- a/configure +++ b/configure @@ -21265,6 +21265,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 88b9426260f..c8aa29b6628 100644 --- a/configure.ac +++ b/configure.ac @@ -3964,6 +3964,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..146246e4b1a --- /dev/null +++ b/programs/robocopy/tests/Makefile.in @@ -0,0 +1,5 @@ +TESTDLL = robocopy.exe +IMPORTS = user32
Why does this import user32?
+C_SRCS = \
- robocopy.c
diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c new file mode 100644 index 00000000000..ca3d8d7da42 --- /dev/null +++ b/programs/robocopy/tests/robocopy.c @@ -0,0 +1,80 @@ +/*
- 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 *command_line, DWORD expected_exit_code) +{
- STARTUPINFOW startup_information;
- PROCESS_INFORMATION process_information;
- DWORD return_value;
- DWORD process_exit_code;
- WCHAR command_line_copy[2048];
This is a bit of a nitpick, but these variable names are more verbose than they need to be. E.g. "return_value" is usually just spelled "ret" elsewhere. "startup_information" can be "startup_info".
- memset(&startup_information, 0, sizeof(STARTUPINFOW));
- startup_information.dwFlags = STARTF_USESTDHANDLES;
- /* CreateProcess must not be called with static strings */
- wcscpy(command_line_copy, command_line);
- if (!CreateProcessW(NULL, command_line_copy, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &startup_information, &process_information))
What's the purpose of CREATE_NO_WINDOW here?
This line is also very long. We don't have a set line length, but I'd recommend breaking after 120 characters, if not sooner.
- {
ok(expected_exit_code == -1, "process execution of \"%S\" failed with error %d\n", command_line_copy, GetLastError());
return -1;
- }
- return_value = WaitForSingleObject(process_information.hProcess, 30000);
- if (return_value == WAIT_TIMEOUT)
- {
TerminateProcess(process_information.hProcess, 1);
ok(FALSE, "process (executing \"%S\") timed out\n", command_line_copy);
ok(FALSE) is something of an antipattern; you generally want ok(!return_value) instead.
Personally I also don't think it's worth trying to clean up like this if you're going to print a failure message anyway, especially if there's no reasonable way this should fail. A test failure is a test failure; it needs to be fixed either way.
return -1;
- }
- GetExitCodeProcess(process_information.hProcess, &process_exit_code);
- CloseHandle(process_information.hThread);
- CloseHandle(process_information.hProcess);
- /* also accept any exit code if expected exit code is -1 */
- ok((expected_exit_code == process_exit_code) || (expected_exit_code == -1),
"process exit code was %d, but expected %d\n", process_exit_code, expected_exit_code);
Usually I'd prefer to just move the ok() to the caller.
- return process_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) == -1) return;
- /* set CWD to temp folder */
This comment is redundant; I can see that from the next line.
- ok(SetCurrentDirectoryW(temp_path), "couldn't set CWD to temp folder "%S"", temp_path);
- /* TODO: conformance tests here */
- /* Reset CWD to previous folder */
- ok(SetCurrentDirectoryW(previous_cwd_path), "couldn't set CWD to previous CWD folder "%S"", previous_cwd_path);
+} \ No newline at end of file
Whitespace error.
Parses (relative / absolute) path arguments as source, destination and files to include
Signed-off-by: Florian Eder others.meder@gmail.com --- Splitted from the next patch to enhance readabilty a bit :-) --- programs/robocopy/Makefile.in | 2 +- programs/robocopy/main.c | 80 +++++++++++++++++++++++++++++++++++ programs/robocopy/robocopy.h | 14 +++++- 3 files changed, 94 insertions(+), 2 deletions(-)
diff --git a/programs/robocopy/Makefile.in b/programs/robocopy/Makefile.in index 5463edcb8b3..3f16d00c0a8 100644 --- a/programs/robocopy/Makefile.in +++ b/programs/robocopy/Makefile.in @@ -1,5 +1,5 @@ MODULE = robocopy.exe -IMPORTS = +IMPORTS = kernelbase
EXTRADLLFLAGS = -mconsole -municode -mno-cygwin
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index a184235e488..8f8974b3528 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -21,9 +21,89 @@ 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 BOOL is_valid_robocopy_flag(WCHAR *string) +{ + /* + * 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 (string[0] != L'/') return FALSE; + if (wcschr(string + 1, L'/') != NULL) return FALSE; + return TRUE; +} + +WCHAR *get_absolute_path_with_trailing_backslash(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 + { + 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 * sizeof(WCHAR*))); + + for (i = 1; i < argc; i++) + { + if (is_valid_robocopy_flag(argv[i])) + WINE_FIXME("encountered an unknown robocopy flag: %S\n", 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_with_trailing_backslash(argv[i]); + } + else if (!options.destination) + { + options.destination = get_absolute_path_with_trailing_backslash(argv[i]); + } + else + { + options.files->array[options.files->size] = calloc(wcslen(argv[i]) + 1, sizeof(WCHAR)); + wcscpy(options.files->array[options.files->size], argv[i]); + options.files->size++; + } + } + } +}
int __cdecl wmain(int argc, WCHAR *argv[]) { + parse_arguments(argc, argv); + WINE_FIXME("robocopy stub"); return 0; } \ No newline at end of file diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index 33e84b82ea0..aa857cd8c35 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -16,4 +16,16 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
-#define WIN32_LEAN_AND_MEAN \ No newline at end of file +#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; +}; \ No newline at end of file
On 9/6/21 9:54 AM, Florian Eder wrote:
Parses (relative / absolute) path arguments as source, destination and files to include
Signed-off-by: Florian Eder others.meder@gmail.com
Splitted from the next patch to enhance readabilty a bit :-)
programs/robocopy/Makefile.in | 2 +- programs/robocopy/main.c | 80 +++++++++++++++++++++++++++++++++++ programs/robocopy/robocopy.h | 14 +++++- 3 files changed, 94 insertions(+), 2 deletions(-)
diff --git a/programs/robocopy/Makefile.in b/programs/robocopy/Makefile.in index 5463edcb8b3..3f16d00c0a8 100644 --- a/programs/robocopy/Makefile.in +++ b/programs/robocopy/Makefile.in @@ -1,5 +1,5 @@ MODULE = robocopy.exe -IMPORTS = +IMPORTS = kernelbase
EXTRADLLFLAGS = -mconsole -municode -mno-cygwin
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index a184235e488..8f8974b3528 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -21,9 +21,89 @@ 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 BOOL is_valid_robocopy_flag(WCHAR *string)
You could probably shorten this name to "is_valid_flag" or even "is_flag". For that matter, it seems not unreasonable to just open-code it where it's used.
+{
- /*
* 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 (string[0] != L'/') return FALSE;
- if (wcschr(string + 1, L'/') != NULL) return FALSE;
- return TRUE;
+}
+WCHAR *get_absolute_path_with_trailing_backslash(WCHAR *path)
This is an awfully long name; it seems reasonable to me to just call it get_absolute_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
- {
absolute_path = calloc(size + 4, sizeof(WCHAR));
wcscpy(absolute_path, L"\\\\?\\");
What's the purpose of this part?
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 * sizeof(WCHAR*)));
This can be expressed as "offsetof(struct path_array, array[argc])".
- for (i = 1; i < argc; i++)
- {
if (is_valid_robocopy_flag(argv[i]))
WINE_FIXME("encountered an unknown robocopy flag: %S\n", 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_with_trailing_backslash(argv[i]);
}
else if (!options.destination)
{
options.destination = get_absolute_path_with_trailing_backslash(argv[i]);
}
else
{
options.files->array[options.files->size] = calloc(wcslen(argv[i]) + 1, sizeof(WCHAR));
wcscpy(options.files->array[options.files->size], argv[i]);
options.files->size++;
}
}
- }
+}
int __cdecl wmain(int argc, WCHAR *argv[]) {
- parse_arguments(argc, argv);
}WINE_FIXME("robocopy stub"); return 0;
\ No newline at end of file diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index 33e84b82ea0..aa857cd8c35 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -16,4 +16,16 @@
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
-#define WIN32_LEAN_AND_MEAN \ No newline at end of file +#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;
+};
A couple style nitpicks here: inconsistent asterisk style, and inconsistent brace style.
\ No newline at end of file
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 --- programs/robocopy/main.c | 66 +++++++++++++++++++++++++++++++++++ programs/robocopy/robocopy.h | 9 ++++- programs/robocopy/robocopy.rc | 11 ++++++ 3 files changed, 85 insertions(+), 1 deletion(-)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 8f8974b3528..97b961a5d0d 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -27,6 +27,46 @@ WINE_DEFAULT_DEBUG_CHANNEL(robocopy);
struct robocopy_options options;
+static void output_message(UINT format_string_id, ...) +{ + WCHAR format_string[2048]; + __ms_va_list va_args; + WCHAR *string; + DWORD length, bytes_written; + + if (!LoadStringW(GetModuleHandleW(NULL), format_string_id, format_string, ARRAY_SIZE(format_string))) + { + WINE_ERR("invalid string loaded"); + return; + } + + __ms_va_start(va_args, format_string_id); + 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 BOOL is_valid_robocopy_flag(WCHAR *string) { /* @@ -41,6 +81,13 @@ static BOOL is_valid_robocopy_flag(WCHAR *string) return TRUE; }
+static WCHAR *strip_path_prefix(WCHAR* path) +{ + /* returns a path without the \?\ prefix */ + if (wcslen(path) <= 4) return path; + return &(path[4]); +} + WCHAR *get_absolute_path_with_trailing_backslash(WCHAR *path) { DWORD size; @@ -100,10 +147,29 @@ static void parse_arguments(int argc, WCHAR *argv[]) } }
+static void print_header(void) +{ + UINT i; + + output_message(STRING_HEADER); + + if (!options.source) output_message(STRING_SOURCE, L"-"); + else output_message(STRING_SOURCE, strip_path_prefix(options.source)); + + if (!options.destination) output_message(STRING_DESTINATION, L"-"); + else output_message(STRING_DESTINATION, strip_path_prefix(options.destination)); + + output_message(STRING_FILES, options.files->array[0]); + for (i = 1; i < options.files->size; i++) + output_message(STRING_ADDITIONAL_INFO, options.files->array[i]); +} + int __cdecl wmain(int argc, WCHAR *argv[]) { parse_arguments(argc, argv);
+ print_header(); + WINE_FIXME("robocopy stub"); return 0; } \ No newline at end of file diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index aa857cd8c35..3be51b460a7 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -28,4 +28,11 @@ struct robocopy_options { WCHAR *destination; WCHAR *source; struct path_array* files; -}; \ No newline at end of file +}; + +/* Resource strings */ +#define STRING_HEADER 1000 +#define STRING_SOURCE 1003 +#define STRING_DESTINATION 1004 +#define STRING_FILES 1005 +#define STRING_ADDITIONAL_INFO 1008 \ No newline at end of file diff --git a/programs/robocopy/robocopy.rc b/programs/robocopy/robocopy.rc index c7acd5ad161..e00d9fc0227 100644 --- a/programs/robocopy/robocopy.rc +++ b/programs/robocopy/robocopy.rc @@ -21,6 +21,17 @@
#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: %1\n" + STRING_ADDITIONAL_INFO, " %1\n" +} + LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
#define WINE_FILEDESCRIPTION_STR "Wine Robocopy"
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 --- Does not yet break at a certain depth, so this will copy as deep as possible Wildcards are already supported, so the "file" * will copy all files in any subdirectories --- programs/robocopy/Makefile.in | 2 +- programs/robocopy/main.c | 160 ++++++++++++++++++++++++++++++++++ programs/robocopy/robocopy.h | 10 ++- programs/robocopy/robocopy.rc | 2 + 4 files changed, 172 insertions(+), 2 deletions(-)
diff --git a/programs/robocopy/Makefile.in b/programs/robocopy/Makefile.in index 3f16d00c0a8..b81d799b75a 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 97b961a5d0d..a28b008a8fa 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; @@ -147,6 +149,162 @@ static void parse_arguments(int argc, WCHAR *argv[]) } }
+static BOOL matches_array_entry(WCHAR *name, struct path_array *excluded_names) +{ + int i; + for (i = 0; i < excluded_names->size; i++) + { + if (PathMatchSpecW(name, excluded_names->array[i])) return TRUE; + } + return FALSE; +} + +static WCHAR *get_combined_path(const WCHAR* path_prefix, const WCHAR* path_suffix) +{ + WCHAR *combined_path; + if (path_prefix[wcslen(path_prefix) - 1] == L'\' || !path_prefix[0]) + { + /* path_prefix ends in a backslash (or is empty) */ + combined_path = calloc(wcslen(path_prefix) + wcslen(path_suffix) + 1, sizeof(WCHAR)); + wcscpy(combined_path, path_prefix); + wcscpy(&(combined_path[wcslen(path_prefix)]), path_suffix); + } + else + { + /* path_prefix ends not in a backslash, we have to add one between the strings */ + combined_path = calloc(wcslen(path_prefix) + wcslen(path_suffix) + 2, sizeof(WCHAR)); + wcscpy(combined_path, path_prefix); + wcscpy(&(combined_path[wcslen(path_prefix) + 1]), path_suffix); + combined_path[wcslen(path_prefix)] = L'\'; + } + return combined_path; +} + +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) + { + WINE_FIXME("error create directory %S %d", current_folder, GetLastError()); + return FALSE; + } + } + else + output_message(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 */ + parent_absolute_path = get_combined_path(directory_path, current_path->name); + + /* ignore files, only search in directories (with files or subdirectories in them) */ + if (!PathIsDirectoryW(parent_absolute_path) || PathIsDirectoryEmptyW(parent_absolute_path)) continue; + + /* append * to recieve every file / subdirectory in this directory */ + current_search_path = get_combined_path(parent_absolute_path, L"*"); + /* 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; + + current_relative_path = get_combined_path(current_path->name, entry_data.cFileName); + current_absolute_path = get_combined_path(directory_path, current_relative_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)) + { + WINE_FIXME("error read directory %S %d", options.source, GetLastError()); + return FALSE; + } + + /* create destination folder if it does not yet exist */ + create_directory_path(options.destination); + + /* get files in the destination folder and source folder */ + get_file_paths_in_folder(options.source, &paths_source); + + /* get files in the source folder */ + LIST_FOR_EACH_ENTRY(current_path, &paths_source, struct path, entry) + { + /* append the relative path to the source to get the absolute path of the source file / directory */ + current_absolute_path = get_combined_path(options.source, current_path->name); + + /* append the relative path to the destination to get the target path */ + target_path = get_combined_path(options.destination, current_path->name); + + if (PathIsDirectoryW(current_absolute_path)) + { + /* Create the directory path and then create the directory itself */ + if (!create_directory_path(target_path)) + WINE_FIXME("error write directory %S %d", target_path, GetLastError()); + } + else + { + if (!CopyFileW(current_absolute_path, target_path, FALSE)) + WINE_FIXME("error write file %S %d", target_path, GetLastError()); + else + { + output_message(STRING_CREATE_FILE, strip_path_prefix(target_path)); + } + } + + } + + return TRUE; +} + static void print_header(void) { UINT i; @@ -170,6 +328,8 @@ int __cdecl wmain(int argc, WCHAR *argv[])
print_header();
+ perform_copy(); + WINE_FIXME("robocopy stub"); return 0; } \ No newline at end of file diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index 3be51b460a7..22c7406e0ea 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -18,6 +18,12 @@
#define WIN32_LEAN_AND_MEAN #include <windows.h> +#include <wine/list.h> + +struct path { + struct list entry; + WCHAR *name; +};
struct path_array { UINT size; @@ -35,4 +41,6 @@ struct robocopy_options { #define STRING_SOURCE 1003 #define STRING_DESTINATION 1004 #define STRING_FILES 1005 -#define STRING_ADDITIONAL_INFO 1008 \ No newline at end of file +#define STRING_ADDITIONAL_INFO 1008 +#define STRING_CREATE_DIRECTORY 1019 +#define STRING_CREATE_FILE 1022 \ No newline at end of file diff --git a/programs/robocopy/robocopy.rc b/programs/robocopy/robocopy.rc index e00d9fc0227..92f3b8efe63 100644 --- a/programs/robocopy/robocopy.rc +++ b/programs/robocopy/robocopy.rc @@ -30,6 +30,8 @@ STRINGTABLE STRING_DESTINATION, " Destination: %1\n\n" STRING_FILES, " Files: %1\n" STRING_ADDITIONAL_INFO, " %1\n" + STRING_CREATE_DIRECTORY, " Created Dir: %1\n" + STRING_CREATE_FILE, " Copied File: %1\n" }
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
On 9/6/21 9:54 AM, Florian Eder wrote:
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
Does not yet break at a certain depth, so this will copy as deep as possible Wildcards are already supported, so the "file" * will copy all files in any subdirectories
programs/robocopy/Makefile.in | 2 +- programs/robocopy/main.c | 160 ++++++++++++++++++++++++++++++++++ programs/robocopy/robocopy.h | 10 ++- programs/robocopy/robocopy.rc | 2 + 4 files changed, 172 insertions(+), 2 deletions(-)
diff --git a/programs/robocopy/Makefile.in b/programs/robocopy/Makefile.in index 3f16d00c0a8..b81d799b75a 100644 --- a/programs/robocopy/Makefile.in +++ b/programs/robocopy/Makefile.in @@ -1,5 +1,5 @@ MODULE = robocopy.exe -IMPORTS = kernelbase +IMPORTS = kernelbase shlwapi
If this is for Path* methods, I believe kernelbase exports them too?
EXTRADLLFLAGS = -mconsole -municode -mno-cygwin
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 97b961a5d0d..a28b008a8fa 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; @@ -147,6 +149,162 @@ static void parse_arguments(int argc, WCHAR *argv[]) } }
+static BOOL matches_array_entry(WCHAR *name, struct path_array *excluded_names)
"excluded" doesn't inhere to this function, and I think it's inaccurate in (as of this patch) its only user.
+{
- int i;
- for (i = 0; i < excluded_names->size; i++)
- {
if (PathMatchSpecW(name, excluded_names->array[i])) return TRUE;
- }
- return FALSE;
+}
+static WCHAR *get_combined_path(const WCHAR* path_prefix, const WCHAR* path_suffix)
More inconsistent asterisk placement.
+{
- WCHAR *combined_path;
- if (path_prefix[wcslen(path_prefix) - 1] == L'\' || !path_prefix[0])
- {
/* path_prefix ends in a backslash (or is empty) */
combined_path = calloc(wcslen(path_prefix) + wcslen(path_suffix) + 1, sizeof(WCHAR));
wcscpy(combined_path, path_prefix);
wcscpy(&(combined_path[wcslen(path_prefix)]), path_suffix);
This can be wcscat(), right?
- }
- else
- {
/* path_prefix ends not in a backslash, we have to add one between the strings */
combined_path = calloc(wcslen(path_prefix) + wcslen(path_suffix) + 2, sizeof(WCHAR));
wcscpy(combined_path, path_prefix);
wcscpy(&(combined_path[wcslen(path_prefix) + 1]), path_suffix);
combined_path[wcslen(path_prefix)] = L'\\';
Same here, or even swprintf().
For that matter I'm wondering if this whole function could be reasonably replaced with PathAllocCombine().
- }
- return combined_path;
+}
+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)
{
WINE_FIXME("error create directory %S %d", current_folder, GetLastError());
This is a bit awkward as-is; I think it'd be reasonable to merge patch 6 with this one.
return FALSE;
}
}
else
output_message(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 */
A lot of these comments feel redundant to me.
- 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 */
parent_absolute_path = get_combined_path(directory_path, current_path->name);
/* ignore files, only search in directories (with files or subdirectories in them) */
if (!PathIsDirectoryW(parent_absolute_path) || PathIsDirectoryEmptyW(parent_absolute_path)) continue;
Do we need PathIsDirectoryEmpty()? Note that it internally does a FindFirstFile(); I don't think it's saving us any time.
For that matter, do we need PathIsDirectory()? Will FindFirstFile just return an error in that case? On the other hand, maybe we want to complain if we get an error from FindFirstFile(), or an error from FindNextFile() other than ERROR_NO_MORE_FILES.
/* append * to recieve every file / subdirectory in this directory */
current_search_path = get_combined_path(parent_absolute_path, L"*");
/* 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;
current_relative_path = get_combined_path(current_path->name, entry_data.cFileName);
current_absolute_path = get_combined_path(directory_path, current_relative_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))
- {
WINE_FIXME("error read directory %S %d", options.source, GetLastError());
return FALSE;
- }
- /* create destination folder if it does not yet exist */
- create_directory_path(options.destination);
- /* get files in the destination folder and source folder */
- get_file_paths_in_folder(options.source, &paths_source);
- /* get files in the source folder */
- LIST_FOR_EACH_ENTRY(current_path, &paths_source, struct path, entry)
- {
/* append the relative path to the source to get the absolute path of the source file / directory */
current_absolute_path = get_combined_path(options.source, current_path->name);
/* append the relative path to the destination to get the target path */
target_path = get_combined_path(options.destination, current_path->name);
if (PathIsDirectoryW(current_absolute_path))
{
/* Create the directory path and then create the directory itself */
if (!create_directory_path(target_path))
WINE_FIXME("error write directory %S %d", target_path, GetLastError());
}
else
{
if (!CopyFileW(current_absolute_path, target_path, FALSE))
WINE_FIXME("error write file %S %d", target_path, GetLastError());
else
{
output_message(STRING_CREATE_FILE, strip_path_prefix(target_path));
}
}
- }
- return TRUE;
+}
- static void print_header(void) { UINT i;
@@ -170,6 +328,8 @@ int __cdecl wmain(int argc, WCHAR *argv[])
print_header();
- perform_copy();
WINE_FIXME("robocopy stub");
At this point I don't think it's a stub any more ;-)
return 0;
} \ No newline at end of file diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index 3be51b460a7..22c7406e0ea 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -18,6 +18,12 @@
#define WIN32_LEAN_AND_MEAN #include <windows.h> +#include <wine/list.h>
+struct path {
- struct list entry;
- WCHAR *name;
+};
struct path_array { UINT size; @@ -35,4 +41,6 @@ struct robocopy_options { #define STRING_SOURCE 1003 #define STRING_DESTINATION 1004 #define STRING_FILES 1005 -#define STRING_ADDITIONAL_INFO 1008 \ No newline at end of file +#define STRING_ADDITIONAL_INFO 1008 +#define STRING_CREATE_DIRECTORY 1019 +#define STRING_CREATE_FILE 1022 \ No newline at end of file diff --git a/programs/robocopy/robocopy.rc b/programs/robocopy/robocopy.rc index e00d9fc0227..92f3b8efe63 100644 --- a/programs/robocopy/robocopy.rc +++ b/programs/robocopy/robocopy.rc @@ -30,6 +30,8 @@ STRINGTABLE STRING_DESTINATION, " Destination: %1\n\n" STRING_FILES, " Files: %1\n" STRING_ADDITIONAL_INFO, " %1\n"
STRING_CREATE_DIRECTORY, " Created Dir: %1\n"
STRING_CREATE_FILE, " Copied File: %1\n" }
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
Outputs error messages with the affected file / directory, the error code and its meaning
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/main.c | 23 +++++++++++++++++++---- programs/robocopy/robocopy.h | 3 +++ programs/robocopy/robocopy.rc | 3 +++ 3 files changed, 25 insertions(+), 4 deletions(-)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index a28b008a8fa..050258ddab2 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -69,6 +69,21 @@ static void output_message(UINT format_string_id, ...) 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_id, L"", error_code_short, error_code_long, path, error_string); + + LocalFree(error_string); +} + static BOOL is_valid_robocopy_flag(WCHAR *string) { /* @@ -194,7 +209,7 @@ static BOOL create_directory_path(WCHAR *path) { if (GetLastError() != ERROR_ALREADY_EXISTS) { - WINE_FIXME("error create directory %S %d", current_folder, GetLastError()); + output_error(STRING_ERROR_WRITE_DIRECTORY, GetLastError(), strip_path_prefix(current_folder)); return FALSE; } } @@ -265,7 +280,7 @@ static BOOL perform_copy(void)
if (!PathIsDirectoryW(options.source)) { - WINE_FIXME("error read directory %S %d", options.source, GetLastError()); + output_error(STRING_ERROR_READ_DIRECTORY, ERROR_FILE_NOT_FOUND, strip_path_prefix(options.source)); return FALSE; }
@@ -288,12 +303,12 @@ static BOOL perform_copy(void) { /* Create the directory path and then create the directory itself */ if (!create_directory_path(target_path)) - WINE_FIXME("error write directory %S %d", target_path, GetLastError()); + output_error(STRING_ERROR_WRITE_DIRECTORY, GetLastError(), strip_path_prefix(target_path)); } else { if (!CopyFileW(current_absolute_path, target_path, FALSE)) - WINE_FIXME("error write file %S %d", target_path, GetLastError()); + output_error(STRING_ERROR_WRITE_FILE, GetLastError(), strip_path_prefix(target_path)); else { output_message(STRING_CREATE_FILE, strip_path_prefix(target_path)); diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index 22c7406e0ea..1cd0a427c03 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -42,5 +42,8 @@ struct robocopy_options { #define STRING_DESTINATION 1004 #define STRING_FILES 1005 #define STRING_ADDITIONAL_INFO 1008 +#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 \ No newline at end of file diff --git a/programs/robocopy/robocopy.rc b/programs/robocopy/robocopy.rc index 92f3b8efe63..45d7bdacd69 100644 --- a/programs/robocopy/robocopy.rc +++ b/programs/robocopy/robocopy.rc @@ -30,6 +30,9 @@ STRINGTABLE STRING_DESTINATION, " Destination: %1\n\n" STRING_FILES, " Files: %1\n" STRING_ADDITIONAL_INFO, " %1\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" }
Implements the /LEV:n switch, which sets the max subdirectory depth and sets default depth to 1
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/main.c | 27 +++++++++++++++++++++++---- programs/robocopy/robocopy.h | 2 ++ 2 files changed, 25 insertions(+), 4 deletions(-)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 050258ddab2..747c242e7de 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -136,10 +136,26 @@ static void parse_arguments(int argc, WCHAR *argv[]) memset(&options, 0, sizeof(options)); options.files = calloc(1, offsetof(struct path_array, array) + (argc * sizeof(WCHAR*)));
+ /* default values */ + options.max_subdirectories_depth = 1; + for (i = 1; i < argc; i++) { if (is_valid_robocopy_flag(argv[i])) - WINE_FIXME("encountered an unknown robocopy flag: %S\n", argv[i]); + /* lev - Limit depth of subdirectories */ + if (!wcsnicmp(argv[i], L"/lev:", 5)) + { + long value = 0; + value = wcstol(&(argv[i][5]), NULL, 10); + if (value >= 0) + { + options.max_subdirectories_depth = (UINT)value; + } + } + else + { + WINE_FIXME("encountered an unknown robocopy flag: %S\n", argv[i]); + } else { /* @@ -220,7 +236,7 @@ static BOOL create_directory_path(WCHAR *path) return TRUE; }
-static void get_file_paths_in_folder(WCHAR *directory_path, struct list *paths) +static void get_file_paths_in_folder(WCHAR *directory_path, struct list *paths, UINT depth) { HANDLE temp_handle; struct path *new_path, *current_path; @@ -232,6 +248,7 @@ static void get_file_paths_in_folder(WCHAR *directory_path, struct list *paths) /* initialize list with a empty relative path */ new_path = calloc(1, sizeof(struct path)); new_path->name = calloc(2, sizeof(WCHAR)); + new_path->level = 1; list_add_tail(paths, &new_path->entry);
LIST_FOR_EACH_ENTRY(current_path, paths, struct path, entry) @@ -258,10 +275,11 @@ static void get_file_paths_in_folder(WCHAR *directory_path, struct list *paths)
/* 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))) + (PathIsDirectoryW(current_absolute_path) && (!depth || depth > current_path->level))) { new_path = calloc(1, sizeof(struct path)); new_path->name = wcsdup(current_relative_path); + new_path->level = current_path->level + 1; list_add_tail(paths, &new_path->entry); } } @@ -288,7 +306,7 @@ static BOOL perform_copy(void) create_directory_path(options.destination);
/* get files in the destination folder and source folder */ - get_file_paths_in_folder(options.source, &paths_source); + get_file_paths_in_folder(options.source, &paths_source, options.max_subdirectories_depth);
/* get files in the source folder */ LIST_FOR_EACH_ENTRY(current_path, &paths_source, struct path, entry) @@ -307,6 +325,7 @@ static BOOL perform_copy(void) } 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 diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index 1cd0a427c03..0e33effe331 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -23,6 +23,7 @@ struct path { struct list entry; WCHAR *name; + UINT level; };
struct path_array { @@ -34,6 +35,7 @@ struct robocopy_options { WCHAR *destination; WCHAR *source; struct path_array* files; + UINT max_subdirectories_depth; };
/* Resource strings */
Implements the /S switch, which copies all subdirectories of a directory
Signed-off-by: Florian Eder others.meder@gmail.com --- However, /LEV has a higher priority than /S, so "/S /LEV:2" limits to depth 2 --- programs/robocopy/main.c | 10 +++++++++- programs/robocopy/robocopy.h | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 747c242e7de..6ebb0e721c4 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -142,14 +142,22 @@ static void parse_arguments(int argc, WCHAR *argv[]) for (i = 1; i < argc; i++) { if (is_valid_robocopy_flag(argv[i])) + /* s - Copy Subdirectories */ + if (!wcsicmp(argv[i], L"/s")) + { + options.copy_subdirectories = TRUE; + if (!options.user_limited_subdirectories_depth) + options.max_subdirectories_depth = 0; + } /* lev - Limit depth of subdirectories */ - if (!wcsnicmp(argv[i], L"/lev:", 5)) + else if (!wcsnicmp(argv[i], L"/lev:", 5)) { long value = 0; value = wcstol(&(argv[i][5]), NULL, 10); if (value >= 0) { options.max_subdirectories_depth = (UINT)value; + options.user_limited_subdirectories_depth = TRUE; } } else diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index 0e33effe331..4b322f8b6f9 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -36,6 +36,8 @@ struct robocopy_options { WCHAR *source; struct path_array* files; UINT max_subdirectories_depth; + BOOL user_limited_subdirectories_depth; + BOOL copy_subdirectories; };
/* Resource strings */
Implements the /E switch, which sets whether empty subdirectories are copied or not
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/main.c | 11 +++++++++++ programs/robocopy/robocopy.h | 1 + 2 files changed, 12 insertions(+)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 6ebb0e721c4..fe550134508 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -149,6 +149,14 @@ static void parse_arguments(int argc, WCHAR *argv[]) if (!options.user_limited_subdirectories_depth) options.max_subdirectories_depth = 0; } + /* e - Copy Subdirectories, including empty ones */ + else if (!wcsicmp(argv[i], L"/e")) + { + options.copy_subdirectories = TRUE; + options.copy_empty_subdirectories = TRUE; + if (!options.user_limited_subdirectories_depth) + options.max_subdirectories_depth = 0; + } /* lev - Limit depth of subdirectories */ else if (!wcsnicmp(argv[i], L"/lev:", 5)) { @@ -327,6 +335,9 @@ static BOOL perform_copy(void)
if (PathIsDirectoryW(current_absolute_path)) { + /* ignore empty directories if empty subdirectories are not copied */ + if ((PathIsDirectoryEmptyW(current_absolute_path) && !options.copy_empty_subdirectories)) continue; + /* Create the directory path and then create the directory itself */ if (!create_directory_path(target_path)) output_error(STRING_ERROR_WRITE_DIRECTORY, GetLastError(), strip_path_prefix(target_path)); diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index 4b322f8b6f9..57797c7df69 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -38,6 +38,7 @@ struct robocopy_options { UINT max_subdirectories_depth; BOOL user_limited_subdirectories_depth; BOOL copy_subdirectories; + BOOL copy_empty_subdirectories; };
/* Resource strings */
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/main.c | 30 ++++++++++++++++++++++++++++++ programs/robocopy/robocopy.h | 1 + programs/robocopy/robocopy.rc | 1 + 3 files changed, 32 insertions(+)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index fe550134508..18bfe1b13f6 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -358,9 +358,36 @@ static BOOL perform_copy(void) return TRUE; }
+static WCHAR *get_option_string(void) +{ + WCHAR *string, temp_string[512]; + memset(temp_string, 0, sizeof(temp_string)); + + /* If no files set, display *.* */ + if (options.files->size == 0) + wcscat(temp_string, L"*.* "); + + /* Subdirectories */ + if (options.copy_subdirectories) + wcscat(temp_string, L"/S "); + + /* Max Subdirectory Depth */ + if (options.user_limited_subdirectories_depth) + swprintf(temp_string + wcslen(temp_string), ARRAY_SIZE(temp_string) - wcslen(temp_string), + L"/LEV:%u ", options.max_subdirectories_depth); + + /* Empty Subdirectories */ + if (options.copy_empty_subdirectories) + wcscat(temp_string, L"/E "); + + string = wcsdup(temp_string); + return string; +} + static void print_header(void) { UINT i; + WCHAR *options_string;
output_message(STRING_HEADER);
@@ -373,6 +400,9 @@ static void print_header(void) output_message(STRING_FILES, options.files->array[0]); for (i = 1; i < options.files->size; i++) output_message(STRING_ADDITIONAL_INFO, options.files->array[i]); + + options_string = get_option_string(); + if (options_string != NULL) output_message(STRING_OPTIONS, options_string); }
int __cdecl wmain(int argc, WCHAR *argv[]) diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index 57797c7df69..96901dfb597 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -47,6 +47,7 @@ struct robocopy_options { #define STRING_DESTINATION 1004 #define STRING_FILES 1005 #define STRING_ADDITIONAL_INFO 1008 +#define STRING_OPTIONS 1009 #define STRING_ERROR_READ_DIRECTORY 1011 #define STRING_ERROR_WRITE_DIRECTORY 1012 #define STRING_ERROR_WRITE_FILE 1014 diff --git a/programs/robocopy/robocopy.rc b/programs/robocopy/robocopy.rc index 45d7bdacd69..519deca3a4d 100644 --- a/programs/robocopy/robocopy.rc +++ b/programs/robocopy/robocopy.rc @@ -30,6 +30,7 @@ STRINGTABLE STRING_DESTINATION, " Destination: %1\n\n" STRING_FILES, " Files: %1\n" STRING_ADDITIONAL_INFO, " %1\n" + STRING_OPTIONS, "\n Options: %1\n\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"
Sets *.* as default if no files to include are set by the user, checks whether both source and destination are set and introduces (the correct) exit codes
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/main.c | 21 ++++++++++++++++++--- programs/robocopy/robocopy.h | 5 +++++ programs/robocopy/robocopy.rc | 1 + 3 files changed, 24 insertions(+), 3 deletions(-)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 18bfe1b13f6..96fbdade6de 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -409,10 +409,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();
- perform_copy(); + /* Break if Source or Destination not set */ + if (!options.destination || !options.source) + { + output_message(STRING_MISSING_DESTINATION_OR_SOURCE); + return ROBOCOPY_ERROR_NO_FILES_COPIED; + } + + if (!perform_copy()) + return ROBOCOPY_ERROR_NO_FILES_COPIED;
- WINE_FIXME("robocopy stub"); - return 0; + return ROBOCOPY_NO_ERROR_FILES_COPIED; } \ No newline at end of file diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index 96901dfb597..bdce05fdcde 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -41,6 +41,10 @@ struct robocopy_options { BOOL copy_empty_subdirectories; };
+/* 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 @@ -48,6 +52,7 @@ struct robocopy_options { #define STRING_FILES 1005 #define STRING_ADDITIONAL_INFO 1008 #define STRING_OPTIONS 1009 +#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 diff --git a/programs/robocopy/robocopy.rc b/programs/robocopy/robocopy.rc index 519deca3a4d..7c025b62b11 100644 --- a/programs/robocopy/robocopy.rc +++ b/programs/robocopy/robocopy.rc @@ -31,6 +31,7 @@ STRINGTABLE STRING_FILES, " Files: %1\n" STRING_ADDITIONAL_INFO, " %1\n" STRING_OPTIONS, "\n Options: %1\n\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"
Basic conformance tests create a test source directory with files and execute robocopy on it, checking whether the resulting destination directory and the exit code is as expected
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/tests/robocopy.c | 183 ++++++++++++++++++++++++++++- 1 file changed, 181 insertions(+), 2 deletions(-)
diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c index ca3d8d7da42..433b65c587f 100644 --- a/programs/robocopy/tests/robocopy.c +++ b/programs/robocopy/tests/robocopy.c @@ -60,9 +60,152 @@ static DWORD execute_robocopy(const WCHAR *command_line, DWORD expected_exit_cod return process_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); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA", FALSE); + 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];
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"); @@ -73,7 +216,43 @@ START_TEST(robocopy) /* set CWD to temp folder */ 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(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /r:1 /w:0", 1); + check_basic_copy_test(); + winetest_pop_context(); + + winetest_push_context("basic copy test 2"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe ./robocopy_source third_folder/../robocopy_destination /r:1 /w:0", 1); + 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); + execute_robocopy(L"robocopy.exe ./robocopy_source robocopy_source/../robocopy_destination /r:1 /w:0", 1); + 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); + execute_robocopy(temp_command_line, 1); + 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); + execute_robocopy(temp_command_line, 1); + check_basic_copy_test(); + winetest_pop_context();
/* Reset CWD to previous folder */ ok(SetCurrentDirectoryW(previous_cwd_path), "couldn't set CWD to previous CWD folder "%S"", previous_cwd_path);
Checks if files are (correctly) overwritten if they already exist in the destination folder, by checking whether the file size is equal in source and destination
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/tests/robocopy.c | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+)
diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c index 433b65c587f..4bbf600e45a 100644 --- a/programs/robocopy/tests/robocopy.c +++ b/programs/robocopy/tests/robocopy.c @@ -172,8 +172,48 @@ static void check_folder_and_delete(const WCHAR *relative_path, BOOL should_exis } }
+static void check_files_equal(const WCHAR *relative_path_1, const WCHAR *relative_path_2, BOOL should_be_equal) +{ + WCHAR path_1[1024], path_2[1024]; + HANDLE file_1, file_2; + LARGE_INTEGER size_1, size_2; + wcscpy(path_1, L"\\?\"); + GetTempPathW(ARRAY_SIZE(path_1) - 4, &(path_1[4])); + wcscpy(path_2, L"\\?\"); + GetTempPathW(ARRAY_SIZE(path_2) - 4, &(path_2[4])); + wcscat(path_1, relative_path_1); + wcscat(path_2, relative_path_2); + file_1 = CreateFileW(path_1, FILE_GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + file_2 = CreateFileW(path_2, FILE_GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (file_1 == INVALID_HANDLE_VALUE || file_2 == INVALID_HANDLE_VALUE) + { + ok(file_1 != INVALID_HANDLE_VALUE, "file "%S" does not exist, can't check equality with file "%S"\n", relative_path_1, relative_path_2); + ok(file_2 != INVALID_HANDLE_VALUE, "file "%S" does not exist, can't check equality with file "%S"\n", relative_path_2, relative_path_1); + CloseHandle(file_1); + CloseHandle(file_2); + return; + } + GetFileSizeEx(file_1, &size_1); + GetFileSizeEx(file_2, &size_2); + CloseHandle(file_1); + CloseHandle(file_2); + if (size_1.QuadPart == size_2.QuadPart) + { + ok(should_be_equal, "files "%S" and "%S" should be different, but are equal (size: %lld bytes)\n", + relative_path_1, relative_path_2, size_1.QuadPart); + } + else + { + ok(!should_be_equal, "files "%S" and "%S" should be equal, but are different (size: %lld bytes vs %lld bytes)\n", + relative_path_1, relative_path_2, size_1.QuadPart, size_2.QuadPart); + } +} + static void check_basic_copy_test(void) { + check_files_equal(L"robocopy_source\fileA.a", L"robocopy_destination\fileA.a", TRUE); + check_files_equal(L"robocopy_source\fileB.b", L"robocopy_destination\fileB.b", TRUE); + 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);
Implements the /MOV switch, which causes the files to be moved instead of being copied to the destination
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/main.c | 28 +++++++++++++++++++++++----- programs/robocopy/robocopy.h | 4 +++- programs/robocopy/robocopy.rc | 1 + 3 files changed, 27 insertions(+), 6 deletions(-)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 96fbdade6de..b9356b4d104 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -157,6 +157,11 @@ static void parse_arguments(int argc, WCHAR *argv[]) if (!options.user_limited_subdirectories_depth) options.max_subdirectories_depth = 0; } + /* mov - Delete files (but not folders) after copying them */ + else if (!wcsicmp(argv[i], L"/mov")) + { + options.purge_source_files = TRUE; + } /* lev - Limit depth of subdirectories */ else if (!wcsnicmp(argv[i], L"/lev:", 5)) { @@ -252,6 +257,18 @@ static BOOL create_directory_path(WCHAR *path) return TRUE; }
+static BOOL copy_or_move_file(WCHAR *source, WCHAR *destination, BOOL do_move) +{ + if (!create_directory_path(destination)) return FALSE; + if (do_move ? !MoveFileExW(source, destination, MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING) + : !CopyFileW(source, destination, FALSE)) + { + output_error(STRING_ERROR_WRITE_FILE, GetLastError(), strip_path_prefix(destination)); + return FALSE; + } + else return TRUE; +} + static void get_file_paths_in_folder(WCHAR *directory_path, struct list *paths, UINT depth) { HANDLE temp_handle; @@ -344,12 +361,9 @@ static BOOL perform_copy(void) } 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 + if (copy_or_move_file(current_absolute_path, target_path, options.purge_source_files)) { - output_message(STRING_CREATE_FILE, strip_path_prefix(target_path)); + output_message(options.purge_source_files ? STRING_MOVE_FILE : STRING_CREATE_FILE, strip_path_prefix(target_path)); } }
@@ -380,6 +394,10 @@ static WCHAR *get_option_string(void) if (options.copy_empty_subdirectories) wcscat(temp_string, L"/E ");
+ /* Move files */ + if (options.purge_source_files) + wcscat(temp_string, L"/MOV "); + string = wcsdup(temp_string); return string; } diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index bdce05fdcde..b7f5c3db89c 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -39,6 +39,7 @@ struct robocopy_options { BOOL user_limited_subdirectories_depth; BOOL copy_subdirectories; BOOL copy_empty_subdirectories; + BOOL purge_source_files; };
/* Exit codes */ @@ -57,4 +58,5 @@ struct robocopy_options { #define STRING_ERROR_WRITE_DIRECTORY 1012 #define STRING_ERROR_WRITE_FILE 1014 #define STRING_CREATE_DIRECTORY 1019 -#define STRING_CREATE_FILE 1022 \ No newline at end of file +#define STRING_CREATE_FILE 1022 +#define STRING_MOVE_FILE 1024 \ No newline at end of file diff --git a/programs/robocopy/robocopy.rc b/programs/robocopy/robocopy.rc index 7c025b62b11..b4a6f8ab218 100644 --- a/programs/robocopy/robocopy.rc +++ b/programs/robocopy/robocopy.rc @@ -37,6 +37,7 @@ STRINGTABLE 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" + STRING_MOVE_FILE, " Moved File: %1\n" }
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/tests/robocopy.c | 86 ++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+)
diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c index 4bbf600e45a..ab482d6a845 100644 --- a/programs/robocopy/tests/robocopy.c +++ b/programs/robocopy/tests/robocopy.c @@ -243,6 +243,68 @@ static void check_basic_copy_test(void) check_folder_and_delete(L"robocopy_destination", TRUE); }
+static void check_mov_1_test(void) +{ + check_file_and_delete(L"robocopy_source\fileA.a", FALSE); + check_file_and_delete(L"robocopy_source\fileB.b", FALSE); + 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); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA", FALSE); + 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); +} + +static void check_mov_2_test(void) +{ + check_file_and_delete(L"robocopy_source\fileA.a", FALSE); + check_file_and_delete(L"robocopy_source\fileB.b", FALSE); + check_file_and_delete(L"robocopy_source\folderA\fileC.c", FALSE); + check_file_and_delete(L"robocopy_source\folderA\fileD.d", FALSE); + check_file_and_delete(L"robocopy_source\folderA\folderD\fileE.e", FALSE); + check_file_and_delete(L"robocopy_source\folderB\fileF.f", FALSE); + check_file_and_delete(L"robocopy_source\folderB\fileG.g", FALSE); + 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); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA", TRUE); + check_folder_and_delete(L"robocopy_destination\folderB", TRUE); + check_folder_and_delete(L"robocopy_destination\folderC", FALSE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + START_TEST(robocopy) { WCHAR temp_command_line[2048], previous_cwd_path[4096], temp_path[4096]; @@ -294,6 +356,30 @@ START_TEST(robocopy) check_basic_copy_test(); winetest_pop_context();
+ winetest_push_context("MOV test 1"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /mov /r:1 /w:0", 1); + check_mov_1_test(); + winetest_pop_context(); + + winetest_push_context("MOV test 2"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /mov /s /r:1 /w:0", 1); + check_mov_2_test(); + winetest_pop_context(); + + winetest_push_context("MOV test 3"); + create_test_source_folder(); + create_test_folder(L"robocopy_destination"); + create_test_file(L"robocopy_destination\fileA.a", 9000, 0, -10); + create_test_file(L"robocopy_destination\fileA2.a", 9000, 0, -10); + todo_wine execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /mov /r:1 /w:0", 3); + /* check whether fileA was overwritten / not identical anymore with fileA2.a */ + check_files_equal(L"robocopy_destination\fileA2.a", L"robocopy_destination\fileA.a", FALSE); + check_file_and_delete(L"robocopy_destination\fileA2.a", TRUE); + check_mov_1_test(); + winetest_pop_context(); + /* Reset CWD to previous folder */ ok(SetCurrentDirectoryW(previous_cwd_path), "couldn't set CWD to previous CWD folder "%S"", previous_cwd_path); } \ No newline at end of file
Saves and displays the number of files / directories copied or moved
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/main.c | 19 ++++++++++++++++--- programs/robocopy/robocopy.h | 7 +++++++ programs/robocopy/robocopy.rc | 1 + 3 files changed, 24 insertions(+), 3 deletions(-)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index b9356b4d104..3bd8b784796 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -321,7 +321,7 @@ static void get_file_paths_in_folder(WCHAR *directory_path, struct list *paths, } }
-static BOOL perform_copy(void) +static BOOL perform_copy(struct robocopy_statistics *statistics) { struct list paths_source; struct path *current_path; @@ -358,12 +358,14 @@ static BOOL perform_copy(void) /* Create the directory path and then create the directory itself */ if (!create_directory_path(target_path)) output_error(STRING_ERROR_WRITE_DIRECTORY, GetLastError(), strip_path_prefix(target_path)); + else statistics->copied_directories++; } else { if (copy_or_move_file(current_absolute_path, target_path, options.purge_source_files)) { output_message(options.purge_source_files ? STRING_MOVE_FILE : STRING_CREATE_FILE, strip_path_prefix(target_path)); + statistics->copied_files++; } }
@@ -425,6 +427,10 @@ static void print_header(void)
int __cdecl wmain(int argc, WCHAR *argv[]) { + struct robocopy_statistics statistics; + int exit_code; + WCHAR dirs_copied[64], files_copied[64]; + parse_arguments(argc, argv);
/* If no file filters are set, set *.* to include all files */ @@ -444,8 +450,15 @@ int __cdecl wmain(int argc, WCHAR *argv[]) return ROBOCOPY_ERROR_NO_FILES_COPIED; }
- if (!perform_copy()) + memset(&statistics, 0, sizeof(struct robocopy_statistics)); + if (!perform_copy(&statistics)) return ROBOCOPY_ERROR_NO_FILES_COPIED;
- return ROBOCOPY_NO_ERROR_FILES_COPIED; + swprintf(dirs_copied, ARRAY_SIZE(dirs_copied), L"%u", statistics.copied_directories); + swprintf(files_copied, ARRAY_SIZE(files_copied), L"%u", statistics.copied_files); + output_message(STRING_STATISTICS, dirs_copied, files_copied); + + exit_code = ROBOCOPY_NO_ERROR_NO_COPY; + if (statistics.copied_files) exit_code += ROBOCOPY_NO_ERROR_FILES_COPIED; + return exit_code; } \ No newline at end of file diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index b7f5c3db89c..6beac9dbc64 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -42,7 +42,13 @@ struct robocopy_options { BOOL purge_source_files; };
+struct robocopy_statistics { + UINT copied_directories; + UINT copied_files; +}; + /* Exit codes */ +#define ROBOCOPY_NO_ERROR_NO_COPY 0 #define ROBOCOPY_NO_ERROR_FILES_COPIED 1 #define ROBOCOPY_ERROR_NO_FILES_COPIED 16
@@ -57,6 +63,7 @@ struct robocopy_options { #define STRING_ERROR_READ_DIRECTORY 1011 #define STRING_ERROR_WRITE_DIRECTORY 1012 #define STRING_ERROR_WRITE_FILE 1014 +#define STRING_STATISTICS 1018 #define STRING_CREATE_DIRECTORY 1019 #define STRING_CREATE_FILE 1022 #define STRING_MOVE_FILE 1024 \ No newline at end of file diff --git a/programs/robocopy/robocopy.rc b/programs/robocopy/robocopy.rc index b4a6f8ab218..b12501017e2 100644 --- a/programs/robocopy/robocopy.rc +++ b/programs/robocopy/robocopy.rc @@ -35,6 +35,7 @@ STRINGTABLE 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_STATISTICS, "\n Copied %1 directories and %2 files\n" STRING_CREATE_DIRECTORY, " Created Dir: %1\n" STRING_CREATE_FILE, " Copied File: %1\n" STRING_MOVE_FILE, " Moved File: %1\n"
Searches for files in the destination, that are not also in the source folder and modifies the exit code in this case
Signed-off-by: Florian Eder others.meder@gmail.com --- Also removed a wine_todo from tests --- programs/robocopy/main.c | 23 ++++++++++++++++++++++- programs/robocopy/robocopy.h | 2 ++ programs/robocopy/tests/robocopy.c | 2 +- 3 files changed, 25 insertions(+), 2 deletions(-)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 3bd8b784796..2d4195daaec 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -323,11 +323,12 @@ static void get_file_paths_in_folder(WCHAR *directory_path, struct list *paths,
static BOOL perform_copy(struct robocopy_statistics *statistics) { - struct list paths_source; + struct list paths_source, paths_destination; struct path *current_path; WCHAR *current_absolute_path, *target_path;
list_init(&paths_source); + list_init(&paths_destination);
if (!PathIsDirectoryW(options.source)) { @@ -339,6 +340,7 @@ static BOOL perform_copy(struct robocopy_statistics *statistics) create_directory_path(options.destination);
/* get files in the destination folder and source folder */ + get_file_paths_in_folder(options.destination, &paths_destination, options.max_subdirectories_depth); get_file_paths_in_folder(options.source, &paths_source, options.max_subdirectories_depth);
/* get files in the source folder */ @@ -371,6 +373,24 @@ static BOOL perform_copy(struct robocopy_statistics *statistics)
}
+ /* search for extra files (files only in destination) to be able to return the correct exit code */ + LIST_FOR_EACH_ENTRY_REV(current_path, &paths_destination, struct path, entry) + { + struct path *source_entry; + BOOL found = FALSE; + /* only extra files, so abort if the same relative path is also in the source folder */ + LIST_FOR_EACH_ENTRY(source_entry, &paths_source, struct path, entry) + { + if (wcsicmp(source_entry->name, current_path->name) == 0) + { + found = TRUE; + break; + } + } + if (found) continue; + else statistics->extra_files = TRUE; + } + return TRUE; }
@@ -460,5 +480,6 @@ int __cdecl wmain(int argc, WCHAR *argv[])
exit_code = ROBOCOPY_NO_ERROR_NO_COPY; if (statistics.copied_files) exit_code += ROBOCOPY_NO_ERROR_FILES_COPIED; + if (statistics.extra_files) exit_code += ROBOCOPY_EXTRA_FILES_IN_DESTINATION; return exit_code; } \ No newline at end of file diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index 6beac9dbc64..677f88cb453 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -45,11 +45,13 @@ struct robocopy_options { struct robocopy_statistics { UINT copied_directories; UINT copied_files; + BOOL extra_files; };
/* Exit codes */ #define ROBOCOPY_NO_ERROR_NO_COPY 0 #define ROBOCOPY_NO_ERROR_FILES_COPIED 1 +#define ROBOCOPY_EXTRA_FILES_IN_DESTINATION 2 #define ROBOCOPY_ERROR_NO_FILES_COPIED 16
/* Resource strings */ diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c index ab482d6a845..d205127d56f 100644 --- a/programs/robocopy/tests/robocopy.c +++ b/programs/robocopy/tests/robocopy.c @@ -373,7 +373,7 @@ START_TEST(robocopy) create_test_folder(L"robocopy_destination"); create_test_file(L"robocopy_destination\fileA.a", 9000, 0, -10); create_test_file(L"robocopy_destination\fileA2.a", 9000, 0, -10); - todo_wine execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /mov /r:1 /w:0", 3); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /mov /r:1 /w:0", 3); /* check whether fileA was overwritten / not identical anymore with fileA2.a */ check_files_equal(L"robocopy_destination\fileA2.a", L"robocopy_destination\fileA.a", FALSE); check_file_and_delete(L"robocopy_destination\fileA2.a", TRUE);
Implements the /PURGE switch, which clears the destination directory and removes any extra files
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/main.c | 29 +++++++++++++++++++++++++++++ programs/robocopy/robocopy.h | 5 +++++ programs/robocopy/robocopy.rc | 4 ++++ 3 files changed, 38 insertions(+)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 2d4195daaec..0bb0e83783c 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -157,6 +157,11 @@ static void parse_arguments(int argc, WCHAR *argv[]) if (!options.user_limited_subdirectories_depth) options.max_subdirectories_depth = 0; } + /* purge - Purge Destination */ + else if (!wcsicmp(argv[i], L"/purge")) + { + options.purge_destination = TRUE; + } /* mov - Delete files (but not folders) after copying them */ else if (!wcsicmp(argv[i], L"/mov")) { @@ -389,6 +394,26 @@ static BOOL perform_copy(struct robocopy_statistics *statistics) } if (found) continue; else statistics->extra_files = TRUE; + + /* if purge is specified (and xx is not set), we delete the (non-excluded) extra files and folders from the destination */ + if (options.purge_destination) + { + current_absolute_path = get_combined_path(options.destination, current_path->name); + /* only files or empty folders */ + if (!PathIsDirectoryW(current_absolute_path)) + { + if (!DeleteFileW(current_absolute_path)) + output_error(STRING_ERROR_DELETE_FILE, GetLastError(), strip_path_prefix(current_absolute_path)); + else output_message(STRING_DELETE_FILE, strip_path_prefix(current_absolute_path)); + } + else if (PathIsDirectoryEmptyW(current_absolute_path)) + { + if (!RemoveDirectoryW(current_absolute_path)) + output_error(STRING_ERROR_DELETE_DIRECTORY, GetLastError(), strip_path_prefix(current_absolute_path)); + else output_message(STRING_DELETE_DIRECTORY, strip_path_prefix(current_absolute_path)); + } + } + else break; /* if purge's not set, we can just break LIST_FOR_EACH_ENTRY, no need to check further */ }
return TRUE; @@ -416,6 +441,10 @@ static WCHAR *get_option_string(void) if (options.copy_empty_subdirectories) wcscat(temp_string, L"/E ");
+ /* Purge */ + if (options.purge_destination) + wcscat(temp_string, L"/PURGE "); + /* Move files */ if (options.purge_source_files) wcscat(temp_string, L"/MOV "); diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index 677f88cb453..1417f8b0e1c 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -40,6 +40,7 @@ struct robocopy_options { BOOL copy_subdirectories; BOOL copy_empty_subdirectories; BOOL purge_source_files; + BOOL purge_destination; };
struct robocopy_statistics { @@ -65,7 +66,11 @@ struct robocopy_statistics { #define STRING_ERROR_READ_DIRECTORY 1011 #define STRING_ERROR_WRITE_DIRECTORY 1012 #define STRING_ERROR_WRITE_FILE 1014 +#define STRING_ERROR_DELETE_DIRECTORY 1016 +#define STRING_ERROR_DELETE_FILE 1017 #define STRING_STATISTICS 1018 #define STRING_CREATE_DIRECTORY 1019 +#define STRING_DELETE_DIRECTORY 1020 #define STRING_CREATE_FILE 1022 +#define STRING_DELETE_FILE 1023 #define STRING_MOVE_FILE 1024 \ No newline at end of file diff --git a/programs/robocopy/robocopy.rc b/programs/robocopy/robocopy.rc index b12501017e2..cdb03085315 100644 --- a/programs/robocopy/robocopy.rc +++ b/programs/robocopy/robocopy.rc @@ -35,9 +35,13 @@ STRINGTABLE 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_ERROR_DELETE_DIRECTORY, "[%1] Error %2 (%3) occurred deleting directory "%4":\n%5\n" + STRING_ERROR_DELETE_FILE, "[%1] Error %2 (%3) occurred deleting file "%4":\n%5\n" STRING_STATISTICS, "\n Copied %1 directories and %2 files\n" STRING_CREATE_DIRECTORY, " Created Dir: %1\n" + STRING_DELETE_DIRECTORY, " Deleted Dir: %1\n" STRING_CREATE_FILE, " Copied File: %1\n" + STRING_DELETE_FILE, " Deleted File: %1\n" STRING_MOVE_FILE, " Moved File: %1\n" }
Implements the /MIR switch, which is mostly an alias of /S /E /PURGE
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/main.c | 16 +++++++++++++++- programs/robocopy/robocopy.h | 1 + 2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 0bb0e83783c..3036776339d 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -162,6 +162,16 @@ static void parse_arguments(int argc, WCHAR *argv[]) { options.purge_destination = TRUE; } + /* mirror - Mirror Source in Destination */ + else if (!wcsicmp(argv[i], L"/mir")) + { + options.copy_empty_subdirectories = TRUE; + options.copy_subdirectories = TRUE; + options.purge_destination = TRUE; + options.mirror = TRUE; + if (!options.user_limited_subdirectories_depth) + options.max_subdirectories_depth = 0; + } /* mov - Delete files (but not folders) after copying them */ else if (!wcsicmp(argv[i], L"/mov")) { @@ -363,7 +373,7 @@ static BOOL perform_copy(struct robocopy_statistics *statistics) if ((PathIsDirectoryEmptyW(current_absolute_path) && !options.copy_empty_subdirectories)) continue;
/* Create the directory path and then create the directory itself */ - if (!create_directory_path(target_path)) + if (!create_directory_path(target_path) || (!CreateDirectoryW(target_path, NULL) && GetLastError() != ERROR_ALREADY_EXISTS)) output_error(STRING_ERROR_WRITE_DIRECTORY, GetLastError(), strip_path_prefix(target_path)); else statistics->copied_directories++; } @@ -428,6 +438,10 @@ static WCHAR *get_option_string(void) if (options.files->size == 0) wcscat(temp_string, L"*.* ");
+ /* Mirror */ + if (options.mirror) + wcscat(temp_string, L"/MIR "); + /* Subdirectories */ if (options.copy_subdirectories) wcscat(temp_string, L"/S "); diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index 1417f8b0e1c..e6779248aa5 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -41,6 +41,7 @@ struct robocopy_options { BOOL copy_empty_subdirectories; BOOL purge_source_files; BOOL purge_destination; + BOOL mirror; };
struct robocopy_statistics {
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/tests/robocopy.c | 60 ++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+)
diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c index d205127d56f..8a5edccd147 100644 --- a/programs/robocopy/tests/robocopy.c +++ b/programs/robocopy/tests/robocopy.c @@ -305,6 +305,45 @@ static void check_mov_2_test(void) check_folder_and_delete(L"robocopy_destination", TRUE); }
+static void check_mir_test(void) +{ + check_files_equal(L"robocopy_source\fileA.a", L"robocopy_destination\fileA.a", TRUE); + check_files_equal(L"robocopy_source\fileB.b", L"robocopy_destination\fileB.b", TRUE); + check_files_equal(L"robocopy_source\folderA\fileC.c", L"robocopy_destination\folderA\fileC.c", TRUE); + check_files_equal(L"robocopy_source\folderA\fileD.d", L"robocopy_destination\folderA\fileD.d", TRUE); + check_files_equal(L"robocopy_source\folderA\folderD\fileE.e", L"robocopy_destination\folderA\folderD\fileE.e", TRUE); + check_files_equal(L"robocopy_source\folderB\fileF.f", L"robocopy_destination\folderB\fileF.f", TRUE); + check_files_equal(L"robocopy_source\folderB\fileG.g", L"robocopy_destination\folderB\fileG.g", TRUE); + + 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); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA", TRUE); + check_folder_and_delete(L"robocopy_destination\folderB", TRUE); + check_folder_and_delete(L"robocopy_destination\folderC", TRUE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + START_TEST(robocopy) { WCHAR temp_command_line[2048], previous_cwd_path[4096], temp_path[4096]; @@ -380,6 +419,27 @@ START_TEST(robocopy) check_mov_1_test(); winetest_pop_context();
+ winetest_push_context("MIR test 1"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /mir /r:1 /w:0", 1); + check_mir_test(); + winetest_pop_context(); + + winetest_push_context("MIR test 2"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /s /e /purge /r:1 /w:0", 1); + check_mir_test(); + winetest_pop_context(); + + winetest_push_context("MIR test 3"); + create_test_source_folder(); + create_test_folder(L"robocopy_destination"); + create_test_file(L"robocopy_destination\extraFile", 9000, 0, -10); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /mir /r:1 /w:0", 3); + check_file_and_delete(L"robocopy_destination\extraFile", FALSE); + check_mir_test(); + winetest_pop_context(); + /* Reset CWD to previous folder */ ok(SetCurrentDirectoryW(previous_cwd_path), "couldn't set CWD to previous CWD folder "%S"", previous_cwd_path); } \ No newline at end of file
Implements the /MOVE switch, which moves not only the files, but also the directories in the source folder
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/main.c | 31 ++++++++++++++++++++++++++++--- programs/robocopy/robocopy.h | 1 + 2 files changed, 29 insertions(+), 3 deletions(-)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 3036776339d..1cfbe65e94b 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -177,6 +177,12 @@ static void parse_arguments(int argc, WCHAR *argv[]) { options.purge_source_files = TRUE; } + /* move - Delete files (including folders) after copying them */ + else if (!wcsicmp(argv[i], L"/move")) + { + options.purge_source = TRUE; + options.purge_source_files = TRUE; + } /* lev - Limit depth of subdirectories */ else if (!wcsnicmp(argv[i], L"/lev:", 5)) { @@ -388,6 +394,19 @@ static BOOL perform_copy(struct robocopy_statistics *statistics)
}
+ /* if move is specified, we delete the empty remaining folders in the source */ + if (options.purge_source) + { + LIST_FOR_EACH_ENTRY_REV(current_path, &paths_source, struct path, entry) + { + /* append relative path to the source to get the absolute path of the source file / directory */ + current_absolute_path = get_combined_path(options.source, current_path->name); + + if (PathIsDirectoryEmptyW(current_absolute_path)) + RemoveDirectoryW(current_absolute_path); + } + } + /* search for extra files (files only in destination) to be able to return the correct exit code */ LIST_FOR_EACH_ENTRY_REV(current_path, &paths_destination, struct path, entry) { @@ -459,9 +478,15 @@ static WCHAR *get_option_string(void) if (options.purge_destination) wcscat(temp_string, L"/PURGE ");
- /* Move files */ - if (options.purge_source_files) - wcscat(temp_string, L"/MOV "); + /* Move files and folders */ + if (options.purge_source) + wcscat(temp_string, L"/MOVE "); + else + { + /* Move files */ + if (options.purge_source_files) + wcscat(temp_string, L"/MOV "); + }
string = wcsdup(temp_string); return string; diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index e6779248aa5..aa58240d8cc 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -40,6 +40,7 @@ struct robocopy_options { BOOL copy_subdirectories; BOOL copy_empty_subdirectories; BOOL purge_source_files; + BOOL purge_source; BOOL purge_destination; BOOL mirror; };
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/tests/robocopy.c | 37 ++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+)
diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c index 8a5edccd147..96d31d9e8c6 100644 --- a/programs/robocopy/tests/robocopy.c +++ b/programs/robocopy/tests/robocopy.c @@ -344,6 +344,37 @@ static void check_mir_test(void) check_folder_and_delete(L"robocopy_destination", TRUE); }
+static void check_move_1_test(void) +{ + check_file_and_delete(L"robocopy_source\fileA.a", FALSE); + check_file_and_delete(L"robocopy_source\fileB.b", FALSE); + check_file_and_delete(L"robocopy_source\folderA\fileC.c", FALSE); + check_file_and_delete(L"robocopy_source\folderA\fileD.d", FALSE); + check_file_and_delete(L"robocopy_source\folderA\folderD\fileE.e", FALSE); + check_file_and_delete(L"robocopy_source\folderB\fileF.f", FALSE); + check_file_and_delete(L"robocopy_source\folderB\fileG.g", FALSE); + check_folder_and_delete(L"robocopy_source\folderA\folderD", FALSE); + check_folder_and_delete(L"robocopy_source\folderA\folderE", FALSE); + check_folder_and_delete(L"robocopy_source\folderA", FALSE); + check_folder_and_delete(L"robocopy_source\folderB", FALSE); + check_folder_and_delete(L"robocopy_source\folderC", FALSE); + check_folder_and_delete(L"robocopy_source", FALSE); + + check_file_and_delete(L"robocopy_destination\fileA.a", TRUE); + check_file_and_delete(L"robocopy_destination\fileB.b", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA", TRUE); + check_folder_and_delete(L"robocopy_destination\folderB", TRUE); + check_folder_and_delete(L"robocopy_destination\folderC", FALSE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + START_TEST(robocopy) { WCHAR temp_command_line[2048], previous_cwd_path[4096], temp_path[4096]; @@ -440,6 +471,12 @@ START_TEST(robocopy) check_mir_test(); winetest_pop_context();
+ winetest_push_context("MOVE test 1"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /move /s /r:1 /w:0", 1); + check_move_1_test(); + winetest_pop_context(); + /* Reset CWD to previous folder */ ok(SetCurrentDirectoryW(previous_cwd_path), "couldn't set CWD to previous CWD folder "%S"", previous_cwd_path); } \ No newline at end of file
Implements the /XF and /XD switches, which set file and directory names that are excluded from any robocopy operation
Signed-off-by: Florian Eder others.meder@gmail.com --- Both switches in one patch because the code is almost identical for both --- programs/robocopy/main.c | 46 +++++++++++++++++++++++++++++++++-- programs/robocopy/robocopy.h | 4 +++ programs/robocopy/robocopy.rc | 2 ++ 3 files changed, 50 insertions(+), 2 deletions(-)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 1cfbe65e94b..41ed29f1725 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -132,9 +132,12 @@ WCHAR *get_absolute_path_with_trailing_backslash(WCHAR *path) static void parse_arguments(int argc, WCHAR *argv[]) { int i; + BOOL is_xd = FALSE, is_xf = FALSE;
memset(&options, 0, sizeof(options)); options.files = calloc(1, offsetof(struct path_array, array) + (argc * sizeof(WCHAR*))); + options.excluded_filenames = calloc(1, offsetof(struct path_array, array) + (argc * sizeof(WCHAR*))); + options.excluded_directories = calloc(1, offsetof(struct path_array, array) + (argc * sizeof(WCHAR*)));
/* default values */ options.max_subdirectories_depth = 1; @@ -142,6 +145,8 @@ static void parse_arguments(int argc, WCHAR *argv[]) for (i = 1; i < argc; i++) { if (is_valid_robocopy_flag(argv[i])) + { + is_xd = FALSE; is_xf = FALSE; /* s - Copy Subdirectories */ if (!wcsicmp(argv[i], L"/s")) { @@ -183,6 +188,18 @@ static void parse_arguments(int argc, WCHAR *argv[]) options.purge_source = TRUE; options.purge_source_files = TRUE; } + /* xf - Excluded Files */ + else if (!wcsicmp(argv[i], L"/xf")) + { + /* xf includes all following files, until the next option / flag */ + is_xf = TRUE; + } + /* xd - Excluded Directories */ + else if (!wcsicmp(argv[i], L"/xd")) + { + /* xd includes all following directories, until the next option / flag */ + is_xd = TRUE; + } /* lev - Limit depth of subdirectories */ else if (!wcsnicmp(argv[i], L"/lev:", 5)) { @@ -198,13 +215,26 @@ static void parse_arguments(int argc, WCHAR *argv[]) { WINE_FIXME("encountered an unknown robocopy flag: %S\n", 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 + * Priority: Excluded > Source > Destination > (more than one) File */ - if (!options.source) + if (is_xf) + { + options.excluded_filenames->array[options.excluded_filenames->size] = calloc(wcslen(argv[i]) + 1, sizeof(WCHAR)); + wcscpy(options.excluded_filenames->array[options.excluded_filenames->size], argv[i]); + options.excluded_filenames->size++; + } + else if (is_xd) + { + options.excluded_directories->array[options.excluded_directories->size] = calloc(wcslen(argv[i]) + 1, sizeof(WCHAR)); + wcscpy(options.excluded_directories->array[options.excluded_directories->size], argv[i]); + options.excluded_directories->size++; + } + else if (!options.source) { options.source = get_absolute_path_with_trailing_backslash(argv[i]); } @@ -327,6 +357,10 @@ static void get_file_paths_in_folder(WCHAR *directory_path, struct list *paths, current_relative_path = get_combined_path(current_path->name, entry_data.cFileName); current_absolute_path = get_combined_path(directory_path, current_relative_path);
+ /* Ignore if it's an excluded folder or file */ + if (matches_array_entry(entry_data.cFileName, PathIsDirectoryW(current_absolute_path) ? + options.excluded_directories : options.excluded_filenames)) continue; + /* 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) && (!depth || depth > current_path->level))) @@ -509,6 +543,14 @@ static void print_header(void) for (i = 1; i < options.files->size; i++) output_message(STRING_ADDITIONAL_INFO, options.files->array[i]);
+ if (options.excluded_filenames->size > 0) output_message(STRING_EXCLUDED_FILES, options.excluded_filenames->array[0]); + for (i = 1; i < options.excluded_filenames->size; i++) + output_message(STRING_ADDITIONAL_INFO, options.excluded_filenames->array[i]); + + if (options.excluded_directories->size > 0) output_message(STRING_EXCLUDED_DIRECTORIES, options.excluded_directories->array[0]); + for (i = 1; i < options.excluded_directories->size; i++) + output_message(STRING_ADDITIONAL_INFO, options.excluded_directories->array[i]); + options_string = get_option_string(); if (options_string != NULL) output_message(STRING_OPTIONS, options_string); } diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index aa58240d8cc..1818fdf90f1 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -35,6 +35,8 @@ struct robocopy_options { WCHAR *destination; WCHAR *source; struct path_array* files; + struct path_array* excluded_filenames; + struct path_array* excluded_directories; UINT max_subdirectories_depth; BOOL user_limited_subdirectories_depth; BOOL copy_subdirectories; @@ -62,6 +64,8 @@ struct robocopy_statistics { #define STRING_SOURCE 1003 #define STRING_DESTINATION 1004 #define STRING_FILES 1005 +#define STRING_EXCLUDED_FILES 1006 +#define STRING_EXCLUDED_DIRECTORIES 1007 #define STRING_ADDITIONAL_INFO 1008 #define STRING_OPTIONS 1009 #define STRING_MISSING_DESTINATION_OR_SOURCE 1010 diff --git a/programs/robocopy/robocopy.rc b/programs/robocopy/robocopy.rc index cdb03085315..315dce5f947 100644 --- a/programs/robocopy/robocopy.rc +++ b/programs/robocopy/robocopy.rc @@ -29,6 +29,8 @@ STRINGTABLE STRING_SOURCE, " Source: %1\n" STRING_DESTINATION, " Destination: %1\n\n" STRING_FILES, " Files: %1\n" + STRING_EXCLUDED_FILES, "\n Excluded Files: %1\n" + STRING_EXCLUDED_DIRECTORIES, "\n Excluded Dirs: %1\n" STRING_ADDITIONAL_INFO, " %1\n" STRING_OPTIONS, "\n Options: %1\n\n" STRING_MISSING_DESTINATION_OR_SOURCE, "No destination or source specified, can't copy anything\n"
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/tests/robocopy.c | 136 +++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+)
diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c index 96d31d9e8c6..8c94311f74d 100644 --- a/programs/robocopy/tests/robocopy.c +++ b/programs/robocopy/tests/robocopy.c @@ -375,6 +375,110 @@ static void check_move_1_test(void) check_folder_and_delete(L"robocopy_destination", TRUE); }
+static void check_wildcard_1_test(void) +{ + check_files_equal(L"robocopy_source\fileA.a", L"robocopy_destination\fileA.a", TRUE); + check_files_equal(L"robocopy_source\fileB.b", L"robocopy_destination\fileB.b", TRUE); + + 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); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA", TRUE); + check_folder_and_delete(L"robocopy_destination\folderB", TRUE); + check_folder_and_delete(L"robocopy_destination\folderC", TRUE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + +static void check_wildcard_2_test(void) +{ + check_files_equal(L"robocopy_source\fileA.a", L"robocopy_destination\fileA.a", TRUE); + check_files_equal(L"robocopy_source\folderA\fileC.c", L"robocopy_destination\folderA\fileC.c", TRUE); + check_files_equal(L"robocopy_source\folderA\folderD\fileE.e", L"robocopy_destination\folderA\folderD\fileE.e", TRUE); + + 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", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA", TRUE); + check_folder_and_delete(L"robocopy_destination\folderB", TRUE); + check_folder_and_delete(L"robocopy_destination\folderC", TRUE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + +static void check_wildcard_3_test(void) +{ + check_files_equal(L"robocopy_source\fileA.a", L"robocopy_destination\fileA.a", TRUE); + check_files_equal(L"robocopy_source\folderA\fileC.c", L"robocopy_destination\folderA\fileC.c", TRUE); + check_files_equal(L"robocopy_source\folderA\folderD\fileE.e", L"robocopy_destination\folderA\folderD\fileE.e", TRUE); + + 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", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA", TRUE); + check_folder_and_delete(L"robocopy_destination\folderB", FALSE); + check_folder_and_delete(L"robocopy_destination\folderC", TRUE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + START_TEST(robocopy) { WCHAR temp_command_line[2048], previous_cwd_path[4096], temp_path[4096]; @@ -477,6 +581,38 @@ START_TEST(robocopy) check_move_1_test(); winetest_pop_context();
+ winetest_push_context("wildcard test 1"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination *.a fileB.? /mir /r:1 /w:0", 1); + check_wildcard_1_test(); + winetest_pop_context(); + + winetest_push_context("wildcard test 2"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination fileC.c file?.e f???A.? /mir /r:1 /w:0", 1); + check_wildcard_2_test(); + winetest_pop_context(); + + winetest_push_context("wildcard test 3"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination file?.* /xf fileB* *D* /mir /xf *.f ???*.g /r:1 /w:0", 1); + check_wildcard_2_test(); + winetest_pop_context(); + + winetest_push_context("wildcard test 4"); + create_test_source_folder(); + create_test_folder(L"robocopy_destination"); + create_test_file(L"robocopy_destination\fileA.a", 9000, 0, -10); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination fileC.c file?.e f???A.? /mir /r:1 /w:0", 1); + check_wildcard_2_test(); + winetest_pop_context(); + + winetest_push_context("wildcard test 5"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination F??E?.* /xf *B* *.d /mir /xd f?l*B /r:1 /w:0", 1); + check_wildcard_3_test(); + winetest_pop_context(); + /* Reset CWD to previous folder */ ok(SetCurrentDirectoryW(previous_cwd_path), "couldn't set CWD to previous CWD folder "%S"", previous_cwd_path); } \ No newline at end of file
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/tests/robocopy.c | 56 ++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+)
diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c index 8c94311f74d..6ded4e98650 100644 --- a/programs/robocopy/tests/robocopy.c +++ b/programs/robocopy/tests/robocopy.c @@ -479,6 +479,44 @@ static void check_wildcard_3_test(void) check_folder_and_delete(L"robocopy_destination", TRUE); }
+static void check_lev_1_test(void) +{ + check_files_equal(L"robocopy_source\fileA.a", L"robocopy_destination\fileA.a", TRUE); + check_files_equal(L"robocopy_source\fileB.b", L"robocopy_destination\fileB.b", TRUE); + check_files_equal(L"robocopy_source\folderA\fileC.c", L"robocopy_destination\folderA\fileC.c", TRUE); + check_files_equal(L"robocopy_source\folderA\fileD.d", L"robocopy_destination\folderA\fileD.d", TRUE); + check_files_equal(L"robocopy_source\folderB\fileF.f", L"robocopy_destination\folderB\fileF.f", TRUE); + check_files_equal(L"robocopy_source\folderB\fileG.g", L"robocopy_destination\folderB\fileG.g", TRUE); + + 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); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA", TRUE); + check_folder_and_delete(L"robocopy_destination\folderB", TRUE); + check_folder_and_delete(L"robocopy_destination\folderC", TRUE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + START_TEST(robocopy) { WCHAR temp_command_line[2048], previous_cwd_path[4096], temp_path[4096]; @@ -613,6 +651,24 @@ START_TEST(robocopy) check_wildcard_3_test(); winetest_pop_context();
+ winetest_push_context("LEV test 1"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /s /e /lev:0 /r:1 /w:0", 1); + check_mir_test(); + winetest_pop_context(); + + winetest_push_context("LEV test 2"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /s /e /lev:1 /r:1 /w:0", 1); + check_basic_copy_test(); + winetest_pop_context(); + + winetest_push_context("LEV test 3"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /s /e /lev:2 /r:1 /w:0", 1); + check_lev_1_test(); + winetest_pop_context(); + /* Reset CWD to previous folder */ ok(SetCurrentDirectoryW(previous_cwd_path), "couldn't set CWD to previous CWD folder "%S"", previous_cwd_path); } \ No newline at end of file
Implements the /L switch, which causes robocopy to do no real copy / move / delete operation
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/main.c | 22 ++++++++++++++++------ programs/robocopy/robocopy.h | 1 + 2 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 41ed29f1725..4f1b07e8954 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -177,6 +177,11 @@ static void parse_arguments(int argc, WCHAR *argv[]) if (!options.user_limited_subdirectories_depth) options.max_subdirectories_depth = 0; } + /* l - Dry Run */ + else if (!wcsicmp(argv[i], L"/l")) + { + options.dry_run = TRUE; + } /* mov - Delete files (but not folders) after copying them */ else if (!wcsicmp(argv[i], L"/mov")) { @@ -293,7 +298,7 @@ static BOOL create_directory_path(WCHAR *path) { 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 (!options.dry_run && !CreateDirectoryW(current_folder, NULL)) { if (GetLastError() != ERROR_ALREADY_EXISTS) { @@ -413,13 +418,14 @@ static BOOL perform_copy(struct robocopy_statistics *statistics) if ((PathIsDirectoryEmptyW(current_absolute_path) && !options.copy_empty_subdirectories)) continue;
/* Create the directory path and then create the directory itself */ - if (!create_directory_path(target_path) || (!CreateDirectoryW(target_path, NULL) && GetLastError() != ERROR_ALREADY_EXISTS)) + if (!options.dry_run && (!create_directory_path(target_path) || + (!CreateDirectoryW(target_path, NULL) && GetLastError() != ERROR_ALREADY_EXISTS))) output_error(STRING_ERROR_WRITE_DIRECTORY, GetLastError(), strip_path_prefix(target_path)); else statistics->copied_directories++; } else { - if (copy_or_move_file(current_absolute_path, target_path, options.purge_source_files)) + if (options.dry_run || copy_or_move_file(current_absolute_path, target_path, options.purge_source_files)) { output_message(options.purge_source_files ? STRING_MOVE_FILE : STRING_CREATE_FILE, strip_path_prefix(target_path)); statistics->copied_files++; @@ -429,7 +435,7 @@ static BOOL perform_copy(struct robocopy_statistics *statistics) }
/* if move is specified, we delete the empty remaining folders in the source */ - if (options.purge_source) + if (!options.dry_run && options.purge_source) { LIST_FOR_EACH_ENTRY_REV(current_path, &paths_source, struct path, entry) { @@ -465,13 +471,13 @@ static BOOL perform_copy(struct robocopy_statistics *statistics) /* only files or empty folders */ if (!PathIsDirectoryW(current_absolute_path)) { - if (!DeleteFileW(current_absolute_path)) + if (!options.dry_run && !DeleteFileW(current_absolute_path)) output_error(STRING_ERROR_DELETE_FILE, GetLastError(), strip_path_prefix(current_absolute_path)); else output_message(STRING_DELETE_FILE, strip_path_prefix(current_absolute_path)); } else if (PathIsDirectoryEmptyW(current_absolute_path)) { - if (!RemoveDirectoryW(current_absolute_path)) + if (!options.dry_run && !RemoveDirectoryW(current_absolute_path)) output_error(STRING_ERROR_DELETE_DIRECTORY, GetLastError(), strip_path_prefix(current_absolute_path)); else output_message(STRING_DELETE_DIRECTORY, strip_path_prefix(current_absolute_path)); } @@ -487,6 +493,10 @@ static WCHAR *get_option_string(void) WCHAR *string, temp_string[512]; memset(temp_string, 0, sizeof(temp_string));
+ /* Dry Run */ + if (options.dry_run) + wcscat(temp_string, L"/L "); + /* If no files set, display *.* */ if (options.files->size == 0) wcscat(temp_string, L"*.* "); diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index 1818fdf90f1..9e3137109b8 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -45,6 +45,7 @@ struct robocopy_options { BOOL purge_source; BOOL purge_destination; BOOL mirror; + BOOL dry_run; };
struct robocopy_statistics {
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/tests/robocopy.c | 53 ++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+)
diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c index 6ded4e98650..0f6e07b3d71 100644 --- a/programs/robocopy/tests/robocopy.c +++ b/programs/robocopy/tests/robocopy.c @@ -517,6 +517,37 @@ static void check_lev_1_test(void) check_folder_and_delete(L"robocopy_destination", TRUE); }
+static void check_dry_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", FALSE); + check_file_and_delete(L"robocopy_destination\fileB.b", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA", FALSE); + 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", FALSE); +} + START_TEST(robocopy) { WCHAR temp_command_line[2048], previous_cwd_path[4096], temp_path[4096]; @@ -669,6 +700,28 @@ START_TEST(robocopy) check_lev_1_test(); winetest_pop_context();
+ winetest_push_context("dry test 1"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /mir /l /r:1 /w:0", 1); + check_dry_test(); + winetest_pop_context(); + + winetest_push_context("dry test 2"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /move /s /l /r:1 /w:0", 1); + check_dry_test(); + winetest_pop_context(); + + winetest_push_context("dry test 3"); + create_test_source_folder(); + create_test_folder(L"robocopy_destination"); + create_test_file(L"robocopy_destination\extraFile", 9000, 0, -10); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /move /s /l /r:1 /w:0", 3); + check_file_and_delete(L"robocopy_destination\extraFile", TRUE); + check_folder_and_delete(L"robocopy_destination", TRUE); + check_dry_test(); + winetest_pop_context(); + /* Reset CWD to previous folder */ ok(SetCurrentDirectoryW(previous_cwd_path), "couldn't set CWD to previous CWD folder "%S"", previous_cwd_path); } \ No newline at end of file
Implements the /MIN:n and /MAX:n switches, which causes only files smaller or bigger than the set values to be included in any copy operation
Signed-off-by: Florian Eder others.meder@gmail.com --- Both switches in one patch, as both are very closely connected and share some code --- programs/robocopy/main.c | 46 ++++++++++++++++++++++++++++++++++++ programs/robocopy/robocopy.h | 2 ++ 2 files changed, 48 insertions(+)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 4f1b07e8954..47ed5a12371 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -141,6 +141,7 @@ static void parse_arguments(int argc, WCHAR *argv[])
/* default values */ options.max_subdirectories_depth = 1; + options.max_size = MAXLONGLONG;
for (i = 1; i < argc; i++) { @@ -216,6 +217,20 @@ static void parse_arguments(int argc, WCHAR *argv[]) options.user_limited_subdirectories_depth = TRUE; } } + /* max - Include only smaller files */ + else if (!wcsnicmp(argv[i], L"/max:", 5)) + { + LONGLONG value = 0; + if (swscanf(&(argv[i][5]), L"%lld", &value) == 1 && value >= 0) + options.max_size = value; + } + /* min - Include only bigger files */ + else if (!wcsnicmp(argv[i], L"/min:", 5)) + { + LONGLONG value = 0; + if (swscanf(&(argv[i][5]), L"%lld", &value) == 1 && value >= 0) + options.min_size = value; + } else { WINE_FIXME("encountered an unknown robocopy flag: %S\n", argv[i]); @@ -381,6 +396,24 @@ static void get_file_paths_in_folder(WCHAR *directory_path, struct list *paths, } }
+static BOOL is_valid_file(WCHAR *source) +{ + HANDLE source_handle; + LARGE_INTEGER source_size; + source_handle = CreateFileW(source, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (source_handle == INVALID_HANDLE_VALUE) return FALSE; + GetFileSizeEx(source_handle, &source_size); + /* ignore file if source is not within max / min size */ + if (source_size.QuadPart < options.min_size || + source_size.QuadPart > options.max_size) + { + CloseHandle(source_handle); + return FALSE; + } + CloseHandle(source_handle); + return TRUE; +} + static BOOL perform_copy(struct robocopy_statistics *statistics) { struct list paths_source, paths_destination; @@ -425,6 +458,9 @@ static BOOL perform_copy(struct robocopy_statistics *statistics) } else { + /* ignore file if the file size is not within the allowed limits */ + if (!is_valid_file(current_absolute_path)) continue; + if (options.dry_run || copy_or_move_file(current_absolute_path, target_path, options.purge_source_files)) { output_message(options.purge_source_files ? STRING_MOVE_FILE : STRING_CREATE_FILE, strip_path_prefix(target_path)); @@ -532,6 +568,16 @@ static WCHAR *get_option_string(void) wcscat(temp_string, L"/MOV "); }
+ /* Max File Size */ + if (options.max_size != MAXLONGLONG) + swprintf(temp_string + wcslen(temp_string), ARRAY_SIZE(temp_string) - wcslen(temp_string), + L"/MAX:%lld ", options.max_size); + + /* Min File Size */ + if (options.min_size != 0) + swprintf(temp_string + wcslen(temp_string), ARRAY_SIZE(temp_string) - wcslen(temp_string), + L"/MIN:%lld ", options.min_size); + string = wcsdup(temp_string); return string; } diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index 9e3137109b8..0d498a9649c 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -46,6 +46,8 @@ struct robocopy_options { BOOL purge_destination; BOOL mirror; BOOL dry_run; + LONGLONG min_size; + LONGLONG max_size; };
struct robocopy_statistics {
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/tests/robocopy.c | 160 +++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+)
diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c index 0f6e07b3d71..38279b643c1 100644 --- a/programs/robocopy/tests/robocopy.c +++ b/programs/robocopy/tests/robocopy.c @@ -548,6 +548,130 @@ static void check_dry_test(void) check_folder_and_delete(L"robocopy_destination", FALSE); }
+static void check_max_1_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", FALSE); + check_file_and_delete(L"robocopy_destination\fileB.b", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA", TRUE); + check_folder_and_delete(L"robocopy_destination\folderB", TRUE); + check_folder_and_delete(L"robocopy_destination\folderC", TRUE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + +static void check_max_2_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", FALSE); + check_file_and_delete(L"robocopy_destination\fileB.b", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA", TRUE); + check_folder_and_delete(L"robocopy_destination\folderB", TRUE); + check_folder_and_delete(L"robocopy_destination\folderC", TRUE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + +static void check_min_1_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", FALSE); + check_file_and_delete(L"robocopy_destination\fileB.b", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA", TRUE); + check_folder_and_delete(L"robocopy_destination\folderB", TRUE); + check_folder_and_delete(L"robocopy_destination\folderC", TRUE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + +static void check_min_2_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); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA", TRUE); + check_folder_and_delete(L"robocopy_destination\folderB", TRUE); + check_folder_and_delete(L"robocopy_destination\folderC", TRUE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + START_TEST(robocopy) { WCHAR temp_command_line[2048], previous_cwd_path[4096], temp_path[4096]; @@ -722,6 +846,42 @@ START_TEST(robocopy) check_dry_test(); winetest_pop_context();
+ winetest_push_context("MAX test 1"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /max:50 /s /e /r:1 /w:0", 0); + check_max_1_test(); + winetest_pop_context(); + + winetest_push_context("MAX test 2"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /max:1000 /s /e /r:1 /w:0", 1); + check_max_2_test(); + winetest_pop_context(); + + winetest_push_context("MAX test 3"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /max:5000000 /s /e /r:1 /w:0", 1); + check_mir_test(); + winetest_pop_context(); + + winetest_push_context("MIN test 1"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /min:10000 /s /e /r:1 /w:0", 1); + check_min_1_test(); + winetest_pop_context(); + + winetest_push_context("MIN test 2"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /min:500 /s /e /r:1 /w:0", 1); + check_min_2_test(); + winetest_pop_context(); + + winetest_push_context("MIN test 3"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /min:10 /s /e /r:1 /w:0", 1); + check_mir_test(); + winetest_pop_context(); + /* Reset CWD to previous folder */ ok(SetCurrentDirectoryW(previous_cwd_path), "couldn't set CWD to previous CWD folder "%S"", previous_cwd_path); } \ No newline at end of file
Implements the /MINAGE and /MAXAGE flags, which causes only files older or newer than the set values to be included in any copy operation
Signed-off-by: Florian Eder others.meder@gmail.com --- Again, both switches in one patch, as both are tightly connected --- programs/robocopy/main.c | 70 ++++++++++++++++++++++++++++++++++++ programs/robocopy/robocopy.h | 2 ++ 2 files changed, 72 insertions(+)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 47ed5a12371..17d991e7435 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -129,6 +129,44 @@ WCHAR *get_absolute_path_with_trailing_backslash(WCHAR *path) return absolute_path; }
+static void parse_date_to_filetime(const WCHAR *date_string, FILETIME *filetime_pointer) +{ + /* + * robocopy interprets values between [0, 1899] as a relative offset (in days) from the current date, + * otherwise as an absolute date in the YYYYMMDD format (regardless of user locale) + */ + long value; + FILETIME time; + value = wcstol(date_string, NULL, 10); + if (value >= 0 && value < 1900) + { + /* relative offset of n days into the past */ + LARGE_INTEGER time_as_integer; + GetSystemTimeAsFileTime(&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 * value; + filetime_pointer->dwHighDateTime = time_as_integer.HighPart; + filetime_pointer->dwLowDateTime = time_as_integer.LowPart; + } + else if (value >= 19000101 && value <= 99991231) + { + /* absolute date in YYYYMMDD format */ + SYSTEMTIME time_as_systemtime; + memset(&time_as_systemtime, 0, sizeof(time_as_systemtime)); + if (swscanf(date_string, L"%4hu%2hu%2hu", &time_as_systemtime.wYear, &time_as_systemtime.wMonth, &time_as_systemtime.wDay) == 3 && + time_as_systemtime.wMonth > 0 && time_as_systemtime.wMonth <= 12 && + time_as_systemtime.wDay > 0 && time_as_systemtime.wDay <= 31 && + SystemTimeToFileTime(&time_as_systemtime, &time)) + { + filetime_pointer->dwHighDateTime = time.dwHighDateTime; + filetime_pointer->dwLowDateTime = time.dwLowDateTime; + } + } +} + static void parse_arguments(int argc, WCHAR *argv[]) { int i; @@ -231,6 +269,16 @@ static void parse_arguments(int argc, WCHAR *argv[]) if (swscanf(&(argv[i][5]), L"%lld", &value) == 1 && value >= 0) options.min_size = value; } + /* maxage - Include only newer files */ + else if (!wcsnicmp(argv[i], L"/maxage:", 8)) + { + parse_date_to_filetime(&(argv[i][8]), &options.max_time); + } + /* minage - Include only older files */ + else if (!wcsnicmp(argv[i], L"/minage:", 8)) + { + parse_date_to_filetime(&(argv[i][8]), &options.min_time); + } else { WINE_FIXME("encountered an unknown robocopy flag: %S\n", argv[i]); @@ -400,6 +448,7 @@ static BOOL is_valid_file(WCHAR *source) { HANDLE source_handle; LARGE_INTEGER source_size; + FILETIME source_creation_time, source_access_time, source_modified_time; source_handle = CreateFileW(source, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (source_handle == INVALID_HANDLE_VALUE) return FALSE; GetFileSizeEx(source_handle, &source_size); @@ -410,6 +459,14 @@ static BOOL is_valid_file(WCHAR *source) CloseHandle(source_handle); return FALSE; } + GetFileTime(source_handle, &source_creation_time, &source_access_time, &source_modified_time); + /* ignore file if source is not within max / min age (if set) */ + if ((options.min_time.dwHighDateTime != 0 && CompareFileTime(&source_creation_time, &options.min_time) > 0) || + (options.max_time.dwHighDateTime != 0 && CompareFileTime(&source_creation_time, &options.max_time) < 0)) + { + CloseHandle(source_handle); + return FALSE; + } CloseHandle(source_handle); return TRUE; } @@ -526,6 +583,7 @@ static BOOL perform_copy(struct robocopy_statistics *statistics)
static WCHAR *get_option_string(void) { + SYSTEMTIME time; WCHAR *string, temp_string[512]; memset(temp_string, 0, sizeof(temp_string));
@@ -578,6 +636,18 @@ static WCHAR *get_option_string(void) swprintf(temp_string + wcslen(temp_string), ARRAY_SIZE(temp_string) - wcslen(temp_string), L"/MIN:%lld ", options.min_size);
+ /* Min Age*/ + if (options.min_time.dwHighDateTime != 0) + if (FileTimeToSystemTime(&options.min_time, &time)) + swprintf(temp_string + wcslen(temp_string), ARRAY_SIZE(temp_string) - wcslen(temp_string), + L"/MINAGE:%hu-%02hu-%02hu ", time.wYear, time.wMonth, time.wDay); + + /* Max Age */ + if (options.max_time.dwHighDateTime != 0) + if (FileTimeToSystemTime(&options.max_time, &time)) + swprintf(temp_string + wcslen(temp_string), ARRAY_SIZE(temp_string) - wcslen(temp_string), + L"/MAXAGE:%hu-%02hu-%02hu ", time.wYear, time.wMonth, time.wDay); + string = wcsdup(temp_string); return string; } diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index 0d498a9649c..27c32d20262 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -48,6 +48,8 @@ struct robocopy_options { BOOL dry_run; LONGLONG min_size; LONGLONG max_size; + FILETIME min_time; + FILETIME max_time; };
struct robocopy_statistics {
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/tests/robocopy.c | 154 +++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+)
diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c index 38279b643c1..4624bc6d92b 100644 --- a/programs/robocopy/tests/robocopy.c +++ b/programs/robocopy/tests/robocopy.c @@ -672,6 +672,130 @@ static void check_min_2_test(void) check_folder_and_delete(L"robocopy_destination", TRUE); }
+static void check_maxage_1_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); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA", TRUE); + check_folder_and_delete(L"robocopy_destination\folderB", TRUE); + check_folder_and_delete(L"robocopy_destination\folderC", TRUE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + +static void check_maxage_2_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", FALSE); + check_file_and_delete(L"robocopy_destination\fileB.b", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA", TRUE); + check_folder_and_delete(L"robocopy_destination\folderB", TRUE); + check_folder_and_delete(L"robocopy_destination\folderC", TRUE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + +static void check_minage_1_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", FALSE); + check_file_and_delete(L"robocopy_destination\fileB.b", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA", TRUE); + check_folder_and_delete(L"robocopy_destination\folderB", TRUE); + check_folder_and_delete(L"robocopy_destination\folderC", TRUE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + +static void check_minage_2_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", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA", TRUE); + check_folder_and_delete(L"robocopy_destination\folderB", TRUE); + check_folder_and_delete(L"robocopy_destination\folderC", TRUE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + START_TEST(robocopy) { WCHAR temp_command_line[2048], previous_cwd_path[4096], temp_path[4096]; @@ -882,6 +1006,36 @@ START_TEST(robocopy) check_mir_test(); winetest_pop_context();
+ winetest_push_context("MAXAGE test 1"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /maxage:20000101 /s /e /r:1 /w:0", 1); + check_mir_test(); + winetest_pop_context(); + + winetest_push_context("MAXAGE test 2"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /maxage:20 /s /e /r:1 /w:0", 1); + check_maxage_1_test(); + winetest_pop_context(); + + winetest_push_context("MAXAGE test 3"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /maxage:4 /s /e /r:1 /w:0", 1); + check_maxage_2_test(); + winetest_pop_context(); + + winetest_push_context("MINAGE test 1"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /minage:20210102 /s /e /r:1 /w:0", 1); + check_minage_1_test(); + winetest_pop_context(); + + winetest_push_context("MINAGE test 2"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /minage:8 /s /e /r:1 /w:0", 1); + check_minage_2_test(); + winetest_pop_context(); + /* Reset CWD to previous folder */ ok(SetCurrentDirectoryW(previous_cwd_path), "couldn't set CWD to previous CWD folder "%S"", previous_cwd_path); } \ No newline at end of file
Implements the /XN and /XO flags, which prevent older / newer files in the destination folder from being overwritten
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/main.c | 46 ++++++++++++++++++++++++++++++++---- programs/robocopy/robocopy.h | 2 ++ 2 files changed, 43 insertions(+), 5 deletions(-)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 17d991e7435..2fc0c39503d 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -232,6 +232,16 @@ static void parse_arguments(int argc, WCHAR *argv[]) options.purge_source = TRUE; options.purge_source_files = TRUE; } + /* xn - Don't overwrite files that are newer (in the destination) */ + else if (!wcsicmp(argv[i], L"/xn")) + { + options.dont_overwrite_newer_files = TRUE; + } + /* xo - Don't overwrite files that are older (in the destination) */ + else if (!wcsicmp(argv[i], L"/xo")) + { + options.dont_overwrite_older_files = TRUE; + } /* xf - Excluded Files */ else if (!wcsicmp(argv[i], L"/xf")) { @@ -444,11 +454,12 @@ static void get_file_paths_in_folder(WCHAR *directory_path, struct list *paths, } }
-static BOOL is_valid_file(WCHAR *source) +static BOOL is_valid_file(WCHAR *source, WCHAR *destination) { - HANDLE source_handle; + HANDLE source_handle, destination_handle; LARGE_INTEGER source_size; - FILETIME source_creation_time, source_access_time, source_modified_time; + FILETIME source_creation_time, source_access_time, source_modified_time, + destination_creation_time, destination_access_time, destination_modified_time; source_handle = CreateFileW(source, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (source_handle == INVALID_HANDLE_VALUE) return FALSE; GetFileSizeEx(source_handle, &source_size); @@ -467,7 +478,24 @@ static BOOL is_valid_file(WCHAR *source) CloseHandle(source_handle); return FALSE; } + + destination_handle = CreateFileW(destination, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (destination_handle == INVALID_HANDLE_VALUE) + { + CloseHandle(source_handle); + return TRUE; + } + GetFileTime(destination_handle, &destination_creation_time, &destination_access_time, &destination_modified_time); + /* don't overwrite newer or older files if set to do so */ + if ((options.dont_overwrite_newer_files && CompareFileTime(&source_creation_time, &destination_creation_time) > 0) || + (options.dont_overwrite_older_files && CompareFileTime(&source_creation_time, &destination_creation_time) < 0)) + { + CloseHandle(source_handle); + CloseHandle(destination_handle); + return FALSE; + } CloseHandle(source_handle); + CloseHandle(destination_handle); return TRUE; }
@@ -515,8 +543,8 @@ static BOOL perform_copy(struct robocopy_statistics *statistics) } else { - /* ignore file if the file size is not within the allowed limits */ - if (!is_valid_file(current_absolute_path)) continue; + /* ignore file if the file size is not within the allowed limits (or would cause an illegal overwrite) */ + if (!is_valid_file(current_absolute_path, target_path)) continue;
if (options.dry_run || copy_or_move_file(current_absolute_path, target_path, options.purge_source_files)) { @@ -626,6 +654,14 @@ static WCHAR *get_option_string(void) wcscat(temp_string, L"/MOV "); }
+ /* Ignore newer files */ + if (options.dont_overwrite_newer_files) + wcscat(temp_string, L"/XN "); + + /* Ignore older files */ + if (options.dont_overwrite_older_files) + wcscat(temp_string, L"/XO "); + /* Max File Size */ if (options.max_size != MAXLONGLONG) swprintf(temp_string + wcslen(temp_string), ARRAY_SIZE(temp_string) - wcslen(temp_string), diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index 27c32d20262..cf396f88dfc 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -50,6 +50,8 @@ struct robocopy_options { LONGLONG max_size; FILETIME min_time; FILETIME max_time; + BOOL dont_overwrite_newer_files; + BOOL dont_overwrite_older_files; };
struct robocopy_statistics {
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/tests/robocopy.c | 122 +++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+)
diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c index 4624bc6d92b..54aed2233ca 100644 --- a/programs/robocopy/tests/robocopy.c +++ b/programs/robocopy/tests/robocopy.c @@ -796,6 +796,76 @@ static void check_minage_2_test(void) check_folder_and_delete(L"robocopy_destination", TRUE); }
+static void check_xn_test(void) +{ + check_files_equal(L"robocopy_source\fileA.a", L"robocopy_destination\fileA.a", FALSE); + check_files_equal(L"robocopy_source\fileB.b", L"robocopy_destination\fileB.b", TRUE); + check_files_equal(L"robocopy_source\folderB\fileF.f", L"robocopy_destination\folderB\fileF.f", TRUE); + + 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); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA", TRUE); + check_folder_and_delete(L"robocopy_destination\folderB", TRUE); + check_folder_and_delete(L"robocopy_destination\folderC", TRUE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + +static void check_xo_test(void) +{ + check_files_equal(L"robocopy_source\fileA.a", L"robocopy_destination\fileA.a", TRUE); + check_files_equal(L"robocopy_source\fileB.b", L"robocopy_destination\fileB.b", TRUE); + check_files_equal(L"robocopy_source\folderB\fileF.f", L"robocopy_destination\folderB\fileF.f", FALSE); + + 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); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA", TRUE); + check_folder_and_delete(L"robocopy_destination\folderB", TRUE); + check_folder_and_delete(L"robocopy_destination\folderC", TRUE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + START_TEST(robocopy) { WCHAR temp_command_line[2048], previous_cwd_path[4096], temp_path[4096]; @@ -1036,6 +1106,58 @@ START_TEST(robocopy) check_minage_2_test(); winetest_pop_context();
+ winetest_push_context("XN test 1"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /mir /xn /r:1 /w:0", 1); + check_mir_test(); + winetest_pop_context(); + + winetest_push_context("XN test 2"); + create_test_source_folder(); + create_test_folder(L"robocopy_destination"); + create_test_file(L"robocopy_destination\fileA.a", 9000, 0, -300); + create_test_folder(L"robocopy_destination\folderB"); + create_test_file(L"robocopy_destination\folderB\fileF.f", 9000, 0, 0); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /mir /xn /r:1 /w:0", 1); + check_xn_test(); + winetest_pop_context(); + + winetest_push_context("XN test 3"); + create_test_source_folder(); + create_test_folder(L"robocopy_destination"); + create_test_file(L"robocopy_destination\fileA.a", 9000, 0, -300); + create_test_folder(L"robocopy_destination\folderB"); + create_test_file(L"robocopy_destination\folderB\fileF.f", 9000, 0, 0); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /s /e /xn /r:1 /w:0", 1); + check_xn_test(); + winetest_pop_context(); + + winetest_push_context("XO test 1"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /mir /xo /r:1 /w:0", 1); + check_mir_test(); + winetest_pop_context(); + + winetest_push_context("XO test 2"); + create_test_source_folder(); + create_test_folder(L"robocopy_destination"); + create_test_file(L"robocopy_destination\fileA.a", 9000, 0, -300); + create_test_folder(L"robocopy_destination\folderB"); + create_test_file(L"robocopy_destination\folderB\fileF.f", 9000, 0, 0); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /mir /xo /r:1 /w:0", 1); + check_xo_test(); + winetest_pop_context(); + + winetest_push_context("XO test 3"); + create_test_source_folder(); + create_test_folder(L"robocopy_destination"); + create_test_file(L"robocopy_destination\fileA.a", 9000, 0, -300); + create_test_folder(L"robocopy_destination\folderB"); + create_test_file(L"robocopy_destination\folderB\fileF.f", 9000, 0, 0); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /s /e /xo /r:1 /w:0", 1); + check_xo_test(); + winetest_pop_context(); + /* Reset CWD to previous folder */ ok(SetCurrentDirectoryW(previous_cwd_path), "couldn't set CWD to previous CWD folder "%S"", previous_cwd_path); } \ No newline at end of file
Implements the /XX and /XL flags, which prevent the overwriting / deletion of files that only exist in the source, but not the destination, or only the destination, but not the source, by flags like /PURGE or /MOVE
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/main.c | 26 ++++++++++++++++++++++++-- programs/robocopy/robocopy.h | 2 ++ 2 files changed, 26 insertions(+), 2 deletions(-)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 2fc0c39503d..620154b58c8 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -254,6 +254,16 @@ static void parse_arguments(int argc, WCHAR *argv[]) /* xd includes all following directories, until the next option / flag */ is_xd = TRUE; } + /* xx - Ignore files in destination that are not also in source */ + else if (!wcsicmp(argv[i], L"/xx")) + { + options.exclude_files_not_in_source = TRUE; + } + /* xl - Create no new files in destination folder */ + else if (!wcsicmp(argv[i], L"/xl")) + { + options.create_no_new_files = TRUE; + } /* lev - Limit depth of subdirectories */ else if (!wcsnicmp(argv[i], L"/lev:", 5)) { @@ -483,6 +493,8 @@ static BOOL is_valid_file(WCHAR *source, WCHAR *destination) if (destination_handle == INVALID_HANDLE_VALUE) { CloseHandle(source_handle); + /* ignore file if no file at the destination path exist and no no files should be created */ + if (options.create_no_new_files) return FALSE; return TRUE; } GetFileTime(destination_handle, &destination_creation_time, &destination_access_time, &destination_modified_time); @@ -533,7 +545,9 @@ static BOOL perform_copy(struct robocopy_statistics *statistics) if (PathIsDirectoryW(current_absolute_path)) { /* ignore empty directories if empty subdirectories are not copied */ - if ((PathIsDirectoryEmptyW(current_absolute_path) && !options.copy_empty_subdirectories)) continue; + if ((PathIsDirectoryEmptyW(current_absolute_path) && !options.copy_empty_subdirectories) || + options.create_no_new_files) + continue;
/* Create the directory path and then create the directory itself */ if (!options.dry_run && (!create_directory_path(target_path) || @@ -586,7 +600,7 @@ static BOOL perform_copy(struct robocopy_statistics *statistics) else statistics->extra_files = TRUE;
/* if purge is specified (and xx is not set), we delete the (non-excluded) extra files and folders from the destination */ - if (options.purge_destination) + if (options.purge_destination && !options.exclude_files_not_in_source) { current_absolute_path = get_combined_path(options.destination, current_path->name); /* only files or empty folders */ @@ -662,6 +676,14 @@ static WCHAR *get_option_string(void) if (options.dont_overwrite_older_files) wcscat(temp_string, L"/XO ");
+ /* Ignore files in destination only */ + if (options.exclude_files_not_in_source) + wcscat(temp_string, L"/XX "); + + /* No new files */ + if (options.create_no_new_files) + wcscat(temp_string, L"/XL "); + /* Max File Size */ if (options.max_size != MAXLONGLONG) swprintf(temp_string + wcslen(temp_string), ARRAY_SIZE(temp_string) - wcslen(temp_string), diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index cf396f88dfc..376c466d907 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -52,6 +52,8 @@ struct robocopy_options { FILETIME max_time; BOOL dont_overwrite_newer_files; BOOL dont_overwrite_older_files; + BOOL exclude_files_not_in_source; + BOOL create_no_new_files; };
struct robocopy_statistics {
Implements the /XC flag, which prevents the overwriting of files in the destination folder with an identical timestamp, but a different file size
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/main.c | 21 ++++++++++++++++++++- programs/robocopy/robocopy.h | 1 + 2 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 620154b58c8..a7c419e37d7 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -254,6 +254,11 @@ static void parse_arguments(int argc, WCHAR *argv[]) /* xd includes all following directories, until the next option / flag */ is_xd = TRUE; } + /* xc - Don't overwrite files that are changed */ + else if (!wcsicmp(argv[i], L"/xc")) + { + options.dont_overwrite_changed_files = TRUE; + } /* xx - Ignore files in destination that are not also in source */ else if (!wcsicmp(argv[i], L"/xx")) { @@ -467,7 +472,7 @@ static void get_file_paths_in_folder(WCHAR *directory_path, struct list *paths, static BOOL is_valid_file(WCHAR *source, WCHAR *destination) { HANDLE source_handle, destination_handle; - LARGE_INTEGER source_size; + LARGE_INTEGER source_size, destination_size; FILETIME source_creation_time, source_access_time, source_modified_time, destination_creation_time, destination_access_time, destination_modified_time; source_handle = CreateFileW(source, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); @@ -498,6 +503,16 @@ static BOOL is_valid_file(WCHAR *source, WCHAR *destination) return TRUE; } GetFileTime(destination_handle, &destination_creation_time, &destination_access_time, &destination_modified_time); + GetFileSizeEx(destination_handle, &destination_size); + /* don't overwrite "changed" files (meaning "different file size but same filetime") if set to do so */ + if (options.dont_overwrite_changed_files && + destination_size.QuadPart != source_size.QuadPart && + !CompareFileTime(&destination_creation_time, &source_creation_time)) + { + CloseHandle(source_handle); + CloseHandle(destination_handle); + return FALSE; + } /* don't overwrite newer or older files if set to do so */ if ((options.dont_overwrite_newer_files && CompareFileTime(&source_creation_time, &destination_creation_time) > 0) || (options.dont_overwrite_older_files && CompareFileTime(&source_creation_time, &destination_creation_time) < 0)) @@ -684,6 +699,10 @@ static WCHAR *get_option_string(void) if (options.create_no_new_files) wcscat(temp_string, L"/XL ");
+ /* Ignore changed files */ + if (options.dont_overwrite_changed_files) + wcscat(temp_string, L"/XC "); + /* Max File Size */ if (options.max_size != MAXLONGLONG) swprintf(temp_string + wcslen(temp_string), ARRAY_SIZE(temp_string) - wcslen(temp_string), diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index 376c466d907..67fbd9eb822 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -52,6 +52,7 @@ struct robocopy_options { FILETIME max_time; BOOL dont_overwrite_newer_files; BOOL dont_overwrite_older_files; + BOOL dont_overwrite_changed_files; BOOL exclude_files_not_in_source; BOOL create_no_new_files; };
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/tests/robocopy.c | 205 +++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+)
diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c index 54aed2233ca..94911cfe632 100644 --- a/programs/robocopy/tests/robocopy.c +++ b/programs/robocopy/tests/robocopy.c @@ -866,6 +866,141 @@ static void check_xo_test(void) check_folder_and_delete(L"robocopy_destination", TRUE); }
+static void check_xc_test(void) +{ + check_files_equal(L"robocopy_source\fileA.a", L"robocopy_destination\fileA.a", TRUE); + check_files_equal(L"robocopy_source\folderB\fileF.f", L"robocopy_destination\folderB\fileF.f", FALSE); + + 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); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA", TRUE); + check_folder_and_delete(L"robocopy_destination\folderB", TRUE); + check_folder_and_delete(L"robocopy_destination\folderC", TRUE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + +static void check_xx_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); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA", TRUE); + check_folder_and_delete(L"robocopy_destination\folderB", TRUE); + check_folder_and_delete(L"robocopy_destination\folderC", FALSE); + check_file_and_delete(L"robocopy_destination\extraFile1", TRUE); + check_file_and_delete(L"robocopy_destination\extraFolder1\extraFile2", TRUE); + check_folder_and_delete(L"robocopy_destination\extraFolder1", TRUE); + check_folder_and_delete(L"robocopy_destination\extraFolder2", TRUE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + +static void check_xl_1_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", FALSE); + check_file_and_delete(L"robocopy_destination\fileB.b", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA", FALSE); + 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); +} + +static void check_xl_2_test(void) +{ + check_files_equal(L"robocopy_source\fileA.a", L"robocopy_destination\fileA.a", TRUE); + check_files_equal(L"robocopy_source\folderB\fileF.f", L"robocopy_destination\folderB\fileF.f", TRUE); + + 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", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA", FALSE); + check_folder_and_delete(L"robocopy_destination\folderB", TRUE); + check_folder_and_delete(L"robocopy_destination\folderC", FALSE); + check_file_and_delete(L"robocopy_destination\extraFile", FALSE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + START_TEST(robocopy) { WCHAR temp_command_line[2048], previous_cwd_path[4096], temp_path[4096]; @@ -1158,6 +1293,76 @@ START_TEST(robocopy) check_xo_test(); winetest_pop_context();
+ winetest_push_context("XC test 1"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /mir /xc /r:1 /w:0", 1); + check_mir_test(); + winetest_pop_context(); + + winetest_push_context("XC test 2"); + create_test_source_folder(); + create_test_folder(L"robocopy_destination"); + create_test_file(L"robocopy_destination\fileA.a", 1000, 0, -50); + create_test_folder(L"robocopy_destination\folderB"); + create_test_file(L"robocopy_destination\folderB\fileF.f", 1000, 132223104000000000, 0); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /mir /xc /r:1 /w:0", 1); + check_xc_test(); + winetest_pop_context(); + + winetest_push_context("XC test 3"); + create_test_source_folder(); + create_test_folder(L"robocopy_destination"); + create_test_file(L"robocopy_destination\fileA.a", 1000, 0, -50); + create_test_folder(L"robocopy_destination\folderB"); + create_test_file(L"robocopy_destination\folderB\fileF.f", 1000, 132223104000000000, 0); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /s /e /xc /r:1 /w:0", 1); + check_xc_test(); + winetest_pop_context(); + + winetest_push_context("XX test 1"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /mir /xx /r:1 /w:0", 1); + check_mir_test(); + winetest_pop_context(); + + winetest_push_context("XX test 2"); + create_test_source_folder(); + create_test_folder(L"robocopy_destination"); + create_test_file(L"robocopy_destination\fileA.a", 9000, 0, -10); + create_test_folder(L"robocopy_destination\folderB"); + create_test_file(L"robocopy_destination\folderB\fileF.f", 9000, 0, -10); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /mir /xx /r:1 /w:0", 1); + check_mir_test(); + winetest_pop_context(); + + winetest_push_context("XX test 3"); + create_test_source_folder(); + create_test_folder(L"robocopy_destination"); + create_test_file(L"robocopy_destination\extraFile1", 9000, 0, -20); + create_test_folder(L"robocopy_destination\extraFolder1"); + create_test_folder(L"robocopy_destination\extraFolder2"); + create_test_file(L"robocopy_destination\extraFolder1\extraFile2", 9000, 0, -20); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /purge /s /lev:100 /xx /r:1 /w:0", 3); + check_xx_test(); + winetest_pop_context(); + + winetest_push_context("XL test 1"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /mir /xl /r:1 /w:0", 0); + check_xl_1_test(); + winetest_pop_context(); + + winetest_push_context("XL test 2"); + create_test_source_folder(); + create_test_folder(L"robocopy_destination"); + create_test_file(L"robocopy_destination\extraFile", 9000, 0, -20); + create_test_file(L"robocopy_destination\fileA.a", 9000, 0, -20); + create_test_folder(L"robocopy_destination\folderB"); + create_test_file(L"robocopy_destination\folderB\fileF.f", 9000, 0, -20); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /mir /xl /r:1 /w:0", 3); + check_xl_2_test(); + winetest_pop_context(); + /* Reset CWD to previous folder */ ok(SetCurrentDirectoryW(previous_cwd_path), "couldn't set CWD to previous CWD folder "%S"", previous_cwd_path); } \ No newline at end of file
Tests for some mixed flag usage, that could produce quirky behavior, and support for file paths longer than MAX_PATH
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/tests/robocopy.c | 196 +++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+)
diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c index 94911cfe632..f699a9dc57b 100644 --- a/programs/robocopy/tests/robocopy.c +++ b/programs/robocopy/tests/robocopy.c @@ -1001,6 +1001,171 @@ static void check_xl_2_test(void) check_folder_and_delete(L"robocopy_destination", TRUE); }
+static void check_mixed_1_test(void) +{ + check_file_and_delete(L"robocopy_source\fileA.a", FALSE); + 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", FALSE); + 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", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA", FALSE); + check_folder_and_delete(L"robocopy_destination\folderB", TRUE); + check_folder_and_delete(L"robocopy_destination\folderC", TRUE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + +static void check_mixed_2_test(void) +{ + check_file_and_delete(L"robocopy_source\fileA.a", FALSE); + check_file_and_delete(L"robocopy_source\fileB.b", FALSE); + check_file_and_delete(L"robocopy_source\folderA\fileC.c", TRUE); + check_file_and_delete(L"robocopy_source\folderA\fileD.d", FALSE); + check_file_and_delete(L"robocopy_source\folderA\folderD\fileE.e", TRUE); + check_file_and_delete(L"robocopy_source\folderB\fileF.f", FALSE); + check_file_and_delete(L"robocopy_source\folderB\fileG.g", FALSE); + check_folder_and_delete(L"robocopy_source\folderA\folderD", TRUE); + check_folder_and_delete(L"robocopy_source\folderA\folderE", FALSE); + check_folder_and_delete(L"robocopy_source\folderA", TRUE); + check_folder_and_delete(L"robocopy_source\folderB", FALSE); + check_folder_and_delete(L"robocopy_source\folderC", FALSE); + 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); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", TRUE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", TRUE); + check_folder_and_delete(L"robocopy_destination\folderA", TRUE); + check_folder_and_delete(L"robocopy_destination\folderB", TRUE); + check_folder_and_delete(L"robocopy_destination\folderC", TRUE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + +static void check_mixed_3_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); + check_file_and_delete(L"robocopy_destination\folderA\fileC.c", TRUE); + check_file_and_delete(L"robocopy_destination\folderA\fileD.d", FALSE); + check_file_and_delete(L"robocopy_destination\folderA\folderD\fileE.e", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileF.f", FALSE); + check_file_and_delete(L"robocopy_destination\folderB\fileG.g", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderD", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA\folderE", FALSE); + check_folder_and_delete(L"robocopy_destination\folderA", TRUE); + check_folder_and_delete(L"robocopy_destination\folderB", TRUE); + check_folder_and_delete(L"robocopy_destination\folderC", TRUE); + check_file_and_delete(L"robocopy_destination\extraFile", TRUE); + check_folder_and_delete(L"robocopy_destination", TRUE); +} + +static void check_long_filename_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"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\ +cccccccccccccccccccccccccccccccccccccccc\dddddddddddddddddddddddddddddddddddddddd\eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\\ +ffffffffffffffffffffffffffffffffffffffff\gggggggggggggggggggggggggggggggggggggggg\robocopy_destination\fileA.a", TRUE); + check_file_and_delete(L"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\ +cccccccccccccccccccccccccccccccccccccccc\dddddddddddddddddddddddddddddddddddddddd\eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\\ +ffffffffffffffffffffffffffffffffffffffff\gggggggggggggggggggggggggggggggggggggggg\robocopy_destination\fileB.b", TRUE); + check_file_and_delete(L"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\ +cccccccccccccccccccccccccccccccccccccccc\dddddddddddddddddddddddddddddddddddddddd\eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\\ +ffffffffffffffffffffffffffffffffffffffff\gggggggggggggggggggggggggggggggggggggggg\robocopy_destination\folderA\fileC.c", FALSE); + check_file_and_delete(L"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\ +cccccccccccccccccccccccccccccccccccccccc\dddddddddddddddddddddddddddddddddddddddd\eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\\ +ffffffffffffffffffffffffffffffffffffffff\gggggggggggggggggggggggggggggggggggggggg\robocopy_destination\folderA\fileD.d", FALSE); + check_file_and_delete(L"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\ +cccccccccccccccccccccccccccccccccccccccc\dddddddddddddddddddddddddddddddddddddddd\eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\\ +ffffffffffffffffffffffffffffffffffffffff\gggggggggggggggggggggggggggggggggggggggg\robocopy_destination\folderA\folderD\fileE.e", FALSE); + check_file_and_delete(L"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\ +cccccccccccccccccccccccccccccccccccccccc\dddddddddddddddddddddddddddddddddddddddd\eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\\ +ffffffffffffffffffffffffffffffffffffffff\gggggggggggggggggggggggggggggggggggggggg\robocopy_destination\folderB\fileF.f", FALSE); + check_file_and_delete(L"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\ +cccccccccccccccccccccccccccccccccccccccc\dddddddddddddddddddddddddddddddddddddddd\eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\\ +ffffffffffffffffffffffffffffffffffffffff\gggggggggggggggggggggggggggggggggggggggg\robocopy_destination\folderB\fileG.g", FALSE); + check_folder_and_delete(L"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\ +cccccccccccccccccccccccccccccccccccccccc\dddddddddddddddddddddddddddddddddddddddd\eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\\ +ffffffffffffffffffffffffffffffffffffffff\gggggggggggggggggggggggggggggggggggggggg\robocopy_destination\folderA\folderD", FALSE); + check_folder_and_delete(L"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\ +cccccccccccccccccccccccccccccccccccccccc\dddddddddddddddddddddddddddddddddddddddd\eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\\ +ffffffffffffffffffffffffffffffffffffffff\gggggggggggggggggggggggggggggggggggggggg\robocopy_destination\folderA\folderE", FALSE); + check_folder_and_delete(L"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\ +cccccccccccccccccccccccccccccccccccccccc\dddddddddddddddddddddddddddddddddddddddd\eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\\ +ffffffffffffffffffffffffffffffffffffffff\gggggggggggggggggggggggggggggggggggggggg\robocopy_destination\folderA", FALSE); + check_folder_and_delete(L"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\ +cccccccccccccccccccccccccccccccccccccccc\dddddddddddddddddddddddddddddddddddddddd\eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\\ +ffffffffffffffffffffffffffffffffffffffff\gggggggggggggggggggggggggggggggggggggggg\robocopy_destination\folderB", FALSE); + check_folder_and_delete(L"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\ +cccccccccccccccccccccccccccccccccccccccc\dddddddddddddddddddddddddddddddddddddddd\eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\\ +ffffffffffffffffffffffffffffffffffffffff\gggggggggggggggggggggggggggggggggggggggg\robocopy_destination\folderC", FALSE); + check_folder_and_delete(L"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\ +cccccccccccccccccccccccccccccccccccccccc\dddddddddddddddddddddddddddddddddddddddd\eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\\ +ffffffffffffffffffffffffffffffffffffffff\gggggggggggggggggggggggggggggggggggggggg\robocopy_destination", TRUE); + check_folder_and_delete(L"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\ +cccccccccccccccccccccccccccccccccccccccc\dddddddddddddddddddddddddddddddddddddddd\eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\\ +ffffffffffffffffffffffffffffffffffffffff\gggggggggggggggggggggggggggggggggggggggg", TRUE); + check_folder_and_delete(L"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\ +cccccccccccccccccccccccccccccccccccccccc\dddddddddddddddddddddddddddddddddddddddd\eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\\ +ffffffffffffffffffffffffffffffffffffffff", TRUE); + check_folder_and_delete(L"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\ +cccccccccccccccccccccccccccccccccccccccc\dddddddddddddddddddddddddddddddddddddddd\eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", TRUE); + check_folder_and_delete(L"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\ +cccccccccccccccccccccccccccccccccccccccc\dddddddddddddddddddddddddddddddddddddddd", TRUE); + check_folder_and_delete(L"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\\ +cccccccccccccccccccccccccccccccccccccccc", TRUE); + check_folder_and_delete(L"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", TRUE); + check_folder_and_delete(L"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", TRUE); +} + START_TEST(robocopy) { WCHAR temp_command_line[2048], previous_cwd_path[4096], temp_path[4096]; @@ -1363,6 +1528,37 @@ START_TEST(robocopy) check_xl_2_test(); winetest_pop_context();
+ winetest_push_context("mixed flags test 1"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /min:100 /max:5000 file?.? /xd folderA /mov /s /e /r:1 /w:0", 1); + check_mixed_1_test(); + winetest_pop_context(); + + winetest_push_context("mixed flags test 2"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /purge /move /s /xd *d /xf *.c /e /r:1 /w:0", 1); + check_mixed_2_test(); + winetest_pop_context(); + + winetest_push_context("mixed flags test 3"); + create_test_source_folder(); + create_test_folder(L"robocopy_destination"); + create_test_file(L"robocopy_destination\extraFile", 9000, 0, -1); + create_test_file(L"robocopy_destination\fileA.a", 9000, 0, -20); + create_test_file(L"robocopy_destination\fileB.b", 10, 0, -20); + execute_robocopy(L"robocopy.exe robocopy_source robocopy_destination /minage:2 /maxage:20210102 /xc /mir /xx /lev:2 /r:1 /w:0", 3); + check_mixed_3_test(); + winetest_pop_context(); + + winetest_push_context("long filenames test"); + create_test_source_folder(); + execute_robocopy(L"robocopy.exe robocopy_source \ +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\cccccccccccccccccccccccccccccccccccccccc\\ +dddddddddddddddddddddddddddddddddddddddd\eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\ffffffffffffffffffffffffffffffffffffffff\\ +gggggggggggggggggggggggggggggggggggggggg\robocopy_destination /r:1 /w:0", 1); + check_long_filename_test(); + winetest_pop_context(); + /* Reset CWD to previous folder */ ok(SetCurrentDirectoryW(previous_cwd_path), "couldn't set CWD to previous CWD folder "%S"", previous_cwd_path); } \ No newline at end of file
Prints the date / time at the start, the end and on any error message
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/main.c | 43 +++++++++++++++++++++++++++++++---- programs/robocopy/robocopy.h | 2 ++ programs/robocopy/robocopy.rc | 2 ++ 3 files changed, 43 insertions(+), 4 deletions(-)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index a7c419e37d7..c54d7d9360c 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -69,17 +69,44 @@ static void output_message(UINT format_string_id, ...) LocalFree(string); }
+static WCHAR* get_date_time_string(BOOL log_format) +{ + SYSTEMTIME current_time; + WCHAR *date_time_string, temp_string[128]; + + date_time_string = calloc(128, sizeof(WCHAR)); + + GetLocalTime(¤t_time); + + /* Local time / date format is ignored, to enable scripted extraction of parts of the date / time from the log output */ + if (log_format) + GetDateFormatW(LOCALE_USER_DEFAULT, 0, ¤t_time, L"yyyy/MM/dd ", temp_string, ARRAY_SIZE(temp_string)); + else + GetDateFormatW(LOCALE_USER_DEFAULT, 0, ¤t_time, L"ddd MMM dd ", temp_string, ARRAY_SIZE(temp_string)); + + wcscpy(date_time_string, temp_string); + GetTimeFormatW(LOCALE_USER_DEFAULT, 0, ¤t_time, L"hh:mm:ss", temp_string, ARRAY_SIZE(temp_string)); + wcscat(date_time_string, temp_string); + if (!log_format) + { + GetDateFormatW(LOCALE_USER_DEFAULT, 0, ¤t_time, L" yyyy", temp_string, ARRAY_SIZE(temp_string)); + wcscat(date_time_string, temp_string); + } + return date_time_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]; + WCHAR *error_string, error_code_long[64], error_code_short[64], *date_time_string;
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, error_code, 0, (LPWSTR)&error_string, 0, NULL); + date_time_string = get_date_time_string(TRUE);
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_id, L"", error_code_short, error_code_long, path, error_string); + output_message(format_string_id, date_time_string, error_code_short, error_code_long, path, error_string);
LocalFree(error_string); } @@ -732,10 +759,13 @@ static WCHAR *get_option_string(void) static void print_header(void) { UINT i; - WCHAR *options_string; + WCHAR *options_string, *date_time_string;
output_message(STRING_HEADER);
+ date_time_string = get_date_time_string(FALSE); + if (date_time_string) output_message(STRING_DATE_TIME_START, date_time_string); + if (!options.source) output_message(STRING_SOURCE, L"-"); else output_message(STRING_SOURCE, strip_path_prefix(options.source));
@@ -762,7 +792,7 @@ int __cdecl wmain(int argc, WCHAR *argv[]) { struct robocopy_statistics statistics; int exit_code; - WCHAR dirs_copied[64], files_copied[64]; + WCHAR dirs_copied[64], files_copied[64], *date_time_string;
parse_arguments(argc, argv);
@@ -791,6 +821,11 @@ int __cdecl wmain(int argc, WCHAR *argv[]) swprintf(files_copied, ARRAY_SIZE(files_copied), L"%u", statistics.copied_files); output_message(STRING_STATISTICS, dirs_copied, files_copied);
+ date_time_string = get_date_time_string(FALSE); + if (!date_time_string) + return ROBOCOPY_ERROR_NO_FILES_COPIED; + output_message(STRING_DATE_TIME_END, date_time_string); + exit_code = ROBOCOPY_NO_ERROR_NO_COPY; if (statistics.copied_files) exit_code += ROBOCOPY_NO_ERROR_FILES_COPIED; if (statistics.extra_files) exit_code += ROBOCOPY_EXTRA_FILES_IN_DESTINATION; diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index 67fbd9eb822..459e5a7f8f7 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -71,6 +71,8 @@ struct robocopy_statistics {
/* Resource strings */ #define STRING_HEADER 1000 +#define STRING_DATE_TIME_START 1001 +#define STRING_DATE_TIME_END 1002 #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 315dce5f947..df66753ca6b 100644 --- a/programs/robocopy/robocopy.rc +++ b/programs/robocopy/robocopy.rc @@ -26,6 +26,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT STRINGTABLE { STRING_HEADER, "Robocopy for Wine\n\n" + STRING_DATE_TIME_START, " Started: %1\n\n" + STRING_DATE_TIME_END, "\n Ended: %1\n\n" STRING_SOURCE, " Source: %1\n" STRING_DESTINATION, " Destination: %1\n\n" STRING_FILES, " Files: %1\n"
Implements the /LOG:<path> and /LOG+:<path> arguments, which redirect all output to a logfile
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/main.c | 39 ++++++++++++++++++++++++++++++++++-- programs/robocopy/robocopy.h | 2 ++ 2 files changed, 39 insertions(+), 2 deletions(-)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index c54d7d9360c..5c3c51d052a 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -28,6 +28,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(robocopy); #include "robocopy.h"
struct robocopy_options options; +HANDLE logfile_handle;
static void output_message(UINT format_string_id, ...) { @@ -52,8 +53,21 @@ static void output_message(UINT format_string_id, ...) return; }
- /* If WriteConsole fails, the output is being redirected to a file */ - if (!WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), string, wcslen(string), &bytes_written, NULL)) + /* If a logfile handle is set, the output is written to this file / handle */ + if (logfile_handle != INVALID_HANDLE_VALUE) + { + CHAR *string_unicode; + DWORD length_unicode; + + length_unicode = WideCharToMultiByte(GetConsoleOutputCP(), 0, string, wcslen(string), NULL, 0, NULL, NULL); + string_unicode = malloc(length_unicode); + + WideCharToMultiByte(CP_UTF8, 0, string, wcslen(string), string_unicode, length_unicode, NULL, NULL); + WriteFile(logfile_handle, string_unicode, length_unicode, &bytes_written, NULL); + free(string_unicode); + } + /* Otherwise, if WriteConsole fails, the output is being redirected to a file */ + else if (!WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), string, wcslen(string), &bytes_written, NULL)) { CHAR *string_multibyte; DWORD length_multibyte; @@ -331,6 +345,21 @@ static void parse_arguments(int argc, WCHAR *argv[]) { parse_date_to_filetime(&(argv[i][8]), &options.min_time); } + /* log - output to logfile */ + else if (!wcsnicmp(argv[i], L"/log:", 5)) + { + if (wcslen(argv[i]) > 5) + options.logfile = wcsdup(&(argv[i][5])); + } + /* log+ - output to (appended) logfile */ + else if (!wcsnicmp(argv[i], L"/log+:", 6)) + { + if (wcslen(argv[i]) > 6) + { + options.logfile = wcsdup(&(argv[i][6])); + options.log_append = TRUE; + } + } else { WINE_FIXME("encountered an unknown robocopy flag: %S\n", argv[i]); @@ -796,6 +825,12 @@ int __cdecl wmain(int argc, WCHAR *argv[])
parse_arguments(argc, argv);
+ /* Open or create logfile if (and as) specified */ + if (options.logfile != NULL) + logfile_handle = CreateFileW(options.logfile, options.log_append ? FILE_APPEND_DATA : GENERIC_WRITE, + FILE_SHARE_READ, NULL, OPEN_ALWAYS, 0, NULL); + else logfile_handle = INVALID_HANDLE_VALUE; + /* If no file filters are set, set *.* to include all files */ if (options.files->size == 0) { diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index 459e5a7f8f7..c0bdd836e31 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -55,6 +55,8 @@ struct robocopy_options { BOOL dont_overwrite_changed_files; BOOL exclude_files_not_in_source; BOOL create_no_new_files; + WCHAR *logfile; + BOOL log_append; };
struct robocopy_statistics {
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/tests/robocopy.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+)
diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c index f699a9dc57b..48366ed8e7a 100644 --- a/programs/robocopy/tests/robocopy.c +++ b/programs/robocopy/tests/robocopy.c @@ -1559,6 +1559,26 @@ gggggggggggggggggggggggggggggggggggggggg\robocopy_destination /r:1 /w:0", 1); check_long_filename_test(); winetest_pop_context();
+ winetest_push_context("LOG test 1"); + create_test_source_folder(); + swprintf(temp_command_line, ARRAY_SIZE(temp_command_line), + L"robocopy.exe %s\robocopy_source %s\robocopy_destination /log:%s\log.txt /r:1 /w:0", + temp_path, temp_path, temp_path); + execute_robocopy(temp_command_line, 1); + check_file_and_delete(L"log.txt", TRUE); + check_basic_copy_test(); + winetest_pop_context(); + + winetest_push_context("LOG test 2"); + create_test_source_folder(); + swprintf(temp_command_line, ARRAY_SIZE(temp_command_line), + L"robocopy.exe %s\robocopy_source %s\robocopy_destination /log+:%s\log.txt /r:1 /w:0", + temp_path, temp_path, temp_path); + execute_robocopy(temp_command_line, 1); + check_file_and_delete(L"log.txt", TRUE); + check_basic_copy_test(); + winetest_pop_context(); + /* Reset CWD to previous folder */ ok(SetCurrentDirectoryW(previous_cwd_path), "couldn't set CWD to previous CWD folder "%S"", previous_cwd_path); } \ No newline at end of file
Hi,
While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=97470
Your paranoid android.
=== w864 (64 bit report) ===
robocopy.exe: robocopy.c:140: Test failed: mixed flags test 3: file "robocopy_destination\folderA\fileC.c" does not exist, but should exist
Signed-off-by: Florian Eder others.meder@gmail.com --- programs/robocopy/main.c | 12 ++++++++++++ programs/robocopy/robocopy.h | 4 +++- programs/robocopy/robocopy.rc | 24 ++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-)
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c index 5c3c51d052a..154957ba08c 100644 --- a/programs/robocopy/main.c +++ b/programs/robocopy/main.c @@ -310,6 +310,11 @@ static void parse_arguments(int argc, WCHAR *argv[]) { options.create_no_new_files = TRUE; } + /* ? - Show help */ + else if (!wcsicmp(argv[i], L"/?")) + { + options.show_help = TRUE; + } /* lev - Limit depth of subdirectories */ else if (!wcsnicmp(argv[i], L"/lev:", 5)) { @@ -831,6 +836,13 @@ int __cdecl wmain(int argc, WCHAR *argv[]) FILE_SHARE_READ, NULL, OPEN_ALWAYS, 0, NULL); else logfile_handle = INVALID_HANDLE_VALUE;
+ if (options.show_help) + { + output_message(STRING_HEADER); + output_message(STRING_HELP); + return ROBOCOPY_ERROR_NO_FILES_COPIED; + } + /* If no file filters are set, set *.* to include all files */ if (options.files->size == 0) { diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h index c0bdd836e31..dc3390a7f25 100644 --- a/programs/robocopy/robocopy.h +++ b/programs/robocopy/robocopy.h @@ -57,6 +57,7 @@ struct robocopy_options { BOOL create_no_new_files; WCHAR *logfile; BOOL log_append; + BOOL show_help; };
struct robocopy_statistics { @@ -93,4 +94,5 @@ struct robocopy_statistics { #define STRING_DELETE_DIRECTORY 1020 #define STRING_CREATE_FILE 1022 #define STRING_DELETE_FILE 1023 -#define STRING_MOVE_FILE 1024 \ No newline at end of file +#define STRING_MOVE_FILE 1024 +#define STRING_HELP 1027 \ No newline at end of file diff --git a/programs/robocopy/robocopy.rc b/programs/robocopy/robocopy.rc index df66753ca6b..b89418b578b 100644 --- a/programs/robocopy/robocopy.rc +++ b/programs/robocopy/robocopy.rc @@ -47,6 +47,30 @@ STRINGTABLE STRING_CREATE_FILE, " Copied File: %1\n" STRING_DELETE_FILE, " Deleted File: %1\n" STRING_MOVE_FILE, " Moved File: %1\n" + STRING_HELP, "Usage: robocopy source destination [files] [options]\n\ + \Available Options:\n\ + \ /s Copy non-empty sub-directories\n\ + \ /e Copy all sub-directories\n\ + \ /purge Remove files and directories already present in the destination\n\ + \ /mov Remove files from source after copying them\n\ + \ /move Remove files and directories from source after copying them\n\ + \ /l Dry run, does no actual file system operation\n\ + \ /lev:<n> Copy only files and directories until n sub-directories deep into the source\n\ + \ /mir Alias of "/e /purge", mirrors source to destination\n\ + \ /xf path Exclude files (can contain wildcards)\n\ + \ /xd dir Exclude directories (can contain wildcards)\n\ + \ /min:<n> Only include files that are at least n bytes big\n\ + \ /max:<n> Only include files that are at most n bytes big\n\ + \ /minage:<n> Only include files that are at least n days old\n\ + \ /maxage:<n> Only include files that are at most n days old\n\ + \ /xo Don't overwrite files that are newer than the source file\n\ + \ /xn Don't overwrite files that are older than the source file\n\ + \ /xl Don't add new files to destination\n\ + \ /xx Don't remove files from the source when moving\n\ + \ /xc Don't overwrite files that have the same timestamp, but different size than the\n\ + \ source file\n\ + \ /log:path Write output to log file\n\ + \ /log+:path Append output to log file\n" }
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
Hello Florian!
On 9/6/21 9:54 AM, Florian Eder wrote:
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
In the future (and in future revisions of this patch set) please don't send so many patches at once. It's pretty much impossible to review this many at once, and it means you'd have to resend the whole series if changes are requested. I'd recommend limiting a single series to about five patches.
I'll review the first five patches of this submission.
configure | 2 +- configure.ac | 1 + programs/robocopy/Makefile.in | 9 +++++++++ programs/robocopy/main.c | 29 +++++++++++++++++++++++++++++ programs/robocopy/robocopy.h | 19 +++++++++++++++++++ programs/robocopy/robocopy.rc | 35 +++++++++++++++++++++++++++++++++++ 6 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 programs/robocopy/Makefile.in create mode 100644 programs/robocopy/main.c create mode 100644 programs/robocopy/robocopy.h create mode 100644 programs/robocopy/robocopy.rc
diff --git a/configure b/configure index 99283e0ef18..9e2fa9ef009 100755 --- a/configure +++ b/configure @@ -21264,6 +21264,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 @@ -22797,4 +22798,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 26f74985924..88b9426260f 100644 --- a/configure.ac +++ b/configure.ac @@ -3963,6 +3963,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..5463edcb8b3 --- /dev/null +++ b/programs/robocopy/Makefile.in @@ -0,0 +1,9 @@ +MODULE = robocopy.exe +IMPORTS =
Nitpick, but the empty imports line is kind of useless.
+EXTRADLLFLAGS = -mconsole -municode -mno-cygwin
+C_SRCS = \
- main.c
+RC_SRCS = robocopy.rc \ No newline at end of file diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c new file mode 100644 index 00000000000..a184235e488 --- /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;
+} \ No newline at end of file
Whitespace error here; git should have warned you about this when sending.
diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h new file mode 100644 index 00000000000..33e84b82ea0 --- /dev/null +++ b/programs/robocopy/robocopy.h @@ -0,0 +1,19 @@ +/*
- 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 \ No newline at end of file
This file does nothing; it should be left out until it's used.
diff --git a/programs/robocopy/robocopy.rc b/programs/robocopy/robocopy.rc new file mode 100644 index 00000000000..c7acd5ad161 --- /dev/null +++ b/programs/robocopy/robocopy.rc @@ -0,0 +1,35 @@ +/*
- 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> +#include "robocopy.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"