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 DllFreeSplMem(pwszPortNameWithoutColon); 143 } 144 } 145 146 /** 147 * @name _CreateNonspooledPort 148 * 149 * Queries the system-wide device definition of the given port. 150 * If such a definition exists, it's a legacy port remapped to a named pipe by the spooler. 151 * 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). 152 * 153 * @param pPort 154 * Pointer to the LOCALMON_PORT structure of the desired port. 155 * 156 * @return 157 * TRUE if a NONSPOOLED port was successfully created, FALSE otherwise. 158 * A more specific error code can be obtained through GetLastError. 159 * In particular, if the return value is FALSE and GetLastError returns ERROR_SUCCESS, no NONSPOOLED port is needed for this port. 160 */ 161 static BOOL 162 _CreateNonspooledPort(PLOCALMON_PORT pPort) 163 { 164 const WCHAR wszLocalSlashes[] = L"\\\\.\\"; 165 const DWORD cchLocalSlashes = _countof(wszLocalSlashes) - 1; 166 167 const WCHAR wszSpoolerNamedPipe[] = L"\\Device\\NamedPipe\\Spooler\\"; 168 const DWORD cchSpoolerNamedPipe = _countof(wszSpoolerNamedPipe) - 1; 169 170 BOOL bReturnValue = FALSE; 171 DWORD cchPortNameWithoutColon; 172 DWORD dwErrorCode; 173 HANDLE hToken = NULL; 174 PWSTR p; 175 PWSTR pwszDeviceMappings = NULL; 176 PWSTR pwszNonspooledFileName = NULL; 177 PWSTR pwszNonspooledPortName = NULL; 178 PWSTR pwszPipeName = NULL; 179 PWSTR pwszPortNameWithoutColon = NULL; 180 181 // We need the port name without the trailing colon. 182 dwErrorCode = GetPortNameWithoutColon(pPort->pwszPortName, &pwszPortNameWithoutColon); 183 if (dwErrorCode == ERROR_INVALID_PARAMETER) 184 { 185 // This port has no trailing colon, so we also need no NONSPOOLED mapping for it. 186 dwErrorCode = ERROR_SUCCESS; 187 goto Cleanup; 188 } 189 else if (dwErrorCode != ERROR_SUCCESS) 190 { 191 // Another unexpected failure. 192 goto Cleanup; 193 } 194 195 cchPortNameWithoutColon = wcslen(pwszPortNameWithoutColon); 196 197 // The spooler has usually remapped the legacy port to a named pipe of the format in wszSpoolerNamedPipe. 198 // Construct the device name of this pipe. 199 pwszPipeName = DllAllocSplMem((cchSpoolerNamedPipe + cchPortNameWithoutColon + 1) * sizeof(WCHAR)); 200 if (!pwszPipeName) 201 { 202 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY; 203 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError()); 204 goto Cleanup; 205 } 206 207 CopyMemory(pwszPipeName, wszSpoolerNamedPipe, cchSpoolerNamedPipe * sizeof(WCHAR)); 208 CopyMemory(&pwszPipeName[cchSpoolerNamedPipe], pwszPortNameWithoutColon, (cchPortNameWithoutColon + 1) * sizeof(WCHAR)); 209 210 // 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. 211 // Examples show that a value of MAX_PATH * sizeof(WCHAR) is usually taken here, so we have no other option either. 212 pwszDeviceMappings = DllAllocSplMem(MAX_PATH * sizeof(WCHAR)); 213 if (!pwszDeviceMappings) 214 { 215 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY; 216 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError()); 217 goto Cleanup; 218 } 219 220 // Switch to the SYSTEM context, because we're only interested in creating NONSPOOLED ports for system-wide ports. 221 // User-local ports (like _some_ redirected networked ones) aren't remapped by the spooler and can be opened directly. 222 hToken = RevertToPrinterSelf(); 223 if (!hToken) 224 { 225 dwErrorCode = GetLastError(); 226 ERR("RevertToPrinterSelf failed with error %lu!\n", dwErrorCode); 227 goto Cleanup; 228 } 229 230 // 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. 231 if (!QueryDosDeviceW(pwszPortNameWithoutColon, pwszDeviceMappings, MAX_PATH)) 232 { 233 // No system-wide port exists, so we also need no NONSPOOLED mapping. 234 dwErrorCode = ERROR_SUCCESS; 235 goto Cleanup; 236 } 237 238 // Check if this port has already been opened by _CreateNonspooledPort previously. 239 if (pPort->pwszMapping) 240 { 241 // In this case, we just need to do something if the mapping has changed. 242 // Therefore, check if the stored mapping equals the current mapping. 243 if (wcscmp(pPort->pwszMapping, pwszDeviceMappings) == 0) 244 { 245 // We don't need to do anything in this case. 246 dwErrorCode = ERROR_SUCCESS; 247 goto Cleanup; 248 } 249 else 250 { 251 // Close the open file handle and free the memory for pwszMapping before remapping. 252 CloseHandle(pPort->hFile); 253 pPort->hFile = INVALID_HANDLE_VALUE; 254 255 DllFreeSplStr(pPort->pwszMapping); 256 pPort->pwszMapping = NULL; 257 } 258 } 259 260 // The port is usually mapped to the named pipe and this is how we received our data for printing. 261 // What we now need for accessing the actual port is the most recent mapping different from the named pipe. 262 p = pwszDeviceMappings; 263 264 for (;;) 265 { 266 if (!*p) 267 { 268 // We reached the end of the list without finding a mapping. 269 ERR("Can't find a suitable mapping for the port \"%S\"!", pPort->pwszPortName); 270 goto Cleanup; 271 } 272 273 if (_wcsicmp(p, pwszPipeName) != 0) 274 break; 275 276 // Advance to the next mapping in the list. 277 p += wcslen(p) + 1; 278 } 279 280 // We now want to create a DOS device "NONSPOOLED_<PortName>" to this mapping, so that we're able to open it through CreateFileW. 281 dwErrorCode = _GetNonspooledPortName(pwszPortNameWithoutColon, &pwszNonspooledPortName); 282 if (dwErrorCode != ERROR_SUCCESS) 283 goto Cleanup; 284 285 // Delete a possibly existing NONSPOOLED device before creating the new one, so we don't stack up device definitions. 286 DefineDosDeviceW(DDD_REMOVE_DEFINITION, pwszNonspooledPortName, NULL); 287 288 if (!DefineDosDeviceW(DDD_RAW_TARGET_PATH, pwszNonspooledPortName, p)) 289 { 290 dwErrorCode = GetLastError(); 291 ERR("DefineDosDeviceW failed with error %lu!\n", dwErrorCode); 292 goto Cleanup; 293 } 294 295 // This is all we needed to do in SYSTEM context. 296 ImpersonatePrinterClient(hToken); 297 hToken = NULL; 298 299 // Construct the file name to our created device for CreateFileW. 300 pwszNonspooledFileName = DllAllocSplMem((cchLocalSlashes + cchNonspooledPrefix + cchPortNameWithoutColon + 1) * sizeof(WCHAR)); 301 if (!pwszNonspooledFileName) 302 { 303 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY; 304 ERR("DllAllocSplMem failed with error %lu!\n", GetLastError()); 305 goto Cleanup; 306 } 307 308 CopyMemory(pwszNonspooledFileName, wszLocalSlashes, cchLocalSlashes * sizeof(WCHAR)); 309 CopyMemory(&pwszNonspooledFileName[cchLocalSlashes], wszNonspooledPrefix, cchNonspooledPrefix * sizeof(WCHAR)); 310 CopyMemory(&pwszNonspooledFileName[cchLocalSlashes + cchNonspooledPrefix], pwszPortNameWithoutColon, (cchPortNameWithoutColon + 1) * sizeof(WCHAR)); 311 312 // Finally open it for reading and writing. 313 pPort->hFile = CreateFileW(pwszNonspooledFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL); 314 if (pPort->hFile == INVALID_HANDLE_VALUE) 315 { 316 dwErrorCode = GetLastError(); 317 ERR("CreateFileW failed with error %lu!\n", dwErrorCode); 318 goto Cleanup; 319 } 320 321 // Store the current mapping of the port, so that we can check if it has changed. 322 pPort->pwszMapping = AllocSplStr(pwszDeviceMappings); 323 if (!pPort->pwszMapping) 324 { 325 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY; 326 goto Cleanup; 327 } 328 329 bReturnValue = TRUE; 330 dwErrorCode = ERROR_SUCCESS; 331 332 Cleanup: 333 if (hToken) 334 ImpersonatePrinterClient(hToken); 335 336 if (pwszDeviceMappings) 337 DllFreeSplMem(pwszDeviceMappings); 338 339 if (pwszNonspooledFileName) 340 DllFreeSplMem(pwszNonspooledFileName); 341 342 if (pwszNonspooledPortName) 343 DllFreeSplMem(pwszNonspooledPortName); 344 345 if (pwszPipeName) 346 DllFreeSplMem(pwszPipeName); 347 348 if (pwszPortNameWithoutColon) 349 DllFreeSplMem(pwszPortNameWithoutColon); 350 351 SetLastError(dwErrorCode); 352 return bReturnValue; 353 } 354 355 static PLOCALMON_PORT 356 _FindPort(PLOCALMON_HANDLE pLocalmon, PCWSTR pwszPortName) 357 { 358 PLIST_ENTRY pEntry; 359 PLOCALMON_PORT pPort; 360 361 for (pEntry = pLocalmon->RegistryPorts.Flink; pEntry != &pLocalmon->RegistryPorts; pEntry = pEntry->Flink) 362 { 363 pPort = CONTAINING_RECORD(pEntry, LOCALMON_PORT, Entry); 364 365 if (wcscmp(pPort->pwszPortName, pwszPortName) == 0) 366 return pPort; 367 } 368 369 return NULL; 370 } 371 372 static void 373 _LocalmonGetPortLevel1(PLOCALMON_PORT pPort, PPORT_INFO_1W* ppPortInfo, PBYTE* ppPortInfoEnd, PDWORD pcbNeeded) 374 { 375 DWORD cbPortName; 376 PCWSTR pwszStrings[1]; 377 378 // Calculate the string lengths. 379 if (!ppPortInfo) 380 { 381 cbPortName = (wcslen(pPort->pwszPortName) + 1) * sizeof(WCHAR); 382 383 *pcbNeeded += sizeof(PORT_INFO_1W) + cbPortName; 384 return; 385 } 386 387 // Set the pName field. 388 pwszStrings[0] = pPort->pwszPortName; 389 390 // Copy the structure and advance to the next one in the output buffer. 391 *ppPortInfoEnd = PackStrings(pwszStrings, (PBYTE)(*ppPortInfo), dwPortInfo1Offsets, *ppPortInfoEnd); 392 (*ppPortInfo)++; 393 } 394 395 static void 396 _LocalmonGetPortLevel2(PLOCALMON_PORT pPort, PPORT_INFO_2W* ppPortInfo, PBYTE* ppPortInfoEnd, PDWORD pcbNeeded) 397 { 398 DWORD cbPortName; 399 PCWSTR pwszStrings[3]; 400 401 // Calculate the string lengths. 402 if (!ppPortInfo) 403 { 404 cbPortName = (wcslen(pPort->pwszPortName) + 1) * sizeof(WCHAR); 405 406 *pcbNeeded += sizeof(PORT_INFO_2W) + cbPortName + cbLocalMonitor + cbLocalPort; 407 return; 408 } 409 410 // All local ports are writable and readable. 411 (*ppPortInfo)->fPortType = PORT_TYPE_WRITE | PORT_TYPE_READ; 412 (*ppPortInfo)->Reserved = 0; 413 414 // Set the pPortName field. 415 pwszStrings[0] = pPort->pwszPortName; 416 417 // Set the pMonitorName field. 418 pwszStrings[1] = (PWSTR)pwszLocalMonitor; 419 420 // Set the pDescription field. 421 pwszStrings[2] = (PWSTR)pwszLocalPort; 422 423 // Copy the structure and advance to the next one in the output buffer. 424 *ppPortInfoEnd = PackStrings(pwszStrings, (PBYTE)(*ppPortInfo), dwPortInfo2Offsets, *ppPortInfoEnd); 425 (*ppPortInfo)++; 426 } 427 428 /** 429 * @name _SetTransmissionRetryTimeout 430 * 431 * Checks if the given port is a physical one and sets the transmission retry timeout in this case using the value from registry. 432 * 433 * @param pPort 434 * The port to operate on. 435 * 436 * @return 437 * TRUE if the given port is a physical one, FALSE otherwise. 438 */ 439 static BOOL 440 _SetTransmissionRetryTimeout(PLOCALMON_PORT pPort) 441 { 442 COMMTIMEOUTS CommTimeouts; 443 444 // Get the timeout from the port. 445 if (!GetCommTimeouts(pPort->hFile, &CommTimeouts)) 446 return FALSE; 447 448 // Set the timeout using the value from registry. 449 CommTimeouts.WriteTotalTimeoutConstant = GetLPTTransmissionRetryTimeout() * 1000; 450 SetCommTimeouts(pPort->hFile, &CommTimeouts); 451 452 return TRUE; 453 } 454 455 BOOL WINAPI 456 LocalmonClosePort(HANDLE hPort) 457 { 458 PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort; 459 460 TRACE("LocalmonClosePort(%p)\n", hPort); 461 462 // Sanity checks 463 if (!pPort) 464 { 465 SetLastError(ERROR_INVALID_PARAMETER); 466 return FALSE; 467 } 468 469 // Close the file handle, free memory for pwszMapping and delete any NONSPOOLED port. 470 _ClosePortHandles(pPort); 471 472 // Close any open printer handle. 473 if (pPort->hPrinter) 474 { 475 ClosePrinter(pPort->hPrinter); 476 pPort->hPrinter = NULL; 477 } 478 479 // Free virtual FILE: ports which were created in LocalmonOpenPort. 480 if (pPort->PortType == PortType_FILE) 481 { 482 EnterCriticalSection(&pPort->pLocalmon->Section); 483 RemoveEntryList(&pPort->Entry); 484 LeaveCriticalSection(&pPort->pLocalmon->Section); 485 DllFreeSplMem(pPort); 486 } 487 488 SetLastError(ERROR_SUCCESS); 489 return TRUE; 490 } 491 492 BOOL WINAPI 493 LocalmonEndDocPort(HANDLE hPort) 494 { 495 PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort; 496 497 TRACE("LocalmonEndDocPort(%p)\n", hPort); 498 499 // Sanity checks 500 if (!pPort) 501 { 502 SetLastError(ERROR_INVALID_PARAMETER); 503 return FALSE; 504 } 505 506 // Ending a document requires starting it first :-P 507 if (pPort->bStartedDoc) 508 { 509 // Close all ports opened in StartDocPort. 510 // That is, all but physical LPT ports (opened in OpenPort). 511 if (pPort->PortType != PortType_PhysicalLPT) 512 _ClosePortHandles(pPort); 513 514 // Report our progress. 515 SetJobW(pPort->hPrinter, pPort->dwJobID, 0, NULL, JOB_CONTROL_SENT_TO_PRINTER); 516 517 // We're done with the printer. 518 ClosePrinter(pPort->hPrinter); 519 pPort->hPrinter = NULL; 520 521 // A new document can now be started again. 522 pPort->bStartedDoc = FALSE; 523 } 524 525 SetLastError(ERROR_SUCCESS); 526 return TRUE; 527 } 528 529 BOOL WINAPI 530 LocalmonEnumPorts(HANDLE hMonitor, PWSTR pName, DWORD Level, PBYTE pPorts, DWORD cbBuf, PDWORD pcbNeeded, PDWORD pcReturned) 531 { 532 DWORD dwErrorCode; 533 PBYTE pPortInfoEnd; 534 PLIST_ENTRY pEntry; 535 PLOCALMON_HANDLE pLocalmon = (PLOCALMON_HANDLE)hMonitor; 536 PLOCALMON_PORT pPort; 537 538 TRACE("LocalmonEnumPorts(%p, %S, %lu, %p, %lu, %p, %p)\n", hMonitor, pName, Level, pPorts, cbBuf, pcbNeeded, pcReturned); 539 540 // Windows Server 2003's Local Port Monitor does absolutely no sanity checks here, not even for the Level parameter. 541 // As we implement a more modern MONITOR2-based Port Monitor, check at least our hMonitor. 542 if (!pLocalmon) 543 { 544 dwErrorCode = ERROR_INVALID_HANDLE; 545 goto Cleanup; 546 } 547 548 // Begin counting. 549 *pcbNeeded = 0; 550 *pcReturned = 0; 551 552 EnterCriticalSection(&pLocalmon->Section); 553 554 // Count the required buffer size and the number of ports. 555 for (pEntry = pLocalmon->RegistryPorts.Flink; pEntry != &pLocalmon->RegistryPorts; pEntry = pEntry->Flink) 556 { 557 pPort = CONTAINING_RECORD(pEntry, LOCALMON_PORT, Entry); 558 559 if (Level == 1) 560 _LocalmonGetPortLevel1(pPort, NULL, NULL, pcbNeeded); 561 else if (Level == 2) 562 _LocalmonGetPortLevel2(pPort, NULL, NULL, pcbNeeded); 563 } 564 565 // Check if the supplied buffer is large enough. 566 if (cbBuf < *pcbNeeded) 567 { 568 LeaveCriticalSection(&pLocalmon->Section); 569 dwErrorCode = ERROR_INSUFFICIENT_BUFFER; 570 goto Cleanup; 571 } 572 573 // Copy over the Port information. 574 pPortInfoEnd = &pPorts[*pcbNeeded]; 575 576 for (pEntry = pLocalmon->RegistryPorts.Flink; pEntry != &pLocalmon->RegistryPorts; pEntry = pEntry->Flink) 577 { 578 pPort = CONTAINING_RECORD(pEntry, LOCALMON_PORT, Entry); 579 580 if (Level == 1) 581 _LocalmonGetPortLevel1(pPort, (PPORT_INFO_1W*)&pPorts, &pPortInfoEnd, NULL); 582 else if (Level == 2) 583 _LocalmonGetPortLevel2(pPort, (PPORT_INFO_2W*)&pPorts, &pPortInfoEnd, NULL); 584 585 (*pcReturned)++; 586 } 587 588 LeaveCriticalSection(&pLocalmon->Section); 589 dwErrorCode = ERROR_SUCCESS; 590 591 Cleanup: 592 SetLastError(dwErrorCode); 593 return (dwErrorCode == ERROR_SUCCESS); 594 } 595 596 /* 597 * @name LocalmonGetPrinterDataFromPort 598 * 599 * Performs a DeviceIoControl call for the given port. 600 * 601 * @param hPort 602 * The port to operate on. 603 * 604 * @param ControlID 605 * The dwIoControlCode passed to DeviceIoControl. Must not be zero! 606 * 607 * @param pValueName 608 * This parameter is ignored. 609 * 610 * @param lpInBuffer 611 * The lpInBuffer passed to DeviceIoControl. 612 * 613 * @param cbInBuffer 614 * The nInBufferSize passed to DeviceIoControl. 615 * 616 * @param lpOutBuffer 617 * The lpOutBuffer passed to DeviceIoControl. 618 * 619 * @param cbOutBuffer 620 * The nOutBufferSize passed to DeviceIoControl. 621 * 622 * @param lpcbReturned 623 * The lpBytesReturned passed to DeviceIoControl. Must not be zero! 624 * 625 * @return 626 * TRUE if the DeviceIoControl call was successful, FALSE otherwise. 627 * A more specific error code can be obtained through GetLastError. 628 */ 629 BOOL WINAPI 630 LocalmonGetPrinterDataFromPort(HANDLE hPort, DWORD ControlID, PWSTR pValueName, PWSTR lpInBuffer, DWORD cbInBuffer, PWSTR lpOutBuffer, DWORD cbOutBuffer, PDWORD lpcbReturned) 631 { 632 BOOL bOpenedPort = FALSE; 633 DWORD dwErrorCode; 634 PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort; 635 636 TRACE("LocalmonGetPrinterDataFromPort(%p, %lu, %p, %p, %lu, %p, %lu, %p)\n", hPort, ControlID, pValueName, lpInBuffer, cbInBuffer, lpOutBuffer, cbOutBuffer, lpcbReturned); 637 638 // Sanity checks 639 if (!pPort || !ControlID || !lpcbReturned) 640 { 641 dwErrorCode = ERROR_INVALID_PARAMETER; 642 goto Cleanup; 643 } 644 645 // If this is a serial port, a temporary file handle may be opened. 646 if (pPort->PortType == PortType_PhysicalCOM) 647 { 648 if (_CreateNonspooledPort(pPort)) 649 { 650 bOpenedPort = TRUE; 651 } 652 else if (GetLastError() != ERROR_SUCCESS) 653 { 654 dwErrorCode = GetLastError(); 655 goto Cleanup; 656 } 657 } 658 else if (pPort->hFile == INVALID_HANDLE_VALUE) 659 { 660 // All other port types need to be opened already. 661 dwErrorCode = ERROR_INVALID_PARAMETER; 662 goto Cleanup; 663 } 664 665 // Pass the parameters to DeviceIoControl. 666 if (!DeviceIoControl(pPort->hFile, ControlID, lpInBuffer, cbInBuffer, lpOutBuffer, cbOutBuffer, lpcbReturned, NULL)) 667 { 668 dwErrorCode = GetLastError(); 669 ERR("DeviceIoControl failed with error %lu!\n", dwErrorCode); 670 goto Cleanup; 671 } 672 673 dwErrorCode = ERROR_SUCCESS; 674 675 Cleanup: 676 if (bOpenedPort) 677 _ClosePortHandles(pPort); 678 679 SetLastError(dwErrorCode); 680 return (dwErrorCode == ERROR_SUCCESS); 681 } 682 683 BOOL WINAPI 684 LocalmonOpenPort(HANDLE hMonitor, PWSTR pName, PHANDLE pHandle) 685 { 686 DWORD dwErrorCode; 687 PLOCALMON_HANDLE pLocalmon = (PLOCALMON_HANDLE)hMonitor; 688 PLOCALMON_PORT pPort; 689 690 TRACE("LocalmonOpenPort(%p, %S, %p)\n", hMonitor, pName, pHandle); 691 692 // Sanity checks 693 if (!pLocalmon || !pName || !pHandle) 694 { 695 dwErrorCode = ERROR_INVALID_PARAMETER; 696 goto Cleanup; 697 } 698 699 EnterCriticalSection(&pLocalmon->Section); 700 701 // Check if this is a FILE: port. 702 if (_wcsicmp(pName, L"FILE:") == 0) 703 { 704 // For FILE:, we create a virtual port for each request. 705 pPort = DllAllocSplMem(sizeof(LOCALMON_PORT)); 706 pPort->pLocalmon = pLocalmon; 707 pPort->PortType = PortType_FILE; 708 pPort->hFile = INVALID_HANDLE_VALUE; 709 710 // Add it to the list of file ports. 711 InsertTailList(&pLocalmon->FilePorts, &pPort->Entry); 712 } 713 else 714 { 715 // Check if the port name is valid. 716 pPort = _FindPort(pLocalmon, pName); 717 if (!pPort) 718 { 719 LeaveCriticalSection(&pLocalmon->Section); 720 dwErrorCode = ERROR_UNKNOWN_PORT; 721 goto Cleanup; 722 } 723 724 // Even if this API is called OpenPort, port file handles aren't always opened here :-P 725 // Windows only does this for physical LPT ports here to enable bidirectional communication with the printer outside of jobs (using ReadPort and WritePort). 726 // The others are only opened per job in StartDocPort. 727 if (_IsLegacyPort(pName, L"LPT")) 728 { 729 // Try to create a NONSPOOLED port and open it. 730 if (_CreateNonspooledPort(pPort)) 731 { 732 // Set the transmission retry timeout for the ReadPort and WritePort calls. 733 // This also checks if this port is a physical one. 734 if (_SetTransmissionRetryTimeout(pPort)) 735 { 736 // This is definitely a physical LPT port! 737 pPort->PortType = PortType_PhysicalLPT; 738 } 739 else 740 { 741 // This is no physical port, so don't keep its handle open. 742 _ClosePortHandles(pPort); 743 } 744 } 745 else if (GetLastError() != ERROR_SUCCESS) 746 { 747 LeaveCriticalSection(&pLocalmon->Section); 748 dwErrorCode = GetLastError(); 749 goto Cleanup; 750 } 751 } 752 else if (_IsLegacyPort(pName, L"COM")) 753 { 754 // COM ports can't be redirected over the network, so this is a physical one. 755 pPort->PortType = PortType_PhysicalCOM; 756 } 757 } 758 759 LeaveCriticalSection(&pLocalmon->Section); 760 761 // Return our fetched LOCALMON_PORT structure in the handle. 762 *pHandle = (PHANDLE)pPort; 763 dwErrorCode = ERROR_SUCCESS; 764 765 Cleanup: 766 SetLastError(dwErrorCode); 767 return (dwErrorCode == ERROR_SUCCESS); 768 } 769 770 /* 771 * @name LocalmonSetPortTimeOuts 772 * 773 * Performs a SetCommTimeouts call for the given port. 774 * 775 * @param hPort 776 * The port to operate on. 777 * 778 * @param lpCTO 779 * Pointer to a COMMTIMEOUTS structure that is passed to SetCommTimeouts. 780 * 781 * @param Reserved 782 * Reserved parameter, must be 0. 783 * 784 * @return 785 * TRUE if the SetCommTimeouts call was successful, FALSE otherwise. 786 * A more specific error code can be obtained through GetLastError. 787 */ 788 BOOL WINAPI 789 LocalmonSetPortTimeOuts(HANDLE hPort, LPCOMMTIMEOUTS lpCTO, DWORD Reserved) 790 { 791 BOOL bOpenedPort = FALSE; 792 DWORD dwErrorCode; 793 PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort; 794 795 TRACE("LocalmonSetPortTimeOuts(%p, %p, %lu)\n", hPort, lpCTO, Reserved); 796 797 // Sanity checks 798 if (!pPort || !lpCTO) 799 { 800 dwErrorCode = ERROR_INVALID_PARAMETER; 801 goto Cleanup; 802 } 803 804 // If this is a serial port, a temporary file handle may be opened. 805 if (pPort->PortType == PortType_PhysicalCOM) 806 { 807 if (_CreateNonspooledPort(pPort)) 808 { 809 bOpenedPort = TRUE; 810 } 811 else if (GetLastError() != ERROR_SUCCESS) 812 { 813 dwErrorCode = GetLastError(); 814 goto Cleanup; 815 } 816 } 817 else if (pPort->hFile == INVALID_HANDLE_VALUE) 818 { 819 // All other port types need to be opened already. 820 dwErrorCode = ERROR_INVALID_PARAMETER; 821 goto Cleanup; 822 } 823 824 // Finally pass the parameters to SetCommTimeouts. 825 if (!SetCommTimeouts(pPort->hFile, lpCTO)) 826 { 827 dwErrorCode = GetLastError(); 828 ERR("SetCommTimeouts failed with error %lu!\n", dwErrorCode); 829 goto Cleanup; 830 } 831 832 dwErrorCode = ERROR_SUCCESS; 833 834 Cleanup: 835 if (bOpenedPort) 836 _ClosePortHandles(pPort); 837 838 SetLastError(dwErrorCode); 839 return (dwErrorCode == ERROR_SUCCESS); 840 } 841 842 BOOL WINAPI 843 LocalmonReadPort(HANDLE hPort, PBYTE pBuffer, DWORD cbBuffer, PDWORD pcbRead) 844 { 845 BOOL bOpenedPort = FALSE; 846 DWORD dwErrorCode; 847 PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort; 848 849 TRACE("LocalmonReadPort(%p, %p, %lu, %p)\n", hPort, pBuffer, cbBuffer, pcbRead); 850 851 // Sanity checks 852 if (!pPort || (cbBuffer && !pBuffer) || !pcbRead) 853 { 854 dwErrorCode = ERROR_INVALID_PARAMETER; 855 goto Cleanup; 856 } 857 858 // Reading is only supported for physical ports. 859 if (pPort->PortType != PortType_PhysicalCOM && pPort->PortType != PortType_PhysicalLPT) 860 { 861 dwErrorCode = ERROR_INVALID_HANDLE; 862 goto Cleanup; 863 } 864 865 // If this is a serial port, a temporary file handle may be opened. 866 if (pPort->PortType == PortType_PhysicalCOM) 867 { 868 if (_CreateNonspooledPort(pPort)) 869 { 870 bOpenedPort = TRUE; 871 } 872 else if (GetLastError() != ERROR_SUCCESS) 873 { 874 dwErrorCode = GetLastError(); 875 goto Cleanup; 876 } 877 } 878 879 // Pass the parameters to ReadFile. 880 if (!ReadFile(pPort->hFile, pBuffer, cbBuffer, pcbRead, NULL)) 881 { 882 dwErrorCode = GetLastError(); 883 ERR("ReadFile failed with error %lu!\n", dwErrorCode); 884 goto Cleanup; 885 } 886 887 Cleanup: 888 if (bOpenedPort) 889 _ClosePortHandles(pPort); 890 891 SetLastError(dwErrorCode); 892 return (dwErrorCode == ERROR_SUCCESS); 893 } 894 895 BOOL WINAPI 896 LocalmonStartDocPort(HANDLE hPort, PWSTR pPrinterName, DWORD JobId, DWORD Level, PBYTE pDocInfo) 897 { 898 DWORD dwErrorCode; 899 PDOC_INFO_1W pDocInfo1 = (PDOC_INFO_1W)pDocInfo; // DOC_INFO_1W is the least common denominator for both DOC_INFO levels. 900 PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort; 901 902 TRACE("LocalmonStartDocPort(%p, %S, %lu, %lu, %p)\n", hPort, pPrinterName, JobId, Level, pDocInfo); 903 904 // Sanity checks 905 if (!pPort || !pPrinterName || (pPort->PortType == PortType_FILE && (!pDocInfo1 || !pDocInfo1->pOutputFile || !*pDocInfo1->pOutputFile))) 906 { 907 dwErrorCode = ERROR_INVALID_PARAMETER; 908 goto Cleanup; 909 } 910 911 if (Level > 2) 912 { 913 dwErrorCode = ERROR_INVALID_LEVEL; 914 goto Cleanup; 915 } 916 917 // Calling StartDocPort multiple times isn't considered a failure, but we don't need to do anything then. 918 if (pPort->bStartedDoc) 919 { 920 dwErrorCode = ERROR_SUCCESS; 921 goto Cleanup; 922 } 923 924 // Open a handle to the given printer for later reporting our progress using SetJobW. 925 if (!OpenPrinterW(pPrinterName, &pPort->hPrinter, NULL)) 926 { 927 dwErrorCode = GetLastError(); 928 ERR("OpenPrinterW failed with error %lu!\n", dwErrorCode); 929 goto Cleanup; 930 } 931 932 // We need our Job ID for SetJobW as well. 933 pPort->dwJobID = JobId; 934 935 // Check the port type. 936 if (pPort->PortType == PortType_PhysicalLPT) 937 { 938 // Update the NONSPOOLED mapping if the port mapping has changed since our OpenPort call. 939 if (!_CreateNonspooledPort(pPort) && GetLastError() != ERROR_SUCCESS) 940 { 941 dwErrorCode = GetLastError(); 942 goto Cleanup; 943 } 944 945 // Update the transmission retry timeout as well. 946 _SetTransmissionRetryTimeout(pPort); 947 } 948 else if(pPort->PortType == PortType_FILE) 949 { 950 // This is a FILE: port. Open the output file given in the Document Info. 951 pPort->hFile = CreateFileW(pDocInfo1->pOutputFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL); 952 if (pPort->hFile == INVALID_HANDLE_VALUE) 953 { 954 dwErrorCode = GetLastError(); 955 goto Cleanup; 956 } 957 } 958 else 959 { 960 // This can be: 961 // - a physical COM port 962 // - a non-physical LPT port (e.g. with "net use LPT1 ...") 963 // - any other port (e.g. a file or a shared printer installed as a local port) 964 // 965 // For all these cases, we try to create a NONSPOOLED port per job. 966 // If _CreateNonspooledPort reports that no NONSPOOLED port is necessary, we can just open the port name. 967 if (!_CreateNonspooledPort(pPort)) 968 { 969 if (GetLastError() == ERROR_SUCCESS) 970 { 971 pPort->hFile = CreateFileW(pPort->pwszPortName, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL); 972 if (pPort->hFile == INVALID_HANDLE_VALUE) 973 { 974 dwErrorCode = GetLastError(); 975 goto Cleanup; 976 } 977 } 978 else 979 { 980 dwErrorCode = GetLastError(); 981 goto Cleanup; 982 } 983 } 984 } 985 986 // We were successful! 987 dwErrorCode = ERROR_SUCCESS; 988 pPort->bStartedDoc = TRUE; 989 990 Cleanup: 991 SetLastError(dwErrorCode); 992 return (dwErrorCode == ERROR_SUCCESS); 993 } 994 995 BOOL WINAPI 996 LocalmonWritePort(HANDLE hPort, PBYTE pBuffer, DWORD cbBuf, PDWORD pcbWritten) 997 { 998 BOOL bOpenedPort = FALSE; 999 DWORD dwErrorCode; 1000 PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort; 1001 1002 TRACE("LocalmonWritePort(%p, %p, %lu, %p)\n", hPort, pBuffer, cbBuf, pcbWritten); 1003 1004 // Sanity checks 1005 if (!pPort || (cbBuf && !pBuffer) || !pcbWritten) 1006 { 1007 dwErrorCode = ERROR_INVALID_PARAMETER; 1008 goto Cleanup; 1009 } 1010 1011 // If this is a serial port, a temporary file handle may be opened. 1012 if (pPort->PortType == PortType_PhysicalCOM) 1013 { 1014 if (_CreateNonspooledPort(pPort)) 1015 { 1016 bOpenedPort = TRUE; 1017 } 1018 else if (GetLastError() != ERROR_SUCCESS) 1019 { 1020 dwErrorCode = GetLastError(); 1021 goto Cleanup; 1022 } 1023 } 1024 else if (pPort->hFile == INVALID_HANDLE_VALUE) 1025 { 1026 // All other port types need to be opened already. 1027 dwErrorCode = ERROR_INVALID_PARAMETER; 1028 goto Cleanup; 1029 } 1030 1031 // Pass the parameters to WriteFile. 1032 if (!WriteFile(pPort->hFile, pBuffer, cbBuf, pcbWritten, NULL)) 1033 { 1034 dwErrorCode = GetLastError(); 1035 ERR("WriteFile failed with error %lu!\n", dwErrorCode); 1036 goto Cleanup; 1037 } 1038 1039 // If something was written down, we consider that a success, otherwise it's a timeout. 1040 if (*pcbWritten) 1041 dwErrorCode = ERROR_SUCCESS; 1042 else 1043 dwErrorCode = ERROR_TIMEOUT; 1044 1045 Cleanup: 1046 if (bOpenedPort) 1047 _ClosePortHandles(pPort); 1048 1049 SetLastError(dwErrorCode); 1050 return (dwErrorCode == ERROR_SUCCESS); 1051 } 1052 1053 BOOL WINAPI 1054 LocalmonAddPortEx( HANDLE hMonitor, LPWSTR pName, DWORD Level, LPBYTE lpBuffer, LPWSTR lpMonitorName ) 1055 { 1056 PLOCALMON_HANDLE pLocalmon = (PLOCALMON_HANDLE)hMonitor; 1057 PLOCALMON_PORT pPort; 1058 HKEY hKey; 1059 DWORD dwErrorCode, cbPortName; 1060 PORT_INFO_1W * pi = (PORT_INFO_1W *) lpBuffer; 1061 1062 FIXME("LocalmonAddPortEx(%p, %lu, %p, %s) => %s\n", hMonitor, Level, lpBuffer, debugstr_w(lpMonitorName), debugstr_w(pi ? pi->pName : NULL)); 1063 1064 // Sanity checks 1065 if ( !pLocalmon ) 1066 { 1067 dwErrorCode = ERROR_INVALID_PARAMETER; 1068 goto Cleanup; 1069 } 1070 1071 if ( ( lpMonitorName == NULL ) || 1072 ( lstrcmpiW( lpMonitorName, L"Local Port" ) != 0 ) || 1073 ( pi == NULL ) || 1074 ( pi->pName == NULL ) || 1075 ( pi->pName[0] == '\0' ) ) 1076 { 1077 ERR("Fail Monitor Port Name\n"); 1078 SetLastError(ERROR_INVALID_PARAMETER); 1079 return FALSE; 1080 } 1081 1082 if ( Level != 1 ) 1083 { 1084 SetLastError(ERROR_INVALID_LEVEL); 1085 return FALSE; 1086 } 1087 1088 dwErrorCode = RegOpenKeyW( HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Ports", &hKey ); 1089 if ( dwErrorCode == ERROR_SUCCESS ) 1090 { 1091 if ( DoesPortExist( pi->pName ) ) 1092 { 1093 RegCloseKey( hKey) ; 1094 FIXME("Port Exist => FALSE with %u\n", ERROR_INVALID_PARAMETER); 1095 SetLastError(ERROR_INVALID_PARAMETER); 1096 return FALSE; 1097 } 1098 1099 cbPortName = (wcslen( pi->pName ) + 1) * sizeof(WCHAR); 1100 1101 // Create a new LOCALMON_PORT structure for it. 1102 pPort = DllAllocSplMem(sizeof(LOCALMON_PORT) + cbPortName); 1103 if (!pPort) 1104 { 1105 dwErrorCode = ERROR_NOT_ENOUGH_MEMORY; 1106 RegCloseKey( hKey ); 1107 goto Cleanup; 1108 } 1109 1110 pPort->Sig = SIGLCMPORT; 1111 pPort->hFile = INVALID_HANDLE_VALUE; 1112 pPort->pLocalmon = pLocalmon; 1113 pPort->pwszPortName = wcscpy( (PWSTR)(pPort+1), pi->pName ); 1114 1115 // Insert it into the Registry list. 1116 InsertTailList(&pLocalmon->RegistryPorts, &pPort->Entry); 1117 1118 dwErrorCode = RegSetValueExW( hKey, pi->pName, 0, REG_SZ, (const BYTE *) L"", sizeof(L"") ); 1119 RegCloseKey( hKey ); 1120 } 1121 1122 Cleanup: 1123 if (dwErrorCode != ERROR_SUCCESS) SetLastError(ERROR_INVALID_PARAMETER); 1124 1125 FIXME("LocalmonAddPortEx => %u with %u\n", (dwErrorCode == ERROR_SUCCESS), GetLastError()); 1126 1127 return (dwErrorCode == ERROR_SUCCESS); 1128 } 1129 1130 // Fallback Throw Back code.... 1131 // 1132 // This is pre-w2k support, seems to be moved into LocalUI. 1133 // 1134 // 1135 1136 BOOL WINAPI 1137 LocalmonAddPort( HANDLE hMonitor, LPWSTR pName, HWND hWnd, LPWSTR pMonitorName ) 1138 { 1139 DWORD res, cbPortName; 1140 HKEY hroot; 1141 PLOCALMON_HANDLE pLocalmon = (PLOCALMON_HANDLE)hMonitor; 1142 PLOCALMON_PORT pPort; 1143 WCHAR PortName[MAX_PATH] = {0}; // Need to use a Dialog to get name. 1144 1145 FIXME("LocalmonAddPort : %s\n", debugstr_w( (LPWSTR) pMonitorName ) ); 1146 1147 res = RegOpenKeyW(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Ports", &hroot); 1148 if (res == ERROR_SUCCESS) 1149 { 1150 if ( DoesPortExist( PortName ) ) 1151 { 1152 RegCloseKey(hroot); 1153 FIXME("=> %u\n", ERROR_ALREADY_EXISTS); 1154 res = ERROR_ALREADY_EXISTS; 1155 goto Cleanup; 1156 } 1157 1158 cbPortName = (wcslen( PortName ) + 1) * sizeof(WCHAR); 1159 1160 // Create a new LOCALMON_PORT structure for it. 1161 pPort = DllAllocSplMem(sizeof(LOCALMON_PORT) + cbPortName); 1162 if (!pPort) 1163 { 1164 res = ERROR_NOT_ENOUGH_MEMORY; 1165 RegCloseKey( hroot ); 1166 goto Cleanup; 1167 } 1168 1169 pPort->Sig = SIGLCMPORT; 1170 pPort->hFile = INVALID_HANDLE_VALUE; 1171 pPort->pLocalmon = pLocalmon; 1172 pPort->pwszPortName = wcscpy( (PWSTR)(pPort+1), PortName ); 1173 1174 // Insert it into the Registry list. 1175 InsertTailList(&pLocalmon->RegistryPorts, &pPort->Entry); 1176 1177 res = RegSetValueExW(hroot, PortName, 0, REG_SZ, (const BYTE *) L"", sizeof(L"")); 1178 RegCloseKey(hroot); 1179 } 1180 1181 FIXME("=> %u\n", res); 1182 1183 Cleanup: 1184 SetLastError(res); 1185 return (res == ERROR_SUCCESS); 1186 } 1187 1188 BOOL WINAPI 1189 LocalmonConfigurePort( HANDLE hMonitor, LPWSTR pName, HWND hWnd, LPWSTR pPortName ) 1190 { 1191 //// See ConfigurePortUI 1192 FIXME("LocalmonConfigurePort : %s\n", debugstr_w( pPortName ) ); 1193 return FALSE; 1194 } 1195 1196 BOOL WINAPI 1197 LocalmonDeletePort( HANDLE hMonitor, LPWSTR pName, HWND hWnd, LPWSTR pPortName ) 1198 { 1199 DWORD res; 1200 HKEY hroot; 1201 PLOCALMON_HANDLE pLocalmon = (PLOCALMON_HANDLE)hMonitor; 1202 PLOCALMON_PORT pPort; 1203 1204 FIXME("LocalmonDeletePort : %s\n", debugstr_w( pPortName ) ); 1205 1206 res = RegOpenKeyW(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Ports", &hroot); 1207 if ( res == ERROR_SUCCESS ) 1208 { 1209 res = RegDeleteValueW(hroot, pPortName ); 1210 1211 RegCloseKey(hroot); 1212 1213 pPort = _FindPort( pLocalmon, pPortName ); 1214 if ( pPort ) 1215 { 1216 EnterCriticalSection(&pPort->pLocalmon->Section); 1217 RemoveEntryList(&pPort->Entry); 1218 LeaveCriticalSection(&pPort->pLocalmon->Section); 1219 1220 DllFreeSplMem(pPort); 1221 } 1222 1223 FIXME("=> %u with %u\n", res, GetLastError() ); 1224 } 1225 1226 SetLastError(res); 1227 return (res == ERROR_SUCCESS); 1228 } 1229