From 3aadaee511bd348b7b4c7e2425cfb201e172209a Mon Sep 17 00:00:00 2001 From: Bill Zissimopoulos Date: Wed, 1 Feb 2023 17:42:11 +0000 Subject: [PATCH] dll: FspFileSystemStopServiceIfNecessary --- inc/winfsp/winfsp.h | 56 +++++++++++++++++++++++++++++++++++- src/dll/fsop.c | 9 ++++++ src/dll/fuse/fuse_intf.c | 13 +++++++++ src/dll/library.h | 1 + src/dll/service.c | 45 +++++++++++++++++++++++++++++ src/dotnet/FileSystemBase.cs | 37 ++++++++++++++++++++++++ src/dotnet/FileSystemHost.cs | 16 +++++++++++ src/dotnet/Interop.cs | 11 +++++++ tst/memfs/memfs.cpp | 4 +-- tst/ntptfs/ptfs.c | 7 +++++ 10 files changed, 196 insertions(+), 3 deletions(-) diff --git a/inc/winfsp/winfsp.h b/inc/winfsp/winfsp.h index bf117260..ace82794 100644 --- a/inc/winfsp/winfsp.h +++ b/inc/winfsp/winfsp.h @@ -1047,6 +1047,41 @@ typedef struct _FSP_FILE_SYSTEM_INTERFACE NTSTATUS (*Obsolete0)(VOID); + /** + * Inform the file system that its dispatcher has been stopped. + * + * Prior to WinFsp v2.0 the FSD would never unmount a file system volume unless + * the user mode file system requested the unmount. Since WinFsp v2.0 it is possible + * for the FSD to unmount a file system volume without an explicit user mode file system + * request. For example, this happens when the FSD is being uninstalled. + * + * A user mode file system can use this operation to determine when its dispatcher + * has been stopped. The Normally parameter can be used to determine why the dispatcher + * was stopped: it is TRUE when the file system is being stopped via + * FspFileSystemStopDispatcher and FALSE otherwise. + * + * When the file system receives a request with Normally == TRUE it need not take any + * extra steps. This case is the same as for pre-v2.0 versions: since the file system + * stopped the dispatcher via FspFileSystemStopDispatcher, it will likely exit its + * process soon. + * + * When the file system receives a request with Normally == FALSE it may need to take + * extra steps to exit its process as this is not done by default. + * + * A file system that uses the FspService infrastructure may use the + * FspFileSystemStopServiceIfNecessary API to correctly handle all cases. + * + * This operation is the last one that a file system will receive. + * + * @param FileSystem + * The file system on which this request is posted. + * @param Normally + * TRUE if the file system is being stopped via FspFileSystemStopDispatcher. + * FALSE if the file system is being stopped because of another reason such + * as driver unload/uninstall. + * @see + * FspFileSystemStopServiceIfNecessary + */ VOID (*DispatcherStopped)(FSP_FILE_SYSTEM *FileSystem, BOOLEAN Normally); @@ -1112,7 +1147,7 @@ FSP_API NTSTATUS FspFileSystemPreflight(PWSTR DevicePath, * @param VolumeParams * Volume parameters for the newly created file system. * @param Interface - * A pointer to the actual operations that actually implement this user mode file system. + * A pointer to the operations that implement this user mode file system. * @param PFileSystem [out] * Pointer that will receive the file system object created on successful return from this * call. @@ -1750,6 +1785,23 @@ UINT32 FspFileSystemGetEaPackedSize(PFILE_FULL_EA_INFORMATION SingleEa) */ FSP_API BOOLEAN FspFileSystemAddNotifyInfo(FSP_FSCTL_NOTIFY_INFO *NotifyInfo, PVOID Buffer, ULONG Length, PULONG PBytesTransferred); +/** + * Stop a file system service, if any. + * + * This is a helper for implementing the DispatcherStopped operation, but only for file systems + * that use the FspService infrastructure. + * + * @param FileSystem + * The file system object. + * @param Normally + * TRUE if the file system is being stopped via FspFileSystemStopDispatcher. + * FALSE if the file system is being stopped because of another reason such + * as driver unload/uninstall. + * @see + * DispatcherStopped + */ +FSP_API VOID FspFileSystemStopServiceIfNecessary(FSP_FILE_SYSTEM *FileSystem, + BOOLEAN Normally); /* * Directory buffering @@ -2047,6 +2099,8 @@ FSP_API ULONG FspServiceGetExitCode(FSP_SERVICE *Service); * to connect the service process to the Service Control Manager. If the Service Control Manager is * not available (and console mode is allowed) it will enter console mode. * + * This function should be called once per process. + * * @param Service * The service object. * @return diff --git a/src/dll/fsop.c b/src/dll/fsop.c index e9e51cbd..4ab4ec39 100644 --- a/src/dll/fsop.c +++ b/src/dll/fsop.c @@ -1883,3 +1883,12 @@ FSP_API BOOLEAN FspFileSystemAddNotifyInfo(FSP_FSCTL_NOTIFY_INFO *NotifyInfo, { return FspFileSystemAddXxxInfo(NotifyInfo, Buffer, Length, PBytesTransferred); } + +FSP_API VOID FspFileSystemStopServiceIfNecessary(FSP_FILE_SYSTEM *FileSystem, + BOOLEAN Normally) +{ + /* NOTE: .NET calls us with a zero FileSystem pointer! */ + if (Normally) + return; + FspServiceStopLoop(); +} diff --git a/src/dll/fuse/fuse_intf.c b/src/dll/fuse/fuse_intf.c index bca425d0..3f70d51b 100644 --- a/src/dll/fuse/fuse_intf.c +++ b/src/dll/fuse/fuse_intf.c @@ -2635,6 +2635,17 @@ static NTSTATUS fsp_fuse_intf_SetEa(FSP_FILE_SYSTEM *FileSystem, &Uid, &Gid, &Mode, FileInfo); } +static VOID fsp_fuse_intf_DispatcherStopped(FSP_FILE_SYSTEM *FileSystem, + BOOLEAN Normally) +{ + if (Normally) + return; + + struct fuse *f = FileSystem->UserContext; + + fsp_fuse_exit(f->env, f); +} + FSP_FILE_SYSTEM_INTERFACE fsp_fuse_intf = { fsp_fuse_intf_GetVolumeInfo, @@ -2668,6 +2679,8 @@ FSP_FILE_SYSTEM_INTERFACE fsp_fuse_intf = fsp_fuse_intf_Overwrite, fsp_fuse_intf_GetEa, fsp_fuse_intf_SetEa, + 0, + fsp_fuse_intf_DispatcherStopped, }; /* diff --git a/src/dll/library.h b/src/dll/library.h index 8bbf5743..28cbeb98 100644 --- a/src/dll/library.h +++ b/src/dll/library.h @@ -110,6 +110,7 @@ NTSTATUS FspGetModuleFileName( VOID FspFileSystemPeekInDirectoryBuffer(PVOID *PDirBuffer, PUINT8 *PBuffer, PULONG *PIndex, PULONG PCount); +VOID FspServiceStopLoop(VOID); BOOL WINAPI FspServiceConsoleCtrlHandler(DWORD CtrlType); static inline ULONG FspPathSuffixIndex(PWSTR FileName) diff --git a/src/dll/service.c b/src/dll/service.c index 229c4424..83616796 100644 --- a/src/dll/service.c +++ b/src/dll/service.c @@ -40,6 +40,8 @@ enum GetStatus_WaitHint = 0x4000, }; +static SRWLOCK FspServiceLoopLock = SRWLOCK_INIT; +static SRWLOCK FspServiceTableLock = SRWLOCK_INIT; static SERVICE_TABLE_ENTRYW *FspServiceTable; static HANDLE FspServiceConsoleModeEvent; static UINT32 FspServiceConsoleCtrlHandlerDisabled; @@ -220,6 +222,14 @@ FSP_API ULONG FspServiceGetExitCode(FSP_SERVICE *Service) FSP_API NTSTATUS FspServiceLoop(FSP_SERVICE *Service) { + /* + * FspServiceLoop can only be called once per process, because of StartServiceCtrlDispatcherW + * (which returns ERROR_SERVICE_ALREADY_RUNNING if called more than once). Unfortunately this + * limitation was never documented and there may be users of FspServiceLoop out there that call + * it more than once per process. + */ + AcquireSRWLockExclusive(&FspServiceLoopLock); + NTSTATUS Result; SERVICE_TABLE_ENTRYW ServiceTable[2]; @@ -236,7 +246,9 @@ FSP_API NTSTATUS FspServiceLoop(FSP_SERVICE *Service) ServiceTable[0].lpServiceProc = FspServiceEntry; ServiceTable[1].lpServiceName = 0; ServiceTable[1].lpServiceProc = 0; + AcquireSRWLockExclusive(&FspServiceTableLock); FspServiceTable = ServiceTable; + ReleaseSRWLockExclusive(&FspServiceTableLock); if (!StartServiceCtrlDispatcherW(ServiceTable)) { @@ -331,11 +343,42 @@ FSP_API NTSTATUS FspServiceLoop(FSP_SERVICE *Service) Result = STATUS_SUCCESS; exit: + AcquireSRWLockExclusive(&FspServiceTableLock); FspServiceTable = 0; + ReleaseSRWLockExclusive(&FspServiceTableLock); + + ReleaseSRWLockExclusive(&FspServiceLoopLock); return Result; } +static DWORD WINAPI FspServiceStopLoopThread(PVOID Context); +VOID FspServiceStopLoop(VOID) +{ + BOOLEAN HasService; + HANDLE Thread; + + AcquireSRWLockShared(&FspServiceTableLock); + HasService = 0 != FspServiceFromTable(); + ReleaseSRWLockShared(&FspServiceTableLock); + + if (HasService) + { + Thread = CreateThread(0, 0, FspServiceStopLoopThread, 0, 0, 0); + if (0 != Thread) + CloseHandle(Thread); + } +} +static DWORD WINAPI FspServiceStopLoopThread(PVOID Context) +{ + AcquireSRWLockShared(&FspServiceTableLock); + FSP_SERVICE *Service = FspServiceFromTable(); + if (0 != Service) + FspServiceStop(Service); + ReleaseSRWLockShared(&FspServiceTableLock); + return 0; +} + FSP_API VOID FspServiceStop(FSP_SERVICE *Service) { SERVICE_STATUS ServiceStatus; @@ -393,6 +436,7 @@ static VOID WINAPI FspServiceEntry(DWORD Argc, PWSTR *Argv) FSP_SERVICE *Service; Service = FspServiceFromTable(); + /* we are subordinate to FspServiceLoop; no need to protect this access with FspServiceTableLock */ if (0 == Service) { FspServiceLog(EVENTLOG_ERROR_TYPE, @@ -501,6 +545,7 @@ static DWORD WINAPI FspServiceConsoleModeThread(PVOID Context) ; Service = FspServiceFromTable(); + /* we are subordinate to FspServiceLoop; no need to protect this access with FspServiceTableLock */ if (0 == Service) FspServiceLog(EVENTLOG_ERROR_TYPE, L"" __FUNCTION__ ": internal error: FspServiceFromTable = 0"); diff --git a/src/dotnet/FileSystemBase.cs b/src/dotnet/FileSystemBase.cs index 0d45646d..53149c6c 100644 --- a/src/dotnet/FileSystemBase.cs +++ b/src/dotnet/FileSystemBase.cs @@ -1188,6 +1188,39 @@ namespace Fsp { return STATUS_INVALID_DEVICE_REQUEST; } + /// + /// Inform the file system that its dispatcher has been stopped. + /// + /// + /// + /// Prior to WinFsp v2.0 the FSD would never unmount a file system volume unless + /// the user mode file system requested the unmount. Since WinFsp v2.0 it is possible + /// for the FSD to unmount a file system volume without an explicit user mode file system + /// request. For example, this happens when the FSD is being uninstalled. + /// + /// A user mode file system can use this operation to determine when its dispatcher + /// has been stopped. The Normally parameter can be used to determine why the dispatcher + /// was stopped: it is TRUE when the file system is being stopped normally (i.e. via the + /// native FspFileSystemStopDispatcher) and FALSE otherwise. + /// + /// A file system that uses the Service class infrastructure may use the + /// StopServiceIfNecessary method to correctly handle all cases. The base implementation + /// of this method calls the StopServiceIfNecessary method. + /// + /// This operation is the last one that a file system will receive. + /// + /// + /// + /// TRUE if the file system is being stopped via the native FspFileSystemStopDispatcher. + /// FALSE if the file system is being stopped because of another reason such + /// as driver unload/uninstall. + /// + /// + public virtual void DispatcherStopped( + Boolean Normally) + { + StopServiceIfNecessary(Normally); + } /* helpers */ /// @@ -1483,6 +1516,10 @@ namespace Fsp { return FullEaInformation.PackedSize(EaName, EaValue, NeedEa); } + public void StopServiceIfNecessary(Boolean Normally) + { + Api.FspFileSystemStopServiceIfNecessary(IntPtr.Zero, Normally); + } } } diff --git a/src/dotnet/FileSystemHost.cs b/src/dotnet/FileSystemHost.cs index 2b59b03e..db600603 100644 --- a/src/dotnet/FileSystemHost.cs +++ b/src/dotnet/FileSystemHost.cs @@ -1426,6 +1426,21 @@ namespace Fsp } } + private static void DispatcherStopped( + IntPtr FileSystemPtr, + Boolean Normally) + { + FileSystemBase FileSystem = (FileSystemBase)Api.GetUserContext(FileSystemPtr); + try + { + FileSystem.DispatcherStopped(Normally); + } + catch (Exception ex) + { + ExceptionHandler(FileSystem, ex); + } + } + static FileSystemHost() { _FileSystemInterface.GetVolumeInfo = GetVolumeInfo; @@ -1456,6 +1471,7 @@ namespace Fsp _FileSystemInterface.SetDelete = SetDelete; _FileSystemInterface.GetEa = GetEa; _FileSystemInterface.SetEa = SetEa; + _FileSystemInterface.DispatcherStopped = DispatcherStopped; _FileSystemInterfacePtr = Marshal.AllocHGlobal(FileSystemInterface.Size); /* Marshal.AllocHGlobal does not zero memory; we must do it ourselves! */ diff --git a/src/dotnet/Interop.cs b/src/dotnet/Interop.cs index bcd58305..2f81d5e7 100644 --- a/src/dotnet/Interop.cs +++ b/src/dotnet/Interop.cs @@ -745,6 +745,10 @@ namespace Fsp.Interop out FileInfo FileInfo); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate Int32 Obsolete0(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void DispatcherStopped( + IntPtr FileSystem, + [MarshalAs(UnmanagedType.U1)] Boolean Normally); } internal static int Size = IntPtr.Size * 64; @@ -781,6 +785,7 @@ namespace Fsp.Interop internal Proto.GetEa GetEa; internal Proto.SetEa SetEa; internal Proto.Obsolete0 Obsolete0; + internal Proto.DispatcherStopped DispatcherStopped; /* NTSTATUS (*Reserved[33])(); */ } @@ -907,6 +912,10 @@ namespace Fsp.Interop UInt32 Length, out UInt32 PBytesTransferred); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void FspFileSystemStopServiceIfNecessary( + IntPtr FileSystem, + [MarshalAs(UnmanagedType.U1)] Boolean Normally); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.U1)] internal delegate Boolean FspFileSystemAcquireDirectoryBuffer( ref IntPtr PDirBuffer, @@ -1048,6 +1057,7 @@ namespace Fsp.Interop internal static Proto.FspFileSystemAddStreamInfo _FspFileSystemAddStreamInfo; internal static Proto.FspFileSystemAddEa _FspFileSystemAddEa; internal static Proto.FspFileSystemAddNotifyInfo _FspFileSystemAddNotifyInfo; + internal static Proto.FspFileSystemStopServiceIfNecessary FspFileSystemStopServiceIfNecessary; internal static Proto.FspFileSystemAcquireDirectoryBuffer FspFileSystemAcquireDirectoryBuffer; internal static Proto.FspFileSystemFillDirectoryBuffer FspFileSystemFillDirectoryBuffer; internal static Proto.FspFileSystemReleaseDirectoryBuffer FspFileSystemReleaseDirectoryBuffer; @@ -1506,6 +1516,7 @@ namespace Fsp.Interop _FspFileSystemAddStreamInfo = GetEntryPoint(Module); _FspFileSystemAddEa = GetEntryPoint(Module); _FspFileSystemAddNotifyInfo = GetEntryPoint(Module); + FspFileSystemStopServiceIfNecessary = GetEntryPoint(Module); FspFileSystemAcquireDirectoryBuffer = GetEntryPoint(Module); FspFileSystemFillDirectoryBuffer = GetEntryPoint(Module); FspFileSystemReleaseDirectoryBuffer = GetEntryPoint(Module); diff --git a/tst/memfs/memfs.cpp b/tst/memfs/memfs.cpp index 5bed0407..aa7d07bb 100644 --- a/tst/memfs/memfs.cpp +++ b/tst/memfs/memfs.cpp @@ -2275,10 +2275,10 @@ static NTSTATUS SetEa(FSP_FILE_SYSTEM *FileSystem, #endif #if defined(MEMFS_DISPATCHER_STOPPED) -static void DispatcherStopped(FSP_FILE_SYSTEM *FileSystem, +static VOID DispatcherStopped(FSP_FILE_SYSTEM *FileSystem, BOOLEAN Normally) { - //FspDebugLog(__FUNCTION__ ": Normally=%d\n", Normally); + FspFileSystemStopServiceIfNecessary(FileSystem, Normally); } #endif diff --git a/tst/ntptfs/ptfs.c b/tst/ntptfs/ptfs.c index e834b355..51ba3eee 100644 --- a/tst/ntptfs/ptfs.c +++ b/tst/ntptfs/ptfs.c @@ -1143,6 +1143,12 @@ exit: return Result; } +static VOID DispatcherStopped(FSP_FILE_SYSTEM *FileSystem, + BOOLEAN Normally) +{ + FspFileSystemStopServiceIfNecessary(FileSystem, Normally); +} + static FSP_FILE_SYSTEM_INTERFACE PtfsInterface = { .GetVolumeInfo = GetVolumeInfo, @@ -1171,6 +1177,7 @@ static FSP_FILE_SYSTEM_INTERFACE PtfsInterface = .GetStreamInfo = GetStreamInfo, .GetEa = GetEa, .SetEa = SetEa, + .DispatcherStopped = DispatcherStopped, }; NTSTATUS PtfsCreate(