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.

824 lines
21 KiB

  1. /*++
  2. Copyright (c) Microsoft Corporation. All rights reserved.
  3. Module Name:
  4. IoFileUtil.c
  5. Abstract:
  6. This module implements various file utility functions for the Io subsystem.
  7. Author:
  8. Adrian J. Oney - April 4, 2000
  9. Revision History:
  10. --*/
  11. #include "pnpmgrp.h"
  12. #include "IopFileUtil.h"
  13. #pragma hdrstop
  14. #ifdef ALLOC_PRAGMA
  15. #pragma alloc_text(INIT, IopFileUtilWalkDirectoryTreeTopDown)
  16. #pragma alloc_text(INIT, IopFileUtilWalkDirectoryTreeBottomUp)
  17. #pragma alloc_text(INIT, IopFileUtilWalkDirectoryTreeHelper)
  18. #pragma alloc_text(INIT, IopFileUtilClearAttributes)
  19. #pragma alloc_text(INIT, IopFileUtilRename)
  20. #endif
  21. #define POOLTAG_FILEUTIL ('uFoI')
  22. NTSTATUS
  23. IopFileUtilWalkDirectoryTreeTopDown(
  24. IN PUNICODE_STRING Directory,
  25. IN ULONG Flags,
  26. IN DIRWALK_CALLBACK CallbackFunction,
  27. IN PVOID Context
  28. )
  29. /*++
  30. Routine Description:
  31. This funcion walks a directory tree *top down*, passing each entry to the
  32. callback with the below restrictions. Note that the root directory itself
  33. is not included in the callback!
  34. Arguments:
  35. Directory - Supplies the NT Path to the directory to walk. The directory
  36. should *not* have a slash '\\'.
  37. Flags - Specifies constraints on how the directory tree should be walked:
  38. DIRWALK_INCLUDE_FILES - Files should be included in the dump.
  39. DIRWALK_INCLUDE_DIRECTORIES - Directories should be included in the
  40. dump.
  41. DIRWALK_CULL_DOTPATHS - "." and ".." should *not* be included
  42. in the list of directories passed to
  43. the callback function.
  44. DIRWALK_TRAVERSE - Each subdirectory should be traversed
  45. in turn.
  46. DIRWALK_TRAVERSE_MOUNTPOINTS - Set if mountpoints/symlinks should
  47. be traversed as well.
  48. CallbackFunction - Pointer to a function to call for each entry in the
  49. directory/subtree.
  50. Context - Context to pass to the callback function.
  51. Return Value:
  52. NTSTATUS - status of the operation.
  53. --*/
  54. {
  55. PDIRWALK_ENTRY pDirEntry;
  56. PLIST_ENTRY pListEntry;
  57. NTSTATUS status;
  58. UCHAR buffer[1024];
  59. LIST_ENTRY dirListHead;
  60. InitializeListHead(&dirListHead);
  61. //
  62. // Walk the first directory.
  63. //
  64. status = IopFileUtilWalkDirectoryTreeHelper(
  65. Directory,
  66. Flags,
  67. CallbackFunction,
  68. Context,
  69. buffer,
  70. sizeof(buffer),
  71. &dirListHead
  72. );
  73. //
  74. // Each directory that WalkDirectory finds gets added to the list.
  75. // process the list until we have no more directories.
  76. //
  77. while((!IsListEmpty(&dirListHead)) && NT_SUCCESS(status)) {
  78. pListEntry = RemoveHeadList(&dirListHead);
  79. pDirEntry = (PDIRWALK_ENTRY) CONTAINING_RECORD(pListEntry, DIRWALK_ENTRY, Link);
  80. status = IopFileUtilWalkDirectoryTreeHelper(
  81. &pDirEntry->Directory,
  82. Flags,
  83. CallbackFunction,
  84. Context,
  85. buffer,
  86. sizeof(buffer),
  87. &dirListHead
  88. );
  89. ExFreePool(pDirEntry);
  90. }
  91. //
  92. // If we failed we need to empty out our directory list.
  93. //
  94. if (!NT_SUCCESS(status)) {
  95. while (!IsListEmpty(&dirListHead)) {
  96. pListEntry = RemoveHeadList(&dirListHead);
  97. pDirEntry = (PDIRWALK_ENTRY) CONTAINING_RECORD(pListEntry, DIRWALK_ENTRY, Link);
  98. ExFreePool(pDirEntry);
  99. }
  100. }
  101. return status;
  102. }
  103. NTSTATUS
  104. IopFileUtilWalkDirectoryTreeBottomUp(
  105. IN PUNICODE_STRING Directory,
  106. IN ULONG Flags,
  107. IN DIRWALK_CALLBACK CallbackFunction,
  108. IN PVOID Context
  109. )
  110. /*++
  111. Routine Description:
  112. This funcion walks a directory tree *bottom up*, passing each entry to the
  113. callback with the below restrictions. Note that the root directory itself
  114. is not included in the callback!
  115. Arguments:
  116. Directory - Supplies the NT Path to the directory to walk. The directory
  117. should *not* have a slash trailing '\\'.
  118. Flags - Specifies constraints on how the directory tree should be walked:
  119. DIRWALK_INCLUDE_FILES - Files should be included in the dump.
  120. DIRWALK_INCLUDE_DIRECTORIES - Directories should be included in the
  121. dump.
  122. DIRWALK_CULL_DOTPATHS - "." and ".." should *not* be included
  123. in the list of directories passed to
  124. the callback function.
  125. DIRWALK_TRAVERSE - Each subdirectory should be traversed
  126. in turn.
  127. DIRWALK_TRAVERSE_MOUNTPOINTS - Set if mountpoints/symlinks should
  128. be traversed as well.
  129. CallbackFunction - Pointer to a function to call for each entry in the
  130. directory/subtree.
  131. Context - Context to pass to the callback function.
  132. Return Value:
  133. NTSTATUS - status of the operation.
  134. --*/
  135. {
  136. PDIRWALK_ENTRY pDirEntry;
  137. PLIST_ENTRY pListEntry;
  138. NTSTATUS status;
  139. UCHAR buffer[1024];
  140. LIST_ENTRY dirListHead, dirNothingHead;
  141. InitializeListHead(&dirListHead);
  142. InitializeListHead(&dirNothingHead);
  143. //
  144. // Create an entry for the root directory.
  145. //
  146. pDirEntry = (PDIRWALK_ENTRY) ExAllocatePoolWithTag(
  147. PagedPool,
  148. sizeof(DIRWALK_ENTRY) + Directory->Length - sizeof(WCHAR),
  149. POOLTAG_FILEUTIL
  150. );
  151. if (pDirEntry == NULL) {
  152. return STATUS_INSUFFICIENT_RESOURCES;
  153. }
  154. pDirEntry->Directory.Length = 0;
  155. pDirEntry->Directory.MaximumLength = Directory->Length;
  156. pDirEntry->Directory.Buffer = &pDirEntry->Name[0];
  157. RtlCopyUnicodeString(&pDirEntry->Directory, Directory);
  158. InsertHeadList(&dirListHead, &pDirEntry->Link);
  159. //
  160. // Collect the directory trees. When we are done we will walk the list in
  161. // reverse.
  162. //
  163. status = STATUS_SUCCESS;
  164. if (Flags & DIRWALK_TRAVERSE) {
  165. for(pListEntry = &dirListHead;
  166. pListEntry->Flink != &dirListHead;
  167. pListEntry = pListEntry->Flink) {
  168. pDirEntry = (PDIRWALK_ENTRY) CONTAINING_RECORD(pListEntry, DIRWALK_ENTRY, Link);
  169. status = IopFileUtilWalkDirectoryTreeHelper(
  170. &pDirEntry->Directory,
  171. DIRWALK_TRAVERSE,
  172. NULL,
  173. NULL,
  174. buffer,
  175. sizeof(buffer),
  176. &dirListHead
  177. );
  178. if (!NT_SUCCESS(status)) {
  179. break;
  180. }
  181. }
  182. }
  183. //
  184. // Each directory that WalkDirectory finds gets added to the list.
  185. // process the list until we have no more directories.
  186. //
  187. while((!IsListEmpty(&dirListHead)) && NT_SUCCESS(status)) {
  188. pListEntry = RemoveTailList(&dirListHead);
  189. pDirEntry = (PDIRWALK_ENTRY) CONTAINING_RECORD(pListEntry, DIRWALK_ENTRY, Link);
  190. status = IopFileUtilWalkDirectoryTreeHelper(
  191. &pDirEntry->Directory,
  192. Flags & ~DIRWALK_TRAVERSE,
  193. CallbackFunction,
  194. Context,
  195. buffer,
  196. sizeof(buffer),
  197. &dirNothingHead
  198. );
  199. ExFreePool(pDirEntry);
  200. ASSERT(IsListEmpty(&dirNothingHead));
  201. }
  202. //
  203. // Now do any final cleanup.
  204. //
  205. if (!NT_SUCCESS(status)) {
  206. while (!IsListEmpty(&dirListHead)) {
  207. pListEntry = RemoveHeadList(&dirListHead);
  208. pDirEntry = (PDIRWALK_ENTRY) CONTAINING_RECORD(pListEntry, DIRWALK_ENTRY, Link);
  209. ExFreePool(pDirEntry);
  210. }
  211. }
  212. return status;
  213. }
  214. NTSTATUS
  215. IopFileUtilWalkDirectoryTreeHelper(
  216. IN PUNICODE_STRING Directory,
  217. IN ULONG Flags,
  218. IN DIRWALK_CALLBACK CallbackFunction,
  219. IN PVOID Context,
  220. IN PUCHAR Buffer,
  221. IN ULONG BufferSize,
  222. IN OUT PLIST_ENTRY DirList
  223. )
  224. /*++
  225. Routine Description:
  226. This is a helper function for the IopFileUtilWalkDirectoryTree* functions.
  227. Arguments:
  228. Directory - Supplies the NT Path to the directory to walk. The directory
  229. should *not* have a slash trailing '\\'.
  230. Flags - Specifies constraints on how the directory tree should be walked:
  231. DIRWALK_INCLUDE_FILES - Files should be included in the dump.
  232. DIRWALK_INCLUDE_DIRECTORIES - Directories should be included in the
  233. dump.
  234. DIRWALK_CULL_DOTPATHS - "." and ".." should *not* be included
  235. in the list of directories passed to
  236. the callback function.
  237. DIRWALK_TRAVERSE - Each subdirectory should be traversed
  238. in turn.
  239. DIRWALK_TRAVERSE_MOUNTPOINTS - Set if mountpoints/symlinks should
  240. be traversed as well.
  241. DirList - Recieves list of new directories to scan after completion of this
  242. directory. Each entry is a member of the DIRECTORY_ENTRY
  243. structure.
  244. CallbackFunction - Pointer to a function to call for each entry in the
  245. directory/subtree.
  246. Context - Context to pass to the callback function.
  247. Buffer - A scratch buffer to use.
  248. BufferSize - The length of Buffer. Must be greater than sizeof(WCHAR).
  249. Return Value:
  250. NTSTATUS - status of the operation.
  251. --*/
  252. {
  253. NTSTATUS status;
  254. HANDLE fileHandle;
  255. OBJECT_ATTRIBUTES objectAttributes;
  256. IO_STATUS_BLOCK ioStatus;
  257. BOOLEAN bRestartScan, bIsDotPath;
  258. WCHAR savedChar;
  259. PFILE_BOTH_DIR_INFORMATION pFileInfo;
  260. UNICODE_STRING entryName;
  261. USHORT newNameLength;
  262. PDIRWALK_ENTRY pDirEntry;
  263. ULONG OpenFlags;
  264. //
  265. // Setup initial values
  266. //
  267. bRestartScan = TRUE;
  268. //
  269. // Open the file for list directory access
  270. //
  271. if (Flags & DIRWALK_TRAVERSE_MOUNTPOINTS) {
  272. OpenFlags = FILE_DIRECTORY_FILE |
  273. FILE_OPEN_FOR_BACKUP_INTENT;
  274. } else {
  275. OpenFlags = FILE_OPEN_REPARSE_POINT |
  276. FILE_DIRECTORY_FILE |
  277. FILE_OPEN_FOR_BACKUP_INTENT;
  278. }
  279. InitializeObjectAttributes(
  280. &objectAttributes,
  281. Directory,
  282. OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
  283. NULL,
  284. NULL
  285. );
  286. status = ZwOpenFile(
  287. &fileHandle,
  288. FILE_LIST_DIRECTORY | SYNCHRONIZE,
  289. &objectAttributes,
  290. &ioStatus,
  291. FILE_SHARE_READ,
  292. OpenFlags
  293. );
  294. if (!NT_SUCCESS(status)) {
  295. goto cleanup;
  296. }
  297. //
  298. // Do the directory loop
  299. //
  300. while(1) {
  301. //
  302. // We subtract off a WCHAR so that we can append a terminating null as
  303. // needed.
  304. //
  305. ASSERT(BufferSize > sizeof(WCHAR));
  306. status = ZwQueryDirectoryFile(
  307. fileHandle,
  308. (HANDLE)NULL,
  309. (PIO_APC_ROUTINE)NULL,
  310. (PVOID)NULL,
  311. &ioStatus,
  312. Buffer,
  313. BufferSize - sizeof(WCHAR),
  314. FileBothDirectoryInformation,
  315. FALSE,
  316. (PUNICODE_STRING)NULL,
  317. bRestartScan
  318. );
  319. if (!NT_SUCCESS(status)) {
  320. break;
  321. }
  322. //
  323. // We may come back here. Make sure the file scan doesn't start back
  324. // over.
  325. //
  326. bRestartScan = FALSE;
  327. //
  328. // Wait for the event to complete if neccessary.
  329. //
  330. if (status == STATUS_PENDING) {
  331. ZwWaitForSingleObject(fileHandle, TRUE, NULL);
  332. status = ioStatus.Status;
  333. //
  334. // Check the Irp for success
  335. //
  336. if (!NT_SUCCESS(status)) {
  337. break;
  338. }
  339. }
  340. //
  341. // Walk each returned record. Note that we won't be here if there are
  342. // no records, as ioStatus will have contains STATUS_NO_MORE_FILES.
  343. //
  344. pFileInfo = (PFILE_BOTH_DIR_INFORMATION) Buffer;
  345. while(1) {
  346. //
  347. // Temporarily terminate the file. We allocated an extra WCHAR to
  348. // make sure we could safely do this.
  349. //
  350. savedChar = pFileInfo->FileName[pFileInfo->FileNameLength/sizeof(WCHAR)];
  351. pFileInfo->FileName[pFileInfo->FileNameLength/sizeof(WCHAR)] = 0;
  352. //
  353. // Build a full unicode path for the file along with a directory
  354. // entry at the same time.
  355. //
  356. RtlInitUnicodeString(&entryName, pFileInfo->FileName);
  357. newNameLength =
  358. (Directory->Length + entryName.Length + sizeof(WCHAR));
  359. pDirEntry = (PDIRWALK_ENTRY) ExAllocatePoolWithTag(
  360. PagedPool,
  361. sizeof(DIRWALK_ENTRY) + newNameLength - sizeof(WCHAR),
  362. POOLTAG_FILEUTIL
  363. );
  364. if (pDirEntry == NULL) {
  365. status = STATUS_INSUFFICIENT_RESOURCES;
  366. break;
  367. }
  368. pDirEntry->Directory.Length = 0;
  369. pDirEntry->Directory.MaximumLength = newNameLength;
  370. pDirEntry->Directory.Buffer = &pDirEntry->Name[0];
  371. RtlCopyUnicodeString(&pDirEntry->Directory, Directory);
  372. RtlAppendUnicodeToString(&pDirEntry->Directory, L"\\");
  373. RtlAppendUnicodeStringToString(&pDirEntry->Directory, &entryName);
  374. if (pFileInfo->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  375. //
  376. // Check for . and ..
  377. //
  378. if ((!_wcsicmp(pFileInfo->FileName, L".")) ||
  379. (!_wcsicmp(pFileInfo->FileName, L".."))) {
  380. bIsDotPath = TRUE;
  381. }
  382. else {
  383. bIsDotPath = FALSE;
  384. }
  385. if ((Flags & DIRWALK_INCLUDE_DIRECTORIES) &&
  386. ((!(Flags & DIRWALK_CULL_DOTPATHS)) || (!bIsDotPath))) {
  387. status = CallbackFunction(
  388. &pDirEntry->Directory,
  389. &entryName,
  390. pFileInfo->FileAttributes,
  391. Context
  392. );
  393. }
  394. if ((!bIsDotPath) && (Flags & DIRWALK_TRAVERSE)) {
  395. InsertTailList(DirList, &pDirEntry->Link);
  396. } else {
  397. ExFreePool(pDirEntry);
  398. }
  399. } else {
  400. if (Flags & DIRWALK_INCLUDE_FILES) {
  401. status = CallbackFunction(
  402. &pDirEntry->Directory,
  403. &entryName,
  404. pFileInfo->FileAttributes,
  405. Context
  406. );
  407. }
  408. ExFreePool(pDirEntry);
  409. }
  410. if (!NT_SUCCESS(status)) {
  411. break;
  412. }
  413. //
  414. // Put back the character we wrote down. It might have been part of
  415. // the next entry.
  416. //
  417. pFileInfo->FileName[pFileInfo->FileNameLength/sizeof(WCHAR)] = savedChar;
  418. //
  419. // Check if there is another record, if there isn't then we
  420. // simply get out of this loop
  421. //
  422. if (pFileInfo->NextEntryOffset == 0) {
  423. break;
  424. }
  425. //
  426. // There is another record so advance FileInfo to the next
  427. // record
  428. //
  429. pFileInfo = (PFILE_BOTH_DIR_INFORMATION)
  430. (((PUCHAR) pFileInfo) + pFileInfo->NextEntryOffset);
  431. }
  432. if (!NT_SUCCESS(status)) {
  433. break;
  434. }
  435. }
  436. ZwClose( fileHandle );
  437. if (status == STATUS_NO_MORE_FILES) {
  438. status = STATUS_SUCCESS;
  439. }
  440. cleanup:
  441. return status;
  442. }
  443. NTSTATUS
  444. IopFileUtilClearAttributes(
  445. IN PUNICODE_STRING FullPathName,
  446. IN ULONG FileAttributes
  447. )
  448. /*++
  449. Routine Description:
  450. This function clears the passed in attributes off the specified file.
  451. Arguments:
  452. FullPathName - Full path name of the identified file.
  453. FileAttributes - Attributes to clear.
  454. Return Value:
  455. NTSTATUS.
  456. --*/
  457. {
  458. HANDLE fileHandle;
  459. OBJECT_ATTRIBUTES objectAttributes;
  460. IO_STATUS_BLOCK ioStatus;
  461. FILE_BASIC_INFORMATION fileBasicInformation;
  462. ULONG newAttributes;
  463. NTSTATUS status;
  464. //
  465. // First we open the file.
  466. //
  467. InitializeObjectAttributes(
  468. &objectAttributes,
  469. FullPathName,
  470. OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
  471. NULL,
  472. NULL
  473. );
  474. status = ZwOpenFile(
  475. &fileHandle,
  476. FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE,
  477. &objectAttributes,
  478. &ioStatus,
  479. FILE_SHARE_READ | FILE_SHARE_WRITE,
  480. FILE_OPEN_REPARSE_POINT | FILE_SYNCHRONOUS_IO_NONALERT |
  481. FILE_OPEN_FOR_BACKUP_INTENT | FILE_WRITE_THROUGH
  482. );
  483. if (!NT_SUCCESS(status)) {
  484. return status;
  485. }
  486. //
  487. // Then we get the file attributes.
  488. //
  489. status = ZwQueryInformationFile(
  490. fileHandle,
  491. &ioStatus,
  492. &fileBasicInformation,
  493. sizeof(fileBasicInformation),
  494. FileBasicInformation
  495. );
  496. if (!NT_SUCCESS(status)) {
  497. ZwClose(fileHandle);
  498. return status;
  499. }
  500. //
  501. // Anything to do?
  502. //
  503. if (fileBasicInformation.FileAttributes & FileAttributes) {
  504. //
  505. // Clear the specified bits.
  506. //
  507. newAttributes = fileBasicInformation.FileAttributes;
  508. newAttributes &= ~FileAttributes;
  509. if (newAttributes == 0) {
  510. newAttributes = FILE_ATTRIBUTE_NORMAL;
  511. }
  512. //
  513. // Zero fields that shouldn't be touched.
  514. //
  515. RtlZeroMemory(
  516. &fileBasicInformation,
  517. sizeof(FILE_BASIC_INFORMATION)
  518. );
  519. fileBasicInformation.FileAttributes = newAttributes;
  520. //
  521. // Commit the changes.
  522. //
  523. status = ZwSetInformationFile(
  524. fileHandle,
  525. &ioStatus,
  526. &fileBasicInformation,
  527. sizeof(fileBasicInformation),
  528. FileBasicInformation
  529. );
  530. }
  531. ZwClose(fileHandle);
  532. return status;
  533. }
  534. NTSTATUS
  535. IopFileUtilRename(
  536. IN PUNICODE_STRING SourcePathName,
  537. IN PUNICODE_STRING DestinationPathName,
  538. IN BOOLEAN ReplaceIfPresent
  539. )
  540. /*++
  541. Routine Description:
  542. This function renames or moves a file or directory.
  543. Arguments:
  544. SourcePathName - Full path name of the file or directory to rename.
  545. DestinationPathName - Future full path name of the file or directory.
  546. ReplaceIfPresent - If true, NewPathName is deleted if already present.
  547. Return Value:
  548. NTSTATUS.
  549. --*/
  550. {
  551. HANDLE fileHandle;
  552. OBJECT_ATTRIBUTES objectAttributes;
  553. IO_STATUS_BLOCK ioStatus;
  554. PFILE_RENAME_INFORMATION pNewName;
  555. NTSTATUS status;
  556. pNewName = ExAllocatePoolWithTag(
  557. PagedPool,
  558. sizeof(FILE_RENAME_INFORMATION) + DestinationPathName->Length,
  559. POOLTAG_FILEUTIL
  560. );
  561. if (pNewName == NULL) {
  562. return STATUS_INSUFFICIENT_RESOURCES;
  563. }
  564. //
  565. // If we may be replacing the file, we first need to clear the read only
  566. // attributes.
  567. //
  568. if (ReplaceIfPresent) {
  569. //
  570. // Errors are ignored as the file may not exist.
  571. //
  572. IopFileUtilClearAttributes(
  573. DestinationPathName,
  574. ( FILE_ATTRIBUTE_READONLY |
  575. FILE_ATTRIBUTE_HIDDEN |
  576. FILE_ATTRIBUTE_SYSTEM )
  577. );
  578. }
  579. InitializeObjectAttributes(
  580. &objectAttributes,
  581. SourcePathName,
  582. OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
  583. NULL,
  584. NULL
  585. );
  586. status = ZwOpenFile(
  587. &fileHandle,
  588. FILE_READ_ATTRIBUTES | DELETE | SYNCHRONIZE,
  589. &objectAttributes,
  590. &ioStatus,
  591. FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
  592. FILE_OPEN_REPARSE_POINT | FILE_SYNCHRONOUS_IO_NONALERT |
  593. FILE_OPEN_FOR_BACKUP_INTENT | FILE_WRITE_THROUGH
  594. );
  595. if (!NT_SUCCESS(status)) {
  596. ExFreePool(pNewName);
  597. return status;
  598. }
  599. //
  600. // Change \\SystemRoot\LastGood\Blah... to \\SystemRoot\Blah...
  601. //
  602. RtlCopyMemory(
  603. pNewName->FileName,
  604. DestinationPathName->Buffer,
  605. DestinationPathName->Length
  606. );
  607. pNewName->ReplaceIfExists = ReplaceIfPresent;
  608. pNewName->RootDirectory = NULL;
  609. pNewName->FileNameLength = DestinationPathName->Length;
  610. status = ZwSetInformationFile(
  611. fileHandle,
  612. &ioStatus,
  613. pNewName,
  614. pNewName->FileNameLength + sizeof(FILE_RENAME_INFORMATION),
  615. FileRenameInformation
  616. );
  617. ExFreePool(pNewName);
  618. ZwClose(fileHandle);
  619. return status;
  620. }