/* 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 . */ #pragma once #include "logging.h" #include "stringcast.h" #include "windows_sane.h" #include #define ALIAS(alias, original) \ template \ auto alias(Args&&... args) -> decltype(original(std::forward(args)...)) \ { \ return original(std::forward(args)...); \ } #define ALIAST(alias, original) \ template \ auto alias(Args && ... args) \ -> decltype(original(std::forward(args)...)) \ { \ return original(std::forward(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& buffer, bool& eof) { if (stdoutPipe != INVALID_HANDLE_VALUE) { DWORD read; BOOL res = ReadFile(stdoutPipe, &buffer[0], static_cast(buffer.size()), &read, nullptr); eof = (res == TRUE) && (read == 0); return static_cast(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 class _Create { public: _Create(const std::basic_string& binaryName); _Create(const _Create& reference) = delete; _Create& operator=(const _Create& reference) = delete; _Create(_Create&& 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 _Create& argument(const ArgT& argin) { m_CommandLine << " " << argin; return *this; } template _Create& arg(const ArgT& argin) { return this->argument(argin); } template _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& 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(m_CommandLine.tellp()); std::unique_ptr 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(lpApplicationName); } std::wstring cmdline; if (lpCommandLine != nullptr) { cmdline = usvfs::shared::string_cast(lpCommandLine); } std::wstring cwd; if (lpCurrentDirectory != nullptr) { cwd = usvfs::shared::string_cast(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 m_CommandLine; std::basic_string 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 class _Create { public: _Create(const std::basic_string& 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()); } 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 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 getFullPathName(LPCSTR fileName); std::string getCurrentDirectory(); typedef process::_Create createProcess; typedef file::_Create createFile; typedef file::_Create openFile; } // namespace winapi::ansi namespace winapi::wide { std::wstring getModuleFileName(HMODULE module, HANDLE process = INVALID_HANDLE_VALUE); std::pair getFullPathName(LPCWSTR fileName); std::wstring getCurrentDirectory(); std::wstring getKnownFolderPath(REFKNOWNFOLDERID folderID); typedef process::_Create createProcess; typedef file::_Create createFile; typedef file::_Create 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 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::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::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 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 _Create::_Create(const std::basic_string& binaryName) { if (binaryName.length() > MAX_PATH) { throw parameter_error("executable filename can't be longer than 260 characters"); } m_CommandLine << "\"" << binaryName << "\""; } template _Create& _Create::workingDirectory(const std::basic_string& path) { m_CurrentDirectory = path; return *this; } template _Create& _Create::processAttributes(SECURITY_ATTRIBUTES* attributes) { m_ProcessAttributes = attributes; return *this; } template _Create& _Create::threadAttributes(SECURITY_ATTRIBUTES* attributes) { m_ThreadAttributes = attributes; return *this; } template _Create& _Create::inheritHandles() { m_InheritHandles = true; return *this; } template _Create& _Create::suspended() { m_CreationFlags |= CREATE_SUSPENDED; return *this; } template _Create& _Create::stdoutPipe() { m_StdoutPipe = true; return *this; } } // namespace winapi::process