/*
Userspace Virtual Filesystem
Copyright (C) 2015 Sebastian Herbord. All rights reserved.
This file is part of usvfs.
usvfs is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
usvfs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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 .
*/
#include "usvfs.h"
#include "hookmanager.h"
#include "loghelpers.h"
#include "redirectiontree.h"
#include "usvfs_version.h"
#include "usvfsparametersprivate.h"
#include
#include
#include
#include
#include
#include
#include
// note that there's a mix of boost and std filesystem stuff in this file and
// that they're not completely compatible
#include
namespace bfs = boost::filesystem;
namespace ush = usvfs::shared;
namespace bip = boost::interprocess;
namespace ba = boost::algorithm;
using usvfs::log::ConvertLogLevel;
usvfs::HookManager* manager = nullptr;
usvfs::HookContext* context = nullptr;
HMODULE dllModule = nullptr;
PVOID exceptionHandler = nullptr;
CrashDumpsType usvfs_dump_type = CrashDumpsType::None;
std::wstring usvfs_dump_path;
// this is called for every single file, so it's a bit long winded, but it's
// as fast as it gets, probably
//
template
bool extensionMatchesCI(
std::string_view name,
const std::array& extensionsLC,
const std::array& extensionsUC)
{
constexpr std::size_t longestExtensionWithDot = LongestExtension + 1;
// quick check
if (name.size() < longestExtensionWithDot) {
return false;
}
// for each extension
for (std::size_t i = 0; i < ExtensionsCount; ++i) {
const std::size_t extensionLength = extensionsLC[i].size();
const std::size_t extensionLengthWithDot = extensionLength + 1;
// check size
if (name.size() < extensionLengthWithDot) {
continue;
}
// check dot
if (name[name.size() - extensionLengthWithDot] != '.') {
continue;
}
// starts at one past the dot
const auto* p = name.data() + name.size() - extensionLength;
// set to false as soon as a character doesn't match
bool found = true;
// for each character in extension
for (std::size_t c = 0; c < extensionLength; ++c) {
// checking both lowercase and uppercase
if (*p != extensionsLC[i][c] && *p != extensionsUC[i][c]) {
// neither
found = false;
break;
}
// matches, check next
++p;
}
if (found) {
return true;
}
}
return false;
}
bool shouldAddToInverseTree(std::string_view name)
{
static std::array extensionsLC{"exe", "dll"};
static std::array extensionsUC{"EXE", "DLL"};
// must be changed if any extension longer than 3 letters is added
constexpr std::size_t longestExtension = 3;
return extensionMatchesCI(name, extensionsLC, extensionsUC);
}
//
// Logging
//
void InitLoggingInternal(bool toConsole, bool connectExistingSHM)
{
try {
if (!toConsole && !SHMLogger::isInstantiated()) {
if (connectExistingSHM) {
SHMLogger::open("usvfs");
} else {
SHMLogger::create("usvfs");
}
}
// a temporary logger was created in DllMain
spdlog::drop("usvfs");
#pragma message("need a customized name for the shm")
auto logger = spdlog::get("usvfs");
if (logger.get() == nullptr) {
logger = toConsole ? spdlog::create("usvfs")
: spdlog::create("usvfs", "usvfs");
logger->set_pattern("%H:%M:%S.%e [%L] %v");
}
logger->set_level(spdlog::level::debug);
spdlog::drop("hooks");
logger = spdlog::get("hooks");
if (logger.get() == nullptr) {
logger = toConsole ? spdlog::create("hooks")
: spdlog::create("hooks", "usvfs");
logger->set_pattern("%H:%M:%S.%e <%P:%t> [%L] %v");
}
logger->set_level(spdlog::level::debug);
} catch (const std::exception&) {
// TODO should really report this
// OutputDebugStringA((boost::format("init exception: %1%\n") %
// e.what()).str().c_str());
if (spdlog::get("usvfs").get() == nullptr) {
spdlog::create("usvfs");
}
if (spdlog::get("hooks").get() == nullptr) {
spdlog::create("hooks");
}
}
spdlog::get("usvfs")->info("usvfs dll {} initialized in process {}",
USVFS_VERSION_STRING, GetCurrentProcessId());
}
void WINAPI usvfsInitLogging(bool toConsole)
{
InitLoggingInternal(toConsole, false);
}
extern "C" DLLEXPORT bool WINAPI usvfsGetLogMessages(LPSTR buffer, size_t size,
bool blocking)
{
buffer[0] = '\0';
try {
if (blocking) {
SHMLogger::instance().get(buffer, size);
return true;
} else {
return SHMLogger::instance().tryGet(buffer, size);
}
} catch (const std::exception& e) {
_snprintf_s(buffer, size, _TRUNCATE, "Failed to retrieve log messages: %s",
e.what());
return false;
}
}
void SetLogLevel(LogLevel level)
{
spdlog::get("usvfs")->set_level(ConvertLogLevel(level));
spdlog::get("hooks")->set_level(ConvertLogLevel(level));
}
void WINAPI usvfsUpdateParameters(usvfsParameters* p)
{
spdlog::get("usvfs")->info("updating parameters:\n"
" . debugMode: {}\n"
" . log level: {}\n"
" . dump type: {}\n"
" . dump path: {}\n"
" . delay process: {}ms",
p->debugMode, usvfsLogLevelToString(p->logLevel),
usvfsCrashDumpTypeToString(p->crashDumpsType),
p->crashDumpsPath, p->delayProcessMs);
// update actual values used:
usvfs_dump_type = p->crashDumpsType;
usvfs_dump_path =
ush::string_cast(p->crashDumpsPath, ush::CodePage::UTF8);
SetLogLevel(p->logLevel);
// update parameters in context so spawned process will inherit changes:
context->setDebugParameters(p->logLevel, p->crashDumpsType, p->crashDumpsPath,
std::chrono::milliseconds(p->delayProcessMs));
}
//
// Structured Exception handling
//
std::wstring generate_minidump_name(const wchar_t* dumpPath)
{
DWORD pid = GetCurrentProcessId();
wchar_t pname[100];
if (GetModuleBaseName(GetCurrentProcess(), NULL, pname, _countof(pname)) == 0)
return std::wstring();
// find an available name:
wchar_t dmpFile[MAX_PATH];
int count = 0;
_snwprintf_s(dmpFile, _TRUNCATE, L"%s\\%s-%lu.dmp", dumpPath, pname, pid);
while (winapi::ex::wide::fileExists(dmpFile)) {
if (++count > 99)
return std::wstring();
_snwprintf_s(dmpFile, _TRUNCATE, L"%s\\%s-%lu_%02d.dmp", dumpPath, pname, pid,
count);
}
return dmpFile;
}
int createMiniDumpImpl(PEXCEPTION_POINTERS exceptionPtrs, CrashDumpsType type,
const wchar_t* dumpPath, HMODULE dbgDLL)
{
typedef BOOL(WINAPI * FuncMiniDumpWriteDump)(
HANDLE process, DWORD pid, HANDLE file, MINIDUMP_TYPE dumpType,
const PMINIDUMP_EXCEPTION_INFORMATION exceptionParam,
const PMINIDUMP_USER_STREAM_INFORMATION userStreamParam,
const PMINIDUMP_CALLBACK_INFORMATION callbackParam);
// notice we avoid logging here on purpose because this is called from the VEHandler
// and the logger can crash it in extreme cases.
// additionally it is also called for MO crashes which use it's own logging.
winapi::ex::wide::createPath(dumpPath);
auto dmpName = generate_minidump_name(dumpPath);
if (dmpName.empty())
return 4;
FuncMiniDumpWriteDump funcDump = reinterpret_cast(
GetProcAddress(dbgDLL, "MiniDumpWriteDump"));
if (!funcDump)
return 5;
HANDLE dumpFile = winapi::wide::createFile(dmpName)
.createAlways()
.access(GENERIC_WRITE)
.share(FILE_SHARE_WRITE)();
if (dumpFile != INVALID_HANDLE_VALUE) {
DWORD dumpType = MiniDumpNormal | MiniDumpWithHandleData |
MiniDumpWithUnloadedModules | MiniDumpWithProcessThreadData;
if (type == CrashDumpsType::Data)
dumpType |= MiniDumpWithDataSegs;
if (type == CrashDumpsType::Full)
dumpType |= MiniDumpWithFullMemory;
_MINIDUMP_EXCEPTION_INFORMATION exceptionInfo;
exceptionInfo.ThreadId = GetCurrentThreadId();
exceptionInfo.ExceptionPointers = exceptionPtrs;
exceptionInfo.ClientPointers = FALSE;
BOOL success = funcDump(GetCurrentProcess(), GetCurrentProcessId(), dumpFile,
static_cast(dumpType), &exceptionInfo,
nullptr, nullptr);
CloseHandle(dumpFile);
return success ? 0 : 7;
} else
return 6;
}
int WINAPI usvfsCreateMiniDump(PEXCEPTION_POINTERS exceptionPtrs, CrashDumpsType type,
const wchar_t* dumpPath)
{
if (type == CrashDumpsType::None)
return 0;
int res = 1;
if (HMODULE dbgDLL = LoadLibraryW(L"dbghelp.dll")) {
try {
res = createMiniDumpImpl(exceptionPtrs, type, dumpPath, dbgDLL);
} catch (...) {
res = 2;
}
FreeLibrary(dbgDLL);
}
return res;
}
static bool exceptionInUSVFS(PEXCEPTION_POINTERS exceptionPtrs)
{
if (!dllModule) // shouldn't happen, check just in case
return true; // create dump to better understand how this could happen
std::pair range = winapi::ex::getSectionRange(dllModule);
uintptr_t exceptionAddress =
reinterpret_cast(exceptionPtrs->ExceptionRecord->ExceptionAddress);
return range.first <= exceptionAddress && exceptionAddress < range.second;
}
LONG WINAPI VEHandler(PEXCEPTION_POINTERS exceptionPtrs)
{
// NOTICE: don't use logger in VEHandler as it can cause another fault causing
// VEHandler to be called again and so on.
if ((exceptionPtrs->ExceptionRecord->ExceptionCode < 0x80000000) // non-critical
||
(exceptionPtrs->ExceptionRecord->ExceptionCode == 0xe06d7363)) { // cpp exception
// don't report non-critical exceptions
return EXCEPTION_CONTINUE_SEARCH;
}
/*
if (((exceptionPtrs->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE) != 0)
|| (exceptionPtrs->ExceptionRecord->ExceptionCode == 0xe06d7363)) {
// don't want to break on non-critical exceptions. 0xe06d7363 indicates a C++
exception. why are those marked non-continuable? return EXCEPTION_CONTINUE_SEARCH;
}
*/
// VEHandler is called on "first-chance" exceptions which might be caught and handled.
// Ideally we would like to use an UnhandledExceptionFilter but that fails to catch
// crashes inside our hooks at least on x64, which is the main reason why want a crash
// collection from usvfs. As a workaround/compromise we catch vectored exception but
// only ones that originate directly within the usvfs code:
if (!exceptionInUSVFS(exceptionPtrs))
return EXCEPTION_CONTINUE_SEARCH;
// disable our hooking mechanism to increase chances the dump writing won't crash
HookLib::TrampolinePool& trampPool = HookLib::TrampolinePool::instance();
if (&trampPool) { // need to test this in case of crash before TrampolinePool
// initialized
trampPool.forceUnlockBarrier();
trampPool.setBlock(true);
}
usvfsCreateMiniDump(exceptionPtrs, usvfs_dump_type, usvfs_dump_path.c_str());
return EXCEPTION_CONTINUE_SEARCH;
}
//
// Exported functions
//
void __cdecl InitHooks(LPVOID parameters, size_t)
{
InitLoggingInternal(false, true);
const usvfsParameters* params = reinterpret_cast(parameters);
// there is already a wait in the constructor of HookManager, but this one is useful
// to debug code here (from experience... ), should not wait twice since the second
// will return true immediately
if (params->debugMode) {
while (!::IsDebuggerPresent()) {
// wait for debugger to attach
::Sleep(100);
}
}
usvfs_dump_type = params->crashDumpsType;
usvfs_dump_path =
ush::string_cast(params->crashDumpsPath, ush::CodePage::UTF8);
if (params->delayProcessMs > 0) {
::Sleep(static_cast(params->delayProcessMs));
}
SetLogLevel(params->logLevel);
if (exceptionHandler == nullptr) {
if (usvfs_dump_type != CrashDumpsType::None)
exceptionHandler = ::AddVectoredExceptionHandler(0, VEHandler);
} else {
spdlog::get("usvfs")->info("vectored exception handler already active");
// how did this happen??
}
spdlog::get("usvfs")->info(
"inithooks called {0} in process {1}:{2} (log level {3}, dump type {4}, dump "
"path {5})",
params->instanceName, winapi::ansi::getModuleFileName(nullptr),
::GetCurrentProcessId(), static_cast(params->logLevel),
static_cast(params->crashDumpsType), params->crashDumpsPath);
try {
manager = new usvfs::HookManager(*params, dllModule);
auto context = manager->context();
auto exePath = boost::dll::program_location();
auto libraries = context->librariesToForceLoad(exePath.filename().c_str());
for (auto library : libraries) {
if (std::filesystem::exists(library)) {
const auto ret = LoadLibraryExW(library.c_str(), NULL, 0);
if (ret) {
spdlog::get("usvfs")->info("inithooks succeeded to force load {0}",
ush::string_cast(library).c_str());
} else {
spdlog::get("usvfs")->critical(
"inithooks failed to force load {0}",
ush::string_cast(library).c_str());
}
}
}
spdlog::get("usvfs")->info("inithooks in process {0} successful",
::GetCurrentProcessId());
} catch (const std::exception& e) {
spdlog::get("usvfs")->debug("failed to initialise hooks: {0}", e.what());
}
}
void WINAPI usvfsGetCurrentVFSName(char* buffer, size_t size)
{
ush::strncpy_sz(buffer, context->callParameters().currentSHMName, size);
}
BOOL WINAPI usvfsCreateVFS(const usvfsParameters* p)
{
usvfs::HookContext::remove(p->instanceName);
return usvfsConnectVFS(p);
}
BOOL WINAPI usvfsConnectVFS(const usvfsParameters* params)
{
if (spdlog::get("usvfs").get() == nullptr) {
// create temporary logger so we don't get null-pointer exceptions
spdlog::create("usvfs");
}
try {
usvfsDisconnectVFS();
context = new usvfs::HookContext(*params, dllModule);
return TRUE;
} catch (const std::exception& e) {
spdlog::get("usvfs")->debug("failed to connect to vfs: {}", e.what());
return FALSE;
}
}
void WINAPI usvfsDisconnectVFS()
{
if (spdlog::get("usvfs").get() == nullptr) {
// create temporary logger so we don't get null-pointer exceptions
spdlog::create("usvfs");
}
spdlog::get("usvfs")->debug("remove from process {}", GetCurrentProcessId());
if (manager != nullptr) {
delete manager;
manager = nullptr;
}
if (context != nullptr) {
delete context;
context = nullptr;
spdlog::get("usvfs")->debug("vfs unloaded");
}
}
bool processStillActive(DWORD pid)
{
HANDLE proc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
if (proc == nullptr) {
return false;
}
ON_BLOCK_EXIT([&]() {
if (proc != INVALID_HANDLE_VALUE)
::CloseHandle(proc);
});
DWORD exitCode;
if (!GetExitCodeProcess(proc, &exitCode)) {
spdlog::get("usvfs")->warn("failed to query exit code on process {}: {}", pid,
::GetLastError());
return false;
} else {
return exitCode == STILL_ACTIVE;
}
}
BOOL WINAPI usvfsGetVFSProcessList(size_t* count, LPDWORD processIDs)
{
if (count == nullptr) {
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
if (context == nullptr) {
*count = 0;
} else {
std::vector pids = context->registeredProcesses();
size_t realCount = 0;
for (DWORD pid : pids) {
if (processStillActive(pid)) {
if ((realCount < *count) && (processIDs != nullptr)) {
processIDs[realCount] = pid;
}
++realCount;
} // else the process has already ended
}
*count = realCount;
}
return TRUE;
}
BOOL WINAPI usvfsGetVFSProcessList2(size_t* count, DWORD** buffer)
{
if (!count || !buffer) {
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
*count = 0;
*buffer = nullptr;
std::vector pids = context->registeredProcesses();
auto last = std::remove_if(pids.begin(), pids.end(), [](DWORD id) {
return !processStillActive(id);
});
pids.erase(last, pids.end());
if (pids.empty()) {
return TRUE;
}
*count = pids.size();
*buffer = static_cast(std::calloc(pids.size(), sizeof(DWORD)));
std::copy(pids.begin(), pids.end(), *buffer);
return TRUE;
}
void WINAPI usvfsClearVirtualMappings()
{
context->redirectionTable()->clear();
context->inverseTable()->clear();
}
/// ensure the specified path exists. If a physical path of the same name
/// exists, it is inserted into the virtual directory as an empty reference. If
/// the path doesn't exist virtually and can't be cloned from a physical
/// directory, this returns false
/// \todo if this fails (i.e. not all intermediate directories exists) any
/// intermediate directories already created aren't removed
bool assertPathExists(usvfs::RedirectionTreeContainer& table, LPCWSTR path)
{
bfs::path p(path);
p = p.parent_path();
usvfs::RedirectionTree::NodeT* current = table.get();
for (auto iter = p.begin(); iter != p.end(); iter = ush::nextIter(iter, p.end())) {
if (current->exists(iter->string().c_str())) {
// subdirectory exists virtually, all good
usvfs::RedirectionTree::NodePtrT found = current->node(iter->string().c_str());
current = found.get().get();
} else {
// targetPath is relative to the last rerouted "real" path. This means
// that if virtual c:/foo maps to real c:/windows then creating virtual
// c:/foo/bar will map to real c:/windows/bar
bfs::path targetPath = current->data().linkTarget.size() > 0
? bfs::path(current->data().linkTarget.c_str()) / *iter
: *iter / "\\";
// is_directory returns false for symlinks and reparse points,
// which causes this function to fail if the target path contains
// either of those. paths containing reparse points is a common
// scenario when running under Wine, so check for those explicitly.
// this check could have a false positive if the path contains a
// symlink to a file, but such a scenario is extremely unlikely.
if (is_directory(targetPath) || is_symlink(targetPath) ||
status(targetPath).type() == bfs::file_type::reparse_file) {
usvfs::RedirectionTree::NodePtrT newNode =
table.addDirectory(current->path() / *iter, targetPath.string().c_str(),
ush::FLAG_DUMMY, false);
current = newNode.get().get();
} else {
spdlog::get("usvfs")->info("{} doesn't exist", targetPath.c_str());
return false;
}
}
}
return true;
}
static bool fileNameInSkipSuffixes(const std::string& fileNameUtf8,
const std::vector& skipFileSuffixes)
{
for (const auto& skipFileSuffix : skipFileSuffixes) {
if (boost::algorithm::iends_with(fileNameUtf8, skipFileSuffix)) {
spdlog::get("usvfs")->debug(
"file '{}' should be skipped, matches file suffix '{}'", fileNameUtf8,
skipFileSuffix);
return true;
}
}
return false;
}
static bool fileNameInSkipDirectories(const std::string& directoryNameUtf8,
const std::vector& skipDirectories)
{
for (const auto& skipDir : skipDirectories) {
if (boost::algorithm::iequals(directoryNameUtf8, skipDir)) {
spdlog::get("usvfs")->debug("directory '{}' should be skipped",
directoryNameUtf8);
return true;
}
}
return false;
}
BOOL WINAPI usvfsVirtualLinkFile(LPCWSTR source, LPCWSTR destination,
unsigned int flags)
{
// TODO difference between winapi and ntdll api regarding system32 vs syswow64
// (and other windows links?)
try {
if (!assertPathExists(context->redirectionTable(), destination)) {
SetLastError(ERROR_PATH_NOT_FOUND);
return FALSE;
}
const auto skipFileSuffixes = context->skipFileSuffixes();
std::string sourceU8 = ush::string_cast(source, ush::CodePage::UTF8);
// Check if the file should be skipped
if (fileNameInSkipSuffixes(sourceU8, skipFileSuffixes)) {
// return false when we want to fail when the file is skipped
return (flags & LINKFLAG_FAILIFSKIPPED) ? FALSE : TRUE;
}
auto res = context->redirectionTable().addFile(
bfs::path(destination), usvfs::RedirectionDataLocal(sourceU8),
!(flags & LINKFLAG_FAILIFEXISTS));
if (shouldAddToInverseTree(sourceU8)) {
std::string destinationU8 =
ush::string_cast(destination, ush::CodePage::UTF8);
context->inverseTable().addFile(bfs::path(source),
usvfs::RedirectionDataLocal(destinationU8), true);
}
context->updateParameters();
if (res.get() == nullptr) {
// the tree structure currently doesn't provide useful error codes but
// this is currently the only reason
// we would return a nullptr.
SetLastError(ERROR_FILE_EXISTS);
return FALSE;
} else {
return TRUE;
}
} catch (const std::exception& e) {
spdlog::get("usvfs")->error("failed to copy file {}", e.what());
// TODO: no clue what's wrong
SetLastError(ERROR_INVALID_DATA);
return FALSE;
}
}
/**
* @brief extract the flags relevant to redirection
*/
static usvfs::shared::TreeFlags convertRedirectionFlags(unsigned int flags)
{
usvfs::shared::TreeFlags result = 0;
if (flags & LINKFLAG_CREATETARGET) {
result |= usvfs::shared::FLAG_CREATETARGET;
}
return result;
}
BOOL WINAPI usvfsVirtualLinkDirectoryStatic(LPCWSTR source, LPCWSTR destination,
unsigned int flags)
{
// TODO change notification not yet implemented
try {
if ((flags & LINKFLAG_FAILIFEXISTS) && winapi::ex::wide::fileExists(destination)) {
SetLastError(ERROR_FILE_EXISTS);
return FALSE;
}
if (!assertPathExists(context->redirectionTable(), destination)) {
SetLastError(ERROR_PATH_NOT_FOUND);
return FALSE;
}
std::string sourceU8 =
ush::string_cast(source, ush::CodePage::UTF8) + "\\";
context->redirectionTable().addDirectory(
destination, usvfs::RedirectionDataLocal(sourceU8),
usvfs::shared::FLAG_DIRECTORY | convertRedirectionFlags(flags),
(flags & LINKFLAG_CREATETARGET) != 0);
const auto skipDirectories = context->skipDirectories();
const auto skipFileSuffixes = context->skipFileSuffixes();
if ((flags & LINKFLAG_RECURSIVE) != 0) {
std::wstring sourceP(source);
std::wstring sourceW = sourceP + L"\\";
std::wstring destinationW = std::wstring(destination) + L"\\";
if (sourceP.length() >= MAX_PATH && !ush::startswith(sourceP.c_str(), LR"(\\?\)"))
sourceP = LR"(\\?\)" + sourceP;
for (winapi::ex::wide::FileResult file :
winapi::ex::wide::quickFindFiles(sourceP.c_str(), L"*")) {
if (file.attributes & FILE_ATTRIBUTE_DIRECTORY) {
if ((file.fileName != L".") && (file.fileName != L"..")) {
const auto nameU8 = ush::string_cast(file.fileName.c_str(),
ush::CodePage::UTF8);
// Check if the directory should be skipped
if (fileNameInSkipDirectories(nameU8, skipDirectories)) {
// Fail if we desire to fail when a dir/file is skipped
if (flags & LINKFLAG_FAILIFSKIPPED) {
spdlog::get("usvfs")->debug(
"directory '{}' skipped, failing as defined by link flags", nameU8);
return FALSE;
}
continue;
}
usvfsVirtualLinkDirectoryStatic((sourceW + file.fileName).c_str(),
(destinationW + file.fileName).c_str(),
flags);
}
} else {
const auto nameU8 =
ush::string_cast(file.fileName.c_str(), ush::CodePage::UTF8);
// Check if the file should be skipped
if (fileNameInSkipSuffixes(nameU8, skipFileSuffixes)) {
// Fail if we desire to fail when a dir/file is skipped
if (flags & LINKFLAG_FAILIFSKIPPED) {
spdlog::get("usvfs")->debug(
"file '{}' skipped, failing as defined by link flags", nameU8);
return FALSE;
}
continue;
}
// TODO could save memory here by storing only the file name for the
// source and constructing the full name using the parent directory
context->redirectionTable().addFile(
bfs::path(destination) / nameU8,
usvfs::RedirectionDataLocal(sourceU8 + nameU8), true);
if (shouldAddToInverseTree(nameU8)) {
std::string destinationU8 =
ush::string_cast(destination, ush::CodePage::UTF8) + "\\";
context->inverseTable().addFile(
bfs::path(source) / nameU8,
usvfs::RedirectionDataLocal(destinationU8 + nameU8), true);
}
}
}
}
context->updateParameters();
return TRUE;
} catch (const std::exception& e) {
spdlog::get("usvfs")->error("failed to copy file {}", e.what());
// TODO: no clue what's wrong
SetLastError(ERROR_INVALID_DATA);
return FALSE;
}
}
BOOL WINAPI usvfsCreateProcessHooked(LPCWSTR lpApplicationName, LPWSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles, DWORD dwCreationFlags,
LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory,
LPSTARTUPINFOW lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation)
{
BOOL susp = dwCreationFlags & CREATE_SUSPENDED;
DWORD flags = dwCreationFlags | CREATE_SUSPENDED;
BOOL blacklisted = context->executableBlacklisted(lpApplicationName, lpCommandLine);
BOOL res = CreateProcessW(lpApplicationName, lpCommandLine, lpProcessAttributes,
lpThreadAttributes, bInheritHandles, flags, lpEnvironment,
lpCurrentDirectory, lpStartupInfo, lpProcessInformation);
if (!res) {
spdlog::get("usvfs")->error("failed to spawn {}",
ush::string_cast(lpCommandLine));
return FALSE;
}
if (!blacklisted) {
std::wstring applicationDirPath = winapi::wide::getModuleFileName(dllModule);
boost::filesystem::path p(applicationDirPath);
try {
usvfs::injectProcess(p.parent_path().wstring(), context->callParameters(),
*lpProcessInformation);
} catch (const std::exception& e) {
spdlog::get("usvfs")->error("failed to inject: {}", e.what());
logExtInfo(e, LogLevel::Error);
::TerminateProcess(lpProcessInformation->hProcess, 1);
::SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
}
if (!susp) {
ResumeThread(lpProcessInformation->hThread);
}
return TRUE;
}
BOOL WINAPI usvfsCreateVFSDump(LPSTR buffer, size_t* size)
{
assert(size != nullptr);
std::ostringstream output;
usvfs::shared::dumpTree(output, *context->redirectionTable().get());
std::string str = output.str();
if ((buffer != NULL) && (*size > 0)) {
strncpy_s(buffer, *size, str.c_str(), _TRUNCATE);
}
bool success = *size >= str.length();
*size = str.length();
return success ? TRUE : FALSE;
}
VOID WINAPI usvfsBlacklistExecutable(LPCWSTR executableName)
{
context->blacklistExecutable(executableName);
}
VOID WINAPI usvfsClearExecutableBlacklist()
{
context->clearExecutableBlacklist();
}
VOID WINAPI usvfsAddSkipFileSuffix(LPCWSTR fileSuffix)
{
context->addSkipFileSuffix(fileSuffix);
}
VOID WINAPI usvfsClearSkipFileSuffixes()
{
context->clearSkipFileSuffixes();
}
VOID WINAPI usvfsAddSkipDirectory(LPCWSTR directory)
{
context->addSkipDirectory(directory);
}
VOID WINAPI usvfsClearSkipDirectories()
{
context->clearSkipDirectories();
}
VOID WINAPI usvfsForceLoadLibrary(LPCWSTR processName, LPCWSTR libraryPath)
{
context->forceLoadLibrary(processName, libraryPath);
}
VOID WINAPI usvfsClearLibraryForceLoads()
{
context->clearLibraryForceLoads();
}
VOID WINAPI usvfsPrintDebugInfo()
{
spdlog::get("usvfs")->warn("===== debug {} =====",
context->redirectionTable().shmName());
void* buffer = nullptr;
size_t bufferSize = 0;
context->redirectionTable().getBuffer(buffer, bufferSize);
std::ostringstream temp;
for (size_t i = 0; i < bufferSize; ++i) {
temp << std::hex << std::setfill('0') << std::setw(2)
<< (unsigned)reinterpret_cast(buffer)[i] << " ";
if ((i % 16) == 15) {
spdlog::get("usvfs")->info("{}", temp.str());
temp.str("");
temp.clear();
}
}
if (!temp.str().empty()) {
spdlog::get("usvfs")->info("{}", temp.str());
}
spdlog::get("usvfs")->warn("===== / debug {} =====",
context->redirectionTable().shmName());
}
const char* WINAPI usvfsVersionString()
{
return USVFS_VERSION_STRING;
}
//
// DllMain
//
BOOL APIENTRY DllMain(HMODULE module, DWORD reasonForCall, LPVOID)
{
switch (reasonForCall) {
case DLL_PROCESS_ATTACH: {
dllModule = module;
} break;
case DLL_PROCESS_DETACH: {
if (exceptionHandler)
::RemoveVectoredExceptionHandler(exceptionHandler);
} break;
case DLL_THREAD_ATTACH: {
} break;
case DLL_THREAD_DETACH: {
} break;
}
return TRUE;
}