#include "precomp.h"
#include "devenum.h"

#define NUMDRIVELETTERS      26

// drvletter struct.
typedef struct _DRIVELETTERS {

    BOOL    ExistsOnSystem[NUMDRIVELETTERS];
    DWORD   Type[NUMDRIVELETTERS];              // Returned from GetDriveType
    TCHAR   IdentifierString[NUMDRIVELETTERS][MAX_PATH];  // Varies by Drive type.

} DRIVELETTERS, *PDRIVELETTERS;

DRIVELETTERS g_DriveLetters;

PCTSTR DriveTypeAsString(
    IN UINT Type
    )
{
    static PCTSTR driveTypeStrings[] = {
        TEXT("DRIVE_UNKNOWN"),        //The drive type cannot be determined.
        TEXT("DRIVE_NO_ROOT_DIR"),    //The root directory does not exist.
        TEXT("DRIVE_REMOVABLE"),      //The disk can be removed from the drive.
        TEXT("DRIVE_FIXED"),          //The disk cannot be removed from the drive.
        TEXT("DRIVE_REMOTE"),         //The drive is a remote (network) drive.
        TEXT("DRIVE_CDROM"),          //The drive is a CD-ROM drive.
        TEXT("DRIVE_RAMDISK"),        //The drive is a RAM disk.
    };

    return driveTypeStrings[Type];
}

BOOL
InitializeDriveLetterStructure (
    VOID
    )
{
    DWORD DriveLettersOnSystem = GetLogicalDrives();
    BYTE  bitPosition;
    DWORD maxBitPosition = NUMDRIVELETTERS;
    TCHAR rootPath[4];
    BOOL  driveExists;
    UINT  type;
    BOOL  rf = TRUE;


    //
    //rootPath[0] will be set to the drive letter of interest.
    //
    rootPath[1] = TEXT(':');
    rootPath[2] = TEXT('\\');
    rootPath[3] = TEXT('\0');

    //
    // GetLogicalDrives returns a bitmask of all of the drive letters
    // in use on the system. (i.e. bit position 0 is turned on if there is
    // an 'A' drive, 1 is turned on if there is a 'B' drive, etc.
    // This loop will use this bitmask to fill in the global drive
    // letters structure with information about what drive letters
    // are available and what there drive types are.
    //

    for (bitPosition = 0; bitPosition < maxBitPosition; bitPosition++) {

        //
        // Initialize the entry to safe values.
        //
        g_DriveLetters.Type[bitPosition]                   = 0;
        g_DriveLetters.ExistsOnSystem[bitPosition]         = FALSE;
        *g_DriveLetters.IdentifierString[bitPosition]      = 0;

        //
        // Now, determine if there is a drive in this spot.
        //
        driveExists = DriveLettersOnSystem & (1 << bitPosition);

        if (driveExists) {

            //
            // There is. Now, see if it is one that we care about.
            //
            *rootPath = bitPosition + TEXT('A');
            type = GetDriveType(rootPath);

            if (type == DRIVE_FIXED     ||
                type == DRIVE_REMOVABLE ||
                type == DRIVE_CDROM) {

                //
                // This is a drive that we are interested in.
                //
                g_DriveLetters.ExistsOnSystem[bitPosition]  = driveExists;
                g_DriveLetters.Type[bitPosition]            = type;

                //
                // Identifier String is not filled in this function.
                //
            }
        }
    }


    return rf;
}

VOID
CleanUpHardDriveTags (
    VOID
    )
{
    //
    // User cancelled. We need to clean up the tag files
    // that were created for drive migration.
    //
    UINT i;
    TCHAR  path[MAX_PATH];

    lstrcpy(path,TEXT("*:\\"));
    lstrcat(path,TEXT(WINNT_WIN95UPG_DRVLTR_A));


    for (i = 0; i < NUMDRIVELETTERS; i++) {

        if (g_DriveLetters.ExistsOnSystem[i] &&
            g_DriveLetters.Type[i] == DRIVE_FIXED) {

            *path = (TCHAR) i + TEXT('A');
            DeleteFile (path);
        }
    }
}


BOOL
GatherHardDriveInformation (
    VOID
    )
{
    BOOL        rf = TRUE;
    DWORD       index;
    HANDLE      signatureFile;
    TCHAR       signatureFilePath[sizeof (WINNT_WIN95UPG_DRVLTR_A) + 3];
    DWORD       signatureFilePathLength;
    DWORD       bytesWritten;

    //
    // Hard drive information is actually written to a special signature file
    // on the root directory of each fixed hard drive. The information is nothing special --
    // just the drive number (0 = A, etc.)
    //

    lstrcpy(signatureFilePath,TEXT("*:\\"));
    lstrcat(signatureFilePath,TEXT(WINNT_WIN95UPG_DRVLTR_A));
    signatureFilePathLength = lstrlen(signatureFilePath);



    for (index = 0; index < NUMDRIVELETTERS; index++) {

        if (g_DriveLetters.ExistsOnSystem[index] &&
            g_DriveLetters.Type[index] == DRIVE_FIXED) {

            *signatureFilePath = (TCHAR) index + TEXT('A');

            signatureFile = CreateFile(
                signatureFilePath,
                GENERIC_WRITE | GENERIC_READ,
                0,
                NULL,
                CREATE_ALWAYS,
                FILE_ATTRIBUTE_NORMAL,
                NULL
                );

            if (signatureFile != INVALID_HANDLE_VALUE) {

                WriteFile (signatureFile, &index, sizeof(DWORD), &bytesWritten, NULL);



                CloseHandle (signatureFile);
                SetFileAttributes (signatureFilePath, FILE_ATTRIBUTE_HIDDEN);


            }
        }
    }


    return rf;
}

/*BOOL
GatherCdRomDriveInformation (
    VOID
    )

{
    BOOL   rf = TRUE;
    HKEY   scsiKey = NULL;
    HKEY   deviceKey = NULL;

    TCHAR  classData[25];
    DWORD  classDataSize = 25;

    TCHAR  targetData[5];
    DWORD  targetDataSize = 5;

    TCHAR  lunData[5];
    DWORD  lunDataSize = 5;

    TCHAR  driveLetterData[5];
    DWORD  driveLetterSize = 5;

    TCHAR  buffer [4096];
    DWORD  subKeyLength;
    DWORD  tempLength;

    HKEY   locationKey = NULL;
    PTSTR  locationName;

    DWORD  outerIndex;
    DWORD  enumReturn;

    DWORD  port;
    DWORD  unusedType;
    DWORD  error;
    
    
    //
    // Walk the SCSI tree looking for CD rom devices.
    //
    error = RegOpenKeyEx (HKEY_LOCAL_MACHINE, TEXT("ENUM\\SCSI"), 0, KEY_READ, &scsiKey);

    if (error) {

        return TRUE;
    }

    //
    // Gather information about the key in preparation for enumerating
    // it.
    //
    error = RegQueryInfoKey (
        scsiKey,
        NULL,                   // Don't care about the class.
        NULL,                   // class size.
        NULL,                   // reserved.
        NULL,                   // Don't care about the number of subkeys.
        &subKeyLength,
        NULL,                   // Don't care about subclasses.
        NULL,                   // Don't care about values.
        NULL,                   // Don't care about max value name length.
        NULL,                   // Don't care about max component.
        NULL,                   // Don't care about the security descriptor.
        NULL                    // Don't care about the last write time.
        );

    if (error) {
        //
        // This should really not happen.
        //
        return FALSE;
    }



    //
    // Succssesfully opened a key to HKLM\Enum\SCSI. Enumerate it.
    //
    outerIndex = 0;
    do {

        if (locationKey) {

            RegCloseKey (locationKey);
            locationKey = NULL;
        }
        if (deviceKey) {

            RegCloseKey (deviceKey);
            deviceKey = NULL;
        }


        tempLength = sizeof(buffer) / sizeof(TCHAR);

        enumReturn = RegEnumKeyEx (
            scsiKey,
            outerIndex,
            buffer,
            &tempLength,
            0,                // Reserved
            NULL,             // Class name - not necessary.
            NULL,             // size of class name buffer.
            NULL
            );

        outerIndex++;

        //
        // For each returned key, look up the "Class" value.
        //
        error = RegOpenKeyEx (scsiKey,buffer,0,KEY_READ,&deviceKey);
        if (error) {

            //
            // Something is hosed. Give up on collecting SCSI data.
            //
            rf = FALSE;
            break;
        }


        //
        // The port has to be decoded from the key one level
        // below.
        //
        tempLength = sizeof (buffer) / sizeof(TCHAR);

        error = RegEnumKeyEx (
            deviceKey,
            0,
            buffer,
            &tempLength,
            0,                // Reserved
            NULL,             // Class name - not necessary.
            NULL,             // size of class name buffer.
            NULL
            );

        error = RegOpenKeyEx (deviceKey, buffer, 0, KEY_READ, &locationKey);

        if (error) {

            //
            // This should really never happen. However, guard against it.
            // Its not serious enough to abort the search. Just skip this
            // particular key and continue.
            //
            continue;
        }



        tempLength = classDataSize;
        error = RegQueryValueEx(
            locationKey,
            TEXT("CLASS"),
            0,
            &unusedType,
            (PBYTE) classData,
            &tempLength
            );

        if (error) {

            //
            // This isn't a serious enough error to bring down the whole
            // enumeration. Just note it in the logs and continue to the
            // next key.
            //
            continue;
        }

        if (!lstrcmpi(classData, TEXT("CDROM"))) {


            lstrcpy (targetData, TEXT("-1"));
            lstrcpy (lunData, TEXT("-1"));
            lstrcpy (driveLetterData, TEXT("%"));

            //
            // Found a CdRom. Get the information that will be used in
            // textmode setup to identify the drive.
            //
            tempLength = targetDataSize;
            RegQueryValueEx(
                locationKey,
                TEXT("ScsiTargetId"),
                0,
                &unusedType,
                (PBYTE) targetData,
                &tempLength
                );

            tempLength = lunDataSize;
            RegQueryValueEx(
                locationKey,
                TEXT("ScsiLun"),
                0,
                &unusedType,
                (PBYTE) lunData,
                &tempLength
                );

            tempLength = driveLetterSize;
            RegQueryValueEx(
                locationKey,
                TEXT("CurrentDriveLetterAssignment"),
                0,
                &unusedType,
                (PBYTE) driveLetterData,
                &tempLength
                );




            if (*driveLetterData != TEXT('%')) {

                //
                // At this point, we have all of the information
                // necessary to write a SCSI CdRom identifier
                // string.
                //

                wsprintf(g_DriveLetters.IdentifierString[*driveLetterData - TEXT('A')], TEXT("%u^%s^%s"), 1, targetData, lunData);


            }

        }

        if (locationKey) {

            RegCloseKey (locationKey);
            locationKey = NULL;
        }
        if (deviceKey) {

            RegCloseKey (deviceKey);
            deviceKey = NULL;
        }



    } while (rf && enumReturn == ERROR_SUCCESS);

    if (locationKey) {
        RegCloseKey(locationKey);
        locationKey = NULL;
    }
    if (deviceKey) {
        RegCloseKey(deviceKey);
        deviceKey = NULL;
    }
    if (scsiKey) {
        RegCloseKey(scsiKey);
        scsiKey = NULL;
    }



    return rf;
}*/

BOOL pCDROMDeviceEnumCallback(
    IN  HKEY   hDevice, 
    IN  PCONTROLLERS_COLLECTION    ControllersCollection, 
    IN  UINT   ControllerIndex, 
    IN  PVOID  CallbackData
    )
{
    DRIVE_SCSI_ADDRESS scsiAddress;
    BOOL bResult;
    
    MYASSERT(hDevice && ControllersCollection);

    bResult = GetSCSIAddressFromPnPId(ControllersCollection, 
                                      hDevice, 
                                      ControllersCollection->ControllersInfo[ControllerIndex].PNPID, 
                                      &scsiAddress);
    
    MYASSERT(bResult);

    if(bResult && 
       ((UCHAR)INVALID_SCSI_PORT) != scsiAddress.PortNumber && 
       DRIVE_CDROM == scsiAddress.DriveType){
        wsprintf(g_DriveLetters.IdentifierString[scsiAddress.DriveLetter - TEXT('A')], 
                 TEXT("%u^%u^%u"), 
                 (UINT)scsiAddress.PortNumber, 
                 (UINT)scsiAddress.TargetId, 
                 (UINT)scsiAddress.Lun);
    }

    return TRUE;
}

BOOL
GatherCdRomDriveInformation (
    VOID
    )
{
    PCONTROLLERS_COLLECTION ControllersCollection;
    UINT i;
    BOOL bResult;
    BOOL bDetectedExtraIDEController = FALSE;
    UINT numberOfSCSIController = 0;

    //
    // Collect all active IDE and SCSI controllers
    //
    bResult = GatherControllersInfo(&ControllersCollection);
    if(!bResult){
        MYASSERT(FALSE);
        return FALSE;
    }

    MYASSERT(ControllersCollection->ControllersInfo);
    for(i = 0; i < ControllersCollection->NumberOfControllers; i++){
        switch(ControllersCollection->ControllersInfo[i].ControllerType){
        case CONTROLLER_EXTRA_IDE:
            bDetectedExtraIDEController = TRUE;
            break;
        case CONTROLLER_SCSI:
            numberOfSCSIController++;
            break;
        }
    }

    if(bDetectedExtraIDEController){
        DebugLog(Winnt32LogWarning, TEXT("Setup has detected that machine have extra IDE controller(s). Setup may not preserve drive letters."), 0);
    }

    if(numberOfSCSIController > 1){
        DebugLog(Winnt32LogWarning, TEXT("Setup has detected that machine have more than one SCSI controllers. Setup may not preserve drive letters only for SCSI devices."), 0);
    }
    
    //
    // If we found extra IDE controller(s) we can't ensure rigth device detection in this case.
    // If we found more than one SCSI controllers without extra IDE controller(s), 
    // at least we can guarantee correct IDE devices detection.
    //
    bResult = DeviceEnum(ControllersCollection, 
                         TEXT("SCSI"), 
                         (PDEVICE_ENUM_CALLBACK_FUNCTION)pCDROMDeviceEnumCallback, 
                         NULL);
    MYASSERT(bResult);

    ReleaseControllersInfo(ControllersCollection);

    return bResult;
}




BOOL
WriteInfoToSifFile (
    IN PCTSTR FileName
    )
{
    BOOL    rSuccess = TRUE;
    DWORD   index;
    TCHAR   dataString[MAX_PATH * 2]; // Well over the size needed.
    TCHAR   driveString[20]; // Well over the size needed.
    PCTSTR  sectionString = WINNT_D_WIN9XDRIVES;




    for (index = 0; index < NUMDRIVELETTERS; index++) {

        if (g_DriveLetters.ExistsOnSystem[index]) {

            wsprintf(
                driveString,
                TEXT("%u"),
                index
                );

            wsprintf(
                dataString,
                TEXT("%u,%s"),
                g_DriveLetters.Type[index],
                g_DriveLetters.IdentifierString[index]
                );

            //
            // Ending string looks like <drive num>,<drive type>,<identifier string>
            //

            WritePrivateProfileString (sectionString, driveString, dataString, FileName);
        }

    }


    return rSuccess;
}



DWORD
SaveDriveLetterInformation (
    IN PCTSTR FileName
    )
{
    BOOL rf = TRUE;

    if (InitializeDriveLetterStructure ()) {

        GatherHardDriveInformation ();
        GatherCdRomDriveInformation ();
        WriteInfoToSifFile (FileName);

    }

    return ERROR_SUCCESS;
}