diff --git a/src/usvfs_dll/hooks/file_information_utils.h b/src/usvfs_dll/hooks/file_information_utils.h index ec2fc7c..dbd3597 100644 --- a/src/usvfs_dll/hooks/file_information_utils.h +++ b/src/usvfs_dll/hooks/file_information_utils.h @@ -41,9 +41,6 @@ DECLARE_HAS_FIELD(ShortName) template struct FileInformationClassUtilsImpl { - // minimum required size for the structure - std::size_t get_minimum_struct_size() { return sizeof(FileInformationClass); } - static void get_data(LPCVOID address, ULONG& offset, std::wstring& fileName) { const FileInformationClass* info = @@ -101,7 +98,7 @@ struct FileInformationClassUtils template <> struct FileInformationClassUtils : FileInformationClassUtilsImpl + FILE_FULL_DIR_INFORMATION> {}; template <> struct FileInformationClassUtils @@ -125,10 +122,6 @@ struct FileInformationClassUtils : FileInformationClassUtilsImpl {}; template <> -struct FileInformationClassUtils - : FileInformationClassUtilsImpl -{}; -template <> struct FileInformationClassUtils : FileInformationClassUtilsImpl {}; @@ -183,6 +176,32 @@ struct FileInformationClassUtils FILE_ID_ALL_EXTD_BOTH_DIR_INFORMATION> {}; +// FILE_ALL_INFORMATION needs to be handled differently because it has a field that +// is itself a structure +template <> +struct FileInformationClassUtils +{ + static void get_data(LPCVOID address, ULONG& offset, std::wstring& fileName) + { + FileInformationClassUtils::get_data( + &reinterpret_cast(address)->NameInformation, + offset, fileName); + } + + static void set_offset(LPVOID address, ULONG offset) + { + // this is a no-op but it's consistent to do that everywhere + FileInformationClassUtils::set_offset( + &reinterpret_cast(address)->NameInformation, offset); + } + + static void set_filename(LPVOID address, const std::wstring& fileName) + { + FileInformationClassUtils::set_filename( + &reinterpret_cast(address)->NameInformation, fileName); + } +}; + } // namespace usvfs::details #define _APP_FINFO_CASE(clazz, fn, ...) \ @@ -190,6 +209,7 @@ struct FileInformationClassUtils return usvfs::details::FileInformationClassUtils::fn(__VA_ARGS__); #define _APPLY_FILEINFO_FN(fn, ...) \ + _APP_FINFO_CASE(FileAllInformation, fn, __VA_ARGS__) \ _APP_FINFO_CASE(FileDirectoryInformation, fn, __VA_ARGS__) \ _APP_FINFO_CASE(FileFullDirectoryInformation, fn, __VA_ARGS__) \ _APP_FINFO_CASE(FileBothDirectoryInformation, fn, __VA_ARGS__) \ @@ -197,7 +217,6 @@ struct FileInformationClassUtils _APP_FINFO_CASE(FileNameInformation, fn, __VA_ARGS__) \ _APP_FINFO_CASE(FileRenameInformation, fn, __VA_ARGS__) \ _APP_FINFO_CASE(FileNamesInformation, fn, __VA_ARGS__) \ - _APP_FINFO_CASE(FileAllInformation, fn, __VA_ARGS__) \ _APP_FINFO_CASE(FileObjectIdInformation, fn, __VA_ARGS__) \ _APP_FINFO_CASE(FileReparsePointInformation, fn, __VA_ARGS__) \ _APP_FINFO_CASE(FileIdBothDirectoryInformation, fn, __VA_ARGS__) \ @@ -210,17 +229,6 @@ struct FileInformationClassUtils _APP_FINFO_CASE(FileIdAllExtdDirectoryInformation, fn, __VA_ARGS__) \ _APP_FINFO_CASE(FileIdAllExtdBothDirectoryInformation, fn, __VA_ARGS__) -// minimum required size for the structure -// std::size_t -// get_file_information_minimum_struct_size(FILE_INFORMATION_CLASS fileInformationClass) -// { -// switch (fileInformationClass) { -// _APPLY_FILEINFO_FN(get_minimum_struct_size, ); -// default: -// return 0; -// } -// } - void GetFileInformationData(FILE_INFORMATION_CLASS fileInformationClass, LPCVOID address, ULONG& offset, std::wstring& fileName) { diff --git a/test/tvfs_test/CMakeLists.txt b/test/tvfs_test/CMakeLists.txt index 76a5306..f66a7b4 100644 --- a/test/tvfs_test/CMakeLists.txt +++ b/test/tvfs_test/CMakeLists.txt @@ -4,7 +4,7 @@ find_package(GTest CONFIG REQUIRED) add_executable(tvfs_test main.cpp) usvfs_set_test_properties(tvfs_test) -target_link_libraries(tvfs_test PRIVATE test_utils usvfs_helper GTest::gtest GTest::gtest_main) +target_link_libraries(tvfs_test PRIVATE test_utils usvfs_helper GTest::gtest GTest::gmock GTest::gtest_main) usvfs_target_link_usvfs(tvfs_test) # tvfs_test uses a private USVFS header so we need to include it manually diff --git a/test/tvfs_test/main.cpp b/test/tvfs_test/main.cpp index 8eebb40..7c7998b 100644 --- a/test/tvfs_test/main.cpp +++ b/test/tvfs_test/main.cpp @@ -25,12 +25,8 @@ along with usvfs. If not, see . #include -#pragma warning(push, 3) -#include -#include - #include -#pragma warning(pop) +#include #include #include @@ -47,6 +43,9 @@ along with usvfs. If not, see . #include #include +#include +#include + namespace spd = spdlog; namespace ush = usvfs::shared; @@ -299,7 +298,7 @@ HANDLE hooked_NtOpenFile(LPCWSTR path, ACCESS_MASK accessMask, ULONG shareAccess string.Buffer = stringBuffer; lstrcpyW(stringBuffer, L"\\??\\"); lstrcatW(stringBuffer, path); - string.Length = lstrlenW(stringBuffer) * 2; + string.Length = static_cast(lstrlenW(stringBuffer) * 2); string.MaximumLength = BUFFER_SIZE; attributes.ObjectName = &string; @@ -365,6 +364,53 @@ TEST_F(USVFSTest, NtQueryDirectoryFileFindsVirtualFile) usvfs::hook_NtClose(hdl); } +TEST_F(USVFSTest, NtQueryDirectoryFileExVirtualFile) +{ + auto params = defaultUsvfsParams(); + std::unique_ptr ctx( + usvfsCreateHookContext(*params, ::GetModuleHandle(nullptr))); + usvfs::RedirectionTreeContainer& tree = ctx->redirectionTable(); + + tree.addFile(L"C:\\0123456789.txt", usvfs::RedirectionDataLocal(REAL_FILEA)); + tree.addFile(L"C:\\123456", usvfs::RedirectionDataLocal(REAL_FILEA)); + tree.addFile(L"C:\\abcdef", usvfs::RedirectionDataLocal(REAL_FILEA)); + tree.addFile(L"C:\\abcdefghijklmnopqrstuvwxyz.txt", + usvfs::RedirectionDataLocal(REAL_FILEA)); + + HANDLE hdl = + hooked_NtOpenFile(L"C:\\", FILE_GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT); + ASSERT_NE(INVALID_HANDLE_VALUE, hdl); + + IO_STATUS_BLOCK status; + + constexpr size_t BUFFER_SIZE = 2048; + char buffer[BUFFER_SIZE]; + + std::vector foundFiles; + while (usvfs::hook_NtQueryDirectoryFileEx( + hdl, nullptr, nullptr, nullptr, &status, buffer, BUFFER_SIZE, + FileFullDirectoryInformation, 0, nullptr) == STATUS_SUCCESS) { + std::size_t offset = 0; + while (offset < BUFFER_SIZE) { + const auto* info = reinterpret_cast(buffer + offset); + foundFiles.emplace_back(info->FileName, info->FileNameLength / sizeof(wchar_t)); + + if (info->NextEntryOffset == 0) { + break; // no more entries + } + + offset += info->NextEntryOffset; + } + } + + ASSERT_THAT(foundFiles, + ::testing::IsSupersetOf({L"0123456789.txt", L"123456", L"abcdef", + L"abcdefghijklmnopqrstuvwxyz.txt"})); + + usvfs::hook_NtClose(hdl); +} + TEST_F(USVFSTest, NtQueryObjectVirtualFile) { std::wstring c_drive_device; @@ -405,6 +451,7 @@ TEST_F(USVFSTest, NtQueryObjectVirtualFile) IO_STATUS_BLOCK status; const auto res = usvfs::hook_NtQueryInformationFile( hdl, &status, buffer, sizeof(buffer), FileNameInformation); + ASSERT_EQ(STATUS_SUCCESS, res); ASSERT_EQ(STATUS_SUCCESS, status.Status); FILE_NAME_INFORMATION* fileNameInfo = @@ -471,7 +518,7 @@ TEST_F(USVFSTest, NtQueryObjectVirtualFile) { // expected length is sizeof struct + size of path (in bytes), including the // null-character - const ULONG expectedLength = + const auto expectedLength = sizeof(OBJECT_NAME_INFORMATION) + c_drive_device.size() * 2 + 12 + 2; ULONG requiredLength; NTSTATUS res;