diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-11 02:37:39 -0600 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-11 02:37:39 -0600 |
| commit | 7ee008e150bc5bcf76082d726f719ee0fdfda982 (patch) | |
| tree | 27fb39be241fdb5ac2734c574de678977d1856d0 /libs/usvfs/src/shared | |
Fluorine Manager: full Linux port of Mod Organizer 2
Complete native Linux port with FUSE-based virtual filesystem,
Proton/umu-run integration, and Flatpak packaging.
Key features:
- FUSE VFS replacing Windows USVFS (in-process + standalone helper for Flatpak)
- Proton/GE-Proton/umu-run launcher with env var forwarding
- Flatpak support (sandbox-aware VFS, NXM handler, umu-run)
- Wine prefix management UI
- Case-insensitive path resolution for Linux filesystems
- QSettings-safe INI handling (avoids Bethesda INI corruption)
- Portable instance support with auto-generated launcher scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'libs/usvfs/src/shared')
28 files changed, 5074 insertions, 0 deletions
diff --git a/libs/usvfs/src/shared/CMakeLists.txt b/libs/usvfs/src/shared/CMakeLists.txt new file mode 100644 index 0000000..b1a7c05 --- /dev/null +++ b/libs/usvfs/src/shared/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.16) + +find_package(Boost CONFIG REQUIRED COMPONENTS algorithm interprocess filesystem) +find_package(spdlog CONFIG REQUIRED) + +file(GLOB sources "*.cpp" "*.h") +source_group("" FILES ${sources}) + +add_library(shared STATIC ${sources}) +target_link_libraries(shared + PUBLIC + Boost::algorithm Boost::interprocess comsuppw + Boost::filesystem spdlog::spdlog_header_only) +target_include_directories(shared + PUBLIC ${PROJECT_SOURCE_DIR}/include/usvfs ${CMAKE_CURRENT_SOURCE_DIR}) +target_precompile_headers(shared PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/pch.h) +target_compile_options(shared PUBLIC /FI"pch.h") +target_compile_definitions(shared PUBLIC + SPDLOG_USE_STD_FORMAT SPDLOG_NO_NAME SPDLOG_NO_REGISTRY_MUTEX + BOOST_INTERPROCESS_SHARED_DIR_FUNC BOOST_CONFIG_SUPPRESS_OUTDATED_MESSAGE + NOMINMAX _WINDOWS _UNICODE UNICODE + _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR +) diff --git a/libs/usvfs/src/shared/addrtools.h b/libs/usvfs/src/shared/addrtools.h new file mode 100644 index 0000000..b888ebf --- /dev/null +++ b/libs/usvfs/src/shared/addrtools.h @@ -0,0 +1,64 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ +#pragma once + +#include "windows_sane.h" + +namespace usvfs::shared +{ + +#ifdef _M_AMD64 +typedef DWORD64 REGWORD; +#elif _M_IX86 +typedef DWORD REGWORD; +#endif + +inline LPVOID AddrAdd(LPVOID address, size_t offset) +{ + return reinterpret_cast<LPVOID>(reinterpret_cast<LPBYTE>(address) + offset); +} + +inline std::ptrdiff_t AddrDiff(LPVOID lhs, LPVOID rhs) +{ + return reinterpret_cast<LPBYTE>(lhs) - reinterpret_cast<LPBYTE>(rhs); +} + +// implicitly cast pointer to void*, from there cast to target type. +// This is supposed to be safer than directly reinterpret-casting +template <typename T> +inline T void_ptr_cast(void* ptr) +{ + return reinterpret_cast<T>(ptr); +} + +template <> +inline int64_t void_ptr_cast(void* ptr) +{ + return reinterpret_cast<int64_t>(ptr); +} + +template <> +inline uint64_t void_ptr_cast(void* ptr) +{ + return reinterpret_cast<uint64_t>(ptr); +} + +} // namespace usvfs::shared diff --git a/libs/usvfs/src/shared/directory_tree.cpp b/libs/usvfs/src/shared/directory_tree.cpp new file mode 100644 index 0000000..34cc797 --- /dev/null +++ b/libs/usvfs/src/shared/directory_tree.cpp @@ -0,0 +1,43 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ +#include "directory_tree.h" +#include "tree_container.h" + +namespace usvfs::shared +{ + +fs::path::iterator nextIter(const fs::path::iterator& iter, + const fs::path::iterator& end) +{ + fs::path::iterator next = iter; + advanceIter(next, end); + return next; +} + +void advanceIter(fs::path::iterator& iter, const fs::path::iterator& end) +{ + ++iter; + while (iter != end && (iter->wstring() == L"/" || iter->wstring() == L"\\" || + iter->wstring() == L".")) + ++iter; +} + +} // namespace usvfs::shared diff --git a/libs/usvfs/src/shared/directory_tree.h b/libs/usvfs/src/shared/directory_tree.h new file mode 100644 index 0000000..f82187f --- /dev/null +++ b/libs/usvfs/src/shared/directory_tree.h @@ -0,0 +1,629 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ +#pragma once + +#include "exceptionex.h" +#include "logging.h" +#include "shared_memory.h" +#include "stringutils.h" +#include "wildcard.h" + +// simplify unit tests by allowing access to private members +#ifndef PRIVATE +#define PRIVATE private +#endif // PRIVATE + +namespace usvfs::shared +{ + +template <typename T, typename U> +struct SHMDataCreator +{ + static T create(const U& source, const VoidAllocatorT& allocator) + { + return T(source, allocator); + } +}; + +template <typename T, typename U> +T createData(const U& source, const VoidAllocatorT& allocator) +{ + return SHMDataCreator<T, U>::create(source, allocator); +} + +template <typename T> +T createDataEmpty(const VoidAllocatorT& allocator); + +template <typename T> +void dataAssign(T& destination, const T& source); + +// crappy little workaround for fs::path iterating over path separators +fs::path::iterator nextIter(const fs::path::iterator& iter, + const fs::path::iterator& end); + +void advanceIter(fs::path::iterator& iter, const fs::path::iterator& end); + +// decomposes a path into its components +// +class DecomposablePath +{ +public: + explicit DecomposablePath(std::string s) : m_s(std::move(s)), m_begin(0), m_end(0) + { + m_end = nextSeparator(m_begin); + } + + // move to the next component, returns false when there are no more components + // + bool next() + { + for (;;) { + if (m_end >= m_s.size()) { + // done + m_begin = m_end; + return false; + } + + // move begin to one past the last separator found + m_begin = m_end + 1; + + // find the next separator + m_end = nextSeparator(m_begin); + + // check for components that should be ignored: + // - empty, happens when the path ends with a separator + // - slashes, happens with consecutive separators + // - dot, unnecessary + const auto c = current(); + if (!c.empty() && c != "\\" && c != "/" && c != ".") { + return true; + } + } + } + + // checks if next() would return false + // + bool peekNext() const + { + auto copy = *this; + return copy.next(); + } + + // the current component, empty when next() returned false + // + std::string_view current() const { return {m_s.data() + m_begin, m_end - m_begin}; } + +private: + const std::string m_s; + std::size_t m_begin, m_end; + + // finds the next path separator + // + std::size_t nextSeparator(std::size_t from) const + { + while (from < m_s.size()) { + if (m_s[from] == '/' || m_s[from] == '\\') { + break; + } + + ++from; + } + + return from; + } +}; + +namespace bi = boost::interprocess; +namespace bmi = boost::multi_index; + +typedef uint8_t TreeFlags; + +static const TreeFlags FLAG_DIRECTORY = 0x01; +static const TreeFlags FLAG_DUMMY = 0x02; +static const TreeFlags FLAG_FIRSTUSERFLAG = 0x10; + +struct MissingThrowT +{}; +static const MissingThrowT MissingThrow = MissingThrowT(); + +template <typename NodeDataT> +class TreeContainer; + +template <typename T1, typename T2, typename Alloc> +struct mutable_pair +{ + typedef T1 first_type; + typedef T2 second_type; + + mutable_pair(Alloc alloc) : first(T1(alloc)), second(T2(alloc)) {} + + mutable_pair(const T1& f, const T2& s) : first(f), second(s) {} + + mutable_pair(const std::pair<T1, T2>& p) : first(p.first), second(p.second) {} + + T1 first; + mutable T2 second; +}; + +template <typename Key, typename T, typename Compare, typename Allocator, + typename Element = mutable_pair<Key, T, Allocator>> +using mimap = bmi::multi_index_container< + Element, + bmi::indexed_by< + bmi::ordered_unique<bmi::member<Element, Key, &Element::first>, Compare>>, + typename Allocator::template rebind<Element>::other>; + +/** + * a representation of a directory tree in memory. + * This class is designed to be stored in shared memory. + */ +template <typename NodeDataT> +class DirectoryTree +{ + template <typename T> + friend class TreeContainer; + +public: + struct CILess + { + template <typename U, typename V> + bool operator()(const U& lhs, const V& rhs) const + { + const size_t lhsLength = getLength(lhs); + const size_t rhsLength = getLength(rhs); + + const auto r = + _strnicmp(getCharPtr(lhs), getCharPtr(rhs), std::min(lhsLength, rhsLength)); + + if (r == 0) { + return lhsLength < rhsLength; + } + + return (r < 0); + } + + private: + const char* getCharPtr(const StringT& s) const { return s.c_str(); } + + const char* getCharPtr(const std::string& s) const { return s.c_str(); } + + const char* getCharPtr(const char* s) const { return s; } + + const char* getCharPtr(std::string_view s) const { return s.data(); } + + size_t getLength(const StringT& s) const { return s.size(); } + + size_t getLength(const std::string& s) const { return s.size(); } + + size_t getLength(const char* s) const { return strlen(s); } + + size_t getLength(std::string_view s) const { return s.size(); } + }; + + typedef DirectoryTree<NodeDataT> NodeT; + typedef bi::deleter<NodeT, SegmentManagerT> DeleterT; + typedef NodeDataT DataT; + + typedef bi::shared_ptr<NodeT, VoidAllocatorT, DeleterT> NodePtrT; + typedef bi::weak_ptr<NodeT, VoidAllocatorT, DeleterT> WeakPtrT; + + typedef bi::allocator<std::pair<const StringT, NodePtrT>, SegmentManagerT> + NodeEntryAllocatorT; + + typedef mimap<StringT, NodePtrT, CILess, NodeEntryAllocatorT> NodeMapT; + typedef typename NodeMapT::iterator file_iterator; + typedef typename NodeMapT::const_iterator const_file_iterator; + + typedef std::function<void(const NodePtrT&)> VisitorFunction; + + DirectoryTree() = delete; + DirectoryTree(const NodeT& reference) = delete; + DirectoryTree(NodeT&& reference) = delete; + NodeT& operator=(NodeT reference) = delete; + + /** + * @brief construct a new node to be inserted in an existing tree + **/ + DirectoryTree(std::string_view name, TreeFlags flags, const NodePtrT& parent, + const NodeDataT& data, const VoidAllocatorT& allocator) + : m_Parent(parent), m_Name(name.begin(), name.end(), allocator), m_Data(data), + m_Nodes(allocator), m_Flags(flags) + {} + + ~DirectoryTree() { m_Nodes.clear(); } + + /** + * @return parent node + */ + NodePtrT parent() const { return m_Parent.lock(); } + + /** + * @return the full path to the node + */ + fs::path path() const + { + if (m_Parent.lock().get() == nullptr) { + if (m_Name.size() == 0) { + return fs::path(); + } else { + return fs::path(m_Name.c_str()) / "\\"; + } + } else { + return m_Parent.lock()->path() / m_Name.c_str(); + } + } + + /** + * @return data connected to this node + **/ + const NodeDataT& data() const { return m_Data; } + + /** + * @return name of this node + */ + std::string name() const { return m_Name.c_str(); } + + /** + * @brief setFlag change a flag for this node + * @param enabled new state for the specified flag + */ + void setFlag(TreeFlags flag, bool enabled = true) + { + m_Flags = enabled ? m_Flags | flag : m_Flags & ~flag; + } + + /** + * @return true if the specified flag is set, false otherwise + */ + bool hasFlag(TreeFlags flag) const { return (m_Flags & flag) != 0; } + + /** + * @return true if this node is a directory, false if it's a regular file + */ + bool isDirectory() const { return hasFlag(FLAG_DIRECTORY); } + + /** + * @return the number of subnodes (directly) below this one + */ + size_t numNodes() const { return m_Nodes.size(); } + + /** + * @return number of nodes in this (sub-)tree including this one + */ + size_t numNodesRecursive() const + { + size_t result = numNodes() + 1; + + for (const auto& node : m_Nodes) { + result += node.second->numNodesRecursive(); + } + + return result; + } + + /** + * @brief find a node by its path + * @param path the path to look up + * @return a pointer to the node or a null ptr + */ + NodePtrT findNode(const fs::path& path) + { + fs::path::iterator iter = path.begin(); + return findNode(path, iter); + } + + /** + * @brief find a node by its path + * @param path the path to look up + * @return a pointer to the node or a null ptr + */ + const NodePtrT findNode(const fs::path& path) const + { + fs::path::iterator iter = path.begin(); + return findNode(path, iter); + } + + /** + * @brief visit the nodes along the specified path (in order) calling the visitor for + * each + * @param path the path to visit + * @param visitor a function called for each node + */ + void visitPath(const fs::path& path, const VisitorFunction& visitor) const + { + fs::path::iterator iter = path.begin(); + visitPath(path, iter, visitor); + } + + /** + * @brief retrieve a node by the specified name + * @param name name of the node + * @return the node found or an empty pointer if no such node was found + */ + NodePtrT node(std::string_view name, MissingThrowT) const + { + auto iter = m_Nodes.find(name); + + if (iter != m_Nodes.end()) { + return iter->second; + } else { + USVFS_THROW_EXCEPTION(node_missing_error()); + } + } + + /** + * @brief retrieve a node by the specified name + * @param name name of the node + * @return the node found or an empty pointer if no such node was found + */ + NodePtrT node(std::string_view name) + { + auto iter = m_Nodes.find(name); + + if (iter != m_Nodes.end()) { + return iter->second; + } else { + return NodePtrT(); + } + } + + /** + * @brief retrieve a node by the specified name + * @param name name of the node + * @return the node found or an empty pointer if no such node was found + */ + const NodePtrT node(std::string_view name, MissingThrowT) + { + auto iter = m_Nodes.find(name); + + if (iter != m_Nodes.end()) { + return iter->second; + } else { + USVFS_THROW_EXCEPTION(node_missing_error()); + } + } + + /** + * @brief retrieve a node by the specified name + * @param name name of the node + * @return the node found or an empty pointer if no such node was found + */ + const NodePtrT node(std::string_view name) const + { + auto iter = m_Nodes.find(name); + + if (iter != m_Nodes.end()) { + return iter->second; + } else { + return NodePtrT(); + } + } + + /** + * @brief test if a node by the specified name exists + * @param name name of the node + * @return true if the node exists, false otherwise + */ + bool exists(std::string_view name) const + { + return m_Nodes.find(name) != m_Nodes.end(); + } + + /** + * @brief find all matches for a pattern + * @param pattern the pattern to look for + * @return a vector of the found nodes + */ + std::vector<NodePtrT> find(const std::string& pattern) const + { + // determine if there is a prefix in the pattern that indicates a specific + // directory. + size_t fixedPart = pattern.find_first_of("*?"); + + if (fixedPart == 0) + fixedPart = std::string::npos; + if (fixedPart != std::string::npos) + fixedPart = pattern.find_last_of(R"(\/)", fixedPart); + + std::vector<NodePtrT> result; + + if (fixedPart != std::string::npos) { + // if there is a prefix, search for the node representing that path and + // search only on that + NodePtrT node = findNode(fs::path(pattern.substr(0, fixedPart))); + if (node.get() != nullptr) { + node->findLocal(result, pattern.substr(fixedPart + 1)); + } + } else { + findLocal(result, pattern); + } + + return result; + } + + /** + * @return an iterator to the first leaf + **/ + file_iterator filesBegin() { return m_Nodes.begin(); } + + /** + * @return a const iterator to the first leaf + **/ + const_file_iterator filesBegin() const { return m_Nodes.begin(); } + + /** + * @return an iterator one past the last leaf + **/ + file_iterator filesEnd() { return m_Nodes.end(); } + + /** + * @return a const iterator one past the last leaf + **/ + const_file_iterator filesEnd() const { return m_Nodes.end(); } + + /** + * @brief erase the leaf at the specified iterator + * @return an iterator to the following file + **/ + file_iterator erase(file_iterator iter) { return m_Nodes.erase(iter); } + + /** + * @brief clear all nodes + */ + void clear() { m_Nodes.clear(); } + + void removeFromTree() + { + if (auto par = parent()) { + spdlog::get("usvfs")->info("remove from tree {}", m_Name.c_str()); + auto self = par->m_Nodes.find(m_Name.c_str()); + if (self != par->m_Nodes.end()) { + par->erase(self); + } else { + // trying to remove a node that does not exist, most likely because it was + // already removed in a lower level call. this is known to happen when MoveFile + // has the MOVEFILE_COPY_ALLOWED flag and moving a mapped file. + spdlog::get("usvfs")->warn("Failed to remove inexisting node from tree: {}", + m_Name.c_str()); + } + } + } + + PRIVATE : void set(StringT key, const NodePtrT& value) + { + auto res = m_Nodes.emplace(std::move(key), value); + if (!res.second) { + res.first->second = value; + } + } + + WeakPtrT findRoot() const + { + if (m_Parent.lock().get() == nullptr) { + return m_Self; + } else { + return m_Parent.lock()->findRoot(); + } + } + + NodePtrT findNode(const fs::path& name, fs::path::iterator& iter) + { + std::string l = iter->string(); + auto subNode = m_Nodes.find(iter->string()); + advanceIter(iter, name.end()); + + if (iter == name.end()) { + // last name component, should be a local node + if (subNode != m_Nodes.end()) { + return subNode->second; + } else { + return NodePtrT(); + } + } else { + if (subNode != m_Nodes.end()) { + return subNode->second->findNode(name, iter); + } else { + return NodePtrT(); + } + } + } + + const NodePtrT findNode(const fs::path& name, fs::path::iterator& iter) const + { + auto subNode = m_Nodes.find(iter->string()); + advanceIter(iter, name.end()); + + if (iter == name.end()) { + // last name component, should be a local node + if (subNode != m_Nodes.end()) { + return subNode->second; + } else { + return NodePtrT(); + } + } else { + if (subNode != m_Nodes.end()) { + return subNode->second->findNode(name, iter); + } else { + return NodePtrT(); + } + } + } + + void visitPath(const fs::path& path, fs::path::iterator& iter, + const VisitorFunction& visitor) const + { + auto subNode = m_Nodes.find(iter->string()); + + if (subNode != m_Nodes.end()) { + visitor(subNode->second); + advanceIter(iter, path.end()); + if (iter != path.end()) { + subNode->second->visitPath(path, iter, visitor); + } + } + } + + void findLocal(std::vector<NodePtrT>& output, const std::string& pattern) const + { + for (auto iter = m_Nodes.begin(); iter != m_Nodes.end(); ++iter) { + LPCSTR remainder = nullptr; + + if (pattern.size() > 1 && (pattern[0] == '*') && + ((pattern[1] == '/') || (pattern[1] == '\\')) && + iter->second->isDirectory()) { + // the star may represent a directory (one directory level, not + // multiple!), search in subdirectory + iter->second->findLocal(output, pattern.substr(1)); + } else if ((remainder = wildcard::PartialMatch(iter->second->name().c_str(), + pattern.c_str())) != nullptr) { + if ((*remainder == '\0') || (strcmp(remainder, "*") == 0)) { + NodePtrT node = iter->second; + output.push_back(node); + } + + if (iter->second->isDirectory()) { + iter->second->findLocal(output, remainder); + } + } + } + } + + PRIVATE : TreeFlags m_Flags; + + WeakPtrT m_Parent; + WeakPtrT m_Self; + + StringT m_Name; + NodeDataT m_Data; + + NodeMapT m_Nodes; +}; + +template <typename NodeDataT> +void dumpTree(std::ostream& stream, const DirectoryTree<NodeDataT>& tree, int level = 0) +{ + stream << std::string(level, ' ') << tree.name() << " -> " << tree.data() << "\n"; + for (auto iter = tree.filesBegin(); iter != tree.filesEnd(); ++iter) { + dumpTree<NodeDataT>(stream, *iter->second, level + 1); + } +} + +} // namespace usvfs::shared diff --git a/libs/usvfs/src/shared/exceptionex.cpp b/libs/usvfs/src/shared/exceptionex.cpp new file mode 100644 index 0000000..4a814b5 --- /dev/null +++ b/libs/usvfs/src/shared/exceptionex.cpp @@ -0,0 +1,82 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ +#include "exceptionex.h" +#include "winapi.h" +#include <spdlog/spdlog.h> + +namespace usvfs::shared +{ + +std::string windows_error::constructMessage(const std::string& input, int inErrorCode) +{ + std::ostringstream finalMessage; + finalMessage << input; + + LPSTR buffer = nullptr; + + DWORD errorCode = inErrorCode != -1 ? inErrorCode : GetLastError(); + + // TODO: the message is not english? + if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&buffer, 0, nullptr) == 0) { + finalMessage << " (errorcode " << errorCode << ")"; + } else { + LPSTR lastChar = buffer + strlen(buffer) - 2; + *lastChar = '\0'; + finalMessage << " (" << buffer << " [" << errorCode << "])"; + LocalFree(buffer); // allocated by FormatMessage + } + + SetLastError( + errorCode); // restore error code because FormatMessage might have modified it + return finalMessage.str(); +} + +} // namespace usvfs::shared + +void logExtInfo(const std::exception& e, LogLevel logLevel) +{ + std::string content; + + if (const std::string* msg = boost::get_error_info<ex_msg>(e)) { + content = *msg; + } + + if (const DWORD* errorCode = boost::get_error_info<ex_win_errcode>(e)) { + content = std::string("error: ") + winapi::ex::ansi::errorString(*errorCode); + } + + switch (logLevel) { + case LogLevel::Debug: + spdlog::get("usvfs")->debug(content); + break; + case LogLevel::Info: + spdlog::get("usvfs")->info(content); + break; + case LogLevel::Warning: + spdlog::get("usvfs")->warn(content); + break; + case LogLevel::Error: + spdlog::get("usvfs")->error(content); + break; + } +} diff --git a/libs/usvfs/src/shared/exceptionex.h b/libs/usvfs/src/shared/exceptionex.h new file mode 100644 index 0000000..4c298a5 --- /dev/null +++ b/libs/usvfs/src/shared/exceptionex.h @@ -0,0 +1,99 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ +#pragma once + +#include "logging.h" +#include <boost/exception/all.hpp> +#include <stdexcept> + +typedef boost::error_info<struct tag_message, DWORD> ex_win_errcode; +typedef boost::error_info<struct tag_message, std::string> ex_msg; + +struct std_boost_exception : virtual boost::exception, virtual std::exception +{ + const char* what() const noexcept override + { + return boost::diagnostic_information_what(*this); + } +}; + +struct incompatibility_error : std_boost_exception +{}; +struct usage_error : std_boost_exception +{}; +struct data_error : std_boost_exception +{}; +struct file_not_found_error : std_boost_exception +{}; +struct timeout_error : std_boost_exception +{}; +struct unknown_error : std_boost_exception +{}; +struct node_missing_error : std_boost_exception +{}; + +#define USVFS_THROW_EXCEPTION(x) BOOST_THROW_EXCEPTION(x) + +void logExtInfo(const std::exception& e, LogLevel logLevel = LogLevel::Warning); + +namespace usvfs::shared +{ + +class windows_error : public std::runtime_error +{ +public: + windows_error(const std::string& message, int errorcode = GetLastError()) + : runtime_error(constructMessage(message, errorcode)), m_ErrorCode(errorcode) + {} + + int getErrorCode() const { return m_ErrorCode; } + +private: + int m_ErrorCode; + + std::string constructMessage(const std::string& input, int errorcode); +}; + +class guard +{ +public: + explicit guard(std::function<void()> f) : m_f(std::move(f)) {} + + ~guard() + { + if (m_f) { + m_f(); + } + } + + guard(const guard&); + guard& operator=(const guard&); + +private: + std::function<void()> m_f; +}; + +} // namespace usvfs::shared + +#define CONCATENATE_DIRECT(s1, s2) s1##s2 +#define CONCATENATE(s1, s2) CONCATENATE_DIRECT(s1, s2) +#define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __LINE__) +#define ON_BLOCK_EXIT(f) usvfs::shared::guard ANONYMOUS_VARIABLE(guard)(f) diff --git a/libs/usvfs/src/shared/formatters.h b/libs/usvfs/src/shared/formatters.h new file mode 100644 index 0000000..3d0e47e --- /dev/null +++ b/libs/usvfs/src/shared/formatters.h @@ -0,0 +1,158 @@ +/* +Userspace Virtual Filesystem + +Copyright (C) 2024. 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 <http://www.gnu.org/licenses/>. +*/ +#pragma once + +#include <format> +#include <type_traits> + +#include "ntdll_declarations.h" + +// formatters for standard types + +namespace usvfs::log +{ +std::string to_string(LPCWSTR value); +std::string to_string(PCUNICODE_STRING value); +} // namespace usvfs::log + +template <class Enum, class CharT> + requires std::is_enum_v<Enum> +struct std::formatter<Enum, CharT> : std::formatter<std::underlying_type_t<Enum>, CharT> +{ + template <class FmtContext> + FmtContext::iterator format(Enum v, FmtContext& ctx) const + { + return std::formatter<std::underlying_type_t<Enum>, CharT>::format( + static_cast<std::underlying_type_t<Enum>>(v), ctx); + } +}; + +template <> +struct std::formatter<LPCWSTR, char> : std::formatter<std::string, char> +{ + template <class FmtContext> + FmtContext::iterator format(LPCWSTR v, FmtContext& ctx) const + { + return std::formatter<std::string, char>::format(usvfs::log::to_string(v), ctx); + } +}; + +template <> +struct std::formatter<std::wstring, char> : std::formatter<LPCWSTR, char> +{ + template <class FmtContext> + FmtContext::iterator format(const std::wstring& v, FmtContext& ctx) const + { + return std::formatter<LPCWSTR, char>::format(v.c_str(), ctx); + } +}; + +template <> +struct std::formatter<PCUNICODE_STRING, char> : std::formatter<std::string, char> +{ + template <class FmtContext> + FmtContext::iterator format(PCUNICODE_STRING v, FmtContext& ctx) const + { + return std::formatter<std::string, char>::format(usvfs::log::to_string(v), ctx); + } +}; + +template <> +struct std::formatter<UNICODE_STRING, char> : std::formatter<std::string, char> +{ + template <class FmtContext> + FmtContext::iterator format(UNICODE_STRING v, FmtContext& ctx) const + { + return std::formatter<std::string, char>::format(usvfs::log::to_string(&v), ctx); + } +}; + +template <class Pointer> + requires(std::is_pointer_v<Pointer> && !std::is_same_v<Pointer, const char*> && + !std::is_same_v<Pointer, char*> && !std::is_same_v<Pointer, const void*> && + !std::is_same_v<Pointer, void*>) +struct std::formatter<Pointer, char> : std::formatter<const void*, char> +{ + template <class FmtContext> + FmtContext::iterator format(Pointer v, FmtContext& ctx) const + { + return std::formatter<const void*, char>::format(v, ctx); + } +}; + +namespace usvfs::log +{ + +/** + * a small helper class to wrap any object. The whole point is to give us a way + * to ensure our own operator<< is used in addParam calls + */ +template <typename T> +class Wrap +{ +public: + explicit Wrap(const T& data) : m_Data(data) {} + Wrap(Wrap<T>&& reference) : m_Data(std::move(reference.m_Data)) {} + + Wrap(const Wrap<T>& reference) = delete; + Wrap<T>& operator=(const Wrap<T>& reference) = delete; + +private: + friend struct ::std::formatter<Wrap<T>, char>; + const T& m_Data; +}; + +template <typename T> +Wrap<T> wrap(const T& data) +{ + return Wrap<T>(data); +} + +} // namespace usvfs::log + +template <> +struct std::formatter<usvfs::log::Wrap<DWORD>, char> : std::formatter<DWORD, char> +{ + template <class FmtContext> + FmtContext::iterator format(const usvfs::log::Wrap<DWORD>& v, FmtContext& ctx) const + { + return std::format_to(ctx.out(), "{:x}", v.m_Data); + } +}; + +template <> +struct std::formatter<usvfs::log::Wrap<NTSTATUS>, char> : std::formatter<NTSTATUS, char> +{ + template <class FmtContext> + FmtContext::iterator format(const usvfs::log::Wrap<NTSTATUS>& v, + FmtContext& ctx) const + { + switch (v.m_Data) { + case 0x00000000: + return std::format_to(ctx.out(), "ok"); + case 0xC0000022: + return std::format_to(ctx.out(), "access denied"); + case 0xC0000035: + return std::format_to(ctx.out(), "exists already"); + } + return std::format_to(ctx.out(), "err {:x}", v.m_Data); + } +}; diff --git a/libs/usvfs/src/shared/loghelpers.cpp b/libs/usvfs/src/shared/loghelpers.cpp new file mode 100644 index 0000000..bfcabf1 --- /dev/null +++ b/libs/usvfs/src/shared/loghelpers.cpp @@ -0,0 +1,96 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ +#include <format> + +#include "formatters.h" +#include "loghelpers.h" +#include "stringcast.h" +#include "stringutils.h" + +namespace ush = usvfs::shared; + +namespace usvfs::log +{ + +// this is declared in formatters but is defined here to avoid a whole .cpp +// file for two small functions +// +// comments from old code, taken as is: +// +// TODO this does not correctly support surrogate pairs since the size used here +// is the number of 16-bit characters in the buffer whereas toNarrow expects the +// actual number of characters. It will always underestimate though, so worst +// case scenario we truncate the string +// +std::string to_string(LPCWSTR value) +{ + if (value == nullptr) { + return "<null>"; + } + try { + return ush::string_cast<std::string>(value, ush::CodePage::UTF8); + } catch (const std::exception& e) { + return std::format("<err: {}>", e.what()); + } +} +std::string to_string(PCUNICODE_STRING value) +{ + try { + return ush::string_cast<std::string>(value->Buffer, ush::CodePage::UTF8, + value->Length / sizeof(WCHAR)); + } catch (const std::exception& e) { + return std::format("<err: {}>", e.what()); + } +} + +spdlog::level::level_enum ConvertLogLevel(LogLevel level) +{ + switch (level) { + case LogLevel::Debug: + return spdlog::level::debug; + case LogLevel::Info: + return spdlog::level::info; + case LogLevel::Warning: + return spdlog::level::warn; + case LogLevel::Error: + return spdlog::level::err; + default: + return spdlog::level::debug; + } +} + +LogLevel ConvertLogLevel(spdlog::level::level_enum level) +{ + switch (level) { + case spdlog::level::debug: + return LogLevel::Debug; + case spdlog::level::info: + return LogLevel::Info; + case spdlog::level::warn: + return LogLevel::Warning; + case spdlog::level::err: + return LogLevel::Error; + default: + return LogLevel::Debug; + } +} + +} // namespace usvfs::log diff --git a/libs/usvfs/src/shared/loghelpers.h b/libs/usvfs/src/shared/loghelpers.h new file mode 100644 index 0000000..071ad9d --- /dev/null +++ b/libs/usvfs/src/shared/loghelpers.h @@ -0,0 +1,123 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ +#pragma once + +#include "dllimport.h" +#include "formatters.h" +#include "ntdll_declarations.h" +#include "shmlogger.h" +#include "stringutils.h" + +namespace usvfs::log +{ + +enum class DisplayStyle : uint8_t +{ + Hex = 0x01 +}; + +class CallLoggerDummy +{ +public: + template <typename T> + CallLoggerDummy& addParam(const char*, const T&, uint8_t style = 0) + { + return *this; + } +}; + +class CallLogger +{ +public: + explicit CallLogger(const char* function) + { + const char* namespaceend = strrchr(function, ':'); + + if (namespaceend != nullptr) { + function = namespaceend + 1; + } + + m_Message = function; + } + + ~CallLogger() + { + try { + static std::shared_ptr<spdlog::logger> log = spdlog::get("hooks"); + log->debug("{}", m_Message); + } catch (...) { + // suppress all exceptions in destructor + } + } + + template <typename T> + CallLogger& addParam(const char* name, const T& value, uint8_t style = 0); + +private: + std::string m_Message; +}; + +template <typename T> +CallLogger& CallLogger::addParam(const char* name, const T& value, uint8_t style) +{ + static bool enabled = spdlog::get("hooks")->should_log(spdlog::level::debug); + typedef std::underlying_type<DisplayStyle>::type DSType; + + if (enabled) { + if constexpr (std::is_pointer_v<T>) { + if (value == nullptr) { + std::format_to(std::back_inserter(m_Message), "[{}=<null>]", name); + } else { + std::format_to(std::back_inserter(m_Message), "[{}={}]", name, value); + } + } else if constexpr (std::is_integral_v<T>) { + if (style & static_cast<DSType>(DisplayStyle::Hex)) { + std::format_to(std::back_inserter(m_Message), "[{}={:x}]", name, value); + } else { + std::format_to(std::back_inserter(m_Message), "[{}={}]", name, value); + } + } else { + std::format_to(std::back_inserter(m_Message), "[{}={}]", name, value); + } + } + + return *this; +} + +spdlog::level::level_enum ConvertLogLevel(LogLevel level); +LogLevel ConvertLogLevel(spdlog::level::level_enum level); + +} // namespace usvfs::log + +// prefer the short variant of the function name, without signature. +// Fall back to the portable boost macro +#ifdef __FUNCTION__ +#define __MYFUNC__ __FUNCTION__ +#else +#define __MYFUNC__ BOOST_CURRENT_FUNCTION +#endif + +#define LOG_CALL() usvfs::log::CallLogger(__MYFUNC__) + +#define PARAM(val) addParam(#val, val) +#define PARAMHEX(val) \ + addParam(#val, val, static_cast<uint8_t>(usvfs::log::DisplayStyle::Hex)) +#define PARAMWRAP(val) addParam(#val, usvfs::log::wrap(val)) diff --git a/libs/usvfs/src/shared/ntdll_declarations.cpp b/libs/usvfs/src/shared/ntdll_declarations.cpp new file mode 100644 index 0000000..b4bf0f5 --- /dev/null +++ b/libs/usvfs/src/shared/ntdll_declarations.cpp @@ -0,0 +1,75 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ +#include "ntdll_declarations.h" +#include <cassert> + +#define LOAD_EXT(mod, name) \ + name = reinterpret_cast<name##_type>(::GetProcAddress(mod, #name)); \ + assert(name != nullptr) + +NtQueryDirectoryFile_type NtQueryDirectoryFile; +NtQueryDirectoryFileEx_type NtQueryDirectoryFileEx; +NtQueryFullAttributesFile_type NtQueryFullAttributesFile; +NtQueryAttributesFile_type NtQueryAttributesFile; +NtQueryObject_type NtQueryObject; +NtQueryInformationFile_type NtQueryInformationFile; +NtQueryInformationByName_type NtQueryInformationByName; +NtOpenFile_type NtOpenFile; +NtCreateFile_type NtCreateFile; +NtClose_type NtClose; +RtlDoesFileExists_U_type RtlDoesFileExists_U; +RtlDosPathNameToRelativeNtPathName_U_WithStatus_type + RtlDosPathNameToRelativeNtPathName_U_WithStatus; +RtlReleaseRelativeName_type RtlReleaseRelativeName; +RtlGetVersion_type RtlGetVersion; +NtTerminateProcess_type NtTerminateProcess; + +static bool ntdll_initialized; + +void ntdll_declarations_init() +{ + if (!ntdll_initialized) { + HMODULE ntDLLMod = GetModuleHandleW(L"ntdll.dll"); + + LOAD_EXT(ntDLLMod, NtQueryDirectoryFile); + LOAD_EXT(ntDLLMod, NtQueryDirectoryFileEx); + LOAD_EXT(ntDLLMod, NtQueryFullAttributesFile); + LOAD_EXT(ntDLLMod, NtQueryAttributesFile); + LOAD_EXT(ntDLLMod, NtQueryObject); + LOAD_EXT(ntDLLMod, NtQueryInformationFile); + LOAD_EXT(ntDLLMod, NtQueryInformationByName); + LOAD_EXT(ntDLLMod, NtCreateFile); + LOAD_EXT(ntDLLMod, NtOpenFile); + LOAD_EXT(ntDLLMod, NtClose); + LOAD_EXT(ntDLLMod, RtlDoesFileExists_U); + LOAD_EXT(ntDLLMod, RtlDosPathNameToRelativeNtPathName_U_WithStatus); + LOAD_EXT(ntDLLMod, RtlReleaseRelativeName); + LOAD_EXT(ntDLLMod, RtlGetVersion); + LOAD_EXT(ntDLLMod, NtTerminateProcess); + + ntdll_initialized = true; + } +} + +static struct __Initializer +{ + __Initializer() { ntdll_declarations_init(); } +} __initializer; diff --git a/libs/usvfs/src/shared/ntdll_declarations.h b/libs/usvfs/src/shared/ntdll_declarations.h new file mode 100644 index 0000000..813ad8c --- /dev/null +++ b/libs/usvfs/src/shared/ntdll_declarations.h @@ -0,0 +1,612 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ +#pragma once + +#include "windows_sane.h" + +#pragma warning(push) +#pragma warning(disable : 4201) + +typedef LONG NTSTATUS; + +typedef struct _IO_STATUS_BLOCK +{ + union + { + NTSTATUS Status; + PVOID Pointer; + } DUMMYUNIONNAME; + + ULONG_PTR Information; +} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; + +typedef struct _FILE_DIRECTORY_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + WCHAR FileName[1]; +} FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION; + +typedef struct _FILE_FULL_DIR_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + WCHAR FileName[1]; +} FILE_FULL_DIR_INFORMATION, *PFILE_FULL_DIR_INFORMATION; + +typedef struct _FILE_ID_FULL_DIR_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + LARGE_INTEGER FileId; + WCHAR FileName[1]; +} FILE_ID_FULL_DIR_INFORMATION, *PFILE_ID_FULL_DIR_INFORMATION; + +typedef struct _FILE_BOTH_DIR_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + CCHAR ShortNameLength; + WCHAR ShortName[12]; + WCHAR FileName[1]; +} FILE_BOTH_DIR_INFORMATION, *PFILE_BOTH_DIR_INFORMATION; + +typedef struct _FILE_ID_BOTH_DIR_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + CCHAR ShortNameLength; + WCHAR ShortName[12]; + LARGE_INTEGER FileId; + WCHAR FileName[1]; +} FILE_ID_BOTH_DIR_INFORMATION, *PFILE_ID_BOTH_DIR_INFORMATION; + +typedef struct _FILE_BASIC_INFORMATION +{ + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + ULONG FileAttributes; +} FILE_BASIC_INFORMATION, *PFILE_BASIC_INFORMATION; + +typedef struct _FILE_RENAME_INFORMATION +{ +#if (_WIN32_WINNT >= _WIN32_WINNT_WIN10_RS1) + union + { + BOOLEAN ReplaceIfExists; // FileRenameInformation + ULONG Flags; // FileRenameInformationEx + } DUMMYUNIONNAME; +#else + BOOLEAN ReplaceIfExists; +#endif + HANDLE RootDirectory; + ULONG FileNameLength; + WCHAR FileName[1]; +} FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION; + +typedef struct _FILE_STANDARD_INFORMATION +{ + LARGE_INTEGER AllocationSize; + LARGE_INTEGER EndOfFile; + ULONG NumberOfLinks; + BOOLEAN DeletePending; + BOOLEAN Directory; +} FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION; + +typedef struct _FILE_NAMES_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + ULONG FileNameLength; + WCHAR FileName[1]; +} FILE_NAMES_INFORMATION, *PFILE_NAMES_INFORMATION; + +typedef struct _FILE_INTERNAL_INFORMATION +{ + LARGE_INTEGER IndexNumber; +} FILE_INTERNAL_INFORMATION, *PFILE_INTERNAL_INFORMATION; + +typedef struct _FILE_EA_INFORMATION +{ + ULONG EaSize; +} FILE_EA_INFORMATION, *PFILE_EA_INFORMATION; + +typedef struct _FILE_ACCESS_INFORMATION +{ + ACCESS_MASK AccessFlags; +} FILE_ACCESS_INFORMATION, *PFILE_ACCESS_INFORMATION; + +typedef struct _FILE_POSITION_INFORMATION +{ + LARGE_INTEGER CurrentByteOffset; +} FILE_POSITION_INFORMATION, *PFILE_POSITION_INFORMATION; + +typedef struct _FILE_MODE_INFORMATION +{ + ULONG Mode; +} FILE_MODE_INFORMATION, *PFILE_MODE_INFORMATION; + +typedef struct _FILE_ALIGNMENT_INFORMATION +{ + ULONG AlignmentRequirement; +} FILE_ALIGNMENT_INFORMATION, *PFILE_ALIGNMENT_INFORMATION; + +typedef struct _FILE_NAME_INFORMATION +{ + ULONG FileNameLength; + WCHAR FileName[1]; +} FILE_NAME_INFORMATION, *PFILE_NAME_INFORMATION; + +typedef struct _FILE_ID_EXTD_DIR_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + ULONG ReparsePointTag; + FILE_ID_128 FileId; + WCHAR FileName[1]; +} FILE_ID_EXTD_DIR_INFORMATION, *PFILE_ID_EXTD_DIR_INFORMATION; + +typedef struct _FILE_ID_EXTD_BOTH_DIR_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + ULONG ReparsePointTag; + FILE_ID_128 FileId; + CCHAR ShortNameLength; + WCHAR ShortName[12]; + WCHAR FileName[1]; +} FILE_ID_EXTD_BOTH_DIR_INFORMATION, *PFILE_ID_EXTD_BOTH_DIR_INFORMATION; + +typedef struct _FILE_ID_64_EXTD_DIR_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + ULONG ReparsePointTag; + LARGE_INTEGER FileId; + WCHAR FileName[1]; +} FILE_ID_64_EXTD_DIR_INFORMATION, *PFILE_ID_64_EXTD_DIR_INFORMATION; + +typedef struct _FILE_ID_64_EXTD_BOTH_DIR_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + ULONG ReparsePointTag; + LARGE_INTEGER FileId; + CCHAR ShortNameLength; + WCHAR ShortName[12]; + WCHAR FileName[1]; +} FILE_ID_64_EXTD_BOTH_DIR_INFORMATION, *PFILE_ID_64_EXTD_BOTH_DIR_INFORMATION; + +typedef struct _FILE_ID_ALL_EXTD_DIR_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + ULONG ReparsePointTag; + LARGE_INTEGER FileId; + FILE_ID_128 FileId128; + WCHAR FileName[1]; +} FILE_ID_ALL_EXTD_DIR_INFORMATION, *PFILE_ID_ALL_EXTD_DIR_INFORMATION; + +typedef struct _FILE_ID_ALL_EXTD_BOTH_DIR_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + ULONG ReparsePointTag; + LARGE_INTEGER FileId; + FILE_ID_128 FileId128; + CCHAR ShortNameLength; + WCHAR ShortName[12]; + WCHAR FileName[1]; +} FILE_ID_ALL_EXTD_BOTH_DIR_INFORMATION, *PFILE_ID_ALL_EXTD_BOTH_DIR_INFORMATION; + +typedef struct _FILE_ALL_INFORMATION +{ + FILE_BASIC_INFORMATION BasicInformation; + FILE_STANDARD_INFORMATION StandardInformation; + FILE_INTERNAL_INFORMATION InternalInformation; + FILE_EA_INFORMATION EaInformation; + FILE_ACCESS_INFORMATION AccessInformation; + FILE_POSITION_INFORMATION PositionInformation; + FILE_MODE_INFORMATION ModeInformation; + FILE_ALIGNMENT_INFORMATION AlignmentInformation; + FILE_NAME_INFORMATION NameInformation; +} FILE_ALL_INFORMATION, *PFILE_ALL_INFORMATION; + +typedef struct _FILE_OBJECTID_INFORMATION +{ + LONGLONG FileReference; + UCHAR ObjectId[16]; + union + { + struct + { + UCHAR BirthVolumeId[16]; + UCHAR BirthObjectId[16]; + UCHAR DomainId[16]; + }; + UCHAR ExtendedInfo[48]; + }; +} FILE_OBJECTID_INFORMATION, *PFILE_OBJECTID_INFORMATION; + +typedef struct _FILE_REPARSE_POINT_INFORMATION +{ + LONGLONG FileReference; + 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) +#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) +#define STATUS_NO_SUCH_FILE ((NTSTATUS)0xC000000FL) + +#define SL_RESTART_SCAN 0x01 +#define SL_RETURN_SINGLE_ENTRY 0x02 +#define SL_INDEX_SPECIFIED 0x04 +#define SL_RETURN_ON_DISK_ENTRIES_ONLY 0x08 + +#define SL_QUERY_DIRECTORY_MASK 0x0b + +typedef enum _FILE_INFORMATION_CLASS +{ + FileDirectoryInformation = 1, + FileFullDirectoryInformation = 2, + FileBothDirectoryInformation = 3, + FileStandardInformation = 5, + FileNameInformation = 9, + FileRenameInformation = 10, + FileNamesInformation = 12, + FileAllInformation = 18, + FileObjectIdInformation = 29, + FileReparsePointInformation = 33, + FileIdBothDirectoryInformation = 37, + FileIdFullDirectoryInformation = 38, + FileNormalizedNameInformation = 48, + FileIdExtdDirectoryInformation = 60, + FileIdExtdBothDirectoryInformation = 63, + FileId64ExtdDirectoryInformation = 78, + FileId64ExtdBothDirectoryInformation = 79, + FileIdAllExtdDirectoryInformation = 80, + FileIdAllExtdBothDirectoryInformation = 81 +} FILE_INFORMATION_CLASS, + *PFILE_INFORMATION_CLASS; + +typedef enum _MODE +{ + KernelMode, + UserMode, + MaximumMode +} MODE; + +typedef struct _IO_STATUS_BLOCK IO_STATUS_BLOCK; + +typedef struct _IO_STATUS_BLOCK* PIO_STATUS_BLOCK; +// typedef VOID (NTAPI *PIO_APC_ROUTINE )(__in PVOID ApcContext, __in +// PIO_STATUS_BLOCK IoStatusBlock, __in ULONG Reserved); +typedef VOID(NTAPI* PIO_APC_ROUTINE)(PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, + ULONG Reserved); +typedef enum _FILE_INFORMATION_CLASS FILE_INFORMATION_CLASS; + +typedef struct _UNICODE_STRING +{ + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} UNICODE_STRING, *PUNICODE_STRING; +typedef const UNICODE_STRING* PCUNICODE_STRING; + +typedef struct _OBJECT_ATTRIBUTES +{ + ULONG Length; + HANDLE RootDirectory; + PUNICODE_STRING ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; + PVOID SecurityQualityOfService; +} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES; + +typedef enum _POOL_TYPE +{ + NonPagedPool, + NonPagedPoolExecute = NonPagedPool, + PagedPool, + NonPagedPoolMustSucceed = NonPagedPool + 2, + DontUseThisType, + NonPagedPoolCacheAligned = NonPagedPool + 4, + PagedPoolCacheAligned, + NonPagedPoolCacheAlignedMustS = NonPagedPool + 6, + MaxPoolType, + NonPagedPoolBase = 0, + NonPagedPoolBaseMustSucceed = NonPagedPoolBase + 2, + NonPagedPoolBaseCacheAligned = NonPagedPoolBase + 4, + NonPagedPoolBaseCacheAlignedMustS = NonPagedPoolBase + 6, + NonPagedPoolSession = 32, + PagedPoolSession = NonPagedPoolSession + 1, + NonPagedPoolMustSucceedSession = PagedPoolSession + 1, + DontUseThisTypeSession = NonPagedPoolMustSucceedSession + 1, + NonPagedPoolCacheAlignedSession = DontUseThisTypeSession + 1, + PagedPoolCacheAlignedSession = NonPagedPoolCacheAlignedSession + 1, + NonPagedPoolCacheAlignedMustSSession = PagedPoolCacheAlignedSession + 1, + NonPagedPoolNx = 512, + NonPagedPoolNxCacheAligned = NonPagedPoolNx + 4, + NonPagedPoolSessionNx = NonPagedPoolNx + 32 +} POOL_TYPE; + +typedef struct _OBJECT_TYPE_INITIALIZER +{ + WORD Length; + UCHAR ObjectTypeFlags; + ULONG CaseInsensitive : 1; + ULONG UnnamedObjectsOnly : 1; + ULONG UseDefaultObject : 1; + ULONG SecurityRequired : 1; + ULONG MaintainHandleCount : 1; + ULONG MaintainTypeList : 1; + ULONG ObjectTypeCode; + ULONG InvalidAttributes; + GENERIC_MAPPING GenericMapping; + ULONG ValidAccessMask; + POOL_TYPE PoolType; + ULONG DefaultPagedPoolCharge; + ULONG DefaultNonPagedPoolCharge; + PVOID DumpProcedure; + LONG* OpenProcedure; + PVOID CloseProcedure; + PVOID DeleteProcedure; + LONG* ParseProcedure; + LONG* SecurityProcedure; + LONG* QueryNameProcedure; + UCHAR* OkayToCloseProcedure; +} OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER; + +typedef CCHAR KPROCESSOR_MODE; + +typedef struct _OBJECT_HANDLE_INFORMATION +{ + ULONG HandleAttributes; + ACCESS_MASK GrantedAccess; +} OBJECT_HANDLE_INFORMATION, *POBJECT_HANDLE_INFORMATION; + +typedef struct _OBJECT_NAME_INFORMATION +{ + UNICODE_STRING Name; +} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION; + +typedef enum _OBJECT_INFORMATION_CLASS +{ + ObjectBasicInformation = 0, + ObjectNameInformation = 1, + ObjectTypeInformation = 2 +} OBJECT_INFORMATION_CLASS; + +typedef struct _RTL_RELATIVE_NAME +{ + UNICODE_STRING RelativeName; + HANDLE ContainingDirectory; + void* CurDirRef; +} RTL_RELATIVE_NAME, *PRTL_RELATIVE_NAME; + +typedef struct _FILE_NETWORK_OPEN_INFORMATION +{ + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER AllocationSize; + LARGE_INTEGER EndOfFile; + ULONG FileAttributes; +} FILE_NETWORK_OPEN_INFORMATION, *PFILE_NETWORK_OPEN_INFORMATION; + +#define FILE_DIRECTORY_FILE 0x00000001 +#define FILE_WRITE_THROUGH 0x00000002 +#define FILE_SEQUENTIAL_ONLY 0x00000004 +#define FILE_NO_INTERMEDIATE_BUFFERING 0x00000008 +#define FILE_SYNCHRONOUS_IO_ALERT 0x00000010 +#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020 +#define FILE_NON_DIRECTORY_FILE 0x00000040 +#define FILE_CREATE_TREE_CONNECTION 0x00000080 +#define FILE_COMPLETE_IF_OPLOCKED 0x00000100 +#define FILE_NO_EA_KNOWLEDGE 0x00000200 +#define FILE_OPEN_REMOTE_INSTANCE 0x00000400 +#define FILE_RANDOM_ACCESS 0x00000800 +#define FILE_DELETE_ON_CLOSE 0x00001000 +#define FILE_OPEN_BY_FILE_ID 0x00002000 +#define FILE_OPEN_FOR_BACKUP_INTENT 0x00004000 +#define FILE_NO_COMPRESSION 0x00008000 +#define FILE_OPEN_REQUIRING_OPLOCK 0x00010000 +#define FILE_DISALLOW_EXCLUSIVE 0x00020000 +#define FILE_SESSION_AWARE 0x00040000 +#define FILE_RESERVE_OPFILTER 0x00100000 +#define FILE_OPEN_REPARSE_POINT 0x00200000 +#define FILE_OPEN_NO_RECALL 0x00400000 +#define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000 +#define FILE_CONTAINS_EXTENDED_CREATE_INFORMATION 0x10000000 + +// Nt + +using NtQueryDirectoryFile_type = NTSTATUS(WINAPI*)(HANDLE, HANDLE, PIO_APC_ROUTINE, + PVOID, PIO_STATUS_BLOCK, PVOID, + ULONG, FILE_INFORMATION_CLASS, + BOOLEAN, PUNICODE_STRING, BOOLEAN); +using NtQueryDirectoryFileEx_type = NTSTATUS(WINAPI*)(HANDLE, HANDLE, PIO_APC_ROUTINE, + PVOID, PIO_STATUS_BLOCK, PVOID, + ULONG, FILE_INFORMATION_CLASS, + ULONG, PUNICODE_STRING); + +using NtQueryFullAttributesFile_type = + NTSTATUS(WINAPI*)(POBJECT_ATTRIBUTES, PFILE_NETWORK_OPEN_INFORMATION); +using NtQueryAttributesFile_type = NTSTATUS(WINAPI*)(POBJECT_ATTRIBUTES, + PFILE_BASIC_INFORMATION); + +using NtQueryObject_type = NTSTATUS(WINAPI*)( + HANDLE Handle, OBJECT_INFORMATION_CLASS ObjectInformationClass, + PVOID ObjectInformation, ULONG ObjectInformationLength, PULONG ReturnLength); +using NtQueryInformationFile_type = NTSTATUS(WINAPI*)( + HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation, + ULONG Length, FILE_INFORMATION_CLASS FileInformationClass); + +using NtQueryInformationByName_type = NTSTATUS(WINAPI*)( + HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation, + ULONG Length, FILE_INFORMATION_CLASS FileInformationClass); + +using NtOpenFile_type = NTSTATUS(WINAPI*)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, + PIO_STATUS_BLOCK, ULONG, ULONG); +using NtCreateFile_type = NTSTATUS(WINAPI*)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, + PIO_STATUS_BLOCK, PLARGE_INTEGER, ULONG, + ULONG, ULONG, ULONG, PVOID, ULONG); + +using NtClose_type = NTSTATUS(WINAPI*)(HANDLE); + +using NtTerminateProcess_type = NTSTATUS(WINAPI*)(HANDLE ProcessHandle, + NTSTATUS ExitStatus); + +// Rtl + +using RtlDoesFileExists_U_type = NTSYSAPI BOOLEAN(NTAPI*)(PCWSTR); +using RtlDosPathNameToRelativeNtPathName_U_WithStatus_type = + NTSTATUS(NTAPI*)(PCWSTR DosFileName, PUNICODE_STRING NtFileName, PWSTR* FilePath, + PRTL_RELATIVE_NAME RelativeName); +using RtlReleaseRelativeName_type = void(NTAPI*)(PRTL_RELATIVE_NAME RelativeName); +using RtlGetVersion_type = NTSTATUS(NTAPI*)(PRTL_OSVERSIONINFOW); + +extern NtQueryDirectoryFile_type NtQueryDirectoryFile; +extern NtQueryDirectoryFileEx_type NtQueryDirectoryFileEx; +extern NtQueryFullAttributesFile_type NtQueryFullAttributesFile; +extern NtQueryAttributesFile_type NtQueryAttributesFile; +extern NtQueryObject_type NtQueryObject; +extern NtQueryInformationFile_type NtQueryInformationFile; +extern NtQueryInformationByName_type NtQueryInformationByName; +extern NtOpenFile_type NtOpenFile; +extern NtCreateFile_type NtCreateFile; +extern NtClose_type NtClose; +extern NtTerminateProcess_type NtTerminateProcess; +extern RtlDoesFileExists_U_type RtlDoesFileExists_U; +extern RtlDosPathNameToRelativeNtPathName_U_WithStatus_type + RtlDosPathNameToRelativeNtPathName_U_WithStatus; +extern RtlReleaseRelativeName_type RtlReleaseRelativeName; +extern RtlGetVersion_type RtlGetVersion; + +// ensures ntdll functions have been initialized (only needed during static objects +// initialization) +void ntdll_declarations_init(); + +#pragma warning(pop) diff --git a/libs/usvfs/src/shared/pch.cpp b/libs/usvfs/src/shared/pch.cpp new file mode 100644 index 0000000..1d9f38c --- /dev/null +++ b/libs/usvfs/src/shared/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/libs/usvfs/src/shared/pch.h b/libs/usvfs/src/shared/pch.h new file mode 100644 index 0000000..e787f68 --- /dev/null +++ b/libs/usvfs/src/shared/pch.h @@ -0,0 +1,87 @@ +#pragma once + +#include <algorithm> +#include <atomic> +#include <bitset> +#include <cassert> +#include <codecvt> +#include <cstddef> +#include <cstdint> +#include <cstring> +#include <ctime> +#include <exception> +#include <format> +#include <functional> +#include <future> +#include <iomanip> +#include <ios> +#include <limits> +#include <map> +#include <memory> +#include <mutex> +#include <regex> +#include <shared_mutex> +#include <sstream> +#include <stdexcept> +#include <string> +#include <thread> +#include <type_traits> +#include <utility> +#include <vector> + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +// clang-format off +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +#include <ShlObj.h> +#include <comutil.h> +#include <Psapi.h> +#include <shellapi.h> +#include <shlwapi.h> +#include <fileapi.h> +#include <DbgHelp.h> +// clang-format on + +#define BOOST_INTERPROCESS_SEGMENT_MANAGER_ABI 1 +#include <boost/algorithm/string.hpp> +#include <boost/algorithm/string/case_conv.hpp> +#include <boost/any.hpp> +#include <boost/container/flat_set.hpp> +#include <boost/container/map.hpp> +#include <boost/container/scoped_allocator.hpp> +#include <boost/container/slist.hpp> +#include <boost/container/string.hpp> +#include <boost/container/vector.hpp> +#include <boost/current_function.hpp> +#include <boost/dll/runtime_symbol_info.hpp> +#include <boost/filesystem.hpp> +#include <boost/filesystem/detail/utf8_codecvt_facet.hpp> +#include <boost/filesystem/operations.hpp> +#include <boost/filesystem/path.hpp> +#include <boost/format.hpp> +#include <boost/interprocess/ipc/message_queue.hpp> +#include <boost/interprocess/managed_windows_shared_memory.hpp> +#include <boost/interprocess/offset_ptr.hpp> +#include <boost/interprocess/smart_ptr/deleter.hpp> +#include <boost/interprocess/smart_ptr/shared_ptr.hpp> +#include <boost/interprocess/smart_ptr/weak_ptr.hpp> +#include <boost/interprocess/sync/named_mutex.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/locale.hpp> +#include <boost/multi_index/member.hpp> +#include <boost/multi_index/ordered_index.hpp> +#include <boost/multi_index_container.hpp> +#include <boost/predef.h> +#include <boost/static_assert.hpp> +#include <boost/thread.hpp> +#include <boost/thread/shared_lock_guard.hpp> +#include <boost/thread/shared_mutex.hpp> +#include <boost/tokenizer.hpp> +#include <boost/type_traits.hpp> + +#include <spdlog/spdlog.h> + +namespace fs = boost::filesystem; diff --git a/libs/usvfs/src/shared/shared_memory.h b/libs/usvfs/src/shared/shared_memory.h new file mode 100644 index 0000000..28da34b --- /dev/null +++ b/libs/usvfs/src/shared/shared_memory.h @@ -0,0 +1,53 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +namespace bi = boost::interprocess; +namespace bc = boost::container; + +namespace usvfs::shared +{ + +template <typename T> +using OffsetPtrT = bi::offset_ptr<T, std::int32_t, std::uint64_t>; + +using VoidPointerT = OffsetPtrT<void>; + +// important: the windows shared memory mechanism, unlike other implementations, +// automatically removes the SHM object when there are no more "subscribers". +// MO currently depends on that feature! + +// managed_windows_shared_memory apparently doesn't support sharing between +// 64bit and 32bit processes +using managed_windows_shared_memory = bi::basic_managed_windows_shared_memory< + char, bi::rbtree_best_fit<bi::mutex_family, VoidPointerT, 8>, bi::iset_index>; + +using SharedMemoryT = managed_windows_shared_memory; +using SegmentManagerT = SharedMemoryT::segment_manager; + +using VoidAllocatorT = boost::container::scoped_allocator_adaptor< + boost::interprocess::allocator<void, SegmentManagerT>>; +using CharAllocatorT = VoidAllocatorT::rebind<char>::other; + +using StringT = bc::basic_string<char, std::char_traits<char>, CharAllocatorT>; + +} // namespace usvfs::shared diff --git a/libs/usvfs/src/shared/shmlogger.cpp b/libs/usvfs/src/shared/shmlogger.cpp new file mode 100644 index 0000000..66512de --- /dev/null +++ b/libs/usvfs/src/shared/shmlogger.cpp @@ -0,0 +1,205 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ +#pragma warning(disable : 4714) +#pragma warning(disable : 4503) +#pragma warning(push, 3) +#include "shmlogger.h" +#pragma warning(pop) + +#pragma warning(disable : 4996) + +using namespace boost::interprocess; +using namespace boost::posix_time; + +SHMLogger* SHMLogger::s_Instance = nullptr; + +SHMLogger::owner_t SHMLogger::owner; +SHMLogger::client_t SHMLogger::client; + +SHMLogger::SHMLogger(owner_t, const std::string& queueName) + : m_QueueName(queueName), + m_LogQueue(create_only, queueName.c_str(), MESSAGE_COUNT, MESSAGE_SIZE), + m_DroppedMessages(0) +{ + if (s_Instance != nullptr) { + throw std::runtime_error("duplicate shm logger instantiation"); + } else { + s_Instance = this; + } +} + +SHMLogger::SHMLogger(client_t, const std::string& queueName) + : m_QueueName(queueName), m_LogQueue(open_only, queueName.c_str()), + m_DroppedMessages(0) +{ + if (s_Instance != nullptr) { + throw std::runtime_error("duplicate shm logger instantiation"); + } else { + s_Instance = this; + } +} + +SHMLogger::~SHMLogger() +{ + s_Instance = nullptr; + message_queue_interop::remove(m_QueueName.c_str()); +} + +SHMLogger& SHMLogger::create(const char* instanceName) +{ + if (s_Instance != nullptr) { + throw std::runtime_error("duplicate shm logger instantiation"); + } else { + std::string queueName = std::string("__shm_sink_") + instanceName; + message_queue::remove(queueName.c_str()); + new SHMLogger(owner, queueName); + atexit([]() { + delete s_Instance; + }); + } + return *s_Instance; +} + +SHMLogger& SHMLogger::open(const char* instanceName) +{ + if (s_Instance != nullptr) { + throw std::runtime_error("duplicate shm logger instantiation"); + } else { + new SHMLogger(client, std::string("__shm_sink_") + instanceName); + } + return *s_Instance; +} + +void SHMLogger::free() +{ + if (s_Instance != nullptr) { + SHMLogger* temp = s_Instance; + s_Instance = nullptr; + delete temp; + } +} + +bool SHMLogger::tryGet(char* buffer, size_t bufferSize) +{ + message_queue_interop::size_type receivedSize; + unsigned int prio; + bool res = m_LogQueue.try_receive(buffer, static_cast<unsigned int>(bufferSize), + receivedSize, prio); + if (res) { + buffer[std::min(bufferSize - 1, static_cast<size_t>(receivedSize))] = '\0'; + } + return res; +} + +void SHMLogger::get(char* buffer, size_t bufferSize) +{ + message_queue_interop::size_type receivedSize; + unsigned int prio; + m_LogQueue.receive(buffer, static_cast<unsigned int>(bufferSize), receivedSize, prio); + buffer[std::min(bufferSize - 1, static_cast<size_t>(receivedSize))] = '\0'; +} + +usvfs::sinks::shm_sink::shm_sink(const char* queueName) + : m_LogQueue(open_only, (std::string("__shm_sink_") + queueName).c_str()), + m_DroppedMessages(0) +{} + +void usvfs::sinks::shm_sink::flush_() {} + +void usvfs::sinks::shm_sink::sink_it_(const spdlog::details::log_msg& msg) +{ + int droppedMessages = m_DroppedMessages.load(std::memory_order_relaxed); + if (droppedMessages > 0) { + auto dropMessage = std::format("{} debug messages dropped", droppedMessages); + if (m_LogQueue.try_send(dropMessage.c_str(), + static_cast<unsigned int>(dropMessage.size()), 0)) { + m_DroppedMessages.store(0, std::memory_order_relaxed); + } + } + + std::string message{msg.payload}; + + // blacklist %USERNAME% because PII + static const std::string username = std::string(getenv("USERNAME")); + boost::algorithm::ireplace_all(message, std::string("\\") + username, "\\USERNAME"); + boost::algorithm::ireplace_all(message, std::string("/") + username, "/USERNAME"); + + if (message.length() > SHMLogger::MESSAGE_SIZE) { + std::vector<std::string> splitVec; + boost::split(splitVec, message, boost::is_any_of("\r\n"), boost::token_compress_on); + for (const std::string& line : splitVec) { + output(msg.level, line); + } + } else { + output(msg.level, message); + } +} + +void usvfs::sinks::shm_sink::output(spdlog::level::level_enum lev, + const std::string& message) +{ + bool sent = true; + + // spdlog auto-append line breaks which we don't need. Probably would be + // better to not write the breaks to begin with? + size_t count = std::min(message.find_last_not_of("\r\n") + 1, + static_cast<size_t>(m_LogQueue.get_max_msg_size())); + + // depending on the log level, drop less important messages if the receiver + // can't keep up + switch (lev) { + case spdlog::level::trace: + case spdlog::level::debug: + case spdlog::level::info: { + // m_LogQueue.send(message.c_str(), count, 0); + sent = m_LogQueue.try_send(message.c_str(), static_cast<unsigned int>(count), 0); + } break; + case spdlog::level::err: + case spdlog::level::critical: { + m_LogQueue.send(message.c_str(), static_cast<unsigned int>(count), 0); + } break; + default: { + boost::posix_time::ptime time = + microsec_clock::universal_time() + boost::posix_time::milliseconds(200); + sent = m_LogQueue.timed_send(message.c_str(), static_cast<unsigned int>(count), 0, + time); + } break; + } + + if (!sent) { + m_DroppedMessages.fetch_add(1, std::memory_order_relaxed); + } +} + +void __cdecl boost::interprocess::ipcdetail::get_shared_dir(std::string& shared_dir) +{ + PWSTR path; + if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_ProgramData, 0, NULL, &path))) { + _bstr_t bPath(path); + shared_dir = (char*)bPath; + shared_dir += "\\USVFS"; + } else { + shared_dir = "C:\\ProgramData\\USVFS"; + } + boost::filesystem::path boostPath(shared_dir); + if (!boost::filesystem::exists(boostPath)) + boost::filesystem::create_directories(boostPath); +} diff --git a/libs/usvfs/src/shared/shmlogger.h b/libs/usvfs/src/shared/shmlogger.h new file mode 100644 index 0000000..158f941 --- /dev/null +++ b/libs/usvfs/src/shared/shmlogger.h @@ -0,0 +1,105 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ +#pragma once + +#include <spdlog/details/null_mutex.h> +#include <spdlog/sinks/base_sink.h> + +#include "logging.h" +#include "shared_memory.h" +#include "windows_sane.h" + +typedef boost::interprocess::message_queue_t<usvfs::shared::VoidPointerT> + message_queue_interop; + +namespace usvfs::sinks +{ +class shm_sink : public spdlog::sinks::base_sink<spdlog::details::null_mutex> +{ +public: + shm_sink(const char* queueName); + +protected: + void sink_it_(const spdlog::details::log_msg& msg) override; + void output(spdlog::level::level_enum lev, const std::string& message); + void flush_() override; + +private: + message_queue_interop m_LogQueue; + std::atomic<int> m_DroppedMessages; +}; + +} // namespace usvfs::sinks + +class SHMLogger +{ +public: + static const size_t MESSAGE_COUNT = 1024; + static const size_t MESSAGE_SIZE = 512; + + static SHMLogger& create(const char* instanceName); + static SHMLogger& open(const char* instanceName); + static void free(); + + static bool isInstantiated() { return s_Instance != nullptr; } + + static inline SHMLogger& instance() + { + if (s_Instance == nullptr) { + throw std::runtime_error("shm logger not instantiated"); + } + + return *s_Instance; + } + + void log(LogLevel logLevel, const std::string& message); + bool tryGet(char* buffer, size_t bufferSize); + void get(char* buffer, size_t bufferSize); + +private: + struct owner_t + {}; + static owner_t owner; + + struct client_t + {}; + static client_t client; + +private: + SHMLogger(owner_t, const std::string& instanceName); + SHMLogger(client_t, const std::string& instanceName); + + SHMLogger(const SHMLogger&) = delete; + SHMLogger& operator=(const SHMLogger&) = delete; + + ~SHMLogger(); + +private: + static SHMLogger* s_Instance; + + message_queue_interop m_LogQueue; + + std::string m_SHMName; + std::string m_LockName; + std::string m_QueueName; + + std::atomic<int> m_DroppedMessages; +}; diff --git a/libs/usvfs/src/shared/stringcast.cpp b/libs/usvfs/src/shared/stringcast.cpp new file mode 100644 index 0000000..47822d5 --- /dev/null +++ b/libs/usvfs/src/shared/stringcast.cpp @@ -0,0 +1,40 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ +#include "stringcast.h" + +namespace usvfs::shared +{ + +UINT windowsCP(CodePage codePage) +{ + switch (codePage) { + case CodePage::LOCAL: + return CP_ACP; + case CodePage::UTF8: + return CP_UTF8; + case CodePage::LATIN1: + return 850; + } + // this should not be possible in practice + throw std::runtime_error("unsupported codePage"); +} + +} // namespace usvfs::shared diff --git a/libs/usvfs/src/shared/stringcast.h b/libs/usvfs/src/shared/stringcast.h new file mode 100644 index 0000000..0ce56d1 --- /dev/null +++ b/libs/usvfs/src/shared/stringcast.h @@ -0,0 +1,170 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ +#pragma once + +#include "exceptionex.h" + +namespace usvfs::shared +{ + +enum class CodePage +{ + LOCAL, + LATIN1, + UTF8 +}; + +template <typename ToT, typename FromT> +class string_cast_impl +{ +public: + static ToT cast(const FromT& source, CodePage codePage, size_t sourceLength); +}; + +template <typename ToT, typename FromT> +ToT string_cast(FromT source, CodePage codePage = CodePage::LOCAL, + size_t sourceLength = std::numeric_limits<size_t>::max()) +{ + return string_cast_impl<ToT, FromT>::cast(source, codePage, sourceLength); +} + +template <typename ToT, typename CharT> +class string_cast_impl<ToT, std::basic_string<CharT>> +{ +public: + static ToT cast(const std::basic_string<CharT>& source, CodePage codePage, + size_t sourceLength) + { + return string_cast_impl<ToT, const CharT*>::cast(source.c_str(), codePage, + sourceLength); + } +}; + +template <typename ToT, typename CharT> +class string_cast_impl<ToT, CharT*> +{ + BOOST_STATIC_ASSERT(!boost::is_base_and_derived<ToT, CharT>::value); + +public: + static ToT cast(CharT* source, CodePage codePage, size_t sourceLength) + { + return string_cast_impl<ToT, const CharT*>::cast(source, codePage, sourceLength); + } +}; + +template <typename ToT, typename CharT, int N> +class string_cast_impl<ToT, CharT[N]> +{ +public: + static ToT cast(CharT (&source)[N], CodePage codePage, size_t sourceLength) + { + return string_cast_impl<ToT, const CharT*>::cast(source, codePage, sourceLength); + } +}; + +UINT windowsCP(CodePage codePage); + +template <> +class string_cast_impl<std::string, const wchar_t*> +{ +public: + static std::string cast(const wchar_t* const& source, CodePage codePage, + size_t sourceLength) + { + std::string result; + + if (sourceLength == std::numeric_limits<size_t>::max()) { + sourceLength = wcslen(source); + } + + if (sourceLength > 0) { + // use utf8 or local 8-bit encoding depending on user choice + UINT cp = windowsCP(codePage); + // preflight to find out the required buffer size + int outLength = WideCharToMultiByte(cp, 0, source, static_cast<int>(sourceLength), + nullptr, 0, nullptr, nullptr); + if (outLength == 0) { + throw windows_error("string conversion failed"); + } + result.resize(outLength); + outLength = WideCharToMultiByte(cp, 0, source, static_cast<int>(sourceLength), + &result[0], outLength, nullptr, nullptr); + if (outLength == 0) { + throw windows_error("string conversion failed"); + } + // fix output string length (i.e. in case of unconvertible characters + while (result[outLength - 1] == L'\0') { + result.resize(--outLength); + } + } + + return result; + } +}; + +template <> +class string_cast_impl<std::wstring, const char*> +{ +public: + static std::wstring cast(const char* const& source, CodePage codePage, + size_t sourceLength) + { + std::wstring result; + + if (sourceLength == std::numeric_limits<size_t>::max()) { + sourceLength = strlen(source); + } + + if (sourceLength > 0) { + // use utf8 or local 8-bit encoding depending on user choice + UINT cp = windowsCP(codePage); + // preflight to find out the required source size + int outLength = MultiByteToWideChar(cp, 0, source, static_cast<int>(sourceLength), + &result[0], 0); + if (outLength == 0) { + throw windows_error("string conversion failed"); + } + result.resize(outLength); + outLength = MultiByteToWideChar(cp, 0, source, static_cast<int>(sourceLength), + &result[0], outLength); + if (outLength == 0) { + throw windows_error("string conversion failed"); + } + while (result[outLength - 1] == L'\0') { + result.resize(--outLength); + } + } + + return result; + } +}; + +template <> +class string_cast_impl<std::wstring, const wchar_t*> +{ +public: + static std::wstring cast(const wchar_t* const& source, CodePage, size_t) + { + return std::wstring(source); + } +}; + +} // namespace usvfs::shared diff --git a/libs/usvfs/src/shared/stringutils.cpp b/libs/usvfs/src/shared/stringutils.cpp new file mode 100644 index 0000000..2ddbf64 --- /dev/null +++ b/libs/usvfs/src/shared/stringutils.cpp @@ -0,0 +1,146 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ +#include "stringutils.h" +#include "windows_sane.h" + +namespace usvfs::shared +{ + +void strncpy_sz(char* dest, const char* src, size_t destSize) +{ + if (destSize > 0) { + strncpy(dest, src, destSize - 1); + dest[destSize - 1] = '\0'; + } +} + +void wcsncpy_sz(wchar_t* dest, const wchar_t* src, size_t destSize) +{ + if ((destSize > 0) && (dest != nullptr)) { + wcsncpy(dest, src, destSize - 1); + dest[destSize - 1] = L'\0'; + } +} + +bool startswith(const wchar_t* string, const wchar_t* subString) +{ + while ((*string != '\0') && (*subString != '\0')) { + if (towlower(*string) != towlower(*subString)) { + return false; + } + ++string; + ++subString; + } + + return *subString == '\0'; +} + +static fs::path normalize(const fs::path& path) +{ + fs::path result; + + boost::locale::generator gen; + auto loc = gen("en_US.UTF-8"); + for (fs::path::iterator iter = path.begin(); iter != path.end(); ++iter) { + if (*iter == "..") { + result = result.parent_path(); + } else if (*iter != ".") { + result /= boost::to_lower_copy(iter->string(), loc); + } // single dot is ignored + } + return result; +} + +fs::path make_relative(const fs::path& fromIn, const fs::path& toIn) +{ + // converting path to lower case to make iterator comparison work correctly + // on case-insenstive filesystems + fs::path from(fs::absolute(fromIn)); + fs::path to(fs::absolute(toIn)); + + // find common base + fs::path::const_iterator fromIter(from.begin()); + fs::path::const_iterator toIter(to.begin()); + + // TODO the following equivalent test is probably quite expensive as new + // paths are created for each iteration but the case sensitivity depends on + // the fs + while ((fromIter != from.end()) && (toIter != to.end()) && + (boost::iequals(fromIter->string(), toIter->string()))) { + ++fromIter; + ++toIter; + } + + // Navigate backwards in directory to reach previously found base + fs::path result; + for (; fromIter != from.end(); ++fromIter) { + if (*fromIter != ".") { + result /= ".."; + } + } + + // Now navigate down the directory branch + for (; toIter != to.end(); ++toIter) { + result /= *toIter; + } + return result; +} + +std::string to_hex(void* bufferIn, size_t bufferSize) +{ + unsigned char* buffer = static_cast<unsigned char*>(bufferIn); + std::ostringstream temp; + temp << std::hex; + for (size_t i = 0; i < bufferSize; ++i) { + temp << std::setfill('0') << std::setw(2) << (unsigned int)buffer[i]; + if ((i % 16) == 15) { + temp << "\n"; + } else { + temp << " "; + } + } + return temp.str(); +} + +std::wstring to_upper(const std::wstring& input) +{ + std::wstring result; + result.resize(input.size()); + ::LCMapStringW(LOCALE_INVARIANT, LCMAP_UPPERCASE, input.c_str(), + static_cast<int>(input.size()), &result[0], + static_cast<int>(result.size())); + return result; +} + +std::string byte_string(std::size_t n) +{ + auto s = std::to_string(n); + auto p = s.size(); + + while (p > 3) { + p -= 3; + s.insert(p, 1, ','); + } + + return s + " B"; +} + +} // namespace usvfs::shared diff --git a/libs/usvfs/src/shared/stringutils.h b/libs/usvfs/src/shared/stringutils.h new file mode 100644 index 0000000..64d0af7 --- /dev/null +++ b/libs/usvfs/src/shared/stringutils.h @@ -0,0 +1,57 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ +#pragma once + +namespace usvfs::shared +{ + +void strncpy_sz(char* dest, const char* src, size_t destSize); +void wcsncpy_sz(wchar_t* dest, const wchar_t* src, size_t destSize); + +bool startswith(const wchar_t* string, const wchar_t* subString); + +// Return path when appended to a_From will resolve to same as a_To +fs::path make_relative(const fs::path& from, const fs::path& to); + +std::string to_hex(void* bufferIn, size_t bufferSize); + +// convert unicode string to upper-case (locale invariant) +std::wstring to_upper(const std::wstring& input); + +// formats a number with thousand separators and B at the end +// +std::string byte_string(std::size_t n); + +class FormatGuard +{ + std::ostream& m_Stream; + std::ios::fmtflags m_Flags; + +public: + FormatGuard(std::ostream& stream) : m_Stream(stream), m_Flags(stream.flags()) {} + + ~FormatGuard() { m_Stream.flags(m_Flags); } + + FormatGuard(const FormatGuard&) = delete; + FormatGuard& operator=(FormatGuard&) = delete; +}; + +} // namespace usvfs::shared diff --git a/libs/usvfs/src/shared/tree_container.h b/libs/usvfs/src/shared/tree_container.h new file mode 100644 index 0000000..a2f61fa --- /dev/null +++ b/libs/usvfs/src/shared/tree_container.h @@ -0,0 +1,630 @@ +#pragma once + +#include "directory_tree.h" +#include "shared_memory.h" + +namespace usvfs::shared +{ + +// smart pointer to DirectoryTrees (only intended for top-level nodes). This +// will transparently switch to new shared memory regions in case they get +// reallocated +// +// +// reassign() is called from a variety of places when the current chunk of +// shared memory is full or is marked as being outdated; its job is to either +// find another chunk that may have been created by another process or to create +// a brand new one +// +// +// if there is only a single process hooked, it will slowly fill up the shared +// memory when adding files, eventually throw a bi::bad_alloc and end up in +// reassign(); a new block will be allocated, the data copied over, and the old +// block will be deallocated +// +// +// when multiple processes are involved, things are more complicated +// +// two processes A and B will start by using the same shared memory, but they +// have their own pointer to it that's local to the process (the `m_TreeMeta` +// member variable) +// +// so when process A fills up the shared memory and reallocates it, process B +// is still pointing to the old shared memory; only when process B does some +// operation that accesses the file tree will the pointer be checked and +// adjusted to point to the new shared memory +// +// in this example, when process A ran out of memory, it set `outdated` to +// `true` in the shared memory block, allocated a new one and copied the data +// over, but it did not deallocate the block because process B is still +// pointing to it +// +// when process B tries to access the block, it checks `outdated` (see get()); +// if it's true, it means that it's pointing to an outdated shared memory block +// and must find the new one that process A created +// +// the new block is not necessarily the very next name, it is possible for +// process A to burn through a series of blocks quickly when adding a bunch of +// new files, and these blocks will all have been deallocated by the time +// process B tries to find the newest one +// +// so when a process sees its block as outdated, it will try to open a bunch of +// names until one exists that is not outdated; if it can't find the block +// (shouldn't happen), it will just create a new one +// +template <typename TreeT> +class TreeContainer +{ +public: + /** + * @brief Constructor + * @param SHMName name of the shared memory holding the tree. This should contain the + * running number + * @param size initial size in bytes of the container. since the tree is resized by + * doubling this should be a power of two. 64k is supposed to be the page size on + * windows so smaller allocations make little sense + * @note size can't be too small. If initial allocations fail automatic growing won't + * work + */ + TreeContainer(const std::string& SHMName, size_t size = 64 * 1024) + : m_TreeMeta(nullptr), m_SHMName(SHMName) + { + std::locale global_loc = std::locale(); + std::locale loc(global_loc, new fs::detail::utf8_codecvt_facet); + fs::path::imbue(loc); + + // append _1 to the name if it doesn't end with _N already + std::regex pattern(R"exp((.*_)(\d+))exp"); + std::smatch match; + std::string shmName(m_SHMName.c_str()); + regex_match(shmName, match, pattern); + + if (match.size() != 3) { + m_SHMName += "_1"; + } + + // creates a new memory block if this is the first process to run or attach + // to an already existing one + createOrOpen(m_SHMName, size); + + spdlog::get("usvfs")->info("attached to {0} with {1} nodes, size {2}", m_SHMName, + m_TreeMeta->tree->numNodesRecursive(), + byte_string(m_SHM->get_size())); + } + + TreeContainer(const TreeContainer&) = delete; + TreeContainer& operator=(const TreeContainer&) = delete; + + ~TreeContainer() + { + if (unassign(m_SHM, m_TreeMeta)) { + bi::shared_memory_object::remove(m_SHMName.c_str()); + } + } + + /** + * @return retrieve an allocator that can be used to create objects in this tree + */ + VoidAllocatorT allocator() { return VoidAllocatorT(m_SHM->get_segment_manager()); } + + template <typename... Arguments> + typename TreeT::DataT create(Arguments&&... args) + { + return TreeT::DataT(std::forward<Arguments>(args)..., allocator()); + } + + TreeT* operator->() { return get(); } + + /** + * @return raw pointer to the managed tree + */ + TreeT* get() + { + if (m_TreeMeta->outdated) { + reassign(); + } + + return m_TreeMeta->tree.get(); + } + + /** + * @return raw const pointer to the managed tree + */ + const TreeT* get() const + { + if (m_TreeMeta->outdated) { + // safe const_cast, TreeContainer are never created const + const_cast<TreeContainer<TreeT>*>(this)->reassign(); + } + + return m_TreeMeta->tree.get(); + } + + const TreeT* operator->() const { return get(); } + + /** + * @return current name of the managed shared memory + */ + std::string shmName() const { return m_SHMName; } + + void clear() { m_TreeMeta->tree->clear(); } + + /** + * @brief add a new file to the tree + * + * @param name name of the file, expected to be relative to this directory + * @param data the file data to attach + * @param flags flags for this files + * @param overwrite if true, the new leaf will overwrite an existing one that compares + *as "equal" + * @return pointer to the new node or a null ptr + **/ + template <typename T> + typename TreeT::NodePtrT addFile(const fs::path& name, const T& data, + TreeFlags flags = 0, bool overwrite = true) + { + for (;;) { + DecomposablePath dp(name.string()); + + try { + return addNode(m_TreeMeta->tree.get(), dp, data, overwrite, flags, allocator()); + } catch (const bi::bad_alloc&) { + } + + reassign(); + } + } + + /** + * @brief add a new directory to the tree + * + * @param name name of the file, expected to be relative to this directory + * @param data the file data to attach + * @param flags flags for this files + * @param overwrite if true, the new leaf will overwrite an existing one that compares + *as "equal" + * @return pointer to the new node or a null ptr + **/ + template <typename T> + typename TreeT::NodePtrT addDirectory(const fs::path& name, const T& data, + TreeFlags flags = 0, bool overwrite = true) + { + for (;;) { + DecomposablePath dp(name.string()); + + try { + return addNode(m_TreeMeta->tree.get(), dp, data, overwrite, + flags | FLAG_DIRECTORY, allocator()); + } catch (const bi::bad_alloc&) { + } + + reassign(); + } + } + + void getBuffer(void*& buffer, size_t& bufferSize) const + { + buffer = m_SHM->get_address(); + bufferSize = m_SHM->get_size(); + } + +private: + struct TreeMeta + { + TreeMeta(const typename TreeT::DataT& data, SegmentManagerT* segmentManager) + : tree(segmentManager->construct<TreeT>(bi::anonymous_instance)( + "", true, TreeT::NodePtrT(), data, VoidAllocatorT(segmentManager))), + referenceCount(0), // reference count only set on top level node + outdated(false) + {} + + OffsetPtrT<TreeT> tree; + long referenceCount; + bool outdated; + bi::interprocess_mutex mutex; + }; + + std::string m_SHMName; + std::shared_ptr<SharedMemoryT> m_SHM; + TreeMeta* m_TreeMeta; + + typename TreeT::DataT createEmpty() + { + return createDataEmpty<typename TreeT::DataT>(allocator()); + } + + template <typename T> + TreeT* createSubNode(const VoidAllocatorT& allocator, std::string_view name, + unsigned long flags, const T& data) + { + auto* manager = allocator.get_segment_manager(); + + return manager->construct<TreeT>(bi::anonymous_instance)( + name, flags, TreeT::NodePtrT(), + createData<typename TreeT::DataT, T>(data, allocator), manager); + } + + typename TreeT::NodePtrT createSubPtr(TreeT* subNode) + { + SharedMemoryT::segment_manager* manager = m_SHM->get_segment_manager(); + return TreeT::NodePtrT(subNode, allocator(), TreeT::DeleterT(manager)); + } + + template <typename T> + typename TreeT::NodePtrT addNode(TreeT* base, DecomposablePath& path, const T& data, + bool overwrite, unsigned int flags, + const VoidAllocatorT& allocator) + { + if (!path.peekNext()) { + typename TreeT::NodePtrT newNode = base->node(path.current()); + + if (!newNode) { + // last name component, should be the filename + TreeT* node = createSubNode(allocator, path.current(), flags, data); + newNode = createSubPtr(node); + newNode->m_Self = TreeT::WeakPtrT(newNode); + newNode->m_Parent = base->m_Self; + base->set(StringT(path.current(), allocator), newNode); + return newNode; + } else if (overwrite) { + newNode->m_Data = createData<typename TreeT::DataT, T>(data, allocator); + newNode->m_Flags = static_cast<usvfs::shared::TreeFlags>(flags); + return newNode; + } else { + // the node is already in the tree, overwrite is false, nothing to do + return {}; + } + } else { + // not last component, continue search in child node + auto subNode = base->m_Nodes.find(path.current()); + + if (subNode == base->m_Nodes.end()) { + typename TreeT::NodePtrT newNode = createSubPtr(createSubNode( + allocator, path.current(), FLAG_DIRECTORY | FLAG_DUMMY, createEmpty())); + + subNode = + base->m_Nodes.emplace(StringT(path.current(), allocator), newNode).first; + subNode->second->m_Self = TreeT::WeakPtrT(subNode->second); + subNode->second->m_Parent = base->m_Self; + } + + path.next(); + + return addNode(subNode->second.get().get(), path, data, overwrite, flags, + allocator); + } + } + + /** + * @brief copy content of one tree to a different tree (in a different shared memory + * segment + * @param destination + * @param reference + * @note at the time this is called, destination needs to refer to the shm of + * "destination" so that objects can be allocated in the new tree + */ + void copyTree(TreeT* destination, const TreeT* reference) + { + VoidAllocatorT allocator = VoidAllocatorT(m_SHM->get_segment_manager()); + destination->m_Flags = reference->m_Flags; + dataAssign(destination->m_Data, reference->m_Data); + destination->m_Name.assign(reference->m_Name.c_str()); + + for (const auto& kv : reference->m_Nodes) { + TreeT* newNode = createSubNode(allocator, "", true, createEmpty()); + typename TreeT::NodePtrT newNodePtr = createSubPtr(newNode); + + // need to set self BEFORE recursively copying the subtree, otherwise + // how would we assign parent pointers? + newNode->m_Self = newNodePtr; + + TreeT* source = reinterpret_cast<TreeT*>(kv.second.get().get()); + copyTree(newNode, source); + destination->set(newNode->m_Name, newNodePtr); + newNode->m_Parent = destination->m_Self; + } + } + + int increaseRefCount(TreeMeta* treeMeta) + { + bi::scoped_lock<bi::interprocess_mutex> lock(treeMeta->mutex); + return ++treeMeta->referenceCount; + } + + int decreaseRefCount(TreeMeta* treeMeta) + { + bi::scoped_lock<bi::interprocess_mutex> lock(treeMeta->mutex); + return --treeMeta->referenceCount; + } + + // see activateSHM() for return value + // + std::optional<std::string> createOrOpen(const std::string& SHMName, size_t size) + { + SharedMemoryT* newSHM = openSHM(SHMName); + + if (newSHM) { + spdlog::get("usvfs")->info("{} opened in process {}", SHMName, + ::GetCurrentProcessId()); + } else { + newSHM = createSHM(SHMName, size); + + if (newSHM) { + spdlog::get("usvfs")->info("{} created in process {}", SHMName, + ::GetCurrentProcessId()); + } + } + + if (!newSHM) { + spdlog::get("usvfs")->error("failed to create or open {} in process {}", SHMName, + ::GetCurrentProcessId()); + + throw std::exception("no shm instance"); + } + + return activateSHM(newSHM, SHMName); + } + + // see activateSHM() for return value + // + SharedMemoryT* createSHM(const std::string& SHMName, size_t size) + { + try { + return new SharedMemoryT(bi::create_only, SHMName.c_str(), + static_cast<unsigned int>(size)); + } catch (const bi::interprocess_exception&) { + } + + return nullptr; + } + + // see activateSHM() for return value + // + SharedMemoryT* openSHM(const std::string& SHMName) + { + try { + return new SharedMemoryT(bi::open_only, SHMName.c_str()); + } catch (const bi::interprocess_exception&) { + } + + return nullptr; + } + + // makes the given shm current, returns the name of the previous shm block + // if it is now unused and must be destroyed; if the block is still used by + // another process, returns empty + // + // see reassign() + // + std::optional<std::string> activateSHM(SharedMemoryT* shm, const std::string& SHMName) + { + std::shared_ptr<SharedMemoryT> oldSHM = m_SHM; + + m_SHM.reset(shm); + std::pair<TreeMeta*, SharedMemoryT::size_type> res = m_SHM->find<TreeMeta>("Meta"); + + if (res.first == nullptr) { + res.first = m_SHM->construct<TreeMeta>("Meta")(createEmpty(), + m_SHM->get_segment_manager()); + if (res.first == nullptr) { + USVFS_THROW_EXCEPTION(bi::bad_alloc()); + } + if (m_TreeMeta != nullptr) { + copyTree(res.first->tree.get(), m_TreeMeta->tree.get()); + } + } + + increaseRefCount(res.first); + + std::optional<std::string> deadSHMName; + + if (oldSHM.get() != nullptr) { + const bool lastUser = unassign(oldSHM, m_TreeMeta); + if (lastUser) { + deadSHMName = m_SHMName; + } + } + + m_TreeMeta = res.first; + m_SHMName = SHMName; + + return deadSHMName; + } + + static std::string followupName(const std::string& currentName) + { + std::regex pattern(R"exp((.*_)(\d+))exp"); + std::smatch match; + regex_match(currentName, match, pattern); + + if (match.size() != 3) { + USVFS_THROW_EXCEPTION(usage_error() << ex_msg("shared memory name invalid")); + } + + const int count = boost::lexical_cast<int>(match[2]); + return match[1].str() + std::to_string(count + 1); + } + + bool unassign(const std::shared_ptr<SharedMemoryT>& shm, TreeMeta* tree) + { + if (tree == nullptr) { + return true; + } + + if (decreaseRefCount(tree) == 0) { + shm->get_segment_manager()->destroy_ptr(tree); + return true; + } else { + return false; + } + } + + // re-entrancy: every time a process switches to a new shared memory block, it + // will know whether it was the last process to have a handle to it; when that + // happens, the block will be destroyed to avoid leaking it + // + // destroying these blocks is somewhat dangerous: it ends up in boost, which + // will try to access the filesystem to see if the name of the shared memory + // corresponds to a file on the drive, which can call hooked functions and end + // up right back here + // + // (note that in usvfs, only the shared memory for the log file uses a real + // file on the filesystem, see shmlogger.cpp; all the tree stuff uses + // anonymous, memory mapped files that live in the Windows pagefile) + // + // so the old blocks can be destroyed, but only after all the shenanigans with + // finding the correct shared memory block are over and `m_TreeMeta` points to + // a valid block, so all the names of the dead shared memory blocks are kept + // in a vector and deallocated at the very end + // + void reassign() + { + // list of all the shared memory blocks that are now unused and can be + // destroyed + std::vector<std::string> deadSHMNames; + + if (m_TreeMeta->outdated) { + // this block was marked as outdated, which should only happen when + // another process has ran out of memory and started allocating blocks + // with higher numbers + + spdlog::get("usvfs")->info("tree {0} is outdated, looking for another one", + m_SHMName); + + if (findNewerBlock(deadSHMNames)) { + // the new block was found and activated + return; + } + } else { + // this block isn't outdated, so reassign() was called because a bad_alloc + // exception was thrown + spdlog::get("usvfs")->info( + "ran out of memory in tree {0}, will create another one", m_SHMName); + } + + // either the block is full or it's outdated, but no higher block was found; + // just create a new one + + createNewBlock(deadSHMNames); + + // remove the old shared memory blocks; this can be recursive and call + // reassign() again, but it's safe at this point + for (const std::string& name : deadSHMNames) { + spdlog::get("usvfs")->info("destroying {0}", name); + bi::shared_memory_object::remove(name.c_str()); + } + } + + // tries a series of shm names above the current one in the hope of finding + // the most recent one + // + // if the new block was found and activated correctly, returns true + // + // when findNewerBlock() returns false, this container will either still be + // pointing to the same, outdated block as it started with, or it might have + // moved up to another outdated block, but it will always be pointing to + // a valid block in memory + // + bool findNewerBlock(std::vector<std::string>& deadSHMNames) + { + // how many blocks are checked above the current one; it's unlikely there + // will ever be more than 15 to 20 blocks allocated, but this is high + // because it's really the only way to keep processes connected to the same + // shm block, so processes must not fail to find the next one + constexpr int Tries = 100; + + std::string nextName = m_SHMName; + + for (int i = 0; i < Tries; ++i) { + // the shm name is something like "mod_organizer_3", which becomes + // "mod_organizer_4" + nextName = followupName(nextName); + spdlog::get("usvfs")->info("opening {0}", nextName); + + // open the shm, see if it exists + SharedMemoryT* shm = openSHM(nextName); + + if (!shm) { + // this is not necessarily an error, another process might have created + // a bunch of blocks and destroyed them before this process had a chance + // to see them, so just keep going + spdlog::get("usvfs")->info("{0} doesn't exist", nextName); + continue; + } + + spdlog::get("usvfs")->info("{0} exists, activating", nextName); + const auto deadSHMName = activateSHM(shm, nextName); + + // if this process was the last user of the previous block, it must be + // deallocated, but only after this whole thing is finished, because it + // can end up calling reassign() again + if (deadSHMName) { + spdlog::get("usvfs")->info("will destroy {0}", *deadSHMName); + deadSHMNames.push_back(*deadSHMName); + } + + // another process might have already created this block, run out of + // memory and created more, so make sure to only stop when finding a block + // that's not outdated + if (m_TreeMeta->outdated) { + spdlog::get("usvfs")->info("{0} is also outdated", nextName); + } else { + // shm opened correctly, activated and not outdated, done + spdlog::get("usvfs")->info("{0} not outdated, taking it, size now {1}", + nextName, byte_string(m_SHM->get_size())); + + return true; + } + } + + // this block is outdated, but no other valid block was found above; this + // shouldn't happen, but just create a new block to make sure programs can + // still run + + spdlog::get("usvfs")->error( + "found no existing tree above {0}, will create a new one", m_SHMName); + + return false; + } + + // creates a new block and activates it, throws on failure + // + void createNewBlock(std::vector<std::string>& deadSHMNames) + { + // the current block is now considered stale, so make sure other processes + // are aware of it and try to find the new block + // + // todo: there really should be some synchronization here, another process + // can pick up this flag before the next block has been created + m_TreeMeta->outdated = true; + + // the shm name is something like "mod_organizer_3", which becomes + // "mod_organizer_4" + const std::string nextName = followupName(m_SHMName); + spdlog::get("usvfs")->info("creating {0}", nextName); + + SharedMemoryT* shm = createSHM(nextName, m_SHM->get_size() * 2); + + if (!shm) { + // this shouldn't happen + spdlog::get("usvfs")->error("failed to create {0}", nextName); + throw std::exception("cannot create block"); + } + + spdlog::get("usvfs")->info("{0} created, activating", nextName); + const auto deadSHMName = activateSHM(shm, nextName); + + // if this process was the last user of the previous block, it must be + // deallocated, but only after this whole thing is finished, because it + // can end up calling reassign() again + if (deadSHMName) { + spdlog::get("usvfs")->info("will destroy {0}", *deadSHMName); + deadSHMNames.push_back(*deadSHMName); + } + + spdlog::get("usvfs")->info("tree {0} size now {1}", m_SHMName, + byte_string(m_SHM->get_size())); + } +}; + +} // namespace usvfs::shared diff --git a/libs/usvfs/src/shared/unicodestring.cpp b/libs/usvfs/src/shared/unicodestring.cpp new file mode 100644 index 0000000..f997e7f --- /dev/null +++ b/libs/usvfs/src/shared/unicodestring.cpp @@ -0,0 +1,77 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ +#include "unicodestring.h" +#include "logging.h" +#include "stringcast.h" +#include "stringutils.h" + +namespace usvfs +{ + +UnicodeString::UnicodeString(LPCWSTR string, size_t length) +{ + if (length == std::string::npos) { + length = wcslen(string); + } + m_Buffer.resize(length + 1); + memcpy(m_Buffer.data(), string, length * sizeof(wchar_t)); + update(); +} + +UnicodeString::UnicodeString(const std::wstring& string) +{ + m_Buffer.resize(string.length() + 1); + memcpy(m_Buffer.data(), string.data(), string.length() * sizeof(wchar_t)); + update(); +} + +UnicodeString& UnicodeString::operator=(const std::wstring& string) +{ + m_Buffer.resize(string.length() + 1); + memcpy(m_Buffer.data(), string.data(), string.length() * sizeof(wchar_t)); + update(); + return *this; +} + +UnicodeString& UnicodeString::appendPath(PUNICODE_STRING path) +{ + if (path != nullptr && path->Buffer && path->Length) { + auto appendAt = size(); + if (appendAt) { + m_Buffer.resize(m_Buffer.size() + path->Length / sizeof(WCHAR) + 1); + m_Buffer[appendAt++] = L'\\'; + } else + m_Buffer.resize(path->Length / sizeof(WCHAR) + 1); + memcpy(&m_Buffer[appendAt], path->Buffer, path->Length); + update(); + } + + return *this; +} + +void UnicodeString::update() +{ + m_Data.Length = static_cast<USHORT>(size() * sizeof(WCHAR)); + m_Data.MaximumLength = static_cast<USHORT>((m_Buffer.capacity() - 1) * sizeof(WCHAR)); + m_Data.Buffer = m_Buffer.data(); +} + +} // namespace usvfs diff --git a/libs/usvfs/src/shared/unicodestring.h b/libs/usvfs/src/shared/unicodestring.h new file mode 100644 index 0000000..77071ad --- /dev/null +++ b/libs/usvfs/src/shared/unicodestring.h @@ -0,0 +1,110 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ +#pragma once + +#include "ntdll_declarations.h" +#include "windows_sane.h" + +#include "formatters.h" + +namespace usvfs +{ + +/** + * @brief C++ wrapper for the windows UNICODE_STRING structure + */ +class UnicodeString +{ +public: + UnicodeString() : m_Buffer(1) { update(); } + + UnicodeString(const UnicodeString& other) : m_Buffer(other.m_Buffer) { update(); } + + UnicodeString(UnicodeString&& other) : m_Buffer(std::move(other.m_Buffer)) + { + update(); + } + + UnicodeString(const std::wstring& string); + UnicodeString(LPCWSTR string, size_t length = std::string::npos); + + UnicodeString& operator=(const std::wstring& string); + + UnicodeString& operator=(const UnicodeString& other) + { + m_Buffer = other.m_Buffer; + update(); + return *this; + } + + UnicodeString& operator=(UnicodeString&& other) + { + m_Buffer = std::move(other.m_Buffer); + update(); + return *this; + } + + /** + * @brief convert to a WinNt Api-style unicode string. This is only valid as long + * as the string isn't modified + */ + explicit operator PUNICODE_STRING() { return &m_Data; } + + /** + * @brief convert to a Win32 Api-style unicode string. This is only valid as long + * as the string isn't modified + */ + explicit operator LPCWSTR() const { return m_Data.Buffer; } + + /** + * @return length of the string in 16-bit words (not including zero termination) + */ + size_t size() const { return m_Buffer.size() - 1; } + + wchar_t operator[](size_t pos) const { return m_Buffer[pos]; } + + UnicodeString& appendPath(PUNICODE_STRING path); + +private: + friend struct ::std::formatter<UnicodeString, char>; + + void update(); + + UNICODE_STRING m_Data; + std::vector<wchar_t> m_Buffer; +}; + +} // namespace usvfs + +template <> +struct std::formatter<usvfs::UnicodeString, char> + : std::formatter<PCUNICODE_STRING, char> +{ + template <class FmtContext> + FmtContext::iterator format(const usvfs::UnicodeString& v, FmtContext& ctx) const + { + if (v.size() == 0) { + return std::format_to(ctx.out(), "<empty string>"); + } else { + return std::formatter<PCUNICODE_STRING, char>::format(&v.m_Data, ctx); + } + } +}; diff --git a/libs/usvfs/src/shared/wildcard.cpp b/libs/usvfs/src/shared/wildcard.cpp new file mode 100644 index 0000000..a2efe6d --- /dev/null +++ b/libs/usvfs/src/shared/wildcard.cpp @@ -0,0 +1,194 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ +#include "wildcard.h" +#include "logging.h" +#include "windows_sane.h" + +static bool IsInnerMatch(LPCWSTR pszString, LPCWSTR pszMatch) +{ + while (*pszMatch != L'\0') { + if ((*pszMatch == L'?') || (*pszMatch == L'>')) { + if (!*pszString) { + // ? must match exactly one character + return false; + } + + ++pszString; + ++pszMatch; + } else if ((*pszMatch == L'*') || (*pszMatch == L'<')) { + if (IsInnerMatch(pszString, pszMatch + 1)) { + // * may match empty string or we may have matched something already + return true; + } + + return *pszString && IsInnerMatch(pszString + 1, pszMatch); + + // the rest of the string can't be matched + } else { + if (CharUpperW(MAKEINTRESOURCEW(MAKELONG(*pszString++, 0))) != + CharUpperW(MAKEINTRESOURCEW(MAKELONG(*pszMatch++, 0)))) { + // regular chars compare + return false; + } + } + } + + return !*pszString && !*pszMatch; +} + +static LPCSTR InnerMatch(LPCSTR pszString, LPCSTR pszMatch) +{ + // We have a special case where string is empty ("") and the mask is "*". + // We need to handle this too. So we can't test on !*pszString here. + // The loop breaks when the match string is exhausted. + while (*pszString != '\0') { + // Single wildcard character + if ((*pszMatch == '?') || (*pszMatch == '>')) { + // can't match directory separator + if ((*pszString == '\\') || (*pszString == '/')) + return nullptr; + + // Matches any character except empty string + if (*pszString == '\0') { + // string consumed, part of the pattern is left + return pszMatch; + } + + // OK next + ++pszString; + ++pszMatch; + } else if ((*pszMatch == '*') || (*pszMatch == '<')) { + // * doesn't match directory separators + if ((*pszString == '\\') || (*pszString == '/')) { + ++pszMatch; + continue; + } + + // Need to do some tricks. + + // 1. The wildcard * is ignored. + // So just an empty string matches. This is done by recursion. + // Because we eat one character from the match string, the + // recursion will stop. + { + LPCSTR remainder = InnerMatch(pszString, pszMatch + 1); + if (remainder != nullptr) { + // we have a match and the * replaces no other character + return remainder; + } + } + + // 2. Chance we eat the next character and try it again, with a + // wildcard * match. This is done by recursion. Because we eat + // one character from the string, the recursion will stop. + if (*pszString != '\0') { + LPCSTR remainder = InnerMatch(pszString + 1, pszMatch); + if (remainder != nullptr) { + return remainder; + } + } + + // Nothing worked with this wildcard. + return nullptr; + } else { + // Standard compare of 2 chars. Note that *pszSring might be 0 + // here, but then we never get a match on *pszMask that has always + // a value while inside this loop. + if (CharUpperA(MAKEINTRESOURCEA(MAKELONG(*pszString++, 0))) != + CharUpperA(MAKEINTRESOURCEA(MAKELONG(*pszMatch++, 0)))) + return nullptr; + } + } + + // successful match if the input string was completely consumed + if (*pszString == '\0') { + while ((*pszMatch == '*') || (*pszMatch == '<')) { + ++pszMatch; + } + return pszMatch; + } else { + return nullptr; + } +} + +namespace usvfs::shared::wildcard +{ + +bool Match(LPCWSTR pszString, LPCWSTR pszMatch) +{ + if (*pszString == L'.') { + // cmd.exe seems to ignore + return Match(pszString + 1, pszMatch); + } else { + size_t len = wcslen(pszMatch); + if ((len > 2) && (wcscmp(pszMatch + len - 2, L".*") == 0)) { + // cmd.exe seems to completely ignore .* at the end. + std::wstring temp(pszMatch, pszMatch + len - 2); + return IsInnerMatch(pszString, temp.c_str()); + } + return IsInnerMatch(pszString, pszMatch); + } +} + +bool Match(LPCSTR pszString, LPCSTR pszMatch) +{ + if (*pszString == '.') { + // cmd.exe seems to ignore + return Match(pszString + 1, pszMatch); + } else { + size_t len = strlen(pszMatch); + LPCSTR res = nullptr; + if ((len > 2) && (strcmp(pszMatch + len - 2, ".*") == 0)) { + // cmd.exe seems to completely ignore .* at the end. + std::string temp(pszMatch, pszMatch + len - 2); + res = InnerMatch(pszString, temp.c_str()); + } else { + res = InnerMatch(pszString, pszMatch); + } + return ((res != nullptr) && (*res == '\0')); + } +} + +LPCSTR PartialMatch(LPCSTR pszString, LPCSTR pszMatch) +{ + if (*pszString == '.') { + // cmd.exe seems to ignore dots at the start + return PartialMatch(pszString + 1, pszMatch); + } else { + size_t len = strlen(pszMatch); + if ((len > 2) && (strcmp(pszMatch + len - 2, ".*") == 0)) { + // in cmd.exe there seems to be no difference between <something>* and + // <something>*.* + std::string temp(pszMatch, pszMatch + len - 2); + LPCSTR pos = InnerMatch(pszString, temp.c_str()); + if (pos != nullptr) { + if (*pos == '\0') { + return pszMatch + strlen(pszMatch); + } else { + return pszMatch + (pos - temp.c_str()); + } + } + } + return InnerMatch(pszString, pszMatch); + } +} + +} // namespace usvfs::shared::wildcard diff --git a/libs/usvfs/src/shared/wildcard.h b/libs/usvfs/src/shared/wildcard.h new file mode 100644 index 0000000..bf5b4a3 --- /dev/null +++ b/libs/usvfs/src/shared/wildcard.h @@ -0,0 +1,67 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ +/// Wildcard matching code +/// by Martin Richter +/// licensed under The Code Project Open License (CPOL) + +#pragma once + +#include "windows_sane.h" + +namespace usvfs::shared::wildcard +{ + +/** + * @brief match string to wildcard windows-style + * @param pszString Input string to match + * @param pszMatch Match mask that may contain wildcards like ? and * + * @note A ? sign matches any character, except an empty string. + * @note A * sign matches any string inclusive an empty string. + * @note Characters are compared caseless. + * @return true if the string matches the pattern + */ +bool Match(LPCWSTR pszString, LPCWSTR pszMatch); + +/** + * @brief match string to wildcard windows-style + * @param pszString Input string to match + * @param pszMatch Match mask that may contain wildcards like ? and * + * @note A ? sign matches any character, except an empty string. + * @note A * sign matches any string inclusive an empty string. + * @note Characters are compared caseless. + * @return true if the string matches the pattern + */ +bool Match(LPCSTR pszString, LPCSTR pszMatch); + +/** + * @brief match string to wildcard windows-style + * @param pszString Input string to match + * @param pszMatch Match mask that may contain wildcards like ? and * + * @note A ? sign matches any character, except an empty string. + * @note A * sign matches any string inclusive an empty string. + * @note Characters are compared caseless. + * @return the "not-consumed" remainder of the pattern. If this points to a + * zero terminator, this was a full match. + * Returns nullptr if no match is possible + */ +LPCSTR PartialMatch(LPCSTR pszString, LPCSTR pszMatch); + +} // namespace usvfs::shared::wildcard diff --git a/libs/usvfs/src/shared/winapi.cpp b/libs/usvfs/src/shared/winapi.cpp new file mode 100644 index 0000000..e28a13c --- /dev/null +++ b/libs/usvfs/src/shared/winapi.cpp @@ -0,0 +1,490 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ +#include "winapi.h" +#include "exceptionex.h" +#include "logging.h" +#include "ntdll_declarations.h" +#include "stringcast.h" +#include "stringutils.h" +#include "unicodestring.h" + +namespace winapi::ansi +{ + +std::string getModuleFileName(HMODULE module, HANDLE process) +{ + std::wstring result = wide::getModuleFileName(module, process); + return usvfs::shared::string_cast<std::string>(result); +} + +std::string getCurrentDirectory() +{ + std::string result; + DWORD required = GetCurrentDirectoryA(0, nullptr); + if (required == 0UL) { + throw usvfs::shared::windows_error("failed to determine current working directory"); + } + result.resize(required); + GetCurrentDirectoryA(required, &result[0]); + result.resize(required - 1); + return result; +} + +std::pair<std::string, std::string> getFullPathName(LPCSTR fileName) +{ + static const int INIT_SIZE = 128; + std::string result; + result.resize(INIT_SIZE); + LPSTR filePart = nullptr; + DWORD requiredSize = GetFullPathNameA(fileName, INIT_SIZE, &result[0], &filePart); + if (requiredSize >= INIT_SIZE) { + result.resize(requiredSize); + GetFullPathNameA(fileName, requiredSize, &result[0], &filePart); + } + if (requiredSize != 0UL) { + return std::make_pair(result, std::string(filePart != nullptr ? filePart : "")); + } else { + return make_pair(result, std::string()); + } +} + +} // namespace winapi::ansi + +namespace winapi::wide +{ + +std::wstring getModuleFileName(HMODULE module, HANDLE process) +{ + std::wstring result; + result.resize(64); + DWORD rc = 0UL; + + while ((rc = (process == INVALID_HANDLE_VALUE) + ? ::GetModuleFileNameW(module, &result[0], + static_cast<DWORD>(result.size())) + : ::GetModuleFileNameExW(process, module, &result[0], + static_cast<DWORD>(result.size()))) == + result.size()) { + result.resize(result.size() * 2); + } + + if (rc == 0UL) { + if (::GetLastError() == ERROR_PARTIAL_COPY) { +#if BOOST_ARCH_X86_64 + return L"unknown (32-bit process)"; +#else + return L"unknown (64-bit process)"; +#endif + } else { + throw usvfs::shared::windows_error("failed to retrieve module file name"); + } + } + + result.resize(rc); + + return result; +} + +std::pair<std::wstring, std::wstring> getFullPathName(LPCWSTR fileName) +{ + wchar_t buf1[MAX_PATH]; + std::vector<wchar_t> buf2; + wchar_t* result = buf1; + LPWSTR filePart = nullptr; + DWORD requiredSize = GetFullPathNameW(fileName, MAX_PATH, result, &filePart); + if (requiredSize >= MAX_PATH) { + buf2.resize(requiredSize); + result = &buf2[0]; + requiredSize = GetFullPathNameW(fileName, requiredSize, result, &filePart); + } + return make_pair(std::wstring(result, requiredSize), + std::wstring((requiredSize && filePart) ? filePart : L"")); +} + +std::wstring getCurrentDirectory() +{ + // really great api this (::GetCurrentDirectoryW) + // - if it succeeds, returns size in characters WITHOUT zero termination + // - if it fails due to buffer too small, returns size in characters WITH zero + // termination + // - if it fails for other reasons, returns 0 + std::wstring result; + DWORD required = GetCurrentDirectoryW(0, nullptr); + if (required == 0UL) { + throw usvfs::shared::windows_error("failed to determine current working directory"); + } + result.resize(required); + GetCurrentDirectoryW(required, &result[0]); + result.resize(required - 1); + return result; +} + +std::wstring getKnownFolderPath(REFKNOWNFOLDERID folderID) +{ + PWSTR writablePath; + + ::SHGetKnownFolderPath(folderID, 0, nullptr, &writablePath); + + ON_BLOCK_EXIT([writablePath]() { + ::CoTaskMemFree(writablePath); + }); + + return std::wstring(writablePath); +} + +} // namespace winapi::wide + +namespace winapi::ex +{ + +std::pair<uintptr_t, uintptr_t> getSectionRange(HANDLE moduleHandle) +{ + std::pair<uintptr_t, uintptr_t> result; + bool found = false; + uintptr_t exeModule = reinterpret_cast<uintptr_t>(moduleHandle); + if (exeModule == 0) { + throw std::runtime_error("failed to determine address range of executable"); + } + + std::pair<uintptr_t, uintptr_t> totalRange{UINT_MAX, 0}; + + PIMAGE_DOS_HEADER dosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(exeModule); + PIMAGE_NT_HEADERS ntHeader = + reinterpret_cast<PIMAGE_NT_HEADERS>(exeModule + dosHeader->e_lfanew); + PIMAGE_SECTION_HEADER sectionHeader = + reinterpret_cast<PIMAGE_SECTION_HEADER>(ntHeader + 1); + for (int i = 0; i < ntHeader->FileHeader.NumberOfSections && !found; ++i) { + if (memcmp(sectionHeader->Name, ".text", 5) == 0) { + result.first = exeModule + sectionHeader->VirtualAddress; + result.second = result.first + sectionHeader->Misc.VirtualSize; + found = true; + } else { + uintptr_t start = exeModule + sectionHeader->VirtualAddress; + totalRange.first = std::min(totalRange.first, start); + totalRange.second = std::max<uintptr_t>(totalRange.second, + start + sectionHeader->Misc.VirtualSize); + } + ++sectionHeader; + } + + if (!found) { + return totalRange; + } + + return result; +} + +OSVersion getOSVersion() +{ + RTL_OSVERSIONINFOEXW versionInfo; + ZeroMemory(&versionInfo, sizeof(RTL_OSVERSIONINFOEXW)); + versionInfo.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW); + RtlGetVersion((PRTL_OSVERSIONINFOW)&versionInfo); + + OSVersion result; + result.major = versionInfo.dwMajorVersion; + result.minor = versionInfo.dwMinorVersion; + result.build = versionInfo.dwBuildNumber; + result.platformid = versionInfo.dwPlatformId; + result.servicpack = + versionInfo.wServicePackMajor << 16 | versionInfo.wServicePackMinor; + return result; +} + +} // namespace winapi::ex + +namespace winapi::ex::ansi +{ + +std::string errorString(DWORD errorCode) +{ + std::ostringstream finalMessage; + + LPSTR buffer = nullptr; + + DWORD currentErrorCode = GetLastError(); + + errorCode = + errorCode != std::numeric_limits<DWORD>::max() ? errorCode : currentErrorCode; + + // TODO: the message is not english? + if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + nullptr, errorCode, + 0 //, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) + , + (LPSTR)&buffer, 0, nullptr) == 0) { + finalMessage << "(unknown error [" << errorCode << "])"; + } else { + if (buffer != nullptr) { + size_t end = strlen(buffer) - 1; + while ((buffer[end] == '\n') || (buffer[end] == '\r')) { + buffer[end--] = '\0'; + } + finalMessage << "(" << buffer << " [" << errorCode << "])"; + LocalFree(buffer); // allocated by FormatMessage + } + } + + SetLastError(currentErrorCode); // restore error code because FormatMessage might + // have modified it + return finalMessage.str(); +} + +std::string toString(const FILETIME& time) +{ + SYSTEMTIME temp; + FileTimeToSystemTime(&time, &temp); + std::ostringstream stream; + stream << temp.wYear << "-" << temp.wMonth << "-" << temp.wDay << temp.wHour << ":" + << temp.wMinute << ":" << temp.wSecond; + return stream.str(); +} + +LPCSTR GetBaseName(LPCSTR string) +{ + LPCSTR result = string + strlen(string) - 1; + while (result > string) { + if ((*result == '\\') || (*result == '/')) { + ++result; + break; + } else { + --result; + } + } + return result; +} + +} // namespace winapi::ex::ansi + +namespace winapi::ex::wide +{ + +bool fileExists(LPCWSTR fileName, bool* isDirectory) +{ + DWORD attrib = GetFileAttributesW(fileName); + + if (attrib == INVALID_FILE_ATTRIBUTES) { + return false; + } else { + if (isDirectory != nullptr) { + *isDirectory = (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0; + } + return true; + } +} + +std::wstring errorString(DWORD errorCode) +{ + std::wostringstream finalMessage; + + LPWSTR buffer = nullptr; + + DWORD currentErrorCode = GetLastError(); + + errorCode = + errorCode != std::numeric_limits<DWORD>::max() ? errorCode : currentErrorCode; + + // TODO: the message is not english? + if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + nullptr, errorCode, + 0 //, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) + , + (LPWSTR)&buffer, 0, nullptr) == 0) { + finalMessage << L"(unknown error [" << errorCode << "])"; + } else { + if (buffer != nullptr) { + size_t end = wcslen(buffer) - 1; + while ((buffer[end] == L'\n') || (buffer[end] == L'\r')) { + buffer[end--] = L'\0'; + } + finalMessage << L"(" << buffer << L" [" << errorCode << L"])"; + LocalFree(buffer); // allocated by FormatMessage + } + } + + SetLastError(currentErrorCode); // restore error code because FormatMessage might + // have modified it + return finalMessage.str(); +} + +std::wstring toString(const FILETIME& time) +{ + SYSTEMTIME temp; + FileTimeToSystemTime(&time, &temp); + std::wostringstream stream; + stream << temp.wYear << "-" << temp.wMonth << "-" << temp.wDay << temp.wHour << ":" + << temp.wMinute << ":" << temp.wSecond; + return stream.str(); +} + +LPCWSTR GetBaseName(LPCWSTR string) +{ + LPCWSTR result; + if ((string == nullptr) || (string[0] == L'\0')) { + result = string; + } else { + result = string + wcslen(string) - 1; + } + + while (result > string) { + if ((*result == L'\\') || (*result == L'/')) { + ++result; + break; + } else { + --result; + } + } + return result; +} + +LPWSTR GetBaseName(LPWSTR path) +{ + LPCWSTR result = GetBaseName(static_cast<LPCWSTR>(path)); + return const_cast<LPWSTR>(result); +} + +std::wstring getSectionName(PVOID addressIn, HANDLE process) +{ + if (process == nullptr) { + process = GetCurrentProcess(); + } + HMODULE modules[1024]; + intptr_t address = reinterpret_cast<intptr_t>(addressIn); + DWORD required; + if (::EnumProcessModules(process, modules, sizeof(modules), &required)) { + for (DWORD i = 0; i < (std::min<DWORD>(1024UL, required) / sizeof(HMODULE)); ++i) { + std::pair<intptr_t, intptr_t> range = getSectionRange(modules[i]); + if ((address > range.first) && (address < range.second)) { + try { + return winapi::wide::getModuleFileName(modules[i], process); + } catch (const std::exception&) { + return std::wstring(L"unknown"); + } + } + } + } + return std::wstring(L"unknown"); +} + +std::vector<FileResult> quickFindFiles(LPCWSTR directoryName, LPCWSTR pattern) +{ + std::vector<FileResult> result; + + static const unsigned int BUFFER_SIZE = 1024; + + HANDLE hdl = CreateFileW(directoryName, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + + ON_BLOCK_EXIT([hdl]() { + CloseHandle(hdl); + }); + + uint8_t buffer[BUFFER_SIZE]; + + NTSTATUS res = STATUS_SUCCESS; // status success + while (res == STATUS_SUCCESS) { + IO_STATUS_BLOCK status; + + res = NtQueryDirectoryFile( + hdl, nullptr, nullptr, nullptr, &status, buffer, BUFFER_SIZE, + FileFullDirectoryInformation, FALSE, + static_cast<PUNICODE_STRING>(usvfs::UnicodeString(pattern)), FALSE); + if (res == STATUS_SUCCESS) { + FILE_FULL_DIR_INFORMATION* info = + reinterpret_cast<FILE_FULL_DIR_INFORMATION*>(buffer); + void* endPos = buffer + status.Information; + while (info < endPos) { + FileResult file; + file.fileName = + std::wstring(info->FileName, info->FileNameLength / sizeof(wchar_t)); + file.attributes = info->FileAttributes; + + result.push_back(file); + if (info->NextEntryOffset == 0) { + break; + } else { + info = reinterpret_cast<FILE_FULL_DIR_INFORMATION*>( + reinterpret_cast<uint8_t*>(info) + info->NextEntryOffset); + } + } + } + } + + return result; +} + +bool createPath(boost::filesystem::path path, LPSECURITY_ATTRIBUTES securityAttributes) +{ + // sanity and guaranteed recursion end: + if (!path.has_relative_path()) + throw usvfs::shared::windows_error( + "createPath() 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("createPath() called on a file: " + + path.string()); + } + if (err != ERROR_FILE_NOT_FOUND && err != ERROR_PATH_NOT_FOUND) + throw usvfs::shared::windows_error( + "createPath() GetFileAttributesW failed on: " + path.string(), err); + + if (err != ERROR_FILE_NOT_FOUND) // ERROR_FILE_NOT_FOUND means parent directory + // already exists + createPath(path.parent_path(), + securityAttributes); // otherwise create parent directory (recursively) + + BOOL res = CreateDirectoryW(path.c_str(), securityAttributes); + if (!res) { + err = GetLastError(); + throw usvfs::shared::windows_error( + "createPath() CreateDirectoryW failed on: " + path.string(), err); + } + return true; +} + +std::wstring getWindowsBuildLab(bool ex) +{ + HKEY hKey = nullptr; + auto res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, + LR"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", 0, + KEY_READ, &hKey); + if (res != ERROR_SUCCESS || !hKey) + return L"Opening HKLM Windows NT\\CurrentVersion failed?!"; + WCHAR buf[200]; + DWORD size = static_cast<DWORD>(sizeof(buf)); + res = RegQueryValueExW(hKey, ex ? L"BuildLabEx" : L"BuildLab", NULL, NULL, + reinterpret_cast<LPBYTE>(buf), &size); + if (res != ERROR_SUCCESS || size > sizeof(buf)) + return ex ? L"BuildLabEx reg value not found?!" : L"BuildLab reg value not found?!"; + size /= sizeof(buf[0]); + if (size && !buf[size - 1]) + --size; + return std::wstring(buf, size); +} + +} // namespace winapi::ex::wide diff --git a/libs/usvfs/src/shared/winapi.h b/libs/usvfs/src/shared/winapi.h new file mode 100644 index 0000000..f94eff6 --- /dev/null +++ b/libs/usvfs/src/shared/winapi.h @@ -0,0 +1,610 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ +#pragma once + +#include "logging.h" +#include "stringcast.h" +#include "windows_sane.h" +#include <ShlObj.h> + +#define ALIAS(alias, original) \ + template <typename... Args> \ + auto alias(Args&&... args) -> decltype(original(std::forward<Args>(args)...)) \ + { \ + return original(std::forward<Args>(args)...); \ + } + +#define ALIAST(alias, original) \ + template <typename T, typename... Args> \ + auto alias<T>(Args && ... args) \ + -> decltype(original<T>(std::forward<Args>(args)...)) \ + { \ + return original<T>(std::forward<Args>(args)...); \ + } + +namespace winapi +{ + +struct parameter_error : public std::runtime_error +{ + parameter_error(const std::string& msg) : runtime_error(msg) {} +}; + +} // namespace winapi + +namespace winapi::process +{ + +/** + * @brief result of process creation + */ +struct Result +{ + Result() + { + ::ZeroMemory(&processInfo, sizeof(PROCESS_INFORMATION)); + ::ZeroMemory(&startupInfo, sizeof(STARTUPINFO)); + startupInfo.cb = sizeof(STARTUPINFO); + } + + Result(Result&& reference) + : valid(reference.valid), startupInfo(reference.startupInfo), + processInfo(reference.processInfo), errorCode(reference.errorCode) + { + reference.valid = false; + } + + ~Result() + { + if (valid) { + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + } + + if (stdoutPipe != INVALID_HANDLE_VALUE) { + CloseHandle(stdoutPipe); + } + } + + Result(const Result&) = delete; + + size_t readStdout(std::vector<uint8_t>& buffer, bool& eof) + { + if (stdoutPipe != INVALID_HANDLE_VALUE) { + DWORD read; + BOOL res = ReadFile(stdoutPipe, &buffer[0], static_cast<DWORD>(buffer.size()), + &read, nullptr); + eof = (res == TRUE) && (read == 0); + return static_cast<size_t>(read); + } else { + eof = true; + return 0; + } + } + + bool valid{false}; + STARTUPINFO startupInfo; + PROCESS_INFORMATION processInfo; + DWORD errorCode{0UL}; + + HANDLE stdoutPipe{INVALID_HANDLE_VALUE}; +}; + +/** + * @brief internal class to handle process creation with named parameters. + */ +template <typename CharT> +class _Create +{ +public: + _Create(const std::basic_string<CharT>& binaryName); + _Create(const _Create<CharT>& reference) = delete; + _Create<CharT>& operator=(const _Create<CharT>& reference) = delete; + + _Create(_Create<CharT>&& reference) + : m_CurrentDirectory(std::move(reference.m_CurrentDirectory)), + m_ProcessAttributes(reference.m_ProcessAttributes), + m_ThreadAttributes(reference.m_ThreadAttributes), + m_InheritHandles(reference.m_InheritHandles), + m_CreationFlags(std::move(reference.m_CreationFlags)), + m_Executed(reference.m_Executed) + { + // stringstream should be moveable but it seems it isn't on mingw + m_CommandLine << reference.m_CommandLine.rdbuf(); + } + + /// named parameter "argument". May be called repeatedly. This is + /// directly appended to the command line with a separating space. No + /// quoting happens + template <typename ArgT> + _Create& argument(const ArgT& argin) + { + m_CommandLine << " " << argin; + return *this; + } + + template <typename ArgT> + _Create& arg(const ArgT& argin) + { + return this->argument(argin); + } + + template <typename IterT> + _Create& arguments(IterT begin, IterT end) + { + for (; begin != end; ++begin) { + m_CommandLine << " " << *begin; + } + + return *this; + } + + /// @brief set the working directory for the process + _Create& workingDirectory(const std::basic_string<CharT>& path); + + /// @brief set process attributes + _Create& processAttributes(SECURITY_ATTRIBUTES* attributes); + + /// @brief set thread attributes + _Create& threadAttributes(SECURITY_ATTRIBUTES* attributes); + + /// @brief activate inheriting handles + _Create& inheritHandles(); + + /// @brief have the process start suspended + _Create& suspended(); + + /// @brief set the process up to output stout to a pipe which can be + /// retrieved through the result object + _Create& stdoutPipe(); + + /// @brief end the named parameter cascade and create the process + Result operator()() + { + m_CommandLine.seekp(0, std::ios::end); + unsigned int length = static_cast<unsigned int>(m_CommandLine.tellp()); + std::unique_ptr<CharT[]> clBuffer(new CharT[length + 1]); + memset(clBuffer.get(), 0, (length + 1) * sizeof(CharT)); + memcpy(clBuffer.get(), m_CommandLine.str().c_str(), length * sizeof(CharT)); + Result result; + + if (m_StdoutPipe) { + result.stdoutPipe = setupPipe(result.startupInfo.hStdOutput); + result.startupInfo.dwFlags |= STARTF_USESTDHANDLES; + } + + result.valid = + createProcessInt(nullptr, clBuffer.get(), m_ProcessAttributes, + m_ThreadAttributes, m_InheritHandles, m_CreationFlags, nullptr, + m_CurrentDirectory.length() > 0 ? m_CurrentDirectory.c_str() + : nullptr, + &result.startupInfo, &result.processInfo) == TRUE; + + if (m_Stdout != INVALID_HANDLE_VALUE) { + // got to close the write end of pipes + CloseHandle(result.startupInfo.hStdOutput); + } + + if (result.valid) { + result.errorCode = NOERROR; + } else { + result.errorCode = GetLastError(); + } + return result; + } + +private: + static BOOL createProcessInt(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, + SECURITY_ATTRIBUTES* lpProcessAttributes, + SECURITY_ATTRIBUTES* lpThreadAttributes, + BOOL bInheritHandles, DWORD dwCreationFlags, + LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation) + { + return ::CreateProcessW(lpApplicationName, lpCommandLine, lpProcessAttributes, + lpThreadAttributes, bInheritHandles, dwCreationFlags, + lpEnvironment, lpCurrentDirectory, lpStartupInfo, + lpProcessInformation); + } + + static BOOL createProcessInt(LPCSTR lpApplicationName, LPSTR lpCommandLine, + SECURITY_ATTRIBUTES* lpProcessAttributes, + SECURITY_ATTRIBUTES* lpThreadAttributes, + BOOL bInheritHandles, DWORD dwCreationFlags, + LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation) + { + std::wstring executable; + if (lpApplicationName != nullptr) { + executable = usvfs::shared::string_cast<std::wstring>(lpApplicationName); + } + + std::wstring cmdline; + if (lpCommandLine != nullptr) { + cmdline = usvfs::shared::string_cast<std::wstring>(lpCommandLine); + } + + std::wstring cwd; + if (lpCurrentDirectory != nullptr) { + cwd = usvfs::shared::string_cast<std::wstring>(lpCurrentDirectory); + } + + return ::CreateProcessW(lpApplicationName != nullptr ? executable.c_str() : nullptr, + lpCommandLine != nullptr ? &cmdline[0] : nullptr, + lpProcessAttributes, lpThreadAttributes, bInheritHandles, + dwCreationFlags, lpEnvironment, + lpCurrentDirectory != nullptr ? cwd.c_str() : nullptr, + lpStartupInfo, lpProcessInformation); + } + + HANDLE setupPipe(HANDLE& childHandle) + { + SECURITY_ATTRIBUTES attr; + attr.nLength = sizeof(SECURITY_ATTRIBUTES); + attr.bInheritHandle = TRUE; + attr.lpSecurityDescriptor = nullptr; + + HANDLE pipe[2]; + + CreatePipe(&pipe[0], &pipe[1], &attr, 0); + SetHandleInformation(pipe[0], HANDLE_FLAG_INHERIT, 0); + + childHandle = pipe[1]; + + return pipe[0]; + } + +private: + std::basic_stringstream<CharT> m_CommandLine; + std::basic_string<CharT> m_CurrentDirectory{}; + SECURITY_ATTRIBUTES* m_ProcessAttributes{nullptr}; + SECURITY_ATTRIBUTES* m_ThreadAttributes{nullptr}; + BOOL m_InheritHandles{false}; + DWORD m_CreationFlags{0UL}; + bool m_Executed{false}; + bool m_StdoutPipe{false}; + + HANDLE m_Stdout{INVALID_HANDLE_VALUE}; +}; + +} // namespace winapi::process + +namespace winapi::file +{ +/** + * @brief internal class to handle file creation (opening) with named + * parameters. + */ +template <typename CharT, DWORD DefaultDisposition> +class _Create +{ +public: + _Create(const std::basic_string<CharT>& fileName) : m_FileName(fileName) {} + + _Create& access(DWORD desiredAccess) + { + m_DesiredAccess = desiredAccess; + return *this; + } + + _Create& share(DWORD shareMode) + { + m_ShareMode = shareMode; + return *this; + } + + _Create& createAlways() + { + m_CreationDisposition = CREATE_ALWAYS; + return *this; + } + + _Create& openAlways() + { + m_CreationDisposition = OPEN_ALWAYS; + return *this; + } + + _Create& security(SECURITY_ATTRIBUTES* attributes) + { + m_SecurityAttributes = attributes; + return *this; + } + + _Create& templateFile(HANDLE templateFile) + { + m_Template = templateFile; + return *this; + } + + /// @brief end the named parameter cascade and open the file + HANDLE operator()() + { + return callDelegate( + std::integral_constant<bool, sizeof(CharT) == sizeof(wchar_t)>()); + } + +private: + HANDLE callDelegate(std::true_type) + { + return ::CreateFileW(m_FileName.c_str(), m_DesiredAccess, m_ShareMode, + m_SecurityAttributes, m_CreationDisposition, m_Flags, + m_Template); + } + + HANDLE callDelegate(std::false_type) + { + return ::CreateFileA(m_FileName.c_str(), m_DesiredAccess, m_ShareMode, + m_SecurityAttributes, m_CreationDisposition, m_Flags, + m_Template); + } + +private: + std::basic_string<CharT> m_FileName; + DWORD m_DesiredAccess{GENERIC_ALL}; + DWORD m_ShareMode{0UL}; + DWORD m_CreationDisposition{DefaultDisposition}; + DWORD m_Flags{FILE_ATTRIBUTE_NORMAL}; + HANDLE m_Template{nullptr}; + SECURITY_ATTRIBUTES* m_SecurityAttributes{nullptr}; +}; + +} // namespace winapi::file + +namespace winapi::ansi +{ + +std::string getModuleFileName(HMODULE module, HANDLE process = INVALID_HANDLE_VALUE); +std::pair<std::string, std::string> getFullPathName(LPCSTR fileName); +std::string getCurrentDirectory(); +typedef process::_Create<char> createProcess; +typedef file::_Create<char, CREATE_NEW> createFile; +typedef file::_Create<char, OPEN_EXISTING> openFile; + +} // namespace winapi::ansi + +namespace winapi::wide +{ + +std::wstring getModuleFileName(HMODULE module, HANDLE process = INVALID_HANDLE_VALUE); +std::pair<std::wstring, std::wstring> getFullPathName(LPCWSTR fileName); +std::wstring getCurrentDirectory(); +std::wstring getKnownFolderPath(REFKNOWNFOLDERID folderID); + +typedef process::_Create<wchar_t> createProcess; +typedef file::_Create<wchar_t, CREATE_NEW> createFile; +typedef file::_Create<wchar_t, OPEN_EXISTING> openFile; + +} // namespace winapi::wide + +/** + * useful convenience functions close to the api + */ +namespace winapi::ex +{ + +/** + * @brief retrieve the address range covering the code section of a module + * @param moduleHandle handle to the module + * @return start and end address of the code section + * @note the code section can only be identified if it has the standardized section name + * ".text" Otherwise the whole address range of all sections in the module is returned. + * This happens for compressed exectuables for example + */ +std::pair<uintptr_t, uintptr_t> getSectionRange(HANDLE moduleHandle); + +struct OSVersion +{ + DWORD major; + DWORD minor; + DWORD build; + DWORD platformid; + DWORD servicpack; +}; + +OSVersion getOSVersion(); + +} // namespace winapi::ex + +namespace winapi::ex::ansi +{ + +/** + * @brief retrieve an error string for a windows error message + * @param errorCode the error code to look up. If this is left at the default, + * ::GetLastError is used + * @return string representation of the error. Currently this is localized + */ +std::string errorString(DWORD errorCode = std::numeric_limits<DWORD>::max()); + +/** + * @brief convert filetime to string + * @param time time to convert + * @return a string representation (currently only supports utc and iso format with + * second precision) + */ +std::string toString(const FILETIME& time); + +/** + * @brief find file name in a windows file path + * @param path the path to search in + * @return the file name of the path or an empty string if the path ends on + * a slash + * @note this function doesn't access the file system so it doesn't depend + * on whether the file actually exists. This also means it can't + * determine if a path that doesn't end on a slash refers to a file or + * directory + * @note the return value is a pointer into the same buffer, no copy is + * created + */ +LPCSTR GetBaseName(LPCSTR string); + +} // namespace winapi::ex::ansi + +namespace winapi::ex::wide +{ +/** + * retrieve the name of the binary section containing the specified address + * @param address the address to test + * @param process the process for which to retrieve the section. If this is + * nullptr, the current process is analized. + * @return name of the section or "unknown" if no matching section was found + */ +std::wstring getSectionName(PVOID address, HANDLE process = nullptr); + +/** + * @brief test if a file exists + * @param path path to check + * @param isDirectory (optional) if this isn't null, it will be set to true if the path + * specifies a directory, false otherwise + * @return true if the file (or directory) exists. + */ +bool fileExists(LPCWSTR fileName, bool* isDirectory = nullptr); + +/** + * @brief retrieve an error string for a windows error message + * @param errorCode the error code to look up. If this is left at the default, + * ::GetLastError is used + * @return string representation of the error. Currently this is localized + */ +std::wstring errorString(DWORD errorCode = std::numeric_limits<DWORD>::max()); + +/** + * @brief convert filetime to string + * @param time time to convert + * @return a string representation (currently only supports utc and iso format with + * second precision) + */ +std::wstring toString(const FILETIME& time); + +/** + * @brief find file name in a windows file path + * @param path the path to search in + * @return the file name of the path or an empty string if the path ends on + * a slash + * @note this function doesn't access the file system so it doesn't depend + * on whether the file actually exists. This also means it can't + * determine if a path that doesn't end on a slash refers to a file or + * directory + * @note the return value is a pointer into the same buffer, no copy is + * created + */ +LPCWSTR GetBaseName(LPCWSTR path); + +/** + * @see const-variant of this function + */ +LPWSTR GetBaseName(LPWSTR path); + +struct FileResult +{ + std::wstring fileName; + ULONG attributes; +}; + +/** + * @brief a quick function to find all files in a directory or files following a + * pattern. This uses NtQueryDirectoryFile api internally so it should be faster than + * the usual FindFirstFile/FindNextFile pattern + * @param directoryName name of the directory to search in + * @param pattern name pattern that needs to match + * @return the list of files found + */ +std::vector<FileResult> quickFindFiles(LPCWSTR directoryName, LPCWSTR pattern); + +/** + * @brief create the specified directory including all intermediate + * directories + * @param path the path to create + * @param securityAttributes the security attributes to use for all created + * directories. if this is null (default), the standard attributes + * are used + * @return true if the directory (and possibly parent directories) were actually created + * and false if the directory already existed. Throws exceptions on failure. + */ +bool createPath(boost::filesystem::path path, + LPSECURITY_ATTRIBUTES securityAttributes = nullptr); +inline bool createPath(LPCWSTR path, LPSECURITY_ATTRIBUTES securityAttributes = nullptr) +{ + return createPath(boost::filesystem::path(path), securityAttributes); +} + +std::wstring getWindowsBuildLab(bool ex = false); + +} // namespace winapi::ex::wide + +namespace winapi::process +{ + +template <typename CharT> +_Create<CharT>::_Create(const std::basic_string<CharT>& binaryName) +{ + if (binaryName.length() > MAX_PATH) { + throw parameter_error("executable filename can't be longer than 260 characters"); + } + m_CommandLine << "\"" << binaryName << "\""; +} + +template <typename CharT> +_Create<CharT>& _Create<CharT>::workingDirectory(const std::basic_string<CharT>& path) +{ + m_CurrentDirectory = path; + return *this; +} + +template <typename CharT> +_Create<CharT>& _Create<CharT>::processAttributes(SECURITY_ATTRIBUTES* attributes) +{ + m_ProcessAttributes = attributes; + return *this; +} + +template <typename CharT> +_Create<CharT>& _Create<CharT>::threadAttributes(SECURITY_ATTRIBUTES* attributes) +{ + m_ThreadAttributes = attributes; + return *this; +} + +template <typename CharT> +_Create<CharT>& _Create<CharT>::inheritHandles() +{ + m_InheritHandles = true; + return *this; +} + +template <typename CharT> +_Create<CharT>& _Create<CharT>::suspended() +{ + m_CreationFlags |= CREATE_SUSPENDED; + return *this; +} + +template <typename CharT> +_Create<CharT>& _Create<CharT>::stdoutPipe() +{ + m_StdoutPipe = true; + return *this; +} + +} // namespace winapi::process diff --git a/libs/usvfs/src/shared/windows_sane.h b/libs/usvfs/src/shared/windows_sane.h new file mode 100644 index 0000000..7bb3726 --- /dev/null +++ b/libs/usvfs/src/shared/windows_sane.h @@ -0,0 +1,28 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> |
