Source code of Windows XP (NT5)
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.

762 lines
20 KiB

  1. /*++
  2. Copyright (c) 1994 Microsoft Corporation
  3. Module Name:
  4. msvctbl.c
  5. Abstract:
  6. Master Service Table routines. Handles all access to the master service
  7. table kept for per-seat information.
  8. =========================== DATA STRUCTURES =============================
  9. MasterServiceTable (PMASTER_SERVICE_RECORD *)
  10. MasterServiceList (PMASTER_SERVICE_RECORD *)
  11. Each of these points to an array of pointers to dynamically allocated
  12. MASTER_SERVICE_RECORDs. There is exactly one MASTER_SERVICE_RECORD
  13. for each (product, version) pairing; e.g., (SQL 4.0, SNA 2.0,
  14. SNA 2.1). The MasterServiceTable is never re-ordered, so a valid
  15. index into this table is guaranteed to always dereference to the same
  16. (product, version). The MasterServiceList contains the same data
  17. sorted lexicographically by product name (and therefore the data
  18. pointed to by a specific index may change over time as new
  19. (product, version) pairs are added to the table). Each table
  20. contains MasterServiceListSize entries.
  21. RootServiceList (PMASTER_SERVICE_ROOT *)
  22. This points to an array of pointers to dynamically allocated
  23. MASTER_SERVICE_ROOTs. There is exactly one MASTER_SERVICE_ROOT
  24. for each product family. Each MASTER_SERVICE_ROOT contains a
  25. pointer to an array of indices into the MasterServiceTable
  26. corresponding to all the products in the family, sorted by
  27. ascending version number. The RootServiceList itself is
  28. sorted lexicographically by ascending family name. It contains
  29. RootServiceListSize entries.
  30. Author:
  31. Arthur Hanson (arth) 07-Dec-1994
  32. Revision History:
  33. Jeff Parham (jeffparh) 05-Dec-1995
  34. o Added a few comments.
  35. o Added parameter to LicenseServiceListFind().
  36. o Fixed benign bug in memory allocation:
  37. sizeof(PULONG) -> sizeof(ULONG).
  38. --*/
  39. #include <stdlib.h>
  40. #include <nt.h>
  41. #include <ntrtl.h>
  42. #include <nturtl.h>
  43. #include <windows.h>
  44. #include <dsgetdc.h>
  45. #include "llsapi.h"
  46. #include "debug.h"
  47. #include "llssrv.h"
  48. #include "registry.h"
  49. #include "ntlsapi.h"
  50. #include "mapping.h"
  51. #include "msvctbl.h"
  52. #include "svctbl.h"
  53. #include "purchase.h"
  54. #include "perseat.h"
  55. /////////////////////////////////////////////////////////////////////////
  56. //
  57. // Master Service Table - Keeps list of products (SQL, SNA, etc.) with a
  58. // sub-list for each version of the product.
  59. //
  60. /////////////////////////////////////////////////////////////////////////
  61. /////////////////////////////////////////////////////////////////////////
  62. /////////////////////////////////////////////////////////////////////////
  63. #define DEFAULT_SERVICE_TABLE_ENTRIES 10
  64. ULONG RootServiceListSize = 0;
  65. PMASTER_SERVICE_ROOT *RootServiceList = NULL;
  66. ULONG MasterServiceListSize = 0;
  67. PMASTER_SERVICE_RECORD *MasterServiceList = NULL;
  68. PMASTER_SERVICE_RECORD *MasterServiceTable = NULL;
  69. TCHAR BackOfficeStr[100];
  70. PMASTER_SERVICE_RECORD BackOfficeRec;
  71. RTL_RESOURCE MasterServiceListLock;
  72. HANDLE gLlsDllHandle = NULL;
  73. /////////////////////////////////////////////////////////////////////////
  74. NTSTATUS
  75. MasterServiceListInit()
  76. /*++
  77. Routine Description:
  78. Creates the Master Service table, used for tracking the services and
  79. session count. This will pull the initial services from the registry.
  80. The table is linear so a binary search can be used on the table, so
  81. some extra records are initialized so that each time we add a new
  82. service we don't have to do a realloc. We also assume that adding
  83. new services is a relatively rare occurance, since we need to sort
  84. it each time.
  85. The service table is guarded by a read and write semaphore. Multiple
  86. reads can occur, but a write blocks everything.
  87. The service table has two default entries for FilePrint and REMOTE_ACCESS.
  88. Arguments:
  89. None.
  90. Return Value:
  91. None.
  92. --*/
  93. {
  94. int nLen;
  95. NTSTATUS status = STATUS_SUCCESS;
  96. try
  97. {
  98. RtlInitializeResource(&MasterServiceListLock);
  99. } except(EXCEPTION_EXECUTE_HANDLER ) {
  100. status = GetExceptionCode();
  101. }
  102. if (!NT_SUCCESS(status))
  103. return status;
  104. memset(BackOfficeStr, 0, sizeof(BackOfficeStr));
  105. BackOfficeRec = NULL;
  106. gLlsDllHandle = LoadLibrary(TEXT("LLSRPC.DLL"));
  107. if (gLlsDllHandle != NULL) {
  108. nLen = LoadString(gLlsDllHandle, IDS_BACKOFFICE, BackOfficeStr, sizeof(BackOfficeStr)/sizeof(TCHAR));
  109. if (nLen != 0) {
  110. BackOfficeRec = MasterServiceListAdd( BackOfficeStr, BackOfficeStr, 0 );
  111. if (NULL == BackOfficeRec)
  112. status = STATUS_NO_MEMORY;
  113. } else {
  114. #if DBG
  115. dprintf(TEXT("LLS ERROR: Could not load BackOffice string\n"));
  116. #endif
  117. status = GetLastError();
  118. }
  119. status = GetLastError();
  120. }
  121. return status;
  122. } // MasterServiceListInit
  123. /////////////////////////////////////////////////////////////////////////
  124. // used by qsort to sort MasterServiceList by product name
  125. int __cdecl MasterServiceListCompare(const void *arg1, const void *arg2) {
  126. PMASTER_SERVICE_RECORD Svc1, Svc2;
  127. Svc1 = (PMASTER_SERVICE_RECORD) *((PMASTER_SERVICE_RECORD *) arg1);
  128. Svc2 = (PMASTER_SERVICE_RECORD) *((PMASTER_SERVICE_RECORD *) arg2);
  129. return lstrcmpi( Svc1->Name, Svc2->Name );
  130. } // MasterServiceListCompare
  131. /////////////////////////////////////////////////////////////////////////
  132. // used by qsort to sort the Services array of indices pointed to by the
  133. // MASTER_SERVICE_ROOT structure by product version number
  134. int __cdecl MServiceRecordCompare(const void *arg1, const void *arg2) {
  135. PMASTER_SERVICE_RECORD Svc1, Svc2;
  136. Svc1 = (PMASTER_SERVICE_RECORD) MasterServiceTable[*((PULONG) arg1)];
  137. Svc2 = (PMASTER_SERVICE_RECORD) MasterServiceTable[*((PULONG) arg2)];
  138. return (int) Svc1->Version - Svc2->Version;
  139. } // MServiceRecordCompare
  140. /////////////////////////////////////////////////////////////////////////
  141. // used by qsort to sort the RootServiceList array of product families
  142. // by family name
  143. int __cdecl MServiceRootCompare(const void *arg1, const void *arg2) {
  144. PMASTER_SERVICE_ROOT Svc1, Svc2;
  145. Svc1 = (PMASTER_SERVICE_ROOT) *((PMASTER_SERVICE_ROOT *) arg1);
  146. Svc2 = (PMASTER_SERVICE_ROOT) *((PMASTER_SERVICE_ROOT *) arg2);
  147. return lstrcmpi( Svc1->Name, Svc2->Name );
  148. } // MServiceRootCompare
  149. /////////////////////////////////////////////////////////////////////////
  150. PMASTER_SERVICE_ROOT
  151. MServiceRootFind(
  152. LPTSTR ServiceName
  153. )
  154. /*++
  155. Routine Description:
  156. Internal routine to actually do binary search on MasterServiceList, this
  157. does not do any locking as we expect the wrapper routine to do this.
  158. The search is a simple binary search.
  159. Arguments:
  160. ServiceName -
  161. Return Value:
  162. Pointer to found service table entry or NULL if not found.
  163. --*/
  164. {
  165. LONG begin = 0;
  166. LONG end = (LONG) RootServiceListSize - 1;
  167. LONG cur;
  168. int match;
  169. PMASTER_SERVICE_ROOT ServiceRoot;
  170. #if DBG
  171. if (TraceFlags & TRACE_FUNCTION_TRACE)
  172. dprintf(TEXT("LLS TRACE: MServiceRootFind\n"));
  173. #endif
  174. if ((RootServiceListSize == 0) || (ServiceName == NULL))
  175. return NULL;
  176. while (end >= begin) {
  177. // go halfway in-between
  178. cur = (begin + end) / 2;
  179. ServiceRoot = RootServiceList[cur];
  180. // compare the two result into match
  181. match = lstrcmpi(ServiceName, ServiceRoot->Name);
  182. if (match < 0)
  183. // move new begin
  184. end = cur - 1;
  185. else
  186. begin = cur + 1;
  187. if (match == 0)
  188. return ServiceRoot;
  189. }
  190. return NULL;
  191. } // MServiceRootFind
  192. /////////////////////////////////////////////////////////////////////////
  193. PMASTER_SERVICE_RECORD
  194. MasterServiceListFind(
  195. LPTSTR Name
  196. )
  197. /*++
  198. Routine Description:
  199. Internal routine to actually do binary search on MasterServiceList, this
  200. does not do any locking as we expect the wrapper routine to do this.
  201. The search is a simple binary search.
  202. Arguments:
  203. ServiceName -
  204. Return Value:
  205. Pointer to found service table entry or NULL if not found.
  206. --*/
  207. {
  208. LONG begin = 0;
  209. LONG end;
  210. LONG cur;
  211. int match;
  212. PMASTER_SERVICE_RECORD Service;
  213. #if DBG
  214. if (TraceFlags & TRACE_FUNCTION_TRACE)
  215. dprintf(TEXT("LLS TRACE: MasterServiceListFind\n"));
  216. #endif
  217. if ((Name == NULL) || (MasterServiceListSize == 0))
  218. return NULL;
  219. end = (LONG) MasterServiceListSize - 1;
  220. while (end >= begin) {
  221. // go halfway in-between
  222. cur = (begin + end) / 2;
  223. Service = MasterServiceList[cur];
  224. // compare the two result into match
  225. match = lstrcmpi(Name, Service->Name);
  226. if (match < 0)
  227. // move new begin
  228. end = cur - 1;
  229. else
  230. begin = cur + 1;
  231. if (match == 0)
  232. return Service;
  233. }
  234. return NULL;
  235. } // MasterServiceListFind
  236. /////////////////////////////////////////////////////////////////////////
  237. PMASTER_SERVICE_RECORD
  238. MasterServiceListAdd(
  239. LPTSTR FamilyName,
  240. LPTSTR Name,
  241. DWORD Version
  242. )
  243. /*++
  244. Routine Description:
  245. Arguments:
  246. ServiceName -
  247. Return Value:
  248. Pointer to added service table entry, or NULL if failed.
  249. --*/
  250. {
  251. ULONG i;
  252. ULONG SessionLimit = 0;
  253. BOOL PerSeatLicensing = FALSE;
  254. LPTSTR NewServiceName, pDisplayName;
  255. PMASTER_SERVICE_RECORD Service = NULL;
  256. PMASTER_SERVICE_ROOT ServiceRoot = NULL;
  257. PULONG ServiceList;
  258. PLICENSE_SERVICE_RECORD pLicense;
  259. PMASTER_SERVICE_ROOT *pRootServiceListTmp;
  260. PULONG pServiceListTmp;
  261. PMASTER_SERVICE_RECORD *pMasterServiceListTmp, *pMasterServiceTableTmp;
  262. #if DBG
  263. if (TraceFlags & TRACE_FUNCTION_TRACE)
  264. dprintf(TEXT("LLS TRACE: MasterServiceListAdd\n"));
  265. #endif
  266. if ((FamilyName == NULL) || (Name == NULL))
  267. {
  268. return NULL;
  269. }
  270. //
  271. // Mask off low word of version - as it doesn't matter to licensing
  272. //
  273. Version &= 0xFFFF0000;
  274. //
  275. // Try to find a root node for that family of products
  276. //
  277. ServiceRoot = MServiceRootFind(FamilyName);
  278. if (ServiceRoot == NULL) {
  279. //
  280. // No root record - so create a new one
  281. //
  282. if (RootServiceList == NULL)
  283. pRootServiceListTmp = (PMASTER_SERVICE_ROOT *) LocalAlloc(LPTR, sizeof(PMASTER_SERVICE_ROOT));
  284. else
  285. pRootServiceListTmp = (PMASTER_SERVICE_ROOT *) LocalReAlloc(RootServiceList, sizeof(PMASTER_SERVICE_ROOT) * (RootServiceListSize + 1), LHND);
  286. //
  287. // Make sure we could allocate service table
  288. //
  289. if (pRootServiceListTmp == NULL) {
  290. return NULL;
  291. } else {
  292. RootServiceList = pRootServiceListTmp;
  293. }
  294. //
  295. // Allocate space for Root.
  296. //
  297. ServiceRoot = (PMASTER_SERVICE_ROOT) LocalAlloc(LPTR, sizeof(MASTER_SERVICE_ROOT));
  298. if (ServiceRoot == NULL) {
  299. return NULL;
  300. }
  301. NewServiceName = (LPTSTR) LocalAlloc(LPTR, (lstrlen(FamilyName) + 1) * sizeof(TCHAR));
  302. if (NewServiceName == NULL) {
  303. LocalFree(ServiceRoot);
  304. return NULL;
  305. }
  306. try
  307. {
  308. RtlInitializeResource(&ServiceRoot->ServiceLock);
  309. } except(EXCEPTION_EXECUTE_HANDLER ) {
  310. LocalFree(ServiceRoot);
  311. LocalFree(NewServiceName);
  312. return NULL;
  313. }
  314. RootServiceList[RootServiceListSize] = ServiceRoot;
  315. // now copy it over...
  316. ServiceRoot->Name = NewServiceName;
  317. lstrcpy(NewServiceName, FamilyName);
  318. //
  319. // Initialize stuff for list of various versions of this product
  320. //
  321. ServiceRoot->ServiceTableSize = 0;
  322. ServiceRoot->Services = NULL;
  323. ServiceRoot->Flags = 0;
  324. RootServiceListSize++;
  325. // Have added the entry - now need to sort it in order of the service names
  326. qsort((void *) RootServiceList, (size_t) RootServiceListSize, sizeof(PMASTER_SERVICE_ROOT), MServiceRootCompare);
  327. }
  328. RtlAcquireResourceShared(&ServiceRoot->ServiceLock, TRUE);
  329. Service = MasterServiceListFind(Name);
  330. RtlReleaseResource(&ServiceRoot->ServiceLock);
  331. if (Service != NULL)
  332. return Service;
  333. ////////////////////////////////////////////////////////////////////////
  334. //
  335. // Whether added or found, ServiceRoot points to the Root Node entry.
  336. // Now double check to see if another thread just got done adding the
  337. // actual service before we got the write lock.
  338. //
  339. RtlAcquireResourceShared(&ServiceRoot->ServiceLock, TRUE);
  340. Service = MasterServiceListFind(Name);
  341. if (Service == NULL) {
  342. //
  343. // No Service Record - so create a new one
  344. //
  345. RtlConvertSharedToExclusive(&ServiceRoot->ServiceLock);
  346. //
  347. // Double-check that no one snuck in and created it
  348. //
  349. Service = MasterServiceListFind(Name);
  350. if (Service == NULL) {
  351. ServiceList = ServiceRoot->Services;
  352. if (ServiceList == NULL)
  353. pServiceListTmp = (PULONG) LocalAlloc(LPTR, sizeof(ULONG));
  354. else
  355. pServiceListTmp = (PULONG) LocalReAlloc(ServiceList, sizeof(ULONG) * (ServiceRoot->ServiceTableSize + 1), LHND);
  356. if (MasterServiceList == NULL) {
  357. pMasterServiceListTmp = (PMASTER_SERVICE_RECORD *) LocalAlloc(LPTR, sizeof(PMASTER_SERVICE_RECORD));
  358. pMasterServiceTableTmp = (PMASTER_SERVICE_RECORD *) LocalAlloc(LPTR, sizeof(PMASTER_SERVICE_RECORD));
  359. } else {
  360. pMasterServiceListTmp = (PMASTER_SERVICE_RECORD *) LocalReAlloc(MasterServiceList, sizeof(PMASTER_SERVICE_RECORD) * (MasterServiceListSize + 1), LHND);
  361. pMasterServiceTableTmp = (PMASTER_SERVICE_RECORD *) LocalReAlloc(MasterServiceTable, sizeof(PMASTER_SERVICE_RECORD) * (MasterServiceListSize + 1), LHND);
  362. }
  363. //
  364. // Make sure we could allocate service table
  365. //
  366. if ((pServiceListTmp == NULL) || (pMasterServiceListTmp == NULL) || (pMasterServiceTableTmp == NULL)) {
  367. if (pServiceListTmp != NULL)
  368. LocalFree(pServiceListTmp);
  369. if (pMasterServiceListTmp != NULL)
  370. LocalFree(pMasterServiceListTmp);
  371. if (pMasterServiceTableTmp != NULL)
  372. LocalFree(pMasterServiceTableTmp);
  373. RtlReleaseResource(&ServiceRoot->ServiceLock);
  374. return NULL;
  375. } else {
  376. ServiceList = pServiceListTmp;
  377. MasterServiceList = pMasterServiceListTmp;
  378. MasterServiceTable = pMasterServiceTableTmp;
  379. }
  380. ServiceRoot->Services = ServiceList;
  381. //
  382. // Allocate space for saving off Service Record.
  383. //
  384. Service = (PMASTER_SERVICE_RECORD) LocalAlloc(LPTR, sizeof(MASTER_SERVICE_RECORD));
  385. if (Service == NULL) {
  386. ASSERT(FALSE);
  387. RtlReleaseResource(&ServiceRoot->ServiceLock);
  388. return NULL;
  389. }
  390. //
  391. // ...DisplayName
  392. //
  393. NewServiceName = (LPTSTR) LocalAlloc(LPTR, (lstrlen(Name) + 1) * sizeof(TCHAR));
  394. if (NewServiceName == NULL) {
  395. ASSERT(FALSE);
  396. LocalFree(Service);
  397. RtlReleaseResource(&ServiceRoot->ServiceLock);
  398. return NULL;
  399. }
  400. ServiceList[ServiceRoot->ServiceTableSize] = MasterServiceListSize;
  401. MasterServiceList[MasterServiceListSize] = Service;
  402. MasterServiceTable[MasterServiceListSize] = Service;
  403. // now copy it over...
  404. Service->Name = NewServiceName;
  405. lstrcpy(NewServiceName, Name);
  406. //
  407. // Init rest of values.
  408. //
  409. Service->Version= Version;
  410. Service->LicensesUsed = 0;
  411. Service->LicensesClaimed = 0;
  412. Service->next = 0;
  413. Service->Index = MasterServiceListSize;
  414. Service->Family = ServiceRoot;
  415. pLicense = LicenseServiceListFind(Service->Name, FALSE);
  416. if (pLicense == NULL)
  417. Service->Licenses = 0;
  418. else
  419. Service->Licenses = pLicense->NumberLicenses;
  420. //
  421. // Init next pointer
  422. //
  423. i = 0;
  424. while ((i < ServiceRoot->ServiceTableSize) && (MasterServiceTable[ServiceRoot->Services[i]]->Version < Version))
  425. i++;
  426. if (i > 0) {
  427. Service->next = MasterServiceTable[ServiceRoot->Services[i - 1]]->next;
  428. MasterServiceTable[ServiceRoot->Services[i - 1]]->next = Service->Index + 1;
  429. }
  430. ServiceRoot->ServiceTableSize++;
  431. MasterServiceListSize++;
  432. // Have added the entry - now need to sort it in order of the versions
  433. qsort((void *) ServiceList, (size_t) ServiceRoot->ServiceTableSize, sizeof(ULONG), MServiceRecordCompare);
  434. // And sort the list the UI uses (sorted by service name)
  435. qsort((void *) MasterServiceList, (size_t) MasterServiceListSize, sizeof(PMASTER_SERVICE_RECORD), MasterServiceListCompare);
  436. }
  437. }
  438. RtlReleaseResource(&ServiceRoot->ServiceLock);
  439. return Service;
  440. } // MasterServiceListAdd
  441. #if DBG
  442. /////////////////////////////////////////////////////////////////////////
  443. //
  444. // DEBUG INFORMATION DUMP ROUTINES
  445. //
  446. /////////////////////////////////////////////////////////////////////////
  447. /////////////////////////////////////////////////////////////////////////
  448. VOID
  449. MasterServiceRootDebugDump( )
  450. /*++
  451. Routine Description:
  452. Arguments:
  453. Return Value:
  454. --*/
  455. {
  456. ULONG i = 0;
  457. //
  458. // Need to scan list so get read access.
  459. //
  460. RtlAcquireResourceShared(&MasterServiceListLock, TRUE);
  461. dprintf(TEXT("Service Family Table, # Entries: %lu\n"), RootServiceListSize);
  462. if (RootServiceList == NULL)
  463. goto MasterServiceRootDebugDumpExit;
  464. for (i = 0; i < RootServiceListSize; i++) {
  465. dprintf(TEXT("%3lu) Services: %3lu Svc: %s [%s]\n"),
  466. i + 1, RootServiceList[i]->ServiceTableSize, RootServiceList[i]->Name, RootServiceList[i]->Name);
  467. }
  468. MasterServiceRootDebugDumpExit:
  469. RtlReleaseResource(&MasterServiceListLock);
  470. return;
  471. } // MasterServiceRootDebugDump
  472. /////////////////////////////////////////////////////////////////////////
  473. VOID
  474. MasterServiceRootDebugInfoDump( PVOID Data )
  475. /*++
  476. Routine Description:
  477. Arguments:
  478. Return Value:
  479. --*/
  480. {
  481. ULONG i = 0;
  482. //
  483. // Need to scan list so get read access.
  484. //
  485. RtlAcquireResourceShared(&MasterServiceListLock, TRUE);
  486. dprintf(TEXT("Service Family Table, # Entries: %lu\n"), RootServiceListSize);
  487. if (RootServiceList == NULL)
  488. goto MasterServiceRootDebugDumpExit;
  489. for (i = 0; i < RootServiceListSize; i++) {
  490. dprintf(TEXT("%3lu) Services: %3lu Svc: %s [%s]\n"),
  491. i + 1, RootServiceList[i]->ServiceTableSize, RootServiceList[i]->Name, RootServiceList[i]->Name);
  492. }
  493. MasterServiceRootDebugDumpExit:
  494. RtlReleaseResource(&MasterServiceListLock);
  495. return;
  496. } // MasterServiceRootDebugInfoDump
  497. /////////////////////////////////////////////////////////////////////////
  498. VOID
  499. MasterServiceListDebugDump( )
  500. /*++
  501. Routine Description:
  502. Arguments:
  503. Return Value:
  504. --*/
  505. {
  506. ULONG i = 0;
  507. //
  508. // Need to scan list so get read access.
  509. //
  510. RtlAcquireResourceShared(&MasterServiceListLock, TRUE);
  511. dprintf(TEXT("Master Service Table, # Entries: %lu\n"), MasterServiceListSize);
  512. if (MasterServiceList == NULL)
  513. goto MasterServiceListDebugDumpExit;
  514. for (i = 0; i < MasterServiceListSize; i++) {
  515. dprintf(TEXT("%3lu) [%3lu] LU: %4lu LP: %4lu LC: %4lu MS: %4lu HM: %4lu Next: %3lu Svc: %s %lX\n"),
  516. i + 1, MasterServiceList[i]->Index,
  517. MasterServiceList[i]->LicensesUsed, MasterServiceList[i]->Licenses, MasterServiceList[i]->LicensesClaimed,
  518. MasterServiceList[i]->MaxSessionCount, MasterServiceList[i]->HighMark,
  519. MasterServiceList[i]->next, MasterServiceList[i]->Name, MasterServiceList[i]->Version);
  520. }
  521. MasterServiceListDebugDumpExit:
  522. RtlReleaseResource(&MasterServiceListLock);
  523. return;
  524. } // MasterServiceListDebugDump
  525. /////////////////////////////////////////////////////////////////////////
  526. VOID
  527. MasterServiceListDebugInfoDump( PVOID Data )
  528. /*++
  529. Routine Description:
  530. Arguments:
  531. Return Value:
  532. --*/
  533. {
  534. PMASTER_SERVICE_RECORD CurrentRecord = NULL;
  535. dprintf(TEXT("Master Service Table, # Entries: %lu\n"), RootServiceListSize);
  536. if (lstrlen((LPWSTR) Data) > 0) {
  537. // CurrentRecord = MasterServiceListFind((LPWSTR) Data);
  538. if (CurrentRecord != NULL) {
  539. }
  540. }
  541. } // MasterServiceListDebugInfoDump
  542. #endif