#include "DriveVolume.h"


DriveVolume::DriveVolume()
{
    Handle = INVALID_HANDLE_VALUE;
    BitmapDetail = NULL;
    ZeroMemory(&Geometry, sizeof(Geometry));
}


DriveVolume::~DriveVolume ()
{
    Close ();
    Directories.clear ();
    Files.clear ();
    return;
}


void DriveVolume::Close (void)
{
    if (Handle != INVALID_HANDLE_VALUE)
    {
        CloseHandle (Handle);
        Handle = INVALID_HANDLE_VALUE;
    }

    if (BitmapDetail != NULL)
    {
        HeapFree(GetProcessHeap(), 0, BitmapDetail);
        BitmapDetail = NULL;
    }

    return;
}


// "Name" should be the drive letter followed by a colon. ie, "c:"
// It's a string to allow for further expansion (ie, defragging over the network?)
// or some other baloney reason
bool DriveVolume::Open (wstring Name)
{
    wchar_t FileName[100];
    bool ReturnVal;

    swprintf (FileName, L"\\\\.\\%s", Name.c_str());
    RootPath = Name.c_str();
    RootPath += L"\\";

    Handle = CreateFileW
    (
        FileName,
        MAXIMUM_ALLOWED,                          // access
        FILE_SHARE_READ | FILE_SHARE_WRITE,       // share type
        NULL,                                     // security descriptor
        OPEN_EXISTING,                            // open type
        0,                                     // attributes (none)
        NULL                                      // template
    );

    if (Handle == INVALID_HANDLE_VALUE)
        ReturnVal = false;
    else
    {
        wchar_t  VolName[64];
        DWORD VolSN;
        DWORD VolMaxFileLen;
        DWORD FSFlags;
        wchar_t  FSName[64];
        BOOL  Result;

        ReturnVal = true;
        Result = GetVolumeInformationW
        (
            RootPath.c_str(),
            VolName,
            sizeof (VolName),
            &VolSN,
            &VolMaxFileLen,
            &FSFlags,
            FSName,
            sizeof (FSName)
        );

        if (Result)
        {
            wchar_t SerialText[10];

            VolInfo.FileSystem = FSName;
            VolInfo.MaxNameLen = VolMaxFileLen;
            VolInfo.Name       = VolName;

            swprintf (SerialText, L"%x-%x", (VolSN & 0xffff0000) >> 16,
                VolSN & 0x0000ffff);

            _wcsupr (SerialText);
            VolInfo.Serial     = SerialText;
        }
        else
        {
            VolInfo.FileSystem = L"(Unknown)";
            VolInfo.MaxNameLen = 255;
            VolInfo.Name       = L"(Unknown)";
            VolInfo.Serial     = L"(Unknown)";
        }
    }

    return (ReturnVal);
}


bool DriveVolume::ObtainInfo (void)
{
    BOOL Result;
    DWORD BytesGot;
    uint64 nan;

    BytesGot = 0;
    ZeroMemory (&Geometry, sizeof (Geometry));
    Result = DeviceIoControl
    (
        Handle,
        IOCTL_DISK_GET_DRIVE_GEOMETRY,
        NULL,
        0,
        &Geometry,
        sizeof (Geometry),
        &BytesGot,
        NULL
    );

    // Call failed? Aww :(
    if (!Result)
        return (false);

    // Get cluster size
    DWORD SectorsPerCluster;
    DWORD BytesPerSector;
    DWORD FreeClusters;
    DWORD TotalClusters;

    Result = GetDiskFreeSpaceW
    (
        RootPath.c_str(),
        &SectorsPerCluster,
        &BytesPerSector,
        &FreeClusters,
        &TotalClusters
    );

    // Failed? Weird.
    if (!Result)
        return (false);

    VolInfo.ClusterSize = SectorsPerCluster * BytesPerSector;

    Result = GetDiskFreeSpaceExW
    (
        RootPath.c_str(),
        (PULARGE_INTEGER)&nan,
        (PULARGE_INTEGER)&VolInfo.TotalBytes,
        (PULARGE_INTEGER)&VolInfo.FreeBytes
    );

    return (true);
}


// Get bitmap, several clusters at a time ...
#define CLUSTERS 4096
bool DriveVolume::GetBitmap (void)
{
    STARTING_LCN_INPUT_BUFFER StartingLCN;
    VOLUME_BITMAP_BUFFER *Bitmap = NULL;
    uint32 BitmapSize;
    DWORD BytesReturned;
    BOOL Result;

    StartingLCN.StartingLcn.QuadPart = 0;

    // Allocate buffer
    // Call FSCTL_GET_VOLUME_BITMAP once with a very small buffer
    // This will leave the total number of clusters in Bitmap->BitmapSize and we can
    // then correctly allocate based off that
    // I suppose this won't work if your drive has only 40 clusters on it or so :)
    BitmapSize = sizeof (VOLUME_BITMAP_BUFFER) + 4;
    Bitmap = (VOLUME_BITMAP_BUFFER *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, BitmapSize);

    Result = DeviceIoControl
    (
        Handle,
        FSCTL_GET_VOLUME_BITMAP,
        &StartingLCN,
        sizeof (StartingLCN),
        Bitmap,
        BitmapSize,
        &BytesReturned,
        NULL
    );

    // Bad result?
    if (Result == FALSE && GetLastError() != ERROR_MORE_DATA)
    {
        //wprintf ("\nDeviceIoControl returned false, GetLastError() was not ERROR_MORE_DATA\n");
        HeapFree(GetProcessHeap(), 0, Bitmap);
        return (false);
    }

    // Otherwise, we're good
    BitmapSize = sizeof (VOLUME_BITMAP_BUFFER) + (Bitmap->BitmapSize.QuadPart / 8) + 1;

    void *reallBitmap = HeapReAlloc(GetProcessHeap(), 0, Bitmap, BitmapSize);

    if (reallBitmap == NULL)
    {
        // Fail "miserably"
        wprintf(L"\nNot enough memory to read volume bitmap\n");
        HeapFree(GetProcessHeap(), 0, Bitmap);
        return (false);
    }

    Bitmap = (VOLUME_BITMAP_BUFFER *)reallBitmap;
    Result = DeviceIoControl
    (
        Handle,
        FSCTL_GET_VOLUME_BITMAP,
        &StartingLCN,
        sizeof (StartingLCN),
        Bitmap,
        BitmapSize,
        &BytesReturned,
        NULL
    );

    //DWORD LastError = GetLastError ();

    if (Result == FALSE)
    {
        wprintf (L"\nCouldn't properly read volume bitmap\n");
        HeapFree(GetProcessHeap(), 0, Bitmap);
        return (false);
    }

    // Convert to a L'quick use' bitmap
    //const int BitShift[] = { 1, 2, 4, 8, 16, 32, 64, 128 };

    VolInfo.ClusterCount = Bitmap->BitmapSize.QuadPart;

    if (BitmapDetail != NULL)
        HeapFree(GetProcessHeap(), 0, BitmapDetail);

    BitmapDetail = (uint32 *) HeapAlloc(GetProcessHeap(), 0, sizeof(uint32) * (1 + (VolInfo.ClusterCount / 32)));
    memcpy (BitmapDetail, Bitmap->Buffer, sizeof(uint32) * (1 + (VolInfo.ClusterCount / 32)));

    /*
    BitmapDetail = (Cluster *) malloc (VolInfo.ClusterCount * sizeof (Cluster));
    for (uint64 i = 0; i < VolInfo.ClusterCount; i++)
    {
        if (Bitmap->Buffer[i / 8] & BitShift[i % 8])
            BitmapDetail[i].Allocated = true;
        else
            BitmapDetail[i].Allocated = false;
    }
    */

    HeapFree(GetProcessHeap(), 0, Bitmap);
    return (true);
}


bool DriveVolume::IsClusterUsed (uint64 Cluster)
{
    return ((BitmapDetail[Cluster / 32] & (1 << (Cluster % 32))) ? true : false);
    //return (BitmapDetail[Cluster].Allocated);
}


void DriveVolume::SetClusterUsed (uint64 Cluster, bool Used)
{
    if (Used)
        BitmapDetail[Cluster / 32] |= (1 << (Cluster % 32));
    else
        BitmapDetail[Cluster / 32] &= ~(1 << (Cluster % 32));

    return;
}


typedef struct
{
    DriveVolume *Volume;
    double       *Percent;
    bool        *QuitMonitor;
    uint64       ClusterCount;
    uint64       ClusterProgress;
} BuildDBInfo;


bool DriveVolume::BuildFileList (bool &QuitMonitor, double &Percent)
{
    BuildDBInfo Info;

    Files.clear ();
    Directories.clear ();
    Directories.push_back (RootPath);

    Info.Volume = this;
    Info.QuitMonitor = &QuitMonitor;
    Info.ClusterCount = (GetVolumeInfo().TotalBytes - GetVolumeInfo().FreeBytes) / (uint64)GetVolumeInfo().ClusterSize;
    Info.ClusterProgress = 0;
    Info.Percent = &Percent;

    ScanDirectory (RootPath, BuildDBCallback, &Info);

    if (QuitMonitor == true)
    {
        Directories.resize (0);
        Files.resize (0);
    }

    return (true);
}


// UserData = pointer to BuildDBInfo instance
bool BuildDBCallback (FileInfo &Info, HANDLE &FileHandle, void *UserData)
{
    BuildDBInfo *DBInfo = (BuildDBInfo *) UserData;
    DriveVolume *Vol = DBInfo->Volume;

    Vol->Files.push_back (Info);

    if (*(DBInfo->QuitMonitor) == true)
        return (false);

    DBInfo->ClusterProgress += (uint64)Info.Clusters;
    *(DBInfo->Percent) =
        ((double)DBInfo->ClusterProgress / (double)DBInfo->ClusterCount) * 100.0f;

    return (true);
}


wstring &DriveVolume::GetDBDir (uint32 Indice)
{
    return (Directories[Indice]);
}


uint32 DriveVolume::GetDBDirCount (void)
{
    return (Directories.size());
}


FileInfo &DriveVolume::GetDBFile (uint32 Indice)
{
    return (Files[Indice]);
}


uint32 DriveVolume::GetDBFileCount (void)
{
    return (Files.size());
}


uint32 DriveVolume::RemoveDBFile (uint32 Indice)
{
    vector<FileInfo>::iterator it;

    it = Files.begin() + Indice;
    Files.erase (it);
    return (GetDBFileCount());
}


bool DriveVolume::ScanDirectory (wstring DirPrefix, ScanCallback Callback, void *UserData)
{
    WIN32_FIND_DATAW FindData;
    HANDLE          FindHandle;
    wstring          SearchString;
    uint32          DirIndice;

    DirIndice = Directories.size() - 1;

    SearchString = DirPrefix;
    SearchString += L"*.*";
    ZeroMemory (&FindData, sizeof (FindData));
    FindHandle = FindFirstFileW (SearchString.c_str(), &FindData);

    if (FindHandle == INVALID_HANDLE_VALUE)
        return (false);

    do
    {
        FileInfo Info;
        HANDLE   Handle;
        bool     CallbackResult;

        Handle = INVALID_HANDLE_VALUE;

        // First copy over the easy stuff.
        Info.Name = FindData.cFileName;

        // DonLL't ever include '.L' and '..'
        if (Info.Name == L"."  ||  Info.Name == L"..")
            continue;

        //Info.FullName = DirPrefix + Info.Name;
        Info.Size      = (uint64)FindData.nFileSizeLow + ((uint64)FindData.nFileSizeHigh << (uint64)32);
        Info.DirIndice = DirIndice;

        Info.Attributes.Archive    = (FindData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)       ? 1 : 0;
        Info.Attributes.Compressed = (FindData.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED)    ? 1 : 0;
        Info.Attributes.Directory  = (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)     ? 1 : 0;
        Info.Attributes.Encrypted  = (FindData.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED)     ? 1 : 0;
        Info.Attributes.Hidden     = (FindData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)        ? 1 : 0;
        Info.Attributes.Normal     = (FindData.dwFileAttributes & FILE_ATTRIBUTE_NORMAL)        ? 1 : 0;
        Info.Attributes.Offline    = (FindData.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE)       ? 1 : 0;
        Info.Attributes.ReadOnly   = (FindData.dwFileAttributes & FILE_ATTRIBUTE_READONLY)      ? 1 : 0;
        Info.Attributes.Reparse    = (FindData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) ? 1 : 0;
        Info.Attributes.Sparse     = (FindData.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE)   ? 1 : 0;
        Info.Attributes.System     = (FindData.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)        ? 1 : 0;
        Info.Attributes.Temporary  = (FindData.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY)     ? 1 : 0;
        Info.Attributes.AccessDenied = 0;
        Info.Attributes.Unmovable    = 0;
        Info.Attributes.Process      = 1;

        Info.Clusters = 0;
        if (GetClusterInfo (Info, Handle))
        {
            uint64 TotalClusters = 0;

            for (size_t i = 0; i < Info.Fragments.size(); i++)
            {
                TotalClusters += Info.Fragments[i].Length;
            }

            Info.Clusters = TotalClusters;
        }
        else
        {
            Info.Attributes.Unmovable = 1;
            Info.Attributes.Process = 0;
        }

        if (Info.Attributes.Process == 1)
            Info.Attributes.Process = ShouldProcess (Info.Attributes) ? 1 : 0;

        // Run the user-defined callback function
        CallbackResult = Callback (Info, Handle, UserData);

        if (Handle != INVALID_HANDLE_VALUE)
            CloseHandle (Handle);

        if (!CallbackResult)
            break;

        // If directory, perform recursion
        if (Info.Attributes.Directory == 1)
        {
            wstring Dir;

            Dir = GetDBDir (Info.DirIndice);
            Dir += Info.Name;
            Dir += L"\\";

            Directories.push_back (Dir);
            ScanDirectory (Dir, Callback, UserData);
        }

    } while (FindNextFileW (FindHandle, &FindData) == TRUE);

    FindClose (FindHandle);
    return (false);
}


bool DriveVolume::ShouldProcess (FileAttr Attr)
{
    if (Attr.Offline == 1  ||  Attr.Reparse == 1  ||  Attr.Temporary == 1)
    {
        return (false);
    }

    return (true);
}


// Gets info on a file and returns a valid handle for read/write access
// Name, FullName, Clusters, Attributes, and Size should already be filled out.
// This function fills in the Fragments vector
bool DriveVolume::GetClusterInfo (FileInfo &Info, HANDLE &HandleResult)
{
    BOOL     Result;
    HANDLE   Handle;
    wstring   FullName;
    BY_HANDLE_FILE_INFORMATION FileInfo;

    Info.Fragments.resize (0);

    /*
    if (Info.Attributes.Directory == 1)
        return (false);
    */

    FullName = GetDBDir (Info.DirIndice) + Info.Name;

    Handle = CreateFileW
    (
        FullName.c_str(),
        0, //GENERIC_READ,
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        (Info.Attributes.Directory == 1) ? FILE_FLAG_BACKUP_SEMANTICS : 0,
        NULL
    );

    if (Handle == INVALID_HANDLE_VALUE)
    {
	    LPVOID lpMsgBuf;

	    FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
					    NULL, GetLastError(),
					    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
					    (LPTSTR) &lpMsgBuf, 0, NULL );


        Info.Attributes.AccessDenied = 1;
	    LocalFree( lpMsgBuf );
        return (false);
    }

    ZeroMemory (&FileInfo, sizeof (FileInfo));
    Result = GetFileInformationByHandle (Handle, &FileInfo);

    if (Result == FALSE)
    {
        Info.Attributes.AccessDenied = 1;
        wprintf (L"GetFileInformationByHandle ('%s%s') failed\n", GetDBDir (Info.DirIndice).c_str(),
            Info.Name.c_str());

        CloseHandle (Handle);
        return (false);
    }

    // Get cluster allocation information
    STARTING_VCN_INPUT_BUFFER  StartingVCN;
    RETRIEVAL_POINTERS_BUFFER *Retrieval;
    uint64                     RetSize;
    uint64                     Extents;
    DWORD                      BytesReturned;

    // Grab info one extent at a time, until it's done grabbing all the extent data
    // Yeah, well it doesn't give us a way to ask L"how many extents?" that I know of ...
    // btw, the Extents variable tends to only reflect memory usage, so when we have
    // all the extents we look at the structure Win32 gives us for the REAL count!
    Extents = 10;
    Retrieval = NULL;
    RetSize = 0;
    StartingVCN.StartingVcn.QuadPart = 0;

    do
    {
        Extents *= 2;
        RetSize = sizeof (RETRIEVAL_POINTERS_BUFFER) + ((Extents - 1) * sizeof (LARGE_INTEGER) * 2);

        if (Retrieval != NULL)
            Retrieval = (RETRIEVAL_POINTERS_BUFFER *) realloc (Retrieval, RetSize);
        else
            Retrieval = (RETRIEVAL_POINTERS_BUFFER *) malloc (RetSize);

        Result = DeviceIoControl
        (
            Handle,
            FSCTL_GET_RETRIEVAL_POINTERS,
            &StartingVCN,
            sizeof (StartingVCN),
            Retrieval,
            RetSize,
            &BytesReturned,
            NULL
        );

        if (Result == FALSE)
        {
            if (GetLastError() != ERROR_MORE_DATA)
            {
                Info.Clusters = 0;
                Info.Attributes.AccessDenied = 1;
                Info.Attributes.Process = 0;
                Info.Fragments.clear ();
                CloseHandle (Handle);
                free (Retrieval);

                return (false);
            }

            Extents++;
        }
    } while (Result == FALSE);

    // Readjust extents, as it only reflects how much memory was allocated and may not
    // be accurate
    Extents = Retrieval->ExtentCount;

    // Ok, we have the info. Now translate it. hrmrmr

    Info.Fragments.clear ();
    for (uint64 i = 0; i < Extents; i++)
    {
        Extent Add;

        Add.StartLCN = Retrieval->Extents[i].Lcn.QuadPart;
        if (i != 0)
            Add.Length = Retrieval->Extents[i].NextVcn.QuadPart - Retrieval->Extents[i - 1].NextVcn.QuadPart;
        else
            Add.Length = Retrieval->Extents[i].NextVcn.QuadPart - Retrieval->StartingVcn.QuadPart;

        Info.Fragments.push_back (Add);
    }

    free (Retrieval);
    HandleResult = Handle;
    return (true);
}


bool DriveVolume::FindFreeRange (uint64 StartLCN, uint64 ReqLength, uint64 &LCNResult)
{
    uint64 Max;
    uint64 i;
    uint64 j;

    // Make sure we don't spill over our array
    Max = VolInfo.ClusterCount - ReqLength;

    for (i = StartLCN; i < Max; i++)
    {
        bool Found = true;

        // First check the first cluster
        if (IsClusterUsed (i))
            Found = false;
        else
        // THen check the last cluster
        if (IsClusterUsed (i + ReqLength - 1))
            Found = false;
        else
        // Check the whole darn range.
        for (j = (i + 1); j < (i + ReqLength - 2); j++)
        {
            if (IsClusterUsed (j) == true)
            {
                Found = false;
                break;
            }
        }

        if (!Found)
            continue;
        else
        {
            LCNResult = i;
            return (true);
        }
    }

    return (false);
}


// btw we have to move each fragment of the file, as per the Win32 API
bool DriveVolume::MoveFileDumb (uint32 FileIndice, uint64 NewLCN)
{
    bool ReturnVal = false;
    FileInfo Info;
    HANDLE FileHandle;
    wstring FullName;
    MOVE_FILE_DATA MoveData;
    uint64 CurrentLCN;
    uint64 CurrentVCN;

    // Set up variables
    Info = GetDBFile (FileIndice);
    FullName = GetDBDir (Info.DirIndice);
    FullName += Info.Name;
    CurrentLCN = NewLCN;
    CurrentVCN = 0;

    /*
    if (Info.Attributes.Directory == 1)
    {
        //
    }
    */

    // Open file
    FileHandle = CreateFileW
    (
        FullName.c_str (),
        GENERIC_READ,
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        (Info.Attributes.Directory == 1) ? FILE_FLAG_BACKUP_SEMANTICS : 0,
        NULL
    );

    if (FileHandle == INVALID_HANDLE_VALUE)
    {
        //
	    LPVOID lpMsgBuf;

	    FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
					    NULL, GetLastError(),
					    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
					    (LPTSTR) &lpMsgBuf, 0, NULL );


	    LocalFree (lpMsgBuf);
        //

        ReturnVal = false;
    }
    else
    {
        ReturnVal = true; // innocent until proven guilty ...

        for (uint32 i = 0; i < Info.Fragments.size(); i++)
        {
            BOOL Result;
            DWORD BytesReturned;

            //wprintf (L"%3u", i);

            MoveData.ClusterCount = Info.Fragments[i].Length;
            MoveData.StartingLcn.QuadPart = CurrentLCN;
            MoveData.StartingVcn.QuadPart = CurrentVCN;

            MoveData.FileHandle = FileHandle;

            /*
            wprintf (L"\n");
            wprintf (L"StartLCN: %I64u\n", MoveData.StartingLcn.QuadPart);
            wprintf (L"StartVCN: %I64u\n", MoveData.StartingVcn.QuadPart);
            wprintf (L"Clusters: %u (%I64u-%I64u --> %I64u-%I64u)\n", MoveData.ClusterCount,
                Info.Fragments[i].StartLCN,
                Info.Fragments[i].StartLCN + MoveData.ClusterCount,
                MoveData.StartingLcn.QuadPart,
                MoveData.StartingLcn.QuadPart + MoveData.ClusterCount - 1);
            wprintf (L"\n");
            */

            Result = DeviceIoControl
            (
                Handle,
                FSCTL_MOVE_FILE,
                &MoveData,
                sizeof (MoveData),
                NULL,
                0,
                &BytesReturned,
                NULL
            );

            //wprintf (L"\b\b\b");

            if (Result == FALSE)
            {
                //
	            LPVOID lpMsgBuf;

	            FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
					            NULL, GetLastError(),
					            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
					            (LPTSTR) &lpMsgBuf, 0, NULL );


	            LocalFree( lpMsgBuf );
                //

                ReturnVal = false;
                goto FinishUp;  // yeah, bite me
            }

            // Ok good. Now update our drive bitmap and file infos.
            uint64 j;
            for (j = 0;
                 j < Info.Fragments[i].Length;
                 j++)
            {
                SetClusterUsed (Info.Fragments[i].StartLCN + j, false);
                SetClusterUsed (CurrentLCN + j, true);
                //BitmapDetail[Info.Fragments[i].StartLCN + j].Allocated = false;
                //BitmapDetail[CurrentLCN + j].Allocated = true;
            }

            CurrentLCN += Info.Fragments[i].Length;
            CurrentVCN += Info.Fragments[i].Length;
        }

        // Update file info either way
    FinishUp:
        CloseHandle (FileHandle);
        FileHandle = INVALID_HANDLE_VALUE;
        GetClusterInfo (Files[FileIndice], FileHandle);
        CloseHandle (FileHandle);
    }

    return (ReturnVal);
}


