/*
 * COPYRIGHT:   See COPYING in the top level directory
 * PROJECT:     ReactOS VFAT filesystem library
 * FILE:        lib\fslib\vfatlib\vfatlib.c
 * PURPOSE:     Main API
 * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
 *              Aleksey Bragin (aleksey@reactos.org)
 *              Hermes Belusca-Maito (hermes.belusca@sfr.fr)
 */
/* fsck.fat.c - User interface

   Copyright (C) 1993 Werner Almesberger <werner.almesberger@lrc.di.epfl.ch>
   Copyright (C) 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
   Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program. If not, see <http://www.gnu.org/licenses/>.

   The complete text of the GNU General Public License
   can be found in /usr/share/common-licenses/GPL-3 file.
*/

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

#include "vfatlib.h"

#define NDEBUG
#include <debug.h>


/* GLOBALS & FUNCTIONS ********************************************************/

PFMIFSCALLBACK ChkdskCallback = NULL;
ULONG FsCheckFlags;
PVOID FsCheckMemQueue;
ULONG FsCheckTotalFiles;

NTSTATUS
NTAPI
VfatFormat(IN PUNICODE_STRING DriveRoot,
           IN FMIFS_MEDIA_FLAG MediaFlag,
           IN PUNICODE_STRING Label,
           IN BOOLEAN QuickFormat,
           IN ULONG ClusterSize,
           IN PFMIFSCALLBACK Callback)
{
    OBJECT_ATTRIBUTES ObjectAttributes;
    DISK_GEOMETRY DiskGeometry;
    IO_STATUS_BLOCK Iosb;
    HANDLE FileHandle;
    PARTITION_INFORMATION PartitionInfo;
    FORMAT_CONTEXT Context;
    NTSTATUS Status, LockStatus;

    DPRINT("VfatFormat(DriveRoot '%wZ')\n", DriveRoot);

    Context.TotalSectorCount = 0;
    Context.CurrentSectorCount = 0;
    Context.Callback = Callback;
    Context.Success = FALSE;
    Context.Percent = 0;

    InitializeObjectAttributes(&ObjectAttributes,
                               DriveRoot,
                               0,
                               NULL,
                               NULL);

    Status = NtOpenFile(&FileHandle,
                        FILE_GENERIC_READ | FILE_GENERIC_WRITE | SYNCHRONIZE,
                        &ObjectAttributes,
                        &Iosb,
                        FILE_SHARE_READ,
                        FILE_SYNCHRONOUS_IO_ALERT);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("NtOpenFile() failed with status 0x%.08x\n", Status);
        return Status;
    }

    Status = NtDeviceIoControlFile(FileHandle,
                                   NULL,
                                   NULL,
                                   NULL,
                                   &Iosb,
                                   IOCTL_DISK_GET_DRIVE_GEOMETRY,
                                   NULL,
                                   0,
                                   &DiskGeometry,
                                   sizeof(DISK_GEOMETRY));
    if (!NT_SUCCESS(Status))
    {
        DPRINT("IOCTL_DISK_GET_DRIVE_GEOMETRY failed with status 0x%.08x\n", Status);
        NtClose(FileHandle);
        return Status;
    }

    if (DiskGeometry.MediaType == FixedMedia)
    {
        DPRINT("Cylinders %I64d\n", DiskGeometry.Cylinders.QuadPart);
        DPRINT("TracksPerCylinder %ld\n", DiskGeometry.TracksPerCylinder);
        DPRINT("SectorsPerTrack %ld\n", DiskGeometry.SectorsPerTrack);
        DPRINT("BytesPerSector %ld\n", DiskGeometry.BytesPerSector);
        DPRINT("DiskSize %I64d\n",
               DiskGeometry.Cylinders.QuadPart *
               (ULONGLONG)DiskGeometry.TracksPerCylinder *
               (ULONGLONG)DiskGeometry.SectorsPerTrack *
               (ULONGLONG)DiskGeometry.BytesPerSector);

        Status = NtDeviceIoControlFile(FileHandle,
                                       NULL,
                                       NULL,
                                       NULL,
                                       &Iosb,
                                       IOCTL_DISK_GET_PARTITION_INFO,
                                       NULL,
                                       0,
                                       &PartitionInfo,
                                       sizeof(PARTITION_INFORMATION));
        if (!NT_SUCCESS(Status))
        {
            DPRINT("IOCTL_DISK_GET_PARTITION_INFO failed with status 0x%.08x\n", Status);
            NtClose(FileHandle);
            return Status;
        }
    }
    else
    {
        PartitionInfo.PartitionType = 0;
        PartitionInfo.StartingOffset.QuadPart = 0ULL;
        PartitionInfo.PartitionLength.QuadPart =
            DiskGeometry.Cylinders.QuadPart *
            (ULONGLONG)DiskGeometry.TracksPerCylinder *
            (ULONGLONG)DiskGeometry.SectorsPerTrack *
            (ULONGLONG)DiskGeometry.BytesPerSector;
        PartitionInfo.HiddenSectors = 0;
        PartitionInfo.PartitionNumber = 0;
        PartitionInfo.BootIndicator = FALSE;
        PartitionInfo.RewritePartition = FALSE;
        PartitionInfo.RecognizedPartition = FALSE;
    }
    
    /* If it already has a FAT FS, we'll use that type.
     * If it doesn't, we will determine the FAT type based on size and offset */
    if (PartitionInfo.PartitionType != PARTITION_FAT_12 &&
        PartitionInfo.PartitionType != PARTITION_FAT_16 &&
        PartitionInfo.PartitionType != PARTITION_HUGE &&
        PartitionInfo.PartitionType != PARTITION_XINT13 &&
        PartitionInfo.PartitionType != PARTITION_FAT32 &&
        PartitionInfo.PartitionType != PARTITION_FAT32_XINT13)
    {
        /* Determine the correct type based upon size and offset (copied from usetup) */
        if (PartitionInfo.PartitionLength.QuadPart < (4200LL * 1024LL))
        {
            /* FAT12 CHS partition (disk is smaller than 4.1MB) */
            PartitionInfo.PartitionType = PARTITION_FAT_12;
        }
        else if (PartitionInfo.StartingOffset.QuadPart < (1024LL * 255LL * 63LL * 512LL))
        {
            /* Partition starts below the 8.4GB boundary ==> CHS partition */
            
            if (PartitionInfo.PartitionLength.QuadPart < (32LL * 1024LL * 1024LL))
            {
                /* FAT16 CHS partition (partition size < 32MB) */
                PartitionInfo.PartitionType = PARTITION_FAT_16;
            }
            else if (PartitionInfo.PartitionLength.QuadPart < (512LL * 1024LL * 1024LL))
            {
                /* FAT16 CHS partition (partition size < 512MB) */
                PartitionInfo.PartitionType = PARTITION_HUGE;
            }
            else
            {
                /* FAT32 CHS partition (partition size >= 512MB) */
                PartitionInfo.PartitionType = PARTITION_FAT32;
            }
        }
        else
        {
            /* Partition starts above the 8.4GB boundary ==> LBA partition */
            
            if (PartitionInfo.PartitionLength.QuadPart < (512LL * 1024LL * 1024LL))
            {
                /* FAT16 LBA partition (partition size < 512MB) */
                PartitionInfo.PartitionType = PARTITION_XINT13;
            }
            else
            {
                /* FAT32 LBA partition (partition size >= 512MB) */
                PartitionInfo.PartitionType = PARTITION_FAT32_XINT13;
            }
        }
    }

    DPRINT("PartitionType 0x%x\n", PartitionInfo.PartitionType);
    DPRINT("StartingOffset %I64d\n", PartitionInfo.StartingOffset.QuadPart);
    DPRINT("PartitionLength %I64d\n", PartitionInfo.PartitionLength.QuadPart);
    DPRINT("HiddenSectors %lu\n", PartitionInfo.HiddenSectors);
    DPRINT("PartitionNumber %d\n", PartitionInfo.PartitionNumber);
    DPRINT("BootIndicator 0x%x\n", PartitionInfo.BootIndicator);
    DPRINT("RewritePartition %d\n", PartitionInfo.RewritePartition);
    DPRINT("RecognizedPartition %d\n", PartitionInfo.RecognizedPartition);

    if (Callback != NULL)
    {
        Context.Percent = 0;
        Callback (PROGRESS, 0, (PVOID)&Context.Percent);
    }

    LockStatus = NtFsControlFile(FileHandle,
                                 NULL,
                                 NULL,
                                 NULL,
                                 &Iosb,
                                 FSCTL_LOCK_VOLUME,
                                 NULL,
                                 0,
                                 NULL,
                                 0);
    if (!NT_SUCCESS(LockStatus))
    {
        DPRINT1("WARNING: Failed to lock volume for formatting! Format may fail! (Status: 0x%x)\n", LockStatus);
    }

    if (PartitionInfo.PartitionType == PARTITION_FAT_12)
    {
        /* FAT12 */
        Status = Fat12Format(FileHandle,
                             &PartitionInfo,
                             &DiskGeometry,
                             Label,
                             QuickFormat,
                             ClusterSize,
                             &Context);
    }
    else if (PartitionInfo.PartitionType == PARTITION_FAT_16 ||
             PartitionInfo.PartitionType == PARTITION_HUGE ||
             PartitionInfo.PartitionType == PARTITION_XINT13)
    {
        /* FAT16 */
        Status = Fat16Format(FileHandle,
                             &PartitionInfo,
                             &DiskGeometry,
                             Label,
                             QuickFormat,
                             ClusterSize,
                             &Context);
    }
    else if (PartitionInfo.PartitionType == PARTITION_FAT32 ||
             PartitionInfo.PartitionType == PARTITION_FAT32_XINT13)
    {
        /* FAT32 */
        Status = Fat32Format(FileHandle,
                             &PartitionInfo,
                             &DiskGeometry,
                             Label,
                             QuickFormat,
                             ClusterSize,
                             &Context);
    }
    else
    {
        Status = STATUS_INVALID_PARAMETER;
    }

    /* Attempt to dismount formatted volume */
    LockStatus = NtFsControlFile(FileHandle,
                                 NULL,
                                 NULL,
                                 NULL,
                                 &Iosb,
                                 FSCTL_DISMOUNT_VOLUME,
                                 NULL,
                                 0,
                                 NULL,
                                 0);
    if (!NT_SUCCESS(LockStatus))
    {
        DPRINT1("Failed to umount volume (Status: 0x%x)\n", LockStatus);
    }

    LockStatus = NtFsControlFile(FileHandle,
                                 NULL,
                                 NULL,
                                 NULL,
                                 &Iosb,
                                 FSCTL_UNLOCK_VOLUME,
                                 NULL,
                                 0,
                                 NULL,
                                 0);
    if (!NT_SUCCESS(LockStatus))
    {
        DPRINT1("Failed to unlock volume (Status: 0x%x)\n", LockStatus);
    }

    NtClose(FileHandle);

    if (Callback != NULL)
    {
        Context.Success = (BOOLEAN)(NT_SUCCESS(Status));
        Callback(DONE, 0, (PVOID)&Context.Success);
    }

    DPRINT("VfatFormat() done. Status 0x%.08x\n", Status);

    return Status;
}


VOID
UpdateProgress(PFORMAT_CONTEXT Context,
               ULONG Increment)
{
    ULONG NewPercent;

    Context->CurrentSectorCount += (ULONGLONG)Increment;

    NewPercent = (Context->CurrentSectorCount * 100ULL) / Context->TotalSectorCount;

    if (NewPercent > Context->Percent)
    {
        Context->Percent = NewPercent;
        if (Context->Callback != NULL)
        {
            Context->Callback(PROGRESS, 0, &Context->Percent);
        }
    }
}


VOID
VfatPrintV(PCHAR Format, va_list args)
{
    TEXTOUTPUT TextOut;
    CHAR TextBuf[512];

    _vsnprintf(TextBuf, sizeof(TextBuf), Format, args);

    /* Prepare parameters */
    TextOut.Lines = 1;
    TextOut.Output = TextBuf;

    DPRINT1("VfatPrint -- %s", TextBuf);

    /* Do the callback */
    if (ChkdskCallback)
        ChkdskCallback(OUTPUT, 0, &TextOut);
}


VOID
VfatPrint(PCHAR Format, ...)
{
    va_list args;

    va_start(args, Format);
    VfatPrintV(Format, args);
    va_end(args);
}


NTSTATUS
NTAPI
VfatChkdsk(IN PUNICODE_STRING DriveRoot,
           IN BOOLEAN FixErrors,
           IN BOOLEAN Verbose,
           IN BOOLEAN CheckOnlyIfDirty,
           IN BOOLEAN ScanDrive,
           IN PFMIFSCALLBACK Callback)
{
    BOOLEAN verify;
    BOOLEAN salvage_files;
    ULONG free_clusters;
    DOS_FS fs;

    RtlZeroMemory(&fs, sizeof(fs));

    /* Store callback pointer */
    ChkdskCallback = Callback;
    FsCheckMemQueue = NULL;

    /* Set parameters */
    FsCheckFlags = 0;
    if (Verbose)
        FsCheckFlags |= FSCHECK_VERBOSE;
    if (FixErrors)
        FsCheckFlags |= FSCHECK_READ_WRITE;

    FsCheckTotalFiles = 0;

    verify = TRUE;
    salvage_files = TRUE;

    /* Open filesystem and lock it */
    fs_open(DriveRoot, FsCheckFlags & FSCHECK_READ_WRITE);

    if (CheckOnlyIfDirty && !fs_isdirty())
    {
        /* Unlock volume if required */
        if (FsCheckFlags & FSCHECK_READ_WRITE)
            fs_lock(FALSE);

        /* No need to check FS */
        return (fs_close(FALSE) == 0 ? STATUS_SUCCESS : STATUS_DISK_CORRUPT_ERROR);
    }

    read_boot(&fs);

    if (verify)
        VfatPrint("Starting check/repair pass.\n");

    while (read_fat(&fs), scan_root(&fs))
        qfree(&FsCheckMemQueue);

    if (ScanDrive)
        fix_bad(&fs);

    if (salvage_files)
        reclaim_file(&fs);
    else
        reclaim_free(&fs);

    free_clusters = update_free(&fs);
    file_unused();
    qfree(&FsCheckMemQueue);
    if (verify)
    {
        FsCheckTotalFiles = 0;
        VfatPrint("Starting verification pass.\n");
        read_fat(&fs);
        scan_root(&fs);
        reclaim_free(&fs);
        qfree(&FsCheckMemQueue);
    }

    if (fs_changed())
    {
        if (FsCheckFlags & FSCHECK_READ_WRITE)
        {
            if (FsCheckFlags & FSCHECK_INTERACTIVE)
            {
                FixErrors = get_key("yn", "Perform changes ? (y/n)") == 'y';
                if (FixErrors)
                    FsCheckFlags |= FSCHECK_READ_WRITE;
                else
                    FsCheckFlags &= ~FSCHECK_READ_WRITE;
            }
            else
                VfatPrint("Performing changes.\n");
        }
        else
        {
            VfatPrint("Leaving filesystem unchanged.\n");
        }
    }

    VfatPrint("%wZ: %u files, %lu/%lu clusters\n", DriveRoot,
        FsCheckTotalFiles, fs.data_clusters - free_clusters, fs.data_clusters);

    if (FsCheckFlags & FSCHECK_READ_WRITE)
    {
        /* Dismount the volume */
        fs_dismount();

        /* Unlock the volume */
        fs_lock(FALSE);
    }

    // https://technet.microsoft.com/en-us/library/cc730714.aspx
    // https://support.microsoft.com/en-us/kb/265533

    /* Close the volume */
    return (fs_close(FsCheckFlags & FSCHECK_READ_WRITE) == 0 ? STATUS_SUCCESS : STATUS_DISK_CORRUPT_ERROR);
}

/* EOF */