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());
}