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.

1528 lines
44 KiB

  1. /*++
  2. Copyright (c) 1989-2000 Microsoft Corporation
  3. Module Name:
  4. DirCtrl.c
  5. Abstract:
  6. This module implements the File Directory Control routines for Cdfs called
  7. by the Fsd/Fsp dispatch drivers.
  8. // @@BEGIN_DDKSPLIT
  9. Author:
  10. Brian Andrew [BrianAn] 01-July-1995
  11. Revision History:
  12. // @@END_DDKSPLIT
  13. --*/
  14. #include "CdProcs.h"
  15. //
  16. // The Bug check file id for this module
  17. //
  18. #define BugCheckFileId (CDFS_BUG_CHECK_DIRCTRL)
  19. //
  20. // Local support routines
  21. //
  22. NTSTATUS
  23. CdQueryDirectory (
  24. IN PIRP_CONTEXT IrpContext,
  25. IN PIRP Irp,
  26. IN PIO_STACK_LOCATION IrpSp,
  27. IN PFCB Fcb,
  28. IN PCCB Ccb
  29. );
  30. NTSTATUS
  31. CdNotifyChangeDirectory (
  32. IN PIRP_CONTEXT IrpContext,
  33. IN PIRP Irp,
  34. IN PIO_STACK_LOCATION IrpSp,
  35. IN PCCB Ccb
  36. );
  37. VOID
  38. CdInitializeEnumeration (
  39. IN PIRP_CONTEXT IrpContext,
  40. IN PIO_STACK_LOCATION IrpSp,
  41. IN PFCB Fcb,
  42. IN OUT PCCB Ccb,
  43. IN OUT PFILE_ENUM_CONTEXT FileContext,
  44. OUT PBOOLEAN ReturnNextEntry,
  45. OUT PBOOLEAN ReturnSingleEntry,
  46. OUT PBOOLEAN InitialQuery
  47. );
  48. BOOLEAN
  49. CdEnumerateIndex (
  50. IN PIRP_CONTEXT IrpContext,
  51. IN PCCB Ccb,
  52. IN OUT PFILE_ENUM_CONTEXT FileContext,
  53. IN BOOLEAN ReturnNextEntry
  54. );
  55. #ifdef ALLOC_PRAGMA
  56. #pragma alloc_text(PAGE, CdCommonDirControl)
  57. #pragma alloc_text(PAGE, CdEnumerateIndex)
  58. #pragma alloc_text(PAGE, CdInitializeEnumeration)
  59. #pragma alloc_text(PAGE, CdNotifyChangeDirectory)
  60. #pragma alloc_text(PAGE, CdQueryDirectory)
  61. #endif
  62. NTSTATUS
  63. CdCommonDirControl (
  64. IN PIRP_CONTEXT IrpContext,
  65. IN PIRP Irp
  66. )
  67. /*++
  68. Routine Description:
  69. This routine is the entry point for the directory control operations. These
  70. are directory enumerations and directory notify calls. We verify the
  71. user's handle is for a directory and then call the appropriate routine.
  72. Arguments:
  73. Irp - Irp for this request.
  74. Return Value:
  75. NTSTATUS - Status returned from the lower level routines.
  76. --*/
  77. {
  78. NTSTATUS Status;
  79. PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp );
  80. PFCB Fcb;
  81. PCCB Ccb;
  82. PAGED_CODE();
  83. //
  84. // Decode the user file object and fail this request if it is not
  85. // a user directory.
  86. //
  87. if (CdDecodeFileObject( IrpContext,
  88. IrpSp->FileObject,
  89. &Fcb,
  90. &Ccb ) != UserDirectoryOpen) {
  91. CdCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER );
  92. return STATUS_INVALID_PARAMETER;
  93. }
  94. //
  95. // We know this is a directory control so we'll case on the
  96. // minor function, and call a internal worker routine to complete
  97. // the irp.
  98. //
  99. switch (IrpSp->MinorFunction) {
  100. case IRP_MN_QUERY_DIRECTORY:
  101. Status = CdQueryDirectory( IrpContext, Irp, IrpSp, Fcb, Ccb );
  102. break;
  103. case IRP_MN_NOTIFY_CHANGE_DIRECTORY:
  104. Status = CdNotifyChangeDirectory( IrpContext, Irp, IrpSp, Ccb );
  105. break;
  106. default:
  107. CdCompleteRequest( IrpContext, Irp, STATUS_INVALID_DEVICE_REQUEST );
  108. Status = STATUS_INVALID_DEVICE_REQUEST;
  109. break;
  110. }
  111. return Status;
  112. }
  113. //
  114. // Local support routines
  115. //
  116. NTSTATUS
  117. CdQueryDirectory (
  118. IN PIRP_CONTEXT IrpContext,
  119. IN PIRP Irp,
  120. IN PIO_STACK_LOCATION IrpSp,
  121. IN PFCB Fcb,
  122. IN PCCB Ccb
  123. )
  124. /*++
  125. Routine Description:
  126. This routine performs the query directory operation. It is responsible
  127. for either completing of enqueuing the input Irp. We store the state of the
  128. search in the Ccb.
  129. Arguments:
  130. Irp - Supplies the Irp to process
  131. IrpSp - Stack location for this Irp.
  132. Fcb - Fcb for this directory.
  133. Ccb - Ccb for this directory open.
  134. Return Value:
  135. NTSTATUS - The return status for the operation
  136. --*/
  137. {
  138. NTSTATUS Status = STATUS_SUCCESS;
  139. ULONG Information = 0;
  140. ULONG LastEntry = 0;
  141. ULONG NextEntry = 0;
  142. ULONG FileNameBytes;
  143. ULONG SeparatorBytes;
  144. ULONG VersionStringBytes;
  145. FILE_ENUM_CONTEXT FileContext;
  146. PDIRENT ThisDirent;
  147. BOOLEAN InitialQuery;
  148. BOOLEAN ReturnNextEntry;
  149. BOOLEAN ReturnSingleEntry;
  150. BOOLEAN Found;
  151. BOOLEAN DoCcbUpdate = FALSE;
  152. PCHAR UserBuffer;
  153. ULONG BytesRemainingInBuffer;
  154. ULONG BaseLength;
  155. PFILE_BOTH_DIR_INFORMATION DirInfo;
  156. PFILE_NAMES_INFORMATION NamesInfo;
  157. PFILE_ID_FULL_DIR_INFORMATION IdFullDirInfo;
  158. PFILE_ID_BOTH_DIR_INFORMATION IdBothDirInfo;
  159. PAGED_CODE();
  160. //
  161. // Check if we support this search mode. Also remember the size of the base part of
  162. // each of these structures.
  163. //
  164. switch (IrpSp->Parameters.QueryDirectory.FileInformationClass) {
  165. case FileDirectoryInformation:
  166. BaseLength = FIELD_OFFSET( FILE_DIRECTORY_INFORMATION,
  167. FileName[0] );
  168. break;
  169. case FileFullDirectoryInformation:
  170. BaseLength = FIELD_OFFSET( FILE_FULL_DIR_INFORMATION,
  171. FileName[0] );
  172. break;
  173. case FileIdFullDirectoryInformation:
  174. BaseLength = FIELD_OFFSET( FILE_ID_FULL_DIR_INFORMATION,
  175. FileName[0] );
  176. break;
  177. case FileNamesInformation:
  178. BaseLength = FIELD_OFFSET( FILE_NAMES_INFORMATION,
  179. FileName[0] );
  180. break;
  181. case FileBothDirectoryInformation:
  182. BaseLength = FIELD_OFFSET( FILE_BOTH_DIR_INFORMATION,
  183. FileName[0] );
  184. break;
  185. case FileIdBothDirectoryInformation:
  186. BaseLength = FIELD_OFFSET( FILE_ID_BOTH_DIR_INFORMATION,
  187. FileName[0] );
  188. break;
  189. default:
  190. CdCompleteRequest( IrpContext, Irp, STATUS_INVALID_INFO_CLASS );
  191. return STATUS_INVALID_INFO_CLASS;
  192. }
  193. //
  194. // Get the user buffer.
  195. //
  196. CdMapUserBuffer( IrpContext, &UserBuffer);
  197. //
  198. // Initialize our search context.
  199. //
  200. CdInitializeFileContext( IrpContext, &FileContext );
  201. //
  202. // Acquire the directory.
  203. //
  204. CdAcquireFileShared( IrpContext, Fcb );
  205. //
  206. // Use a try-finally to facilitate cleanup.
  207. //
  208. try {
  209. //
  210. // Verify the Fcb is still good.
  211. //
  212. CdVerifyFcbOperation( IrpContext, Fcb );
  213. //
  214. // Start by getting the initial state for the enumeration. This will set up the Ccb with
  215. // the initial search parameters and let us know the starting offset in the directory
  216. // to search.
  217. //
  218. CdInitializeEnumeration( IrpContext,
  219. IrpSp,
  220. Fcb,
  221. Ccb,
  222. &FileContext,
  223. &ReturnNextEntry,
  224. &ReturnSingleEntry,
  225. &InitialQuery );
  226. //
  227. // The current dirent is stored in the InitialDirent field. We capture
  228. // this here so that we have a valid restart point even if we don't
  229. // find a single entry.
  230. //
  231. ThisDirent = &FileContext.InitialDirent->Dirent;
  232. //
  233. // At this point we are about to enter our query loop. We have
  234. // determined the index into the directory file to begin the
  235. // search. LastEntry and NextEntry are used to index into the user
  236. // buffer. LastEntry is the last entry we've added, NextEntry is
  237. // current one we're working on. If NextEntry is non-zero, then
  238. // at least one entry was added.
  239. //
  240. while (TRUE) {
  241. //
  242. // If the user had requested only a single match and we have
  243. // returned that, then we stop at this point. We update the Ccb with
  244. // the status based on the last entry returned.
  245. //
  246. if ((NextEntry != 0) && ReturnSingleEntry) {
  247. DoCcbUpdate = TRUE;
  248. try_leave( Status );
  249. }
  250. //
  251. // We try to locate the next matching dirent. Our search if based on a starting
  252. // dirent offset, whether we should return the current or next entry, whether
  253. // we should be doing a short name search and finally whether we should be
  254. // checking for a version match.
  255. //
  256. Found = CdEnumerateIndex( IrpContext, Ccb, &FileContext, ReturnNextEntry );
  257. //
  258. // Initialize the value for the next search.
  259. //
  260. ReturnNextEntry = TRUE;
  261. //
  262. // If we didn't receive a dirent, then we are at the end of the
  263. // directory. If we have returned any files, we exit with
  264. // success, otherwise we return STATUS_NO_MORE_FILES.
  265. //
  266. if (!Found) {
  267. if (NextEntry == 0) {
  268. Status = STATUS_NO_MORE_FILES;
  269. if (InitialQuery) {
  270. Status = STATUS_NO_SUCH_FILE;
  271. }
  272. }
  273. DoCcbUpdate = TRUE;
  274. try_leave( Status );
  275. }
  276. //
  277. // Remember the dirent for the file we just found.
  278. //
  279. ThisDirent = &FileContext.InitialDirent->Dirent;
  280. //
  281. // Here are the rules concerning filling up the buffer:
  282. //
  283. // 1. The Io system garentees that there will always be
  284. // enough room for at least one base record.
  285. //
  286. // 2. If the full first record (including file name) cannot
  287. // fit, as much of the name as possible is copied and
  288. // STATUS_BUFFER_OVERFLOW is returned.
  289. //
  290. // 3. If a subsequent record cannot completely fit into the
  291. // buffer, none of it (as in 0 bytes) is copied, and
  292. // STATUS_SUCCESS is returned. A subsequent query will
  293. // pick up with this record.
  294. //
  295. //
  296. // Let's compute the number of bytes we need to transfer the current entry.
  297. //
  298. SeparatorBytes =
  299. VersionStringBytes = 0;
  300. //
  301. // We can look directly at the dirent that we found.
  302. //
  303. FileNameBytes = ThisDirent->CdFileName.FileName.Length;
  304. //
  305. // Compute the number of bytes for the version string if
  306. // we will return this. Allow directories with illegal ";".
  307. //
  308. if (((Ccb->SearchExpression.VersionString.Length != 0) ||
  309. (FlagOn(ThisDirent->DirentFlags, CD_ATTRIBUTE_DIRECTORY))) &&
  310. (ThisDirent->CdFileName.VersionString.Length != 0)) {
  311. SeparatorBytes = 2;
  312. VersionStringBytes = ThisDirent->CdFileName.VersionString.Length;
  313. }
  314. //
  315. // If the slot for the next entry would be beyond the length of the
  316. // user's buffer just exit (we know we've returned at least one entry
  317. // already). This will happen when we align the pointer past the end.
  318. //
  319. if (NextEntry > IrpSp->Parameters.QueryDirectory.Length) {
  320. ReturnNextEntry = FALSE;
  321. DoCcbUpdate = TRUE;
  322. try_leave( Status = STATUS_SUCCESS );
  323. }
  324. //
  325. // Compute the number of bytes remaining in the buffer. Round this
  326. // down to a WCHAR boundary so we can copy full characters.
  327. //
  328. BytesRemainingInBuffer = IrpSp->Parameters.QueryDirectory.Length - NextEntry;
  329. ClearFlag( BytesRemainingInBuffer, 1 );
  330. //
  331. // If this won't fit and we have returned a previous entry then just
  332. // return STATUS_SUCCESS.
  333. //
  334. if ((BaseLength + FileNameBytes + SeparatorBytes + VersionStringBytes) > BytesRemainingInBuffer) {
  335. //
  336. // If we already found an entry then just exit.
  337. //
  338. if (NextEntry != 0) {
  339. ReturnNextEntry = FALSE;
  340. DoCcbUpdate = TRUE;
  341. try_leave( Status = STATUS_SUCCESS );
  342. }
  343. //
  344. // Don't even try to return the version string if it doesn't all fit.
  345. // Reduce the FileNameBytes to just fit in the buffer.
  346. //
  347. if ((BaseLength + FileNameBytes) > BytesRemainingInBuffer) {
  348. FileNameBytes = BytesRemainingInBuffer - BaseLength;
  349. }
  350. //
  351. // Don't return any version string bytes.
  352. //
  353. VersionStringBytes =
  354. SeparatorBytes = 0;
  355. //
  356. // Use a status code of STATUS_BUFFER_OVERFLOW. Also set
  357. // ReturnSingleEntry so that we will exit the loop at the top.
  358. //
  359. Status = STATUS_BUFFER_OVERFLOW;
  360. ReturnSingleEntry = TRUE;
  361. }
  362. //
  363. // Protect access to the user buffer with an exception handler.
  364. // Since (at our request) IO doesn't buffer these requests, we have
  365. // to guard against a user messing with the page protection and other
  366. // such trickery.
  367. //
  368. try {
  369. //
  370. // Zero and initialize the base part of the current entry.
  371. //
  372. RtlZeroMemory( Add2Ptr( UserBuffer, NextEntry, PVOID ),
  373. BaseLength );
  374. //
  375. // Now we have an entry to return to our caller.
  376. // We'll case on the type of information requested and fill up
  377. // the user buffer if everything fits.
  378. //
  379. switch (IrpSp->Parameters.QueryDirectory.FileInformationClass) {
  380. case FileBothDirectoryInformation:
  381. case FileFullDirectoryInformation:
  382. case FileIdBothDirectoryInformation:
  383. case FileIdFullDirectoryInformation:
  384. case FileDirectoryInformation:
  385. DirInfo = Add2Ptr( UserBuffer, NextEntry, PFILE_BOTH_DIR_INFORMATION );
  386. //
  387. // Use the create time for all the time stamps.
  388. //
  389. CdConvertCdTimeToNtTime( IrpContext,
  390. FileContext.InitialDirent->Dirent.CdTime,
  391. &DirInfo->CreationTime );
  392. DirInfo->LastWriteTime = DirInfo->ChangeTime = DirInfo->CreationTime;
  393. //
  394. // Set the attributes and sizes separately for directories and
  395. // files.
  396. //
  397. if (FlagOn( ThisDirent->DirentFlags, CD_ATTRIBUTE_DIRECTORY )) {
  398. DirInfo->EndOfFile.QuadPart = DirInfo->AllocationSize.QuadPart = 0;
  399. SetFlag( DirInfo->FileAttributes, FILE_ATTRIBUTE_DIRECTORY );
  400. } else {
  401. DirInfo->EndOfFile.QuadPart = FileContext.FileSize;
  402. DirInfo->AllocationSize.QuadPart = LlSectorAlign( FileContext.FileSize );
  403. }
  404. //
  405. // All Cdrom files are readonly. We also copy the existence
  406. // bit to the hidden attribute.
  407. //
  408. SetFlag( DirInfo->FileAttributes, FILE_ATTRIBUTE_READONLY );
  409. if (FlagOn( ThisDirent->DirentFlags,
  410. CD_ATTRIBUTE_HIDDEN )) {
  411. SetFlag( DirInfo->FileAttributes, FILE_ATTRIBUTE_HIDDEN );
  412. }
  413. DirInfo->FileIndex = ThisDirent->DirentOffset;
  414. DirInfo->FileNameLength = FileNameBytes + SeparatorBytes + VersionStringBytes;
  415. break;
  416. case FileNamesInformation:
  417. NamesInfo = Add2Ptr( UserBuffer, NextEntry, PFILE_NAMES_INFORMATION );
  418. NamesInfo->FileIndex = ThisDirent->DirentOffset;
  419. NamesInfo->FileNameLength = FileNameBytes + SeparatorBytes + VersionStringBytes;
  420. break;
  421. }
  422. //
  423. // Fill in the FileId
  424. //
  425. switch (IrpSp->Parameters.QueryDirectory.FileInformationClass) {
  426. case FileIdBothDirectoryInformation:
  427. IdBothDirInfo = Add2Ptr( UserBuffer, NextEntry, PFILE_ID_BOTH_DIR_INFORMATION );
  428. CdSetFidFromParentAndDirent( IdBothDirInfo->FileId, Fcb, ThisDirent );
  429. break;
  430. case FileIdFullDirectoryInformation:
  431. IdFullDirInfo = Add2Ptr( UserBuffer, NextEntry, PFILE_ID_FULL_DIR_INFORMATION );
  432. CdSetFidFromParentAndDirent( IdFullDirInfo->FileId, Fcb, ThisDirent );
  433. break;
  434. default:
  435. break;
  436. }
  437. //
  438. // Now copy as much of the name as possible. We also may have a version
  439. // string to copy.
  440. //
  441. if (FileNameBytes != 0) {
  442. //
  443. // This is a Unicode name, we can copy the bytes directly.
  444. //
  445. RtlCopyMemory( Add2Ptr( UserBuffer, NextEntry + BaseLength, PVOID ),
  446. ThisDirent->CdFileName.FileName.Buffer,
  447. FileNameBytes );
  448. if (SeparatorBytes != 0) {
  449. *(Add2Ptr( UserBuffer,
  450. NextEntry + BaseLength + FileNameBytes,
  451. PWCHAR )) = L';';
  452. if (VersionStringBytes != 0) {
  453. RtlCopyMemory( Add2Ptr( UserBuffer,
  454. NextEntry + BaseLength + FileNameBytes + sizeof( WCHAR ),
  455. PVOID ),
  456. ThisDirent->CdFileName.VersionString.Buffer,
  457. VersionStringBytes );
  458. }
  459. }
  460. }
  461. //
  462. // Fill in the short name if we got STATUS_SUCCESS. The short name
  463. // may already be in the file context. Otherwise we will check
  464. // whether the long name is 8.3. Special case the self and parent
  465. // directory names.
  466. //
  467. if ((Status == STATUS_SUCCESS) &&
  468. (IrpSp->Parameters.QueryDirectory.FileInformationClass == FileBothDirectoryInformation ||
  469. IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdBothDirectoryInformation) &&
  470. (Ccb->SearchExpression.VersionString.Length == 0) &&
  471. !FlagOn( ThisDirent->Flags, DIRENT_FLAG_CONSTANT_ENTRY )) {
  472. //
  473. // If we already have the short name then copy into the user's buffer.
  474. //
  475. if (FileContext.ShortName.FileName.Length != 0) {
  476. RtlCopyMemory( DirInfo->ShortName,
  477. FileContext.ShortName.FileName.Buffer,
  478. FileContext.ShortName.FileName.Length );
  479. DirInfo->ShortNameLength = (CCHAR) FileContext.ShortName.FileName.Length;
  480. //
  481. // If the short name length is currently zero then check if
  482. // the long name is not 8.3. We can copy the short name in
  483. // unicode form directly into the caller's buffer.
  484. //
  485. } else {
  486. if (!CdIs8dot3Name( IrpContext,
  487. ThisDirent->CdFileName.FileName )) {
  488. CdGenerate8dot3Name( IrpContext,
  489. &ThisDirent->CdCaseFileName.FileName,
  490. ThisDirent->DirentOffset,
  491. DirInfo->ShortName,
  492. &FileContext.ShortName.FileName.Length );
  493. DirInfo->ShortNameLength = (CCHAR) FileContext.ShortName.FileName.Length;
  494. }
  495. }
  496. }
  497. //
  498. // Sum the total number of bytes for the information field.
  499. //
  500. FileNameBytes += SeparatorBytes + VersionStringBytes;
  501. //
  502. // Update the information with the number of bytes stored in the
  503. // buffer. We quad-align the existing buffer to add any necessary
  504. // pad bytes.
  505. //
  506. Information = NextEntry + BaseLength + FileNameBytes;
  507. //
  508. // Go back to the previous entry and fill in the update to this entry.
  509. //
  510. *(Add2Ptr( UserBuffer, LastEntry, PULONG )) = NextEntry - LastEntry;
  511. //
  512. // Set up our variables for the next dirent.
  513. //
  514. InitialQuery = FALSE;
  515. LastEntry = NextEntry;
  516. NextEntry = QuadAlign( Information );
  517. } except (EXCEPTION_EXECUTE_HANDLER) {
  518. //
  519. // We had a problem filling in the user's buffer, so stop and
  520. // fail this request. This is the only reason any exception
  521. // would have occured at this level.
  522. //
  523. Information = 0;
  524. try_leave( Status = GetExceptionCode());
  525. }
  526. }
  527. DoCcbUpdate = TRUE;
  528. } finally {
  529. //
  530. // Cleanup our search context - *before* aquiring the FCB mutex exclusive,
  531. // else can block on threads in cdcreateinternalstream/purge which
  532. // hold the FCB but are waiting for all maps in this stream to be released.
  533. //
  534. CdCleanupFileContext( IrpContext, &FileContext );
  535. //
  536. // Now we can safely aqure the FCB mutex if we need to.
  537. //
  538. if (DoCcbUpdate && !NT_ERROR( Status )) {
  539. //
  540. // Update the Ccb to show the current state of the enumeration.
  541. //
  542. CdLockFcb( IrpContext, Fcb );
  543. Ccb->CurrentDirentOffset = ThisDirent->DirentOffset;
  544. ClearFlag( Ccb->Flags, CCB_FLAG_ENUM_RETURN_NEXT );
  545. if (ReturnNextEntry) {
  546. SetFlag( Ccb->Flags, CCB_FLAG_ENUM_RETURN_NEXT );
  547. }
  548. CdUnlockFcb( IrpContext, Fcb );
  549. }
  550. //
  551. // Release the Fcb.
  552. //
  553. CdReleaseFile( IrpContext, Fcb );
  554. }
  555. //
  556. // Complete the request here.
  557. //
  558. Irp->IoStatus.Information = Information;
  559. CdCompleteRequest( IrpContext, Irp, Status );
  560. return Status;
  561. }
  562. //
  563. // Local support routines
  564. //
  565. NTSTATUS
  566. CdNotifyChangeDirectory (
  567. IN PIRP_CONTEXT IrpContext,
  568. IN PIRP Irp,
  569. IN PIO_STACK_LOCATION IrpSp,
  570. IN PCCB Ccb
  571. )
  572. /*++
  573. Routine Description:
  574. This routine performs the notify change directory operation. It is
  575. responsible for either completing of enqueuing the input Irp. Although there
  576. will never be a notify signalled on a CDROM disk we still support this call.
  577. We have already checked that this is not an OpenById handle.
  578. Arguments:
  579. Irp - Supplies the Irp to process
  580. IrpSp - Io stack location for this request.
  581. Ccb - Handle to the directory being watched.
  582. Return Value:
  583. NTSTATUS - STATUS_PENDING, any other error will raise.
  584. --*/
  585. {
  586. PAGED_CODE();
  587. //
  588. // Always set the wait bit in the IrpContext so the initial wait can't fail.
  589. //
  590. SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
  591. //
  592. // Acquire the Vcb shared.
  593. //
  594. CdAcquireVcbShared( IrpContext, IrpContext->Vcb, FALSE );
  595. //
  596. // Use a try-finally to facilitate cleanup.
  597. //
  598. try {
  599. //
  600. // Verify the Vcb.
  601. //
  602. CdVerifyVcb( IrpContext, IrpContext->Vcb );
  603. //
  604. // Call the Fsrtl package to process the request. We cast the
  605. // unicode strings to ansi strings as the dir notify package
  606. // only deals with memory matching.
  607. //
  608. FsRtlNotifyFullChangeDirectory( IrpContext->Vcb->NotifySync,
  609. &IrpContext->Vcb->DirNotifyList,
  610. Ccb,
  611. (PSTRING) &IrpSp->FileObject->FileName,
  612. BooleanFlagOn( IrpSp->Flags, SL_WATCH_TREE ),
  613. FALSE,
  614. IrpSp->Parameters.NotifyDirectory.CompletionFilter,
  615. Irp,
  616. NULL,
  617. NULL );
  618. } finally {
  619. //
  620. // Release the Vcb.
  621. //
  622. CdReleaseVcb( IrpContext, IrpContext->Vcb );
  623. }
  624. //
  625. // Cleanup the IrpContext.
  626. //
  627. CdCompleteRequest( IrpContext, NULL, STATUS_SUCCESS );
  628. return STATUS_PENDING;
  629. }
  630. //
  631. // Local support routine
  632. //
  633. VOID
  634. CdInitializeEnumeration (
  635. IN PIRP_CONTEXT IrpContext,
  636. IN PIO_STACK_LOCATION IrpSp,
  637. IN PFCB Fcb,
  638. IN OUT PCCB Ccb,
  639. IN OUT PFILE_ENUM_CONTEXT FileContext,
  640. OUT PBOOLEAN ReturnNextEntry,
  641. OUT PBOOLEAN ReturnSingleEntry,
  642. OUT PBOOLEAN InitialQuery
  643. )
  644. /*++
  645. Routine Description:
  646. This routine is called to initialize the enumeration variables and structures.
  647. We look at the state of a previous enumeration from the Ccb as well as any
  648. input values from the user. On exit we will position the FileContext at
  649. a file in the directory and let the caller know whether this entry or the
  650. next entry should be returned.
  651. Arguments:
  652. IrpSp - Irp stack location for this request.
  653. Fcb - Fcb for this directory.
  654. Ccb - Ccb for the directory handle.
  655. FileContext - FileContext to use for this enumeration.
  656. ReturnNextEntry - Address to store whether we should return the entry at
  657. the FileContext position or the next entry.
  658. ReturnSingleEntry - Address to store whether we should only return
  659. a single entry.
  660. InitialQuery - Address to store whether this is the first enumeration
  661. query on this handle.
  662. Return Value:
  663. None.
  664. --*/
  665. {
  666. NTSTATUS Status;
  667. PUNICODE_STRING FileName;
  668. CD_NAME WildCardName;
  669. CD_NAME SearchExpression;
  670. ULONG CcbFlags;
  671. ULONG DirentOffset;
  672. ULONG LastDirentOffset;
  673. BOOLEAN KnownOffset;
  674. BOOLEAN Found;
  675. PAGED_CODE();
  676. //
  677. // If this is the initial query then build a search expression from the input
  678. // file name.
  679. //
  680. if (!FlagOn( Ccb->Flags, CCB_FLAG_ENUM_INITIALIZED )) {
  681. FileName = (PUNICODE_STRING) IrpSp->Parameters.QueryDirectory.FileName;
  682. CcbFlags = 0;
  683. //
  684. // If the filename is not specified or is a single '*' then we will
  685. // match all names.
  686. //
  687. if ((FileName == NULL) ||
  688. (FileName->Buffer == NULL) ||
  689. (FileName->Length == 0) ||
  690. ((FileName->Length == sizeof( WCHAR )) &&
  691. (FileName->Buffer[0] == L'*'))) {
  692. SetFlag( CcbFlags, CCB_FLAG_ENUM_MATCH_ALL );
  693. RtlZeroMemory( &SearchExpression, sizeof( SearchExpression ));
  694. //
  695. // Otherwise build the CdName from the name in the stack location.
  696. // This involves building both the name and version portions and
  697. // checking for wild card characters. We also upcase the string if
  698. // this is a case-insensitive search.
  699. //
  700. } else {
  701. //
  702. // Create a CdName to check for wild cards.
  703. //
  704. WildCardName.FileName = *FileName;
  705. CdConvertNameToCdName( IrpContext, &WildCardName );
  706. //
  707. // The name better have at least one character.
  708. //
  709. if (WildCardName.FileName.Length == 0) {
  710. CdRaiseStatus( IrpContext, STATUS_INVALID_PARAMETER );
  711. }
  712. //
  713. // Check for wildcards in the separate components.
  714. //
  715. if (FsRtlDoesNameContainWildCards( &WildCardName.FileName)) {
  716. SetFlag( CcbFlags, CCB_FLAG_ENUM_NAME_EXP_HAS_WILD );
  717. }
  718. if ((WildCardName.VersionString.Length != 0) &&
  719. (FsRtlDoesNameContainWildCards( &WildCardName.VersionString ))) {
  720. SetFlag( CcbFlags, CCB_FLAG_ENUM_VERSION_EXP_HAS_WILD );
  721. //
  722. // Check if this is a wild card only and match all version
  723. // strings.
  724. //
  725. if ((WildCardName.VersionString.Length == sizeof( WCHAR )) &&
  726. (WildCardName.VersionString.Buffer[0] == L'*')) {
  727. SetFlag( CcbFlags, CCB_FLAG_ENUM_VERSION_MATCH_ALL );
  728. }
  729. }
  730. //
  731. // Now create the search expression to store in the Ccb.
  732. //
  733. SearchExpression.FileName.Buffer = FsRtlAllocatePoolWithTag( CdPagedPool,
  734. FileName->Length,
  735. TAG_ENUM_EXPRESSION );
  736. SearchExpression.FileName.MaximumLength = FileName->Length;
  737. //
  738. // Either copy the name directly or perform the upcase.
  739. //
  740. if (FlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE )) {
  741. Status = RtlUpcaseUnicodeString( (PUNICODE_STRING) &SearchExpression.FileName,
  742. FileName,
  743. FALSE );
  744. //
  745. // This should never fail.
  746. //
  747. ASSERT( Status == STATUS_SUCCESS );
  748. } else {
  749. RtlCopyMemory( SearchExpression.FileName.Buffer,
  750. FileName->Buffer,
  751. FileName->Length );
  752. }
  753. //
  754. // Now split into the separate name and version components.
  755. //
  756. SearchExpression.FileName.Length = WildCardName.FileName.Length;
  757. SearchExpression.VersionString.Length = WildCardName.VersionString.Length;
  758. SearchExpression.VersionString.MaximumLength = WildCardName.VersionString.MaximumLength;
  759. SearchExpression.VersionString.Buffer = Add2Ptr( SearchExpression.FileName.Buffer,
  760. SearchExpression.FileName.Length + sizeof( WCHAR ),
  761. PWCHAR );
  762. }
  763. //
  764. // But we do not want to return the constant "." and ".." entries for
  765. // the root directory, for consistency with the rest of Microsoft's
  766. // filesystems.
  767. //
  768. if (Fcb == Fcb->Vcb->RootIndexFcb) {
  769. SetFlag( CcbFlags, CCB_FLAG_ENUM_NOMATCH_CONSTANT_ENTRY );
  770. }
  771. //
  772. // Now lock the Fcb in order to update the Ccb with the inital
  773. // enumeration values.
  774. //
  775. CdLockFcb( IrpContext, Fcb );
  776. //
  777. // Check again that this is the initial search.
  778. //
  779. if (!FlagOn( Ccb->Flags, CCB_FLAG_ENUM_INITIALIZED )) {
  780. //
  781. // Update the values in the Ccb.
  782. //
  783. Ccb->CurrentDirentOffset = Fcb->StreamOffset;
  784. Ccb->SearchExpression = SearchExpression;
  785. //
  786. // Set the appropriate flags in the Ccb.
  787. //
  788. SetFlag( Ccb->Flags, CcbFlags | CCB_FLAG_ENUM_INITIALIZED );
  789. //
  790. // Otherwise cleanup any buffer allocated here.
  791. //
  792. } else {
  793. if (!FlagOn( CcbFlags, CCB_FLAG_ENUM_MATCH_ALL )) {
  794. ExFreePool( SearchExpression.FileName.Buffer );
  795. }
  796. }
  797. //
  798. // Otherwise lock the Fcb so we can read the current enumeration values.
  799. //
  800. } else {
  801. CdLockFcb( IrpContext, Fcb );
  802. }
  803. //
  804. // Capture the current state of the enumeration.
  805. //
  806. // If the user specified an index then use his offset. We always
  807. // return the next entry in this case.
  808. //
  809. if (FlagOn( IrpSp->Flags, SL_INDEX_SPECIFIED )) {
  810. KnownOffset = FALSE;
  811. DirentOffset = IrpSp->Parameters.QueryDirectory.FileIndex;
  812. *ReturnNextEntry = TRUE;
  813. //
  814. // If we are restarting the scan then go from the self entry.
  815. //
  816. } else if (FlagOn( IrpSp->Flags, SL_RESTART_SCAN )) {
  817. KnownOffset = TRUE;
  818. DirentOffset = Fcb->StreamOffset;
  819. *ReturnNextEntry = FALSE;
  820. //
  821. // Otherwise use the values from the Ccb.
  822. //
  823. } else {
  824. KnownOffset = TRUE;
  825. DirentOffset = Ccb->CurrentDirentOffset;
  826. *ReturnNextEntry = BooleanFlagOn( Ccb->Flags, CCB_FLAG_ENUM_RETURN_NEXT );
  827. }
  828. //
  829. // Unlock the Fcb.
  830. //
  831. CdUnlockFcb( IrpContext, Fcb );
  832. //
  833. // We have the starting offset in the directory and whether to return
  834. // that entry or the next. If we are at the beginning of the directory
  835. // and are returning that entry, then tell our caller this is the
  836. // initial query.
  837. //
  838. *InitialQuery = FALSE;
  839. if ((DirentOffset == Fcb->StreamOffset) &&
  840. !(*ReturnNextEntry)) {
  841. *InitialQuery = TRUE;
  842. }
  843. //
  844. // If there is no file object then create it now.
  845. //
  846. if (Fcb->FileObject == NULL) {
  847. CdCreateInternalStream( IrpContext, Fcb->Vcb, Fcb );
  848. }
  849. //
  850. // Determine the offset in the stream to position the FileContext and
  851. // whether this offset is known to be a file offset.
  852. //
  853. // If this offset is known to be safe then go ahead and position the
  854. // file context. This handles the cases where the offset is the beginning
  855. // of the stream, the offset is from a previous search or this is the
  856. // initial query.
  857. //
  858. if (KnownOffset) {
  859. CdLookupInitialFileDirent( IrpContext, Fcb, FileContext, DirentOffset );
  860. //
  861. // Otherwise we walk through the directory from the beginning until
  862. // we reach the entry which contains this offset.
  863. //
  864. } else {
  865. LastDirentOffset = Fcb->StreamOffset;
  866. Found = TRUE;
  867. CdLookupInitialFileDirent( IrpContext, Fcb, FileContext, LastDirentOffset );
  868. //
  869. // If the requested offset is prior to the beginning offset in the stream
  870. // then don't return the next entry.
  871. //
  872. if (DirentOffset < LastDirentOffset) {
  873. *ReturnNextEntry = FALSE;
  874. //
  875. // Else look for the last entry which ends past the desired index.
  876. //
  877. } else {
  878. //
  879. // Keep walking through the directory until we run out of
  880. // entries or we find an entry which ends beyond the input
  881. // index value.
  882. //
  883. do {
  884. //
  885. // If we have passed the index value then exit.
  886. //
  887. if (FileContext->InitialDirent->Dirent.DirentOffset > DirentOffset) {
  888. Found = FALSE;
  889. break;
  890. }
  891. //
  892. // Remember the current position in case we need to go back.
  893. //
  894. LastDirentOffset = FileContext->InitialDirent->Dirent.DirentOffset;
  895. //
  896. // Exit if the next entry is beyond the desired index value.
  897. //
  898. if (LastDirentOffset + FileContext->InitialDirent->Dirent.DirentLength > DirentOffset) {
  899. break;
  900. }
  901. Found = CdLookupNextInitialFileDirent( IrpContext, Fcb, FileContext );
  902. } while (Found);
  903. //
  904. // If we didn't find the entry then go back to the last known entry.
  905. // This can happen if the index lies in the unused range at the
  906. // end of a sector.
  907. //
  908. if (!Found) {
  909. CdCleanupFileContext( IrpContext, FileContext );
  910. CdInitializeFileContext( IrpContext, FileContext );
  911. CdLookupInitialFileDirent( IrpContext, Fcb, FileContext, LastDirentOffset );
  912. }
  913. }
  914. }
  915. //
  916. // Only update the dirent name if we will need it for some reason.
  917. // Don't update this name if we are returning the next entry and
  918. // the search string has a version component.
  919. //
  920. FileContext->ShortName.FileName.Length = 0;
  921. if (!(*ReturnNextEntry) ||
  922. (Ccb->SearchExpression.VersionString.Length == 0)) {
  923. //
  924. // Update the name in the dirent into filename and version components.
  925. //
  926. CdUpdateDirentName( IrpContext,
  927. &FileContext->InitialDirent->Dirent,
  928. FlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE ));
  929. }
  930. //
  931. // Look at the flag in the IrpSp indicating whether to return just
  932. // one entry.
  933. //
  934. *ReturnSingleEntry = FALSE;
  935. if (FlagOn( IrpSp->Flags, SL_RETURN_SINGLE_ENTRY )) {
  936. *ReturnSingleEntry = TRUE;
  937. }
  938. return;
  939. }
  940. //
  941. // Local support routine
  942. //
  943. BOOLEAN
  944. CdEnumerateIndex (
  945. IN PIRP_CONTEXT IrpContext,
  946. IN PCCB Ccb,
  947. IN OUT PFILE_ENUM_CONTEXT FileContext,
  948. IN BOOLEAN ReturnNextEntry
  949. )
  950. /*++
  951. Routine Description:
  952. This routine is the worker routine for index enumeration. We are positioned
  953. at some dirent in the directory and will either return the first match
  954. at that point or look to the next entry. The Ccb contains details about
  955. the type of matching to do. If the user didn't specify a version in
  956. his search string then we only return the first version of a sequence
  957. of files with versions. We also don't return any associated files.
  958. Arguments:
  959. Ccb - Ccb for this directory handle.
  960. FileContext - File context already positioned at some entry in the directory.
  961. ReturnNextEntry - Indicates if we are returning this entry or should start
  962. with the next entry.
  963. Return Value:
  964. BOOLEAN - TRUE if next entry is found, FALSE otherwise.
  965. --*/
  966. {
  967. PDIRENT PreviousDirent = NULL;
  968. PDIRENT ThisDirent = &FileContext->InitialDirent->Dirent;
  969. BOOLEAN Found = FALSE;
  970. PAGED_CODE();
  971. //
  972. // Loop until we find a match or exaust the directory.
  973. //
  974. while (TRUE) {
  975. //
  976. // Move to the next entry unless we want to consider the current
  977. // entry.
  978. //
  979. if (ReturnNextEntry) {
  980. if (!CdLookupNextInitialFileDirent( IrpContext, Ccb->Fcb, FileContext )) {
  981. break;
  982. }
  983. PreviousDirent = ThisDirent;
  984. ThisDirent = &FileContext->InitialDirent->Dirent;
  985. CdUpdateDirentName( IrpContext, ThisDirent, FlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE ));
  986. } else {
  987. ReturnNextEntry = TRUE;
  988. }
  989. //
  990. // Don't bother if we have a constant entry and are ignoring them.
  991. //
  992. if (FlagOn( ThisDirent->Flags, DIRENT_FLAG_CONSTANT_ENTRY ) &&
  993. FlagOn( Ccb->Flags, CCB_FLAG_ENUM_NOMATCH_CONSTANT_ENTRY )) {
  994. continue;
  995. }
  996. //
  997. // Look at the current entry if it is not an associated file
  998. // and the name doesn't match the previous file if the version
  999. // name is not part of the search.
  1000. //
  1001. if (!FlagOn( ThisDirent->DirentFlags, CD_ATTRIBUTE_ASSOC )) {
  1002. //
  1003. // Check if this entry matches the previous entry except
  1004. // for version number and whether we should return the
  1005. // entry in that case. Go directly to the name comparison
  1006. // if:
  1007. //
  1008. // There is no previous entry.
  1009. // The search expression has a version component.
  1010. // The name length doesn't match the length of the previous entry.
  1011. // The base name strings don't match.
  1012. //
  1013. if ((PreviousDirent == NULL) ||
  1014. (Ccb->SearchExpression.VersionString.Length != 0) ||
  1015. (PreviousDirent->CdCaseFileName.FileName.Length != ThisDirent->CdCaseFileName.FileName.Length) ||
  1016. FlagOn( PreviousDirent->DirentFlags, CD_ATTRIBUTE_ASSOC ) ||
  1017. !RtlEqualMemory( PreviousDirent->CdCaseFileName.FileName.Buffer,
  1018. ThisDirent->CdCaseFileName.FileName.Buffer,
  1019. ThisDirent->CdCaseFileName.FileName.Length )) {
  1020. //
  1021. // If we match all names then return to our caller.
  1022. //
  1023. if (FlagOn( Ccb->Flags, CCB_FLAG_ENUM_MATCH_ALL )) {
  1024. FileContext->ShortName.FileName.Length = 0;
  1025. Found = TRUE;
  1026. break;
  1027. }
  1028. //
  1029. // Check if the long name matches the search expression.
  1030. //
  1031. if (CdIsNameInExpression( IrpContext,
  1032. &ThisDirent->CdCaseFileName,
  1033. &Ccb->SearchExpression,
  1034. Ccb->Flags,
  1035. TRUE )) {
  1036. //
  1037. // Let our caller know we found an entry.
  1038. //
  1039. Found = TRUE;
  1040. FileContext->ShortName.FileName.Length = 0;
  1041. break;
  1042. }
  1043. //
  1044. // The long name didn't match so we need to check for a
  1045. // possible short name match. There is no match if the
  1046. // long name is 8dot3 or the search expression has a
  1047. // version component. Special case the self and parent
  1048. // entries.
  1049. //
  1050. if ((Ccb->SearchExpression.VersionString.Length == 0) &&
  1051. !FlagOn( ThisDirent->Flags, DIRENT_FLAG_CONSTANT_ENTRY ) &&
  1052. !CdIs8dot3Name( IrpContext,
  1053. ThisDirent->CdFileName.FileName )) {
  1054. CdGenerate8dot3Name( IrpContext,
  1055. &ThisDirent->CdCaseFileName.FileName,
  1056. ThisDirent->DirentOffset,
  1057. FileContext->ShortName.FileName.Buffer,
  1058. &FileContext->ShortName.FileName.Length );
  1059. //
  1060. // Check if this name matches.
  1061. //
  1062. if (CdIsNameInExpression( IrpContext,
  1063. &FileContext->ShortName,
  1064. &Ccb->SearchExpression,
  1065. Ccb->Flags,
  1066. FALSE )) {
  1067. //
  1068. // Let our caller know we found an entry.
  1069. //
  1070. Found = TRUE;
  1071. break;
  1072. }
  1073. }
  1074. }
  1075. }
  1076. }
  1077. //
  1078. // If we found the entry then make sure we walk through all of the
  1079. // file dirents.
  1080. //
  1081. if (Found) {
  1082. CdLookupLastFileDirent( IrpContext, Ccb->Fcb, FileContext );
  1083. }
  1084. return Found;
  1085. }