1 /* 2 * PROJECT: ReactOS Local Spooler API Tests 3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) 4 * PURPOSE: Test list 5 * COPYRIGHT: Copyright 2015-2016 Colin Finck (colin@reactos.org) 6 */ 7 8 /* 9 * The original localspl.dll from Windows Server 2003 is not easily testable. 10 * It relies on a proper initialization inside spoolsv.exe, so we can't just load it in an API-Test as usual. 11 * See https://www.reactos.org/pipermail/ros-dev/2015-June/017395.html for more information. 12 * 13 * To make testing possible anyway, this program basically does four things: 14 * - Injecting our testing code into spoolsv.exe. 15 * - Registering and running us as a service in the SYSTEM security context like spoolsv.exe, so that injection is possible at all. 16 * - Sending the test name and receiving the console output over named pipes. 17 * - Redirecting the received console output to stdout again, so it looks and feels like a standard API-Test. 18 * 19 * To simplify debugging of the injected code, it is entirely separated into a DLL file localspl_apitest.dll. 20 * What we actually inject is a LoadLibraryW call, so that the DLL is loaded gracefully without any hacks. 21 * Therefore, you can just attach your debugger to the spoolsv.exe process and set breakpoints on the localspl_apitest.dll code. 22 */ 23 24 #include <apitest.h> 25 26 #define WIN32_NO_STATUS 27 #include <stdio.h> 28 #include <stdlib.h> 29 #include <windef.h> 30 #include <winbase.h> 31 #include <wingdi.h> 32 #include <winreg.h> 33 #include <winsvc.h> 34 #include <winspool.h> 35 #include <winsplp.h> 36 37 #include "localspl_apitest.h" 38 39 40 static void 41 _RunRemoteTest(const char* szTestName) 42 { 43 BOOL bSuccessful = FALSE; 44 char szBuffer[1024]; 45 DWORD cbRead; 46 DWORD cbWritten; 47 HANDLE hCommandPipe = INVALID_HANDLE_VALUE; 48 HANDLE hFind = INVALID_HANDLE_VALUE; 49 HANDLE hOutputPipe = INVALID_HANDLE_VALUE; 50 PWSTR p; 51 SC_HANDLE hSC = NULL; 52 SC_HANDLE hService = NULL; 53 SERVICE_STATUS ServiceStatus; 54 WCHAR wszFilePath[MAX_PATH + 20]; 55 WIN32_FIND_DATAW fd; 56 57 // Do a dummy EnumPrintersW call. 58 // This guarantees that the Spooler Service has actually loaded localspl.dll, which is a requirement for our injected DLL to work properly. 59 EnumPrintersW(PRINTER_ENUM_LOCAL | PRINTER_ENUM_NAME, NULL, 1, NULL, 0, &cbRead, &cbWritten); 60 61 // Get the full path to our EXE file. 62 if (!GetModuleFileNameW(NULL, wszFilePath, MAX_PATH)) 63 { 64 skip("GetModuleFileNameW failed with error %lu!\n", GetLastError()); 65 goto Cleanup; 66 } 67 68 // Replace the extension. 69 p = wcsrchr(wszFilePath, L'.'); 70 if (!p) 71 { 72 skip("File path has no file extension: %S\n", wszFilePath); 73 goto Cleanup; 74 } 75 76 wcscpy(p, L".dll"); 77 78 // Check if the corresponding DLL file exists. 79 hFind = FindFirstFileW(wszFilePath, &fd); 80 if (hFind == INVALID_HANDLE_VALUE) 81 { 82 skip("My DLL file \"%S\" does not exist!\n", wszFilePath); 83 goto Cleanup; 84 } 85 86 // Change the extension back to .exe and add the parameters. 87 wcscpy(p, L".exe service dummy"); 88 89 // Open a handle to the service manager. 90 hSC = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS); 91 if (!hSC) 92 { 93 skip("OpenSCManagerW failed with error %lu!\n", GetLastError()); 94 goto Cleanup; 95 } 96 97 // Ensure that the spooler service is running. 98 hService = OpenServiceW(hSC, L"spooler", SERVICE_QUERY_STATUS); 99 if (!hService) 100 { 101 skip("OpenServiceW failed for the spooler service with error %lu!\n", GetLastError()); 102 goto Cleanup; 103 } 104 105 if (!QueryServiceStatus(hService, &ServiceStatus)) 106 { 107 skip("QueryServiceStatus failed for the spooler service with error %lu!\n", GetLastError()); 108 goto Cleanup; 109 } 110 111 if (ServiceStatus.dwCurrentState != SERVICE_RUNNING) 112 { 113 skip("Spooler Service is not running!\n"); 114 goto Cleanup; 115 } 116 117 CloseServiceHandle(hService); 118 119 // Try to open the service if we've created it in a previous run. 120 hService = OpenServiceW(hSC, SERVICE_NAME, SERVICE_ALL_ACCESS); 121 if (!hService) 122 { 123 if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) 124 { 125 // Create the service. 126 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); 127 if (!hService) 128 { 129 skip("CreateServiceW failed with error %lu!\n", GetLastError()); 130 goto Cleanup; 131 } 132 } 133 else 134 { 135 skip("OpenServiceW failed with error %lu!\n", GetLastError()); 136 goto Cleanup; 137 } 138 } 139 140 // Create pipes for the communication with the injected DLL. 141 hCommandPipe = CreateNamedPipeW(COMMAND_PIPE_NAME, PIPE_ACCESS_OUTBOUND, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, 1, 1024, 1024, 10000, NULL); 142 if (hCommandPipe == INVALID_HANDLE_VALUE) 143 { 144 skip("CreateNamedPipeW failed for the command pipe with error %lu!\n", GetLastError()); 145 goto Cleanup; 146 } 147 148 hOutputPipe = CreateNamedPipeW(OUTPUT_PIPE_NAME, PIPE_ACCESS_INBOUND, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 1024, 1024, 10000, NULL); 149 if (hOutputPipe == INVALID_HANDLE_VALUE) 150 { 151 skip("CreateNamedPipeW failed for the output pipe with error %lu!\n", GetLastError()); 152 goto Cleanup; 153 } 154 155 // Start the service with "service" and a dummy parameter (to distinguish it from a call by rosautotest to localspl_apitest:service) 156 if (!StartServiceW(hService, 0, NULL)) 157 { 158 skip("StartServiceW failed with error %lu!\n", GetLastError()); 159 goto Cleanup; 160 } 161 162 // Wait till it has injected the DLL and the DLL expects its test name. 163 if (!ConnectNamedPipe(hCommandPipe, NULL) && GetLastError() != ERROR_PIPE_CONNECTED) 164 { 165 skip("ConnectNamedPipe failed for the command pipe with error %lu!\n", GetLastError()); 166 goto Cleanup; 167 } 168 169 // Send the test name. 170 if (!WriteFile(hCommandPipe, szTestName, strlen(szTestName) + sizeof(char), &cbWritten, NULL)) 171 { 172 skip("WriteFile failed with error %lu!\n", GetLastError()); 173 goto Cleanup; 174 } 175 176 // Now wait for the DLL to connect to the output pipe. 177 if (!ConnectNamedPipe(hOutputPipe, NULL)) 178 { 179 skip("ConnectNamedPipe failed for the output pipe with error %lu!\n", GetLastError()); 180 goto Cleanup; 181 } 182 183 // Get all testing messages from the pipe and output them on stdout. 184 while (ReadFile(hOutputPipe, szBuffer, sizeof(szBuffer), &cbRead, NULL) && cbRead) 185 fwrite(szBuffer, sizeof(char), cbRead, stdout); 186 187 bSuccessful = TRUE; 188 189 Cleanup: 190 if (hCommandPipe != INVALID_HANDLE_VALUE) 191 CloseHandle(hCommandPipe); 192 193 if (hOutputPipe != INVALID_HANDLE_VALUE) 194 CloseHandle(hOutputPipe); 195 196 if (hFind != INVALID_HANDLE_VALUE) 197 FindClose(hFind); 198 199 if (hService) 200 CloseServiceHandle(hService); 201 202 if (hSC) 203 CloseServiceHandle(hSC); 204 205 // If we successfully received test output through the named pipe, we have also output a summary line already. 206 // Prevent the testing framework from outputting another "0 tests executed" line in this case. 207 if (bSuccessful) 208 ExitProcess(0); 209 } 210 211 START_TEST(fpEnumPrinters) 212 { 213 _RunRemoteTest("fpEnumPrinters"); 214 } 215 216 START_TEST(fpGetPrintProcessorDirectory) 217 { 218 _RunRemoteTest("fpGetPrintProcessorDirectory"); 219 } 220 221 START_TEST(fpSetJob) 222 { 223 _RunRemoteTest("fpSetJob"); 224 } 225