|
|
/*++
Copyright (c) 1991 Microsoft Corporation
Module Name:
DirCtrl.c
Abstract:
This module implements the File Directory Control routine for Ntfs called by the dispatch driver.
Author:
Tom Miller [TomM] 1-Jan-1992
(Based heavily on GaryKi's dirctrl.c for pinball.)
Revision History:
--*/
#include "NtfsProc.h"
//
// The local debug trace level
//
#define Dbg (DEBUG_TRACE_DIRCTRL)
//
// Define a tag for general pool allocations from this module
//
#undef MODULE_POOL_TAG
#define MODULE_POOL_TAG ('dFtN')
NTSTATUS NtfsQueryDirectory ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp, IN PVCB Vcb, IN PSCB Scb, IN PCCB Ccb );
NTSTATUS NtfsNotifyChangeDirectory ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp, IN PVCB Vcb, IN PSCB Scb, IN PCCB Ccb );
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, NtfsCommonDirectoryControl)
#pragma alloc_text(PAGE, NtfsFsdDirectoryControl)
#pragma alloc_text(PAGE, NtfsNotifyChangeDirectory)
#pragma alloc_text(PAGE, NtfsReportViewIndexNotify)
#pragma alloc_text(PAGE, NtfsQueryDirectory)
#endif
NTSTATUS NtfsFsdDirectoryControl ( IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject, IN PIRP Irp )
/*++
Routine Description:
This routine implements the FSD part of Directory Control.
Arguments:
VolumeDeviceObject - Supplies the volume device object where the file exists
Irp - Supplies the Irp being processed
Return Value:
NTSTATUS - The FSD status for the IRP
--*/
{ TOP_LEVEL_CONTEXT TopLevelContext; PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
NTSTATUS Status = STATUS_SUCCESS; PIRP_CONTEXT IrpContext = NULL; IRP_CONTEXT LocalIrpContext;
BOOLEAN Wait;
ASSERT_IRP( Irp );
UNREFERENCED_PARAMETER( VolumeDeviceObject );
PAGED_CODE();
DebugTrace( +1, Dbg, ("NtfsFsdDirectoryControl\n") );
//
// Call the common Directory Control routine
//
FsRtlEnterFileSystem();
//
// Always make these requests look top level.
//
ThreadTopLevelContext = NtfsInitializeTopLevelIrp( &TopLevelContext, TRUE, TRUE );
do {
try {
//
// We are either initiating this request or retrying it.
//
if (IrpContext == NULL) {
//
// Allocate and initialize the IrpContext.
//
Wait = FALSE; if (CanFsdWait( Irp )) {
Wait = TRUE; IrpContext = &LocalIrpContext; }
NtfsInitializeIrpContext( Irp, Wait, &IrpContext );
//
// Initialize the thread top level structure, if needed.
//
NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
} else if (Status == STATUS_LOG_FILE_FULL) {
NtfsCheckpointForLogFileFull( IrpContext ); }
Status = NtfsCommonDirectoryControl( IrpContext, Irp ); break;
} except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
//
// We had some trouble trying to perform the requested
// operation, so we'll abort the I/O request with
// the error status that we get back from the
// execption code
//
Status = NtfsProcessException( IrpContext, Irp, GetExceptionCode() ); }
} while (Status == STATUS_CANT_WAIT || Status == STATUS_LOG_FILE_FULL);
ASSERT( IoGetTopLevelIrp() != (PIRP) &TopLevelContext ); FsRtlExitFileSystem();
//
// And return to our caller
//
DebugTrace( -1, Dbg, ("NtfsFsdDirectoryControl -> %08lx\n", Status) );
return Status; }
NTSTATUS NtfsCommonDirectoryControl ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp )
/*++
Routine Description:
This is the common routine for Directory Control called by both the fsd and fsp threads.
Arguments:
Irp - Supplies the Irp to process
Return Value:
NTSTATUS - The return status for the operation
--*/
{ NTSTATUS Status; PIO_STACK_LOCATION IrpSp; PFILE_OBJECT FileObject;
TYPE_OF_OPEN TypeOfOpen; PVCB Vcb; PSCB Scb; PCCB Ccb; PFCB Fcb;
ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_IRP( Irp ); ASSERT( FlagOn( IrpContext->TopLevelIrpContext->State, IRP_CONTEXT_STATE_OWNS_TOP_LEVEL ));
PAGED_CODE();
//
// Get the current Irp stack location
//
IrpSp = IoGetCurrentIrpStackLocation( Irp );
DebugTrace( +1, Dbg, ("NtfsCommonDirectoryControl\n") ); DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) ); DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
//
// Extract and decode the file object
//
FileObject = IrpSp->FileObject; TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
//
// We know this is a directory control so we'll case on the
// minor function, and call an internal worker routine to complete
// the irp.
//
switch ( IrpSp->MinorFunction ) {
case IRP_MN_QUERY_DIRECTORY:
//
// Decide if this is a view or filename index.
//
if ((UserViewIndexOpen == TypeOfOpen) && FlagOn( Scb->ScbState, SCB_STATE_VIEW_INDEX )) {
Status = NtfsQueryViewIndex( IrpContext, Irp, Vcb, Scb, Ccb );
} else if ((UserDirectoryOpen == TypeOfOpen) && !FlagOn( Scb->ScbState, SCB_STATE_VIEW_INDEX )) {
Status = NtfsQueryDirectory( IrpContext, Irp, Vcb, Scb, Ccb );
} else {
NtfsCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER );
DebugTrace( -1, Dbg, ("NtfsCommonDirectoryControl -> STATUS_INVALID_PARAMETER\n") ); return STATUS_INVALID_PARAMETER; }
break;
case IRP_MN_NOTIFY_CHANGE_DIRECTORY:
//
// We can't perform this operation on open by Id or if the caller has
// closed his handle. Make sure the handle is for either a view index
// or file name index.
//
if (((TypeOfOpen != UserDirectoryOpen) && (TypeOfOpen != UserViewIndexOpen)) || FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID ) || FlagOn( FileObject->Flags, FO_CLEANUP_COMPLETE )) {
NtfsCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER );
DebugTrace( -1, Dbg, ("NtfsCommonDirectoryControl -> STATUS_INVALID_PARAMETER\n") ); return STATUS_INVALID_PARAMETER; }
Status = NtfsNotifyChangeDirectory( IrpContext, Irp, Vcb, Scb, Ccb ); break;
default:
DebugTrace( 0, Dbg, ("Invalid Minor Function %08lx\n", IrpSp->MinorFunction) );
NtfsCompleteRequest( IrpContext, Irp, STATUS_INVALID_DEVICE_REQUEST );
Status = STATUS_INVALID_DEVICE_REQUEST; break; }
//
// And return to our caller
//
DebugTrace( -1, Dbg, ("NtfsCommonDirectoryControl -> %08lx\n", Status) );
return Status; }
VOID NtfsReportViewIndexNotify ( IN PVCB Vcb, IN PFCB Fcb, IN ULONG FilterMatch, IN ULONG Action, IN PVOID ChangeInfoBuffer, IN USHORT ChangeInfoBufferLength )
/*++
Routine Description:
This function notifies processes that there has been a change to a view index they are watching. It is analogous to the NtfsReportDirNotify macro, which is used only for directories, while this function is used only for view indices.
Arguments:
Vcb - The volume on which the change is taking place.
Fcb - The file on which the change is taking place.
FilterMatch - This flag field is compared with the completion filter in the notify structure. If any of the corresponding bits in the completion filter are set, then a notify condition exists.
Action - This is the action code to store in the user's buffer if present.
ChangeInfoBuffer - Pointer to a buffer of information related to the change being reported. This information is returned to the process that owns the notify handle.
ChangeInfoBufferLength - The length, in bytes, of the buffer passed in ChangeInfoBuffer.
Return Value:
None.
--*/
{ STRING ChangeInfo;
PAGED_CODE( );
ChangeInfo.Length = ChangeInfo.MaximumLength = ChangeInfoBufferLength; ChangeInfo.Buffer = ChangeInfoBuffer;
FsRtlNotifyFilterReportChange( Vcb->NotifySync, &Vcb->ViewIndexNotifyList, NULL, 0, &ChangeInfo, &ChangeInfo, FilterMatch, Action, Fcb, NULL ); }
//
// Local Support Routine
//
NTSTATUS NtfsQueryDirectory ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp, IN PVCB Vcb, IN PSCB Scb, IN PCCB Ccb )
/*++
Routine Description:
This routine performs the query directory operation. It is responsible for either completing or enqueuing the input Irp.
Arguments:
Irp - Supplies the Irp to process
Vcb - Supplies its Vcb
Scb - Supplies its Scb
Ccb - Supplies its Ccb
Return Value:
NTSTATUS - The return status for the operation
--*/
{ NTSTATUS Status = STATUS_SUCCESS; PIO_STACK_LOCATION IrpSp;
PUCHAR Buffer; CLONG UserBufferLength;
ULONG BaseLength;
PUNICODE_STRING UniFileName; FILE_INFORMATION_CLASS FileInformationClass; ULONG FileIndex; BOOLEAN RestartScan; BOOLEAN ReturnSingleEntry; BOOLEAN IndexSpecified; BOOLEAN AccessingUserBuffer = FALSE;
BOOLEAN IgnoreCase;
BOOLEAN NextFlag;
BOOLEAN GotEntry;
BOOLEAN CallRestart;
ULONG NextEntry; ULONG LastEntry;
PFILE_DIRECTORY_INFORMATION DirInfo; PFILE_FULL_DIR_INFORMATION FullDirInfo; PFILE_BOTH_DIR_INFORMATION BothDirInfo; PFILE_NAMES_INFORMATION NamesInfo;
PFILE_NAME FileNameBuffer; PVOID UnwindFileNameBuffer = NULL; ULONG FileNameLength;
ULONG SizeOfFileName = FIELD_OFFSET( FILE_NAME, FileName );
INDEX_CONTEXT OtherContext;
PFCB AcquiredFcb = NULL;
BOOLEAN VcbAcquired = FALSE; BOOLEAN CcbAcquired = FALSE;
BOOLEAN ScbAcquired = FALSE; BOOLEAN FirstQuery = FALSE;
ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_IRP( Irp ); ASSERT_VCB( Vcb ); ASSERT_CCB( Ccb ); ASSERT_SCB( Scb );
PAGED_CODE();
//
// Get the current Stack location
//
IrpSp = IoGetCurrentIrpStackLocation( Irp );
DebugTrace( +1, Dbg, ("NtfsQueryDirectory...\n") ); DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) ); DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) ); DebugTrace( 0, Dbg, (" ->Length = %08lx\n", IrpSp->Parameters.QueryDirectory.Length) ); DebugTrace( 0, Dbg, (" ->FileName = %08lx\n", IrpSp->Parameters.QueryDirectory.FileName) ); DebugTrace( 0, Dbg, (" ->FileInformationClass = %08lx\n", IrpSp->Parameters.QueryDirectory.FileInformationClass) ); DebugTrace( 0, Dbg, (" ->FileIndex = %08lx\n", IrpSp->Parameters.QueryDirectory.FileIndex) ); DebugTrace( 0, Dbg, (" ->SystemBuffer = %08lx\n", Irp->AssociatedIrp.SystemBuffer) ); DebugTrace( 0, Dbg, (" ->RestartScan = %08lx\n", FlagOn(IrpSp->Flags, SL_RESTART_SCAN)) ); DebugTrace( 0, Dbg, (" ->ReturnSingleEntry = %08lx\n", FlagOn(IrpSp->Flags, SL_RETURN_SINGLE_ENTRY)) ); DebugTrace( 0, Dbg, (" ->IndexSpecified = %08lx\n", FlagOn(IrpSp->Flags, SL_INDEX_SPECIFIED)) ); DebugTrace( 0, Dbg, ("Vcb = %08lx\n", Vcb) ); DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) ); DebugTrace( 0, Dbg, ("Ccb = %08lx\n", Ccb) );
#if DBG
//
// Enable debug port displays when certain enumeration strings are given
//
#if NTFSPOOLCHECK
if (IrpSp->Parameters.QueryDirectory.FileName != NULL) { if (IrpSp->Parameters.QueryDirectory.FileName->Length >= 10 && RtlEqualMemory( IrpSp->Parameters.QueryDirectory.FileName->Buffer, L"$HEAP", 10 )) {
NtfsDebugHeapDump( (PUNICODE_STRING) IrpSp->Parameters.QueryDirectory.FileName );
} } #endif // NTFSPOOLCHECK
#endif // DBG
//
// Because we probably need to do the I/O anyway we'll reject any request
// right now that cannot wait for I/O. We do not want to abort after
// processing a few index entries.
//
if (!FlagOn(IrpContext->State, IRP_CONTEXT_STATE_WAIT)) {
DebugTrace( 0, Dbg, ("Automatically enqueue Irp to Fsp\n") );
Status = NtfsPostRequest( IrpContext, Irp );
DebugTrace( -1, Dbg, ("NtfsQueryDirectory -> %08lx\n", Status) ); return Status; }
//
// Reference our input parameters to make things easier
//
UserBufferLength = IrpSp->Parameters.QueryDirectory.Length;
FileInformationClass = IrpSp->Parameters.QueryDirectory.FileInformationClass; FileIndex = IrpSp->Parameters.QueryDirectory.FileIndex;
//
// Look in the Ccb to see the type of search.
//
IgnoreCase = BooleanFlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE );
RestartScan = BooleanFlagOn(IrpSp->Flags, SL_RESTART_SCAN); ReturnSingleEntry = BooleanFlagOn(IrpSp->Flags, SL_RETURN_SINGLE_ENTRY); IndexSpecified = BooleanFlagOn(IrpSp->Flags, SL_INDEX_SPECIFIED);
//
// Determine the size of the constant part of the structure.
//
switch (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 FileIdFullDirectoryInformation:
BaseLength = FIELD_OFFSET( FILE_ID_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;
case FileIdBothDirectoryInformation:
BaseLength = FIELD_OFFSET( FILE_ID_BOTH_DIR_INFORMATION, FileName[0] ); break;
default:
Status = STATUS_INVALID_INFO_CLASS; NtfsCompleteRequest( IrpContext, Irp, Status ); DebugTrace( -1, Dbg, ("NtfsQueryDirectory -> %08lx\n", Status) ); return Status; }
NtfsInitializeIndexContext( &OtherContext );
//
// Use a try-finally to facilitate cleanup.
//
try {
//
// We only allow one active request in this handle at a time. If this is
// not a synchronous request then wait on the handle.
//
if (!FlagOn( IrpSp->FileObject->Flags, FO_SYNCHRONOUS_IO )) {
EOF_WAIT_BLOCK WaitBlock; NtfsAcquireIndexCcb( Scb, Ccb, &WaitBlock ); CcbAcquired = TRUE; }
//
// We have to create a File Name string for querying if there is either
// one specified in this request, or we do not already have a value
// in the Ccb. If we already have one then we will ignore the input
// name in this case unless the INDEX_SPECIFIED bit is set.
//
if ((Ccb->QueryBuffer == NULL) || ((IrpSp->Parameters.QueryDirectory.FileName != NULL) && IndexSpecified)) {
//
// Now, if the input string is NULL, we have to create the default
// string "*".
//
if (IrpSp->Parameters.QueryDirectory.FileName == NULL) {
FileNameLength = SizeOfFileName + sizeof(WCHAR); FileNameBuffer = NtfsAllocatePool(PagedPool, FileNameLength );
//
// Initialize it.
//
FileNameBuffer->ParentDirectory = Scb->Fcb->FileReference; FileNameBuffer->FileNameLength = 1; FileNameBuffer->Flags = 0; FileNameBuffer->FileName[0] = '*';
//
// We know we have an input file name, and we may or may not already
// have one in the Ccb. Allocate space for it, initialize it, and
// set up to deallocate on the way out if we already have a pattern
// in the Ccb.
//
} else {
UniFileName = (PUNICODE_STRING) IrpSp->Parameters.QueryDirectory.FileName;
if (!NtfsIsFileNameValid(UniFileName, TRUE)) {
if (Ccb->QueryBuffer == NULL || UniFileName->Length > 4 || UniFileName->Length == 0 || UniFileName->Buffer[0] != L'.' || (UniFileName->Length == 4 && UniFileName->Buffer[1] != L'.')) {
try_return( Status = STATUS_OBJECT_NAME_INVALID ); } }
FileNameLength = (USHORT)IrpSp->Parameters.QueryDirectory.FileName->Length;
FileNameBuffer = NtfsAllocatePool(PagedPool, SizeOfFileName + FileNameLength );
RtlCopyMemory( FileNameBuffer->FileName, UniFileName->Buffer, FileNameLength );
FileNameLength += SizeOfFileName;
FileNameBuffer->ParentDirectory = Scb->Fcb->FileReference; FileNameBuffer->FileNameLength = (UCHAR)((FileNameLength - SizeOfFileName) / sizeof( WCHAR )); FileNameBuffer->Flags = 0; }
//
// If we already have a query buffer, deallocate this on the way
// out.
//
if (Ccb->QueryBuffer != NULL) {
//
// If we have a name to resume from then override the restart
// scan boolean.
//
if ((UnwindFileNameBuffer = FileNameBuffer) != NULL) {
RestartScan = FALSE; }
//
// Otherwise, store this one in the Ccb.
//
} else {
UNICODE_STRING Expression;
Ccb->QueryBuffer = (PVOID)FileNameBuffer; Ccb->QueryLength = (USHORT)FileNameLength; FirstQuery = TRUE;
//
// If the search expression contains a wild card then remember this in
// the Ccb.
//
Expression.MaximumLength = Expression.Length = FileNameBuffer->FileNameLength * sizeof( WCHAR ); Expression.Buffer = FileNameBuffer->FileName;
//
// When we establish the search pattern, we must also establish
// whether the user wants to see "." and "..". This code does
// not necessarily have to be perfect (he said), but should be
// good enough to catch the common cases. Dos does not have
// perfect semantics for these cases, and the following determination
// will mimic what FastFat does exactly.
//
if (Scb != Vcb->RootIndexScb) { static UNICODE_STRING DotString = CONSTANT_UNICODE_STRING( L"." );
if (FsRtlDoesNameContainWildCards(&Expression)) {
if (FsRtlIsNameInExpression( &Expression, &DotString, FALSE, NULL )) {
SetFlag( Ccb->Flags, CCB_FLAG_RETURN_DOT | CCB_FLAG_RETURN_DOTDOT ); } } else { if (NtfsAreNamesEqual( Vcb->UpcaseTable, &Expression, &DotString, FALSE )) {
SetFlag( Ccb->Flags, CCB_FLAG_RETURN_DOT | CCB_FLAG_RETURN_DOTDOT ); } } } }
//
// Otherwise we are just restarting the query from the Ccb.
//
} else {
FileNameBuffer = (PFILE_NAME)Ccb->QueryBuffer; FileNameLength = Ccb->QueryLength; }
Irp->IoStatus.Information = 0;
//
// Use a try-except to handle errors accessing the user buffer.
//
try {
ULONG BytesToCopy;
FCB_TABLE_ELEMENT Key; PFCB_TABLE_ELEMENT Entry;
BOOLEAN MatchAll = FALSE;
//
// See if we are supposed to try to acquire an Fcb on this
// resume.
//
if (Ccb->FcbToAcquire.LongValue != 0) {
//
// First we need to acquire the Vcb shared, since we will
// acquire two Fcbs.
//
NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE ); VcbAcquired = TRUE;
//
// Now look up the Fcb, and if it is there, reference it
// and remember it.
//
Key.FileReference = Ccb->FcbToAcquire.FileReference; NtfsAcquireFcbTable( IrpContext, Vcb ); Entry = RtlLookupElementGenericTable( &Vcb->FcbTable, &Key ); if (Entry != NULL) { AcquiredFcb = Entry->Fcb; AcquiredFcb->ReferenceCount += 1; } NtfsReleaseFcbTable( IrpContext, Vcb );
//
// Now that it cannot go anywhere, acquire it.
//
if (AcquiredFcb != NULL) { NtfsAcquireSharedFcb( IrpContext, AcquiredFcb, NULL, ACQUIRE_NO_DELETE_CHECK ); }
//
// Now that we actually acquired it, we may as well clear this
// field.
//
Ccb->FcbToAcquire.LongValue = 0; }
//
// Acquire shared access to the Scb.
//
NtfsAcquireSharedScb( IrpContext, Scb ); ScbAcquired = TRUE;
//
// Now that we have both files acquired, we can free the Vcb.
//
if (VcbAcquired) { NtfsReleaseVcb( IrpContext, Vcb ); VcbAcquired = FALSE; }
//
// If the volume is no longer mounted, we should fail this
// request. Since we have the Scb shared now, we know that
// a dismount request can't sneak in.
//
if (FlagOn( Scb->ScbState, SCB_STATE_VOLUME_DISMOUNTED )) {
try_return( Status = STATUS_VOLUME_DISMOUNTED ); }
//
// If we are in the Fsp now because we had to wait earlier,
// we must map the user buffer, otherwise we can use the
// user's buffer directly.
//
Buffer = NtfsMapUserBuffer( Irp );
//
// Check if this is the first call to query directory for this file
// object. It is the first call if the enumeration context field of
// the ccb is null. Also check if we are to restart the scan.
//
if (FirstQuery || RestartScan) {
CallRestart = TRUE; NextFlag = FALSE;
//
// On first/restarted scan, note that we have not returned either
// of these guys.
//
ClearFlag( Ccb->Flags, CCB_FLAG_DOT_RETURNED | CCB_FLAG_DOTDOT_RETURNED );
//
// Otherwise check to see if we were given a file name to restart from
//
} else if (UnwindFileNameBuffer != NULL) {
CallRestart = TRUE; NextFlag = TRUE;
//
// The guy could actually be asking to return to one of the dot
// file positions, so we must handle that correctly.
//
if ((FileNameBuffer->FileNameLength <= 2) && (FileNameBuffer->FileName[0] == L'.')) {
if (FileNameBuffer->FileNameLength == 1) {
//
// He wants to resume after ".", so we set to return
// ".." again, and change the temporary pattern to
// rewind our context to the front.
//
ClearFlag( Ccb->Flags, CCB_FLAG_DOTDOT_RETURNED ); SetFlag( Ccb->Flags, CCB_FLAG_DOT_RETURNED );
FileNameBuffer->FileName[0] = L'*'; NextFlag = FALSE;
} else if (FileNameBuffer->FileName[1] == L'.') {
//
// He wants to resume after "..", so we the change
// the temporary pattern to rewind our context to the
// front.
//
SetFlag( Ccb->Flags, CCB_FLAG_DOT_RETURNED | CCB_FLAG_DOTDOT_RETURNED ); FileNameBuffer->FileName[0] = FileNameBuffer->FileName[1] = L'*'; NextFlag = FALSE; }
//
// Always return the entry after the user's file name.
//
} else {
SetFlag( Ccb->Flags, CCB_FLAG_DOT_RETURNED | CCB_FLAG_DOTDOT_RETURNED ); }
//
// Otherwise we're simply continuing a previous enumeration from
// where we last left off. And we always leave off one beyond the
// last entry we returned.
//
} else {
CallRestart = FALSE; NextFlag = FALSE; }
//
// At this point we are about to enter our query loop. We have
// already decided if we need to call restart or continue when we
// go after an index entry. The variables LastEntry and NextEntry are
// used to index into the user buffer. LastEntry is the last entry
// we added to the user buffer, and NextEntry is the current
// one we're working on.
//
LastEntry = 0; NextEntry = 0;
//
// Remember if we are matching everything by checking these two common
// cases.
//
MatchAll = (FileNameBuffer->FileName[0] == L'*')
&&
((FileNameBuffer->FileNameLength == 1) ||
((FileNameBuffer->FileNameLength == 3) && (FileNameBuffer->FileName[1] == L'.') && (FileNameBuffer->FileName[2] == L'*')));
while (TRUE) {
PINDEX_ENTRY IndexEntry; PFILE_NAME NtfsFileName; PDUPLICATED_INFORMATION DupInfo; PFILE_NAME DosFileName; FILE_REFERENCE FileId;
ULONG BytesRemainingInBuffer; ULONG FoundFileNameLength;
struct {
FILE_NAME FileName; WCHAR LastChar; } DotDotName;
BOOLEAN SynchronizationError;
DebugTrace( 0, Dbg, ("Top of Loop\n") ); DebugTrace( 0, Dbg, ("LastEntry = %08lx\n", LastEntry) ); DebugTrace( 0, Dbg, ("NextEntry = %08lx\n", NextEntry) );
//
// If a previous pass through the loop acquired the Fcb table then
// release it now. We don't want to be holding it if we take a fault
// on the directory stream. Otherwise we can get into a circular
// deadlock if we need to acquire the mutex for this file while
// holding the mutex for the Fcb Table.
//
if (FlagOn( OtherContext.Flags, INDX_CTX_FLAG_FCB_TABLE_ACQUIRED )) { NtfsReleaseFcbTable( IrpContext, IrpContext->Vcb ); ClearFlag( OtherContext.Flags, INDX_CTX_FLAG_FCB_TABLE_ACQUIRED ); } DosFileName = NULL;
//
// Lookup the next index entry. Check if we need to do the lookup
// by calling restart or continue. If we do need to call restart
// check to see if we have a real AnsiFileName. And set ourselves
// up for subsequent iternations through the loop
//
if (CallRestart) {
GotEntry = NtfsRestartIndexEnumeration( IrpContext, Ccb, Scb, (PVOID)FileNameBuffer, IgnoreCase, NextFlag, &IndexEntry, AcquiredFcb ); CallRestart = FALSE;
} else {
GotEntry = NtfsContinueIndexEnumeration( IrpContext, Ccb, Scb, NextFlag, &IndexEntry ); }
//
// Check to see if we should quit the loop because we are only
// returning a single entry. We actually want to spin around
// the loop top twice so that our enumeration has has us left off
// at the last entry we didn't return. We know this is now our
// second time though the loop if NextEntry is not zero.
//
if ((ReturnSingleEntry) && (NextEntry != 0)) {
break; }
//
// Assume we won't be returning the file id.
//
*((PLONGLONG) &FileId) = 0;
//
// Assume we are to return one of the names "." or "..".
// We should not search farther in the index so we set
// NextFlag to FALSE.
//
RtlZeroMemory( &DotDotName, sizeof(DotDotName) ); NtfsFileName = &DotDotName.FileName; NtfsFileName->Flags = FILE_NAME_NTFS | FILE_NAME_DOS; NtfsFileName->FileName[0] = NtfsFileName->FileName[1] = L'.'; DupInfo = &Scb->Fcb->Info; NextFlag = FALSE;
//
// Handle "." first.
//
if (!FlagOn(Ccb->Flags, CCB_FLAG_DOT_RETURNED) && FlagOn(Ccb->Flags, CCB_FLAG_RETURN_DOT)) {
FoundFileNameLength = 2; GotEntry = TRUE; SetFlag( Ccb->Flags, CCB_FLAG_DOT_RETURNED );
FileId = Scb->Fcb->FileReference;
//
// Handle ".." next.
//
} else if (!FlagOn(Ccb->Flags, CCB_FLAG_DOTDOT_RETURNED) && FlagOn(Ccb->Flags, CCB_FLAG_RETURN_DOTDOT)) {
FoundFileNameLength = 4; GotEntry = TRUE; SetFlag( Ccb->Flags, CCB_FLAG_DOTDOT_RETURNED );
} else {
//
// Compute the length of the name we found.
//
if (GotEntry) {
FileId = IndexEntry->FileReference;
NtfsFileName = (PFILE_NAME)(IndexEntry + 1);
FoundFileNameLength = NtfsFileName->FileNameLength * sizeof( WCHAR );
//
// Verify the index entry is valid.
//
if (FoundFileNameLength != IndexEntry->AttributeLength - SizeOfFileName) {
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb ); }
DupInfo = &NtfsFileName->Info; NextFlag = TRUE;
//
// Don't return any system files.
//
if (NtfsSegmentNumber( &IndexEntry->FileReference ) < FIRST_USER_FILE_NUMBER && NtfsProtectSystemFiles) {
continue; }
} }
//
// Now check to see if we actually got another index entry. If
// we didn't then we also need to check if we never got any
// or if we just ran out. If we just ran out then we break out
// of the main loop and finish the Irp after the loop
//
if (!GotEntry) {
DebugTrace( 0, Dbg, ("GotEntry is FALSE\n") );
if (NextEntry == 0) {
if (FirstQuery) {
try_return( Status = STATUS_NO_SUCH_FILE ); }
try_return( Status = STATUS_NO_MORE_FILES ); }
break; }
//
// Cleanup and reinitialize context from previous loop.
//
NtfsReinitializeIndexContext( IrpContext, &OtherContext );
//
// We may have matched a Dos-Only name. If so we will save
// it and go get the Ntfs name.
//
if (!FlagOn(NtfsFileName->Flags, FILE_NAME_NTFS) && FlagOn(NtfsFileName->Flags, FILE_NAME_DOS)) {
//
// If we are returning everything, then we can skip
// the Dos-Only names and save some cycles.
//
if (MatchAll) { continue; }
DosFileName = NtfsFileName;
NtfsFileName = NtfsRetrieveOtherFileName( IrpContext, Ccb, Scb, IndexEntry, &OtherContext, AcquiredFcb, &SynchronizationError );
//
// If we got an Ntfs name, then we need to list this entry now
// iff the Ntfs name is not in the expression. If the Ntfs
// name is in the expression, we can just continue and print
// this name when we encounter it by the Ntfs name.
//
if (NtfsFileName != NULL) {
if (FlagOn(Ccb->Flags, CCB_FLAG_WILDCARD_IN_EXPRESSION)) {
if (NtfsFileNameIsInExpression( Vcb->UpcaseTable, (PFILE_NAME)Ccb->QueryBuffer, NtfsFileName, IgnoreCase )) {
continue; }
} else {
if (NtfsFileNameIsEqual( Vcb->UpcaseTable, (PFILE_NAME)Ccb->QueryBuffer, NtfsFileName, IgnoreCase )) {
continue; } }
FoundFileNameLength = NtfsFileName->FileNameLength * sizeof( WCHAR );
} else if (SynchronizationError) {
if (Irp->IoStatus.Information != 0) { try_return( Status = STATUS_SUCCESS ); } else { NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL ); }
} else {
continue; } }
//
// 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.
//
BytesRemainingInBuffer = UserBufferLength - NextEntry;
if ( (NextEntry != 0) && ( (BaseLength + FoundFileNameLength > BytesRemainingInBuffer) || (UserBufferLength < NextEntry) ) ) {
DebugTrace( 0, Dbg, ("Next entry won't fit\n") );
try_return( Status = STATUS_SUCCESS ); }
ASSERT( BytesRemainingInBuffer >= BaseLength );
//
// Zero the base part of the structure.
//
AccessingUserBuffer = TRUE; RtlZeroMemory( &Buffer[NextEntry], BaseLength ); AccessingUserBuffer = FALSE;
//
// 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 (FileInformationClass) {
case FileIdFullDirectoryInformation:
((PFILE_ID_FULL_DIR_INFORMATION)&Buffer[NextEntry])->FileId.QuadPart = *((PLONGLONG) &FileId); goto FillFullDirectoryInformation;
case FileIdBothDirectoryInformation:
((PFILE_ID_BOTH_DIR_INFORMATION)&Buffer[NextEntry])->FileId.QuadPart = *((PLONGLONG) &FileId); // Fall thru
case FileBothDirectoryInformation:
BothDirInfo = (PFILE_BOTH_DIR_INFORMATION)&Buffer[NextEntry];
//
// If this is not also a Dos name, and the Ntfs flag is set
// (meaning there is a separate Dos name), then call the
// routine to get the short name, if we do not already have
// it from above.
//
if (!FlagOn(NtfsFileName->Flags, FILE_NAME_DOS) && FlagOn(NtfsFileName->Flags, FILE_NAME_NTFS)) {
if (DosFileName == NULL) {
DosFileName = NtfsRetrieveOtherFileName( IrpContext, Ccb, Scb, IndexEntry, &OtherContext, AcquiredFcb, &SynchronizationError ); }
if (DosFileName != NULL) {
AccessingUserBuffer = TRUE; BothDirInfo->ShortNameLength = DosFileName->FileNameLength * sizeof( WCHAR ); RtlCopyMemory( BothDirInfo->ShortName, DosFileName->FileName, BothDirInfo->ShortNameLength ); } else if (SynchronizationError) {
if (Irp->IoStatus.Information != 0) { try_return( Status = STATUS_SUCCESS ); } else { NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL ); } } }
// Fallthru
case FileFullDirectoryInformation:
FillFullDirectoryInformation:
DebugTrace( 0, Dbg, ("Getting file full Unicode directory information\n") );
FullDirInfo = (PFILE_FULL_DIR_INFORMATION)&Buffer[NextEntry];
//
// EAs and reparse points cannot both be in a file at the same
// time. We return different information for each case.
//
AccessingUserBuffer = TRUE; if (FlagOn( DupInfo->FileAttributes, FILE_ATTRIBUTE_REPARSE_POINT)) {
FullDirInfo->EaSize = DupInfo->ReparsePointTag; } else {
FullDirInfo->EaSize = DupInfo->PackedEaSize;
//
// Add 4 bytes for the CbListHeader.
//
if (DupInfo->PackedEaSize != 0) {
FullDirInfo->EaSize += 4; } }
// Fallthru
case FileDirectoryInformation:
DebugTrace( 0, Dbg, ("Getting file Unicode directory information\n") );
DirInfo = (PFILE_DIRECTORY_INFORMATION)&Buffer[NextEntry];
AccessingUserBuffer = TRUE; DirInfo->CreationTime.QuadPart = DupInfo->CreationTime; DirInfo->LastAccessTime.QuadPart = DupInfo->LastAccessTime; DirInfo->LastWriteTime.QuadPart = DupInfo->LastModificationTime; DirInfo->ChangeTime.QuadPart = DupInfo->LastChangeTime;
DirInfo->FileAttributes = DupInfo->FileAttributes & FILE_ATTRIBUTE_VALID_FLAGS;
if (IsDirectory( DupInfo ) || IsViewIndex( DupInfo )) { DirInfo->FileAttributes |= FILE_ATTRIBUTE_DIRECTORY; } if (DirInfo->FileAttributes == 0) { DirInfo->FileAttributes = FILE_ATTRIBUTE_NORMAL; }
DirInfo->FileNameLength = FoundFileNameLength;
DirInfo->EndOfFile.QuadPart = DupInfo->FileSize; DirInfo->AllocationSize.QuadPart = DupInfo->AllocatedLength;
break;
case FileNamesInformation:
DebugTrace( 0, Dbg, ("Getting file Unicode names information\n") );
AccessingUserBuffer = TRUE; NamesInfo = (PFILE_NAMES_INFORMATION)&Buffer[NextEntry];
NamesInfo->FileNameLength = FoundFileNameLength;
break;
default:
try_return( Status = STATUS_INVALID_INFO_CLASS ); }
//
// Compute how many bytes we can copy. This should only be less
// than the file name length if we are only returning a single
// entry.
//
if (BytesRemainingInBuffer >= BaseLength + FoundFileNameLength) {
BytesToCopy = FoundFileNameLength;
} else {
BytesToCopy = BytesRemainingInBuffer - BaseLength;
Status = STATUS_BUFFER_OVERFLOW; }
ASSERT( AccessingUserBuffer ); RtlCopyMemory( &Buffer[NextEntry + BaseLength], NtfsFileName->FileName, BytesToCopy );
//
// If/when we actually emit a record for the Fcb acquired,
// then we can release that file now. Note we do not just
// do it on the first time through the loop, because some of
// our callers back up a bit when they give us the resume point.
//
if ((AcquiredFcb != NULL) && (DupInfo != &Scb->Fcb->Info) && NtfsEqualMftRef(&IndexEntry->FileReference, &Ccb->FcbToAcquire.FileReference)) {
//
// Now look up the Fcb, and if it is there, reference it
// and remember it.
//
// It is pretty inconvenient here to see if the ReferenceCount
// goes to zero and try to do a TearDown, we do not have the
// right resources. Note that the window is small, and the Fcb
// will go away if either someone opens the file again, someone
// tries to delete the directory, or someone tries to lock the
// volume.
//
NtfsAcquireFcbTable( IrpContext, Vcb ); AcquiredFcb->ReferenceCount -= 1; NtfsReleaseFcbTable( IrpContext, Vcb ); NtfsReleaseFcb( IrpContext, AcquiredFcb ); AcquiredFcb = NULL; }
//
// Set up the previous next entry offset
//
*((PULONG)(&Buffer[LastEntry])) = NextEntry - LastEntry; AccessingUserBuffer = FALSE;
//
// And indicate how much of the user buffer we have currently
// used up. We must compute this value before we long align
// ourselves for the next entry. This is the point where we
// quad-align the length of the previous entry.
//
Irp->IoStatus.Information = QuadAlign( Irp->IoStatus.Information) + BaseLength + BytesToCopy;
//
// If we weren't able to copy the whole name, then we bail here.
//
if ( !NT_SUCCESS( Status ) ) {
try_return( Status ); }
//
// Set ourselves up for the next iteration
//
LastEntry = NextEntry; NextEntry += (ULONG)QuadAlign( BaseLength + BytesToCopy ); }
//
// At this point we've successfully filled up some of the buffer so
// now is the time to set our status to success.
//
Status = STATUS_SUCCESS;
} except( (!FsRtlIsNtstatusExpected( GetExceptionCode() ) && AccessingUserBuffer) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) {
NtfsRaiseStatus( IrpContext, STATUS_INVALID_USER_BUFFER, NULL, NULL ); }
try_exit:
//
// Abort transaction on error by raising.
//
NtfsCleanupTransaction( IrpContext, Status, FALSE );
//
// Set the last access flag in the Fcb if the caller
// didn't set it explicitly.
//
if (!FlagOn( Ccb->Flags, CCB_FLAG_USER_SET_LAST_ACCESS_TIME ) && !FlagOn( NtfsData.Flags, NTFS_FLAGS_DISABLE_LAST_ACCESS )) {
NtfsGetCurrentTime( IrpContext, Scb->Fcb->CurrentLastAccess ); SetFlag( Scb->Fcb->InfoFlags, FCB_INFO_UPDATE_LAST_ACCESS ); }
} finally {
DebugUnwind( NtfsQueryDirectory );
if (VcbAcquired) { NtfsReleaseVcb( IrpContext, Vcb ); }
NtfsCleanupIndexContext( IrpContext, &OtherContext );
if (AcquiredFcb != NULL) {
//
// Now look up the Fcb, and if it is there, reference it
// and remember it.
//
// It is pretty inconvenient here to see if the ReferenceCount
// goes to zero and try to do a TearDown, we do not have the
// right resources. Note that the window is small, and the Fcb
// will go away if either someone opens the file again, someone
// tries to delete the directory, or someone tries to lock the
// volume.
//
NtfsAcquireFcbTable( IrpContext, Vcb ); AcquiredFcb->ReferenceCount -= 1; NtfsReleaseFcbTable( IrpContext, Vcb ); NtfsReleaseFcb( IrpContext, AcquiredFcb ); }
if (ScbAcquired) { NtfsReleaseScb( IrpContext, Scb ); }
NtfsCleanupAfterEnumeration( IrpContext, Ccb );
if (CcbAcquired) {
NtfsReleaseIndexCcb( Scb, Ccb ); }
if (!AbnormalTermination()) {
NtfsCompleteRequest( IrpContext, Irp, Status ); }
if (UnwindFileNameBuffer != NULL) {
NtfsFreePool(UnwindFileNameBuffer); } }
//
// And return to our caller
//
DebugTrace( -1, Dbg, ("NtfsQueryDirectory -> %08lx\n", Status) );
return Status; }
//
// Local Support Routine
//
NTSTATUS NtfsNotifyChangeDirectory ( IN PIRP_CONTEXT IrpContext, IN PIRP Irp, IN PVCB Vcb, IN PSCB Scb, IN PCCB Ccb )
/*++
Routine Description:
This routine performs the notify change directory operation. It is responsible for either completing or enqueuing the input Irp.
Arguments:
Irp - Supplies the Irp to process
Vcb - Supplies its Vcb
Scb - Supplies its Scb
Ccb - Supplies its Ccb
Return Value:
NTSTATUS - The return status for the operation
--*/
{ NTSTATUS Status; PIO_STACK_LOCATION IrpSp;
ULONG CompletionFilter; BOOLEAN WatchTree; BOOLEAN ViewIndex;
PSECURITY_SUBJECT_CONTEXT SubjectContext = NULL; BOOLEAN FreeSubjectContext = FALSE;
PCHECK_FOR_TRAVERSE_ACCESS CallBack = NULL;
ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_IRP( Irp ); ASSERT_VCB( Vcb ); ASSERT_CCB( Ccb ); ASSERT_SCB( Scb );
PAGED_CODE();
//
// Get the current Stack location
//
IrpSp = IoGetCurrentIrpStackLocation( Irp );
DebugTrace( +1, Dbg, ("NtfsNotifyChangeDirectory...\n") ); DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) ); DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) ); DebugTrace( 0, Dbg, (" ->CompletionFilter = %08lx\n", IrpSp->Parameters.NotifyDirectory.CompletionFilter) ); DebugTrace( 0, Dbg, (" ->WatchTree = %08lx\n", FlagOn( IrpSp->Flags, SL_WATCH_TREE )) ); DebugTrace( 0, Dbg, ("Vcb = %08lx\n", Vcb) ); DebugTrace( 0, Dbg, ("Ccb = %08lx\n", Ccb) ); DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
//
// Reference our input parameter to make things easier
//
CompletionFilter = IrpSp->Parameters.NotifyDirectory.CompletionFilter; WatchTree = BooleanFlagOn( IrpSp->Flags, SL_WATCH_TREE );
//
// Always set the wait bit in the IrpContext so the initial wait can't fail.
//
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_WAIT );
//
// We will only acquire the Vcb to perform the dirnotify task. The dirnotify
// package will provide synchronization between this operation and cleanup.
// We need the Vcb to synchronize with any rename or link operations underway.
//
NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
try {
//
// If the Link count is zero on this Fcb then complete this request
// with STATUS_DELETE_PENDING.
//
if (Scb->Fcb->LinkCount == 0) {
NtfsRaiseStatus( IrpContext, STATUS_DELETE_PENDING, NULL, NULL ); }
ViewIndex = BooleanFlagOn( Scb->ScbState, SCB_STATE_VIEW_INDEX );
//
// If we need to verify traverse access for this caller then allocate and
// capture the subject context to pass to the dir notify package. That
// package will be responsible for deallocating it.
//
if (FlagOn( Ccb->Flags, CCB_FLAG_TRAVERSE_CHECK )) {
//
// We only use the subject context for directories
//
if (!ViewIndex) { SubjectContext = NtfsAllocatePool( PagedPool, sizeof( SECURITY_SUBJECT_CONTEXT ));
FreeSubjectContext = TRUE; SeCaptureSubjectContext( SubjectContext );
FreeSubjectContext = FALSE; } CallBack = NtfsNotifyTraverseCheck; }
//
// 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.
//
if (ViewIndex) {
//
// View indices use different values for the overloaded inputs
// to FsRtlNotifyFilterChangeDirectory.
//
FsRtlNotifyFilterChangeDirectory( Vcb->NotifySync, &Vcb->ViewIndexNotifyList, Ccb, NULL, WatchTree, FALSE, CompletionFilter, Irp, CallBack, (PSECURITY_SUBJECT_CONTEXT) Scb->Fcb, NULL ); } else {
FsRtlNotifyFilterChangeDirectory( Vcb->NotifySync, &Vcb->DirNotifyList, Ccb, (PSTRING) &Scb->ScbType.Index.NormalizedName, WatchTree, FALSE, CompletionFilter, Irp, CallBack, SubjectContext, NULL ); }
Status = STATUS_PENDING;
if (!FlagOn( Ccb->Flags, CCB_FLAG_DIR_NOTIFY )) {
SetFlag( Ccb->Flags, CCB_FLAG_DIR_NOTIFY );
if (ViewIndex) {
InterlockedIncrement( &Vcb->ViewIndexNotifyCount );
} else {
InterlockedIncrement( &Vcb->NotifyCount ); } }
} finally {
DebugUnwind( NtfsNotifyChangeDirectory );
NtfsReleaseVcb( IrpContext, Vcb );
//
// Since the dir notify package is holding the Irp, we discard the
// the IrpContext.
//
if (!AbnormalTermination()) {
NtfsCompleteRequest( IrpContext, NULL, 0 );
} else if (FreeSubjectContext) {
NtfsFreePool( SubjectContext ); } }
//
// And return to our caller
//
DebugTrace( -1, Dbg, ("NtfsNotifyChangeDirectory -> %08lx\n", Status) );
return Status; }
|