You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2108 lines
62 KiB
2108 lines
62 KiB
/*++
|
|
|
|
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;
|
|
FILE_INFORMATION_CLASS InfoClass = IrpSp->Parameters.QueryDirectory.FileInformationClass;
|
|
|
|
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 ( InfoClass) {
|
|
|
|
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;
|
|
}
|
|
|
|
//
|
|
// Zero and initialize the base part of the current entry.
|
|
//
|
|
|
|
SafeZeroMemory( IrpContext, Add2Ptr( UserBuffer, NextEntry, PVOID ), BaseLength );
|
|
|
|
//
|
|
// Most query types require that we lookup the FE referenced by the FID
|
|
// to extract attribute information. Do this outside the user buffer exception
|
|
// handler block to avoid the risk of masking real driver errors.
|
|
//
|
|
|
|
EasCorrupt = FALSE;
|
|
ThisFe = NULL;
|
|
|
|
if (InfoClass != FileNamesInformation) {
|
|
|
|
//
|
|
// 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 user attempts to open the corrupt
|
|
// object.
|
|
//
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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 {
|
|
|
|
//
|
|
// 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 (InfoClass) {
|
|
|
|
case FileBothDirectoryInformation:
|
|
case FileFullDirectoryInformation:
|
|
case FileIdBothDirectoryInformation:
|
|
case FileIdFullDirectoryInformation:
|
|
case FileDirectoryInformation:
|
|
|
|
DirInfo = Add2Ptr( UserBuffer, NextEntry, PFILE_BOTH_DIR_INFORMATION );
|
|
|
|
//
|
|
// We should already have looked up the FE, unless it was corrupt. 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 arbirary but 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 (InfoClass) {
|
|
|
|
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) &&
|
|
(InfoClass == FileBothDirectoryInformation ||
|
|
InfoClass == 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 = 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 = 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;
|
|
}
|
|
|