TradDriverLoaderLib/TradDriverLoaderLib.c

/*
    File:       TradDriverLoaderLib.c
 
    Contains:   Implementation for the pseudo-DriverLoaderLib for 'DRVR's.
 
    Written by: Quinn "The Eskimo!"
 
    Copyright:  © 1996 by Apple Computer, Inc., all rights reserved.
 
    Change History (most recent first):
 
    You may incorporate this sample code into your applications without
    restriction, though the sample code has been provided "AS IS" and the
    responsibility for its operation is 100% yours.  However, what you are
    not permitted to do is to redistribute the source as "DSC Sample 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 Code, but that you've made changes.
*/
 
#include <LowMem.h>
#include <DriverGestalt.h>
#include <TextUtils.h>
 
// Switched from using:
//
//   #include <PLStringFuncs.h>
//
// to using BlockMoveData because it's so hard to get PLstrcpy working
// across a zillion different compilers.  *sigh*
 
#include "TradDriverLoaderLib.h"
 
///////////////////////////////////////////////////////////////////////////
 
extern pascal SInt16 TradHigherDriverVersion(NumVersion *dv1, NumVersion *dv2)
{
    UInt16 nonRelRev1, nonRelRev2;
 
    if (dv1->majorRev           > dv2->majorRev)        return  1;
    if (dv1->majorRev           < dv2->majorRev)        return -1;
    if (dv1->minorAndBugRev     > dv2->minorAndBugRev)  return  1;
    if (dv1->minorAndBugRev     < dv2->minorAndBugRev)  return -1;
    if (dv1->stage              > dv2->stage)           return  1;
    if (dv1->stage              < dv2->stage)           return -1;
 
    nonRelRev1 = dv1->nonRelRev;
    nonRelRev2 = dv2->nonRelRev;
    
    if (dv1->stage == finalStage) {
        if (dv1->nonRelRev == 0)                nonRelRev1 = 0xFFFF;
        if (dv2->nonRelRev == 0)                nonRelRev2 = 0xFFFF;
    }
 
    if (nonRelRev1 > nonRelRev2)                        return  1;
    if (nonRelRev1 < nonRelRev2)                        return -1;
 
    return 0;
}
 
 
///////////////////////////////////////////////////////////////////////////
 
extern pascal UnitNumber TradHighestUnitNumber(void)
    // See comment in header file.
{
    return ( LMGetUnitTableEntryCount() - 1);
}
 
///////////////////////////////////////////////////////////////////////////
 
extern pascal Boolean TradDriverGestaltIsOn(DriverFlags flags)
    // See comment in header file.
{
    return ( (flags & kmDriverGestaltEnableMask) != 0 );
}
 
///////////////////////////////////////////////////////////////////////////
 
static OSErr DriverGestaltOnOff(DriverRefNum refNum, Boolean setIt)
    // This routine is called by TradDriverGestaltOn and
    //  TradDriverGestaltOff to either set or clear the
    //  kmDriverGestaltEnableMask bit in the DCE flags.
{
    OSErr err;
    AuxDCEHandle thisDCE;
    
    // First called TradGetDriverInformation to validate the refNum
    //  and verify that the driver exists.
    err = TradGetDriverInformation(refNum, nil, nil, nil, nil);
    if (err == noErr) {
        thisDCE = (AuxDCEHandle) GetDCtlEntry(refNum);
        if (setIt) {
            (**thisDCE).dCtlFlags |= kmDriverGestaltEnableMask;
        } else {
            (**thisDCE).dCtlFlags &= ~kmDriverGestaltEnableMask;
        }
    }
    
    return (err);
}
 
///////////////////////////////////////////////////////////////////////////
 
extern pascal OSErr TradDriverGestaltOn(DriverRefNum refNum)
    // See comment in header file.
{
    return ( DriverGestaltOnOff(refNum, true) );
}
 
///////////////////////////////////////////////////////////////////////////
 
extern pascal OSErr TradDriverGestaltOff(DriverRefNum refNum)
    // See comment in header file.
{
    return ( DriverGestaltOnOff(refNum, false) );
}
 
///////////////////////////////////////////////////////////////////////////
 
extern pascal OSErr TradOpenInstalledDriver(DriverRefNum refNum, SInt8 ioPermission)
    // See comment in header file.
{
    OSErr               err;
    Str255              driverName;
    DriverRefNum    realRefNum;
 
    // Check parameters.
    err = noErr;
    if (ioPermission != fsRdWrPerm) {
        err = paramErr;
    }
    
    // Get the name of the driver, then simply open it.
    if (err == noErr) {
        err = TradGetDriverInformation(refNum, nil, nil, driverName, nil);
    }
    if (err == noErr) {
        if ( driverName[0] == 0 ) {
            err = paramErr;
        }
    }
    if (err == noErr) {
        err = OpenDriver(driverName, &realRefNum);
    }
    if (err == noErr) {
        if (realRefNum != refNum) {
            err = paramErr;     // My favourite error code -- at some intrinsic level, every error is a paramErr (-;
        }
    }
    
    return (err);
}
 
///////////////////////////////////////////////////////////////////////////
 
extern pascal OSErr TradLookupDrivers(UnitNumber beginningUnit,
                                        UnitNumber endingUnit,
                                        Boolean emptyUnits,
                                        ItemCount *returnedRefNums, 
                                        DriverRefNum *refNums)
    // See comment in header file.
{
    OSErr err;
    AuxDCEHandle    *unitTable;
    ItemCount       maxRefNums;
    UnitNumber      currentUnit;
    
    // Sanity check the parameters.
    if ( endingUnit > TradHighestUnitNumber() ) {
        endingUnit = TradHighestUnitNumber();
    }
    err = noErr;
    if ( beginningUnit > TradHighestUnitNumber() ) {
        err = badUnitErr;
    }
    if (err == noErr) {
        if (beginningUnit > endingUnit ) {
            err = paramErr;
        }
    }
 
    // Now do the real work...
    if (err == noErr) {
        unitTable = (AuxDCEHandle *) LMGetUTableBase();
 
        maxRefNums = *returnedRefNums;
        
        // Loop through each unit table entry from beginningUnit to endingUnit inclusive.
        *returnedRefNums = 0;
        currentUnit = beginningUnit;
        while ( currentUnit <= endingUnit ) {
 
            // If we've still got space to return a unit...
            if ( *returnedRefNums < maxRefNums ) {
            
                // and we're interested in this unit...
                if (    (emptyUnits && unitTable[currentUnit] == nil) ||
                            (!emptyUnits && unitTable[currentUnit] != nil) ) {
                    
                    // then copy the unit out to the caller's array
                    refNums[*returnedRefNums] = ~currentUnit;
                    *returnedRefNums += 1;
                }
            }
            currentUnit += 1;
        }
    
    }
    
    return (err);
}
 
///////////////////////////////////////////////////////////////////////////
 
enum {
    kNoUnitNumber = 0xFFFF
};
 
static UnitNumber IsDriverInstalled(ConstStr255Param name, UnitNumber skipThisUnit)
    // Look through the unit table to see if there is a driver with this name
    //  already installed.  Note that you might consider calling OpenDriver
    //  here, but that would be wrong.  OpenDriver has similar semantics, but
    //  if it fails to find a driver in the unit table it will search the
    //  current resource chain looking for a DRVR resource to install.
    //  Given that it's likely our client has a DRVR resource in their
    //  resource chain ('cause they're messing around trying to install
    //  drivers), and that OpenDriver will install it without detaching
    //  it from the client's resource file, and that the client's
    //  resource file may go away (ie they're a DropMounter-like application
    //  or some INIT running at system startup), this would be bad.
{
    UnitNumber  endingUnit;
    UnitNumber  unit;
    Str255          unitName;
    
    endingUnit = TradHighestUnitNumber();
    
    for (unit = 0; unit <= endingUnit; unit++) {
        if ( TradGetDriverInformation(~unit, nil, nil, unitName, nil) == noErr) {
            if ( unit != skipThisUnit && EqualString(name, unitName, false, true) ) {
                return (unit);
            }
        }
    }
    
    return (kNoUnitNumber);
}
 
///////////////////////////////////////////////////////////////////////////
 
enum {
    kMaximumNumberOfUnitTableEntries = 128,
    // kMaximumNumberOfUnitTableEntries = 8192,
    
    // kMaximumNumberOfUnitTableEntries is documented in Technote
    //  DV 23 "Driver Education" <http://devworld.apple.com/dev/technotes/dv/dv_23.html>
    //  as being the maximum size that the classic Device Manager
    //  would grow the unit table.  In theory, this limits the system
    //  to 128 unit table entries.  This limit is also enforced by the
    //  PCI DriverLoaderLib.
    // However the traditional Mac OS is capable of dealing with much more
    //  than 128 units.  In fact, some multi-port serial card vendors
    //  regularly install more.  So while I've set this limit to 128,
    //  I have tested TradDriverLoaderLib installing up to 500 device
    //  drivers.  I've also filed a bug against the PCI DriverLoaderLib to
    //  get its limit raised.
    // The alternative maximum I've supplied (8192) is designed to
    //  keep the unit table smaller than 32K.  This is important
    //  because many people use 68K word indexing (ie x(a0,d0.w) to
    //  to access the entries.
    
    kNumberOfEntriesToGrowUnitTable = 4
    
    // Technote DV 23 "Driver Education"
    //  <http://devworld.apple.com/dev/technotes/dv/dv_23.html>
    //  documents that the system grows the unit table by 4 entries
    //  at a time.
};
 
///////////////////////////////////////////////////////////////////////////
 
static OSErr GrowUnitTable()
    // This routine grows the unit table by kNumberOfEntriesToGrowUnitTable,
    //  up to a maximum of kMaximumNumberOfUnitTableEntries.  The routine
    //  is guaranteed to grow the table by at least one entry, or fail
    //  with an error.
{
    OSErr       err;
    Ptr         oldTable;
    Ptr         newTable;
    UInt32  oldCount;
    UInt32  newCount;
 
    // Get the info about the old table, and calculate the new table size.  
    oldTable = LMGetUTableBase();
    oldCount = LMGetUnitTableEntryCount();
    newCount = oldCount + kNumberOfEntriesToGrowUnitTable;
 
    // Guard against growing the table too big. 
    err = noErr;
    if (newCount > kMaximumNumberOfUnitTableEntries) {
        err = unitTblFullErr;
    }
    
    // Allocate the new unit table in the system heap.  Note that we
    //  clear the newly allocated memory, so that later on, when we
    //  use this memory as the new unit table, the newly allocated
    //  entries will be empty.
 
    if (err == noErr) {
        newTable = NewPtrSysClear( newCount * sizeof(AuxDCEHandle));
        err = MemError();
    }
 
    // Copy the unit table entries over to the new table and then switch
    //  to that table.  Note that this sequence doesn't disable interrupts,
    //  instead it relies on the fact that programs can't modify the
    //  unit table at interrupt time, and thus we, running at non-interrupt
    //  time, have exclusive write access to the table.
 
    // Note that the sequence of these next few lines is *very* important.
    //  If we did this in any other order, you could get to a situation
    //  where interrupt code might be looking at an inconsistent 
    //  unit table, which would be bad.
    
    // The sequence is:
    //  1. copy the old unit table entries to the new table
    //  2. change the unit table base pointer, so that interrupts
    //       start using the new unit table
    //  3. then change the unit table count, so that we have
    //       more entries available
    
    if (err == noErr) {
        BlockMoveData(oldTable, newTable, oldCount * sizeof(AuxDCEHandle)); // 1.
        LMSetUTableBase(newTable);                                                                                  // 2.
        LMSetUnitTableEntryCount(newCount);                                                                 // 3.
        
        // Now its safe to dispose of the old unit table.
        DisposePtr(oldTable);
    }
 
    return (err);   
}
 
///////////////////////////////////////////////////////////////////////////
 
static OSErr FindFreeUnitNumber(UnitNumber beginningUnit,
                                UnitNumber endingUnit, 
                                UnitNumber *foundUnit)
    // This routine walks the unit table looking for a free
    //  slot.  The slot must be between beginningUnit
    //  and endingUnit.  If endingUnit is greater than
    //  TradHighestUnitNumber(), then we're allowed
    //  to grow the unit table to meet our needs.
{
    OSErr err;
    Boolean found;
    UnitNumber currentUnit;
    UnitNumber trueEndingUnit;
    AuxDCEHandle *unitTable;
    
    unitTable = (AuxDCEHandle *) LMGetUTableBase();
    
    // Find trueEndingUnit, which is the minimum
    //  of endingUnit and the highest unit number.
    trueEndingUnit = endingUnit;
    if ( trueEndingUnit > TradHighestUnitNumber() ) {
        trueEndingUnit = TradHighestUnitNumber();
    }
 
    // Scan through the unit table, starting at beginningUnit
    //  and ending at trueEndingUnit, looking for an
    //  empty slot.
    currentUnit = beginningUnit;
    found = false;
    while (currentUnit <= trueEndingUnit && !found) {
        found = (unitTable[currentUnit] == nil);
        if (!found) {
            currentUnit += 1;
        }
    }
 
    // Finish up.   
    if (found) {
        // We found an empty slot, return it.
        *foundUnit = currentUnit;
        err = noErr;
    } else {
 
        // We didn't find an empty slot.  If we're
        //  allowed to, grow the unit table, otherwise
        //  just return an error.
        
        if (endingUnit > trueEndingUnit) {
            err = GrowUnitTable();
            if (err == noErr) {
                *foundUnit = trueEndingUnit + 1;
            }
        } else {
            err = unitTblFullErr;
        }
    }
    
    return (err);   
}
 
///////////////////////////////////////////////////////////////////////////
 
extern pascal OSErr TradInstallDriverFromPtr(DRVRHeaderPtr driver,
                                                UnitNumber beginningUnit,
                                                UnitNumber endingUnit,
                                                DriverRefNum *refNum)
    // See comment in header file.
{
    OSErr err;
    UnitNumber foundUnit;
    AuxDCEHandle theDCE;
    
    // Sanity check parameters.
    err = noErr;
    if ( driver == nil ) {
        err = paramErr;
    }
    if ( beginningUnit > TradHighestUnitNumber() ) {
        err = badUnitErr;
    }
    if ( err == noErr && beginningUnit > endingUnit ) {
        err = paramErr;
    }
    
    // Check whether this driver is already installed.
    if ( err == noErr ) {
        // Check whether it's already installed.
        foundUnit = IsDriverInstalled(&driver->drvrName[0], kNoUnitNumber);
        if (foundUnit != kNoUnitNumber) {
            // Return the refnum of the existing driver to the caller.
            *refNum = ~foundUnit;
            err = dupFNErr;
        }
    }
    
    // Now walk the unit table looking for a free slot.
    if (err == noErr) {
        err = FindFreeUnitNumber(beginningUnit, endingUnit, &foundUnit);
    }
 
    // We've got a free slot, so let's install the device driver.
    //  Note that we use DriverInstallReserveMem, rather than the standard
    //  DriverInstall, so that the DCE is allocated low in the system
    //  heap.  DriverInstallReserveMem was introduced with the 128K ROM.
 
    if (err == noErr) {
        err = DriverInstallReserveMem(driver, ~foundUnit);
    }
    
    // Now do some important tidying up.
    if (err == noErr) {
 
        // Return the refNum to the caller.
        *refNum = ~foundUnit;
 
        theDCE = (AuxDCEHandle) GetDCtlEntry(*refNum);
        
        // Now setup the DCE properly.  There's a whole pile of things we
        //  have to do, mainly because DriverInstall is such a brain-dead
        //  routine.
        
        // First up, DriverInstall seems to ignore the first parameter
        //  passed to it, so we have to blat the pointer to the driver code in
        //  yourself afterwards.
        
        (**theDCE).dCtlDriver = (Ptr) driver;
 
        // Then we have to set up the flags.  We do this by copying the flags
        //  out of the first word of the driver code.  We make sure to clear
        //  the dRAMBased bit because we're actually a pointer-based driver
        //  and DriverInstallReserveMem sets it to provisionally indicate that
        //  we're a handle based driver.  We also set dNeedLock because
        //  we want the the Device Manager to lock down the DCE.
        
        (**theDCE).dCtlFlags = (driver->drvrFlags & ~dRAMBasedMask) | dNeedLockMask;
 
        // There's also a bunch of fields we copy straight across without
        //  any modification.  You might expect DriverInstall to copy
        //  across these fields from the driver header to the DCE, but it doesn't
        //  do that, so we do it ourselves.
 
        (**theDCE).dCtlDelay = driver->drvrDelay;
        (**theDCE).dCtlEMask = driver->drvrEMask;
        (**theDCE).dCtlMenu  = driver->drvrMenu;
 
        // Finally, we lock the DCE.
        // Note that strictly speaking we don't need to HLock the DCE
        //  because the Device Manager will do it when it you open a driver
        //  that has dNeedLock set.  However, we want to
        //  lock it now because DriverInstallReserveMem has just made sure
        //  that the DCE was created low in the system heap, so we might as
        //  well lock it down low rather than let it float.
 
        HLock( (Handle) theDCE );
    }
    
    return (err);
}
 
///////////////////////////////////////////////////////////////////////////
 
extern pascal OSErr TradInstallDriverFromHandle(DRVRHeaderHandle driver,
                                                UnitNumber beginningUnit,
                                                UnitNumber endingUnit,
                                                DriverRefNum *refNum)
    // See comment in header file.
{
    OSErr err;
    Size  driverSize;
    DRVRHeaderPtr driverPtr;
    
    driverPtr = nil;
    
    err = noErr;
    if (driver == nil || *driver == nil) {
        err = paramErr;
    }
    if (err == noErr) {
        driverSize = GetHandleSize( (Handle) driver );
    }
    if (err == noErr) {
        driverPtr = (DRVRHeaderPtr) NewPtrSys( driverSize );
        err = MemError();
    }
    
    if (err == noErr) {
        // This is *not* a BlockMoveData call. This time, we really are moving code!
        //  I could have put cache flushing code in here, but then I would have
        //  had to check whether it was available or not.
        BlockMove( *driver, driverPtr, driverSize );
        
        err = TradInstallDriverFromPtr(driverPtr, beginningUnit, endingUnit, refNum);
    }
    
    // Clean up.
    if (err != noErr) {
        // We're returning an error.  The API says we should leave the handle untouched,
        //  but we should definitely clean up our new copy of the driver code.
        if (driverPtr != nil) {
            DisposePtr( (Ptr) driverPtr );
        }
    }
    
    return (err);
}
 
///////////////////////////////////////////////////////////////////////////
 
extern pascal OSErr TradInstallDriverFromResource(SInt16 rsrcID, StringPtr rsrcName,
                                                UnitNumber beginningUnit,
                                                UnitNumber endingUnit,
                                                DriverRefNum *refNum)
    // See comment in header file.
{
    OSStatus err;
    Handle driverHandle;
    
    // Note: We don't care which zone the resource gets loaded, because 
    //  TradInstallDriverFromHandle makes a copy of it anyway.
 
    // Get the resource, using either rsrcID or rsrcName.
    if (rsrcName == nil) {
        driverHandle = Get1Resource('DRVR', rsrcID);
    } else {
        driverHandle = Get1NamedResource('DRVR', rsrcName);
    }
    
    // Set err if we couldn't get the resource.
    if (driverHandle == nil) {
        err = ResError();
        if (err == noErr) {
            err = resNotFound;
        }
    } else {
        // Make sure we're not killed by some clown making the 'DRVR' purgeable.
        HNoPurge(driverHandle);                 
        err = MemError();
    }
    
    // Now install the driver as if we'd got it from a memory handle.   
    if (err == noErr) {
        err = TradInstallDriverFromHandle( (DRVRHeaderHandle) driverHandle, beginningUnit, endingUnit, refNum);
 
        ReleaseResource(driverHandle);
        if (err == noErr) {
            err = ResError();
        }
    }
    
    return (err);
}
 
///////////////////////////////////////////////////////////////////////////
 
extern pascal OSErr TradGetDriverInformation(DriverRefNum refNum,
                                                UnitNumber *thisUnit,
                                                DriverFlags *flags,
                                                StringPtr name,
                                                DRVRHeaderPtr *driverHeader
                                                )
    // See comment in header file.
{
    OSErr err;
    UnitNumber          tmpUnit;
    AuxDCEHandle        tmpDCE;
    DRVRHeaderPtr       tmpHeader;
    DRVRHeaderHandle    tmpDriverHandle;
    
    // Get some initial information.
    tmpUnit = ~refNum;
    
    // Sanity check the refNum parameter.
    err = noErr;
    if (tmpUnit > TradHighestUnitNumber()) {
        err = badUnitErr;
    }
    if (err == noErr) {
        tmpDCE = (AuxDCEHandle) GetDCtlEntry(refNum);
        if ( tmpDCE == nil ) {
            err = unitEmptyErr;
        }
    }
    if (err == noErr) {
        if ( (*tmpDCE == nil) || (GetHandleSize( (Handle) tmpDCE) < sizeof(DCtlEntry)) ) {
            err = dceExtErr;
        }
    }
    
    // Get the information from the DCE.
    if (err == noErr) {
 
        // From the DCE, find the DRVR header.  This can fail for a number of reasons:
        //  1. dCtlDriver is nil
        //  2. the driver is handle based, and the handle's master point is nil
        //  3. the driver is handle based, and the driver's handle is too small
        // In all of these cases, we set tmpHeader to nil, returning nil to our
        // client.
        
        tmpHeader = (DRVRHeaderPtr) (**tmpDCE).dCtlDriver;
        if ( tmpHeader != nil ) {
            if ( ((**tmpDCE).dCtlFlags & dRAMBasedMask) != 0 ) {
 
                tmpDriverHandle = (DRVRHeaderHandle) tmpHeader;
                
                if ( (*tmpDriverHandle != nil) &&
                            (GetHandleSize( (Handle) tmpDriverHandle) >= sizeof(DRVRHeader)) ) {
                    tmpHeader = *tmpDriverHandle;
                }
            }
        }
        
        // Now copy out the various requested parameters
        if (thisUnit != nil) {
            *thisUnit = tmpUnit;
        }
        if (flags != nil) {
            *flags = (**tmpDCE).dCtlFlags;
        }
        if (name != nil) {
            if ( tmpHeader == nil ) {
                name[0] = 0;
            } else {
                BlockMoveData(&tmpHeader->drvrName[0], name, tmpHeader->drvrName[0] + 1);
            }
        }
        if (driverHeader != nil) {
            *driverHeader = tmpHeader;
        }
    }
    
    return (err);
}
 
///////////////////////////////////////////////////////////////////////////
 
extern pascal OSErr TradRemoveDriver(DriverRefNum refNum, Boolean immediate)
    // See comment in header file.
{
    OSErr               err;
    DriverFlags     flags;
    DRVRHeaderPtr driverHeader;
 
    // Check parameters.
    err = noErr;
    if (immediate) {
        err = paramErr;
    }
 
    // Get information about the driver we're closing.
    if (err == noErr) {
        err = TradGetDriverInformation(refNum, nil, &flags, nil, &driverHeader);
    }
    if (err == noErr) {
        if ( driverHeader == nil ) {
            err = paramErr;
        }
    }
    
    // If the driver is open, close it.
    if (err == noErr) {
        if ( (flags & dOpenedMask) != 0 ) {
            err = CloseDriver(refNum);
        }
    }
    
    // Now call the system to remove the driver from the unit table.  Note that this
    //  works because of a subtlety in DriverRemove.  If the driver being removed
    //  is a RAM-based driver (which our drivers aren't), DriverRemove will call
    //  ReleaseResource on the dCtlDriver.  We don't want this, so we make our drivers
    //  not RAM-based.
    
    if (err == noErr) {
        err = DriverRemove(refNum);
    }
    
    if (err == noErr) {
        // All is cool, so let's dispose of the code.
        DisposePtr( (Ptr) driverHeader);
    }
    
    return (err);
}
 
///////////////////////////////////////////////////////////////////////////
 
extern pascal OSErr TradRenameDriver(DriverRefNum refNum, ConstStr255Param newDriverName)
    // See *important* comment in header file.
{
    OSErr           err;
    Str255          driverName;
    DRVRHeaderPtr   driverHeader;
    
    err = noErr;
    if ( newDriverName[0] == 0 ) {
        err = paramErr;
    }
    if (err == noErr) {
        // Get information about the driver we're renaming.
        err = TradGetDriverInformation(refNum, nil, nil, driverName, &driverHeader);
    }
    if (err == noErr) {
        if ( driverHeader == nil ) {
            err = paramErr;
        }
    }
    
    // Now check the name lengths.  See comment in implementation for details.
    if (err == noErr) {
        if ( newDriverName[0] > driverName[0] ) {
            err = paramErr;
        }
    }
    
    // Now check whether the new name is already present in the unit table.
    if (err == noErr) {
        if ( IsDriverInstalled(newDriverName, ~refNum) != kNoUnitNumber ) {
            err = dupFNErr;
        }
    }
    
    // Now copy in the new driver name.
    if (err == noErr) {
        BlockMoveData( newDriverName, &driverHeader->drvrName[0], newDriverName[0] + 1 );
    }
    
    return (err);
}