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