2 * PROJECT: ReactOS Local Port Monitor
3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4 * PURPOSE: Functions related to ports
5 * COPYRIGHT: Copyright 2015-2017 Colin Finck (colin@reactos.org)
11 static const WCHAR wszNonspooledPrefix
[] = L
"NONSPOOLED_";
12 static const DWORD cchNonspooledPrefix
= _countof(wszNonspooledPrefix
) - 1;
14 static DWORD dwPortInfo1Offsets
[] = {
15 FIELD_OFFSET(PORT_INFO_1W
, pName
),
19 static DWORD dwPortInfo2Offsets
[] = {
20 FIELD_OFFSET(PORT_INFO_2W
, pPortName
),
21 FIELD_OFFSET(PORT_INFO_2W
, pMonitorName
),
22 FIELD_OFFSET(PORT_INFO_2W
, pDescription
),
28 * @name _GetNonspooledPortName
30 * Prepends "NONSPOOLED_" to a port name without colon.
32 * @param pwszPortNameWithoutColon
33 * Result of a previous GetPortNameWithoutColon call.
35 * @param ppwszNonspooledPortName
36 * Pointer to a buffer that will contain the NONSPOOLED port name.
37 * You have to free this buffer using DllFreeSplMem.
40 * ERROR_SUCCESS if the NONSPOOLED port name was successfully copied into the buffer.
41 * ERROR_NOT_ENOUGH_MEMORY if memory allocation failed.
44 _GetNonspooledPortName(PCWSTR pwszPortNameWithoutColon
, PWSTR
* ppwszNonspooledPortName
)
46 DWORD cchPortNameWithoutColon
;
48 cchPortNameWithoutColon
= wcslen(pwszPortNameWithoutColon
);
50 *ppwszNonspooledPortName
= DllAllocSplMem((cchNonspooledPrefix
+ cchPortNameWithoutColon
+ 1) * sizeof(WCHAR
));
51 if (!*ppwszNonspooledPortName
)
53 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
54 return ERROR_NOT_ENOUGH_MEMORY
;
57 CopyMemory(*ppwszNonspooledPortName
, wszNonspooledPrefix
, cchNonspooledPrefix
* sizeof(WCHAR
));
58 CopyMemory(&(*ppwszNonspooledPortName
)[cchNonspooledPrefix
], pwszPortNameWithoutColon
, (cchPortNameWithoutColon
+ 1) * sizeof(WCHAR
));
66 * Checks if the given port name is a legacy port (COM or LPT).
67 * This check is extra picky to not cause false positives (like file name ports starting with "COM" or "LPT").
70 * The port name to check.
76 * TRUE if this is definitely the asked legacy port, FALSE if not.
79 _IsLegacyPort(PCWSTR pwszPortName
, PCWSTR pwszPortType
)
81 const DWORD cchPortType
= 3;
82 PCWSTR p
= pwszPortName
;
84 // The port name must begin with pwszPortType.
85 if (_wcsnicmp(p
, pwszPortType
, cchPortType
) != 0)
90 // Now an arbitrary number of digits may follow.
91 while (*p
>= L
'0' && *p
<= L
'9')
94 // Finally, the legacy port must be terminated by a colon.
98 // If this is the end of the string, we have a legacy port.
100 return (*p
== L
'\0');
104 * @name _ClosePortHandles
106 * Closes a port of any type if it's open.
107 * Removes any saved mapping or existing definition of a NONSPOOLED device mapping.
110 * The port you want to close.
113 _ClosePortHandles(PLOCALMON_PORT pPort
)
115 PWSTR pwszNonspooledPortName
;
116 PWSTR pwszPortNameWithoutColon
;
118 // A port is already fully closed if the file handle is invalid.
119 if (pPort
->hFile
== INVALID_HANDLE_VALUE
)
122 // Close the file handle.
123 CloseHandle(pPort
->hFile
);
124 pPort
->hFile
= INVALID_HANDLE_VALUE
;
126 // A NONSPOOLED port was only created if pwszMapping contains the current port mapping.
127 if (!pPort
->pwszMapping
)
130 // Free the information about the current mapping.
131 DllFreeSplStr(pPort
->pwszMapping
);
132 pPort
->pwszMapping
= NULL
;
134 // Finally get the required strings and remove the DOS device definition for the NONSPOOLED port.
135 if (GetPortNameWithoutColon(pPort
->pwszPortName
, &pwszPortNameWithoutColon
) == ERROR_SUCCESS
)
137 if (_GetNonspooledPortName(pwszPortNameWithoutColon
, &pwszNonspooledPortName
) == ERROR_SUCCESS
)
139 DefineDosDeviceW(DDD_REMOVE_DEFINITION
, pwszNonspooledPortName
, NULL
);
140 DllFreeSplMem(pwszNonspooledPortName
);
143 DllFreeSplMem(pwszPortNameWithoutColon
);
148 * @name _CreateNonspooledPort
150 * Queries the system-wide device definition of the given port.
151 * If such a definition exists, it's a legacy port remapped to a named pipe by the spooler.
152 * In this case, the function creates and opens a NONSPOOLED device definition to the most recent mapping before the current one (usually the physical device).
155 * Pointer to the LOCALMON_PORT structure of the desired port.
158 * TRUE if a NONSPOOLED port was successfully created, FALSE otherwise.
159 * A more specific error code can be obtained through GetLastError.
160 * In particular, if the return value is FALSE and GetLastError returns ERROR_SUCCESS, no NONSPOOLED port is needed for this port.
163 _CreateNonspooledPort(PLOCALMON_PORT pPort
)
165 const WCHAR wszLocalSlashes
[] = L
"\\\\.\\";
166 const DWORD cchLocalSlashes
= _countof(wszLocalSlashes
) - 1;
168 const WCHAR wszSpoolerNamedPipe
[] = L
"\\Device\\NamedPipe\\Spooler\\";
169 const DWORD cchSpoolerNamedPipe
= _countof(wszSpoolerNamedPipe
) - 1;
171 BOOL bReturnValue
= FALSE
;
172 DWORD cchPortNameWithoutColon
;
174 HANDLE hToken
= NULL
;
176 PWSTR pwszDeviceMappings
= NULL
;
177 PWSTR pwszNonspooledFileName
= NULL
;
178 PWSTR pwszNonspooledPortName
= NULL
;
179 PWSTR pwszPipeName
= NULL
;
180 PWSTR pwszPortNameWithoutColon
= NULL
;
182 // We need the port name without the trailing colon.
183 dwErrorCode
= GetPortNameWithoutColon(pPort
->pwszPortName
, &pwszPortNameWithoutColon
);
184 if (dwErrorCode
== ERROR_INVALID_PARAMETER
)
186 // This port has no trailing colon, so we also need no NONSPOOLED mapping for it.
187 dwErrorCode
= ERROR_SUCCESS
;
190 else if (dwErrorCode
!= ERROR_SUCCESS
)
192 // Another unexpected failure.
196 cchPortNameWithoutColon
= wcslen(pwszPortNameWithoutColon
);
198 // The spooler has usually remapped the legacy port to a named pipe of the format in wszSpoolerNamedPipe.
199 // Construct the device name of this pipe.
200 pwszPipeName
= DllAllocSplMem((cchSpoolerNamedPipe
+ cchPortNameWithoutColon
+ 1) * sizeof(WCHAR
));
203 dwErrorCode
= ERROR_NOT_ENOUGH_MEMORY
;
204 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
208 CopyMemory(pwszPipeName
, wszSpoolerNamedPipe
, cchSpoolerNamedPipe
* sizeof(WCHAR
));
209 CopyMemory(&pwszPipeName
[cchSpoolerNamedPipe
], pwszPortNameWithoutColon
, (cchPortNameWithoutColon
+ 1) * sizeof(WCHAR
));
211 // QueryDosDeviceW is one of the shitty APIs that gives no information about the required buffer size and wants you to know it by pure magic.
212 // Examples show that a value of MAX_PATH * sizeof(WCHAR) is usually taken here, so we have no other option either.
213 pwszDeviceMappings
= DllAllocSplMem(MAX_PATH
* sizeof(WCHAR
));
214 if (!pwszDeviceMappings
)
216 dwErrorCode
= ERROR_NOT_ENOUGH_MEMORY
;
217 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
221 // Switch to the SYSTEM context, because we're only interested in creating NONSPOOLED ports for system-wide ports.
222 // User-local ports (like _some_ redirected networked ones) aren't remapped by the spooler and can be opened directly.
223 hToken
= RevertToPrinterSelf();
226 dwErrorCode
= GetLastError();
227 ERR("RevertToPrinterSelf failed with error %lu!\n", dwErrorCode
);
231 // QueryDosDeviceW returns the current mapping and a list of prior mappings of this legacy port, which is managed as a DOS device in the system.
232 if (!QueryDosDeviceW(pwszPortNameWithoutColon
, pwszDeviceMappings
, MAX_PATH
))
234 // No system-wide port exists, so we also need no NONSPOOLED mapping.
235 dwErrorCode
= ERROR_SUCCESS
;
239 // Check if this port has already been opened by _CreateNonspooledPort previously.
240 if (pPort
->pwszMapping
)
242 // In this case, we just need to do something if the mapping has changed.
243 // Therefore, check if the stored mapping equals the current mapping.
244 if (wcscmp(pPort
->pwszMapping
, pwszDeviceMappings
) == 0)
246 // We don't need to do anything in this case.
247 dwErrorCode
= ERROR_SUCCESS
;
252 // Close the open file handle and free the memory for pwszMapping before remapping.
253 CloseHandle(pPort
->hFile
);
254 pPort
->hFile
= INVALID_HANDLE_VALUE
;
256 DllFreeSplStr(pPort
->pwszMapping
);
257 pPort
->pwszMapping
= NULL
;
261 // The port is usually mapped to the named pipe and this is how we received our data for printing.
262 // What we now need for accessing the actual port is the most recent mapping different from the named pipe.
263 p
= pwszDeviceMappings
;
269 // We reached the end of the list without finding a mapping.
270 ERR("Can't find a suitable mapping for the port \"%S\"!", pPort
->pwszPortName
);
274 if (_wcsicmp(p
, pwszPipeName
) != 0)
277 // Advance to the next mapping in the list.
281 // We now want to create a DOS device "NONSPOOLED_<PortName>" to this mapping, so that we're able to open it through CreateFileW.
282 dwErrorCode
= _GetNonspooledPortName(pwszPortNameWithoutColon
, &pwszNonspooledPortName
);
283 if (dwErrorCode
!= ERROR_SUCCESS
)
286 // Delete a possibly existing NONSPOOLED device before creating the new one, so we don't stack up device definitions.
287 DefineDosDeviceW(DDD_REMOVE_DEFINITION
, pwszNonspooledPortName
, NULL
);
289 if (!DefineDosDeviceW(DDD_RAW_TARGET_PATH
, pwszNonspooledPortName
, p
))
291 dwErrorCode
= GetLastError();
292 ERR("DefineDosDeviceW failed with error %lu!\n", dwErrorCode
);
296 // This is all we needed to do in SYSTEM context.
297 ImpersonatePrinterClient(hToken
);
300 // Construct the file name to our created device for CreateFileW.
301 pwszNonspooledFileName
= DllAllocSplMem((cchLocalSlashes
+ cchNonspooledPrefix
+ cchPortNameWithoutColon
+ 1) * sizeof(WCHAR
));
302 if (!pwszNonspooledFileName
)
304 dwErrorCode
= ERROR_NOT_ENOUGH_MEMORY
;
305 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
309 CopyMemory(pwszNonspooledFileName
, wszLocalSlashes
, cchLocalSlashes
* sizeof(WCHAR
));
310 CopyMemory(&pwszNonspooledFileName
[cchLocalSlashes
], wszNonspooledPrefix
, cchNonspooledPrefix
* sizeof(WCHAR
));
311 CopyMemory(&pwszNonspooledFileName
[cchLocalSlashes
+ cchNonspooledPrefix
], pwszPortNameWithoutColon
, (cchPortNameWithoutColon
+ 1) * sizeof(WCHAR
));
313 // Finally open it for reading and writing.
314 pPort
->hFile
= CreateFileW(pwszNonspooledFileName
, GENERIC_READ
| GENERIC_WRITE
, FILE_SHARE_READ
, NULL
, CREATE_ALWAYS
, 0, NULL
);
315 if (pPort
->hFile
== INVALID_HANDLE_VALUE
)
317 dwErrorCode
= GetLastError();
318 ERR("CreateFileW failed with error %lu!\n", dwErrorCode
);
322 // Store the current mapping of the port, so that we can check if it has changed.
323 pPort
->pwszMapping
= AllocSplStr(pwszDeviceMappings
);
324 if (!pPort
->pwszMapping
)
326 dwErrorCode
= ERROR_NOT_ENOUGH_MEMORY
;
331 dwErrorCode
= ERROR_SUCCESS
;
335 ImpersonatePrinterClient(hToken
);
337 if (pwszDeviceMappings
)
338 DllFreeSplMem(pwszDeviceMappings
);
340 if (pwszNonspooledFileName
)
341 DllFreeSplMem(pwszNonspooledFileName
);
343 if (pwszNonspooledPortName
)
344 DllFreeSplMem(pwszNonspooledPortName
);
347 DllFreeSplMem(pwszPipeName
);
349 if (pwszPortNameWithoutColon
)
350 DllFreeSplMem(pwszPortNameWithoutColon
);
352 SetLastError(dwErrorCode
);
356 static PLOCALMON_PORT
357 _FindPort(PLOCALMON_HANDLE pLocalmon
, PCWSTR pwszPortName
)
360 PLOCALMON_PORT pPort
;
362 for (pEntry
= pLocalmon
->RegistryPorts
.Flink
; pEntry
!= &pLocalmon
->RegistryPorts
; pEntry
= pEntry
->Flink
)
364 pPort
= CONTAINING_RECORD(pEntry
, LOCALMON_PORT
, Entry
);
366 if (wcscmp(pPort
->pwszPortName
, pwszPortName
) == 0)
374 _LocalmonGetPortLevel1(PLOCALMON_PORT pPort
, PPORT_INFO_1W
* ppPortInfo
, PBYTE
* ppPortInfoEnd
, PDWORD pcbNeeded
)
377 PCWSTR pwszStrings
[1];
379 // Calculate the string lengths.
382 cbPortName
= (wcslen(pPort
->pwszPortName
) + 1) * sizeof(WCHAR
);
384 *pcbNeeded
+= sizeof(PORT_INFO_1W
) + cbPortName
;
388 // Set the pName field.
389 pwszStrings
[0] = pPort
->pwszPortName
;
391 // Copy the structure and advance to the next one in the output buffer.
392 *ppPortInfoEnd
= PackStrings(pwszStrings
, (PBYTE
)(*ppPortInfo
), dwPortInfo1Offsets
, *ppPortInfoEnd
);
397 _LocalmonGetPortLevel2(PLOCALMON_PORT pPort
, PPORT_INFO_2W
* ppPortInfo
, PBYTE
* ppPortInfoEnd
, PDWORD pcbNeeded
)
400 PCWSTR pwszStrings
[3];
402 // Calculate the string lengths.
405 cbPortName
= (wcslen(pPort
->pwszPortName
) + 1) * sizeof(WCHAR
);
407 *pcbNeeded
+= sizeof(PORT_INFO_2W
) + cbPortName
+ cbLocalMonitor
+ cbLocalPort
;
411 // All local ports are writable and readable.
412 (*ppPortInfo
)->fPortType
= PORT_TYPE_WRITE
| PORT_TYPE_READ
;
413 (*ppPortInfo
)->Reserved
= 0;
415 // Set the pPortName field.
416 pwszStrings
[0] = pPort
->pwszPortName
;
418 // Set the pMonitorName field.
419 pwszStrings
[1] = (PWSTR
)pwszLocalMonitor
;
421 // Set the pDescription field.
422 pwszStrings
[2] = (PWSTR
)pwszLocalPort
;
424 // Copy the structure and advance to the next one in the output buffer.
425 *ppPortInfoEnd
= PackStrings(pwszStrings
, (PBYTE
)(*ppPortInfo
), dwPortInfo2Offsets
, *ppPortInfoEnd
);
430 * @name _SetTransmissionRetryTimeout
432 * Checks if the given port is a physical one and sets the transmission retry timeout in this case using the value from registry.
435 * The port to operate on.
438 * TRUE if the given port is a physical one, FALSE otherwise.
441 _SetTransmissionRetryTimeout(PLOCALMON_PORT pPort
)
443 COMMTIMEOUTS CommTimeouts
;
445 // Get the timeout from the port.
446 if (!GetCommTimeouts(pPort
->hFile
, &CommTimeouts
))
449 // Set the timeout using the value from registry.
450 CommTimeouts
.WriteTotalTimeoutConstant
= GetLPTTransmissionRetryTimeout() * 1000;
451 SetCommTimeouts(pPort
->hFile
, &CommTimeouts
);
457 LocalmonClosePort(HANDLE hPort
)
459 PLOCALMON_PORT pPort
= (PLOCALMON_PORT
)hPort
;
461 TRACE("LocalmonClosePort(%p)\n", hPort
);
466 SetLastError(ERROR_INVALID_PARAMETER
);
470 // Close the file handle, free memory for pwszMapping and delete any NONSPOOLED port.
471 _ClosePortHandles(pPort
);
473 // Close any open printer handle.
476 ClosePrinter(pPort
->hPrinter
);
477 pPort
->hPrinter
= NULL
;
480 // Free virtual FILE: ports which were created in LocalmonOpenPort.
481 if (pPort
->PortType
== PortType_FILE
)
483 EnterCriticalSection(&pPort
->pLocalmon
->Section
);
484 RemoveEntryList(&pPort
->Entry
);
485 LeaveCriticalSection(&pPort
->pLocalmon
->Section
);
486 DllFreeSplMem(pPort
);
489 SetLastError(ERROR_SUCCESS
);
494 LocalmonEndDocPort(HANDLE hPort
)
496 PLOCALMON_PORT pPort
= (PLOCALMON_PORT
)hPort
;
498 TRACE("LocalmonEndDocPort(%p)\n", hPort
);
503 SetLastError(ERROR_INVALID_PARAMETER
);
507 // Ending a document requires starting it first :-P
508 if (pPort
->bStartedDoc
)
510 // Close all ports opened in StartDocPort.
511 // That is, all but physical LPT ports (opened in OpenPort).
512 if (pPort
->PortType
!= PortType_PhysicalLPT
)
513 _ClosePortHandles(pPort
);
515 // Report our progress.
516 SetJobW(pPort
->hPrinter
, pPort
->dwJobID
, 0, NULL
, JOB_CONTROL_SENT_TO_PRINTER
);
518 // We're done with the printer.
519 ClosePrinter(pPort
->hPrinter
);
520 pPort
->hPrinter
= NULL
;
522 // A new document can now be started again.
523 pPort
->bStartedDoc
= FALSE
;
526 SetLastError(ERROR_SUCCESS
);
531 LocalmonEnumPorts(HANDLE hMonitor
, PWSTR pName
, DWORD Level
, PBYTE pPorts
, DWORD cbBuf
, PDWORD pcbNeeded
, PDWORD pcReturned
)
536 PLOCALMON_HANDLE pLocalmon
= (PLOCALMON_HANDLE
)hMonitor
;
537 PLOCALMON_PORT pPort
;
539 TRACE("LocalmonEnumPorts(%p, %S, %lu, %p, %lu, %p, %p)\n", hMonitor
, pName
, Level
, pPorts
, cbBuf
, pcbNeeded
, pcReturned
);
541 // Windows Server 2003's Local Port Monitor does absolutely no sanity checks here, not even for the Level parameter.
542 // As we implement a more modern MONITOR2-based Port Monitor, check at least our hMonitor.
545 dwErrorCode
= ERROR_INVALID_HANDLE
;
553 EnterCriticalSection(&pLocalmon
->Section
);
555 // Count the required buffer size and the number of ports.
556 for (pEntry
= pLocalmon
->RegistryPorts
.Flink
; pEntry
!= &pLocalmon
->RegistryPorts
; pEntry
= pEntry
->Flink
)
558 pPort
= CONTAINING_RECORD(pEntry
, LOCALMON_PORT
, Entry
);
561 _LocalmonGetPortLevel1(pPort
, NULL
, NULL
, pcbNeeded
);
563 _LocalmonGetPortLevel2(pPort
, NULL
, NULL
, pcbNeeded
);
566 // Check if the supplied buffer is large enough.
567 if (cbBuf
< *pcbNeeded
)
569 LeaveCriticalSection(&pLocalmon
->Section
);
570 dwErrorCode
= ERROR_INSUFFICIENT_BUFFER
;
574 // Copy over the Port information.
575 pPortInfoEnd
= &pPorts
[*pcbNeeded
];
577 for (pEntry
= pLocalmon
->RegistryPorts
.Flink
; pEntry
!= &pLocalmon
->RegistryPorts
; pEntry
= pEntry
->Flink
)
579 pPort
= CONTAINING_RECORD(pEntry
, LOCALMON_PORT
, Entry
);
582 _LocalmonGetPortLevel1(pPort
, (PPORT_INFO_1W
*)&pPorts
, &pPortInfoEnd
, NULL
);
584 _LocalmonGetPortLevel2(pPort
, (PPORT_INFO_2W
*)&pPorts
, &pPortInfoEnd
, NULL
);
589 LeaveCriticalSection(&pLocalmon
->Section
);
590 dwErrorCode
= ERROR_SUCCESS
;
593 SetLastError(dwErrorCode
);
594 return (dwErrorCode
== ERROR_SUCCESS
);
598 * @name LocalmonGetPrinterDataFromPort
600 * Performs a DeviceIoControl call for the given port.
603 * The port to operate on.
606 * The dwIoControlCode passed to DeviceIoControl. Must not be zero!
609 * This parameter is ignored.
612 * The lpInBuffer passed to DeviceIoControl.
615 * The nInBufferSize passed to DeviceIoControl.
618 * The lpOutBuffer passed to DeviceIoControl.
621 * The nOutBufferSize passed to DeviceIoControl.
623 * @param lpcbReturned
624 * The lpBytesReturned passed to DeviceIoControl. Must not be zero!
627 * TRUE if the DeviceIoControl call was successful, FALSE otherwise.
628 * A more specific error code can be obtained through GetLastError.
631 LocalmonGetPrinterDataFromPort(HANDLE hPort
, DWORD ControlID
, PWSTR pValueName
, PWSTR lpInBuffer
, DWORD cbInBuffer
, PWSTR lpOutBuffer
, DWORD cbOutBuffer
, PDWORD lpcbReturned
)
633 BOOL bOpenedPort
= FALSE
;
635 PLOCALMON_PORT pPort
= (PLOCALMON_PORT
)hPort
;
637 TRACE("LocalmonGetPrinterDataFromPort(%p, %lu, %p, %p, %lu, %p, %lu, %p)\n", hPort
, ControlID
, pValueName
, lpInBuffer
, cbInBuffer
, lpOutBuffer
, cbOutBuffer
, lpcbReturned
);
640 if (!pPort
|| !ControlID
|| !lpcbReturned
)
642 dwErrorCode
= ERROR_INVALID_PARAMETER
;
646 // If this is a serial port, a temporary file handle may be opened.
647 if (pPort
->PortType
== PortType_PhysicalCOM
)
649 if (_CreateNonspooledPort(pPort
))
653 else if (GetLastError() != ERROR_SUCCESS
)
655 dwErrorCode
= GetLastError();
659 else if (pPort
->hFile
== INVALID_HANDLE_VALUE
)
661 // All other port types need to be opened already.
662 dwErrorCode
= ERROR_INVALID_PARAMETER
;
666 // Pass the parameters to DeviceIoControl.
667 if (!DeviceIoControl(pPort
->hFile
, ControlID
, lpInBuffer
, cbInBuffer
, lpOutBuffer
, cbOutBuffer
, lpcbReturned
, NULL
))
669 dwErrorCode
= GetLastError();
670 ERR("DeviceIoControl failed with error %lu!\n", dwErrorCode
);
674 dwErrorCode
= ERROR_SUCCESS
;
678 _ClosePortHandles(pPort
);
680 SetLastError(dwErrorCode
);
681 return (dwErrorCode
== ERROR_SUCCESS
);
685 LocalmonOpenPort(HANDLE hMonitor
, PWSTR pName
, PHANDLE pHandle
)
688 PLOCALMON_HANDLE pLocalmon
= (PLOCALMON_HANDLE
)hMonitor
;
689 PLOCALMON_PORT pPort
;
691 TRACE("LocalmonOpenPort(%p, %S, %p)\n", hMonitor
, pName
, pHandle
);
694 if (!pLocalmon
|| !pName
|| !pHandle
)
696 dwErrorCode
= ERROR_INVALID_PARAMETER
;
700 EnterCriticalSection(&pLocalmon
->Section
);
702 // Check if this is a FILE: port.
703 if (_wcsicmp(pName
, L
"FILE:") == 0)
705 // For FILE:, we create a virtual port for each request.
706 pPort
= DllAllocSplMem(sizeof(LOCALMON_PORT
));
707 pPort
->pLocalmon
= pLocalmon
;
708 pPort
->PortType
= PortType_FILE
;
709 pPort
->hFile
= INVALID_HANDLE_VALUE
;
711 // Add it to the list of file ports.
712 InsertTailList(&pLocalmon
->FilePorts
, &pPort
->Entry
);
716 // Check if the port name is valid.
717 pPort
= _FindPort(pLocalmon
, pName
);
720 LeaveCriticalSection(&pLocalmon
->Section
);
721 dwErrorCode
= ERROR_UNKNOWN_PORT
;
725 // Even if this API is called OpenPort, port file handles aren't always opened here :-P
726 // Windows only does this for physical LPT ports here to enable bidirectional communication with the printer outside of jobs (using ReadPort and WritePort).
727 // The others are only opened per job in StartDocPort.
728 if (_IsLegacyPort(pName
, L
"LPT"))
730 // Try to create a NONSPOOLED port and open it.
731 if (_CreateNonspooledPort(pPort
))
733 // Set the transmission retry timeout for the ReadPort and WritePort calls.
734 // This also checks if this port is a physical one.
735 if (_SetTransmissionRetryTimeout(pPort
))
737 // This is definitely a physical LPT port!
738 pPort
->PortType
= PortType_PhysicalLPT
;
742 // This is no physical port, so don't keep its handle open.
743 _ClosePortHandles(pPort
);
746 else if (GetLastError() != ERROR_SUCCESS
)
748 LeaveCriticalSection(&pLocalmon
->Section
);
749 dwErrorCode
= GetLastError();
753 else if (_IsLegacyPort(pName
, L
"COM"))
755 // COM ports can't be redirected over the network, so this is a physical one.
756 pPort
->PortType
= PortType_PhysicalCOM
;
760 LeaveCriticalSection(&pLocalmon
->Section
);
762 // Return our fetched LOCALMON_PORT structure in the handle.
763 *pHandle
= (PHANDLE
)pPort
;
764 dwErrorCode
= ERROR_SUCCESS
;
767 SetLastError(dwErrorCode
);
768 return (dwErrorCode
== ERROR_SUCCESS
);
772 * @name LocalmonSetPortTimeOuts
774 * Performs a SetCommTimeouts call for the given port.
777 * The port to operate on.
780 * Pointer to a COMMTIMEOUTS structure that is passed to SetCommTimeouts.
783 * Reserved parameter, must be 0.
786 * TRUE if the SetCommTimeouts call was successful, FALSE otherwise.
787 * A more specific error code can be obtained through GetLastError.
790 LocalmonSetPortTimeOuts(HANDLE hPort
, LPCOMMTIMEOUTS lpCTO
, DWORD Reserved
)
792 BOOL bOpenedPort
= FALSE
;
794 PLOCALMON_PORT pPort
= (PLOCALMON_PORT
)hPort
;
796 TRACE("LocalmonSetPortTimeOuts(%p, %p, %lu)\n", hPort
, lpCTO
, Reserved
);
799 if (!pPort
|| !lpCTO
)
801 dwErrorCode
= ERROR_INVALID_PARAMETER
;
805 // If this is a serial port, a temporary file handle may be opened.
806 if (pPort
->PortType
== PortType_PhysicalCOM
)
808 if (_CreateNonspooledPort(pPort
))
812 else if (GetLastError() != ERROR_SUCCESS
)
814 dwErrorCode
= GetLastError();
818 else if (pPort
->hFile
== INVALID_HANDLE_VALUE
)
820 // All other port types need to be opened already.
821 dwErrorCode
= ERROR_INVALID_PARAMETER
;
825 // Finally pass the parameters to SetCommTimeouts.
826 if (!SetCommTimeouts(pPort
->hFile
, lpCTO
))
828 dwErrorCode
= GetLastError();
829 ERR("SetCommTimeouts failed with error %lu!\n", dwErrorCode
);
833 dwErrorCode
= ERROR_SUCCESS
;
837 _ClosePortHandles(pPort
);
839 SetLastError(dwErrorCode
);
840 return (dwErrorCode
== ERROR_SUCCESS
);
844 LocalmonReadPort(HANDLE hPort
, PBYTE pBuffer
, DWORD cbBuffer
, PDWORD pcbRead
)
846 BOOL bOpenedPort
= FALSE
;
848 PLOCALMON_PORT pPort
= (PLOCALMON_PORT
)hPort
;
850 TRACE("LocalmonReadPort(%p, %p, %lu, %p)\n", hPort
, pBuffer
, cbBuffer
, pcbRead
);
853 if (!pPort
|| (cbBuffer
&& !pBuffer
) || !pcbRead
)
855 dwErrorCode
= ERROR_INVALID_PARAMETER
;
859 // Reading is only supported for physical ports.
860 if (pPort
->PortType
!= PortType_PhysicalCOM
&& pPort
->PortType
!= PortType_PhysicalLPT
)
862 dwErrorCode
= ERROR_INVALID_HANDLE
;
866 // If this is a serial port, a temporary file handle may be opened.
867 if (pPort
->PortType
== PortType_PhysicalCOM
)
869 if (_CreateNonspooledPort(pPort
))
873 else if (GetLastError() != ERROR_SUCCESS
)
875 dwErrorCode
= GetLastError();
880 // Pass the parameters to ReadFile.
881 if (!ReadFile(pPort
->hFile
, pBuffer
, cbBuffer
, pcbRead
, NULL
))
883 dwErrorCode
= GetLastError();
884 ERR("ReadFile failed with error %lu!\n", dwErrorCode
);
890 _ClosePortHandles(pPort
);
892 SetLastError(dwErrorCode
);
893 return (dwErrorCode
== ERROR_SUCCESS
);
897 LocalmonStartDocPort(HANDLE hPort
, PWSTR pPrinterName
, DWORD JobId
, DWORD Level
, PBYTE pDocInfo
)
900 PDOC_INFO_1W pDocInfo1
= (PDOC_INFO_1W
)pDocInfo
; // DOC_INFO_1W is the least common denominator for both DOC_INFO levels.
901 PLOCALMON_PORT pPort
= (PLOCALMON_PORT
)hPort
;
903 TRACE("LocalmonStartDocPort(%p, %S, %lu, %lu, %p)\n", hPort
, pPrinterName
, JobId
, Level
, pDocInfo
);
906 if (!pPort
|| !pPrinterName
|| (pPort
->PortType
== PortType_FILE
&& (!pDocInfo1
|| !pDocInfo1
->pOutputFile
|| !*pDocInfo1
->pOutputFile
)))
908 dwErrorCode
= ERROR_INVALID_PARAMETER
;
914 dwErrorCode
= ERROR_INVALID_LEVEL
;
918 // Calling StartDocPort multiple times isn't considered a failure, but we don't need to do anything then.
919 if (pPort
->bStartedDoc
)
921 dwErrorCode
= ERROR_SUCCESS
;
925 // Open a handle to the given printer for later reporting our progress using SetJobW.
926 if (!OpenPrinterW(pPrinterName
, &pPort
->hPrinter
, NULL
))
928 dwErrorCode
= GetLastError();
929 ERR("OpenPrinterW failed with error %lu!\n", dwErrorCode
);
933 // We need our Job ID for SetJobW as well.
934 pPort
->dwJobID
= JobId
;
936 // Check the port type.
937 if (pPort
->PortType
== PortType_PhysicalLPT
)
939 // Update the NONSPOOLED mapping if the port mapping has changed since our OpenPort call.
940 if (!_CreateNonspooledPort(pPort
) && GetLastError() != ERROR_SUCCESS
)
942 dwErrorCode
= GetLastError();
946 // Update the transmission retry timeout as well.
947 _SetTransmissionRetryTimeout(pPort
);
949 else if(pPort
->PortType
== PortType_FILE
)
951 // This is a FILE: port. Open the output file given in the Document Info.
952 pPort
->hFile
= CreateFileW(pDocInfo1
->pOutputFile
, GENERIC_WRITE
, FILE_SHARE_READ
| FILE_SHARE_WRITE
, NULL
, CREATE_ALWAYS
, 0, NULL
);
953 if (pPort
->hFile
== INVALID_HANDLE_VALUE
)
955 dwErrorCode
= GetLastError();
962 // - a physical COM port
963 // - a non-physical LPT port (e.g. with "net use LPT1 ...")
964 // - any other port (e.g. a file or a shared printer installed as a local port)
966 // For all these cases, we try to create a NONSPOOLED port per job.
967 // If _CreateNonspooledPort reports that no NONSPOOLED port is necessary, we can just open the port name.
968 if (!_CreateNonspooledPort(pPort
))
970 if (GetLastError() == ERROR_SUCCESS
)
972 pPort
->hFile
= CreateFileW(pPort
->pwszPortName
, GENERIC_WRITE
, FILE_SHARE_READ
, NULL
, CREATE_ALWAYS
, 0, NULL
);
973 if (pPort
->hFile
== INVALID_HANDLE_VALUE
)
975 dwErrorCode
= GetLastError();
981 dwErrorCode
= GetLastError();
987 // We were successful!
988 dwErrorCode
= ERROR_SUCCESS
;
989 pPort
->bStartedDoc
= TRUE
;
992 SetLastError(dwErrorCode
);
993 return (dwErrorCode
== ERROR_SUCCESS
);
997 LocalmonWritePort(HANDLE hPort
, PBYTE pBuffer
, DWORD cbBuf
, PDWORD pcbWritten
)
999 BOOL bOpenedPort
= FALSE
;
1001 PLOCALMON_PORT pPort
= (PLOCALMON_PORT
)hPort
;
1003 TRACE("LocalmonWritePort(%p, %p, %lu, %p)\n", hPort
, pBuffer
, cbBuf
, pcbWritten
);
1006 if (!pPort
|| (cbBuf
&& !pBuffer
) || !pcbWritten
)
1008 dwErrorCode
= ERROR_INVALID_PARAMETER
;
1012 // If this is a serial port, a temporary file handle may be opened.
1013 if (pPort
->PortType
== PortType_PhysicalCOM
)
1015 if (_CreateNonspooledPort(pPort
))
1019 else if (GetLastError() != ERROR_SUCCESS
)
1021 dwErrorCode
= GetLastError();
1025 else if (pPort
->hFile
== INVALID_HANDLE_VALUE
)
1027 // All other port types need to be opened already.
1028 dwErrorCode
= ERROR_INVALID_PARAMETER
;
1032 // Pass the parameters to WriteFile.
1033 if (!WriteFile(pPort
->hFile
, pBuffer
, cbBuf
, pcbWritten
, NULL
))
1035 dwErrorCode
= GetLastError();
1036 ERR("WriteFile failed with error %lu!\n", dwErrorCode
);
1040 // If something was written down, we consider that a success, otherwise it's a timeout.
1042 dwErrorCode
= ERROR_SUCCESS
;
1044 dwErrorCode
= ERROR_TIMEOUT
;
1048 _ClosePortHandles(pPort
);
1050 SetLastError(dwErrorCode
);
1051 return (dwErrorCode
== ERROR_SUCCESS
);