diff options
author | Adam <you@example.com> | 2020-05-17 05:51:50 +0200 |
---|---|---|
committer | Adam <you@example.com> | 2020-05-17 05:51:50 +0200 |
commit | e611b132f9b8abe35b362e5870b74bce94a1e58e (patch) | |
tree | a5781d2ec0e085eeca33cf350cf878f2efea6fe5 /private/ntos/cdfs/dirctrl.c | |
download | NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.gz NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.bz2 NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.lz NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.xz NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.zst NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.zip |
Diffstat (limited to 'private/ntos/cdfs/dirctrl.c')
-rw-r--r-- | private/ntos/cdfs/dirctrl.c | 1431 |
1 files changed, 1431 insertions, 0 deletions
diff --git a/private/ntos/cdfs/dirctrl.c b/private/ntos/cdfs/dirctrl.c new file mode 100644 index 000000000..aaaf5d98f --- /dev/null +++ b/private/ntos/cdfs/dirctrl.c @@ -0,0 +1,1431 @@ +/*++ + +Copyright (c) 1989 Microsoft Corporation + +Module Name: + + DirCtrl.c + +Abstract: + + This module implements the File Directory Control routines for Cdfs called + by the Fsd/Fsp dispatch drivers. + +Author: + + Brian Andrew [BrianAn] 01-July-1995 + +Revision History: + +--*/ + +#include "CdProcs.h" + +// +// The Bug check file id for this module +// + +#define BugCheckFileId (CDFS_BUG_CHECK_DIRCTRL) + +// +// Local support routines +// + +NTSTATUS +CdQueryDirectory ( + IN PIRP_CONTEXT IrpContext, + IN PIRP Irp, + IN PIO_STACK_LOCATION IrpSp, + IN PFCB Fcb, + IN PCCB Ccb + ); + +NTSTATUS +CdNotifyChangeDirectory ( + IN PIRP_CONTEXT IrpContext, + IN PIRP Irp, + IN PIO_STACK_LOCATION IrpSp, + IN PCCB Ccb + ); + +VOID +CdInitializeEnumeration ( + IN PIRP_CONTEXT IrpContext, + IN PIO_STACK_LOCATION IrpSp, + IN PFCB Fcb, + IN OUT PCCB Ccb, + IN OUT PFILE_ENUM_CONTEXT FileContext, + OUT PBOOLEAN ReturnNextEntry, + OUT PBOOLEAN ReturnSingleEntry, + OUT PBOOLEAN InitialQuery + ); + +BOOLEAN +CdEnumerateIndex ( + IN PIRP_CONTEXT IrpContext, + IN PCCB Ccb, + IN OUT PFILE_ENUM_CONTEXT FileContext, + IN BOOLEAN ReturnNextEntry + ); + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGE, CdCommonDirControl) +#pragma alloc_text(PAGE, CdEnumerateIndex) +#pragma alloc_text(PAGE, CdInitializeEnumeration) +#pragma alloc_text(PAGE, CdNotifyChangeDirectory) +#pragma alloc_text(PAGE, CdQueryDirectory) +#endif + + +NTSTATUS +CdCommonDirControl ( + IN PIRP_CONTEXT IrpContext, + IN PIRP Irp + ) + +/*++ + +Routine Description: + + This routine is the entry point for the directory control operations. These + are directory enumerations and directory notify calls. We verify the + user's handle is for a directory and then call the appropriate routine. + +Arguments: + + Irp - Irp for this request. + +Return Value: + + NTSTATUS - Status returned from the lower level routines. + +--*/ + +{ + NTSTATUS Status; + PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp ); + + PFCB Fcb; + PCCB Ccb; + + PAGED_CODE(); + + // + // Decode the user file object and fail this request if it is not + // a user directory. + // + + if (CdDecodeFileObject( IrpContext, + IrpSp->FileObject, + &Fcb, + &Ccb ) != UserDirectoryOpen) { + + CdCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER ); + return STATUS_INVALID_PARAMETER; + } + + // + // We know this is a directory control so we'll case on the + // minor function, and call a internal worker routine to complete + // the irp. + // + + switch (IrpSp->MinorFunction) { + + case IRP_MN_QUERY_DIRECTORY: + + Status = CdQueryDirectory( IrpContext, Irp, IrpSp, Fcb, Ccb ); + break; + + case IRP_MN_NOTIFY_CHANGE_DIRECTORY: + + Status = CdNotifyChangeDirectory( IrpContext, Irp, IrpSp, Ccb ); + break; + + default: + + CdCompleteRequest( IrpContext, Irp, STATUS_INVALID_DEVICE_REQUEST ); + Status = STATUS_INVALID_DEVICE_REQUEST; + break; + } + + return Status; +} + + +// +// Local support routines +// + +NTSTATUS +CdQueryDirectory ( + IN PIRP_CONTEXT IrpContext, + IN PIRP Irp, + IN PIO_STACK_LOCATION IrpSp, + IN PFCB Fcb, + IN PCCB Ccb + ) + +/*++ + +Routine Description: + + This routine performs the query directory operation. It is responsible + for either completing of enqueuing the input Irp. We store the state of the + search in the Ccb. + +Arguments: + + Irp - Supplies the Irp to process + + IrpSp - Stack location for this Irp. + + Fcb - Fcb for this directory. + + Ccb - Ccb for this directory open. + +Return Value: + + NTSTATUS - The return status for the operation + +--*/ + +{ + NTSTATUS Status = STATUS_SUCCESS; + ULONG Information = 0; + + ULONG LastEntry = 0; + ULONG NextEntry = 0; + + ULONG FileNameBytes; + ULONG SeparatorBytes; + ULONG VersionStringBytes; + ULONG BytesConverted; + + FILE_ENUM_CONTEXT FileContext; + PDIRENT ThisDirent; + BOOLEAN InitialQuery; + BOOLEAN ReturnNextEntry; + BOOLEAN ReturnSingleEntry; + BOOLEAN Found; + + + PCHAR UserBuffer; + ULONG BytesRemainingInBuffer; + + ULONG BaseLength; + + PFILE_BOTH_DIR_INFORMATION DirInfo; + PFILE_NAMES_INFORMATION NamesInfo; + + PAGED_CODE(); + + // + // Check if we support this search mode. Also remember the size of the base part of + // each of these structures. + // + + switch (IrpSp->Parameters.QueryDirectory.FileInformationClass) { + + case FileDirectoryInformation: + + BaseLength = FIELD_OFFSET( FILE_DIRECTORY_INFORMATION, + FileName[0] ); + break; + + case FileFullDirectoryInformation: + + BaseLength = FIELD_OFFSET( FILE_FULL_DIR_INFORMATION, + FileName[0] ); + break; + + case FileNamesInformation: + + BaseLength = FIELD_OFFSET( FILE_NAMES_INFORMATION, + FileName[0] ); + break; + + case FileBothDirectoryInformation: + + BaseLength = FIELD_OFFSET( FILE_BOTH_DIR_INFORMATION, + FileName[0] ); + break; + + default: + + CdCompleteRequest( IrpContext, Irp, STATUS_INVALID_INFO_CLASS ); + return STATUS_INVALID_INFO_CLASS; + } + + // + // Get the user buffer. + // + + UserBuffer = CdMapUserBuffer( IrpContext ); + + // + // Initialize our search context. + // + + CdInitializeFileContext( IrpContext, &FileContext ); + + // + // Acquire the directory. + // + + CdAcquireFileShared( IrpContext, Fcb ); + + // + // Use a try-finally to facilitate cleanup. + // + + try { + + // + // Verify the Fcb is still good. + // + + CdVerifyFcbOperation( IrpContext, Fcb ); + + // + // Start by getting the initial state for the enumeration. This will set up the Ccb with + // the initial search parameters and let us know the starting offset in the directory + // to search. + // + + CdInitializeEnumeration( IrpContext, + IrpSp, + Fcb, + Ccb, + &FileContext, + &ReturnNextEntry, + &ReturnSingleEntry, + &InitialQuery ); + + // + // The current dirent is stored in the InitialDirent field. We capture + // this here so that we have a valid restart point even if we don't + // find a single entry. + // + + ThisDirent = &FileContext.InitialDirent->Dirent; + + // + // At this point we are about to enter our query loop. We have + // determined the index into the directory file to begin the + // search. LastEntry and NextEntry are used to index into the user + // buffer. LastEntry is the last entry we've added, NextEntry is + // current one we're working on. If NextEntry is non-zero, then + // at least one entry was added. + // + + while (TRUE) { + + // + // If the user had requested only a single match and we have + // returned that, then we stop at this point. We update the Ccb with + // the status based on the last entry returned. + // + + if ((NextEntry != 0) && ReturnSingleEntry) { + + try_return( Status ); + } + + // + // We try to locate the next matching dirent. Our search if based on a starting + // dirent offset, whether we should return the current or next entry, whether + // we should be doing a short name search and finally whether we should be + // checking for a version match. + // + + Found = CdEnumerateIndex( IrpContext, Ccb, &FileContext, ReturnNextEntry ); + + // + // Initialize the value for the next search. + // + + ReturnNextEntry = TRUE; + + // + // If we didn't receive a dirent, then we are at the end of the + // directory. If we have returned any files, we exit with + // success, otherwise we return STATUS_NO_MORE_FILES. + // + + if (!Found) { + + if (NextEntry == 0) { + + Status = STATUS_NO_MORE_FILES; + + if (InitialQuery) { + + Status = STATUS_NO_SUCH_FILE; + } + } + + try_return( Status ); + } + + // + // Remember the dirent for the file we just found. + // + + ThisDirent = &FileContext.InitialDirent->Dirent; + + // + // Here are the rules concerning filling up the buffer: + // + // 1. The Io system garentees that there will always be + // enough room for at least one base record. + // + // 2. If the full first record (including file name) cannot + // fit, as much of the name as possible is copied and + // STATUS_BUFFER_OVERFLOW is returned. + // + // 3. If a subsequent record cannot completely fit into the + // buffer, none of it (as in 0 bytes) is copied, and + // STATUS_SUCCESS is returned. A subsequent query will + // pick up with this record. + // + + // + // Let's compute the number of bytes we need to transfer the current entry. + // + + SeparatorBytes = + VersionStringBytes = 0; + + // + // We can look directly at the dirent that we found. + // + + FileNameBytes = ThisDirent->CdFileName.FileName.Length; + + // + // Compute the number of bytes for the version string if + // we will return this. Allow directories with illegal ";". + // + + if (((Ccb->SearchExpression.VersionString.Length != 0) || + (FlagOn(ThisDirent->DirentFlags, CD_ATTRIBUTE_DIRECTORY))) && + (ThisDirent->CdFileName.VersionString.Length != 0)) { + + SeparatorBytes = 2; + + VersionStringBytes = ThisDirent->CdFileName.VersionString.Length; + } + + // + // If the slot for the next entry would be beyond the length of the + // user's buffer just exit (we know we've returned at least one entry + // already). This will happen when we align the pointer past the end. + // + + if (NextEntry > IrpSp->Parameters.QueryDirectory.Length) { + + ReturnNextEntry = FALSE; + try_return( Status = STATUS_SUCCESS ); + } + + // + // Compute the number of bytes remaining in the buffer. Round this + // down to a WCHAR boundary so we can copy full characters. + // + + BytesRemainingInBuffer = IrpSp->Parameters.QueryDirectory.Length - NextEntry; + ClearFlag( BytesRemainingInBuffer, 1 ); + + // + // If this won't fit and we have returned a previous entry then just + // return STATUS_SUCCESS. + // + + if ((BaseLength + FileNameBytes + SeparatorBytes + VersionStringBytes) > BytesRemainingInBuffer) { + + // + // If we already found an entry then just exit. + // + + if (NextEntry != 0) { + + ReturnNextEntry = FALSE; + try_return( Status = STATUS_SUCCESS ); + } + + // + // Don't even try to return the version string if it doesn't all fit. + // Reduce the FileNameBytes to just fit in the buffer. + // + + if ((BaseLength + FileNameBytes) > BytesRemainingInBuffer) { + + FileNameBytes = BytesRemainingInBuffer - BaseLength; + } + + // + // Don't return any version string bytes. + // + + VersionStringBytes = + SeparatorBytes = 0; + + // + // Use a status code of STATUS_BUFFER_OVERFLOW. Also set + // ReturnSingleEntry so that we will exit the loop at the top. + // + + Status = STATUS_BUFFER_OVERFLOW; + ReturnSingleEntry = TRUE; + } + + // + // Zero and initialize the base part of the current entry. + // + + RtlZeroMemory( Add2Ptr( UserBuffer, NextEntry, PVOID ), + BaseLength ); + + // + // Now we have an entry to return to our caller. + // We'll case on the type of information requested and fill up + // the user buffer if everything fits. + // + + switch (IrpSp->Parameters.QueryDirectory.FileInformationClass) { + + case FileBothDirectoryInformation: + case FileFullDirectoryInformation: + case FileDirectoryInformation: + + DirInfo = Add2Ptr( UserBuffer, NextEntry, PFILE_BOTH_DIR_INFORMATION ); + + // + // Use the create time for all the time stamps. + // + + CdConvertCdTimeToNtTime( IrpContext, + FileContext.InitialDirent->Dirent.CdTime, + &DirInfo->CreationTime ); + + DirInfo->LastWriteTime = DirInfo->ChangeTime = DirInfo->CreationTime; + + // + // Set the attributes and sizes separately for directories and + // files. + // + + if (FlagOn( ThisDirent->DirentFlags, CD_ATTRIBUTE_DIRECTORY )) { + + DirInfo->EndOfFile.QuadPart = DirInfo->AllocationSize.QuadPart = 0; + + SetFlag( DirInfo->FileAttributes, FILE_ATTRIBUTE_DIRECTORY ); + + } else { + + DirInfo->EndOfFile.QuadPart = FileContext.FileSize; + DirInfo->AllocationSize.QuadPart = LlSectorAlign( FileContext.FileSize ); + } + + // + // All Cdrom files are readonly. We also copy the existence + // bit to the hidden attribute. + // + + SetFlag( DirInfo->FileAttributes, FILE_ATTRIBUTE_READONLY ); + + if (FlagOn( ThisDirent->DirentFlags, + CD_ATTRIBUTE_HIDDEN )) { + + SetFlag( DirInfo->FileAttributes, FILE_ATTRIBUTE_HIDDEN ); + } + + DirInfo->FileIndex = ThisDirent->DirentOffset; + + DirInfo->FileNameLength = FileNameBytes + SeparatorBytes + VersionStringBytes; + + break; + + case FileNamesInformation: + + NamesInfo = Add2Ptr( UserBuffer, NextEntry, PFILE_NAMES_INFORMATION ); + + NamesInfo->FileIndex = ThisDirent->DirentOffset; + + NamesInfo->FileNameLength = FileNameBytes + SeparatorBytes + VersionStringBytes; + + break; + } + + // + // Now copy as much of the name as possible. We also may have a version + // string to copy. + // + + if (FileNameBytes != 0) { + + // + // This is a Unicode name, we can copy the bytes directly. + // + + RtlCopyMemory( Add2Ptr( UserBuffer, NextEntry + BaseLength, PVOID ), + ThisDirent->CdFileName.FileName.Buffer, + FileNameBytes ); + + if (SeparatorBytes != 0) { + + *(Add2Ptr( UserBuffer, + NextEntry + BaseLength + FileNameBytes, + PWCHAR )) = L';'; + + if (VersionStringBytes != 0) { + + RtlCopyMemory( Add2Ptr( UserBuffer, + NextEntry + BaseLength + FileNameBytes + sizeof( WCHAR ), + PVOID ), + ThisDirent->CdFileName.VersionString.Buffer, + VersionStringBytes ); + } + } + } + + // + // Fill in the short name if we got STATUS_SUCCESS. The short name + // may already be in the file context. Otherwise we will check + // whether the long name is 8.3. Special case the self and parent + // directory names. + // + + if ((Status == STATUS_SUCCESS) && + (IrpSp->Parameters.QueryDirectory.FileInformationClass == FileBothDirectoryInformation) && + (Ccb->SearchExpression.VersionString.Length == 0) && + (ThisDirent->CdFileName.FileName.Buffer != CdUnicodeSelfArray) && + (ThisDirent->CdFileName.FileName.Buffer != CdUnicodeParentArray)) { + + // + // If we already have the short name then copy into the user's buffer. + // + + if (FileContext.ShortName.FileName.Length != 0) { + + RtlCopyMemory( DirInfo->ShortName, + FileContext.ShortName.FileName.Buffer, + FileContext.ShortName.FileName.Length ); + + DirInfo->ShortNameLength = (CCHAR) FileContext.ShortName.FileName.Length; + + // + // If the short name length is currently zero then check if + // the long name is not 8.3. We can copy the short name in + // unicode form directly into the caller's buffer. + // + + } else { + + if (!CdIs8dot3Name( IrpContext, + ThisDirent->CdFileName.FileName )) { + + CdGenerate8dot3Name( IrpContext, + &ThisDirent->CdCaseFileName.FileName, + ThisDirent->DirentOffset, + DirInfo->ShortName, + &FileContext.ShortName.FileName.Length ); + + DirInfo->ShortNameLength = (CCHAR) FileContext.ShortName.FileName.Length; + } + } + + } + + // + // Sum the total number of bytes for the information field. + // + + FileNameBytes += SeparatorBytes + VersionStringBytes; + + // + // Update the information with the number of bytes stored in the + // buffer. We quad-align the existing buffer to add any necessary + // pad bytes. + // + + Information = NextEntry + BaseLength + FileNameBytes; + + // + // Go back to the previous entry and fill in the update to this entry. + // + + *(Add2Ptr( UserBuffer, LastEntry, PULONG )) = NextEntry - LastEntry; + + // + // Set up our variables for the next dirent. + // + + InitialQuery = FALSE; + + LastEntry = NextEntry; + NextEntry = QuadAlign( Information ); + } + + try_exit: NOTHING; + + // + // Update the Ccb to show the current state of the enumeration. + // + + CdLockFcb( IrpContext, Fcb ); + + Ccb->CurrentDirentOffset = ThisDirent->DirentOffset; + + ClearFlag( Ccb->Flags, CCB_FLAG_ENUM_RETURN_NEXT ); + + if (ReturnNextEntry) { + + SetFlag( Ccb->Flags, CCB_FLAG_ENUM_RETURN_NEXT ); + } + + CdUnlockFcb( IrpContext, Fcb ); + + } finally { + + // + // Cleanup our search context. + // + + CdCleanupFileContext( IrpContext, &FileContext ); + + // + // Release the Fcb. + // + + CdReleaseFile( IrpContext, Fcb ); + } + + // + // Complete the request here. + // + + Irp->IoStatus.Information = Information; + + CdCompleteRequest( IrpContext, Irp, Status ); + return Status; +} + + +// +// Local support routines +// + +NTSTATUS +CdNotifyChangeDirectory ( + IN PIRP_CONTEXT IrpContext, + IN PIRP Irp, + IN PIO_STACK_LOCATION IrpSp, + IN PCCB Ccb + ) + +/*++ + +Routine Description: + + This routine performs the notify change directory operation. It is + responsible for either completing of enqueuing the input Irp. Although there + will never be a notify signalled on a CDROM disk we still support this call. + + We have already checked that this is not an OpenById handle. + +Arguments: + + Irp - Supplies the Irp to process + + IrpSp - Io stack location for this request. + + Ccb - Handle to the directory being watched. + +Return Value: + + NTSTATUS - STATUS_PENDING, any other error will raise. + +--*/ + +{ + PAGED_CODE(); + + // + // Always set the wait bit in the IrpContext so the initial wait can't fail. + // + + SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT ); + + // + // Acquire the Vcb shared. + // + + CdAcquireVcbShared( IrpContext, IrpContext->Vcb, FALSE ); + + // + // Use a try-finally to facilitate cleanup. + // + + try { + + // + // Verify the Vcb. + // + + CdVerifyVcb( IrpContext, IrpContext->Vcb ); + + // + // Call the Fsrtl package to process the request. We cast the + // unicode strings to ansi strings as the dir notify package + // only deals with memory matching. + // + + FsRtlNotifyFullChangeDirectory( IrpContext->Vcb->NotifySync, + &IrpContext->Vcb->DirNotifyList, + Ccb, + (PSTRING) &IrpSp->FileObject->FileName, + BooleanFlagOn( IrpSp->Flags, SL_WATCH_TREE ), + FALSE, + IrpSp->Parameters.NotifyDirectory.CompletionFilter, + Irp, + NULL, + NULL ); + + } finally { + + // + // Release the Vcb. + // + + CdReleaseVcb( IrpContext, IrpContext->Vcb ); + } + + // + // Cleanup the IrpContext. + // + + CdCompleteRequest( IrpContext, NULL, STATUS_SUCCESS ); + + return STATUS_PENDING; +} + + +// +// Local support routine +// + +VOID +CdInitializeEnumeration ( + IN PIRP_CONTEXT IrpContext, + IN PIO_STACK_LOCATION IrpSp, + IN PFCB Fcb, + IN OUT PCCB Ccb, + IN OUT PFILE_ENUM_CONTEXT FileContext, + OUT PBOOLEAN ReturnNextEntry, + OUT PBOOLEAN ReturnSingleEntry, + OUT PBOOLEAN InitialQuery + ) + +/*++ + +Routine Description: + + This routine is called to initialize the enumeration variables and structures. + We look at the state of a previous enumeration from the Ccb as well as any + input values from the user. On exit we will position the FileContext at + a file in the directory and let the caller know whether this entry or the + next entry should be returned. + +Arguments: + + IrpSp - Irp stack location for this request. + + Fcb - Fcb for this directory. + + Ccb - Ccb for the directory handle. + + FileContext - FileContext to use for this enumeration. + + ReturnNextEntry - Address to store whether we should return the entry at + the FileContext position or the next entry. + + ReturnSingleEntry - Address to store whether we should only return + a single entry. + + InitialQuery - Address to store whether this is the first enumeration + query on this handle. + +Return Value: + + None. + +--*/ + +{ + NTSTATUS Status; + + PUNICODE_STRING FileName; + CD_NAME WildCardName; + CD_NAME SearchExpression; + + ULONG CcbFlags; + + ULONG DirentOffset; + ULONG LastDirentOffset; + BOOLEAN KnownOffset; + + BOOLEAN Found; + + PAGED_CODE(); + + // + // If this is the initial query then build a search expression from the input + // file name. + // + + if (!FlagOn( Ccb->Flags, CCB_FLAG_ENUM_INITIALIZED )) { + + FileName = (PUNICODE_STRING) IrpSp->Parameters.QueryDirectory.FileName; + + CcbFlags = 0; + + // + // If the filename is not specified or is a single '*' then we will + // match all names. + // + + if ((FileName == NULL) || + (FileName->Buffer == NULL) || + (FileName->Length == 0) || + ((FileName->Length == sizeof( WCHAR )) && + (FileName->Buffer[0] == L'*'))) { + + SetFlag( CcbFlags, CCB_FLAG_ENUM_MATCH_ALL ); + RtlZeroMemory( &SearchExpression, sizeof( SearchExpression )); + + // + // Otherwise build the CdName from the name in the stack location. + // This involves building both the name and version portions and + // checking for wild card characters. We also upcase the string if + // this is a case-insensitive search. + // + + } else { + + // + // Create a CdName to check for wild cards. + // + + WildCardName.FileName = *FileName; + + CdConvertNameToCdName( IrpContext, &WildCardName ); + + // + // The name better have at least one character. + // + + if (WildCardName.FileName.Length == 0) { + + CdRaiseStatus( IrpContext, STATUS_INVALID_PARAMETER ); + } + + // + // Check for wildcards in the separate components. + // + + if (FsRtlDoesNameContainWildCards( &WildCardName.FileName)) { + + SetFlag( CcbFlags, CCB_FLAG_ENUM_NAME_EXP_HAS_WILD ); + } + + if ((WildCardName.VersionString.Length != 0) && + (FsRtlDoesNameContainWildCards( &WildCardName.VersionString ))) { + + SetFlag( CcbFlags, CCB_FLAG_ENUM_VERSION_EXP_HAS_WILD ); + + // + // Check if this is a wild card only and match all version + // strings. + // + + if ((WildCardName.VersionString.Length == sizeof( WCHAR )) && + (WildCardName.VersionString.Buffer[0] == L'*')) { + + SetFlag( CcbFlags, CCB_FLAG_ENUM_VERSION_MATCH_ALL ); + } + } + + // + // Now create the search expression to store in the Ccb. + // + + SearchExpression.FileName.Buffer = FsRtlAllocatePoolWithTag( CdPagedPool, + FileName->Length, + TAG_ENUM_EXPRESSION ); + + SearchExpression.FileName.MaximumLength = FileName->Length; + + // + // Either copy the name directly or perform the upcase. + // + + if (FlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE )) { + + Status = RtlUpcaseUnicodeString( (PUNICODE_STRING) &SearchExpression.FileName, + FileName, + FALSE ); + + // + // This should never fail. + // + + ASSERT( Status == STATUS_SUCCESS ); + + } else { + + RtlCopyMemory( SearchExpression.FileName.Buffer, + FileName->Buffer, + FileName->Length ); + } + + // + // Now split into the separate name and version components. + // + + SearchExpression.FileName.Length = WildCardName.FileName.Length; + SearchExpression.VersionString.Length = WildCardName.VersionString.Length; + SearchExpression.VersionString.MaximumLength = WildCardName.VersionString.MaximumLength; + + SearchExpression.VersionString.Buffer = Add2Ptr( SearchExpression.FileName.Buffer, + SearchExpression.FileName.Length + sizeof( WCHAR ), + PWCHAR ); + } + + // + // Now lock the Fcb in order to update the Ccb with the inital + // enumeration values. + // + + CdLockFcb( IrpContext, Fcb ); + + // + // Check again that this is the initial search. + // + + if (!FlagOn( Ccb->Flags, CCB_FLAG_ENUM_INITIALIZED )) { + + // + // Update the values in the Ccb. + // + + Ccb->CurrentDirentOffset = Fcb->StreamOffset; + Ccb->SearchExpression = SearchExpression; + + // + // Set the appropriate flags in the Ccb. + // + + SetFlag( Ccb->Flags, CcbFlags | CCB_FLAG_ENUM_INITIALIZED ); + + // + // Otherwise cleanup any buffer allocated here. + // + + } else { + + if (!FlagOn( CcbFlags, CCB_FLAG_ENUM_MATCH_ALL )) { + + ExFreePool( SearchExpression.FileName.Buffer ); + } + } + + // + // Otherwise lock the Fcb so we can read the current enumeration values. + // + + } else { + + CdLockFcb( IrpContext, Fcb ); + } + + // + // Capture the current state of the enumeration. + // + // If the user specified an index then use his offset. We always + // return the next entry in this case. + // + + if (FlagOn( IrpSp->Flags, SL_INDEX_SPECIFIED )) { + + KnownOffset = FALSE; + DirentOffset = IrpSp->Parameters.QueryDirectory.FileIndex; + *ReturnNextEntry = TRUE; + + // + // If we are restarting the scan then go from the self entry. + // + + } else if (FlagOn( IrpSp->Flags, SL_RESTART_SCAN )) { + + KnownOffset = TRUE; + DirentOffset = Fcb->StreamOffset; + *ReturnNextEntry = FALSE; + + // + // Otherwise use the values from the Ccb. + // + + } else { + + KnownOffset = TRUE; + DirentOffset = Ccb->CurrentDirentOffset; + *ReturnNextEntry = BooleanFlagOn( Ccb->Flags, CCB_FLAG_ENUM_RETURN_NEXT ); + } + + // + // Unlock the Fcb. + // + + CdUnlockFcb( IrpContext, Fcb ); + + // + // We have the starting offset in the directory and whether to return + // that entry or the next. If we are at the beginning of the directory + // and are returning that entry, then tell our caller this is the + // initial query. + // + + *InitialQuery = FALSE; + + if ((DirentOffset == Fcb->StreamOffset) && + !(*ReturnNextEntry)) { + + *InitialQuery = TRUE; + } + + // + // If there is no file object then create it now. + // + + if (Fcb->FileObject == NULL) { + + CdCreateInternalStream( IrpContext, Fcb->Vcb, Fcb ); + } + + // + // Determine the offset in the stream to position the FileContext and + // whether this offset is known to be a file offset. + // + // If this offset is known to be safe then go ahead and position the + // file context. This handles the cases where the offset is the beginning + // of the stream, the offset is from a previous search or this is the + // initial query. + // + + if (KnownOffset) { + + CdLookupInitialFileDirent( IrpContext, Fcb, FileContext, DirentOffset ); + + // + // Otherwise we walk through the directory from the beginning until + // we reach the entry which contains this offset. + // + + } else { + + LastDirentOffset = Fcb->StreamOffset; + Found = TRUE; + + CdLookupInitialFileDirent( IrpContext, Fcb, FileContext, LastDirentOffset ); + + // + // If the requested offset is prior to the beginning offset in the stream + // then don't return the next entry. + // + + if (DirentOffset < LastDirentOffset) { + + *ReturnNextEntry = FALSE; + + // + // Else look for the last entry which ends past the desired index. + // + + } else { + + // + // Keep walking through the directory until we run out of + // entries or we find an entry which ends beyond the input + // index value. + // + + do { + + // + // If we have passed the index value then exit. + // + + if (FileContext->InitialDirent->Dirent.DirentOffset > DirentOffset) { + + Found = FALSE; + break; + } + + // + // Remember the current position in case we need to go back. + // + + LastDirentOffset = FileContext->InitialDirent->Dirent.DirentOffset; + + // + // Exit if the next entry is beyond the desired index value. + // + + if (LastDirentOffset + FileContext->InitialDirent->Dirent.DirentLength > DirentOffset) { + + break; + } + + Found = CdLookupNextInitialFileDirent( IrpContext, Fcb, FileContext ); + + } while (Found); + + // + // If we didn't find the entry then go back to the last known entry. + // This can happen if the index lies in the unused range at the + // end of a sector. + // + + if (!Found) { + + CdCleanupFileContext( IrpContext, FileContext ); + CdInitializeFileContext( IrpContext, FileContext ); + + CdLookupInitialFileDirent( IrpContext, Fcb, FileContext, LastDirentOffset ); + } + } + } + + // + // Only update the dirent name if we will need it for some reason. + // Don't update this name if we are returning the next entry and + // the search string has a version component. + // + + FileContext->ShortName.FileName.Length = 0; + + if (!(*ReturnNextEntry) || + (Ccb->SearchExpression.VersionString.Length == 0)) { + + // + // Update the name in the dirent into filename and version components. + // + + CdUpdateDirentName( IrpContext, + &FileContext->InitialDirent->Dirent, + FlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE )); + } + + // + // Look at the flag in the IrpSp indicating whether to return just + // one entry. + // + + *ReturnSingleEntry = FALSE; + + if (FlagOn( IrpSp->Flags, SL_RETURN_SINGLE_ENTRY )) { + + *ReturnSingleEntry = TRUE; + } + + return; +} + + +// +// Local support routine +// + +BOOLEAN +CdEnumerateIndex ( + IN PIRP_CONTEXT IrpContext, + IN PCCB Ccb, + IN OUT PFILE_ENUM_CONTEXT FileContext, + IN BOOLEAN ReturnNextEntry + ) + +/*++ + +Routine Description: + + This routine is the worker routine for index enumeration. We are positioned + at some dirent in the directory and will either return the first match + at that point or look to the next entry. The Ccb contains details about + the type of matching to do. If the user didn't specify a version in + his search string then we only return the first version of a sequence + of files with versions. We also don't return any associated files. + +Arguments: + + Ccb - Ccb for this directory handle. + + FileContext - File context already positioned at some entry in the directory. + + ReturnNextEntry - Indicates if we are returning this entry or should start + with the next entry. + +Return Value: + + BOOLEAN - TRUE if next entry is found, FALSE otherwise. + +--*/ + +{ + PDIRENT PreviousDirent = NULL; + PDIRENT ThisDirent = &FileContext->InitialDirent->Dirent; + + BOOLEAN Found = FALSE; + + PAGED_CODE(); + + // + // Loop until we find a match or exaust the directory. + // + + while (TRUE) { + + // + // Move to the next entry unless we want to consider the current + // entry. + // + + if (ReturnNextEntry) { + + if (!CdLookupNextInitialFileDirent( IrpContext, Ccb->Fcb, FileContext )) { + + break; + } + + PreviousDirent = ThisDirent; + ThisDirent = &FileContext->InitialDirent->Dirent; + + CdUpdateDirentName( IrpContext, ThisDirent, FlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE )); + } + + // + // Look at the current entry if it is not an associated file + // and the name doesn't match the previous file if the version + // name is not part of the search. + // + + if (!FlagOn( ThisDirent->DirentFlags, CD_ATTRIBUTE_ASSOC )) { + + // + // Check if this entry matches the previous entry except + // for version number and whether we should return the + // entry in that case. Go directly to the name comparison + // if: + // + // There is no previous entry. + // The search expression has a version component. + // The name length doesn't match the length of the previous entry. + // The base name strings don't match. + // + + if ((PreviousDirent == NULL) || + (Ccb->SearchExpression.VersionString.Length != 0) || + (PreviousDirent->CdCaseFileName.FileName.Length != ThisDirent->CdCaseFileName.FileName.Length) || + FlagOn( PreviousDirent->DirentFlags, CD_ATTRIBUTE_ASSOC ) || + !RtlEqualMemory( PreviousDirent->CdCaseFileName.FileName.Buffer, + ThisDirent->CdCaseFileName.FileName.Buffer, + ThisDirent->CdCaseFileName.FileName.Length )) { + + // + // If we match all names then return to our caller. + // + + if (FlagOn( Ccb->Flags, CCB_FLAG_ENUM_MATCH_ALL )) { + + FileContext->ShortName.FileName.Length = 0; + Found = TRUE; + break; + } + + // + // Check if the long name matches the search expression. + // + + if (CdIsNameInExpression( IrpContext, + &ThisDirent->CdCaseFileName, + &Ccb->SearchExpression, + Ccb->Flags, + TRUE )) { + + // + // Let our caller know we found an entry. + // + + Found = TRUE; + FileContext->ShortName.FileName.Length = 0; + break; + } + + // + // The long name didn't match so we need to check for a + // possible short name match. There is no match if the + // long name is 8dot3 or the search expression has a + // version component. Special case the self and parent + // entries. + // + + if ((Ccb->SearchExpression.VersionString.Length == 0) && + (ThisDirent->CdFileName.FileName.Buffer != CdUnicodeSelfArray) && + (ThisDirent->CdFileName.FileName.Buffer != CdUnicodeParentArray) && + !CdIs8dot3Name( IrpContext, + ThisDirent->CdFileName.FileName )) { + + CdGenerate8dot3Name( IrpContext, + &ThisDirent->CdCaseFileName.FileName, + ThisDirent->DirentOffset, + FileContext->ShortName.FileName.Buffer, + &FileContext->ShortName.FileName.Length ); + + // + // Check if this name matches. + // + + if (CdIsNameInExpression( IrpContext, + &FileContext->ShortName, + &Ccb->SearchExpression, + Ccb->Flags, + FALSE )) { + + // + // Let our caller know we found an entry. + // + + Found = TRUE; + break; + } + } + } + } + + ReturnNextEntry = TRUE; + } + + // + // If we found the entry then make sure we walk through all of the + // file dirents. + // + + if (Found) { + + CdLookupLastFileDirent( IrpContext, Ccb->Fcb, FileContext ); + } + + return Found; +} + |