2015-06-08 17:15:44 +00:00
|
|
|
/*
|
|
|
|
* PROJECT: ReactOS Local Spooler API Tests
|
|
|
|
* LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation
|
|
|
|
* PURPOSE: Test list
|
2016-11-16 14:59:01 +00:00
|
|
|
* COPYRIGHT: Copyright 2015-2016 Colin Finck <colin@reactos.org>
|
2015-06-08 17:15:44 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The original localspl.dll from Windows Server 2003 is not easily testable.
|
|
|
|
* It relies on a proper initialization inside spoolsv.exe, so we can't just load it in an API-Test as usual.
|
|
|
|
* See https://www.reactos.org/pipermail/ros-dev/2015-June/017395.html for more information.
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* To simplify debugging of the injected code, it is entirely separated into a DLL file localspl_apitest.dll.
|
|
|
|
* What we actually inject is a LoadLibraryW call, so that the DLL is loaded gracefully without any hacks.
|
|
|
|
* Therefore, you can just attach your debugger to the spoolsv.exe process and set breakpoints on the localspl_apitest.dll code.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <apitest.h>
|
|
|
|
|
|
|
|
#define WIN32_NO_STATUS
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <windef.h>
|
|
|
|
#include <winbase.h>
|
|
|
|
#include <wingdi.h>
|
|
|
|
#include <winreg.h>
|
|
|
|
#include <winsvc.h>
|
|
|
|
#include <winspool.h>
|
|
|
|
#include <winsplp.h>
|
|
|
|
|
|
|
|
#include "localspl_apitest.h"
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
_RunRemoteTest(const char* szTestName)
|
|
|
|
{
|
2015-06-09 13:22:25 +00:00
|
|
|
BOOL bSuccessful = FALSE;
|
2015-06-08 17:15:44 +00:00
|
|
|
char szBuffer[1024];
|
|
|
|
DWORD cbRead;
|
|
|
|
DWORD cbWritten;
|
2015-06-09 13:22:25 +00:00
|
|
|
HANDLE hCommandPipe = INVALID_HANDLE_VALUE;
|
2016-11-16 14:59:01 +00:00
|
|
|
HANDLE hFind = INVALID_HANDLE_VALUE;
|
2015-06-09 13:22:25 +00:00
|
|
|
HANDLE hOutputPipe = INVALID_HANDLE_VALUE;
|
2015-06-08 17:15:44 +00:00
|
|
|
PWSTR p;
|
2015-06-09 13:22:25 +00:00
|
|
|
SC_HANDLE hSC = NULL;
|
|
|
|
SC_HANDLE hService = NULL;
|
|
|
|
SERVICE_STATUS ServiceStatus;
|
2015-06-08 17:15:44 +00:00
|
|
|
WCHAR wszFilePath[MAX_PATH + 20];
|
|
|
|
WIN32_FIND_DATAW fd;
|
|
|
|
|
|
|
|
// Do a dummy EnumPrintersW call.
|
|
|
|
// This guarantees that the Spooler Service has actually loaded localspl.dll, which is a requirement for our injected DLL to work properly.
|
|
|
|
EnumPrintersW(PRINTER_ENUM_LOCAL | PRINTER_ENUM_NAME, NULL, 1, NULL, 0, &cbRead, &cbWritten);
|
|
|
|
|
|
|
|
// Get the full path to our EXE file.
|
|
|
|
if (!GetModuleFileNameW(NULL, wszFilePath, MAX_PATH))
|
|
|
|
{
|
|
|
|
skip("GetModuleFileNameW failed with error %lu!\n", GetLastError());
|
2015-06-09 13:22:25 +00:00
|
|
|
goto Cleanup;
|
2015-06-08 17:15:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Replace the extension.
|
|
|
|
p = wcsrchr(wszFilePath, L'.');
|
|
|
|
if (!p)
|
|
|
|
{
|
|
|
|
skip("File path has no file extension: %S\n", wszFilePath);
|
2015-06-09 13:22:25 +00:00
|
|
|
goto Cleanup;
|
2015-06-08 17:15:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
wcscpy(p, L".dll");
|
|
|
|
|
|
|
|
// Check if the corresponding DLL file exists.
|
2015-06-09 13:22:25 +00:00
|
|
|
hFind = FindFirstFileW(wszFilePath, &fd);
|
2016-11-16 14:59:01 +00:00
|
|
|
if (hFind == INVALID_HANDLE_VALUE)
|
2015-06-08 17:15:44 +00:00
|
|
|
{
|
|
|
|
skip("My DLL file \"%S\" does not exist!\n", wszFilePath);
|
2015-06-09 13:22:25 +00:00
|
|
|
goto Cleanup;
|
2015-06-08 17:15:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Change the extension back to .exe and add the parameters.
|
|
|
|
wcscpy(p, L".exe service dummy");
|
|
|
|
|
|
|
|
// Open a handle to the service manager.
|
|
|
|
hSC = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);
|
|
|
|
if (!hSC)
|
|
|
|
{
|
|
|
|
skip("OpenSCManagerW failed with error %lu!\n", GetLastError());
|
2015-06-09 13:22:25 +00:00
|
|
|
goto Cleanup;
|
2015-06-08 17:15:44 +00:00
|
|
|
}
|
|
|
|
|
2015-06-09 13:22:25 +00:00
|
|
|
// Ensure that the spooler service is running.
|
|
|
|
hService = OpenServiceW(hSC, L"spooler", SERVICE_QUERY_STATUS);
|
|
|
|
if (!hService)
|
|
|
|
{
|
|
|
|
skip("OpenServiceW failed for the spooler service with error %lu!\n", GetLastError());
|
|
|
|
goto Cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!QueryServiceStatus(hService, &ServiceStatus))
|
|
|
|
{
|
|
|
|
skip("QueryServiceStatus failed for the spooler service with error %lu!\n", GetLastError());
|
|
|
|
goto Cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ServiceStatus.dwCurrentState != SERVICE_RUNNING)
|
|
|
|
{
|
|
|
|
skip("Spooler Service is not running!\n");
|
|
|
|
goto Cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
CloseServiceHandle(hService);
|
|
|
|
|
2015-06-08 17:15:44 +00:00
|
|
|
// Try to open the service if we've created it in a previous run.
|
|
|
|
hService = OpenServiceW(hSC, SERVICE_NAME, SERVICE_ALL_ACCESS);
|
|
|
|
if (!hService)
|
|
|
|
{
|
|
|
|
if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST)
|
|
|
|
{
|
|
|
|
// Create the service.
|
|
|
|
hService = CreateServiceW(hSC, SERVICE_NAME, NULL, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, wszFilePath, NULL, NULL, NULL, NULL, NULL);
|
|
|
|
if (!hService)
|
|
|
|
{
|
|
|
|
skip("CreateServiceW failed with error %lu!\n", GetLastError());
|
2015-06-09 13:22:25 +00:00
|
|
|
goto Cleanup;
|
2015-06-08 17:15:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
skip("OpenServiceW failed with error %lu!\n", GetLastError());
|
2015-06-09 13:22:25 +00:00
|
|
|
goto Cleanup;
|
2015-06-08 17:15:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create pipes for the communication with the injected DLL.
|
|
|
|
hCommandPipe = CreateNamedPipeW(COMMAND_PIPE_NAME, PIPE_ACCESS_OUTBOUND, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, 1, 1024, 1024, 10000, NULL);
|
|
|
|
if (hCommandPipe == INVALID_HANDLE_VALUE)
|
|
|
|
{
|
|
|
|
skip("CreateNamedPipeW failed for the command pipe with error %lu!\n", GetLastError());
|
2015-06-09 13:22:25 +00:00
|
|
|
goto Cleanup;
|
2015-06-08 17:15:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
hOutputPipe = CreateNamedPipeW(OUTPUT_PIPE_NAME, PIPE_ACCESS_INBOUND, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 1024, 1024, 10000, NULL);
|
|
|
|
if (hOutputPipe == INVALID_HANDLE_VALUE)
|
|
|
|
{
|
|
|
|
skip("CreateNamedPipeW failed for the output pipe with error %lu!\n", GetLastError());
|
2015-06-09 13:22:25 +00:00
|
|
|
goto Cleanup;
|
2015-06-08 17:15:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Start the service with "service" and a dummy parameter (to distinguish it from a call by rosautotest to localspl_apitest:service)
|
|
|
|
if (!StartServiceW(hService, 0, NULL))
|
|
|
|
{
|
|
|
|
skip("StartServiceW failed with error %lu!\n", GetLastError());
|
2015-06-09 13:22:25 +00:00
|
|
|
goto Cleanup;
|
2015-06-08 17:15:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Wait till it has injected the DLL and the DLL expects its test name.
|
|
|
|
if (!ConnectNamedPipe(hCommandPipe, NULL) && GetLastError() != ERROR_PIPE_CONNECTED)
|
|
|
|
{
|
|
|
|
skip("ConnectNamedPipe failed for the command pipe with error %lu!\n", GetLastError());
|
2015-06-09 13:22:25 +00:00
|
|
|
goto Cleanup;
|
2015-06-08 17:15:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Send the test name.
|
|
|
|
if (!WriteFile(hCommandPipe, szTestName, strlen(szTestName) + sizeof(char), &cbWritten, NULL))
|
|
|
|
{
|
|
|
|
skip("WriteFile failed with error %lu!\n", GetLastError());
|
2015-06-09 13:22:25 +00:00
|
|
|
goto Cleanup;
|
2015-06-08 17:15:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Now wait for the DLL to connect to the output pipe.
|
|
|
|
if (!ConnectNamedPipe(hOutputPipe, NULL))
|
|
|
|
{
|
|
|
|
skip("ConnectNamedPipe failed for the output pipe with error %lu!\n", GetLastError());
|
2015-06-09 13:22:25 +00:00
|
|
|
goto Cleanup;
|
2015-06-08 17:15:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get all testing messages from the pipe and output them on stdout.
|
|
|
|
while (ReadFile(hOutputPipe, szBuffer, sizeof(szBuffer), &cbRead, NULL) && cbRead)
|
|
|
|
fwrite(szBuffer, sizeof(char), cbRead, stdout);
|
|
|
|
|
2015-06-09 13:22:25 +00:00
|
|
|
bSuccessful = TRUE;
|
|
|
|
|
|
|
|
Cleanup:
|
2016-11-16 14:59:01 +00:00
|
|
|
if (hCommandPipe != INVALID_HANDLE_VALUE)
|
2015-06-09 13:22:25 +00:00
|
|
|
CloseHandle(hCommandPipe);
|
|
|
|
|
2016-11-16 14:59:01 +00:00
|
|
|
if (hOutputPipe != INVALID_HANDLE_VALUE)
|
2015-06-09 13:22:25 +00:00
|
|
|
CloseHandle(hOutputPipe);
|
|
|
|
|
2016-11-16 14:59:01 +00:00
|
|
|
if (hFind != INVALID_HANDLE_VALUE)
|
2015-06-09 13:22:25 +00:00
|
|
|
FindClose(hFind);
|
|
|
|
|
|
|
|
if (hService)
|
|
|
|
CloseServiceHandle(hService);
|
|
|
|
|
|
|
|
if (hSC)
|
|
|
|
CloseServiceHandle(hSC);
|
2015-06-08 17:15:44 +00:00
|
|
|
|
2015-06-09 13:22:25 +00:00
|
|
|
// If we successfully received test output through the named pipe, we have also output a summary line already.
|
|
|
|
// Prevent the testing framework from outputting another "0 tests executed" line in this case.
|
|
|
|
if (bSuccessful)
|
|
|
|
ExitProcess(0);
|
2015-06-08 17:15:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
START_TEST(fpEnumPrinters)
|
|
|
|
{
|
|
|
|
_RunRemoteTest("fpEnumPrinters");
|
|
|
|
}
|
2016-11-17 22:23:28 +00:00
|
|
|
|
|
|
|
START_TEST(fpGetPrintProcessorDirectory)
|
|
|
|
{
|
|
|
|
_RunRemoteTest("fpGetPrintProcessorDirectory");
|
|
|
|
}
|