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