HANDLE __stdcall FindFirstFileExFixup()

in fixups/FileRedirectionFixup/FindFirstFileFixup.cpp [97:324]


HANDLE __stdcall FindFirstFileExFixup(
    _In_ const CharT* fileName,
    _In_ FINDEX_INFO_LEVELS infoLevelId,
    _Out_writes_bytes_(sizeof(win32_find_data_t<CharT>)) LPVOID findFileData,
    _In_ FINDEX_SEARCH_OPS searchOp,
    _Reserved_ LPVOID searchFilter,
    _In_ DWORD additionalFlags) noexcept try
{
    auto guard = g_reentrancyGuard.enter();
    if (!guard)
    {
        LogString(L"\tFindFirstFileExFixup: for fileName", fileName);

        return impl::FindFirstFileEx(fileName, infoLevelId, findFileData, searchOp, searchFilter, additionalFlags);
    }
    DWORD FindFirstFileExInstance = ++g_FileIntceptInstance;


    // Split the input into directory and pattern
    auto path = widen(fileName, CP_ACP);
    LogString(FindFirstFileExInstance,L"\tFindFirstFileEx: for fileName", path.c_str());
    Log(L"[%d]\tFindFirstFileEx: addtionalFlags=0x%x", FindFirstFileExInstance, additionalFlags);

    normalized_path dir;
    const wchar_t* pattern = nullptr;
    if (auto dirPos = path.find_last_of(LR"(\/)"); dirPos != std::wstring::npos)
    {
        Log("[%d]\tFileFirstFileEx: has slash", FindFirstFileExInstance);
        // Special case for single separator at beginning of the path "/foo.txt"
        if (dirPos == 0)
        {
            auto nextChar = std::exchange(path[dirPos + 1], L'\0');
            dir = NormalizePath(path.c_str());
            path[dirPos + 1] = nextChar;
        }
        else
        {
            auto separator = std::exchange(path[dirPos], L'\0');
            dir = NormalizePath(path.c_str());
            path[dirPos] = separator;
        }
        pattern = path.c_str() + dirPos + 1;
    }
    else
    {
        Log("[%d]\tFileFirstFileEx: no slash, assume cwd based.", FindFirstFileExInstance);
        // TODO: This is a messy situation and the code I am replacing doen't handle it well and can crash the app later on in PathRedirection.
        // While FindFirstFileEx is usually passed a regular (unique) filepath in the first parameter, 
        // it is permissible for the caller to use wildcards to select multiple subfolders or files to be searched.
        // And if they don't give a full path (we end up here and) we should assume it means the wildcards are relative to the current working directory.
        // For example, the app I'm looking at puts "*" in this field. This currently causes NormalizePath to return a normailzed path that is type relative but with a null entry for the path.
        // The right solution might be to first find subfolders matching the path pattern and make individual layered calls on the rest, but that seems too ugly.
        // Here is the code being replaced, but while it fixes the "*" I'm not comforatble that the replacement is right in all cases, 
        // so this comment is here to try to explain.
        //     dir = NormalizePath(L".");
        //     pattern = path.c_str();
        std::filesystem::path cwd = std::filesystem::current_path();
        Log("[%d]\tFileFirstFileEx: swap to cwd: %ls", FindFirstFileExInstance,cwd.c_str());
        dir = NormalizePath(cwd.c_str()); 
        pattern = path.c_str();
        Log("[%d]\tFileFirstFileEx: no slash, assumed cwd based type=x%x dap=%ls", FindFirstFileExInstance, psf::path_type(cwd.c_str()),dir.drive_absolute_path);
    }
    
    // If you change the below logic, or
	// you you change what goes into RedirectedPath
	// you need to mirror all changes to ShouldRedirectImpl
	// in PathRedirection.cpp
 
    //Basically, what goes into RedirectedPath here also needs to go into 
    // RedirectedPath in PathRedirection.cpp
    dir = NormalizePath(dir.drive_absolute_path);
    dir = VirtualizePath(std::move(dir), FindFirstFileExInstance);

    auto result = std::make_unique<find_data>();

    result->requested_path = path.c_str();
    result->redirect_path = RedirectedPath(dir,false, FindFirstFileExInstance);
    if (result->redirect_path.back() != L'\\')
    {
        result->redirect_path.push_back(L'\\');
    }
    Log(L"[%d]FindFirstFile redirected_path is", FindFirstFileExInstance);
    Log(result->redirect_path.c_str());
    
    std::filesystem::path vfspath = GetPackageVFSPath(path.c_str());
    if (wcslen(vfspath.c_str()) > 0)
    {
        result->package_vfs_path = vfspath.c_str();
        //if (result->package_vfs_path.back() != L'\\')
        //{
        //    result->package_vfs_path.push_back(L'\\');
        //}
        Log(L"[%d]FindFirstFile package_vfs_path is", FindFirstFileExInstance);
        Log(result->package_vfs_path.c_str());
    }
    else
    {
        Log(L"[%d]FindFirstFile package_vfs_path is [empty]", FindFirstFileExInstance);
    }

    [[maybe_unused]] auto ansiData = reinterpret_cast<WIN32_FIND_DATAA*>(findFileData);
    [[maybe_unused]] auto wideData = reinterpret_cast<WIN32_FIND_DATAW*>(findFileData);
    WIN32_FIND_DATAW* findData = psf::is_ansi<CharT> ? &result->cached_data : wideData;

    //
    // Open the redirected find handle
    auto revertSize = result->redirect_path.length();
    result->redirect_path += pattern;
    result->find_handles[0].reset(impl::FindFirstFileEx(result->redirect_path.c_str(), infoLevelId, findData, searchOp, searchFilter, additionalFlags));
    result->redirect_path.resize(revertSize);

    // Some applications really care about the failure reason. Try and make this the best that we can, preferring
    // something like "file not found" over "path does not exist"
    auto initialFindError = ::GetLastError();

    if (result->find_handles[0])
    {
        if constexpr (psf::is_ansi<CharT>)
        {
            if (copy_find_data(*findData, *ansiData))
            {
                // NOTE: Last error set by caller
                return INVALID_HANDLE_VALUE;
            }
        }
        else
        {
            // No need to copy since we wrote directly into the output buffer
            assert(findData == wideData);
        }
        Log(L"[%d]FindFirstFile[0] redirected (from redirected): had results", FindFirstFileExInstance);
    }
    else
    {
        // Path doesn't exist or match any files. We can safely get away without the redirected file exists check
        result->redirect_path.clear();
        Log(L"[%d]FindFirstFile[0] redirected (from redirected): no results", FindFirstFileExInstance);
    }

    findData = (result->find_handles[0] || psf::is_ansi<CharT>) ? &result->cached_data : wideData;

    //
    // Open the package_vfsP_path handle if AppData or LocalAppData (letting the runtime handle other VFSs)
    if (IsUnderUserAppDataLocal(path.c_str()) || IsUnderUserAppDataRoaming(path.c_str()))
    {
        ///auto vfspathSize = result->package_vfs_path.length();
        ///result->package_vfs_path += pattern;
        result->find_handles[1].reset(impl::FindFirstFileEx(result->package_vfs_path.c_str(), infoLevelId, findData, searchOp, searchFilter, additionalFlags));
        ///result->package_vfs_path.resize(vfspathSize);
        if (result->find_handles[1])
        {
            Log(L"[%d]FindFirstFile[1] (from vfs_path): had results", FindFirstFileExInstance);
        }
        else
        {
            result->package_vfs_path.clear();
            Log(L"[%d]FindFirstFile[1] (from vfs_path): no results", FindFirstFileExInstance);
        }
        if (!result->find_handles[0])
        {
            if constexpr (psf::is_ansi<CharT>)
            {
                if (copy_find_data(*findData, *ansiData))
                {
                    Log(L"[%d]FindFirstFile error set by caller", FindFirstFileExInstance);
                    // NOTE: Last error set by caller
                    return INVALID_HANDLE_VALUE;
                }
            }
            else
            {
                // No need to copy since we wrote directly into the output buffer
                assert(findData == wideData);
            }
        }
    }

    //
    // Open the non-redirected find handle
    result->find_handles[2].reset(impl::FindFirstFileEx(result->requested_path.c_str(), infoLevelId, findData, searchOp, searchFilter, additionalFlags));
    if (result->find_handles[2])
    {
        Log(L"[%d]FindFirstFile[2] (from origial): had results", FindFirstFileExInstance);
    }
    else
    {
        result->requested_path.clear();
        Log(L"[%d]FindFirstFile[2] (from original): no results", FindFirstFileExInstance);
    }
    if (!result->find_handles[0] &&
        !result->find_handles[1])
    {
        if (!result->find_handles[2])
        {
            // Neither path exists. The last error should still be set by FindFirstFileEx, but prefer the initial error
            // if it indicates that the redirected directory structure exists
            if (initialFindError == ERROR_FILE_NOT_FOUND)
            {
                Log(L"[%d]FindFirstFile error 0x%x", FindFirstFileExInstance, initialFindError);
                ::SetLastError(initialFindError);
            }

            return INVALID_HANDLE_VALUE;
        }
        else
        {
            if constexpr (psf::is_ansi<CharT>)
            {
                if (copy_find_data(*findData, *ansiData))
                {
                    Log(L"[%d]FindFirstFile error set by caller", FindFirstFileExInstance);
                    // NOTE: Last error set by caller
                    return INVALID_HANDLE_VALUE;
                }
            }
            else
            {
                // No need to copy since we wrote directly into the output buffer
                assert(findData == wideData);
            }
        }
    }


    ::SetLastError(ERROR_SUCCESS);
    return reinterpret_cast<HANDLE>(result.release());

}