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

Florian Eder others.meder at gmail.com
Wed Sep 15 16:56:59 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>
---
Combined patches 5, 6 and 11 from V1 to one patch, replaced open coded function
with PathAllocCombine, removed some obvious comments and removed pointless check whether
some directory is a file or directory in get_file_paths_in_folder as any error thrown by
FindFirstFile is caught anyway

Still does not have any kind of max depth, so the complete source directory tree will be copied

Import of shlwapi is required for PathIsDirectoryW, which is AFAIAA not
exported by kernelbase :-/
---
 programs/robocopy/Makefile.in |   2 +-
 programs/robocopy/main.c      | 166 +++++++++++++++++++++++++++++++++-
 programs/robocopy/robocopy.h  |  17 ++++
 programs/robocopy/robocopy.rc |   6 ++
 4 files changed, 188 insertions(+), 3 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 fc16fa1c2a2..16d1ba16445 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 *strip_path_prefix(WCHAR* path)
 {
     /* returns a path without the \\?\ prefix */
@@ -145,6 +162,134 @@ static void parse_arguments(int argc, WCHAR *argv[])
     }
 }
 
+static BOOL matches_array_entry(WCHAR *name, struct path_array *names_to_match)
+{
+    int i;
+    for (i = 0; i < names_to_match->size; i++)
+    {
+        if (PathMatchSpecW(name, names_to_match->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(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)
+            {
+                output_error(STRING_ERROR_WRITE_DIRECTORY, GetLastError(), strip_path_prefix(current_folder));
+                return FALSE;
+            }
+        }
+        else
+            output_message(format_string(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 */
+        PathAllocCombine(directory_path, current_path->name, PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH, &parent_absolute_path);
+
+        /* append * to recieve every file / subdirectory in this directory */
+        PathAllocCombine(parent_absolute_path, L"*", PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH, &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_ENSURE_IS_EXTENDED_LENGTH_PATH, &current_relative_path);
+                PathAllocCombine(directory_path, current_relative_path, PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH, &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) && 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))
+    {
+        output_error(STRING_ERROR_READ_DIRECTORY, ERROR_FILE_NOT_FOUND, strip_path_prefix(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_ENSURE_IS_EXTENDED_LENGTH_PATH, &current_absolute_path);
+
+        /* append the relative source path to the destination to get the target path */
+        PathAllocCombine(options.destination, current_path->name, PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH, &target_path);
+
+        if (PathIsDirectoryW(current_absolute_path))
+        {
+            if (!create_directory_path(target_path))
+                output_error(STRING_ERROR_WRITE_DIRECTORY, GetLastError(), strip_path_prefix(target_path));
+        }
+        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
+            {
+                output_message(format_string(STRING_CREATE_FILE), strip_path_prefix(target_path));
+            }
+        }
+    }
+    return TRUE;
+}
+
 static void print_header(void)
 {
     UINT i;
@@ -166,8 +311,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();
 
-    WINE_FIXME("robocopy stub");
-    return 0;
+    /* 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 3a2ddc9474f..fd74722ae63 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: %1\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
-- 
2.32.0




More information about the wine-devel mailing list