winfsp/src/launcher/launcher.c
2016-05-11 13:31:07 -07:00

460 lines
12 KiB
C

/**
* @file launcher.c
*
* @copyright 2015-2016 Bill Zissimopoulos
*/
/*
* This file is part of WinFsp.
*
* You can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License version 3 as published by the
* Free Software Foundation.
*
* Licensees holding a valid commercial license may use this file in
* accordance with the commercial license agreement provided with the
* software.
*/
#include <launcher/launcher.h>
#define PROGNAME "WinFsp-Launcher"
#define REGKEY "SYSTEM\\CurrentControlSet\\Services\\" PROGNAME "\\Services"
HANDLE ProcessHeap;
typedef struct
{
PWSTR ClassName;
PWSTR InstanceName;
PWSTR CommandLine;
DWORD ProcessId;
HANDLE Process;
HANDLE ProcessWait;
LIST_ENTRY ListEntry;
WCHAR Buffer[];
} SVC_INSTANCE;
static CRITICAL_SECTION SvcInstanceLock;
static LIST_ENTRY SvcInstanceList = { &SvcInstanceList, &SvcInstanceList };
static VOID CALLBACK SvcInstanceTerminated(PVOID Context, BOOLEAN Fired);
static SVC_INSTANCE *SvcInstanceFromName(PWSTR InstanceName)
{
SVC_INSTANCE *SvcInstance;
PLIST_ENTRY ListEntry;
for (ListEntry = SvcInstanceList.Flink;
&SvcInstanceList != ListEntry;
ListEntry = ListEntry->Flink)
{
SvcInstance = CONTAINING_RECORD(ListEntry, SVC_INSTANCE, ListEntry);
if (0 == lstrcmpW(InstanceName, SvcInstance->InstanceName))
return SvcInstance;
}
return 0;
}
static ULONG SvcInstanceArgumentLength(PWSTR Arg)
{
ULONG Length;
Length = 2; /* for beginning and ending quotes */
for (PWSTR P = Arg; *P; P++)
if (L'"' != *P)
Length++;
return Length;
}
static PWSTR SvcInstanceArgumentCopy(PWSTR Dest, PWSTR Arg)
{
*Dest++ = L'"';
for (PWSTR P = Arg; *P; P++)
if (L'"' != *P)
*Dest++ = *P;
*Dest++ = L'"';
return Dest;
}
static NTSTATUS SvcInstanceReplaceArguments(PWSTR String, ULONG Argc, PWSTR *Argv,
PWSTR *PNewString)
{
PWSTR NewString = 0;
ULONG Length;
*PNewString = 0;
Length = 0;
for (PWSTR P = String; *P; P++)
{
switch (*P)
{
case L'%':
P++;
if (L'0' <= *P && *P <= '9' && Argc > (ULONG)(*P - L'0'))
Length += SvcInstanceArgumentLength(Argv[*P - L'0']);
break;
default:
Length++;
break;
}
}
NewString = MemAlloc((Length + 1) * sizeof(WCHAR));
if (0 == NewString)
return STATUS_INSUFFICIENT_RESOURCES;
*PNewString = NewString;
for (PWSTR P = String, Q = NewString; *P; P++)
{
switch (*P)
{
case L'%':
P++;
if (L'0' <= *P && *P <= '9' && Argc > (ULONG)(*P - L'0'))
Q = SvcInstanceArgumentCopy(Q, Argv[*P - L'0']);
break;
default:
Q++;
break;
}
}
return STATUS_SUCCESS;
}
NTSTATUS SvcInstanceCreate(PWSTR ClassName, PWSTR InstanceName, ULONG Argc, PWSTR *Argv,
SVC_INSTANCE **PSvcInstance)
{
SVC_INSTANCE *SvcInstance = 0;
HKEY RegKey = 0;
DWORD RegResult, RegSize;
DWORD ClassNameSize, InstanceNameSize;
WCHAR Executable[MAX_PATH], CommandLine[512];
STARTUPINFOW StartupInfo;
PROCESS_INFORMATION ProcessInfo;
NTSTATUS Result;
*PSvcInstance = 0;
EnterCriticalSection(&SvcInstanceLock);
if (0 != SvcInstanceFromName(InstanceName))
{
Result = STATUS_OBJECT_NAME_COLLISION;
goto exit;
}
RegResult = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"" REGKEY, 0, KEY_READ, &RegKey);
if (ERROR_SUCCESS != RegResult)
{
Result = FspNtStatusFromWin32(RegResult);
goto exit;
}
RegSize = sizeof Executable;
RegResult = RegGetValueW(RegKey, ClassName, L"Executable", RRF_RT_REG_SZ, 0,
&Executable, &RegSize);
if (ERROR_SUCCESS != RegResult)
{
Result = FspNtStatusFromWin32(RegResult);
goto exit;
}
RegSize = sizeof CommandLine;
RegResult = RegGetValueW(RegKey, ClassName, L"CommandLine", RRF_RT_REG_SZ, 0,
&CommandLine, &RegSize);
if (ERROR_SUCCESS != RegResult)
{
Result = FspNtStatusFromWin32(RegResult);
goto exit;
}
RegCloseKey(RegKey);
RegKey = 0;
ClassNameSize = (lstrlenW(ClassName) + 1) * sizeof(WCHAR);
InstanceNameSize = (lstrlenW(InstanceName) + 1) * sizeof(WCHAR);
SvcInstance = MemAlloc(sizeof *SvcInstance + ClassNameSize + InstanceNameSize);
if (0 == SvcInstance)
{
Result = STATUS_INSUFFICIENT_RESOURCES;
goto exit;
}
memset(SvcInstance, 0, sizeof *SvcInstance);
memcpy(SvcInstance->Buffer, ClassName, ClassNameSize);
memcpy(SvcInstance->Buffer + ClassNameSize / sizeof(WCHAR), InstanceName, InstanceNameSize);
SvcInstance->ClassName = SvcInstance->Buffer;
SvcInstance->InstanceName = SvcInstance->Buffer + ClassNameSize / sizeof(WCHAR);
Result = SvcInstanceReplaceArguments(CommandLine, Argc, Argv, &SvcInstance->CommandLine);
if (!NT_SUCCESS(Result))
goto exit;
memset(&StartupInfo, 0, sizeof StartupInfo);
StartupInfo.cb = sizeof StartupInfo;
if (!CreateProcessW(0, SvcInstance->CommandLine, 0, 0, FALSE, CREATE_NEW_PROCESS_GROUP, 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;
}
InsertTailList(&SvcInstanceList, &SvcInstance->ListEntry);
*PSvcInstance = SvcInstance;
Result = STATUS_SUCCESS;
exit:
if (!NT_SUCCESS(Result))
{
MemFree(SvcInstance->CommandLine);
MemFree(SvcInstance);
}
if (0 != RegKey)
RegCloseKey(RegKey);
LeaveCriticalSection(&SvcInstanceLock);
return Result;
}
VOID SvcInstanceDelete(SVC_INSTANCE *SvcInstance)
{
EnterCriticalSection(&SvcInstanceLock);
RemoveEntryList(&SvcInstance->ListEntry);
LeaveCriticalSection(&SvcInstanceLock);
if (0 != SvcInstance->ProcessWait)
UnregisterWaitEx(SvcInstance->ProcessWait, 0);
if (0 != SvcInstance->Process)
CloseHandle(SvcInstance->Process);
MemFree(SvcInstance->CommandLine);
MemFree(SvcInstance);
}
static VOID CALLBACK SvcInstanceTerminated(PVOID Context, BOOLEAN Fired)
{
SVC_INSTANCE *SvcInstance = Context;
SvcInstanceDelete(SvcInstance);
}
NTSTATUS SvcInstanceStart(PWSTR ClassName, PWSTR InstanceName, ULONG Argc, PWSTR *Argv)
{
SVC_INSTANCE *SvcInstance;
return SvcInstanceCreate(ClassName, InstanceName, Argc, Argv, &SvcInstance);
}
VOID SvcInstanceStop(PWSTR InstanceName)
{
SVC_INSTANCE *SvcInstance;
EnterCriticalSection(&SvcInstanceLock);
SvcInstance = SvcInstanceFromName(InstanceName);
if (0 != SvcInstance)
GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, SvcInstance->ProcessId);
LeaveCriticalSection(&SvcInstanceLock);
}
static HANDLE SvcThread, SvcEvent;
static HANDLE SvcPipe = INVALID_HANDLE_VALUE;
static OVERLAPPED SvcOverlapped;
static DWORD WINAPI SvcPipeServer(PVOID Context);
static NTSTATUS SvcStart(FSP_SERVICE *Service, ULONG argc, PWSTR *argv)
{
InitializeCriticalSection(&SvcInstanceLock);
SvcEvent = CreateEventW(0, TRUE, FALSE, 0);
if (0 == SvcEvent)
goto fail;
SvcOverlapped.hEvent = CreateEventW(0, TRUE, FALSE, 0);
if (0 == SvcOverlapped.hEvent)
goto fail;
SvcPipe = CreateNamedPipeW(L"" PIPE_NAME,
PIPE_ACCESS_DUPLEX |
FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_WRITE_THROUGH | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS,
1, PIPE_SRVBUF_SIZE, PIPE_CLIBUF_SIZE, 0, 0);
if (INVALID_HANDLE_VALUE == SvcPipe)
goto fail;
SvcThread = CreateThread(0, 0, SvcPipeServer, Service, 0, 0);
if (0 == SvcThread)
goto fail;
return STATUS_SUCCESS;
fail:
DWORD LastError = GetLastError();
if (0 != SvcThread)
CloseHandle(SvcThread);
if (INVALID_HANDLE_VALUE != SvcPipe)
CloseHandle(SvcPipe);
if (0 != SvcOverlapped.hEvent)
CloseHandle(SvcOverlapped.hEvent);
if (0 != SvcEvent)
CloseHandle(SvcEvent);
DeleteCriticalSection(&SvcInstanceLock);
return FspNtStatusFromWin32(LastError);
}
static NTSTATUS SvcStop(FSP_SERVICE *Service)
{
SetEvent(SvcEvent);
FspServiceRequestTime(Service, 4500); /* just under 5 sec */
WaitForSingleObject(SvcThread, 4500);
if (0 != SvcThread)
CloseHandle(SvcThread);
if (INVALID_HANDLE_VALUE != SvcPipe)
CloseHandle(SvcPipe);
if (0 != SvcOverlapped.hEvent)
CloseHandle(SvcOverlapped.hEvent);
if (0 != SvcEvent)
CloseHandle(SvcEvent);
DeleteCriticalSection(&SvcInstanceLock);
return STATUS_SUCCESS;
}
static inline DWORD SvcPipeWaitResult(BOOL Success, HANDLE StopEvent,
HANDLE Handle, OVERLAPPED *Overlapped, PDWORD PBytesTransferred)
{
HANDLE WaitObjects[2];
DWORD WaitResult;
if (!Success && ERROR_IO_PENDING != GetLastError())
return GetLastError();
WaitObjects[0] = StopEvent;
WaitObjects[1] = Overlapped->hEvent;
WaitResult = WaitForMultipleObjects(2, WaitObjects, FALSE, INFINITE);
if (WAIT_OBJECT_0 == WaitResult)
return -1; /* special: stop thread */
else if (WAIT_OBJECT_0 + 1 == WaitResult)
{
if (!GetOverlappedResult(Handle, Overlapped, PBytesTransferred, TRUE))
return GetLastError();
return 0;
}
else
return GetLastError();
}
static DWORD WINAPI SvcPipeServer(PVOID Context)
{
FSP_SERVICE *Service = Context;
PWSTR PipeBuf = 0;
DWORD LastError, BytesTransferred;
PipeBuf = MemAlloc(PIPE_SRVBUF_SIZE);
if (0 == PipeBuf)
{
FspServiceSetExitCode(Service, ERROR_NO_SYSTEM_RESOURCES);
goto exit;
}
for (;;)
{
LastError = SvcPipeWaitResult(
ConnectNamedPipe(SvcPipe, &SvcOverlapped),
SvcEvent, SvcPipe, &SvcOverlapped, &BytesTransferred);
if (-1 == LastError)
break;
else if (ERROR_PIPE_CONNECTED != LastError && ERROR_NO_DATA != LastError)
{
FspServiceLog(EVENTLOG_WARNING_TYPE,
L"Error in service main loop (ConnectNamedPipe = %ld). Continuing...", LastError);
continue;
}
LastError = SvcPipeWaitResult(
ReadFile(SvcPipe, PipeBuf, PIPE_SRVBUF_SIZE, &BytesTransferred, &SvcOverlapped),
SvcEvent, SvcPipe, &SvcOverlapped, &BytesTransferred);
if (-1 == LastError)
break;
else if (0 != LastError)
{
DisconnectNamedPipe(SvcPipe);
FspServiceLog(EVENTLOG_WARNING_TYPE,
L"Error in service main loop (ReadFile = %ld). Continuing...", LastError);
continue;
}
/* handle PipeBuf */
LastError = SvcPipeWaitResult(
WriteFile(SvcPipe, PipeBuf, PIPE_SRVBUF_SIZE, &BytesTransferred, &SvcOverlapped),
SvcEvent, SvcPipe, &SvcOverlapped, &BytesTransferred);
if (-1 == LastError)
break;
else if (0 != LastError)
{
DisconnectNamedPipe(SvcPipe);
FspServiceLog(EVENTLOG_WARNING_TYPE,
L"Error in service main loop (WriteFile = %ld). Continuing...", LastError);
continue;
}
DisconnectNamedPipe(SvcPipe);
}
exit:
MemFree(PipeBuf);
FspServiceStop(Service);
return 0;
}
int wmain(int argc, wchar_t **argv)
{
ProcessHeap = GetProcessHeap();
if (0 == ProcessHeap)
return GetLastError();
return FspServiceRun(L"" PROGNAME, SvcStart, SvcStop, 0);
}
int wmainCRTStartup(void)
{
return wmain(0, 0);
}