mac-rom/Toolbox/DiskInit/DiskInitBadBlock.c
Elliot Nunn 4325cdcc78 Bring in CubeE sources
Resource forks are included only for .rsrc files. These are DeRezzed into their data fork. 'ckid' resources, from the Projector VCS, are not included.

The Tools directory, containing mostly junk, is also excluded.
2017-12-26 09:52:23 +08:00

961 lines
39 KiB
C

/*
File: DiskInitBadBlock.c
Contains: diBadBlockPass1 and diBadBlockPass2, which implement sparing
on HFS file systems.
Written by: Philip D. L. Koch, May 1990.
and Kenny SC. Tung, 1990-91
Copyright: © 1990-91 by Apple Computer, Inc., all rights reserved.
Change History (most recent first):
<7> 2/10/92 JSM Moved this file to DiskInit folder, keeping all the old
revisions.
<6> 2/8/91 KST ewa, #81598: diSweep will not depend on 8K stack space to do
disk sparing, we allocate space from the system heap now.
<5> 2/8/91 KST ewa, #81502: diBadBlockPass1 and diBadBlockPass2 will return
meaningful error code to DIBadMount (If sparing happened).
<4> 1/24/91 KST BB, #73806: Fixed a bug that spare too many disk blocks than it
should. And some documentation changes. This change is ready for
b4 but forgot to check in.
<3> 7/23/90 PK Bug when block 1594 is bad fixed.
<2> 7/3/90 PK For 800K floppies only, reduce allocation blocks from 1594 to
1593, so System 6 Finders don't do physical disk copies.
<1> 6/14/90 PK first checked in
Overview:
These routines are linked into the PACK2 (Disk Init) resource, and thus at present are
only available via the DIBadMount function. DIBadMount calls DIVerify as it always has,
and if the latter returns a good status the disk is presumed to be perfect and disk
initialization proceeds without calling these routines. But if DIVerify returns a bad
status, then the following happens:
1. diBadBlockPass1 is called, which will manually re-verify the disk by writing and reading
test patterns to every track, building a list of bad tracks. If too many badspots are found,
if one of the crucial sectors such as the boot blocks are found to be bad, or if we suspect
drive problems rather than media failure, diBadBlockPass1 returns a bad status and
initialization fails.
2. DIZero is called, to write a blank HFS directory to the disk. DIZero is not told
about the bad spots. If Pass 1 has done its job, DIZero won't touch any bad spots
and so it won't notice the disk is imperfect, but if we blow it and DIZero gets
a bad status, the disk won't initialize but no serious harm is done.
3. diBadBlockPass2 is called with the list of bad extents. It removes the bad blocks from
the bitmap, adjusts the MDB, and puts the extents in the extent B*-tree, but a catalog
node for the bad-block "file" is not created.
Because there is no catalog entry for the bad-block file, it is completely invisible
to users and to logical disk and directory manipulation. Fortunately, the HFS mount-
volume routine will respect the bad-block extents in the B*-tree even though there is
no catalog entry.
To identify imperfect disks, we set the 0x0200 ("vcbAtBB") volume attribute bit in the MDB.
The bad extents are described in the B*-tree with reserved file-ID 5 ("fsBBCNID").
Note that these routines are intimately aware of the internal structure of HFS volumes,
and do not use any of the HFS subroutines. Thus this module is sure to break if the file
format or even arbitrary decisions made by 'DiskInitHFS.a' change. In addition, testing
is done a track at a time, which means that this module is also aware of disk geometry.
We do this both for speed -- the floppy drivers xfer the whole track anyway -- and because
experience has shown that if a track has any imperfections, its best to avoid it completely.
See "diNextTrack", which is set up in such a way that even if we get the geometry wrong
the algorithms still work, albeit less efficiently.
We limit the number of badspot extents to "MAXBAD", where an extent is an arbitrarily large set
of contiguous sectors. This is because we allocate fixed-size arrays for simplicity, and also
because we don't bother to split B*-tree nodes when hiding the bad extents in the extent B*-tree.
MAXBAD is set to the max number of extents that will fit in a single 512-byte block. In practice
this is not a problem; after coallescing, there are rarely more than a few bad extents.
On the other hand, these routines will handle arbitrarily large disks (and thus bitmaps),
and although the sector size is wired in with the "SECTOR_xxx" defines, it will be pretty
easy to make these variables rather than constants. Note, however, that in practice the
floppy disks that are actually operated on have blocksize equal to sectorsize, and also
are small enough that their bitmaps are only one sector long. Thus the more complex cases
will probably never have been executed/tested as you read this.
Notation:
sector == Physical block, as defined by the driver. In HFS, these are assumed to
be 512 bytes long ("SECTOR_BYTES").
block == Allocation block, or unit of HFS allocation. These are always a multiple
of sectors, set so that the number of blocks on a device will not overflow a
16-bit field. In practice, the number of sectors per block on the floppies
that these routines actually deal with is always one, since they don't have
more than 2**16 sectors, but we don't rely on this.
*/
#include <Errors.h>
#include <Files.h>
#include <Devices.h>
#include <Memory.h>
#include <HFSDefs.h>
#include <Disks.h>
typedef unsigned long ulong; /* idiosyncrasy of the author */
typedef unsigned short ushort;
typedef unsigned char uchar;
#define BUFSIZE 18 /* test buffer size, in sectors (mb >=18) */
/* 18 is bad 'cause it's greater than a track (KSCT) */
#define CLUMP 1 /* coallesce badspots only if they are contig */
#define SECTOR_BYTES 512 /* HFS sector size in bytes */
#define SECTOR_ULONGS (SECTOR_BYTES / sizeof(ulong)) /* ulongs per sector */
#define SECTOR_BITS (SECTOR_BYTES * 8) /* bits per sector */
#define SECTOR_BYTES_LOG 9 /* log2(bytes per sector) */
#define SECTOR_BITS_LOG (SECTOR_BYTES_LOG + 3) /* log2(bits per sector) */
#define MAXBAD 66 /* limit on bad extent count (see above) */
#define BAD_DATA 1 /* internal ioResult for bad data read */
#define SOFT_ERRORS 2 /* internal ioResult for soft errrors */
#define STATUS_OK 0 /* diCategorize: good status */
#define STATUS_BAD_DRIVE 1 /* diCategorize: drive failure */
#define STATUS_BAD_MEDIA 2 /* diCategorize: media failure */
#define FINDER_6x_KLUDGE 1594 /* magic: #ablocks in 800K floppy */
/* change PERCENTBAD from 3 to 2 so we'll sparing more bad blocks -- KST */
#define PERCENTBAD 2 /* allow only 1/4 (25%) bad sectors */
/* The following structure contains the data computed during pass 1 that is used during
pass 2. It is kept in the heap on a handle provided by our caller (ie, the disk init
package) across the two calls. */
typedef struct data {
int bsize; /* block size in bytes */
int spb; /* sectors per block, probably 1 */
long nsectors; /* sectors on the disk */
int nblocks; /* blocks on the disk (nsectors / spb) */
int cnt; /* number of bad extents in array below */
ushort loc[MAXBAD]; /* bad extent start, in blocks */
ushort len[MAXBAD]; /* bad extent length, in blocks */
} data;
/*****************
* d i A b o r t *
*****************
Our custom system error routine, which consists of the "ILLEGAL" opcode. Called with an
integer argument which differs at each place it is called, for easy identification under
MacsBug. Under normal operation in the field, the result is a system crash.
*/
static pascal void diAbort(
int errorCode) /* used to identify location in code */
= 0x4AFC; /* ILLEGAL */
/*************
* d i S e t *
*************
Called to fill a buffer with a test pattern. The buffer must be an integral number of
ulongs in length.
*/
static void diSet(
void *bufp, /* the buffer */
int sectors, /* buffer size in sectors */
ulong pattern) /* the test pattern */
{
ulong *p = bufp; /* ptr into the buffer */
int n; /* index*/
while( sectors-- ) /* for each sector in the buffer */
for( n = SECTOR_ULONGS; n; n--) /* for each ulong in the sector */
*p++ = pattern; /* stuff in the pattern */
}
/***************
* d i R e a d *
***************
Read a sector. We assume that the passed parameter block has been set up with the proper
ioVRefNum, ioRefNum, ioBuffer, and ioPosMode (ie, "from start").
*/
static OSErr diRead(
IOParam *iopp, /* the parameter block */
long sector, /* the phys sector# */
int n) /* number of sectors to read */
{
iopp->ioPosOffset = sector << SECTOR_BYTES_LOG;/* fill in the byte address */
iopp->ioReqCount = n << SECTOR_BYTES_LOG; /* fill in buffer length */
return PBRead((ParmBlkPtr)iopp,false); /* do the read, return driver's status */
}
/*****************
* d i W r i t e *
*****************
Write a sector. We assume that the passed parameter block has been set up with the proper
ioVRefNum, ioRefNum, ioBuffer, and ioPosMode (ie, "from start"). Note that the 14 tag bytes
in low memory are not used anymore, and that we therefore leave them the way the last 'diRead'
left them.
*/
static OSErr diWrite(
IOParam *iopp, /* the parameter block */
long sector, /* the phys sector# */
int n) /* number of sectors to write */
{
iopp->ioPosOffset = sector << SECTOR_BYTES_LOG;/* fill in the byte address */
iopp->ioReqCount = n << SECTOR_BYTES_LOG; /* fill in buffer length */
return PBWrite((ParmBlkPtr)iopp,false); /* do the write, return driver's status */
}
/***************************
* d i E r r o r C o u n t *
***************************
Read the error counter maintained by the disk driver.
*/
static int diErrorCount(
IOParam *iopp) /* I/O parameter block */
{
DrvSts sta; /* the drive status buffer */
if (DriveStatus(iopp->ioVRefNum,&sta)) /* get the status info */
return 0; /* return 0 if call fails */
return sta.diskErrs; /* OK: return error count */
}
/*****************
* d i W h e r e *
*****************
Set a breakpoint here and "dm a7" to see where the badspots are, and what the status was.
*/
static void diWhere(
int cylinder, /* cylinder# (0..79, usually) */
int track, /* track# in the cylinder (0..1) */
int status) /* the bad status (an ioResult, usually) */
{
#pragma unused(cylinder)
#pragma unused(track)
#pragma unused(status)
}
/*************************
* d i N e x t T r a c k *
*************************
Determine how many sectors to test next. We try to test a track at a time, even though
that means we need to know about disk geometry, because its both much faster to do so and
because we've found that if any part of a track is bad, the entire track is probably suspect.
Even if we are wrong and miscompute the size of the next track, things will still work, though
they'll be inoptimal. Note that, when initially called, both the track# and Cyl# are -1.
The return value is the number of sectors in the next track, or 0 if we've reached the end.
*/
static int diNextTrack(
long sector, /* next sector to xfer */
int *cylp, /* ptr to cylinder# (0..79, usually) */
int *trackp, /* ptr to track# (0..1) */
long size) /* size of disk, in sectors */
{
int trackSize;
if (*trackp==0) /* if we were on side 0 */
*trackp = 1; /* switch to side 1 */
else {
*cylp += 1; /* else switch to next cylinder */
*trackp = 0; /* side 0 */
}
if (size==(1440*2)) /* 1440K MFM floppy? */
trackSize = 18; /* 18 sectors per track */
else if (size==(800*2)) /* 800K GCR floppy? */
trackSize = 12 - (*cylp>>4); /* variable sectors/track */
else if (size==(720*2)) /* single-density MFM disk? */
trackSize = 9; /* half as many sectors as FDHD */
else trackSize = BUFSIZE; /* unknown disk, use full buffer */
size -= 2; /* never test last two sectors (alt MDB) */
if ((sector + trackSize) >= size) { /* will we run past end? */
trackSize = size - sector; /* yes, clamp down sectors to xfer */
if (trackSize < 0) /* if overrun... */
trackSize = 0; /* ...return 0 to flag end of disk */
}
return trackSize; /* this is #sectors to test */
}
/***************
* d i T e s t *
***************
Test a group of sectors with a given pattern. A test consists of writing the test pattern to
the sectors, reading it back in, and checking to see that we read in the correct data. We
assume that the passed buffer has been initialized with the test pattern, and always leave the
buffer as we found it. The result of our test is returned in "iopp->ioResult", and will be
"noErr" if the test was passed, or any of several I/O or internally-generated errors on
failure. The parameter block we are passed is assumed to be set up with the proper ioVRefNum,
ioRefNum, ioBuffer, and ioPosMode (ie, "from start"). In addition to the I/O errors returned by
the driver, we add two of our own:
BAD_DATA Both the read and the write returned "noErr", but the data read was
not correct. Unlikely, as the sectors are protected with CRC checksums,
but driver bugs and parity errors in RAM can cause this to happen.
SOFT_ERRORS Both tha read and the write returned "noErr", and the data read was
correct, but the driver reported one or more "soft" (correctible)
errors.
We actually read the test pattern twice, once normally and once in verify mode, due to rumors
that some 3rd-party drivers do not support the verify function but simply return "noErr".
Also, its good to try several times, in case there's a latent error.
*/
static void diTest(
long sector, /* 1st physical sector to be tested */
int nsectors, /* number of sectors to test */
IOParam *iopp, /* param block, initialized (see above) */
ulong pattern) /* the test pattern */
{
ulong *p = (ulong*) iopp->ioBuffer; /* ptr to buffer, as ulong array */
int i, j; /* temps */
int errs = diErrorCount(iopp); /* count of errors before test */
if (diWrite(iopp,sector,nsectors)) /* write pattern, and if it fails... */
return; /* ...test is done */
if (diRead(iopp,sector,nsectors)) { /* read pattern, and if it fails... */
diSet(iopp->ioBuffer,nsectors,pattern); /* restore pattern after bad data read */
return; /* return bad status in ioResult */
}
for( j = nsectors; j; --j) /* for each sector in group */
for( i = SECTOR_ULONGS; i; --i) /* for each ulong in the sector */
if (*p++ != pattern) { /* if data read != data written */
diSet(iopp->ioBuffer,nsectors,pattern); /* restore pattern */
iopp->ioResult = BAD_DATA; /* internal status for bad data */
return; /* fail on first inconsistency */
}
iopp->ioPosMode += rdVerify; /* OK so far: set mode to "verify" */
j = diRead(iopp,sector,nsectors); /* read 2nd time in verify mode */
iopp->ioPosMode -= rdVerify; /* restore mode */
if ( j==0 && (diErrorCount(iopp) != errs)) /* if correctible errors occured... */
iopp->ioResult = SOFT_ERRORS; /* ...then say so */
}
/***************************
* d i C a t e g o r i z e *
***************************
Called with the ioResult returned by "diTest", to categorize it as good, media failure, or
drive failure. Note that the default category is "bad media". There is little to loose if
we incorrectly map an unknown status to "bad media", even if it means "bad drive", because
we'll probably get the same bad status on nearly every sector, and thus fail to format the
disk because MAXBAD overflows. The major policy decision embedded in our categorization is
the way we map soft errors to "bad media". This is because we want to be very conservative
when formatting the disk. If a sector is marginal now, we assume it may produce hard errors
in the future.
*/
static int diCategorize(
short ioResult) /* the status returned by driver */
{
switch( ioResult ) { /* branch on status */
case noErr: /* the sole good status? */
return STATUS_OK;
case initIWMErr: /* cannot initialize IWM */
case cantStepErr: /* step handshake failed */
case tk0BadErr: /* track 0 detect doesn't change */
case spdAdjErr: /* unable to adjust disk speed */
return STATUS_BAD_DRIVE;
case BAD_DATA: /* data read ok but value incorrect */
case SOFT_ERRORS: /* correctible errors encountered */
default: /* map other statuses to "bad media" */
return STATUS_BAD_MEDIA;
}
}
/***********************
* d i C r i t i c a l *
***********************
Called for each group of sectors that are "bad". We decide whether they are critical, in the
sense that a sector has special meaning and cannot simply be hidden in the extent B*-tree.
We also declare a sector critical if it is touched by "DiskInitHFS.a", as that routine is called
*before* diBadBlockPass2, and thus before we can hide the bad blocks. The critical sectors are
as follows, starting at 0:
the 2 boot sector
the master directory (MDB) sector
the sectors that the bitmap will be written to
the initial extent of the Extent B*-tree
the initial extent of the Catalog B*-tree
Thus, if the bitmap is 'b' sectors long, and each of the B*-tree initial extents are 'e' sectors
long, then the first '2+1+b+2e' sectors are critical.
Note that we don't check for the sector the alternate copy of the MDB is written to, which is the
2nd from the end of the disk. This is because experience has shown that on floppies
the last track is the most error prone by far, and if we insisted that it be perfect too many
disks would be rejected. So we stop testing at the last sector in the allocatable region of the
disk. Then, "diBadBlockPass1" tests the alternate MDB sector individually, and tolerates
soft errors in this test.
We return true if any sector in the group is critical.
*/
static int diCritical(
long sector, /* the 1st sector in the group */
data *g) /* the global data */
{
long b; /* size of bitmap in sectors */
long e; /* size of init B*-tree extents, sectors */
long firstOK; /* first non-critical sector */
/* Conservatively compute 'b' and 'e'. If we get them too high, it just means that
we'll reject a disk that might have been useable. Better this than trying to use
a bad disk! Note that we're really in bed with "DiskInitHFS.a" here. */
e = (g->nblocks >> 7) * g->spb; /* extent size is #blocks/128 */
b = (g->nblocks >> SECTOR_BITS_LOG) + 1; /* number of sectors in the bitmap */
firstOK = 3 + b + (e<<1); /* first noncritical sector */
if (sector < firstOK) /* if on the critical list... */
return true; /* then say so */
else return false; /* block is not critical */
}
/*********************
* d i B a d S p o t *
*********************
Add a group of bad blocks to the bad extent list. As we do so, we coallesce adjacent bad-spots
by creating an extent that encorporates them all. We return true iff the extent list overflows.
Note that this routine relies on one pass across the disk, starting with sector 0. If you decide
to make multiple passes, you'll have to extend the algorithm to allow interior insertions.
*/
/* 21Nov90 KSCT CLUMP size 18 is bad 'cause it's greater than a track, and we are coallesce
* a whole track into the bad extent.
*/
static int diBadSpot(
long sector, /* the 1st bad sector in group */
int sects, /* number of bad sectors */
data *g) /* the global data, w extent list */
{
int n = g->cnt; /* get number of bad extents so far */
int bb = sector / g->spb; /* 1st block in group of bad sectors */
int be = ((sector+(sects-1)) / g->spb) + 1; /* 1st block not in group of bad sectors */
int end; /* 1st block not in current ext, if any */
if (n!=0) { /* if these are not first bad sectors */
end = g->loc[n-1] + g->len[n-1]; /* 1st block not in current extent */
/* We coallesce two badspots if separated by no more than CLUMP good sectors. */
/* We coallesce two badspots only if they are contiguous. -- KSCT */
if ((bb-end)*g->spb <= CLUMP) { /* if we should coallesce */
be -= end; /* be <- number of blocks added to extent */
if (be<0) /* abort if badspots not left-to-right */
diAbort(0); /* can't call "diSweep" more than once */
g->len[n-1] += be; /* coallesce adjacent badspots into 1 ext */
return false; /* done, no overflow */
}
}
if (n>=MAXBAD) /* overflow? */
return true; /* fail if so */
g->loc[n] = bb; /* this sector starts a new bad extent */
g->len[n] = be - bb; /* which is initially this long */
g->cnt++; /* increment bad extent count */
return false; /* added wo overflow */
} /* diBadSpot */
/*****************
* d i S w e e p *
*****************
Make a pass, or sweep, through the disk checking for badspots. We return true iff the sweep
fails due to some critical error, making the disk unformattable.
*/
/***********************************************************************************
* Modification history:
* 15Nov90 KSCT Return meaningful "error code" so we know why diSweep (Pass1) failed
***********************************************************************************/
#define PS1CANTRDTHESEC 17 /* can't read the sector for alt MDB */
#define PS1NOMEMERR memFullErr /* no heap memory (this is negative) -- <08Feb91 #6> */
#define PS1BADDRIVE 19 /* hardware failure (hopeless) */
#define PS1CRITICAL 20 /* critical sectors are bad (hopeless) */
#define PS1EXTOVERFLOW 21 /* extent record overflow (n>=MAXBAD) */
#define PS1TMBADSECS 22 /* too many (> 25%) bad sectors */
/***********************************************************************************
* Modification history:
* 25Jan91 KSCT Allocate buffer from system heap, not on stack., (brc#81598)
***********************************************************************************/
static int diSweep(
long nsectors, /* number of sectors on the volume */
IOParam *iopp, /* param block, with drive# and refnum */
data *g, /* the global data */
long pattern) /* the test pattern */
{
long i = 0; /* sector index */
int cyl = -1; /* current cylinder */
int track = -1; /* current track */
int n; /* number of sectors tested each pass */
long bad = 0; /* rough count of bad sectors */
char *buf; /* buffer of BUFSIZE sectors for tests -- <08Feb91 #6> */
THz currentZone; /* current zone -- <08Feb91 #6> */
/* allocate buffer space from system heap -- <08Feb91 #6> */
currentZone = GetZone(); /* -- <08Feb91 #6> */
SetZone(SystemZone()); /* -- <08Feb91 #6> */
buf = NewPtr(BUFSIZE*SECTOR_BYTES); /* get the pointer from system heap -- <08Feb91 #6> */
SetZone(currentZone); /* -- <08Feb91 #6> */
if (buf == NULL) return PS1NOMEMERR; /* ...not enough memory, give up -- <08Feb91 #6> */
/* Set up the test pattern. */
diSet(buf,BUFSIZE,pattern); /* fill the buffer */
iopp->ioBuffer = buf; /* set up buffer pointer */
iopp->ioPosMode = fsFromStart; /* position from start of device */
/* Loop over each track (more or less). */
for(;;) {
n = diNextTrack(i,&cyl,&track,nsectors);/* get size of next track */
if (n==0) /* if no more... */
return false; /* ... then done */
diTest(i,n,iopp,pattern); /* write the pattern and verify */
switch( diCategorize(iopp->ioResult)) { /* categorize status */
case STATUS_OK: /* sector looks good */
break; /* nothing to do */
case STATUS_BAD_DRIVE: /* we think its the drive, not the media */
return PS1BADDRIVE; /* don't try to format if drive is bad */
case STATUS_BAD_MEDIA:
diWhere(cyl,track,iopp->ioResult);/* make it easy if debugging */
if (diCritical(i,g)) /* if these sectors must not be bad */
return PS1CRITICAL; /* then disk also unuseable */
if (diBadSpot(i,n,g)) /* add bad sectors to bad extent list */
return PS1EXTOVERFLOW; /* fail if list overflows (n>=MAXBAD) */
bad += n; /* update count of bad sectors */
if (bad > (nsectors>>PERCENTBAD)) /* more than 25% of disk bad? */
return PS1TMBADSECS+bad; /* yes, too many bad sectors, so quit */
break;
default: /* here on unknown status category */
diAbort(1); /* crump */
}
i += n; /* advance to next track */
}
}
/*********************************
* d i B a d B l o c k P a s s 1 *
*********************************
Called when the disk fails to verify. For each track, we write and verify a few test patterns,
building a list of bad extents. We return true if the disk is unuseable and false if useable.
*/
/***********************************************************************************
* Modification history:
* 15Nov90 KSCT Return meaningful "error code" so we know why Pass1 failed.
* 25Jan91 KSCT Allocate buffer from heap, not on stack.
***********************************************************************************/
int diBadBlockPass1(
long nsectors, /* number of sectors on the volume */
IOParam *iopp, /* param block, with drive# and refnum */
void *bufp, /* buffer of size SECTOR_BYTES */
Handle *hp) /* ptr to handle to global data */
{
data g; /* the global data */
int err;
/* Initialize the global data that is later used by Pass 2. */
g.nsectors = nsectors; /* remember number of sectors on disk */
g.spb = (nsectors >>16) + 1; /* compute sectors per block */
g.nblocks = nsectors / g.spb; /* compute number of blocks */
g.bsize = SECTOR_BYTES * g.spb; /* compute block size in bytes */
g.cnt = 0; /* initially, no bad extents found */
/* Make a special check to see if the alternate MDB sector is useable. This check
allows soft errors (resolved with normal driver error retry). In contrast,
the main diTest checks are more conservative; they reject an entire track if any
sector on it is bad or has soft errors. Thus we can use a disk whose alt MDB is
readable, even though the track its on is in a bad-spot. This is an important
special case, since on floppies track 79 side 1 is by far the most error prone. */
iopp->ioBuffer = bufp; /* set up buffer pointer */
if (diRead(iopp,nsectors-2,1)) /* read the sector */
return PS1CANTRDTHESEC; /* fail if we cannot read it */
/* clear g-> for easy debugging: <KT/07Dec90> */
for (err=0; err<MAXBAD; err++) { /* clear them all <KT/07Dec90> */
g.loc[err] = 0; /* <KT/07Dec90> */
g.len[err] = 0; /* <KT/07Dec90> */
}
/* Certify disk with each of our test patterns, compiling badspot list. *WARNING*,
the "diBadSpot" routine does not yet handle multiple passes, as it expects
badspots to be discovered in order. Ie, don't call diSweep more than once! */
if (err=diSweep(nsectors,iopp,&g,0x504B4B54)) /* try first test pattern */
return err; /* serious failure, cannot format disk */
/* If our tests showed no errors, return a good status (ie, "false") without
setting up the handle, so our caller won't bother to call the 2nd pass. */
if (g.cnt==0) /* if we don't find ANY bad sectors... */
return false; /* ...then don't save the global data */
/* We've found bad-spots, but not too many and not in critical places. Save the
global data for diBadBlockPass2 and return a good status. */
*hp = NewHandle(sizeof(data)); /* get the handle */
if (*hp==NULL) /* did allocate fail? */
return PS1NOMEMERR; /* yes, cannot format if out of memory */
BlockMove((char*)&g,**hp,sizeof(g)); /* copy data to handle */
return false; /* normal exit, disk is formattable */
}
/*******************
* d i M a r k B M *
*******************
Mark a contiguous set of bits in the bitmap. We are passed, and return, the bitmap sector#
which is currently in the buffer. If we return (-1), something is wrong and our caller should
return failure.
*/
static int diMarkBM(
IOParam *iopp, /* IO parameter block */
int cursector, /* current bitmap sector */
int bmstart, /* 1st bitmap sector */
int start, /* first block# to mark */
int len) /* number of blocks in extent */
{
int bit0; /* block mapped by 1st bit in sector */
int i, n; /* temps */
uchar *b; /* ptr into sector buffer */
/* Loop over each sector of bitmap covered by this extent. */
while( len > 0 ) { /* while there's more to do */
/* Get block# mapped by the first bit in the current sector. */
bit0 = (cursector - bmstart) << SECTOR_BITS_LOG;
/* Make sure current sector contains 1st bit of extent. */
if (start<bit0 || start>=(bit0+SECTOR_BITS)) {
i = start >> SECTOR_BITS_LOG; /* get sector# from bitmap start */
n = i + bmstart; /* get sector# we need */
if (diWrite(iopp,cursector,1)) /* write out current block... */
return -1; /* ...fail if write fails */
if (diRead(iopp,n,1)) /* then read the sector we want */
return -1;
cursector = n; /* reset current sector */
bit0 = i << SECTOR_BITS_LOG; /* reset first block# mapped by sector */
}
i = start - bit0; /* offset within sector of 1st bit to set */
n = SECTOR_BITS - i; /* bits in sector that follow 1st bit */
if (n > len) /* more bits available than we need? */
n = len; /* yes, clamp down */
start += n; /* compute 1st bit not in sector */
len -= n; /* and bits remaining to be marked */
b = ((uchar*)iopp->ioBuffer) + (i>>3); /* point to 1st byte to mark */
i = 0x80 >> (i&7); /* get 1st bit to mark */
/* Loop over each bit, marking one at a time. This is slow of course, but
the theory is that most badspots are short, and its more important to
keep the code short and simple. */
while( --n >= 0 ) { /* i == next bit to mark */
if (*b & i) /* bit already lit? */
diAbort(2); /* serious confusion if so */
*b |= i; /* light the bit */
i >>= 1; /* shift right, to get next bit to mark */
if (i==0) { /* end of byte? */
b++; /* yes, advance byte ptr */
i = 0x80; /* start with leftmost bit of next byte */
}
}
}
return cursector; /* done: return current sector# */
}
/*****************
* d i B t r e e *
*****************
Add the bad extents to the extent btree. This involves updating the btree header node to
show that the 2nd block of the extent file is the root (the btree is initially empty), and
filling the 2nd block as the root and sole leaf node. We insist that all bad extents fit
in one node of the tree, to avoid the complexity of splitting the root. This limits the
number of bad extents to MAXBAD (66).
*/
/***********************************************************************************
* Modification history:
* 25Jan91 KSCT Return low level error code to DIBadMount.
***********************************************************************************/
static short diBtree( /* -- <08Feb91 #5> */
IOParam *iopp, /* IO parameter block */
data *g, /* the global data, including bad extents */
int exstart) /* first sector in extent btree */
{
NDPtr nh = (NDPtr) iopp->ioBuffer; /* ptr to btree node header */
BTHPtr bth; /* ptr to btree header record */
xkrPtr k; /* ptr to extent record key */
ushort *p, *b, *locp, *lenp; /* temps */
int n, i, j; /* temps */
int nrecs = (g->cnt+NumExts-1) / NumExts; /* extent records needed (3 per) */
short err; /* -- <08Feb91 #5> */
/* Read in and message the btree header node. */
if (err=diRead(iopp,exstart,1)) /* read in header node */
return err; /* crump on failure -- <08Feb91 #5> */
if (nh->NDType!=NDHdrNode || nh->NDNRecs!=3) /* does it look like a btree hdr? */
diAbort(3); /* no */
p = (ushort*) (iopp->ioBuffer + BTNodeSize); /* point to base of record offsets */
bth = (BTHPtr)(iopp->ioBuffer + *(p - HRec_BTH));/* point to btree header record */
if (bth->BTHNRecs!=0 || bth->BTHFNode!=0 || bth->BTHKeyLen!=Max_XKey)
diAbort(4); /* abort if it looks bogus */
bth->BTHDepth += 1; /* change depth from 0 to 1 */
bth->BTHRoot += 1; /* change root from 0 to 1 */
bth->BTHNRecs = nrecs; /* set up number of extent tree records */
bth->BTHFNode += 1; /* change first leaf node from 0 to 1 */
bth->BTHLNode += 1; /* change last leaf node from 0 to 1 */
bth->BTHFree -= 1; /* decrement number of free nodes */
b = (ushort*)(iopp->ioBuffer + *(p - HRec_Map));/* point to btree map record */
if (*b != 0x8000) /* abort if bitmap doesn't look correct */
diAbort(5); /* ie, only first block s.b. in use */
*b = 0xC000; /* mark 2nd block as used as well */
if (err=diWrite(iopp,exstart,1)) /* write modified header back out */
return err; /* -- <08Feb91 #5> */
/* Build the new root node, containing our bad extents. */
diSet(nh,1,0); /* first, zero out the block */
nh->NDType = NDLeafNode; /* this is a leaf node, as well as root */
nh->NDNHeight = 1; /* leafs are height 1 */
nh->NDNRecs = nrecs; /* fill in record count */
k = (xkrPtr) (nh + 1); /* point to first extent key in the node */
locp = g->loc; /* initialize ptr into extent start array */
lenp = g->len; /* and into extent length array */
i = g->cnt; /* get #extents */
n = 0; /* accumulate position in bb 'file' */
/* Loop over each extent record. */
for(;;) { /* break out when i==0 */
*--p = ((char*)k) - iopp->ioBuffer; /* stuff record offset into array */
if (i==0) /* done? */
break; /* yes, exit loop */
k->xkrKeyLen = Max_XKey; /* key length (7) */
k->xkrFNum = BB_FNum; /* canonic file number for bad-blocks (5) */
k->xkrFABN = n; /* stuff in cumulative block count */
b = (ushort*) (k + 1); /* point to first extent in record */
k = (xkrPtr) (b + 6); /* point to next record */
for( j = 3; j; j--) { /* loop over each extent in this record */
n += *lenp; /* advance length of bad-block file */
*b++ = *locp++; /* stuff in next block# */
*b++ = *lenp++; /* and length */
if ((--i)==0) /* more extents? */
break; /* exit inner loop if not */
}
}
if (((char*)p) < ((char*)k)) /* did node overflow? (too many rcrds) */
diAbort(6); /* yes, crump (MAXBAD too high) */
return diWrite(iopp,exstart+1,1); /* write out the block and done */
}
/*********************************
* d i B a d B l o c k P a s s 2 *
*********************************
Called after "diBadBlockPass1" has compiled a list of bad extents, and the volume has been
initialized by "DiskInitHFS.a". We must adjust the MDB, bitmap, and extents file by hiding
the bad sectors. We return true if the disk is unuseable (we failed), and false if successful.
In either case, we free the global data handle before returning.
*/
/***********************************************************************************
* Modification history:
* 21Nov90 KSCT We have created the volume, what if Pass2 failed ??
* 25Jan91 KSCT Return low level error code to DIBadMount.
***********************************************************************************/
short diBadBlockPass2( /* return short -- <08Feb91 #5> */
void *bufp, /* ptr to buffer of size SECTOR_BYTES */
IOParam *iopp, /* param block, with drive# and refnum */
Handle bbh) /* handle to global data, w bad sectors */
{
data g; /* the global data, passed by handle */
MDBPtr d = bufp; /* used to ref MDB in buffer */
int bmstart; /* sector# bit map begins on */
int exstart; /* sector# extent tree begins on */
int i, j; /* temps */
ushort *p, *b; /* ptrs into g.loc[] and g.len[] */
short err; /* error code -- <08Feb91 #5> */
/* Copy global data into our SF and free the handle. */
i = GetHandleSize( bbh ); /* get byte length of global data */
if (i != sizeof(data)) /* consistent? */
diAbort(7);
BlockMove(*bbh,(char*)&g,i); /* copy heap->sf */
DisposHandle(bbh); /* done with the handle */
/* Get the MDB. */
iopp->ioBuffer = bufp; /* set up buffer pointer */
iopp->ioPosMode = fsFromStart; /* position from start of device */
if (err=diRead(iopp,MDB_BlkN,1)) /* read MDB, and if it fails... */
return err; /* ...we failed -- <08Feb91 #5> */
if (d->DrSigWord != HFS_SigW || /* if we get garbage... */
d->DrAlBlkSiz != g.bsize) /* ...or if we've miscomputed block size */
diAbort(8); /* abort */
/* Pass 1 has compiled the "g.loc[]" array as the block that the extent begins on
relative to sector#0, but HFS uses block#s that start at the first allocatable
SECTOR, which is "DrAlBlSt". Now that we know the value of DrAlBlSt, we can go
through the list relocating the extent starts. */
p = g.loc; /* initialize ptr into extent starts */
for(i = g.cnt; i>0; i--) { /* for each bad extent */
j = *p * g.spb; /* get extent start, convert to sectors */
j -= d->DrAlBlSt; /* relocate */
if (j < 0) /* die if alloc block# goes negative */
diAbort(9);
*p++ = j / g.spb; /* pack back into array */
}
/* Special case for 800K GCR disk: decrease the number of allocation blocks by one.
This is so 6.x Finders won't do disk-to-disk copies physically, which is an
optimization triggered only if both disks have exactly 1594 (0x63A) allocation blocks.
We don't want them to try to do a physical copy as they'd see the bad blocks. */
if (d->DrNmAlBlks == FINDER_6x_KLUDGE) { /* the magic number finder looks for? */
d->DrNmAlBlks--; /* yes, toss one */
d->DrFreeBks--; /* and decrement free block count too */
i = g.loc[g.cnt - 1]; /* get start of last badspot */
j = g.len[g.cnt - 1]; /* and length */
if ((i+j) > d->DrNmAlBlks) { /* if last badspot contains 1594th block */
j--; /* then take out of badspot too */
if (j<=0 || (i+j)!=d->DrNmAlBlks)
diAbort(10); /* die if last badspot too long or short */
g.len[g.cnt - 1] = j; /* update last badspot */
}
}
/* Sum number of bad blocks, and subtract from MDB free block count. */
i = 0; /* initialize bad block count */
b = g.len; /* initialize ptr into bad extent list */
for( j = g.cnt; j; j--) /* for each bad extent... */
i += *b++; /* ...add in its size */
d->DrFreeBks -= i; /* adjust free block count */
/* Update both copies of the MDB on disk, and salt away the data we need before its gone. */
d->DrAtrb |= VAtrb_BB; /* set bit for spared bad blocks */
if (err=diWrite(iopp,MDB_BlkN,1)) /* write primary copy */
return err; /* give up if write fails -- <08Feb91 #5> */
if (err=diWrite(iopp,g.nsectors-2,1)) /* write backup copy at end of disk */
return err; /* give up if write fails -- <08Feb91 #5> */
bmstart = d->DrVBMSt; /* save sector# bit map begins on */
exstart = d->DrXTExtRec[0].extStABN; /* get starting block of extent tree */
exstart = (exstart * g.spb) + d->DrAlBlSt; /* save sector# extent tree begins on */
/* Go through our extent list, setting bits in the bitmap for the unuseable blocks. */
p = g.loc; /* initialize ptr into extent starts */
b = g.len; /* and ptr into extent sizes */
j = bmstart; /* 'j' will carry current bitmap sector# */
if (err=diRead(iopp,j,1)) /* read in 1st bitmap sector */
return err; /* fail if cannot read it -- <08Feb91 #5> */
for(i = g.cnt; i>0; i--) { /* for each bad extent */
j = diMarkBM(iopp,j,bmstart,*p++,*b++);
if (j<0) /* quit if mark failed somehow */
return ioErr; /* treat it as io error -- <08Feb91 #5> */
}
if (j >= exstart) /* did bitmap wander up into extent tree? */
diAbort(11); /* if so, we've blown it */
if (err=diWrite(iopp,j,1)) /* write out last bitmap sector */
return err; /* <08Feb91 #5> */
/* Go through extent list once more, making an entry in the extent B*-tree for each. */
return diBtree(iopp,&g,exstart);
}