/*++

Copyright (c) 2000-2001  Microsoft Corporation

Module Name:

    mountvol.c

Abstract:

    This module implements routines related to the mounting of a FAT volume.

--*/

#include "fatx.h"

NTSTATUS
FatxCreateVolumeDevice(
    IN PDEVICE_OBJECT TargetDeviceObject,
    IN PDISK_GEOMETRY DiskGeometry,
    IN PPARTITION_INFORMATION PartitionInformation,
    OUT PDEVICE_OBJECT *ReturnedVolumeDeviceObject
    )
/*++

Routine Description:

    This routine creates an instance of a volume device object.

Arguments:

    TargetDeviceObject - Specifies the device object that the volume object
        sends I/O requests to.

    DiskGeometry - Specifies the disk geometry of the target device.

    PartitionInformation - Specifies the partition information for the target
        device.

    ReturnedVolumeDeviceObject - Specifies the buffer to receive the created
        device object.

Return Value:

    Status of operation.

--*/
{
    NTSTATUS status;
    PDEVICE_OBJECT VolumeDeviceObject;
    PFAT_VOLUME_EXTENSION VolumeExtension;

    //
    // Create the volume's device object.
    //

    status = IoCreateDevice(&FatxDriverObject, sizeof(FAT_VOLUME_EXTENSION),
        NULL, FILE_DEVICE_DISK_FILE_SYSTEM, FALSE, &VolumeDeviceObject);

    if (!NT_SUCCESS(status)) {
        return status;
    }

    //
    // Increase the volume's IRP stack size for requests that we pass down to
    // the target device object.
    //

    VolumeDeviceObject->StackSize = (UCHAR)(VolumeDeviceObject->StackSize +
        TargetDeviceObject->StackSize);

    //
    // Ensure that the alignment for this device matches the requirements of the
    // target device.
    //

    if (VolumeDeviceObject->AlignmentRequirement < TargetDeviceObject->AlignmentRequirement) {
        VolumeDeviceObject->AlignmentRequirement = TargetDeviceObject->AlignmentRequirement;
    }

    //
    // If the target device object supports scatter gather I/O, then indicate
    // that we can as well.
    //

    if (FatxIsFlagSet(TargetDeviceObject->Flags, DO_SCATTER_GATHER_IO)) {
        VolumeDeviceObject->Flags |= DO_SCATTER_GATHER_IO;
    }

    //
    // Set the size of a sector for this device.
    //

    VolumeDeviceObject->SectorSize = (USHORT)DiskGeometry->BytesPerSector;

    //
    // Increment the reference count on the target device object since we'll be
    // holding a pointer to it.
    //

    ObReferenceObject(TargetDeviceObject);

    //
    // Initialize the volume's device extension data.
    //

    VolumeExtension = (PFAT_VOLUME_EXTENSION)VolumeDeviceObject->DeviceExtension;

    VolumeExtension->TargetDeviceObject = TargetDeviceObject;
    VolumeExtension->PartitionLength = PartitionInformation->PartitionLength;
    VolumeExtension->SectorSize = DiskGeometry->BytesPerSector;
    VolumeExtension->SectorShift = RtlFindFirstSetRightMember(DiskGeometry->BytesPerSector);

    //
    // Initialize the volume's instance lock.
    //

    ExInitializeReadWriteLock(&VolumeExtension->VolumeMutex);

    //
    // Initialize the volume's cluster cache lock.
    //

    RtlInitializeCriticalSection(&VolumeExtension->ClusterCacheMutex);

    //
    // Initialize the file control block for the physical volume.
    //

    VolumeExtension->VolumeFcb.Flags = FAT_FCB_VOLUME;
    VolumeExtension->VolumeFcb.ReferenceCount = 1;

    //
    // The device has finished initializing and is ready to accept requests.
    //

    VolumeDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;

    //
    // Return the device object to the caller.
    //

    *ReturnedVolumeDeviceObject = VolumeDeviceObject;

    return STATUS_SUCCESS;
}

VOID
FatxDeleteVolumeDevice(
    IN PDEVICE_OBJECT VolumeDeviceObject
    )
/*++

Routine Description:

    This routine deletes an instance of a volume device object.

Arguments:

    VolumeDeviceObject - Specifies the volume device object to delete.

Return Value:

    Status of operation.

--*/
{
    PFAT_VOLUME_EXTENSION VolumeExtension;

    VolumeExtension = (PFAT_VOLUME_EXTENSION)VolumeDeviceObject->DeviceExtension;

    //
    // There shouldn't be any open files for this volume.
    //

    ASSERT(VolumeExtension->FileObjectCount == 0);

    //
    // Delete the volume's cluster cache lock.
    //

    RtlDeleteCriticalSection(&VolumeExtension->ClusterCacheMutex);

    //
    // Delete any file control blocks created by the volume.
    //

    if (VolumeExtension->RootDirectoryFcb != NULL) {
        FatxDereferenceFcb(VolumeExtension->RootDirectoryFcb);
    }

    //
    // Invalidate any file system cache buffers for this device.
    //

    FscInvalidateDevice(&VolumeExtension->CacheExtension);

    //
    // Release the reference on the target device object.
    //

    if (VolumeExtension->TargetDeviceObject != NULL) {
        ObDereferenceObject(VolumeExtension->TargetDeviceObject);
    }

    //
    // Delete the device object.
    //

    IoDeleteDevice(VolumeDeviceObject);
}

NTSTATUS
FatxProcessBootSector(
    IN PDEVICE_OBJECT VolumeDeviceObject,
    IN PIRP Irp,
    IN PLARGE_INTEGER PartitionLength
    )
/*++

Routine Description:

    This routine reads the boot sector for the supplied volume device object,
    validates its structure, and caches any interesting information in the
    device extension.

Arguments:

    VolumeDeviceObject - Specifies the volume device object to mount.

    Irp - Specifies the packet that describes the I/O request.

    PartitionLength - Specifies the length of the partition in bytes.

Return Value:

    Status of operation.

--*/
{
    NTSTATUS status;
    PFAT_VOLUME_EXTENSION VolumeExtension;
    PVOID CacheBuffer;
    ULONG StructureAlignment;
    ULONG NumberOfReservedBytes;
    PFAT_VOLUME_METADATA VolumeMetadata;
    ULONG RootDirectoryFirstCluster;
    ULONG NumberOfClustersLimit;
    ULONG NumberOfBytesPerFat;
    ULONGLONG NumberOfUnreservedBytes;
    ULONGLONG NumberOfFileAreaBytes;

    VolumeExtension = (PFAT_VOLUME_EXTENSION)VolumeDeviceObject->DeviceExtension;
    CacheBuffer = NULL;

    //
    // Compute the alignment between the various file system structures.
    // Everything should be page aligned in order to maximum file system cache
    // efficiency.
    //

    StructureAlignment = ROUND_TO_PAGES(VolumeExtension->SectorSize);

    //
    // Compute the number of reserved bytes for the volume.
    //
    // We only need to reserve enough space for the volume metadata block.
    //

    NumberOfReservedBytes = PAGE_SIZE;

    if (NumberOfReservedBytes < StructureAlignment) {
        NumberOfReservedBytes = StructureAlignment;
    }

    //
    // Verify that the partition has enough space to contain the reserved bytes.
    //

    if ((ULONGLONG)NumberOfReservedBytes >= (ULONGLONG)PartitionLength->QuadPart) {
        status = STATUS_UNRECOGNIZED_VOLUME;
        goto CleanupAndExit;
    }

    //
    // Map the volume metadata block into the cache.
    //

    status = FscMapBuffer(&VolumeExtension->CacheExtension, Irp, 0, FALSE,
        &CacheBuffer);

    if (!NT_SUCCESS(status)) {
        status = STATUS_UNRECOGNIZED_VOLUME;
        goto CleanupAndExit;
    }

    VolumeMetadata = (PFAT_VOLUME_METADATA)CacheBuffer;

    //
    // Verify that the signature is valid.
    //

    if (VolumeMetadata->Signature != FAT_VOLUME_SIGNATURE) {
        FatxDbgPrint(("FATX: found unrecognized signature\n"));
        status = STATUS_UNRECOGNIZED_VOLUME;
        goto CleanupAndExit;
    }

    //
    // Verify that the sectors per cluster looks reasonable.
    //

    switch (VolumeMetadata->SectorsPerCluster) {

        case 1:
        case 2:
        case 4:
        case 8:
        case 16:
        case 32:
        case 64:
        case 128:
            break;

        default:
            FatxDbgPrint(("FATX: found invalid sectors per cluster\n"));
            status = STATUS_UNRECOGNIZED_VOLUME;
            goto CleanupAndExit;
    }

    VolumeExtension->BytesPerCluster = VolumeMetadata->SectorsPerCluster <<
        VolumeExtension->SectorShift;
    VolumeExtension->ClusterShift =
        RtlFindFirstSetRightMember(VolumeExtension->BytesPerCluster);

    //
    // Copy the serial number from to the volume extension.
    //

    VolumeExtension->SerialNumber = VolumeMetadata->SerialNumber;

    //
    // Remember the root directory cluster and unmap the volume metadata block.
    //

    RootDirectoryFirstCluster = VolumeMetadata->RootDirFirstCluster;

    FscUnmapBuffer(CacheBuffer);
    CacheBuffer = NULL;

    //
    // Verify that the number of bytes per cluster is greater than or equal to
    // the structure alignment requirement.
    //

    if (VolumeExtension->BytesPerCluster < StructureAlignment) {
        FatxDbgPrint(("FATX: found too small of cluster size\n"));
        status = STATUS_UNRECOGNIZED_VOLUME;
        goto CleanupAndExit;
    }

    //
    // For volumes with more than 64K of clusters, we use 32-bit file allocation
    // table entries.  Otherwise, we use 16-bit file allocation table entries.
    // This is not FAT16 as defined by DOS; we still have a FAT32 style BIOS
    // parameter block, but the FAT is in a "compressed" format.
    //
    // Note that we base the test on the limit of the number of clusters for the
    // entire partition.  The same test is performed in the FAT format code.
    //
    // The below number isn't exactly 64K, because we need to avoid the magic
    // cluster numbers that mark free or end of file clusters.
    //

    NumberOfClustersLimit = (ULONG)((ULONGLONG)PartitionLength->QuadPart >>
        VolumeExtension->ClusterShift) + FAT_RESERVED_FAT_ENTRIES;

    if (NumberOfClustersLimit < FAT_CLUSTER16_RESERVED) {
        VolumeExtension->Flags |= FAT_VOLUME_FAT16X;
        VolumeExtension->FatEntryShift = 1;
        NumberOfBytesPerFat = NumberOfClustersLimit * sizeof(USHORT);
    } else {
        VolumeExtension->FatEntryShift = 2;
        NumberOfBytesPerFat = NumberOfClustersLimit * sizeof(ULONG);
    }

    NumberOfBytesPerFat = (NumberOfBytesPerFat + (StructureAlignment - 1)) &
        ~(StructureAlignment - 1);

    //
    // Compute the number of bytes that haven't been reserved above.
    //

    NumberOfUnreservedBytes = (ULONGLONG)PartitionLength->QuadPart -
        NumberOfReservedBytes;

    //
    // Verify that the partition has enough space to contain the FAT.
    //

    if ((ULONGLONG)NumberOfBytesPerFat >= NumberOfUnreservedBytes) {
        FatxDbgPrint(("FATX: volume too small to hold the FAT\n"));
        status = STATUS_UNRECOGNIZED_VOLUME;
        goto CleanupAndExit;
    }

    //
    // Compute the number of clusters available on the volume.
    //

    NumberOfFileAreaBytes = NumberOfUnreservedBytes - NumberOfBytesPerFat;

    VolumeExtension->NumberOfClusters = (ULONG)(NumberOfFileAreaBytes >>
        VolumeExtension->ClusterShift);

    //
    // Compute the physical byte offsets of the FAT and the first cluster.
    //

    VolumeExtension->FatByteOffset = NumberOfReservedBytes;
    VolumeExtension->FileAreaByteOffset = VolumeExtension->FatByteOffset +
        NumberOfBytesPerFat;

    //
    // When dealing with the file system cache, other parts of the file system
    // assume that the file allocation table or file area start on a page
    // aligned byte offset.  Also, the number of bytes per cluster must be a
    // multiple of the page size.
    //

    ASSERT(BYTE_OFFSET(VolumeExtension->FatByteOffset) == 0);
    ASSERT(BYTE_OFFSET(VolumeExtension->FileAreaByteOffset) == 0);
    ASSERT(BYTE_OFFSET(VolumeExtension->BytesPerCluster) == 0);

    //
    // Create the file control block for the root directory.
    //

    status = FatxCreateFcb(NULL, RootDirectoryFirstCluster, NULL, 0,
        &VolumeExtension->RootDirectoryFcb);

    if (!NT_SUCCESS(status)) {
        goto CleanupAndExit;
    }

    status = STATUS_SUCCESS;

CleanupAndExit:
    if (CacheBuffer != NULL) {
        FscUnmapBuffer(CacheBuffer);
    }

    return status;
}

NTSTATUS
FatxMountVolume(
    IN PDEVICE_OBJECT TargetDeviceObject
    )
/*++

Routine Description:

    This routine is called by the I/O manager to attempt to mount this file
    system.

Arguments:

    TargetDeviceObject - Specifies the device object to attempt to mount.

Return Value:

    Status of operation.

--*/
{
    NTSTATUS status;
    PIRP Irp;
    PDEVICE_OBJECT VolumeDeviceObject;
    PFAT_VOLUME_EXTENSION VolumeExtension;
    DISK_GEOMETRY DiskGeometry;
    PARTITION_INFORMATION PartitionInformation;

    Irp = NULL;
    VolumeDeviceObject = NULL;

    FatxAcquireGlobalMutexExclusive();

    //
    // Get the drive geometry from the device.
    //

    status = IoSynchronousDeviceIoControlRequest(IOCTL_DISK_GET_DRIVE_GEOMETRY,
        TargetDeviceObject, NULL, 0, &DiskGeometry, sizeof(DISK_GEOMETRY), NULL,
        FALSE);

    if (!NT_SUCCESS(status)) {
        goto CleanupAndExit;
    }

    //
    // Get the partition information from the device.
    //

    status = IoSynchronousDeviceIoControlRequest(IOCTL_DISK_GET_PARTITION_INFO,
        TargetDeviceObject, NULL, 0, &PartitionInformation,
        sizeof(PARTITION_INFORMATION), NULL, FALSE);

    if (!NT_SUCCESS(status)) {
        goto CleanupAndExit;
    }

    //
    // Create an I/O request packet in order to submit requests to the target
    // device.
    //

    Irp = IoAllocateIrp(TargetDeviceObject->StackSize);

    if (Irp == NULL) {
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto CleanupAndExit;
    }

    //
    // Create the volume's device object.
    //

    status = FatxCreateVolumeDevice(TargetDeviceObject, &DiskGeometry,
        &PartitionInformation, &VolumeDeviceObject);

    if (!NT_SUCCESS(status)) {
        goto CleanupAndExit;
    }

    VolumeExtension = (PFAT_VOLUME_EXTENSION)VolumeDeviceObject->DeviceExtension;

    //
    // Process the boot sector for the volume.
    //

    status = FatxProcessBootSector(VolumeDeviceObject, Irp,
        &PartitionInformation.PartitionLength);

    if (!NT_SUCCESS(status)) {
        goto CleanupAndExit;
    }

    //
    // Initialize the allocation support for the volume.
    //

    status = FatxInitializeAllocationSupport(VolumeExtension, Irp);

    if (!NT_SUCCESS(status)) {
        goto CleanupAndExit;
    }

    //
    // Connect the volume device object to the target device object and return
    // success.
    //

    TargetDeviceObject->MountedOrSelfDevice = VolumeDeviceObject;
    VolumeDeviceObject = NULL;

    status = STATUS_SUCCESS;

CleanupAndExit:
    if (VolumeDeviceObject != NULL) {
        FatxDeleteVolumeDevice(VolumeDeviceObject);
    }

    if (Irp != NULL) {
        IoFreeIrp(Irp);
    }

    FatxReleaseGlobalMutex();

    return status;
}
