Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1446 lines
42 KiB

  1. /*++
  2. Copyright (c) 1999 Microsoft Corporation
  3. All Rights Reserved
  4. Module Name:
  5. EnumPorts.c
  6. Abstract:
  7. USBMON enumports routines
  8. Author:
  9. Revision History:
  10. --*/
  11. #include "precomp.h"
  12. TCHAR sComma = TEXT(',');
  13. TCHAR sZero = TEXT('\0');
  14. TCHAR cszUSB[] = TEXT("USB");
  15. TCHAR cszBaseName[] = TEXT("Base Name");
  16. TCHAR cszPortNumber[] = TEXT("Port Number");
  17. TCHAR cszRecyclable[] = TEXT("recyclable");
  18. TCHAR cszPortDescription[] = TEXT("Port Description");
  19. TCHAR cszUSBDescription[] = TEXT("Virtual printer port for USB");
  20. TCHAR cszMonitorName[] = TEXT("USB Print Monitor");
  21. DWORD gdwMonitorNameSize = sizeof(cszMonitorName);
  22. BACKGROUND_THREAD_DATA FirstBackgroundThreadData = { NULL, NULL, NULL },
  23. SecondBackgroundThreadData = { NULL, NULL, NULL };
  24. #ifdef MYDEBUG
  25. #include <stdio.h>
  26. DWORD dwCount[10], dwTotalTime[10];
  27. DWORD dwSkippedPorts, dwSkippedEnumPorts, dwPortUpdates;
  28. #endif
  29. //
  30. // Default timeout values
  31. //
  32. #define READ_TIMEOUT_MULTIPLIER 0
  33. #define READ_TIMEOUT_CONSTANT 60000
  34. #define WRITE_TIMEOUT_MULTIPLIER 0
  35. #define WRITE_TIMEOUT_CONSTANT 60000
  36. /*++
  37. This section explains how we do multiple thread synchronization between
  38. enumport threads and background threads.
  39. 1. Only one enumports thread can walk the port list of dynamon.
  40. Enumports uses EnumPortsCS critical section to ensure this.
  41. 2. We never want to make a spooler call from the EnumPorts thread. This is
  42. because such a call could generate another call back to dynamon and we
  43. do not want a deadlock. For example OpenPrinter call to netware print
  44. provider will generate an EnumPorts call.
  45. So when we find a need to change the printer online/offline staus,
  46. because correponding dynamon ports are active/inactive, we will spin a
  47. background thread to make the spooler calls to do that.
  48. 3. We want to make sure background thread does not block EnumPorts. The
  49. reason is each EnumPorts call could spin a background thread, and
  50. each backgound thread call a number of OpenPrinter calls, and each
  51. OpenPrinter call generate an EnumPorts call.
  52. So we pass a separate port update list to the background thread. And
  53. we make sure EnumPorts thread does not wait for the background thread
  54. to complete to return to spooler.
  55. 4. We want to control the number and execution order of background threads.
  56. We will make sure at a time there can be only 2 background threads. One
  57. actually processing port update lists, and another just pending
  58. execution -- scheduled, but waiting for the active background thread
  59. to complete execution before processing it's port update list.
  60. --*/
  61. PUSELESS_PORT_INFO
  62. FindUselessEntry(
  63. IN PUSBMON_MONITOR_INFO pMonitorInfo,
  64. IN LPTSTR pszDevicePath,
  65. OUT PUSELESS_PORT_INFO *ppPrev
  66. )
  67. /*++
  68. Routine Description:
  69. Searches for a device path in the useless port list
  70. Arguments:
  71. Return Value:
  72. NULL if no entry found in the list
  73. Else a valid USELESS_PORT_INFO pointer
  74. Weather port is found or not *ppPrev will return the previous element
  75. --*/
  76. {
  77. INT iCmp;
  78. PUSELESS_PORT_INFO pHead;
  79. for ( pHead = pMonitorInfo->pJunkList, *ppPrev = NULL ;
  80. pHead && (iCmp = lstrcmp(pszDevicePath, pHead->szDevicePath)) < 0 ;
  81. *ppPrev = pHead, pHead = pHead->pNext )
  82. ;
  83. //
  84. // If useless port should go in the middle but not currently there
  85. //
  86. if ( pHead && iCmp != 0 )
  87. pHead = NULL;
  88. return pHead;
  89. }
  90. VOID
  91. AddUselessPortEntry(
  92. IN PUSBMON_MONITOR_INFO pMonitorInfo,
  93. IN LPTSTR pszDevicePath
  94. )
  95. /*++
  96. Routine Description:
  97. This adds a useless port entry to our list. So next time we see an inactive
  98. port that is already in our known usless port list we can skip the port
  99. entry
  100. Arguments:
  101. pMonitorInfo : Pointer to monitor inf
  102. pszDevicePath : Device path for the useless port
  103. Return Value:
  104. None. Under normal circumstances will add a useless entry to our list
  105. --*/
  106. {
  107. PUSELESS_PORT_INFO pTemp, pPrev;
  108. pTemp = FindUselessEntry(pMonitorInfo, pszDevicePath, &pPrev);
  109. //
  110. // Don't add an entry that is already there
  111. //
  112. SPLASSERT(pTemp == NULL);
  113. if ( pTemp = (PUSELESS_PORT_INFO) AllocSplMem(sizeof(*pTemp)) ) {
  114. lstrcpy(pTemp->szDevicePath, pszDevicePath);
  115. ++pMonitorInfo->dwUselessPortCount;
  116. if ( pPrev ) {
  117. pTemp->pNext = pPrev->pNext;
  118. pPrev->pNext = pTemp;
  119. } else {
  120. pTemp->pNext = pMonitorInfo->pJunkList;
  121. pMonitorInfo->pJunkList = pTemp;
  122. }
  123. }
  124. }
  125. VOID
  126. AddToPortUpdateList(
  127. IN OUT PPORT_UPDATE_INFO *ppPortUpdateInfo,
  128. IN PUSBMON_PORT_INFO pPortInfo,
  129. IN OUT HKEY *phKey
  130. )
  131. /*++
  132. Routine Description:
  133. Adds a port to the list of ports that need to status updated.
  134. Arguments:
  135. ppPortUpdateInfo : Pointer to the head of port update list
  136. pPortInfo : Gives the port for which we need to update
  137. port status
  138. phKey : Pointer to reg handle. If port update element created
  139. this will be passed to background thread for use and
  140. closing
  141. Return Value:
  142. None
  143. If the port update element is created then phKey is set to invalid hanlde
  144. since ownership is going to be passed to background thread.
  145. New port update element will be the first in the list
  146. --*/
  147. {
  148. PPORT_UPDATE_INFO pTemp;
  149. if ( pTemp = (PPORT_UPDATE_INFO) AllocSplMem(sizeof(*pTemp)) ) {
  150. lstrcpy(pTemp->szPortName, pPortInfo->szPortName);
  151. pTemp->bActive = (pPortInfo->dwDeviceFlags & SPINT_ACTIVE) != 0;
  152. pTemp->hKey = *phKey;
  153. pTemp->pNext = *ppPortUpdateInfo;
  154. *ppPortUpdateInfo = pTemp;
  155. *phKey = INVALID_HANDLE_VALUE;
  156. }
  157. }
  158. PUSBMON_PORT_INFO
  159. FindPortUsingDevicePath(
  160. IN PUSBMON_MONITOR_INFO pMonitorInfo,
  161. IN LPTSTR pszDevicePath
  162. )
  163. /*++
  164. Routine Description:
  165. Finds a port by device path.
  166. Arguments:
  167. pMonitorInfo : Pointer to MONITOR_INFO structure
  168. pszDevicePath : Device path name to search for
  169. Return Value:
  170. If NULL port is not in list, else pointer to the PORT_INFO entry for the
  171. given device path
  172. --*/
  173. {
  174. INT iCmp;
  175. PUSBMON_PORT_INFO pHead;
  176. EnterCriticalSection(&pMonitorInfo->EnumPortsCS);
  177. //
  178. // Port list is sorted on port name, so we have to scan the whole list
  179. //
  180. for ( pHead = pMonitorInfo->pPortInfo ; pHead ; pHead = pHead->pNext )
  181. if ( lstrcmp(pszDevicePath, pHead->szDevicePath) == 0 )
  182. break;
  183. LeaveCriticalSection(&pMonitorInfo->EnumPortsCS);
  184. return pHead;
  185. }
  186. PUSBMON_PORT_INFO
  187. FindPort(
  188. IN PUSBMON_MONITOR_INFO pMonitorInfo,
  189. IN LPTSTR pszPortName,
  190. OUT PUSBMON_PORT_INFO *ppPrev
  191. )
  192. /*++
  193. Routine Description:
  194. Finds a port by name. Ports are kept in singly linked list sorted by name.
  195. If found previous in the list is returned via *ppPrev.
  196. Arguments:
  197. pHead : Head pointer to port list
  198. pszPortName : Name of port to look
  199. ppPrev : On return will have pointer to previous element
  200. Return Value:
  201. If NULL port is not in list, else the found element
  202. Weather port is found or not *ppPrev will return the previous element
  203. --*/
  204. {
  205. INT iCmp;
  206. PUSBMON_PORT_INFO pHead;
  207. EnterCriticalSection(&pMonitorInfo->EnumPortsCS);
  208. pHead = pMonitorInfo->pPortInfo;
  209. for ( *ppPrev = NULL ;
  210. pHead && (iCmp = lstrcmp(pszPortName, pHead->szPortName)) < 0 ;
  211. *ppPrev = pHead, pHead = pHead->pNext )
  212. ;
  213. //
  214. // If port should go in the middle but not currently there
  215. //
  216. if ( pHead && iCmp != 0 )
  217. pHead = NULL;
  218. LeaveCriticalSection(&pMonitorInfo->EnumPortsCS);
  219. return pHead;
  220. }
  221. BOOL
  222. AddPortToList(
  223. LPTSTR pszPortName,
  224. LPTSTR pszDevicePath,
  225. DWORD dwDeviceFlags,
  226. HKEY *phKey,
  227. PUSBMON_MONITOR_INFO pMonitorInfo,
  228. PUSBMON_PORT_INFO pPrevPortInfo,
  229. PPORT_UPDATE_INFO *ppPortUpdateInfo
  230. )
  231. {
  232. DWORD dwSize;
  233. PUSBMON_PORT_INFO pPortInfo;
  234. PUSELESS_PORT_INFO pCur, pPrev;
  235. SPLASSERT(FindPortUsingDevicePath(pMonitorInfo, pszDevicePath) == NULL);
  236. pPortInfo = (PUSBMON_PORT_INFO) AllocSplMem(sizeof(USBMON_PORT_INFO));
  237. if ( !pPortInfo )
  238. return FALSE;
  239. pPortInfo->dwSignature = USB_SIGNATURE;
  240. pPortInfo->hDeviceHandle = INVALID_HANDLE_VALUE;
  241. pPortInfo->dwDeviceFlags = dwDeviceFlags;
  242. pPortInfo->ReadTimeoutMultiplier = READ_TIMEOUT_MULTIPLIER;
  243. pPortInfo->ReadTimeoutMultiplier = READ_TIMEOUT_MULTIPLIER;
  244. pPortInfo->WriteTimeoutConstant = WRITE_TIMEOUT_CONSTANT;
  245. pPortInfo->WriteTimeoutConstant = WRITE_TIMEOUT_CONSTANT;
  246. lstrcpy(pPortInfo->szPortName, pszPortName);
  247. lstrcpy(pPortInfo->szDevicePath, pszDevicePath);
  248. dwSize = sizeof(pPortInfo->szPortDescription);
  249. if ( ERROR_SUCCESS != RegQueryValueEx(*phKey,
  250. cszPortDescription,
  251. 0,
  252. NULL,
  253. (LPBYTE)(pPortInfo->szPortDescription),
  254. &dwSize) ) {
  255. lstrcpy(pPortInfo->szPortDescription, cszUSBDescription);
  256. }
  257. if ( pPrevPortInfo ) {
  258. pPortInfo->pNext = pPrevPortInfo->pNext;
  259. pPrevPortInfo->pNext = pPortInfo;
  260. } else {
  261. pPortInfo->pNext = pMonitorInfo->pPortInfo;
  262. pMonitorInfo->pPortInfo = pPortInfo;
  263. }
  264. //
  265. // If this is a port that is getting recycled remove from useless list
  266. //
  267. if ( pCur = FindUselessEntry(pMonitorInfo, pszDevicePath, &pPrev) ) {
  268. if ( pPrev )
  269. pPrev->pNext = pCur->pNext;
  270. else
  271. pMonitorInfo->pJunkList = pCur->pNext;
  272. --pMonitorInfo->dwUselessPortCount;
  273. FreeSplMem(pCur);
  274. }
  275. //
  276. // On spooler startup we always have to check if the online/offline status
  277. // has to be changed. This is because spooler will remember the last state
  278. // before previous spooler shutdown which may be incorrect
  279. //
  280. AddToPortUpdateList(ppPortUpdateInfo, pPortInfo, phKey);
  281. ++pMonitorInfo->dwPortCount;
  282. return TRUE;
  283. }
  284. HKEY
  285. GetPortNameAndRegKey(
  286. IN PSETUPAPI_INFO pSetupInfo,
  287. IN HDEVINFO hDevList,
  288. IN PSP_DEVICE_INTERFACE_DATA pDeviceInterface,
  289. OUT LPTSTR pszPortName
  290. )
  291. /*++
  292. Routine Description:
  293. Find port name for a device interface and also return reg handle
  294. Arguments:
  295. hDevList : List of USB printer devices
  296. pDeviceInterface : pointer to device interface in question
  297. pszPortName : Port name on return.
  298. Return Value:
  299. INVALID_HANDLE_VALUE on some errors.
  300. Otherwize a valid registry handle with pszPortName giving port name
  301. --*/
  302. {
  303. HKEY hKey = INVALID_HANDLE_VALUE;
  304. DWORD dwPortNumber, dwNeeded, dwLastError;
  305. TCHAR szPortBaseName[MAX_PORT_LEN-3];
  306. #ifdef MYDEBUG
  307. DWORD dwTime;
  308. dwTime = GetTickCount();
  309. #endif
  310. hKey = pSetupInfo->OpenDeviceInterfaceRegKey(hDevList,
  311. pDeviceInterface,
  312. 0,
  313. KEY_ALL_ACCESS);
  314. #ifdef MYDEBUG
  315. dwTime = GetTickCount() - dwTime;
  316. ++dwCount[0];
  317. dwTotalTime[0] += dwTime;
  318. #endif
  319. if ( hKey == INVALID_HANDLE_VALUE ) {
  320. dwLastError = GetLastError();
  321. DBGMSG(DBG_ERROR,
  322. ("usbmon: WalkPortList: SetupDiOpenDeviceInterfaceRegKey failed with error %d\n",
  323. dwLastError));
  324. return INVALID_HANDLE_VALUE;
  325. }
  326. dwNeeded = sizeof(dwPortNumber);
  327. if ( ERROR_SUCCESS != RegQueryValueEx(hKey, cszPortNumber, 0, NULL,
  328. (LPBYTE)&dwPortNumber, &dwNeeded) ) {
  329. DBGMSG(DBG_WARNING,
  330. ("usbmon: GetPortNameAndRegKey: RegQueryValueEx failed for port number\n"));
  331. goto Fail;
  332. }
  333. dwNeeded = sizeof(szPortBaseName);
  334. if ( ERROR_SUCCESS != RegQueryValueEx(hKey, cszBaseName, 0, NULL,
  335. (LPBYTE)szPortBaseName, &dwNeeded) ) {
  336. lstrcpy(szPortBaseName, cszUSB);
  337. }
  338. wsprintf(pszPortName, TEXT("%s%03u"), szPortBaseName, dwPortNumber);
  339. return hKey;
  340. Fail:
  341. RegCloseKey(hKey);
  342. return INVALID_HANDLE_VALUE;
  343. }
  344. BOOL
  345. SetOnlineStaus(
  346. LPPRINTER_INFO_5 pPrinterInfo5,
  347. BOOL bOnline
  348. )
  349. {
  350. BOOL bRet = FALSE;
  351. HANDLE hPrinter;
  352. PRINTER_DEFAULTS PrinterDefault = {NULL, NULL, PRINTER_ALL_ACCESS};
  353. #ifdef MYDEBUG
  354. DWORD dwTime;
  355. #endif
  356. //
  357. // Force all DOT4 ports to remain online at all times.
  358. //
  359. if( lstrncmpi( pPrinterInfo5->pPortName, TEXT("DOT4"), lstrlen(TEXT("DOT4")) ) == 0 )
  360. bOnline = TRUE;
  361. //
  362. // Check if spooler already has the correct status
  363. // (can happen on spooler startup)
  364. //
  365. if ( bOnline ) {
  366. if ( !(pPrinterInfo5->Attributes & PRINTER_ATTRIBUTE_WORK_OFFLINE) )
  367. return TRUE;
  368. } else
  369. if ( pPrinterInfo5->Attributes & PRINTER_ATTRIBUTE_WORK_OFFLINE )
  370. return TRUE;
  371. #ifdef MYDEBUG
  372. dwTime = GetTickCount();
  373. #endif
  374. if ( !OpenPrinter(pPrinterInfo5->pPrinterName, &hPrinter, &PrinterDefault) )
  375. return FALSE;
  376. if ( bOnline )
  377. pPrinterInfo5->Attributes &= ~PRINTER_ATTRIBUTE_WORK_OFFLINE;
  378. else
  379. pPrinterInfo5->Attributes |= PRINTER_ATTRIBUTE_WORK_OFFLINE;
  380. bRet = SetPrinter(hPrinter, 5, (LPBYTE)pPrinterInfo5, 0);
  381. ClosePrinter(hPrinter);
  382. #ifdef MYDEBUG
  383. dwTime = GetTickCount() - dwTime;
  384. ++dwCount[7];
  385. dwTotalTime[7] += dwTime;
  386. #endif
  387. return bRet;
  388. }
  389. BOOL
  390. PortNameNeededBySpooler(
  391. IN LPTSTR pszPortName,
  392. IN LPPRINTER_INFO_5 pPrinterInfo5,
  393. IN DWORD dwPrinters,
  394. IN BOOL bActive
  395. )
  396. /*++
  397. Routine Description:
  398. Tells if a port is needed by spooler. Any port to which spooler currently
  399. has a printer going is needed.
  400. Arguments:
  401. pszPortName : Port name in question
  402. pPrinterInfo5 : List of PrinterInfo5s
  403. dwPrinters : Count of the list of printers
  404. Return Value:
  405. TRUE if spooler currently has a printer which is using the port
  406. FALSE otherwise
  407. --*/
  408. {
  409. BOOL bPortUsedByAPrinter = FALSE, bPrinterUsesOnlyThisPort;
  410. DWORD dwIndex;
  411. LPTSTR pszStr1, pszStr2;
  412. for ( dwIndex = 0 ; dwIndex < dwPrinters ; ++dwIndex, ++pPrinterInfo5 ) {
  413. bPrinterUsesOnlyThisPort = FALSE;
  414. //
  415. // Port names are returned comma separated by spooler,
  416. // and there are blanks
  417. //
  418. pszStr1 = pPrinterInfo5->pPortName;
  419. if ( lstrcmpi(pszPortName, pszStr1) == 0 )
  420. bPortUsedByAPrinter = bPrinterUsesOnlyThisPort = TRUE;
  421. else {
  422. //
  423. // Look at each port in the list of ports printer uses
  424. //
  425. while ( pszStr2 = lstrchr(pszStr1, sComma) ) {
  426. *pszStr2 = sZero;
  427. if( lstrcmpi(pszPortName, pszStr1) == 0 )
  428. bPortUsedByAPrinter = TRUE;
  429. *pszStr2 = sComma; // Put the comma back
  430. if ( bPortUsedByAPrinter )
  431. break;
  432. pszStr1 = pszStr2 + 1;
  433. //
  434. // Skip spaces
  435. //
  436. while ( *pszStr1 == TEXT(' ') )
  437. ++pszStr1;
  438. }
  439. if ( !bPortUsedByAPrinter )
  440. bPortUsedByAPrinter = lstrcmpi(pszPortName, pszStr1) == 0;
  441. }
  442. //
  443. // We will change only status of printer for non-pooled printers only
  444. //
  445. if ( bPrinterUsesOnlyThisPort )
  446. SetOnlineStaus(pPrinterInfo5, bActive);
  447. }
  448. return bPortUsedByAPrinter;
  449. }
  450. VOID
  451. UpdatePortInfo(
  452. PUSBMON_PORT_INFO pPortInfo,
  453. LPTSTR pszDevicePath,
  454. DWORD dwDeviceFlags,
  455. HKEY *phKey,
  456. PPORT_UPDATE_INFO *ppPortUpdateInfo
  457. )
  458. {
  459. DWORD dwSize;
  460. lstrcpy(pPortInfo->szDevicePath, pszDevicePath);
  461. dwSize = sizeof(pPortInfo->szPortDescription);
  462. if ( ERROR_SUCCESS != RegQueryValueEx(*phKey,
  463. cszPortDescription,
  464. 0,
  465. NULL,
  466. (LPBYTE)(pPortInfo->szPortDescription),
  467. &dwSize) ) {
  468. lstrcpy(pPortInfo->szPortDescription, cszUSBDescription);
  469. }
  470. if ( pPortInfo->dwDeviceFlags != dwDeviceFlags ) {
  471. pPortInfo->dwDeviceFlags = dwDeviceFlags;
  472. AddToPortUpdateList(ppPortUpdateInfo, pPortInfo, phKey);
  473. }
  474. }
  475. BOOL
  476. PrinterInfo5s(
  477. OUT LPPRINTER_INFO_5 *ppPrinterInfo5,
  478. OUT LPDWORD pdwReturned
  479. )
  480. /*++
  481. Routine Description:
  482. Does an EnumPrinter and returns a list of PRINTER_INFO_5s of all local
  483. printers. Caller should free the pointer.
  484. Arguments:
  485. ppPrinterInfo5 : Points to PRINTER_INFO_5s on return
  486. pdwReturned : Tells how many PRINTER_INFO_5s are returned
  487. Return Value:
  488. TRUE on success, FALSE else
  489. --*/
  490. {
  491. BOOL bRet = FALSE;
  492. static DWORD dwNeeded = 0;
  493. LPBYTE pBuf = NULL;
  494. #ifdef MYDEBUG
  495. DWORD dwTime;
  496. dwTime = GetTickCount();
  497. #endif
  498. *pdwReturned = 0;
  499. if ( !(pBuf = AllocSplMem(dwNeeded)) )
  500. goto Cleanup;
  501. if ( !EnumPrinters(PRINTER_ENUM_LOCAL,
  502. NULL,
  503. 5,
  504. pBuf,
  505. dwNeeded,
  506. &dwNeeded,
  507. pdwReturned) ) {
  508. if ( GetLastError() != ERROR_INSUFFICIENT_BUFFER )
  509. goto Cleanup;
  510. FreeSplMem(pBuf);
  511. if ( !(pBuf = AllocSplMem(dwNeeded)) ||
  512. !EnumPrinters(PRINTER_ENUM_LOCAL,
  513. NULL,
  514. 5,
  515. pBuf,
  516. dwNeeded,
  517. &dwNeeded,
  518. pdwReturned) ) {
  519. goto Cleanup;
  520. }
  521. }
  522. bRet = TRUE;
  523. Cleanup:
  524. if ( bRet && *pdwReturned ) {
  525. *ppPrinterInfo5 = (LPPRINTER_INFO_5)pBuf;
  526. } else {
  527. FreeSplMem(pBuf);
  528. *ppPrinterInfo5 = NULL;
  529. *pdwReturned = 0;
  530. }
  531. #ifdef MYDEBUG
  532. dwTime = GetTickCount() - dwTime;
  533. ++dwCount[6];
  534. dwTotalTime[6] += dwTime;
  535. #endif
  536. return bRet;
  537. }
  538. VOID
  539. BackgroundThread(
  540. HANDLE hEvent
  541. )
  542. /*++
  543. This is the body of background thread which does the following:
  544. 1. Update printer online/offline with spooler for printers using
  545. dynamon ports
  546. 2. Mark those ports that are inactive and not needed by spooler
  547. as recyclable
  548. 3. When exiting if there is a second background thread scheduled
  549. then trigger it to go
  550. --*/
  551. {
  552. HANDLE hEventToSet = NULL;
  553. DWORD dwPrinters;
  554. PPORT_UPDATE_INFO pPortUpdateList, pCur;
  555. LPCRITICAL_SECTION pBackThreadCS;
  556. LPPRINTER_INFO_5 pPrinterInfo5List = NULL;
  557. #ifdef MYDEBUG
  558. DWORD dwTime;
  559. CHAR szBuf[200];
  560. dwTime = GetTickCount();
  561. #endif
  562. //
  563. // Background waits here to be triggered to do it's work
  564. //
  565. WaitForSingleObject(hEvent, INFINITE);
  566. //
  567. // This is the first/active background thread at this point
  568. //
  569. SPLASSERT(hEvent == FirstBackgroundThreadData.hWaitToStart);
  570. //
  571. // Until we are here (i.e. the thread is triggered to tell this is the
  572. // active/first background thread) we can't access any of these things
  573. //
  574. pPortUpdateList = FirstBackgroundThreadData.pPortUpdateList;
  575. pBackThreadCS = &FirstBackgroundThreadData.pMonitorInfo->BackThreadCS;
  576. if ( PrinterInfo5s(&pPrinterInfo5List, &dwPrinters) ) {
  577. for ( pCur = pPortUpdateList ; pCur ; pCur = pCur->pNext ) {
  578. if ( !PortNameNeededBySpooler(pCur->szPortName,
  579. pPrinterInfo5List,
  580. dwPrinters,
  581. pCur->bActive) &&
  582. !pCur->bActive ) {
  583. RegSetValueEx(pCur->hKey, cszRecyclable, 0, REG_NONE, 0, 0);
  584. }
  585. }
  586. }
  587. //
  588. // Now the thread has done what it was spun off to do.
  589. //
  590. EnterCriticalSection(pBackThreadCS);
  591. //
  592. // Remove this thread from being the first/active background thread
  593. //
  594. FirstBackgroundThreadData.hWaitToStart = NULL;
  595. FirstBackgroundThreadData.pPortUpdateList = NULL;
  596. FirstBackgroundThreadData.pMonitorInfo = NULL;
  597. CloseHandle(hEvent);
  598. //
  599. // If there is a second thread it becomes the first now
  600. //
  601. if ( SecondBackgroundThreadData.hWaitToStart ) {
  602. hEventToSet
  603. = FirstBackgroundThreadData.hWaitToStart
  604. = SecondBackgroundThreadData.hWaitToStart;
  605. FirstBackgroundThreadData.pPortUpdateList
  606. = SecondBackgroundThreadData.pPortUpdateList;
  607. FirstBackgroundThreadData.pMonitorInfo
  608. = SecondBackgroundThreadData.pMonitorInfo;
  609. SecondBackgroundThreadData.hWaitToStart = NULL;
  610. SecondBackgroundThreadData.pPortUpdateList = NULL;
  611. SecondBackgroundThreadData.pMonitorInfo = NULL;
  612. }
  613. LeaveCriticalSection(pBackThreadCS);
  614. //
  615. // If there is a second thread trigger it
  616. //
  617. if ( hEventToSet )
  618. SetEvent(hEventToSet);
  619. FreeSplMem(pPrinterInfo5List);
  620. //
  621. // Free the port update list
  622. //
  623. while ( pCur = pPortUpdateList ) {
  624. pPortUpdateList = pPortUpdateList->pNext;
  625. RegCloseKey(pCur->hKey);
  626. FreeSplMem(pCur);
  627. }
  628. #ifdef MYDEBUG
  629. dwTime = GetTickCount() - dwTime;
  630. ++dwCount[4];
  631. dwTotalTime[4] += dwTime;
  632. sprintf(szBuf, "BackgroundThread: %d\n",
  633. dwTotalTime[4]/dwCount[4]);
  634. OutputDebugStringA(szBuf);
  635. sprintf(szBuf, "PrinterInfo5s: %d\n",
  636. dwTotalTime[6]/dwCount[6]);
  637. OutputDebugStringA(szBuf);
  638. if ( dwCount[7] ) {
  639. sprintf(szBuf, "SetOnlineStatus: %d\n",
  640. dwTotalTime[7]/dwCount[7]);
  641. OutputDebugStringA(szBuf);
  642. }
  643. #endif
  644. }
  645. HANDLE
  646. CreateBackgroundThreadAndReturnEventToTrigger(
  647. VOID
  648. )
  649. /*++
  650. Creates a background thread and passes it an event on which to wait for
  651. starting execution. Returns the event handle.
  652. --*/
  653. {
  654. HANDLE hThread = NULL, hEvent;
  655. DWORD dwThreadId;
  656. if ( hEvent = CreateEvent(NULL, TRUE, FALSE, NULL) ) {
  657. if ( hThread = CreateThread(NULL,
  658. 0,
  659. (LPTHREAD_START_ROUTINE)BackgroundThread,
  660. hEvent,
  661. 0,
  662. &dwThreadId) ) {
  663. CloseHandle(hThread);
  664. } else {
  665. CloseHandle(hEvent);
  666. hEvent = NULL;
  667. }
  668. }
  669. return hEvent;
  670. }
  671. VOID
  672. PassPortUpdateListToBackgroundThread(
  673. PUSBMON_MONITOR_INFO pMonitorInfo,
  674. PPORT_UPDATE_INFO pPortUpdateList
  675. )
  676. /*++
  677. Called from EnumPorts thread with a list of port update elements to be
  678. passed to the background thread.
  679. a. If there is no background thread then spin one and trigger it
  680. b. If there is only one background thread then spin the second one. First
  681. one will trigger the second one on completion
  682. c. If there are two background threads, one active and one waiting to be
  683. triggered, then add the port update elements to the second one's list
  684. --*/
  685. {
  686. DWORD dwThreadCount = 0;
  687. PPORT_UPDATE_INFO pCur, pNext, pLast;
  688. if ( pPortUpdateList == NULL )
  689. return;
  690. EnterCriticalSection(&pMonitorInfo->BackThreadCS);
  691. if ( FirstBackgroundThreadData.hWaitToStart ) {
  692. ++dwThreadCount;
  693. if ( SecondBackgroundThreadData.hWaitToStart )
  694. ++dwThreadCount;
  695. }
  696. switch (dwThreadCount) {
  697. case 0:
  698. if ( FirstBackgroundThreadData.hWaitToStart
  699. = CreateBackgroundThreadAndReturnEventToTrigger() ) {
  700. FirstBackgroundThreadData.pMonitorInfo = pMonitorInfo;
  701. FirstBackgroundThreadData.pPortUpdateList = pPortUpdateList;
  702. SetEvent(FirstBackgroundThreadData.hWaitToStart);
  703. }
  704. break;
  705. case 1:
  706. if ( SecondBackgroundThreadData.hWaitToStart
  707. = CreateBackgroundThreadAndReturnEventToTrigger() ) {
  708. SecondBackgroundThreadData.pMonitorInfo = pMonitorInfo;
  709. SecondBackgroundThreadData.pPortUpdateList = pPortUpdateList;
  710. }
  711. break;
  712. case 2:
  713. //
  714. // Note: We know both lists can't be empty
  715. //
  716. for ( pCur = pPortUpdateList; pCur ; pCur = pNext ) {
  717. pNext = pCur->pNext;
  718. for ( pLast = SecondBackgroundThreadData.pPortUpdateList ;
  719. pLast ; pLast = pLast->pNext ) {
  720. //
  721. // If there is a duplicate update old entry with info
  722. // from new one and free the new entry
  723. //
  724. if ( !lstrcmpi(pLast->szPortName, pCur->szPortName) ) {
  725. RegCloseKey(pLast->hKey);
  726. pLast->hKey = pCur->hKey;
  727. pLast->bActive = pCur->bActive;
  728. FreeSplMem(pCur);
  729. break; // out of inner for loop
  730. } else if ( pLast->pNext == NULL ) {
  731. //
  732. // If we hit end of list then append entry
  733. //
  734. pLast->pNext = pCur;
  735. pCur->pNext = NULL;
  736. break; // out of inner for loop
  737. }
  738. }
  739. }
  740. break;
  741. default:
  742. //
  743. // Should not happen
  744. //
  745. SPLASSERT(dwThreadCount == 0);
  746. }
  747. LeaveCriticalSection(&pMonitorInfo->BackThreadCS);
  748. }
  749. VOID
  750. ProcessPortInfo(
  751. IN PSETUPAPI_INFO pSetupApiInfo,
  752. IN PUSBMON_MONITOR_INFO pMonitorInfo,
  753. IN HDEVINFO hDevList,
  754. IN PSP_DEVICE_INTERFACE_DATA pDeviceInterface,
  755. IN PSP_DEVICE_INTERFACE_DETAIL_DATA pDeviceDetail,
  756. IN OUT PPORT_UPDATE_INFO *ppPortUpdateInfo
  757. )
  758. {
  759. HKEY hKey = INVALID_HANDLE_VALUE;
  760. TCHAR szPortName[MAX_PORT_LEN];
  761. PUSBMON_PORT_INFO pCur, pPrev;
  762. #ifdef MYDEBUG
  763. ++dwPortUpdates;
  764. #endif
  765. hKey = GetPortNameAndRegKey(pSetupApiInfo, hDevList,
  766. pDeviceInterface, szPortName);
  767. if ( hKey == INVALID_HANDLE_VALUE ) {
  768. //
  769. // If this port is inactive and is not in our known port list
  770. // add to useless list. Earlier we would have been opening the registry
  771. // every time and find that port number is missing because of KM drivers
  772. // not deleting the inactive device interfaces
  773. //
  774. if ( !(pDeviceInterface->Flags & SPINT_ACTIVE) &&
  775. !FindPortUsingDevicePath(pMonitorInfo, pDeviceDetail->DevicePath) )
  776. AddUselessPortEntry(pMonitorInfo, pDeviceDetail->DevicePath);
  777. return;
  778. }
  779. pCur = FindPort(pMonitorInfo, szPortName, &pPrev);
  780. //
  781. // Port info is currently in our list?
  782. //
  783. if ( pCur ) {
  784. //
  785. // Did the device path or flags change?
  786. //
  787. if ( pCur->dwDeviceFlags != pDeviceInterface->Flags ||
  788. lstrcmp(pDeviceDetail->DevicePath, pCur->szDevicePath) ) {
  789. UpdatePortInfo(pCur, pDeviceDetail->DevicePath,
  790. pDeviceInterface->Flags, &hKey, ppPortUpdateInfo);
  791. }
  792. } else {
  793. AddPortToList(szPortName, pDeviceDetail->DevicePath,
  794. pDeviceInterface->Flags, &hKey, pMonitorInfo, pPrev,
  795. ppPortUpdateInfo);
  796. }
  797. if ( hKey != INVALID_HANDLE_VALUE )
  798. RegCloseKey(hKey);
  799. }
  800. DWORD
  801. WalkPortList(
  802. PSETUPAPI_INFO pSetupApiInfo,
  803. PUSBMON_MONITOR_INFO pMonitorInfo,
  804. PPORT_UPDATE_INFO *ppPortUpdateInfo
  805. )
  806. {
  807. DWORD dwIndex, dwLastError, dwSize, dwNeeded;
  808. HANDLE hToken;
  809. HDEVINFO hDevList = INVALID_HANDLE_VALUE;
  810. PUSBMON_PORT_INFO pPtr;
  811. PUSELESS_PORT_INFO pCur, pPrev;
  812. SP_DEVICE_INTERFACE_DATA DeviceInterface;
  813. PSP_DEVICE_INTERFACE_DETAIL_DATA pDeviceDetail = NULL;
  814. #ifdef MYDEBUG
  815. DWORD dwTime;
  816. #endif
  817. EnterCriticalSection(&pMonitorInfo->EnumPortsCS);
  818. hToken = RevertToPrinterSelf();
  819. hDevList = pSetupApiInfo->GetClassDevs((GUID *)&USB_PRINTER_GUID,
  820. NULL,
  821. NULL,
  822. DIGCF_INTERFACEDEVICE);
  823. if ( hDevList == INVALID_HANDLE_VALUE ) {
  824. dwLastError = GetLastError();
  825. goto Done;
  826. }
  827. dwSize = sizeof(PSP_DEVICE_INTERFACE_DETAIL_DATA)
  828. + MAX_DEVICE_PATH * sizeof(TCHAR);
  829. pDeviceDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA) AllocSplMem(dwSize);
  830. if ( !pDeviceDetail ) {
  831. dwLastError = GetLastError();
  832. goto Done;
  833. }
  834. dwLastError = ERROR_SUCCESS;
  835. dwIndex = 0;
  836. pDeviceDetail->cbSize = sizeof(*pDeviceDetail);
  837. DeviceInterface.cbSize = sizeof(DeviceInterface);
  838. do {
  839. #ifdef MYDEBUG
  840. dwTime = GetTickCount();
  841. #endif
  842. if ( !pSetupApiInfo->EnumDeviceInterfaces(hDevList,
  843. NULL,
  844. (GUID *)&USB_PRINTER_GUID,
  845. dwIndex,
  846. &DeviceInterface) ) {
  847. dwLastError = GetLastError();
  848. if ( dwLastError == ERROR_NO_MORE_ITEMS )
  849. break; // Normal exit
  850. DBGMSG(DBG_WARNING,
  851. ("usbmon: WalkPortList: SetupDiEnumDeviceInterfaces failed with %d for inderx %d\n",
  852. dwLastError, dwIndex));
  853. goto Next;
  854. }
  855. #ifdef MYDEBUG
  856. dwTime = GetTickCount() - dwTime;
  857. ++dwCount[1];
  858. dwTotalTime[1] += dwTime;
  859. dwTime = GetTickCount();
  860. #endif
  861. if ( !pSetupApiInfo->GetDeviceInterfaceDetail(hDevList,
  862. &DeviceInterface,
  863. pDeviceDetail,
  864. dwSize,
  865. &dwNeeded,
  866. NULL) ) {
  867. dwLastError = GetLastError();
  868. DBGMSG(DBG_ERROR,
  869. ("usbmon: WalkPortList: SetupDiGetDeviceInterfaceDetail failed with error %d size %d\n",
  870. dwLastError, dwNeeded));
  871. goto Next;
  872. }
  873. #ifdef MYDEBUG
  874. dwTime = GetTickCount() - dwTime;
  875. ++dwCount[2];
  876. dwTotalTime[2] += dwTime;
  877. #endif
  878. //
  879. // This is the only flag we care about
  880. //
  881. DeviceInterface.Flags &= SPINT_ACTIVE;
  882. //
  883. // For inactive port if it is already known as a useless port
  884. // no need to process further
  885. //
  886. if ( !(DeviceInterface.Flags & SPINT_ACTIVE) &&
  887. FindUselessEntry(pMonitorInfo, pDeviceDetail->DevicePath, &pPrev) ) {
  888. #ifdef MYDEBUG
  889. ++dwSkippedPorts;
  890. #endif
  891. goto Next;
  892. }
  893. //
  894. // When port active status did not change we should have nothing
  895. // to update. By skipping the PortUpdateInfo we avoid registry access
  896. // and it is a performance improvement
  897. //
  898. if ( (pPtr = FindPortUsingDevicePath(pMonitorInfo,
  899. pDeviceDetail->DevicePath)) &&
  900. DeviceInterface.Flags == pPtr->dwDeviceFlags ) {
  901. #ifdef MYDEBUG
  902. ++dwSkippedPorts;
  903. #endif
  904. goto Next;
  905. }
  906. ProcessPortInfo(pSetupApiInfo, pMonitorInfo, hDevList, &DeviceInterface,
  907. pDeviceDetail, ppPortUpdateInfo);
  908. Next:
  909. dwLastError = ERROR_SUCCESS;
  910. ++dwIndex;
  911. pDeviceDetail->cbSize = sizeof(*pDeviceDetail);
  912. DeviceInterface.cbSize = sizeof(DeviceInterface);
  913. } while ( dwLastError == ERROR_SUCCESS);
  914. if ( dwLastError == ERROR_NO_MORE_ITEMS )
  915. dwLastError = ERROR_SUCCESS;
  916. Done:
  917. LeaveCriticalSection(&pMonitorInfo->EnumPortsCS);
  918. if ( hDevList != INVALID_HANDLE_VALUE )
  919. pSetupApiInfo->DestroyDeviceInfoList(hDevList);
  920. ImpersonatePrinterClient(hToken);
  921. FreeSplMem(pDeviceDetail);
  922. return dwLastError;
  923. }
  924. LPBYTE
  925. CopyPortToBuf(
  926. PUSBMON_PORT_INFO pPortInfo,
  927. DWORD dwLevel,
  928. LPBYTE pPorts,
  929. LPBYTE pEnd
  930. )
  931. {
  932. DWORD dwLen;
  933. LPTSTR pszStr;
  934. LPPORT_INFO_1 pPortInfo1 = (LPPORT_INFO_1) pPorts;
  935. LPPORT_INFO_2 pPortInfo2 = (LPPORT_INFO_2) pPorts;
  936. switch (dwLevel) {
  937. case 2:
  938. dwLen = gdwMonitorNameSize;
  939. pEnd -= dwLen;
  940. pPortInfo2->pMonitorName = (LPTSTR)pEnd;
  941. lstrcpy(pPortInfo2->pMonitorName, cszMonitorName);
  942. dwLen = lstrlen(pPortInfo->szPortDescription) + 1;
  943. dwLen *= sizeof(TCHAR);
  944. pEnd -= dwLen;
  945. pPortInfo2->pDescription = (LPTSTR)pEnd;
  946. lstrcpy(pPortInfo2->pDescription, pPortInfo->szPortDescription);
  947. //
  948. // Fall through
  949. //
  950. case 1:
  951. dwLen = lstrlen(pPortInfo->szPortName) + 1;
  952. dwLen *= sizeof(TCHAR);
  953. pEnd -= dwLen;
  954. pPortInfo1->pName = (LPTSTR)pEnd;
  955. lstrcpy(pPortInfo1->pName, pPortInfo->szPortName);
  956. }
  957. return pEnd;
  958. }
  959. BOOL
  960. LoadSetupApiDll(
  961. PSETUPAPI_INFO pSetupInfo
  962. )
  963. {
  964. UINT uOldErrMode = SetErrorMode(SEM_FAILCRITICALERRORS);
  965. #ifdef MYDEBUG
  966. DWORD dwTime;
  967. dwTime = GetTickCount();
  968. #endif
  969. pSetupInfo->hSetupApi = LoadLibrary(TEXT("setupapi"));
  970. SetErrorMode(uOldErrMode);
  971. if ( !pSetupInfo->hSetupApi )
  972. return FALSE;
  973. (FARPROC) pSetupInfo->DestroyDeviceInfoList
  974. = GetProcAddress(pSetupInfo->hSetupApi,
  975. "SetupDiDestroyDeviceInfoList");
  976. (FARPROC) pSetupInfo->GetClassDevs
  977. = GetProcAddress(pSetupInfo->hSetupApi,
  978. "SetupDiGetClassDevsW");
  979. (FARPROC) pSetupInfo->EnumDeviceInterfaces
  980. = GetProcAddress(pSetupInfo->hSetupApi,
  981. "SetupDiEnumDeviceInterfaces");
  982. (FARPROC) pSetupInfo->GetDeviceInterfaceDetail
  983. = GetProcAddress(pSetupInfo->hSetupApi,
  984. "SetupDiGetDeviceInterfaceDetailW");
  985. (FARPROC) pSetupInfo->OpenDeviceInterfaceRegKey
  986. = GetProcAddress(pSetupInfo->hSetupApi,
  987. "SetupDiOpenDeviceInterfaceRegKey");
  988. if ( !pSetupInfo->DestroyDeviceInfoList ||
  989. !pSetupInfo->GetClassDevs ||
  990. !pSetupInfo->EnumDeviceInterfaces ||
  991. !pSetupInfo->GetDeviceInterfaceDetail ||
  992. !pSetupInfo->OpenDeviceInterfaceRegKey ) {
  993. SPLASSERT(FALSE);
  994. FreeLibrary(pSetupInfo->hSetupApi);
  995. pSetupInfo->hSetupApi = NULL;
  996. return FALSE;
  997. }
  998. #ifdef MYDEBUG
  999. dwTime = GetTickCount() - dwTime;
  1000. ++dwCount[5];
  1001. dwTotalTime[5] += dwTime;
  1002. #endif
  1003. return TRUE;
  1004. }
  1005. BOOL
  1006. WINAPI
  1007. USBMON_EnumPorts(
  1008. LPTSTR pszName,
  1009. DWORD dwLevel,
  1010. LPBYTE pPorts,
  1011. DWORD cbBuf,
  1012. LPDWORD pcbNeeded,
  1013. LPDWORD pcReturned
  1014. )
  1015. {
  1016. DWORD dwLastError = ERROR_SUCCESS, dwRequestIndex;
  1017. LPBYTE pEnd;
  1018. SETUPAPI_INFO SetupApiInfo;
  1019. PUSBMON_PORT_INFO pPortInfo;
  1020. PPORT_UPDATE_INFO pPortUpdateInfo = NULL;
  1021. #ifdef MYDEBUG
  1022. DWORD dwTime;
  1023. CHAR szBuf[200];
  1024. dwTime = GetTickCount();
  1025. #endif
  1026. dwRequestIndex = gUsbmonInfo.dwLastEnumIndex;
  1027. *pcbNeeded = *pcReturned = 0;
  1028. if ( dwLevel != 1 && dwLevel != 2 ) {
  1029. SetLastError(ERROR_INVALID_LEVEL);
  1030. return FALSE;
  1031. }
  1032. if ( !LoadSetupApiDll(&SetupApiInfo) )
  1033. return FALSE;
  1034. EnterCriticalSection(&gUsbmonInfo.EnumPortsCS);
  1035. if ( dwRequestIndex >= gUsbmonInfo.dwLastEnumIndex ) {
  1036. //
  1037. // No complete enumeration has occurred since this request was made.
  1038. // Since the request may be an indication that something has changed,
  1039. // the full reenumeration must be done.
  1040. //
  1041. // Updated the index of enumeration before actually doing the
  1042. // work so it will show up as the most conservative
  1043. //
  1044. // Consequence of rollover on gdwLastEnumIndex:
  1045. // Any threads that recorded 0xFFFFFFFF as the dwRequestIndex
  1046. // will show as greater than the new value 0 and therefore reenum
  1047. // gratuitously. Not very much extra work.
  1048. //
  1049. ++gUsbmonInfo.dwLastEnumIndex;
  1050. if ( dwLastError = WalkPortList(&SetupApiInfo, &gUsbmonInfo,
  1051. &pPortUpdateInfo) )
  1052. goto Done;
  1053. }
  1054. #ifdef MYDEBUG
  1055. else
  1056. ++dwSkippedEnumPorts;
  1057. #endif
  1058. for ( pPortInfo = gUsbmonInfo.pPortInfo ;
  1059. pPortInfo ;
  1060. pPortInfo = pPortInfo->pNext ) {
  1061. if ( dwLevel == 1 )
  1062. *pcbNeeded += sizeof(PORT_INFO_1) +
  1063. (lstrlen(pPortInfo->szPortName) + 1)
  1064. * sizeof(TCHAR);
  1065. else
  1066. *pcbNeeded += sizeof(PORT_INFO_2) +
  1067. gdwMonitorNameSize +
  1068. (lstrlen(pPortInfo->szPortName) + 1 +
  1069. lstrlen(pPortInfo->szPortDescription) + 1 )
  1070. * sizeof(TCHAR);
  1071. }
  1072. if ( cbBuf < *pcbNeeded ) {
  1073. dwLastError = ERROR_INSUFFICIENT_BUFFER;
  1074. goto Done;
  1075. }
  1076. pEnd = pPorts + cbBuf;
  1077. for ( pPortInfo = gUsbmonInfo.pPortInfo ;
  1078. pPortInfo ;
  1079. pPortInfo = pPortInfo->pNext ) {
  1080. pEnd = CopyPortToBuf(pPortInfo, dwLevel, pPorts, pEnd);
  1081. if ( dwLevel == 1 )
  1082. pPorts += sizeof(PORT_INFO_1);
  1083. else
  1084. pPorts += sizeof(PORT_INFO_2);
  1085. ++(*pcReturned);
  1086. }
  1087. SPLASSERT(pEnd >= pPorts);
  1088. Done:
  1089. PassPortUpdateListToBackgroundThread(&gUsbmonInfo, pPortUpdateInfo);
  1090. LeaveCriticalSection(&gUsbmonInfo.EnumPortsCS);
  1091. if ( SetupApiInfo.hSetupApi )
  1092. FreeLibrary(SetupApiInfo.hSetupApi);
  1093. ++gUsbmonInfo.dwEnumPortCount;
  1094. #ifdef MYDEBUG
  1095. dwTime = GetTickCount() - dwTime;
  1096. ++(dwCount[3]);
  1097. dwTotalTime[3] += dwTime;
  1098. sprintf(szBuf, "SetupDiOpenDeviceInterfaceRegKey: %d\n", dwTotalTime[0]/dwCount[0]);
  1099. OutputDebugStringA(szBuf);
  1100. sprintf(szBuf, "SetupDiSetupDiEnumDeviceInterfaces: %d\n", dwTotalTime[1]/dwCount[1]);
  1101. OutputDebugStringA(szBuf);
  1102. sprintf(szBuf, "SetupDiGetDeviceInterfaceDetail: %d\n", dwTotalTime[2]/dwCount[2]);
  1103. OutputDebugStringA(szBuf);
  1104. sprintf(szBuf, "EnumPorts: %d\n", dwTotalTime[3]/dwCount[3]);
  1105. OutputDebugStringA(szBuf);
  1106. sprintf(szBuf, "LoadSetupApi: %d\n", dwTotalTime[5]/dwCount[5]);
  1107. OutputDebugStringA(szBuf);
  1108. sprintf(szBuf, "Port updates per call %d\n", dwPortUpdates/gUsbmonInfo.dwEnumPortCount);
  1109. OutputDebugStringA(szBuf);
  1110. sprintf(szBuf, "Skipped port updates per call %d\n", dwSkippedPorts/gUsbmonInfo.dwEnumPortCount);
  1111. OutputDebugStringA(szBuf);
  1112. sprintf(szBuf, "Skipped enumport percentage %d\n", 100 * dwSkippedEnumPorts/gUsbmonInfo.dwEnumPortCount);
  1113. OutputDebugStringA(szBuf);
  1114. sprintf(szBuf, "Ports/Useless ports %d/%d\n", gUsbmonInfo.dwPortCount, gUsbmonInfo.dwUselessPortCount);
  1115. OutputDebugStringA(szBuf);
  1116. #endif
  1117. if ( dwLastError ) {
  1118. SetLastError(dwLastError);
  1119. return FALSE;
  1120. } else
  1121. return TRUE;
  1122. }