MoreIsBetterBits/MoreDisks.c

/*
    File:       MoreDisks.c
 
    Contains:   General disk driver utility routines.
 
    Written by: Quinn
 
    Copyright:  Copyright © 1999 by Apple Computer, Inc., all rights reserved.
 
                You may incorporate this Apple sample source code into your program(s) without
                restriction. This Apple sample source code has been provided "AS IS" and the
                responsibility for its operation is yours. You are not permitted to redistribute
                this Apple sample source code as "Apple sample source code" after having made
                changes. If you're going to re-distribute the source, we require that you make
                it clear in the source that the code was descended from Apple sample source
                code, but that you've made changes.
 
    Change History (most recent first):
 
         <2>      7/5/99    Quinn   Added MoreIsDriveCDROM.  Fixed bug in MoreGetDriveRefNum where
                                    it was calling MoreUTFindDrive rather than MoreUTFindDriveQ, and
                                    hence failing on disks controlled by foreign file systems.
         <1>     16/3/99    Quinn   First checked in.
*/
 
/////////////////////////////////////////////////////////////////
 
// MoreIsBetter Setup
 
#include "MoreSetup.h"
 
// Mac OS Interfaces
 
#include <Devices.h>
#include <DriverGestalt.h>
#include <Gestalt.h>
#include <FSM.h>
#include <Disks.h>
#include <StringCompare.h>
 
// MIB Prototypes
 
#include "TradDriverLoaderLib.h"
#include "MoreInterfaceLib.h"
#include "MoreMemory.h"
 
// Our Prototypes
 
#include "MoreDisks.h"
 
/////////////////////////////////////////////////////////////////
#pragma mark ----- Basic Disk Driver Utilities -----
 
extern pascal DriveFlagsPtr MoreGetDriveFlags(DrvQElPtr drvQEl)
    // See comment in interface part.
{
    MoreAssertQ(drvQEl != nil);
 
    return ((DriveFlagsPtr) drvQEl) - 1;
}
 
extern pascal OSErr MoreUTFindDriveQ(SInt16 drive, DrvQElPtr *foundDrvQEl)
    // See comment in interface part.
{
    OSErr err;
    UInt32 fsmVers;
 
    MoreAssertQ(drive > 0);
    MoreAssertQ(foundDrvQEl != nil);
    
    // Check to see whether we have a useful version of FSM.  Versions of FSM
    // prior to 1.2 do not support the documented FSM API, so we just treat
    // them as if FSM wasn't installed.
    
    if ((Gestalt(gestaltFSMVersion, (SInt32 *) &fsmVers) == noErr) && (fsmVers >= 0x0120)) {
 
        // We have FSM, let's call its version of UTFindDrive,
        // and handle the weirdo error we get for non-HFS disks.
 
        err = MoreUTFindDrive(drive, foundDrvQEl);
        if (err == extFSErr) {
            err = noErr;
        }
    } else {
        DrvQElPtr thisDrv;
    
        // No FSM, let's go poking around in low memory )-:
        
        *foundDrvQEl = nil;
 
        thisDrv = (DrvQElPtr) GetDrvQHdr()->qHead;
        while (thisDrv != nil && *foundDrvQEl == nil) {
            if (thisDrv->dQDrive == drive) {
                *foundDrvQEl = thisDrv;
            } else {
                thisDrv = (DrvQElPtr) thisDrv->qLink;
            }
        }
        if (*foundDrvQEl == nil) {
            err = nsDrvErr;
        } else {
            err = noErr;
        }
    }
    
    return err;
}
 
extern pascal DrvQElPtr MoreGetIndDrive(SInt16 index)
    // See comment in interface part.
{
    DrvQElPtr thisDrv;
    SInt16    thisDrvIndex;
 
    MoreAssertQ(index > 0);
    
    thisDrvIndex = 1;
    thisDrv = (DrvQElPtr) GetDrvQHdr()->qHead;
    while (thisDrv != nil && thisDrvIndex != index) {
        thisDrvIndex += 1;
        thisDrv = (DrvQElPtr) thisDrv->qLink;
    }
    return thisDrv;
}
 
extern pascal SInt16 MoreFindFreeDriveNumber(SInt16 firstDrive)
    // See comment in interface part.
{
    SInt16 candidate;
    DrvQElPtr junkDrvQElPtr;
    
    MoreAssertQ(firstDrive >= 5);
    
    candidate = firstDrive;
    while ( MoreUTFindDriveQ(candidate, &junkDrvQElPtr) == noErr ) {
        candidate += 1;
    }
    
    // This post condition checks that we didn't wrap
    // around to a negative drive number.
 
    MoreAssertQ(candidate >= 5);
 
    return candidate;
}
 
extern pascal OSErr MoreRemoveDrive(DrvQElPtr drvQEl)
    // See comment in interface part.
{
    OSStatus err;
    
    if ( MoreVolumeMountedOnDrive(drvQEl->dQDrive, false) == 0 ) {
        err = Dequeue( (QElemPtr) drvQEl, GetDrvQHdr());
    } else {
        err = volOnLinErr;
    }
    return err;
}
 
extern pascal DriverRefNum MoreGetDriveRefNum(SInt16 drive)
    // See comment in interface part.
{
    DrvQElPtr foundDrvQEl;
 
    MoreAssertQ(drive > 0);
    
    if ( MoreUTFindDriveQ(drive, &foundDrvQEl) == noErr) {
        return foundDrvQEl->dQRefNum;
    } else {
        return 0;
    }
}
 
static pascal Boolean MoreDriveSupportsDriverGestaltInternal(DriverRefNum refNum)
    // An internal version of MoreDriveSupportsDriverGestalt that allows
    // you to pass in the refNum and the drive number.  You can pass
    // in 0 for either refNum or drive (but not both) and the routine
    // will do the appropriate mapping.
{
    OSErr junk;
    DriverFlags driverFlags;
    
    junk = TradGetDriverInformation(refNum, nil, &driverFlags, nil, nil);
    MoreAssertQ(junk == noErr);
    return TradDriverGestaltIsOn(driverFlags);
}
 
extern pascal Boolean MoreDriveSupportsDriverGestalt(SInt16 drive)
    // See comment in interface part.
{
    return MoreDriveSupportsDriverGestaltInternal(MoreGetDriveRefNum(drive));
}
 
static pascal Boolean MoreDriveSupportFileExchangeInternal(DriverRefNum refNum, SInt16 drive)
    // An internal version of MoreDriveSupportFileExchange that allows
    // you to pass in the refNum and the drive number.  You can pass
    // in 0 for either refNum or drive (but not both) and the routine
    // will do the appropriate mapping.
{
    DriverGestaltParam pb;
    Boolean result;
 
    MoreAssertQ( (refNum < 0 && drive >= 0 && (drive == 0 || MoreGetDriveRefNum(drive) == refNum)) ||
                 (refNum == 0 && drive > 0) );
    
    if (refNum == 0) {
        refNum = MoreGetDriveRefNum(drive);
    }
    
    result = false;
    if ( MoreDriveSupportsDriverGestaltInternal(refNum) ) {
        pb.ioVRefNum = drive;
        pb.ioCRefNum = refNum;
        pb.csCode = kDriverGestaltCode;
        pb.driverGestaltSelector = kdgAPI;
 
        if ( PBStatusSync((ParmBlkPtr) &pb) == noErr 
                && GetDriverGestaltAPIResponse(&pb)->partitionCmds & 0x01 ) {
            result = true;
        }
    }
    return result;
}
 
extern pascal Boolean MoreDriveSupportFileExchange(SInt16 drive)
    // See comment in interface part.
{
    MoreAssertQ(drive > 0);
    return MoreDriveSupportFileExchangeInternal(0, drive);
}
 
// This is the number of format list entries we allocate when issuing
// the return format list status call.  There's no way we can calculate
// the "correct" number, but this should be more than enough.
 
enum {
    kFormatListEntryCount = 16
};
 
extern pascal OSErr MoreGetDriveSize(SInt16 drive, UInt32 *sizeInBlocks)
    // See comment in interface part.
{
    OSErr err;
    DrvQElPtr drvQEl;
    CntrlParam pb;
    FormatListRec formatList[kFormatListEntryCount];
    SInt16 formatIndex;
    Boolean foundFormat;
    Str255 driverName;
    DrvSts status;
    
    MoreAssertQ(drive > 0);
    MoreAssertQ(sizeInBlocks != nil);
 
    // Start by finding the drive queue element for
    // the drive, and by making sure that there's a disk
    // in the drive.
    
    err = MoreUTFindDriveQ(drive, &drvQEl);
    if (err == noErr) {
        if ( MoreGetDriveFlags(drvQEl)->diskInPlace <= 0 ) {
            err = offLinErr;
        }
    }
 
    // Wow, this is harder than it should be, all because of the
    // silly ".Sony" driver.  The basic problem is that
    // the ".Sony" driver doesn't store the disk size in the
    // drive queue element like every other disk drive on the
    // planet.  The solution is a three step process as described
    // in the comments below.
    
    if (err == noErr) {
 
        // Step 1.  If the driver supports the kReturnFormatList status call,
        //          use it to get a list of formats for the drive and then
        //          return the format marked as current.
 
        pb.ioNamePtr = nil;
        pb.ioVRefNum = drvQEl->dQDrive;
        pb.ioCRefNum = drvQEl->dQRefNum;
        pb.csCode = kReturnFormatList;
        pb.csParam[0] = kFormatListEntryCount;
        *((FormatListRec **) &pb.csParam[1]) = formatList;
        err = PBStatusSync( (ParmBlkPtr) &pb);
        if (err == noErr) {
            foundFormat = false;
            for (formatIndex = 0; formatIndex < pb.csParam[0]; formatIndex++) {
                if ((formatList[formatIndex].formatFlags & diCIFmtFlagsCurrentMask) != 0) {
                    *sizeInBlocks = formatList[formatIndex].volSize;
                    foundFormat = true;
                }
            }
            if ( ! foundFormat ) {
 
                // Hmmm, this isn't good.  The disk driver returned a format
                // list but none of the formats were marked as "current".
                // We handle this correctly but, in debug builds, we'll also
                // drop into MacsBug, just to let you know this is happening.
 
                MoreAssertQ(false);
                err = paramErr;
            }
        } else {
        
            // ¥¥¥ The logic here is slightly screwed up.  The problem is that
            // I can't tell whether the kReturnFormatList call failed because
            // the driver just doesn't support it, or because the driver failed
            // to get the information for some other reason.  If that driver
            // happens to be the ".Sony" driver, I'm going to take a wrong step
            // next.
            //
            // For example, say that there's a 1.4MB disk in the floppy drive
            // and I call kReturnFormatList and it fails with an error because
            // of a cosmic ray.  I then test the driver name, find that it's
            // ".Sony", call DriveStatus, and then return noErr with a size
            // of either 400KB or 800KB.  Not good.
            // 
            // You might think this is an unlikely occurence, but it's exactly
            // what happens when there's no disk in the floppy drive.  I've
            // special-cased that away above, but the general problem still stands.
            // 
            // What are the alternatives?  I could special case the error
            // result from kReturnFormatList and only run this code if I get
            // statusErr.  But can you guarantee that all ".Sony" drives
            // return statusErr for an unrecognised status code?  I thought not.
            // Beyond that, I can't think of any options.  So this code
            // stands.  It's probably never going to bite anyone, but it's
            // worth noting here, just in case.  Besides, this is what
            // the equivalent routine in MoreFiles does (-:
            //
            // -- Quinn, 3 Mar 1999
 
            // Step 2.  If that doesn't work, then look at the driver.  If it's
            //          the ".Sony" driver (and this will be a really old ".Sony" driver
            //          because new ones support kReturnFormatList), special case
            //          the possible media types.
 
            err = TradGetDriverInformation(drvQEl->dQRefNum, nil, nil, driverName, nil);
            if (err == noErr) {
                if ( EqualString(driverName, "\p.Sony", false, true) ) {
                    err = DriveStatus(drvQEl->dQDrive, &status);
                    if (err == noErr) {
                        if ( status.twoSideFmt == 0 ) {
                            *sizeInBlocks = 400 * 2;
                        } else {
                            *sizeInBlocks = 800 * 2;
                        }
                    }
                } else {
 
                    // Step 3.  If it's not the ".Sony" driver, get the size out of the
                    //          drive queue element.
 
                    if (drvQEl->qType == 0) {
 
                        // Old style drive, with just 16 bits of size information
                        // in dQDrvSz.
 
                        *sizeInBlocks = drvQEl->dQDrvSz;
                    } else {
                    
                        // New style drive, with 32 bits of size information spread
                        // between dQDrvSz and dQDrvSz2.
                        
                        *sizeInBlocks = (((UInt32 ) drvQEl->dQDrvSz2) * 65536) + (UInt32 ) drvQEl->dQDrvSz;
                    }
                }
            }
        }
    }
    
    return err;
}
 
extern pascal SInt16 MoreVolumeMountedOnDrive(SInt16 drive, Boolean ejectedIsMounted)
    // See comment in interface part.
{
    SInt16 result;
    VCBPtr thisVCB;
 
    MoreAssertQ(drive > 0);
 
    // Get the VCB queue (in low memory) and walk it.
    // We can't use UTLocateNextVCB because it will only
    // iterate volumes by name, not return a complete list.
    
    result = 0;
    thisVCB = (VCBPtr) GetVCBQHdr()->qHead;
    while (thisVCB != nil && result == 0) {
        if (thisVCB->vcbDrvNum == drive ||
                (ejectedIsMounted &&
                 thisVCB->vcbDrvNum == 0 &&
                 thisVCB->vcbDRefNum == drive
                )
           ) {
            MoreAssertQ(thisVCB->vcbDrvNum == 0 || thisVCB->vcbDRefNum == MoreGetDriveRefNum(drive));
            result = thisVCB->vcbVRefNum;
        } else {
            thisVCB = (VCBPtr) thisVCB->qLink;
        }
    }
    
    return result;
}
 
extern pascal SInt16 MoreFirstDriveWithoutVolume(DriverRefNum refNum)
    // See comment in interface part.
{
    Boolean found;
    DrvQElPtr thisDrv;
 
    found = false;
    thisDrv = (DrvQElPtr) GetDrvQHdr()->qHead;
    while (thisDrv != nil && ! found) {
        if (thisDrv->dQRefNum == refNum && MoreVolumeMountedOnDrive(thisDrv->dQDrive, false) == 0) {
            found = true;
        } else {
            thisDrv = (DrvQElPtr) thisDrv->qLink;
        }
    }
    if (found) {
        return thisDrv->dQDrive;
    } else {
        return 0;
    }
}
 
extern pascal void MoreIsDriveCDROM(SInt16 drive, MoreDisksCDROMResponse *response)
    // See comment in interface part.
{
    DriverRefNum refNum;
    DriverFlags drvrFlags;
    Str255 drvrName;
    DriverGestaltParam pb;
    
    MoreAssertQ(drive > 0);
    MoreAssertQ(response != nil);
    
    *response = kMoreDriveUnableToDetermineCDROM;
    
    refNum = MoreGetDriveRefNum(drive);
    if (refNum != 0) {
        if ( TradGetDriverInformation(refNum, nil, &drvrFlags, drvrName, nil) == noErr ) {
        
            // Step 1 -- if the driver supports driver gestalt, we
            // exclusively rely on its response for the kdgDeviceType
            // selector.
            
            if ( TradDriverGestaltIsOn(drvrFlags) ) {
                pb.ioVRefNum = drive;
                pb.ioCRefNum = refNum;
                pb.csCode = kDriverGestaltCode;
                pb.driverGestaltSelector = kdgDeviceType;
 
                if ( PBStatusSync((ParmBlkPtr) &pb) == noErr ) {
                    if (GetDriverGestaltDevTResponse(&pb)->deviceType == kdgCDType) {
                        *response = kMoreDriveIsCDROM;
                    } else {
                        *response = kMoreDriveIsNotCDROM;
                    }
                }
            }
            
            // Step 2 -- if the above didn't work, we only say it's a CD-ROM
            // if the driver is ".AppleCD".
 
            if (*response == kMoreDriveUnableToDetermineCDROM) {
                if ( EqualString(drvrName, "\p.AppleCD", false, true) ) {
                    *response = kMoreDriveIsCDROM;
                } else if ( EqualString(drvrName, "\p.AFPTranslator", false, true) ) {
                    // ".AFPTranslator" does not respond to Driver Gestalt,
                    // which is pretty lame IMHO.  [Radar ID ]  Regardless,
                    // we know it's not a CD-ROM.
                    *response = kMoreDriveIsNotCDROM;
                } else if ( EqualString(drvrName, "\p.Sony", false, true) ) {
                    *response = kMoreDriveIsNotCDROM;
                }
            }
        }
    }
}
 
/////////////////////////////////////////////////////////////////
#pragma mark ----- File Exchange Control Call Interface -----
 
extern pascal OSErr MoreCreateNewDriveQueueElement(SInt16 driveToClone,
                        UInt32 firstBlock, UInt32 sizeInBlocks,
                        SInt16 *newDrive)
    // See comment in interface part.
{
    OSErr err;
    OSErr junk;
    CntrlParam pb;
    DrvQElPtr drvQEl;
 
    MoreAssertQ(driveToClone > 0);
    MoreAssertQ(newDrive != nil);
    
    // First check that the driver supports the File Exchange
    // control call interface.
        
    err = noErr;
    if ( ! MoreDriveSupportFileExchange(driveToClone) ) {
        err = controlErr;
    }
    
    // Find the drive queue element associated with
    // driveToClone.  This is an input parameter to
    // kGetADrive.
    
    if (err == noErr) {    
        err = MoreUTFindDriveQ(driveToClone, &drvQEl);
    }
    
    // Make the kGetADrive call to the driver.  Because
    // we pass a pointer to memory outside of the parameter
    // block (drvQEl) and the driver might be a paging device,
    // we must hold drvQEl (and make sure to unhold it later!).
    
    if (err == noErr) {
        err = SafeHoldMemory(&drvQEl, sizeof(drvQEl));
        if (err == noErr) {
            pb.ioVRefNum = driveToClone;
            pb.ioCRefNum = MoreGetDriveRefNum(driveToClone);
            pb.csCode = kGetADrive;
            *((DrvQElPtr **) &pb.csParam[0]) = &drvQEl;
 
            err = PBControlSync((ParmBlkPtr) &pb);
            if (err == noErr) {
                *newDrive = drvQEl->dQDrive;
            }
            junk = SafeUnholdMemory(&drvQEl, sizeof(drvQEl));
            MoreAssertQ(junk == noErr);
        }
    }
    
    // Now re-target the new drive to the partition on the
    // disk specified by firstBlock and sizeInBlocks.  We do
    // this in the create call because some disk drivers
    // don't always inherit the partition information from
    // the drive that was cloned.
    
    if (err == noErr) {
        err = MoreSetDrivePartition(*newDrive, firstBlock, sizeInBlocks);
    }
    
    return err;
}
 
extern pascal OSErr MoreSetDrivePartition(SInt16 drive, UInt32 firstBlock, UInt32 sizeInBlocks)
    // See comment in interface part.
{
    OSErr err;
    CntrlParam pb;
    DrvQElPtr drvQEl;
 
    MoreAssertQ(drive > 0);
    
    // First check that the driver supports the File Exchange
    // control call interface.
        
    err = noErr;
    if ( ! MoreDriveSupportFileExchange(drive) ) {
        err = controlErr;
    }
    
    // Find the drive queue element associated with
    // drive.  This is an input parameter to
    // kRegisterPartition.
    
    if (err == noErr) {    
        err = MoreUTFindDriveQ(drive, &drvQEl);
    }
    
    // Make the kRegisterPartition control call.  We
    // don't need to hold any memory because all the
    // parameters to this control call are entirely
    // contained within the parameter block.
 
    if (err == noErr) {
        pb.ioVRefNum = drive;
        pb.ioCRefNum = MoreGetDriveRefNum(drive);
        pb.csCode = kRegisterPartition;
        *((DrvQElPtr *) &pb.csParam[0]) = drvQEl;
        *((UInt32 *) &pb.csParam[2]) = firstBlock;
        *((UInt32 *) &pb.csParam[4]) = sizeInBlocks;
 
        err = PBControlSync((ParmBlkPtr) &pb);
    }
    
    // In the debug build, check that our changes stuck.
    
    #if MORE_DEBUG
        if (err == noErr) {
            OSErr debugErr;
            UInt32 trueFirstBlock;
            UInt32 trueSizeInBlocks;
 
            debugErr = MoreGetDrivePartition(drive, &trueFirstBlock, &trueSizeInBlocks);
            MoreAssertQ(debugErr == noErr && trueSizeInBlocks == sizeInBlocks && trueFirstBlock == firstBlock);
        }
    #endif
    
    return err;
}
 
extern pascal OSErr MoreGetDrivePartition(SInt16 drive, UInt32 *firstBlock, UInt32 *sizeInBlocks)
    // See comment in interface part.
{
    OSErr err;
    partInfoRec partInfo;
    
    MoreAssertQ(drive > 0);
    MoreAssertQ(firstBlock != nil);
    MoreAssertQ(sizeInBlocks != nil);
 
    err = MoreGetPartitionInfo(drive, &partInfo);
    if (err == noErr) {
        *firstBlock = partInfo.physPartitionLoc;
        err = MoreGetDriveSize(drive, sizeInBlocks);
    }
    return err;
}
 
extern pascal OSErr MoreGetPartitionInfo(SInt16 drive, partInfoRec *partInfo)
    // See comment in interface part.
{
    OSErr err;
    OSErr junk;
    CntrlParam pb;
    
    MoreAssertQ(drive > 0);
    MoreAssertQ(partInfo != nil);
 
    // First check that the driver supports the File Exchange
    // control call interface.
        
    err = noErr;
    if ( ! MoreDriveSupportFileExchange(drive) ) {
        err = controlErr;
    }
 
    // Make the kGetADrive call to the driver.  Because
    // we pass a pointer to memory outside of the parameter
    // block (partInfo) and the driver might be a paging device,
    // we must hold partInfo (and make sure to unhold it later!).
 
    if (err == noErr) {    
        err = SafeHoldMemory(partInfo, sizeof(*partInfo));
        if (err == noErr) {
            pb.ioVRefNum = drive;
            pb.ioCRefNum = MoreGetDriveRefNum(drive);
            pb.csCode = kGetPartInfo;
            *((partInfoRec **) &pb.csParam[0]) = partInfo;
 
            err = PBStatusSync((ParmBlkPtr) &pb);
            
            junk = SafeUnholdMemory(partInfo, sizeof(*partInfo));
            MoreAssertQ(junk == noErr);
        }
    }
    
    return err;
}
 
#if 0
 
// ¥¥¥
// This code temporarily disable while I figure out what's going on.
// -- Quinn, 3 Mar 1999
 
extern pascal OSErr MoreGetPartitionVolume(DriverRefNum refNum, const partInfoRec *partInfo, SInt16 *vRefNum)
    // See comment in interface part.
{
    OSErr err;
    OSErr junk;
    CntrlParam pb;
    
    MoreAssertQ(refNum < 0);
    MoreAssertQ(partInfo != nil);
    MoreAssertQ(vRefNum != nil);
 
    // First check that the driver supports the File Exchange
    // control call interface.
        
    err = noErr;
    if ( ! MoreDriveSupportFileExchangeInternal(refNum, 0) ) {
        err = controlErr;
    }
 
    // Make the kGetPartitionStatus call to the driver.  Because
    // we pass a pointer to memory outside of the parameter
    // block (partInfo and vRefNum) and the driver might be a paging device,
    // we must hold that memory (and make sure to unhold it later!).
 
    if (err == noErr) {    
        err = SafeHoldMemory( (partInfoRec *) partInfo, sizeof(*partInfo));
        if (err == noErr) {
            err = SafeHoldMemory(vRefNum, sizeof(*vRefNum));
            if (err == noErr) {
                pb.ioVRefNum = 0;
                pb.ioCRefNum = refNum;
                pb.csCode = kGetPartitionStatus;
                *((partInfoRec **) &pb.csParam[0]) = (partInfoRec *) partInfo;
                *((SInt16 **) &pb.csParam[2]) = vRefNum;
 
                err = PBStatusSync((ParmBlkPtr) &pb);
 
                junk = SafeUnholdMemory(vRefNum, sizeof(*vRefNum));
                MoreAssertQ(junk == noErr);
            }
            junk = SafeUnholdMemory( (partInfoRec *) partInfo, sizeof(*partInfo));
            MoreAssertQ(junk == noErr);
        }
    }
    return err;
}
 
#endif