From d7a6f33d26961f2af72d1eb202d611908ac7e500 Mon Sep 17 00:00:00 2001 From: Bill Zissimopoulos Date: Fri, 6 May 2016 11:41:45 -0700 Subject: [PATCH] dll: add FSP_SERVICE and EventLog functionality --- build/VStudio/winfsp_dll.vcxproj | 2 + build/VStudio/winfsp_dll.vcxproj.filters | 6 + inc/winfsp/winfsp.h | 42 +++ src/dll/eventlog.c | 56 +++ src/dll/library.c | 1 + src/dll/library.h | 2 + src/dll/ntstatus.c | 27 ++ src/dll/service.c | 418 +++++++++++++++++++++++ 8 files changed, 554 insertions(+) create mode 100644 src/dll/eventlog.c create mode 100644 src/dll/service.c diff --git a/build/VStudio/winfsp_dll.vcxproj b/build/VStudio/winfsp_dll.vcxproj index 82b3ef51..8b634a97 100644 --- a/build/VStudio/winfsp_dll.vcxproj +++ b/build/VStudio/winfsp_dll.vcxproj @@ -25,6 +25,7 @@ + @@ -34,6 +35,7 @@ + diff --git a/build/VStudio/winfsp_dll.vcxproj.filters b/build/VStudio/winfsp_dll.vcxproj.filters index df52df4a..bc3fc63d 100644 --- a/build/VStudio/winfsp_dll.vcxproj.filters +++ b/build/VStudio/winfsp_dll.vcxproj.filters @@ -52,6 +52,12 @@ Source + + Source + + + Source + diff --git a/inc/winfsp/winfsp.h b/inc/winfsp/winfsp.h index e473feac..451a4fed 100644 --- a/inc/winfsp/winfsp.h +++ b/inc/winfsp/winfsp.h @@ -838,10 +838,52 @@ FSP_API VOID FspPathPrefix(PWSTR Path, PWSTR *PPrefix, PWSTR *PRemain, PWSTR Roo FSP_API VOID FspPathSuffix(PWSTR Path, PWSTR *PRemain, PWSTR *PSuffix, PWSTR Root); FSP_API VOID FspPathCombine(PWSTR Prefix, PWSTR Suffix); +/* + * Service Framework + */ +typedef struct _FSP_SERVICE FSP_SERVICE; +typedef NTSTATUS FSP_SERVICE_START(FSP_SERVICE *, ULONG, PWSTR *); +typedef NTSTATUS FSP_SERVICE_STOP(FSP_SERVICE *); +typedef NTSTATUS FSP_SERVICE_CONTROL(FSP_SERVICE *, ULONG, ULONG, PVOID); +#pragma warning(push) +#pragma warning(disable:4200) /* zero-sized array in struct/union */ +typedef struct _FSP_SERVICE +{ + UINT16 Version; + PVOID UserContext; + FSP_SERVICE_START *OnStart; + FSP_SERVICE_STOP *OnStop; + FSP_SERVICE_CONTROL *OnControl; + ULONG AcceptControl; + ULONG ExitCode; + SERVICE_STATUS_HANDLE StatusHandle; + CRITICAL_SECTION ServiceStatusGuard; + SERVICE_STATUS ServiceStatus; + BOOLEAN AllowInteractive; + HANDLE InteractiveEvent; + WCHAR ServiceName[]; +} FSP_SERVICE; +#pragma warning(pop) +FSP_API NTSTATUS FspServiceCreate(PWSTR ServiceName, + FSP_SERVICE_START *OnStart, + FSP_SERVICE_STOP *OnStop, + FSP_SERVICE_CONTROL *OnControl, + FSP_SERVICE **PService); +FSP_API VOID FspServiceDelete(FSP_SERVICE *Service); +FSP_API VOID FspServiceAllowInteractive(FSP_SERVICE *Service); +FSP_API VOID FspServiceAcceptControl(FSP_SERVICE *Service, ULONG Control); +FSP_API VOID FspServiceRequestTime(FSP_SERVICE *Service, ULONG Time); +FSP_API VOID FspServiceSetExitCode(FSP_SERVICE *Service, ULONG ExitCode); +FSP_API NTSTATUS FspServiceRun(FSP_SERVICE *Service); +FSP_API VOID FspServiceStop(FSP_SERVICE *Service); + /* * Utility */ FSP_API NTSTATUS FspNtStatusFromWin32(DWORD Error); +FSP_API DWORD FspWin32FromNtStatus(NTSTATUS Status); +FSP_API VOID FspEventLog(ULONG Type, PWSTR Format, ...); +FSP_API VOID FspEventLogV(ULONG Type, PWSTR Format, va_list ap); FSP_API VOID FspDebugLog(const char *format, ...); FSP_API VOID FspDebugLogSD(const char *format, PSECURITY_DESCRIPTOR SecurityDescriptor); FSP_API VOID FspDebugLogFT(const char *format, PFILETIME FileTime); diff --git a/src/dll/eventlog.c b/src/dll/eventlog.c new file mode 100644 index 00000000..fb992e9b --- /dev/null +++ b/src/dll/eventlog.c @@ -0,0 +1,56 @@ +/** + * @file dll/eventlog.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 +#include + +static HANDLE FspEventLogHandle; +static INIT_ONCE FspEventLogInitOnce = INIT_ONCE_STATIC_INIT; +static BOOL WINAPI FspEventLogRegisterEventSource( + PINIT_ONCE InitOnce, PVOID Parameter, PVOID *Context); + +FSP_API VOID FspEventLog(ULONG Type, PWSTR Format, ...) +{ + va_list ap; + + va_start(ap, Format); + FspEventLogV(Type, Format, ap); + va_end(ap); +} + +FSP_API VOID FspEventLogV(ULONG Type, PWSTR Format, va_list ap) +{ + InitOnceExecuteOnce(&FspEventLogInitOnce, FspEventLogRegisterEventSource, 0, 0); + if (0 == FspEventLogHandle) + return; + + WCHAR Buf[1024], *Strings[1]; + /* wvsprintfW is only safe with a 1024 WCHAR buffer */ + + wvsprintfW(Buf, Format, ap); + Buf[(sizeof Buf / sizeof Buf[0]) - 1] = L'\0'; + + Strings[0] = Buf; + ReportEventW(FspEventLogHandle, (WORD)Type, 0, 1, 0, 1, 0, Strings, 0); +} + +static BOOL WINAPI FspEventLogRegisterEventSource( + PINIT_ONCE InitOnce, PVOID Parameter, PVOID *Context) +{ + FspEventLogHandle = RegisterEventSourceW(0, L"" LIBRARY_NAME); + return TRUE; +} diff --git a/src/dll/library.c b/src/dll/library.c index 4233120f..ab63664e 100644 --- a/src/dll/library.c +++ b/src/dll/library.c @@ -29,6 +29,7 @@ BOOL WINAPI DllMain(HINSTANCE Instance, DWORD Reason, PVOID Reserved) ProcessHeap = GetProcessHeap(); if (0 == ProcessHeap) return FALSE; + FspNtStatusInitialize(); FspFileSystemInitialize(); break; case DLL_PROCESS_DETACH: diff --git a/src/dll/library.h b/src/dll/library.h index 40aa6c12..c71c3f63 100644 --- a/src/dll/library.h +++ b/src/dll/library.h @@ -103,6 +103,8 @@ BOOLEAN RemoveEntryList(PLIST_ENTRY Entry) return Flink == Blink; } +VOID FspNtStatusInitialize(VOID); + VOID FspFileSystemInitialize(VOID); VOID FspFileSystemFinalize(VOID); diff --git a/src/dll/ntstatus.c b/src/dll/ntstatus.c index a37b0eb6..25a78167 100644 --- a/src/dll/ntstatus.c +++ b/src/dll/ntstatus.c @@ -17,6 +17,25 @@ #include +static ULONG (WINAPI *FspRtlNtStatusToDosError)(NTSTATUS Status); + +VOID FspNtStatusInitialize(VOID) +{ + /* + * This function is called during DLL_PROCESS_ATTACH. We must therefore keep initialization + * tasks to a minimum. + * + * GetModuleHandle/GetProcAddress is allowed (because they are kernel32 API's)! See: + * https://msdn.microsoft.com/en-us/library/windows/desktop/dn633971(v=vs.85).aspx + */ + + HANDLE Handle; + + Handle = GetModuleHandleW(L"ntdll.dll"); + if (0 != Handle) + FspRtlNtStatusToDosError = (PVOID)GetProcAddress(Handle, "RtlNtStatusToDosError"); +} + FSP_API NTSTATUS FspNtStatusFromWin32(DWORD Error) { switch (Error) @@ -26,3 +45,11 @@ FSP_API NTSTATUS FspNtStatusFromWin32(DWORD Error) return STATUS_ACCESS_DENIED; } } + +FSP_API DWORD FspWin32FromNtStatus(NTSTATUS Status) +{ + if (0 == FspRtlNtStatusToDosError) + return ERROR_MR_MID_NOT_FOUND; + + return FspRtlNtStatusToDosError(Status); +} diff --git a/src/dll/service.c b/src/dll/service.c new file mode 100644 index 00000000..6f163611 --- /dev/null +++ b/src/dll/service.c @@ -0,0 +1,418 @@ +/** + * @file dll/service.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 + +// !!!: NOTIMPLEMENTED +#define FspEventLog(Type, Message, ...) + +enum +{ + SetStatus_ServiceType = 0x0001, + SetStatus_CurrentState = 0x0002, + SetStatus_ControlsAccepted = 0x0004, + SetStatus_Win32ExitCode = 0x0008, + SetStatus_ServiceSpecificExitCode = 0x0010, + SetStatus_CheckPoint = 0x0020, + SetStatus_WaitHint = 0x0040, + GetStatus_ServiceType = 0x0100, + GetStatus_CurrentState = 0x0200, + GetStatus_ControlsAccepted = 0x0400, + GetStatus_Win32ExitCode = 0x0800, + GetStatus_ServiceSpecificExitCode = 0x1000, + GetStatus_CheckPoint = 0x2000, + GetStatus_WaitHint = 0x4000, +}; + +static SERVICE_TABLE_ENTRYW *FspServiceTable; +static VOID FspServiceSetStatus(FSP_SERVICE *Service, ULONG Flags, SERVICE_STATUS *ServiceStatus); +static VOID WINAPI FspServiceEntry(DWORD Argc, PWSTR *Argv); +static VOID FspServiceMain(FSP_SERVICE *Service, DWORD Argc, PWSTR *Argv); +static DWORD WINAPI FspServiceCtrlHandler( + DWORD Control, DWORD EventType, PVOID EventData, PVOID Context); +static DWORD WINAPI FspServiceStopRoutine(PVOID Context); +static DWORD WINAPI FspServiceInteractiveThread(PVOID Context); +static BOOL WINAPI FspServiceConsoleCtrlHandler(DWORD CtrlType); + +static inline FSP_SERVICE *FspServiceFromTable(VOID) +{ + FSP_SERVICE *Service = 0; + + if (0 != FspServiceTable) + { + Service = (PVOID)((PUINT8)FspServiceTable[0].lpServiceName - + FIELD_OFFSET(FSP_SERVICE, ServiceName)); + FspServiceTable = 0; + } + + return Service; +} + +FSP_API NTSTATUS FspServiceCreate(PWSTR ServiceName, + FSP_SERVICE_START *OnStart, + FSP_SERVICE_STOP *OnStop, + FSP_SERVICE_CONTROL *OnControl, + FSP_SERVICE **PService) +{ + FSP_SERVICE *Service; + DWORD Size; + + *PService = 0; + + Size = (lstrlenW(ServiceName) + 1) * sizeof(WCHAR); + + Service = MemAlloc(sizeof *Service + Size); + if (0 == Service) + return STATUS_INSUFFICIENT_RESOURCES; + memset(Service, 0, sizeof *Service); + memcpy(Service->ServiceName, ServiceName, Size); + + Service->OnStart = OnStart; + Service->OnStop = OnStop; + Service->OnControl = OnControl; + + Service->AcceptControl = OnStop ? SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN : 0; + + InitializeCriticalSection(&Service->ServiceStatusGuard); + Service->ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + Service->ServiceStatus.dwCurrentState = SERVICE_STOPPED; + Service->ServiceStatus.dwControlsAccepted = 0; + Service->ServiceStatus.dwWin32ExitCode = NO_ERROR; + Service->ServiceStatus.dwServiceSpecificExitCode = 0; + Service->ServiceStatus.dwCheckPoint = 0; + Service->ServiceStatus.dwWaitHint = 0; + + *PService = Service; + + return STATUS_SUCCESS; +} + +FSP_API VOID FspServiceDelete(FSP_SERVICE *Service) +{ + if (0 != Service->InteractiveEvent) + CloseHandle(Service->InteractiveEvent); + + DeleteCriticalSection(&Service->ServiceStatusGuard); + MemFree(Service); +} + +static VOID FspServiceSetStatus(FSP_SERVICE *Service, ULONG Flags, SERVICE_STATUS *ServiceStatus) +{ +#define XCHG(FIELD)\ + if (Flags & SetStatus_##FIELD)\ + {\ + DWORD Temp = ServiceStatus->dw##FIELD;\ + if (Flags & GetStatus_##FIELD)\ + ServiceStatus->dw##FIELD = Service->ServiceStatus.dw##FIELD;\ + Service->ServiceStatus.dw##FIELD = Temp;\ + } + + EnterCriticalSection(&Service->ServiceStatusGuard); + + //XCHG(ServiceType); + XCHG(CurrentState); + XCHG(ControlsAccepted); + XCHG(Win32ExitCode); + //XCHG(ServiceSpecificExitCode); + if (Flags & SetStatus_CheckPoint) + { + DWORD Temp = ServiceStatus->dwCheckPoint; + if (Flags & GetStatus_CheckPoint) + ServiceStatus->dwCheckPoint = Service->ServiceStatus.dwCheckPoint; + + /* treat CheckPoint specially! */ + if (0 == Temp) + Service->ServiceStatus.dwCheckPoint = 0; + else + Service->ServiceStatus.dwCheckPoint += Temp; + } + XCHG(WaitHint); + + if (0 != Service->StatusHandle) + { + if (!SetServiceStatus(Service->StatusHandle, &Service->ServiceStatus)) + FspEventLog(EVENTLOG_ERROR_TYPE, + L"" __FUNCTION__ ": error = %ld", GetLastError()); + } + else if (0 != Service->InteractiveEvent && + SERVICE_STOPPED == Service->ServiceStatus.dwCurrentState) + { + SetEvent(Service->InteractiveEvent); + } + + LeaveCriticalSection(&Service->ServiceStatusGuard); + +#undef XCHG +} + +FSP_API VOID FspServiceAllowInteractive(FSP_SERVICE *Service) +{ + Service->AllowInteractive = TRUE; +} + +FSP_API VOID FspServiceAcceptControl(FSP_SERVICE *Service, ULONG Control) +{ + Service->AcceptControl = Control & ~SERVICE_ACCEPT_PAUSE_CONTINUE; +} + +FSP_API VOID FspServiceRequestTime(FSP_SERVICE *Service, ULONG Time) +{ + SERVICE_STATUS ServiceStatus; + + ServiceStatus.dwCheckPoint = +1; + ServiceStatus.dwWaitHint = Time; + FspServiceSetStatus(Service, + SetStatus_CheckPoint | SetStatus_WaitHint, &ServiceStatus); +} + +FSP_API VOID FspServiceSetExitCode(FSP_SERVICE *Service, ULONG ExitCode) +{ + Service->ExitCode = ExitCode; +} + +FSP_API NTSTATUS FspServiceRun(FSP_SERVICE *Service) +{ + SERVICE_TABLE_ENTRYW ServiceTable[2]; + + ServiceTable[0].lpServiceName = Service->ServiceName; + ServiceTable[0].lpServiceProc = FspServiceEntry; + ServiceTable[1].lpServiceName = 0; + ServiceTable[1].lpServiceProc = 0; + FspServiceTable = ServiceTable; + + if (!StartServiceCtrlDispatcherW(ServiceTable)) + { + HANDLE Thread; + DWORD WaitResult; + DWORD LastError; + + LastError = GetLastError(); + if (!Service->AllowInteractive || ERROR_FAILED_SERVICE_CONTROLLER_CONNECT != LastError) + return FspNtStatusFromWin32(LastError); + + /* enter INTERACTIVE (DEBUG) mode! */ + + Service->InteractiveEvent = CreateEventW(0, TRUE, FALSE, 0); + if (0 == Service->InteractiveEvent) + return FspNtStatusFromWin32(GetLastError()); + + Thread = CreateThread(0, 0, FspServiceInteractiveThread, Service, 0, 0); + if (0 == Thread) + return FspNtStatusFromWin32(GetLastError()); + WaitResult = WaitForSingleObject(Thread, INFINITE); + CloseHandle(Thread); + if (WAIT_OBJECT_0 != WaitResult) + return FspNtStatusFromWin32(GetLastError()); + + if (!SetConsoleCtrlHandler(FspServiceConsoleCtrlHandler, TRUE)) + return FspNtStatusFromWin32(GetLastError()); + + WaitResult = WaitForSingleObject(Service->InteractiveEvent, INFINITE); + if (WAIT_OBJECT_0 != WaitResult) + return FspNtStatusFromWin32(GetLastError()); + } + + return STATUS_SUCCESS; +} + +FSP_API VOID FspServiceStop(FSP_SERVICE *Service) +{ + SERVICE_STATUS ServiceStatus; + NTSTATUS Result; + + ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING; + ServiceStatus.dwCheckPoint = 0; + ServiceStatus.dwWaitHint = 0; + FspServiceSetStatus(Service, + SetStatus_CurrentState | SetStatus_CheckPoint | SetStatus_WaitHint | + GetStatus_CurrentState | GetStatus_CheckPoint | GetStatus_WaitHint, + &ServiceStatus); + + Result = STATUS_SUCCESS; + if (0 != Service->OnStop) + Result = Service->OnStop(Service); + + if (NT_SUCCESS(Result)) + { + ServiceStatus.dwCurrentState = SERVICE_STOPPED; + ServiceStatus.dwWin32ExitCode = Service->ExitCode; + FspServiceSetStatus(Service, + SetStatus_CurrentState | SetStatus_Win32ExitCode, &ServiceStatus); + } + else + { + /* revert the service status */ + FspServiceSetStatus(Service, + SetStatus_CurrentState | SetStatus_CheckPoint | SetStatus_WaitHint, + &ServiceStatus); + } +} + +static VOID WINAPI FspServiceEntry(DWORD Argc, PWSTR *Argv) +{ + FSP_SERVICE *Service; + + Service = FspServiceFromTable(); + if (0 == Service) + { + FspEventLog(EVENTLOG_ERROR_TYPE, + L"" __FUNCTION__ ": internal error: FspServiceFromTable = 0"); + return; + } + + Service->StatusHandle = RegisterServiceCtrlHandlerExW(Service->ServiceName, + FspServiceCtrlHandler, Service); + if (0 == Service->StatusHandle) + { + FspEventLog(EVENTLOG_ERROR_TYPE, + L"" __FUNCTION__ ": RegisterServiceCtrlHandlerW = %ld", GetLastError()); + return; + } + + FspServiceMain(Service, Argc, Argv); +} + +static VOID FspServiceMain(FSP_SERVICE *Service, DWORD Argc, PWSTR *Argv) +{ + SERVICE_STATUS ServiceStatus; + NTSTATUS Result; + + ServiceStatus.dwCurrentState = SERVICE_START_PENDING; + ServiceStatus.dwControlsAccepted = 0; + ServiceStatus.dwCheckPoint = 0; + ServiceStatus.dwWaitHint = 0; + FspServiceSetStatus(Service, + SetStatus_CurrentState | SetStatus_ControlsAccepted | SetStatus_CheckPoint | SetStatus_WaitHint, + &ServiceStatus); + + Result = STATUS_SUCCESS; + if (0 != Service->OnStart) + Result = Service->OnStart(Service, Argc, Argv); + + if (NT_SUCCESS(Result)) + { + ServiceStatus.dwCurrentState = SERVICE_RUNNING; + ServiceStatus.dwControlsAccepted = Service->AcceptControl; + FspServiceSetStatus(Service, + SetStatus_CurrentState | SetStatus_ControlsAccepted, &ServiceStatus); + } + else + { + ServiceStatus.dwCurrentState = SERVICE_STOPPED; + ServiceStatus.dwWin32ExitCode = FspWin32FromNtStatus(Result); + FspServiceSetStatus(Service, + SetStatus_CurrentState | SetStatus_Win32ExitCode, &ServiceStatus); + } +} + +static DWORD WINAPI FspServiceCtrlHandler( + DWORD Control, DWORD EventType, PVOID EventData, PVOID Context) +{ + FSP_SERVICE *Service = Context; + NTSTATUS Result; + + switch (Control) + { + case SERVICE_CONTROL_SHUTDOWN: + Result = STATUS_NOT_IMPLEMENTED; + if (0 != Service->OnControl) + Result = Service->OnControl(Service, Control, EventType, EventData); + if (STATUS_NOT_IMPLEMENTED != Result) + return FspWin32FromNtStatus(Result); + /* fall through */ + + case SERVICE_CONTROL_STOP: + if (!QueueUserWorkItem(FspServiceStopRoutine, Service, WT_EXECUTEDEFAULT)) + FspServiceStopRoutine(Service); + return NO_ERROR; + + case SERVICE_CONTROL_PAUSE: + case SERVICE_CONTROL_CONTINUE: + return ERROR_CALL_NOT_IMPLEMENTED; + + case SERVICE_CONTROL_INTERROGATE: + return NO_ERROR; + + default: + Result = STATUS_SUCCESS; + if (0 != Service->OnControl) + Result = Service->OnControl(Service, Control, EventType, EventData); + return FspWin32FromNtStatus(Result); + } +} + +static DWORD WINAPI FspServiceStopRoutine(PVOID Context) +{ + FSP_SERVICE *Service = Context; + + FspServiceStop(Service); + return 0; +} + +static DWORD WINAPI FspServiceInteractiveThread(PVOID Context) +{ + FSP_SERVICE *Service; + PWSTR Args[2] = { 0, 0 }; + PWSTR *Argv; + DWORD Argc; + + Service = FspServiceFromTable(); + if (0 == Service) + { + FspEventLog(EVENTLOG_ERROR_TYPE, + L"" __FUNCTION__ ": internal error: FspServiceFromTable = 0"); + return FALSE; + } + + Argv = CommandLineToArgvW(GetCommandLineW(), &Argc); + if (0 == Argv) + { + Argv = Args; + Argc = 1; + } + Argv[0] = Service->ServiceName; + + FspServiceMain(Service, Argc, Argv); + + if (Args != Argv) + LocalFree(Argv); + + return 0; +} + +static BOOL WINAPI FspServiceConsoleCtrlHandler(DWORD CtrlType) +{ + FSP_SERVICE *Service; + + Service = FspServiceFromTable(); + if (0 == Service) + { + FspEventLog(EVENTLOG_ERROR_TYPE, + L"" __FUNCTION__ ": internal error: FspServiceFromTable = 0"); + return FALSE; + } + + switch (CtrlType) + { + case CTRL_SHUTDOWN_EVENT: + FspServiceCtrlHandler(SERVICE_CONTROL_SHUTDOWN, 0, 0, Service); + return TRUE; + default: + FspServiceCtrlHandler(SERVICE_CONTROL_STOP, 0, 0, Service); + return TRUE; + } +}