9149f52fd04a7d5e20802ab2cfb617403e3141f4
[reactos.git] / reactos / win32ss / printing / providers / localspl / printers.c
1 /*
2 * PROJECT: ReactOS Local Spooler
3 * LICENSE: GNU LGPL v2.1 or any later version as published by the Free Software Foundation
4 * PURPOSE: Functions related to Printers and printing
5 * COPYRIGHT: Copyright 2015 Colin Finck <colin@reactos.org>
6 */
7
8 #include "precomp.h"
9
10 // Global Variables
11 RTL_GENERIC_TABLE PrinterTable;
12
13
14 /**
15 * @name _PrinterTableCompareRoutine
16 *
17 * RTL_GENERIC_COMPARE_ROUTINE for the Printer Table.
18 * Does a case-insensitive comparison, because e.g. LocalOpenPrinter doesn't match the case when looking for Printers.
19 */
20 static RTL_GENERIC_COMPARE_RESULTS NTAPI
21 _PrinterTableCompareRoutine(PRTL_GENERIC_TABLE Table, PVOID FirstStruct, PVOID SecondStruct)
22 {
23 PLOCAL_PRINTER A = (PLOCAL_PRINTER)FirstStruct;
24 PLOCAL_PRINTER B = (PLOCAL_PRINTER)SecondStruct;
25
26 int iResult = wcsicmp(A->pwszPrinterName, B->pwszPrinterName);
27
28 if (iResult < 0)
29 return GenericLessThan;
30 else if (iResult > 0)
31 return GenericGreaterThan;
32 else
33 return GenericEqual;
34 }
35
36 /**
37 * @name InitializePrinterTable
38 *
39 * Initializes a RTL_GENERIC_TABLE of locally available Printers.
40 * The table is searchable by name and returns information about the printers, including their job queues.
41 * During this process, the job queues are also initialized.
42 */
43 void
44 InitializePrinterTable()
45 {
46 const WCHAR wszPrintersKey[] = L"SYSTEM\\CurrentControlSet\\Control\\Print\\Printers";
47
48 DWORD cbDevMode;
49 DWORD cchPrinterName;
50 DWORD dwSubKeys;
51 DWORD i;
52 HKEY hKey = NULL;
53 HKEY hSubKey = NULL;
54 LONG lStatus;
55 PLOCAL_PRINT_PROCESSOR pPrintProcessor;
56 PLOCAL_PRINTER pPrinter = NULL;
57 PWSTR pwszPrintProcessor = NULL;
58 WCHAR wszPrinterName[MAX_PRINTER_NAME + 1];
59
60 // Initialize an empty table for our printers.
61 // We will search it by printer name.
62 RtlInitializeGenericTable(&PrinterTable, _PrinterTableCompareRoutine, GenericTableAllocateRoutine, GenericTableFreeRoutine, NULL);
63
64 // Open our printers registry key. Each subkey is a local printer there.
65 lStatus = RegOpenKeyExW(HKEY_LOCAL_MACHINE, wszPrintersKey, 0, KEY_READ, &hKey);
66 if (lStatus != ERROR_SUCCESS)
67 {
68 ERR("RegOpenKeyExW failed with status %ld!\n", lStatus);
69 goto Cleanup;
70 }
71
72 // Get the number of subkeys.
73 lStatus = RegQueryInfoKeyW(hKey, NULL, NULL, NULL, &dwSubKeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
74 if (lStatus != ERROR_SUCCESS)
75 {
76 ERR("RegQueryInfoKeyW failed with status %ld!\n", lStatus);
77 goto Cleanup;
78 }
79
80 // Loop through all available local printers.
81 for (i = 0; i < dwSubKeys; i++)
82 {
83 // Cleanup tasks from the previous run
84 if (hSubKey)
85 {
86 RegCloseKey(hSubKey);
87 hSubKey = NULL;
88 }
89
90 if (pPrinter)
91 {
92 if (pPrinter->pwszDefaultDatatype)
93 HeapFree(hProcessHeap, 0, pPrinter->pwszDefaultDatatype);
94
95 HeapFree(hProcessHeap, 0, pPrinter);
96 pPrinter = NULL;
97 }
98
99 if (pwszPrintProcessor)
100 {
101 HeapFree(hProcessHeap, 0, pwszPrintProcessor);
102 pwszPrintProcessor = NULL;
103 }
104
105 // Get the name of this printer.
106 cchPrinterName = sizeof(wszPrinterName) / sizeof(WCHAR);
107 lStatus = RegEnumKeyExW(hKey, i, wszPrinterName, &cchPrinterName, NULL, NULL, NULL, NULL);
108 if (lStatus == ERROR_MORE_DATA)
109 {
110 // This printer name exceeds the maximum length and is invalid.
111 continue;
112 }
113 else if (lStatus != ERROR_SUCCESS)
114 {
115 ERR("RegEnumKeyExW failed for iteration %lu with status %ld!\n", i, lStatus);
116 continue;
117 }
118
119 // Open this Printer's registry key.
120 lStatus = RegOpenKeyExW(hKey, wszPrinterName, 0, KEY_READ, &hSubKey);
121 if (lStatus != ERROR_SUCCESS)
122 {
123 ERR("RegOpenKeyExW failed for Printer \"%S\" with status %ld!\n", wszPrinterName, lStatus);
124 continue;
125 }
126
127 // Get the Print Processor.
128 pwszPrintProcessor = AllocAndRegQueryWSZ(hSubKey, L"Print Processor");
129 if (!pwszPrintProcessor)
130 continue;
131
132 // Try to find it in the Print Processor Table.
133 pPrintProcessor = RtlLookupElementGenericTable(&PrintProcessorTable, pwszPrintProcessor);
134 if (!pPrintProcessor)
135 {
136 ERR("Invalid Print Processor \"%S\" for Printer \"%S\"!\n", pwszPrintProcessor, wszPrinterName);
137 continue;
138 }
139
140 // Create a new LOCAL_PRINTER structure for it.
141 pPrinter = HeapAlloc(hProcessHeap, 0, sizeof(LOCAL_PRINTER));
142 if (!pPrinter)
143 {
144 ERR("HeapAlloc failed with error %lu!\n", GetLastError());
145 goto Cleanup;
146 }
147
148 pPrinter->pwszPrinterName = DuplicateStringW(wszPrinterName);
149 pPrinter->pPrintProcessor = pPrintProcessor;
150 InitializeListHead(&pPrinter->JobQueue);
151
152 // Get the default datatype.
153 pPrinter->pwszDefaultDatatype = AllocAndRegQueryWSZ(hSubKey, L"Datatype");
154 if (!pPrinter->pwszDefaultDatatype)
155 continue;
156
157 // Verify that it's valid.
158 if (!RtlLookupElementGenericTable(&pPrintProcessor->DatatypeTable, pPrinter->pwszDefaultDatatype))
159 {
160 ERR("Invalid default datatype \"%S\" for Printer \"%S\"!\n", pPrinter->pwszDefaultDatatype, wszPrinterName);
161 continue;
162 }
163
164 // Get the default DevMode.
165 cbDevMode = sizeof(DEVMODEW);
166 lStatus = RegQueryValueExW(hSubKey, L"Default DevMode", NULL, NULL, (PBYTE)&pPrinter->DefaultDevMode, &cbDevMode);
167 if (lStatus != ERROR_SUCCESS || cbDevMode != sizeof(DEVMODEW))
168 {
169 ERR("Couldn't query DevMode for Printer \"%S\", status is %ld, cbDevMode is %lu!\n", wszPrinterName, lStatus, cbDevMode);
170 continue;
171 }
172
173 // Add this printer to the printer table.
174 if (!RtlInsertElementGenericTable(&PrinterTable, pPrinter, sizeof(LOCAL_PRINTER), NULL))
175 {
176 ERR("RtlInsertElementGenericTable failed with error %lu!\n", GetLastError());
177 goto Cleanup;
178 }
179
180 // Don't let the cleanup routines free this.
181 pPrinter = NULL;
182 }
183
184 Cleanup:
185 if (pwszPrintProcessor)
186 HeapFree(hProcessHeap, 0, pwszPrintProcessor);
187
188 if (pPrinter)
189 {
190 if (pPrinter->pwszDefaultDatatype)
191 HeapFree(hProcessHeap, 0, pPrinter->pwszDefaultDatatype);
192
193 HeapFree(hProcessHeap, 0, pPrinter);
194 }
195
196 if (hSubKey)
197 RegCloseKey(hSubKey);
198
199 if (hKey)
200 RegCloseKey(hKey);
201 }
202
203
204 BOOL WINAPI
205 LocalEnumPrinters(DWORD Flags, LPWSTR Name, DWORD Level, LPBYTE pPrinterEnum, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned)
206 {
207 ///////////// TODO /////////////////////
208 return FALSE;
209 }
210
211 BOOL WINAPI
212 LocalOpenPrinter(PWSTR lpPrinterName, HANDLE* phPrinter, PPRINTER_DEFAULTSW pDefault)
213 {
214 BOOL bReturnValue = ROUTER_UNKNOWN;
215 DWORD cchComputerName;
216 DWORD cchPrinterName;
217 DWORD dwJobID;
218 PWSTR p = lpPrinterName;
219 PWSTR pwszPrinterName = NULL;
220 PLOCAL_JOB pJob;
221 PLOCAL_HANDLE pHandle;
222 PLOCAL_PRINTER pPrinter;
223 PLOCAL_PRINTER_HANDLE pPrinterHandle = NULL;
224 PLIST_ENTRY pEntry;
225 WCHAR wszComputerName[MAX_COMPUTERNAME_LENGTH + 1];
226
227 // Sanity checks
228 if (!lpPrinterName || !phPrinter)
229 {
230 SetLastError(ERROR_INVALID_PARAMETER);
231 goto Cleanup;
232 }
233
234 // Does lpPrinterName begin with two backslashes to indicate a server name?
235 if (lpPrinterName[0] == L'\\' && lpPrinterName[1] == L'\\')
236 {
237 // Skip these two backslashes.
238 lpPrinterName += 2;
239
240 // Look for the closing backslash.
241 p = wcschr(lpPrinterName, L'\\');
242 if (!p)
243 {
244 // We didn't get a proper server name.
245 SetLastError(ERROR_INVALID_PRINTER_NAME);
246 goto Cleanup;
247 }
248
249 // Null-terminate the string here to enable comparison.
250 *p = 0;
251
252 // Get the local computer name for comparison.
253 cchComputerName = sizeof(wszComputerName) / sizeof(WCHAR);
254 if (!GetComputerNameW(wszComputerName, &cchComputerName))
255 {
256 ERR("GetComputerNameW failed with error %lu!\n", GetLastError());
257 goto Cleanup;
258 }
259
260 // Now compare this with the local computer name and reject if it doesn't match, because this print provider only supports local printers.
261 if (wcsicmp(lpPrinterName, wszComputerName) != 0)
262 {
263 SetLastError(ERROR_INVALID_PRINTER_NAME);
264 goto Cleanup;
265 }
266
267 // We have checked the server name and don't need it anymore.
268 lpPrinterName = p + 1;
269 }
270
271 // Look for a comma. If it exists, it indicates the end of the printer name.
272 p = wcschr(lpPrinterName, L',');
273 if (p)
274 cchPrinterName = p - lpPrinterName;
275 else
276 cchPrinterName = wcslen(lpPrinterName);
277
278 // No printer name and no comma? This is invalid!
279 if (!cchPrinterName && !p)
280 {
281 SetLastError(ERROR_INVALID_PRINTER_NAME);
282 goto Cleanup;
283 }
284
285 // Do we have a printer name?
286 if (cchPrinterName)
287 {
288 // Yes, extract it.
289 pwszPrinterName = HeapAlloc(hProcessHeap, 0, (cchPrinterName + 1) * sizeof(WCHAR));
290 CopyMemory(pwszPrinterName, lpPrinterName, cchPrinterName * sizeof(WCHAR));
291 pwszPrinterName[cchPrinterName] = 0;
292
293 // Retrieve the associated printer from the table.
294 pPrinter = RtlLookupElementGenericTable(&PrinterTable, pwszPrinterName);
295 if (!pPrinter)
296 {
297 // The printer does not exist.
298 SetLastError(ERROR_INVALID_PRINTER_NAME);
299 goto Cleanup;
300 }
301
302 // Create a new printer handle.
303 pPrinterHandle = HeapAlloc(hProcessHeap, HEAP_ZERO_MEMORY, sizeof(LOCAL_PRINTER_HANDLE));
304 pPrinterHandle->Printer = pPrinter;
305
306 // Check if a datatype was given.
307 if (pDefault && pDefault->pDatatype)
308 {
309 // Use the datatype if it's valid.
310 if (!RtlLookupElementGenericTable(&pPrinter->pPrintProcessor->DatatypeTable, pDefault->pDatatype))
311 {
312 SetLastError(ERROR_INVALID_DATATYPE);
313 goto Cleanup;
314 }
315
316 pPrinterHandle->pwszDatatype = DuplicateStringW(pDefault->pDatatype);
317 }
318 else
319 {
320 // Use the default datatype.
321 pPrinterHandle->pwszDatatype = DuplicateStringW(pPrinter->pwszDefaultDatatype);
322 }
323
324 // Check if a DevMode was given, otherwise use the default.
325 if (pDefault && pDefault->pDevMode)
326 CopyMemory(&pPrinterHandle->DevMode, pDefault->pDevMode, sizeof(DEVMODEW));
327 else
328 CopyMemory(&pPrinterHandle->DevMode, &pPrinter->DefaultDevMode, sizeof(DEVMODEW));
329
330 // Did we have a comma? Then the user may want a handle to an existing job instead of creating a new job.
331 if (p)
332 {
333 ++p;
334
335 // Skip whitespace.
336 do
337 {
338 ++p;
339 }
340 while (*p == ' ');
341
342 // The "Job " string has to follow now.
343 if (wcscmp(p, L"Job ") != 0)
344 {
345 SetLastError(ERROR_INVALID_PRINTER_NAME);
346 goto Cleanup;
347 }
348
349 // Skip the "Job " string.
350 p += sizeof("Job ") - 1;
351
352 // Skip even more whitespace.
353 while (*p == ' ')
354 ++p;
355
356 // Finally extract the desired Job ID.
357 dwJobID = wcstoul(p, NULL, 10);
358 if (!IS_VALID_JOB_ID(dwJobID))
359 {
360 // The user supplied an invalid Job ID.
361 SetLastError(ERROR_INVALID_PRINTER_NAME);
362 goto Cleanup;
363 }
364
365 // Look for this job in the job queue of the printer.
366 pEntry = pPrinter->JobQueue.Flink;
367
368 for (;;)
369 {
370 if (pEntry == &pPrinter->JobQueue)
371 {
372 // We have reached the end of the list without finding the desired Job ID.
373 SetLastError(ERROR_INVALID_PRINTER_NAME);
374 goto Cleanup;
375 }
376
377 // Get our job structure.
378 pJob = CONTAINING_RECORD(pEntry, LOCAL_JOB, Entry);
379
380 if (pJob->dwJobID == dwJobID)
381 {
382 // We have found the desired job. Give the caller a printer handle referencing it.
383 pPrinterHandle->StartedJob = pJob;
384 break;
385 }
386
387 pEntry = pEntry->Flink;
388 }
389 }
390
391 // Create a new handle that references a printer.
392 pHandle = HeapAlloc(hProcessHeap, 0, sizeof(LOCAL_HANDLE));
393 pHandle->HandleType = Printer;
394 pHandle->SpecificHandle = pPrinterHandle;
395 }
396 else
397 {
398 // No printer name, but we have a comma!
399 // This may be a request to a XcvMonitor or XcvPort handle.
400 ++p;
401
402 // Skip whitespace.
403 do
404 {
405 ++p;
406 }
407 while (*p == ' ');
408
409 // Check if this is a request to a XcvMonitor.
410 if (wcscmp(p, L"XcvMonitor ") == 0)
411 {
412 // Skip the "XcvMonitor " string.
413 p += sizeof("XcvMonitor ") - 1;
414
415 ///////////// TODO /////////////////////
416 pHandle = HeapAlloc(hProcessHeap, 0, sizeof(LOCAL_HANDLE));
417 pHandle->HandleType = Monitor;
418 //pHandle->SpecificHandle = pMonitorHandle;
419 }
420 else if (wcscmp(p, L"XcvPort ") == 0)
421 {
422 // Skip the "XcvPort " string.
423 p += sizeof("XcvPort ") - 1;
424
425 //////////// TODO //////////////////////
426 pHandle = HeapAlloc(hProcessHeap, 0, sizeof(LOCAL_HANDLE));
427 pHandle->HandleType = Port;
428 //pHandle->SpecificHandle = pPortHandle;
429 }
430 else
431 {
432 SetLastError(ERROR_INVALID_PRINTER_NAME);
433 goto Cleanup;
434 }
435 }
436
437 *phPrinter = (HANDLE)pHandle;
438 bReturnValue = ROUTER_SUCCESS;
439
440 // Don't let the cleanup routines free this.
441 pPrinterHandle = NULL;
442 pwszPrinterName = NULL;
443
444 Cleanup:
445 if (pPrinterHandle)
446 {
447 if (pPrinterHandle->pwszDatatype)
448 HeapFree(hProcessHeap, 0, pPrinterHandle->pwszDatatype);
449
450 HeapFree(hProcessHeap, 0, pPrinterHandle);
451 }
452
453 if (pwszPrinterName)
454 HeapFree(hProcessHeap, 0, pwszPrinterName);
455
456 return bReturnValue;
457 }
458
459 DWORD WINAPI
460 LocalStartDocPrinter(HANDLE hPrinter, DWORD Level, LPBYTE pDocInfo)
461 {
462 DWORD dwReturnValue = 0;
463 PDOC_INFO_1W pDocumentInfo1;
464 PLOCAL_HANDLE pHandle;
465 PLOCAL_PRINTER_HANDLE pPrinterHandle;
466 PLOCAL_JOB pJob;
467
468 // Sanity checks
469 if (!pDocInfo)
470 {
471 SetLastError(ERROR_INVALID_PARAMETER);
472 return 0;
473 }
474
475 if (!hPrinter)
476 {
477 SetLastError(ERROR_INVALID_HANDLE);
478 return 0;
479 }
480
481 // Check if this is a printer handle.
482 pHandle = (PLOCAL_HANDLE)hPrinter;
483 if (pHandle->HandleType != Printer)
484 {
485 SetLastError(ERROR_INVALID_HANDLE);
486 return 0;
487 }
488
489 pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->SpecificHandle;
490
491 // Check if this is the right document information level.
492 if (Level != 1)
493 {
494 SetLastError(ERROR_INVALID_LEVEL);
495 return 0;
496 }
497
498 pDocumentInfo1 = (PDOC_INFO_1W)pDocInfo;
499
500 // Create a new job.
501 pJob = HeapAlloc(hProcessHeap, HEAP_ZERO_MEMORY, sizeof(LOCAL_JOB));
502 pJob->Printer = pPrinterHandle->Printer;
503
504 // Check if a datatype was given.
505 if (pDocumentInfo1->pDatatype)
506 {
507 // Use the datatype if it's valid.
508 if (!RtlLookupElementGenericTable(&pJob->Printer->pPrintProcessor->DatatypeTable, pDocumentInfo1->pDatatype))
509 {
510 SetLastError(ERROR_INVALID_DATATYPE);
511 goto Cleanup;
512 }
513
514 pJob->pwszDatatype = DuplicateStringW(pDocumentInfo1->pDatatype);
515 }
516 else
517 {
518 // Use the printer handle datatype.
519 pJob->pwszDatatype = DuplicateStringW(pPrinterHandle->pwszDatatype);
520 }
521
522 // Copy over printer defaults.
523 CopyMemory(&pJob->DevMode, &pPrinterHandle->DevMode, sizeof(DEVMODEW));
524
525 // Copy over supplied information.
526 if (pDocumentInfo1->pDocName)
527 pJob->pwszDocumentName = DuplicateStringW(pDocumentInfo1->pDocName);
528
529 if (pDocumentInfo1->pOutputFile)
530 pJob->pwszOutputFile = DuplicateStringW(pDocumentInfo1->pOutputFile);
531
532 // Enqueue the job.
533 ///////////// TODO /////////////////////
534
535 Cleanup:
536 if (pJob)
537 HeapFree(hProcessHeap, 0, pJob);
538
539 return dwReturnValue;
540 }
541
542 BOOL WINAPI
543 LocalStartPagePrinter(HANDLE hPrinter)
544 {
545 ///////////// TODO /////////////////////
546 return FALSE;
547 }
548
549 BOOL WINAPI
550 LocalWritePrinter(HANDLE hPrinter, LPVOID pBuf, DWORD cbBuf, LPDWORD pcWritten)
551 {
552 ///////////// TODO /////////////////////
553 return FALSE;
554 }
555
556 BOOL WINAPI
557 LocalEndPagePrinter(HANDLE hPrinter)
558 {
559 ///////////// TODO /////////////////////
560 return FALSE;
561 }
562
563 BOOL WINAPI
564 LocalEndDocPrinter(HANDLE hPrinter)
565 {
566 ///////////// TODO /////////////////////
567 return FALSE;
568 }
569
570 BOOL WINAPI
571 LocalClosePrinter(HANDLE hPrinter)
572 {
573 PLOCAL_HANDLE pHandle;
574
575 if (!hPrinter)
576 {
577 SetLastError(ERROR_INVALID_HANDLE);
578 return FALSE;
579 }
580
581 pHandle = (PLOCAL_HANDLE)hPrinter;
582
583 ///////////// TODO /////////////////////
584 /// Check the handle type, do thoroughful checks on all data fields and clean them.
585 ////////////////////////////////////////
586
587 HeapFree(hProcessHeap, 0, pHandle);
588
589 return TRUE;
590 }