/*
 * COPYRIGHT:        See COPYING in the top level directory
 * PROJECT:          ReactOS File System Recognizer
 * FILE:             drivers/filesystems/fs_rec/blockdev.c
 * PURPOSE:          Generic Helper Functions
 * PROGRAMMER:       Alex Ionescu (alex.ionescu@reactos.org)
 *                   Eric Kohl
 */

/* INCLUDES *****************************************************************/

#include "fs_rec.h"

#include <ntdddisk.h>
#include <ntddcdrm.h>

#define NDEBUG
#include <debug.h>

/* FUNCTIONS ****************************************************************/

BOOLEAN
NTAPI
FsRecGetDeviceSectors(IN PDEVICE_OBJECT DeviceObject,
                      IN ULONG SectorSize,
                      OUT PLARGE_INTEGER SectorCount)
{
    PARTITION_INFORMATION PartitionInfo;
    IO_STATUS_BLOCK IoStatusBlock;
    KEVENT Event;
    PIRP Irp;
    NTSTATUS Status;
    ULONG Remainder;
    PAGED_CODE();

    /* Only needed for disks */
    if (DeviceObject->DeviceType != FILE_DEVICE_DISK) return FALSE;

    /* Build the information IRP */
    KeInitializeEvent(&Event, SynchronizationEvent, FALSE);
    Irp = IoBuildDeviceIoControlRequest(IOCTL_DISK_GET_PARTITION_INFO,
                                        DeviceObject,
                                        NULL,
                                        0,
                                        &PartitionInfo,
                                        sizeof(PARTITION_INFORMATION),
                                        FALSE,
                                        &Event,
                                        &IoStatusBlock);
    if (!Irp) return FALSE;

    /* Override verification */
    IoGetNextIrpStackLocation(Irp)->Flags |= SL_OVERRIDE_VERIFY_VOLUME;

    /* Do the request */
    Status = IoCallDriver(DeviceObject, Irp);
    if (Status == STATUS_PENDING)
    {
        /* Wait for completion */
        KeWaitForSingleObject(&Event,
                              Executive,
                              KernelMode,
                              FALSE,
                              NULL);
        Status = IoStatusBlock.Status;
    }

    /* Fail if we couldn't get the data */
    if (!NT_SUCCESS(Status)) return FALSE;

    /* Otherwise, return the number of sectors */
    *SectorCount = RtlExtendedLargeIntegerDivide(PartitionInfo.PartitionLength,
                                                 SectorSize,
                                                 &Remainder);
    return TRUE;
}

BOOLEAN
NTAPI
FsRecGetDeviceSectorSize(IN PDEVICE_OBJECT DeviceObject,
                         OUT PULONG SectorSize)
{
    DISK_GEOMETRY DiskGeometry;
    IO_STATUS_BLOCK IoStatusBlock;
    KEVENT Event;
    PIRP Irp;
    NTSTATUS Status;
    ULONG ControlCode;
    PAGED_CODE();

    /* Check what device we have */
    switch (DeviceObject->DeviceType)
    {
        case FILE_DEVICE_CD_ROM:

            /* Use the CD IOCTL */
            ControlCode = IOCTL_CDROM_GET_DRIVE_GEOMETRY;
            break;

        case FILE_DEVICE_DISK:

            /* Use the Disk IOCTL */
            ControlCode = IOCTL_DISK_GET_DRIVE_GEOMETRY;
            break;

        default:

            /* Invalid device type */
            return FALSE;
    }

    /* Build the information IRP */
    KeInitializeEvent(&Event, SynchronizationEvent, FALSE);
    Irp = IoBuildDeviceIoControlRequest(ControlCode,
                                        DeviceObject,
                                        NULL,
                                        0,
                                        &DiskGeometry,
                                        sizeof(DISK_GEOMETRY),
                                        FALSE,
                                        &Event,
                                        &IoStatusBlock);
    if (!Irp) return FALSE;

    /* Override verification */
    IoGetNextIrpStackLocation(Irp)->Flags |= SL_OVERRIDE_VERIFY_VOLUME;

    /* Do the request */
    Status = IoCallDriver(DeviceObject, Irp);
    if (Status == STATUS_PENDING)
    {
        /* Wait for completion */
        KeWaitForSingleObject(&Event,
                              Executive,
                              KernelMode,
                              FALSE,
                              NULL);
        Status = IoStatusBlock.Status;
    }

    /* Fail if we couldn't get the data */
    if (!NT_SUCCESS(Status)) return FALSE;

    /* Return the sector size if it's valid */
    if (!DiskGeometry.BytesPerSector) return FALSE;
    *SectorSize = DiskGeometry.BytesPerSector;
    return TRUE;
}

BOOLEAN
NTAPI
FsRecReadBlock(IN PDEVICE_OBJECT DeviceObject,
               IN PLARGE_INTEGER Offset,
               IN ULONG Length,
               IN ULONG SectorSize,
               IN OUT PVOID *Buffer,
               OUT PBOOLEAN DeviceError OPTIONAL)
{
    IO_STATUS_BLOCK IoStatusBlock;
    KEVENT Event;
    PIRP Irp;
    NTSTATUS Status;
    PAGED_CODE();

    /* Assume failure */
    if (DeviceError) *DeviceError = FALSE;

    /* Check if the caller requested too little */
    if (Length < SectorSize)
    {
        /* Read at least the sector size */
        Length = SectorSize;
    }
    else
    {
        /* Otherwise, just round up the request to sector size */
        Length = ROUND_UP(Length, SectorSize);
    }

    /* Check if the caller gave us a buffer */
    if (!*Buffer)
    {
        /* He didn't, allocate one */
        *Buffer = ExAllocatePoolWithTag(NonPagedPool,
                                        ROUND_TO_PAGES(Length),
                                        FSREC_TAG);
        if (!*Buffer) return FALSE;
    }

    /* Build the IRP */
    KeInitializeEvent(&Event, SynchronizationEvent, FALSE);
    Irp = IoBuildSynchronousFsdRequest(IRP_MJ_READ,
                                       DeviceObject,
                                       *Buffer,
                                       Length,
                                       Offset,
                                       &Event,
                                       &IoStatusBlock);
    if (!Irp) return FALSE;

    /* Override verification */
    IoGetNextIrpStackLocation(Irp)->Flags |= SL_OVERRIDE_VERIFY_VOLUME;

    /* Do the request */
    Status = IoCallDriver(DeviceObject, Irp);
    if (Status == STATUS_PENDING)
    {
        /* Wait for completion */
        KeWaitForSingleObject(&Event,
                              Executive,
                              KernelMode,
                              FALSE,
                              NULL);
        Status = IoStatusBlock.Status;
    }

    /* Check if we couldn't get the data */
    if (!NT_SUCCESS(Status))
    {
        /* Check if caller wanted to know about the device and fail */
        if (DeviceError) *DeviceError = TRUE;
        return FALSE;
    }

    /* All went well */
    return TRUE;
}