TradDriverLoaderLib/TradDriverLoaderLib.c

/*
    File:       TradDriverLoaderLib.c
    
    Description:Implementation for the pseudo-DriverLoaderLib for 'DRVR's.
 
    Author:     Quinn "The Eskimo!"
 
    Copyright:  Copyright: © 1996-1999 by Apple Computer, Inc.
                all rights reserved.
    
    Disclaimer: 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.
    
    Change History (most recent first):
                6/23/99 Updated for Metrowerks Codewarrrior Pro 2.1(KG)
 
*/
 
#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);
}