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