[PATCH 05/41] robocopy: add basic copy logic

Zebediah Figura zfigura at codeweavers.com
Tue Sep 7 12:06:24 CDT 2021


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 at 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
> 



More information about the wine-devel mailing list