3 Copyright (c) 1989-2000 Microsoft Corporation
11 This module implements the File Directory Control routines for Cdfs called
12 by the Fsd/Fsp dispatch drivers.
20 // The Bug check file id for this module
23 #define BugCheckFileId (CDFS_BUG_CHECK_DIRCTRL)
26 // Local support routines
29 _Requires_lock_held_(_Global_critical_region_
)
32 _Inout_ PIRP_CONTEXT IrpContext
,
34 _In_ PIO_STACK_LOCATION IrpSp
,
39 _Requires_lock_held_(_Global_critical_region_
)
41 CdNotifyChangeDirectory (
42 _Inout_ PIRP_CONTEXT IrpContext
,
44 _In_ PIO_STACK_LOCATION IrpSp
,
49 CdInitializeEnumeration (
50 _In_ PIRP_CONTEXT IrpContext
,
51 _In_ PIO_STACK_LOCATION IrpSp
,
54 _Inout_ PFILE_ENUM_CONTEXT FileContext
,
55 _Out_ PBOOLEAN ReturnNextEntry
,
56 _Out_ PBOOLEAN ReturnSingleEntry
,
57 _Out_ PBOOLEAN InitialQuery
62 _In_ PIRP_CONTEXT IrpContext
,
64 _Inout_ PFILE_ENUM_CONTEXT FileContext
,
65 _In_ BOOLEAN ReturnNextEntry
69 #pragma alloc_text(PAGE, CdCommonDirControl)
70 #pragma alloc_text(PAGE, CdEnumerateIndex)
71 #pragma alloc_text(PAGE, CdInitializeEnumeration)
72 #pragma alloc_text(PAGE, CdNotifyChangeDirectory)
73 #pragma alloc_text(PAGE, CdQueryDirectory)
78 _Requires_lock_held_(_Global_critical_region_
)
81 _Inout_ PIRP_CONTEXT IrpContext
,
89 This routine is the entry point for the directory control operations. These
90 are directory enumerations and directory notify calls. We verify the
91 user's handle is for a directory and then call the appropriate routine.
95 Irp - Irp for this request.
99 NTSTATUS - Status returned from the lower level routines.
105 PIO_STACK_LOCATION IrpSp
= IoGetCurrentIrpStackLocation( Irp
);
113 // Decode the user file object and fail this request if it is not
117 if (CdDecodeFileObject( IrpContext
,
120 &Ccb
) != UserDirectoryOpen
) {
122 CdCompleteRequest( IrpContext
, Irp
, STATUS_INVALID_PARAMETER
);
123 return STATUS_INVALID_PARAMETER
;
127 // We know this is a directory control so we'll case on the
128 // minor function, and call a internal worker routine to complete
132 switch (IrpSp
->MinorFunction
) {
134 case IRP_MN_QUERY_DIRECTORY
:
136 Status
= CdQueryDirectory( IrpContext
, Irp
, IrpSp
, Fcb
, Ccb
);
139 case IRP_MN_NOTIFY_CHANGE_DIRECTORY
:
141 Status
= CdNotifyChangeDirectory( IrpContext
, Irp
, IrpSp
, Ccb
);
146 CdCompleteRequest( IrpContext
, Irp
, STATUS_INVALID_DEVICE_REQUEST
);
147 Status
= STATUS_INVALID_DEVICE_REQUEST
;
156 // Local support routines
159 _Requires_lock_held_(_Global_critical_region_
)
162 _Inout_ PIRP_CONTEXT IrpContext
,
164 _In_ PIO_STACK_LOCATION IrpSp
,
173 This routine performs the query directory operation. It is responsible
174 for either completing of enqueuing the input Irp. We store the state of the
179 Irp - Supplies the Irp to process
181 IrpSp - Stack location for this Irp.
183 Fcb - Fcb for this directory.
185 Ccb - Ccb for this directory open.
189 NTSTATUS - The return status for the operation
194 NTSTATUS Status
= STATUS_SUCCESS
;
195 ULONG Information
= 0;
201 ULONG SeparatorBytes
;
202 ULONG VersionStringBytes
;
204 FILE_ENUM_CONTEXT FileContext
;
205 PDIRENT ThisDirent
= NULL
;
206 BOOLEAN InitialQuery
;
207 BOOLEAN ReturnNextEntry
= FALSE
;
208 BOOLEAN ReturnSingleEntry
;
210 BOOLEAN DoCcbUpdate
= FALSE
;
213 ULONG BytesRemainingInBuffer
;
217 PFILE_BOTH_DIR_INFORMATION DirInfo
= NULL
;
218 PFILE_NAMES_INFORMATION NamesInfo
;
219 PFILE_ID_FULL_DIR_INFORMATION IdFullDirInfo
;
220 PFILE_ID_BOTH_DIR_INFORMATION IdBothDirInfo
;
225 // Check if we support this search mode. Also remember the size of the base part of
226 // each of these structures.
229 switch (IrpSp
->Parameters
.QueryDirectory
.FileInformationClass
) {
231 case FileDirectoryInformation
:
233 BaseLength
= FIELD_OFFSET( FILE_DIRECTORY_INFORMATION
,
237 case FileFullDirectoryInformation
:
239 BaseLength
= FIELD_OFFSET( FILE_FULL_DIR_INFORMATION
,
243 case FileIdFullDirectoryInformation
:
245 BaseLength
= FIELD_OFFSET( FILE_ID_FULL_DIR_INFORMATION
,
249 case FileNamesInformation
:
251 BaseLength
= FIELD_OFFSET( FILE_NAMES_INFORMATION
,
255 case FileBothDirectoryInformation
:
257 BaseLength
= FIELD_OFFSET( FILE_BOTH_DIR_INFORMATION
,
261 case FileIdBothDirectoryInformation
:
263 BaseLength
= FIELD_OFFSET( FILE_ID_BOTH_DIR_INFORMATION
,
269 CdCompleteRequest( IrpContext
, Irp
, STATUS_INVALID_INFO_CLASS
);
270 return STATUS_INVALID_INFO_CLASS
;
274 // Get the user buffer.
277 CdMapUserBuffer( IrpContext
, &UserBuffer
);
280 // Initialize our search context.
283 CdInitializeFileContext( IrpContext
, &FileContext
);
286 // Acquire the directory.
289 CdAcquireFileShared( IrpContext
, Fcb
);
292 // Use a try-finally to facilitate cleanup.
298 // Verify the Fcb is still good.
301 CdVerifyFcbOperation( IrpContext
, Fcb
);
304 // Start by getting the initial state for the enumeration. This will set up the Ccb with
305 // the initial search parameters and let us know the starting offset in the directory
309 CdInitializeEnumeration( IrpContext
,
319 // The current dirent is stored in the InitialDirent field. We capture
320 // this here so that we have a valid restart point even if we don't
321 // find a single entry.
324 ThisDirent
= &FileContext
.InitialDirent
->Dirent
;
327 // At this point we are about to enter our query loop. We have
328 // determined the index into the directory file to begin the
329 // search. LastEntry and NextEntry are used to index into the user
330 // buffer. LastEntry is the last entry we've added, NextEntry is
331 // current one we're working on. If NextEntry is non-zero, then
332 // at least one entry was added.
338 // If the user had requested only a single match and we have
339 // returned that, then we stop at this point. We update the Ccb with
340 // the status based on the last entry returned.
343 if ((NextEntry
!= 0) && ReturnSingleEntry
) {
350 // We try to locate the next matching dirent. Our search if based on a starting
351 // dirent offset, whether we should return the current or next entry, whether
352 // we should be doing a short name search and finally whether we should be
353 // checking for a version match.
356 Found
= CdEnumerateIndex( IrpContext
, Ccb
, &FileContext
, ReturnNextEntry
);
359 // Initialize the value for the next search.
362 ReturnNextEntry
= TRUE
;
365 // If we didn't receive a dirent, then we are at the end of the
366 // directory. If we have returned any files, we exit with
367 // success, otherwise we return STATUS_NO_MORE_FILES.
372 if (NextEntry
== 0) {
374 Status
= STATUS_NO_MORE_FILES
;
378 Status
= STATUS_NO_SUCH_FILE
;
387 // Remember the dirent for the file we just found.
390 ThisDirent
= &FileContext
.InitialDirent
->Dirent
;
393 // Here are the rules concerning filling up the buffer:
395 // 1. The Io system garentees that there will always be
396 // enough room for at least one base record.
398 // 2. If the full first record (including file name) cannot
399 // fit, as much of the name as possible is copied and
400 // STATUS_BUFFER_OVERFLOW is returned.
402 // 3. If a subsequent record cannot completely fit into the
403 // buffer, none of it (as in 0 bytes) is copied, and
404 // STATUS_SUCCESS is returned. A subsequent query will
405 // pick up with this record.
409 // Let's compute the number of bytes we need to transfer the current entry.
413 VersionStringBytes
= 0;
416 // We can look directly at the dirent that we found.
419 FileNameBytes
= ThisDirent
->CdFileName
.FileName
.Length
;
422 // Compute the number of bytes for the version string if
423 // we will return this. Allow directories with illegal ";".
426 if (((Ccb
->SearchExpression
.VersionString
.Length
!= 0) ||
427 (FlagOn(ThisDirent
->DirentFlags
, CD_ATTRIBUTE_DIRECTORY
))) &&
428 (ThisDirent
->CdFileName
.VersionString
.Length
!= 0)) {
432 VersionStringBytes
= ThisDirent
->CdFileName
.VersionString
.Length
;
436 // If the slot for the next entry would be beyond the length of the
437 // user's buffer just exit (we know we've returned at least one entry
438 // already). This will happen when we align the pointer past the end.
441 if (NextEntry
> IrpSp
->Parameters
.QueryDirectory
.Length
) {
443 ReturnNextEntry
= FALSE
;
445 try_leave( Status
= STATUS_SUCCESS
);
449 // Compute the number of bytes remaining in the buffer. Round this
450 // down to a WCHAR boundary so we can copy full characters.
453 BytesRemainingInBuffer
= IrpSp
->Parameters
.QueryDirectory
.Length
- NextEntry
;
454 ClearFlag( BytesRemainingInBuffer
, 1 );
457 // If this won't fit and we have returned a previous entry then just
458 // return STATUS_SUCCESS.
461 if ((BaseLength
+ FileNameBytes
+ SeparatorBytes
+ VersionStringBytes
) > BytesRemainingInBuffer
) {
464 // If we already found an entry then just exit.
467 if (NextEntry
!= 0) {
469 ReturnNextEntry
= FALSE
;
471 try_leave( Status
= STATUS_SUCCESS
);
475 // Don't even try to return the version string if it doesn't all fit.
476 // Reduce the FileNameBytes to just fit in the buffer.
479 if ((BaseLength
+ FileNameBytes
) > BytesRemainingInBuffer
) {
481 FileNameBytes
= BytesRemainingInBuffer
- BaseLength
;
485 // Don't return any version string bytes.
492 // Use a status code of STATUS_BUFFER_OVERFLOW. Also set
493 // ReturnSingleEntry so that we will exit the loop at the top.
496 Status
= STATUS_BUFFER_OVERFLOW
;
497 ReturnSingleEntry
= TRUE
;
501 // Protect access to the user buffer with an exception handler.
502 // Since (at our request) IO doesn't buffer these requests, we have
503 // to guard against a user messing with the page protection and other
510 // Zero and initialize the base part of the current entry.
513 RtlZeroMemory( Add2Ptr( UserBuffer
, NextEntry
, PVOID
),
517 // Now we have an entry to return to our caller.
518 // We'll case on the type of information requested and fill up
519 // the user buffer if everything fits.
522 switch (IrpSp
->Parameters
.QueryDirectory
.FileInformationClass
) {
524 case FileBothDirectoryInformation
:
525 case FileFullDirectoryInformation
:
526 case FileIdBothDirectoryInformation
:
527 case FileIdFullDirectoryInformation
:
528 case FileDirectoryInformation
:
530 DirInfo
= Add2Ptr( UserBuffer
, NextEntry
, PFILE_BOTH_DIR_INFORMATION
);
533 // Use the create time for all the time stamps.
536 CdConvertCdTimeToNtTime( IrpContext
,
537 FileContext
.InitialDirent
->Dirent
.CdTime
,
538 &DirInfo
->CreationTime
);
540 DirInfo
->LastWriteTime
= DirInfo
->ChangeTime
= DirInfo
->CreationTime
;
543 // Set the attributes and sizes separately for directories and
547 if (FlagOn( ThisDirent
->DirentFlags
, CD_ATTRIBUTE_DIRECTORY
)) {
549 DirInfo
->EndOfFile
.QuadPart
= DirInfo
->AllocationSize
.QuadPart
= 0;
551 SetFlag( DirInfo
->FileAttributes
, FILE_ATTRIBUTE_DIRECTORY
);
555 DirInfo
->EndOfFile
.QuadPart
= FileContext
.FileSize
;
556 DirInfo
->AllocationSize
.QuadPart
= LlSectorAlign( FileContext
.FileSize
);
558 SetFlag( DirInfo
->FileAttributes
, FILE_ATTRIBUTE_READONLY
);
561 if (FlagOn( ThisDirent
->DirentFlags
,
562 CD_ATTRIBUTE_HIDDEN
)) {
564 SetFlag( DirInfo
->FileAttributes
, FILE_ATTRIBUTE_HIDDEN
);
567 DirInfo
->FileIndex
= ThisDirent
->DirentOffset
;
569 DirInfo
->FileNameLength
= FileNameBytes
+ SeparatorBytes
+ VersionStringBytes
;
573 case FileNamesInformation
:
575 NamesInfo
= Add2Ptr( UserBuffer
, NextEntry
, PFILE_NAMES_INFORMATION
);
577 NamesInfo
->FileIndex
= ThisDirent
->DirentOffset
;
579 NamesInfo
->FileNameLength
= FileNameBytes
+ SeparatorBytes
+ VersionStringBytes
;
583 /* ReactOS Change: GCC "enumeration value not handled in switch" */
588 // Fill in the FileId
591 switch (IrpSp
->Parameters
.QueryDirectory
.FileInformationClass
) {
593 case FileIdBothDirectoryInformation
:
595 IdBothDirInfo
= Add2Ptr( UserBuffer
, NextEntry
, PFILE_ID_BOTH_DIR_INFORMATION
);
596 CdSetFidFromParentAndDirent( IdBothDirInfo
->FileId
, Fcb
, ThisDirent
);
599 case FileIdFullDirectoryInformation
:
601 IdFullDirInfo
= Add2Ptr( UserBuffer
, NextEntry
, PFILE_ID_FULL_DIR_INFORMATION
);
602 CdSetFidFromParentAndDirent( IdFullDirInfo
->FileId
, Fcb
, ThisDirent
);
610 // Now copy as much of the name as possible. We also may have a version
614 if (FileNameBytes
!= 0) {
617 // This is a Unicode name, we can copy the bytes directly.
620 RtlCopyMemory( Add2Ptr( UserBuffer
, NextEntry
+ BaseLength
, PVOID
),
621 ThisDirent
->CdFileName
.FileName
.Buffer
,
624 if (SeparatorBytes
!= 0) {
626 *(Add2Ptr( UserBuffer
,
627 NextEntry
+ BaseLength
+ FileNameBytes
,
630 if (VersionStringBytes
!= 0) {
632 RtlCopyMemory( Add2Ptr( UserBuffer
,
633 NextEntry
+ BaseLength
+ FileNameBytes
+ sizeof( WCHAR
),
635 ThisDirent
->CdFileName
.VersionString
.Buffer
,
636 VersionStringBytes
);
642 // Fill in the short name if we got STATUS_SUCCESS. The short name
643 // may already be in the file context. Otherwise we will check
644 // whether the long name is 8.3. Special case the self and parent
648 if ((Status
== STATUS_SUCCESS
) &&
649 (IrpSp
->Parameters
.QueryDirectory
.FileInformationClass
== FileBothDirectoryInformation
||
650 IrpSp
->Parameters
.QueryDirectory
.FileInformationClass
== FileIdBothDirectoryInformation
) &&
651 (Ccb
->SearchExpression
.VersionString
.Length
== 0) &&
652 !FlagOn( ThisDirent
->Flags
, DIRENT_FLAG_CONSTANT_ENTRY
)) {
655 // If we already have the short name then copy into the user's buffer.
658 if (FileContext
.ShortName
.FileName
.Length
!= 0) {
660 RtlCopyMemory( DirInfo
->ShortName
,
661 FileContext
.ShortName
.FileName
.Buffer
,
662 FileContext
.ShortName
.FileName
.Length
);
664 DirInfo
->ShortNameLength
= (CCHAR
) FileContext
.ShortName
.FileName
.Length
;
667 // If the short name length is currently zero then check if
668 // the long name is not 8.3. We can copy the short name in
669 // unicode form directly into the caller's buffer.
674 if (!CdIs8dot3Name( IrpContext
,
675 ThisDirent
->CdFileName
.FileName
)) {
677 CdGenerate8dot3Name( IrpContext
,
678 &ThisDirent
->CdCaseFileName
.FileName
,
679 ThisDirent
->DirentOffset
,
681 &FileContext
.ShortName
.FileName
.Length
);
683 DirInfo
->ShortNameLength
= (CCHAR
) FileContext
.ShortName
.FileName
.Length
;
690 // Sum the total number of bytes for the information field.
693 FileNameBytes
+= SeparatorBytes
+ VersionStringBytes
;
696 // Update the information with the number of bytes stored in the
697 // buffer. We quad-align the existing buffer to add any necessary
701 Information
= NextEntry
+ BaseLength
+ FileNameBytes
;
704 // Go back to the previous entry and fill in the update to this entry.
707 *(Add2Ptr( UserBuffer
, LastEntry
, PULONG
)) = NextEntry
- LastEntry
;
710 // Set up our variables for the next dirent.
713 InitialQuery
= FALSE
;
715 LastEntry
= NextEntry
;
716 NextEntry
= QuadAlign( Information
);
719 #pragma warning(suppress: 6320)
721 } _SEH2_EXCEPT (EXCEPTION_EXECUTE_HANDLER
) {
724 // We had a problem filling in the user's buffer, so stop and
725 // fail this request. This is the only reason any exception
726 // would have occured at this level.
730 try_leave( Status
= _SEH2_GetExceptionCode());
739 // Cleanup our search context - *before* aquiring the FCB mutex exclusive,
740 // else can block on threads in cdcreateinternalstream/purge which
741 // hold the FCB but are waiting for all maps in this stream to be released.
744 CdCleanupFileContext( IrpContext
, &FileContext
);
747 // Now we can safely aqure the FCB mutex if we need to.
750 if (DoCcbUpdate
&& !NT_ERROR( Status
)) {
753 // Update the Ccb to show the current state of the enumeration.
756 CdLockFcb( IrpContext
, Fcb
);
758 Ccb
->CurrentDirentOffset
= ThisDirent
->DirentOffset
;
760 ClearFlag( Ccb
->Flags
, CCB_FLAG_ENUM_RETURN_NEXT
);
762 if (ReturnNextEntry
) {
764 SetFlag( Ccb
->Flags
, CCB_FLAG_ENUM_RETURN_NEXT
);
767 CdUnlockFcb( IrpContext
, Fcb
);
774 CdReleaseFile( IrpContext
, Fcb
);
778 // Complete the request here.
781 Irp
->IoStatus
.Information
= Information
;
783 CdCompleteRequest( IrpContext
, Irp
, Status
);
789 // Local support routines
792 _Requires_lock_held_(_Global_critical_region_
)
794 CdNotifyChangeDirectory (
795 _Inout_ PIRP_CONTEXT IrpContext
,
797 _In_ PIO_STACK_LOCATION IrpSp
,
805 This routine performs the notify change directory operation. It is
806 responsible for either completing of enqueuing the input Irp. Although there
807 will never be a notify signalled on a CDROM disk we still support this call.
809 We have already checked that this is not an OpenById handle.
813 Irp - Supplies the Irp to process
815 IrpSp - Io stack location for this request.
817 Ccb - Handle to the directory being watched.
821 NTSTATUS - STATUS_PENDING, any other error will raise.
829 // Always set the wait bit in the IrpContext so the initial wait can't fail.
832 SetFlag( IrpContext
->Flags
, IRP_CONTEXT_FLAG_WAIT
);
835 // Acquire the Vcb shared.
838 CdAcquireVcbShared( IrpContext
, IrpContext
->Vcb
, FALSE
);
841 // Use a try-finally to facilitate cleanup.
850 CdVerifyVcb( IrpContext
, IrpContext
->Vcb
);
853 // Call the Fsrtl package to process the request. We cast the
854 // unicode strings to ansi strings as the dir notify package
855 // only deals with memory matching.
858 FsRtlNotifyFullChangeDirectory( IrpContext
->Vcb
->NotifySync
,
859 &IrpContext
->Vcb
->DirNotifyList
,
861 (PSTRING
) &IrpSp
->FileObject
->FileName
,
862 BooleanFlagOn( IrpSp
->Flags
, SL_WATCH_TREE
),
864 IrpSp
->Parameters
.NotifyDirectory
.CompletionFilter
,
875 CdReleaseVcb( IrpContext
, IrpContext
->Vcb
);
879 // Cleanup the IrpContext.
882 CdCompleteRequest( IrpContext
, NULL
, STATUS_SUCCESS
);
884 return STATUS_PENDING
;
889 // Local support routine
893 CdInitializeEnumeration (
894 _In_ PIRP_CONTEXT IrpContext
,
895 _In_ PIO_STACK_LOCATION IrpSp
,
898 _Inout_ PFILE_ENUM_CONTEXT FileContext
,
899 _Out_ PBOOLEAN ReturnNextEntry
,
900 _Out_ PBOOLEAN ReturnSingleEntry
,
901 _Out_ PBOOLEAN InitialQuery
908 This routine is called to initialize the enumeration variables and structures.
909 We look at the state of a previous enumeration from the Ccb as well as any
910 input values from the user. On exit we will position the FileContext at
911 a file in the directory and let the caller know whether this entry or the
912 next entry should be returned.
916 IrpSp - Irp stack location for this request.
918 Fcb - Fcb for this directory.
920 Ccb - Ccb for the directory handle.
922 FileContext - FileContext to use for this enumeration.
924 ReturnNextEntry - Address to store whether we should return the entry at
925 the FileContext position or the next entry.
927 ReturnSingleEntry - Address to store whether we should only return
930 InitialQuery - Address to store whether this is the first enumeration
931 query on this handle.
942 PUNICODE_STRING FileName
;
943 CD_NAME WildCardName
;
944 CD_NAME SearchExpression
;
949 ULONG LastDirentOffset
;
957 // If the user has specified that the scan be restarted, and has specicified
958 // a new query pattern, reinitialize the CCB.
961 if (FlagOn( IrpSp
->Flags
, SL_RESTART_SCAN
)) {
963 CdLockFcb( IrpContext
, Fcb
);
965 FileName
= (PUNICODE_STRING
) IrpSp
->Parameters
.QueryDirectory
.FileName
;
966 if (FileName
&& FileName
->Length
> 0) {
968 if (!FlagOn( Ccb
->Flags
, CCB_FLAG_ENUM_MATCH_ALL
)) {
970 CdFreePool( &Ccb
->SearchExpression
.FileName
.Buffer
);
973 ClearFlag(Ccb
->Flags
, CCB_FLAG_ENUM_MATCH_ALL
);
974 ClearFlag(Ccb
->Flags
, CCB_FLAG_ENUM_INITIALIZED
);
975 ClearFlag(Ccb
->Flags
, CCB_FLAG_ENUM_NAME_EXP_HAS_WILD
);
978 CdUnlockFcb( IrpContext
, Fcb
);
982 // If this is the initial query then build a search expression from the input
986 if (!FlagOn( Ccb
->Flags
, CCB_FLAG_ENUM_INITIALIZED
)) {
988 FileName
= IrpSp
->Parameters
.QueryDirectory
.FileName
;
993 // If the filename is not specified or is a single '*' then we will
997 if ((FileName
== NULL
) ||
998 (FileName
->Buffer
== NULL
) ||
999 (FileName
->Length
== 0) ||
1000 ((FileName
->Length
== sizeof( WCHAR
)) &&
1001 (FileName
->Buffer
[0] == L
'*'))) {
1003 SetFlag( CcbFlags
, CCB_FLAG_ENUM_MATCH_ALL
);
1004 RtlZeroMemory( &SearchExpression
, sizeof( SearchExpression
));
1007 // Otherwise build the CdName from the name in the stack location.
1008 // This involves building both the name and version portions and
1009 // checking for wild card characters. We also upcase the string if
1010 // this is a case-insensitive search.
1016 // Create a CdName to check for wild cards.
1019 WildCardName
.FileName
= *FileName
;
1021 CdConvertNameToCdName( IrpContext
, &WildCardName
);
1024 // The name better have at least one character.
1027 if (WildCardName
.FileName
.Length
== 0) {
1029 CdRaiseStatus( IrpContext
, STATUS_INVALID_PARAMETER
);
1033 // Check for wildcards in the separate components.
1036 if (FsRtlDoesNameContainWildCards( &WildCardName
.FileName
)) {
1038 SetFlag( CcbFlags
, CCB_FLAG_ENUM_NAME_EXP_HAS_WILD
);
1041 if ((WildCardName
.VersionString
.Length
!= 0) &&
1042 (FsRtlDoesNameContainWildCards( &WildCardName
.VersionString
))) {
1044 SetFlag( CcbFlags
, CCB_FLAG_ENUM_VERSION_EXP_HAS_WILD
);
1047 // Check if this is a wild card only and match all version
1051 if ((WildCardName
.VersionString
.Length
== sizeof( WCHAR
)) &&
1052 (WildCardName
.VersionString
.Buffer
[0] == L
'*')) {
1054 SetFlag( CcbFlags
, CCB_FLAG_ENUM_VERSION_MATCH_ALL
);
1059 // Now create the search expression to store in the Ccb.
1062 SearchExpression
.FileName
.Buffer
= FsRtlAllocatePoolWithTag( CdPagedPool
,
1064 TAG_ENUM_EXPRESSION
);
1066 SearchExpression
.FileName
.MaximumLength
= FileName
->Length
;
1069 // Either copy the name directly or perform the upcase.
1072 if (FlagOn( Ccb
->Flags
, CCB_FLAG_IGNORE_CASE
)) {
1074 Status
= RtlUpcaseUnicodeString( (PUNICODE_STRING
) &SearchExpression
.FileName
,
1079 // This should never fail.
1081 __analysis_assert( Status
== STATUS_SUCCESS
);
1082 NT_ASSERT( Status
== STATUS_SUCCESS
);
1086 RtlCopyMemory( SearchExpression
.FileName
.Buffer
,
1092 // Now split into the separate name and version components.
1095 SearchExpression
.FileName
.Length
= WildCardName
.FileName
.Length
;
1096 SearchExpression
.VersionString
.Length
= WildCardName
.VersionString
.Length
;
1097 SearchExpression
.VersionString
.MaximumLength
= WildCardName
.VersionString
.MaximumLength
;
1099 SearchExpression
.VersionString
.Buffer
= Add2Ptr( SearchExpression
.FileName
.Buffer
,
1100 SearchExpression
.FileName
.Length
+ sizeof( WCHAR
),
1105 // But we do not want to return the constant "." and ".." entries for
1106 // the root directory, for consistency with the rest of Microsoft's
1110 if (Fcb
== Fcb
->Vcb
->RootIndexFcb
) {
1112 SetFlag( CcbFlags
, CCB_FLAG_ENUM_NOMATCH_CONSTANT_ENTRY
);
1116 // Now lock the Fcb in order to update the Ccb with the inital
1117 // enumeration values.
1120 CdLockFcb( IrpContext
, Fcb
);
1123 // Check again that this is the initial search.
1126 if (!FlagOn( Ccb
->Flags
, CCB_FLAG_ENUM_INITIALIZED
)) {
1129 // Update the values in the Ccb.
1132 Ccb
->CurrentDirentOffset
= Fcb
->StreamOffset
;
1133 Ccb
->SearchExpression
= SearchExpression
;
1136 // Set the appropriate flags in the Ccb.
1139 SetFlag( Ccb
->Flags
, CcbFlags
| CCB_FLAG_ENUM_INITIALIZED
);
1142 // Otherwise cleanup any buffer allocated here.
1147 if (!FlagOn( CcbFlags
, CCB_FLAG_ENUM_MATCH_ALL
)) {
1149 CdFreePool( &SearchExpression
.FileName
.Buffer
);
1154 // Otherwise lock the Fcb so we can read the current enumeration values.
1159 CdLockFcb( IrpContext
, Fcb
);
1163 // Capture the current state of the enumeration.
1165 // If the user specified an index then use his offset. We always
1166 // return the next entry in this case.
1169 if (FlagOn( IrpSp
->Flags
, SL_INDEX_SPECIFIED
)) {
1171 KnownOffset
= FALSE
;
1172 DirentOffset
= IrpSp
->Parameters
.QueryDirectory
.FileIndex
;
1173 *ReturnNextEntry
= TRUE
;
1176 // If we are restarting the scan then go from the self entry.
1179 } else if (FlagOn( IrpSp
->Flags
, SL_RESTART_SCAN
)) {
1182 DirentOffset
= Fcb
->StreamOffset
;
1183 *ReturnNextEntry
= FALSE
;
1186 // Otherwise use the values from the Ccb.
1192 DirentOffset
= Ccb
->CurrentDirentOffset
;
1193 *ReturnNextEntry
= BooleanFlagOn( Ccb
->Flags
, CCB_FLAG_ENUM_RETURN_NEXT
);
1200 CdUnlockFcb( IrpContext
, Fcb
);
1203 // We have the starting offset in the directory and whether to return
1204 // that entry or the next. If we are at the beginning of the directory
1205 // and are returning that entry, then tell our caller this is the
1209 *InitialQuery
= FALSE
;
1211 if ((DirentOffset
== Fcb
->StreamOffset
) &&
1212 !(*ReturnNextEntry
)) {
1214 *InitialQuery
= TRUE
;
1218 // If there is no file object then create it now.
1221 CdVerifyOrCreateDirStreamFile( IrpContext
, Fcb
);
1224 // Determine the offset in the stream to position the FileContext and
1225 // whether this offset is known to be a file offset.
1227 // If this offset is known to be safe then go ahead and position the
1228 // file context. This handles the cases where the offset is the beginning
1229 // of the stream, the offset is from a previous search or this is the
1235 CdLookupInitialFileDirent( IrpContext
, Fcb
, FileContext
, DirentOffset
);
1238 // Otherwise we walk through the directory from the beginning until
1239 // we reach the entry which contains this offset.
1244 LastDirentOffset
= Fcb
->StreamOffset
;
1247 CdLookupInitialFileDirent( IrpContext
, Fcb
, FileContext
, LastDirentOffset
);
1250 // If the requested offset is prior to the beginning offset in the stream
1251 // then don't return the next entry.
1254 if (DirentOffset
< LastDirentOffset
) {
1256 *ReturnNextEntry
= FALSE
;
1259 // Else look for the last entry which ends past the desired index.
1265 // Keep walking through the directory until we run out of
1266 // entries or we find an entry which ends beyond the input
1273 // If we have passed the index value then exit.
1276 if (FileContext
->InitialDirent
->Dirent
.DirentOffset
> DirentOffset
) {
1283 // Remember the current position in case we need to go back.
1286 LastDirentOffset
= FileContext
->InitialDirent
->Dirent
.DirentOffset
;
1289 // Exit if the next entry is beyond the desired index value.
1292 if (LastDirentOffset
+ FileContext
->InitialDirent
->Dirent
.DirentLength
> DirentOffset
) {
1297 Found
= CdLookupNextInitialFileDirent( IrpContext
, Fcb
, FileContext
);
1302 // If we didn't find the entry then go back to the last known entry.
1303 // This can happen if the index lies in the unused range at the
1309 CdCleanupFileContext( IrpContext
, FileContext
);
1310 CdInitializeFileContext( IrpContext
, FileContext
);
1312 CdLookupInitialFileDirent( IrpContext
, Fcb
, FileContext
, LastDirentOffset
);
1318 // Only update the dirent name if we will need it for some reason.
1319 // Don't update this name if we are returning the next entry and
1320 // the search string has a version component.
1323 FileContext
->ShortName
.FileName
.Length
= 0;
1325 if (!(*ReturnNextEntry
) ||
1326 (Ccb
->SearchExpression
.VersionString
.Length
== 0)) {
1329 // Update the name in the dirent into filename and version components.
1332 CdUpdateDirentName( IrpContext
,
1333 &FileContext
->InitialDirent
->Dirent
,
1334 FlagOn( Ccb
->Flags
, CCB_FLAG_IGNORE_CASE
));
1338 // Look at the flag in the IrpSp indicating whether to return just
1342 *ReturnSingleEntry
= FALSE
;
1344 if (FlagOn( IrpSp
->Flags
, SL_RETURN_SINGLE_ENTRY
)) {
1346 *ReturnSingleEntry
= TRUE
;
1354 // Local support routine
1359 _In_ PIRP_CONTEXT IrpContext
,
1361 _Inout_ PFILE_ENUM_CONTEXT FileContext
,
1362 _In_ BOOLEAN ReturnNextEntry
1367 Routine Description:
1369 This routine is the worker routine for index enumeration. We are positioned
1370 at some dirent in the directory and will either return the first match
1371 at that point or look to the next entry. The Ccb contains details about
1372 the type of matching to do. If the user didn't specify a version in
1373 his search string then we only return the first version of a sequence
1374 of files with versions. We also don't return any associated files.
1378 Ccb - Ccb for this directory handle.
1380 FileContext - File context already positioned at some entry in the directory.
1382 ReturnNextEntry - Indicates if we are returning this entry or should start
1383 with the next entry.
1387 BOOLEAN - TRUE if next entry is found, FALSE otherwise.
1392 PDIRENT PreviousDirent
= NULL
;
1393 PDIRENT ThisDirent
= &FileContext
->InitialDirent
->Dirent
;
1395 BOOLEAN Found
= FALSE
;
1400 // Loop until we find a match or exaust the directory.
1406 // Move to the next entry unless we want to consider the current
1410 if (ReturnNextEntry
) {
1412 if (!CdLookupNextInitialFileDirent( IrpContext
, Ccb
->Fcb
, FileContext
)) {
1417 PreviousDirent
= ThisDirent
;
1418 ThisDirent
= &FileContext
->InitialDirent
->Dirent
;
1420 CdUpdateDirentName( IrpContext
, ThisDirent
, FlagOn( Ccb
->Flags
, CCB_FLAG_IGNORE_CASE
));
1424 ReturnNextEntry
= TRUE
;
1428 // Don't bother if we have a constant entry and are ignoring them.
1431 if (FlagOn( ThisDirent
->Flags
, DIRENT_FLAG_CONSTANT_ENTRY
) &&
1432 FlagOn( Ccb
->Flags
, CCB_FLAG_ENUM_NOMATCH_CONSTANT_ENTRY
)) {
1438 // Look at the current entry if it is not an associated file
1439 // and the name doesn't match the previous file if the version
1440 // name is not part of the search.
1443 if (!FlagOn( ThisDirent
->DirentFlags
, CD_ATTRIBUTE_ASSOC
)) {
1446 // Check if this entry matches the previous entry except
1447 // for version number and whether we should return the
1448 // entry in that case. Go directly to the name comparison
1451 // There is no previous entry.
1452 // The search expression has a version component.
1453 // The name length doesn't match the length of the previous entry.
1454 // The base name strings don't match.
1457 if ((PreviousDirent
== NULL
) ||
1458 (Ccb
->SearchExpression
.VersionString
.Length
!= 0) ||
1459 (PreviousDirent
->CdCaseFileName
.FileName
.Length
!= ThisDirent
->CdCaseFileName
.FileName
.Length
) ||
1460 FlagOn( PreviousDirent
->DirentFlags
, CD_ATTRIBUTE_ASSOC
) ||
1461 !RtlEqualMemory( PreviousDirent
->CdCaseFileName
.FileName
.Buffer
,
1462 ThisDirent
->CdCaseFileName
.FileName
.Buffer
,
1463 ThisDirent
->CdCaseFileName
.FileName
.Length
)) {
1466 // If we match all names then return to our caller.
1469 if (FlagOn( Ccb
->Flags
, CCB_FLAG_ENUM_MATCH_ALL
)) {
1471 FileContext
->ShortName
.FileName
.Length
= 0;
1477 // Check if the long name matches the search expression.
1480 if (CdIsNameInExpression( IrpContext
,
1481 &ThisDirent
->CdCaseFileName
,
1482 &Ccb
->SearchExpression
,
1487 // Let our caller know we found an entry.
1491 FileContext
->ShortName
.FileName
.Length
= 0;
1496 // The long name didn't match so we need to check for a
1497 // possible short name match. There is no match if the
1498 // long name is 8dot3 or the search expression has a
1499 // version component. Special case the self and parent
1503 if ((Ccb
->SearchExpression
.VersionString
.Length
== 0) &&
1504 !FlagOn( ThisDirent
->Flags
, DIRENT_FLAG_CONSTANT_ENTRY
) &&
1505 !CdIs8dot3Name( IrpContext
,
1506 ThisDirent
->CdFileName
.FileName
)) {
1508 CdGenerate8dot3Name( IrpContext
,
1509 &ThisDirent
->CdCaseFileName
.FileName
,
1510 ThisDirent
->DirentOffset
,
1511 FileContext
->ShortName
.FileName
.Buffer
,
1512 &FileContext
->ShortName
.FileName
.Length
);
1515 // Check if this name matches.
1518 if (CdIsNameInExpression( IrpContext
,
1519 &FileContext
->ShortName
,
1520 &Ccb
->SearchExpression
,
1525 // Let our caller know we found an entry.
1537 // If we found the entry then make sure we walk through all of the
1543 CdLookupLastFileDirent( IrpContext
, Ccb
->Fcb
, FileContext
);