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(