#include "stdafx.h"

#ifdef OFFLINEDK
    extern "C"{
        #include <stdio.h>
    }
#endif

#ifdef BOOTIME
    #include "Offline.h"
#else
    #include "Windows.h"
#endif

#include <winioctl.h>

extern "C" {
    #include "SysStruc.h"
}

#include "ErrMacro.h"

#include "DfrgCmn.h"
#include "DfrgRes.h"

#include "Alloc.h"

#define THIS_MODULE 'U'
#include "logfile.h"

//
// start of helpers for IsValidVolume functions below
//

/////////////////////////////////////////
// Is this a valid drive type?
// Check if we have a drive that is even in the ball park.
//
static BOOL IsValidDriveType(UINT uDriveType)
{
    //sks bug #211782 take out CDROM and RAMDISK to allow for DVD-RAM drives
    if (uDriveType == DRIVE_UNKNOWN     ||
        uDriveType == DRIVE_NO_ROOT_DIR ||
        uDriveType == DRIVE_REMOTE 
        ) {
        return FALSE;
    }

    return TRUE;
}

/////////////////////////////////////////
// Get a handle to a volume
//
static HANDLE GetVolumeHandle(PTCHAR cVolume)
{
    HANDLE hVolume = INVALID_HANDLE_VALUE;

    // Get a handle to the volume
    UINT uiErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS);

    hVolume = CreateFile(cVolume, 
                         0, 
                         FILE_SHARE_READ|FILE_SHARE_WRITE, 
                         NULL, 
                         OPEN_EXISTING, 
                         FILE_ATTRIBUTE_NORMAL|FILE_FLAG_NO_BUFFERING, 
                         NULL);

    SetErrorMode(uiErrorMode);

    return hVolume;
}

/////////////////////////////////////////
// Is this volume valid (actual work routine)
//
// Note: volumeName is required to get volumeLabel and fileSystem
//
static BOOL IsValidVolumeCheck(HANDLE hVolume,          // IN volume handle
                               UINT uDriveType,         // IN drive type
                               PTCHAR volumeName,       // IN volume name
                               PTCHAR volumeLabel,      // OUT volume label
                               PTCHAR fileSystem)       // OUT file system
{
    require(hVolume != INVALID_HANDLE_VALUE && hVolume != NULL);
    require(volumeName != NULL);

    BOOL                         bReturn = FALSE;       // assume not valid
    HANDLE                       hFsDevInfo = NULL;
    FILE_FS_DEVICE_INFORMATION * pFsDevInfo = NULL;

    // clear return values
    if (volumeLabel != NULL) {
        _tcscpy(volumeLabel, TEXT(""));
    }
    if (fileSystem != NULL) {
        _tcscpy(fileSystem, TEXT(""));
    }

    __try {

        // read-only, network, etc. check

        if (!AllocateMemory(sizeof(FILE_FS_DEVICE_INFORMATION) + MAX_PATH, 
                                            &hFsDevInfo, (void**) &pFsDevInfo)) {
            EH(FALSE);
            __leave;
        }

        IO_STATUS_BLOCK IoStatus = {0};

        NTSTATUS Status = NtQueryVolumeInformationFile(hVolume, 
                                                       &IoStatus, 
                                                       pFsDevInfo, 
                                                       sizeof(FILE_FS_DEVICE_INFORMATION) + 50, 
                                                       FileFsDeviceInformation);

        if (NT_SUCCESS(Status)) {

            if (pFsDevInfo->Characteristics & 
                (FILE_READ_ONLY_DEVICE | FILE_WRITE_ONCE_MEDIA | FILE_REMOTE_DEVICE)) {
                __leave;
            }
        }
        else {
            __leave;
        }


        // media check

        if (uDriveType == DRIVE_REMOVABLE) {

            DISK_GEOMETRY medias[20];
            DWORD nummedias = 0;
            DWORD numbytes;

            if (DeviceIoControl(hVolume, IOCTL_STORAGE_GET_MEDIA_TYPES, 
                                NULL, 0, medias, 20 * sizeof(DISK_GEOMETRY), &numbytes, NULL)) {

                nummedias = numbytes / sizeof(DISK_GEOMETRY);

                for (UINT i=0; i<nummedias; i++) {

                    switch (medias[i].MediaType) {

                    // these are OK
                    case F3_20Pt8_512:  // 3.5",  20.8MB, 512   bytes/sector
                    case FixedMedia:    // Fixed hard disk media
                    case F3_120M_512:   // 3.5",   120M Floppy
                    case F3_128Mb_512:  // 3.5" MO 128Mb   512 bytes/sector
                    case F3_230Mb_512:  // 3.5" MO 230Mb   512 bytes/sector
                        break;

                    // but nothing else is
                    default:
                        __leave;
                        break;
                    }
                }
            }
            else {
                
                GetLastError(); // debug

                // TODO: figure out why JAZ drives fail on the above call
                // we should probably __leave here, but then JAZ drives are filtered out
                // maybe we should EH so at least it will register albeit every second or two
                // question: do or should every removable drive type report supported media?
//              EH(FALSE);
//              __leave;
            }
        }

        // file system check

        TCHAR tmpVolumeLabel[100];
        TCHAR tmpFileSystem[20];

        TCHAR tmpVolumeName[GUID_LENGTH];
        _tcscpy(tmpVolumeName, volumeName);
        if (volumeName[_tcslen(tmpVolumeName) - 1] != TEXT('\\')){
            _tcscat(tmpVolumeName, TEXT("\\"));
        }

        BOOL isOk = GetVolumeInformation(tmpVolumeName, 
                                         tmpVolumeLabel, 
                                         100, 
                                         NULL, 
                                         NULL, 
                                         NULL, 
                                         tmpFileSystem, 
                                         20);

        if (!isOk) {
            __leave;
        }

        if (volumeLabel != NULL) {
            _tcscpy(volumeLabel, tmpVolumeLabel);
        }
        if (fileSystem != NULL) {
            _tcscpy(fileSystem, tmpFileSystem);
        }

        // Only NTFS, FAT or FAT32
        if (_tcscmp(tmpFileSystem, TEXT("NTFS")) &&
            _tcscmp(tmpFileSystem, TEXT("FAT"))  &&
            _tcscmp(tmpFileSystem, TEXT("FAT32"))) {

            __leave;    // if none of the above, bail
        }

        bReturn = TRUE; // all the checks "passed"
    }

    __finally {

        if (hFsDevInfo) {

            EH_ASSERT(GlobalUnlock(hFsDevInfo) == FALSE);
            EH_ASSERT(GlobalFree(hFsDevInfo) == NULL);
        }
    }

    return bReturn;
}

/////////////////////////////////////////
// Is this volume valid (drive letter only version)
//
BOOL IsValidVolume(TCHAR cDrive)
{

    // Check if we have a drive that is even in the ball park.
    // If so, then continue on and gather more data (mostly for removable drives)
    TCHAR cRootPath[100];
    _stprintf(cRootPath, TEXT("%c:\\"), cDrive);

    UINT uDriveType = GetDriveType(cRootPath);
    if (!IsValidDriveType(uDriveType)) {
        return FALSE;
    }
    
    // Get a handle to the volume
    TCHAR cVolume[MAX_PATH];
    _stprintf(cVolume, TEXT("\\\\.\\%c:"), cDrive);
    HANDLE hVolume = GetVolumeHandle(cVolume);

    if (hVolume == INVALID_HANDLE_VALUE) {
        return FALSE;
    }

    // check volume
    BOOL bReturn = IsValidVolumeCheck(hVolume, uDriveType, cVolume, NULL, NULL);

    CloseHandle(hVolume);

    return bReturn;
}

/////////////////////////////////////////
// Is this volume valid (version 2)
//
// INPUT:
//  volumeName
//
// OUTPUT:
//  volumeLabel
//  fileSystem
//
BOOL IsValidVolume(PTCHAR volumeName, PTCHAR volumeLabel, PTCHAR fileSystem)
{
    BOOL bReturn = FALSE; // assume error


    if(!volumeName) {
        assert(0);
        return FALSE;
    }

    // Check if we have a drive that is even in the ball park.
    // If so, then continue on and gather more data (mostly for removable drives)
    UINT uDriveType = GetDriveType(volumeName);
    if (!IsValidDriveType(uDriveType)) {
        return FALSE;
    }

    //sks 8/30/2000 fix for prefix bug #109657
    TCHAR tmpVolumeName[GUID_LENGTH + 1];
    _tcsncpy(tmpVolumeName, volumeName, GUID_LENGTH);
    tmpVolumeName[GUID_LENGTH] = (TCHAR) NULL;

    // strip off the trailing whack
    if (tmpVolumeName[_tcslen(tmpVolumeName)-1] == TEXT('\\')){
        tmpVolumeName[_tcslen(tmpVolumeName)-1] = (TCHAR) NULL;
    }

    // Get a handle to the volume
    HANDLE hVolume = GetVolumeHandle(tmpVolumeName);

    if (hVolume == INVALID_HANDLE_VALUE) {
        return FALSE;
    }

    // check volume
    bReturn = IsValidVolumeCheck(hVolume, uDriveType, volumeName, volumeLabel, fileSystem);

    CloseHandle(hVolume);

    return bReturn;
}

// is the file system supported?
BOOL IsVolumeWriteable(PTCHAR volumeName, DWORD* dLastError)
{
    *dLastError = 0;
    // create a temp file name
    TCHAR cTempFile[MAX_PATH + 50];

    if (0 == GetTempFileName(volumeName, L"DFRG", 0, cTempFile)) {
        *dLastError = GetLastError();
        return FALSE;
    }

    // get rid of the temp file
    DeleteFile(cTempFile);

    // true means that we can write to it
    return TRUE;
}