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>
12 static const WCHAR wszNonspooledPrefix
[] = L
"NONSPOOLED_";
13 static const DWORD cchNonspooledPrefix
= _countof(wszNonspooledPrefix
) - 1;
17 * @name _GetNonspooledPortName
19 * Prepends "NONSPOOLED_" to a port name without colon.
21 * @param pwszPortNameWithoutColon
22 * Result of a previous GetPortNameWithoutColon call.
24 * @param ppwszNonspooledPortName
25 * Pointer to a buffer that will contain the NONSPOOLED port name.
26 * You have to free this buffer using DllFreeSplMem.
29 * ERROR_SUCCESS if the NONSPOOLED port name was successfully copied into the buffer.
30 * ERROR_NOT_ENOUGH_MEMORY if memory allocation failed.
33 _GetNonspooledPortName(PCWSTR pwszPortNameWithoutColon
, PWSTR
* ppwszNonspooledPortName
)
35 DWORD cchPortNameWithoutColon
;
37 cchPortNameWithoutColon
= wcslen(pwszPortNameWithoutColon
);
39 *ppwszNonspooledPortName
= DllAllocSplMem((cchNonspooledPrefix
+ cchPortNameWithoutColon
+ 1) * sizeof(WCHAR
));
40 if (!*ppwszNonspooledPortName
)
42 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
43 return ERROR_NOT_ENOUGH_MEMORY
;
46 CopyMemory(*ppwszNonspooledPortName
, wszNonspooledPrefix
, cchNonspooledPrefix
* sizeof(WCHAR
));
47 CopyMemory(&(*ppwszNonspooledPortName
)[cchNonspooledPrefix
], pwszPortNameWithoutColon
, (cchPortNameWithoutColon
+ 1) * sizeof(WCHAR
));
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").
59 * The port name to check.
65 * TRUE if this is definitely the asked legacy port, FALSE if not.
68 _IsLegacyPort(PCWSTR pwszPortName
, PCWSTR pwszPortType
)
70 const DWORD cchPortType
= 3;
71 PCWSTR p
= pwszPortName
;
73 // The port name must begin with pwszPortType.
74 if (_wcsnicmp(p
, pwszPortType
, cchPortType
) != 0)
79 // Now an arbitrary number of digits may follow.
80 while (*p
>= L
'0' && *p
<= L
'9')
83 // Finally, the legacy port must be terminated by a colon.
87 // If this is the end of the string, we have a legacy port.
93 * @name _ClosePortHandles
95 * Closes a port of any type if it's open.
96 * Removes any saved mapping or existing definition of a NONSPOOLED device mapping.
99 * The port you want to close.
102 _ClosePortHandles(PLOCALMON_PORT pPort
)
104 PWSTR pwszNonspooledPortName
;
105 PWSTR pwszPortNameWithoutColon
;
107 // A port is already fully closed if the file handle is invalid.
108 if (pPort
->hFile
== INVALID_HANDLE_VALUE
)
111 // Close the file handle.
112 CloseHandle(pPort
->hFile
);
113 pPort
->hFile
= INVALID_HANDLE_VALUE
;
115 // A NONSPOOLED port was only created if pwszMapping contains the current port mapping.
116 if (!pPort
->pwszMapping
)
119 // Free the information about the current mapping.
120 DllFreeSplStr(pPort
->pwszMapping
);
121 pPort
->pwszMapping
= NULL
;
123 // Finally get the required strings and remove the DOS device definition for the NONSPOOLED port.
124 if (GetPortNameWithoutColon(pPort
->pwszPortName
, &pwszPortNameWithoutColon
) == ERROR_SUCCESS
)
126 if (_GetNonspooledPortName(pwszPortNameWithoutColon
, &pwszNonspooledPortName
) == ERROR_SUCCESS
)
128 DefineDosDeviceW(DDD_REMOVE_DEFINITION
, pwszNonspooledPortName
, NULL
);
129 DllFreeSplMem(pwszNonspooledPortName
);
132 DllFreeSplMem(pwszPortNameWithoutColon
);
137 * @name _CreateNonspooledPort
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).
144 * Pointer to the LOCALMON_PORT structure of the desired port.
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.
152 _CreateNonspooledPort(PLOCALMON_PORT pPort
)
154 const WCHAR wszLocalSlashes
[] = L
"\\\\.\\";
155 const DWORD cchLocalSlashes
= _countof(wszLocalSlashes
) - 1;
157 const WCHAR wszSpoolerNamedPipe
[] = L
"\\Device\\NamedPipe\\Spooler\\";
158 const DWORD cchSpoolerNamedPipe
= _countof(wszSpoolerNamedPipe
) - 1;
160 BOOL bReturnValue
= FALSE
;
161 DWORD cchPortNameWithoutColon
;
163 HANDLE hToken
= NULL
;
165 PWSTR pwszDeviceMappings
= NULL
;
166 PWSTR pwszNonspooledFileName
= NULL
;
167 PWSTR pwszNonspooledPortName
= NULL
;
168 PWSTR pwszPipeName
= NULL
;
169 PWSTR pwszPortNameWithoutColon
= NULL
;
171 // We need the port name without the trailing colon.
172 dwErrorCode
= GetPortNameWithoutColon(pPort
->pwszPortName
, &pwszPortNameWithoutColon
);
173 if (dwErrorCode
== ERROR_INVALID_PARAMETER
)
175 // This port has no trailing colon, so we also need no NONSPOOLED mapping for it.
176 dwErrorCode
= ERROR_SUCCESS
;
179 else if (dwErrorCode
!= ERROR_SUCCESS
)
181 // Another unexpected failure.
185 cchPortNameWithoutColon
= wcslen(pwszPortNameWithoutColon
);
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
));
192 dwErrorCode
= ERROR_NOT_ENOUGH_MEMORY
;
193 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
197 CopyMemory(pwszPipeName
, wszSpoolerNamedPipe
, cchSpoolerNamedPipe
* sizeof(WCHAR
));
198 CopyMemory(&pwszPipeName
[cchSpoolerNamedPipe
], pwszPortNameWithoutColon
, (cchPortNameWithoutColon
+ 1) * sizeof(WCHAR
));
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
)
205 dwErrorCode
= ERROR_NOT_ENOUGH_MEMORY
;
206 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
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();
215 dwErrorCode
= GetLastError();
216 ERR("RevertToPrinterSelf failed with error %lu!\n", dwErrorCode
);
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
))
223 // No system-wide port exists, so we also need no NONSPOOLED mapping.
224 dwErrorCode
= ERROR_SUCCESS
;
228 // Check if this port has already been opened by _CreateNonspooledPort previously.
229 if (pPort
->pwszMapping
)
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)
235 // We don't need to do anything in this case.
236 dwErrorCode
= ERROR_SUCCESS
;
241 // Close the open file handle and free the memory for pwszMapping before remapping.
242 CloseHandle(pPort
->hFile
);
243 pPort
->hFile
= INVALID_HANDLE_VALUE
;
245 DllFreeSplStr(pPort
->pwszMapping
);
246 pPort
->pwszMapping
= NULL
;
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
;
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
);
263 if (_wcsicmp(p
, pwszPipeName
) != 0)
266 // Advance to the next mapping in the list.
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
)
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
);
278 if (!DefineDosDeviceW(DDD_RAW_TARGET_PATH
, pwszNonspooledPortName
, p
))
280 dwErrorCode
= GetLastError();
281 ERR("DefineDosDeviceW failed with error %lu!\n", dwErrorCode
);
285 // This is all we needed to do in SYSTEM context.
286 ImpersonatePrinterClient(hToken
);
289 // Construct the file name to our created device for CreateFileW.
290 pwszNonspooledFileName
= DllAllocSplMem((cchLocalSlashes
+ cchNonspooledPrefix
+ cchPortNameWithoutColon
+ 1) * sizeof(WCHAR
));
291 if (!pwszNonspooledFileName
)
293 dwErrorCode
= ERROR_NOT_ENOUGH_MEMORY
;
294 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
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
));
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
)
306 dwErrorCode
= GetLastError();
307 ERR("CreateFileW failed with error %lu!\n", dwErrorCode
);
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
)
315 dwErrorCode
= ERROR_NOT_ENOUGH_MEMORY
;
320 dwErrorCode
= ERROR_SUCCESS
;
324 ImpersonatePrinterClient(hToken
);
326 if (pwszDeviceMappings
)
327 DllFreeSplMem(pwszDeviceMappings
);
329 if (pwszNonspooledFileName
)
330 DllFreeSplMem(pwszNonspooledFileName
);
332 if (pwszNonspooledPortName
)
333 DllFreeSplMem(pwszNonspooledPortName
);
336 DllFreeSplMem(pwszPipeName
);
338 if (pwszPortNameWithoutColon
)
339 DllFreeSplMem(pwszPortNameWithoutColon
);
341 SetLastError(dwErrorCode
);
345 static PLOCALMON_PORT
346 _FindPort(PLOCALMON_HANDLE pLocalmon
, PCWSTR pwszPortName
)
349 PLOCALMON_PORT pPort
;
351 for (pEntry
= pLocalmon
->RegistryPorts
.Flink
; pEntry
!= &pLocalmon
->RegistryPorts
; pEntry
= pEntry
->Flink
)
353 pPort
= CONTAINING_RECORD(pEntry
, LOCALMON_PORT
, Entry
);
355 if (wcscmp(pPort
->pwszPortName
, pwszPortName
) == 0)
363 _LocalmonEnumPortsLevel1(PLOCALMON_HANDLE pLocalmon
, PBYTE pPorts
, DWORD cbBuf
, PDWORD pcbNeeded
, PDWORD pcReturned
)
367 DWORD dwPortCount
= 0;
371 PLOCALMON_PORT pPort
;
372 PORT_INFO_1W PortInfo1
;
374 // Count the required buffer size and the number of datatypes.
375 for (pEntry
= pLocalmon
->RegistryPorts
.Flink
; pEntry
!= &pLocalmon
->RegistryPorts
; pEntry
= pEntry
->Flink
)
377 pPort
= CONTAINING_RECORD(pEntry
, LOCALMON_PORT
, Entry
);
379 cbPortName
= (wcslen(pPort
->pwszPortName
) + 1) * sizeof(WCHAR
);
380 *pcbNeeded
+= sizeof(PORT_INFO_1W
) + cbPortName
;
384 // Check if the supplied buffer is large enough.
385 if (cbBuf
< *pcbNeeded
)
387 dwErrorCode
= ERROR_INSUFFICIENT_BUFFER
;
391 // Put the strings right after the last PORT_INFO_1W structure.
393 pPortString
= pPorts
+ dwPortCount
* sizeof(PORT_INFO_1W
);
395 // Copy over all ports.
396 for (pEntry
= pLocalmon
->RegistryPorts
.Flink
; pEntry
!= &pLocalmon
->RegistryPorts
; pEntry
= pEntry
->Flink
)
398 pPort
= CONTAINING_RECORD(pEntry
, LOCALMON_PORT
, Entry
);
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
;
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
);
411 *pcReturned
= dwPortCount
;
412 dwErrorCode
= ERROR_SUCCESS
;
419 _LocalmonEnumPortsLevel2(PLOCALMON_HANDLE pLocalmon
, PBYTE pPorts
, DWORD cbBuf
, PDWORD pcbNeeded
, PDWORD pcReturned
)
423 DWORD dwPortCount
= 0;
427 PLOCALMON_PORT pPort
;
428 PORT_INFO_2W PortInfo2
;
430 // Count the required buffer size and the number of datatypes.
431 for (pEntry
= pLocalmon
->RegistryPorts
.Flink
; pEntry
!= &pLocalmon
->RegistryPorts
; pEntry
= pEntry
->Flink
)
433 pPort
= CONTAINING_RECORD(pEntry
, LOCALMON_PORT
, Entry
);
435 cbPortName
= (wcslen(pPort
->pwszPortName
) + 1) * sizeof(WCHAR
);
436 *pcbNeeded
+= sizeof(PORT_INFO_2W
) + cbPortName
+ cbLocalMonitor
+ cbLocalPort
;
440 // Check if the supplied buffer is large enough.
441 if (cbBuf
< *pcbNeeded
)
443 dwErrorCode
= ERROR_INSUFFICIENT_BUFFER
;
447 // Put the strings right after the last PORT_INFO_2W structure.
449 pPortString
= pPorts
+ dwPortCount
* sizeof(PORT_INFO_2W
);
451 // Copy over all ports.
452 for (pEntry
= pLocalmon
->RegistryPorts
.Flink
; pEntry
!= &pLocalmon
->RegistryPorts
; pEntry
= pEntry
->Flink
)
454 pPort
= CONTAINING_RECORD(pEntry
, LOCALMON_PORT
, Entry
);
456 // All local ports are writable and readable.
457 PortInfo2
.fPortType
= PORT_TYPE_WRITE
| PORT_TYPE_READ
;
458 PortInfo2
.Reserved
= 0;
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
;
466 // Copy the monitor name.
467 PortInfo2
.pMonitorName
= (PWSTR
)pPortString
;
468 CopyMemory(pPortString
, pwszLocalMonitor
, cbLocalMonitor
);
469 pPortString
+= cbLocalMonitor
;
471 // Copy the description.
472 PortInfo2
.pDescription
= (PWSTR
)pPortString
;
473 CopyMemory(pPortString
, pwszLocalPort
, cbLocalPort
);
474 pPortString
+= cbLocalPort
;
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
);
481 *pcReturned
= dwPortCount
;
482 dwErrorCode
= ERROR_SUCCESS
;
489 * @name _SetTransmissionRetryTimeout
491 * Checks if the given port is a physical one and sets the transmission retry timeout in this case using the value from registry.
494 * The port to operate on.
497 * TRUE if the given port is a physical one, FALSE otherwise.
500 _SetTransmissionRetryTimeout(PLOCALMON_PORT pPort
)
502 COMMTIMEOUTS CommTimeouts
;
504 // Get the timeout from the port.
505 if (!GetCommTimeouts(pPort
->hFile
, &CommTimeouts
))
508 // Set the timeout using the value from registry.
509 CommTimeouts
.WriteTotalTimeoutConstant
= GetLPTTransmissionRetryTimeout() * 1000;
510 SetCommTimeouts(pPort
->hFile
, &CommTimeouts
);
516 LocalmonClosePort(HANDLE hPort
)
518 PLOCALMON_PORT pPort
= (PLOCALMON_PORT
)hPort
;
520 TRACE("LocalmonClosePort(%p)\n", hPort
);
525 SetLastError(ERROR_INVALID_PARAMETER
);
529 // Close the file handle, free memory for pwszMapping and delete any NONSPOOLED port.
530 _ClosePortHandles(pPort
);
532 // Close any open printer handle.
535 ClosePrinter(pPort
->hPrinter
);
536 pPort
->hPrinter
= NULL
;
539 // Free virtual FILE: ports which were created in LocalmonOpenPort.
540 if (pPort
->PortType
== PortType_FILE
)
542 EnterCriticalSection(&pPort
->pLocalmon
->Section
);
543 RemoveEntryList(&pPort
->Entry
);
544 DllFreeSplMem(pPort
);
545 LeaveCriticalSection(&pPort
->pLocalmon
->Section
);
548 SetLastError(ERROR_SUCCESS
);
553 LocalmonEndDocPort(HANDLE hPort
)
555 PLOCALMON_PORT pPort
= (PLOCALMON_PORT
)hPort
;
557 TRACE("LocalmonEndDocPort(%p)\n", hPort
);
562 SetLastError(ERROR_INVALID_PARAMETER
);
566 // Ending a document requires starting it first :-P
567 if (pPort
->bStartedDoc
)
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
);
574 // Report our progress.
575 SetJobW(pPort
->hPrinter
, pPort
->dwJobID
, 0, NULL
, JOB_CONTROL_SENT_TO_PRINTER
);
577 // We're done with the printer.
578 ClosePrinter(pPort
->hPrinter
);
579 pPort
->hPrinter
= NULL
;
581 // A new document can now be started again.
582 pPort
->bStartedDoc
= FALSE
;
585 SetLastError(ERROR_SUCCESS
);
590 LocalmonEnumPorts(HANDLE hMonitor
, PWSTR pName
, DWORD Level
, PBYTE pPorts
, DWORD cbBuf
, PDWORD pcbNeeded
, PDWORD pcReturned
)
593 PLOCALMON_HANDLE pLocalmon
= (PLOCALMON_HANDLE
)hMonitor
;
595 TRACE("LocalmonEnumPorts(%p, %S, %lu, %p, %lu, %p, %p)\n", hMonitor
, pName
, Level
, pPorts
, cbBuf
, pcbNeeded
, pcReturned
);
598 if (!pLocalmon
|| (cbBuf
&& !pPorts
) || !pcbNeeded
|| !pcReturned
)
600 dwErrorCode
= ERROR_INVALID_PARAMETER
;
606 dwErrorCode
= ERROR_INVALID_LEVEL
;
614 EnterCriticalSection(&pLocalmon
->Section
);
616 // The function behaves differently for each level.
618 dwErrorCode
= _LocalmonEnumPortsLevel1(pLocalmon
, pPorts
, cbBuf
, pcbNeeded
, pcReturned
);
620 dwErrorCode
= _LocalmonEnumPortsLevel2(pLocalmon
, pPorts
, cbBuf
, pcbNeeded
, pcReturned
);
622 LeaveCriticalSection(&pLocalmon
->Section
);
625 SetLastError(dwErrorCode
);
626 return (dwErrorCode
== ERROR_SUCCESS
);
630 * @name LocalmonGetPrinterDataFromPort
632 * Performs a DeviceIoControl call for the given port.
635 * The port to operate on.
638 * The dwIoControlCode passed to DeviceIoControl. Must not be zero!
641 * This parameter is ignored.
644 * The lpInBuffer passed to DeviceIoControl.
647 * The nInBufferSize passed to DeviceIoControl.
650 * The lpOutBuffer passed to DeviceIoControl.
653 * The nOutBufferSize passed to DeviceIoControl.
655 * @param lpcbReturned
656 * The lpBytesReturned passed to DeviceIoControl. Must not be zero!
659 * TRUE if the DeviceIoControl call was successful, FALSE otherwise.
660 * A more specific error code can be obtained through GetLastError.
663 LocalmonGetPrinterDataFromPort(HANDLE hPort
, DWORD ControlID
, PWSTR pValueName
, PWSTR lpInBuffer
, DWORD cbInBuffer
, PWSTR lpOutBuffer
, DWORD cbOutBuffer
, PDWORD lpcbReturned
)
665 BOOL bOpenedPort
= FALSE
;
667 PLOCALMON_PORT pPort
= (PLOCALMON_PORT
)hPort
;
669 TRACE("LocalmonGetPrinterDataFromPort(%p, %lu, %p, %p, %lu, %p, %lu, %p)\n", hPort
, ControlID
, pValueName
, lpInBuffer
, cbInBuffer
, lpOutBuffer
, cbOutBuffer
, lpcbReturned
);
672 if (!pPort
|| !ControlID
|| !lpcbReturned
)
674 dwErrorCode
= ERROR_INVALID_PARAMETER
;
678 // If this is a serial port, a temporary file handle may be opened.
679 if (pPort
->PortType
== PortType_PhysicalCOM
)
681 if (_CreateNonspooledPort(pPort
))
685 else if (GetLastError() != ERROR_SUCCESS
)
687 dwErrorCode
= GetLastError();
691 else if (pPort
->hFile
== INVALID_HANDLE_VALUE
)
693 // All other port types need to be opened already.
694 dwErrorCode
= ERROR_INVALID_PARAMETER
;
698 // Pass the parameters to DeviceIoControl.
699 if (!DeviceIoControl(pPort
->hFile
, ControlID
, lpInBuffer
, cbInBuffer
, lpOutBuffer
, cbOutBuffer
, lpcbReturned
, NULL
))
701 dwErrorCode
= GetLastError();
702 ERR("DeviceIoControl failed with error %lu!\n", dwErrorCode
);
706 dwErrorCode
= ERROR_SUCCESS
;
710 _ClosePortHandles(pPort
);
712 SetLastError(dwErrorCode
);
713 return (dwErrorCode
== ERROR_SUCCESS
);
717 LocalmonOpenPort(HANDLE hMonitor
, PWSTR pName
, PHANDLE pHandle
)
720 PLOCALMON_HANDLE pLocalmon
= (PLOCALMON_HANDLE
)hMonitor
;
721 PLOCALMON_PORT pPort
;
723 TRACE("LocalmonOpenPort(%p, %S, %p)\n", hMonitor
, pName
, pHandle
);
726 if (!pLocalmon
|| !pName
|| !pHandle
)
728 dwErrorCode
= ERROR_INVALID_PARAMETER
;
732 // Check if this is a FILE: port.
733 if (_wcsicmp(pName
, L
"FILE:") == 0)
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
;
741 // Add it to the list of file ports.
742 EnterCriticalSection(&pLocalmon
->Section
);
743 InsertTailList(&pLocalmon
->FilePorts
, &pPort
->Entry
);
747 EnterCriticalSection(&pLocalmon
->Section
);
749 // Check if the port name is valid.
750 pPort
= _FindPort(pLocalmon
, pName
);
753 LeaveCriticalSection(&pLocalmon
->Section
);
754 dwErrorCode
= ERROR_UNKNOWN_PORT
;
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"))
763 // Try to create a NONSPOOLED port and open it.
764 if (_CreateNonspooledPort(pPort
))
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
))
770 // This is definitely a physical LPT port!
771 pPort
->PortType
= PortType_PhysicalLPT
;
775 // This is no physical port, so don't keep its handle open.
776 _ClosePortHandles(pPort
);
779 else if (GetLastError() != ERROR_SUCCESS
)
781 LeaveCriticalSection(&pLocalmon
->Section
);
782 dwErrorCode
= GetLastError();
786 else if (_IsLegacyPort(pName
, L
"COM"))
788 // COM ports can't be redirected over the network, so this is a physical one.
789 pPort
->PortType
= PortType_PhysicalCOM
;
793 LeaveCriticalSection(&pLocalmon
->Section
);
795 // Return our fetched LOCALMON_PORT structure in the handle.
796 *pHandle
= (PHANDLE
)pPort
;
797 dwErrorCode
= ERROR_SUCCESS
;
800 SetLastError(dwErrorCode
);
801 return (dwErrorCode
== ERROR_SUCCESS
);
805 * @name LocalmonSetPortTimeOuts
807 * Performs a SetCommTimeouts call for the given port.
810 * The port to operate on.
813 * Pointer to a COMMTIMEOUTS structure that is passed to SetCommTimeouts.
816 * Reserved parameter, must be 0.
819 * TRUE if the SetCommTimeouts call was successful, FALSE otherwise.
820 * A more specific error code can be obtained through GetLastError.
823 LocalmonSetPortTimeOuts(HANDLE hPort
, LPCOMMTIMEOUTS lpCTO
, DWORD Reserved
)
825 BOOL bOpenedPort
= FALSE
;
827 PLOCALMON_PORT pPort
= (PLOCALMON_PORT
)hPort
;
829 TRACE("LocalmonSetPortTimeOuts(%p, %p, %lu)\n", hPort
, lpCTO
, Reserved
);
832 if (!pPort
|| !lpCTO
)
834 dwErrorCode
= ERROR_INVALID_PARAMETER
;
838 // If this is a serial port, a temporary file handle may be opened.
839 if (pPort
->PortType
== PortType_PhysicalCOM
)
841 if (_CreateNonspooledPort(pPort
))
845 else if (GetLastError() != ERROR_SUCCESS
)
847 dwErrorCode
= GetLastError();
851 else if (pPort
->hFile
== INVALID_HANDLE_VALUE
)
853 // All other port types need to be opened already.
854 dwErrorCode
= ERROR_INVALID_PARAMETER
;
858 // Finally pass the parameters to SetCommTimeouts.
859 if (!SetCommTimeouts(pPort
->hFile
, lpCTO
))
861 dwErrorCode
= GetLastError();
862 ERR("SetCommTimeouts failed with error %lu!\n", dwErrorCode
);
866 dwErrorCode
= ERROR_SUCCESS
;
870 _ClosePortHandles(pPort
);
872 SetLastError(dwErrorCode
);
873 return (dwErrorCode
== ERROR_SUCCESS
);
877 LocalmonReadPort(HANDLE hPort
, PBYTE pBuffer
, DWORD cbBuffer
, PDWORD pcbRead
)
879 BOOL bOpenedPort
= FALSE
;
881 PLOCALMON_PORT pPort
= (PLOCALMON_PORT
)hPort
;
883 TRACE("LocalmonReadPort(%p, %p, %lu, %p)\n", hPort
, pBuffer
, cbBuffer
, pcbRead
);
886 if (!pPort
|| (cbBuffer
&& !pBuffer
) || !pcbRead
)
888 dwErrorCode
= ERROR_INVALID_PARAMETER
;
892 // Reading is only supported for physical ports.
893 if (pPort
->PortType
!= PortType_PhysicalCOM
&& pPort
->PortType
!= PortType_PhysicalLPT
)
895 dwErrorCode
= ERROR_INVALID_HANDLE
;
899 // If this is a serial port, a temporary file handle may be opened.
900 if (pPort
->PortType
== PortType_PhysicalCOM
)
902 if (_CreateNonspooledPort(pPort
))
906 else if (GetLastError() != ERROR_SUCCESS
)
908 dwErrorCode
= GetLastError();
913 // Pass the parameters to ReadFile.
914 if (!ReadFile(pPort
->hFile
, pBuffer
, cbBuffer
, pcbRead
, NULL
))
916 dwErrorCode
= GetLastError();
917 ERR("ReadFile failed with error %lu!\n", dwErrorCode
);
923 _ClosePortHandles(pPort
);
925 SetLastError(dwErrorCode
);
926 return (dwErrorCode
== ERROR_SUCCESS
);
930 LocalmonStartDocPort(HANDLE hPort
, PWSTR pPrinterName
, DWORD JobId
, DWORD Level
, PBYTE pDocInfo
)
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
;
936 TRACE("LocalmonStartDocPort(%p, %S, %lu, %lu, %p)\n", hPort
, pPrinterName
, JobId
, Level
, pDocInfo
);
939 if (!pPort
|| !pPrinterName
|| (pPort
->PortType
== PortType_FILE
&& (!pDocInfo1
|| !pDocInfo1
->pOutputFile
|| !*pDocInfo1
->pOutputFile
)))
941 dwErrorCode
= ERROR_INVALID_PARAMETER
;
947 dwErrorCode
= ERROR_INVALID_LEVEL
;
951 // Calling StartDocPort multiple times isn't considered a failure, but we don't need to do anything then.
952 if (pPort
->bStartedDoc
)
954 dwErrorCode
= ERROR_SUCCESS
;
958 // Open a handle to the given printer for later reporting our progress using SetJobW.
959 if (!OpenPrinterW(pPrinterName
, &pPort
->hPrinter
, NULL
))
961 dwErrorCode
= GetLastError();
962 ERR("OpenPrinterW failed with error %lu!\n", dwErrorCode
);
966 // We need our Job ID for SetJobW as well.
967 pPort
->dwJobID
= JobId
;
969 // Check the port type.
970 if (pPort
->PortType
== PortType_PhysicalLPT
)
972 // Update the NONSPOOLED mapping if the port mapping has changed since our OpenPort call.
973 if (!_CreateNonspooledPort(pPort
) && GetLastError() != ERROR_SUCCESS
)
975 dwErrorCode
= GetLastError();
979 // Update the transmission retry timeout as well.
980 _SetTransmissionRetryTimeout(pPort
);
982 else if(pPort
->PortType
== PortType_FILE
)
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
)
988 dwErrorCode
= GetLastError();
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)
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
))
1003 if (GetLastError() == ERROR_SUCCESS
)
1005 pPort
->hFile
= CreateFileW(pPort
->pwszPortName
, GENERIC_WRITE
, FILE_SHARE_READ
, NULL
, CREATE_ALWAYS
, 0, NULL
);
1006 if (pPort
->hFile
== INVALID_HANDLE_VALUE
)
1008 dwErrorCode
= GetLastError();
1014 dwErrorCode
= GetLastError();
1020 // We were successful!
1021 dwErrorCode
= ERROR_SUCCESS
;
1022 pPort
->bStartedDoc
= TRUE
;
1025 SetLastError(dwErrorCode
);
1026 return (dwErrorCode
== ERROR_SUCCESS
);
1030 LocalmonWritePort(HANDLE hPort
, PBYTE pBuffer
, DWORD cbBuf
, PDWORD pcbWritten
)
1032 BOOL bOpenedPort
= FALSE
;
1034 PLOCALMON_PORT pPort
= (PLOCALMON_PORT
)hPort
;
1036 TRACE("LocalmonWritePort(%p, %p, %lu, %p)\n", hPort
, pBuffer
, cbBuf
, pcbWritten
);
1039 if (!pPort
|| (cbBuf
&& !pBuffer
) || !pcbWritten
)
1041 dwErrorCode
= ERROR_INVALID_PARAMETER
;
1045 // If this is a serial port, a temporary file handle may be opened.
1046 if (pPort
->PortType
== PortType_PhysicalCOM
)
1048 if (_CreateNonspooledPort(pPort
))
1052 else if (GetLastError() != ERROR_SUCCESS
)
1054 dwErrorCode
= GetLastError();
1058 else if (pPort
->hFile
== INVALID_HANDLE_VALUE
)
1060 // All other port types need to be opened already.
1061 dwErrorCode
= ERROR_INVALID_PARAMETER
;
1065 // Pass the parameters to WriteFile.
1066 if (!WriteFile(pPort
->hFile
, pBuffer
, cbBuf
, pcbWritten
, NULL
))
1068 dwErrorCode
= GetLastError();
1069 ERR("WriteFile failed with error %lu!\n", dwErrorCode
);
1073 // If something was written down, we consider that a success, otherwise it's a timeout.
1075 dwErrorCode
= ERROR_SUCCESS
;
1077 dwErrorCode
= ERROR_TIMEOUT
;
1081 _ClosePortHandles(pPort
);
1083 SetLastError(dwErrorCode
);
1084 return (dwErrorCode
== ERROR_SUCCESS
);