602 lines
19 KiB
C
602 lines
19 KiB
C
/*++
|
|
|
|
Copyright (c) 2000-2001 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
dirctrl.c
|
|
|
|
Abstract:
|
|
|
|
This module implements routines related to handling
|
|
IRP_MJ_DIRECTORY_CONTROL.
|
|
|
|
--*/
|
|
|
|
#include "fatx.h"
|
|
|
|
NTSTATUS
|
|
FatxFindNextDirectoryEntry(
|
|
IN PFAT_VOLUME_EXTENSION VolumeExtension,
|
|
IN PIRP Irp,
|
|
IN PFAT_FCB DirectoryFcb,
|
|
IN ULONG DirectoryByteOffset,
|
|
IN POBJECT_STRING TemplateFileName,
|
|
OUT PDIRENT ReturnedDirectoryEntry,
|
|
OUT PULONG ReturnedDirectoryByteOffset
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine finds the next directory entry that matches the query template
|
|
specification.
|
|
|
|
Arguments:
|
|
|
|
VolumeExtension - Specifies the extension that the I/O request is for.
|
|
|
|
Irp - Specifies the packet that describes the I/O request.
|
|
|
|
DirectoryFcb - Specifies the file control block that describes where to
|
|
read the directory stream from.
|
|
|
|
DirectoryByteOffset - Specifies the directory byte offset to start scanning
|
|
from.
|
|
|
|
TemplateFileName - Specifies the optional file name to match.
|
|
|
|
ReturnedDirectoryEntry - Specifies the buffer to receive the directory entry
|
|
for the file if found.
|
|
|
|
ReturnedDirectoryByteOffset - Specifies the buffer to receive the byte
|
|
offset of the entry in the directory stream.
|
|
|
|
Return Value:
|
|
|
|
Status of operation.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status;
|
|
PVOID CacheBuffer;
|
|
ULONG ClusterNumber;
|
|
LONG FileNameLengthLimit;
|
|
ULONGLONG PhysicalByteOffset;
|
|
ULONG ClusterBytesRemaining;
|
|
ULONG ClusterByteOffset;
|
|
ULONG PageBytesRemaining;
|
|
PDIRENT DirectoryEntry;
|
|
PDIRENT EndingDirectoryEntry;
|
|
OBJECT_STRING DirectoryEntryFileName;
|
|
|
|
ASSERT(VolumeExtension->VolumeMutexExclusiveOwner == KeGetCurrentThread());
|
|
|
|
CacheBuffer = NULL;
|
|
|
|
//
|
|
// If we're in the first cluster, then we can skip accessing the cluster
|
|
// cache and go directly to the first cluster. Otherwise, we need to go
|
|
// through the cluster cache to map the current byte offset to the cluster
|
|
// number.
|
|
//
|
|
|
|
if (DirectoryByteOffset < VolumeExtension->BytesPerCluster) {
|
|
|
|
//
|
|
// Get the first cluster of the directory stream and validate it.
|
|
//
|
|
|
|
ClusterNumber = DirectoryFcb->FirstCluster;
|
|
|
|
if (!FatxIsValidCluster(VolumeExtension, ClusterNumber)) {
|
|
FatxDbgPrint(("FATX: invalid starting cluster for directory\n"));
|
|
status = STATUS_FILE_CORRUPT_ERROR;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Get the cluster number corresponding to the current byte offset.
|
|
//
|
|
|
|
status = FatxFileByteOffsetToCluster(VolumeExtension, Irp, DirectoryFcb,
|
|
DirectoryByteOffset, &ClusterNumber, NULL);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
goto CleanupAndExit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Compute the longest file name that we'll allow to be enumerated. If the
|
|
// directory's path name length is near FAT_PATH_NAME_LIMIT, then we don't
|
|
// want to return names that would cause us to overflow that limit.
|
|
//
|
|
|
|
FileNameLengthLimit = FAT_PATH_NAME_LIMIT - (DirectoryFcb->PathNameLength +
|
|
sizeof(OBJ_NAME_PATH_SEPARATOR));
|
|
|
|
if (FileNameLengthLimit > FAT_FILE_NAME_LENGTH) {
|
|
FileNameLengthLimit = FAT_FILE_NAME_LENGTH;
|
|
} else if (FileNameLengthLimit <= 0) {
|
|
status = STATUS_END_OF_FILE;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
//
|
|
// Process the directory stream.
|
|
//
|
|
|
|
for (;;) {
|
|
|
|
//
|
|
// Compute the physical byte offset of the next cluster and the number
|
|
// of bytes remaining in this cluster.
|
|
//
|
|
|
|
PhysicalByteOffset = FatxClusterToPhysicalByteOffset(VolumeExtension,
|
|
ClusterNumber);
|
|
ClusterBytesRemaining = VolumeExtension->BytesPerCluster;
|
|
|
|
//
|
|
// If the directory byte offset is not cluster aligned, then bump up the
|
|
// physical byte offset and reduce the number of bytes remaining by the
|
|
// number of misaligned bytes.
|
|
//
|
|
|
|
ClusterByteOffset = FatxByteOffsetIntoCluster(VolumeExtension,
|
|
DirectoryByteOffset);
|
|
PhysicalByteOffset += ClusterByteOffset;
|
|
ClusterBytesRemaining -= ClusterByteOffset;
|
|
|
|
do {
|
|
|
|
//
|
|
// Map in the next page of the cluster.
|
|
//
|
|
|
|
status = FscMapBuffer(&VolumeExtension->CacheExtension, Irp,
|
|
PhysicalByteOffset, FALSE, &CacheBuffer);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
//
|
|
// Compute the number of bytes remaining on this cache buffer.
|
|
//
|
|
|
|
PageBytesRemaining = PAGE_SIZE - BYTE_OFFSET(PhysicalByteOffset);
|
|
|
|
//
|
|
// Adjust the number of bytes remaining in this cluster and the next
|
|
// cache byte offset depending on whether we're near the end of the
|
|
// cluster or not.
|
|
//
|
|
|
|
if (PageBytesRemaining < ClusterBytesRemaining) {
|
|
ClusterBytesRemaining -= PageBytesRemaining;
|
|
PhysicalByteOffset += PageBytesRemaining;
|
|
} else {
|
|
PageBytesRemaining = ClusterBytesRemaining;
|
|
ClusterBytesRemaining = 0;
|
|
}
|
|
|
|
//
|
|
// Process the directory entries on this cache buffer.
|
|
//
|
|
|
|
DirectoryEntry = (PDIRENT)CacheBuffer;
|
|
EndingDirectoryEntry = (PDIRENT)((PUCHAR)DirectoryEntry +
|
|
PageBytesRemaining);
|
|
|
|
do {
|
|
|
|
//
|
|
// If we find a directory entry with a file name starting with
|
|
// FAT_DIRENT_NEVER_USED or FAT_DIRENT_NEVER_USED2, then we're
|
|
// at the end of the directory stream.
|
|
//
|
|
|
|
if ((DirectoryEntry->FileNameLength == FAT_DIRENT_NEVER_USED) ||
|
|
(DirectoryEntry->FileNameLength == FAT_DIRENT_NEVER_USED2)) {
|
|
status = STATUS_END_OF_FILE;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
//
|
|
// Check if the file name matches the name we're looking for.
|
|
//
|
|
// Make sure that the file name is legal so that we don't return
|
|
// names to the caller that FatxFsdCreate will fail to locate.
|
|
//
|
|
|
|
DirectoryEntryFileName.Length = DirectoryEntry->FileNameLength;
|
|
DirectoryEntryFileName.Buffer = DirectoryEntry->FileName;
|
|
|
|
if ((DirectoryEntry->FileNameLength <= (ULONG)FileNameLengthLimit) &&
|
|
FatxIsValidFileAttributes(DirectoryEntry->FileAttributes) &&
|
|
FatxIsValidFatFileName(&DirectoryEntryFileName) &&
|
|
((TemplateFileName->Buffer == NULL) ||
|
|
IoIsNameInExpression(TemplateFileName, &DirectoryEntryFileName))) {
|
|
|
|
//
|
|
// Copy the directory entry and its file byte offset back
|
|
// into the caller's buffers.
|
|
//
|
|
|
|
*ReturnedDirectoryEntry = *DirectoryEntry;
|
|
*ReturnedDirectoryByteOffset = DirectoryByteOffset;
|
|
|
|
status = STATUS_SUCCESS;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
//
|
|
// Advance to the next directory entry.
|
|
//
|
|
|
|
DirectoryEntry++;
|
|
DirectoryByteOffset += sizeof(DIRENT);
|
|
|
|
} while (DirectoryEntry < EndingDirectoryEntry);
|
|
|
|
//
|
|
// Unmap this cache buffer.
|
|
//
|
|
|
|
FscUnmapBuffer(CacheBuffer);
|
|
CacheBuffer = NULL;
|
|
|
|
} while (ClusterBytesRemaining > 0);
|
|
|
|
//
|
|
// Advance to the next cluster.
|
|
//
|
|
|
|
status = FatxFileByteOffsetToCluster(VolumeExtension, Irp, DirectoryFcb,
|
|
DirectoryByteOffset, &ClusterNumber, NULL);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
//
|
|
// Don't allow a directory to exceed the maximum allowed directory size.
|
|
// We check for this case after the call to FatxFileByteOffsetToCluster
|
|
// so that a directory that's exactly the maximum allowed directory size
|
|
// will properly fill its allocation size and ending cluster number for
|
|
// the sake of code further downstream.
|
|
//
|
|
|
|
if (NT_SUCCESS(status) &&
|
|
(DirectoryByteOffset >= FAT_MAXIMUM_DIRECTORY_FILE_SIZE)) {
|
|
status = STATUS_FILE_CORRUPT_ERROR;
|
|
goto CleanupAndExit;
|
|
}
|
|
}
|
|
|
|
CleanupAndExit:
|
|
if (CacheBuffer != NULL) {
|
|
FscUnmapBuffer(CacheBuffer);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
FatxFsdDirectoryControl(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called by the I/O manager to handle IRP_MJ_DIRECTORY_CONTROL
|
|
requests.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Specifies the device object that the I/O request is for.
|
|
|
|
Irp - Specifies the packet that describes the I/O request.
|
|
|
|
Return Value:
|
|
|
|
Status of operation.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status;
|
|
PFAT_VOLUME_EXTENSION VolumeExtension;
|
|
PIO_STACK_LOCATION IrpSp;
|
|
PFILE_OBJECT FileObject;
|
|
PFAT_FCB DirectoryFcb;
|
|
PDIRECTORY_ENUM_CONTEXT DirectoryEnumContext;
|
|
POBJECT_STRING TemplateFileName;
|
|
ULONG FileInformationClass;
|
|
SIZE_T FileInformationBaseLength;
|
|
BOOLEAN InitialQuery;
|
|
DIRENT DirectoryEntry;
|
|
ULONG DirectoryByteOffset;
|
|
PFILE_DIRECTORY_INFORMATION DirectoryInformation;
|
|
ULONG FileNameBytesToCopy;
|
|
ULONG OutputBytesRemaining;
|
|
|
|
VolumeExtension = (PFAT_VOLUME_EXTENSION)DeviceObject->DeviceExtension;
|
|
IrpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
FileObject = IrpSp->FileObject;
|
|
DirectoryFcb = (PFAT_FCB)FileObject->FsContext;
|
|
|
|
//
|
|
// Synchronize the creation and access of the directory context control
|
|
// block by acquiring the exclusive mutex for the volume.
|
|
//
|
|
|
|
FatxAcquireVolumeMutexExclusive(VolumeExtension);
|
|
|
|
//
|
|
// Check if the volume has been dismounted.
|
|
//
|
|
|
|
if (FatxIsFlagSet(VolumeExtension->Flags, FAT_VOLUME_DISMOUNTED)) {
|
|
status = STATUS_VOLUME_DISMOUNTED;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
//
|
|
// Ensure that the file object is for a directory.
|
|
//
|
|
|
|
if (FatxIsFlagClear(DirectoryFcb->Flags, FAT_FCB_DIRECTORY)) {
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
//
|
|
// If the directory is marked for deletion, then the directory won't contain
|
|
// any files and it won't allow any files to be created, so bail out now.
|
|
// This also catches code that uses a directory file object after its handle
|
|
// has been closed and the directory has been deleted, such as for symbolic
|
|
// link objects.
|
|
//
|
|
|
|
if (FatxIsFlagSet(DirectoryFcb->Flags, FAT_FCB_DELETE_ON_CLOSE)) {
|
|
status = STATUS_DELETE_PENDING;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
//
|
|
// Verify that this is a supported information class.
|
|
//
|
|
|
|
FileInformationClass = IrpSp->Parameters.QueryDirectory.FileInformationClass;
|
|
|
|
switch (FileInformationClass) {
|
|
|
|
case FileDirectoryInformation:
|
|
FileInformationBaseLength = FIELD_OFFSET(FILE_DIRECTORY_INFORMATION,
|
|
FileName[0]);
|
|
break;
|
|
|
|
case FileNamesInformation:
|
|
FileInformationBaseLength = FIELD_OFFSET(FILE_NAMES_INFORMATION,
|
|
FileName[0]);
|
|
break;
|
|
|
|
default:
|
|
status = STATUS_INVALID_INFO_CLASS;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
//
|
|
// The query cannot be started relative to a starting index.
|
|
//
|
|
|
|
if (FatxIsFlagSet(IrpSp->Flags, SL_INDEX_SPECIFIED)) {
|
|
status = STATUS_NOT_IMPLEMENTED;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
//
|
|
// If this is the first query for this directory, then allocate a directory
|
|
// context control block and initialize it.
|
|
//
|
|
|
|
DirectoryEnumContext = (PDIRECTORY_ENUM_CONTEXT)FileObject->FsContext2;
|
|
|
|
if (DirectoryEnumContext == NULL) {
|
|
|
|
TemplateFileName = IrpSp->Parameters.QueryDirectory.FileName;
|
|
|
|
status = IoCreateDirectoryEnumContext(TemplateFileName,
|
|
&DirectoryEnumContext);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
//
|
|
// Connect the directory enumeration context to the file object.
|
|
//
|
|
|
|
FileObject->FsContext2 = DirectoryEnumContext;
|
|
|
|
InitialQuery = TRUE;
|
|
|
|
} else {
|
|
|
|
InitialQuery = FALSE;
|
|
}
|
|
|
|
//
|
|
// If we're to restart the directory scan, then reset the current index to
|
|
// zero.
|
|
//
|
|
|
|
if (FatxIsFlagSet(IrpSp->Flags, SL_RESTART_SCAN)) {
|
|
DirectoryEnumContext->QueryOffset = 0;
|
|
}
|
|
|
|
//
|
|
// Find the next directory entry that matches our query criteria.
|
|
//
|
|
// On return, DirectoryEnumContext->QueryOffset still points at the original
|
|
// identifier. It's only updated after we're about to successfully return
|
|
// so that no entries are lost in the event of an invalid parameter or pool
|
|
// allocation failure.
|
|
//
|
|
|
|
status = FatxFindNextDirectoryEntry(VolumeExtension, Irp, DirectoryFcb,
|
|
DirectoryEnumContext->QueryOffset,
|
|
&DirectoryEnumContext->TemplateFileName, &DirectoryEntry,
|
|
&DirectoryByteOffset);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
//
|
|
// The I/O manager has already checked that the user's buffer has enough
|
|
// space to contain at least the header.
|
|
//
|
|
|
|
ASSERT(IrpSp->Parameters.QueryDirectory.Length >= FileInformationBaseLength);
|
|
|
|
//
|
|
// Zero out the header.
|
|
//
|
|
|
|
DirectoryInformation = (PFILE_DIRECTORY_INFORMATION)Irp->UserBuffer;
|
|
RtlZeroMemory(DirectoryInformation, FileInformationBaseLength);
|
|
|
|
//
|
|
// For FileDirectoryInformation and FileNamesInformation, the
|
|
// FileNameLength field is immediately before the FileName buffer.
|
|
//
|
|
|
|
*((PULONG)((PUCHAR)DirectoryInformation + FileInformationBaseLength -
|
|
sizeof(ULONG))) = DirectoryEntry.FileNameLength;
|
|
|
|
//
|
|
// If this is a FileDirectoryInformation request, then fill in more
|
|
// information. We have to go to dig into the file entry descriptor
|
|
// to get the information we need, so we'll construct a file control
|
|
// block to get the attributes.
|
|
//
|
|
|
|
if (FileInformationClass == FileDirectoryInformation) {
|
|
|
|
if (FatxIsFlagSet(DirectoryEntry.FileAttributes, FILE_ATTRIBUTE_DIRECTORY)) {
|
|
|
|
DirectoryInformation->EndOfFile.QuadPart = 0;
|
|
DirectoryInformation->AllocationSize.QuadPart = 0;
|
|
|
|
} else {
|
|
|
|
//
|
|
// AllocationSize should be filled in with the number of clusters
|
|
// actually allocated to the file, but we don't want to have to
|
|
// go parse the entire FAT chain to obtain this piece of
|
|
// information. We'll let the caller assume that the file has
|
|
// been allocated the number of bytes actually in the file.
|
|
//
|
|
|
|
DirectoryInformation->EndOfFile.QuadPart =
|
|
(ULONGLONG)DirectoryEntry.FileSize;
|
|
DirectoryInformation->AllocationSize.QuadPart =
|
|
(ULONGLONG)DirectoryEntry.FileSize;
|
|
}
|
|
|
|
DirectoryInformation->CreationTime =
|
|
FatxFatTimestampToTime(&DirectoryEntry.CreationTime);
|
|
DirectoryInformation->LastAccessTime =
|
|
DirectoryInformation->LastWriteTime =
|
|
DirectoryInformation->ChangeTime =
|
|
FatxFatTimestampToTime(&DirectoryEntry.LastWriteTime);
|
|
|
|
if (DirectoryEntry.FileAttributes == 0) {
|
|
DirectoryInformation->FileAttributes = FILE_ATTRIBUTE_NORMAL;
|
|
} else {
|
|
DirectoryInformation->FileAttributes = DirectoryEntry.FileAttributes;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If nothing has gone wrong yet, then copy the file name to the user's
|
|
// buffer.
|
|
//
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
OutputBytesRemaining =
|
|
ALIGN_DOWN(IrpSp->Parameters.QueryDirectory.Length -
|
|
FileInformationBaseLength, sizeof(OCHAR));
|
|
FileNameBytesToCopy = DirectoryEntry.FileNameLength;
|
|
|
|
if (FileNameBytesToCopy > OutputBytesRemaining) {
|
|
FileNameBytesToCopy = OutputBytesRemaining;
|
|
status = STATUS_BUFFER_OVERFLOW;
|
|
} else {
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
RtlCopyMemory((PUCHAR)DirectoryInformation + FileInformationBaseLength,
|
|
DirectoryEntry.FileName, FileNameBytesToCopy);
|
|
|
|
//
|
|
// Fill in the number of bytes that we wrote to the user's buffer.
|
|
//
|
|
|
|
Irp->IoStatus.Information = FileInformationBaseLength +
|
|
FileNameBytesToCopy;
|
|
|
|
//
|
|
// Check that we didn't overflow the user's buffer. The I/O manager
|
|
// does the initial check to make sure there's enough space for the
|
|
// static structure for a given information class, but we might
|
|
// overflow the buffer when copying in the variable length file
|
|
// name.
|
|
//
|
|
|
|
ASSERT(Irp->IoStatus.Information <=
|
|
IrpSp->Parameters.QueryDirectory.Length);
|
|
|
|
//
|
|
// Update the query offset.
|
|
//
|
|
|
|
DirectoryEnumContext->QueryOffset = DirectoryByteOffset +
|
|
sizeof(DIRENT);
|
|
|
|
//
|
|
// Store the directory byte offset in the directory file control
|
|
// block. If the caller attempts to open this file, then we'll use
|
|
// this to quickly locate the file's directory entry.
|
|
//
|
|
|
|
DirectoryFcb->Directory.DirectoryByteOffsetLookupHint =
|
|
DirectoryByteOffset;
|
|
}
|
|
|
|
} else if (status == STATUS_END_OF_FILE) {
|
|
|
|
//
|
|
// If we hit the end of the directory stream, then return an appropriate
|
|
// status code depending on whether this was the first pass through this
|
|
// routine for this handle or not.
|
|
//
|
|
|
|
status = InitialQuery ? STATUS_NO_SUCH_FILE : STATUS_NO_MORE_FILES;
|
|
}
|
|
|
|
CleanupAndExit:
|
|
FatxReleaseVolumeMutex(VolumeExtension);
|
|
|
|
Irp->IoStatus.Status = status;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
|
|
return status;
|
|
}
|