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.
20 * You have to free the returned buffer using DllFreeSplMem.
22 * @param pwszPortNameWithoutColon
23 * Result of a previous GetPortNameWithoutColon call.
26 * Buffer containing the NONSPOOLED port name or NULL in case of failure.
29 _GetNonspooledPortName(PCWSTR pwszPortNameWithoutColon
)
31 DWORD cchPortNameWithoutColon
;
32 PWSTR pwszNonspooledPortName
;
34 cchPortNameWithoutColon
= wcslen(pwszPortNameWithoutColon
);
36 pwszNonspooledPortName
= DllAllocSplMem((cchNonspooledPrefix
+ cchPortNameWithoutColon
+ 1) * sizeof(WCHAR
));
37 if (!pwszNonspooledPortName
)
39 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
43 CopyMemory(pwszNonspooledPortName
, wszNonspooledPrefix
, cchNonspooledPrefix
* sizeof(WCHAR
));
44 CopyMemory(&pwszNonspooledPortName
[cchNonspooledPrefix
], pwszPortNameWithoutColon
, (cchPortNameWithoutColon
+ 1) * sizeof(WCHAR
));
46 return pwszNonspooledPortName
;
50 * @name _ClosePortHandles
52 * Closes a port of any type if it's open.
53 * Removes any saved mapping or existing definition of a NONSPOOLED device mapping.
56 * The port you want to close.
59 _ClosePortHandles(PLOCALMON_PORT pPort
)
61 PWSTR pwszNonspooledPortName
;
62 PWSTR pwszPortNameWithoutColon
;
64 // A port is already fully closed if the file handle is invalid.
65 if (pPort
->hFile
== INVALID_HANDLE_VALUE
)
68 // Close the file handle.
69 CloseHandle(pPort
->hFile
);
70 pPort
->hFile
= INVALID_HANDLE_VALUE
;
72 // A NONSPOOLED port was only created if pwszMapping contains the current port mapping.
73 if (!pPort
->pwszMapping
)
76 // Free the information about the current mapping.
77 DllFreeSplStr(pPort
->pwszMapping
);
78 pPort
->pwszMapping
= NULL
;
80 // Finally get the required strings and remove the DOS device definition for the NONSPOOLED port.
81 pwszPortNameWithoutColon
= GetPortNameWithoutColon(pPort
->pwszPortName
);
82 if (pwszPortNameWithoutColon
)
84 pwszNonspooledPortName
= _GetNonspooledPortName(pwszPortNameWithoutColon
);
85 if (pwszNonspooledPortName
)
87 DefineDosDeviceW(DDD_REMOVE_DEFINITION
, pwszNonspooledPortName
, NULL
);
88 DllFreeSplMem(pwszNonspooledPortName
);
91 DllFreeSplMem(pwszPortNameWithoutColon
);
96 * @name _CreateNonspooledPort
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).
103 * Pointer to the LOCALMON_PORT structure of the desired port.
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.
112 _CreateNonspooledPort(PLOCALMON_PORT pPort
)
114 const WCHAR wszLocalSlashes
[] = L
"\\\\.\\";
115 const DWORD cchLocalSlashes
= _countof(wszLocalSlashes
) - 1;
117 const WCHAR wszSpoolerNamedPipe
[] = L
"\\Device\\NamedPipe\\Spooler\\";
118 const DWORD cchSpoolerNamedPipe
= _countof(wszSpoolerNamedPipe
) - 1;
120 BOOL bReturnValue
= FALSE
;
121 DWORD cchPortNameWithoutColon
;
123 HANDLE hToken
= NULL
;
125 PWSTR pwszDeviceMappings
= NULL
;
126 PWSTR pwszNonspooledFileName
= NULL
;
127 PWSTR pwszNonspooledPortName
= NULL
;
128 PWSTR pwszPipeName
= NULL
;
129 PWSTR pwszPortNameWithoutColon
= NULL
;
131 // We need the port name without the trailing colon.
132 pwszPortNameWithoutColon
= GetPortNameWithoutColon(pPort
->pwszPortName
);
133 if (!pwszPortNameWithoutColon
)
135 dwErrorCode
= ERROR_NOT_ENOUGH_MEMORY
;
139 cchPortNameWithoutColon
= wcslen(pwszPortNameWithoutColon
);
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
));
146 dwErrorCode
= ERROR_NOT_ENOUGH_MEMORY
;
147 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
151 CopyMemory(pwszPipeName
, wszSpoolerNamedPipe
, cchSpoolerNamedPipe
* sizeof(WCHAR
));
152 CopyMemory(&pwszPipeName
[cchSpoolerNamedPipe
], pwszPortNameWithoutColon
, (cchPortNameWithoutColon
+ 1) * sizeof(WCHAR
));
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
)
159 dwErrorCode
= ERROR_NOT_ENOUGH_MEMORY
;
160 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
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();
169 dwErrorCode
= GetLastError();
170 ERR("RevertToPrinterSelf failed with error %lu!\n", dwErrorCode
);
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
))
177 // No system-wide port exists, so we also need no NONSPOOLED mapping.
178 dwErrorCode
= ERROR_SUCCESS
;
182 // Check if this port has already been opened by _CreateNonspooledPort previously.
183 if (pPort
->pwszMapping
)
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)
189 // We don't need to do anything in this case.
190 dwErrorCode
= ERROR_SUCCESS
;
195 // Close the open file handle and free the memory for pwszMapping before remapping.
196 CloseHandle(pPort
->hFile
);
197 pPort
->hFile
= INVALID_HANDLE_VALUE
;
199 DllFreeSplStr(pPort
->pwszMapping
);
200 pPort
->pwszMapping
= NULL
;
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
;
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
);
217 if (_wcsicmp(p
, pwszPipeName
) != 0)
220 // Advance to the next mapping in the list.
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
)
228 dwErrorCode
= ERROR_NOT_ENOUGH_MEMORY
;
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
);
235 if (!DefineDosDeviceW(DDD_RAW_TARGET_PATH
, pwszNonspooledPortName
, p
))
237 dwErrorCode
= GetLastError();
238 ERR("DefineDosDeviceW failed with error %lu!\n", dwErrorCode
);
242 // This is all we needed to do in SYSTEM context.
243 ImpersonatePrinterClient(hToken
);
246 // Construct the file name to our created device for CreateFileW.
247 pwszNonspooledFileName
= DllAllocSplMem((cchLocalSlashes
+ cchNonspooledPrefix
+ cchPortNameWithoutColon
+ 1) * sizeof(WCHAR
));
248 if (!pwszNonspooledFileName
)
250 dwErrorCode
= ERROR_NOT_ENOUGH_MEMORY
;
251 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
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
));
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
)
263 dwErrorCode
= GetLastError();
264 ERR("CreateFileW failed with error %lu!\n", dwErrorCode
);
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
)
272 dwErrorCode
= ERROR_NOT_ENOUGH_MEMORY
;
277 dwErrorCode
= ERROR_SUCCESS
;
281 ImpersonatePrinterClient(hToken
);
283 if (pwszDeviceMappings
)
284 DllFreeSplMem(pwszDeviceMappings
);
286 if (pwszNonspooledFileName
)
287 DllFreeSplMem(pwszNonspooledFileName
);
289 if (pwszNonspooledPortName
)
290 DllFreeSplMem(pwszNonspooledPortName
);
293 DllFreeSplMem(pwszPipeName
);
295 if (pwszPortNameWithoutColon
)
296 DllFreeSplMem(pwszPortNameWithoutColon
);
298 SetLastError(dwErrorCode
);
302 static PLOCALMON_PORT
303 _FindPort(PLOCALMON_HANDLE pLocalmon
, PCWSTR pwszPortName
)
306 PLOCALMON_PORT pPort
;
308 for (pEntry
= pLocalmon
->RegistryPorts
.Flink
; pEntry
!= &pLocalmon
->RegistryPorts
; pEntry
= pEntry
->Flink
)
310 pPort
= CONTAINING_RECORD(pEntry
, LOCALMON_PORT
, Entry
);
312 if (wcscmp(pPort
->pwszPortName
, pwszPortName
) == 0)
320 _LocalmonEnumPortsLevel1(PLOCALMON_HANDLE pLocalmon
, PBYTE pPorts
, DWORD cbBuf
, PDWORD pcbNeeded
, PDWORD pcReturned
)
324 DWORD dwPortCount
= 0;
328 PLOCALMON_PORT pPort
;
329 PORT_INFO_1W PortInfo1
;
331 // Count the required buffer size and the number of datatypes.
332 for (pEntry
= pLocalmon
->RegistryPorts
.Flink
; pEntry
!= &pLocalmon
->RegistryPorts
; pEntry
= pEntry
->Flink
)
334 pPort
= CONTAINING_RECORD(pEntry
, LOCALMON_PORT
, Entry
);
336 cbPortName
= (wcslen(pPort
->pwszPortName
) + 1) * sizeof(WCHAR
);
337 *pcbNeeded
+= sizeof(PORT_INFO_1W
) + cbPortName
;
341 // Check if the supplied buffer is large enough.
342 if (cbBuf
< *pcbNeeded
)
344 dwErrorCode
= ERROR_INSUFFICIENT_BUFFER
;
348 // Put the strings right after the last PORT_INFO_1W structure.
350 pPortString
= pPorts
+ dwPortCount
* sizeof(PORT_INFO_1W
);
352 // Copy over all ports.
353 for (pEntry
= pLocalmon
->RegistryPorts
.Flink
; pEntry
!= &pLocalmon
->RegistryPorts
; pEntry
= pEntry
->Flink
)
355 pPort
= CONTAINING_RECORD(pEntry
, LOCALMON_PORT
, Entry
);
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
;
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
);
368 *pcReturned
= dwPortCount
;
369 dwErrorCode
= ERROR_SUCCESS
;
376 _LocalmonEnumPortsLevel2(PLOCALMON_HANDLE pLocalmon
, PBYTE pPorts
, DWORD cbBuf
, PDWORD pcbNeeded
, PDWORD pcReturned
)
380 DWORD dwPortCount
= 0;
384 PLOCALMON_PORT pPort
;
385 PORT_INFO_2W PortInfo2
;
387 // Count the required buffer size and the number of datatypes.
388 for (pEntry
= pLocalmon
->RegistryPorts
.Flink
; pEntry
!= &pLocalmon
->RegistryPorts
; pEntry
= pEntry
->Flink
)
390 pPort
= CONTAINING_RECORD(pEntry
, LOCALMON_PORT
, Entry
);
392 cbPortName
= (wcslen(pPort
->pwszPortName
) + 1) * sizeof(WCHAR
);
393 *pcbNeeded
+= sizeof(PORT_INFO_2W
) + cbPortName
+ cbLocalMonitor
+ cbLocalPort
;
397 // Check if the supplied buffer is large enough.
398 if (cbBuf
< *pcbNeeded
)
400 dwErrorCode
= ERROR_INSUFFICIENT_BUFFER
;
404 // Put the strings right after the last PORT_INFO_2W structure.
406 pPortString
= pPorts
+ dwPortCount
* sizeof(PORT_INFO_2W
);
408 // Copy over all ports.
409 for (pEntry
= pLocalmon
->RegistryPorts
.Flink
; pEntry
!= &pLocalmon
->RegistryPorts
; pEntry
= pEntry
->Flink
)
411 pPort
= CONTAINING_RECORD(pEntry
, LOCALMON_PORT
, Entry
);
413 // All local ports are writable and readable.
414 PortInfo2
.fPortType
= PORT_TYPE_WRITE
| PORT_TYPE_READ
;
415 PortInfo2
.Reserved
= 0;
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
;
423 // Copy the monitor name.
424 PortInfo2
.pMonitorName
= (PWSTR
)pPortString
;
425 CopyMemory(pPortString
, pwszLocalMonitor
, cbLocalMonitor
);
426 pPortString
+= cbLocalMonitor
;
428 // Copy the description.
429 PortInfo2
.pDescription
= (PWSTR
)pPortString
;
430 CopyMemory(pPortString
, pwszLocalPort
, cbLocalPort
);
431 pPortString
+= cbLocalPort
;
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
);
438 *pcReturned
= dwPortCount
;
439 dwErrorCode
= ERROR_SUCCESS
;
446 * @name _SetTransmissionRetryTimeout
448 * Checks if the given port is a physical one and sets the transmission retry timeout in this case using the value from registry.
451 * The port to operate on.
454 * TRUE if the given port is a physical one, FALSE otherwise.
457 _SetTransmissionRetryTimeout(PLOCALMON_PORT pPort
)
459 COMMTIMEOUTS CommTimeouts
;
461 // Get the timeout from the port.
462 if (!GetCommTimeouts(pPort
->hFile
, &CommTimeouts
))
465 // Set the timeout using the value from registry.
466 CommTimeouts
.WriteTotalTimeoutConstant
= GetLPTTransmissionRetryTimeout() * 1000;
467 SetCommTimeouts(pPort
->hFile
, &CommTimeouts
);
473 LocalmonClosePort(HANDLE hPort
)
475 PLOCALMON_PORT pPort
= (PLOCALMON_PORT
)hPort
;
477 TRACE("LocalmonClosePort(%p)\n", hPort
);
482 SetLastError(ERROR_INVALID_PARAMETER
);
486 // Close the file handle, free memory for pwszMapping and delete any NONSPOOLED port.
487 _ClosePortHandles(pPort
);
489 // Close any open printer handle.
492 ClosePrinter(pPort
->hPrinter
);
493 pPort
->hPrinter
= NULL
;
496 // Free virtual FILE ports which were created in LocalmonOpenPort.
497 if (pPort
->PortType
== PortType_FILE
)
499 EnterCriticalSection(&pPort
->pLocalmon
->Section
);
500 RemoveEntryList(&pPort
->Entry
);
501 DllFreeSplMem(pPort
);
502 LeaveCriticalSection(&pPort
->pLocalmon
->Section
);
505 SetLastError(ERROR_SUCCESS
);
510 LocalmonEndDocPort(HANDLE hPort
)
512 PLOCALMON_PORT pPort
= (PLOCALMON_PORT
)hPort
;
514 TRACE("LocalmonEndDocPort(%p)\n", hPort
);
519 SetLastError(ERROR_INVALID_PARAMETER
);
523 // Ending a document requires starting it first :-P
524 if (pPort
->bStartedDoc
)
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
);
531 // Report our progress.
532 SetJobW(pPort
->hPrinter
, pPort
->dwJobID
, 0, NULL
, JOB_CONTROL_SENT_TO_PRINTER
);
534 // We're done with the printer.
535 ClosePrinter(pPort
->hPrinter
);
536 pPort
->hPrinter
= NULL
;
539 SetLastError(ERROR_SUCCESS
);
544 LocalmonEnumPorts(HANDLE hMonitor
, PWSTR pName
, DWORD Level
, PBYTE pPorts
, DWORD cbBuf
, PDWORD pcbNeeded
, PDWORD pcReturned
)
547 PLOCALMON_HANDLE pLocalmon
= (PLOCALMON_HANDLE
)hMonitor
;
549 TRACE("LocalmonEnumPorts(%p, %S, %lu, %p, %lu, %p, %p)\n", hMonitor
, pName
, Level
, pPorts
, cbBuf
, pcbNeeded
, pcReturned
);
552 if (!pLocalmon
|| (cbBuf
&& !pPorts
) || !pcbNeeded
|| !pcReturned
)
554 dwErrorCode
= ERROR_INVALID_PARAMETER
;
560 dwErrorCode
= ERROR_INVALID_LEVEL
;
568 EnterCriticalSection(&pLocalmon
->Section
);
570 // The function behaves differently for each level.
572 dwErrorCode
= _LocalmonEnumPortsLevel1(pLocalmon
, pPorts
, cbBuf
, pcbNeeded
, pcReturned
);
574 dwErrorCode
= _LocalmonEnumPortsLevel2(pLocalmon
, pPorts
, cbBuf
, pcbNeeded
, pcReturned
);
576 LeaveCriticalSection(&pLocalmon
->Section
);
579 SetLastError(dwErrorCode
);
580 return (dwErrorCode
== ERROR_SUCCESS
);
584 * @name LocalmonGetPrinterDataFromPort
586 * Performs a DeviceIoControl call for the given port.
589 * The port to operate on.
592 * The dwIoControlCode passed to DeviceIoControl. Must not be zero!
595 * This parameter is ignored.
598 * The lpInBuffer passed to DeviceIoControl.
601 * The nInBufferSize passed to DeviceIoControl.
604 * The lpOutBuffer passed to DeviceIoControl.
607 * The nOutBufferSize passed to DeviceIoControl.
609 * @param lpcbReturned
610 * The lpBytesReturned passed to DeviceIoControl. Must not be zero!
613 * TRUE if the DeviceIoControl call was successful, FALSE otherwise.
614 * A more specific error code can be obtained through GetLastError.
617 LocalmonGetPrinterDataFromPort(HANDLE hPort
, DWORD ControlID
, PWSTR pValueName
, PWSTR lpInBuffer
, DWORD cbInBuffer
, PWSTR lpOutBuffer
, DWORD cbOutBuffer
, PDWORD lpcbReturned
)
619 BOOL bOpenedPort
= FALSE
;
621 PLOCALMON_PORT pPort
= (PLOCALMON_PORT
)hPort
;
623 TRACE("LocalmonGetPrinterDataFromPort(%p, %lu, %p, %p, %lu, %p, %lu, %p)\n", hPort
, ControlID
, pValueName
, lpInBuffer
, cbInBuffer
, lpOutBuffer
, cbOutBuffer
, lpcbReturned
);
626 if (!pPort
|| !ControlID
|| !lpcbReturned
)
628 dwErrorCode
= ERROR_INVALID_PARAMETER
;
632 // If this is a serial port, a temporary file handle may be opened.
633 if (pPort
->PortType
== PortType_PhysicalCOM
)
635 if (_CreateNonspooledPort(pPort
))
639 else if (GetLastError() != ERROR_SUCCESS
)
641 dwErrorCode
= GetLastError();
645 else if (pPort
->hFile
== INVALID_HANDLE_VALUE
)
647 // All other port types need to be opened already.
648 dwErrorCode
= ERROR_INVALID_PARAMETER
;
652 // Pass the parameters to DeviceIoControl.
653 if (!DeviceIoControl(pPort
->hFile
, ControlID
, lpInBuffer
, cbInBuffer
, lpOutBuffer
, cbOutBuffer
, lpcbReturned
, NULL
))
655 dwErrorCode
= GetLastError();
656 ERR("DeviceIoControl failed with error %lu!\n", dwErrorCode
);
660 dwErrorCode
= ERROR_SUCCESS
;
664 _ClosePortHandles(pPort
);
666 SetLastError(dwErrorCode
);
667 return (dwErrorCode
== ERROR_SUCCESS
);
671 LocalmonOpenPort(HANDLE hMonitor
, PWSTR pName
, PHANDLE pHandle
)
674 PLOCALMON_HANDLE pLocalmon
= (PLOCALMON_HANDLE
)hMonitor
;
675 PLOCALMON_PORT pPort
;
677 TRACE("LocalmonOpenPort(%p, %S, %p)\n", hMonitor
, pName
, pHandle
);
680 if (!pLocalmon
|| !pName
|| !pHandle
)
682 dwErrorCode
= ERROR_INVALID_PARAMETER
;
686 // Check if this is a FILE: port.
687 if (_wcsicmp(pName
, L
"FILE:") == 0)
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
;
695 // Add it to the list of file ports.
696 EnterCriticalSection(&pLocalmon
->Section
);
697 InsertTailList(&pLocalmon
->FilePorts
, &pPort
->Entry
);
701 EnterCriticalSection(&pLocalmon
->Section
);
703 // Check if the port name is valid.
704 pPort
= _FindPort(pLocalmon
, pName
);
707 LeaveCriticalSection(&pLocalmon
->Section
);
708 dwErrorCode
= ERROR_UNKNOWN_PORT
;
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)
717 // Treat all ports as other LPT ports until we can definitely say that it's a physical one.
718 pPort
->PortType
= PortType_OtherLPT
;
720 // Try to create a NONSPOOLED port and open it.
721 if (_CreateNonspooledPort(pPort
))
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
))
727 // This is definitely a physical LPT port!
728 pPort
->PortType
= PortType_PhysicalLPT
;
732 // This is no physical port, so don't keep its handle open.
733 _ClosePortHandles(pPort
);
736 else if (GetLastError() != ERROR_SUCCESS
)
738 LeaveCriticalSection(&pLocalmon
->Section
);
739 dwErrorCode
= GetLastError();
745 // This can only be a COM port.
746 pPort
->PortType
= PortType_PhysicalCOM
;
750 LeaveCriticalSection(&pLocalmon
->Section
);
752 // Return our fetched LOCALMON_PORT structure in the handle.
753 *pHandle
= (PHANDLE
)pPort
;
754 dwErrorCode
= ERROR_SUCCESS
;
757 SetLastError(dwErrorCode
);
758 return (dwErrorCode
== ERROR_SUCCESS
);
762 * @name LocalmonSetPortTimeOuts
764 * Performs a SetCommTimeouts call for the given port.
767 * The port to operate on.
770 * Pointer to a COMMTIMEOUTS structure that is passed to SetCommTimeouts.
773 * Reserved parameter, must be 0.
776 * TRUE if the SetCommTimeouts call was successful, FALSE otherwise.
777 * A more specific error code can be obtained through GetLastError.
780 LocalmonSetPortTimeOuts(HANDLE hPort
, LPCOMMTIMEOUTS lpCTO
, DWORD Reserved
)
782 BOOL bOpenedPort
= FALSE
;
784 PLOCALMON_PORT pPort
= (PLOCALMON_PORT
)hPort
;
786 TRACE("LocalmonSetPortTimeOuts(%p, %p, %lu)\n", hPort
, lpCTO
, Reserved
);
789 if (!pPort
|| !lpCTO
)
791 dwErrorCode
= ERROR_INVALID_PARAMETER
;
795 // If this is a serial port, a temporary file handle may be opened.
796 if (pPort
->PortType
== PortType_PhysicalCOM
)
798 if (_CreateNonspooledPort(pPort
))
802 else if (GetLastError() != ERROR_SUCCESS
)
804 dwErrorCode
= GetLastError();
808 else if (pPort
->hFile
== INVALID_HANDLE_VALUE
)
810 // All other port types need to be opened already.
811 dwErrorCode
= ERROR_INVALID_PARAMETER
;
815 // Finally pass the parameters to SetCommTimeouts.
816 if (!SetCommTimeouts(pPort
->hFile
, lpCTO
))
818 dwErrorCode
= GetLastError();
819 ERR("SetCommTimeouts failed with error %lu!\n", dwErrorCode
);
823 dwErrorCode
= ERROR_SUCCESS
;
827 _ClosePortHandles(pPort
);
829 SetLastError(dwErrorCode
);
830 return (dwErrorCode
== ERROR_SUCCESS
);
834 LocalmonReadPort(HANDLE hPort
, PBYTE pBuffer
, DWORD cbBuffer
, PDWORD pcbRead
)
836 BOOL bOpenedPort
= FALSE
;
838 PLOCALMON_PORT pPort
= (PLOCALMON_PORT
)hPort
;
840 TRACE("LocalmonReadPort(%p, %p, %lu, %p)\n", hPort
, pBuffer
, cbBuffer
, pcbRead
);
843 if (!pPort
|| (cbBuffer
&& !pBuffer
) || !pcbRead
)
845 dwErrorCode
= ERROR_INVALID_PARAMETER
;
849 // Reading is only supported for physical ports.
850 if (pPort
->PortType
!= PortType_PhysicalCOM
&& pPort
->PortType
!= PortType_PhysicalLPT
)
852 dwErrorCode
= ERROR_INVALID_HANDLE
;
856 // If this is a serial port, a temporary file handle may be opened.
857 if (pPort
->PortType
== PortType_PhysicalCOM
)
859 if (_CreateNonspooledPort(pPort
))
863 else if (GetLastError() != ERROR_SUCCESS
)
865 dwErrorCode
= GetLastError();
870 // Pass the parameters to ReadFile.
871 if (!ReadFile(pPort
->hFile
, pBuffer
, cbBuffer
, pcbRead
, NULL
))
873 dwErrorCode
= GetLastError();
874 ERR("ReadFile failed with error %lu!\n", dwErrorCode
);
880 _ClosePortHandles(pPort
);
882 SetLastError(dwErrorCode
);
883 return (dwErrorCode
== ERROR_SUCCESS
);
887 LocalmonStartDocPort(HANDLE hPort
, PWSTR pPrinterName
, DWORD JobId
, DWORD Level
, PBYTE pDocInfo
)
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
;
893 TRACE("LocalmonStartDocPort(%p, %S, %lu, %lu, %p)\n", hPort
, pPrinterName
, JobId
, Level
, pDocInfo
);
896 if (!pPort
|| !pPrinterName
|| (pPort
->PortType
== PortType_FILE
&& (!pDocInfo1
|| !pDocInfo1
->pOutputFile
|| !*pDocInfo1
->pOutputFile
)))
898 dwErrorCode
= ERROR_INVALID_PARAMETER
;
904 dwErrorCode
= ERROR_INVALID_LEVEL
;
908 // Calling StartDocPort multiple times isn't considered a failure, but we don't need to do anything then.
909 if (pPort
->bStartedDoc
)
911 dwErrorCode
= ERROR_SUCCESS
;
915 // Open a handle to the given printer for later reporting our progress using SetJobW.
916 if (!OpenPrinterW(pPrinterName
, &pPort
->hPrinter
, NULL
))
918 dwErrorCode
= GetLastError();
919 ERR("OpenPrinterW failed with error %lu!\n", dwErrorCode
);
923 // We need our Job ID for SetJobW as well.
924 pPort
->dwJobID
= JobId
;
926 // Check the port type.
927 if (pPort
->PortType
== PortType_PhysicalLPT
)
929 // Update the NONSPOOLED mapping if the port mapping has changed since our OpenPort call.
930 if (!_CreateNonspooledPort(pPort
) && GetLastError() != ERROR_SUCCESS
)
932 dwErrorCode
= GetLastError();
936 // Update the transmission retry timeout as well.
937 _SetTransmissionRetryTimeout(pPort
);
939 else if(pPort
->PortType
== PortType_FILE
)
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
)
945 dwErrorCode
= GetLastError();
951 // This is a COM port or a non-physical LPT port. We open NONSPOOLED ports for these per job.
952 if (!_CreateNonspooledPort(pPort
))
954 if (GetLastError() == ERROR_SUCCESS
)
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
)
961 dwErrorCode
= GetLastError();
967 dwErrorCode
= GetLastError();
973 // We were successful!
974 dwErrorCode
= ERROR_SUCCESS
;
975 pPort
->bStartedDoc
= TRUE
;
978 SetLastError(dwErrorCode
);
979 return (dwErrorCode
== ERROR_SUCCESS
);
983 LocalmonWritePort(HANDLE hPort
, PBYTE pBuffer
, DWORD cbBuf
, PDWORD pcbWritten
)
985 BOOL bOpenedPort
= FALSE
;
987 PLOCALMON_PORT pPort
= (PLOCALMON_PORT
)hPort
;
989 TRACE("LocalmonWritePort(%p, %p, %lu, %p)\n", hPort
, pBuffer
, cbBuf
, pcbWritten
);
992 if (!pPort
|| (cbBuf
&& !pBuffer
) || !pcbWritten
)
994 dwErrorCode
= ERROR_INVALID_PARAMETER
;
998 // If this is a serial port, a temporary file handle may be opened.
999 if (pPort
->PortType
== PortType_PhysicalCOM
)
1001 if (_CreateNonspooledPort(pPort
))
1005 else if (GetLastError() != ERROR_SUCCESS
)
1007 dwErrorCode
= GetLastError();
1011 else if (pPort
->hFile
== INVALID_HANDLE_VALUE
)
1013 // All other port types need to be opened already.
1014 dwErrorCode
= ERROR_INVALID_PARAMETER
;
1018 // Pass the parameters to WriteFile.
1019 if (!WriteFile(pPort
->hFile
, pBuffer
, cbBuf
, pcbWritten
, NULL
))
1021 dwErrorCode
= GetLastError();
1022 ERR("WriteFile failed with error %lu!\n", dwErrorCode
);
1026 // If something was written down, we consider that a success, otherwise it's a timeout.
1028 dwErrorCode
= ERROR_SUCCESS
;
1030 dwErrorCode
= ERROR_TIMEOUT
;
1034 _ClosePortHandles(pPort
);
1036 SetLastError(dwErrorCode
);
1037 return (dwErrorCode
== ERROR_SUCCESS
);