[PATCH v8 2/3] robocopy: Add argument parser and basic copy logic

Florian Eder others.meder at gmail.com
Sun Oct 31 15:34:35 CDT 2021


Parses path arguments as source, destination and files to include,
reads all files in the source folder that match any of the files to include
and copies them to the destination, creating necessary folders in the process

Signed-off-by: Florian Eder <others.meder at gmail.com>
---
v8: Made some arguments constant, added helper function to replace / remove duplicates of very long flags for PathAllocCombine and
simplified logic in append_matching_directory_content

This patch still converts both source and destination path to absolute paths, as doing otherwise would break the already existing
support for paths > MAX_PATH. It would also not really save many LoCs / much logic, as it would still be required to make sure the
paths both end with a backslash, to simplify the copy logic (as we then can just append the relative paths).
---
 programs/robocopy/Makefile.in      |   1 +
 programs/robocopy/main.c           | 192 ++++++++++++++++++++++++++++-
 programs/robocopy/tests/robocopy.c |  22 ++--
 3 files changed, 202 insertions(+), 13 deletions(-)

diff --git a/programs/robocopy/Makefile.in b/programs/robocopy/Makefile.in
index f5d9dff6e4d..d1f5db665df 100644
--- a/programs/robocopy/Makefile.in
+++ b/programs/robocopy/Makefile.in
@@ -1,6 +1,7 @@
 MODULE    = robocopy.exe
 
 EXTRADLLFLAGS = -mconsole -municode
+IMPORTS   = kernelbase shlwapi
 
 C_SRCS = \
 	main.c
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c
index 5e2ca5c0f01..b58111c7bff 100644
--- a/programs/robocopy/main.c
+++ b/programs/robocopy/main.c
@@ -18,13 +18,201 @@
 
 #define WIN32_LEAN_AND_MEAN
 #include <windows.h>
+#include <stdlib.h>
+#include <pathcch.h>
+#include <shlwapi.h>
 
 #include "wine/debug.h"
 
 WINE_DEFAULT_DEBUG_CHANNEL(robocopy);
 
+#include <wine/list.h>
+
+struct path_array
+{
+    UINT size;
+    WCHAR *array[1];
+};
+
+struct path
+{
+    struct list entry;
+    WCHAR *name;
+};
+
+struct robocopy_options
+{
+    WCHAR *destination;
+    WCHAR *source;
+    struct path_array *files;
+};
+
+#define ROBOCOPY_SUCCESS_FILES_COPIED        1
+#define ROBOCOPY_ERROR_NO_FILES_COPIED        16
+
+struct robocopy_options options;
+
+static WCHAR *get_absolute_path(const WCHAR *path)
+{
+    DWORD size;
+    WCHAR *absolute_path;
+
+    size = GetFullPathNameW(path, 0, NULL, NULL) + 2;
+    absolute_path = calloc(size, sizeof(WCHAR));
+    GetFullPathNameW(path, size, absolute_path, NULL);
+    PathCchAddBackslashEx(absolute_path, size, NULL, NULL);
+    return absolute_path;
+}
+
+static BOOL path_in_array(const WCHAR *name, const struct path_array *names)
+{
+    int i;
+    for (i = 0; i < names->size; i++)
+    {
+        if (PathMatchSpecW(name, names->array[i])) return TRUE;
+    }
+    return FALSE;
+}
+
+static BOOL create_directory_path(const WCHAR *path)
+{
+    WCHAR *pointer, *current_folder;
+    current_folder = calloc(wcslen(path) + 1, sizeof(WCHAR));
+    pointer = wcschr(path, L'\\');
+    while (pointer != NULL)
+    {
+        if (!lstrcpynW(current_folder, path, pointer - path + 2)) return FALSE;
+        if (!CreateDirectoryW(current_folder, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) return FALSE;
+        pointer = wcschr(pointer + 1, L'\\');
+    }
+    return TRUE;
+}
+
+static void combine_path(const WCHAR *path1, const WCHAR *path2, WCHAR **out)
+{
+    PathAllocCombine(path1, path2,
+                     PATHCCH_ALLOW_LONG_PATHS | PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS,
+                     out);
+}
+
+static void append_matching_directory_content(struct list *paths, const WCHAR *search_path, const WCHAR *root, struct path_array *file_names)
+{
+    WIN32_FIND_DATAW entry_data;
+    HANDLE handle;
+    WCHAR *current_absolute_path;
+    struct path *new_path;
+
+    handle = FindFirstFileExW(search_path, FindExInfoStandard, &entry_data, FindExSearchNameMatch, NULL, 0);
+    if (handle == INVALID_HANDLE_VALUE) return;
+    do
+    {
+        if (!wcscmp(L".", entry_data.cFileName) || !wcscmp(L"..", entry_data.cFileName)) continue;
+
+        combine_path(root, entry_data.cFileName, &current_absolute_path);
+
+        if (PathIsDirectoryW(current_absolute_path) || path_in_array(entry_data.cFileName, file_names))
+        {
+            new_path = calloc(1, sizeof(struct path));
+            new_path->name = wcsdup(entry_data.cFileName);
+            list_add_tail(paths, &new_path->entry);
+        }
+    }
+    while (FindNextFileW(handle, &entry_data));
+}
+
+static void get_file_paths_in_folder(const WCHAR *root, struct list *paths)
+{
+    struct path *new_path;
+    WCHAR *current_search_path;
+
+    list_init(paths);
+    new_path = calloc(1, sizeof(struct path));
+    new_path->name = calloc(2, sizeof(WCHAR));
+    list_add_tail(paths, &new_path->entry);
+
+    combine_path(root, L"*", &current_search_path);
+    append_matching_directory_content(paths, current_search_path, root, options.files);
+}
+
+static BOOL perform_copy(void)
+{
+    struct list paths_source;
+    struct path *current_path;
+    WCHAR *current_absolute_path, *target_path;
+
+    list_init(&paths_source);
+
+    if (!PathIsDirectoryW(options.source)) return FALSE;
+
+    create_directory_path(options.destination);
+
+    /* get files in the source folder */
+    get_file_paths_in_folder(options.source, &paths_source);
+
+    /* walk through files in the source folder */
+    LIST_FOR_EACH_ENTRY(current_path, &paths_source, struct path, entry)
+    {
+        combine_path(options.source, current_path->name, &current_absolute_path);
+        combine_path(options.destination, current_path->name, &target_path);
+
+        if (!PathIsDirectoryW(current_absolute_path))
+        {
+            create_directory_path(target_path);
+            CopyFileW(current_absolute_path, target_path, FALSE);
+        }
+    }
+    return TRUE;
+}
+
+static void parse_arguments(int argc, WCHAR *argv[])
+{
+    int i;
+
+    memset(&options, 0, sizeof(options));
+    options.files = calloc(1, offsetof(struct path_array, array[argc]));
+
+    for (i = 1; i < argc; i++)
+    {
+        /*
+         * Robocopy switches contain one (and only one) backslash at the start
+         * /xf => valid flag
+         * /r:1 => valid flag
+         * /r:1aö => valid flag
+         * /r:1aö/ => not a valid flag, is interpreted as a filename
+         */
+        if ((argv[i][0] == '/') && !wcschr(argv[i] + 1, '/'))
+            WINE_FIXME("encountered an unknown robocopy flag: %s\n", debugstr_w(argv[i]));
+        else
+        {
+            if (!options.source)
+                options.source = get_absolute_path(argv[i]);
+            else if (!options.destination)
+                options.destination = get_absolute_path(argv[i]);
+            else
+            {
+                options.files->array[options.files->size] = wcsdup(argv[i]);
+                options.files->size++;
+            }
+        }
+    }
+}
+
 int __cdecl wmain(int argc, WCHAR *argv[])
 {
-    FIXME("robocopy stub\n");
-    return 16;
+    parse_arguments(argc, argv);
+
+    /* If no file filters are set, set *.* to include all files */
+    if (options.files->size == 0)
+    {
+        options.files->array[options.files->size] = wcsdup(L"*.*");
+        options.files->size++;
+    }
+
+    if (!options.destination || !options.source)
+        return ROBOCOPY_ERROR_NO_FILES_COPIED;
+
+    if (!perform_copy())
+        return ROBOCOPY_ERROR_NO_FILES_COPIED;
+
+    return ROBOCOPY_SUCCESS_FILES_COPIED;
 }
diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c
index 48075709cf5..3e268f1cb30 100644
--- a/programs/robocopy/tests/robocopy.c
+++ b/programs/robocopy/tests/robocopy.c
@@ -133,8 +133,8 @@ static void check_source_folder_unchanged(void)
 static void check_basic_copy_1_test(void)
 {
     check_source_folder_unchanged();
-    todo_wine check_file(L"destination\\fileA.a", TRUE);
-    todo_wine check_file(L"destination\\fileB.b", TRUE);
+    check_file(L"destination\\fileA.a", TRUE);
+    check_file(L"destination\\fileB.b", TRUE);
     check_file(L"destination\\folderA\\fileC.c", FALSE);
     check_file(L"destination\\folderA\\fileD.d", FALSE);
     check_file(L"destination\\folderA\\folderD\\fileE.e", FALSE);
@@ -145,14 +145,14 @@ static void check_basic_copy_1_test(void)
     check_folder(L"destination\\folderA", FALSE);
     check_folder(L"destination\\folderB", FALSE);
     check_folder(L"destination\\folderC", FALSE);
-    todo_wine check_folder(L"destination", TRUE);
+    check_folder(L"destination", TRUE);
 }
 
 static void check_basic_copy_2_test(void)
 {
     check_source_folder_unchanged();
     check_file(L"destination\\fileA.a", TRUE);
-    todo_wine check_file(L"destination\\fileB.b", TRUE);
+    check_file(L"destination\\fileB.b", TRUE);
     check_file(L"destination\\folderA\\fileC.c", FALSE);
     check_file(L"destination\\folderA\\fileD.d", FALSE);
     check_file(L"destination\\folderA\\folderD\\fileE.e", FALSE);
@@ -187,7 +187,7 @@ static void check_no_copy_test(void)
 static void check_wildcard_1_test(void)
 {
     check_source_folder_unchanged();
-    todo_wine check_file(L"destination\\fileA.a", TRUE);
+    check_file(L"destination\\fileA.a", TRUE);
     check_file(L"destination\\fileB.b", FALSE);
     check_file(L"destination\\folderA\\fileC.c", FALSE);
     check_file(L"destination\\folderA\\fileD.d", FALSE);
@@ -199,7 +199,7 @@ static void check_wildcard_1_test(void)
     check_folder(L"destination\\folderA", FALSE);
     check_folder(L"destination\\folderB", FALSE);
     check_folder(L"destination\\folderC", FALSE);
-    todo_wine check_folder(L"destination", TRUE);
+    check_folder(L"destination", TRUE);
 }
 
 START_TEST(robocopy)
@@ -267,7 +267,7 @@ START_TEST(robocopy)
         winetest_push_context("basic copy test %d", i + 1);
         create_test_source_folder();
         run_cmd(basic_copy_tests[i], &exit_code);
-        todo_wine ok(exit_code == 1, "unexpected exit code %d from command %s\n", exit_code, debugstr_w(basic_copy_tests[i]));
+        ok(exit_code == 1, "unexpected exit code %d from command %s\n", exit_code, debugstr_w(basic_copy_tests[i]));
         check_basic_copy_1_test();
         winetest_pop_context();
     }
@@ -277,7 +277,7 @@ START_TEST(robocopy)
     create_test_folder(L"destination");
     create_test_file(L"destination\\fileA.a", 9000);
     run_cmd(L"robocopy ./source source/../destination /r:1 /w:0", &exit_code);
-    todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
     check_basic_copy_2_test();
     winetest_pop_context();
 
@@ -287,7 +287,7 @@ START_TEST(robocopy)
         create_test_source_folder();
         swprintf(temp_cmd, ARRAY_SIZE(temp_cmd), absolute_copy_tests[i], temp_path, temp_path);
         run_cmd(temp_cmd, &exit_code);
-        todo_wine ok(exit_code == 1, "unexpected exit code %d from command %s\n", exit_code, debugstr_w(absolute_copy_tests[i]));
+        ok(exit_code == 1, "unexpected exit code %d from command %s\n", exit_code, debugstr_w(absolute_copy_tests[i]));
         check_basic_copy_1_test();
         winetest_pop_context();
     }
@@ -297,7 +297,7 @@ START_TEST(robocopy)
         winetest_push_context("parser test %d", i + 1);
         create_test_source_folder();
         run_cmd(parser_tests[i], &exit_code);
-        todo_wine ok(exit_code == 1, "unexpected exit code %d from command %s\n", exit_code, debugstr_w(parser_tests[i]));
+        ok(exit_code == 1, "unexpected exit code %d from command %s\n", exit_code, debugstr_w(parser_tests[i]));
         check_basic_copy_1_test();
         winetest_pop_context();
     }
@@ -319,7 +319,7 @@ START_TEST(robocopy)
     winetest_push_context("wildcard test 1");
     create_test_source_folder();
     run_cmd(L"robocopy source destination *A.? /r:1 /w:0", &exit_code);
-    todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
     check_wildcard_1_test();
     winetest_pop_context();
 
-- 
2.32.0




More information about the wine-devel mailing list