[PATCH v6 5/6] robocopy: Add basic copy logic and error handling

Florian Eder others.meder at gmail.com
Tue Sep 21 09:54:49 CDT 2021


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>
---
v6: Remove wine_todo from the additional syntax tests, as these tests pass now
---
 programs/robocopy/Makefile.in      |   2 +-
 programs/robocopy/main.c           | 177 ++++++++++++++++++++++++++++-
 programs/robocopy/robocopy.h       |  17 +++
 programs/robocopy/robocopy.rc      |   6 +
 programs/robocopy/tests/robocopy.c |  16 +--
 5 files changed, 207 insertions(+), 11 deletions(-)

diff --git a/programs/robocopy/Makefile.in b/programs/robocopy/Makefile.in
index 0f4f5c76119..1c640599b4b 100644
--- a/programs/robocopy/Makefile.in
+++ b/programs/robocopy/Makefile.in
@@ -1,5 +1,5 @@
 MODULE    = robocopy.exe
-IMPORTS   = kernelbase
+IMPORTS   = kernelbase shlwapi
 
 EXTRADLLFLAGS = -mconsole -municode -mno-cygwin
 
diff --git a/programs/robocopy/main.c b/programs/robocopy/main.c
index 6781fc17504..05546715718 100644
--- a/programs/robocopy/main.c
+++ b/programs/robocopy/main.c
@@ -23,6 +23,8 @@ WINE_DEFAULT_DEBUG_CHANNEL(robocopy);
 #include <windows.h>
 #include <stdlib.h>
 #include <pathcch.h>
+#include <shlwapi.h>
+#include <wine/list.h>
 #include "robocopy.h"
 
 struct robocopy_options options;
@@ -71,6 +73,21 @@ static void output_message(const WCHAR *format_string, ...)
     LocalFree(string);
 }
 
+static void output_error(UINT format_string_id, HRESULT error_code, WCHAR* path)
+{
+    WCHAR *error_string, error_code_long[64], error_code_short[64];
+
+    FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
+                            NULL, error_code, 0, (LPWSTR)&error_string, 0, NULL);
+
+    swprintf(error_code_long, ARRAY_SIZE(error_code_long), L"0x%08x", error_code);
+    swprintf(error_code_short, ARRAY_SIZE(error_code_short), L"%u", error_code);
+
+    output_message(format_string(format_string_id), L"", error_code_short, error_code_long, path, error_string);
+
+    LocalFree(error_string);
+}
+
 static WCHAR *get_absolute_path(const WCHAR *path)
 {
     DWORD size;
@@ -125,6 +142,146 @@ static void parse_arguments(int argc, WCHAR *argv[])
     }
 }
 
+static BOOL path_in_array(WCHAR *name, struct path_array *names)
+{
+    int i;
+    for (i = 0; i < names->size; i++)
+    {
+        if (PathMatchSpecW(name, names->array[i])) return TRUE;
+    }
+    return FALSE;
+}
+
+static BOOL create_directory_path(WCHAR *path)
+{
+    WCHAR *pointer, *current_folder;
+    current_folder = calloc(wcslen(path) + 1, sizeof(WCHAR));
+    /* ignore the "\\?\" prefix, so that those backslashes are not matched */
+    pointer = wcschr(path, L'\\');
+    while (pointer != NULL)
+    {
+        if (!lstrcpynW(current_folder, path, pointer - path + 2)) return FALSE;
+        /* try to create the folder, ignoring any failure due to ERROR_ALREADY_EXISTS */
+        if (!CreateDirectoryW(current_folder, NULL))
+        {
+            if (GetLastError() != ERROR_ALREADY_EXISTS)
+            {
+                output_error(STRING_ERROR_WRITE_DIRECTORY, GetLastError(), current_folder);
+                return FALSE;
+            }
+        }
+        else
+            output_message(format_string(STRING_CREATE_DIRECTORY), current_folder);
+        pointer = wcschr(pointer + 1, L'\\');
+    }
+    return TRUE;
+}
+
+static void get_file_paths_in_folder(WCHAR *directory_path, struct list *paths)
+{
+    HANDLE temp_handle;
+    struct path *new_path, *current_path;
+    WIN32_FIND_DATAW entry_data;
+    WCHAR *parent_absolute_path, *current_relative_path, *current_absolute_path, *current_search_path;
+
+    list_init(paths);
+
+    /* initialize list with a empty relative path */
+    new_path = calloc(1, sizeof(struct path));
+    new_path->name = calloc(2, sizeof(WCHAR));
+    list_add_tail(paths, &new_path->entry);
+
+    LIST_FOR_EACH_ENTRY(current_path, paths, struct path, entry)
+    {
+        /* append relative path to the (prefix) directory path */
+        PathAllocCombine(directory_path, current_path->name,
+                         PATHCCH_ALLOW_LONG_PATHS | PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS,
+                         &parent_absolute_path);
+
+        /* append * to recieve every file / subdirectory in this directory */
+        PathAllocCombine(parent_absolute_path, L"*",
+                         PATHCCH_ALLOW_LONG_PATHS | PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS,
+                         &current_search_path);
+        /* walk through all files / directories in this directory */
+        temp_handle = FindFirstFileExW(current_search_path, FindExInfoStandard, &entry_data, FindExSearchNameMatch, NULL, 0);
+        if (temp_handle != INVALID_HANDLE_VALUE)
+        {
+            do
+            {
+                /* Ignore . and .. entries */
+                if (!wcscmp(L".", entry_data.cFileName) || !wcscmp(L"..", entry_data.cFileName)) continue;
+
+                PathAllocCombine(current_path->name, entry_data.cFileName,
+                                 PATHCCH_ALLOW_LONG_PATHS | PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS,
+                                 &current_relative_path);
+                PathAllocCombine(directory_path, current_relative_path,
+                                 PATHCCH_ALLOW_LONG_PATHS | PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS,
+                                 &current_absolute_path);
+
+                /* If this entry is a matching file or empty directory, add it to the list of results */
+                if ((!PathIsDirectoryW(current_absolute_path) && path_in_array(entry_data.cFileName, options.files)) ||
+                     (PathIsDirectoryW(current_absolute_path)))
+                {
+                    new_path = calloc(1, sizeof(struct path));
+                    new_path->name = wcsdup(current_relative_path);
+                    list_add_tail(paths, &new_path->entry);
+                }
+            }
+            while (FindNextFileW(temp_handle, &entry_data) != 0);
+        }
+    }
+}
+
+static BOOL perform_copy(void)
+{
+    struct list paths_source;
+    struct path *current_path;
+    WCHAR *current_absolute_path, *target_path;
+
+    list_init(&paths_source);
+
+    if (!PathIsDirectoryW(options.source))
+    {
+        output_error(STRING_ERROR_READ_DIRECTORY, ERROR_FILE_NOT_FOUND, 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);
+
+        /* append the relative source path to the destination to get the target path */
+        PathAllocCombine(options.destination, current_path->name,
+                         PATHCCH_ALLOW_LONG_PATHS | PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS,
+                         &target_path);
+
+        if (PathIsDirectoryW(current_absolute_path))
+        {
+            if (!create_directory_path(target_path))
+                output_error(STRING_ERROR_WRITE_DIRECTORY, GetLastError(), target_path);
+        }
+        else
+        {
+            create_directory_path(target_path);
+            if (!CopyFileW(current_absolute_path, target_path, FALSE))
+                output_error(STRING_ERROR_WRITE_FILE, GetLastError(), target_path);
+            else
+            {
+                output_message(format_string(STRING_CREATE_FILE), target_path);
+            }
+        }
+    }
+    return TRUE;
+}
+
 static void print_header(void)
 {
     UINT i;
@@ -146,8 +303,24 @@ int __cdecl wmain(int argc, WCHAR *argv[])
 {
     parse_arguments(argc, argv);
 
+    /* If no file filters are set, set *.* to include all files */
+    if (options.files->size == 0)
+    {
+        options.files->array[options.files->size] = wcsdup(L"*.*");
+        options.files->size++;
+    }
+
     print_header();
 
-    WINE_FIXME("robocopy stub");
-    return 1;
+    /* Break if Source or Destination not set */
+    if (!options.destination || !options.source)
+    {
+        output_message(format_string(STRING_MISSING_DESTINATION_OR_SOURCE));
+        return ROBOCOPY_ERROR_NO_FILES_COPIED;
+    }
+
+    if (!perform_copy())
+        return ROBOCOPY_ERROR_NO_FILES_COPIED;
+
+    return ROBOCOPY_NO_ERROR_FILES_COPIED;
 }
diff --git a/programs/robocopy/robocopy.h b/programs/robocopy/robocopy.h
index f5c2ac56fcf..86be775da01 100644
--- a/programs/robocopy/robocopy.h
+++ b/programs/robocopy/robocopy.h
@@ -18,6 +18,13 @@
 
 #define WIN32_LEAN_AND_MEAN
 #include <windows.h>
+#include <wine/list.h>
+
+struct path
+{
+    struct list entry;
+    WCHAR *name;
+};
 
 struct path_array
 {
@@ -32,8 +39,18 @@ struct robocopy_options
     struct path_array *files;
 };
 
+/* Exit codes */
+#define ROBOCOPY_NO_ERROR_FILES_COPIED        1
+#define ROBOCOPY_ERROR_NO_FILES_COPIED        16
+
 /* Resource strings */
 #define STRING_HEADER                         1000
 #define STRING_SOURCE                         1003
 #define STRING_DESTINATION                    1004
 #define STRING_FILES                          1005
+#define STRING_MISSING_DESTINATION_OR_SOURCE  1010
+#define STRING_ERROR_READ_DIRECTORY           1011
+#define STRING_ERROR_WRITE_DIRECTORY          1012
+#define STRING_ERROR_WRITE_FILE               1014
+#define STRING_CREATE_DIRECTORY               1019
+#define STRING_CREATE_FILE                    1022
diff --git a/programs/robocopy/robocopy.rc b/programs/robocopy/robocopy.rc
index 044bab77774..1ce605f432d 100644
--- a/programs/robocopy/robocopy.rc
+++ b/programs/robocopy/robocopy.rc
@@ -29,6 +29,12 @@ STRINGTABLE
     STRING_SOURCE, "         Source: %1\n"
     STRING_DESTINATION, "    Destination: %1\n\n"
     STRING_FILES, "          Files:\n"
+    STRING_MISSING_DESTINATION_OR_SOURCE, "No destination or source specified, can't copy anything\n"
+    STRING_ERROR_READ_DIRECTORY, "[%1] Error %2 (%3) occurred reading directory \"%4\":\n%5\n"
+    STRING_ERROR_WRITE_DIRECTORY, "[%1] Error %2 (%3) occurred writing directory \"%4\":\n%5\n"
+    STRING_ERROR_WRITE_FILE, "[%1] Error %2 (%3) occurred writing file \"%4\":\n%5\n"
+    STRING_CREATE_DIRECTORY, " Created Dir: %1\n"
+    STRING_CREATE_FILE, " Copied File: %1\n"
 }
 
 LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
diff --git a/programs/robocopy/tests/robocopy.c b/programs/robocopy/tests/robocopy.c
index f99a027baec..d5d3dee473a 100644
--- a/programs/robocopy/tests/robocopy.c
+++ b/programs/robocopy/tests/robocopy.c
@@ -63,41 +63,41 @@ START_TEST(robocopy)
 
     winetest_push_context("syntax test 1");
     execute_robocopy(L"", &exit_code);
-    todo_wine ok(exit_code == 16, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 16, "unexpected exit code %d\n", exit_code);
     winetest_pop_context();
 
     winetest_push_context("syntax test 2");
     execute_robocopy(L"invalid_folder", &exit_code);
-    todo_wine ok(exit_code == 16, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 16, "unexpected exit code %d\n", exit_code);
     winetest_pop_context();
 
     winetest_push_context("syntax test 3");
     execute_robocopy(L"-flag invalid_folder", &exit_code);
-    todo_wine ok(exit_code == 16, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 16, "unexpected exit code %d\n", exit_code);
     winetest_pop_context();
 
     winetest_push_context("syntax test 4");
     execute_robocopy(L"invalid_folder robocopy_destination", &exit_code);
-    todo_wine ok(exit_code == 16, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 16, "unexpected exit code %d\n", exit_code);
     winetest_pop_context();
 
     winetest_push_context("syntax test 5");
     execute_robocopy(L"-?", &exit_code);
-    todo_wine ok(exit_code == 16, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 16, "unexpected exit code %d\n", exit_code);
     winetest_pop_context();
 
     winetest_push_context("syntax test 6");
     execute_robocopy(L"invalid_folder -?", &exit_code);
-    todo_wine ok(exit_code == 16, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 16, "unexpected exit code %d\n", exit_code);
     winetest_pop_context();
 
     winetest_push_context("syntax test 7");
     execute_robocopy(L"/?", &exit_code);
-    todo_wine ok(exit_code == 16, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 16, "unexpected exit code %d\n", exit_code);
     winetest_pop_context();
 
     winetest_push_context("syntax test 8");
     execute_robocopy(L"invalid_folder /?", &exit_code);
-    todo_wine ok(exit_code == 16, "unexpected exit code %d\n", exit_code);
+    ok(exit_code == 16, "unexpected exit code %d\n", exit_code);
     winetest_pop_context();
 }
-- 
2.32.0




More information about the wine-devel mailing list