diff --git a/build/VStudio/testing/winfsp-tests.vcxproj b/build/VStudio/testing/winfsp-tests.vcxproj
index c15e3f4a..9bbe80cb 100644
--- a/build/VStudio/testing/winfsp-tests.vcxproj
+++ b/build/VStudio/testing/winfsp-tests.vcxproj
@@ -185,6 +185,7 @@
+
@@ -213,6 +214,9 @@
{4a7c0b21-9e10-4c81-92de-1493efcf24eb}
+
+
+
diff --git a/build/VStudio/testing/winfsp-tests.vcxproj.filters b/build/VStudio/testing/winfsp-tests.vcxproj.filters
index a2afe2f8..9295b644 100644
--- a/build/VStudio/testing/winfsp-tests.vcxproj.filters
+++ b/build/VStudio/testing/winfsp-tests.vcxproj.filters
@@ -79,6 +79,9 @@
Source
+
+ Source
+
@@ -91,4 +94,9 @@
Source
+
+
+ Source
+
+
\ No newline at end of file
diff --git a/tst/winfsp-tests/exec-test.c b/tst/winfsp-tests/exec-test.c
new file mode 100644
index 00000000..fbe4814f
--- /dev/null
+++ b/tst/winfsp-tests/exec-test.c
@@ -0,0 +1,228 @@
+/**
+ * @file exec-test.c
+ *
+ * @copyright 2015-2017 Bill Zissimopoulos
+ */
+/*
+ * This file is part of WinFsp.
+ *
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License version 3 as published by the Free Software
+ * Foundation.
+ *
+ * Licensees holding a valid commercial license may use this file in
+ * accordance with the commercial license agreement provided with the
+ * software.
+ */
+
+#include
+#include
+#include
+#include "memfs.h"
+
+#include "winfsp-tests.h"
+
+static NTSTATUS WriteResource(
+ HANDLE Handle, HANDLE Module, PWSTR ResourceName, PULONG PBytesTransferred)
+{
+ HRSRC Resource;
+ HGLOBAL ResourceGlob;
+ PVOID ResourceData;
+ DWORD ResourceSize;
+
+ if ((Resource = FindResourceW(Module, ResourceName, RT_RCDATA)) &&
+ (ResourceGlob = LoadResource(Module, Resource)) &&
+ (ResourceData = LockResource(ResourceGlob)) &&
+ (ResourceSize = SizeofResource(Module, Resource)) &&
+ (WriteFile(Handle, ResourceData, ResourceSize, PBytesTransferred, 0)))
+ return STATUS_SUCCESS;
+ else
+ return FspNtStatusFromWin32(GetLastError());
+}
+
+static NTSTATUS ExtractHelperProgram(PWSTR FileName)
+{
+ HANDLE Handle;
+ ULONG BytesTransferred;
+ NTSTATUS Result;
+
+ Handle = CreateFileW(FileName,
+ FILE_WRITE_DATA, FILE_SHARE_WRITE, 0,
+ CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
+ if (INVALID_HANDLE_VALUE == Handle)
+ return FspNtStatusFromWin32(GetLastError());
+
+ Result = WriteResource(
+ Handle,
+ 0,
+#if defined(_WIN64)
+ L"winfsp-tests-helper-x64.exe",
+#elif defined(_WIN32)
+ L"winfsp-tests-helper-x86.exe",
+#else
+#error
+#endif
+ &BytesTransferred);
+
+ CloseHandle(Handle);
+
+ return Result;
+}
+
+static NTSTATUS CreateHelperProcess(PWSTR FileName, ULONG Timeout, PHANDLE PProcess)
+{
+ HANDLE Event;
+ SECURITY_ATTRIBUTES EventAttributes;
+ WCHAR CommandLine[MAX_PATH + 64];
+ STARTUPINFOW StartupInfo;
+ PROCESS_INFORMATION ProcessInfo;
+ DWORD WaitResult;
+ NTSTATUS Result;
+
+ memset(&EventAttributes, 0, sizeof EventAttributes);
+ EventAttributes.nLength = sizeof EventAttributes;
+ EventAttributes.bInheritHandle = TRUE;
+
+ Event = CreateEventW(&EventAttributes, TRUE, FALSE, 0);
+ if (0 == Event)
+ return FspNtStatusFromWin32(GetLastError());
+
+ StringCbPrintfW(CommandLine, sizeof CommandLine, L"\"%s\" %lx %lx",
+ FileName, (ULONG)(UINT_PTR)Event, Timeout);
+
+ memset(&StartupInfo, 0, sizeof StartupInfo);
+ StartupInfo.cb = sizeof StartupInfo;
+
+ // !!!: need hook
+ if (!CreateProcessW(FileName, CommandLine, 0, 0, TRUE, 0, 0, 0, &StartupInfo, &ProcessInfo))
+ {
+ Result = FspNtStatusFromWin32(GetLastError());
+ CloseHandle(Event);
+ return Result;
+ }
+
+ WaitResult = WaitForSingleObject(Event, 3000);
+ if (WaitResult == WAIT_FAILED)
+ Result = FspNtStatusFromWin32(GetLastError());
+ else if (WaitResult == WAIT_TIMEOUT)
+ Result = STATUS_UNSUCCESSFUL;
+ else
+ Result = STATUS_SUCCESS;
+
+ CloseHandle(Event);
+ CloseHandle(ProcessInfo.hThread);
+
+ if (!NT_SUCCESS(Result))
+ CloseHandle(ProcessInfo.hProcess);
+ else
+ *PProcess = ProcessInfo.hProcess;
+
+ return Result;
+}
+
+static VOID ExecHelper(PWSTR FileName, ULONG Timeout, PHANDLE PProcess)
+{
+ NTSTATUS Result;
+
+ Result = ExtractHelperProgram(FileName);
+ ASSERT(NT_SUCCESS(Result));
+
+ Result = CreateHelperProcess(FileName, Timeout, PProcess);
+ ASSERT(NT_SUCCESS(Result));
+}
+
+static VOID WaitHelper(HANDLE Process, ULONG Timeout)
+{
+ DWORD ExitCode;
+
+ ASSERT(WAIT_OBJECT_0 == WaitForSingleObject(Process, Timeout + 1000));
+
+ ASSERT(GetExitCodeProcess(Process, &ExitCode));
+ ASSERT(0 == ExitCode);
+
+ ASSERT(CloseHandle(Process));
+}
+
+static void exec_dotest(ULONG Flags, PWSTR Prefix, ULONG FileInfoTimeout)
+{
+ void *memfs = memfs_start_ex(Flags, FileInfoTimeout);
+
+ WCHAR FilePath[MAX_PATH];
+ HANDLE Process;
+
+ StringCbPrintfW(FilePath, sizeof FilePath, L"%s%s\\helper.exe",
+ Prefix ? L"" : L"\\\\?\\GLOBALROOT", Prefix ? Prefix : memfs_volumename(memfs));
+
+ ExecHelper(FilePath, 0, &Process);
+ WaitHelper(Process, 0);
+
+ ASSERT(DeleteFileW(FilePath));
+
+ memfs_stop(memfs);
+}
+
+static void exec_test(void)
+{
+ if (NtfsTests)
+ {
+ WCHAR DirBuf[MAX_PATH];
+ GetTestDirectory(DirBuf);
+ exec_dotest(-1, DirBuf, 0);
+ }
+ if (WinFspDiskTests)
+ {
+ exec_dotest(MemfsDisk, 0, 0);
+ exec_dotest(MemfsDisk, 0, 1000);
+ }
+ if (WinFspNetTests)
+ {
+ exec_dotest(MemfsNet, L"\\\\memfs\\share", 0);
+ exec_dotest(MemfsNet, L"\\\\memfs\\share", 1000);
+ }
+}
+
+static void exec_delete_dotest(ULONG Flags, PWSTR Prefix, ULONG FileInfoTimeout)
+{
+ void *memfs = memfs_start_ex(Flags, FileInfoTimeout);
+
+ WCHAR FilePath[MAX_PATH];
+ HANDLE Process;
+
+ StringCbPrintfW(FilePath, sizeof FilePath, L"%s%s\\helper.exe",
+ Prefix ? L"" : L"\\\\?\\GLOBALROOT", Prefix ? Prefix : memfs_volumename(memfs));
+
+ ExecHelper(FilePath, 1000, &Process);
+ ASSERT(!DeleteFileW(FilePath));
+ ASSERT(ERROR_ACCESS_DENIED == GetLastError());
+ WaitHelper(Process, 1000);
+
+ ASSERT(DeleteFileW(FilePath));
+
+ memfs_stop(memfs);
+}
+
+static void exec_delete_test(void)
+{
+ if (NtfsTests)
+ {
+ WCHAR DirBuf[MAX_PATH];
+ GetTestDirectory(DirBuf);
+ exec_delete_dotest(-1, DirBuf, 0);
+ }
+ if (WinFspDiskTests)
+ {
+ exec_delete_dotest(MemfsDisk, 0, 0);
+ exec_delete_dotest(MemfsDisk, 0, 1000);
+ }
+ if (WinFspNetTests)
+ {
+ exec_delete_dotest(MemfsNet, L"\\\\memfs\\share", 0);
+ exec_delete_dotest(MemfsNet, L"\\\\memfs\\share", 1000);
+ }
+}
+
+void exec_tests(void)
+{
+ TEST(exec_test);
+ TEST(exec_delete_test);
+}
diff --git a/tst/winfsp-tests/helper/build.bat b/tst/winfsp-tests/helper/build.bat
new file mode 100644
index 00000000..d0d0a287
--- /dev/null
+++ b/tst/winfsp-tests/helper/build.bat
@@ -0,0 +1,13 @@
+@echo off
+
+setlocal
+
+cd %~dp0
+
+call "%VS140COMNTOOLS%\..\..\VC\vcvarsall.bat" x64
+cl /Fewinfsp-tests-helper-x64.exe /MT /W2 winfsp-tests-helper.c kernel32.lib shell32.lib /link /subsystem:console /nodefaultlib
+del *.obj
+
+call "%VS140COMNTOOLS%\..\..\VC\vcvarsall.bat" x86
+cl /Fewinfsp-tests-helper-x86.exe /MT /W2 winfsp-tests-helper.c kernel32.lib shell32.lib /link /subsystem:console
+del *.obj
diff --git a/tst/winfsp-tests/helper/winfsp-tests-helper-x64.exe b/tst/winfsp-tests/helper/winfsp-tests-helper-x64.exe
new file mode 100644
index 00000000..b512fdf4
Binary files /dev/null and b/tst/winfsp-tests/helper/winfsp-tests-helper-x64.exe differ
diff --git a/tst/winfsp-tests/helper/winfsp-tests-helper-x86.exe b/tst/winfsp-tests/helper/winfsp-tests-helper-x86.exe
new file mode 100644
index 00000000..45805f49
Binary files /dev/null and b/tst/winfsp-tests/helper/winfsp-tests-helper-x86.exe differ
diff --git a/tst/winfsp-tests/helper/winfsp-tests-helper.c b/tst/winfsp-tests/helper/winfsp-tests-helper.c
new file mode 100644
index 00000000..753ae66f
--- /dev/null
+++ b/tst/winfsp-tests/helper/winfsp-tests-helper.c
@@ -0,0 +1,104 @@
+/**
+ * @file winfsp-tests-helper.c
+ *
+ * @copyright 2015-2017 Bill Zissimopoulos
+ */
+/*
+ * This file is part of WinFsp.
+ *
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License version 3 as published by the Free Software
+ * Foundation.
+ *
+ * Licensees holding a valid commercial license may use this file in
+ * accordance with the commercial license agreement provided with the
+ * software.
+ */
+
+#include
+
+/* based on src/dll/fuse/fuse_opt.c */
+static long long wcstoint(const wchar_t *p, int base, int is_signed)
+{
+ long long v;
+ int maxdig, maxalp, sign = +1;
+
+ if (is_signed)
+ {
+ if ('+' == *p)
+ p++;
+ else if ('-' == *p)
+ p++, sign = -1;
+ }
+
+ if (0 == base)
+ {
+ if ('0' == *p)
+ {
+ p++;
+ if ('x' == *p || 'X' == *p)
+ {
+ p++;
+ base = 16;
+ }
+ else
+ base = 8;
+ }
+ else
+ {
+ base = 10;
+ }
+ }
+
+ maxdig = 10 < base ? '9' : (base - 1) + '0';
+ maxalp = 10 < base ? (base - 1 - 10) + 'a' : 0;
+
+ for (v = 0; *p; p++)
+ {
+ int c = *p;
+
+ if ('0' <= c && c <= maxdig)
+ v = base * v + (c - '0');
+ else
+ {
+ c |= 0x20;
+ if ('a' <= c && c <= maxalp)
+ v = base * v + (c - 'a') + 10;
+ else
+ break;
+ }
+ }
+
+ return sign * v;
+}
+
+int wmain(int argc, wchar_t **argv)
+{
+ HANDLE Event;
+ ULONG Timeout;
+
+ if (argc != 3)
+ return 1;
+
+ Event = (HANDLE)(UINT_PTR)wcstoint(argv[1], 16, 0);
+ Timeout = wcstoint(argv[2], 16, 0);
+
+ SetEvent(Event);
+ CloseHandle(Event);
+
+ Sleep(Timeout);
+
+ return 0;
+}
+
+void wmainCRTStartup(void)
+{
+ DWORD Argc;
+ PWSTR *Argv;
+
+ Argv = CommandLineToArgvW(GetCommandLineW(), &Argc);
+ if (0 == Argv)
+ ExitProcess(GetLastError());
+
+ ExitProcess(wmain(Argc, Argv));
+}
diff --git a/tst/winfsp-tests/helper/winfsp-tests-helper.rc b/tst/winfsp-tests/helper/winfsp-tests-helper.rc
new file mode 100644
index 00000000..afe77f60
--- /dev/null
+++ b/tst/winfsp-tests/helper/winfsp-tests-helper.rc
@@ -0,0 +1,2 @@
+winfsp-tests-helper-x64.exe RCDATA "winfsp-tests-helper-x64.exe"
+winfsp-tests-helper-x86.exe RCDATA "winfsp-tests-helper-x86.exe"
diff --git a/tst/winfsp-tests/hooks.c b/tst/winfsp-tests/hooks.c
index ec32c59c..ed736999 100644
--- a/tst/winfsp-tests/hooks.c
+++ b/tst/winfsp-tests/hooks.c
@@ -427,3 +427,35 @@ BOOL WINAPI HookSetCurrentDirectoryW(
MaybeAdjustTraversePrivilege(TRUE);
return Success;
}
+
+BOOL WINAPI HookCreateProcessW(
+ LPCWSTR lpApplicationName,
+ LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation)
+{
+ WCHAR FileNameBuf[FILENAMEBUF_SIZE];
+ BOOL Success;
+
+ PrepareFileName(lpApplicationName, FileNameBuf);
+
+ MaybeAdjustTraversePrivilege(FALSE);
+ Success = CreateProcessW(FileNameBuf,
+ lpCommandLine, /* we should probably change this as well */
+ lpProcessAttributes,
+ lpThreadAttributes,
+ bInheritHandles,
+ dwCreationFlags,
+ lpEnvironment,
+ lpCurrentDirectory,
+ lpStartupInfo,
+ lpProcessInformation);
+ MaybeAdjustTraversePrivilege(TRUE);
+ return Success;
+}
diff --git a/tst/winfsp-tests/winfsp-tests.c b/tst/winfsp-tests/winfsp-tests.c
index 1c296089..b5053caa 100644
--- a/tst/winfsp-tests/winfsp-tests.c
+++ b/tst/winfsp-tests/winfsp-tests.c
@@ -194,6 +194,7 @@ int main(int argc, char *argv[])
TESTSUITE(flush_tests);
TESTSUITE(lock_tests);
TESTSUITE(dirctl_tests);
+ TESTSUITE(exec_tests);
TESTSUITE(reparse_tests);
TESTSUITE(stream_tests);
TESTSUITE(oplock_tests);
diff --git a/tst/winfsp-tests/winfsp-tests.h b/tst/winfsp-tests/winfsp-tests.h
index fe5e1ad9..67aa7cdc 100644
--- a/tst/winfsp-tests/winfsp-tests.h
+++ b/tst/winfsp-tests/winfsp-tests.h
@@ -85,6 +85,17 @@ static inline BOOL RealSetCurrentDirectoryW(
{
return SetCurrentDirectoryW(lpPathName);
}
+BOOL WINAPI HookCreateProcessW(
+ LPCWSTR lpApplicationName,
+ LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation);
#if !defined(WINFSP_TESTS_NO_HOOKS)
#define CreateFileW HookCreateFileW
#define CloseHandle HookCloseHandle
@@ -99,6 +110,7 @@ static inline BOOL RealSetCurrentDirectoryW(
#define GetVolumeInformationW HookGetVolumeInformationW
#define SetVolumeLabelW HookSetVolumeLabelW
#define SetCurrentDirectoryW HookSetCurrentDirectoryW
+#define CreateProcessW HookCreateProcessW
#endif
HANDLE WINAPI ResilientCreateFileW(