xbox-kernel/private/ntos/fatx/dirctrl.c
2020-09-30 17:17:25 +02:00

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;
}