From 5fa103b74fcafbc2469697c24c27f112f51e6b96 Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Sat, 6 Jan 2018 17:40:39 +0200 Subject: [PATCH 01/89] Cleaner usvfs dll versioning --- include/usvfs_version.h | 19 +++++++++++++++++++ src/usvfs_dll/version.rc | 17 +++++++++++------ vsbuild/platform_x64.props | 6 +++++- vsbuild/usvfs_dll.vcxproj | 1 + vsbuild/usvfs_dll.vcxproj.filters | 3 +++ 5 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 include/usvfs_version.h diff --git a/include/usvfs_version.h b/include/usvfs_version.h new file mode 100644 index 00000000..c0a44f6b --- /dev/null +++ b/include/usvfs_version.h @@ -0,0 +1,19 @@ +#pragma once + +#define USVFS_VERSION_MAJOR 0 +#define USVFS_VERSION_MINOR 2 +#define USVFS_VERSION_BUILD 3 +#define USVFS_VERSION_REVISION 0 + +#ifndef USVFS_STRINGIFY +# define USVFS_STRINGIFY(x) USVFS_STRINGIFY_HELPER(x) +# define USVFS_STRINGIFY_HELPER(x) #x +#endif + +#ifndef USVFS_STRINGIFYW +# define USVFS_STRINGIFYW(x) USVFS_STRINGIFYW_HELPER(x) +# define USVFS_STRINGIFYW_HELPER(x) L ## #x +#endif + +#define USVFS_VERSION_STRING USVFS_STRINGIFY(USVFS_VERSION_MAJOR) "." USVFS_STRINGIFY(USVFS_VERSION_MINOR) "." USVFS_STRINGIFY(USVFS_VERSION_BUILD) "." USVFS_STRINGIFY(USVFS_VERSION_REVISION) +#define USVFS_VERSION_WSTRING USVFS_STRINGIFYW(USVFS_VERSION_MAJOR) L"." USVFS_STRINGIFYW(USVFS_VERSION_MINOR) L"." USVFS_STRINGIFYW(USVFS_VERSION_BUILD) L"." USVFS_STRINGIFYW(USVFS_VERSION_REVISION) diff --git a/src/usvfs_dll/version.rc b/src/usvfs_dll/version.rc index cd5ba5d5..4dbb04aa 100644 --- a/src/usvfs_dll/version.rc +++ b/src/usvfs_dll/version.rc @@ -1,10 +1,11 @@ #include "Winver.h" +#include "..\..\include\usvfs_version.h" -#define VER_FILEVERSION 0,2,3,0 -#define VER_FILEVERSION_STR "0,2,3,0\0" +#define VER_FILEVERSION USVFS_VERSION_MAJOR,USVFS_VERSION_MINOR,USVFS_VERSION_BUILD,USVFS_VERSION_REVISION +#define VER_FILEVERSION_STR USVFS_VERSION_STRING -#define VER_PRODUCTVERSION 0,2,3,0 -#define VER_PRODUCTVERSION_STR "0,2,3,0\0" +#define VER_PRODUCTVERSION USVFS_VERSION_MAJOR,USVFS_VERSION_MINOR,USVFS_VERSION_BUILD,USVFS_VERSION_REVISION +#define VER_PRODUCTVERSION_STR USVFS_VERSION_STRING VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION @@ -20,9 +21,13 @@ BEGIN BLOCK "040904B0" BEGIN VALUE "FileVersion", VER_FILEVERSION_STR - VALUE "CompanyName", "Tannin\0" + VALUE "CompanyName", "Community Edition\0" VALUE "FileDescription", "Windows OverlayFS\0" - VALUE "OriginalFilename", "usvfs.dll\0" +#ifdef _WIN64 + VALUE "OriginalFilename", "usvfs_x64.dll\0" +#else + VALUE "OriginalFilename", "usvfs_x86.dll\0" +#endif VALUE "ProductName", "USVFS\0" VALUE "ProductVersion", VER_PRODUCTVERSION_STR END diff --git a/vsbuild/platform_x64.props b/vsbuild/platform_x64.props index 8aa3b4ef..b15018c7 100644 --- a/vsbuild/platform_x64.props +++ b/vsbuild/platform_x64.props @@ -10,7 +10,11 @@ $(STAGING_PDB_64) - + + + _WIN64;%(PreprocessorDefinitions) + + $(PLATFORM_32) diff --git a/vsbuild/usvfs_dll.vcxproj b/vsbuild/usvfs_dll.vcxproj index dcee379a..7db214d2 100644 --- a/vsbuild/usvfs_dll.vcxproj +++ b/vsbuild/usvfs_dll.vcxproj @@ -219,6 +219,7 @@ + diff --git a/vsbuild/usvfs_dll.vcxproj.filters b/vsbuild/usvfs_dll.vcxproj.filters index 91b6d740..763826b8 100644 --- a/vsbuild/usvfs_dll.vcxproj.filters +++ b/vsbuild/usvfs_dll.vcxproj.filters @@ -106,5 +106,8 @@ Header Files + + Header Files\include + \ No newline at end of file From c95bcd8fa25707c208d3518a850d83d80c66a79e Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Sat, 6 Jan 2018 17:52:25 +0200 Subject: [PATCH 02/89] Align usvfs proxy version --- src/usvfs_proxy/version.rc | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/usvfs_proxy/version.rc b/src/usvfs_proxy/version.rc index c2f269a5..446807b9 100644 --- a/src/usvfs_proxy/version.rc +++ b/src/usvfs_proxy/version.rc @@ -1,10 +1,11 @@ #include "Winver.h" +#include "..\..\include\usvfs_version.h" -#define VER_FILEVERSION 0,0,1,0 -#define VER_FILEVERSION_STR "0,0,1,0\0" +#define VER_FILEVERSION USVFS_VERSION_MAJOR,USVFS_VERSION_MINOR,USVFS_VERSION_BUILD,USVFS_VERSION_REVISION +#define VER_FILEVERSION_STR USVFS_VERSION_STRING -#define VER_PRODUCTVERSION 0,0,1,0 -#define VER_PRODUCTVERSION_STR "0,0,1,0\0" +#define VER_PRODUCTVERSION USVFS_VERSION_MAJOR,USVFS_VERSION_MINOR,USVFS_VERSION_BUILD,USVFS_VERSION_REVISION +#define VER_PRODUCTVERSION_STR USVFS_VERSION_STRING VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION @@ -20,9 +21,13 @@ BEGIN BLOCK "040904B0" BEGIN VALUE "FileVersion", VER_FILEVERSION_STR - VALUE "CompanyName", "Tannin\0" + VALUE "CompanyName", "Community Edition\0" VALUE "FileDescription", "Windows OverlayFS\0" - VALUE "OriginalFilename", "usvfs.dll\0" +#ifdef _WIN64 + VALUE "OriginalFilename", "usvfs_proxy_x64.exe\0" +#else + VALUE "OriginalFilename", "usvfs_proxy_x86.exe\0" +#endif VALUE "ProductName", "USVFS\0" VALUE "ProductVersion", VER_PRODUCTVERSION_STR END From 0a805dd25a4ed881b46ebfe0d1b72ca035a0b2e1 Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Sat, 6 Jan 2018 17:52:31 +0200 Subject: [PATCH 03/89] Print usvfs version on log initialize --- src/usvfs_dll/usvfs.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/usvfs_dll/usvfs.cpp b/src/usvfs_dll/usvfs.cpp index 3bcbdc5d..356b5642 100644 --- a/src/usvfs_dll/usvfs.cpp +++ b/src/usvfs_dll/usvfs.cpp @@ -19,6 +19,7 @@ You should have received a copy of the GNU General Public License along with usvfs. If not, see . */ #include "usvfs.h" +#include "usvfs_version.h" #include "hookmanager.h" #include "redirectiontree.h" #include "loghelpers.h" @@ -131,6 +132,8 @@ void InitLoggingInternal(bool toConsole, bool connectExistingSHM) spdlog::create("hooks"); } } + + spdlog::get("usvfs")->info("usvfs dll {} initialized in process {}", USVFS_VERSION_STRING, GetCurrentProcessId()); } From 7c03c9c192939d9f78c8e6fe77f652ee638108e4 Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Sat, 6 Jan 2018 18:28:18 +0200 Subject: [PATCH 04/89] More detailed windows version logging --- src/shared/winapi.cpp | 19 +++++++++++++++++++ src/shared/winapi.h | 4 ++++ src/usvfs_dll/hookmanager.cpp | 5 +++-- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/shared/winapi.cpp b/src/shared/winapi.cpp index 9b0e9ecd..38c85aca 100644 --- a/src/shared/winapi.cpp +++ b/src/shared/winapi.cpp @@ -203,6 +203,8 @@ OSVersion getOSVersion() 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; @@ -476,6 +478,23 @@ void createPath(LPCWSTR path, LPSECURITY_ATTRIBUTES securityAttributes) } } +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(sizeof(buf)); + res = RegQueryValueExW(hKey, ex ? L"BuildLabEx" : L"BuildLab", NULL, NULL, reinterpret_cast(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); +} + } diff --git a/src/shared/winapi.h b/src/shared/winapi.h index 149d1063..a7af7e9d 100644 --- a/src/shared/winapi.h +++ b/src/shared/winapi.h @@ -365,6 +365,8 @@ namespace ex { struct OSVersion { DWORD major; DWORD minor; + DWORD build; + DWORD platformid; DWORD servicpack; }; @@ -476,6 +478,8 @@ namespace ex { */ void createPath(LPCWSTR path, LPSECURITY_ATTRIBUTES securityAttributes = nullptr); + + std::wstring getWindowsBuildLab(bool ex = false); } } diff --git a/src/usvfs_dll/hookmanager.cpp b/src/usvfs_dll/hookmanager.cpp index 623c72da..d2acbd2e 100644 --- a/src/usvfs_dll/hookmanager.cpp +++ b/src/usvfs_dll/hookmanager.cpp @@ -61,8 +61,9 @@ HookManager::HookManager(const USVFSParameters ¶ms, HMODULE module) spdlog::get("usvfs")->info("Process registered in shared process list : {}",::GetCurrentProcessId()); winapi::ex::OSVersion version = winapi::ex::getOSVersion(); - spdlog::get("usvfs")->info("Windows version {}.{} sp {}", - version.major, version.minor, version.servicpack); + spdlog::get("usvfs")->info("Windows version {}.{}.{} sp {} platform {} ({})", + version.major, version.minor, version.build, version.servicpack, version.platformid, + shared::string_cast(winapi::ex::wide::getWindowsBuildLab(true)).c_str()); initHooks(); From 415f24131bf4ca96237662320db41843c6d867f9 Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Sat, 6 Jan 2018 18:50:20 +0200 Subject: [PATCH 05/89] Remove stupendous "debug messages dropped" random number generator --- src/shared/shmlogger.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/shmlogger.cpp b/src/shared/shmlogger.cpp index 5f41981a..04ad61d5 100644 --- a/src/shared/shmlogger.cpp +++ b/src/shared/shmlogger.cpp @@ -122,7 +122,7 @@ void SHMLogger::get(char *buffer, size_t bufferSize) } spdlog::sinks::shm_sink::shm_sink(const char *queueName) - : m_LogQueue(open_only, (std::string("__shm_sink_") + queueName).c_str()) + : m_LogQueue(open_only, (std::string("__shm_sink_") + queueName).c_str()), m_DroppedMessages(0) { } From bdf38020060a3e91ac8a312a923b57d77b45e01e Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Fri, 5 Jan 2018 23:20:21 +0200 Subject: [PATCH 06/89] Support injecting 64bit processes from 32bit ones if 64bit proxy is available (and support lib\, bin\ directory structure for tests) --- src/usvfs_helper/inject.cpp | 61 ++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/src/usvfs_helper/inject.cpp b/src/usvfs_helper/inject.cpp index beb169cb..5e6342f1 100644 --- a/src/usvfs_helper/inject.cpp +++ b/src/usvfs_helper/inject.cpp @@ -82,34 +82,65 @@ void usvfs::injectProcess(const std::wstring &applicationPath ::GetProcessId(processHandle), sameBitness ? "same" : "different"); if (sameBitness) { - std::string libName = std::string("usvfs_") + (proc64 ? "x64" : "x86"); -#ifdef _DEBUG - std::wstring dllPath = (binPath / (libName + "-d.dll")).wstring(); -#else // DEBUG - std::wstring dllPath = (binPath / (libName + ".dll")).wstring(); -#endif // DEBUG - if (!boost::filesystem::exists(dllPath)) { + static constexpr auto USVFS_DLL = +#ifdef _WIN64 + L"usvfs_x64.dll"; +#else + L"usvfs_x86.dll"; +#endif + const auto& preferedDll = binPath / USVFS_DLL; + boost::filesystem::path dllPath = preferedDll; + bool dllFound = boost::filesystem::exists(dllPath); + // support for runing tests using a usvfs dll in lib folder (and proxy under bin): + if (!dllFound && binPath.filename() == L"bin") { + dllPath = binPath.parent_path() / L"lib" / USVFS_DLL; + dllFound = boost::filesystem::exists(dllPath); + } + if (!dllFound) { USVFS_THROW_EXCEPTION( file_not_found_error() << ex_msg(std::string("dll missing: ") - + ush::string_cast(dllPath).c_str())); + + ush::string_cast(preferedDll.wstring()).c_str())); } - spdlog::get("usvfs")->debug("dll path: {}", log::wrap(dllPath)); + spdlog::get("usvfs")->info("dll path: {}", log::wrap(dllPath.wstring())); InjectLib::InjectDLL(processHandle, threadHandle, dllPath.c_str(), "InitHooks", ¶meters, sizeof(USVFSParameters)); spdlog::get("usvfs")->info("injection to same bitness process {} successfull", ::GetProcessId(processHandle)); } else { - std::wstring exePath = (binPath / "usvfs_proxy.exe").wstring(); - if (!boost::filesystem::exists(exePath)) { + // first try platform specific proxy exe: + static constexpr auto USVFS_PREFERED_EXE = +#ifdef _WIN64 + L"usvfs_proxy_x86.exe"; +#else + L"usvfs_proxy_x64.exe"; +#endif + const auto& preferedExe = binPath / USVFS_PREFERED_EXE; + boost::filesystem::path exePath = preferedExe; + bool exeFound = boost::filesystem::exists(exePath); + // support for runing tests using a usvfs dll in lib folder (and proxy under bin): + if (!exeFound && binPath.filename() == L"lib") { + exePath = binPath.parent_path() / L"bin" / USVFS_PREFERED_EXE; + exeFound = boost::filesystem::exists(exePath); + } + // finally fallback to old proxy naming (but only for 64bit as we don't have a 64bit proxy in this case): +#ifdef _WIN64 + if (!exeFound) { + exePath = binPath / L"usvfs_proxy.exe"; + exeFound = boost::filesystem::exists(exePath); + } +#endif + if (!exeFound) { USVFS_THROW_EXCEPTION(file_not_found_error() << ex_msg( - std::string("exe missing: ") - + ush::string_cast(exePath))); + std::string("usvfs proxy not found: ") + + ush::string_cast(preferedExe.wstring()))); } + else + spdlog::get("usvfs")->info("using usvfs proxy: {}", ush::string_cast(preferedExe.wstring())); // need to use proxy aplication to inject - auto proxyProcess = std::move(wide::createProcess(exePath) + auto proxyProcess = std::move(wide::createProcess(exePath.wstring()) .arg(L"--instance").arg(ush::string_cast(parameters.instanceName)) .arg(L"--pid").arg(GetProcessId(processHandle))); @@ -120,7 +151,7 @@ void usvfs::injectProcess(const std::wstring &applicationPath if (!result.valid) { USVFS_THROW_EXCEPTION(unknown_error() << ex_msg(std::string("failed to start proxy ") - + ush::string_cast(exePath)) + + ush::string_cast(exePath.wstring())) << ex_win_errcode(result.errorCode)); } else { // wait for proxy completion. this shouldn't take long, 5 seconds is very generous From eb8b0268e2523ccd5e491e9dbcffb57c44823d20 Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Sat, 6 Jan 2018 00:41:08 +0200 Subject: [PATCH 07/89] Stage both 32bit and 64bit usvfs proxy executables --- vsbuild/usvfs_proxy.vcxproj | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/vsbuild/usvfs_proxy.vcxproj b/vsbuild/usvfs_proxy.vcxproj index 9f017f17..92db5baf 100644 --- a/vsbuild/usvfs_proxy.vcxproj +++ b/vsbuild/usvfs_proxy.vcxproj @@ -100,6 +100,7 @@ ..\bin\ $(ProjectName)_$(PlatformShortName) false + Build @@ -131,8 +132,8 @@ Staging - stage_helper.cmd $(ProjectName) $(TargetPath) $(STAGING_EXE)\$(ProjectName).exe $(TargetDir)$(TargetName).pdb $(STAGING_PDB)\$(ProjectName).pdb - $(STAGING_EXE)\$(ProjectName).exe + stage_helper.cmd $(TargetName) $(TargetPath) $(STAGING_EXE)\$(TargetFileName) $(TargetDir)$(TargetName).pdb $(STAGING_PDB)\$(TargetName).pdb + $(STAGING_EXE)\$(TargetFileName) $(TargetPath) @@ -148,6 +149,12 @@ true true + + Staging + $(TargetPath) + $(STAGING_EXE)\$(TargetFileName) + stage_helper.cmd $(TargetName) $(TargetPath) $(STAGING_EXE)\$(TargetFileName) $(TargetDir)$(TargetName).pdb $(STAGING_PDB)\$(TargetName).pdb + From 7fea9ea49b2898d93d6707e9f1300e4688292f60 Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Thu, 21 Dec 2017 15:25:22 +0200 Subject: [PATCH 08/89] rename and small cleanup of usvfs_vfs_test --- src/shared/test_helpers.cpp | 40 +++++++++++++++++++ src/shared/test_helpers.h | 17 ++++++++ test/{usvfs_test => tvfs_test}/main.cpp | 20 ++++------ vsbuild/shared.vcxproj | 2 + vsbuild/shared.vcxproj.filters | 6 +++ .../{usvfs_test.vcxproj => tvfs_test.vcxproj} | 2 +- ...proj.filters => tvfs_test.vcxproj.filters} | 2 +- vsbuild/usvfs.sln | 2 +- 8 files changed, 75 insertions(+), 16 deletions(-) create mode 100644 src/shared/test_helpers.cpp create mode 100644 src/shared/test_helpers.h rename test/{usvfs_test => tvfs_test}/main.cpp (96%) rename vsbuild/{usvfs_test.vcxproj => tvfs_test.vcxproj} (99%) rename vsbuild/{usvfs_test.vcxproj.filters => tvfs_test.vcxproj.filters} (94%) diff --git a/src/shared/test_helpers.cpp b/src/shared/test_helpers.cpp new file mode 100644 index 00000000..69bc0a71 --- /dev/null +++ b/src/shared/test_helpers.cpp @@ -0,0 +1,40 @@ +#pragma once + +#include "test_helpers.h" +#include "winapi.h" +#include + + +namespace test { + + path path_of_test_bin(path relative_) { + path base(winapi::wide::getModuleFileName(nullptr)); + return base.parent_path() / relative_; + } + + path path_of_test_temp(path relative_) { + return path_of_test_bin().parent_path() / "temp" / relative_; + } + + path path_of_usvfs_lib(path relative_) { + return path_of_test_bin().parent_path().parent_path() / "lib" / relative_; + } + + std::string platform_dependant_executable(const char* name_, const char* ext_, const char* platform_) + { + std::string res = name_; + res += "_"; + if (platform_) + res += platform_; + else +#if _WIN64 + res += "x64"; +#else + res += "x86"; +#endif + res += "."; + res += ext_; + return res; + } + +}; \ No newline at end of file diff --git a/src/shared/test_helpers.h b/src/shared/test_helpers.h new file mode 100644 index 00000000..58f35104 --- /dev/null +++ b/src/shared/test_helpers.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace test { + + using std::experimental::filesystem::path; + + // path functions assume they are called by a test executable + // (calculate the requested path relative to the current executable path) + + path path_of_test_bin(path relative_ = path()); + path path_of_test_temp(path relative_ = path()); + path path_of_usvfs_lib(path relative_ = path()); + + std::string platform_dependant_executable(const char* name_, const char* ext_ = "exe", const char* platform_ = nullptr); +}; \ No newline at end of file diff --git a/test/usvfs_test/main.cpp b/test/tvfs_test/main.cpp similarity index 96% rename from test/usvfs_test/main.cpp rename to test/tvfs_test/main.cpp index 04bf0288..fe097514 100644 --- a/test/usvfs_test/main.cpp +++ b/test/tvfs_test/main.cpp @@ -18,6 +18,9 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with usvfs. If not, see . */ + +#include + #pragma warning (push, 3) #include #include @@ -29,7 +32,6 @@ along with usvfs. If not, see . #include #include #include -#include #include @@ -42,9 +44,6 @@ along with usvfs. If not, see . #include -/*namespace logging = boost::log; -namespace sinks = boost::log::sinks; -namespace expr = boost::log::expressions;*/ namespace spd = spdlog; @@ -337,14 +336,10 @@ TEST_F(USVFSTestAuto, CanCreateMultipleLinks) } int main(int argc, char **argv) { - boost::filesystem::path dllPath(winapi::wide::getModuleFileName(nullptr)); - dllPath = dllPath.parent_path().parent_path().parent_path() / "lib" / -#if BOOST_ARCH_X86_64 - "usvfs_x64.dll"; -#else - "usvfs_x86.dll"; -#endif - HMODULE loadDll = LoadLibrary(dllPath.c_str()); + using namespace test; + + auto dllPath = path_of_usvfs_lib(platform_dependant_executable("usvfs", "dll")); + ScopedLoadLibrary loadDll(dllPath.c_str()); if (!loadDll) { std::wcerr << L"failed to load usvfs dll: " << dllPath.c_str() << L", " << GetLastError() << std::endl; return 1; @@ -357,6 +352,5 @@ int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); int res = RUN_ALL_TESTS(); - FreeLibrary(loadDll); return res; } diff --git a/vsbuild/shared.vcxproj b/vsbuild/shared.vcxproj index 2ed67355..1e85c6e3 100644 --- a/vsbuild/shared.vcxproj +++ b/vsbuild/shared.vcxproj @@ -125,6 +125,7 @@ + @@ -145,6 +146,7 @@ + diff --git a/vsbuild/shared.vcxproj.filters b/vsbuild/shared.vcxproj.filters index 68a070a0..2e64df65 100644 --- a/vsbuild/shared.vcxproj.filters +++ b/vsbuild/shared.vcxproj.filters @@ -57,6 +57,9 @@ Source Files + + Source Files + @@ -116,5 +119,8 @@ Header Files + + Header Files + \ No newline at end of file diff --git a/vsbuild/usvfs_test.vcxproj b/vsbuild/tvfs_test.vcxproj similarity index 99% rename from vsbuild/usvfs_test.vcxproj rename to vsbuild/tvfs_test.vcxproj index ee08ac35..65cf92d6 100644 --- a/vsbuild/usvfs_test.vcxproj +++ b/vsbuild/tvfs_test.vcxproj @@ -127,7 +127,7 @@ - + diff --git a/vsbuild/usvfs_test.vcxproj.filters b/vsbuild/tvfs_test.vcxproj.filters similarity index 94% rename from vsbuild/usvfs_test.vcxproj.filters rename to vsbuild/tvfs_test.vcxproj.filters index 2539db9d..fdb0f00b 100644 --- a/vsbuild/usvfs_test.vcxproj.filters +++ b/vsbuild/tvfs_test.vcxproj.filters @@ -15,7 +15,7 @@ - + Source Files diff --git a/vsbuild/usvfs.sln b/vsbuild/usvfs.sln index 61d37ae0..9c75e384 100644 --- a/vsbuild/usvfs.sln +++ b/vsbuild/usvfs.sln @@ -63,7 +63,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "usvfs_helper", "usvfs_helpe {D18D0B1C-37B4-48A8-88C0-399DE2815C05} = {D18D0B1C-37B4-48A8-88C0-399DE2815C05} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "usvfs_test", "usvfs_test.vcxproj", "{1DDD14EC-2274-4793-803E-B64D278AFD7A}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tvfs_test", "tvfs_test.vcxproj", "{1DDD14EC-2274-4793-803E-B64D278AFD7A}" ProjectSection(ProjectDependencies) = postProject {562B0058-4701-4284-8B40-D87648A3F64C} = {562B0058-4701-4284-8B40-D87648A3F64C} EndProjectSection From 073762f9da52a1060bf54e122eb012ff644e3f28 Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Sat, 6 Jan 2018 16:40:26 +0200 Subject: [PATCH 09/89] test_helpers towards new usvfs test --- src/shared/test_helpers.cpp | 232 ++++++++++++++++++++++++++++++++++-- src/shared/test_helpers.h | 108 ++++++++++++++++- 2 files changed, 325 insertions(+), 15 deletions(-) diff --git a/src/shared/test_helpers.cpp b/src/shared/test_helpers.cpp index 69bc0a71..9b07f3ca 100644 --- a/src/shared/test_helpers.cpp +++ b/src/shared/test_helpers.cpp @@ -3,29 +3,78 @@ #include "test_helpers.h" #include "winapi.h" #include +#include namespace test { - path path_of_test_bin(path relative_) { + std::string FuncFailed::msg(const char* func, const char* arg1, const unsigned long* res, const char* what) + { + fmt::MemoryWriter msg; + msg << func << "() " << (what ? what : "failed"); + const char* sep = " : "; + if (arg1) { + msg << sep << arg1; + sep = ", "; + } + if (res) { + msg << sep << "result = " << *res << " (0x" << fmt::hex(*res) << ")"; + sep = ", "; + } + return msg.str(); + } + + WinFuncFailed WinFuncFailedGenerator::operator()(const char* func) + { + fmt::MemoryWriter msg; + msg << func << "() failed : lastError=" << m_gle; + return WinFuncFailed(msg.str()); + } + + WinFuncFailed WinFuncFailedGenerator::operator()(const char* func, unsigned long res) + { + fmt::MemoryWriter msg; + msg << func << "() failed : result=" << res << " (0x" << fmt::hex(res) << "), lastError=" << m_gle; + return WinFuncFailed(msg.str()); + } + + WinFuncFailed WinFuncFailedGenerator::operator()(const char* func, const char* arg1) + { + fmt::MemoryWriter msg; + msg << func << "() failed : " << arg1 << ", lastError=" << m_gle; + return WinFuncFailed(msg.str()); + } + + WinFuncFailed WinFuncFailedGenerator::operator()(const char* func, const char* arg1, unsigned long res) + { + fmt::MemoryWriter msg; + msg << func << "() failed : " << arg1 << ", result=" << res << " (0x" << fmt::hex(res) << "), lastError=" << m_gle; + return WinFuncFailed(msg.str()); + } + + path path_of_test_bin(const path& relative) { path base(winapi::wide::getModuleFileName(nullptr)); - return base.parent_path() / relative_; + return base.parent_path() / relative; } - path path_of_test_temp(path relative_) { - return path_of_test_bin().parent_path() / "temp" / relative_; + path path_of_test_temp(const path& relative) { + return path_of_test_bin().parent_path() / "temp" / relative; } - path path_of_usvfs_lib(path relative_) { - return path_of_test_bin().parent_path().parent_path() / "lib" / relative_; + path path_of_test_fixtures(const path& relative) { + return path_of_test_bin().parent_path() / "fixtures" / relative; } - std::string platform_dependant_executable(const char* name_, const char* ext_, const char* platform_) + path path_of_usvfs_lib(const path& relative) { + return path_of_test_bin().parent_path().parent_path() / "lib" / relative; + } + + std::string platform_dependant_executable(const char* name, const char* ext, const char* platform) { - std::string res = name_; + std::string res = name; res += "_"; - if (platform_) - res += platform_; + if (platform) + res += platform; else #if _WIN64 res += "x64"; @@ -33,8 +82,169 @@ namespace test { res += "x86"; #endif res += "."; - res += ext_; + res += ext; return res; } + path path_as_relative(const path& base, const path& full_path) + { + auto rel_begin = full_path.begin(); + auto base_iter = base.begin(); + while (rel_begin != full_path.end() && base_iter != base.end() && *rel_begin == *base_iter) { + ++rel_begin; + ++base_iter; + } + + if (base_iter != base.end()) // full_path is not a sub-folder of base + return full_path; + + if (rel_begin == full_path.end()) // full_path == base + return path(L"."); + + // full_path is a sub-folder of base so take only relative path + path result; + for (; rel_begin != full_path.end(); ++rel_begin) + result /= *rel_begin; + return result; + } + + + std::vector read_small_file(const path& file, bool binary) + { + using namespace std; + + ScopedFILE f; + errno_t err = _wfopen_s(f, file.c_str(), binary ? L"rb" : L"rt"); + if (err || !f) + throw_testWinFuncFailed("_wfopen_s", file.u8string().c_str(), err); + + if (fseek(f, 0, SEEK_END)) + throw_testWinFuncFailed("fseek", (unsigned long) 0); + + long size = ftell(f); + if (size < 0) + throw_testWinFuncFailed("ftell", (unsigned long) size); + if (size > 0x10000000) // sanity check limit to 256M + throw test::FuncFailed("read_small_file", "file size too large", (unsigned long) size); + + if (fseek(f, 0, SEEK_SET)) + throw_testWinFuncFailed("fseek", (unsigned long) 0); + + std::vector content(static_cast(size)); + content.resize(fread(content.data(), sizeof(char), content.size(), f)); + + return std::move(content); + } + + bool compare_files(const path& file1, const path& file2, bool binary) + { + // TODO: if this is ever used for big file should read files in chunks + return read_small_file(file1, binary) == read_small_file(file2, binary); + } + + bool is_empty_folder(const path& dpath, bool or_doesnt_exist) + { + bool isDir = false; + if (!winapi::ex::wide::fileExists(dpath.c_str(), &isDir)) + return or_doesnt_exist; + + if (!isDir) + return false; + + for (const auto& f : winapi::ex::wide::quickFindFiles(dpath.c_str(), L"*")) + if (f.fileName != L"." && f.fileName != L"..") + return false; + return true; + } + + void delete_file(const path& file) + { + if (!DeleteFileW(file.c_str())) { + auto err = GetLastError(); + if (err != ERROR_FILE_NOT_FOUND && err != ERROR_PATH_NOT_FOUND) + throw_testWinFuncFailed("DeleteFile", file.u8string().c_str()); + } + } + + void recursive_delete_files(const path& dpath) + { + bool isDir = false; + if (!winapi::ex::wide::fileExists(dpath.c_str(), &isDir)) + return; + if (!isDir) + delete_file(dpath); + else { + // dpath exists and its a directory: + std::vector recurse; + for (const auto& f : winapi::ex::wide::quickFindFiles(dpath.c_str(), L"*")) + { + if (f.fileName == L"." || f.fileName == L"..") + continue; + if (f.attributes & FILE_ATTRIBUTE_DIRECTORY) + recurse.push_back(f.fileName); + else + delete_file(dpath / f.fileName); + } + for (auto r : recurse) + recursive_delete_files(dpath / r); + if (!RemoveDirectoryW(dpath.c_str())) + throw_testWinFuncFailed("RemoveDirectory", dpath.u8string().c_str()); + } + if (winapi::ex::wide::fileExists(dpath.c_str())) + throw FuncFailed("delete_directory_tree", dpath.u8string().c_str()); + } + + void recursive_copy_files(const path& src_path, const path& dest_path, bool overwrite) + { + bool srcIsDir = false, destIsDir = false; + if (!winapi::ex::wide::fileExists(src_path.c_str(), &srcIsDir)) + throw FuncFailed("recursive_copy", "source doesn't exist", src_path.u8string().c_str()); + if (!winapi::ex::wide::fileExists(dest_path.c_str(), &destIsDir) && srcIsDir) + { + winapi::ex::wide::createPath(dest_path.c_str()); + destIsDir = true; + } + + if (!srcIsDir) + if (!destIsDir) + { + if (!CopyFileW(src_path.c_str(), dest_path.c_str(), overwrite)) + throw_testWinFuncFailed("CopyFile", (src_path.u8string() + " => " + dest_path.u8string()).c_str()); + return; + } + else + throw FuncFailed("recursive_copy", "source is a file but destination is a directory", + (src_path.u8string() + ", " + dest_path.u8string()).c_str()); + + if (!destIsDir) + throw FuncFailed("recursive_copy", "source is a directory but destination is a file", + (src_path.u8string() + ", " + dest_path.u8string()).c_str()); + + // source and destination are both directories: + std::vector recurse; + for (const auto& f : winapi::ex::wide::quickFindFiles(src_path.c_str(), L"*")) + { + if (f.fileName == L"." || f.fileName == L"..") + continue; + if (f.attributes & FILE_ATTRIBUTE_DIRECTORY) + recurse.push_back(f.fileName); + else + if (!CopyFileW((src_path / f.fileName).c_str(), (dest_path / f.fileName).c_str(), overwrite)) + throw_testWinFuncFailed("CopyFile", + ((src_path / f.fileName).u8string() + " => " + (dest_path / f.fileName).u8string()).c_str()); + } + for (auto r : recurse) + recursive_copy_files(src_path / r, dest_path / r, overwrite); + } + + ScopedLoadLibrary::ScopedLoadLibrary(const wchar_t* dll_path) + : m_mod(LoadLibrary(dll_path)) + { + } + ScopedLoadLibrary::~ScopedLoadLibrary() + { + if (m_mod) + FreeLibrary(m_mod); + } + }; \ No newline at end of file diff --git a/src/shared/test_helpers.h b/src/shared/test_helpers.h index 58f35104..fb2b1b63 100644 --- a/src/shared/test_helpers.h +++ b/src/shared/test_helpers.h @@ -1,17 +1,117 @@ #pragma once +#include #include +#include +#include "windows_sane.h" namespace test { + class FuncFailed : public std::runtime_error + { + public: + FuncFailed(const char* func) + : std::runtime_error(msg(func)) {} + FuncFailed(const char* func, unsigned long res) + : std::runtime_error(msg(func, nullptr, &res)) {} + FuncFailed(const char* func, const char* arg1) + : std::runtime_error(msg(func, arg1)) {} + FuncFailed(const char* func, const char* arg1, unsigned long res) + : std::runtime_error(msg(func, arg1, &res)) {} + FuncFailed(const char* func, const char* what, const char* arg1) + : std::runtime_error(msg(func, arg1, nullptr, what)) {} + FuncFailed(const char* func, const char* what, const char* arg1, unsigned long res) + : std::runtime_error(msg(func, arg1, &res, what)) {} + + private: + std::string msg(const char* func, const char* arg1 = nullptr, const unsigned long* res = nullptr, const char* what = nullptr); + }; + + class WinFuncFailed : public std::runtime_error + { + public: + using runtime_error::runtime_error; + }; + + class WinFuncFailedGenerator + { + public: + WinFuncFailedGenerator(DWORD gle = GetLastError()) : m_gle(gle) {} + WinFuncFailedGenerator(const WinFuncFailedGenerator&) = delete; + + DWORD lastError() const { return m_gle; } + + WinFuncFailed operator()(const char* func); + WinFuncFailed operator()(const char* func, unsigned long res); + WinFuncFailed operator()(const char* func, const char* arg1); + WinFuncFailed operator()(const char* func, const char* arg1, unsigned long res); + + private: + DWORD m_gle; + }; + + // trick to guarantee the evalutation of GetLastError() before the evalution of the parameters to the WinFuncFailed message generation +#define throw_testWinFuncFailed(...) do { ::test::WinFuncFailedGenerator exceptionGenerator; throw exceptionGenerator(__VA_ARGS__); } while (false) + + class ScopedFILE { + public: + ScopedFILE(FILE* f = nullptr) : m_f(f) {} + ScopedFILE(const ScopedFILE&) = delete; + ScopedFILE(ScopedFILE&& other) : m_f(other.m_f) { other.m_f = nullptr; } + ~ScopedFILE() { if (m_f) fclose(m_f); } + + void close() { if (m_f) { fclose(m_f); m_f = nullptr; } } + + operator bool() const { return m_f; } + operator FILE*() const { return m_f; } + operator FILE**() { return &m_f; } + private: + FILE* m_f; + }; + using std::experimental::filesystem::path; // path functions assume they are called by a test executable // (calculate the requested path relative to the current executable path) - path path_of_test_bin(path relative_ = path()); - path path_of_test_temp(path relative_ = path()); - path path_of_usvfs_lib(path relative_ = path()); + path path_of_test_bin(const path& relative = path()); + path path_of_test_temp(const path& relative = path()); + path path_of_test_fixtures(const path& relative = path()); + path path_of_usvfs_lib(const path& relative = path()); + + std::string platform_dependant_executable(const char* name, const char* ext = "exe", const char* platform = nullptr); + + // if full_path is a subfolder of base returns only the relative path, + // if full_path and base are the same folder "." is returned, + // otherwise full_path is returned unchanged + path path_as_relative(const path& base, const path& full_path); + + std::vector read_small_file(const path& file, bool binary = true); + + // true iff the the contents of the two files is exactly the same + bool compare_files(const path& file1, const path& file2, bool binary = true); + + // return true iff the given path is an empty (optionally true also if path doesn't exist) + bool is_empty_folder(const path& dpath, bool or_doesnt_exist = false); + + void delete_file(const path& file); + + // Recursively deletes the given path and all the files and directories under it + // Use with care!!! + void recursive_delete_files(const path& dpath); + + // Recursively copies all files and directories from srcPath to destPath + void recursive_copy_files(const path& src_path, const path& dest_path, bool overwrite); + + class ScopedLoadLibrary { + public: + ScopedLoadLibrary(const wchar_t* dll_path); + ~ScopedLoadLibrary(); + + // returns zero if load library failed + operator HMODULE() const { return m_mod; } - std::string platform_dependant_executable(const char* name_, const char* ext_ = "exe", const char* platform_ = nullptr); + private: + HMODULE m_mod; + }; }; \ No newline at end of file From ca40a7c8209b18ec96a849cb6d5e1d8ff508f88b Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Sat, 6 Jan 2018 16:19:33 +0200 Subject: [PATCH 10/89] Add test_file_operations test helper --- .../test_file_operations.cpp | 411 +++++++++++++++++ test/test_file_operations/test_filesystem.cpp | 129 ++++++ test/test_file_operations/test_filesystem.h | 74 ++++ test/test_file_operations/test_ntapi.cpp | 419 ++++++++++++++++++ test/test_file_operations/test_ntapi.h | 30 ++ .../test_ntdll_declarations.h | 184 ++++++++ test/test_file_operations/test_w32api.cpp | 308 +++++++++++++ test/test_file_operations/test_w32api.h | 29 ++ vsbuild/test_file_operations.vcxproj | 145 ++++++ vsbuild/test_file_operations.vcxproj.filters | 45 ++ vsbuild/usvfs.sln | 11 + 11 files changed, 1785 insertions(+) create mode 100644 test/test_file_operations/test_file_operations.cpp create mode 100644 test/test_file_operations/test_filesystem.cpp create mode 100644 test/test_file_operations/test_filesystem.h create mode 100644 test/test_file_operations/test_ntapi.cpp create mode 100644 test/test_file_operations/test_ntapi.h create mode 100644 test/test_file_operations/test_ntdll_declarations.h create mode 100644 test/test_file_operations/test_w32api.cpp create mode 100644 test/test_file_operations/test_w32api.h create mode 100644 vsbuild/test_file_operations.vcxproj create mode 100644 vsbuild/test_file_operations.vcxproj.filters diff --git a/test/test_file_operations/test_file_operations.cpp b/test/test_file_operations/test_file_operations.cpp new file mode 100644 index 00000000..12feed4f --- /dev/null +++ b/test/test_file_operations/test_file_operations.cpp @@ -0,0 +1,411 @@ +#include +#include +#include +#include +#include +#include "test_ntapi.h" +#include "test_w32api.h" + +#define WIN32_LEAN_AND_MEAN +#include + +using usvfs::shared::string_cast; +using usvfs::shared::CodePage; + +void print_usage(const char* myname) { + using namespace std; + fprintf(stderr, "usage: %s [] [] ...\n", myname); + fprintf(stderr, "options and commands are parsed and executed in the order they appear.\n"); + fprintf(stderr, "\nsupported commands:\n"); + fprintf(stderr, " -list : lists the given directory and outputs the results.\n"); + fprintf(stderr, " -listcontents : lists the given directory, reading all files and outputs the results.\n"); + fprintf(stderr, " -read : reads the given file and outputs the results.\n"); + fprintf(stderr, " -overwrite : overwrites the file at the given path with the given line (creating directories if in recursive mode).\n"); + fprintf(stderr, " -rewrite : rewrites the file at the given path with the given line (fails if file doesn't exist; uses read/write access).\n"); + fprintf(stderr, " -delete : deletes the given file.\n"); + fprintf(stderr, " -rename : renames the given file.\n"); + fprintf(stderr, " -renameover : renames the given file (replacing existing destination).\n"); + fprintf(stderr, " -move : moves the given file (not supported by ntapi).\n"); + fprintf(stderr, " -moveover : moves the given file (replacing existing destination; not supported by ntapi).\n"); + fprintf(stderr, " -debug : shows a message box and wait for a debugger to connect.\n"); + fprintf(stderr, "\nsupported options:\n"); + fprintf(stderr, " -out : file to log output to (use \"-\" for the stdout; otherwise path to output should exist).\n"); + fprintf(stderr, " -out+ : similar to -out but appends the file instead of overwriting it.\n"); + fprintf(stderr, " -cout : similar to -out but does not log PID and other info which may change between runs.\n"); + fprintf(stderr, " -cout+ : similar to -cout but appends the file instead of overwriting it.\n"); + fprintf(stderr, " -r : recursively list/create directories.\n"); + fprintf(stderr, " -r- : don't recursively list/create directories.\n"); + fprintf(stderr, " -basedir : any paths under the basedir will outputed in a relative manner (default is current directory).\n"); + fprintf(stderr, " -w32api : use regular Win32 API for file access (default).\n"); + fprintf(stderr, " -ntapi : use lower level ntdll functions for file access.\n"); +} + +class CommandExecuter +{ +public: + CommandExecuter() + : m_output(stdout) + , m_api(&w32api) + { + set_basedir(TestFileSystem::current_directory().u8string().c_str()); + } + + ~CommandExecuter() + { + if (m_output && m_output != stdout) + fclose(m_output); + } + + FILE* output() const + { + return m_output; + } + + bool file_output() const + { + return m_output != stdout; + } + + // Options: + + void set_output(const char* output_file, bool clean, bool append, const char* cmdline) + { + if (m_output && m_output != stdout) { + fprintf(m_output, "#< Output log closed.\n", output_file); + fclose(m_output); + } + + m_cleanoutput = clean; + m_output = nullptr; + errno_t err = fopen_s(&m_output, output_file, append ? "at" : "wt"); + if (err || !m_output) + throw_testWinFuncFailed("fopen_s", output_file, err); + else { + if (m_cleanoutput) + fprintf(m_output, "#> Output log openned for: %s\n", clean_cmdline_heuristic(cmdline).c_str()); + else + fprintf(m_output, "#> Output log openned for (pid %d): %s\n", GetCurrentProcessId(), cmdline); + w32api.set_output(m_output); + ntapi.set_output(m_output); + } + } + + bool cleanoutput() const + { + return m_cleanoutput; + } + + void set_recursive(bool recursive) + { + m_recursive = recursive; + } + + void set_basedir(const char* basedir) + { + w32api.set_basepath(basedir); + ntapi.set_basepath(basedir); + } + + void set_ntapi(bool enable) + { + if (enable) + m_api = &ntapi; + else + m_api = &w32api; + } + + // Commands: + + void list(const char* dir, bool read_files) + { + if (debug_pending()) __debugbreak(); + + list_impl(m_api->real_path(dir), read_files); + } + + void read(const char* path) + { + if (debug_pending()) __debugbreak(); + + m_api->read_file(m_api->real_path(path)); + } + + void overwrite(const char* path, const char* value) + { + if (debug_pending()) __debugbreak(); + + auto real = m_api->real_path(path); + if (m_recursive) + try { + m_api->create_path(real.parent_path()); + } + catch (const std::exception& e) { + fmt::MemoryWriter msg; + msg << "Failed to create_path [" << m_api->relative_path(real.parent_path()).u8string() << "] : " << e.what(); + throw std::runtime_error(msg.str()); + } + m_api->write_file(real, value, strlen(value), true, TestFileSystem::write_mode::overwrite); + } + + void rewrite(const char* path, const char* value) + { + if (debug_pending()) __debugbreak(); + + auto real = m_api->real_path(path); + // Use read/write access when rewriting to "simulate" the harder case where it is not known if the file is going to actually be changed + m_api->write_file(real, value, strlen(value), false, TestFileSystem::write_mode::manual_truncate, true); + m_api->write_file(real, "\r\n", 2, false, TestFileSystem::write_mode::append); + } + + void deletef(const char* path) + { + if (debug_pending()) __debugbreak(); + + m_api->delete_file(m_api->real_path(path)); + } + + void rename(const char* source, const char* destination, bool replace_existing, bool allow_copy) + { + if (debug_pending()) __debugbreak(); + + m_api->rename_file(m_api->real_path(source), m_api->real_path(destination), replace_existing, allow_copy); + } + + void debug() + { + m_debug_pending = true; + } + + bool debug_pending() + { + if (!m_debug_pending) + return false; + m_debug_pending = false; + if (!IsDebuggerPresent()) + MessageBoxA(NULL, "Connect a debugger and press OK to trigger a breakpoint", "DEBUG", 0); + return IsDebuggerPresent(); + } + + // Traversal: + + void list_impl(TestFileSystem::path real, bool read_files) + { + std::vector recurse; + { + auto files = m_api->list_directory(real); + fprintf(m_output, ">> Listing directory {%s}:\n", m_api->relative_path(real).u8string().c_str()); + for (auto f : files) { + if (f.is_dir()) { + fprintf(m_output, "[%s] DIR (attributes 0x%x)\n", + string_cast(f.name, CodePage::UTF8).c_str(), f.attributes); + if (m_recursive && f.name != L"." && f.name != L"..") + recurse.push_back(real / f.name); + } + else { + fprintf(m_output, "[%s] FILE (attributes 0x%x, %lld bytes)\n", + string_cast(f.name, CodePage::UTF8).c_str(), f.attributes, f.size); + if (read_files) + m_api->read_file(real / f.name); + } + } + } + for (auto r : recurse) + list_impl(r, read_files); + } + +private: + std::string clean_cmdline_arg(const char* arg_start, const char* arg_end) + { + if (arg_start == arg_end) + return std::string(); + bool quoted = *arg_start == '\"' && *(arg_end - 1) == '\"'; + const char* last_sep = arg_end; + while (last_sep != arg_start && *last_sep != '\\') --last_sep; + if (arg_end - arg_start < (quoted ? 5 : 3) || arg_start[0] == '-' || arg_start[quoted ? 2 : 1] != ':' || last_sep == arg_start) + return std::string(arg_start, arg_end); + std::string res = quoted ? "\"" : ""; + res.append(last_sep+1, arg_end); + return res; + } + + std::string clean_cmdline_heuristic(const char* cmdline) + { + std::string res; + bool first = true; + while (*cmdline) { + const char* end = strchr(cmdline, ' '); + if (!end) + end = cmdline + strlen(cmdline); + if (first) + first = false; + else + res.push_back(' '); + res += clean_cmdline_arg(cmdline, end); + cmdline = end; + while (*cmdline == ' ') ++cmdline; + } + return res; + } + + FILE* m_output; + bool m_cleanoutput = false; + bool m_recursive = false; + bool m_debug_pending = false; + + TestFileSystem* m_api; + static TestW32Api w32api; + static TestNtApi ntapi; +}; + +//static +TestW32Api CommandExecuter::w32api(stdout); +TestNtApi CommandExecuter::ntapi(stdout); + +class abort_invalid_argument : std::exception {}; + +bool verify_args_exist(const char* flag, int params, int index, int count) +{ + if (index + params >= count) { + fprintf(stderr, "ERROR: %s requires %d arguments\n", flag, params); + throw abort_invalid_argument(); + } + return true; +} + +const char* UntouchedCommandLineArguments() +{ + const char* cmd = GetCommandLineA(); + for (; *cmd && *cmd != ' '; ++cmd) + { + if (*cmd == '"') { + int escaped = 0; + for (++cmd; *cmd && (*cmd != '"' || escaped % 2 != 0); ++cmd) + escaped = *cmd == '\\' ? escaped + 1 : 0; + } + } + while (*cmd == ' ') ++cmd; + return cmd; +} + +int main(int argc, char *argv[]) +{ + bool found_commands = false; + CommandExecuter executer; + + TestFileSystem::path exe_path = argv[0]; + std::string exename = exe_path.filename().u8string(); + std::string cmdline = exename + " " + UntouchedCommandLineArguments(); + fprintf(stdout, "#> process %d started with commandline: %s\n", GetCurrentProcessId(), cmdline.c_str()); + + for (int ai = 1; ai < argc; ++ai) + { + try + { + SetLastError(0); + + // options: + if (strcmp(argv[ai], "-out") == 0 && verify_args_exist("-out", 1, ai, argc) + || strcmp(argv[ai], "-out+") == 0 && verify_args_exist("-out+", 1, ai, argc) + || strcmp(argv[ai], "-cout") == 0 && verify_args_exist("-cout", 1, ai, argc) + || strcmp(argv[ai], "-cout+") == 0 && verify_args_exist("-cout+", 1, ai, argc)) + { + bool clean = argv[ai][1] == 'c'; + bool append = argv[ai][clean?5:4] == '+'; + executer.set_output(argv[++ai], clean, append, cmdline.c_str()); + } + else if (strcmp(argv[ai], "-r") == 0) + executer.set_recursive(true); + else if (strcmp(argv[ai], "-r-") == 0) + executer.set_recursive(false); + else if (strcmp(argv[ai], "-basedir") == 0 && verify_args_exist("-basedir", 1, ai, argc)) { + executer.set_basedir(argv[++ai]); + } + else if (strcmp(argv[ai], "-w32api") == 0) + executer.set_ntapi(false); + else if (strcmp(argv[ai], "-ntapi") == 0) + executer.set_ntapi(true); + // commands: + else if ((strcmp(argv[ai], "-list") == 0 + || strcmp(argv[ai], "-listcontents") == 0) + && verify_args_exist("-list", 1, ai, argc)) + { + bool contents = strcmp(argv[ai], "-listcontents") == 0; + executer.list(argv[++ai], contents); + found_commands = true; + } + else if (strcmp(argv[ai], "-read") == 0 && verify_args_exist("-read", 1, ai, argc)) + { + executer.read(argv[++ai]); + found_commands = true; + } + else if (strcmp(argv[ai], "-overwrite") == 0 && verify_args_exist("-overwrite", 2, ai, argc)) { + executer.overwrite(argv[ai + 1], argv[ai + 2]); + ++++ai; + found_commands = true; + } + else if (strcmp(argv[ai], "-rewrite") == 0 && verify_args_exist("-rewrite", 2, ai, argc)) { + executer.rewrite(argv[ai + 1], argv[ai + 2]); + ++++ai; + found_commands = true; + } + else if (strcmp(argv[ai], "-delete") == 0 && verify_args_exist("-delete", 1, ai, argc)) + { + executer.deletef(argv[++ai]); + found_commands = true; + } + else if (strcmp(argv[ai], "-rename") == 0 && verify_args_exist("-rename", 2, ai, argc) + || strcmp(argv[ai], "-renameover") == 0 && verify_args_exist("-renameover", 2, ai, argc) + || strcmp(argv[ai], "-move") == 0 && verify_args_exist("-move", 2, ai, argc) + || strcmp(argv[ai], "-moveover") == 0 && verify_args_exist("-moveover", 2, ai, argc)) + { + bool move = argv[ai][1] == 'm'; + bool over = argv[ai][move ? 5 : 7] == 'o'; + executer.rename(argv[ai + 1], argv[ai + 2], over, move); + ++++ai; + found_commands = true; + } + else if (strcmp(argv[ai], "-debug") == 0) { + executer.debug(); + } + else { + if (executer.file_output()) + fprintf(executer.output(), "ERROR: invalid argument {%s}\n", argv[ai]); + fprintf(stderr, "ERROR: invalid argument {%s}\n", argv[ai]); + return 1; + } + } + catch (const abort_invalid_argument&) { + return 1; + } +#if 1 // just a convient way to not catch exception when debugging + catch (const std::exception& e) + { + if (executer.file_output()) + fprintf(executer.output(), "ERROR: %hs\n", e.what()); + fprintf(stderr, "ERROR: %hs\n", e.what()); + return 1; + } + catch (...) + { + if (executer.file_output()) + fprintf(executer.output(), "ERROR: unknown exception"); + fprintf(stderr, "ERROR: unknown exception\n"); + return 1; + } +#endif + } + + if (!found_commands) { + print_usage(exename.c_str()); + return 2; + } + + if (executer.file_output()) + if (executer.cleanoutput()) + fprintf(executer.output(), "#< %s ended properly.\n", exename.c_str()); + else + fprintf(executer.output(), "#< %s ended properly in process %d.\n", exename.c_str(), GetCurrentProcessId()); + fprintf(stdout, "#< %s ended properly in process %d.\n", exename.c_str(), GetCurrentProcessId()); + + return 0; +} diff --git a/test/test_file_operations/test_filesystem.cpp b/test/test_file_operations/test_filesystem.cpp new file mode 100644 index 00000000..bd86ea4a --- /dev/null +++ b/test/test_file_operations/test_filesystem.cpp @@ -0,0 +1,129 @@ + +#include "test_filesystem.h" +#include +#define WIN32_LEAN_AND_MEAN +#include + +bool TestFileSystem::FileInformation::is_dir() const +{ + return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0; +} + +bool TestFileSystem::FileInformation::is_file() const +{ + return (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0; +} + +TestFileSystem::TestFileSystem(FILE* output) + : m_output(output) +{} + +TestFileSystem::path TestFileSystem::current_directory() +{ + DWORD res = GetCurrentDirectoryW(0, NULL); + if (!res) + throw_testWinFuncFailed("GetCurrentDirectory", res); + std::wstring buf(res + 1,'\0'); + res = GetCurrentDirectoryW(buf.length(), &buf[0]); + if (!res || res >= buf.length()) + throw_testWinFuncFailed("GetCurrentDirectory", res); + buf.resize(res); + return buf; +} + +TestFileSystem::path TestFileSystem::relative_path(path full_path) +{ + return test::path_as_relative(m_basepath, full_path); +} + +//static +const char* TestFileSystem::write_operation_name(write_mode mode) +{ + switch (mode) { + case write_mode::manual_truncate: + return "Writing file (by open & truncate)"; + case write_mode::truncate: + return "Truncating file"; + case write_mode::create: + return "Creating file"; + case write_mode::overwrite: + return "Overwriting file"; + case write_mode::append: + return "Appending file"; + } + return "Unknown write operation?!"; +} + +//static +const char* TestFileSystem::rename_operation_name(bool replace_existing, bool allow_copy) +{ + if (allow_copy) + return replace_existing ? "Moving file over" : "Moving file"; + else + return replace_existing ? "Renaming file over" : "Renaming file"; +} + +void TestFileSystem::print_operation(const char* operation, const path& target) +{ + if (m_output) + fprintf(m_output, "# (%s) %s {%s}\n", id(), operation, relative_path(target).u8string().c_str()); +} + +void TestFileSystem::print_operation(const char* operation, const path& source, const path& target) +{ + if (m_output) + fprintf(m_output, "# (%s) %s {%s} {%s}\n", id(), operation, relative_path(source).u8string().c_str(), relative_path(target).u8string().c_str()); +} + +static inline void print_op_with_result(FILE* output, const char* prefix, const char* operation, const uint32_t* result, DWORD* last_error, const char* opt_arg) +{ + if (output) { + fprintf(output, "%s%s", prefix, operation); + if (opt_arg) + fprintf(output, " %s", opt_arg); + if (result) + fprintf(output, " returned %u (0x%x)", *result, *result); + if (last_error) + fprintf(output, " last error %u (0x%x)", *last_error, *last_error); + fprintf(output, "\n"); + } +} + +void TestFileSystem::print_result(const char* operation, uint32_t result, bool with_last_error, const char* opt_arg, bool hide_result) +{ + if (m_output) + { + DWORD last_error = GetLastError(); + std::string prefix = "# ("; prefix += id(); prefix += ") "; + print_op_with_result(m_output, prefix.c_str(), operation, hide_result ? nullptr : &result, with_last_error ? &last_error : nullptr, opt_arg); + SetLastError(last_error); + } +} + +void TestFileSystem::print_error(const char* operation, uint32_t result, bool with_last_error, const char* opt_arg) +{ + DWORD last_error = with_last_error ? GetLastError() : 0; + print_op_with_result(stderr, "ERROR: ", operation, &result, with_last_error ? &last_error : nullptr, opt_arg); + if (m_output && m_output != stdout) + print_op_with_result(m_output, "ERROR: ", operation, &result, with_last_error ? &last_error : nullptr, opt_arg); +} + +void TestFileSystem::print_write_success(const void* data, std::size_t size, std::size_t written) +{ + if (m_output) + { + fprintf(m_output, "# Successfully written %u bytes ", static_cast(written)); + // heuristics to print nicer one liners: + if (size == 1 && reinterpret_cast(data)[0] == '\n' + || size == 2 && reinterpret_cast(data)[0] == '\r' && reinterpret_cast(data)[1] == '\n') + fprintf(m_output, ""); + else { + fprintf(m_output, "{"); + if (size && reinterpret_cast(data)[size - 1] == '\n') + --size; + fwrite(data, 1, size, m_output); + fprintf(m_output, "}"); + } + fprintf(output(), "\n"); + } +} diff --git a/test/test_file_operations/test_filesystem.h b/test/test_file_operations/test_filesystem.h new file mode 100644 index 00000000..12ac7be0 --- /dev/null +++ b/test/test_file_operations/test_filesystem.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include +#include + +class TestFileSystem +{ +public: + static constexpr auto FILE_CONTENTS_PRINT_PREFIX = "== "; + + typedef std::experimental::filesystem::path path; + typedef std::FILE FILE; + + static path current_directory(); + + TestFileSystem(FILE* output); + + void set_output(FILE* output) { m_output = output; } + + // base path used to trim outputs (which is important so we can compare tests ran at different base paths) + void set_basepath(const char* path) { m_basepath = real_path(path); } + + // returns the path relative to the base path + path relative_path(path full_path); + + virtual const char* id() = 0; + + virtual path real_path(const char* abs_or_rel_path) = 0; + + struct FileInformation { + std::wstring name; + uint32_t attributes; + uint64_t size; + + FileInformation(const std::wstring& iname, uint32_t iattributes, uint64_t isize) + : name(iname), attributes(iattributes), size(isize) + {} + + bool is_dir() const; + bool is_file() const; + }; + typedef std::vector FileInfoList; + + virtual FileInfoList list_directory(const path& directory_path) = 0; + + virtual void create_path(const path& directory_path) = 0; + + virtual void read_file(const path& file_path) = 0; + + enum class write_mode { manual_truncate, truncate, create, overwrite, append }; + virtual void write_file(const path& file_path, const void* data, std::size_t size, bool add_new_line, write_mode mode, bool rw_access = false) = 0; + + virtual void delete_file(const path& file_path) = 0; + + virtual void rename_file(const path& source_path, const path& destination_path, bool replace_existing, bool allow_copy) = 0; + +protected: + FILE* output() { return m_output; } + static const char* write_operation_name(write_mode mode); + static const char* rename_operation_name(bool replace_existing, bool allow_copy); + +public: // mainly for derived class (but also used by helper classes like SafeHandle so public) + void print_operation(const char* operation, const path& target); + void print_operation(const char* operation, const path& source, const path& target); + void print_result(const char* operation, uint32_t result, bool with_last_error = false, const char* opt_arg = nullptr, bool hide_result = false); + void print_error(const char* operation, uint32_t result, bool with_last_error = false, const char* opt_arg = nullptr); + void print_write_success(const void* data, std::size_t size, std::size_t written); + +private: + FILE* m_output; + path m_basepath; +}; diff --git a/test/test_file_operations/test_ntapi.cpp b/test/test_file_operations/test_ntapi.cpp new file mode 100644 index 00000000..7598baae --- /dev/null +++ b/test/test_file_operations/test_ntapi.cpp @@ -0,0 +1,419 @@ + +#include "test_ntapi.h" +#include +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include "test_ntdll_declarations.h" +#include + +class TestNtApi::SafeHandle +{ +public: + SafeHandle(TestFileSystem* tfs, HANDLE handle = NULL) : m_handle(handle), m_tfs(tfs) {} + SafeHandle(const SafeHandle&) = delete; + SafeHandle(SafeHandle&& other) : m_handle(other.m_handle), m_tfs(other.m_tfs) { other.m_handle = nullptr; } + + operator HANDLE() { return m_handle; } + operator PHANDLE() { return &m_handle; } + + bool valid() const { return m_handle != NULL; } + + ~SafeHandle() { + if (m_handle) { + NTSTATUS status = NtClose(m_handle); + if (m_tfs) + m_tfs->print_result("NtClose", status); + if (!NT_SUCCESS(status)) + if (m_tfs) + m_tfs->print_error("NtClose", status); + else + std::fprintf(stderr, "NtClose failed : 0x%lx", status); + m_handle = NULL; + } + } + +private: + HANDLE m_handle; + TestFileSystem* m_tfs; +}; + +const char* TestNtApi::id() +{ + return "Nt"; +} + +TestNtApi::path TestNtApi::real_path(const char* abs_or_rel_path) +{ + if (!abs_or_rel_path || !abs_or_rel_path[0]) + return path(); + + static constexpr char nt_path_prefix[] = "\\??\\"; + static constexpr wchar_t nt_path_prefix_w[] = L"\\??\\"; + + bool path_dos = strncmp(abs_or_rel_path, nt_path_prefix, strlen(nt_path_prefix)) == 0; + bool path_has_drive = abs_or_rel_path[1] == ':'; + bool path_unc = abs_or_rel_path[0] == '\\' && abs_or_rel_path[1] == '\\'; + bool path_absolute = path_has_drive || abs_or_rel_path[0] == '\\'; + + path result; + if (!path_dos) + { + if (!path_unc) + result.assign(nt_path_prefix_w); + if (!path_absolute) + result /= current_directory(); + else if (!path_has_drive && !path_unc) + // if "absolute" path but without a drive letter (i.e. \windows) + // the take the drive from the current directory: (i.e. "C:") + result /= current_directory().root_name(); + } + + int result_size = 0; + for (auto r : result) ++result_size; + + // now append abs_or_rel_path, handling ".." and "." properly: + path arp{ abs_or_rel_path }; + int base_size = path_unc ? 3 : 4; + for (auto p : arp) + { + if (p == "..") { + if (result_size > base_size) { // refuse to remove top level element (i.e. \??\C:\ which is 4 elements) + result.remove_filename(); + --result_size; + } + } + else if (!p.empty() && p != ".") { + result /= p; + ++result_size; + } + } + + return result; +} + +TestNtApi::SafeHandle TestNtApi::open_directory(const path& directory_path, bool create, bool allow_non_existence, long* pstatus) +{ + print_operation(create ? "Creating directory" : "Openning directory", directory_path); + + UNICODE_STRING unicode_path; + RtlInitUnicodeString(&unicode_path, directory_path.c_str()); + + OBJECT_ATTRIBUTES attributes; + InitializeObjectAttributes(&attributes, &unicode_path, OBJ_CASE_INSENSITIVE, NULL, NULL); + + SafeHandle dir(this); + IO_STATUS_BLOCK iosb; + NTSTATUS status = + NtCreateFile(dir, + FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE, + &attributes, &iosb, NULL, FILE_ATTRIBUTE_DIRECTORY, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + create ? FILE_OPEN_IF : FILE_OPEN, + FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, + NULL, 0); + + print_result("NtCreateFile", status); + + if (pstatus) + *pstatus = status; + if ((status == STATUS_OBJECT_NAME_NOT_FOUND || status == STATUS_OBJECT_PATH_NOT_FOUND) && allow_non_existence) + return NULL; + if (!NT_SUCCESS(status)) + throw test::FuncFailed("NtCreateFile", status); + if (!NT_SUCCESS(iosb.Status)) + throw test::FuncFailed("NtCreateFile", "bad iosb.Status", iosb.Status); + + return dir; +} + +TestFileSystem::FileInfoList TestNtApi::list_directory(const path& directory_path) +{ + SafeHandle dir = open_directory(directory_path, false); + + print_operation("Querying directory", directory_path); + + FileInfoList files; + while (true) + { + char buf[4096]{ 0 }; + IO_STATUS_BLOCK iosb; + + NTSTATUS status = + NtQueryDirectoryFile(dir, NULL, NULL, NULL, + &iosb, buf, sizeof(buf), MyFileBothDirectoryInformation, FALSE, NULL, FALSE); + print_result("NtQueryDirectoryFile", status); + + if (status == STATUS_NO_MORE_FILES) + break; + if (!NT_SUCCESS(status)) + throw test::FuncFailed("NtQueryDirectoryFile", status); + if (!NT_SUCCESS(iosb.Status)) + throw test::FuncFailed("NtQueryDirectoryFile", "bad iosb.Status", iosb.Status); + if (iosb.Information == 0) // This shouldn't happend unless the filename (not full path) is larger then sizeof(buf) + throw test::FuncFailed("NtQueryDirectoryFile", "buffer too small", iosb.Information); + + PFILE_BOTH_DIR_INFORMATION info = reinterpret_cast(buf); + while (true) + { + files.push_back(FileInformation( + std::wstring(info->FileName, info->FileNameLength / sizeof(info->FileName[0])), + info->FileAttributes, info->EndOfFile.QuadPart)); + if (info->NextEntryOffset) + info = reinterpret_cast(reinterpret_cast(info) + info->NextEntryOffset); + else + break; + } + } + + return files; +} + +void TestNtApi::create_path(const path& directory_path) +{ + // sanity and guaranteed recursion end: + if (!directory_path.has_relative_path()) + throw std::runtime_error("Refusing to create non-existing top level path"); + + // if directory already exists all is good + NTSTATUS status; + if (open_directory(directory_path, false, true, &status).valid()) + return; + + if (status != STATUS_OBJECT_NAME_NOT_FOUND) // STATUS_OBJECT_NAME_NOT_FOUND means parent directory already exists + create_path(directory_path.parent_path()); // otherwise create parent directory (recursively) + + open_directory(directory_path, true); +} + +void TestNtApi::read_file(const path& file_path) +{ + print_operation("Reading file", file_path); + + UNICODE_STRING unicode_path; + RtlInitUnicodeString(&unicode_path, file_path.c_str()); + + OBJECT_ATTRIBUTES attributes; + InitializeObjectAttributes(&attributes, &unicode_path, OBJ_CASE_INSENSITIVE, NULL, NULL); + + SafeHandle file(this); + IO_STATUS_BLOCK iosb; + NTSTATUS status = + NtOpenFile(file, GENERIC_READ|SYNCHRONIZE, &attributes, &iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT); + print_result("NtOpenFile", status); + + if (!NT_SUCCESS(status)) + throw test::FuncFailed("NtOpenFile", status); + if (!NT_SUCCESS(iosb.Status)) + throw test::FuncFailed("NtOpenFile", "bad iosb.Status", iosb.Status); + + uint32_t total = 0; + bool ends_with_newline = true; + bool pending_prefix = true; + while (true) { + char buf[4096]; + + memset(&iosb, 0, sizeof(iosb)); + status = NtReadFile(file, NULL, NULL, NULL, &iosb, buf, sizeof(buf), NULL, NULL); + print_result("NtReadFile", status); + if (status == STATUS_END_OF_FILE) + break; + if (!NT_SUCCESS(status)) + throw test::FuncFailed("NtReadFile", status); + + total += iosb.Information; + char* begin = buf; + char* end = begin + iosb.Information; + while (begin != end) { + if (pending_prefix) { + if (output()) + fwrite(FILE_CONTENTS_PRINT_PREFIX, 1, strlen(FILE_CONTENTS_PRINT_PREFIX), output()); + pending_prefix = false; + } + bool skip_newline = false; + char* print_end = reinterpret_cast(std::memchr(begin, '\n', end - begin)); + if (print_end) { + pending_prefix = true; + if (print_end > begin && *(print_end - 1) == '\r') { + // convert \r\n => \n: + *(print_end - 1) = '\n'; + skip_newline = true; + } + else // only a '\n' so just print it + ++print_end; + } + else { + print_end = end; + if (print_end > begin && *(print_end - 1) == '\r') { + // buffer ends with \r so skip it under the hope it will be followed with a \n + --print_end; + skip_newline = true; + } + } + if (output()) + fwrite(begin, 1, print_end - begin, output()); + ends_with_newline = print_end > begin && *(print_end - 1) == '\n'; + begin = print_end; + if (skip_newline) + ++begin; + } + if (output() && !ends_with_newline) { + fwrite("\n", 1, 1, output()); + ends_with_newline = true; + } + } + if (output()) + { + fprintf(output(), "# Successfully read %u bytes.\n", total); + } +} + +void TestNtApi::write_file(const path& file_path, const void* data, std::size_t size, bool add_new_line, write_mode mode, bool rw_access) +{ + print_operation(write_operation_name(mode), file_path); + + UNICODE_STRING unicode_path; + RtlInitUnicodeString(&unicode_path, file_path.c_str()); + + OBJECT_ATTRIBUTES attributes; + InitializeObjectAttributes(&attributes, &unicode_path, OBJ_CASE_INSENSITIVE, NULL, NULL); + + ACCESS_MASK access = GENERIC_WRITE | SYNCHRONIZE; + ULONG disposition = FILE_OPEN; + switch (mode) { + case write_mode::truncate: + disposition = FILE_OVERWRITE; + break; + case write_mode::create: + disposition = FILE_CREATE; + break; + case write_mode::overwrite: + disposition = FILE_SUPERSEDE; + break; + case write_mode::append: + disposition = FILE_OPEN_IF; + access = FILE_APPEND_DATA | SYNCHRONIZE; + break; + } + if (rw_access) + access |= GENERIC_READ; + + SafeHandle file(this); + IO_STATUS_BLOCK iosb; + NTSTATUS status = + NtCreateFile( + file, access, &attributes, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, 0, + disposition, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0); + print_result("NtCreateFile", status); + + if (!NT_SUCCESS(status)) + throw test::FuncFailed("NtCreateFile", status); + if (!NT_SUCCESS(iosb.Status)) + throw test::FuncFailed("NtCreateFile", "bad iosb.Status", iosb.Status); + + if (mode == write_mode::manual_truncate) + { + FILE_END_OF_FILE_INFORMATION eofinfo{ 0 }; + status = + NtSetInformationFile(file, &iosb, &eofinfo, sizeof(eofinfo), MyFileEndOfFileInformation); + print_result("NtSetInformationFile", status, false, "EOF"); + + if (!NT_SUCCESS(status)) + throw test::FuncFailed("NtSetInformationFile", status); + if (!NT_SUCCESS(iosb.Status)) + throw test::FuncFailed("NtSetInformationFile", "bad iosb.Status", iosb.Status); + } + + // finally write the data: + size_t total = 0; + + status = + NtWriteFile(file, NULL, NULL, NULL, &iosb, const_cast(data), static_cast(size), NULL, NULL); + print_result("NtWriteFile", status); + if (!NT_SUCCESS(status)) + throw test::FuncFailed("NtWriteFile", status); + if (!NT_SUCCESS(iosb.Status)) + throw test::FuncFailed("NtWriteFile", "bad iosb.Status", iosb.Status); + total += iosb.Information; + + if (add_new_line) + { + status = + NtWriteFile(file, NULL, NULL, NULL, &iosb, "\r\n", 2, NULL, NULL); + print_result("NtWriteFile", status); + if (!NT_SUCCESS(status)) + throw test::FuncFailed("NtWriteFile", status); + if (!NT_SUCCESS(iosb.Status)) + throw test::FuncFailed("NtWriteFile", "bad iosb.Status", iosb.Status); + total += iosb.Information; + } + + print_write_success(data, size, total); +} + +void TestNtApi::delete_file(const path& file_path) +{ + print_operation("Deleting file", file_path); + + UNICODE_STRING unicode_path; + RtlInitUnicodeString(&unicode_path, file_path.c_str()); + + OBJECT_ATTRIBUTES attributes; + InitializeObjectAttributes(&attributes, &unicode_path, OBJ_CASE_INSENSITIVE, NULL, NULL); + + NTSTATUS status = + NtDeleteFile(&attributes); + print_result("NtCreateFile", status); + + if (!NT_SUCCESS(status)) + throw test::FuncFailed("NtDeleteFile", status); +} + +void TestNtApi::rename_file(const path& source_path, const path& destination_path, bool replace_existing, bool allow_copy) +{ + if (allow_copy) + throw test::FuncFailed("rename_file", "ntapi does not support file move"); + + print_operation(rename_operation_name(replace_existing, allow_copy), source_path, destination_path); + + UNICODE_STRING unicode_path; + RtlInitUnicodeString(&unicode_path, source_path.c_str()); + + OBJECT_ATTRIBUTES attributes; + InitializeObjectAttributes(&attributes, &unicode_path, OBJ_CASE_INSENSITIVE, NULL, NULL); + + SafeHandle file(this); + IO_STATUS_BLOCK iosb; + NTSTATUS status = + NtCreateFile( + file, FILE_READ_ATTRIBUTES|DELETE|SYNCHRONIZE, &attributes, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, + FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0); + print_result("NtCreateFile", status); + + if (!NT_SUCCESS(status)) + throw test::FuncFailed("NtCreateFile", status); + if (!NT_SUCCESS(iosb.Status)) + throw test::FuncFailed("NtCreateFile", "bad iosb.Status", iosb.Status); + + bool dest_full_path = source_path.parent_path() != destination_path.parent_path(); + std::wstring dest = dest_full_path ? destination_path : destination_path.filename(); + std::vector buf(sizeof(FILE_RENAME_INFORMATION) + sizeof(wchar_t)*dest.length()); + FILE_RENAME_INFORMATION* rename = reinterpret_cast(buf.data()); + rename->ReplaceIfExists = replace_existing ? TRUE : FALSE; + rename->FileNameLength = sizeof(wchar_t)*dest.length(); + memcpy(&rename->FileName[0], dest.data(), sizeof(wchar_t)*dest.length()); + + status = + NtSetInformationFile(file, &iosb, rename, buf.size(), MyFileRenameInformation); + print_result("NtSetInformationFile", status, false, dest_full_path ? "rename full path" : "rename filename"); + + if (!NT_SUCCESS(status)) + throw test::FuncFailed("NtSetInformationFile", status); + if (!NT_SUCCESS(iosb.Status)) + throw test::FuncFailed("NtSetInformationFile", "bad iosb.Status", iosb.Status); +} diff --git a/test/test_file_operations/test_ntapi.h b/test/test_file_operations/test_ntapi.h new file mode 100644 index 00000000..e94cdc70 --- /dev/null +++ b/test/test_file_operations/test_ntapi.h @@ -0,0 +1,30 @@ +#pragma once + +#include "test_filesystem.h" + +class TestNtApi : public TestFileSystem +{ +public: + TestNtApi(FILE* output) : TestFileSystem(output) {} + + path real_path(const char* abs_or_rel_path) override; + + FileInfoList list_directory(const path& directory_path) override; + + void create_path(const path& directory_path) override; + + void read_file(const path& file_path) override; + + void write_file(const path& file_path, const void* data, std::size_t size, bool add_new_line, write_mode mode, bool rw_access = false) override; + + void delete_file(const path& file_path) override; + + void rename_file(const path& source_path, const path& destination_path, bool replace_existing, bool allow_copy) override; + + const char* id() override; + +private: + class SafeHandle; + + SafeHandle open_directory(const path& directory_path, bool create, bool allow_non_existence=false, long* pstatus=nullptr); +}; diff --git a/test/test_file_operations/test_ntdll_declarations.h b/test/test_file_operations/test_ntdll_declarations.h new file mode 100644 index 00000000..5775974d --- /dev/null +++ b/test/test_file_operations/test_ntdll_declarations.h @@ -0,0 +1,184 @@ +#pragma once + +#define STATUS_NO_MORE_FILES 0x80000006 +#define STATUS_END_OF_FILE 0xC0000011 +#define STATUS_OBJECT_NAME_NOT_FOUND 0xC0000034 +#define STATUS_OBJECT_PATH_NOT_FOUND 0xC000003A + +#define FILE_WRITE_TO_END_OF_FILE 0xffffffff +#define FILE_USE_FILE_POINTER_POSITION 0xfffffffe + +#define InitializeObjectAttributes( p, n, a, r, s ) { \ + (p)->Length = sizeof( OBJECT_ATTRIBUTES ); \ + (p)->RootDirectory = r; \ + (p)->Attributes = a; \ + (p)->ObjectName = n; \ + (p)->SecurityDescriptor = s; \ + (p)->SecurityQualityOfService = NULL; \ + } + +// winternl.h of course defines a joke FILE_INFORMATION_CLASS (why?!) +// so added MY_ to avoid name collision but this is FILE_INFORMATION_CLASS + +typedef enum _MY_FILE_INFORMATION_CLASS { + MyFileDirectoryInformation = 1, + MyFileFullDirectoryInformation, + MyFileBothDirectoryInformation, + MyFileBasicInformation, + MyFileStandardInformation, + MyFileInternalInformation, + MyFileEaInformation, + MyFileAccessInformation, + MyFileNameInformation, + MyFileRenameInformation, + MyFileLinkInformation, + MyFileNamesInformation, + MyFileDispositionInformation, + MyFilePositionInformation, + MyFileFullEaInformation, + MyFileModeInformation, + MyFileAlignmentInformation, + MyFileAllInformation, + MyFileAllocationInformation, + MyFileEndOfFileInformation, + MyFileAlternateNameInformation, + MyFileStreamInformation, + MyFilePipeInformation, + MyFilePipeLocalInformation, + MyFilePipeRemoteInformation, + MyFileMailslotQueryInformation, + MyFileMailslotSetInformation, + MyFileCompressionInformation, + MyFileObjectIdInformation, + MyFileCompletionInformation, + MyFileMoveClusterInformation, + MyFileQuotaInformation, + MyFileReparsePointInformation, + MyFileNetworkOpenInformation, + MyFileAttributeTagInformation, + MyFileTrackingInformation, + MyFileIdBothDirectoryInformation, + MyFileIdFullDirectoryInformation, + MyFileValidDataLengthInformation, + MyFileShortNameInformation, + MyFileIoCompletionNotificationInformation, + MyFileIoStatusBlockRangeInformation, + MyFileIoPriorityHintInformation, + MyFileSfioReserveInformation, + MyFileSfioVolumeInformation, + MyFileHardLinkInformation, + MyFileProcessIdsUsingFileInformation, + MyFileNormalizedNameInformation, + MyFileNetworkPhysicalNameInformation, + MyFileIdGlobalTxDirectoryInformation, + MyFileIsRemoteDeviceInformation, + MyFileUnusedInformation, + MyFileNumaNodeInformation, + MyFileStandardLinkInformation, + MyFileRemoteProtocolInformation, + MyFileRenameInformationBypassAccessCheck, + MyFileLinkInformationBypassAccessCheck, + MyFileVolumeNameInformation, + MyFileIdInformation, + MyFileIdExtdDirectoryInformation, + MyFileReplaceCompletionInformation, + MyFileHardLinkFullIdInformation, + MyFileIdExtdBothDirectoryInformation, + MyFileDispositionInformationEx, + MyFileRenameInformationEx, + MyFileRenameInformationExBypassAccessCheck, + MyFileMaximumInformation +} MY_FILE_INFORMATION_CLASS, *PMY_FILE_INFORMATION_CLASS; + +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_END_OF_FILE_INFORMATION { + LARGE_INTEGER EndOfFile; +} FILE_END_OF_FILE_INFORMATION, *PFILE_END_OF_FILE_INFORMATION; + +typedef struct _FILE_RENAME_INFORMATION { + BOOLEAN ReplaceIfExists; + HANDLE RootDirectory; + ULONG FileNameLength; + WCHAR FileName[1]; +} FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION; + +extern "C" +__kernel_entry NTSTATUS +NTAPI +NtQueryDirectoryFile( + IN HANDLE FileHandle, + IN HANDLE Event, + IN PIO_APC_ROUTINE ApcRoutine, + IN PVOID ApcContext, + OUT PIO_STATUS_BLOCK IoStatusBlock, + OUT PVOID FileInformation, + IN ULONG Length, + IN MY_FILE_INFORMATION_CLASS FileInformationClass, + IN BOOLEAN ReturnSingleEntry, + IN PUNICODE_STRING FileName, + IN BOOLEAN RestartScan +); + +extern "C" +__kernel_entry NTSTATUS +NTAPI +NtReadFile( + IN HANDLE FileHandle, + IN HANDLE Event, + IN PIO_APC_ROUTINE ApcRoutine, + IN PVOID ApcContext, + OUT PIO_STATUS_BLOCK IoStatusBlock, + OUT PVOID Buffer, + IN ULONG Length, + IN PLARGE_INTEGER ByteOffset, + IN PULONG Key +); + +extern "C" +__kernel_entry NTSTATUS +NTAPI +NtWriteFile( + IN HANDLE FileHandle, + IN HANDLE Event, + IN PIO_APC_ROUTINE ApcRoutine, + IN PVOID ApcContext, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN PVOID Buffer, + IN ULONG Length, + IN PLARGE_INTEGER ByteOffset, + IN PULONG Key +); + +extern "C" +__kernel_entry NTSTATUS +NTAPI +NtSetInformationFile( + IN HANDLE FileHandle, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN PVOID FileInformation, + IN ULONG Length, + IN MY_FILE_INFORMATION_CLASS FileInformationClass +); + +extern "C" +__kernel_entry NTSTATUS +NTAPI +NtDeleteFile( + IN POBJECT_ATTRIBUTES ObjectAttributes +); diff --git a/test/test_file_operations/test_w32api.cpp b/test/test_file_operations/test_w32api.cpp new file mode 100644 index 00000000..4becb6ec --- /dev/null +++ b/test/test_file_operations/test_w32api.cpp @@ -0,0 +1,308 @@ + +#include "test_w32api.h" +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include + +class TestW32Api::SafeHandle +{ +public: + SafeHandle(TestFileSystem* tfs, HANDLE handle = NULL) : m_handle(handle), m_tfs(tfs) {} + SafeHandle(const SafeHandle&) = delete; + SafeHandle(SafeHandle&& other) : m_handle(other.m_handle), m_tfs(other.m_tfs) { other.m_handle = nullptr; } + + operator HANDLE() { return m_handle; } + operator PHANDLE() { return &m_handle; } + uint32_t result_for_print() { return static_cast(reinterpret_cast(m_handle)); } + + bool valid() const { return m_handle != INVALID_HANDLE_VALUE; } + + ~SafeHandle() { + if (m_handle != INVALID_HANDLE_VALUE) { + BOOL res = CloseHandle(m_handle); + if (m_tfs) + m_tfs->print_result("CloseHandle", res, true); + if (!res) + if (m_tfs) + m_tfs->print_error("CloseHandle", res, true); + else + std::fprintf(stderr, "CloseHandle failed : %d", GetLastError()); + m_handle = NULL; + } + } + +private: + HANDLE m_handle; + TestFileSystem* m_tfs; +}; + +class TestW32Api::SafeFindHandle +{ +public: + SafeFindHandle(TestFileSystem* tfs, HANDLE handle = NULL) : m_handle(handle), m_tfs(tfs) {} + SafeFindHandle(const SafeFindHandle&) = delete; + SafeFindHandle(SafeFindHandle&& other) : m_handle(other.m_handle), m_tfs(other.m_tfs) { other.m_handle = nullptr; } + + operator HANDLE() { return m_handle; } + operator PHANDLE() { return &m_handle; } + uint32_t result_for_print() { return static_cast(reinterpret_cast(m_handle)); } + + bool valid() const { return m_handle != INVALID_HANDLE_VALUE; } + + ~SafeFindHandle() { + if (m_handle != INVALID_HANDLE_VALUE) { + BOOL res = FindClose(m_handle); + if (m_tfs) + m_tfs->print_result("CloseHandle", res, true); + if (!res) + if (m_tfs) + m_tfs->print_error("CloseHandle", res, true); + else + std::fprintf(stderr, "CloseHandle failed : %d", GetLastError()); + m_handle = NULL; + } + } + +private: + HANDLE m_handle; + TestFileSystem* m_tfs; +}; + +const char* TestW32Api::id() +{ + return "W32"; +} + +TestW32Api::path TestW32Api::real_path(const char* abs_or_rel_path) +{ + if (!abs_or_rel_path || !abs_or_rel_path[0]) + return path(); + + char buf[1024]; + size_t res = GetFullPathNameA(abs_or_rel_path, _countof(buf), buf, NULL); + if (!res || res >= _countof(buf)) + throw_testWinFuncFailed("GetFullPathNameA", res); + return buf; +} + +TestFileSystem::FileInfoList TestW32Api::list_directory(const path& directory_path) +{ + print_operation("Querying directory", directory_path); + + WIN32_FIND_DATA fd; + SafeFindHandle find(this, + FindFirstFileW((directory_path / L"*").c_str(), &fd)); + print_result("FindFirstFileW", 0, true, nullptr, true); + if (!find.valid()) + throw_testWinFuncFailed("FindFirstFileW"); + + FileInfoList files; + while (true) + { + files.push_back(FileInformation(fd.cFileName, fd.dwFileAttributes, fd.nFileSizeHigh*(MAXDWORD + 1) + fd.nFileSizeLow)); + BOOL res = FindNextFileW(find, &fd); + print_result("FindNextFileW", res, true); + if (!res) + break; + } + + return files; +} + +void TestW32Api::create_path(const path& directory_path) +{ + // sanity and guaranteed recursion end: + if (!directory_path.has_relative_path()) + throw std::runtime_error("Refusing to create non-existing top level path"); + + print_operation("Checking directory", directory_path); + + DWORD attr = GetFileAttributesW(directory_path.c_str()); + DWORD err = GetLastError(); + print_result("GetFileAttributesW", attr, true); + if (attr != INVALID_FILE_ATTRIBUTES) { + if (attr & FILE_ATTRIBUTE_DIRECTORY) + return; // if directory already exists all is good + else + throw std::runtime_error("path exists but not a directory"); + } + if (err != ERROR_FILE_NOT_FOUND && err != ERROR_PATH_NOT_FOUND) + throw_testWinFuncFailed("GetFileAttributesW"); + + if (err != ERROR_FILE_NOT_FOUND) // ERROR_FILE_NOT_FOUND means parent directory already exists + create_path(directory_path.parent_path()); // otherwise create parent directory (recursively) + + print_operation("Creating directory", directory_path); + + BOOL res = CreateDirectoryW(directory_path.c_str(), NULL); + print_result("CreateDirectoryW", res, true); + if (!res) + throw_testWinFuncFailed("CreateDirectoryW"); +} + +void TestW32Api::read_file(const path& file_path) +{ + print_operation("Reading file", file_path); + + SafeHandle file(this, + CreateFileW(file_path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)); + print_result("CreateFileW", 0, true, nullptr, true); + if (!file.valid()) + throw_testWinFuncFailed("CreateFileW"); + + uint32_t total = 0; + bool ends_with_newline = true; + bool pending_prefix = true; + while (true) { + char buf[4096]; + + DWORD read = 0; + BOOL res = ReadFile(file, buf, sizeof(buf), &read, NULL); + print_result("ReadFile", res, true); + if (!res) + throw_testWinFuncFailed("ReadFile"); + if (!read) // eof + break; + + total += read; + char* begin = buf; + char* end = begin + read; + while (begin != end) { + if (pending_prefix) { + if (output()) + fwrite(FILE_CONTENTS_PRINT_PREFIX, 1, strlen(FILE_CONTENTS_PRINT_PREFIX), output()); + pending_prefix = false; + } + bool skip_newline = false; + char* print_end = reinterpret_cast(std::memchr(begin, '\n', end - begin)); + if (print_end) { + pending_prefix = true; + if (print_end > begin && *(print_end-1) == '\r') { + // convert \r\n => \n: + *(print_end-1) = '\n'; + skip_newline = true; + } + else // only a '\n' so just print it + ++print_end; + } + else { + print_end = end; + if (print_end > begin && *(print_end - 1) == '\r') { + // buffer ends with \r so skip it under the hope it will be followed with a \n + --print_end; + skip_newline = true; + } + } + if (output()) + fwrite(begin, 1, print_end - begin, output()); + ends_with_newline = print_end > begin && *(print_end - 1) == '\n'; + begin = print_end; + if (skip_newline) + ++begin; + } + if (output() && !ends_with_newline) { + fwrite("\n", 1, 1, output()); + ends_with_newline = true; + } + } + if (output()) + { + fprintf(output(), "# Successfully read %u bytes.\n", total); + } +} + +void TestW32Api::write_file(const path& file_path, const void* data, std::size_t size, bool add_new_line, write_mode mode, bool rw_access) +{ + print_operation(write_operation_name(mode), file_path); + + ACCESS_MASK access = GENERIC_WRITE; + DWORD disposition = OPEN_EXISTING; + switch (mode) { + case write_mode::truncate: + disposition = TRUNCATE_EXISTING; + break; + case write_mode::create: + disposition = CREATE_NEW; + break; + case write_mode::overwrite: + disposition = CREATE_ALWAYS; + break; + case write_mode::append: + disposition = OPEN_ALWAYS; + access = FILE_APPEND_DATA; + break; + } + if (rw_access) + access |= GENERIC_READ; + + SafeHandle file(this, + CreateFile(file_path.c_str(), access, 0, NULL, disposition, FILE_ATTRIBUTE_NORMAL, NULL)); + print_result("CreateFileW", 0, true, nullptr, true); + if (!file.valid()) + throw_testWinFuncFailed("CreateFile"); + + if (mode == write_mode::manual_truncate) + { + BOOL res = SetEndOfFile(file); + print_result("SetEndOfFile", res, true); + if (!res) + throw_testWinFuncFailed("SetEndOfFile"); + } + + if (mode == write_mode::append) + { + DWORD res = SetFilePointer(file, 0, NULL, FILE_END); + print_result("SetFilePointer(FILE_END)", res, true); + if (res == INVALID_SET_FILE_POINTER) + throw_testWinFuncFailed("SetEndOfFile"); + } + + // finally write the data: + size_t total = 0; + + DWORD written = 0; + BOOL res = WriteFile(file, data, static_cast(size), &written, NULL); + print_result("WriteFile", written, true); + if (!res) + throw_testWinFuncFailed("WriteFile"); + total += written; + + if (add_new_line) { + res = WriteFile(file, "\r\n", 2, &written, NULL); + print_result("WriteFile", written, true, ""); + if (!res) + throw_testWinFuncFailed("WriteFile"); + total += written; + } + + print_write_success(data, size, total); +} + +void TestW32Api::delete_file(const path& file_path) +{ + print_operation("Deleting file", file_path); + + BOOL res = DeleteFileW(file_path.c_str()); + print_result("DeleteFileW", res, true); + if (!res) + throw_testWinFuncFailed("DeleteFileW"); +} + +void TestW32Api::rename_file(const path& source_path, const path& destination_path, bool replace_existing, bool allow_copy) +{ + print_operation(rename_operation_name(replace_existing, allow_copy), source_path, destination_path); + + DWORD flags = 0; + if (replace_existing) + flags |= MOVEFILE_REPLACE_EXISTING; + if (allow_copy) + flags |= MOVEFILE_COPY_ALLOWED; + + BOOL res = MoveFileExW(source_path.c_str(), destination_path.c_str(), flags); + print_result("MoveFileExW", res, true); + if (!res) + throw_testWinFuncFailed("MoveFileExW"); +} diff --git a/test/test_file_operations/test_w32api.h b/test/test_file_operations/test_w32api.h new file mode 100644 index 00000000..7f90704b --- /dev/null +++ b/test/test_file_operations/test_w32api.h @@ -0,0 +1,29 @@ +#pragma once + +#include "test_filesystem.h" + +class TestW32Api : public TestFileSystem +{ +public: + TestW32Api(FILE* output) : TestFileSystem(output) {} + + path real_path(const char* abs_or_rel_path) override; + + FileInfoList list_directory(const path& directory_path) override; + + void create_path(const path& directory_path) override; + + void read_file(const path& file_path) override; + + void write_file(const path& file_path, const void* data, std::size_t size, bool add_new_line, write_mode mode, bool rw_access = false) override; + + void delete_file(const path& file_path) override; + + void rename_file(const path& source_path, const path& destination_path, bool replace_existing, bool allow_copy) override; + + const char* id() override; + +private: + class SafeHandle; + class SafeFindHandle; +}; diff --git a/vsbuild/test_file_operations.vcxproj b/vsbuild/test_file_operations.vcxproj new file mode 100644 index 00000000..6c7f2971 --- /dev/null +++ b/vsbuild/test_file_operations.vcxproj @@ -0,0 +1,145 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {0B2FF5AF-8580-458C-8EF4-10E6B6398D3A} + testfileoperations + 10.0.16299.0 + + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Disabled + + + Console + ntdll.lib;%(AdditionalDependencies) + + + + + Disabled + + + Console + ntdll.lib;%(AdditionalDependencies) + + + + + MaxSpeed + true + true + + + true + true + Console + ntdll.lib;%(AdditionalDependencies) + + + + + MaxSpeed + true + true + + + true + true + Console + ntdll.lib;%(AdditionalDependencies) + + + + + + + + + + + {2bb3300b-f08a-4063-95c4-8a0fadae6c51} + + + + + + + + + + + + \ No newline at end of file diff --git a/vsbuild/test_file_operations.vcxproj.filters b/vsbuild/test_file_operations.vcxproj.filters new file mode 100644 index 00000000..1f7f1291 --- /dev/null +++ b/vsbuild/test_file_operations.vcxproj.filters @@ -0,0 +1,45 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/vsbuild/usvfs.sln b/vsbuild/usvfs.sln index 9c75e384..f44bd1c1 100644 --- a/vsbuild/usvfs.sln +++ b/vsbuild/usvfs.sln @@ -80,6 +80,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "udis86", "udis86.vcxproj", EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "spdlog", "spdlog.vcxproj", "{CDADDEC0-B72B-43B4-831F-72BA85B72805}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_file_operations", "test_file_operations.vcxproj", "{0B2FF5AF-8580-458C-8EF4-10E6B6398D3A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -216,6 +218,14 @@ Global {CDADDEC0-B72B-43B4-831F-72BA85B72805}.Release|x64.Build.0 = Release|x64 {CDADDEC0-B72B-43B4-831F-72BA85B72805}.Release|x86.ActiveCfg = Release|Win32 {CDADDEC0-B72B-43B4-831F-72BA85B72805}.Release|x86.Build.0 = Release|Win32 + {0B2FF5AF-8580-458C-8EF4-10E6B6398D3A}.Debug|x64.ActiveCfg = Debug|x64 + {0B2FF5AF-8580-458C-8EF4-10E6B6398D3A}.Debug|x64.Build.0 = Debug|x64 + {0B2FF5AF-8580-458C-8EF4-10E6B6398D3A}.Debug|x86.ActiveCfg = Debug|Win32 + {0B2FF5AF-8580-458C-8EF4-10E6B6398D3A}.Debug|x86.Build.0 = Debug|Win32 + {0B2FF5AF-8580-458C-8EF4-10E6B6398D3A}.Release|x64.ActiveCfg = Release|x64 + {0B2FF5AF-8580-458C-8EF4-10E6B6398D3A}.Release|x64.Build.0 = Release|x64 + {0B2FF5AF-8580-458C-8EF4-10E6B6398D3A}.Release|x86.ActiveCfg = Release|Win32 + {0B2FF5AF-8580-458C-8EF4-10E6B6398D3A}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -231,6 +241,7 @@ Global {E75F344A-1373-4E3F-97EA-BC80CA42E0F0} = {9A2047E2-0EC2-409E-842B-B7B360B2A45F} {5B0E078A-D196-4EBB-BD6E-DCFB48B15E90} = {9A2047E2-0EC2-409E-842B-B7B360B2A45F} {CDADDEC0-B72B-43B4-831F-72BA85B72805} = {9A2047E2-0EC2-409E-842B-B7B360B2A45F} + {0B2FF5AF-8580-458C-8EF4-10E6B6398D3A} = {EDA9B67D-1E64-4CAB-8391-10712538C821} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C416F0AE-21DB-4936-84B9-065D8AEC1597} From aebbdb2a8717f90460d7c54666bd71bf0636bcc7 Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Sat, 6 Jan 2018 16:20:16 +0200 Subject: [PATCH 11/89] Add usvfs_test regression test --- test/usvfs_test/usvfs_basic_test.cpp | 210 ++++++ test/usvfs_test/usvfs_basic_test.h | 14 + test/usvfs_test/usvfs_test.cpp | 221 +++++++ test/usvfs_test/usvfs_test_base.cpp | 925 +++++++++++++++++++++++++++ test/usvfs_test/usvfs_test_base.h | 89 +++ vsbuild/usvfs.sln | 11 + vsbuild/usvfs_test.vcxproj | 152 +++++ vsbuild/usvfs_test.vcxproj.filters | 36 ++ 8 files changed, 1658 insertions(+) create mode 100644 test/usvfs_test/usvfs_basic_test.cpp create mode 100644 test/usvfs_test/usvfs_basic_test.h create mode 100644 test/usvfs_test/usvfs_test.cpp create mode 100644 test/usvfs_test/usvfs_test_base.cpp create mode 100644 test/usvfs_test/usvfs_test_base.h create mode 100644 vsbuild/usvfs_test.vcxproj create mode 100644 vsbuild/usvfs_test.vcxproj.filters diff --git a/test/usvfs_test/usvfs_basic_test.cpp b/test/usvfs_test/usvfs_basic_test.cpp new file mode 100644 index 00000000..92ed09f8 --- /dev/null +++ b/test/usvfs_test/usvfs_basic_test.cpp @@ -0,0 +1,210 @@ + +#include "usvfs_basic_test.h" + +const char* usvfs_basic_test::scenario_name() +{ + return SCENARIO_NAME; +} + +bool usvfs_basic_test::scenario_run() +{ + // Note: For regression purposes we don't really need to verify the results of most our operations + // as the usvfs_test_base postmortem_check will verify the final state. + // We still also verify the results here because: + // A. In some cases a later step may change the results. + // B. It is easier to understand and maintain the test when the important checks are together with + // their related operations. + // C. For open issues the verifications here serve as a "documentation" of the issue (i.e. not having + // proper copy_on_write, etc.). + + ops_list(LR"(.)", true, true); + + // test proper path creation under overwrite when a virtualized folder is written to: + + verify_source_existance(LR"(overwrite\mfolder1)", false); + verify_source_existance(LR"(overwrite\mfolder2)", false); + verify_source_existance(LR"(overwrite\mfolder3)", false); + verify_source_existance(LR"(overwrite\mfolder4)", false); + + if (bool strict_overwrite_path_creation = false) + { + ops_overwrite(LR"(mfolder1\fail\epic\fail\newfile1.txt)", R"(newfile1.txt nonrecursive overwrite should fail)", false, false); + verify_source_existance(LR"(overwrite\mfolder1)", false); + ops_overwrite(LR"(mfolder1\fail\newfile1.txt)", R"(newfile1.txt nonrecursive overwrite should fail)", false, false); + verify_source_existance(LR"(overwrite\mfolder1)", false); + ops_overwrite(LR"(mfolder1\newfile1.txt)", R"(newfile1.txt nonrecursive overwrite)", false); + ops_read(LR"(mfolder1\newfile1.txt)"); + verify_source_contents(LR"(overwrite\mfolder1\newfile1.txt)", R"(newfile1.txt nonrecursive overwrite)"); + // repeat mfolder1\fail test as that folder now exists in overwrite and that changes things + ops_overwrite(LR"(mfolder1\fail\newfile1.txt)", R"(newfile1.txt nonrecursive overwrite should fail)", false, false); + } + else { + ops_overwrite(LR"(mfolder1\fail\epic\fail\newfile1.txt)", R"(newfile1.txt nonrecursive overwrite should fail)", false); + verify_source_contents(LR"(overwrite\mfolder1\fail\epic\fail\newfile1.txt)", R"(newfile1.txt nonrecursive overwrite should fail)"); + ops_overwrite(LR"(mfolder1\fail\newfile1.txt)", R"(newfile1.txt nonrecursive overwrite should fail)", false); + verify_source_contents(LR"(overwrite\mfolder1\fail\newfile1.txt)", R"(newfile1.txt nonrecursive overwrite should fail)"); + ops_overwrite(LR"(mfolder1\newfile1.txt)", R"(newfile1.txt nonrecursive overwrite)", false); + ops_read(LR"(mfolder1\newfile1.txt)"); + verify_source_contents(LR"(overwrite\mfolder1\newfile1.txt)", R"(newfile1.txt nonrecursive overwrite)"); + } + + ops_overwrite(LR"(mfolder2\newfile2.txt)", R"(newfile2.txt recursive overwrite)", true); + ops_read(LR"(mfolder2\newfile2.txt)"); + verify_source_contents(LR"(overwrite\mfolder2\newfile2.txt)", R"(newfile2.txt recursive overwrite)"); + + if (bool not_bugged = false) + { + ops_overwrite(LR"(mfolder3\newfolder3\newfile3.txt)", R"(newfile3.txt recursive overwrite)", true); + ops_read(LR"(mfolder3\newfolder3\newfile3.txt)"); + verify_source_contents(LR"(overwrite\mfolder3\newfolder3\newfile3.txt)", R"(newfile3.txt recursive overwrite)"); + // repeat mfolder3\newfolder3 test as that folder now exists in overwrite and that changes things + ops_overwrite(LR"(mfolder3\newfolder3\newfile3e.txt)", R"(newfile3e.txt recursive overwrite)", true); + ops_read(LR"(mfolder3\newfolder3\newfile3e.txt)"); + verify_source_contents(LR"(overwrite\mfolder3\newfolder3\newfile3e.txt)", R"(newfile3e.txt recursive overwrite)"); + } + else + { + ops_overwrite(LR"(mfolder3\newfolder3\newfile3.txt)", R"(newfile3.txt recursive overwrite)", true, false); + verify_source_existance(LR"(overwrite\mfolder3\newfolder3)", false); + } + + if (bool not_bugged = false) + { + ops_overwrite(LR"(mfolder4\newfolder4\d\e\e\p\newfile4.txt)", R"(newfile4.txt recursive overwrite)", true); + ops_read(LR"(mfolder4\newfolder4\d\e\e\p\newfile4.txt)"); + verify_source_contents(LR"(overwrite\mfolder4\newfolder4\d\e\e\p\newfile4.txt)", R"(newfile4.txt recursive overwrite)"); + // repeat mfolder4\newfolder4\d\e\e\p test as that folder now exists in overwrite and that changes things + ops_overwrite(LR"(mfolder4\newfolder4\d\e\e\p\newfile4e.txt)", R"(newfile4e.txt recursive overwrite)", true); + ops_read(LR"(mfolder4\newfolder4\d\e\e\p\newfile4e.txt)"); + verify_source_contents(LR"(overwrite\mfolder4\newfolder4\d\e\e\p\newfile4e.txt)", R"(newfile4e.txt recursive overwrite)"); + // and finally verify also non-recursive works + ops_overwrite(LR"(mfolder4\newfolder4\d\e\e\p\newfile4enr.txt)", R"(newfile4enr.txt nonrecursive overwrite)", false); + ops_read(LR"(mfolder4\newfolder4\d\e\e\p\newfile4enr.txt)"); + verify_source_contents(LR"(overwrite\mfolder4\newfolder4\d\e\e\p\newfile4enr.txt)", R"(newfile4enr.txt nonrecursive overwrite)"); + } + else + { + ops_overwrite(LR"(mfolder4\newfolder4\d\e\e\p\newfile4.txt)", R"(newfile4.txt recursive overwrite)", true, false); + verify_source_existance(LR"(overwrite\mfolder4\newfolder4)", false); + } + + // test copy on write/delete against source "mod": + + + { + const auto& old_contents = source_contents(LR"(mod4\mfolder4\mfileoverwrite.txt)"); + verify_source_contents(LR"(mod4\mfolder4\mfileoverwrite.txt)", old_contents.c_str()); + ops_overwrite(LR"(mfolder4\mfileoverwrite.txt)", R"(mfolder4\mfileoverwrite.txt overwrite)", false); + ops_read(LR"(mfolder4\mfileoverwrite.txt)"); + if (auto copy_on_write_implemented = false) + { + verify_source_contents(LR"(mod4\mfolder4\mfileoverwrite.txt)", old_contents.c_str()); + verify_source_contents(LR"(overwrite\mfolder4\mfileoverwrite.txt)", R"(mfolder4\mfileoverwrite.txt overwrite)"); + } + else { + verify_source_contents(LR"(mod4\mfolder4\mfileoverwrite.txt)", R"(mfolder4\mfileoverwrite.txt overwrite)"); + verify_source_existance(LR"(overwrite\mfolder4\mfileoverwrite.txt)", false); + } + } + + { + const auto& old_contents = source_contents(LR"(mod4\mfolder4\mfilerewrite.txt)"); + verify_source_contents(LR"(mod4\mfolder4\mfilerewrite.txt)", old_contents.c_str()); + ops_rewrite(LR"(mfolder4\mfilerewrite.txt)", R"(mfolder4\mfilerewrite.txt rewrite)"); + ops_read(LR"(mfolder4\mfilerewrite.txt)"); + if (auto copy_on_write_implemented = false) + { + verify_source_contents(LR"(mod4\mfolder4\mfilerewrite.txt)", old_contents.c_str()); + verify_source_contents(LR"(overwrite\mfolder4\mfilerewrite.txt)", R"(mfolder4\mfilerewrite.txt rewrite)"); + } + else { + verify_source_contents(LR"(mod4\mfolder4\mfilerewrite.txt)", R"(mfolder4\mfilerewrite.txt rewrite)"); + verify_source_existance(LR"(overwrite\mfolder4\mfilerewrite.txt)", false); + } + } + + { + const auto& old_contents = source_contents(LR"(mod4\mfolder4\mfilemoveover.txt)"); + verify_source_contents(LR"(mod4\mfolder4\mfilemoveover.txt)", old_contents.c_str()); + ops_overwrite(LR"(mfolder4\temp_mfilemoveover.txt)", R"(mfolder4\mfilemoveover.txt overwrite)", false); + verify_source_contents(LR"(overwrite\mfolder4\temp_mfilemoveover.txt)", R"(mfolder4\mfilemoveover.txt overwrite)"); + ops_rename(LR"(mfolder4\temp_mfilemoveover.txt)", LR"(mfolder4\mfilemoveover.txt)", true); + ops_read(LR"(mfolder4\mfilemoveover.txt)"); + verify_source_existance(LR"(overwrite\mfolder4\temp_mfilemoveover.txt)", false); + verify_source_contents(LR"(mod4\mfolder4\mfilemoveover.txt)", old_contents.c_str()); + verify_source_contents(LR"(overwrite\mfolder4\mfilemoveover.txt)", R"(mfolder4\mfilemoveover.txt overwrite)"); + } + + { + const auto& old_contents = source_contents(LR"(mod4\mfolder4\mfiledeletewrite.txt)"); + verify_source_contents(LR"(mod4\mfolder4\mfiledeletewrite.txt)", old_contents.c_str()); + ops_delete(LR"(mfolder4\mfiledeletewrite.txt)"); + ops_overwrite(LR"(mfolder4\mfiledeletewrite.txt)", R"(mfolder4\mfiledeletewrite.txt overwrite)", false); + ops_read(LR"(mfolder4\mfiledeletewrite.txt)"); + verify_source_contents(LR"(overwrite\mfolder4\mfiledeletewrite.txt)", R"(mfolder4\mfiledeletewrite.txt deletewrite)"); + if (auto proper_delete_implemented = false) + verify_source_contents(LR"(mod4\mfolder4\mfiledeletewrite.txt)", old_contents.c_str()); + else + verify_source_existance(LR"(mod4\mfolder4\mfiledeletewrite.txt)", false); + } + + { + const auto& old_contents = source_contents(LR"(mod4\mfolder4\mfiledeletemove.txt)"); + verify_source_contents(LR"(mod4\mfolder4\mfiledeletemove.txt)", old_contents.c_str()); + ops_delete(LR"(mfolder4\mfiledeletemove.txt)"); + ops_overwrite(LR"(mfolder4\temp_mfiledeletemove.txt)", R"(mfolder4\mfiledeletemove.txt overwrite)", false); + verify_source_contents(LR"(overwrite\mfolder4\temp_mfiledeletemove.txt)", R"(mfolder4\mfiledeletemove.txt overwrite)"); + ops_rename(LR"(mfolder4\temp_mfiledeletemove.txt)", LR"(mfolder4\mfiledeletemove.txt)", false); + ops_read(LR"(mfolder4\mfiledeletemove.txt)"); + verify_source_existance(LR"(overwrite\mfolder4\temp_mfiledeletemove.txt)", false); + verify_source_contents(LR"(overwrite\mfolder4\mfiledeletemove.txt)", R"(mfolder4\mfiledeletemove.txt overwrite)"); + if (auto proper_delete_implemented = false) + verify_source_contents(LR"(mod4\mfolder4\mfiledeletemove.txt)", old_contents.c_str()); + else + verify_source_existance(LR"(mod4\mfolder4\mfiledeletemove.txt)", false); + } + + // test copy on write/delete/move against original mount files: + + { + const auto& old_contents = mount_contents(LR"(rfolder\rfilerewrite.txt)"); + ops_rewrite(LR"(rfolder\rfilerewrite.txt)", R"(rfolder\rfilerewrite.txt rewrite)"); + ops_read(LR"(rfolder\rfilerewrite.txt)"); + if (auto copy_on_write_implemented = false) + { + verify_source_contents(LR"(overwrite\rfolder\rfilerewrite.txt)", R"(rfolder\rfilerewrite.txt rewrite)"); + verify_mount_contents(LR"(rfolder\rfilerewrite.txt)", old_contents.c_str()); + } + else { + verify_source_existance(LR"(overwrite\rfolder\rfilerewrite.txt)", false); + verify_mount_contents(LR"(rfolder\rfilerewrite.txt)", R"(rfolder\rfilerewrite.txt rewrite)"); + } + } + + { + const auto& old_contents = mount_contents(LR"(rfolder\rfiledelete.txt)"); + ops_delete(LR"(rfolder\rfiledelete.txt)"); + ops_read(LR"(rfolder\rfiledelete.txt)", false); + if (auto proper_delete_implemented = false) + verify_mount_contents(LR"(rfolder\rfiledelete.txt)", old_contents.c_str()); + else + verify_mount_existance(LR"(rfolder\rfiledelete.txt)", false); + } + + { + const auto& old_contents = mount_contents(LR"(rfolder\rfileoldname.txt)"); + ops_rename(LR"(rfolder\rfileoldname.txt)", LR"(rfolder\rfilenewname.txt)", false, false); + ops_read(LR"(rfolder\rfileoldname.txt)", false); + ops_read(LR"(rfolder\rfilenewname.txt)"); + verify_source_contents(LR"(overwrite\rfolder\rfilenewname.txt)", old_contents.c_str()); + verify_mount_existance(LR"(rfolder\rfilenewname.txt)", false); + if (auto copy_on_move_implemented = false) + verify_mount_contents(LR"(rfolder\rfileoldname.txt)", old_contents.c_str()); + else + verify_mount_existance(LR"(rfolder\rfileoldname.txt)", false); + } + + ops_list(LR"(.)", true, true); + + return true; +} diff --git a/test/usvfs_test/usvfs_basic_test.h b/test/usvfs_test/usvfs_basic_test.h new file mode 100644 index 00000000..bec81f23 --- /dev/null +++ b/test/usvfs_test/usvfs_basic_test.h @@ -0,0 +1,14 @@ +#pragma once + +#include "usvfs_test_base.h" + +class usvfs_basic_test : public usvfs_test_base +{ +public: + static constexpr auto SCENARIO_NAME = "basic"; + + usvfs_basic_test(const usvfs_test_options& options) : usvfs_test_base(options) {} + + virtual const char* scenario_name(); + virtual bool scenario_run(); +}; diff --git a/test/usvfs_test/usvfs_test.cpp b/test/usvfs_test/usvfs_test.cpp new file mode 100644 index 00000000..40a14dc1 --- /dev/null +++ b/test/usvfs_test/usvfs_test.cpp @@ -0,0 +1,221 @@ + +#include +#include +#include +#include +#include "usvfs_basic_test.h" + +void print_usage(const std::wstring& exe_name, const std::wstring& test_name) { + using namespace std; + wcerr << "usage: " << exe_name << " [] [: