diff --git a/.gitignore b/.gitignore index 3358e12..18d42f2 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ /source/P4VFS.Driver/*smv* /source/P4VFS.Driver/Scripts/*.txt /source/P4VFS.Driver/Scripts/*.etl +/source/P4VFS.Driver/Scripts/*.log **/obj/ **/.vs/ **/.vscode/ diff --git a/external/P4VFS/P4VFS.Module.cs b/external/P4VFS/P4VFS.Module.cs index 6fe4ae8..88d3489 100644 --- a/external/P4VFS/P4VFS.Module.cs +++ b/external/P4VFS/P4VFS.Module.cs @@ -11,7 +11,7 @@ namespace Microsoft.P4VFS.External { public class P4vfsModule : Module { - private const string P4VFS_SIGNED_VERSION = "1.28.0.0"; + private const string P4VFS_SIGNED_VERSION = "1.28.3.0"; private const string P4VFS_SIGNED_ARTIFACTS_URL = "https://github.com/microsoft/p4vfs/releases/download"; public override string Name diff --git a/source/P4VFS.Console/P4VFS.Notes.txt b/source/P4VFS.Console/P4VFS.Notes.txt index 4991160..ce1371c 100644 --- a/source/P4VFS.Console/P4VFS.Notes.txt +++ b/source/P4VFS.Console/P4VFS.Notes.txt @@ -1,5 +1,33 @@ Microsoft P4VFS Release Notes +Version [1.29.0.0] +* Fixing virtual file hydration to be excluded from system directory notifications. + This avoids unecessary FILE_NOTIFY_INFORMATION or DirectoryWatcher events indicating + a virtual file has been modified, when it's only been hydrated as-expected on read. + This technique is common across filter drivers. This is only supported for STREAM + populate method (default). New unit test TestReadDirectoryChanges for verification. +* Additional unit test varible P4VFS_TEST_SERVER_ROOT for overriding the root directory + for the test servers. This better facilitates running all tests remotely on a local + virtual machine with including kernel mode debugging. Includes support for + RestartPerforceServer script to quick spin-up of existing server on override drive +* Reconfiguring our 32 bit driver allocation pool tags to 4-byte string "Pvs*" +* Fixing bug recieving large messages to/from the P4VFS.Service. This was typically + encountered when using global "-x " with a file containing many arguments. + There's a new ReflectPackage message which is now used by SocketModelCommunicationTest + to stress test a wide range of message sizes +* Fixing unit tests deterministic random byte generation to more randomly include + zero as randomly as possible +* Support for additional registry keys to set defined in the setup application configuration + file (typically named P4VFS.Setup.xml residing in the same folder as the installer). + This is helpfull for setting possibly setting a version key to indicate what settings + template was also installed. + +Version [1.28.4.0] +* Addition of optional service commandline argument to launch with debugger attached. + This is done as needed from admin command prompt "sc.exe start P4VFS.Service --break" +* Moving service logging system to initialize before ExtensionsInterop. This avoids + missed logging from service communication initialization. + Version [1.28.3.0] * Fixing service initialization error if registry ImagePath value contains quotes under key "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\P4VFS.Service". diff --git a/source/P4VFS.Core/Include/FileOperations.h b/source/P4VFS.Core/Include/FileOperations.h index d45c981..44dfb54 100644 --- a/source/P4VFS.Core/Include/FileOperations.h +++ b/source/P4VFS.Core/Include/FileOperations.h @@ -10,6 +10,17 @@ namespace Microsoft { namespace P4VFS { namespace FileOperations { + HRESULT + RestrictFileTimeChange( + HANDLE hFile + ); + + HRESULT + SetFileAttributesOnHandle( + HANDLE hFile, + DWORD dwFileAttributes + ); + bool SleepAndVerifyFileExists( const WCHAR* filePath, diff --git a/source/P4VFS.Core/Source/FileOperations.cpp b/source/P4VFS.Core/Source/FileOperations.cpp index 4ccce78..341eca8 100644 --- a/source/P4VFS.Core/Source/FileOperations.cpp +++ b/source/P4VFS.Core/Source/FileOperations.cpp @@ -71,6 +71,23 @@ RestrictFileTimeChange( return S_OK; } +HRESULT +SetFileAttributesOnHandle( + HANDLE hFile, + DWORD dwFileAttributes + ) +{ + FILE_BASIC_INFO basicInfo = {0}; + basicInfo.FileAttributes = dwFileAttributes; + + if (!SetFileInformationByHandle(hFile, FileBasicInfo, &basicInfo, sizeof(basicInfo))) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + return S_OK; +} + bool SleepAndVerifyFileExists( const WCHAR* filePath, @@ -1116,12 +1133,6 @@ PopulateFileByStream( return hr; } - if (SetFileAttributes(fileToPopulate.c_str(), dstFileAttributes & ~FILE_ATTRIBUTE_READONLY) == FALSE) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - return hr; - } - FileCore::FileStream* fileToPopulateFrom = srcFileStream; if (fileToPopulateFrom == nullptr || fileToPopulateFrom->CanRead() == false) { @@ -1170,17 +1181,16 @@ PopulateFileByStream( hr = RemoveSparseFileSizeOnHandle(dstFile.Handle()); if (FAILED(hr)) { - return hr; + return hr; } - dstFile.Close(); - - if (!SetFileAttributes(fileToPopulate.c_str(), dstFileAttributes & ~(FILE_ATTRIBUTE_OFFLINE))) + hr = SetFileAttributesOnHandle(dstFile.Handle(), dstFileAttributes & ~(FILE_ATTRIBUTE_OFFLINE)); + if (FAILED(hr)) { - hr = HRESULT_FROM_WIN32(GetLastError()); return hr; } + dstFile.Close(); return S_OK; } diff --git a/source/P4VFS.Core/Source/Pch.h b/source/P4VFS.Core/Source/Pch.h index 70a8bdd..3a1e384 100644 --- a/source/P4VFS.Core/Source/Pch.h +++ b/source/P4VFS.Core/Source/Pch.h @@ -2,14 +2,15 @@ // Licensed under the MIT license. #pragma once -#pragma warning(disable: 4127) // conditional expression is constant -#pragma warning(disable: 4100) // unreferenced formal parameter -#pragma warning(disable: 4239) // Nonstandard extension used : 'token' : conversion from 'type' to 'type' -#pragma warning(disable: 4458) // Declaration of 'identifier' hides class member +#pragma warning(disable: 4127) // conditional expression is constant +#pragma warning(disable: 4100) // unreferenced formal parameter +#pragma warning(disable: 4239) // Nonstandard extension used : 'token' : conversion from 'type' to 'type' +#pragma warning(disable: 4458) // Declaration of 'identifier' hides class member #pragma warning(disable: 6031) // Return value ignored +#pragma warning(disable: 6101) // A successful path through the function doesn't set the _Out_ annotated parameter. #pragma warning(disable: 26444) // Avoid unnamed objects with custom construction and destruction #pragma warning(disable: 26451) // Arithmetic overflow: Using operator '%operator%' on a %size1% byte value and then casting the result to a %size2% byte value -#pragma warning(disable: 26495) // Variable '%variable%' is uninitialized. Always initialize a member variable +#pragma warning(disable: 26495) // Variable '%variable%' is uninitialized. Always initialize a member variable #pragma warning(disable: 26812) // Prefer 'enum class' over 'enum' #pragma warning(disable: 26813) // Use 'bitwise and' to check if a flag is set. diff --git a/source/P4VFS.Core/Tests/TestDriver.cpp b/source/P4VFS.Core/Tests/TestDriver.cpp index 90385db..7391e73 100644 --- a/source/P4VFS.Core/Tests/TestDriver.cpp +++ b/source/P4VFS.Core/Tests/TestDriver.cpp @@ -20,6 +20,7 @@ typedef INT FLT_FILE_NAME_OPTIONS; typedef VOID* PACCESS_STATE; typedef INT FILE_INFORMATION_CLASS; typedef HANDLE PEPROCESS; +typedef VOID* PESILO; typedef struct _UNICODE_STRING { USHORT Length; @@ -60,7 +61,7 @@ typedef struct _IO_DRIVER_CREATE_CONTEXT PVOID DeviceObjectHint; PVOID TxnParameters; #if (NTDDI_VERSION >= NTDDI_WIN10_RS1) - PVOID SiloContext; + PESILO SiloContext; #endif } IO_DRIVER_CREATE_CONTEXT, *PIO_DRIVER_CREATE_CONTEXT; @@ -218,6 +219,11 @@ typedef struct _FILE_STANDARD_INFORMATION_EX { BOOLEAN MetadataAttribute; } FILE_STANDARD_INFORMATION_EX, *PFILE_STANDARD_INFORMATION_EX; +typedef struct _FILE_ID_INFORMATION { + ULONGLONG VolumeSerialNumber; + FILE_ID_128 FileId; +} FILE_ID_INFORMATION, *PFILE_ID_INFORMATION; + typedef enum _POOL_TYPE { NonPagedPoolNx = 512, } POOL_TYPE; @@ -227,25 +233,28 @@ typedef enum _POOL_TYPE { #define P4vfsTraceInfo(...) __noop #define PAGED_CODE() __noop #define FlagOn(_F,_SF) ((_F) & (_SF)) +#define ClearFlag(_F,_SF) ((_F) &= ~(_SF)) #define ObDereferenceObject(f) __noop #define PsGetCurrentProcess() GetCurrentProcess() #define PsGetCurrentProcessId() GetCurrentProcessId() #define PsGetCurrentThreadId() GetCurrentThreadId() +#define PsGetHostSilo() NULL #define min(a,b) (((a) < (b)) ? (a) : (b)) #define max(a,b) (((a) > (b)) ? (a) : (b)) #define RtlUShortAdd UShortAdd #define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) -#define STATUS_DATA_ERROR ((NTSTATUS)0xC000003EL) -#define STATUS_INSUFFICIENT_RESOURCES ((NTSTATUS)0xC000009AL) #define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L) #define STATUS_BUFFER_OVERFLOW ((NTSTATUS)0x80000005L) +#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) #define STATUS_PORT_DISCONNECTED ((NTSTATUS)0xC0000037L) -#define STATUS_MEMORY_NOT_ALLOCATED ((NTSTATUS)0xC00000A0L) +#define STATUS_DATA_ERROR ((NTSTATUS)0xC000003EL) #define STATUS_INVALID_PORT_HANDLE ((NTSTATUS)0xC0000042L) +#define STATUS_INSUFFICIENT_RESOURCES ((NTSTATUS)0xC000009AL) +#define STATUS_MEMORY_NOT_ALLOCATED ((NTSTATUS)0xC00000A0L) +#define STATUS_NOT_ALL_ASSIGNED ((NTSTATUS)0x00000106L) #define STATUS_INVALID_BUFFER_SIZE ((NTSTATUS)0xC0000206L) -#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) #define IO_IGNORE_SHARE_ACCESS_CHECK 0x0800 #define FILE_SHARE_VALID_FLAGS 0x00000007 @@ -258,9 +267,11 @@ typedef enum _POOL_TYPE { #define FileBasicInformation 4 #define FileStandardInformation 5 #define FileInternalInformation 6 +#define FileIdInformation 59 #define FILE_OPEN 0x00000001 #define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020 +#define FILE_NON_DIRECTORY_FILE 0x00000040 #define FILE_NO_INTERMEDIATE_BUFFERING 0x00000008 #define FILE_OPEN_REPARSE_POINT 0x00200000 #define FILE_OPEN_BY_FILE_ID 0x00002000 @@ -280,8 +291,11 @@ std::function FltObjectDereference; std::function FltClose; std::function FltQueryInformationFile; +std::function FltSetInformationFile; std::function FltGetVolumeFromInstance; std::function FltGetVolumeName; +std::function FltGetVolumeFromFileObject; +std::function FltGetVolumeInstanceFromName; std::function ExAllocatePoolZero; std::function ExFreePoolWithTag; @@ -431,7 +445,11 @@ static void InternalTestDriverReset(const TestContext& context) FltObjectDereference = P4VFS_DEFAULT_FUNCTION_NTSTATUS(); FltClose = P4VFS_DEFAULT_FUNCTION_NTSTATUS(); FltQueryInformationFile = P4VFS_DEFAULT_FUNCTION_NTSTATUS(); + FltSetInformationFile = P4VFS_DEFAULT_FUNCTION_NTSTATUS(); FltGetVolumeFromInstance = P4VFS_DEFAULT_FUNCTION_NTSTATUS(); + FltGetVolumeName = P4VFS_DEFAULT_FUNCTION_NTSTATUS(); + FltGetVolumeFromFileObject = P4VFS_DEFAULT_FUNCTION_NTSTATUS(); + FltGetVolumeInstanceFromName = P4VFS_DEFAULT_FUNCTION_NTSTATUS(); ExAllocatePoolZero = [](POOL_TYPE, SIZE_T s, ULONG) -> PVOID { return GAlloc(s); }; ExFreePoolWithTag = [](PVOID p, ULONG) -> VOID { GFree(p); }; diff --git a/source/P4VFS.Core/Tests/TestFileSystem.cpp b/source/P4VFS.Core/Tests/TestFileSystem.cpp index 7257cd1..1d67beb 100644 --- a/source/P4VFS.Core/Tests/TestFileSystem.cpp +++ b/source/P4VFS.Core/Tests/TestFileSystem.cpp @@ -266,3 +266,170 @@ void TestDevDriveAttachPolicy(const TestContext& context) SetFsutilAttachPolicy(prevNames); Assert(GetFsutilAttachPolicy() == prevNames); } + +void TestReadDirectoryChanges(const TestContext& context) +{ + TestUtilities::WorkspaceReset(context); + + DepotClient client = FDepotClient::New(context.m_FileContext); + Assert(client->Connect(context.GetDepotConfig())); + + Assert(client->Connection().get() != nullptr); + const String clientRootFolder = StringInfo::ToWide(client->Connection()->Root()); + Assert(FileInfo::IsDirectory(clientRootFolder.c_str()) == false); + const String clientSearchFolder = StringInfo::Format(TEXT("%s\\depot\\gears1\\Development"), clientRootFolder.c_str()); + + FDepotSyncOptions syncOptions; + syncOptions.m_SyncFlags = DepotSyncFlags::Quiet; + syncOptions.m_Files.push_back(StringInfo::Format("%s\\...", CSTR_WTOA(clientSearchFolder))); + DepotSyncResult syncResult = DepotOperations::SyncVirtual(client, syncOptions); + Assert(syncResult.get() != nullptr); + Assert(syncResult->m_Status == DepotSyncStatus::Success); + Assert(FileInfo::IsDirectory(clientSearchFolder.c_str())); + + struct FReadChangesThread + { + struct FChange + { + String m_File; + DWORD m_Action; + bool operator==(const FChange& c) const { return c.m_File == m_File && c.m_Action == m_Action; } + }; + + struct FData + { + String m_FolderPath; + HANDLE m_hCancelationEvent; + HANDLE m_hBeginReadEvent; + Array m_Changes; + }; + + static DWORD Execute(void* threadData) + { + FData* data = reinterpret_cast(threadData); + Assert(data != nullptr); + Assert(data->m_hCancelationEvent != NULL); + + AutoHandle hFolder = CreateFileW(data->m_FolderPath.c_str(), + FILE_LIST_DIRECTORY, + FILE_SHARE_READ | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, + NULL); + Assert(hFolder.IsValid()); + + while (WaitForSingleObject(data->m_hCancelationEvent, 0) != WAIT_OBJECT_0) + { + // Watch for notifications on all flags except attributes changes. It's common for AV filter + // drivers (mssecflt) to set kernel extended attributes (FsRtlSetKernelEaFile) which should + // we do not expect p4vfsflt to prevent. + const DWORD changesNotifyFlags = + FILE_NOTIFY_CHANGE_CREATION | + FILE_NOTIFY_CHANGE_LAST_WRITE | + FILE_NOTIFY_CHANGE_SIZE | + //FILE_NOTIFY_CHANGE_ATTRIBUTES | + FILE_NOTIFY_CHANGE_DIR_NAME | + FILE_NOTIFY_CHANGE_FILE_NAME; + + BYTE changesBuffer[8*1024] = {0}; + OVERLAPPED changesOverlapped = {}; + Assert(SetEvent(data->m_hBeginReadEvent)); + + if (ReadDirectoryChangesW(hFolder.Handle(), changesBuffer, sizeof(changesBuffer), true, changesNotifyFlags, NULL, &changesOverlapped, NULL) == FALSE) + { + AssertMsg(false, TEXT("ReadDirectoryChanges failed")); + return 1; + } + + // Wait indefinitely for any directory changes or a cancellation + const HANDLE handles[] = { hFolder.Handle(), data->m_hCancelationEvent }; + const DWORD waitResult = WaitForMultipleObjects(_countof(handles), handles, FALSE, INFINITE); + + if (waitResult == WAIT_OBJECT_0) + { + for (const BYTE* changesPos = changesBuffer;;) + { + if (changesPos >= changesBuffer+sizeof(changesBuffer)-sizeof(FILE_NOTIFY_INFORMATION)) + { + AssertMsg(false, TEXT("ReadDirectoryChanges changesPos overflow")); + return 1; + } + + const FILE_NOTIFY_INFORMATION* fni = reinterpret_cast(changesPos); + if (reinterpret_cast(fni->FileName+fni->FileNameLength) > changesBuffer+sizeof(changesBuffer)) + { + AssertMsg(false, TEXT("ReadDirectoryChanges FileName overflow")); + return 1; + } + + data->m_Changes.push_back(FChange{ String(fni->FileName, fni->FileNameLength/sizeof(WCHAR)).c_str(), fni->Action }); + if (fni->NextEntryOffset == 0) + { + break; + } + + changesPos += fni->NextEntryOffset; + } + } + else if (waitResult == WAIT_OBJECT_0+1) + { + // Expected thread cancellation + } + else + { + AssertMsg(false, TEXT("Unexpected wait result %d"), waitResult); + return 1; + } + } + + return 0; + } + }; + + // Start the thread to watch for changes under clientSearchFolder + AutoHandle hThreadCancelation = CreateEvent(NULL, TRUE, FALSE, NULL); + AutoHandle hThreadBeginReadEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + FReadChangesThread::FData threadData{ clientSearchFolder, hThreadCancelation.Handle(), hThreadBeginReadEvent.Handle() }; + AutoHandle hThread = CreateThread(NULL, 0, FReadChangesThread::Execute, &threadData, 0, NULL); + + // Wait for the thread to begin reading for changes, plus a little time for it to wait + Assert(WaitForSingleObject(hThreadBeginReadEvent.Handle(), 5*60*1000) == WAIT_OBJECT_0); + Sleep(1000); + + // Reconcile a single readonly file ... there should be no notifications + const String immutableClientRelativeFile = TEXT("Src\\Engine\\Src\\AnimationUtils.cpp"); + const String immutableClientFile = StringInfo::Format(TEXT("%s\\%s"), clientSearchFolder.c_str(), immutableClientRelativeFile.c_str()); + Assert(context.m_IsPlaceholderFile(immutableClientFile)); + Assert(context.m_ReconcilePreviewAny(immutableClientFile) == false); + Assert(context.m_IsPlaceholderFile(immutableClientFile) == false); + + // Open a placeholder file without hydrating, and write to it to trigger a notification + const String mutableClientRelativeFile = TEXT("Src\\Core\\Src\\BitArray.cpp"); + const String mutableClientFile = StringInfo::Format(TEXT("%s\\%s"), clientSearchFolder.c_str(), mutableClientRelativeFile.c_str()); + Assert(context.m_IsPlaceholderFile(mutableClientFile)); + Assert(FileInfo::SetReadOnly(mutableClientFile.c_str(), false)); + AutoHandle hMutableClientFile = CreateFile(mutableClientFile.c_str(), FILE_GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + Assert(hMutableClientFile.IsValid()); + Assert(WriteFile(hMutableClientFile.Handle(), CSTR_WTOA(mutableClientRelativeFile), static_cast(mutableClientRelativeFile.length()), nullptr, nullptr)); + hMutableClientFile.Close(); + + // Gracefully cancel the thread now that we're done testing for changes + SetEvent(hThreadCancelation.Handle()); + Assert(WaitForSingleObject(hThread.Handle(), 5*60*1000) == WAIT_OBJECT_0); + DWORD dwThreadExitCode = 1; + Assert(GetExitCodeThread(hThread.Handle(), &dwThreadExitCode)); + Assert(dwThreadExitCode == 0); + + // Gather the unique list changes, ignoring directories + Array uniqueChanges; + Algo::AppendIf(uniqueChanges, threadData.m_Changes, [&](const FReadChangesThread::FChange& c) -> bool + { + return !Algo::Contains(uniqueChanges, c) && !FileInfo::IsDirectory(StringInfo::Format(TEXT("%s\\%s"), clientSearchFolder.c_str(), c.m_File.c_str()).c_str()); + }); + + // Confirm that only the notifications that we expected were recorded + Assert(uniqueChanges.size() == 1); + Assert(uniqueChanges[0].m_File == mutableClientRelativeFile); +} + diff --git a/source/P4VFS.Core/Tests/TestRegistry.cpp b/source/P4VFS.Core/Tests/TestRegistry.cpp index 602dc65..87b14eb 100644 --- a/source/P4VFS.Core/Tests/TestRegistry.cpp +++ b/source/P4VFS.Core/Tests/TestRegistry.cpp @@ -19,6 +19,7 @@ P4VFS_REGISTER_TEST( TestResolveFileResidency, 10200 ) P4VFS_REGISTER_TEST( TestRequireFilterOpLock, 10201 ) P4VFS_REGISTER_TEST( TestFileAlternateStream, 10202 ) P4VFS_REGISTER_TEST( TestDevDriveAttachPolicy, 10203 ) +P4VFS_REGISTER_TEST( TestReadDirectoryChanges, 10204 ) // TestStringInfo P4VFS_REGISTER_TEST( TestStringInfoHash, 10300 ) diff --git a/source/P4VFS.Driver/Include/DriverCore.h b/source/P4VFS.Driver/Include/DriverCore.h index 5cfff12..e0e6244 100644 --- a/source/P4VFS.Driver/Include/DriverCore.h +++ b/source/P4VFS.Driver/Include/DriverCore.h @@ -6,13 +6,13 @@ #define P4VFS_RESOLVE_FILE_FLAG_NONE 0 #define P4VFS_RESOLVE_FILE_FLAG_IGNORE_TAG 1 -#define P4VFS_REPARSE_ACTION_ALLOC_TAG 'BACA' -#define P4VFS_SERVICE_MSG_ALLOC_TAG 'BACR' -#define P4VFS_REPLY_MSG_ALLOC_TAG 'BACY' -#define P4VFS_REPARSE_BUFFER_ALLOC_TAG 'BACB' -#define P4VFS_FILE_NAME_ALLOC_TAG 'BAFN' -#define P4VFS_SERVICE_PORT_HANDLE_ALLOC_TAG 'BASH' -#define P4VFS_CONTROL_PORT_HANDLE_ALLOC_TAG 'BACH' +#define P4VFS_REPARSE_ACTION_ALLOC_TAG 'AsvP' +#define P4VFS_SERVICE_MSG_ALLOC_TAG 'RsvP' +#define P4VFS_REPLY_MSG_ALLOC_TAG 'YsvP' +#define P4VFS_REPARSE_BUFFER_ALLOC_TAG 'BsvP' +#define P4VFS_FILE_NAME_ALLOC_TAG 'NsvP' +#define P4VFS_SERVICE_PORT_HANDLE_ALLOC_TAG 'SsvP' +#define P4VFS_CONTROL_PORT_HANDLE_ALLOC_TAG 'CsvP' NTSTATUS P4vfsUserModeExecuteDrvRequest( @@ -113,12 +113,23 @@ P4vfsToUnicodeString( _Out_ UNICODE_STRING* pDstString ); +NTSTATUS +P4vfsSetFileWritable( + _In_ PFLT_INSTANCE pFltInstance, + _In_ PUNICODE_STRING pFileIdPath + ); + +NTSTATUS +P4vfsGetFileIdByFileName( + _In_ PUNICODE_STRING pFileName, + _Out_ PUNICODE_STRING pOutFileIdPath, + _Outptr_opt_ PFLT_INSTANCE* ppFltInstance + ); + NTSTATUS P4vfsOpenReparsePoint( - _In_opt_ PFLT_INSTANCE pFltInstance, _In_ PUNICODE_STRING pFileName, _In_ ACCESS_MASK desiredAccess, - _In_ ULONG objectAttributes, _Out_ PHANDLE pTargetHandle, _Outptr_ PFILE_OBJECT* ppTargetFileObject ); diff --git a/source/P4VFS.Driver/Include/DriverVersion.h b/source/P4VFS.Driver/Include/DriverVersion.h index 61b7acb..85748d7 100644 --- a/source/P4VFS.Driver/Include/DriverVersion.h +++ b/source/P4VFS.Driver/Include/DriverVersion.h @@ -4,7 +4,7 @@ #define P4VFS_VER_MAJOR 1 // Increment this number almost never #define P4VFS_VER_MINOR 28 // Increment this number whenever the driver changes -#define P4VFS_VER_BUILD 3 // Increment this number when a major user mode change has been made +#define P4VFS_VER_BUILD 5 // Increment this number when a major user mode change has been made #define P4VFS_VER_REVISION 0 // Increment this number when we rebuild with any change #define P4VFS_VER_STRINGIZE_EX(v) L#v diff --git a/source/P4VFS.Driver/P4VFS.Driver.vcxproj b/source/P4VFS.Driver/P4VFS.Driver.vcxproj index 23ad7cd..f64dafe 100644 --- a/source/P4VFS.Driver/P4VFS.Driver.vcxproj +++ b/source/P4VFS.Driver/P4VFS.Driver.vcxproj @@ -61,12 +61,14 @@ $(P4VFSDriverBuildDir)\$(Configuration)\ $(P4VFSDriverBuildDir)\$(Configuration)\ $(P4VFSDriverTargetName) + true DbgengKernelDebugger $(P4VFSDriverBuildDir)\$(Configuration)\ $(P4VFSDriverBuildDir)\$(Configuration)\ $(P4VFSDriverTargetName) + true diff --git a/source/P4VFS.Driver/Source/DriverCore.c b/source/P4VFS.Driver/Source/DriverCore.c index 886580b..b69da4b 100644 --- a/source/P4VFS.Driver/Source/DriverCore.c +++ b/source/P4VFS.Driver/Source/DriverCore.c @@ -598,6 +598,7 @@ P4vfsPopReparseActionInProgress( NTSTATUS status = STATUS_SUCCESS; P4VFS_REPARSE_ACTION* pAction = NULL; P4VFS_REPARSE_ACTION* pPrevAction = NULL; + P4VFS_REPARSE_ACTION* pFreeAction = NULL; UNICODE_STRING actionFileKey = {0}; PAGED_CODE(); @@ -614,7 +615,9 @@ P4vfsPopReparseActionInProgress( for (pAction = g_FltContext.pReparseActionList; pAction != NULL; pAction = pAction->pNext) { if (P4vfsIsEqualActionFileKey(&actionFileKey, &pAction->fileKey)) + { break; + } pPrevAction = pAction; } @@ -627,12 +630,15 @@ P4vfsPopReparseActionInProgress( if (pAction->nRefCount <= 0) { if (g_FltContext.pReparseActionList == pAction) + { g_FltContext.pReparseActionList = pAction->pNext; + } else if (pPrevAction != NULL) + { pPrevAction->pNext = pAction->pNext; + } - RtlFreeUnicodeString(&pAction->fileKey); - ExFreePoolWithTag(pAction, P4VFS_REPARSE_ACTION_ALLOC_TAG); + pFreeAction = pAction; pAction = NULL; } } @@ -644,6 +650,12 @@ P4vfsPopReparseActionInProgress( { RtlFreeUnicodeString(&actionFileKey); } + + if (pFreeAction != NULL) + { + RtlFreeUnicodeString(&pFreeAction->fileKey); + ExFreePoolWithTag(pFreeAction, P4VFS_REPARSE_ACTION_ALLOC_TAG); + } } NTSTATUS @@ -785,40 +797,355 @@ P4vfsToUnicodeString( return STATUS_SUCCESS; } +NTSTATUS +P4vfsSetFileWritable( + _In_ PFLT_INSTANCE pFltInstance, + _In_ PUNICODE_STRING pFileIdPath + ) +{ + NTSTATUS status = STATUS_SUCCESS; + IO_STATUS_BLOCK ioStatus = {0}; + OBJECT_ATTRIBUTES objectAttributes = {0}; + IO_DRIVER_CREATE_CONTEXT createContext = {0}; + HANDLE hLocalFile = NULL; + PFILE_OBJECT pLocalFileObject = NULL; + FILE_BASIC_INFORMATION fileBasicInfo = {0}; + + PAGED_CODE(); + + if (!pFltInstance) + { + status = STATUS_INVALID_PARAMETER; + P4vfsTraceError(Core, L"P4vfsSetFileWritable: pFltInstance is NULL"); + goto CLEANUP; + } + + if (!pFileIdPath) + { + status = STATUS_INVALID_PARAMETER; + P4vfsTraceError(Core, L"P4vfsSetFileWritable: pFileIdPath is NULL"); + goto CLEANUP; + } + + IoInitializeDriverCreateContext(&createContext); + createContext.SiloContext = PsGetHostSilo(); + + InitializeObjectAttributes(&objectAttributes, + pFileIdPath, + OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, + NULL, + NULL); + + status = FltCreateFileEx2(g_FltContext.pFilter, + pFltInstance, + &hLocalFile, + &pLocalFileObject, + FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE, + &objectAttributes, + &ioStatus, + NULL, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_VALID_FLAGS, + FILE_OPEN, + FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_REPARSE_POINT | FILE_OPEN_BY_FILE_ID, + NULL, + 0, + IO_IGNORE_SHARE_ACCESS_CHECK, + &createContext); + + if (!NT_SUCCESS(status)) + { + P4vfsTraceError(Core, L"P4vfsSetFileWritable: FltCreateFileEx2 failed fileIdPath [%wZ] [%!STATUS!]", pFileIdPath, status); + goto CLEANUP; + } + + status = FltQueryInformationFile(pFltInstance, + pLocalFileObject, + &fileBasicInfo, + sizeof(fileBasicInfo), + FileBasicInformation, + NULL); + + if (!NT_SUCCESS(status)) + { + P4vfsTraceError(Core, L"P4vfsSetFileWritable: FltQueryInformationFile FileBasicInformation fileIdPath [%wZ] [%!STATUS!]", pFileIdPath, status); + goto CLEANUP; + } + + if (FlagOn(fileBasicInfo.FileAttributes, FILE_ATTRIBUTE_READONLY)) + { + ClearFlag(fileBasicInfo.FileAttributes, FILE_ATTRIBUTE_READONLY); + + status = FltSetInformationFile(pFltInstance, + pLocalFileObject, + &fileBasicInfo, + sizeof(fileBasicInfo), + FileBasicInformation); + + if (!NT_SUCCESS(status)) + { + P4vfsTraceError(Core, L"P4vfsSetFileWritable: FltSetInformationFile FileBasicInformation fileIdPath [%wZ] [%!STATUS!]", pFileIdPath, status); + goto CLEANUP; + } + } + +CLEANUP: + if (hLocalFile != NULL) + { + FltClose(hLocalFile); + } + + if (pLocalFileObject != NULL) + { + ObDereferenceObject(pLocalFileObject); + } + + return status; +} + +NTSTATUS +P4vfsGetFileIdByFileName( + _In_ PUNICODE_STRING pFileName, + _Out_ PUNICODE_STRING pOutFileIdPath, + _Outptr_opt_ PFLT_INSTANCE* ppFltInstance + ) +{ + NTSTATUS status = STATUS_SUCCESS; + IO_STATUS_BLOCK ioStatus = {0}; + OBJECT_ATTRIBUTES objectAttributes = {0}; + HANDLE hLocalFile = NULL; + PFILE_OBJECT pLocalFileObject = NULL; + FILE_ID_INFORMATION fileIdInfo = {0}; + PFLT_VOLUME pVolume = NULL; + PFLT_INSTANCE pVolumeInstance = NULL; + UNICODE_STRING fileIdPath = {0}; + + PAGED_CODE(); + + if (pFileName == NULL) + { + status = STATUS_INVALID_PARAMETER; + P4vfsTraceError(Core, L"P4vfsGetFileIdByFileName: pFileName is NULL"); + goto CLEANUP; + } + + if (pOutFileIdPath == NULL) + { + status = STATUS_INVALID_PARAMETER; + P4vfsTraceError(Core, L"P4vfsGetFileIdByFileName: pOutFileIdPath is NULL"); + goto CLEANUP; + } + + InitializeObjectAttributes(&objectAttributes, + pFileName, + OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, + NULL, + NULL); + + status = FltCreateFileEx2(g_FltContext.pFilter, + NULL, + &hLocalFile, + &pLocalFileObject, + 0, + &objectAttributes, + &ioStatus, + NULL, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_VALID_FLAGS, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT, + NULL, + 0, + IO_IGNORE_SHARE_ACCESS_CHECK, + NULL); + + if (!NT_SUCCESS(status)) + { + P4vfsTraceError(Core, L"P4vfsGetFileIdByFileName: FltCreateFileEx2 failed [%wZ] [%!STATUS!]", pFileName, status); + goto CLEANUP; + } + + status = FltGetVolumeFromFileObject(g_FltContext.pFilter, + pLocalFileObject, + &pVolume); + + if (!NT_SUCCESS(status) || pVolume == NULL) + { + status = !NT_SUCCESS(status) ? status : STATUS_NOT_ALL_ASSIGNED; + P4vfsTraceError(Core, L"P4vfsGetFileIdByFileName: FltGetVolumeFromFileObject failed pVolume [%p] [%wZ] [%!STATUS!]", pVolume, pFileName, status); + goto CLEANUP; + } + + status = FltGetVolumeInstanceFromName(g_FltContext.pFilter, + pVolume, + NULL, + &pVolumeInstance); + + if (!NT_SUCCESS(status) || pVolumeInstance == NULL) + { + status = !NT_SUCCESS(status) ? status : STATUS_NOT_ALL_ASSIGNED; + P4vfsTraceError(Core, L"P4vfsGetFileIdByFileName: FltGetVolumeInstanceFromName failed pVolumeInstance [%p] [%wZ] [%!STATUS!]", pVolumeInstance, pFileName, status); + goto CLEANUP; + } + + status = FltQueryInformationFile(pVolumeInstance, + pLocalFileObject, + &fileIdInfo, + sizeof(FILE_ID_INFORMATION), + FileIdInformation, + NULL); + + if (!NT_SUCCESS(status)) + { + P4vfsTraceError(Core, L"P4vfsGetFileIdByFileName: FltQueryInformationFile failed [%wZ] [%!STATUS!]", pFileName, status); + goto CLEANUP; + } + + ULONG volumeNameLengthRequired = 0; + status = FltGetVolumeName(pVolume, NULL, &volumeNameLengthRequired); + + if (!NT_SUCCESS(status) && (status != STATUS_BUFFER_TOO_SMALL)) + { + P4vfsTraceError(Core, L"P4vfsGetFileIdByFileName: FltGetVolumeName length query failed [%wZ] [%!STATUS!]", pFileName, status); + goto CLEANUP; + } + + status = RtlUShortAdd((USHORT)volumeNameLengthRequired, sizeof(WCHAR)+sizeof(fileIdInfo.FileId), &fileIdPath.MaximumLength); + + if (!NT_SUCCESS(status)) + { + P4vfsTraceError(Core, L"P4vfsGetFileIdByFileName: RtlUShortAdd failed [%wZ] [%!STATUS!]", pFileName, status); + goto CLEANUP; + } + + status = P4vfsAllocateUnicodeString(P4VFS_FILE_NAME_ALLOC_TAG, &fileIdPath); + + if (!NT_SUCCESS(status)) + { + P4vfsTraceError(Core, L"P4vfsGetFileIdByFileName: P4vfsAllocateUnicodeString failed [%wZ] [%!STATUS!]", pFileName, status); + goto CLEANUP; + } + + status = FltGetVolumeName(pVolume, &fileIdPath, &volumeNameLengthRequired); + + if (!NT_SUCCESS(status)) + { + P4vfsTraceError(Core, L"P4vfsGetFileIdByFileName: FltGetVolumeName failed [%wZ] [%!STATUS!]", pFileName, status); + goto CLEANUP; + } + + status = RtlAppendUnicodeToString(&fileIdPath, L"\\"); + + if (!NT_SUCCESS(status)) + { + P4vfsTraceError(Core, L"P4vfsGetFileIdByFileName: RtlAppendUnicodeToString separator failed [%wZ] fileIdPath [%wZ] [%!STATUS!]", pFileName, &fileIdPath, status); + goto CLEANUP; + } + + UNICODE_STRING fileIdString; + fileIdString.Length = sizeof(fileIdInfo.FileId); + fileIdString.MaximumLength = sizeof(fileIdInfo.FileId); + fileIdString.Buffer = (PWCHAR)&fileIdInfo.FileId; + + status = RtlAppendUnicodeStringToString(&fileIdPath, &fileIdString); + + if (!NT_SUCCESS(status)) + { + P4vfsTraceError(Core, L"P4vfsGetFileIdByFileName: RtlAppendUnicodeToString fileIdString failed [%wZ] fileIdPath [%wZ] [%!STATUS!]", pFileName, &fileIdPath, status); + goto CLEANUP; + } + + *pOutFileIdPath = fileIdPath; + fileIdPath.Buffer = NULL; + + if (ppFltInstance != NULL) + { + *ppFltInstance = pVolumeInstance; + pVolumeInstance = NULL; + } + +CLEANUP: + if (hLocalFile != NULL) + { + FltClose(hLocalFile); + } + + if (pLocalFileObject != NULL) + { + ObDereferenceObject(pLocalFileObject); + } + + if (pVolume != NULL) + { + FltObjectDereference(pVolume); + } + + if (pVolumeInstance != NULL) + { + FltObjectDereference(pVolumeInstance); + } + + if (fileIdPath.Buffer != NULL) + { + ExFreePoolWithTag(fileIdPath.Buffer, P4VFS_FILE_NAME_ALLOC_TAG); + } + + return status; +} + NTSTATUS P4vfsOpenReparsePoint( - _In_opt_ PFLT_INSTANCE pFltInstance, _In_ PUNICODE_STRING pFileName, _In_ ACCESS_MASK desiredAccess, - _In_ ULONG objectAttributes, _Out_ PHANDLE pTargetHandle, _Outptr_ PFILE_OBJECT* ppTargetFileObject ) { NTSTATUS status = STATUS_SUCCESS; IO_STATUS_BLOCK ioStatus = {0}; - OBJECT_ATTRIBUTES objAttributes = {0}; + OBJECT_ATTRIBUTES objectAttributes = {0}; IO_DRIVER_CREATE_CONTEXT createContext = {0}; HANDLE hLocalFile = NULL; PFILE_OBJECT pLocalFileObject = NULL; + UNICODE_STRING fileIdPath = {0}; + PFLT_INSTANCE pFltInstance = NULL; PAGED_CODE(); - if (!pFileName) + // We wish to open an existing reparse point file by using FILE_OPEN_BY_FILE_ID so as to avoid + // directory notifications. We take this opportunity to query our PFLT_INSTANCE for the volume + // which holds this file, which will be optimal for future filter operations + + status = P4vfsGetFileIdByFileName(pFileName, + &fileIdPath, + &pFltInstance); + + if (!NT_SUCCESS(status)) { - status = STATUS_INVALID_PARAMETER; - P4vfsTraceError(Core, L"P4vfsReopenFile: pFileName is NULL"); + P4vfsTraceError(Core, L"P4vfsOpenReparsePoint: P4vfsGetFileIdByFileName failed [%wZ] [%!STATUS!]", pFileName, status); goto CLEANUP; } + if (FlagOn(desiredAccess, FILE_WRITE_DATA | FILE_APPEND_DATA)) + { + status = P4vfsSetFileWritable(pFltInstance, &fileIdPath); + + if (!NT_SUCCESS(status)) + { + P4vfsTraceError(Core, L"P4vfsOpenReparsePoint: P4vfsSetFileWritable failed [%wZ] [%!STATUS!]", pFileName, status); + goto CLEANUP; + } + } + IoInitializeDriverCreateContext(&createContext); -#if (NTDDI_VERSION >= NTDDI_WIN10_RS1) && defined(P4VFS_KERNEL_MODE) createContext.SiloContext = PsGetHostSilo(); -#endif - InitializeObjectAttributes(&objAttributes, - pFileName, - objectAttributes | OBJ_CASE_INSENSITIVE, + // We use the fileIdPath in place of the pFileName path for FILE_OPEN_BY_FILE_ID. + // The IO_IGNORE_SHARE_ACCESS_CHECK is used to avoid existing share conflicts + + InitializeObjectAttributes(&objectAttributes, + &fileIdPath, + OBJ_CASE_INSENSITIVE, NULL, NULL); @@ -827,13 +1154,13 @@ P4vfsOpenReparsePoint( &hLocalFile, &pLocalFileObject, desiredAccess | SYNCHRONIZE, - &objAttributes, + &objectAttributes, &ioStatus, NULL, FILE_ATTRIBUTE_NORMAL, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_SHARE_VALID_FLAGS, FILE_OPEN, - FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_REPARSE_POINT, + FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_REPARSE_POINT | FILE_OPEN_BY_FILE_ID, NULL, 0, IO_IGNORE_SHARE_ACCESS_CHECK, @@ -841,10 +1168,12 @@ P4vfsOpenReparsePoint( if (!NT_SUCCESS(status)) { - P4vfsTraceError(Core, L"P4vfsReopenFile: FltCreateFileEx2 failed [%wZ] [%!STATUS!]", pFileName, status); + P4vfsTraceError(Core, L"P4vfsReopenFile: FltCreateFileEx2 failed [%wZ] fileIdPath [%wZ] [%!STATUS!]", pFileName, &fileIdPath, status); goto CLEANUP; } + P4vfsTraceInfo(Core, L"P4vfsReopenFile: FltCreateFileEx2 success [%wZ] fileIdPath [%wZ] [%!STATUS!]", pFileName, &fileIdPath, status); + *pTargetHandle = hLocalFile; hLocalFile = NULL; *ppTargetFileObject = pLocalFileObject; @@ -861,6 +1190,16 @@ P4vfsOpenReparsePoint( ObDereferenceObject(pLocalFileObject); } + if (pFltInstance != NULL) + { + FltObjectDereference(pFltInstance); + } + + if (fileIdPath.Buffer != NULL) + { + ExFreePoolWithTag(fileIdPath.Buffer, P4VFS_FILE_NAME_ALLOC_TAG); + } + return status; } diff --git a/source/P4VFS.Driver/Source/DriverFilter.c b/source/P4VFS.Driver/Source/DriverFilter.c index f9bbc21..8b033b2 100644 --- a/source/P4VFS.Driver/Source/DriverFilter.c +++ b/source/P4VFS.Driver/Source/DriverFilter.c @@ -89,9 +89,9 @@ P4vfsControlPortDisconnect( NTSTATUS P4vfsControlPortMessage( _In_opt_ PVOID pPortCookie, - _In_reads_bytes_opt_(InputBufferLength) PVOID pInputBuffer, + _In_reads_bytes_opt_(dwInputBufferLength) PVOID pInputBuffer, _In_ ULONG dwInputBufferLength, - _Out_writes_bytes_to_opt_(OutputBufferLength, *ReturnOutputBufferLength) PVOID pOutputBuffer, + _Out_writes_bytes_to_opt_(dwOutputBufferLength, *pReturnOutputBufferLength) PVOID pOutputBuffer, _In_ ULONG dwOutputBufferLength, _Out_ PULONG pReturnOutputBufferLength ); @@ -712,9 +712,9 @@ P4vfsControlPortDisconnect( NTSTATUS P4vfsControlPortMessage( _In_opt_ PVOID pPortCookie, - _In_reads_bytes_opt_(InputBufferLength) PVOID pInputBuffer, + _In_reads_bytes_opt_(dwInputBufferLength) PVOID pInputBuffer, _In_ ULONG dwInputBufferLength, - _Out_writes_bytes_to_opt_(OutputBufferLength, *ReturnOutputBufferLength) PVOID pOutputBuffer, + _Out_writes_bytes_to_opt_(dwOutputBufferLength, *pReturnOutputBufferLength) PVOID pOutputBuffer, _In_ ULONG dwOutputBufferLength, _Out_ PULONG pReturnOutputBufferLength ) @@ -810,16 +810,13 @@ P4vfsControlPortMessage( break; } - ULONG objectAttributes = 0; ACCESS_MASK desiredAccess = 0; desiredAccess |= input->data.OPEN_REPARSE_POINT.accessRead ? FILE_GENERIC_READ : 0; desiredAccess |= input->data.OPEN_REPARSE_POINT.accessWrite ? FILE_GENERIC_WRITE : 0; desiredAccess |= input->data.OPEN_REPARSE_POINT.accessDelete ? DELETE : 0; - status = P4vfsOpenReparsePoint(NULL, - &unicodeFilePath, + status = P4vfsOpenReparsePoint(&unicodeFilePath, desiredAccess, - objectAttributes, &output->data.OPEN_REPARSE_POINT.handle.fileHandle, &output->data.OPEN_REPARSE_POINT.handle.fileObject); @@ -957,7 +954,10 @@ P4vfsPostCreate( if (!NT_SUCCESS(status)) { P4vfsTraceInfo(Filter, L"P4vfsPostCreate: Failed ExecuteReparseAction [%wZ] [%!STATUS!]", (pFltObjects->FileObject ? &pFltObjects->FileObject->FileName : NULL), status); - FltCancelFileOpen(pFltObjects->Instance, pFltObjects->FileObject); + if (pFltObjects->FileObject != NULL) + { + FltCancelFileOpen(pFltObjects->Instance, pFltObjects->FileObject); + } goto CLEANUP; } } diff --git a/source/P4VFS.Extensions/Source/Common/SocketModel.cs b/source/P4VFS.Extensions/Source/Common/SocketModel.cs index 7cfb503..baaecb9 100644 --- a/source/P4VFS.Extensions/Source/Common/SocketModel.cs +++ b/source/P4VFS.Extensions/Source/Common/SocketModel.cs @@ -48,7 +48,9 @@ public void Shutdown() try { if (_Cancellation != null) + { _Cancellation.Cancel(); + } if (_TcpListener != null) { @@ -66,7 +68,9 @@ public void Shutdown() if (_ListenThread != null) { if (_ListenThread.IsAlive) + { _ListenThread.Join(500); + } _ListenThread = null; } } @@ -96,7 +100,9 @@ private void ListenForClients() catch (Exception e) { if (_Cancellation.IsCancellationRequested == false) + { VirtualFileSystemLog.Error("SocketModelServer.ListenForClients exiting with exception: {0}", e.Message); + } } } @@ -109,7 +115,7 @@ private void HandleClientConnection(object param) { using (NetworkStream stream = client.GetStream()) { - SocketModelMessage msg = SocketModelProtocol.ReceiveMessage(stream); + SocketModelMessage msg = SocketModelProtocol.ReceiveMessage(stream, _Cancellation.Token); if (msg == null) { VirtualFileSystemLog.Info("SocketModelServer.HandleClientConnection exiting from null message"); @@ -133,7 +139,7 @@ private void HandleSocketModelMessage(NetworkStream stream, SocketModelMessage m { SocketModelRequestSync request = msg.GetData(); SocketModelReplySync reply = Sync(stream, request); - SocketModelProtocol.SendMessage(stream, new SocketModelMessage(reply)); + SocketModelProtocol.SendMessage(stream, new SocketModelMessage(reply), _Cancellation.Token); } else if (msg.Type == typeof(SocketModelRequestServiceStatus).Name) { @@ -144,30 +150,36 @@ private void HandleSocketModelMessage(NetworkStream stream, SocketModelMessage m reply.LastModifiedTime = VirtualFileSystem.ServiceHost.GetLastModifiedTime(); reply.LastRequestTime = VirtualFileSystem.ServiceHost.GetLastRequestTime(); } - SocketModelProtocol.SendMessage(stream, new SocketModelMessage(reply)); + SocketModelProtocol.SendMessage(stream, new SocketModelMessage(reply), _Cancellation.Token); } else if (msg.Type == typeof(SocketModelRequestSetServiceSetting).Name) { SocketModelRequestSetServiceSetting request = msg.GetData(); - SocketModelReply reply = new SocketModelReply() { Success = ServiceSettings.SetProperty(request.Value, request.Name) }; - SocketModelProtocol.SendMessage(stream, new SocketModelMessage(reply)); + SocketModelReply reply = new SocketModelReply{ Success = ServiceSettings.SetProperty(request.Value, request.Name) }; + SocketModelProtocol.SendMessage(stream, new SocketModelMessage(reply), _Cancellation.Token); } else if (msg.Type == typeof(SocketModelRequestGetServiceSetting).Name) { SocketModelRequestGetServiceSetting request = msg.GetData(); - SocketModelReplyGetServiceSetting reply = new SocketModelReplyGetServiceSetting() { Value = ServiceSettings.GetProperty(request.Name) }; - SocketModelProtocol.SendMessage(stream, new SocketModelMessage(reply)); + SocketModelReplyGetServiceSetting reply = new SocketModelReplyGetServiceSetting{ Value = ServiceSettings.GetProperty(request.Name) }; + SocketModelProtocol.SendMessage(stream, new SocketModelMessage(reply), _Cancellation.Token); } else if (msg.Type == typeof(SocketModelRequestGetServiceSettings).Name) { - SocketModelReplyGetServiceSettings reply = new SocketModelReplyGetServiceSettings() { Settings = SettingManager.GetProperties() }; - SocketModelProtocol.SendMessage(stream, new SocketModelMessage(reply)); + SocketModelReplyGetServiceSettings reply = new SocketModelReplyGetServiceSettings{ Settings = SettingManager.GetProperties() }; + SocketModelProtocol.SendMessage(stream, new SocketModelMessage(reply), _Cancellation.Token); } else if (msg.Type == typeof(SocketModelRequestGarbageCollect).Name) { SocketModelRequestGarbageCollect request = msg.GetData(); - SocketModelReply reply = new SocketModelReply() { Success = VirtualFileSystem.ServiceHost != null ? VirtualFileSystem.ServiceHost.GarbageCollect(request.Timeout) : false }; - SocketModelProtocol.SendMessage(stream, new SocketModelMessage(reply)); + SocketModelReply reply = new SocketModelReply{ Success = VirtualFileSystem.ServiceHost != null ? VirtualFileSystem.ServiceHost.GarbageCollect(request.Timeout) : false }; + SocketModelProtocol.SendMessage(stream, new SocketModelMessage(reply), _Cancellation.Token); + } + else if (msg.Type == typeof(SocketModelRequestReflectPackage).Name) + { + SocketModelRequestReflectPackage request = msg.GetData(); + SocketModelReplyReflectPackage reply = new SocketModelReplyReflectPackage{ Package = request.Package }; + SocketModelProtocol.SendMessage(stream, new SocketModelMessage(reply), _Cancellation.Token); } else { @@ -229,10 +241,10 @@ public override void Write(LogElement element) { if (IsFaulted() == false) { - SocketModelMessage msg = new SocketModelMessage(new SocketModelRequestLog(){ Element = element }); + SocketModelMessage msg = new SocketModelMessage(new SocketModelRequestLog{ Element = element }); lock (_StreamMutex) { - SocketModelProtocol.SendMessage(_NetworkStream, msg); + SocketModelProtocol.SendMessage(_NetworkStream, msg, _CancelationToken); } } } @@ -250,7 +262,10 @@ public DepotSyncStatus Sync(DepotConfig config, DepotSyncOptions syncOptions) { List result = new List(); if (SendCommand(new SocketModelRequestSync{ Config=config, SyncOptions=syncOptions }, result) == false) + { return DepotSyncStatus.Error; + } + SocketModelReplySync reply = SocketModelMessage.GetData(result); return reply != null ? reply.Status : DepotSyncStatus.Error; } @@ -259,7 +274,10 @@ public SocketModelReplyServiceStatus GetServiceStatus() { List result = new List(); if (SendCommand(new SocketModelRequestServiceStatus(), result) == false) + { return null; + } + return SocketModelMessage.GetData(result); } @@ -267,7 +285,10 @@ public SettingNodeMap GetServiceSettings() { List result = new List(); if (SendCommand(new SocketModelRequestGetServiceSettings(), result) == false) + { return null; + } + SocketModelReplyGetServiceSettings reply = SocketModelMessage.GetData(result); return reply?.Settings; } @@ -276,7 +297,10 @@ public bool SetServiceSetting(string name, SettingNode value) { List result = new List(); if (SendCommand(new SocketModelRequestSetServiceSetting{ Name=name, Value=value }, result) == false) + { return false; + } + SocketModelReply reply = SocketModelMessage.GetData(result); return reply != null && reply.Success; } @@ -285,7 +309,10 @@ public SettingNode GetServiceSetting(string name) { List result = new List(); if (SendCommand(new SocketModelRequestGetServiceSetting{ Name=name }, result) == false) + { return null; + } + SocketModelReplyGetServiceSetting reply = SocketModelMessage.GetData(result); return reply?.Value; } @@ -293,13 +320,28 @@ public SettingNode GetServiceSetting(string name) public bool GarbageCollect(Int64 timeout = 0) { List result = new List(); - if (SendCommand(new SocketModelRequestGarbageCollect(){ Timeout = timeout }, result) == false) + if (SendCommand(new SocketModelRequestGarbageCollect{ Timeout = timeout }, result) == false) + { return false; + } + SocketModelReply reply = SocketModelMessage.GetData(result); return reply != null && reply.Success; } - private bool SendCommand(CommandType cmd, List result = null) + public byte[] ReflectPackage(byte[] package) + { + List result = new List(); + if (SendCommand(new SocketModelRequestReflectPackage{ Package = package }, result) == false) + { + return null; + } + + SocketModelReplyReflectPackage reply = SocketModelMessage.GetData(result); + return reply?.Package; + } + + private bool SendCommand(CommandType cmd, List result = null, CancellationToken cancelationToken = default) { try { @@ -309,15 +351,18 @@ private bool SendCommand(CommandType cmd, List using (NetworkStream stream = client.GetStream()) { SocketModelMessage request = new SocketModelMessage(cmd); - SocketModelProtocol.SendMessage(stream, request); + SocketModelProtocol.SendMessage(stream, request, cancelationToken); while (client.Connected) { - SocketModelMessage msg = SocketModelProtocol.ReceiveMessage(stream); + SocketModelMessage msg = SocketModelProtocol.ReceiveMessage(stream, cancelationToken); if (msg == null) + { break; + } if (result != null) + { result.Add(msg); - + } HandleSocketModelMessage(stream, msg); } } @@ -357,16 +402,18 @@ static SocketModelProtocol() _Encoding = new UTF8Encoding(false, true); } - public static void SendMessage(NetworkStream stream, MessageType msg) + public static void SendMessage(NetworkStream stream, MessageType msg, CancellationToken cancelationToken) { - SendMessageAsync(stream, msg).Wait(); + SendMessageAsync(stream, msg, cancelationToken).Wait(cancelationToken); } - public static async Task SendMessageAsync(NetworkStream stream, MessageType msg) + public static async Task SendMessageAsync(NetworkStream stream, MessageType msg, CancellationToken cancelationToken) { string content = Serialize(msg); if (content == null) + { throw new Exception("Failed to serialize message"); + } byte[] contentBytes = _Encoding.GetBytes(content); @@ -380,35 +427,52 @@ public static async Task SendMessageAsync(NetworkStream stream, Mes Array.Copy(headerBytes, 0, packetBytes, 0, headerBytes.Length); Array.Copy(contentBytes, 0, packetBytes, headerBytes.Length, contentBytes.Length); - await stream.WriteAsync(packetBytes, 0, packetBytes.Length); + await stream.WriteAsync(packetBytes, 0, packetBytes.Length, cancelationToken); } - public static MessageType ReceiveMessage(NetworkStream stream) + public static MessageType ReceiveMessage(NetworkStream stream, CancellationToken cancelationToken) { - Task msg = ReceiveMessageAsync(stream); - msg.Wait(); + Task msg = ReceiveMessageAsync(stream, cancelationToken); + msg.Wait(cancelationToken); return msg.Result; } - public static async Task ReceiveMessageAsync(NetworkStream stream) + public static async Task ReceiveMessageAsync(NetworkStream stream, CancellationToken cancelationToken) { int headerSize = Marshal.SizeOf(typeof(Header)); byte[] headerBytes = new byte[headerSize]; - int headerRead = await stream.ReadAsync(headerBytes, 0, headerBytes.Length); + int headerRead = await stream.ReadAsync(headerBytes, 0, headerBytes.Length, cancelationToken); if (headerRead == 0) + { return default(MessageType); + } if (headerRead != headerBytes.Length) + { throw new Exception("Failed to read header bytes"); + } Header header = BytesToStruct
(headerBytes); if (header.MagicNumber != _MagicNumber) + { throw new Exception(String.Format("Invalid header format 0x{0:X8} expected 0x{1:X8}", header.MagicNumber, _MagicNumber)); + } if (header.Version != _Version) + { throw new Exception(String.Format("Invalid header version {0} expected {1}", header.Version, _Version)); + } byte[] contentBytes = new byte[header.ContentLength]; - if (await stream.ReadAsync(contentBytes, 0, contentBytes.Length) != contentBytes.Length) - throw new Exception("Failed to read content bytes"); + for (int contentIndex = 0; contentIndex < contentBytes.Length;) + { + cancelationToken.ThrowIfCancellationRequested(); + int remainingCount = contentBytes.Length - contentIndex; + int readCount = await stream.ReadAsync(contentBytes, contentIndex, remainingCount, cancelationToken); + if (readCount <= 0) + { + throw new Exception($"Failed to read expected number of content bytes {contentBytes.Length}"); + } + contentIndex += readCount; + } string contentString = _Encoding.GetString(contentBytes); return Deserialize(contentString); @@ -445,7 +509,9 @@ public static T BytesToStruct(byte[] valueBytes) where T : struct { int valueSize = Marshal.SizeOf(typeof(T)); if (valueBytes.Length < valueBytes.Length) + { throw new Exception("Invalid buffer size"); + } T value = default(T); IntPtr valuePtr = Marshal.AllocHGlobal(valueSize); @@ -557,4 +623,14 @@ public class SocketModelRequestGarbageCollect { public Int64 Timeout; } + + public class SocketModelRequestReflectPackage + { + public byte[] Package; + } + + public class SocketModelReplyReflectPackage + { + public byte[] Package; + } } diff --git a/source/P4VFS.Extensions/Source/Utilities/RandomEx.cs b/source/P4VFS.Extensions/Source/Utilities/RandomEx.cs index 774bea1..03aa433 100644 --- a/source/P4VFS.Extensions/Source/Utilities/RandomEx.cs +++ b/source/P4VFS.Extensions/Source/Utilities/RandomEx.cs @@ -143,5 +143,21 @@ public UInt32 Next() m_Seed = (UInt32)(((UInt64)m_Seed * 214013UL + 2531011UL) & UInt32.MaxValue); return (UInt32)((m_Seed >> 16) & 0x7fff); } + + public byte[] NextBytes(int count) + { + byte[] values = new byte[count]; + for (int index = 0; index < count; ++index) + { + UInt32 data = Next(); + byte v = 0; + v ^= (byte)(data & 0xFF); + v ^= (byte)((data>>8) & 0xFF); + v ^= (byte)((data>>16) & 0xFF); + v ^= (byte)((data>>24) & 0xFF); + values[index] = v; + } + return values; + } } } diff --git a/source/P4VFS.Service/Include/ServiceHost.h b/source/P4VFS.Service/Include/ServiceHost.h index a55bd78..3361f1a 100644 --- a/source/P4VFS.Service/Include/ServiceHost.h +++ b/source/P4VFS.Service/Include/ServiceHost.h @@ -10,8 +10,8 @@ class ServiceHost : public ExtensionsInterop::ServiceHost { public: - static ServiceHost* - GetInstance( + static ServiceHost& + StaticInstance( ); static VOID WINAPI @@ -83,11 +83,17 @@ class ServiceHost : public ExtensionsInterop::ServiceHost int64_t timeout ) override; -public: - static WCHAR* SERVICE_NAME; +private: + static bool + HasArgument( + DWORD dwNumServicesArgs, + LPWSTR* lpServiceArgVectors, + LPCWSTR lpArgName + ); private: - static ServiceHost* s_instance; + static const WCHAR* SERVICE_NAME; + static ServiceHost* m_Instance; SERVICE_STATUS m_SrvStatus; SERVICE_STATUS_HANDLE m_SrvStatusHandle; diff --git a/source/P4VFS.Service/Source/Service.cpp b/source/P4VFS.Service/Source/Service.cpp index 4c43016..c1e88e8 100644 --- a/source/P4VFS.Service/Source/Service.cpp +++ b/source/P4VFS.Service/Source/Service.cpp @@ -9,8 +9,6 @@ ServiceHost g_SrvHost; int _tmain() { - ServiceHost* pSrvHost = ServiceHost::GetInstance(); - Assert(pSrvHost != nullptr); - pSrvHost->Start(); + ServiceHost::StaticInstance().Start(); return 0; } diff --git a/source/P4VFS.Service/Source/ServiceHost.cpp b/source/P4VFS.Service/Source/ServiceHost.cpp index afb90b7..22c2c58 100644 --- a/source/P4VFS.Service/Source/ServiceHost.cpp +++ b/source/P4VFS.Service/Source/ServiceHost.cpp @@ -17,14 +17,15 @@ using namespace Microsoft::P4VFS::FileCore; namespace Microsoft { namespace P4VFS { -WCHAR* ServiceHost::SERVICE_NAME = TEXT("P4VFS.Service"); -ServiceHost* ServiceHost::s_instance = nullptr; +const WCHAR* ServiceHost::SERVICE_NAME = TEXT("P4VFS.Service"); +ServiceHost* ServiceHost::m_Instance = nullptr; -ServiceHost* -ServiceHost::GetInstance( +ServiceHost& +ServiceHost::StaticInstance( ) { - return s_instance; + Assert(m_Instance != nullptr); + return *m_Instance; } VOID WINAPI @@ -33,7 +34,7 @@ ServiceHost::StaticSrvMain( LPWSTR* lpServiceArgVectors ) { - ServiceHost::GetInstance()->SrvMain(dwNumServicesArgs, lpServiceArgVectors); + StaticInstance().SrvMain(dwNumServicesArgs, lpServiceArgVectors); } VOID WINAPI @@ -41,7 +42,7 @@ ServiceHost::StaticSrvCtrlHandler( DWORD dwCtrl ) { - ServiceHost::GetInstance()->SrvCtrlHandler(dwCtrl); + StaticInstance().SrvCtrlHandler(dwCtrl); } ServiceHost::ServiceHost() : @@ -52,8 +53,8 @@ ServiceHost::ServiceHost() : m_SrvLastRequestTime({0}), m_SrvTickThread(NULL) { - Assert(s_instance == nullptr); - s_instance = this; + Assert(m_Instance == nullptr); + m_Instance = this; } void @@ -63,7 +64,7 @@ ServiceHost::Start( SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); const SERVICE_TABLE_ENTRY dispatchTable[] = { - { ServiceHost::SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)StaticSrvMain }, + { (LPWSTR)ServiceHost::SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)StaticSrvMain }, { NULL, NULL } }; @@ -79,9 +80,11 @@ ServiceHost::SrvMain( LPWSTR* lpServiceArgVectors ) { - UNREFERENCED_PARAMETER(dwNumServicesArgs); - UNREFERENCED_PARAMETER(lpServiceArgVectors); - //ExtensionsInterop::LaunchAttachDebugger(); + // Development option to attach debugger on launch + if (HasArgument(dwNumServicesArgs, lpServiceArgVectors, TEXT("--break"))) + { + ExtensionsInterop::LaunchAttachDebugger(); + } // Note that this handle does not have to be closed m_SrvStatusHandle = RegisterServiceCtrlHandler(ServiceHost::SERVICE_NAME, StaticSrvCtrlHandler); @@ -105,8 +108,9 @@ ServiceHost::SrvMain( return; } - ExtensionsInterop::InitializeServiceHost(this); LogSystem::StaticInstance().Initialize(); + ExtensionsInterop::InitializeServiceHost(this); + SrvBeginTickThread(); ServiceLog::Info(TEXT("ServiceHost::SrvMain Begin")); @@ -270,4 +274,24 @@ ServiceHost::GarbageCollect( return true; } +bool +ServiceHost::HasArgument( + DWORD dwNumServicesArgs, + LPWSTR* lpServiceArgVectors, + LPCWSTR lpArgName + ) +{ + if (lpServiceArgVectors != nullptr) + { + for (DWORD argIndex = 0; argIndex < dwNumServicesArgs; ++argIndex) + { + if (StringInfo::Stricmp(lpArgName, lpServiceArgVectors[argIndex]) == 0) + { + return true; + } + } + } + return false; +} + }} diff --git a/source/P4VFS.Service/Source/ServiceTask.cpp b/source/P4VFS.Service/Source/ServiceTask.cpp index 4f81c5f..1fa9a21 100644 --- a/source/P4VFS.Service/Source/ServiceTask.cpp +++ b/source/P4VFS.Service/Source/ServiceTask.cpp @@ -218,7 +218,7 @@ ServiceTaskManager::HandleResolveFileRequest( } ServiceLog::Verbose(StringInfo::Format(TEXT("HandleResolveFileRequest Start '%s' process [%d.%d]"), message.dataName.c_str(), message.processId, message.threadId).c_str()); - ServiceHost::GetInstance()->NotifyLastRequestTime(); + ServiceHost::StaticInstance().NotifyLastRequestTime(); String localFileToMakeResident; HRESULT hr = ResolvePathFromMessage(message, localFileToMakeResident); diff --git a/source/P4VFS.Setup/P4VFS.Setup.csproj b/source/P4VFS.Setup/P4VFS.Setup.csproj index 938aca9..aa3dc31 100644 --- a/source/P4VFS.Setup/P4VFS.Setup.csproj +++ b/source/P4VFS.Setup/P4VFS.Setup.csproj @@ -104,7 +104,7 @@ - + SetupWindow.xaml diff --git a/source/P4VFS.Setup/Source/Configuration.cs b/source/P4VFS.Setup/Source/Configuration.cs deleted file mode 100644 index 425a5ee..0000000 --- a/source/P4VFS.Setup/Source/Configuration.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -using System; -using System.IO; -using System.Linq; -using System.Collections.Generic; -using System.Xml.Serialization; - -namespace Microsoft.P4VFS.Setup -{ - [XmlRoot("Configuration", Namespace="")] - public class Configuration - { - [XmlArray("IncludeFiles"), XmlArrayItem("Path", typeof(string))] - public string[] IncludeFiles; - - public static Configuration LoadFromFile(string fileName) - { - try - { - using (FileStream fs = File.OpenRead(fileName)) - { - XmlSerializer xml = new XmlSerializer(typeof(Configuration)); - Configuration config = xml.Deserialize(fs) as Configuration; - return config; - } - } - catch {} - return null; - } - } -} - diff --git a/source/P4VFS.Setup/Source/Program.cs b/source/P4VFS.Setup/Source/Program.cs index bbf1e4e..005ac5b 100644 --- a/source/P4VFS.Setup/Source/Program.cs +++ b/source/P4VFS.Setup/Source/Program.cs @@ -17,7 +17,7 @@ namespace Microsoft.P4VFS.Setup public class Program { public static SetupWindow _SetupWindow; - public static List _IncludeFiles; + public static SetupConfiguration _Configuration; public static bool _Admin; public static bool _Console; public static bool _P4vfsDebug; @@ -58,11 +58,9 @@ public static int Main(string[] args) _Admin = false; _Console = false; _P4vfsDebug = false; - _IncludeFiles = new List(); + _Configuration = new SetupConfiguration(); - Configuration config = LoadConfiguration(); - if (config != null && config.IncludeFiles != null) - _IncludeFiles.AddRange(config.IncludeFiles); + _Configuration.Import(LoadConfiguration()); int argIndex = 0; for (; argIndex < args.Length; ++argIndex) @@ -74,7 +72,7 @@ public static int Main(string[] args) else if (String.Compare(args[argIndex], "-c") == 0) _Console = true; else if (String.Compare(args[argIndex], "-i") == 0 && argIndex+1 < args.Length) - _IncludeFiles.Add(args[++argIndex]); + _Configuration.IncludeFiles.Add(args[++argIndex]); else if (String.Compare(args[argIndex], "-d") == 0) _P4vfsDebug = true; else @@ -250,7 +248,7 @@ private static bool CommandInstall(string[] args) } progress.WriteLine(6, String.Format("Installing application setup ...")); - if (InstallAplicationSetup() == false) + if (InstallApplicationSetup() == false) { WriteLine(String.Format("Failed installing requirements for application setup")); return false; @@ -368,7 +366,9 @@ private static bool InstallEnvironment() List paths = new List(srcPath.Split(new char[]{';'}, StringSplitOptions.RemoveEmptyEntries)); if (paths.Any(s => String.Compare(InstallFolder, Regex.Replace(s, @"[\\/]+", "\\").TrimEnd('\\'), StringComparison.InvariantCultureIgnoreCase) == 0) == false) + { paths.Add(InstallFolder); + } string dstPath = String.Join(";", paths); envKey.SetValue("PATH", dstPath, RegistryValueKind.ExpandString); @@ -398,7 +398,7 @@ private static void RefreshEnvironmentVariables() Environment.SetEnvironmentVariable("P4VFS_INSTALL", null, EnvironmentVariableTarget.Machine); } - private static bool InstallAplicationSetup() + private static bool InstallApplicationSetup() { try { @@ -419,6 +419,12 @@ private static bool InstallAplicationSetup() appKey.SetValue("UninstallString", String.Format("\"{0}\" uninstall", installedSetupExe), RegistryValueKind.String); appKey.SetValue("Publisher", "Microsoft Corporation", RegistryValueKind.String); appKey.SetValue("InstallLocation", InstallFolder, RegistryValueKind.String); + + foreach (SetupRegistryKey setupKey in _Configuration.RegistryKeys) + { + WriteLine(String.Format("Setting additional registry key: {0}={1}", setupKey.Name, setupKey.Value)); + appKey.SetValue(setupKey.Name, setupKey.Value, RegistryValueKind.String); + } } } catch (Exception e) @@ -637,13 +643,13 @@ private static string CreateStagingFolder() } string[] resourceNames = Assembly.GetExecutingAssembly().GetManifestResourceNames(); - using (LogProgress progress = new LogProgress(resourceNames.Length+_IncludeFiles.Count+2)) + using (LogProgress progress = new LogProgress(resourceNames.Length+_Configuration.IncludeFiles.Count+2)) { WriteLine(String.Format("Staging: {0}", stagingFolder)); if (ExtractResourcesToFolder(resourceNames, stagingFolder, progress) == false) return null; - foreach (string includeFile in _IncludeFiles) + foreach (string includeFile in _Configuration.IncludeFiles) { progress.Increment(); string includeFilePath = ResolveIncludeFilePath(includeFile); @@ -728,7 +734,7 @@ private static string ResolveIncludeFilePath(string filePath) return filePath; } - private static Configuration LoadConfiguration() + private static SetupConfiguration LoadConfiguration() { string[] configFiles = new[]{ "P4VFS.Setup.xml", @@ -740,7 +746,7 @@ private static Configuration LoadConfiguration() string configFilePath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), configFile)); if (File.Exists(configFilePath)) { - Configuration config = Configuration.LoadFromFile(configFilePath); + SetupConfiguration config = SetupConfiguration.LoadFromFile(configFilePath); if (config != null) { WriteLine(String.Format("Loaded Configuration: {0}", configFilePath)); diff --git a/source/P4VFS.Setup/Source/SetupConfiguration.cs b/source/P4VFS.Setup/Source/SetupConfiguration.cs new file mode 100644 index 0000000..1581f34 --- /dev/null +++ b/source/P4VFS.Setup/Source/SetupConfiguration.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +using System; +using System.IO; +using System.Linq; +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.P4VFS.Setup +{ + [XmlRoot("Configuration", Namespace="")] + public class SetupConfiguration + { + [XmlArray("IncludeFiles"), XmlArrayItem("Path", typeof(string))] + public List IncludeFiles; + + [XmlArray("RegistryKeys"), XmlArrayItem("Key", typeof(SetupRegistryKey))] + public List RegistryKeys; + + public static SetupConfiguration LoadFromFile(string fileName) + { + try + { + using (FileStream fs = File.OpenRead(fileName)) + { + XmlSerializer xml = new XmlSerializer(typeof(SetupConfiguration)); + SetupConfiguration config = xml.Deserialize(fs) as SetupConfiguration; + return config; + } + } + catch {} + return null; + } + + public void Import(SetupConfiguration config) + { + if (IncludeFiles == null) + IncludeFiles = new List(); + if (config?.IncludeFiles != null) + IncludeFiles.AddRange(config.IncludeFiles); + + if (RegistryKeys == null) + RegistryKeys = new List(); + if (config?.RegistryKeys != null) + RegistryKeys.AddRange(config.RegistryKeys); + } + } + + public class SetupRegistryKey + { + [XmlAttribute] + public string Name; + + [XmlAttribute] + public string Value; + } +} + diff --git a/source/P4VFS.UnitTest/Scripts/RestartPerforceServer.bat b/source/P4VFS.UnitTest/Scripts/RestartPerforceServer.bat index e26f9b4..c064419 100644 --- a/source/P4VFS.UnitTest/Scripts/RestartPerforceServer.bat +++ b/source/P4VFS.UnitTest/Scripts/RestartPerforceServer.bat @@ -14,7 +14,12 @@ PUSHD "%SCRIPT_FOLDER%\..\..\.." SET REPO_FOLDER=%CD% POPD -SET SERVER_FOLDER=%REPO_FOLDER%\intermediate\1666 +IF NOT "%P4VFS_TEST_SERVER_ROOT%"=="" ( + SET SERVER_FOLDER=%P4VFS_TEST_SERVER_ROOT%\1666 +) ELSE ( + SET SERVER_FOLDER=%REPO_FOLDER%\intermediate\1666 +) + SET P4D_EXE=%SERVER_FOLDER%\p4d.exe SET P4_EXE=p4.exe FOR /F "tokens=*" %%A IN ('dir /s /b "%REPO_FOLDER%\external\P4API\p4.exe"') DO ( diff --git a/source/P4VFS.UnitTest/Source/UnitTestBase.cs b/source/P4VFS.UnitTest/Source/UnitTestBase.cs index aafc7d3..074d9de 100644 --- a/source/P4VFS.UnitTest/Source/UnitTestBase.cs +++ b/source/P4VFS.UnitTest/Source/UnitTestBase.cs @@ -859,6 +859,10 @@ public ServiceSettingScope(string name, string value) UnitTestBase.Assert(serviceClient.SetServiceSetting(Name, SettingNode.FromString(Value))); UnitTestBase.Assert(serviceClient.GetServiceSetting(Name).ToString() == Value); } + + public ServiceSettingScope(string name) : this(name, ServiceSettings.GetProperty(name).ToString()) + { + } public void Dispose() { diff --git a/source/P4VFS.UnitTest/Source/UnitTestCommon.cs b/source/P4VFS.UnitTest/Source/UnitTestCommon.cs index 0c1baa3..8357ea3 100644 --- a/source/P4VFS.UnitTest/Source/UnitTestCommon.cs +++ b/source/P4VFS.UnitTest/Source/UnitTestCommon.cs @@ -1428,6 +1428,17 @@ public void SocketModelCommunicationTest() Assert(client.SetServiceSetting("NonExistingSetting", SettingNode.FromBool(false)) == false); Assert(client.GetServiceSetting("NonExistingSetting") == null); + RandomFast random = new RandomFast(291481); + const int maxPackageSize = 1<<25; // 32*1024*1024 (32 MiB) + for (int packageSize = 1; packageSize <= maxPackageSize; packageSize <<= 5) + { + VirtualFileSystemLog.Info("SocketModelCommunicationTest ReflectPackage size {0}", packageSize); + byte[] packageBytes = random.NextBytes(packageSize); + byte[] receiveBytes = client.ReflectPackage(packageBytes); + Assert(receiveBytes?.Length == packageBytes.Length); + Assert(receiveBytes.SequenceEqual(packageBytes)); + } + ServiceRestart(); } diff --git a/source/P4VFS.UnitTest/Source/UnitTestServer.cs b/source/P4VFS.UnitTest/Source/UnitTestServer.cs index d79f45a..d8ffb43 100644 --- a/source/P4VFS.UnitTest/Source/UnitTestServer.cs +++ b/source/P4VFS.UnitTest/Source/UnitTestServer.cs @@ -343,16 +343,7 @@ private void GenerateServerUnitTestFile(string filePath, long fileSize, string f { using (FileStream file = File.Create(filePath)) { - uint data = 0; - for (long position = 0; position < fileSize; ++position) - { - if (data == 0) - { - data = ServerRandom.Next(); - } - file.WriteByte((byte)(data & 0xFF)); - data >>= 8; - } + file.Write(ServerRandom.NextBytes((int)fileSize), 0, (int)fileSize); } break; } @@ -496,12 +487,18 @@ public static string ServerRootFolderOverride public static string GetServerRootFolder(string p4Port = null) { - // Special case for overriding the server root folder possibly on another drive if (String.IsNullOrEmpty(ServerRootFolderOverride) == false) { return ServerRootFolderOverride; } - return String.Format("{0}\\{1}", UnitTestInstall.GetIntermediateRootFolder(), GetServerPortNumber(p4Port)); + + string serverRoot = Environment.GetEnvironmentVariable("P4VFS_TEST_SERVER_ROOT"); + if (String.IsNullOrEmpty(serverRoot)) + { + serverRoot = UnitTestInstall.GetIntermediateRootFolder(); + } + + return String.Format("{0}\\{1}", serverRoot, GetServerPortNumber(p4Port)); } public static string GetServerClientRootFolder(string clientName, string p4Port = null) diff --git a/source/P4VFS.UnitTest/Source/UnitTestWorkflow.cs b/source/P4VFS.UnitTest/Source/UnitTestWorkflow.cs index 60c3f07..f3c70b7 100644 --- a/source/P4VFS.UnitTest/Source/UnitTestWorkflow.cs +++ b/source/P4VFS.UnitTest/Source/UnitTestWorkflow.cs @@ -88,7 +88,6 @@ public void SyncProtocolsTest() string revision = "@21"; string[] syncArgs = WindowsInterop.CommandLineToArgs(syncOption); - ServiceRestart(); Assert(ProcessInfo.ExecuteWait(P4vfsExe, String.Format("{0} sync {1} \"{2}\\...{3}\"", ClientConfig, syncOption, directory, revision), echo:true, log:true) == 0); Assert(ProcessInfo.ExecuteWait(P4Exe, String.Format("{0} flush -f \"{1}\\...{2}\"", ClientConfig, directory, revision), echo:true) == 0); @@ -103,7 +102,6 @@ public void SyncProtocolsTest() } Assert(ReconcilePreview(directory).Any() == false); - ServiceRestart(); foreach (string filePath in placeholderSizeMap.Keys) {