mirror of
https://github.com/winfsp/winfsp.git
synced 2025-04-22 16:33:02 -05:00
launcher: better handling of service instance stopping:
- instances are now launched in a job so that they get killed if the parent process dies - instances are killed after a timeout if they do not respond timely to console control events
This commit is contained in:
parent
147c90be9f
commit
3035ba2847
@ -21,6 +21,63 @@
|
||||
#define PROGNAME "WinFsp.Launcher"
|
||||
#define REGKEY "SYSTEM\\CurrentControlSet\\Services\\" PROGNAME "\\Services"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
HANDLE Process;
|
||||
HANDLE ProcessWait;
|
||||
} KILL_PROCESS_DATA;
|
||||
|
||||
static VOID CALLBACK KillProcessWait(PVOID Context, BOOLEAN Timeout);
|
||||
|
||||
VOID KillProcess(ULONG ProcessId, HANDLE Process, ULONG Timeout)
|
||||
{
|
||||
if (GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, ProcessId))
|
||||
{
|
||||
/*
|
||||
* If GenerateConsoleCtrlEvent succeeds, but the child process does not exit
|
||||
* timely we will terminate it with extreme prejudice. This is done by calling
|
||||
* RegisterWaitForSingleObject with timeout on a duplicated process handle.
|
||||
*
|
||||
* If GenerateConsoleCtrlEvent succeeds, but we are not able to successfully call
|
||||
* RegisterWaitForSingleObject, we do NOT terminate the child process forcibly.
|
||||
* This is by design as it is not the child process's fault and the child process
|
||||
* should (we hope in this case) respond to the console control event timely.
|
||||
*/
|
||||
|
||||
KILL_PROCESS_DATA *KillProcessData;
|
||||
|
||||
KillProcessData = MemAlloc(sizeof *KillProcessData);
|
||||
if (0 != KillProcessData)
|
||||
{
|
||||
if (DuplicateHandle(GetCurrentProcess(), Process, GetCurrentProcess(), &KillProcessData->Process,
|
||||
0, FALSE, DUPLICATE_SAME_ACCESS))
|
||||
{
|
||||
if (RegisterWaitForSingleObject(&KillProcessData->ProcessWait, KillProcessData->Process,
|
||||
KillProcessWait, KillProcessData, Timeout, WT_EXECUTEONLYONCE))
|
||||
KillProcessData = 0;
|
||||
else
|
||||
CloseHandle(KillProcessData->Process);
|
||||
}
|
||||
|
||||
MemFree(KillProcessData);
|
||||
}
|
||||
}
|
||||
else
|
||||
TerminateProcess(Process, 0);
|
||||
}
|
||||
|
||||
static VOID CALLBACK KillProcessWait(PVOID Context, BOOLEAN Timeout)
|
||||
{
|
||||
KILL_PROCESS_DATA *KillProcessData = Context;
|
||||
|
||||
if (Timeout)
|
||||
TerminateProcess(KillProcessData->Process, 0);
|
||||
|
||||
UnregisterWaitEx(KillProcessData->ProcessWait, 0);
|
||||
CloseHandle(KillProcessData->Process);
|
||||
MemFree(KillProcessData);
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
PWSTR ClassName;
|
||||
@ -35,9 +92,10 @@ typedef struct
|
||||
} SVC_INSTANCE;
|
||||
|
||||
static CRITICAL_SECTION SvcInstanceLock;
|
||||
static HANDLE SvcInstanceEvent;
|
||||
static LIST_ENTRY SvcInstanceList = { &SvcInstanceList, &SvcInstanceList };
|
||||
|
||||
static VOID CALLBACK SvcInstanceTerminated(PVOID Context, BOOLEAN Fired);
|
||||
static VOID CALLBACK SvcInstanceTerminated(PVOID Context, BOOLEAN Timeout);
|
||||
|
||||
static SVC_INSTANCE *SvcInstanceLookup(PWSTR ClassName, PWSTR InstanceName)
|
||||
{
|
||||
@ -174,7 +232,7 @@ static NTSTATUS SvcInstanceAccessCheck(HANDLE ClientToken, ULONG DesiredAccess,
|
||||
}
|
||||
|
||||
NTSTATUS SvcInstanceCreate(HANDLE ClientToken,
|
||||
PWSTR ClassName, PWSTR InstanceName, ULONG Argc, PWSTR *Argv0,
|
||||
PWSTR ClassName, PWSTR InstanceName, ULONG Argc, PWSTR *Argv0, HANDLE Job,
|
||||
SVC_INSTANCE **PSvcInstance)
|
||||
{
|
||||
SVC_INSTANCE *SvcInstance = 0;
|
||||
@ -198,6 +256,9 @@ NTSTATUS SvcInstanceCreate(HANDLE ClientToken,
|
||||
Argv[0] = 0;
|
||||
Argc++;
|
||||
|
||||
memset(&StartupInfo, 0, sizeof StartupInfo);
|
||||
memset(&ProcessInfo, 0, sizeof ProcessInfo);
|
||||
|
||||
EnterCriticalSection(&SvcInstanceLock);
|
||||
|
||||
if (0 != SvcInstanceLookup(ClassName, InstanceName))
|
||||
@ -289,30 +350,41 @@ NTSTATUS SvcInstanceCreate(HANDLE ClientToken,
|
||||
if (!NT_SUCCESS(Result))
|
||||
goto exit;
|
||||
|
||||
memset(&StartupInfo, 0, sizeof StartupInfo);
|
||||
StartupInfo.cb = sizeof StartupInfo;
|
||||
if (!CreateProcessW(Executable, SvcInstance->CommandLine, 0, 0, FALSE, CREATE_NEW_PROCESS_GROUP, 0, 0,
|
||||
&StartupInfo, &ProcessInfo))
|
||||
if (!CreateProcessW(Executable, SvcInstance->CommandLine, 0, 0, FALSE,
|
||||
CREATE_NEW_PROCESS_GROUP | CREATE_SUSPENDED, 0, 0, &StartupInfo, &ProcessInfo))
|
||||
{
|
||||
Result = FspNtStatusFromWin32(GetLastError());
|
||||
goto exit;
|
||||
}
|
||||
|
||||
CloseHandle(ProcessInfo.hThread);
|
||||
SvcInstance->ProcessId = ProcessInfo.dwProcessId;
|
||||
SvcInstance->Process = ProcessInfo.hProcess;
|
||||
|
||||
if (!RegisterWaitForSingleObject(&SvcInstance->ProcessWait, SvcInstance->Process,
|
||||
SvcInstanceTerminated, SvcInstance, INFINITE, WT_EXECUTEONLYONCE))
|
||||
{
|
||||
/* we have no way when the new process will terminate so go ahead and close its handle */
|
||||
FspServiceLog(EVENTLOG_WARNING_TYPE,
|
||||
L"RegisterWaitForSingleObject = %ld", GetLastError());
|
||||
CloseHandle(SvcInstance->Process);
|
||||
SvcInstance->Process = 0;
|
||||
Result = FspNtStatusFromWin32(GetLastError());
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (0 != Job)
|
||||
{
|
||||
if (!AssignProcessToJobObject(Job, SvcInstance->Process))
|
||||
FspServiceLog(EVENTLOG_WARNING_TYPE,
|
||||
L"Ignorning error: AssignProcessToJobObject = %ld", GetLastError());
|
||||
}
|
||||
|
||||
/*
|
||||
* ONCE THE PROCESS IS RESUMED NO MORE FAILURES ALLOWED!
|
||||
*/
|
||||
|
||||
ResumeThread(ProcessInfo.hThread);
|
||||
CloseHandle(ProcessInfo.hThread);
|
||||
ProcessInfo.hThread = 0;
|
||||
|
||||
InsertTailList(&SvcInstanceList, &SvcInstance->ListEntry);
|
||||
ResetEvent(SvcInstanceEvent);
|
||||
|
||||
*PSvcInstance = SvcInstance;
|
||||
|
||||
@ -322,8 +394,21 @@ exit:
|
||||
if (!NT_SUCCESS(Result))
|
||||
{
|
||||
LocalFree(SecurityDescriptor);
|
||||
|
||||
if (0 != ProcessInfo.hThread)
|
||||
CloseHandle(ProcessInfo.hThread);
|
||||
|
||||
if (0 != SvcInstance)
|
||||
{
|
||||
if (0 != SvcInstance->ProcessWait)
|
||||
UnregisterWaitEx(SvcInstance->ProcessWait, 0);
|
||||
|
||||
if (0 != SvcInstance->Process)
|
||||
{
|
||||
TerminateProcess(SvcInstance->Process, 0);
|
||||
CloseHandle(SvcInstance->Process);
|
||||
}
|
||||
|
||||
MemFree(SvcInstance->CommandLine);
|
||||
MemFree(SvcInstance);
|
||||
}
|
||||
@ -340,19 +425,22 @@ exit:
|
||||
VOID SvcInstanceDelete(SVC_INSTANCE *SvcInstance)
|
||||
{
|
||||
EnterCriticalSection(&SvcInstanceLock);
|
||||
RemoveEntryList(&SvcInstance->ListEntry);
|
||||
if (RemoveEntryList(&SvcInstance->ListEntry))
|
||||
SetEvent(SvcInstanceEvent);
|
||||
LeaveCriticalSection(&SvcInstanceLock);
|
||||
|
||||
if (0 != SvcInstance->ProcessWait)
|
||||
UnregisterWaitEx(SvcInstance->ProcessWait, 0);
|
||||
if (0 != SvcInstance->Process)
|
||||
CloseHandle(SvcInstance->Process);
|
||||
|
||||
LocalFree(SvcInstance->SecurityDescriptor);
|
||||
|
||||
MemFree(SvcInstance->CommandLine);
|
||||
MemFree(SvcInstance);
|
||||
}
|
||||
|
||||
static VOID CALLBACK SvcInstanceTerminated(PVOID Context, BOOLEAN Fired)
|
||||
static VOID CALLBACK SvcInstanceTerminated(PVOID Context, BOOLEAN Timeout)
|
||||
{
|
||||
SVC_INSTANCE *SvcInstance = Context;
|
||||
|
||||
@ -360,11 +448,11 @@ static VOID CALLBACK SvcInstanceTerminated(PVOID Context, BOOLEAN Fired)
|
||||
}
|
||||
|
||||
NTSTATUS SvcInstanceStart(HANDLE ClientToken,
|
||||
PWSTR ClassName, PWSTR InstanceName, ULONG Argc, PWSTR *Argv)
|
||||
PWSTR ClassName, PWSTR InstanceName, ULONG Argc, PWSTR *Argv, HANDLE Job)
|
||||
{
|
||||
SVC_INSTANCE *SvcInstance;
|
||||
|
||||
return SvcInstanceCreate(ClientToken, ClassName, InstanceName, Argc, Argv, &SvcInstance);
|
||||
return SvcInstanceCreate(ClientToken, ClassName, InstanceName, Argc, Argv, Job, &SvcInstance);
|
||||
}
|
||||
|
||||
NTSTATUS SvcInstanceStop(HANDLE ClientToken,
|
||||
@ -386,11 +474,7 @@ NTSTATUS SvcInstanceStop(HANDLE ClientToken,
|
||||
if (!NT_SUCCESS(Result))
|
||||
goto exit;
|
||||
|
||||
if (!GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, SvcInstance->ProcessId))
|
||||
{
|
||||
Result = FspNtStatusFromWin32(GetLastError());
|
||||
goto exit;
|
||||
}
|
||||
KillProcess(SvcInstance->ProcessId, SvcInstance->Process, STOP_TIMEOUT);
|
||||
|
||||
Result = STATUS_SUCCESS;
|
||||
|
||||
@ -478,7 +562,30 @@ NTSTATUS SvcInstanceGetNameList(HANDLE ClientToken,
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static HANDLE SvcThread, SvcEvent;
|
||||
NTSTATUS SvcInstanceStopAndWaitAll(VOID)
|
||||
{
|
||||
SVC_INSTANCE *SvcInstance;
|
||||
PLIST_ENTRY ListEntry;
|
||||
|
||||
EnterCriticalSection(&SvcInstanceLock);
|
||||
|
||||
for (ListEntry = SvcInstanceList.Flink;
|
||||
&SvcInstanceList != ListEntry;
|
||||
ListEntry = ListEntry->Flink)
|
||||
{
|
||||
SvcInstance = CONTAINING_RECORD(ListEntry, SVC_INSTANCE, ListEntry);
|
||||
|
||||
KillProcess(SvcInstance->ProcessId, SvcInstance->Process, STOP_TIMEOUT);
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&SvcInstanceLock);
|
||||
|
||||
WaitForSingleObject(SvcInstanceEvent, STOP_TIMEOUT);
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static HANDLE SvcJob, SvcThread, SvcEvent;
|
||||
static DWORD SvcThreadId;
|
||||
static HANDLE SvcPipe = INVALID_HANDLE_VALUE;
|
||||
static OVERLAPPED SvcOverlapped;
|
||||
@ -500,6 +607,26 @@ static NTSTATUS SvcStart(FSP_SERVICE *Service, ULONG argc, PWSTR *argv)
|
||||
|
||||
FspDebugLogSD(__FUNCTION__ ": SDDL = %s\n", SecurityAttributes.lpSecurityDescriptor);
|
||||
|
||||
SvcInstanceEvent = CreateEventW(0, TRUE, TRUE, 0);
|
||||
if (0 == SvcInstanceEvent)
|
||||
goto fail;
|
||||
|
||||
SvcJob = CreateJobObjectW(0, 0);
|
||||
if (0 != SvcJob)
|
||||
{
|
||||
JOBOBJECT_EXTENDED_LIMIT_INFORMATION LimitInfo;
|
||||
|
||||
memset(&LimitInfo, 0, sizeof LimitInfo);
|
||||
LimitInfo.BasicLimitInformation.LimitFlags =
|
||||
JOB_OBJECT_LIMIT_BREAKAWAY_OK | JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
||||
if (!SetInformationJobObject(SvcJob, JobObjectExtendedLimitInformation,
|
||||
&LimitInfo, sizeof LimitInfo))
|
||||
{
|
||||
CloseHandle(SvcJob);
|
||||
SvcJob = 0;
|
||||
}
|
||||
}
|
||||
|
||||
SvcEvent = CreateEventW(0, TRUE, FALSE, 0);
|
||||
if (0 == SvcEvent)
|
||||
goto fail;
|
||||
@ -527,6 +654,10 @@ static NTSTATUS SvcStart(FSP_SERVICE *Service, ULONG argc, PWSTR *argv)
|
||||
fail:
|
||||
DWORD LastError = GetLastError();
|
||||
|
||||
/*
|
||||
* The OS will cleanup for us. So there is no need to explicitly release these resources.
|
||||
*/
|
||||
#if 0
|
||||
if (0 != SvcThread)
|
||||
CloseHandle(SvcThread);
|
||||
|
||||
@ -539,9 +670,16 @@ fail:
|
||||
if (0 != SvcEvent)
|
||||
CloseHandle(SvcEvent);
|
||||
|
||||
if (0 != SvcJob)
|
||||
CloseHandle(SvcJob);
|
||||
|
||||
if (0 != SvcInstanceEvent)
|
||||
CloseHandle(SvcInstanceEvent);
|
||||
|
||||
LocalFree(SecurityAttributes.lpSecurityDescriptor);
|
||||
|
||||
DeleteCriticalSection(&SvcInstanceLock);
|
||||
#endif
|
||||
|
||||
return FspNtStatusFromWin32(LastError);
|
||||
}
|
||||
@ -551,10 +689,18 @@ static NTSTATUS SvcStop(FSP_SERVICE *Service)
|
||||
if (GetCurrentThreadId() != SvcThreadId)
|
||||
{
|
||||
SetEvent(SvcEvent);
|
||||
FspServiceRequestTime(Service, 4500); /* just under 5 sec */
|
||||
WaitForSingleObject(SvcThread, 4500);
|
||||
FspServiceRequestTime(Service, STOP_TIMEOUT);
|
||||
WaitForSingleObject(SvcThread, STOP_TIMEOUT);
|
||||
}
|
||||
|
||||
/*
|
||||
* The OS will cleanup for us. So there is no need to explicitly release these resources.
|
||||
*
|
||||
* This also protects us from scenarios where not all child processes terminate timely
|
||||
* and KillProcess decides to terminate them forcibly, thus creating racing conditions
|
||||
* with SvcInstanceTerminated.
|
||||
*/
|
||||
#if 0
|
||||
if (0 != SvcThread)
|
||||
CloseHandle(SvcThread);
|
||||
|
||||
@ -567,7 +713,14 @@ static NTSTATUS SvcStop(FSP_SERVICE *Service)
|
||||
if (0 != SvcEvent)
|
||||
CloseHandle(SvcEvent);
|
||||
|
||||
if (0 != SvcJob)
|
||||
CloseHandle(SvcJob);
|
||||
|
||||
if (0 != SvcInstanceEvent)
|
||||
CloseHandle(SvcInstanceEvent);
|
||||
|
||||
DeleteCriticalSection(&SvcInstanceLock);
|
||||
#endif
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
@ -690,6 +843,8 @@ static DWORD WINAPI SvcPipeServer(PVOID Context)
|
||||
exit:
|
||||
MemFree(PipeBuf);
|
||||
|
||||
SvcInstanceStopAndWaitAll();
|
||||
|
||||
FspServiceStop(Service);
|
||||
|
||||
return 0;
|
||||
@ -748,7 +903,7 @@ static VOID SvcPipeTransact(HANDLE ClientToken, PWSTR PipeBuf, PULONG PSize)
|
||||
|
||||
Result = STATUS_INVALID_PARAMETER;
|
||||
if (0 != ClassName && 0 != InstanceName)
|
||||
Result = SvcInstanceStart(ClientToken, ClassName, InstanceName, Argc, Argv);
|
||||
Result = SvcInstanceStart(ClientToken, ClassName, InstanceName, Argc, Argv, SvcJob);
|
||||
|
||||
SvcPipeTransactResult(Result, PipeBuf, PSize);
|
||||
break;
|
||||
|
@ -21,6 +21,8 @@
|
||||
#include <winfsp/winfsp.h>
|
||||
#include <shared/minimal.h>
|
||||
|
||||
#define STOP_TIMEOUT 5000
|
||||
|
||||
#define PIPE_NAME "\\\\.\\pipe\\WinFsp.{14E7137D-22B4-437A-B0C1-D21D1BDF3767}"
|
||||
#define PIPE_BUFFER_SIZE 2048
|
||||
#define PIPE_DEFAULT_TIMEOUT 3000
|
||||
|
Loading…
x
Reference in New Issue
Block a user