[LOCALMON, LOCALSPL]
[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 SKIPLIST PrinterList;
12
13
14 /**
15 * @name _PrinterListCompareRoutine
16 *
17 * SKIPLIST_COMPARE_ROUTINE for the Printer List.
18 * Does a case-insensitive comparison, because e.g. LocalOpenPrinter doesn't match the case when looking for Printers.
19 */
20 static int WINAPI
21 _PrinterListCompareRoutine(PVOID FirstStruct, PVOID SecondStruct)
22 {
23 PLOCAL_PRINTER A = (PLOCAL_PRINTER)FirstStruct;
24 PLOCAL_PRINTER B = (PLOCAL_PRINTER)SecondStruct;
25
26 return wcsicmp(A->pwszPrinterName, B->pwszPrinterName);
27 }
28
29 /**
30 * @name InitializePrinterList
31 *
32 * Initializes a list of locally available Printers.
33 * The list is searchable by name and returns information about the printers, including their job queues.
34 * During this process, the job queues are also initialized.
35 */
36 BOOL
37 InitializePrinterList()
38 {
39 const WCHAR wszPrintersKey[] = L"SYSTEM\\CurrentControlSet\\Control\\Print\\Printers";
40
41 DWORD cbData;
42 DWORD cchPrinterName;
43 DWORD dwErrorCode;
44 DWORD dwSubKeys;
45 DWORD i;
46 HKEY hKey = NULL;
47 HKEY hSubKey = NULL;
48 PLOCAL_PORT pPort;
49 PLOCAL_PRINTER pPrinter = NULL;
50 PLOCAL_PRINT_PROCESSOR pPrintProcessor;
51 PWSTR pwszPort = NULL;
52 PWSTR pwszPrintProcessor = NULL;
53 WCHAR wszPrinterName[MAX_PRINTER_NAME + 1];
54
55 // Initialize an empty list for our printers.
56 InitializeSkiplist(&PrinterList, DllAllocSplMem, _PrinterListCompareRoutine, (PSKIPLIST_FREE_ROUTINE)DllFreeSplMem);
57
58 // Open our printers registry key. Each subkey is a local printer there.
59 dwErrorCode = (DWORD)RegOpenKeyExW(HKEY_LOCAL_MACHINE, wszPrintersKey, 0, KEY_READ, &hKey);
60 if (dwErrorCode != ERROR_SUCCESS)
61 {
62 ERR("RegOpenKeyExW failed with status %lu!\n", dwErrorCode);
63 goto Cleanup;
64 }
65
66 // Get the number of subkeys.
67 dwErrorCode = (DWORD)RegQueryInfoKeyW(hKey, NULL, NULL, NULL, &dwSubKeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
68 if (dwErrorCode != ERROR_SUCCESS)
69 {
70 ERR("RegQueryInfoKeyW failed with status %lu!\n", dwErrorCode);
71 goto Cleanup;
72 }
73
74 // Loop through all available local printers.
75 for (i = 0; i < dwSubKeys; i++)
76 {
77 // Cleanup tasks from the previous run
78 if (hSubKey)
79 {
80 RegCloseKey(hSubKey);
81 hSubKey = NULL;
82 }
83
84 if (pPrinter)
85 {
86 if (pPrinter->pDefaultDevMode)
87 DllFreeSplMem(pPrinter->pDefaultDevMode);
88
89 if (pPrinter->pwszDefaultDatatype)
90 DllFreeSplStr(pPrinter->pwszDefaultDatatype);
91
92 if (pPrinter->pwszDescription)
93 DllFreeSplStr(pPrinter->pwszDescription);
94
95 if (pPrinter->pwszPrinterDriver)
96 DllFreeSplStr(pPrinter->pwszPrinterDriver);
97
98 if (pPrinter->pwszPrinterName)
99 DllFreeSplStr(pPrinter->pwszPrinterName);
100
101 DllFreeSplMem(pPrinter);
102 pPrinter = NULL;
103 }
104
105 if (pwszPrintProcessor)
106 {
107 DllFreeSplStr(pwszPrintProcessor);
108 pwszPrintProcessor = NULL;
109 }
110
111 // Get the name of this printer.
112 cchPrinterName = _countof(wszPrinterName);
113 dwErrorCode = (DWORD)RegEnumKeyExW(hKey, i, wszPrinterName, &cchPrinterName, NULL, NULL, NULL, NULL);
114 if (dwErrorCode == ERROR_MORE_DATA)
115 {
116 // This printer name exceeds the maximum length and is invalid.
117 continue;
118 }
119 else if (dwErrorCode != ERROR_SUCCESS)
120 {
121 ERR("RegEnumKeyExW failed for iteration %lu with status %lu!\n", i, dwErrorCode);
122 continue;
123 }
124
125 // Open this Printer's registry key.
126 dwErrorCode = (DWORD)RegOpenKeyExW(hKey, wszPrinterName, 0, KEY_READ, &hSubKey);
127 if (dwErrorCode != ERROR_SUCCESS)
128 {
129 ERR("RegOpenKeyExW failed for Printer \"%S\" with status %lu!\n", wszPrinterName, dwErrorCode);
130 continue;
131 }
132
133 // Get the Print Processor.
134 pwszPrintProcessor = AllocAndRegQueryWSZ(hSubKey, L"Print Processor");
135 if (!pwszPrintProcessor)
136 continue;
137
138 // Try to find it in the Print Processor List.
139 pPrintProcessor = FindPrintProcessor(pwszPrintProcessor);
140 if (!pPrintProcessor)
141 {
142 ERR("Invalid Print Processor \"%S\" for Printer \"%S\"!\n", pwszPrintProcessor, wszPrinterName);
143 continue;
144 }
145
146 // Get the Port.
147 pwszPort = AllocAndRegQueryWSZ(hSubKey, L"Port");
148 if (!pwszPort)
149 continue;
150
151 // Try to find it in the Port List.
152 pPort = FindPort(pwszPort);
153 if (!pPort)
154 {
155 ERR("Invalid Port \"%S\" for Printer \"%S\"!\n", pwszPort, wszPrinterName);
156 continue;
157 }
158
159 // Create a new LOCAL_PRINTER structure for it.
160 pPrinter = DllAllocSplMem(sizeof(LOCAL_PRINTER));
161 if (!pPrinter)
162 {
163 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
164 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
165 goto Cleanup;
166 }
167
168 pPrinter->pwszPrinterName = AllocSplStr(wszPrinterName);
169 pPrinter->pPrintProcessor = pPrintProcessor;
170 pPrinter->pPort = pPort;
171 InitializePrinterJobList(pPrinter);
172
173 // Get the printer driver.
174 pPrinter->pwszPrinterDriver = AllocAndRegQueryWSZ(hSubKey, L"Printer Driver");
175 if (!pPrinter->pwszPrinterDriver)
176 continue;
177
178 // Get the description.
179 pPrinter->pwszDescription = AllocAndRegQueryWSZ(hSubKey, L"Description");
180 if (!pPrinter->pwszDescription)
181 continue;
182
183 // Get the default datatype.
184 pPrinter->pwszDefaultDatatype = AllocAndRegQueryWSZ(hSubKey, L"Datatype");
185 if (!pPrinter->pwszDefaultDatatype)
186 continue;
187
188 // Verify that it's valid.
189 if (!FindDatatype(pPrintProcessor, pPrinter->pwszDefaultDatatype))
190 {
191 ERR("Invalid default datatype \"%S\" for Printer \"%S\"!\n", pPrinter->pwszDefaultDatatype, wszPrinterName);
192 continue;
193 }
194
195 // Determine the size of the DevMode.
196 dwErrorCode = (DWORD)RegQueryValueExW(hSubKey, L"Default DevMode", NULL, NULL, NULL, &cbData);
197 if (dwErrorCode != ERROR_SUCCESS)
198 {
199 ERR("Couldn't query the size of the DevMode for Printer \"%S\", status is %lu, cbData is %lu!\n", wszPrinterName, dwErrorCode, cbData);
200 continue;
201 }
202
203 // Allocate enough memory for the DevMode.
204 pPrinter->pDefaultDevMode = DllAllocSplMem(cbData);
205 if (!pPrinter->pDefaultDevMode)
206 {
207 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
208 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
209 goto Cleanup;
210 }
211
212 // Get the default DevMode.
213 dwErrorCode = (DWORD)RegQueryValueExW(hSubKey, L"Default DevMode", NULL, NULL, (PBYTE)pPrinter->pDefaultDevMode, &cbData);
214 if (dwErrorCode != ERROR_SUCCESS)
215 {
216 ERR("Couldn't query a DevMode for Printer \"%S\", status is %lu, cbData is %lu!\n", wszPrinterName, dwErrorCode, cbData);
217 continue;
218 }
219
220 // Get the Attributes.
221 cbData = sizeof(DWORD);
222 dwErrorCode = (DWORD)RegQueryValueExW(hSubKey, L"Attributes", NULL, NULL, (PBYTE)&pPrinter->dwAttributes, &cbData);
223 if (dwErrorCode != ERROR_SUCCESS)
224 {
225 ERR("Couldn't query Attributes for Printer \"%S\", status is %lu!\n", wszPrinterName, dwErrorCode);
226 continue;
227 }
228
229 // Get the Status.
230 cbData = sizeof(DWORD);
231 dwErrorCode = (DWORD)RegQueryValueExW(hSubKey, L"Status", NULL, NULL, (PBYTE)&pPrinter->dwStatus, &cbData);
232 if (dwErrorCode != ERROR_SUCCESS)
233 {
234 ERR("Couldn't query Status for Printer \"%S\", status is %lu!\n", wszPrinterName, dwErrorCode);
235 continue;
236 }
237
238 // Add this printer to the printer list.
239 if (!InsertElementSkiplist(&PrinterList, pPrinter))
240 {
241 ERR("InsertElementSkiplist failed for Printer \"%S\"!\n", pPrinter->pwszPrinterName);
242 goto Cleanup;
243 }
244
245 // Don't let the cleanup routines free this.
246 pPrinter = NULL;
247 }
248
249 dwErrorCode = ERROR_SUCCESS;
250
251 Cleanup:
252 // Inside the loop
253 if (hSubKey)
254 RegCloseKey(hSubKey);
255
256 if (pPrinter)
257 {
258 if (pPrinter->pDefaultDevMode)
259 DllFreeSplMem(pPrinter->pDefaultDevMode);
260
261 if (pPrinter->pwszDefaultDatatype)
262 DllFreeSplStr(pPrinter->pwszDefaultDatatype);
263
264 if (pPrinter->pwszDescription)
265 DllFreeSplStr(pPrinter->pwszDescription);
266
267 if (pPrinter->pwszPrinterDriver)
268 DllFreeSplStr(pPrinter->pwszPrinterDriver);
269
270 if (pPrinter->pwszPrinterName)
271 DllFreeSplStr(pPrinter->pwszPrinterName);
272
273 DllFreeSplMem(pPrinter);
274 }
275
276 if (pwszPrintProcessor)
277 DllFreeSplStr(pwszPrintProcessor);
278
279 // Outside the loop
280 if (hKey)
281 RegCloseKey(hKey);
282
283 SetLastError(dwErrorCode);
284 return (dwErrorCode == ERROR_SUCCESS);
285 }
286
287
288 DWORD
289 _LocalEnumPrintersLevel1(DWORD Flags, LPWSTR Name, LPBYTE pPrinterEnum, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned)
290 {
291 const WCHAR wszComma[] = L",";
292
293 DWORD cbName;
294 DWORD cbComment;
295 DWORD cbDescription;
296 DWORD cchComputerName = 0;
297 DWORD dwErrorCode;
298 DWORD i;
299 PBYTE pPrinterInfo;
300 PBYTE pPrinterString;
301 PSKIPLIST_NODE pNode;
302 PLOCAL_PRINTER pPrinter;
303 PRINTER_INFO_1W PrinterInfo1;
304 WCHAR wszComputerName[2 + MAX_COMPUTERNAME_LENGTH + 1 + 1];
305
306 DWORD dwOffsets[] = {
307 FIELD_OFFSET(PRINTER_INFO_1W, pName),
308 FIELD_OFFSET(PRINTER_INFO_1W, pDescription),
309 FIELD_OFFSET(PRINTER_INFO_1W, pComment),
310 MAXDWORD
311 };
312
313 if (Flags & PRINTER_ENUM_NAME)
314 {
315 if (Name)
316 {
317 // The user supplied a Computer Name (with leading double backslashes) or Print Provider Name.
318 // Only process what's directed at us and dismiss every other request with ERROR_INVALID_NAME.
319 if (Name[0] == L'\\' && Name[1] == L'\\')
320 {
321 // Prepend slashes to the computer name.
322 wszComputerName[0] = L'\\';
323 wszComputerName[1] = L'\\';
324
325 // Get the local computer name for comparison.
326 cchComputerName = MAX_COMPUTERNAME_LENGTH + 1;
327 if (!GetComputerNameW(&wszComputerName[2], &cchComputerName))
328 {
329 dwErrorCode = GetLastError();
330 ERR("GetComputerNameW failed with error %lu!\n", dwErrorCode);
331 goto Cleanup;
332 }
333
334 // Add the leading slashes to the total length.
335 cchComputerName += 2;
336
337 // Now compare this with the local computer name and reject if it doesn't match.
338 if (wcsicmp(&Name[2], &wszComputerName[2]) != 0)
339 {
340 dwErrorCode = ERROR_INVALID_NAME;
341 goto Cleanup;
342 }
343
344 // Add a trailing backslash to wszComputerName, which will later be prepended in front of the printer names.
345 wszComputerName[cchComputerName++] = L'\\';
346 wszComputerName[cchComputerName] = 0;
347 }
348 else if (wcsicmp(Name, wszPrintProviderInfo[0]) != 0)
349 {
350 // The user supplied a name that cannot be processed by the local print provider.
351 dwErrorCode = ERROR_INVALID_NAME;
352 goto Cleanup;
353 }
354 }
355 else
356 {
357 // The caller wants information about this Print Provider.
358 // spoolss packs this into an array of information about all Print Providers.
359 *pcbNeeded = sizeof(PRINTER_INFO_1W);
360
361 for (i = 0; i < 3; i++)
362 *pcbNeeded += (wcslen(wszPrintProviderInfo[i]) + 1) * sizeof(WCHAR);
363
364 // Check if the supplied buffer is large enough.
365 if (cbBuf < *pcbNeeded)
366 {
367 dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
368 goto Cleanup;
369 }
370
371 // Copy over the print processor information.
372 ((PPRINTER_INFO_1W)pPrinterEnum)->Flags = 0;
373 PackStrings(wszPrintProviderInfo, pPrinterEnum, dwOffsets, &pPrinterEnum[*pcbNeeded]);
374 *pcReturned = 1;
375 dwErrorCode = ERROR_SUCCESS;
376 goto Cleanup;
377 }
378 }
379
380 // Count the required buffer size and the number of printers.
381 i = 0;
382
383 for (pNode = PrinterList.Head.Next[0]; pNode; pNode = pNode->Next[0])
384 {
385 pPrinter = (PLOCAL_PRINTER)pNode->Element;
386
387 // This looks wrong, but is totally right. PRINTER_INFO_1W has three members pName, pComment and pDescription.
388 // But pComment equals the "Description" registry value while pDescription is concatenated out of pName and pComment.
389 // On top of this, the computer name is prepended to the printer name if the user supplied the local computer name during the query.
390 cbName = (wcslen(pPrinter->pwszPrinterName) + 1) * sizeof(WCHAR);
391 cbComment = (wcslen(pPrinter->pwszDescription) + 1) * sizeof(WCHAR);
392 cbDescription = cchComputerName * sizeof(WCHAR) + cbName + cbComment + sizeof(WCHAR);
393
394 *pcbNeeded += sizeof(PRINTER_INFO_1W) + cchComputerName * sizeof(WCHAR) + cbName + cbComment + cbDescription;
395 i++;
396 }
397
398 // Check if the supplied buffer is large enough.
399 if (cbBuf < *pcbNeeded)
400 {
401 dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
402 goto Cleanup;
403 }
404
405 // Put the strings right after the last PRINTER_INFO_1W structure.
406 // Due to all the required string processing, we can't just use PackStrings here :(
407 pPrinterInfo = pPrinterEnum;
408 pPrinterString = pPrinterEnum + i * sizeof(PRINTER_INFO_1W);
409
410 // Copy over the printer information.
411 for (pNode = PrinterList.Head.Next[0]; pNode; pNode = pNode->Next[0])
412 {
413 pPrinter = (PLOCAL_PRINTER)pNode->Element;
414
415 // FIXME: As for now, the Flags member returns no information.
416 PrinterInfo1.Flags = 0;
417
418 // Copy the printer name.
419 PrinterInfo1.pName = (PWSTR)pPrinterString;
420 CopyMemory(pPrinterString, wszComputerName, cchComputerName * sizeof(WCHAR));
421 pPrinterString += cchComputerName * sizeof(WCHAR);
422 cbName = (wcslen(pPrinter->pwszPrinterName) + 1) * sizeof(WCHAR);
423 CopyMemory(pPrinterString, pPrinter->pwszPrinterName, cbName);
424 pPrinterString += cbName;
425
426 // Copy the printer comment (equals the "Description" registry value).
427 PrinterInfo1.pComment = (PWSTR)pPrinterString;
428 cbComment = (wcslen(pPrinter->pwszDescription) + 1) * sizeof(WCHAR);
429 CopyMemory(pPrinterString, pPrinter->pwszDescription, cbComment);
430 pPrinterString += cbComment;
431
432 // Copy the description, which for PRINTER_INFO_1W has the form "Name,Comment,"
433 PrinterInfo1.pDescription = (PWSTR)pPrinterString;
434 CopyMemory(pPrinterString, wszComputerName, cchComputerName * sizeof(WCHAR));
435 pPrinterString += cchComputerName * sizeof(WCHAR);
436 CopyMemory(pPrinterString, pPrinter->pwszPrinterName, cbName - sizeof(WCHAR));
437 pPrinterString += cbName - sizeof(WCHAR);
438 CopyMemory(pPrinterString, wszComma, sizeof(WCHAR));
439 pPrinterString += sizeof(WCHAR);
440 CopyMemory(pPrinterString, pPrinter->pwszDescription, cbComment - sizeof(WCHAR));
441 pPrinterString += cbComment - sizeof(WCHAR);
442 CopyMemory(pPrinterString, wszComma, sizeof(wszComma));
443 pPrinterString += sizeof(wszComma);
444
445 // Finally copy the structure and advance to the next one in the output buffer.
446 CopyMemory(pPrinterInfo, &PrinterInfo1, sizeof(PRINTER_INFO_1W));
447 pPrinterInfo += sizeof(PRINTER_INFO_1W);
448 }
449
450 *pcReturned = i;
451 dwErrorCode = ERROR_SUCCESS;
452
453 Cleanup:
454 return dwErrorCode;
455 }
456
457 BOOL WINAPI
458 LocalEnumPrinters(DWORD Flags, LPWSTR Name, DWORD Level, LPBYTE pPrinterEnum, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned)
459 {
460 DWORD dwErrorCode;
461
462 // Do no sanity checks here. This is verified by localspl_apitest!
463
464 // Begin counting.
465 *pcbNeeded = 0;
466 *pcReturned = 0;
467
468 // Think positive :)
469 // Treat it as success if the caller queried no information and we don't need to return any.
470 dwErrorCode = ERROR_SUCCESS;
471
472 if (Flags & PRINTER_ENUM_LOCAL)
473 {
474 // The function behaves quite differently for each level.
475 if (Level == 1)
476 {
477 dwErrorCode = _LocalEnumPrintersLevel1(Flags, Name, pPrinterEnum, cbBuf, pcbNeeded, pcReturned);
478 }
479 else
480 {
481 // TODO: Handle other levels.
482 // The caller supplied an invalid level.
483 dwErrorCode = ERROR_INVALID_LEVEL;
484 goto Cleanup;
485 }
486 }
487
488 Cleanup:
489 SetLastError(dwErrorCode);
490 return (dwErrorCode == ERROR_SUCCESS);
491 }
492
493 BOOL WINAPI
494 LocalOpenPrinter(PWSTR lpPrinterName, HANDLE* phPrinter, PPRINTER_DEFAULTSW pDefault)
495 {
496 BOOL bReturnValue;
497 DWORD cchComputerName;
498 DWORD cchFirstParameter;
499 DWORD dwErrorCode;
500 DWORD dwJobID;
501 HANDLE hExternalHandle;
502 PWSTR p = lpPrinterName;
503 PWSTR pwszFirstParameter = NULL;
504 PWSTR pwszSecondParameter = NULL;
505 PLOCAL_JOB pJob;
506 PLOCAL_HANDLE pHandle = NULL;
507 PLOCAL_PORT pPort;
508 PLOCAL_PRINT_MONITOR pPrintMonitor;
509 PLOCAL_PRINTER pPrinter;
510 PLOCAL_PRINTER_HANDLE pPrinterHandle = NULL;
511 WCHAR wszComputerName[MAX_COMPUTERNAME_LENGTH + 1];
512 WCHAR wszFullPath[MAX_PATH];
513
514 // TODO: lpPrinterName == NULL is supported and means access to the local printer server.
515 // Not sure yet if that is passed down to localspl.dll or processed in advance.
516
517 // Sanity checks
518 if (!lpPrinterName || !phPrinter)
519 {
520 dwErrorCode = ERROR_INVALID_PARAMETER;
521 goto Cleanup;
522 }
523
524 *phPrinter = NULL;
525
526 // Skip any server name in the first parameter.
527 // Does lpPrinterName begin with two backslashes to indicate a server name?
528 if (lpPrinterName[0] == L'\\' && lpPrinterName[1] == L'\\')
529 {
530 // Skip these two backslashes.
531 lpPrinterName += 2;
532
533 // Look for the closing backslash.
534 p = wcschr(lpPrinterName, L'\\');
535 if (!p)
536 {
537 // We didn't get a proper server name.
538 dwErrorCode = ERROR_INVALID_PRINTER_NAME;
539 goto Cleanup;
540 }
541
542 // Get the local computer name for comparison.
543 cchComputerName = _countof(wszComputerName);
544 if (!GetComputerNameW(wszComputerName, &cchComputerName))
545 {
546 dwErrorCode = GetLastError();
547 ERR("GetComputerNameW failed with error %lu!\n", dwErrorCode);
548 goto Cleanup;
549 }
550
551 // Now compare this string excerpt with the local computer name.
552 // The input parameter may not be writable, so we can't null-terminate the input string at this point.
553 // This print provider only supports local printers, so both strings have to match.
554 if (p - lpPrinterName != cchComputerName || _wcsnicmp(lpPrinterName, wszComputerName, cchComputerName) != 0)
555 {
556 dwErrorCode = ERROR_INVALID_PRINTER_NAME;
557 goto Cleanup;
558 }
559
560 // We have checked the server name and don't need it anymore.
561 lpPrinterName = p + 1;
562 }
563
564 // Look for a comma. If it exists, it indicates the end of the first parameter.
565 pwszSecondParameter = wcschr(lpPrinterName, L',');
566 if (pwszSecondParameter)
567 cchFirstParameter = pwszSecondParameter - p;
568 else
569 cchFirstParameter = wcslen(lpPrinterName);
570
571 // We must have at least one parameter.
572 if (!cchFirstParameter && !pwszSecondParameter)
573 {
574 dwErrorCode = ERROR_INVALID_PRINTER_NAME;
575 goto Cleanup;
576 }
577
578 // Do we have a first parameter?
579 if (cchFirstParameter)
580 {
581 // Yes, extract it.
582 // No null-termination is necessary here, because DllAllocSplMem returns a zero-initialized buffer.
583 pwszFirstParameter = DllAllocSplMem((cchFirstParameter + 1) * sizeof(WCHAR));
584 CopyMemory(pwszFirstParameter, lpPrinterName, cchFirstParameter * sizeof(WCHAR));
585 }
586
587 // Do we have a second parameter?
588 if (pwszSecondParameter)
589 {
590 // Yes, skip the comma at the beginning.
591 ++pwszSecondParameter;
592
593 // Skip whitespace as well.
594 while (*pwszSecondParameter == L' ')
595 ++pwszSecondParameter;
596 }
597
598 // Create a new handle.
599 pHandle = DllAllocSplMem(sizeof(LOCAL_HANDLE));
600 if (!pHandle)
601 {
602 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
603 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
604 goto Cleanup;
605 }
606
607 // Now we can finally check the type of handle actually requested.
608 if (pwszFirstParameter && pwszSecondParameter && wcsncmp(pwszSecondParameter, L"Port", 4) == 0)
609 {
610 // The caller wants a port handle and provided a string like:
611 // "LPT1:, Port"
612 // "\\COMPUTERNAME\LPT1:, Port"
613
614 // Look for this port in our Print Monitor Port list.
615 pPort = FindPort(pwszFirstParameter);
616 if (!pPort)
617 {
618 // The supplied port is unknown to all our Print Monitors.
619 dwErrorCode = ERROR_INVALID_PRINTER_NAME;
620 goto Cleanup;
621 }
622
623 pPrintMonitor = pPort->pPrintMonitor;
624
625 // Call the monitor's OpenPort function.
626 if (pPrintMonitor->bIsLevel2)
627 bReturnValue = ((PMONITOR2)pPrintMonitor->pMonitor)->pfnOpenPort(pPrintMonitor->hMonitor, pwszFirstParameter, &hExternalHandle);
628 else
629 bReturnValue = ((LPMONITOREX)pPrintMonitor->pMonitor)->Monitor.pfnOpenPort(pwszFirstParameter, &hExternalHandle);
630
631 if (!bReturnValue)
632 {
633 // The OpenPort function failed. Return its last error.
634 dwErrorCode = GetLastError();
635 goto Cleanup;
636 }
637
638 // Return the Port handle through our general handle.
639 pHandle->HandleType = HandleType_Port;
640 pHandle->pSpecificHandle = hExternalHandle;
641 }
642 else if (!pwszFirstParameter && pwszSecondParameter && wcsncmp(pwszSecondParameter, L"Xcv", 3) == 0)
643 {
644 // The caller wants an Xcv handle and provided a string like:
645 // ", XcvMonitor Local Port"
646 // "\\COMPUTERNAME\, XcvMonitor Local Port"
647 // ", XcvPort LPT1:"
648 // "\\COMPUTERNAME\, XcvPort LPT1:"
649
650 // Skip the "Xcv" string.
651 pwszSecondParameter += 3;
652
653 // Is XcvMonitor or XcvPort requested?
654 if (wcsncmp(pwszSecondParameter, L"Monitor ", 8) == 0)
655 {
656 // Skip the "Monitor " string.
657 pwszSecondParameter += 8;
658
659 // Look for this monitor in our Print Monitor list.
660 pPrintMonitor = FindPrintMonitor(pwszSecondParameter);
661 if (!pPrintMonitor)
662 {
663 // The caller supplied a non-existing Monitor name.
664 dwErrorCode = ERROR_INVALID_PRINTER_NAME;
665 goto Cleanup;
666 }
667 }
668 else if (wcsncmp(pwszSecondParameter, L"Port ", 5) == 0)
669 {
670 // Skip the "Port " string.
671 pwszSecondParameter += 5;
672
673 // Look for this port in our Print Monitor Port list.
674 pPort = FindPort(pwszFirstParameter);
675 if (!pPort)
676 {
677 // The supplied port is unknown to all our Print Monitors.
678 dwErrorCode = ERROR_INVALID_PRINTER_NAME;
679 goto Cleanup;
680 }
681
682 pPrintMonitor = pPort->pPrintMonitor;
683 }
684 else
685 {
686 dwErrorCode = ERROR_INVALID_PRINTER_NAME;
687 goto Cleanup;
688 }
689
690 // Call the monitor's XcvOpenPort function.
691 if (pPrintMonitor->bIsLevel2)
692 bReturnValue = ((PMONITOR2)pPrintMonitor->pMonitor)->pfnXcvOpenPort(pPrintMonitor->hMonitor, pwszSecondParameter, SERVER_EXECUTE, &hExternalHandle);
693 else
694 bReturnValue = ((LPMONITOREX)pPrintMonitor->pMonitor)->Monitor.pfnXcvOpenPort(pwszSecondParameter, SERVER_EXECUTE, &hExternalHandle);
695
696 if (!bReturnValue)
697 {
698 // The XcvOpenPort function failed. Return its last error.
699 dwErrorCode = GetLastError();
700 goto Cleanup;
701 }
702
703 // Return the Xcv handle through our general handle.
704 pHandle->HandleType = HandleType_Xcv;
705 pHandle->pSpecificHandle = hExternalHandle;
706 }
707 else
708 {
709 // The caller wants a Printer or Printer Job handle and provided a string like:
710 // "HP DeskJet"
711 // "\\COMPUTERNAME\HP DeskJet"
712 // "HP DeskJet, Job 5"
713 // "\\COMPUTERNAME\HP DeskJet, Job 5"
714
715 // Retrieve the printer from the list.
716 pPrinter = LookupElementSkiplist(&PrinterList, &pwszFirstParameter, NULL);
717 if (!pPrinter)
718 {
719 // The printer does not exist.
720 dwErrorCode = ERROR_INVALID_PRINTER_NAME;
721 goto Cleanup;
722 }
723
724 // Create a new printer handle.
725 pPrinterHandle = DllAllocSplMem(sizeof(LOCAL_PRINTER_HANDLE));
726 if (!pPrinterHandle)
727 {
728 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
729 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
730 goto Cleanup;
731 }
732
733 pPrinterHandle->hSPLFile = INVALID_HANDLE_VALUE;
734 pPrinterHandle->pPrinter = pPrinter;
735
736 // Check if a datatype was given.
737 if (pDefault && pDefault->pDatatype)
738 {
739 // Use the datatype if it's valid.
740 if (!FindDatatype(pPrinter->pPrintProcessor, pDefault->pDatatype))
741 {
742 dwErrorCode = ERROR_INVALID_DATATYPE;
743 goto Cleanup;
744 }
745
746 pPrinterHandle->pwszDatatype = AllocSplStr(pDefault->pDatatype);
747 }
748 else
749 {
750 // Use the default datatype.
751 pPrinterHandle->pwszDatatype = AllocSplStr(pPrinter->pwszDefaultDatatype);
752 }
753
754 // Check if a DevMode was given, otherwise use the default.
755 if (pDefault && pDefault->pDevMode)
756 pPrinterHandle->pDevMode = DuplicateDevMode(pDefault->pDevMode);
757 else
758 pPrinterHandle->pDevMode = DuplicateDevMode(pPrinter->pDefaultDevMode);
759
760 // Check if the caller wants a handle to an existing Print Job.
761 if (pwszSecondParameter)
762 {
763 // The "Job " string has to follow now.
764 if (wcsncmp(pwszSecondParameter, L"Job ", 4) != 0)
765 {
766 dwErrorCode = ERROR_INVALID_PRINTER_NAME;
767 goto Cleanup;
768 }
769
770 // Skip the "Job " string.
771 pwszSecondParameter += 4;
772
773 // Skip even more whitespace.
774 while (*pwszSecondParameter == ' ')
775 ++pwszSecondParameter;
776
777 // Finally extract the desired Job ID.
778 dwJobID = wcstoul(pwszSecondParameter, NULL, 10);
779 if (!IS_VALID_JOB_ID(dwJobID))
780 {
781 // The user supplied an invalid Job ID.
782 dwErrorCode = ERROR_INVALID_PRINTER_NAME;
783 goto Cleanup;
784 }
785
786 // Look for this job in the Global Job List.
787 pJob = LookupElementSkiplist(&GlobalJobList, &dwJobID, NULL);
788 if (!pJob || pJob->pPrinter != pPrinter)
789 {
790 // The user supplied a non-existing Job ID or the Job ID does not belong to the supplied printer name.
791 dwErrorCode = ERROR_INVALID_PRINTER_NAME;
792 goto Cleanup;
793 }
794
795 // Try to open its SPL file.
796 GetJobFilePath(L"SPL", dwJobID, wszFullPath);
797 pPrinterHandle->hSPLFile = CreateFileW(wszFullPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
798 if (pPrinterHandle->hSPLFile == INVALID_HANDLE_VALUE)
799 {
800 dwErrorCode = GetLastError();
801 ERR("CreateFileW failed with error %lu for \"%S\"!", dwErrorCode, wszFullPath);
802 goto Cleanup;
803 }
804
805 // Associate the job to our Printer Handle, but don't set bStartedDoc.
806 // This prevents the caller from doing further StartDocPrinter, WritePrinter, etc. calls on it.
807 pPrinterHandle->pJob = pJob;
808 }
809
810 // Return the Printer handle through our general handle.
811 pHandle->HandleType = HandleType_Printer;
812 pHandle->pSpecificHandle = pPrinterHandle;
813 }
814
815 // We were successful! Return the handle.
816 *phPrinter = (HANDLE)pHandle;
817 dwErrorCode = ERROR_SUCCESS;
818
819 // Don't let the cleanup routines free this.
820 pHandle = NULL;
821 pPrinterHandle = NULL;
822
823 Cleanup:
824 if (pHandle)
825 DllFreeSplMem(pHandle);
826
827 if (pPrinterHandle)
828 {
829 if (pPrinterHandle->pwszDatatype)
830 DllFreeSplStr(pPrinterHandle->pwszDatatype);
831
832 if (pPrinterHandle->pDevMode)
833 DllFreeSplMem(pPrinterHandle->pDevMode);
834
835 DllFreeSplMem(pPrinterHandle);
836 }
837
838 if (pwszFirstParameter)
839 DllFreeSplMem(pwszFirstParameter);
840
841 SetLastError(dwErrorCode);
842 return (dwErrorCode == ERROR_SUCCESS);
843 }
844
845 BOOL WINAPI
846 LocalReadPrinter(HANDLE hPrinter, PVOID pBuf, DWORD cbBuf, PDWORD pNoBytesRead)
847 {
848 DWORD dwErrorCode;
849 PLOCAL_HANDLE pHandle = (PLOCAL_HANDLE)hPrinter;
850 PLOCAL_PRINTER_HANDLE pPrinterHandle;
851
852 // Sanity checks.
853 if (!pHandle || pHandle->HandleType != HandleType_Printer)
854 {
855 dwErrorCode = ERROR_INVALID_HANDLE;
856 goto Cleanup;
857 }
858
859 pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
860
861 // ReadPrinter needs an opened SPL file to work.
862 // This only works if a Printer Job Handle was requested in OpenPrinter.
863 if (pPrinterHandle->hSPLFile == INVALID_HANDLE_VALUE)
864 {
865 dwErrorCode = ERROR_INVALID_HANDLE;
866 goto Cleanup;
867 }
868
869 // Pass the parameters to ReadFile.
870 if (!ReadFile(pPrinterHandle->hSPLFile, pBuf, cbBuf, pNoBytesRead, NULL))
871 {
872 dwErrorCode = GetLastError();
873 ERR("ReadFile failed with error %lu!\n", dwErrorCode);
874 goto Cleanup;
875 }
876
877 dwErrorCode = ERROR_SUCCESS;
878
879 Cleanup:
880 SetLastError(dwErrorCode);
881 return (dwErrorCode == ERROR_SUCCESS);
882 }
883
884 DWORD WINAPI
885 LocalStartDocPrinter(HANDLE hPrinter, DWORD Level, PBYTE pDocInfo)
886 {
887 DWORD dwErrorCode;
888 DWORD dwReturnValue = 0;
889 PDOC_INFO_1W pDocInfo1 = (PDOC_INFO_1W)pDocInfo;
890 PLOCAL_HANDLE pHandle = (PLOCAL_HANDLE)hPrinter;
891 PLOCAL_PRINTER_HANDLE pPrinterHandle;
892
893 // Sanity checks.
894 if (!pDocInfo1)
895 {
896 dwErrorCode = ERROR_INVALID_PARAMETER;
897 goto Cleanup;
898 }
899
900 if (!pHandle || pHandle->HandleType != HandleType_Printer)
901 {
902 dwErrorCode = ERROR_INVALID_HANDLE;
903 goto Cleanup;
904 }
905
906 pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
907
908 // pJob may already be occupied if this is a Print Job handle. In this case, StartDocPrinter has to fail.
909 if (pPrinterHandle->pJob)
910 {
911 dwErrorCode = ERROR_INVALID_PARAMETER;
912 goto Cleanup;
913 }
914
915 // Check the validity of the datatype if we got one.
916 if (pDocInfo1->pDatatype && !FindDatatype(pPrinterHandle->pJob->pPrintProcessor, pDocInfo1->pDatatype))
917 {
918 dwErrorCode = ERROR_INVALID_DATATYPE;
919 goto Cleanup;
920 }
921
922 // Check if this is the right document information level.
923 if (Level != 1)
924 {
925 dwErrorCode = ERROR_INVALID_LEVEL;
926 goto Cleanup;
927 }
928
929 // All requirements are met - create a new job.
930 dwErrorCode = CreateJob(pPrinterHandle);
931 if (dwErrorCode != ERROR_SUCCESS)
932 goto Cleanup;
933
934 // Use any given datatype.
935 if (pDocInfo1->pDatatype && !ReallocSplStr(&pPrinterHandle->pJob->pwszDatatype, pDocInfo1->pDatatype))
936 {
937 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
938 ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
939 goto Cleanup;
940 }
941
942 // Use any given document name.
943 if (pDocInfo1->pDocName && !ReallocSplStr(&pPrinterHandle->pJob->pwszDocumentName, pDocInfo1->pDocName))
944 {
945 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
946 ERR("ReallocSplStr failed, last error is %lu!\n", GetLastError());
947 goto Cleanup;
948 }
949
950 // We were successful!
951 dwErrorCode = ERROR_SUCCESS;
952 dwReturnValue = pPrinterHandle->pJob->dwJobID;
953
954 Cleanup:
955 SetLastError(dwErrorCode);
956 return dwReturnValue;
957 }
958
959 BOOL WINAPI
960 LocalStartPagePrinter(HANDLE hPrinter)
961 {
962 DWORD dwErrorCode;
963 PLOCAL_HANDLE pHandle = (PLOCAL_HANDLE)hPrinter;
964 PLOCAL_PRINTER_HANDLE pPrinterHandle;
965
966 // Sanity checks.
967 if (!pHandle || pHandle->HandleType != HandleType_Printer)
968 {
969 dwErrorCode = ERROR_INVALID_HANDLE;
970 goto Cleanup;
971 }
972
973 pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
974
975 // We require StartDocPrinter or AddJob to be called first.
976 if (!pPrinterHandle->bStartedDoc)
977 {
978 dwErrorCode = ERROR_SPL_NO_STARTDOC;
979 goto Cleanup;
980 }
981
982 // Increase the page count.
983 ++pPrinterHandle->pJob->dwTotalPages;
984 dwErrorCode = ERROR_SUCCESS;
985
986 Cleanup:
987 SetLastError(dwErrorCode);
988 return (dwErrorCode == ERROR_SUCCESS);
989 }
990
991 BOOL WINAPI
992 LocalWritePrinter(HANDLE hPrinter, LPVOID pBuf, DWORD cbBuf, LPDWORD pcWritten)
993 {
994 DWORD dwErrorCode;
995 PLOCAL_HANDLE pHandle = (PLOCAL_HANDLE)hPrinter;
996 PLOCAL_PRINTER_HANDLE pPrinterHandle;
997
998 // Sanity checks.
999 if (!pHandle || pHandle->HandleType != HandleType_Printer)
1000 {
1001 dwErrorCode = ERROR_INVALID_HANDLE;
1002 goto Cleanup;
1003 }
1004
1005 pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
1006
1007 // We require StartDocPrinter or AddJob to be called first.
1008 if (!pPrinterHandle->bStartedDoc)
1009 {
1010 dwErrorCode = ERROR_SPL_NO_STARTDOC;
1011 goto Cleanup;
1012 }
1013
1014 // TODO: This function is only called when doing non-spooled printing.
1015 // This needs to be investigated further. We can't just use pPrinterHandle->hSPLFile here, because that's currently reserved for Printer Job handles (see LocalReadPrinter).
1016 #if 0
1017 // Pass the parameters to WriteFile.
1018 if (!WriteFile(SOME_SPOOL_FILE_HANDLE, pBuf, cbBuf, pcWritten, NULL))
1019 {
1020 dwErrorCode = GetLastError();
1021 ERR("WriteFile failed with error %lu!\n", GetLastError());
1022 goto Cleanup;
1023 }
1024 #endif
1025
1026 dwErrorCode = ERROR_SUCCESS;
1027
1028 Cleanup:
1029 SetLastError(dwErrorCode);
1030 return (dwErrorCode == ERROR_SUCCESS);
1031 }
1032
1033 BOOL WINAPI
1034 LocalEndPagePrinter(HANDLE hPrinter)
1035 {
1036 DWORD dwErrorCode;
1037 PLOCAL_HANDLE pHandle = (PLOCAL_HANDLE)hPrinter;
1038
1039 // Sanity checks.
1040 if (!pHandle || pHandle->HandleType != HandleType_Printer)
1041 {
1042 dwErrorCode = ERROR_INVALID_HANDLE;
1043 goto Cleanup;
1044 }
1045
1046 // This function doesn't do anything else for now.
1047 dwErrorCode = ERROR_SUCCESS;
1048
1049 Cleanup:
1050 SetLastError(dwErrorCode);
1051 return (dwErrorCode == ERROR_SUCCESS);
1052 }
1053
1054 BOOL WINAPI
1055 LocalEndDocPrinter(HANDLE hPrinter)
1056 {
1057 DWORD dwErrorCode;
1058 PLOCAL_HANDLE pHandle = (PLOCAL_HANDLE)hPrinter;
1059 PLOCAL_PRINTER_HANDLE pPrinterHandle;
1060
1061 // Sanity checks.
1062 if (!pHandle || pHandle->HandleType != HandleType_Printer)
1063 {
1064 dwErrorCode = ERROR_INVALID_HANDLE;
1065 goto Cleanup;
1066 }
1067
1068 pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
1069
1070 // We require StartDocPrinter or AddJob to be called first.
1071 if (!pPrinterHandle->bStartedDoc)
1072 {
1073 dwErrorCode = ERROR_SPL_NO_STARTDOC;
1074 goto Cleanup;
1075 }
1076
1077 // TODO: Something like ScheduleJob
1078
1079 // Finish the job.
1080 pPrinterHandle->bStartedDoc = FALSE;
1081 pPrinterHandle->pJob = NULL;
1082 dwErrorCode = ERROR_SUCCESS;
1083
1084 Cleanup:
1085 SetLastError(dwErrorCode);
1086 return (dwErrorCode == ERROR_SUCCESS);
1087 }
1088
1089 BOOL WINAPI
1090 LocalClosePrinter(HANDLE hPrinter)
1091 {
1092 PLOCAL_HANDLE pHandle = (PLOCAL_HANDLE)hPrinter;
1093 PLOCAL_PRINTER_HANDLE pPrinterHandle;
1094
1095 if (!pHandle)
1096 {
1097 SetLastError(ERROR_INVALID_HANDLE);
1098 return FALSE;
1099 }
1100
1101 if (pHandle->HandleType == HandleType_Port)
1102 {
1103 // TODO
1104 }
1105 else if (pHandle->HandleType == HandleType_Printer)
1106 {
1107 pPrinterHandle = (PLOCAL_PRINTER_HANDLE)pHandle->pSpecificHandle;
1108
1109 // Terminate any started job.
1110 if (pPrinterHandle->pJob)
1111 FreeJob(pPrinterHandle->pJob);
1112
1113 // Free memory for the fields.
1114 DllFreeSplMem(pPrinterHandle->pDevMode);
1115 DllFreeSplStr(pPrinterHandle->pwszDatatype);
1116
1117 // Free memory for the printer handle itself.
1118 DllFreeSplMem(pPrinterHandle);
1119 }
1120 else if (pHandle->HandleType == HandleType_Xcv)
1121 {
1122 // TODO
1123 }
1124
1125 // Free memory for the handle itself.
1126 DllFreeSplMem(pHandle);
1127
1128 return TRUE;
1129 }