2100 lines
60 KiB
C
2100 lines
60 KiB
C
/*++
|
||
|
||
Copyright (c) 1989-2000 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
DirCtrl.c
|
||
|
||
Abstract:
|
||
|
||
This module implements the File Directory Control routines for Udfs called
|
||
by the Fsd/Fsp dispatch drivers.
|
||
|
||
// @@BEGIN_DDKSPLIT
|
||
|
||
Author:
|
||
|
||
Dan Lovinger [DanLo] 27-Nov-1996
|
||
|
||
Revision History:
|
||
|
||
Tom Jolly [TomJolly] 1-March-2000 UDF 2.01 support
|
||
|
||
// @@END_DDKSPLIT
|
||
|
||
--*/
|
||
|
||
#include "UdfProcs.h"
|
||
|
||
//
|
||
// The Bug check file id for this module
|
||
//
|
||
|
||
#define BugCheckFileId (UDFS_BUG_CHECK_DIRCTRL)
|
||
|
||
//
|
||
// The local debug trace level
|
||
//
|
||
|
||
#define Dbg (UDFS_DEBUG_LEVEL_DIRCTRL)
|
||
|
||
//
|
||
// Local structures
|
||
//
|
||
|
||
//
|
||
// The following is used for the more complete enumeration required in the DirectoryControl path
|
||
// and encapsulates the structures for enumerating both directories and ICBs, as well as converted
|
||
// data from the ICB.
|
||
//
|
||
|
||
typedef struct _COMPOUND_DIR_ENUM_CONTEXT {
|
||
|
||
//
|
||
// Standard enumeration contexts. For this enumeration we walk the directory and lift the
|
||
// associated ICB for each entry.
|
||
//
|
||
|
||
DIR_ENUM_CONTEXT DirContext;
|
||
ICB_SEARCH_CONTEXT IcbContext;
|
||
|
||
//
|
||
// Timestamps converted from the ICB into NT-native form.
|
||
//
|
||
|
||
TIMESTAMP_BUNDLE Timestamps;
|
||
|
||
//
|
||
// File index corresponding to the current position in the enumeration.
|
||
//
|
||
|
||
LARGE_INTEGER FileIndex;
|
||
|
||
} COMPOUND_DIR_ENUM_CONTEXT, *PCOMPOUND_DIR_ENUM_CONTEXT;
|
||
|
||
//
|
||
// Local macros
|
||
//
|
||
|
||
//
|
||
// Constants defining the space of FileIndices for directory enumeration.
|
||
//
|
||
|
||
//
|
||
// The virtual (synthesized) file indices
|
||
//
|
||
|
||
#define UDF_FILE_INDEX_VIRTUAL_SELF 0
|
||
|
||
//
|
||
// The file index where the physical directory entries begin
|
||
//
|
||
|
||
#define UDF_FILE_INDEX_PHYSICAL 1
|
||
|
||
//
|
||
// Provide initialization and cleanup for compound enumeration contexts.
|
||
//
|
||
|
||
INLINE
|
||
VOID
|
||
UdfInitializeCompoundDirContext (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PCOMPOUND_DIR_ENUM_CONTEXT CompoundDirContext
|
||
)
|
||
{
|
||
|
||
UdfInitializeDirContext( IrpContext, &CompoundDirContext->DirContext );
|
||
UdfFastInitializeIcbContext( IrpContext, &CompoundDirContext->IcbContext );
|
||
|
||
RtlZeroMemory( &CompoundDirContext->Timestamps, sizeof( TIMESTAMP_BUNDLE ));
|
||
|
||
CompoundDirContext->FileIndex.QuadPart = 0;
|
||
}
|
||
|
||
INLINE
|
||
VOID
|
||
UdfCleanupCompoundDirContext (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PCOMPOUND_DIR_ENUM_CONTEXT CompoundDirContext
|
||
)
|
||
{
|
||
|
||
UdfCleanupDirContext( IrpContext, &CompoundDirContext->DirContext );
|
||
UdfCleanupIcbContext( IrpContext, &CompoundDirContext->IcbContext );
|
||
}
|
||
|
||
//
|
||
// UDF directories are unsorted (UDF 1.0.1 2.3.5.3) and do not contain self
|
||
// entries. For directory enumeration we must provide a way for a restart to
|
||
// occur at a random entry (SL_INDEX_SPECIFIED), but the key used is only
|
||
// 32bits. Since the directory is unsorted, the filename is unsuitable for
|
||
// quickly finding a restart point (even assuming that it was sorted,
|
||
// discovering a directory entry is still not fast). Additionally, we must
|
||
// synthesize the self-entry. So, here is how we map the space of file
|
||
// indices to directory entries:
|
||
//
|
||
// File Index Directory Entry
|
||
//
|
||
// 0 self ('.')
|
||
// 1 at byte offset 0 in the stream
|
||
// N at byte offset N-1 in the stream
|
||
//
|
||
// The highest 32bit FileIndex returned will be stashed in the Ccb.
|
||
//
|
||
// For FileIndex > 2^32, we will return FileIndex 0 in the query structure.
|
||
// On a restart, we will notice a FileIndex of zero and use the saved high
|
||
// 32bit FileIndex as the starting point for a linear scan to find the named
|
||
// directory entry in the restart request. In this way we only penalize the
|
||
// improbable case of a directory stream > 2^32 bytes.
|
||
//
|
||
// The following inline routines assist with this mapping.
|
||
//
|
||
|
||
INLINE
|
||
LONGLONG
|
||
UdfFileIndexToPhysicalOffset(
|
||
LONGLONG FileIndex
|
||
)
|
||
{
|
||
|
||
return FileIndex - UDF_FILE_INDEX_PHYSICAL;
|
||
}
|
||
|
||
INLINE
|
||
LONGLONG
|
||
UdfPhysicalOffsetToFileIndex(
|
||
LONGLONG PhysicalOffset
|
||
)
|
||
{
|
||
|
||
return PhysicalOffset + UDF_FILE_INDEX_PHYSICAL;
|
||
}
|
||
|
||
INLINE
|
||
BOOLEAN
|
||
UdfIsFileIndexVirtual(
|
||
LONGLONG FileIndex
|
||
)
|
||
{
|
||
|
||
return FileIndex < UDF_FILE_INDEX_PHYSICAL;
|
||
}
|
||
|
||
//
|
||
// Local support routines
|
||
//
|
||
|
||
NTSTATUS
|
||
UdfQueryDirectory (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PIRP Irp,
|
||
IN PIO_STACK_LOCATION IrpSp,
|
||
IN PFCB Fcb,
|
||
IN PCCB Ccb
|
||
);
|
||
|
||
NTSTATUS
|
||
UdfNotifyChangeDirectory (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PIRP Irp,
|
||
IN PIO_STACK_LOCATION IrpSp,
|
||
IN PCCB Ccb
|
||
);
|
||
|
||
NTSTATUS
|
||
UdfInitializeEnumeration (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PIO_STACK_LOCATION IrpSp,
|
||
IN PFCB Fcb,
|
||
IN OUT PCCB Ccb,
|
||
IN OUT PCOMPOUND_DIR_ENUM_CONTEXT CompoundDirContext,
|
||
OUT PBOOLEAN ReturnNextEntry,
|
||
OUT PBOOLEAN ReturnSingleEntry,
|
||
OUT PBOOLEAN InitialQuery
|
||
);
|
||
|
||
BOOLEAN
|
||
UdfEnumerateIndex (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PCCB Ccb,
|
||
IN OUT PCOMPOUND_DIR_ENUM_CONTEXT CompoundDirContext,
|
||
IN BOOLEAN ReturnNextEntry
|
||
);
|
||
|
||
VOID
|
||
UdfLookupFileEntryInEnumeration (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PFCB Fcb,
|
||
IN PCOMPOUND_DIR_ENUM_CONTEXT CompoundDirContext
|
||
);
|
||
|
||
BOOLEAN
|
||
UdfLookupInitialFileIndex (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PFCB Fcb,
|
||
IN PCOMPOUND_DIR_ENUM_CONTEXT CompoundDirContext,
|
||
IN PLONGLONG InitialIndex
|
||
);
|
||
|
||
BOOLEAN
|
||
UdfLookupNextFileIndex (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PFCB Fcb,
|
||
IN PCOMPOUND_DIR_ENUM_CONTEXT CompoundDirContext
|
||
);
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
#pragma alloc_text(PAGE, UdfCommonDirControl)
|
||
#pragma alloc_text(PAGE, UdfEnumerateIndex)
|
||
#pragma alloc_text(PAGE, UdfInitializeEnumeration)
|
||
#pragma alloc_text(PAGE, UdfLookupFileEntryInEnumeration)
|
||
#pragma alloc_text(PAGE, UdfLookupInitialFileIndex)
|
||
#pragma alloc_text(PAGE, UdfLookupNextFileIndex)
|
||
#pragma alloc_text(PAGE, UdfNotifyChangeDirectory)
|
||
#pragma alloc_text(PAGE, UdfQueryDirectory)
|
||
#endif
|
||
|
||
|
||
NTSTATUS
|
||
UdfCommonDirControl (
|
||
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 (UdfDecodeFileObject( IrpSp->FileObject,
|
||
&Fcb,
|
||
&Ccb ) != UserDirectoryOpen) {
|
||
|
||
UdfCompleteRequest( 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 = UdfQueryDirectory( IrpContext, Irp, IrpSp, Fcb, Ccb );
|
||
break;
|
||
|
||
case IRP_MN_NOTIFY_CHANGE_DIRECTORY:
|
||
|
||
Status = UdfNotifyChangeDirectory( IrpContext, Irp, IrpSp, Ccb );
|
||
break;
|
||
|
||
default:
|
||
|
||
UdfCompleteRequest( IrpContext, Irp, STATUS_INVALID_DEVICE_REQUEST );
|
||
Status = STATUS_INVALID_DEVICE_REQUEST;
|
||
break;
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
//
|
||
// Local support routines
|
||
//
|
||
|
||
NTSTATUS
|
||
UdfQueryDirectory (
|
||
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 BytesConverted;
|
||
|
||
LARGE_INTEGER PreviousFileIndex;
|
||
|
||
COMPOUND_DIR_ENUM_CONTEXT CompoundDirContext;
|
||
|
||
PNSR_FID ThisFid;
|
||
PICBFILE ThisFe;
|
||
|
||
BOOLEAN InitialQuery;
|
||
BOOLEAN ReturnNextEntry;
|
||
BOOLEAN ReturnSingleEntry;
|
||
BOOLEAN Found;
|
||
BOOLEAN EasCorrupt;
|
||
|
||
PCHAR UserBuffer;
|
||
ULONG BytesRemainingInBuffer;
|
||
|
||
ULONG BaseLength;
|
||
|
||
PFILE_BOTH_DIR_INFORMATION DirInfo;
|
||
PFILE_NAMES_INFORMATION NamesInfo;
|
||
PFILE_ID_FULL_DIR_INFORMATION IdFullDirInfo;
|
||
PFILE_ID_BOTH_DIR_INFORMATION IdBothDirInfo;
|
||
|
||
PAGED_CODE();
|
||
|
||
DebugTrace(( 0, Dbg, "UdfQueryDirectory\n" ));
|
||
|
||
//
|
||
// 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 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:
|
||
|
||
UdfCompleteRequest( IrpContext, Irp, STATUS_INVALID_INFO_CLASS );
|
||
return STATUS_INVALID_INFO_CLASS;
|
||
}
|
||
|
||
//
|
||
// Get the user buffer.
|
||
//
|
||
|
||
UdfMapUserBuffer( IrpContext, &UserBuffer);
|
||
|
||
//
|
||
// Initialize our search context.
|
||
//
|
||
|
||
UdfInitializeCompoundDirContext( IrpContext, &CompoundDirContext );
|
||
|
||
//
|
||
// Acquire the directory.
|
||
//
|
||
|
||
UdfAcquireFileShared( IrpContext, Fcb );
|
||
|
||
//
|
||
// Use a try-finally to facilitate cleanup.
|
||
//
|
||
|
||
try {
|
||
|
||
//
|
||
// Verify the Fcb is still good.
|
||
//
|
||
|
||
UdfVerifyFcbOperation( 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.
|
||
//
|
||
|
||
Status = UdfInitializeEnumeration( IrpContext,
|
||
IrpSp,
|
||
Fcb,
|
||
Ccb,
|
||
&CompoundDirContext,
|
||
&ReturnNextEntry,
|
||
&ReturnSingleEntry,
|
||
&InitialQuery );
|
||
if (!NT_SUCCESS( Status )) {
|
||
|
||
try_leave( Status );
|
||
}
|
||
|
||
|
||
//
|
||
// 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_leave( 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.
|
||
//
|
||
|
||
PreviousFileIndex = CompoundDirContext.FileIndex;
|
||
|
||
try {
|
||
|
||
Found = UdfEnumerateIndex( IrpContext, Ccb, &CompoundDirContext, ReturnNextEntry );
|
||
}
|
||
except (((0 != NextEntry) &&
|
||
((GetExceptionCode() == STATUS_FILE_CORRUPT_ERROR) ||
|
||
(GetExceptionCode() == STATUS_CRC_ERROR)))
|
||
? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
|
||
|
||
DebugTrace((0, Dbg, "UdfQueryDirectory - Corrupt. Returning buffer so far, setting back enumeration\n"));
|
||
|
||
//
|
||
// We encountered corruption in the directory. We will swallow this
|
||
// error since we have already placed some previous entries in the user
|
||
// buffer, and raise it when we're called again. This is
|
||
// to return as much as possible in the case of corrupt directories,
|
||
// particularly discs which are padded wrong at the end of the dir.
|
||
//
|
||
|
||
ReturnNextEntry = TRUE;
|
||
|
||
//
|
||
// Point to the previous Fid, so that we will advance again to the point
|
||
// of corruption on next call (we only do bounds checking on advance, not
|
||
// when restarting *at* a particular entry).
|
||
//
|
||
|
||
CompoundDirContext.FileIndex = PreviousFileIndex;
|
||
|
||
try_leave( Status = STATUS_SUCCESS);
|
||
}
|
||
|
||
//
|
||
// 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_leave( Status );
|
||
}
|
||
|
||
//
|
||
// Remember the dirent/file entry for the file we just found.
|
||
//
|
||
|
||
ThisFid = CompoundDirContext.DirContext.Fid;
|
||
|
||
//
|
||
// 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.
|
||
//
|
||
|
||
//
|
||
// We can look directly at the dirent that we found.
|
||
//
|
||
|
||
FileNameBytes = CompoundDirContext.DirContext.CaseObjectName.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_leave( 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) > BytesRemainingInBuffer) {
|
||
|
||
//
|
||
// If we already found an entry then just exit.
|
||
//
|
||
|
||
if (NextEntry != 0) {
|
||
|
||
ReturnNextEntry = FALSE;
|
||
try_leave( Status = STATUS_SUCCESS );
|
||
}
|
||
|
||
//
|
||
// Reduce the FileNameBytes to just fit in the buffer.
|
||
//
|
||
|
||
FileNameBytes = BytesRemainingInBuffer - BaseLength;
|
||
|
||
//
|
||
// 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;
|
||
}
|
||
|
||
//
|
||
// Protect access to the user buffer with an exception handler.
|
||
// Since (at our request) IO doesn't buffer these requests, we have
|
||
// to guard against a user messing with the page protection and other
|
||
// such trickery.
|
||
//
|
||
|
||
try {
|
||
|
||
//
|
||
// 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 FileIdBothDirectoryInformation:
|
||
case FileIdFullDirectoryInformation:
|
||
case FileDirectoryInformation:
|
||
|
||
DirInfo = Add2Ptr( UserBuffer, NextEntry, PFILE_BOTH_DIR_INFORMATION );
|
||
|
||
//
|
||
// These information types require we look up the file entry.
|
||
// We will swallow certain corruption errors here in the interest of
|
||
// allowing users to access other objects in the directory. The
|
||
// errors will be reported later if the corrupt object is opened.
|
||
//
|
||
|
||
EasCorrupt = FALSE;
|
||
ThisFe = NULL;
|
||
|
||
try {
|
||
|
||
UdfLookupFileEntryInEnumeration( IrpContext,
|
||
Fcb,
|
||
&CompoundDirContext );
|
||
//
|
||
// Directly reference the file entry we just looked up.
|
||
//
|
||
|
||
ThisFe = (PICBFILE) CompoundDirContext.IcbContext.Active.View;
|
||
|
||
//
|
||
// Now go gather all of the timestamps for this guy.
|
||
//
|
||
|
||
UdfUpdateTimestampsFromIcbContext ( IrpContext,
|
||
&CompoundDirContext.IcbContext,
|
||
&CompoundDirContext.Timestamps );
|
||
}
|
||
except (UdfQueryDirExceptionFilter( GetExceptionInformation())) {
|
||
|
||
//
|
||
// The currently mapped ICB will have been left in IcbContext->Current,
|
||
// and we could look at it and pull out timestamps / filesizes,
|
||
// but it could be complete trash, so we'll just zero these fields
|
||
// in this dir record.
|
||
//
|
||
|
||
DebugTrace(( 0, Dbg, "Ignoring corrupt FE (referenced by FID in dir FCB 0x%p) during dir enum\n", Fcb));
|
||
|
||
//
|
||
// We either failed verification of the core FE fields, or looking
|
||
// up the EAs for timestamps. Either way, the EAs are definitely dead
|
||
// which means the create time is invalid
|
||
//
|
||
|
||
EasCorrupt = TRUE;
|
||
|
||
IrpContext->ExceptionStatus = STATUS_SUCCESS;
|
||
}
|
||
|
||
//
|
||
// If we actually have an FE here that means that the core content verified
|
||
// ok, so pull out the relevant information.
|
||
//
|
||
|
||
if (NULL != ThisFe) {
|
||
|
||
DirInfo->LastWriteTime =
|
||
DirInfo->ChangeTime = CompoundDirContext.Timestamps.ModificationTime;
|
||
|
||
DirInfo->LastAccessTime = CompoundDirContext.Timestamps.AccessTime;
|
||
|
||
if (!EasCorrupt) {
|
||
|
||
DirInfo->CreationTime = CompoundDirContext.Timestamps.CreationTime;
|
||
}
|
||
else {
|
||
|
||
DirInfo->CreationTime = UdfCorruptFileTime;
|
||
}
|
||
|
||
//
|
||
// Set the attributes and sizes separately for directories and
|
||
// files.
|
||
//
|
||
|
||
if (ThisFe->Icbtag.FileType == ICBTAG_FILE_T_DIRECTORY) {
|
||
|
||
DirInfo->EndOfFile.QuadPart = DirInfo->AllocationSize.QuadPart = 0;
|
||
|
||
SetFlag( DirInfo->FileAttributes, FILE_ATTRIBUTE_DIRECTORY );
|
||
|
||
} else {
|
||
|
||
DirInfo->EndOfFile.QuadPart = ThisFe->InfoLength;
|
||
DirInfo->AllocationSize.QuadPart = LlBlockAlign( Fcb->Vcb, ThisFe->InfoLength );
|
||
}
|
||
}
|
||
else {
|
||
|
||
//
|
||
// FE is corrupt. Fill in (irrelevant) valid times.
|
||
//
|
||
|
||
DirInfo->CreationTime =
|
||
DirInfo->ChangeTime =
|
||
DirInfo->LastWriteTime =
|
||
DirInfo->LastAccessTime = UdfCorruptFileTime;
|
||
}
|
||
|
||
//
|
||
// All Cdrom files are readonly. We also copy the existence
|
||
// bit to the hidden attribute, assuming that synthesized FIDs
|
||
// are never hidden.
|
||
//
|
||
|
||
SetFlag( DirInfo->FileAttributes, FILE_ATTRIBUTE_READONLY );
|
||
|
||
if (ThisFid && FlagOn( ThisFid->Flags, NSR_FID_F_HIDDEN )) {
|
||
|
||
SetFlag( DirInfo->FileAttributes, FILE_ATTRIBUTE_HIDDEN );
|
||
}
|
||
|
||
//
|
||
// The file index for real file indices > 2^32 is zero. When asked to
|
||
// restart at an index of zero, we will know to use a stashed starting
|
||
// point to beging to search, by name, for the correct restart point.
|
||
//
|
||
|
||
if (CompoundDirContext.FileIndex.HighPart == 0) {
|
||
|
||
DirInfo->FileIndex = CompoundDirContext.FileIndex.LowPart;
|
||
|
||
} else {
|
||
|
||
DirInfo->FileIndex = 0;
|
||
}
|
||
|
||
DirInfo->FileNameLength = FileNameBytes;
|
||
|
||
break;
|
||
|
||
case FileNamesInformation:
|
||
|
||
NamesInfo = Add2Ptr( UserBuffer, NextEntry, PFILE_NAMES_INFORMATION );
|
||
|
||
if (CompoundDirContext.FileIndex.HighPart == 0) {
|
||
|
||
NamesInfo->FileIndex = CompoundDirContext.FileIndex.LowPart;
|
||
|
||
} else {
|
||
|
||
NamesInfo->FileIndex = 0;
|
||
}
|
||
|
||
NamesInfo->FileNameLength = FileNameBytes;
|
||
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Fill in the FileId
|
||
//
|
||
|
||
switch (IrpSp->Parameters.QueryDirectory.FileInformationClass) {
|
||
|
||
case FileIdBothDirectoryInformation:
|
||
|
||
IdBothDirInfo = Add2Ptr( UserBuffer, NextEntry, PFILE_ID_BOTH_DIR_INFORMATION );
|
||
UdfSetFidFromFidAndFe( IdBothDirInfo->FileId, ThisFid, ThisFe );
|
||
break;
|
||
|
||
case FileIdFullDirectoryInformation:
|
||
|
||
IdFullDirInfo = Add2Ptr( UserBuffer, NextEntry, PFILE_ID_FULL_DIR_INFORMATION );
|
||
UdfSetFidFromFidAndFe( IdFullDirInfo->FileId, ThisFid, ThisFe );
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Now copy as much of the name as possible.
|
||
//
|
||
|
||
if (FileNameBytes != 0) {
|
||
|
||
//
|
||
// This is a Unicode name, we can copy the bytes directly.
|
||
//
|
||
|
||
RtlCopyMemory( Add2Ptr( UserBuffer, NextEntry + BaseLength, PVOID ),
|
||
CompoundDirContext.DirContext.ObjectName.Buffer,
|
||
FileNameBytes );
|
||
}
|
||
|
||
//
|
||
// 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 ||
|
||
IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdBothDirectoryInformation) &&
|
||
FlagOn( CompoundDirContext.DirContext.Flags, DIR_CONTEXT_FLAG_SEEN_NONCONSTANT )) {
|
||
|
||
//
|
||
// If we already have the short name then copy into the user's buffer.
|
||
//
|
||
|
||
if (CompoundDirContext.DirContext.ShortObjectName.Length != 0) {
|
||
|
||
RtlCopyMemory( DirInfo->ShortName,
|
||
CompoundDirContext.DirContext.ShortObjectName.Buffer,
|
||
CompoundDirContext.DirContext.ShortObjectName.Length );
|
||
|
||
DirInfo->ShortNameLength = (CCHAR) CompoundDirContext.DirContext.ShortObjectName.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 (!UdfIs8dot3Name( IrpContext,
|
||
CompoundDirContext.DirContext.ObjectName )) {
|
||
|
||
UNICODE_STRING ShortName;
|
||
|
||
ShortName.Buffer = DirInfo->ShortName;
|
||
ShortName.MaximumLength = BYTE_COUNT_8_DOT_3;
|
||
|
||
UdfGenerate8dot3Name( IrpContext,
|
||
&CompoundDirContext.DirContext.PureObjectName,
|
||
&ShortName );
|
||
|
||
DirInfo->ShortNameLength = (CCHAR) ShortName.Length;
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// 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 );
|
||
|
||
}
|
||
except (!FsRtlIsNtstatusExpected(GetExceptionCode()) ?
|
||
EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
|
||
|
||
//
|
||
// We must have had a problem filling in the user's buffer, so stop
|
||
// and fail this request.
|
||
//
|
||
|
||
Information = 0;
|
||
try_leave( Status = GetExceptionCode());
|
||
}
|
||
}
|
||
|
||
}
|
||
finally {
|
||
|
||
if (!AbnormalTermination() && !NT_ERROR( Status )) {
|
||
|
||
//
|
||
// Update the Ccb to show the current state of the enumeration.
|
||
//
|
||
|
||
UdfLockFcb( IrpContext, Fcb );
|
||
|
||
Ccb->CurrentFileIndex = CompoundDirContext.FileIndex.QuadPart;
|
||
|
||
//
|
||
// Update our notion of a high 32bit file index. We only do this once to avoid the hit
|
||
// of thrashing the Fcb mutex to do this for every entry. If it is ever neccesary to use
|
||
// this information, the difference of a few dozen entries from the optimal pick-up point
|
||
// will be trivial.
|
||
//
|
||
|
||
if (CompoundDirContext.FileIndex.HighPart == 0 &&
|
||
CompoundDirContext.FileIndex.LowPart > Ccb->HighestReturnableFileIndex) {
|
||
|
||
Ccb->HighestReturnableFileIndex = CompoundDirContext.FileIndex.LowPart;
|
||
}
|
||
|
||
//
|
||
// Mark in the CCB whether or not to skip the current entry on next call
|
||
// (if we returned it in the current buffer).
|
||
//
|
||
|
||
ClearFlag( Ccb->Flags, CCB_FLAG_ENUM_RETURN_NEXT );
|
||
|
||
if (ReturnNextEntry) {
|
||
|
||
SetFlag( Ccb->Flags, CCB_FLAG_ENUM_RETURN_NEXT );
|
||
}
|
||
|
||
UdfUnlockFcb( IrpContext, Fcb );
|
||
}
|
||
|
||
//
|
||
// Cleanup our search context.
|
||
//
|
||
|
||
UdfCleanupCompoundDirContext( IrpContext, &CompoundDirContext );
|
||
|
||
//
|
||
// Release the Fcb.
|
||
//
|
||
|
||
UdfReleaseFile( IrpContext, Fcb );
|
||
}
|
||
|
||
DebugTrace(( 0, Dbg, "UdfQueryDirectory -> %x\n", Status ));
|
||
|
||
//
|
||
// Complete the request here.
|
||
//
|
||
|
||
Irp->IoStatus.Information = Information;
|
||
|
||
UdfCompleteRequest( IrpContext, Irp, Status );
|
||
return Status;
|
||
}
|
||
|
||
|
||
//
|
||
// Local support routines
|
||
//
|
||
|
||
NTSTATUS
|
||
UdfNotifyChangeDirectory (
|
||
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 readonly 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.
|
||
//
|
||
|
||
UdfAcquireVcbShared( IrpContext, IrpContext->Vcb, FALSE );
|
||
|
||
//
|
||
// Use a try-finally to facilitate cleanup.
|
||
//
|
||
|
||
try {
|
||
|
||
//
|
||
// Verify the Vcb.
|
||
//
|
||
|
||
UdfVerifyVcb( 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.
|
||
//
|
||
|
||
UdfReleaseVcb( IrpContext, IrpContext->Vcb );
|
||
}
|
||
|
||
//
|
||
// Cleanup the IrpContext.
|
||
//
|
||
|
||
UdfCompleteRequest( IrpContext, NULL, STATUS_SUCCESS );
|
||
|
||
return STATUS_PENDING;
|
||
}
|
||
|
||
|
||
//
|
||
// Local support routine
|
||
//
|
||
|
||
NTSTATUS
|
||
UdfInitializeEnumeration (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PIO_STACK_LOCATION IrpSp,
|
||
IN PFCB Fcb,
|
||
IN OUT PCCB Ccb,
|
||
IN OUT PCOMPOUND_DIR_ENUM_CONTEXT CompoundDirContext,
|
||
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 DirContext 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.
|
||
|
||
CompoundDirContext - Context to use for this enumeration.
|
||
|
||
ReturnNextEntry - Address to store whether we should return the entry at
|
||
the context 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;
|
||
UNICODE_STRING SearchExpression;
|
||
|
||
PUNICODE_STRING RestartName = NULL;
|
||
|
||
ULONG CcbFlags;
|
||
|
||
LONGLONG FileIndex;
|
||
ULONG HighFileIndex;
|
||
BOOLEAN KnownIndex;
|
||
|
||
BOOLEAN Found;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// Check inputs.
|
||
//
|
||
|
||
ASSERT_IRP_CONTEXT( IrpContext );
|
||
ASSERT_FCB_INDEX( Fcb );
|
||
ASSERT_CCB( Ccb );
|
||
|
||
//
|
||
// 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 );
|
||
|
||
SearchExpression.Length =
|
||
SearchExpression.MaximumLength = 0;
|
||
SearchExpression.Buffer = NULL;
|
||
|
||
//
|
||
// Otherwise build the name from the name in the stack location.
|
||
// This involves checking for wild card characters and upcasing the
|
||
// string if this is a case-insensitive search.
|
||
//
|
||
|
||
} else {
|
||
|
||
//
|
||
// The name better have at least one character.
|
||
//
|
||
|
||
if (FileName->Length == 0) {
|
||
|
||
UdfRaiseStatus( IrpContext, STATUS_INVALID_PARAMETER );
|
||
}
|
||
|
||
//
|
||
// Check for wildcards in the separate components.
|
||
//
|
||
|
||
if (FsRtlDoesNameContainWildCards( FileName)) {
|
||
|
||
SetFlag( CcbFlags, CCB_FLAG_ENUM_NAME_EXP_HAS_WILD );
|
||
}
|
||
|
||
//
|
||
// Now create the search expression to store in the Ccb.
|
||
//
|
||
|
||
SearchExpression.Buffer = FsRtlAllocatePoolWithTag( UdfPagedPool,
|
||
FileName->Length,
|
||
TAG_ENUM_EXPRESSION );
|
||
|
||
SearchExpression.MaximumLength = FileName->Length;
|
||
|
||
//
|
||
// Either copy the name directly or perform the upcase.
|
||
//
|
||
|
||
if (FlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE )) {
|
||
|
||
Status = RtlUpcaseUnicodeString( &SearchExpression,
|
||
FileName,
|
||
FALSE );
|
||
|
||
//
|
||
// This should never fail.
|
||
//
|
||
|
||
ASSERT( Status == STATUS_SUCCESS );
|
||
|
||
} else {
|
||
|
||
RtlCopyMemory( SearchExpression.Buffer,
|
||
FileName->Buffer,
|
||
FileName->Length );
|
||
}
|
||
|
||
SearchExpression.Length = FileName->Length;
|
||
}
|
||
|
||
//
|
||
// But we do not want to return the constant "." and ".." entries for
|
||
// the root directory, for consistency with the rest of Microsoft's
|
||
// filesystems.
|
||
//
|
||
|
||
if (Fcb == Fcb->Vcb->RootIndexFcb) {
|
||
|
||
SetFlag( CcbFlags, CCB_FLAG_ENUM_NOMATCH_CONSTANT_ENTRY );
|
||
}
|
||
|
||
//
|
||
// Now lock the Fcb in order to update the Ccb with the inital
|
||
// enumeration values.
|
||
//
|
||
|
||
UdfLockFcb( 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->CurrentFileIndex = 0;
|
||
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 )) {
|
||
|
||
UdfFreePool( &SearchExpression.Buffer );
|
||
}
|
||
}
|
||
|
||
//
|
||
// Otherwise lock the Fcb so we can read the current enumeration values.
|
||
//
|
||
|
||
} else {
|
||
|
||
UdfLockFcb( 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 no name is specified,
|
||
// then we can't perform the restart.
|
||
//
|
||
|
||
if (FlagOn( IrpSp->Flags, SL_INDEX_SPECIFIED ) &&
|
||
IrpSp->Parameters.QueryDirectory.FileName != NULL) {
|
||
|
||
KnownIndex = FALSE;
|
||
FileIndex = IrpSp->Parameters.QueryDirectory.FileIndex;
|
||
RestartName = (PUNICODE_STRING) IrpSp->Parameters.QueryDirectory.FileName;
|
||
*ReturnNextEntry = TRUE;
|
||
|
||
//
|
||
// We will use the highest file index reportable to the caller as a
|
||
// starting point as required if we cannot directly land at the
|
||
// specified location.
|
||
//
|
||
|
||
HighFileIndex = Ccb->HighestReturnableFileIndex;
|
||
|
||
//
|
||
// If we are restarting the scan then go from the self entry.
|
||
//
|
||
|
||
} else if (FlagOn( IrpSp->Flags, SL_RESTART_SCAN )) {
|
||
|
||
KnownIndex = TRUE;
|
||
FileIndex = 0;
|
||
*ReturnNextEntry = FALSE;
|
||
|
||
//
|
||
// Otherwise use the values from the Ccb.
|
||
//
|
||
|
||
} else {
|
||
|
||
KnownIndex = TRUE;
|
||
FileIndex = Ccb->CurrentFileIndex;
|
||
*ReturnNextEntry = BooleanFlagOn( Ccb->Flags, CCB_FLAG_ENUM_RETURN_NEXT );
|
||
}
|
||
|
||
//
|
||
// Unlock the Fcb.
|
||
//
|
||
|
||
UdfUnlockFcb( 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 ((FileIndex == 0) &&
|
||
!(*ReturnNextEntry)) {
|
||
|
||
*InitialQuery = TRUE;
|
||
}
|
||
|
||
//
|
||
// Determine the offset in the stream to position the context 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
|
||
// 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 (KnownIndex) {
|
||
|
||
Found = UdfLookupInitialFileIndex( IrpContext, Fcb, CompoundDirContext, &FileIndex );
|
||
|
||
ASSERT( Found );
|
||
|
||
//
|
||
// Avoid a raise in UdfUpdateDirNames if we're re-starting from a CCB index
|
||
// after the parent entry, but in a new call to querydirectory (new DirContext)
|
||
//
|
||
|
||
if (1 <= FileIndex) {
|
||
|
||
SetFlag( CompoundDirContext->DirContext.Flags, DIR_CONTEXT_FLAG_SEEN_PARENT );
|
||
}
|
||
|
||
//
|
||
// Try to directly jump to the specified file index. Otherwise we walk through
|
||
// the directory from the beginning (or the saved highest known offset if that is
|
||
// useful) until we reach the entry which contains this offset.
|
||
//
|
||
|
||
} else {
|
||
|
||
//
|
||
// We need to handle the special case of a restart from a synthesized
|
||
// entry - this is the one time where the restart index can be zero
|
||
// without requiring us to search above the 2^32 byte mark.
|
||
//
|
||
|
||
if (UdfFullCompareNames( IrpContext,
|
||
RestartName,
|
||
&UdfUnicodeDirectoryNames[SELF_ENTRY] ) == EqualTo) {
|
||
|
||
FileIndex = UDF_FILE_INDEX_VIRTUAL_SELF;
|
||
|
||
Found = UdfLookupInitialFileIndex( IrpContext, Fcb, CompoundDirContext, &FileIndex );
|
||
|
||
ASSERT( Found );
|
||
|
||
//
|
||
// We are restarting from a physical entry. If the restart index is zero, we were
|
||
// unable to inform the caller as to the "real" file index due to the dispartity
|
||
// between the ULONG FileIndex in the return structures and the LONGLONG offsets
|
||
// possible in directory streams. In this case, we will go as high as we were able
|
||
// to inform the caller of and search linearly from that point forward.
|
||
//
|
||
// It is also possible (realistic? unknown) that the restart index is somewhere in the
|
||
// middle of an entry and we won't find anything useable. In this case we try to find
|
||
// the entry which contains this index, using it as the real restart point.
|
||
//
|
||
|
||
} else {
|
||
|
||
//
|
||
// See if we need the high water mark.
|
||
//
|
||
|
||
if (FileIndex == 0) {
|
||
|
||
//
|
||
// We know that this is good.
|
||
//
|
||
|
||
FileIndex = Max( Ccb->HighestReturnableFileIndex, UDF_FILE_INDEX_PHYSICAL );;
|
||
KnownIndex = TRUE;
|
||
|
||
}
|
||
|
||
//
|
||
// The file index is now useful, falling into two cases
|
||
//
|
||
// 1) KnownIndex == FALSE - searching by index
|
||
// 2) KnownIndex == TRUE - searching by name
|
||
//
|
||
// Go set up our inquiry.
|
||
//
|
||
|
||
Found = UdfLookupInitialFileIndex( IrpContext, Fcb, CompoundDirContext, &FileIndex );
|
||
|
||
if (KnownIndex) {
|
||
|
||
//
|
||
// Walk forward to discover an entry named per the caller's expectation.
|
||
//
|
||
|
||
do {
|
||
|
||
UdfUpdateDirNames( IrpContext,
|
||
&CompoundDirContext->DirContext,
|
||
BooleanFlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE ));
|
||
|
||
if (UdfFullCompareNames( IrpContext,
|
||
&CompoundDirContext->DirContext.CaseObjectName,
|
||
RestartName ) == EqualTo) {
|
||
|
||
break;
|
||
}
|
||
|
||
Found = UdfLookupNextFileIndex( IrpContext, Fcb, CompoundDirContext );
|
||
|
||
} while (Found);
|
||
|
||
} else if (!Found) {
|
||
|
||
LONGLONG LastFileIndex;
|
||
|
||
//
|
||
// Perform the search for the entry by index from the beginning of the physical directory.
|
||
//
|
||
|
||
LastFileIndex = UDF_FILE_INDEX_PHYSICAL;
|
||
|
||
Found = UdfLookupInitialFileIndex( IrpContext, Fcb, CompoundDirContext, &LastFileIndex );
|
||
|
||
ASSERT( Found );
|
||
|
||
//
|
||
// Keep walking through the directory until we run out of
|
||
// entries or we find an entry which ends beyond the input
|
||
// index value (index search case) or corresponds to the
|
||
// name we are looking for (name search case).
|
||
//
|
||
|
||
do {
|
||
|
||
//
|
||
// If we have passed the index value then exit.
|
||
//
|
||
|
||
if (CompoundDirContext->FileIndex.QuadPart > FileIndex) {
|
||
|
||
Found = FALSE;
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Remember the current position in case we need to go back.
|
||
//
|
||
|
||
LastFileIndex = CompoundDirContext->FileIndex.QuadPart;
|
||
|
||
//
|
||
// Exit if the next entry is beyond the desired index value.
|
||
//
|
||
|
||
if (LastFileIndex + ISONsrFidSize( CompoundDirContext->DirContext.Fid ) > FileIndex) {
|
||
|
||
break;
|
||
}
|
||
|
||
Found = UdfLookupNextFileIndex( IrpContext, Fcb, CompoundDirContext );
|
||
|
||
} while (Found);
|
||
|
||
//
|
||
// If we didn't find the entry then go back to the last known entry.
|
||
//
|
||
|
||
if (!Found) {
|
||
|
||
UdfCleanupDirContext( IrpContext, &CompoundDirContext->DirContext );
|
||
UdfInitializeDirContext( IrpContext, &CompoundDirContext->DirContext );
|
||
|
||
Found = UdfLookupInitialFileIndex( IrpContext, Fcb, CompoundDirContext, &LastFileIndex );
|
||
|
||
ASSERT( Found );
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// 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
|
||
// don't update it if it was already done.
|
||
//
|
||
|
||
if (!(*ReturnNextEntry) &&
|
||
CompoundDirContext->DirContext.PureObjectName.Buffer == NULL) {
|
||
|
||
//
|
||
// If the caller specified an index that corresponds to a
|
||
// deleted file, they are trying to be tricky. Don't let them.
|
||
//
|
||
|
||
if (CompoundDirContext->DirContext.Fid &&
|
||
FlagOn( CompoundDirContext->DirContext.Fid->Flags, NSR_FID_F_DELETED )) {
|
||
|
||
return STATUS_INVALID_PARAMETER;
|
||
}
|
||
|
||
//
|
||
// Update the names in the dirent.
|
||
//
|
||
|
||
UdfUpdateDirNames( IrpContext,
|
||
&CompoundDirContext->DirContext,
|
||
BooleanFlagOn( 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 STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
//
|
||
// Local support routine
|
||
//
|
||
|
||
BOOLEAN
|
||
UdfEnumerateIndex (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PCCB Ccb,
|
||
IN OUT PCOMPOUND_DIR_ENUM_CONTEXT CompoundDirContext,
|
||
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.
|
||
|
||
Arguments:
|
||
|
||
Ccb - Ccb for this directory handle.
|
||
|
||
CompoundDirContext - 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.
|
||
|
||
--*/
|
||
|
||
{
|
||
BOOLEAN Found = FALSE;
|
||
PDIR_ENUM_CONTEXT DirContext;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// Check inputs.
|
||
//
|
||
|
||
ASSERT_IRP_CONTEXT( IrpContext );
|
||
ASSERT_CCB( Ccb );
|
||
|
||
//
|
||
// Directly reference the directory enumeration context for convenience.
|
||
//
|
||
|
||
DirContext = &CompoundDirContext->DirContext;
|
||
|
||
//
|
||
// 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 (!UdfLookupNextFileIndex( IrpContext, Ccb->Fcb, CompoundDirContext )) {
|
||
|
||
break;
|
||
}
|
||
|
||
if (FlagOn( DirContext->Fid->Flags, NSR_FID_F_DELETED )) {
|
||
|
||
continue;
|
||
}
|
||
|
||
UdfUpdateDirNames( IrpContext,
|
||
DirContext,
|
||
BooleanFlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE ));
|
||
} else {
|
||
|
||
ReturnNextEntry = TRUE;
|
||
}
|
||
|
||
//
|
||
// Don't bother if we have a constant entry and are ignoring them.
|
||
//
|
||
|
||
if (!FlagOn( DirContext->Flags, DIR_CONTEXT_FLAG_SEEN_NONCONSTANT ) &&
|
||
FlagOn( Ccb->Flags, CCB_FLAG_ENUM_NOMATCH_CONSTANT_ENTRY )) {
|
||
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// If we match all names then return to our caller.
|
||
//
|
||
|
||
if (FlagOn( Ccb->Flags, CCB_FLAG_ENUM_MATCH_ALL )) {
|
||
|
||
DirContext->ShortObjectName.Length = 0;
|
||
Found = TRUE;
|
||
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Check if the long name matches the search expression.
|
||
//
|
||
|
||
if (UdfIsNameInExpression( IrpContext,
|
||
&DirContext->CaseObjectName,
|
||
&Ccb->SearchExpression,
|
||
BooleanFlagOn( Ccb->Flags, CCB_FLAG_ENUM_NAME_EXP_HAS_WILD ))) {
|
||
|
||
//
|
||
// Let our caller know we found an entry.
|
||
//
|
||
|
||
DirContext->ShortObjectName.Length = 0;
|
||
Found = TRUE;
|
||
|
||
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 one of the constant entries or already
|
||
// is 8dot3.
|
||
//
|
||
|
||
if (!(!FlagOn( DirContext->Flags, DIR_CONTEXT_FLAG_SEEN_NONCONSTANT ) ||
|
||
UdfIs8dot3Name( IrpContext,
|
||
DirContext->CaseObjectName ))) {
|
||
|
||
//
|
||
// Allocate the shortname if it isn't already done.
|
||
//
|
||
|
||
if (DirContext->ShortObjectName.Buffer == NULL) {
|
||
|
||
DirContext->ShortObjectName.Buffer = FsRtlAllocatePoolWithTag( UdfPagedPool,
|
||
BYTE_COUNT_8_DOT_3,
|
||
TAG_SHORT_FILE_NAME );
|
||
DirContext->ShortObjectName.MaximumLength = BYTE_COUNT_8_DOT_3;
|
||
}
|
||
|
||
UdfGenerate8dot3Name( IrpContext,
|
||
&DirContext->PureObjectName,
|
||
&DirContext->ShortObjectName );
|
||
|
||
//
|
||
// Check if this name matches.
|
||
//
|
||
|
||
if (UdfIsNameInExpression( IrpContext,
|
||
&DirContext->ShortObjectName,
|
||
&Ccb->SearchExpression,
|
||
BooleanFlagOn( Ccb->Flags, CCB_FLAG_ENUM_NAME_EXP_HAS_WILD ))) {
|
||
|
||
//
|
||
// Let our caller know we found an entry.
|
||
//
|
||
|
||
Found = TRUE;
|
||
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
return Found;
|
||
}
|
||
|
||
|
||
//
|
||
// Local support routine
|
||
//
|
||
|
||
VOID
|
||
UdfLookupFileEntryInEnumeration (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PFCB Fcb,
|
||
IN PCOMPOUND_DIR_ENUM_CONTEXT CompoundDirContext
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine retrieves the file entry associated with the current location in
|
||
the enumeration of a compound directory context.
|
||
|
||
Arguments:
|
||
|
||
Fcb - the directory being enumerated.
|
||
|
||
CompoundDirContext - a corresponding context for the enumeration.
|
||
|
||
Return Value:
|
||
|
||
None. Status may be raised on discovery of corruption.
|
||
|
||
--*/
|
||
|
||
{
|
||
PNSR_FID Fid;
|
||
PICBFILE Fe;
|
||
ULONG Length;
|
||
|
||
Fid = CompoundDirContext->DirContext.Fid;
|
||
|
||
//
|
||
// Figure out where the ICB we want is.
|
||
//
|
||
|
||
if (UdfIsFileIndexVirtual( CompoundDirContext->FileIndex.QuadPart )) {
|
||
|
||
//
|
||
// Synthesize! We only have to synthesize the self entry. The name is already done,
|
||
// so the remaining work is trivial.
|
||
//
|
||
|
||
ASSERT( Fid == NULL );
|
||
|
||
//
|
||
// Lift the FE corresponding to this directory
|
||
//
|
||
|
||
UdfCleanupIcbContext( IrpContext, &CompoundDirContext->IcbContext );
|
||
|
||
UdfInitializeIcbContextFromFcb( IrpContext,
|
||
&CompoundDirContext->IcbContext,
|
||
Fcb );
|
||
|
||
Length = Fcb->RootExtentLength;
|
||
|
||
} else {
|
||
|
||
//
|
||
// Lift the FE corresponding to this FID.
|
||
//
|
||
|
||
ASSERT( Fid != NULL );
|
||
|
||
UdfCleanupIcbContext( IrpContext, &CompoundDirContext->IcbContext );
|
||
|
||
UdfInitializeIcbContext( IrpContext,
|
||
&CompoundDirContext->IcbContext,
|
||
Fcb->Vcb,
|
||
DESTAG_ID_NSR_FILE,
|
||
Fid->Icb.Start.Partition,
|
||
Fid->Icb.Start.Lbn,
|
||
BlockSize( IrpContext->Vcb) );
|
||
|
||
Length = Fid->Icb.Length.Length;
|
||
}
|
||
|
||
//
|
||
// Retrieve the ICB for inspection.
|
||
//
|
||
|
||
UdfLookupActiveIcb( IrpContext,
|
||
&CompoundDirContext->IcbContext,
|
||
Length);
|
||
|
||
Fe = (PICBFILE) CompoundDirContext->IcbContext.Active.View;
|
||
|
||
//
|
||
// Perform some basic verification that the FE is of the proper type and that
|
||
// FID and FE agree as to the type of the object. We explicitly check that
|
||
// a legal filesystem-level FE type is discovered, even though we don't support
|
||
// them in other paths. Note that we leave the IcbContext->IcbType as FILE even
|
||
// though we may have picked up an extended file entry.
|
||
//
|
||
|
||
if (((Fe->Destag.Ident != DESTAG_ID_NSR_FILE) &&
|
||
((Fe->Destag.Ident != DESTAG_ID_NSR_EXT_FILE) || (!UdfExtendedFEAllowed( IrpContext->Vcb)))) ||
|
||
|
||
(((Fid && FlagOn( Fid->Flags, NSR_FID_F_DIRECTORY )) ||
|
||
Fid == NULL) &&
|
||
Fe->Icbtag.FileType != ICBTAG_FILE_T_DIRECTORY) ||
|
||
|
||
(Fe->Icbtag.FileType != ICBTAG_FILE_T_FILE &&
|
||
Fe->Icbtag.FileType != ICBTAG_FILE_T_DIRECTORY &&
|
||
Fe->Icbtag.FileType != ICBTAG_FILE_T_BLOCK_DEV &&
|
||
Fe->Icbtag.FileType != ICBTAG_FILE_T_CHAR_DEV &&
|
||
Fe->Icbtag.FileType != ICBTAG_FILE_T_FIFO &&
|
||
Fe->Icbtag.FileType != ICBTAG_FILE_T_C_ISSOCK &&
|
||
Fe->Icbtag.FileType != ICBTAG_FILE_T_PATHLINK &&
|
||
Fe->Icbtag.FileType != ICBTAG_FILE_T_REALTIME)
|
||
) {
|
||
|
||
UdfRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR );
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// Local support routine
|
||
//
|
||
|
||
BOOLEAN
|
||
UdfLookupInitialFileIndex (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PFCB Fcb,
|
||
IN PCOMPOUND_DIR_ENUM_CONTEXT CompoundDirContext,
|
||
IN PLONGLONG InitialIndex
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine begins the enumeration of a directory by setting the context
|
||
at the first avaliable virtual directory entry.
|
||
|
||
Arguments:
|
||
|
||
Fcb - the directory being enumerated.
|
||
|
||
CompoundDirContext - a corresponding context for the enumeration.
|
||
|
||
InitialIndex - an optional starting file index to base the enumeration.
|
||
|
||
Return Value:
|
||
|
||
TRUE will be returned if a valid entry is found at this offset, FALSE otherwise.
|
||
|
||
--*/
|
||
|
||
{
|
||
LONGLONG DirOffset;
|
||
|
||
if (UdfIsFileIndexVirtual( *InitialIndex )) {
|
||
|
||
//
|
||
// We only synthesize a single virtual directory entry. Position the context
|
||
// at the virtual self entry.
|
||
//
|
||
|
||
CompoundDirContext->FileIndex.QuadPart = UDF_FILE_INDEX_VIRTUAL_SELF;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
CompoundDirContext->FileIndex.QuadPart = *InitialIndex;
|
||
|
||
//
|
||
// Find the base offset in the directory and look it up.
|
||
//
|
||
|
||
DirOffset = UdfFileIndexToPhysicalOffset( *InitialIndex );
|
||
|
||
return UdfLookupInitialDirEntry( IrpContext,
|
||
Fcb,
|
||
&CompoundDirContext->DirContext,
|
||
&DirOffset );
|
||
}
|
||
|
||
|
||
//
|
||
// Local support routine
|
||
//
|
||
|
||
BOOLEAN
|
||
UdfLookupNextFileIndex (
|
||
IN PIRP_CONTEXT IrpContext,
|
||
IN PFCB Fcb,
|
||
IN PCOMPOUND_DIR_ENUM_CONTEXT CompoundDirContext
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine advances the enumeration of a virtual directory by one entry.
|
||
|
||
Arguments:
|
||
|
||
Fcb - the directory being enumerated.
|
||
|
||
CompoundDirContext - a corresponding context for the enumeration.
|
||
|
||
Return Value:
|
||
|
||
BOOLEAN True if another Fid is avaliable, False if we are at the end.
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG Advance;
|
||
BOOLEAN Result;
|
||
|
||
//
|
||
// Advance from the synthesized to the physical directory.
|
||
//
|
||
|
||
if (UdfIsFileIndexVirtual( CompoundDirContext->FileIndex.QuadPart )) {
|
||
|
||
Result = UdfLookupInitialDirEntry( IrpContext,
|
||
Fcb,
|
||
&CompoundDirContext->DirContext,
|
||
NULL );
|
||
|
||
if (Result) {
|
||
|
||
CompoundDirContext->FileIndex.QuadPart = UDF_FILE_INDEX_PHYSICAL;
|
||
}
|
||
|
||
return Result;
|
||
}
|
||
|
||
Advance = ISONsrFidSize( CompoundDirContext->DirContext.Fid );
|
||
|
||
//
|
||
// Advance to the next entry in this directory.
|
||
//
|
||
|
||
Result = UdfLookupNextDirEntry( IrpContext, Fcb, &CompoundDirContext->DirContext );
|
||
|
||
if (Result) {
|
||
|
||
CompoundDirContext->FileIndex.QuadPart += Advance;
|
||
}
|
||
|
||
return Result;
|
||
}
|
||
|