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.

881 lines
20 KiB

  1. #include "precomp.h"
  2. #pragma hdrstop
  3. /*++
  4. Copyright (c) 1990 Microsoft Corporation
  5. Module Name:
  6. nt_obj.c
  7. Abstract:
  8. 1. Contains routines to access symbolic link objects
  9. 2. Contains routines to convert between the DOS and ARC Name space
  10. Author:
  11. Sunil Pai (sunilp) 20-Nov-1991
  12. --*/
  13. #define BUFFER_SIZE 1024
  14. #define SYMLINKTYPE L"SymbolicLink"
  15. #define ARCNAMEOBJDIR L"\\ArcName"
  16. #define DOSDEVOBJDIR L"\\DosDevices"
  17. BOOL
  18. IsSymbolicLinkType(
  19. IN PUNICODE_STRING Type
  20. );
  21. BOOL
  22. DosPathToNtPathWorker(
  23. IN LPSTR DosPath,
  24. OUT LPSTR NtPath
  25. )
  26. {
  27. CHAR Drive[] = "\\DosDevices\\?:";
  28. WCHAR NtNameDrive[MAX_PATH];
  29. ANSI_STRING AnsiString;
  30. UNICODE_STRING Drive_U, NtNameDrive_U;
  31. ANSI_STRING NtNameDrive_A;
  32. BOOL bStatus;
  33. NTSTATUS Status;
  34. //
  35. // Validate the DOS Path passed in
  36. //
  37. if (lstrlen(DosPath) < 2 || DosPath[1] != ':') {
  38. SetErrorText(IDS_ERROR_INVALIDDISK);
  39. return ( FALSE );
  40. }
  41. //
  42. // Extract the drive
  43. //
  44. Drive[12] = DosPath[0];
  45. //
  46. // Get Unicode string for the drive
  47. //
  48. RtlInitAnsiString(&AnsiString, Drive);
  49. Status = RtlAnsiStringToUnicodeString(
  50. &Drive_U,
  51. &AnsiString,
  52. TRUE
  53. );
  54. if (!NT_SUCCESS(Status)) {
  55. SetErrorText(IDS_ERROR_RTLOOM);
  56. return ( FALSE );
  57. }
  58. //
  59. // Initialise Unicode string to hold the Nt Name for the Drive
  60. //
  61. NtNameDrive_U.Buffer = NtNameDrive;
  62. NtNameDrive_U.Length = 0;
  63. NtNameDrive_U.MaximumLength = MAX_PATH * sizeof(WCHAR);
  64. //
  65. // Query symbolic link of the drive
  66. //
  67. bStatus = GetSymbolicLinkTarget(&Drive_U, &NtNameDrive_U);
  68. RtlFreeUnicodeString(&Drive_U);
  69. if (!bStatus) {
  70. return ( FALSE );
  71. }
  72. //
  73. // Convert the Unicode NtName for drive to ansi
  74. //
  75. Status = RtlUnicodeStringToAnsiString(
  76. &NtNameDrive_A,
  77. &NtNameDrive_U,
  78. TRUE
  79. );
  80. if (!NT_SUCCESS(Status)) {
  81. SetErrorText(IDS_ERROR_RTLOOM);
  82. return ( FALSE );
  83. }
  84. //
  85. // Copy the drive to the output variable
  86. //
  87. NtNameDrive_A.Buffer[NtNameDrive_A.Length] = '\0'; //Null terminate
  88. lstrcpy(NtPath, NtNameDrive_A.Buffer);
  89. RtlFreeAnsiString(&NtNameDrive_A);
  90. //
  91. // concatenate the rest of the DosPath to this ArcPath
  92. //
  93. lstrcat(NtPath, DosPath+2);
  94. return(TRUE);
  95. }
  96. BOOL
  97. NtPathToDosPathWorker(
  98. IN LPSTR NtPath,
  99. OUT LPSTR DosPath
  100. )
  101. {
  102. CHAR Drive;
  103. CHAR DriveStr[3] = "x:";
  104. CHAR NtDevicePath[MAX_PATH];
  105. unsigned MatchLen;
  106. //
  107. // Iterate through each potential drive letter.
  108. //
  109. for(Drive='A'; Drive<='Z'; Drive++) {
  110. DriveStr[0] = Drive;
  111. //
  112. // Get the equivalent NT device path, if any.
  113. //
  114. if(DosPathToNtPathWorker(DriveStr,NtDevicePath)) {
  115. MatchLen = lstrlen(NtDevicePath);
  116. //
  117. // If the NT Path we are trying to translate is a prefix
  118. // of the NT path for the current drive, we've got a match.
  119. //
  120. if(!_strnicmp(NtDevicePath,NtPath,MatchLen)) {
  121. lstrcpy(DosPath,DriveStr);
  122. lstrcat(DosPath,NtPath+MatchLen);
  123. return(TRUE);
  124. }
  125. }
  126. }
  127. //
  128. // Didn't find a drive letter that matches so there is no
  129. // equivalent DOS path.
  130. //
  131. return(FALSE);
  132. }
  133. BOOL
  134. DosPathToArcPathWorker(
  135. IN LPSTR DosPath,
  136. OUT LPSTR ArcPath
  137. )
  138. {
  139. CHAR Drive[] = "\\DosDevices\\?:";
  140. WCHAR NtNameDrive[MAX_PATH];
  141. WCHAR ArcNameDrive[MAX_PATH];
  142. ANSI_STRING AnsiString;
  143. UNICODE_STRING Drive_U, NtNameDrive_U, ObjDir_U, ArcNameDrive_U;
  144. ANSI_STRING ArcNameDrive_A;
  145. BOOL bStatus;
  146. NTSTATUS Status;
  147. //
  148. // Validate the DOS Path passed in
  149. //
  150. if (lstrlen(DosPath) < 2 || DosPath[1] != ':') {
  151. SetErrorText(IDS_ERROR_INVALIDDISK);
  152. return ( FALSE );
  153. }
  154. //
  155. // Extract the drive
  156. //
  157. Drive[12] = DosPath[0];
  158. //
  159. // Get Unicode string for the drive
  160. //
  161. RtlInitAnsiString(&AnsiString, Drive);
  162. Status = RtlAnsiStringToUnicodeString(
  163. &Drive_U,
  164. &AnsiString,
  165. TRUE
  166. );
  167. if (!NT_SUCCESS(Status)) {
  168. SetErrorText(IDS_ERROR_RTLOOM);
  169. return ( FALSE );
  170. }
  171. //
  172. // Initialise Unicode string to hold the Nt Name for the Drive
  173. //
  174. NtNameDrive_U.Buffer = NtNameDrive;
  175. NtNameDrive_U.Length = 0;
  176. NtNameDrive_U.MaximumLength = MAX_PATH * sizeof(WCHAR);
  177. //
  178. // Initialise Unicode string to hold the ArcName for the drive
  179. //
  180. ArcNameDrive_U.Buffer = ArcNameDrive;
  181. ArcNameDrive_U.Length = 0;
  182. ArcNameDrive_U.MaximumLength = MAX_PATH * sizeof(WCHAR);
  183. //
  184. // Initialise Unicode string to hold the \\ArcName objdir name
  185. //
  186. RtlInitUnicodeString(&ObjDir_U, ARCNAMEOBJDIR);
  187. //
  188. // Query symbolic link of the drive
  189. //
  190. bStatus = GetSymbolicLinkTarget(&Drive_U, &NtNameDrive_U);
  191. RtlFreeUnicodeString(&Drive_U);
  192. if (!bStatus) {
  193. return ( FALSE );
  194. }
  195. //
  196. // Find the object in the arcname directory
  197. //
  198. bStatus = GetSymbolicLinkSource(&ObjDir_U, &NtNameDrive_U, &ArcNameDrive_U);
  199. if (!bStatus) {
  200. return ( FALSE );
  201. }
  202. //
  203. // Convert the Unicode ArcName for drive to ansi
  204. //
  205. Status = RtlUnicodeStringToAnsiString(
  206. &ArcNameDrive_A,
  207. &ArcNameDrive_U,
  208. TRUE
  209. );
  210. if (!NT_SUCCESS(Status)) {
  211. SetErrorText(IDS_ERROR_RTLOOM);
  212. return ( FALSE );
  213. }
  214. //
  215. // Copy the drive to the output variable
  216. //
  217. ArcNameDrive_A.Buffer[ArcNameDrive_A.Length] = '\0'; //Null terminate
  218. lstrcpy(ArcPath, ArcNameDrive_A.Buffer);
  219. RtlFreeAnsiString(&ArcNameDrive_A);
  220. //
  221. // concatenate the rest of the DosPath to this ArcPath
  222. //
  223. lstrcat(ArcPath, DosPath+2);
  224. return(TRUE);
  225. }
  226. BOOL
  227. ArcPathToDosPathWorker(
  228. IN LPSTR ArcPath,
  229. OUT LPSTR DosPath
  230. )
  231. {
  232. CHAR ArcDir[] = "\\ArcName\\";
  233. CHAR ArcFullPath[MAX_PATH];
  234. SZ ArcDrive, FilePath;
  235. CHAR ArcDriveObject[MAX_PATH];
  236. WCHAR NtNameDrive[MAX_PATH];
  237. WCHAR DosNameDrive[MAX_PATH];
  238. ANSI_STRING AnsiString;
  239. UNICODE_STRING Drive_U, NtNameDrive_U, ObjDir_U, DosNameDrive_U;
  240. ANSI_STRING DosNameDrive_A;
  241. BOOL bStatus;
  242. NTSTATUS Status;
  243. //
  244. // Validate the Arc Path passed in
  245. //
  246. if (lstrlen(ArcPath) >= MAX_PATH) {
  247. SetErrorText(IDS_ERROR_INVALIDDISK);
  248. return ( FALSE );
  249. }
  250. // extract the arc drive and filename
  251. lstrcpy (ArcFullPath, ArcPath);
  252. ArcDrive = ArcFullPath;
  253. FilePath = strchr (ArcFullPath, '\\');
  254. if (FilePath != NULL) {
  255. *FilePath = '\0';
  256. FilePath++;
  257. }
  258. //
  259. // Form the full spec for the ArcDrive object name
  260. //
  261. lstrcpy (ArcDriveObject, ArcDir);
  262. lstrcat (ArcDriveObject, ArcDrive);
  263. //
  264. // Get Unicode string for the drive
  265. //
  266. RtlInitAnsiString(&AnsiString, ArcDriveObject);
  267. Status = RtlAnsiStringToUnicodeString(
  268. &Drive_U,
  269. &AnsiString,
  270. TRUE
  271. );
  272. if (!NT_SUCCESS(Status)) {
  273. SetErrorText(IDS_ERROR_RTLOOM);
  274. return ( FALSE );
  275. }
  276. //
  277. // Initialise Unicode string to hold the Nt Name for the Drive
  278. //
  279. NtNameDrive_U.Buffer = NtNameDrive;
  280. NtNameDrive_U.Length = 0;
  281. NtNameDrive_U.MaximumLength = MAX_PATH * sizeof(WCHAR);
  282. //
  283. // Initialise Unicode string to hold the ArcName for the drive
  284. //
  285. DosNameDrive_U.Buffer = DosNameDrive;
  286. DosNameDrive_U.Length = 0;
  287. DosNameDrive_U.MaximumLength = MAX_PATH * sizeof(WCHAR);
  288. //
  289. // Initialise Unicode string to hold the \\DosDevices objdir name
  290. //
  291. RtlInitUnicodeString(&ObjDir_U, DOSDEVOBJDIR);
  292. //
  293. // Query symbolic link of the drive
  294. //
  295. bStatus = GetSymbolicLinkTarget(&Drive_U, &NtNameDrive_U);
  296. RtlFreeUnicodeString(&Drive_U);
  297. if (!bStatus) {
  298. return ( FALSE );
  299. }
  300. //
  301. // Find the object in the dosdevices directory
  302. //
  303. bStatus = GetSymbolicLinkSource(&ObjDir_U, &NtNameDrive_U, &DosNameDrive_U);
  304. if (!bStatus) {
  305. return ( FALSE );
  306. }
  307. //
  308. // Convert the Unicode ArcName for drive to ansi
  309. //
  310. Status = RtlUnicodeStringToAnsiString(
  311. &DosNameDrive_A,
  312. &DosNameDrive_U,
  313. TRUE
  314. );
  315. if (!NT_SUCCESS(Status)) {
  316. SetErrorText(IDS_ERROR_RTLOOM);
  317. return ( FALSE );
  318. }
  319. //
  320. // Copy the drive to the output variable
  321. //
  322. DosNameDrive_A.Buffer[DosNameDrive_A.Length] = '\0'; //Null terminate
  323. lstrcpy(DosPath, DosNameDrive_A.Buffer);
  324. RtlFreeAnsiString(&DosNameDrive_A);
  325. //
  326. // concatenate the rest of the DosPath to this ArcPath
  327. //
  328. if (FilePath != NULL) {
  329. lstrcat (DosPath, "\\");
  330. lstrcat (DosPath, FilePath);
  331. }
  332. return(TRUE);
  333. }
  334. BOOL
  335. IsDriveExternalScsi(
  336. IN LPSTR DosDrive,
  337. OUT BOOL *IsExternal
  338. )
  339. {
  340. CHAR ArcNameDrive[MAX_PATH];
  341. BOOL bStatus;
  342. //
  343. // Set IsExternal to false
  344. //
  345. *IsExternal = FALSE;
  346. //
  347. // Query the ArcName for the DOS Drive
  348. //
  349. bStatus = DosPathToArcPathWorker(DosDrive, ArcNameDrive);
  350. if (!bStatus) {
  351. return (FALSE);
  352. }
  353. //
  354. // Find out if the Arcname first four letters are scsi
  355. //
  356. ArcNameDrive[4] = '\0'; // Only interested in first four letters
  357. if(!lstrcmpi(ArcNameDrive, "scsi")) {
  358. *IsExternal = TRUE;
  359. }
  360. return TRUE;
  361. }
  362. /*
  363. * OBJECT MANAGEMENT ROUTINES
  364. */
  365. BOOL
  366. GetSymbolicLinkSource(
  367. IN PUNICODE_STRING pObjDir_U,
  368. IN PUNICODE_STRING pTarget_U,
  369. OUT PUNICODE_STRING pSource_U
  370. )
  371. {
  372. PCHAR Buffer, FullNameObject, ObjectLink, SavedMatch;
  373. UNICODE_STRING ObjName_U, ObjLink_U;
  374. UNICODE_STRING SavedMatch_U;
  375. BOOLEAN IsMatchSaved = FALSE;
  376. OBJECT_ATTRIBUTES Attributes;
  377. HANDLE DirectoryHandle;
  378. ULONG Context = 0;
  379. ULONG ReturnedLength;
  380. BOOLEAN RestartScan = TRUE;
  381. BOOLEAN ReturnSingleEntry = TRUE; //LATER change this to FALSE
  382. POBJECT_DIRECTORY_INFORMATION DirInfo;
  383. NTSTATUS Status;
  384. BOOL bStatus;
  385. //
  386. // Open the object directory.
  387. //
  388. InitializeObjectAttributes(
  389. &Attributes,
  390. pObjDir_U,
  391. OBJ_CASE_INSENSITIVE,
  392. NULL,
  393. NULL
  394. );
  395. Status = NtOpenDirectoryObject(
  396. &DirectoryHandle,
  397. DIRECTORY_ALL_ACCESS,
  398. &Attributes
  399. );
  400. if (!NT_SUCCESS( Status ) ) {
  401. SetErrorText(IDS_ERROR_OBJDIROPEN);
  402. return ( FALSE );
  403. }
  404. //
  405. // Find the symbolic link objects in the directory and query them for
  406. // a match with the string passed in
  407. //
  408. //
  409. // Allocate a buffer to query the directory objects
  410. //
  411. if ((Buffer = SAlloc(BUFFER_SIZE)) == NULL) {
  412. SetErrorText(IDS_ERROR_DLLOOM);
  413. NtClose(DirectoryHandle);
  414. return ( FALSE );
  415. }
  416. //
  417. // Form a Unicode string object to hold the symbolic link objects found
  418. // in the object directory
  419. //
  420. if ((FullNameObject = SAlloc(MAX_PATH * sizeof(WCHAR))) == NULL) {
  421. SetErrorText(IDS_ERROR_DLLOOM);
  422. SFree (Buffer);
  423. NtClose (DirectoryHandle);
  424. return ( FALSE );
  425. }
  426. ObjName_U.Buffer = (PWSTR)FullNameObject;
  427. ObjName_U.Length = 0;
  428. ObjName_U.MaximumLength = MAX_PATH * sizeof(WCHAR);
  429. //
  430. // Form a Unicode string object to hold the symbolic link objects found
  431. // in the object directory
  432. //
  433. if ((ObjectLink = SAlloc(MAX_PATH * sizeof(WCHAR))) == NULL) {
  434. SetErrorText(IDS_ERROR_DLLOOM);
  435. SFree (Buffer);
  436. SFree (FullNameObject);
  437. NtClose (DirectoryHandle);
  438. return ( FALSE );
  439. }
  440. ObjLink_U.Buffer = (PWSTR)ObjectLink;
  441. ObjLink_U.Length = 0;
  442. ObjLink_U.MaximumLength = MAX_PATH * sizeof(WCHAR);
  443. if ((SavedMatch = SAlloc(MAX_PATH * sizeof(WCHAR))) == NULL) {
  444. SetErrorText(IDS_ERROR_DLLOOM);
  445. SFree (Buffer);
  446. SFree (FullNameObject);
  447. SFree (ObjectLink);
  448. NtClose (DirectoryHandle);
  449. return ( FALSE );
  450. }
  451. SavedMatch_U.Buffer = (PWSTR)SavedMatch;
  452. SavedMatch_U.Length = 0;
  453. SavedMatch_U.MaximumLength = MAX_PATH * sizeof(WCHAR);
  454. while (TRUE) {
  455. //
  456. // Clear the buffer
  457. //
  458. RtlZeroMemory( Buffer, BUFFER_SIZE);
  459. //
  460. // repeatedly Query the directory objects till done
  461. //
  462. Status = NtQueryDirectoryObject(
  463. DirectoryHandle,
  464. Buffer,
  465. BUFFER_SIZE,
  466. ReturnSingleEntry, // fetch more than one entry
  467. RestartScan, // start rescan true on first go
  468. &Context,
  469. &ReturnedLength
  470. );
  471. //
  472. // Check the Status of the operation.
  473. //
  474. if (!NT_SUCCESS(Status) && (Status != STATUS_MORE_ENTRIES)) {
  475. if (Status == STATUS_NO_MORE_FILES || Status == STATUS_NO_MORE_ENTRIES) {
  476. SetErrorText(IDS_ERROR_INVALIDDISK);
  477. }
  478. else {
  479. SetErrorText(IDS_ERROR_OBJDIRREAD);
  480. }
  481. SFree(Buffer);
  482. SFree(FullNameObject);
  483. SFree(ObjectLink);
  484. if(IsMatchSaved) {
  485. RtlCopyUnicodeString (pSource_U, &SavedMatch_U);
  486. SFree(SavedMatch);
  487. return(TRUE);
  488. }
  489. SFree(SavedMatch);
  490. return(FALSE);
  491. }
  492. //
  493. // Make sure that restart scan is false for next go
  494. //
  495. RestartScan = FALSE;
  496. //
  497. // For every record in the buffer, see if the type of the object
  498. // is a symbolic link
  499. //
  500. //
  501. // Point to the first record in the buffer, we are guaranteed to have
  502. // one otherwise Status would have been No More Files
  503. //
  504. DirInfo = (POBJECT_DIRECTORY_INFORMATION) Buffer;
  505. while (TRUE) {
  506. //
  507. // Check if there is another record. If there isn't, break out of
  508. // the loop now
  509. //
  510. if (DirInfo->Name.Length == 0) {
  511. break;
  512. }
  513. //
  514. // See if the object type is a symbolic link
  515. //
  516. if (IsSymbolicLinkType(&(DirInfo->TypeName))) {
  517. //
  518. // get full pathname of object
  519. //
  520. //
  521. // Check if we will overflow our buffer
  522. //
  523. if ((
  524. pObjDir_U->Length +
  525. sizeof(WCHAR) +
  526. DirInfo->Name.Length +
  527. sizeof(WCHAR)
  528. ) > ObjName_U.MaximumLength ) {
  529. SetErrorText(IDS_ERROR_OBJNAMOVF);
  530. SFree(Buffer);
  531. SFree(FullNameObject);
  532. SFree(ObjectLink);
  533. if(IsMatchSaved) {
  534. RtlCopyUnicodeString (pSource_U, &SavedMatch_U);
  535. SFree(SavedMatch);
  536. return(TRUE);
  537. }
  538. SFree(SavedMatch);
  539. return( FALSE );
  540. }
  541. //
  542. // Copy the current object name over the the buffer prefixing
  543. // it with the \ArcName\ object directory name
  544. //
  545. RtlCopyUnicodeString ( &ObjName_U, pObjDir_U );
  546. RtlAppendUnicodeToString ( &ObjName_U, L"\\" );
  547. RtlAppendUnicodeStringToString ( &ObjName_U, &(DirInfo->Name));
  548. //
  549. // query the symbolic link target
  550. //
  551. ObjLink_U.Buffer = (PWSTR)ObjectLink;
  552. ObjLink_U.Length = 0;
  553. ObjLink_U.MaximumLength = MAX_PATH * sizeof(WCHAR);
  554. bStatus = GetSymbolicLinkTarget (&ObjName_U, &ObjLink_U);
  555. if (bStatus != TRUE) {
  556. SFree(Buffer);
  557. SFree(FullNameObject);
  558. SFree(ObjectLink);
  559. if(IsMatchSaved) {
  560. RtlCopyUnicodeString (pSource_U, &SavedMatch_U);
  561. SFree(SavedMatch);
  562. return(TRUE);
  563. }
  564. SFree(SavedMatch);
  565. return FALSE;
  566. }
  567. //
  568. // see if it compares to the name we are looking for
  569. //
  570. if (RtlEqualUnicodeString (pTarget_U, &ObjLink_U, TRUE)) {
  571. #if i386
  572. UNICODE_STRING Multi_U;
  573. RtlInitUnicodeString(&Multi_U,L"multi(");
  574. if(RtlPrefixUnicodeString(&Multi_U,&DirInfo->Name,TRUE)) {
  575. RtlCopyUnicodeString(&SavedMatch_U,&DirInfo->Name);
  576. IsMatchSaved = TRUE;
  577. } else // scsi, just return it. Favor scsi over multi.
  578. #endif
  579. {
  580. RtlCopyUnicodeString (pSource_U, &(DirInfo->Name));
  581. SFree(Buffer);
  582. SFree(FullNameObject);
  583. SFree(ObjectLink);
  584. SFree(SavedMatch);
  585. return TRUE;
  586. }
  587. }
  588. }
  589. //
  590. // There is another record so advance DirInfo to the next entry
  591. //
  592. DirInfo = (POBJECT_DIRECTORY_INFORMATION) (((PCHAR) DirInfo) +
  593. sizeof( OBJECT_DIRECTORY_INFORMATION ) );
  594. } // while
  595. } // while
  596. return ( FALSE );
  597. } // getarcname
  598. /* Checks to see if a given object type is a symbolic link type */
  599. BOOL
  600. IsSymbolicLinkType(
  601. IN PUNICODE_STRING Type
  602. )
  603. {
  604. UNICODE_STRING TypeName;
  605. //
  606. // Check the length of the string
  607. //
  608. if (Type->Length == 0) {
  609. return FALSE;
  610. }
  611. //
  612. // compare it to the symbolic link type name
  613. //
  614. RtlInitUnicodeString(&TypeName, SYMLINKTYPE);
  615. return (RtlEqualUnicodeString(Type, &TypeName, TRUE));
  616. }
  617. BOOL
  618. GetSymbolicLinkTarget(
  619. IN PUNICODE_STRING pSourceString_U,
  620. IN OUT PUNICODE_STRING pDestString_U
  621. )
  622. {
  623. NTSTATUS Status;
  624. OBJECT_ATTRIBUTES Attributes;
  625. HANDLE ObjectHandle;
  626. //
  627. // Initialise the object attributes structure for the symbolic
  628. // link object
  629. InitializeObjectAttributes(
  630. &Attributes,
  631. pSourceString_U,
  632. OBJ_CASE_INSENSITIVE,
  633. NULL,
  634. NULL
  635. );
  636. //
  637. // Open the symbolic link for link_query access
  638. //
  639. Status = NtOpenSymbolicLinkObject(
  640. &ObjectHandle,
  641. READ_CONTROL | SYMBOLIC_LINK_QUERY,
  642. &Attributes
  643. );
  644. if (!NT_SUCCESS(Status)) {
  645. SetErrorText(IDS_ERROR_SYMLNKOPEN);
  646. return ( FALSE );
  647. }
  648. //
  649. // query the symbolic link target
  650. //
  651. Status = NtQuerySymbolicLinkObject(
  652. ObjectHandle,
  653. pDestString_U,
  654. NULL
  655. );
  656. if (!NT_SUCCESS(Status)) {
  657. SetErrorText(IDS_ERROR_SYMLNKREAD);
  658. NtClose(ObjectHandle);
  659. return(FALSE);
  660. }
  661. //
  662. // close the link object
  663. //
  664. NtClose(ObjectHandle);
  665. //
  666. // return the link target
  667. //
  668. return ( TRUE );
  669. }