diff --git a/src/shared/ntdll_declarations.h b/src/shared/ntdll_declarations.h index 0f262b3f..1cc096ba 100644 --- a/src/shared/ntdll_declarations.h +++ b/src/shared/ntdll_declarations.h @@ -141,6 +141,7 @@ typedef struct _FILE_REPARSE_POINT_INFORMATION { ULONG Tag; } FILE_REPARSE_POINT_INFORMATION, *PFILE_REPARSE_POINT_INFORMATION; +// copied from ntstatus.h #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) #define STATUS_BUFFER_OVERFLOW ((NTSTATUS)0x80000005L) #define STATUS_NO_MORE_FILES ((NTSTATUS)0x80000006L) diff --git a/src/shared/stringcast_basic.h b/src/shared/stringcast_basic.h index 0dde7d28..70e5e708 100644 --- a/src/shared/stringcast_basic.h +++ b/src/shared/stringcast_basic.h @@ -18,6 +18,8 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with usvfs. If not, see . */ +#pragma once + #include #include #include diff --git a/src/usvfs_dll/hookmanager.cpp b/src/usvfs_dll/hookmanager.cpp index 1ab0cf14..7bb1634b 100644 --- a/src/usvfs_dll/hookmanager.cpp +++ b/src/usvfs_dll/hookmanager.cpp @@ -231,13 +231,6 @@ void HookManager::initHooks() installHook(kbaseMod, k32Mod, "GetFileAttributesW", hook_GetFileAttributesW); installHook(kbaseMod, k32Mod, "SetFileAttributesW", hook_SetFileAttributesW); - // Unfortunately, at least on windows 10 1709 x64 the CreateFileA and CreateFile2 translate - // to CreateFileInternal directly (so hooking CreateFileW alone is not enough) - installHook(kbaseMod, k32Mod, "CreateFileW", hook_CreateFileW); - installHook(kbaseMod, k32Mod, "CreateFileA", hook_CreateFileA); - if (IsWindows8OrGreater()) - installHook(kbaseMod, k32Mod, "CreateFile2", hook_CreateFile2, reinterpret_cast(&CreateFile2)); - installHook(kbaseMod, k32Mod, "CreateDirectoryW", hook_CreateDirectoryW); installHook(kbaseMod, k32Mod, "RemoveDirectoryW", hook_RemoveDirectoryW); installHook(kbaseMod, k32Mod, "DeleteFileW", hook_DeleteFileW); diff --git a/src/usvfs_dll/hooks/kernel32.cpp b/src/usvfs_dll/hooks/kernel32.cpp index 60230bbe..485bacb9 100644 --- a/src/usvfs_dll/hooks/kernel32.cpp +++ b/src/usvfs_dll/hooks/kernel32.cpp @@ -4,6 +4,8 @@ #include "../hookmanager.h" #include "../hookcontext.h" #include "../hookcallcontext.h" +#include "../maptracker.h" + #include #include #include @@ -30,54 +32,10 @@ namespace ush = usvfs::shared; using ush::string_cast; using ush::CodePage; -class MapTracker { -public: - using wstring = std::wstring; - - wstring lookup(const wstring& fromPath) const { - if (!fromPath.empty()) - { - std::shared_lock lock(m_mutex); - auto find = m_map.find(fromPath); - if (find != m_map.end()) - return find->second; - } - return wstring(); - } - - bool contains(const wstring& fromPath) const { - if (!fromPath.empty()) - { - std::shared_lock lock(m_mutex); - auto find = m_map.find(fromPath); - if (find != m_map.end()) - return true; - } - return false; - } - - void insert(const wstring& fromPath, const wstring& toPath) { - if (fromPath.empty()) - return; - std::unique_lock lock(m_mutex); - m_map[fromPath] = toPath; - } - - bool erase(const wstring& fromPath) - { - if (fromPath.empty()) - return false; - std::unique_lock lock(m_mutex); - return m_map.erase(fromPath); - } - -private: - mutable std::shared_mutex m_mutex; - std::unordered_map m_map; -}; - +namespace usvfs { MapTracker k32DeleteTracker; MapTracker k32FakeDirTracker; +} // namespace usvfs class CurrentDirectoryTracker { public: @@ -131,37 +89,9 @@ class CurrentDirectoryTracker { CurrentDirectoryTracker k32CurrentDirectoryTracker; -// returns true iff the path exists (checks only real paths) -static inline bool pathExists(LPCWSTR fileName) -{ - usvfs::FunctionGroupLock lock(usvfs::MutExHookGroup::FILE_ATTRIBUTES); - DWORD attrib = GetFileAttributesW(fileName); - return attrib != INVALID_FILE_ATTRIBUTES; -} -// returns true iff the path exists and is a file (checks only real paths) -static inline bool pathIsFile(LPCWSTR fileName) -{ - usvfs::FunctionGroupLock lock(usvfs::MutExHookGroup::FILE_ATTRIBUTES); - DWORD attrib = GetFileAttributesW(fileName); - return attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) == 0; -} -// returns true iff the path exists and is a file (checks only real paths) -static inline bool pathIsDirectory(LPCWSTR fileName) -{ - usvfs::FunctionGroupLock lock(usvfs::MutExHookGroup::FILE_ATTRIBUTES); - DWORD attrib = GetFileAttributesW(fileName); - return attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY); -} -// returns true iff the path does not exist but it parent directory does (checks only real paths) -static inline bool pathDirectlyAvailable(LPCWSTR pathName) -{ - usvfs::FunctionGroupLock lock(usvfs::MutExHookGroup::FILE_ATTRIBUTES); - DWORD attrib = GetFileAttributesW(pathName); - return attrib == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; -} // attempts to copy source to destination and return the error code static inline DWORD copyFileDirect(LPCWSTR source, LPCWSTR destination, bool overwrite) @@ -201,442 +131,7 @@ static inline bool pathsOnDifferentDrives(LPCWSTR path1, LPCWSTR path2) return drive1 && drive2 && towupper(drive1) != towupper(drive2); } -class RerouteW -{ - std::wstring m_Buffer{}; - std::wstring m_RealPath{}; - bool m_Rerouted{false}; - LPCWSTR m_FileName{nullptr}; - bool m_PathCreated{false}; - bool m_NewReroute{false}; - - usvfs::RedirectionTree::NodePtrT m_FileNode; - -public: - RerouteW() = default; - - RerouteW(RerouteW &&reference) - : m_Buffer(std::move(reference.m_Buffer)) - , m_RealPath(std::move(reference.m_RealPath)) - , m_Rerouted(reference.m_Rerouted) - , m_PathCreated(reference.m_PathCreated) - , m_NewReroute(reference.m_NewReroute) - , m_FileNode(std::move(reference.m_FileNode)) - { - m_FileName = reference.m_FileName != nullptr ? m_Buffer.c_str() : nullptr; - reference.m_FileName = nullptr; - } - - RerouteW &operator=(RerouteW &&reference) - { - m_Buffer = std::move(reference.m_Buffer); - m_RealPath = std::move(reference.m_RealPath); - m_Rerouted = reference.m_Rerouted; - m_PathCreated = reference.m_PathCreated; - m_NewReroute = reference.m_NewReroute; - m_FileName = reference.m_FileName != nullptr ? m_Buffer.c_str() : nullptr; - m_FileNode = std::move(reference.m_FileNode); - return *this; - } - - RerouteW(const RerouteW &reference) = delete; - RerouteW &operator=(const RerouteW &) = delete; - - LPCWSTR fileName() const - { - return m_FileName; - } - - const std::wstring &buffer() const - { - return m_Buffer; - } - - bool wasRerouted() const - { - return m_Rerouted; - } - - bool newReroute() const - { - return m_NewReroute; - } - - void insertMapping(const usvfs::HookContext::Ptr &context, bool directory = false) - { - if (directory) - { - addDirectoryMapping(context, m_RealPath, m_FileName); - - // In case we have just created a "fake" directory, it is no longer fake and need to remove it and all its - // parent folders from the fake map: - std::wstring dir = m_FileName; - while (k32FakeDirTracker.erase(dir)) - dir = fs::path(dir).parent_path().wstring(); - } - else - { - //if (m_PathCreated) - //addDirectoryMapping(context, fs::path(m_RealPath).parent_path(), fs::path(m_FileName).parent_path()); - - spdlog::get("hooks")->info("mapping file in vfs: {}, {}", - ush::string_cast(m_RealPath, ush::CodePage::UTF8), - ush::string_cast(m_FileName, ush::CodePage::UTF8)); - m_FileNode = - context->redirectionTable().addFile(m_RealPath, usvfs::RedirectionDataLocal(string_cast(m_FileName, CodePage::UTF8))); - - k32DeleteTracker.erase(m_RealPath); - } - } - - void removeMapping(const usvfs::HookContext::ConstPtr &readContext, bool directory = false) - { - bool addToDelete = false; - bool dontAddToDelete = false; - - // We need to track deleted files even if they were not rerouted (i.e. files deleted from the real folder which there is - // a virtualized mapped folder on top of it). Since we don't want to add, *every* file which is deleted we check this: - bool found = wasRerouted(); - if (!found) { - FindCreateTarget visitor; - usvfs::RedirectionTree::VisitorFunction visitorWrapper = - [&](const usvfs::RedirectionTree::NodePtrT &node) { visitor(node); }; - readContext->redirectionTable()->visitPath(m_RealPath, visitorWrapper); - if (visitor.target.get()) - found = true; - } - if (found) - addToDelete = true; - - if (wasRerouted()) { - if (m_FileNode.get()) - m_FileNode->removeFromTree(); - else - spdlog::get("usvfs")->warn("Node not removed: {}", string_cast(m_FileName)); - - if (!directory) - { - // check if this file was the last file inside a "fake" directory then remove it - // and possibly also its fake empty parent folders: - std::wstring parent = m_FileName; - while (true) - { - parent = fs::path(parent).parent_path().wstring(); - if (k32FakeDirTracker.contains(parent)) - { - dontAddToDelete = true; - if (RemoveDirectoryW(parent.c_str())) { - k32FakeDirTracker.erase(parent); - spdlog::get("usvfs")->info("removed empty fake directory: {}", string_cast(parent)); - } - else if (GetLastError() != ERROR_DIR_NOT_EMPTY) { - auto error = GetLastError(); - spdlog::get("usvfs")->warn("removing fake directory failed: {}, error={}", string_cast(parent), error); - break; - } - } - else - break; - } - } - } - if (addToDelete && !dontAddToDelete) { - k32DeleteTracker.insert(m_RealPath, m_FileName); - } - } - - static bool createFakePath(fs::path path, LPSECURITY_ATTRIBUTES securityAttributes) - { - // sanity and guaranteed recursion end: - if (!path.has_relative_path()) - throw usvfs::shared::windows_error("createFakePath() refusing to create non-existing top level path: " + path.string()); - - DWORD attr = GetFileAttributesW(path.c_str()); - DWORD err = GetLastError(); - if (attr != INVALID_FILE_ATTRIBUTES) { - if (attr & FILE_ATTRIBUTE_DIRECTORY) - return false; // if directory already exists all is good - else - throw usvfs::shared::windows_error("createFakePath() called on a file: " + path.string()); - } - if (err != ERROR_FILE_NOT_FOUND && err != ERROR_PATH_NOT_FOUND) - throw usvfs::shared::windows_error("createFakePath() GetFileAttributesW failed on: " + path.string(), err); - - if (err != ERROR_FILE_NOT_FOUND) // ERROR_FILE_NOT_FOUND means parent directory already exists - createFakePath(path.parent_path(), securityAttributes); // otherwise create parent directory (recursively) - - BOOL res = CreateDirectoryW(path.c_str(), securityAttributes); - if (res) - k32FakeDirTracker.insert(path.wstring(), std::wstring()); - else { - err = GetLastError(); - throw usvfs::shared::windows_error("createFakePath() CreateDirectoryW failed on: " + path.string(), err); - } - return true; - } - - static bool addDirectoryMapping(const usvfs::HookContext::Ptr &context, const fs::path& originalPath, const fs::path& reroutedPath) - { - if (originalPath.empty() || reroutedPath.empty()) { - spdlog::get("hooks")->error("RerouteW::addDirectoryMapping failed: {}, {}", - string_cast(originalPath.wstring(), CodePage::UTF8).c_str(), - string_cast(reroutedPath.wstring(), CodePage::UTF8).c_str()); - return false; - } - - auto lookupParent = context->redirectionTable()->findNode(originalPath.parent_path()); - if (!lookupParent.get() || lookupParent->data().linkTarget.empty()) { - if (!addDirectoryMapping(context, originalPath.parent_path(), reroutedPath.parent_path())) - { - spdlog::get("hooks")->error("RerouteW::addDirectoryMapping failed: {}, {}", - string_cast(originalPath.wstring(), CodePage::UTF8).c_str(), - string_cast(reroutedPath.wstring(), CodePage::UTF8).c_str()); - return false; - } - } - - std::string reroutedU8 - = ush::string_cast(reroutedPath.wstring(), ush::CodePage::UTF8); - if (reroutedU8.empty() || reroutedU8[reroutedU8.size() - 1] != '\\') - reroutedU8 += "\\"; - - spdlog::get("hooks")->info("mapping directory in vfs: {}, {}", - ush::string_cast(originalPath.wstring(), ush::CodePage::UTF8), reroutedU8.c_str()); - - context->redirectionTable().addDirectory( - originalPath, usvfs::RedirectionDataLocal(reroutedU8), - usvfs::shared::FLAG_DIRECTORY|usvfs::shared::FLAG_CREATETARGET); - - fs::directory_iterator end_itr; - - // cycle through the directory - for (fs::directory_iterator itr(reroutedPath); itr != end_itr; ++itr) - { - // If it's not a directory, add it to the VFS, if it is recurse into it - if (is_regular_file(itr->path())) { - std::string fileReroutedU8 = ush::string_cast(itr->path().wstring(), ush::CodePage::UTF8); - spdlog::get("hooks")->info("mapping file in vfs: {}, {}", - ush::string_cast((originalPath / itr->path().filename()).wstring(), ush::CodePage::UTF8), - fileReroutedU8.c_str()); - context->redirectionTable().addFile(fs::path(originalPath / itr->path().filename()), usvfs::RedirectionDataLocal(fileReroutedU8)); - } else { - addDirectoryMapping(context, originalPath / itr->path().filename(), reroutedPath / itr->path().filename()); - } - } - - return true; - } - - template - static bool interestingPathImpl(const char_t *inPath) - { - if (!inPath || !inPath[0]) - return false; - // ignore \\.\ unless its a \\.\?: - if (inPath[0] == '\\' && inPath[1] == '\\' && inPath[2] == '.' && inPath[3] == '\\' && (!inPath[4] || inPath[5] != ':')) - return false; - // ignore L"hid#": - if ((inPath[0] == 'h' || inPath[0] == 'H') - && ((inPath[1] == 'i' || inPath[1] == 'I')) - && ((inPath[2] == 'd' || inPath[2] == 'D')) - && inPath[3] == '#') - return false; - return true; - } - - static bool interestingPath(const char* inPath) { return interestingPathImpl(inPath); } - static bool interestingPath(const wchar_t* inPath) { return interestingPathImpl(inPath); } - - static fs::path absolutePath(const wchar_t *inPath) - { - if (ush::startswith(inPath, LR"(\\?\)") || ush::startswith(inPath, LR"(\??\)")) { - inPath += 4; - return inPath; - } - else if ((ush::startswith(inPath, LR"(\\localhost\)") || ush::startswith(inPath, LR"(\\127.0.0.1\)")) && inPath[13] == L'$') { - std::wstring newPath; - newPath += towupper(inPath[12]); - newPath += L':'; - newPath += &inPath[14]; - return newPath; - } - else if (inPath[0] == L'\0' || inPath[1] == L':') { - return inPath; - } - else if (inPath[0] == L'\\' || inPath[0] == L'/') { - return fs::path(winapi::wide::getFullPathName(inPath).first); - } - WCHAR currentDirectory[MAX_PATH]; - ::GetCurrentDirectoryW(MAX_PATH, currentDirectory); - fs::path finalPath = fs::path(currentDirectory) / inPath; - return finalPath; - //winapi::wide::getFullPathName(inPath).first; - } - - static fs::path canonizePath(const fs::path& inPath) - { - fs::path p = inPath.lexically_normal(); - if (p.filename_is_dot()) - p = p.remove_filename(); - return p.make_preferred(); - } - - static RerouteW create(const usvfs::HookContext::ConstPtr &context, - const usvfs::HookCallContext &callContext, - const wchar_t *inPath, bool inverse = false) - { - RerouteW result; - - if (interestingPath(inPath) && callContext.active()) - { - const auto& lookupPath = canonizePath(absolutePath(inPath)); - result.m_RealPath = lookupPath.wstring(); - - result.m_Buffer = k32DeleteTracker.lookup(result.m_RealPath); - bool found = !result.m_Buffer.empty(); - if (found) { - spdlog::get("hooks")->info("Rerouting file open to location of deleted file: {}", - ush::string_cast(result.m_Buffer)); - result.m_NewReroute = true; - } else { - const usvfs::RedirectionTreeContainer &table - = inverse ? context->inverseTable() : context->redirectionTable(); - result.m_FileNode = table->findNode(lookupPath); - - if (result.m_FileNode.get() - && (!result.m_FileNode->data().linkTarget.empty() || result.m_FileNode->isDirectory())) - { - if (!result.m_FileNode->data().linkTarget.empty()) { - result.m_Buffer = string_cast( - result.m_FileNode->data().linkTarget.c_str(), CodePage::UTF8); - } - else - { - result.m_Buffer = result.m_FileNode->path().wstring(); - } - found = true; - } - } - if (found) { - result.m_Rerouted = true; - - wchar_t inIt = inPath[wcslen(inPath) - 1]; - std::wstring::iterator outIt = result.m_Buffer.end() - 1; - if ((*outIt == L'\\' || *outIt == L'/') && !(inIt == L'\\' || inIt == L'/')) - result.m_Buffer.erase(outIt); - std::replace(result.m_Buffer.begin(), result.m_Buffer.end(), L'/', L'\\'); - } - else - result.m_Buffer = inPath; - } - else if (inPath) - result.m_Buffer = inPath; - - if (inPath) - result.m_FileName = result.m_Buffer.c_str(); - return result; - } - - static RerouteW createNew(const usvfs::HookContext::ConstPtr &context, - const usvfs::HookCallContext &callContext, - LPCWSTR inPath, bool createPath = true, - LPSECURITY_ATTRIBUTES securityAttributes = nullptr) - { - RerouteW result; - - if (interestingPath(inPath) && callContext.active()) - { - const auto& lookupPath = canonizePath(absolutePath(inPath)); - result.m_RealPath = lookupPath.wstring(); - - result.m_Buffer = k32DeleteTracker.lookup(result.m_RealPath); - bool found = !result.m_Buffer.empty(); - if (found) - spdlog::get("hooks")->info("Rerouting file creation to original location of deleted file: {}", - ush::string_cast(result.m_Buffer)); - else - { - FindCreateTarget visitor; - usvfs::RedirectionTree::VisitorFunction visitorWrapper = - [&](const usvfs::RedirectionTree::NodePtrT &node) { visitor(node); }; - context->redirectionTable()->visitPath(lookupPath, visitorWrapper); - if (visitor.target.get()) { - // the visitor has found the last (deepest in the directory hierarchy) - // create-target - fs::path relativePath - = ush::make_relative(visitor.target->path(), lookupPath); - result.m_Buffer = - (fs::path(visitor.target->data().linkTarget.c_str()) / relativePath).wstring(); - found = true; - } - } - - if (found) - { - if (createPath) { - try { - usvfs::FunctionGroupLock lock(usvfs::MutExHookGroup::ALL_GROUPS); - result.m_PathCreated = - createFakePath(fs::path(result.m_Buffer).parent_path(), securityAttributes); - } - catch (const std::exception &e) { - spdlog::get("hooks")->error("failed to create {}: {}", - ush::string_cast(result.m_Buffer), e.what()); - } - } - - wchar_t inIt = inPath[wcslen(inPath) - 1]; - std::wstring::iterator outIt = result.m_Buffer.end() - 1; - if ((*outIt == L'\\' || *outIt == L'/') && !(inIt == L'\\' || inIt == L'/')) - result.m_Buffer.erase(outIt); - std::replace(result.m_Buffer.begin(), result.m_Buffer.end(), L'/', L'\\'); - result.m_Rerouted = true; - result.m_NewReroute = true; - } - else - result.m_Buffer = inPath; - } - else if (inPath) - result.m_Buffer = inPath; - - if (inPath) - result.m_FileName = result.m_Buffer.c_str(); - return result; - } - - static RerouteW createOrNew(const usvfs::HookContext::ConstPtr &context, const usvfs::HookCallContext &callContext, - LPCWSTR inPath, bool createPath = true, LPSECURITY_ATTRIBUTES securityAttributes = nullptr) - { - { - auto res = create(context, callContext, inPath); - if (res.wasRerouted() || !interestingPath(inPath) || !callContext.active() || pathExists(inPath)) - return std::move(res); - } - return createNew(context, callContext, inPath, createPath, securityAttributes); - } - - static RerouteW noReroute(LPCWSTR inPath) - { - RerouteW result; - if (inPath) - result.m_Buffer = inPath; - if (inPath && inPath[0] && !ush::startswith(inPath, L"hid#")) - std::replace(result.m_Buffer.begin(), result.m_Buffer.end(), L'/', L'\\'); - result.m_FileName = result.m_Buffer.c_str(); - return result; - } -private: - struct FindCreateTarget { - usvfs::RedirectionTree::NodePtrT target; - void operator()(usvfs::RedirectionTree::NodePtrT node) - { - if (node->hasFlag(usvfs::shared::FLAG_CREATETARGET)) { - target = node; - } - } - }; -}; HMODULE WINAPI usvfs::hook_LoadLibraryExW(LPCWSTR lpFileName, HANDLE hFile, DWORD dwFlags) @@ -854,299 +349,6 @@ BOOL WINAPI usvfs::hook_CreateProcessInternalW( return res; } -HANDLE WINAPI usvfs::hook_CreateFileA( - LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, - LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, - DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) -{ - HANDLE res = INVALID_HANDLE_VALUE; - - HOOK_START_GROUP(MutExHookGroup::OPEN_FILE) - - if (!callContext.active()) { - res = CreateFileA(lpFileName, dwDesiredAccess, dwShareMode, - lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); - callContext.updateLastError(); - return res; - } - - // release the MutExHookGroup::OPEN_FILE so that CreateFileW can process the request: - HOOK_END - HOOK_START - - const auto& fileName = ush::string_cast(lpFileName); - - PRE_REALCALL - res = CreateFileW(fileName.c_str(), dwDesiredAccess, dwShareMode, lpSecurityAttributes, - dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); - POST_REALCALL - - HOOK_END - - return res; -} - -namespace usvfs { - class CreateRerouter { - public: - bool rerouteCreate(const usvfs::HookContext::ConstPtr &context, const usvfs::HookCallContext &callContext, - LPCWSTR lpFileName, DWORD& dwCreationDisposition, DWORD dwDesiredAccess, LPSECURITY_ATTRIBUTES lpSecurityAttributes) - { - enum class Open { existing, create, empty }; - Open open = Open::existing; - - // Notice since we are calling our patched GetFileAttributesW here this will also check virtualized paths - DWORD virtAttr = GetFileAttributesW(lpFileName); - bool isFile = virtAttr != INVALID_FILE_ATTRIBUTES && (virtAttr & FILE_ATTRIBUTE_DIRECTORY) == 0; - m_isDir = virtAttr != INVALID_FILE_ATTRIBUTES && (virtAttr & FILE_ATTRIBUTE_DIRECTORY); - - switch (dwCreationDisposition) { - case CREATE_ALWAYS: - open = Open::create; - if (isFile || m_isDir) - m_error = ERROR_ALREADY_EXISTS; - break; - - case CREATE_NEW: - if (isFile || m_isDir) { - m_error = ERROR_FILE_EXISTS; - return false; - } - else - open = Open::create; - break; - - case OPEN_ALWAYS: - if (isFile || m_isDir) - m_error = ERROR_ALREADY_EXISTS; - else - open = Open::create; - break; - - case TRUNCATE_EXISTING: - if ((dwDesiredAccess & GENERIC_WRITE) == 0) { - m_error = ERROR_INVALID_PARAMETER; - return false; - } - if (isFile || m_isDir) - open = Open::empty; - // if !isFile we let the OS create function set the error value - break; - } - - if (m_isDir && pathIsDirectory(lpFileName)) - m_reroute = RerouteW::noReroute(lpFileName); - else - m_reroute = RerouteW::create(context, callContext, lpFileName); - - if (m_reroute.wasRerouted() && open == Open::create && pathIsDirectory(m_reroute.fileName())) - m_reroute = RerouteW::createNew(context, callContext, lpFileName, true, lpSecurityAttributes); - - if (!m_isDir && !isFile && !m_reroute.wasRerouted() && (open == Open::create || open == Open::empty)) - { - m_reroute = RerouteW::createNew(context, callContext, lpFileName, true, lpSecurityAttributes); - - bool newFile = !m_reroute.wasRerouted() && pathDirectlyAvailable(m_reroute.fileName()); - if (newFile && open == Open::empty) - // TRUNCATE_EXISTING will fail since the new file doesn't exist, so change disposition: - dwCreationDisposition = CREATE_ALWAYS; - } - - return true; - } - - // rerouteNew is used for rerouting the destination of copy/move operations. Assumes that the call will be skipped if false is returned. - bool rerouteNew(const usvfs::HookContext::ConstPtr &context, usvfs::HookCallContext &callContext, LPCWSTR lpFileName, bool replaceExisting, const char* hookName) - { - DWORD disposition = replaceExisting ? CREATE_ALWAYS : CREATE_NEW; - if (!rerouteCreate(context, callContext, lpFileName, disposition, GENERIC_WRITE, nullptr)) { - spdlog::get("hooks")->info( - "{} guaranteed failure, skipping original call: {}, replaceExisting={}, error={}", - hookName, ush::string_cast(lpFileName, ush::CodePage::UTF8), replaceExisting ? "true" : "false", error()); - - callContext.updateLastError(error()); - return false; - } - return true; - } - - void updateResult(usvfs::HookCallContext &callContext, bool success) - { - m_originalError = callContext.lastError(); - if (success) { - // m_error != ERROR_SUCCESS means we are overriding the error on success - if (m_error == ERROR_SUCCESS) - m_error = m_originalError; - } - else if (m_originalError == ERROR_PATH_NOT_FOUND && m_directlyAvailable) - m_error = ERROR_FILE_NOT_FOUND; - else - m_error = m_originalError; - if (m_error != m_originalError) - callContext.updateLastError(m_error); - } - - DWORD error() const { return m_error; } - DWORD originalError() const { return m_originalError; } - bool changedError() const { return m_error != m_originalError; } - - bool isDir() const { return m_isDir; } - bool newReroute() const { return m_reroute.newReroute(); } - bool wasRerouted() const { return m_reroute.wasRerouted(); } - LPCWSTR fileName() const { return m_reroute.fileName(); } - - void insertMapping(const usvfs::HookContext::Ptr &context, bool directory = false) { m_reroute.insertMapping(context, directory); } - - private: - DWORD m_error = ERROR_SUCCESS; - DWORD m_originalError = ERROR_SUCCESS; - bool m_directlyAvailable = false; - bool m_isDir = false; - RerouteW m_reroute; - }; -}; - -HANDLE WINAPI usvfs::hook_CreateFileW( - LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, - LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, - DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) -{ - HANDLE res = INVALID_HANDLE_VALUE; - - HOOK_START_GROUP(MutExHookGroup::OPEN_FILE) - - if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) { - res = ::CreateFileW(lpFileName, dwDesiredAccess, dwShareMode, - lpSecurityAttributes, dwCreationDisposition, - dwFlagsAndAttributes, hTemplateFile); - callContext.updateLastError(); - return res; - } - - if (dwFlagsAndAttributes & FILE_FLAG_DELETE_ON_CLOSE) { - spdlog::get("hooks")->warn("hook_CreateFileW: FILE_FLAG_DELETE_ON_CLOSE not supported"); - } - - DWORD originalDisposition = dwCreationDisposition; - CreateRerouter rerouter; - if (rerouter.rerouteCreate(READ_CONTEXT(), callContext, lpFileName, dwCreationDisposition, dwDesiredAccess, lpSecurityAttributes)) - { - PRE_REALCALL - res = ::CreateFileW(rerouter.fileName(), dwDesiredAccess, dwShareMode, - lpSecurityAttributes, dwCreationDisposition, - dwFlagsAndAttributes, hTemplateFile); - POST_REALCALL - rerouter.updateResult(callContext, res != INVALID_HANDLE_VALUE); - - if (res != INVALID_HANDLE_VALUE) { - if (rerouter.newReroute()) - rerouter.insertMapping(WRITE_CONTEXT()); - - if (rerouter.isDir() && rerouter.wasRerouted() && (dwFlagsAndAttributes & FILE_FLAG_BACKUP_SEMANTICS)) - { - // store the original search path for use during iteration - WRITE_CONTEXT() - ->customData(SearchHandles)[res] - = lpFileName; - } - } - - if (rerouter.wasRerouted() || rerouter.changedError() || originalDisposition != dwCreationDisposition) { - LOG_CALL() - .PARAMWRAP(lpFileName) - .PARAMWRAP(rerouter.fileName()) - .PARAMHEX(dwDesiredAccess) - .PARAMHEX(originalDisposition) - .PARAMHEX(dwCreationDisposition) - .PARAMHEX(dwFlagsAndAttributes) - .PARAMHEX(res) - .PARAM(rerouter.originalError()) - .PARAM(rerouter.error()); - } - } else { - spdlog::get("hooks")->info( - "hook_CreateFileW guaranteed failure, skipping original call: {}, disposition={}, access={}, error={}", - ush::string_cast(lpFileName, ush::CodePage::UTF8), - dwCreationDisposition, dwDesiredAccess, rerouter.error()); - - callContext.updateLastError(rerouter.error()); - } - - HOOK_END - - return res; -} - -HANDLE (WINAPI *usvfs::CreateFile2)(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, DWORD dwCreationDisposition, LPCREATEFILE2_EXTENDED_PARAMETERS pCreateExParams); - -HANDLE WINAPI usvfs::hook_CreateFile2(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, DWORD dwCreationDisposition, LPCREATEFILE2_EXTENDED_PARAMETERS pCreateExParams) -{ - HANDLE res = INVALID_HANDLE_VALUE; - - typedef HANDLE(WINAPI * CreateFile2_t)(LPCWSTR, DWORD, DWORD, DWORD, LPCREATEFILE2_EXTENDED_PARAMETERS); - - HOOK_START_GROUP(MutExHookGroup::OPEN_FILE) - - if (!callContext.active() || !RerouteW::interestingPath(lpFileName)) { - HANDLE res = CreateFile2(lpFileName, dwDesiredAccess, dwShareMode, dwCreationDisposition, pCreateExParams); - callContext.updateLastError(); - return res; - } - - if (pCreateExParams && (pCreateExParams->dwFileFlags & FILE_FLAG_DELETE_ON_CLOSE)) { - spdlog::get("hooks")->warn("hook_CreateFile2: FILE_FLAG_DELETE_ON_CLOSE not supported"); - } - - DWORD originalDisposition = dwCreationDisposition; - CreateRerouter rerouter; - if (rerouter.rerouteCreate(READ_CONTEXT(), callContext, lpFileName, dwCreationDisposition, dwDesiredAccess, - pCreateExParams ? pCreateExParams->lpSecurityAttributes : nullptr)) - { - PRE_REALCALL - res = CreateFile2(rerouter.fileName(), dwDesiredAccess, dwShareMode, dwCreationDisposition, pCreateExParams); - POST_REALCALL - rerouter.updateResult(callContext, res != INVALID_HANDLE_VALUE); - - if (res != INVALID_HANDLE_VALUE) { - if (rerouter.newReroute()) - rerouter.insertMapping(WRITE_CONTEXT()); - - if (rerouter.isDir() && rerouter.wasRerouted() - && pCreateExParams && (pCreateExParams->dwFileFlags & FILE_FLAG_BACKUP_SEMANTICS)) - { - // store the original search path for use during iteration - WRITE_CONTEXT() - ->customData(SearchHandles)[res] - = lpFileName; - } - } - - if (rerouter.wasRerouted() || rerouter.changedError() || originalDisposition != dwCreationDisposition) { - LOG_CALL() - .PARAMWRAP(lpFileName) - .PARAMWRAP(rerouter.fileName()) - .PARAMHEX(dwDesiredAccess) - .PARAMHEX(originalDisposition) - .PARAMHEX(dwCreationDisposition) - .PARAMHEX(res) - .PARAM(rerouter.originalError()) - .PARAM(rerouter.error()); - } - } - else { - spdlog::get("hooks")->info( - "hook_CreateFileW guaranteed failure, skipping original call: {}, disposition={}, access={}, error={}", - ush::string_cast(lpFileName, ush::CodePage::UTF8), - dwCreationDisposition, dwDesiredAccess, rerouter.error()); - - callContext.updateLastError(rerouter.error()); - } - - HOOK_END - - return res; -} - BOOL WINAPI usvfs::hook_GetFileAttributesExW( LPCWSTR lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId, LPVOID lpFileInformation) @@ -1327,7 +529,7 @@ BOOL WINAPI usvfs::hook_DeleteFileW(LPCWSTR lpFileName) } BOOL rewriteChangedDrives(LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName, - const RerouteW& readReroute, const usvfs::CreateRerouter& writeReroute) + const usvfs::RerouteW& readReroute, const usvfs::CreateRerouter& writeReroute) { return ((readReroute.wasRerouted() || writeReroute.wasRerouted()) && pathsOnDifferentDrives(readReroute.fileName(), writeReroute.fileName()) @@ -1608,7 +810,7 @@ BOOL WINAPI usvfs::hook_MoveFileWithProgressW(LPCWSTR lpExistingFileName, LPCWST } RerouteW readReroute; - CreateRerouter writeReroute; + usvfs::CreateRerouter writeReroute; bool callOriginal = true; DWORD newFlags = dwFlags; @@ -1701,7 +903,7 @@ BOOL WINAPI usvfs::hook_CopyFileExW(LPCWSTR lpExistingFileName, } RerouteW readReroute; - CreateRerouter writeReroute; + usvfs::CreateRerouter writeReroute; bool callOriginal = true; { diff --git a/src/usvfs_dll/hooks/kernel32.h b/src/usvfs_dll/hooks/kernel32.h index d9fac5cc..82ad1b87 100644 --- a/src/usvfs_dll/hooks/kernel32.h +++ b/src/usvfs_dll/hooks/kernel32.h @@ -6,12 +6,6 @@ namespace usvfs { -DLLEXPORT HANDLE WINAPI hook_CreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); -DLLEXPORT HANDLE WINAPI hook_CreateFileW(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); - -extern HANDLE (WINAPI *CreateFile2)(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, DWORD dwCreationDisposition, LPCREATEFILE2_EXTENDED_PARAMETERS pCreateExParams); -DLLEXPORT HANDLE WINAPI hook_CreateFile2(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, DWORD dwCreationDisposition, LPCREATEFILE2_EXTENDED_PARAMETERS pCreateExParams); - DLLEXPORT BOOL WINAPI hook_GetFileAttributesExW(LPCWSTR lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId, LPVOID lpFileInformation); DLLEXPORT DWORD WINAPI hook_GetFileAttributesW(LPCWSTR lpFileName); DLLEXPORT DWORD WINAPI hook_SetFileAttributesW(LPCWSTR lpFileName, DWORD dwFileAttributes); diff --git a/src/usvfs_dll/hooks/ntdll.cpp b/src/usvfs_dll/hooks/ntdll.cpp index ad88c8f2..b7badb91 100644 --- a/src/usvfs_dll/hooks/ntdll.cpp +++ b/src/usvfs_dll/hooks/ntdll.cpp @@ -3,6 +3,7 @@ #include #include "../hookcontext.h" #include "../hookcallcontext.h" +#include "../maptracker.h" #include "../stringcast_boost.h" #include #pragma warning(push, 3) @@ -49,11 +50,21 @@ using usvfs::UnicodeString; #define FILE_OVERWRITE_IF 0x00000005 #define FILE_MAXIMUM_DISPOSITION 0x00000005 -#define FILE_DELETE_ON_CLOSE 0x00001000 - template using unique_ptr_deleter = std::unique_ptr; +class RedirectionInfo { +public: + UnicodeString path; + bool redirected; + + RedirectionInfo() {} + RedirectionInfo(UnicodeString path, bool redirected) + : path(path) + , redirected(redirected) + {} +}; + class HandleTracker { public: using handle_type = HANDLE; @@ -150,19 +161,19 @@ std::ostream &operator<<(std::ostream &os, POBJECT_ATTRIBUTES attr) return operator<<(os, *attr->ObjectName); } -std::pair +RedirectionInfo applyReroute(const usvfs::HookContext::ConstPtr &context, const usvfs::HookCallContext &callContext, const UnicodeString &inPath) { - std::pair result; - result.first = inPath; - result.second = false; + RedirectionInfo result; + result.path = inPath; + result.redirected = false; if (callContext.active()) { // see if the file exists in the redirection tree std::string lookupPath = ush::string_cast( - static_cast(result.first) + 4, ush::CodePage::UTF8); + static_cast(result.path) + 4, ush::CodePage::UTF8); auto node = context->redirectionTable()->findNode(lookupPath.c_str()); // if so, replace the file name with the path to the mapped file if ((node.get() != nullptr) && (!node->data().linkTarget.empty() || node->isDirectory())) { @@ -178,20 +189,41 @@ applyReroute(const usvfs::HookContext::ConstPtr &context, reroutePath = ush::string_cast( node->path().c_str(), ush::CodePage::UTF8); - } + } if ((*reroutePath.rbegin() == L'\\') && (*lookupPath.rbegin() != '\\')) { reroutePath.resize(reroutePath.size() - 1); } std::replace(reroutePath.begin(), reroutePath.end(), L'/', L'\\'); if (reroutePath[1] == L'\\') reroutePath[1] = L'?'; - result.first = LR"(\??\)" + reroutePath; - result.second = true; + result.path = LR"(\??\)" + reroutePath; + result.redirected = true; } } return result; } +RedirectionInfo +applyReroute(const usvfs::CreateRerouter &rerouter) +{ + RedirectionInfo result; + result.path = rerouter.fileName(); + result.redirected = rerouter.wasRerouted(); + + std::wstring reroutePath(static_cast(result.path)); + std::replace(reroutePath.begin(), reroutePath.end(), L'/', L'\\'); + if (reroutePath[1] == L'\\') + reroutePath[1] = L'?'; + if (!((reroutePath[0] == L'\\') && + (reroutePath[1] == L'?') && + (reroutePath[2] == L'?') && + (reroutePath[3] == L'\\'))) { + result.path = LR"(\??\)" + reroutePath; + } + + return result; +} + struct FindCreateTarget { usvfs::RedirectionTree::NodePtrT target; void operator()(usvfs::RedirectionTree::NodePtrT node) @@ -229,8 +261,7 @@ findCreateTarget(const usvfs::HookContext::ConstPtr &context, return result; } - -std::pair +RedirectionInfo applyReroute(const usvfs::HookContext::ConstPtr &context, const usvfs::HookCallContext &callContext, POBJECT_ATTRIBUTES inAttributes) @@ -614,7 +645,7 @@ void gatherVirtualEntries(const UnicodeString &dirName, m = { ush::string_cast(subNode->path().c_str(), ush::CodePage::UTF8), vName }; } - + info.virtualMatches.push(m); info.foundFiles.insert(ush::to_upper(vName)); } @@ -1014,15 +1045,15 @@ NTSTATUS WINAPI usvfs::hook_NtQueryDirectoryFileEx( } unique_ptr_deleter -makeObjectAttributes(std::pair &redirInfo, +makeObjectAttributes(RedirectionInfo &redirInfo, POBJECT_ATTRIBUTES attributeTemplate) { - if (redirInfo.second) { + if (redirInfo.redirected) { unique_ptr_deleter result( new OBJECT_ATTRIBUTES, [](OBJECT_ATTRIBUTES *ptr) { delete ptr; }); memcpy(result.get(), attributeTemplate, sizeof(OBJECT_ATTRIBUTES)); result->RootDirectory = nullptr; - result->ObjectName = static_cast(redirInfo.first); + result->ObjectName = static_cast(redirInfo.path); return result; } else { // just reuse the template with a dummy deleter @@ -1045,10 +1076,6 @@ NTSTATUS ntdll_mess_NtOpenFile(PHANDLE FileHandle, HOOK_START_GROUP(MutExHookGroup::OPEN_FILE) - if (OpenOptions & FILE_DELETE_ON_CLOSE) { - spdlog::get("hooks")->warn("ntdll_mess_NtOpenFile: FILE_DELETE_ON_CLOSE not supported"); - } - bool storePath = false; if (((OpenOptions & FILE_DIRECTORY_FILE) != 0UL) && ((OpenOptions & FILE_OPEN_FOR_BACKUP_INTENT) != 0UL)) { @@ -1079,7 +1106,7 @@ NTSTATUS ntdll_mess_NtOpenFile(PHANDLE FileHandle, } try { - std::pair redir + RedirectionInfo redir = applyReroute(READ_CONTEXT(), callContext, fullName); unique_ptr_deleter adjustedAttributes = makeObjectAttributes(redir, ObjectAttributes); @@ -1096,7 +1123,7 @@ NTSTATUS ntdll_mess_NtOpenFile(PHANDLE FileHandle, #pragma message("need to clean up this handle in CloseHandle call") } - if (redir.second) { + if (redir.redirected) { LOG_CALL() .addParam("source", ObjectAttributes) .addParam("rerouted", adjustedAttributes.get()) @@ -1127,17 +1154,6 @@ NTSTATUS WINAPI usvfs::hook_NtOpenFile(PHANDLE FileHandle, return res; } -bool fileExists(POBJECT_ATTRIBUTES attributes) -{ - UnicodeString temp = CreateUnicodeString(attributes); - return RtlDoesFileExists_U(static_cast(temp)) == TRUE; -} - -bool fileExists(const UnicodeString &filename) -{ - return RtlDoesFileExists_U(static_cast(filename)) == TRUE; -} - NTSTATUS ntdll_mess_NtCreateFile( PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock, @@ -1147,9 +1163,10 @@ NTSTATUS ntdll_mess_NtCreateFile( { using namespace usvfs; + NTSTATUS res = STATUS_NO_SUCH_FILE; + PreserveGetLastError ntFunctionsDoNotChangeGetLastError; - NTSTATUS res = STATUS_NO_SUCH_FILE; HOOK_START_GROUP(MutExHookGroup::OPEN_FILE) if (!callContext.active()) { return ::NtCreateFile(FileHandle, DesiredAccess, ObjectAttributes, @@ -1158,11 +1175,8 @@ NTSTATUS ntdll_mess_NtCreateFile( EaBuffer, EaLength); } - if (CreateOptions & FILE_DELETE_ON_CLOSE) { - spdlog::get("hooks")->warn("ntdll_mess_NtCreateFile: FILE_DELETE_ON_CLOSE not supported"); - } - UnicodeString inPath = CreateUnicodeString(ObjectAttributes); + LPCWSTR inPathW = static_cast(inPath); if (inPath.size() == 0) { spdlog::get("hooks")->info( @@ -1174,58 +1188,80 @@ NTSTATUS ntdll_mess_NtCreateFile( EaBuffer, EaLength); } - std::pair redir(UnicodeString(), false); - - { // limit context scope - FunctionGroupLock lock(MutExHookGroup::ALL_GROUPS); - HookContext::ConstPtr context = READ_CONTEXT(); + DWORD convertedDisposition = OPEN_EXISTING; + switch(CreateDisposition) { + case FILE_SUPERSEDE: convertedDisposition = CREATE_ALWAYS; break; + case FILE_OPEN: convertedDisposition = OPEN_EXISTING; break; + case FILE_CREATE: convertedDisposition = CREATE_NEW; break; + case FILE_OPEN_IF: convertedDisposition = OPEN_ALWAYS; break; + case FILE_OVERWRITE: convertedDisposition = TRUNCATE_EXISTING; break; + case FILE_OVERWRITE_IF: convertedDisposition = CREATE_ALWAYS; break; + default: spdlog::get("hooks")->error("invalid disposition: {0}", CreateDisposition); break; + } - redir = applyReroute(context, callContext, inPath); - - // TODO would be neat if this could (optionally) reroute all potential write - // accesses to the create target. - // This could be achived by copying the file to the target here in case - // the createdisposition or the requested access rights make that - // necessary - if (((CreateDisposition == FILE_SUPERSEDE) - || (CreateDisposition == FILE_CREATE) - || (CreateDisposition == FILE_OPEN_IF) - || (CreateDisposition == FILE_OVERWRITE_IF)) - && !redir.second && !fileExists(inPath)) { - // the file will be created so now we need to know where - std::pair createTarget - = findCreateTarget(context, inPath); - - if (createTarget.second.size() != 0) { - // there is a reroute target for new files so adjust the path - redir.first = LR"(\??)"; // appendPath will add the second '\' - redir.first.appendPath(static_cast(createTarget.second)); - - spdlog::get("hooks")->info( - "reroute write access: {}", - ush::string_cast(static_cast(redir.first)) - .c_str()); - } + DWORD convertedAccess = 0; + if ((DesiredAccess & FILE_GENERIC_READ) == FILE_GENERIC_READ) convertedAccess |= GENERIC_READ; + if ((DesiredAccess & FILE_GENERIC_WRITE) == FILE_GENERIC_WRITE) convertedAccess |= GENERIC_WRITE; + if ((DesiredAccess & FILE_GENERIC_EXECUTE) == FILE_GENERIC_EXECUTE) convertedAccess |= GENERIC_EXECUTE; + if ((DesiredAccess & FILE_ALL_ACCESS) == FILE_ALL_ACCESS) convertedAccess |= GENERIC_ALL; + + ULONG originalDisposition = CreateDisposition; + CreateRerouter rerouter; + if (rerouter.rerouteCreate(READ_CONTEXT(), callContext, inPathW, convertedDisposition, + convertedAccess, (LPSECURITY_ATTRIBUTES)ObjectAttributes->SecurityDescriptor)) { + switch(convertedDisposition) { + case CREATE_NEW: CreateDisposition = FILE_CREATE; break; + case CREATE_ALWAYS: if (CreateDisposition != FILE_SUPERSEDE) CreateDisposition = FILE_OVERWRITE_IF; break; + case OPEN_EXISTING: CreateDisposition = FILE_OPEN; break; + case OPEN_ALWAYS: CreateDisposition = FILE_OPEN_IF; break; + case TRUNCATE_EXISTING: CreateDisposition = FILE_OVERWRITE; break; } - } - unique_ptr_deleter adjustedAttributes + RedirectionInfo redir = applyReroute(rerouter); + + unique_ptr_deleter adjustedAttributes = makeObjectAttributes(redir, ObjectAttributes); - PRE_REALCALL - res = ::NtCreateFile(FileHandle, DesiredAccess, adjustedAttributes.get(), - IoStatusBlock, AllocationSize, FileAttributes, - ShareAccess, CreateDisposition, CreateOptions, EaBuffer, - EaLength); - POST_REALCALL + PRE_REALCALL + res = ::NtCreateFile(FileHandle, DesiredAccess, adjustedAttributes.get(), + IoStatusBlock, AllocationSize, FileAttributes, + ShareAccess, CreateDisposition, CreateOptions, EaBuffer, + EaLength); + POST_REALCALL + rerouter.updateResult(callContext, res == STATUS_SUCCESS); - if (redir.second) { - LOG_CALL() - .addParam("source", ObjectAttributes) - .addParam("rerouted", adjustedAttributes.get()) - .PARAM(CreateDisposition) - .PARAM(*FileHandle) - .PARAMWRAP(res); + if (res == STATUS_SUCCESS) { + if (rerouter.newReroute()) + rerouter.insertMapping(WRITE_CONTEXT()); + + if (rerouter.isDir() && rerouter.wasRerouted() && ((FileAttributes & FILE_OPEN_FOR_BACKUP_INTENT) == FILE_OPEN_FOR_BACKUP_INTENT)) { + // store the original search path for use during iteration + WRITE_CONTEXT() + ->customData(SearchHandles)[*FileHandle] = inPathW; + } + } + + if (rerouter.wasRerouted() || rerouter.changedError() || originalDisposition != CreateDisposition) { + LOG_CALL() + .PARAMWRAP(inPathW) + .PARAMWRAP(rerouter.fileName()) + .PARAMHEX(DesiredAccess) + .PARAMHEX(originalDisposition) + .PARAMHEX(CreateDisposition) + .PARAMHEX(FileAttributes) + .PARAMHEX(res) + .PARAMHEX(*FileHandle) + .PARAM(rerouter.originalError()) + .PARAM(rerouter.error()); + } + } else { + // make the original call to set up the proper errors and return statuses + PRE_REALCALL + res = ::NtCreateFile(FileHandle, DesiredAccess, ObjectAttributes, + IoStatusBlock, AllocationSize, FileAttributes, + ShareAccess, CreateDisposition, CreateOptions, EaBuffer, + EaLength); + POST_REALCALL } HOOK_END @@ -1246,7 +1282,6 @@ NTSTATUS WINAPI usvfs::hook_NtCreateFile( return res; } - NTSTATUS WINAPI usvfs::hook_NtClose(HANDLE Handle) { PreserveGetLastError ntFunctionsDoNotChangeGetLastError; @@ -1308,7 +1343,7 @@ NTSTATUS WINAPI usvfs::hook_NtQueryAttributesFile( UnicodeString inPath = CreateUnicodeString(ObjectAttributes); - std::pair redir + RedirectionInfo redir = applyReroute(READ_CONTEXT(), callContext, inPath); unique_ptr_deleter adjustedAttributes = makeObjectAttributes(redir, ObjectAttributes); @@ -1317,7 +1352,7 @@ NTSTATUS WINAPI usvfs::hook_NtQueryAttributesFile( res = ::NtQueryAttributesFile(adjustedAttributes.get(), FileInformation); POST_REALCALL - if (redir.second) { + if (redir.redirected) { LOG_CALL() .addParam("source", ObjectAttributes) .addParam("rerouted", adjustedAttributes.get()) @@ -1350,7 +1385,7 @@ NTSTATUS WINAPI usvfs::hook_NtQueryFullAttributesFile( return ::NtQueryFullAttributesFile(ObjectAttributes, FileInformation); } - std::pair redir + RedirectionInfo redir = applyReroute(READ_CONTEXT(), callContext, inPath); unique_ptr_deleter adjustedAttributes = makeObjectAttributes(redir, ObjectAttributes); @@ -1359,7 +1394,7 @@ NTSTATUS WINAPI usvfs::hook_NtQueryFullAttributesFile( res = ::NtQueryFullAttributesFile(adjustedAttributes.get(), FileInformation); POST_REALCALL - if (redir.second) { + if (redir.redirected) { LOG_CALL() .addParam("source", ObjectAttributes) .addParam("rerouted", adjustedAttributes.get()) diff --git a/src/usvfs_dll/maptracker.h b/src/usvfs_dll/maptracker.h new file mode 100644 index 00000000..b90fd88c --- /dev/null +++ b/src/usvfs_dll/maptracker.h @@ -0,0 +1,650 @@ +#pragma once + +#include "windows_sane.h" + +#include +#include +#include + +#include "hookcontext.h" +#include "hookcallcontext.h" +#include "stringcast_basic.h" + +namespace usvfs { + +// returns true iff the path exists (checks only real paths) +static inline bool pathExists(LPCWSTR fileName) +{ + FunctionGroupLock lock(MutExHookGroup::FILE_ATTRIBUTES); + DWORD attrib = GetFileAttributesW(fileName); + return attrib != INVALID_FILE_ATTRIBUTES; +} + +// returns true iff the path exists and is a file (checks only real paths) +static inline bool pathIsFile(LPCWSTR fileName) +{ + FunctionGroupLock lock(MutExHookGroup::FILE_ATTRIBUTES); + DWORD attrib = GetFileAttributesW(fileName); + return attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) == 0; +} + +// returns true iff the path exists and is a file (checks only real paths) +static inline bool pathIsDirectory(LPCWSTR fileName) +{ + FunctionGroupLock lock(MutExHookGroup::FILE_ATTRIBUTES); + DWORD attrib = GetFileAttributesW(fileName); + return attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY); +} + +// returns true iff the path does not exist but it parent directory does (checks only real paths) +static inline bool pathDirectlyAvailable(LPCWSTR pathName) +{ + FunctionGroupLock lock(MutExHookGroup::FILE_ATTRIBUTES); + DWORD attrib = GetFileAttributesW(pathName); + return attrib == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; +} + +class MapTracker { +public: + std::wstring lookup(const std::wstring& fromPath) const { + if (!fromPath.empty()) + { + std::shared_lock lock(m_mutex); + auto find = m_map.find(fromPath); + if (find != m_map.end()) + return find->second; + } + return std::wstring(); + } + + bool contains(const std::wstring& fromPath) const { + if (!fromPath.empty()) + { + std::shared_lock lock(m_mutex); + auto find = m_map.find(fromPath); + if (find != m_map.end()) + return true; + } + return false; + } + + void insert(const std::wstring& fromPath, const std::wstring& toPath) { + if (fromPath.empty()) + return; + std::unique_lock lock(m_mutex); + m_map[fromPath] = toPath; + } + + bool erase(const std::wstring& fromPath) + { + if (fromPath.empty()) + return false; + std::unique_lock lock(m_mutex); + return m_map.erase(fromPath); + } + +private: + mutable std::shared_mutex m_mutex; + std::unordered_map m_map; +}; + +extern MapTracker k32DeleteTracker; +extern MapTracker k32FakeDirTracker; + +class RerouteW +{ + std::wstring m_Buffer{}; + std::wstring m_RealPath{}; + bool m_Rerouted{false}; + LPCWSTR m_FileName{nullptr}; + bool m_PathCreated{false}; + bool m_NewReroute{false}; + + RedirectionTree::NodePtrT m_FileNode; + +public: + RerouteW() = default; + + RerouteW(RerouteW &&reference) + : m_Buffer(std::move(reference.m_Buffer)) + , m_RealPath(std::move(reference.m_RealPath)) + , m_Rerouted(reference.m_Rerouted) + , m_PathCreated(reference.m_PathCreated) + , m_NewReroute(reference.m_NewReroute) + , m_FileNode(std::move(reference.m_FileNode)) + { + m_FileName = reference.m_FileName != nullptr ? m_Buffer.c_str() : nullptr; + reference.m_FileName = nullptr; + } + + RerouteW &operator=(RerouteW &&reference) + { + m_Buffer = std::move(reference.m_Buffer); + m_RealPath = std::move(reference.m_RealPath); + m_Rerouted = reference.m_Rerouted; + m_PathCreated = reference.m_PathCreated; + m_NewReroute = reference.m_NewReroute; + m_FileName = reference.m_FileName != nullptr ? m_Buffer.c_str() : nullptr; + m_FileNode = std::move(reference.m_FileNode); + return *this; + } + + RerouteW(const RerouteW &reference) = delete; + RerouteW &operator=(const RerouteW &) = delete; + + LPCWSTR fileName() const + { + return m_FileName; + } + + const std::wstring &buffer() const + { + return m_Buffer; + } + + bool wasRerouted() const + { + return m_Rerouted; + } + + bool newReroute() const + { + return m_NewReroute; + } + + void insertMapping(const HookContext::Ptr &context, bool directory = false) + { + if (directory) + { + addDirectoryMapping(context, m_RealPath, m_FileName); + + // In case we have just created a "fake" directory, it is no longer fake and need to remove it and all its + // parent folders from the fake map: + std::wstring dir = m_FileName; + while (k32FakeDirTracker.erase(dir)) + dir = fs::path(dir).parent_path().wstring(); + } + else + { + //if (m_PathCreated) + //addDirectoryMapping(context, fs::path(m_RealPath).parent_path(), fs::path(m_FileName).parent_path()); + + spdlog::get("hooks")->info("mapping file in vfs: {}, {}", + shared::string_cast(m_RealPath, shared::CodePage::UTF8), + shared::string_cast(m_FileName, shared::CodePage::UTF8)); + m_FileNode = + context->redirectionTable().addFile(m_RealPath, RedirectionDataLocal(shared::string_cast(m_FileName, shared::CodePage::UTF8))); + + k32DeleteTracker.erase(m_RealPath); + } + } + + void removeMapping(const HookContext::ConstPtr &readContext, bool directory = false) + { + bool addToDelete = false; + bool dontAddToDelete = false; + + // We need to track deleted files even if they were not rerouted (i.e. files deleted from the real folder which there is + // a virtualized mapped folder on top of it). Since we don't want to add, *every* file which is deleted we check this: + bool found = wasRerouted(); + if (!found) { + FindCreateTarget visitor; + RedirectionTree::VisitorFunction visitorWrapper = + [&](const RedirectionTree::NodePtrT &node) { visitor(node); }; + readContext->redirectionTable()->visitPath(m_RealPath, visitorWrapper); + if (visitor.target.get()) + found = true; + } + if (found) + addToDelete = true; + + if (wasRerouted()) { + if (m_FileNode.get()) + m_FileNode->removeFromTree(); + else + spdlog::get("usvfs")->warn("Node not removed: {}", shared::string_cast(m_FileName)); + + if (!directory) + { + // check if this file was the last file inside a "fake" directory then remove it + // and possibly also its fake empty parent folders: + std::wstring parent = m_FileName; + while (true) + { + parent = fs::path(parent).parent_path().wstring(); + if (k32FakeDirTracker.contains(parent)) + { + dontAddToDelete = true; + if (RemoveDirectoryW(parent.c_str())) { + k32FakeDirTracker.erase(parent); + spdlog::get("usvfs")->info("removed empty fake directory: {}", shared::string_cast(parent)); + } + else if (GetLastError() != ERROR_DIR_NOT_EMPTY) { + auto error = GetLastError(); + spdlog::get("usvfs")->warn("removing fake directory failed: {}, error={}", shared::string_cast(parent), error); + break; + } + } + else + break; + } + } + } + if (addToDelete && !dontAddToDelete) { + k32DeleteTracker.insert(m_RealPath, m_FileName); + } + } + + static bool createFakePath(fs::path path, LPSECURITY_ATTRIBUTES securityAttributes) + { + // sanity and guaranteed recursion end: + if (!path.has_relative_path()) + throw shared::windows_error("createFakePath() refusing to create non-existing top level path: " + path.string()); + + DWORD attr = GetFileAttributesW(path.c_str()); + DWORD err = GetLastError(); + if (attr != INVALID_FILE_ATTRIBUTES) { + if (attr & FILE_ATTRIBUTE_DIRECTORY) + return false; // if directory already exists all is good + else + throw shared::windows_error("createFakePath() called on a file: " + path.string()); + } + if (err != ERROR_FILE_NOT_FOUND && err != ERROR_PATH_NOT_FOUND) + throw shared::windows_error("createFakePath() GetFileAttributesW failed on: " + path.string(), err); + + if (err != ERROR_FILE_NOT_FOUND) // ERROR_FILE_NOT_FOUND means parent directory already exists + createFakePath(path.parent_path(), securityAttributes); // otherwise create parent directory (recursively) + + BOOL res = CreateDirectoryW(path.c_str(), securityAttributes); + if (res) + k32FakeDirTracker.insert(path.wstring(), std::wstring()); + else { + err = GetLastError(); + throw shared::windows_error("createFakePath() CreateDirectoryW failed on: " + path.string(), err); + } + return true; + } + + static bool addDirectoryMapping(const HookContext::Ptr &context, const fs::path& originalPath, const fs::path& reroutedPath) + { + if (originalPath.empty() || reroutedPath.empty()) { + spdlog::get("hooks")->error("RerouteW::addDirectoryMapping failed: {}, {}", + shared::string_cast(originalPath.wstring(), shared::CodePage::UTF8).c_str(), + shared::string_cast(reroutedPath.wstring(), shared::CodePage::UTF8).c_str()); + return false; + } + + auto lookupParent = context->redirectionTable()->findNode(originalPath.parent_path()); + if (!lookupParent.get() || lookupParent->data().linkTarget.empty()) { + if (!addDirectoryMapping(context, originalPath.parent_path(), reroutedPath.parent_path())) + { + spdlog::get("hooks")->error("RerouteW::addDirectoryMapping failed: {}, {}", + shared::string_cast(originalPath.wstring(), shared::CodePage::UTF8).c_str(), + shared::string_cast(reroutedPath.wstring(), shared::CodePage::UTF8).c_str()); + return false; + } + } + + std::string reroutedU8 + = shared::string_cast(reroutedPath.wstring(), shared::CodePage::UTF8); + if (reroutedU8.empty() || reroutedU8[reroutedU8.size() - 1] != '\\') + reroutedU8 += "\\"; + + spdlog::get("hooks")->info("mapping directory in vfs: {}, {}", + shared::string_cast(originalPath.wstring(), shared::CodePage::UTF8), reroutedU8.c_str()); + + context->redirectionTable().addDirectory( + originalPath, RedirectionDataLocal(reroutedU8), + shared::FLAG_DIRECTORY|shared::FLAG_CREATETARGET); + + fs::directory_iterator end_itr; + + // cycle through the directory + for (fs::directory_iterator itr(reroutedPath); itr != end_itr; ++itr) + { + // If it's not a directory, add it to the VFS, if it is recurse into it + if (is_regular_file(itr->path())) { + std::string fileReroutedU8 = shared::string_cast(itr->path().wstring(), shared::CodePage::UTF8); + spdlog::get("hooks")->info("mapping file in vfs: {}, {}", + shared::string_cast((originalPath / itr->path().filename()).wstring(), shared::CodePage::UTF8), + fileReroutedU8.c_str()); + context->redirectionTable().addFile(fs::path(originalPath / itr->path().filename()), RedirectionDataLocal(fileReroutedU8)); + } else { + addDirectoryMapping(context, originalPath / itr->path().filename(), reroutedPath / itr->path().filename()); + } + } + + return true; + } + + template + static bool interestingPathImpl(const char_t *inPath) + { + if (!inPath || !inPath[0]) + return false; + // ignore \\.\ unless its a \\.\?: + if (inPath[0] == '\\' && inPath[1] == '\\' && inPath[2] == '.' && inPath[3] == '\\' && (!inPath[4] || inPath[5] != ':')) + return false; + // ignore L"hid#": + if ((inPath[0] == 'h' || inPath[0] == 'H') + && ((inPath[1] == 'i' || inPath[1] == 'I')) + && ((inPath[2] == 'd' || inPath[2] == 'D')) + && inPath[3] == '#') + return false; + return true; + } + + static bool interestingPath(const char* inPath) { return interestingPathImpl(inPath); } + static bool interestingPath(const wchar_t* inPath) { return interestingPathImpl(inPath); } + + static fs::path absolutePath(const wchar_t *inPath) + { + if (shared::startswith(inPath, LR"(\\?\)") || shared::startswith(inPath, LR"(\??\)")) { + inPath += 4; + return inPath; + } + else if ((shared::startswith(inPath, LR"(\\localhost\)") || shared::startswith(inPath, LR"(\\127.0.0.1\)")) && inPath[13] == L'$') { + std::wstring newPath; + newPath += towupper(inPath[12]); + newPath += L':'; + newPath += &inPath[14]; + return newPath; + } + else if (inPath[0] == L'\0' || inPath[1] == L':') { + return inPath; + } + else if (inPath[0] == L'\\' || inPath[0] == L'/') { + return fs::path(winapi::wide::getFullPathName(inPath).first); + } + WCHAR currentDirectory[MAX_PATH]; + ::GetCurrentDirectoryW(MAX_PATH, currentDirectory); + fs::path finalPath = fs::path(currentDirectory) / inPath; + return finalPath; + //winapi::wide::getFullPathName(inPath).first; + } + + static fs::path canonizePath(const fs::path& inPath) + { + fs::path p = inPath.lexically_normal(); + if (p.filename_is_dot()) + p = p.remove_filename(); + return p.make_preferred(); + } + + static RerouteW create(const HookContext::ConstPtr &context, + const HookCallContext &callContext, + const wchar_t *inPath, bool inverse = false) + { + RerouteW result; + + if (interestingPath(inPath) && callContext.active()) + { + const auto& lookupPath = canonizePath(absolutePath(inPath)); + result.m_RealPath = lookupPath.wstring(); + + result.m_Buffer = k32DeleteTracker.lookup(result.m_RealPath); + bool found = !result.m_Buffer.empty(); + if (found) { + spdlog::get("hooks")->info("Rerouting file open to location of deleted file: {}", + shared::string_cast(result.m_Buffer)); + result.m_NewReroute = true; + } else { + const RedirectionTreeContainer &table + = inverse ? context->inverseTable() : context->redirectionTable(); + result.m_FileNode = table->findNode(lookupPath); + + if (result.m_FileNode.get() + && (!result.m_FileNode->data().linkTarget.empty() || result.m_FileNode->isDirectory())) + { + if (!result.m_FileNode->data().linkTarget.empty()) { + result.m_Buffer = shared::string_cast( + result.m_FileNode->data().linkTarget.c_str(), shared::CodePage::UTF8); + } + else + { + result.m_Buffer = result.m_FileNode->path().wstring(); + } + found = true; + } + } + if (found) { + result.m_Rerouted = true; + + wchar_t inIt = inPath[wcslen(inPath) - 1]; + std::wstring::iterator outIt = result.m_Buffer.end() - 1; + if ((*outIt == L'\\' || *outIt == L'/') && !(inIt == L'\\' || inIt == L'/')) + result.m_Buffer.erase(outIt); + std::replace(result.m_Buffer.begin(), result.m_Buffer.end(), L'/', L'\\'); + } + else + result.m_Buffer = inPath; + } + else if (inPath) + result.m_Buffer = inPath; + + if (inPath) + result.m_FileName = result.m_Buffer.c_str(); + return result; + } + + static RerouteW createNew(const HookContext::ConstPtr &context, + const HookCallContext &callContext, + LPCWSTR inPath, bool createPath = true, + LPSECURITY_ATTRIBUTES securityAttributes = nullptr) + { + RerouteW result; + + if (interestingPath(inPath) && callContext.active()) + { + const auto& lookupPath = canonizePath(absolutePath(inPath)); + result.m_RealPath = lookupPath.wstring(); + + result.m_Buffer = k32DeleteTracker.lookup(result.m_RealPath); + bool found = !result.m_Buffer.empty(); + if (found) + spdlog::get("hooks")->info("Rerouting file creation to original location of deleted file: {}", + shared::string_cast(result.m_Buffer)); + else + { + FindCreateTarget visitor; + RedirectionTree::VisitorFunction visitorWrapper = + [&](const RedirectionTree::NodePtrT &node) { visitor(node); }; + context->redirectionTable()->visitPath(lookupPath, visitorWrapper); + if (visitor.target.get()) { + // the visitor has found the last (deepest in the directory hierarchy) + // create-target + fs::path relativePath + = shared::make_relative(visitor.target->path(), lookupPath); + result.m_Buffer = + (fs::path(visitor.target->data().linkTarget.c_str()) / relativePath).wstring(); + found = true; + } + } + + if (found) + { + if (createPath) { + try { + FunctionGroupLock lock(MutExHookGroup::ALL_GROUPS); + result.m_PathCreated = + createFakePath(fs::path(result.m_Buffer).parent_path(), securityAttributes); + } + catch (const std::exception &e) { + spdlog::get("hooks")->error("failed to create {}: {}", + shared::string_cast(result.m_Buffer), e.what()); + } + } + + wchar_t inIt = inPath[wcslen(inPath) - 1]; + std::wstring::iterator outIt = result.m_Buffer.end() - 1; + if ((*outIt == L'\\' || *outIt == L'/') && !(inIt == L'\\' || inIt == L'/')) + result.m_Buffer.erase(outIt); + std::replace(result.m_Buffer.begin(), result.m_Buffer.end(), L'/', L'\\'); + result.m_Rerouted = true; + result.m_NewReroute = true; + } + else + result.m_Buffer = inPath; + } + else if (inPath) + result.m_Buffer = inPath; + + if (inPath) + result.m_FileName = result.m_Buffer.c_str(); + return result; + } + + static RerouteW createOrNew(const HookContext::ConstPtr &context, const HookCallContext &callContext, + LPCWSTR inPath, bool createPath = true, LPSECURITY_ATTRIBUTES securityAttributes = nullptr) + { + { + auto res = create(context, callContext, inPath); + if (res.wasRerouted() || !interestingPath(inPath) || !callContext.active() || pathExists(inPath)) + return std::move(res); + } + return createNew(context, callContext, inPath, createPath, securityAttributes); + } + + static RerouteW noReroute(LPCWSTR inPath) + { + RerouteW result; + if (inPath) + result.m_Buffer = inPath; + if (inPath && inPath[0] && !shared::startswith(inPath, L"hid#")) + std::replace(result.m_Buffer.begin(), result.m_Buffer.end(), L'/', L'\\'); + result.m_FileName = result.m_Buffer.c_str(); + return result; + } + +private: + struct FindCreateTarget { + RedirectionTree::NodePtrT target; + void operator()(RedirectionTree::NodePtrT node) + { + if (node->hasFlag(shared::FLAG_CREATETARGET)) { + target = node; + } + } + }; +}; + +class CreateRerouter { +public: + bool rerouteCreate(const HookContext::ConstPtr &context, const HookCallContext &callContext, + LPCWSTR lpFileName, DWORD& dwCreationDisposition, DWORD dwDesiredAccess, LPSECURITY_ATTRIBUTES lpSecurityAttributes) + { + enum class Open { existing, create, empty }; + Open open = Open::existing; + + // Notice since we are calling our patched GetFileAttributesW here this will also check virtualized paths + DWORD virtAttr = GetFileAttributesW(lpFileName); + bool isFile = virtAttr != INVALID_FILE_ATTRIBUTES && (virtAttr & FILE_ATTRIBUTE_DIRECTORY) == 0; + m_isDir = virtAttr != INVALID_FILE_ATTRIBUTES && (virtAttr & FILE_ATTRIBUTE_DIRECTORY); + + switch (dwCreationDisposition) { + case CREATE_ALWAYS: + open = Open::create; + if (isFile || m_isDir) { + m_error = ERROR_ALREADY_EXISTS; + } + break; + + case CREATE_NEW: + if (isFile || m_isDir) { + m_error = ERROR_FILE_EXISTS; + return false; + } else { + open = Open::create; + } + break; + + case OPEN_ALWAYS: + if (isFile || m_isDir) { + m_error = ERROR_ALREADY_EXISTS; + } else { + open = Open::create; + } + break; + + case TRUNCATE_EXISTING: + if ((dwDesiredAccess & GENERIC_WRITE) == 0) { + m_error = ERROR_INVALID_PARAMETER; + return false; + } + if (isFile || m_isDir) + open = Open::empty; + break; + } + + if (m_isDir && pathIsDirectory(lpFileName)) + m_reroute = RerouteW::noReroute(lpFileName); + else + m_reroute = RerouteW::create(context, callContext, lpFileName); + + if (m_reroute.wasRerouted() && open == Open::create && pathIsDirectory(m_reroute.fileName())) + m_reroute = RerouteW::createNew(context, callContext, lpFileName, true, lpSecurityAttributes); + + if (!m_isDir && !isFile && !m_reroute.wasRerouted() && (open == Open::create || open == Open::empty)) + { + m_reroute = RerouteW::createNew(context, callContext, lpFileName, true, lpSecurityAttributes); + + bool newFile = !m_reroute.wasRerouted() && pathDirectlyAvailable(m_reroute.fileName()); + if (newFile && open == Open::empty) + // TRUNCATE_EXISTING will fail since the new file doesn't exist, so change disposition: + dwCreationDisposition = CREATE_ALWAYS; + } + + return true; + } + + // rerouteNew is used for rerouting the destination of copy/move operations. Assumes that the call will be skipped if false is returned. + bool rerouteNew(const HookContext::ConstPtr &context, HookCallContext &callContext, LPCWSTR lpFileName, bool replaceExisting, const char* hookName) + { + DWORD disposition = replaceExisting ? CREATE_ALWAYS : CREATE_NEW; + if (!rerouteCreate(context, callContext, lpFileName, disposition, GENERIC_WRITE, nullptr)) { + spdlog::get("hooks")->info( + "{} guaranteed failure, skipping original call: {}, replaceExisting={}, error={}", + hookName, shared::string_cast(lpFileName, shared::CodePage::UTF8), replaceExisting ? "true" : "false", error()); + + callContext.updateLastError(error()); + return false; + } + return true; + } + + void updateResult(HookCallContext &callContext, bool success) + { + m_originalError = callContext.lastError(); + if (success) { + // m_error != ERROR_SUCCESS means we are overriding the error on success + if (m_error == ERROR_SUCCESS) + m_error = m_originalError; + } + else if (m_originalError == ERROR_PATH_NOT_FOUND && m_directlyAvailable) + m_error = ERROR_FILE_NOT_FOUND; + else + m_error = m_originalError; + if (m_error != m_originalError) + callContext.updateLastError(m_error); + } + + DWORD error() const { return m_error; } + DWORD originalError() const { return m_originalError; } + bool changedError() const { return m_error != m_originalError; } + + bool isDir() const { return m_isDir; } + bool newReroute() const { return m_reroute.newReroute(); } + bool wasRerouted() const { return m_reroute.wasRerouted(); } + LPCWSTR fileName() const { return m_reroute.fileName(); } + + void insertMapping(const HookContext::Ptr &context, bool directory = false) { m_reroute.insertMapping(context, directory); } + +private: + DWORD m_error = ERROR_SUCCESS; + DWORD m_originalError = ERROR_SUCCESS; + bool m_directlyAvailable = false; + bool m_isDir = false; + RerouteW m_reroute; +}; + +} // namespace usvfs diff --git a/test/tvfs_test/main.cpp b/test/tvfs_test/main.cpp index 031d6f7d..7629c280 100644 --- a/test/tvfs_test/main.cpp +++ b/test/tvfs_test/main.cpp @@ -160,6 +160,7 @@ TEST_F(USVFSTest, CanResizeRedirectiontree) }); } +/* TEST_F(USVFSTest, CreateFileHookReportsCorrectErrorOnMissingFile) { EXPECT_NO_THROW({ @@ -178,7 +179,9 @@ TEST_F(USVFSTest, CreateFileHookReportsCorrectErrorOnMissingFile) EXPECT_EQ(ERROR_FILE_NOT_FOUND, ::GetLastError()); }); } +*/ +/* TEST_F(USVFSTestWithReroute, CreateFileHookRedirectsFile) { EXPECT_NE(INVALID_HANDLE_VALUE @@ -190,6 +193,7 @@ TEST_F(USVFSTestWithReroute, CreateFileHookRedirectsFile) , FILE_ATTRIBUTE_NORMAL , nullptr)); } +*/ TEST_F(USVFSTest, GetFileAttributesHookReportsCorrectErrorOnMissingFile) diff --git a/vsbuild/usvfs_dll.vcxproj b/vsbuild/usvfs_dll.vcxproj index 84a7ff89..1973e02e 100644 --- a/vsbuild/usvfs_dll.vcxproj +++ b/vsbuild/usvfs_dll.vcxproj @@ -226,6 +226,7 @@ + diff --git a/vsbuild/usvfs_dll.vcxproj.filters b/vsbuild/usvfs_dll.vcxproj.filters index 01ffd7ce..407a9c5f 100644 --- a/vsbuild/usvfs_dll.vcxproj.filters +++ b/vsbuild/usvfs_dll.vcxproj.filters @@ -103,5 +103,8 @@ Header Files\include + + Header Files + \ No newline at end of file