[PRINTING] The first argument to PackStrings can be const.
[reactos.git] / win32ss / printing / monitors / localmon / ports.c
1 /*
2 * PROJECT: ReactOS Local Port Monitor
3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4 * PURPOSE: Functions related to ports
5 * COPYRIGHT: Copyright 2015-2017 Colin Finck (colin@reactos.org)
6 */
7
8 #include "precomp.h"
9
10 // Local Constants
11 static const WCHAR wszNonspooledPrefix[] = L"NONSPOOLED_";
12 static const DWORD cchNonspooledPrefix = _countof(wszNonspooledPrefix) - 1;
13
14 static DWORD dwPortInfo1Offsets[] = {
15 FIELD_OFFSET(PORT_INFO_1W, pName),
16 MAXDWORD
17 };
18
19 static DWORD dwPortInfo2Offsets[] = {
20 FIELD_OFFSET(PORT_INFO_2W, pPortName),
21 FIELD_OFFSET(PORT_INFO_2W, pMonitorName),
22 FIELD_OFFSET(PORT_INFO_2W, pDescription),
23 MAXDWORD
24 };
25
26
27 /**
28 * @name _GetNonspooledPortName
29 *
30 * Prepends "NONSPOOLED_" to a port name without colon.
31 *
32 * @param pwszPortNameWithoutColon
33 * Result of a previous GetPortNameWithoutColon call.
34 *
35 * @param ppwszNonspooledPortName
36 * Pointer to a buffer that will contain the NONSPOOLED port name.
37 * You have to free this buffer using DllFreeSplMem.
38 *
39 * @return
40 * ERROR_SUCCESS if the NONSPOOLED port name was successfully copied into the buffer.
41 * ERROR_NOT_ENOUGH_MEMORY if memory allocation failed.
42 */
43 static __inline DWORD
44 _GetNonspooledPortName(PCWSTR pwszPortNameWithoutColon, PWSTR* ppwszNonspooledPortName)
45 {
46 DWORD cchPortNameWithoutColon;
47
48 cchPortNameWithoutColon = wcslen(pwszPortNameWithoutColon);
49
50 *ppwszNonspooledPortName = DllAllocSplMem((cchNonspooledPrefix + cchPortNameWithoutColon + 1) * sizeof(WCHAR));
51 if (!*ppwszNonspooledPortName)
52 {
53 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
54 return ERROR_NOT_ENOUGH_MEMORY;
55 }
56
57 CopyMemory(*ppwszNonspooledPortName, wszNonspooledPrefix, cchNonspooledPrefix * sizeof(WCHAR));
58 CopyMemory(&(*ppwszNonspooledPortName)[cchNonspooledPrefix], pwszPortNameWithoutColon, (cchPortNameWithoutColon + 1) * sizeof(WCHAR));
59
60 return ERROR_SUCCESS;
61 }
62
63 /**
64 * @name _IsLegacyPort
65 *
66 * Checks if the given port name is a legacy port (COM or LPT).
67 * This check is extra picky to not cause false positives (like file name ports starting with "COM" or "LPT").
68 *
69 * @param pwszPortName
70 * The port name to check.
71 *
72 * @param pwszPortType
73 * L"COM" or L"LPT"
74 *
75 * @return
76 * TRUE if this is definitely the asked legacy port, FALSE if not.
77 */
78 static __inline BOOL
79 _IsLegacyPort(PCWSTR pwszPortName, PCWSTR pwszPortType)
80 {
81 const DWORD cchPortType = 3;
82 PCWSTR p = pwszPortName;
83
84 // The port name must begin with pwszPortType.
85 if (_wcsnicmp(p, pwszPortType, cchPortType) != 0)
86 return FALSE;
87
88 p += cchPortType;
89
90 // Now an arbitrary number of digits may follow.
91 while (*p >= L'0' && *p <= L'9')
92 p++;
93
94 // Finally, the legacy port must be terminated by a colon.
95 if (*p != ':')
96 return FALSE;
97
98 // If this is the end of the string, we have a legacy port.
99 p++;
100 return (*p == L'\0');
101 }
102
103 /**
104 * @name _ClosePortHandles
105 *
106 * Closes a port of any type if it's open.
107 * Removes any saved mapping or existing definition of a NONSPOOLED device mapping.
108 *
109 * @param pPort
110 * The port you want to close.
111 */
112 static void
113 _ClosePortHandles(PLOCALMON_PORT pPort)
114 {
115 PWSTR pwszNonspooledPortName;
116 PWSTR pwszPortNameWithoutColon;
117
118 // A port is already fully closed if the file handle is invalid.
119 if (pPort->hFile == INVALID_HANDLE_VALUE)
120 return;
121
122 // Close the file handle.
123 CloseHandle(pPort->hFile);
124 pPort->hFile = INVALID_HANDLE_VALUE;
125
126 // A NONSPOOLED port was only created if pwszMapping contains the current port mapping.
127 if (!pPort->pwszMapping)
128 return;
129
130 // Free the information about the current mapping.
131 DllFreeSplStr(pPort->pwszMapping);
132 pPort->pwszMapping = NULL;
133
134 // Finally get the required strings and remove the DOS device definition for the NONSPOOLED port.
135 if (GetPortNameWithoutColon(pPort->pwszPortName, &pwszPortNameWithoutColon) == ERROR_SUCCESS)
136 {
137 if (_GetNonspooledPortName(pwszPortNameWithoutColon, &pwszNonspooledPortName) == ERROR_SUCCESS)
138 {
139 DefineDosDeviceW(DDD_REMOVE_DEFINITION, pwszNonspooledPortName, NULL);
140 DllFreeSplMem(pwszNonspooledPortName);
141 }
142
143 DllFreeSplMem(pwszPortNameWithoutColon);
144 }
145 }
146
147 /**
148 * @name _CreateNonspooledPort
149 *
150 * Queries the system-wide device definition of the given port.
151 * If such a definition exists, it's a legacy port remapped to a named pipe by the spooler.
152 * In this case, the function creates and opens a NONSPOOLED device definition to the most recent mapping before the current one (usually the physical device).
153 *
154 * @param pPort
155 * Pointer to the LOCALMON_PORT structure of the desired port.
156 *
157 * @return
158 * TRUE if a NONSPOOLED port was successfully created, FALSE otherwise.
159 * A more specific error code can be obtained through GetLastError.
160 * In particular, if the return value is FALSE and GetLastError returns ERROR_SUCCESS, no NONSPOOLED port is needed for this port.
161 */
162 static BOOL
163 _CreateNonspooledPort(PLOCALMON_PORT pPort)
164 {
165 const WCHAR wszLocalSlashes[] = L"\\\\.\\";
166 const DWORD cchLocalSlashes = _countof(wszLocalSlashes) - 1;
167
168 const WCHAR wszSpoolerNamedPipe[] = L"\\Device\\NamedPipe\\Spooler\\";
169 const DWORD cchSpoolerNamedPipe = _countof(wszSpoolerNamedPipe) - 1;
170
171 BOOL bReturnValue = FALSE;
172 DWORD cchPortNameWithoutColon;
173 DWORD dwErrorCode;
174 HANDLE hToken = NULL;
175 PWSTR p;
176 PWSTR pwszDeviceMappings = NULL;
177 PWSTR pwszNonspooledFileName = NULL;
178 PWSTR pwszNonspooledPortName = NULL;
179 PWSTR pwszPipeName = NULL;
180 PWSTR pwszPortNameWithoutColon = NULL;
181
182 // We need the port name without the trailing colon.
183 dwErrorCode = GetPortNameWithoutColon(pPort->pwszPortName, &pwszPortNameWithoutColon);
184 if (dwErrorCode == ERROR_INVALID_PARAMETER)
185 {
186 // This port has no trailing colon, so we also need no NONSPOOLED mapping for it.
187 dwErrorCode = ERROR_SUCCESS;
188 goto Cleanup;
189 }
190 else if (dwErrorCode != ERROR_SUCCESS)
191 {
192 // Another unexpected failure.
193 goto Cleanup;
194 }
195
196 cchPortNameWithoutColon = wcslen(pwszPortNameWithoutColon);
197
198 // The spooler has usually remapped the legacy port to a named pipe of the format in wszSpoolerNamedPipe.
199 // Construct the device name of this pipe.
200 pwszPipeName = DllAllocSplMem((cchSpoolerNamedPipe + cchPortNameWithoutColon + 1) * sizeof(WCHAR));
201 if (!pwszPipeName)
202 {
203 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
204 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
205 goto Cleanup;
206 }
207
208 CopyMemory(pwszPipeName, wszSpoolerNamedPipe, cchSpoolerNamedPipe * sizeof(WCHAR));
209 CopyMemory(&pwszPipeName[cchSpoolerNamedPipe], pwszPortNameWithoutColon, (cchPortNameWithoutColon + 1) * sizeof(WCHAR));
210
211 // QueryDosDeviceW is one of the shitty APIs that gives no information about the required buffer size and wants you to know it by pure magic.
212 // Examples show that a value of MAX_PATH * sizeof(WCHAR) is usually taken here, so we have no other option either.
213 pwszDeviceMappings = DllAllocSplMem(MAX_PATH * sizeof(WCHAR));
214 if (!pwszDeviceMappings)
215 {
216 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
217 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
218 goto Cleanup;
219 }
220
221 // Switch to the SYSTEM context, because we're only interested in creating NONSPOOLED ports for system-wide ports.
222 // User-local ports (like _some_ redirected networked ones) aren't remapped by the spooler and can be opened directly.
223 hToken = RevertToPrinterSelf();
224 if (!hToken)
225 {
226 dwErrorCode = GetLastError();
227 ERR("RevertToPrinterSelf failed with error %lu!\n", dwErrorCode);
228 goto Cleanup;
229 }
230
231 // QueryDosDeviceW returns the current mapping and a list of prior mappings of this legacy port, which is managed as a DOS device in the system.
232 if (!QueryDosDeviceW(pwszPortNameWithoutColon, pwszDeviceMappings, MAX_PATH))
233 {
234 // No system-wide port exists, so we also need no NONSPOOLED mapping.
235 dwErrorCode = ERROR_SUCCESS;
236 goto Cleanup;
237 }
238
239 // Check if this port has already been opened by _CreateNonspooledPort previously.
240 if (pPort->pwszMapping)
241 {
242 // In this case, we just need to do something if the mapping has changed.
243 // Therefore, check if the stored mapping equals the current mapping.
244 if (wcscmp(pPort->pwszMapping, pwszDeviceMappings) == 0)
245 {
246 // We don't need to do anything in this case.
247 dwErrorCode = ERROR_SUCCESS;
248 goto Cleanup;
249 }
250 else
251 {
252 // Close the open file handle and free the memory for pwszMapping before remapping.
253 CloseHandle(pPort->hFile);
254 pPort->hFile = INVALID_HANDLE_VALUE;
255
256 DllFreeSplStr(pPort->pwszMapping);
257 pPort->pwszMapping = NULL;
258 }
259 }
260
261 // The port is usually mapped to the named pipe and this is how we received our data for printing.
262 // What we now need for accessing the actual port is the most recent mapping different from the named pipe.
263 p = pwszDeviceMappings;
264
265 for (;;)
266 {
267 if (!*p)
268 {
269 // We reached the end of the list without finding a mapping.
270 ERR("Can't find a suitable mapping for the port \"%S\"!", pPort->pwszPortName);
271 goto Cleanup;
272 }
273
274 if (_wcsicmp(p, pwszPipeName) != 0)
275 break;
276
277 // Advance to the next mapping in the list.
278 p += wcslen(p) + 1;
279 }
280
281 // We now want to create a DOS device "NONSPOOLED_<PortName>" to this mapping, so that we're able to open it through CreateFileW.
282 dwErrorCode = _GetNonspooledPortName(pwszPortNameWithoutColon, &pwszNonspooledPortName);
283 if (dwErrorCode != ERROR_SUCCESS)
284 goto Cleanup;
285
286 // Delete a possibly existing NONSPOOLED device before creating the new one, so we don't stack up device definitions.
287 DefineDosDeviceW(DDD_REMOVE_DEFINITION, pwszNonspooledPortName, NULL);
288
289 if (!DefineDosDeviceW(DDD_RAW_TARGET_PATH, pwszNonspooledPortName, p))
290 {
291 dwErrorCode = GetLastError();
292 ERR("DefineDosDeviceW failed with error %lu!\n", dwErrorCode);
293 goto Cleanup;
294 }
295
296 // This is all we needed to do in SYSTEM context.
297 ImpersonatePrinterClient(hToken);
298 hToken = NULL;
299
300 // Construct the file name to our created device for CreateFileW.
301 pwszNonspooledFileName = DllAllocSplMem((cchLocalSlashes + cchNonspooledPrefix + cchPortNameWithoutColon + 1) * sizeof(WCHAR));
302 if (!pwszNonspooledFileName)
303 {
304 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
305 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
306 goto Cleanup;
307 }
308
309 CopyMemory(pwszNonspooledFileName, wszLocalSlashes, cchLocalSlashes * sizeof(WCHAR));
310 CopyMemory(&pwszNonspooledFileName[cchLocalSlashes], wszNonspooledPrefix, cchNonspooledPrefix * sizeof(WCHAR));
311 CopyMemory(&pwszNonspooledFileName[cchLocalSlashes + cchNonspooledPrefix], pwszPortNameWithoutColon, (cchPortNameWithoutColon + 1) * sizeof(WCHAR));
312
313 // Finally open it for reading and writing.
314 pPort->hFile = CreateFileW(pwszNonspooledFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL);
315 if (pPort->hFile == INVALID_HANDLE_VALUE)
316 {
317 dwErrorCode = GetLastError();
318 ERR("CreateFileW failed with error %lu!\n", dwErrorCode);
319 goto Cleanup;
320 }
321
322 // Store the current mapping of the port, so that we can check if it has changed.
323 pPort->pwszMapping = AllocSplStr(pwszDeviceMappings);
324 if (!pPort->pwszMapping)
325 {
326 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
327 goto Cleanup;
328 }
329
330 bReturnValue = TRUE;
331 dwErrorCode = ERROR_SUCCESS;
332
333 Cleanup:
334 if (hToken)
335 ImpersonatePrinterClient(hToken);
336
337 if (pwszDeviceMappings)
338 DllFreeSplMem(pwszDeviceMappings);
339
340 if (pwszNonspooledFileName)
341 DllFreeSplMem(pwszNonspooledFileName);
342
343 if (pwszNonspooledPortName)
344 DllFreeSplMem(pwszNonspooledPortName);
345
346 if (pwszPipeName)
347 DllFreeSplMem(pwszPipeName);
348
349 if (pwszPortNameWithoutColon)
350 DllFreeSplMem(pwszPortNameWithoutColon);
351
352 SetLastError(dwErrorCode);
353 return bReturnValue;
354 }
355
356 static PLOCALMON_PORT
357 _FindPort(PLOCALMON_HANDLE pLocalmon, PCWSTR pwszPortName)
358 {
359 PLIST_ENTRY pEntry;
360 PLOCALMON_PORT pPort;
361
362 for (pEntry = pLocalmon->RegistryPorts.Flink; pEntry != &pLocalmon->RegistryPorts; pEntry = pEntry->Flink)
363 {
364 pPort = CONTAINING_RECORD(pEntry, LOCALMON_PORT, Entry);
365
366 if (wcscmp(pPort->pwszPortName, pwszPortName) == 0)
367 return pPort;
368 }
369
370 return NULL;
371 }
372
373 static void
374 _LocalmonGetPortLevel1(PLOCALMON_PORT pPort, PPORT_INFO_1W* ppPortInfo, PBYTE* ppPortInfoEnd, PDWORD pcbNeeded)
375 {
376 DWORD cbPortName;
377 PCWSTR pwszStrings[1];
378
379 // Calculate the string lengths.
380 if (!ppPortInfo)
381 {
382 cbPortName = (wcslen(pPort->pwszPortName) + 1) * sizeof(WCHAR);
383
384 *pcbNeeded += sizeof(PORT_INFO_1W) + cbPortName;
385 return;
386 }
387
388 // Set the pName field.
389 pwszStrings[0] = pPort->pwszPortName;
390
391 // Copy the structure and advance to the next one in the output buffer.
392 *ppPortInfoEnd = PackStrings(pwszStrings, (PBYTE)(*ppPortInfo), dwPortInfo1Offsets, *ppPortInfoEnd);
393 (*ppPortInfo)++;
394 }
395
396 static void
397 _LocalmonGetPortLevel2(PLOCALMON_PORT pPort, PPORT_INFO_2W* ppPortInfo, PBYTE* ppPortInfoEnd, PDWORD pcbNeeded)
398 {
399 DWORD cbPortName;
400 PCWSTR pwszStrings[3];
401
402 // Calculate the string lengths.
403 if (!ppPortInfo)
404 {
405 cbPortName = (wcslen(pPort->pwszPortName) + 1) * sizeof(WCHAR);
406
407 *pcbNeeded += sizeof(PORT_INFO_2W) + cbPortName + cbLocalMonitor + cbLocalPort;
408 return;
409 }
410
411 // All local ports are writable and readable.
412 (*ppPortInfo)->fPortType = PORT_TYPE_WRITE | PORT_TYPE_READ;
413 (*ppPortInfo)->Reserved = 0;
414
415 // Set the pPortName field.
416 pwszStrings[0] = pPort->pwszPortName;
417
418 // Set the pMonitorName field.
419 pwszStrings[1] = (PWSTR)pwszLocalMonitor;
420
421 // Set the pDescription field.
422 pwszStrings[2] = (PWSTR)pwszLocalPort;
423
424 // Copy the structure and advance to the next one in the output buffer.
425 *ppPortInfoEnd = PackStrings(pwszStrings, (PBYTE)(*ppPortInfo), dwPortInfo2Offsets, *ppPortInfoEnd);
426 (*ppPortInfo)++;
427 }
428
429 /**
430 * @name _SetTransmissionRetryTimeout
431 *
432 * Checks if the given port is a physical one and sets the transmission retry timeout in this case using the value from registry.
433 *
434 * @param pPort
435 * The port to operate on.
436 *
437 * @return
438 * TRUE if the given port is a physical one, FALSE otherwise.
439 */
440 static BOOL
441 _SetTransmissionRetryTimeout(PLOCALMON_PORT pPort)
442 {
443 COMMTIMEOUTS CommTimeouts;
444
445 // Get the timeout from the port.
446 if (!GetCommTimeouts(pPort->hFile, &CommTimeouts))
447 return FALSE;
448
449 // Set the timeout using the value from registry.
450 CommTimeouts.WriteTotalTimeoutConstant = GetLPTTransmissionRetryTimeout() * 1000;
451 SetCommTimeouts(pPort->hFile, &CommTimeouts);
452
453 return TRUE;
454 }
455
456 BOOL WINAPI
457 LocalmonClosePort(HANDLE hPort)
458 {
459 PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort;
460
461 TRACE("LocalmonClosePort(%p)\n", hPort);
462
463 // Sanity checks
464 if (!pPort)
465 {
466 SetLastError(ERROR_INVALID_PARAMETER);
467 return FALSE;
468 }
469
470 // Close the file handle, free memory for pwszMapping and delete any NONSPOOLED port.
471 _ClosePortHandles(pPort);
472
473 // Close any open printer handle.
474 if (pPort->hPrinter)
475 {
476 ClosePrinter(pPort->hPrinter);
477 pPort->hPrinter = NULL;
478 }
479
480 // Free virtual FILE: ports which were created in LocalmonOpenPort.
481 if (pPort->PortType == PortType_FILE)
482 {
483 EnterCriticalSection(&pPort->pLocalmon->Section);
484 RemoveEntryList(&pPort->Entry);
485 LeaveCriticalSection(&pPort->pLocalmon->Section);
486 DllFreeSplMem(pPort);
487 }
488
489 SetLastError(ERROR_SUCCESS);
490 return TRUE;
491 }
492
493 BOOL WINAPI
494 LocalmonEndDocPort(HANDLE hPort)
495 {
496 PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort;
497
498 TRACE("LocalmonEndDocPort(%p)\n", hPort);
499
500 // Sanity checks
501 if (!pPort)
502 {
503 SetLastError(ERROR_INVALID_PARAMETER);
504 return FALSE;
505 }
506
507 // Ending a document requires starting it first :-P
508 if (pPort->bStartedDoc)
509 {
510 // Close all ports opened in StartDocPort.
511 // That is, all but physical LPT ports (opened in OpenPort).
512 if (pPort->PortType != PortType_PhysicalLPT)
513 _ClosePortHandles(pPort);
514
515 // Report our progress.
516 SetJobW(pPort->hPrinter, pPort->dwJobID, 0, NULL, JOB_CONTROL_SENT_TO_PRINTER);
517
518 // We're done with the printer.
519 ClosePrinter(pPort->hPrinter);
520 pPort->hPrinter = NULL;
521
522 // A new document can now be started again.
523 pPort->bStartedDoc = FALSE;
524 }
525
526 SetLastError(ERROR_SUCCESS);
527 return TRUE;
528 }
529
530 BOOL WINAPI
531 LocalmonEnumPorts(HANDLE hMonitor, PWSTR pName, DWORD Level, PBYTE pPorts, DWORD cbBuf, PDWORD pcbNeeded, PDWORD pcReturned)
532 {
533 DWORD dwErrorCode;
534 PBYTE pPortInfoEnd;
535 PLIST_ENTRY pEntry;
536 PLOCALMON_HANDLE pLocalmon = (PLOCALMON_HANDLE)hMonitor;
537 PLOCALMON_PORT pPort;
538
539 TRACE("LocalmonEnumPorts(%p, %S, %lu, %p, %lu, %p, %p)\n", hMonitor, pName, Level, pPorts, cbBuf, pcbNeeded, pcReturned);
540
541 // Windows Server 2003's Local Port Monitor does absolutely no sanity checks here, not even for the Level parameter.
542 // As we implement a more modern MONITOR2-based Port Monitor, check at least our hMonitor.
543 if (!pLocalmon)
544 {
545 dwErrorCode = ERROR_INVALID_HANDLE;
546 goto Cleanup;
547 }
548
549 // Begin counting.
550 *pcbNeeded = 0;
551 *pcReturned = 0;
552
553 EnterCriticalSection(&pLocalmon->Section);
554
555 // Count the required buffer size and the number of ports.
556 for (pEntry = pLocalmon->RegistryPorts.Flink; pEntry != &pLocalmon->RegistryPorts; pEntry = pEntry->Flink)
557 {
558 pPort = CONTAINING_RECORD(pEntry, LOCALMON_PORT, Entry);
559
560 if (Level == 1)
561 _LocalmonGetPortLevel1(pPort, NULL, NULL, pcbNeeded);
562 else if (Level == 2)
563 _LocalmonGetPortLevel2(pPort, NULL, NULL, pcbNeeded);
564 }
565
566 // Check if the supplied buffer is large enough.
567 if (cbBuf < *pcbNeeded)
568 {
569 LeaveCriticalSection(&pLocalmon->Section);
570 dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
571 goto Cleanup;
572 }
573
574 // Copy over the Port information.
575 pPortInfoEnd = &pPorts[*pcbNeeded];
576
577 for (pEntry = pLocalmon->RegistryPorts.Flink; pEntry != &pLocalmon->RegistryPorts; pEntry = pEntry->Flink)
578 {
579 pPort = CONTAINING_RECORD(pEntry, LOCALMON_PORT, Entry);
580
581 if (Level == 1)
582 _LocalmonGetPortLevel1(pPort, (PPORT_INFO_1W*)&pPorts, &pPortInfoEnd, NULL);
583 else if (Level == 2)
584 _LocalmonGetPortLevel2(pPort, (PPORT_INFO_2W*)&pPorts, &pPortInfoEnd, NULL);
585
586 (*pcReturned)++;
587 }
588
589 LeaveCriticalSection(&pLocalmon->Section);
590 dwErrorCode = ERROR_SUCCESS;
591
592 Cleanup:
593 SetLastError(dwErrorCode);
594 return (dwErrorCode == ERROR_SUCCESS);
595 }
596
597 /*
598 * @name LocalmonGetPrinterDataFromPort
599 *
600 * Performs a DeviceIoControl call for the given port.
601 *
602 * @param hPort
603 * The port to operate on.
604 *
605 * @param ControlID
606 * The dwIoControlCode passed to DeviceIoControl. Must not be zero!
607 *
608 * @param pValueName
609 * This parameter is ignored.
610 *
611 * @param lpInBuffer
612 * The lpInBuffer passed to DeviceIoControl.
613 *
614 * @param cbInBuffer
615 * The nInBufferSize passed to DeviceIoControl.
616 *
617 * @param lpOutBuffer
618 * The lpOutBuffer passed to DeviceIoControl.
619 *
620 * @param cbOutBuffer
621 * The nOutBufferSize passed to DeviceIoControl.
622 *
623 * @param lpcbReturned
624 * The lpBytesReturned passed to DeviceIoControl. Must not be zero!
625 *
626 * @return
627 * TRUE if the DeviceIoControl call was successful, FALSE otherwise.
628 * A more specific error code can be obtained through GetLastError.
629 */
630 BOOL WINAPI
631 LocalmonGetPrinterDataFromPort(HANDLE hPort, DWORD ControlID, PWSTR pValueName, PWSTR lpInBuffer, DWORD cbInBuffer, PWSTR lpOutBuffer, DWORD cbOutBuffer, PDWORD lpcbReturned)
632 {
633 BOOL bOpenedPort = FALSE;
634 DWORD dwErrorCode;
635 PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort;
636
637 TRACE("LocalmonGetPrinterDataFromPort(%p, %lu, %p, %p, %lu, %p, %lu, %p)\n", hPort, ControlID, pValueName, lpInBuffer, cbInBuffer, lpOutBuffer, cbOutBuffer, lpcbReturned);
638
639 // Sanity checks
640 if (!pPort || !ControlID || !lpcbReturned)
641 {
642 dwErrorCode = ERROR_INVALID_PARAMETER;
643 goto Cleanup;
644 }
645
646 // If this is a serial port, a temporary file handle may be opened.
647 if (pPort->PortType == PortType_PhysicalCOM)
648 {
649 if (_CreateNonspooledPort(pPort))
650 {
651 bOpenedPort = TRUE;
652 }
653 else if (GetLastError() != ERROR_SUCCESS)
654 {
655 dwErrorCode = GetLastError();
656 goto Cleanup;
657 }
658 }
659 else if (pPort->hFile == INVALID_HANDLE_VALUE)
660 {
661 // All other port types need to be opened already.
662 dwErrorCode = ERROR_INVALID_PARAMETER;
663 goto Cleanup;
664 }
665
666 // Pass the parameters to DeviceIoControl.
667 if (!DeviceIoControl(pPort->hFile, ControlID, lpInBuffer, cbInBuffer, lpOutBuffer, cbOutBuffer, lpcbReturned, NULL))
668 {
669 dwErrorCode = GetLastError();
670 ERR("DeviceIoControl failed with error %lu!\n", dwErrorCode);
671 goto Cleanup;
672 }
673
674 dwErrorCode = ERROR_SUCCESS;
675
676 Cleanup:
677 if (bOpenedPort)
678 _ClosePortHandles(pPort);
679
680 SetLastError(dwErrorCode);
681 return (dwErrorCode == ERROR_SUCCESS);
682 }
683
684 BOOL WINAPI
685 LocalmonOpenPort(HANDLE hMonitor, PWSTR pName, PHANDLE pHandle)
686 {
687 DWORD dwErrorCode;
688 PLOCALMON_HANDLE pLocalmon = (PLOCALMON_HANDLE)hMonitor;
689 PLOCALMON_PORT pPort;
690
691 TRACE("LocalmonOpenPort(%p, %S, %p)\n", hMonitor, pName, pHandle);
692
693 // Sanity checks
694 if (!pLocalmon || !pName || !pHandle)
695 {
696 dwErrorCode = ERROR_INVALID_PARAMETER;
697 goto Cleanup;
698 }
699
700 EnterCriticalSection(&pLocalmon->Section);
701
702 // Check if this is a FILE: port.
703 if (_wcsicmp(pName, L"FILE:") == 0)
704 {
705 // For FILE:, we create a virtual port for each request.
706 pPort = DllAllocSplMem(sizeof(LOCALMON_PORT));
707 pPort->pLocalmon = pLocalmon;
708 pPort->PortType = PortType_FILE;
709 pPort->hFile = INVALID_HANDLE_VALUE;
710
711 // Add it to the list of file ports.
712 InsertTailList(&pLocalmon->FilePorts, &pPort->Entry);
713 }
714 else
715 {
716 // Check if the port name is valid.
717 pPort = _FindPort(pLocalmon, pName);
718 if (!pPort)
719 {
720 LeaveCriticalSection(&pLocalmon->Section);
721 dwErrorCode = ERROR_UNKNOWN_PORT;
722 goto Cleanup;
723 }
724
725 // Even if this API is called OpenPort, port file handles aren't always opened here :-P
726 // Windows only does this for physical LPT ports here to enable bidirectional communication with the printer outside of jobs (using ReadPort and WritePort).
727 // The others are only opened per job in StartDocPort.
728 if (_IsLegacyPort(pName, L"LPT"))
729 {
730 // Try to create a NONSPOOLED port and open it.
731 if (_CreateNonspooledPort(pPort))
732 {
733 // Set the transmission retry timeout for the ReadPort and WritePort calls.
734 // This also checks if this port is a physical one.
735 if (_SetTransmissionRetryTimeout(pPort))
736 {
737 // This is definitely a physical LPT port!
738 pPort->PortType = PortType_PhysicalLPT;
739 }
740 else
741 {
742 // This is no physical port, so don't keep its handle open.
743 _ClosePortHandles(pPort);
744 }
745 }
746 else if (GetLastError() != ERROR_SUCCESS)
747 {
748 LeaveCriticalSection(&pLocalmon->Section);
749 dwErrorCode = GetLastError();
750 goto Cleanup;
751 }
752 }
753 else if (_IsLegacyPort(pName, L"COM"))
754 {
755 // COM ports can't be redirected over the network, so this is a physical one.
756 pPort->PortType = PortType_PhysicalCOM;
757 }
758 }
759
760 LeaveCriticalSection(&pLocalmon->Section);
761
762 // Return our fetched LOCALMON_PORT structure in the handle.
763 *pHandle = (PHANDLE)pPort;
764 dwErrorCode = ERROR_SUCCESS;
765
766 Cleanup:
767 SetLastError(dwErrorCode);
768 return (dwErrorCode == ERROR_SUCCESS);
769 }
770
771 /*
772 * @name LocalmonSetPortTimeOuts
773 *
774 * Performs a SetCommTimeouts call for the given port.
775 *
776 * @param hPort
777 * The port to operate on.
778 *
779 * @param lpCTO
780 * Pointer to a COMMTIMEOUTS structure that is passed to SetCommTimeouts.
781 *
782 * @param Reserved
783 * Reserved parameter, must be 0.
784 *
785 * @return
786 * TRUE if the SetCommTimeouts call was successful, FALSE otherwise.
787 * A more specific error code can be obtained through GetLastError.
788 */
789 BOOL WINAPI
790 LocalmonSetPortTimeOuts(HANDLE hPort, LPCOMMTIMEOUTS lpCTO, DWORD Reserved)
791 {
792 BOOL bOpenedPort = FALSE;
793 DWORD dwErrorCode;
794 PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort;
795
796 TRACE("LocalmonSetPortTimeOuts(%p, %p, %lu)\n", hPort, lpCTO, Reserved);
797
798 // Sanity checks
799 if (!pPort || !lpCTO)
800 {
801 dwErrorCode = ERROR_INVALID_PARAMETER;
802 goto Cleanup;
803 }
804
805 // If this is a serial port, a temporary file handle may be opened.
806 if (pPort->PortType == PortType_PhysicalCOM)
807 {
808 if (_CreateNonspooledPort(pPort))
809 {
810 bOpenedPort = TRUE;
811 }
812 else if (GetLastError() != ERROR_SUCCESS)
813 {
814 dwErrorCode = GetLastError();
815 goto Cleanup;
816 }
817 }
818 else if (pPort->hFile == INVALID_HANDLE_VALUE)
819 {
820 // All other port types need to be opened already.
821 dwErrorCode = ERROR_INVALID_PARAMETER;
822 goto Cleanup;
823 }
824
825 // Finally pass the parameters to SetCommTimeouts.
826 if (!SetCommTimeouts(pPort->hFile, lpCTO))
827 {
828 dwErrorCode = GetLastError();
829 ERR("SetCommTimeouts failed with error %lu!\n", dwErrorCode);
830 goto Cleanup;
831 }
832
833 dwErrorCode = ERROR_SUCCESS;
834
835 Cleanup:
836 if (bOpenedPort)
837 _ClosePortHandles(pPort);
838
839 SetLastError(dwErrorCode);
840 return (dwErrorCode == ERROR_SUCCESS);
841 }
842
843 BOOL WINAPI
844 LocalmonReadPort(HANDLE hPort, PBYTE pBuffer, DWORD cbBuffer, PDWORD pcbRead)
845 {
846 BOOL bOpenedPort = FALSE;
847 DWORD dwErrorCode;
848 PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort;
849
850 TRACE("LocalmonReadPort(%p, %p, %lu, %p)\n", hPort, pBuffer, cbBuffer, pcbRead);
851
852 // Sanity checks
853 if (!pPort || (cbBuffer && !pBuffer) || !pcbRead)
854 {
855 dwErrorCode = ERROR_INVALID_PARAMETER;
856 goto Cleanup;
857 }
858
859 // Reading is only supported for physical ports.
860 if (pPort->PortType != PortType_PhysicalCOM && pPort->PortType != PortType_PhysicalLPT)
861 {
862 dwErrorCode = ERROR_INVALID_HANDLE;
863 goto Cleanup;
864 }
865
866 // If this is a serial port, a temporary file handle may be opened.
867 if (pPort->PortType == PortType_PhysicalCOM)
868 {
869 if (_CreateNonspooledPort(pPort))
870 {
871 bOpenedPort = TRUE;
872 }
873 else if (GetLastError() != ERROR_SUCCESS)
874 {
875 dwErrorCode = GetLastError();
876 goto Cleanup;
877 }
878 }
879
880 // Pass the parameters to ReadFile.
881 if (!ReadFile(pPort->hFile, pBuffer, cbBuffer, pcbRead, NULL))
882 {
883 dwErrorCode = GetLastError();
884 ERR("ReadFile failed with error %lu!\n", dwErrorCode);
885 goto Cleanup;
886 }
887
888 Cleanup:
889 if (bOpenedPort)
890 _ClosePortHandles(pPort);
891
892 SetLastError(dwErrorCode);
893 return (dwErrorCode == ERROR_SUCCESS);
894 }
895
896 BOOL WINAPI
897 LocalmonStartDocPort(HANDLE hPort, PWSTR pPrinterName, DWORD JobId, DWORD Level, PBYTE pDocInfo)
898 {
899 DWORD dwErrorCode;
900 PDOC_INFO_1W pDocInfo1 = (PDOC_INFO_1W)pDocInfo; // DOC_INFO_1W is the least common denominator for both DOC_INFO levels.
901 PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort;
902
903 TRACE("LocalmonStartDocPort(%p, %S, %lu, %lu, %p)\n", hPort, pPrinterName, JobId, Level, pDocInfo);
904
905 // Sanity checks
906 if (!pPort || !pPrinterName || (pPort->PortType == PortType_FILE && (!pDocInfo1 || !pDocInfo1->pOutputFile || !*pDocInfo1->pOutputFile)))
907 {
908 dwErrorCode = ERROR_INVALID_PARAMETER;
909 goto Cleanup;
910 }
911
912 if (Level > 2)
913 {
914 dwErrorCode = ERROR_INVALID_LEVEL;
915 goto Cleanup;
916 }
917
918 // Calling StartDocPort multiple times isn't considered a failure, but we don't need to do anything then.
919 if (pPort->bStartedDoc)
920 {
921 dwErrorCode = ERROR_SUCCESS;
922 goto Cleanup;
923 }
924
925 // Open a handle to the given printer for later reporting our progress using SetJobW.
926 if (!OpenPrinterW(pPrinterName, &pPort->hPrinter, NULL))
927 {
928 dwErrorCode = GetLastError();
929 ERR("OpenPrinterW failed with error %lu!\n", dwErrorCode);
930 goto Cleanup;
931 }
932
933 // We need our Job ID for SetJobW as well.
934 pPort->dwJobID = JobId;
935
936 // Check the port type.
937 if (pPort->PortType == PortType_PhysicalLPT)
938 {
939 // Update the NONSPOOLED mapping if the port mapping has changed since our OpenPort call.
940 if (!_CreateNonspooledPort(pPort) && GetLastError() != ERROR_SUCCESS)
941 {
942 dwErrorCode = GetLastError();
943 goto Cleanup;
944 }
945
946 // Update the transmission retry timeout as well.
947 _SetTransmissionRetryTimeout(pPort);
948 }
949 else if(pPort->PortType == PortType_FILE)
950 {
951 // This is a FILE: port. Open the output file given in the Document Info.
952 pPort->hFile = CreateFileW(pDocInfo1->pOutputFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
953 if (pPort->hFile == INVALID_HANDLE_VALUE)
954 {
955 dwErrorCode = GetLastError();
956 goto Cleanup;
957 }
958 }
959 else
960 {
961 // This can be:
962 // - a physical COM port
963 // - a non-physical LPT port (e.g. with "net use LPT1 ...")
964 // - any other port (e.g. a file or a shared printer installed as a local port)
965 //
966 // For all these cases, we try to create a NONSPOOLED port per job.
967 // If _CreateNonspooledPort reports that no NONSPOOLED port is necessary, we can just open the port name.
968 if (!_CreateNonspooledPort(pPort))
969 {
970 if (GetLastError() == ERROR_SUCCESS)
971 {
972 pPort->hFile = CreateFileW(pPort->pwszPortName, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL);
973 if (pPort->hFile == INVALID_HANDLE_VALUE)
974 {
975 dwErrorCode = GetLastError();
976 goto Cleanup;
977 }
978 }
979 else
980 {
981 dwErrorCode = GetLastError();
982 goto Cleanup;
983 }
984 }
985 }
986
987 // We were successful!
988 dwErrorCode = ERROR_SUCCESS;
989 pPort->bStartedDoc = TRUE;
990
991 Cleanup:
992 SetLastError(dwErrorCode);
993 return (dwErrorCode == ERROR_SUCCESS);
994 }
995
996 BOOL WINAPI
997 LocalmonWritePort(HANDLE hPort, PBYTE pBuffer, DWORD cbBuf, PDWORD pcbWritten)
998 {
999 BOOL bOpenedPort = FALSE;
1000 DWORD dwErrorCode;
1001 PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort;
1002
1003 TRACE("LocalmonWritePort(%p, %p, %lu, %p)\n", hPort, pBuffer, cbBuf, pcbWritten);
1004
1005 // Sanity checks
1006 if (!pPort || (cbBuf && !pBuffer) || !pcbWritten)
1007 {
1008 dwErrorCode = ERROR_INVALID_PARAMETER;
1009 goto Cleanup;
1010 }
1011
1012 // If this is a serial port, a temporary file handle may be opened.
1013 if (pPort->PortType == PortType_PhysicalCOM)
1014 {
1015 if (_CreateNonspooledPort(pPort))
1016 {
1017 bOpenedPort = TRUE;
1018 }
1019 else if (GetLastError() != ERROR_SUCCESS)
1020 {
1021 dwErrorCode = GetLastError();
1022 goto Cleanup;
1023 }
1024 }
1025 else if (pPort->hFile == INVALID_HANDLE_VALUE)
1026 {
1027 // All other port types need to be opened already.
1028 dwErrorCode = ERROR_INVALID_PARAMETER;
1029 goto Cleanup;
1030 }
1031
1032 // Pass the parameters to WriteFile.
1033 if (!WriteFile(pPort->hFile, pBuffer, cbBuf, pcbWritten, NULL))
1034 {
1035 dwErrorCode = GetLastError();
1036 ERR("WriteFile failed with error %lu!\n", dwErrorCode);
1037 goto Cleanup;
1038 }
1039
1040 // If something was written down, we consider that a success, otherwise it's a timeout.
1041 if (*pcbWritten)
1042 dwErrorCode = ERROR_SUCCESS;
1043 else
1044 dwErrorCode = ERROR_TIMEOUT;
1045
1046 Cleanup:
1047 if (bOpenedPort)
1048 _ClosePortHandles(pPort);
1049
1050 SetLastError(dwErrorCode);
1051 return (dwErrorCode == ERROR_SUCCESS);
1052 }