static path_redirect_info ShouldRedirectImpl()

in fixups/FileRedirectionFixup/PathRedirection.cpp [1194:1442]


static path_redirect_info ShouldRedirectImpl(const CharT* path, redirect_flags flags, DWORD inst)
{
    path_redirect_info result;

    if (!path)
    {
        return result;
    }
    LogString(inst, L"\tFRF Should: for path", widen(path).c_str());
    

    bool c_presense = flag_set(flags, redirect_flags::check_file_presence);
    bool c_copy = flag_set(flags, redirect_flags::copy_file);
    bool c_ensure = flag_set(flags, redirect_flags::ensure_directory_structure);
    Log(L"[%d]\t\tFRF flags  CheckPresense:%d  CopyFile:%d  EnsureDirectory:%d", inst, c_presense, c_copy, c_ensure);

    // normalizedPath represents the requested path, redirected to the external system if relevant, or just as requested if not.
    // vfsPath represents this as a package relative path
    auto normalizedPath = NormalizePath(path);
    std::filesystem::path destinationTargetBase;

    if (normalizedPath.path_type == psf::dos_path_type::local_device)
    {
        LogString(L"\t\tFRF: Path is of type local device so FRF should ignore.", path);
        return result;
    }

    if (!normalizedPath.drive_absolute_path)
    {
        // FUTURE: We could do better about canonicalising paths, but the cost/benefit doesn't make it worth it right now
        return result;
    }

    LogString(inst, L"\t\tFRF Normalized", normalizedPath.drive_absolute_path);
    // To be consistent in where we redirect files, we need to map VFS paths to their non-package-relative equivalent
    normalizedPath = DeVirtualizePath(std::move(normalizedPath));

    LogString(inst, L"\t\tFRF DeVirtualized", normalizedPath.drive_absolute_path);

	// If you change the below logic, or
	// you you change what goes into RedirectedPath
	// you need to mirror all changes in FindFirstFileFixup.cpp
	
	// Basically, what goes into RedirectedPath here also needs to go into 
	// FindFirstFileFixup.cpp
    auto vfspath = NormalizePath(path);
    vfspath = VirtualizePath(std::move(vfspath),inst);
    if (vfspath.drive_absolute_path != NULL)
    {
        LogString(inst, L"\t\tFRF Virtualized", vfspath.drive_absolute_path);
    }

    // Figure out if this is something we need to redirect
    for (auto& redirectSpec : g_redirectionSpecs)
    {
        //LogString(inst, L"\t\tFRF Check against: base", redirectSpec.base_path.c_str());
        if (path_relative_to(vfspath.drive_absolute_path, redirectSpec.base_path))
        {
            LogString(inst, L"\t\tFRF In ball park of base", redirectSpec.base_path.c_str());
            auto relativePath = vfspath.drive_absolute_path + redirectSpec.base_path.native().length();
            if (psf::is_path_separator(relativePath[0]))
            {
                ++relativePath;
            }
            else if (relativePath[0])
            {
                // Otherwise, just a substring match (e.g. we're trying to match against 'foo' but input was 'foobar')
                continue;
            }
            // Otherwise exact match. Assume an implicit directory separator at the end (e.g. for matches to satisfy the
            // first call to CreateDirectory
            LogString(inst, L"\t\t\tFRF relativePath",relativePath);
            if (std::regex_match(relativePath, redirectSpec.pattern))
            {
                if (redirectSpec.isExclusion)
                {
                    // The impact on isExclusion is that redirection is not needed.
                    result.should_redirect = false;
                    LogString(inst, L"\t\tFRF CASE:Exclusion for path", path);
                }
                else
                {
                    result.should_redirect = true;
                    result.shouldReadonly = (redirectSpec.isReadOnly == true);			

                    // Check if file exists as VFS path in the package
                    if (impl::PathExists(vfspath.drive_absolute_path))
                    {
                        Log(L"[%d]\t\t\tFRF CASE:match, existing in package.", inst);
                        destinationTargetBase = redirectSpec.redirect_targetbase;
                        result.redirect_path = RedirectedPath(vfspath, flag_set(flags, redirect_flags::ensure_directory_structure), destinationTargetBase,inst);
                    }
                    else
                    {
                        Log(L"[%d]\t\t\tFRF CASE:match, not existing in package.",inst);
                        // If the folder above it exists, we might want to redirect anyway?
                        std::filesystem::path abs = vfspath.drive_absolute_path;
                        if (impl::PathExists(abs.parent_path().c_str()))
                        {
                            Log(L"[%d]\t\t\tFRF SUBCASE: parent folder is in package.",inst);
                            destinationTargetBase = redirectSpec.redirect_targetbase;
                            //result.redirect_path = RedirectedPath(normalizedPath, flag_set(flags, redirect_flags::ensure_directory_structure), destinationTargetBase);
                            result.redirect_path = RedirectedPath(vfspath, flag_set(flags, redirect_flags::ensure_directory_structure), destinationTargetBase,inst);
                        }
                        else
                        {
                            Log(L"[%d]\t\t\tFRF SUBCASE: parent folder is also not in package, but since relative should redirect.", inst);
                            //result.should_redirect = false;
                            destinationTargetBase = redirectSpec.redirect_targetbase;
                            result.redirect_path = RedirectedPath(vfspath, flag_set(flags, redirect_flags::ensure_directory_structure), destinationTargetBase,inst);
                        }
                    }
                    if (result.should_redirect)
                        LogString(inst, L"\t\tFRF CASE:match on redirect_path", result.redirect_path.c_str());
                }
                break;
            }
            else
            {
                LogString(inst, L"\t\tFRF no match on parse relativePath", relativePath);
            }
        }
        else
        {
            LogString(inst, L"\t\tFRF Not in ball park of base", redirectSpec.base_path.c_str());
        }
    }

    Log(L"[%d]\t\tFRF post check 1",inst);


    if (!result.should_redirect)
    {
        LogString(inst, L"\tFRF no redirect rule for path", path);
        return result;
    }

    Log(L"[%d]\t\tFRF post check 2",inst);

    if (flag_set(flags, redirect_flags::check_file_presence))
    {
        if (!impl::PathExists(result.redirect_path.c_str()) &&
            !impl::PathExists(vfspath.drive_absolute_path) &&
            !impl::PathExists(normalizedPath.drive_absolute_path))
        {
            result.should_redirect = false;
            result.redirect_path.clear();

            LogString(inst, L"\tFRF skipped (redirected not present check failed) for path", path);
            return result;
        }
    }

    Log(L"[%d]\t\tFRF post check 3",inst);

    if (flag_set(flags, redirect_flags::copy_file))
    {
        Log(L"[%d]\t\tFRF copy_file flag is set",inst);
        [[maybe_unused]] BOOL copyResult = false;
        if (impl::PathExists(result.redirect_path.c_str()))
        {
            Log(L"[%d]\t\tFRF Found that a copy exists in the redirected area so we skip the folder creation.",inst);
        }
        else
        {
            std::filesystem::path CopySource = normalizedPath.drive_absolute_path;
            if (impl::PathExists(vfspath.drive_absolute_path))
            {
                CopySource = vfspath.drive_absolute_path;
            }


            auto attr = impl::GetFileAttributes(CopySource.c_str()); //normalizedPath.drive_absolute_path);
            Log(L"[%d]\t\tFRF source %ls attributes=0x%x", inst, CopySource.c_str(), attr);
            if (attr != INVALID_FILE_ATTRIBUTES)
            {
                if ((attr & FILE_ATTRIBUTE_DIRECTORY) != FILE_ATTRIBUTE_DIRECTORY)
                {
                    Log(L"[%d]FRF we have a file to be copied to %ls", inst, result.redirect_path.c_str());
                    copyResult = impl::CopyFileEx(
                        CopySource.c_str(), //normalizedPath.drive_absolute_path,
                        result.redirect_path.c_str(),
                        nullptr,
                        nullptr,
                        nullptr,
                        COPY_FILE_FAIL_IF_EXISTS | COPY_FILE_NO_BUFFERING);
                    if (copyResult)
                    {
                        LogString(inst, L"\t\tFRF CopyFile Success From", CopySource.c_str());
                        LogString(inst, L"\t\tFRF CopyFile Success To", result.redirect_path.c_str());
                    }
                    else
                    {
                        auto err = ::GetLastError();
                        Log("[%d]\t\tFRF CopyFile Fail=0x%x", inst, err);
                        LogString(inst, L"\t\tFRF CopyFile Fail From", CopySource.c_str());
                        LogString(inst, L"\t\tFRF CopyFile Fail To", result.redirect_path.c_str());
                        switch (err)
                        {
                        case ERROR_FILE_EXISTS:
                            Log(L"[%d]\t\tFRF  was ERROR_FILE_EXISTS", inst);
                            break;
                        case ERROR_PATH_NOT_FOUND:
                            Log(L"[%d]\t\tFRF  was ERROR_PATH_NOT_FOUND", inst);
                            break;
                        case ERROR_FILE_NOT_FOUND:
                            Log(L"[%d]\t\tFRF  was ERROR_FILE_NOT_FOUND", inst);
                            break;
                        case ERROR_ALREADY_EXISTS:
                            Log(L"[%d]\t\tFRF  was ERROR_ALREADY_EXISTS", inst);
                            break;
                        default:
                            Log(L"[%d]\t\tFRF was 0x%x", inst, err);
                            break;
                        }
                    }
                }
                else
                {
                    Log(L"[%d]FRF we have a directory to be copied to %ls.", inst, result.redirect_path.c_str());
                    copyResult = impl::CreateDirectoryEx(CopySource.c_str(), result.redirect_path.c_str(), nullptr);
                    if (copyResult)
                    {
                        LogString(inst, L"\t\tFRF CreateDir Success From", CopySource.c_str());
                        LogString(inst, L"\t\tFRF CreateDir Success To", result.redirect_path.c_str());
                    }
                    else
                    {
                        Log("[%d]\t\tFRF CreateDir Fail=0x%x", inst, ::GetLastError());
                        LogString(inst, L"\t\tFRF CreateDir Fail From", CopySource.c_str());
                        LogString(inst, L"\t\tFRF CreateDir Fail To", result.redirect_path.c_str());
                    }
#if _DEBUG
                    auto err = ::GetLastError();
                    assert(copyResult || (err == ERROR_FILE_EXISTS) || (err == ERROR_PATH_NOT_FOUND) || (err == ERROR_FILE_NOT_FOUND) || (err == ERROR_ALREADY_EXISTS));
#endif
                }
            }
            else
            {
                //There is no source to copy, so we just want to allow it to be created in the redirected area.
                Log(L"[%d]FRF there is no package file to be copied to %ls.", inst, result.redirect_path.c_str());
            }
        }
    }
    LogString(inst, L"\tFRF Should: Redirect to result", result.redirect_path.c_str());

    return result;
}