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.

795 lines
21 KiB

  1. /*++
  2. Copyright (c) 1998 Microsoft Corporation
  3. Module Name:
  4. syspart.c
  5. Abstract:
  6. Routines to determine the system partition on x86 machines.
  7. Author:
  8. Ted Miller (tedm) 30-June-1994
  9. Revision History:
  10. 24-Apr-1997 scotthal
  11. re-horked for system applet
  12. --*/
  13. #include "sysdm.h"
  14. #include <ntdddisk.h>
  15. BOOL g_fInitialized = FALSE;
  16. //
  17. // NT-specific routines we use from ntdll.dll
  18. //
  19. //NTSYSAPI
  20. NTSTATUS
  21. (NTAPI *NtOpenSymLinkRoutine)(
  22. OUT PHANDLE LinkHandle,
  23. IN ACCESS_MASK DesiredAccess,
  24. IN POBJECT_ATTRIBUTES ObjectAttributes
  25. );
  26. //NTSYSAPI
  27. NTSTATUS
  28. (NTAPI *NtQuerSymLinkRoutine)(
  29. IN HANDLE LinkHandle,
  30. IN OUT PUNICODE_STRING LinkTarget,
  31. OUT PULONG ReturnedLength OPTIONAL
  32. );
  33. //NTSYSAPI
  34. NTSTATUS
  35. (NTAPI *NtQuerDirRoutine) (
  36. IN HANDLE DirectoryHandle,
  37. OUT PVOID Buffer,
  38. IN ULONG Length,
  39. IN BOOLEAN ReturnSingleEntry,
  40. IN BOOLEAN RestartScan,
  41. IN OUT PULONG Context,
  42. OUT PULONG ReturnLength OPTIONAL
  43. );
  44. //NTSYSAPI
  45. NTSTATUS
  46. (NTAPI *NtOpenDirRoutine) (
  47. OUT PHANDLE DirectoryHandle,
  48. IN ACCESS_MASK DesiredAccess,
  49. IN POBJECT_ATTRIBUTES ObjectAttributes
  50. );
  51. BOOL
  52. MatchNTSymbolicPaths(
  53. PCTSTR lpDeviceName,
  54. PCTSTR lpSysPart,
  55. PCTSTR lpMatchName
  56. );
  57. IsNEC98(
  58. VOID
  59. )
  60. {
  61. static BOOL Checked = FALSE;
  62. static BOOL Is98;
  63. if(!Checked) {
  64. Is98 = ((GetKeyboardType(0) == 7) && ((GetKeyboardType(1) & 0xff00) == 0x0d00));
  65. Checked = TRUE;
  66. }
  67. return(Is98);
  68. }
  69. BOOL
  70. GetPartitionInfo(
  71. IN TCHAR Drive,
  72. OUT PPARTITION_INFORMATION PartitionInfo
  73. )
  74. /*++
  75. Routine Description:
  76. Fill in a PARTITION_INFORMATION structure with information about
  77. a particular drive.
  78. Arguments:
  79. Drive - supplies drive letter whose partition info is desired.
  80. PartitionInfo - upon success, receives partition info for Drive.
  81. Return Value:
  82. Boolean value indicating whether PartitionInfo has been filled in.
  83. --*/
  84. {
  85. TCHAR DriveName[8];
  86. HANDLE hDisk;
  87. BOOL fRet;
  88. DWORD DataSize;
  89. if (FAILED(PathBuildFancyRoot(DriveName, ARRAYSIZE(DriveName), Drive)))
  90. {
  91. fRet = FALSE;
  92. }
  93. else
  94. {
  95. hDisk = CreateFile(
  96. DriveName,
  97. GENERIC_READ,
  98. FILE_SHARE_READ | FILE_SHARE_WRITE,
  99. NULL,
  100. OPEN_EXISTING,
  101. 0,
  102. NULL
  103. );
  104. if(hDisk == INVALID_HANDLE_VALUE)
  105. {
  106. fRet = FALSE;
  107. }
  108. else
  109. {
  110. fRet = DeviceIoControl(
  111. hDisk,
  112. IOCTL_DISK_GET_PARTITION_INFO,
  113. NULL,
  114. 0,
  115. PartitionInfo,
  116. sizeof(PARTITION_INFORMATION),
  117. &DataSize,
  118. NULL
  119. );
  120. CloseHandle(hDisk);
  121. }
  122. }
  123. return fRet;
  124. }
  125. BOOL
  126. ArcPathToNtPath(
  127. IN LPCTSTR arcPathParam,
  128. OUT LPTSTR NtPath,
  129. IN UINT NtPathBufferLen
  130. )
  131. {
  132. BOOL fRet = FALSE;
  133. WCHAR arcPath[256];
  134. UNICODE_STRING UnicodeString;
  135. OBJECT_ATTRIBUTES Obja;
  136. HANDLE ObjectHandle;
  137. NTSTATUS Status;
  138. WCHAR Buffer[512];
  139. PWSTR ntPath;
  140. if (SUCCEEDED(StringCchCopy(arcPath, ARRAYSIZE(arcPath), L"\\ArcName\\")) &&
  141. SUCCEEDED(StringCchCat(arcPath, ARRAYSIZE(arcPath), arcPathParam)))
  142. {
  143. UnicodeString.Buffer = arcPath;
  144. UnicodeString.Length = (USHORT)lstrlenW(arcPath)*sizeof(WCHAR);
  145. UnicodeString.MaximumLength = UnicodeString.Length + sizeof(WCHAR);
  146. InitializeObjectAttributes(
  147. &Obja,
  148. &UnicodeString,
  149. OBJ_CASE_INSENSITIVE,
  150. NULL,
  151. NULL
  152. );
  153. Status = (*NtOpenSymLinkRoutine)(
  154. &ObjectHandle,
  155. READ_CONTROL | SYMBOLIC_LINK_QUERY,
  156. &Obja
  157. );
  158. if(NT_SUCCESS(Status))
  159. {
  160. //
  161. // Query the object to get the link target.
  162. //
  163. UnicodeString.Buffer = Buffer;
  164. UnicodeString.Length = 0;
  165. UnicodeString.MaximumLength = sizeof(Buffer)-sizeof(WCHAR);
  166. Status = (*NtQuerSymLinkRoutine)(ObjectHandle,&UnicodeString,NULL);
  167. CloseHandle(ObjectHandle);
  168. if(NT_SUCCESS(Status))
  169. {
  170. Buffer[UnicodeString.Length/sizeof(WCHAR)] = 0;
  171. if (SUCCEEDED(StringCchCopy(NtPath, NtPathBufferLen, Buffer)))
  172. {
  173. fRet = TRUE;
  174. }
  175. }
  176. }
  177. }
  178. return fRet;
  179. }
  180. BOOL
  181. AppearsToBeSysPart(
  182. IN PDRIVE_LAYOUT_INFORMATION DriveLayout,
  183. IN TCHAR Drive
  184. )
  185. {
  186. PARTITION_INFORMATION PartitionInfo,*p;
  187. BOOL IsPrimary;
  188. UINT i;
  189. DWORD d;
  190. LPTSTR BootFiles[] = { TEXT("BOOT.INI"),
  191. TEXT("NTLDR"),
  192. TEXT("NTDETECT.COM"),
  193. NULL
  194. };
  195. TCHAR FileName[MAX_PATH];
  196. Drive = (TCHAR)tolower(Drive);
  197. ASSERT(Drive >= 'a' || Drive <= 'z');
  198. //
  199. // Get partition information for this partition.
  200. //
  201. if(!GetPartitionInfo(Drive,&PartitionInfo)) {
  202. return(FALSE);
  203. }
  204. //
  205. // See if the drive is a primary partition.
  206. //
  207. IsPrimary = FALSE;
  208. for(i=0; i<min(DriveLayout->PartitionCount,4); i++) {
  209. p = &DriveLayout->PartitionEntry[i];
  210. if((p->PartitionType != PARTITION_ENTRY_UNUSED)
  211. && (p->StartingOffset.QuadPart == PartitionInfo.StartingOffset.QuadPart)
  212. && (p->PartitionLength.QuadPart == PartitionInfo.PartitionLength.QuadPart)) {
  213. IsPrimary = TRUE;
  214. break;
  215. }
  216. }
  217. if(!IsPrimary) {
  218. return(FALSE);
  219. }
  220. //
  221. // Don't rely on the active partition flag. This could easily not be accurate
  222. // (like user is using os/2 boot manager, for example).
  223. //
  224. //
  225. // See whether all NT boot files are present on this drive.
  226. //
  227. for(i=0; BootFiles[i]; i++)
  228. {
  229. PathBuildRoot(FileName, Drive-'a');
  230. if (!PathAppend(FileName, BootFiles[i]) ||
  231. !PathFileExists(FileName))
  232. {
  233. return(FALSE);
  234. }
  235. }
  236. return(TRUE);
  237. }
  238. DWORD
  239. QueryHardDiskNumber(
  240. IN TCHAR DriveLetter
  241. )
  242. {
  243. DWORD dwRet = -1;
  244. TCHAR driveName[10];
  245. HANDLE h;
  246. BOOL b;
  247. STORAGE_DEVICE_NUMBER number;
  248. DWORD bytes;
  249. if (SUCCEEDED(PathBuildFancyRoot(driveName, ARRAYSIZE(driveName), tolower(DriveLetter) - 'a')))
  250. {
  251. h = CreateFile(driveName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
  252. NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
  253. INVALID_HANDLE_VALUE);
  254. if (INVALID_HANDLE_VALUE != h)
  255. {
  256. if (DeviceIoControl(h, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0,
  257. &number, sizeof(number), &bytes, NULL))
  258. {
  259. dwRet = number.DeviceNumber;
  260. }
  261. CloseHandle(h);
  262. }
  263. }
  264. return dwRet;
  265. }
  266. TCHAR
  267. x86DetermineSystemPartition(
  268. IN HWND ParentWindow
  269. )
  270. /*++
  271. Routine Description:
  272. Determine the system partition on x86 machines.
  273. On Win95, we always return C:. For NT, read on.
  274. The system partition is the primary partition on the boot disk.
  275. Usually this is the active partition on disk 0 and usually it's C:.
  276. However the user could have remapped drive letters and generally
  277. determining the system partition with 100% accuracy is not possible.
  278. The one thing we can be sure of is that the system partition is on
  279. the physical hard disk with the arc path multi(0)disk(0)rdisk(0).
  280. We can be sure of this because by definition this is the arc path
  281. for bios drive 0x80.
  282. This routine determines which drive letters represent drives on
  283. that physical hard drive, and checks each for the nt boot files.
  284. The first drive found with those files is assumed to be the system
  285. partition.
  286. If for some reason we cannot determine the system partition by the above
  287. method, we simply assume it's C:.
  288. Arguments:
  289. ParentWindow - supplies window handle for window to be the parent for
  290. any dialogs, etc.
  291. SysPartDrive - if successful, receives drive letter of system partition.
  292. Return Value:
  293. Boolean value indicating whether SysPartDrive has been filled in.
  294. If FALSE, the user will have been infomred about why.
  295. --*/
  296. {
  297. BOOL GotIt;
  298. TCHAR NtDevicePath[256];
  299. DWORD NtDevicePathLen;
  300. LPTSTR p;
  301. DWORD PhysicalDriveNumber;
  302. TCHAR Buffer[512];
  303. TCHAR FoundSystemPartition[20], temp[5];
  304. HANDLE hDisk;
  305. PDRIVE_LAYOUT_INFORMATION DriveLayout;
  306. DWORD DriveLayoutSize;
  307. TCHAR Drive;
  308. BOOL b;
  309. DWORD DataSize;
  310. DWORD BootPartitionNumber, cnt;
  311. PPARTITION_INFORMATION Start, i;
  312. if (!g_fInitialized) {
  313. GotIt = FALSE;
  314. goto c0;
  315. }
  316. if(IsNEC98())
  317. {
  318. *Buffer = TEXT('c'); // initialize to C in case GetWindowDirectory fails.
  319. if (!GetWindowsDirectory(Buffer, ARRAYSIZE(Buffer)))
  320. {
  321. *Buffer = TEXT('c'); // initialize to C in case GetWindowDirectory fails.
  322. }
  323. return Buffer[0];
  324. }
  325. //
  326. // The system partition must be on multi(0)disk(0)rdisk(0)
  327. // If we can't translate that ARC path then something is really wrong.
  328. // We assume C: because we don't know what else to do.
  329. //
  330. b = ArcPathToNtPath(
  331. TEXT("multi(0)disk(0)rdisk(0)"),
  332. NtDevicePath,
  333. sizeof(NtDevicePath)/sizeof(TCHAR)
  334. );
  335. if(!b) {
  336. //
  337. // Missed. Try scsi(0) in case the user is using ntbootdd.sys
  338. //
  339. b = ArcPathToNtPath(
  340. TEXT("scsi(0)disk(0)rdisk(0)"),
  341. NtDevicePath,
  342. sizeof(NtDevicePath)/sizeof(TCHAR)
  343. );
  344. if(!b) {
  345. GotIt = FALSE;
  346. goto c0;
  347. }
  348. }
  349. //
  350. // The arc path for a disk device is usually linked
  351. // to partition0. Get rid of the partition part of the name.
  352. //
  353. CharLower(NtDevicePath);
  354. if(p = StrStr(NtDevicePath,TEXT("\\partition"))) {
  355. *p = 0;
  356. }
  357. NtDevicePathLen = lstrlen(NtDevicePath);
  358. //
  359. // Determine the physical drive number of this drive.
  360. // If the name is not of the form \device\harddiskx then
  361. // something is very wrong. Just assume we don't understand
  362. // this device type, and return C:.
  363. //
  364. if(memcmp(NtDevicePath,TEXT("\\device\\harddisk"),16*sizeof(TCHAR))) {
  365. Drive = TEXT('C');
  366. GotIt = TRUE;
  367. goto c0;
  368. }
  369. PhysicalDriveNumber = StrToInt(NtDevicePath+16);
  370. if (FAILED(StringCchPrintf(Buffer,
  371. ARRAYSIZE(Buffer),
  372. TEXT("\\\\.\\PhysicalDrive%u"),
  373. PhysicalDriveNumber)))
  374. {
  375. GotIt = FALSE;
  376. goto c0;
  377. }
  378. //
  379. // Get drive layout info for this physical disk.
  380. // If we can't do this something is very wrong.
  381. //
  382. hDisk = CreateFile(Buffer,
  383. GENERIC_READ,
  384. FILE_SHARE_READ | FILE_SHARE_WRITE,
  385. NULL,
  386. OPEN_EXISTING,
  387. 0,
  388. NULL);
  389. if(hDisk == INVALID_HANDLE_VALUE) {
  390. GotIt = FALSE;
  391. goto c0;
  392. }
  393. //
  394. // Get partition information.
  395. //
  396. DriveLayoutSize = 1024;
  397. DriveLayout = LocalAlloc(LPTR, DriveLayoutSize);
  398. if(!DriveLayout) {
  399. GotIt = FALSE;
  400. goto c1;
  401. }
  402. if (FAILED(StringCchCopy(FoundSystemPartition, ARRAYSIZE(FoundSystemPartition), TEXT("Partition"))))
  403. {
  404. GotIt = FALSE;
  405. goto c2;
  406. }
  407. retry:
  408. b = DeviceIoControl(
  409. hDisk,
  410. IOCTL_DISK_GET_DRIVE_LAYOUT,
  411. NULL,
  412. 0,
  413. (PVOID)DriveLayout,
  414. DriveLayoutSize,
  415. &DataSize,
  416. NULL
  417. );
  418. if(!b) {
  419. if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
  420. DriveLayoutSize += 1024;
  421. if(p = LocalReAlloc((HLOCAL)DriveLayout,DriveLayoutSize, 0L)) {
  422. (PVOID)DriveLayout = p;
  423. } else {
  424. GotIt = FALSE;
  425. goto c2;
  426. }
  427. goto retry;
  428. } else {
  429. GotIt = FALSE;
  430. goto c2;
  431. }
  432. }else{
  433. // Now walk the Drive_Layout to find the boot partition
  434. Start = DriveLayout->PartitionEntry;
  435. cnt = 0;
  436. for( i = Start; cnt < DriveLayout->PartitionCount; i++ ){
  437. cnt++;
  438. if( i->BootIndicator == TRUE ){
  439. BootPartitionNumber = i->PartitionNumber;
  440. if (FAILED(StringCchPrintf(temp, ARRAYSIZE(temp), TEXT("%d"), BootPartitionNumber)) ||
  441. FAILED(StringCchCat(FoundSystemPartition, ARRAYSIZE(FoundSystemPartition), temp)))
  442. {
  443. GotIt = FALSE;
  444. goto c2;
  445. }
  446. break;
  447. }
  448. }
  449. }
  450. //
  451. // The system partition can only be a drive that is on
  452. // this disk. We make this determination by looking at NT drive names
  453. // for each drive letter and seeing if the NT equivalent of
  454. // multi(0)disk(0)rdisk(0) is a prefix.
  455. //
  456. GotIt = FALSE;
  457. for(Drive=TEXT('a'); Drive<=TEXT('z'); Drive++)
  458. {
  459. TCHAR DriveName[4];
  460. PathBuildRoot(DriveName, Drive - 'a');
  461. if(VMGetDriveType(DriveName) == DRIVE_FIXED)
  462. {
  463. if(QueryDosDevice(DriveName,Buffer,sizeof(Buffer)/sizeof(TCHAR))) {
  464. if( MatchNTSymbolicPaths(NtDevicePath,FoundSystemPartition,Buffer)) {
  465. //
  466. // Now look to see whether there's an nt boot sector and
  467. // boot files on this drive.
  468. //
  469. if(AppearsToBeSysPart(DriveLayout,Drive)) {
  470. GotIt = TRUE;
  471. break;
  472. }
  473. }
  474. }
  475. }
  476. }
  477. if(!GotIt) {
  478. //
  479. // Strange case, just assume C:
  480. //
  481. GotIt = TRUE;
  482. Drive = TEXT('C');
  483. }
  484. c2:
  485. LocalFree(DriveLayout);
  486. c1:
  487. CloseHandle(hDisk);
  488. c0:
  489. if(GotIt) {
  490. return(Drive);
  491. }
  492. else {
  493. return(TEXT('C'));
  494. }
  495. }
  496. BOOL
  497. InitializeArcStuff(
  498. )
  499. {
  500. HMODULE NtdllLib;
  501. //
  502. // On NT ntdll.dll had better be already loaded.
  503. //
  504. NtdllLib = LoadLibrary(TEXT("NTDLL"));
  505. if(!NtdllLib) {
  506. return(FALSE);
  507. }
  508. (FARPROC)NtOpenSymLinkRoutine = GetProcAddress(NtdllLib,"NtOpenSymbolicLinkObject");
  509. (FARPROC)NtQuerSymLinkRoutine = GetProcAddress(NtdllLib,"NtQuerySymbolicLinkObject");
  510. (FARPROC)NtOpenDirRoutine = GetProcAddress(NtdllLib,"NtOpenDirectoryObject");
  511. (FARPROC)NtQuerDirRoutine = GetProcAddress(NtdllLib,"NtQueryDirectoryObject");
  512. if(!NtOpenSymLinkRoutine || !NtQuerSymLinkRoutine || !NtOpenDirRoutine || !NtQuerDirRoutine) {
  513. FreeLibrary(NtdllLib);
  514. return(FALSE);
  515. }
  516. //
  517. // We don't need the extraneous handle any more.
  518. //
  519. FreeLibrary(NtdllLib);
  520. return(g_fInitialized = TRUE);
  521. }
  522. BOOL
  523. MatchNTSymbolicPaths(
  524. PCTSTR lpDeviceName,
  525. PCTSTR lpSysPart,
  526. PCTSTR lpMatchName
  527. )
  528. /*
  529. Introduced this routine to mend the old way of finding if we determined the right system partition.
  530. Arguments:
  531. lpDeviceName - This should be the symbolic link (\Device\HarddiskX) name for the arcpath
  532. multi/scsi(0)disk(0)rdisk(0) which is the arcpath for bios drive 0x80.
  533. Remember that we strip the PartitionX part to get just \Device\HarddiskX.
  534. lpSysPart - When we traverse the lpDeviceName directory we compare the symbolic link corresponding to
  535. lpSysPart and see if it matches lpMatchName
  536. lpMatchName - This is the symbolic name that a drive letter translates to (got by calling
  537. QueryDosDevice() ).
  538. So it boils down to us trying to match a drive letter to the system partition on bios drive 0x80.
  539. */
  540. {
  541. NTSTATUS Status;
  542. UNICODE_STRING UnicodeString;
  543. OBJECT_ATTRIBUTES Attributes;
  544. HANDLE DirectoryHandle, SymLinkHandle;
  545. POBJECT_DIRECTORY_INFORMATION DirInfo;
  546. BOOLEAN RestartScan, ret;
  547. UCHAR DirInfoBuffer[ 512 ];
  548. WCHAR Buffer[1024];
  549. WCHAR pDevice[512], pMatch[512], pSysPart[20];
  550. ULONG Context = 0;
  551. ULONG ReturnedLength;
  552. if (FAILED(StringCchCopy(pDevice,ARRAYSIZE(pDevice),lpDeviceName)) ||
  553. FAILED(StringCchCopy(pMatch,ARRAYSIZE(pMatch),lpMatchName)) ||
  554. FAILED(StringCchCopy(pSysPart,ARRAYSIZE(pSysPart),lpSysPart)))
  555. {
  556. return FALSE;
  557. }
  558. UnicodeString.Buffer = pDevice;
  559. UnicodeString.Length = (USHORT)lstrlenW(pDevice)*sizeof(WCHAR);
  560. UnicodeString.MaximumLength = UnicodeString.Length + sizeof(WCHAR);
  561. InitializeObjectAttributes( &Attributes,
  562. &UnicodeString,
  563. OBJ_CASE_INSENSITIVE,
  564. NULL,
  565. NULL
  566. );
  567. Status = (*NtOpenDirRoutine)( &DirectoryHandle,
  568. DIRECTORY_QUERY,
  569. &Attributes
  570. );
  571. if (!NT_SUCCESS( Status ))
  572. return(FALSE);
  573. RestartScan = TRUE;
  574. DirInfo = (POBJECT_DIRECTORY_INFORMATION)DirInfoBuffer;
  575. ret = FALSE;
  576. while (TRUE) {
  577. Status = (*NtQuerDirRoutine)( DirectoryHandle,
  578. (PVOID)DirInfo,
  579. sizeof( DirInfoBuffer ),
  580. TRUE,
  581. RestartScan,
  582. &Context,
  583. &ReturnedLength
  584. );
  585. //
  586. // Check the status of the operation.
  587. //
  588. if (!NT_SUCCESS( Status )) {
  589. if (Status == STATUS_NO_MORE_ENTRIES) {
  590. Status = STATUS_SUCCESS;
  591. }
  592. break;
  593. }
  594. if (!wcsncmp( DirInfo->TypeName.Buffer, L"SymbolicLink", DirInfo->TypeName.Length ) &&
  595. !_wcsnicmp( DirInfo->Name.Buffer, pSysPart, DirInfo->Name.Length ) ) {
  596. UnicodeString.Buffer = DirInfo->Name.Buffer;
  597. UnicodeString.Length = (USHORT)lstrlenW(DirInfo->Name.Buffer)*sizeof(WCHAR);
  598. UnicodeString.MaximumLength = UnicodeString.Length + sizeof(WCHAR);
  599. InitializeObjectAttributes( &Attributes,
  600. &UnicodeString,
  601. OBJ_CASE_INSENSITIVE,
  602. DirectoryHandle,
  603. NULL
  604. );
  605. Status = (*NtOpenSymLinkRoutine)(
  606. &SymLinkHandle,
  607. READ_CONTROL | SYMBOLIC_LINK_QUERY,
  608. &Attributes
  609. );
  610. if(NT_SUCCESS(Status)) {
  611. //
  612. // Query the object to get the link target.
  613. //
  614. UnicodeString.Buffer = Buffer;
  615. UnicodeString.Length = 0;
  616. UnicodeString.MaximumLength = sizeof(Buffer)-sizeof(WCHAR);
  617. Status = (*NtQuerSymLinkRoutine)(SymLinkHandle,&UnicodeString,NULL);
  618. CloseHandle(SymLinkHandle);
  619. if( NT_SUCCESS(Status)){
  620. if (!_wcsnicmp(UnicodeString.Buffer, pMatch, (UnicodeString.Length/sizeof(WCHAR)))) {
  621. ret = TRUE;
  622. break;
  623. }
  624. }
  625. }
  626. }
  627. RestartScan = FALSE;
  628. }
  629. CloseHandle( DirectoryHandle );
  630. return( ret );
  631. }