reactos/rostests/apitests/localspl/service.c
Colin Finck f2a66788f8 [LOCALSPL_APITEST]
Write an API-Test for localspl.dll. As the original localspl.dll from Windows Server 2003 relies on proper initialization inside spoolsv.exe, we cannot test it standalone as usual.
To make testing possible anyway, this program basically does four things:

  - Injecting our testing code into spoolsv.exe.
  - Registering and running us as a service in the SYSTEM security context like spoolsv.exe, so that injection is possible at all.
  - Sending the test name and receiving the console output over named pipes.
  - Redirecting the received console output to stdout again, so it looks and feels like a standard API-Test.

Nevertheless, the testing code in fpEnumPrinters.c is still written like a usual test. The known ok(), skip(), etc. macros can be used as usual, their output is just redirected through the named pipes.
Thanks to Thomas for giving me the tip about injecting code into spoolsv! :)

svn path=/branches/colins-printing-for-freedom/; revision=68080
2015-06-08 17:15:44 +00:00

183 lines
5.5 KiB
C

/*
* PROJECT: ReactOS Local Spooler API Tests
* LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation
* PURPOSE: Functions needed to run our code as a service. This is needed to run in SYSTEM security context.
* COPYRIGHT: Copyright 2015 Colin Finck <colin@reactos.org>
*/
#include <apitest.h>
#define WIN32_NO_STATUS
#include <windef.h>
#include <winbase.h>
#include <wingdi.h>
#include <winreg.h>
#include <winsvc.h>
#include <winspool.h>
#include <winsplp.h>
#include <tlhelp32.h>
#include "localspl_apitest.h"
//#define NDEBUG
#include <debug.h>
static void
_DoDLLInjection()
{
DWORD cbDLLPath;
HANDLE hProcess;
HANDLE hSnapshot;
HANDLE hThread;
PROCESSENTRY32W pe;
PVOID pLoadLibraryAddress;
PVOID pLoadLibraryArgument;
PWSTR p;
WCHAR wszFilePath[MAX_PATH];
// Get the full path to our EXE file.
if (!GetModuleFileNameW(NULL, wszFilePath, _countof(wszFilePath)))
{
DPRINT("GetModuleFileNameW failed with error %lu!\n", GetLastError());
return;
}
// Replace the extension.
p = wcsrchr(wszFilePath, L'.');
if (!p)
{
DPRINT("File path has no file extension: %S\n", wszFilePath);
return;
}
wcscpy(p, L".dll");
cbDLLPath = (wcslen(wszFilePath) + 1) * sizeof(WCHAR);
// Create a snapshot of the currently running processes.
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
{
DPRINT("CreateToolhelp32Snapshot failed with error %lu!\n", GetLastError());
return;
}
// Enumerate through all running processes.
pe.dwSize = sizeof(pe);
if (!Process32FirstW(hSnapshot, &pe))
{
DPRINT("Process32FirstW failed with error %lu!\n", GetLastError());
return;
}
do
{
// Check if this is the spooler server process.
if (wcsicmp(pe.szExeFile, L"spoolsv.exe") != 0)
continue;
// Open a handle to the process.
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID);
if (!hProcess)
{
DPRINT("OpenProcess failed with error %lu!\n", GetLastError());
return;
}
// Get the address of LoadLibraryW.
pLoadLibraryAddress = (PVOID)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW");
if (!pLoadLibraryAddress)
{
DPRINT("GetProcAddress failed with error %lu!\n", GetLastError());
return;
}
// Allocate memory for the DLL path in the spooler process.
pLoadLibraryArgument = VirtualAllocEx(hProcess, NULL, cbDLLPath, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!pLoadLibraryArgument)
{
DPRINT("VirtualAllocEx failed with error %lu!\n", GetLastError());
return;
}
// Write the DLL path to the process memory.
if (!WriteProcessMemory(hProcess, pLoadLibraryArgument, wszFilePath, cbDLLPath, NULL))
{
DPRINT("WriteProcessMemory failed with error %lu!\n", GetLastError());
return;
}
// Create a new thread in the spooler process that calls LoadLibraryW as the start routine with our DLL as the argument.
// This effectively injects our DLL into the spooler process and we can inspect localspl.dll there just like the spooler.
hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pLoadLibraryAddress, pLoadLibraryArgument, 0, NULL);
if (!hThread)
{
DPRINT("CreateRemoteThread failed with error %lu!\n", GetLastError());
return;
}
CloseHandle(hThread);
break;
}
while (Process32NextW(hSnapshot, &pe));
}
static DWORD WINAPI
_ServiceControlHandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext)
{
return NO_ERROR;
}
static void WINAPI
_ServiceMain(DWORD dwArgc, LPWSTR* lpszArgv)
{
SERVICE_STATUS_HANDLE hServiceStatus;
SERVICE_STATUS ServiceStatus;
UNREFERENCED_PARAMETER(dwArgc);
UNREFERENCED_PARAMETER(lpszArgv);
// Register our service for control.
hServiceStatus = RegisterServiceCtrlHandlerExW(SERVICE_NAME, _ServiceControlHandlerEx, NULL);
// Report SERVICE_RUNNING status.
ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
ServiceStatus.dwServiceSpecificExitCode = 0;
ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
ServiceStatus.dwWaitHint = 4000;
ServiceStatus.dwWin32ExitCode = NO_ERROR;
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus(hServiceStatus, &ServiceStatus);
// Do our funky crazy stuff.
_DoDLLInjection();
// Our work is done.
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus(hServiceStatus, &ServiceStatus);
}
START_TEST(service)
{
int argc;
char** argv;
SERVICE_TABLE_ENTRYW ServiceTable[] =
{
{ SERVICE_NAME, _ServiceMain },
{ NULL, NULL }
};
// This is no real test, but an easy way to integrate the service handler routines into the API-Test executable.
// Therefore, bail out if someone tries to run "service" as a usual test.
argc = winetest_get_mainargs(&argv);
if (argc != 3)
return;
// If we have exactly 3 arguments, we're run as a service, so initialize the corresponding service handler functions.
StartServiceCtrlDispatcherW(ServiceTable);
// Prevent the testing framework from outputting a "0 tests executed" line here.
ExitProcess(0);
}