[PATCH v7 3/4] robocopy: Add argument parser and basic copy logic

Florian Eder others.meder at gmail.com
Sat Oct 16 06:13:42 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>
---
v7: Merged patches 3 and 5, removed all messaging / error outputs and
made copy function non-recursive for now, until the /S switch is implemented
---
 programs/robocopy/Makefile.in      |   1 +
 programs/robocopy/main.c           | 196 ++++++++++++++++++++++++++++-
 programs/robocopy/tests/robocopy.c |  38 +++---
 3 files changed, 214 insertions(+), 21 deletions(-)

diff --git a/programs/robocopy/Makefile.in b/programs/robocopy/Makefile.in
index f5d9dff6e4d..d1f5db665df 100644
--- a/programs/robocopy/Makefile.in
+++ b/programs/robocopy/Makefile.in
@@ -1,6 +1,7 @@
 MODULE    = robocopy.exe
 
 EXTRADLLFLAGS = -mconsole -municode
+IMPORTS   = kernelbase shlwapi
 
 C_SRCS = \
 	main.c
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c
index 320de52f2d5..6e19170712d 100644
--- a/programs/robocopy/main.c
+++ b/programs/robocopy/main.c
@@ -21,9 +21,201 @@ WINE_DEFAULT_DEBUG_CHANNEL(robocopy);
 
 #define WIN32_LEAN_AND_MEAN
 #include <windows.h>
+#include <stdlib.h>
+#include <pathcch.h>
+#include <shlwapi.h>
+#include <wine/list.h>
+
+struct path_array
+{
+    UINT size;
+    WCHAR *array[1];
+};
+
+struct path
+{
+    struct list entry;
+    WCHAR *name;
+};
+
+struct robocopy_options
+{
+    WCHAR *destination;
+    WCHAR *source;
+    struct path_array *files;
+};
+
+#define ROBOCOPY_SUCCESS_FILES_COPIED        1
+#define ROBOCOPY_ERROR_NO_FILES_COPIED        16
+
+struct robocopy_options options;
+
+static WCHAR *get_absolute_path(const WCHAR *path)
+{
+    DWORD size;
+    WCHAR *absolute_path;
+
+    size = GetFullPathNameW(path, 0, NULL, NULL) + 2;
+    absolute_path = calloc(size, sizeof(WCHAR));
+    GetFullPathNameW(path, size, absolute_path, NULL);
+    PathCchAddBackslashEx(absolute_path, size, NULL, NULL);
+    return absolute_path;
+}
+
+static BOOL path_in_array(WCHAR *name, struct path_array *names)
+{
+    int i;
+    for (i = 0; i < names->size; i++)
+    {
+        if (PathMatchSpecW(name, names->array[i])) return TRUE;
+    }
+    return FALSE;
+}
+
+static BOOL create_directory_path(WCHAR *path)
+{
+    WCHAR *pointer, *current_folder;
+    current_folder = calloc(wcslen(path) + 1, sizeof(WCHAR));
+    pointer = wcschr(path, L'\\');
+    while (pointer != NULL)
+    {
+        if (!lstrcpynW(current_folder, path, pointer - path + 2)) return FALSE;
+        if (!CreateDirectoryW(current_folder, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) return FALSE;
+        pointer = wcschr(pointer + 1, L'\\');
+    }
+    return TRUE;
+}
+
+static void append_matching_directory_content(struct list *paths, WCHAR *search_path, WCHAR *root, struct path_array *file_names)
+{
+    WIN32_FIND_DATAW entry_data;
+    HANDLE handle;
+    WCHAR *current_absolute_path;
+    struct path *new_path;
+
+    handle = FindFirstFileExW(search_path, FindExInfoStandard, &entry_data, FindExSearchNameMatch, NULL, 0);
+    if (handle != INVALID_HANDLE_VALUE)
+    {
+        do
+        {
+            if (!wcscmp(L".", entry_data.cFileName) || !wcscmp(L"..", entry_data.cFileName)) continue;
+
+            PathAllocCombine(root, entry_data.cFileName,
+                             PATHCCH_ALLOW_LONG_PATHS | PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS,
+                             &current_absolute_path);
+
+            if ((!PathIsDirectoryW(current_absolute_path) && path_in_array(entry_data.cFileName, file_names)) ||
+                (PathIsDirectoryW(current_absolute_path)))
+            {
+                new_path = calloc(1, sizeof(struct path));
+                new_path->name = wcsdup(entry_data.cFileName);
+                list_add_tail(paths, &new_path->entry);
+            }
+        }
+        while (FindNextFileW(handle, &entry_data));
+    }
+}
+
+static void get_file_paths_in_folder(WCHAR *root, struct list *paths)
+{
+    struct path *new_path;
+    WCHAR *current_search_path;
+
+    list_init(paths);
+    new_path = calloc(1, sizeof(struct path));
+    new_path->name = calloc(2, sizeof(WCHAR));
+    list_add_tail(paths, &new_path->entry);
+
+    PathAllocCombine(root, L"*",
+                     PATHCCH_ALLOW_LONG_PATHS | PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS,
+                     &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)
+    {
+        PathAllocCombine(options.source, current_path->name,
+                         PATHCCH_ALLOW_LONG_PATHS | PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS,
+                         &current_absolute_path);
+
+        PathAllocCombine(options.destination, current_path->name,
+                         PATHCCH_ALLOW_LONG_PATHS | PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS,
+                         &target_path);
+
+        if (!PathIsDirectoryW(current_absolute_path))
+        {
+            create_directory_path(target_path);
+            CopyFileW(current_absolute_path, target_path, FALSE);
+        }
+    }
+    return TRUE;
+}
+
+static void parse_arguments(int argc, WCHAR *argv[])
+{
+    int i;
+
+    memset(&options, 0, sizeof(options));
+    options.files = calloc(1, offsetof(struct path_array, array[argc]));
+
+    for (i = 1; i < argc; i++)
+    {
+        /*
+         * Robocopy switches contain one (and only one) backslash at the start
+         * /xf => valid flag
+         * /r:1 => valid flag
+         * /r:1aö => valid flag
+         * /r:1aö/ => not a valid flag, is interpreted as a filename
+         */
+        if ((argv[i][0] == '/') && !wcschr(argv[i] + 1, '/'))
+            WINE_FIXME("encountered an unknown robocopy flag: %s\n", debugstr_w(argv[i]));
+        else
+        {
+            if (!options.source)
+                options.source = get_absolute_path(argv[i]);
+            else if (!options.destination)
+                options.destination = get_absolute_path(argv[i]);
+            else
+            {
+                options.files->array[options.files->size] = wcsdup(argv[i]);
+                options.files->size++;
+            }
+        }
+    }
+}
 
 int __cdecl wmain(int argc, WCHAR *argv[])
 {
-    WINE_FIXME("robocopy stub\n");
-    return 16;
+    parse_arguments(argc, argv);
+
+    /* If no file filters are set, set *.* to include all files */
+    if (options.files->size == 0)
+    {
+        options.files->array[options.files->size] = wcsdup(L"*.*");
+        options.files->size++;
+    }
+
+    if (!options.destination || !options.source)
+        return ROBOCOPY_ERROR_NO_FILES_COPIED;
+
+    if (!perform_copy())
+        return ROBOCOPY_ERROR_NO_FILES_COPIED;
+
+    return ROBOCOPY_SUCCESS_FILES_COPIED;
 }
diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c
index 76279f476f9..cfa7fa96f5b 100644
--- a/programs/robocopy/tests/robocopy.c
+++ b/programs/robocopy/tests/robocopy.c
@@ -135,8 +135,8 @@ static void check_basic_copy_1_test(void)
     check_folder(L"source\\folderC", TRUE);
     check_folder(L"source", TRUE);
 
-    todo_wine check_file(L"destination\\fileA.a", TRUE);
-    todo_wine check_file(L"destination\\fileB.b", TRUE);
+    check_file(L"destination\\fileA.a", TRUE);
+    check_file(L"destination\\fileB.b", TRUE);
     check_file(L"destination\\folderA\\fileC.c", FALSE);
     check_file(L"destination\\folderA\\fileD.d", FALSE);
     check_file(L"destination\\folderA\\folderD\\fileE.e", FALSE);
@@ -147,7 +147,7 @@ static void check_basic_copy_1_test(void)
     check_folder(L"destination\\folderA", FALSE);
     check_folder(L"destination\\folderB", FALSE);
     check_folder(L"destination\\folderC", FALSE);
-    todo_wine check_folder(L"destination", TRUE);
+    check_folder(L"destination", TRUE);
 }
 
 static void check_basic_copy_2_test(void)
@@ -167,7 +167,7 @@ static void check_basic_copy_2_test(void)
     check_folder(L"source", TRUE);
 
     check_file(L"destination\\fileA.a", TRUE);
-    todo_wine check_file(L"destination\\fileB.b", TRUE);
+    check_file(L"destination\\fileB.b", TRUE);
     check_file(L"destination\\folderA\\fileC.c", FALSE);
     check_file(L"destination\\folderA\\fileD.d", FALSE);
     check_file(L"destination\\folderA\\folderD\\fileE.e", FALSE);
@@ -228,7 +228,7 @@ static void check_wildcard_1_test(void)
     check_folder(L"source\\folderC", TRUE);
     check_folder(L"source", TRUE);
 
-    todo_wine check_file(L"destination\\fileA.a", TRUE);
+    check_file(L"destination\\fileA.a", TRUE);
     check_file(L"destination\\fileB.b", FALSE);
     check_file(L"destination\\folderA\\fileC.c", FALSE);
     check_file(L"destination\\folderA\\fileD.d", FALSE);
@@ -240,7 +240,7 @@ static void check_wildcard_1_test(void)
     check_folder(L"destination\\folderA", FALSE);
     check_folder(L"destination\\folderB", FALSE);
     check_folder(L"destination\\folderC", FALSE);
-    todo_wine check_folder(L"destination", TRUE);
+    check_folder(L"destination", TRUE);
 }
 
 START_TEST(robocopy)
@@ -276,14 +276,14 @@ START_TEST(robocopy)
     winetest_push_context("basic copy test 1");
     create_test_source_folder();
     run_cmd(L"robocopy source destination /r:1 /w:0", &exit_code);
-    todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
     check_basic_copy_1_test();
     winetest_pop_context();
 
     winetest_push_context("basic copy test 2");
     create_test_source_folder();
     run_cmd(L"robocopy ./source third_folder/../destination /r:1 /w:0", &exit_code);
-    todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
     check_basic_copy_1_test();
     winetest_pop_context();
 
@@ -292,7 +292,7 @@ START_TEST(robocopy)
     create_test_folder(L"destination");
     create_test_file(L"destination\\fileA.a", 9000);
     run_cmd(L"robocopy ./source source/../destination /r:1 /w:0", &exit_code);
-    todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
     check_basic_copy_2_test();
     winetest_pop_context();
 
@@ -302,7 +302,7 @@ START_TEST(robocopy)
                 L"robocopy %s\\source %s\\destination /r:1 /w:0",
                 temp_path, temp_path);
     run_cmd(temp_cmd, &exit_code);
-    todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
     check_basic_copy_1_test();
     winetest_pop_context();
 
@@ -312,7 +312,7 @@ START_TEST(robocopy)
                 L"robocopy %s\\source destination /r:1 /w:0",
                 temp_path);
     run_cmd(temp_cmd, &exit_code);
-    todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
     check_basic_copy_1_test();
     winetest_pop_context();
 
@@ -322,7 +322,7 @@ START_TEST(robocopy)
                 L"robocopy source %s\\destination /r:1 /w:0",
                 temp_path);
     run_cmd(temp_cmd, &exit_code);
-    todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
     check_basic_copy_1_test();
     winetest_pop_context();
 
@@ -332,7 +332,7 @@ START_TEST(robocopy)
                 L"robocopy %s\\third_folder\\..\\source %s\\third_folder\\..\\destination /r:1 /w:0",
                 temp_path, temp_path);
     run_cmd(temp_cmd, &exit_code);
-    todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
     check_basic_copy_1_test();
     winetest_pop_context();
 
@@ -353,28 +353,28 @@ START_TEST(robocopy)
     winetest_push_context("wildcard test 1");
     create_test_source_folder();
     run_cmd(L"robocopy source destination file?.? /r:1 /w:0", &exit_code);
-    todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
     check_basic_copy_1_test();
     winetest_pop_context();
 
     winetest_push_context("wildcard test 2");
     create_test_source_folder();
     run_cmd(L"robocopy source destination file* /r:1 /w:0", &exit_code);
-    todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
     check_basic_copy_1_test();
     winetest_pop_context();
 
     winetest_push_context("wildcard test 3");
     create_test_source_folder();
     run_cmd(L"robocopy source destination *le?.? /r:1 /w:0", &exit_code);
-    todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
     check_basic_copy_1_test();
     winetest_pop_context();
 
     winetest_push_context("wildcard test 4");
     create_test_source_folder();
     run_cmd(L"robocopy source destination *A.? /r:1 /w:0", &exit_code);
-    todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
     check_wildcard_1_test();
     winetest_pop_context();
 
@@ -395,14 +395,14 @@ START_TEST(robocopy)
     winetest_push_context("flag parser test 1");
     create_test_source_folder();
     run_cmd(L"robocopy /r:1 source destination /r:1 /w:0", &exit_code);
-    todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
     check_basic_copy_1_test();
     winetest_pop_context();
 
     winetest_push_context("flag parser test 2");
     create_test_source_folder();
     run_cmd(L"robocopy source /r:1 destination /r:1 /w:0", &exit_code);
-    todo_wine ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 1, "unexpected exit code %d\n", exit_code);
     check_basic_copy_1_test();
     winetest_pop_context();
 
-- 
2.32.0




More information about the wine-devel mailing list