www.pudn.com > tfs.rar > TFSCLEAN.C


/* tfsclean.c:
 *  This portion of the tfs code supports the file system cleanup or
 *  defragmentation.  If INCLUDE_TFSSAFEDEFRAG is set, then the power-safe
 *  cleanup is in place; otherwise, a much simpler and much less robust
 *  cleanup is used.
 *
 *  General notice:
 *  This code is part of a boot-monitor package developed as a generic base
 *  platform for embedded system designs.  As such, it is likely to be
 *  distributed to various projects beyond the control of the original
 *  author.  Please notify the author of any enhancements made or bugs found
 *  so that all may benefit from the changes.  In addition, notification back
 *  to the author will allow the new user to pick up changes that may have
 *  been made by other users after this version of the code was distributed.
 *
 *  Note1: the majority of this code was edited with 4-space tabs.
 *  Note2: as more and more contributions are accepted, the term "author"
 *         is becoming a mis-representation of credit.
 *
 *  Original author:    Ed Sutter
 *  Email:              esutter@lucent.com
 *  Phone:              908-582-2351
 */
#include "config.h"
#include "cpu.h"
#include "stddefs.h"
#include "genlib.h"
#include "tfs.h"
#include "tfsprivate.h"
#include "flash.h"
//#include "monflags.h"

#if INCLUDE_TFS

//static int      TfsCleanEnable;

#if INCLUDE_TFSSAFEDEFRAG   /* This pulls in the power-safe cleanup */

#if DEFRAG_TEST_ENABLED
void
defragExitTestPoint(int val)
{
    if ((DefragTestType == DEFRAG_TEST_EXIT) && (DefragTestPoint == val)) {
        printf("\n+++++++++ EXIT @ TEST POINT(%d)\n",val);
        CommandLoop();
    }
}
#else
#define defragExitTestPoint(val)
#endif

/* Variables for testing tfsclean():
 * They are set up through arguments to "tfs clean" in tfscli.c.
 */
int DefragTestType;
int DefragTestPoint;
int DefragTestSector;

/* defragTick():
 * Used to show progress, just to let the user know that we aren't
 * dead in the water.
 */
static void
defragTick(int verbose)
{
    static int tick;
    static char clockhand[] = { '|', '/', '-', '\\' };

    if (!verbose && (!MFLAGS_NODEFRAGPRN())) {
        if (tick > 3)
            tick = 0;
        printf("%c\b",clockhand[tick++]);
    }
}

/* defragCrcTable():
 * Return a pointer to the crc table for the specified TFS device.
 */
struct sectorcrc *
defragCrcTable(TDEV *tdp)
{
    return((struct sectorcrc *)(tdp->end+1) - tdp->sectorcount);
}

/* defragSerase():
 * Common function to call from within tfsclean() to erase a sector
 * and generate an error message if necessary.
 *
 * If DEFRAG_TEST_ENABLED is defined and the type/sector/point criteria
 * is met, then instead of erasing the sector; just change the first non-zero
 * byte to zero to corrupt it.  This essentially makes it a corrupted erase.
 */
static int
defragSerase(int tag, int snum)
{
    int ret = 0;

#if DEFRAG_TEST_ENABLED
    int ssize;
    uchar *sbase, *send, zero;

    printf("     serase_%02d(%02d)\n",tag,snum);

    if ((DefragTestType == DEFRAG_TEST_SERASE) &&
        (DefragTestSector == snum) && (DefragTestPoint == tag)) {
            sectortoaddr(snum,&ssize,&sbase);
            send = sbase+ssize;
            zero = 0;
            while(sbase < send) {
                if (*sbase != 0) {
                    tfsflashwrite((ulong *)sbase,(ulong *)&zero,1);
                    break;
                }
                sbase++;
            }
            printf("DEFRAG_TEST_SERASE activated @ %d sector %d\n",tag,snum);
            CommandLoop();
    }
    else
#endif
    ret = tfsflasherase(snum);
    if (ret < 0) {
        printf("tfsclean() serase erase failed: %d,%d,%d\n",snum,tag,ret);
    }
    return(ret);
}

/* defragFwrite():
 * Common function to call from within tfsclean() to write to flash
 * and generate an error message if necessary.
 * If DEFRAG_TEST_ENABLED is defined and the test type is set to 
 * DEFRAG_TEST_FWRITE, then use APPRAMBASE as the source of the data
 * so that the end result is an errored flash write.
 */
static int
defragFwrite(int tag, uchar *dest,uchar *src,int size)
{
    int ret = 0;

#if DEFRAG_TEST_ENABLED
    int snum;

    addrtosector((char *)dest,&snum,0,0);
    printf("     fwrite_%02d(%d,0x%lx,0x%lx,%d)\n",
        tag,snum,(ulong)dest,(ulong)src,size);

    if ((DefragTestType == DEFRAG_TEST_FWRITE) &&
        (DefragTestSector == snum) && (DefragTestPoint == tag)) {
            tfsflashwrite((ulong *)dest,(ulong *)getAppRamStart(),size/2);
            printf("DEFRAG_TEST_FWRITE activated @ %d sector %d\n",tag,snum);
            CommandLoop();
    }
    else
#endif
    ret = tfsflashwrite((ulong *)dest,(ulong *)src,size);
    if (ret < 0) {
        printf("tfsclean() fwrite failed: 0x%lx,0x%lx,%d,%d\n",
            (ulong)dest,(ulong)src,size,tag);
    }
    return(ret);
}

/* defragGetSpantype():
 * With the incoming sector base and end (s_base, s_end),
 * determine the type of span that the incoming file (f_base, f_end)
 * has across it.  There are six different ways the spanning can
 * occur:
 *   1. begin and end in previous active sector (bpep);
 *   2. begin in previously active sector, end in this one (bpec);
 *   3. begin in previously active sector, end in later one (bpel);
 *   4. begin and end in this active sector (bcec);
 *   5. begin in this active sector, end in later one (bcel);
 *   6. begin and end in later active sector (blel);
 */
static int
defragGetSpantype(char *s_base,char *s_end,char *f_base,char *f_end)
{
    int spantype;

    if (f_base < s_base) {
        if ((f_end > s_base) && (f_end <= s_end))
            spantype = SPANTYPE_BPEC;
        else if (f_end > s_end)
            spantype = SPANTYPE_BPEL;
        else
            spantype = SPANTYPE_BPEP;
    }
    else {
        if (f_base > s_end)
            spantype = SPANTYPE_BLEL;
        else if (f_end <= s_end)
            spantype = SPANTYPE_BCEC;
        else
            spantype = SPANTYPE_BCEL;
    }
    return(spantype);
}

/* defragGetSpantypeStr():
 * Return a string that corresponds to the incoming state value.
 */
static char *
defragGetSpantypeStr(int spantype)
{
    char *str;

    switch(spantype) {
    case SPANTYPE_BPEC:
        str = "BPEC";
        break;
    case SPANTYPE_BLEL:
        str = "BLEL";
        break;
    case SPANTYPE_BPEL:
        str = "BPEL";
        break;
    case SPANTYPE_BPEP:
        str = "BPEP";
        break;
    case SPANTYPE_BCEC:
        str = "BCEC";
        break;
    case SPANTYPE_BCEL:
        str = "BCEL";
        break;
    default:
        str = "???";
        break;
    }
    return(str);
}

/* defragEraseSpare():
 * Erase the spare sector associated with the incoming TFS device.
 * The underlying flash driver SHOULD have a check so that it only
 * erases the sector if the sector is not already erased, so this
 * extra check (call to flasherased()) may not be necessary in
 * most cases.
 */
static int
defragEraseSpare(TDEV *tdp)
{
    int snum, ssize;
    uchar *sbase;

    if (addrtosector((char *)tdp->spare,&snum,&ssize,&sbase) < 0)
        return(TFSERR_FLASHFAILURE);

    if (!flasherased(sbase,sbase+ssize)) {
        if (defragSerase(1,snum) < 0) {
            return(TFSERR_FLASHFAILURE);
        }
    }
    return(TFS_OKAY);
}

/* defragValidDSI():
 * Test to see if we have a valid defrag state information (DSI)
 * area.  The DSI area, working back from tdp->end, consists of a
 * table of 32-bit crcs (one per sector), a table of defraghdr
 * structures (one per active file) and a 32-bit crc of the DSI
 * itself.  Knowing this format, we can easily step backwards into
 * the DSI space to see if it all makes sense.
 * If the table is 100% valid, then we will be able to step
 * backwards through the DSI to find the 32-bit crc of the DSI area.
 * If it matches, then we can be sure that the DSI is valid.
 * Return total number of files in header if the defrag header
 * table appears to be sane, else 0.
 */ 
static int
defragValidDSI(TDEV *tdp, struct sectorcrc **scp)
{
    int     ftot, valid, lastssize;
    uchar   *lastsbase;
    struct  sectorcrc *crctbl;
    ulong   hdrcrc, *crc;
    struct  defraghdr   *dhp, dfhcpy;

    ftot = valid = 0;
    crctbl = defragCrcTable(tdp);
    dhp = (struct defraghdr *)crctbl - 1;
    dfhcpy = *dhp;
    hdrcrc = dfhcpy.crc;
    dfhcpy.crc = 0;
    if (crc32((uchar *)&dfhcpy,DEFRAGHDRSIZ) == hdrcrc) {
        ftot = dhp->idx + 1;
        dhp = (struct defraghdr *)crctbl - ftot;
        crc = (ulong *)dhp - 1;
        if (crc32((uchar *)dhp,(uchar *)tdp->end-(uchar *)dhp) == *crc) {
            if (scp)
                *scp = crctbl;
            return(ftot);
        }
    }

    /* It's possible that the DSI space has been relocated to the spare
     * sector, so check for that here...
     */
    addrtosector((char *)tdp->end,0,&lastssize,&lastsbase);

    crctbl = ((struct sectorcrc *)(tdp->spare+lastssize) - tdp->sectorcount);
    dhp = (struct defraghdr *)crctbl - 1;
    dfhcpy = *dhp;
    hdrcrc = dfhcpy.crc;
    dfhcpy.crc = 0;
    if (crc32((uchar *)&dfhcpy,DEFRAGHDRSIZ) == hdrcrc) {
        ftot = dhp->idx + 1;
        dhp = (struct defraghdr *)crctbl - ftot;
        crc = (ulong *)dhp - 1;
        if (crc32((uchar *)dhp,
            (uchar *)(tdp->spare+lastssize-1) - (uchar *)dhp) == *crc) {
#if DEFRAG_TEST_ENABLED
            printf("TFS: DSI in spare\n");
#endif
            if (scp)
                *scp = crctbl;
            return(ftot);
        }
    }
    return(0);
}

/* defragSectorInSpare():
 * For each sector, run a CRC32 on the content of the spare
 * using the size of the sector in question.  If the calculated
 * crc matches that of the table, then we have located the sector
 * that has been copied to the spare.
 * This is a pain in the butt because we can't just run a CRC32 on
 * the spare sector itself because the size of the spare may not match
 * the size of the sector that was copied to it.  The source sector
 * might have been smaller; hence when we calculate the CRC32, we need
 * to use the size of the potential source sector.
 */
static int
defragSectorInSpare(TDEV *tdp, struct sectorcrc *crctbl)
{
    uchar   *sbase;
    struct  defraghdr *dhp;
    int     i, ssize, snum, ftot;

    sbase = (uchar *)tdp->start;
    dhp = (struct defraghdr *)crctbl - 1;
    ftot = dhp->idx + 1;

    for(i=0;isectorcount;i++) {
        addrtosector(sbase,&snum,&ssize,0);
        if (i == tdp->sectorcount - 1) {
            ssize -=                            /* CRC table */
                (tdp->sectorcount * sizeof(struct sectorcrc));
            ssize -= (ftot * DEFRAGHDRSIZ);     /* DHT table */
            ssize -= 4;                         /* Crc of the tables */
        }
        if (crc32((uchar *)tdp->spare,ssize) == crctbl[i].precrc)
            return(snum);
        sbase += ssize;
    }
    return(-1);
}

/* defragTouchedSectors():
 * Step through the crc table and TFS flash space to find the first
 * and last sectors that have been touched by defragmentation.
 * This is used by defragGetState() to recover from an interrupted 
 * defragmentation, so a few verbose messages are useful to indicate
 * status to the user.
 */
void
defragTouchedSectors(TDEV *tdp,int *first, int *last)
{
    uchar   *sbase;
    struct  defraghdr *dhp;
    struct  sectorcrc   *crctbl;
    int     i, ssize, snum, ftot;

    *first = -1;
    *last = -1;
    sbase = (uchar *)tdp->start;
    crctbl = defragCrcTable(tdp);
    dhp = (struct defraghdr *)crctbl - 1;
    ftot = dhp->idx + 1;

    printf("TFS: calculating per-sector crcs... ");
    for(i=0;isectorcount;i++) {
        addrtosector(sbase,&snum,&ssize,0);
        if (i == tdp->sectorcount - 1) {
            ssize -=                            /* CRC table */
                (tdp->sectorcount * sizeof(struct sectorcrc));
            ssize -= (ftot * DEFRAGHDRSIZ);     /* DHT table */
            ssize -= 4;                         /* Crc of the tables */
        }
        if (crc32(sbase,ssize) != crctbl[i].precrc) {
            if (*first == -1)
                *first = snum;
            *last = snum;
        }
        sbase += ssize;
        defragTick(0);
    }
    printf("done\n");
    return;
}

/* defragPostCrcCheck():
 * Return 1 if the post-crc check of the incoming sector number passes;
 * else 0.
 */
int
defragPostCrcCheck(TDEV *tdp, int isnum)
{
    uchar   *sbase;
    struct  defraghdr *dhp;
    struct  sectorcrc *crctbl;
    int     ftot, i, snum, ssize, lastsnum, lastssize;

    sbase = (uchar *)tdp->start;
    crctbl = defragCrcTable(tdp);
    dhp = (struct defraghdr *)crctbl - 1;
    ftot = dhp->idx + 1;

    addrtosector((uchar *)tdp->end,&lastsnum,&lastssize,0);

    for(i=0;isectorcount;i++) {
        addrtosector(sbase,&snum,&ssize,0);
        if (snum == isnum)
            break;
        sbase += ssize;
    }
    if (isnum == lastsnum) {
        ssize -= (tdp->sectorcount * sizeof(struct sectorcrc)); 
        ssize -= (ftot * DEFRAGHDRSIZ);
        ssize -= 4;
    }
    if (crctbl[i].postcrc == crc32(sbase,ssize))
        return(1);
    else
        return(0);
}

/* defragGetStateStr():
 * Return a string that corresponds to the incoming state value.
 */
static char *
defragGetStateStr(int state)
{
    char *str;

    switch(state) {
    case SECTOR_DEFRAG_INACTIVE:
        str = "SectorDefragInactive";
        break;
    case SECTOR_DEFRAG_ABORT_RESTART:
        str = "DefragRestartAborted";
        break;
    case SCANNING_ACTIVE_SECTOR_1:
        str = "ScanningActiveSector1";
        break;
    case SCANNING_ACTIVE_SECTOR_2:
        str = "ScanningActiveSector2";
        break;
    case SCANNING_ACTIVE_SECTOR_3:
        str = "ScanningActiveSector3";
        break;
    case SCANNING_ACTIVE_SECTOR_4:
        str = "ScanningActiveSector4";
        break;
    case SCANNING_ACTIVE_SECTOR_5:
        str = "ScanningActiveSector5";
        break;
    case SECTOR_DEFRAG_ALMOST_DONE:
        str = "DefragAlmostDone";
        break;
    default:
        str = "???";
        break;
    }
    return(str);
}

/* defragRestart():
 * Poll the console allowing the user to abort the auto-restart of
 * the defragmentation.  If a character is received on the console,
 * then return 0 indicating that the defrag should not be restarted;
 * else return 1.
 */
int
defragRestart(int state,int snum)
{
    printf("TFS defrag restart state: %s sector %d\n",
        defragGetStateStr(state),snum);
    if (pollConsole("Hit any key to abort..."))
        return(0);
    return(1);
}

/* defragGetState():
 * Step through the files in the specified device and check for
 * sanity.  Return 1 if the conclusion is that we are in the middle
 * of a defragmentation; else 0.
 */
static int
defragGetState(TDEV *tdp, int *activesnum)
{
    TFILE   *tfp;
    struct  defraghdr   *dhp;
    struct  sectorcrc *crctbl;
    int     snum_in_spare, firstsnum;
    int     first_touched_snum, last_touched_snum;
    int     break_cause, break1_cause, spare_is_erased, ftot, ftot1, errstate;

    /* Establish state of spare sector:
     */
    spare_is_erased = flasherased((uchar *)tdp->spare,
        (uchar *)tdp->spare+tdp->sparesize);

    ftot = 0;
    break_cause = break1_cause = 0;
    for(tfp=(TFILE *)tdp->start; tfp < (TFILE *)tdp->end; tfp=tfp->next) {
        /* If we are legally at the end of file storage space, then we
         * will hit a header size that is ERASED16.  If we reach this
         * point and the remaining space dedicated to file storage is
         * erased and the spare is erased, it is safe to assume that we
         * were not in the middle of a defrag.
         */
        if (tfp->hdrsize == ERASED16) {
            /* Is space from last file to end of TFS space erased? */
            if (!flasherased((uchar *)tfp,(uchar *)tdp->end)) {
                break_cause = 1;
                break;
            }
            if (!spare_is_erased) {
                break_cause = 2;
                break;
            }
#if DEFRAG_TEST_ENABLED
            printf("\ndefragGetState: inactive_1\n");
#endif
            return(SECTOR_DEFRAG_INACTIVE);
        }

        /* If the crc32 of the header is corrupt, or if the next pointer
         * doesn't make any sense, then we must assume that a defrag
         * was in progress...
         */
        if (tfshdrcrc(tfp) != tfp->hdrcrc) {
            break;
        }

        if (!(tfp->next) || (tfp->next <= (TFILE *)tdp->start) ||
             (tfp->next >= (TFILE *)tdp->end)) {
            break;
        }
        if (TFS_FILEEXISTS(tfp))
            ftot++;
    }
    /* If we are here, then something is not "perfect" with the flash
     * space used by TFS.  If break_cause is non-zero, there is a chance
     * that the only problem is that a file-write was interrupted and
     * we did not actually interrupt an in-progress-defrag.  An interrupted
     * file write would place some incomplete data after the last file.
     */
    ftot1 = defragValidDSI(tdp,&crctbl);

    /* If we don't have valid defrag state info (DSI), then we can assume
     * that the files in TFS have not yet been touched (since if we had
     * touched them, we would have already successfully created the DSI).
     * This being the case, then we will not continue with any defrag,
     * let TFS clean things up when the space is needed.
     */
    if (!ftot1) {
        if (break_cause) {
            /* Hmmm... Should something be done here? */
        }
#if DEFRAG_TEST_ENABLED
        printf("\ndefragGetState: inactive_2\n");
#endif
        return(SECTOR_DEFRAG_INACTIVE);
    }

    /* If we get here, then we have a valid defrag header table, so we
     * can use it and the state of each of the sectors to figure out
     * where we are in the defragmentation process.  We need to determine
     * which sector was being worked on at the point in time when the
     * defragmentation was interrupted.  A sector is in the "touched"
     * state if a crc32 on its content does not match the crc32 stored
     * in the crc table above the defrag header table.
     * 
     * Here we step through the defrag header table and see if each file
     * in the header table exists in TFS.  If all files exist, then we
     * must have been very close to completion of the defrag process.
     */
    printf("TFS: scanning DSI space... ");
    dhp = (struct defraghdr *)crctbl - ftot1;
    while(dhp < (struct defraghdr *)crctbl) {
        tfp = (TFILE *)dhp->nda;
        if (tfp->hdrcrc != dhp->ohdrcrc)
            break;
        
        if (tfshdrcrc(tfp) != tfp->hdrcrc) {
            break1_cause = 1;
            break;
        }
        if (crc32((uchar *)(tfp+1),tfp->filsize) != tfp->filcrc) {
            break1_cause = 2;
            break;
        }
        dhp++;
        defragTick(0);
    }
    printf("done\n");

    /* If we stepped through the entire table, then we've completed the
     * file relocation process, but we still have to clean up...
     */
    if (dhp >= (struct defraghdr *)crctbl) {
        if (defragRestart(SECTOR_DEFRAG_ALMOST_DONE,0)) {
            return(SECTOR_DEFRAG_ALMOST_DONE);
        }
        else {
            return(SECTOR_DEFRAG_ABORT_RESTART);
        }
    }
    
    if (addrtosector((char *)tdp->start,&firstsnum,0,0) < 0) {
        errstate = 50;
        goto state_error;
    }
    defragTouchedSectors(tdp,&first_touched_snum,&last_touched_snum);

    /* If there are no touched sectors, then we will not continue with
     * the defrag because we didn't start relocation of any of the files
     * yet.
     */
    if (first_touched_snum == -1) {
#if DEFRAG_TEST_ENABLED
        printf("\ndefragGetState: inactive_3\n");
#endif
        return(SECTOR_DEFRAG_INACTIVE);
    }

    if (spare_is_erased)
        snum_in_spare = -1;
    else
        snum_in_spare = defragSectorInSpare(tdp,crctbl);

#if DEFRAG_TEST_ENABLED
    printf("\ndefragGetState info: %d %d %d\n",
        first_touched_snum, last_touched_snum, snum_in_spare);
#endif

    /* At this point we know what sector was the last to be touched. 
     * What we don't know is whether or not the "touch" was completed.
     * So we don't know if the active sector is last_touched_snum or
     * last_touched_snum+1.
     * The only useful piece of data we 'might' have is the fact that
     * the spare may contain the content of the last touched sector.
     */

    /* If the spare is erased, it may be because defrag was just getting
     * ready to start working on the next sector (meaning that the active
     * sector is last_touched_snum+1) or the sector was in the process of
     * being modified.  We use the post-crc in the DHT to determine what
     * the active sector is...
     */
    if (spare_is_erased) {
        if (last_touched_snum >= 0) {
            if (defragPostCrcCheck(tdp,last_touched_snum)) {
                *activesnum = last_touched_snum + 1;

                if (defragRestart(SCANNING_ACTIVE_SECTOR_1,*activesnum))
                    return(SCANNING_ACTIVE_SECTOR_1);
                else
                    return(SECTOR_DEFRAG_ABORT_RESTART);
            }
            else {
                if (defragSerase(2,last_touched_snum) < 0) {
                    errstate = 51;
                    goto state_error;
                }
                *activesnum = last_touched_snum;

                if (defragRestart(SCANNING_ACTIVE_SECTOR_2,*activesnum))
                    return(SCANNING_ACTIVE_SECTOR_2);
                else
                    return(SECTOR_DEFRAG_ABORT_RESTART);
            }

        }
        else {
            errstate = 52;
            goto state_error;
        }
    }

    /* If the sector copied to spare is one greater than the last touched
     * sector, then the active sector is last_touched_snum+1 and it was
     * just copied to the spare.  In this case we erase the spare and
     * return indicating the active sector.
     */
    if (snum_in_spare == last_touched_snum+1) {
        if (defragEraseSpare(tdp) < 0) {
            errstate = 53;
            goto state_error;
        }
        *activesnum = snum_in_spare;

        if (defragRestart(SCANNING_ACTIVE_SECTOR_3,*activesnum))
            return(SCANNING_ACTIVE_SECTOR_3);
        else
            return(SECTOR_DEFRAG_ABORT_RESTART);
    }

    /* If the spare is not erased, but it does not match any of the 
     * sector CRCs, then we must have been in the process of copying
     * the active sector to the spare, so we can erase it and return
     * to the SCANNING_ACTIVE_SECTOR state.
     */
    if (snum_in_spare == -1) {
        if (last_touched_snum >= 0) {
            *activesnum = last_touched_snum + 1;

            if (!defragRestart(SCANNING_ACTIVE_SECTOR_4,*activesnum))
                return(SECTOR_DEFRAG_ABORT_RESTART);

            if (defragEraseSpare(tdp) < 0) {
                errstate = 54;
                goto state_error;
            }
            return(SCANNING_ACTIVE_SECTOR_4);
        }
        else {
            errstate = 55;
            goto state_error;
        }
    }

    /* If the sector copied to spare is the number of the last touched
     * sector, then we were in the middle of modifying the sector, so
     * we have to erase that sector, copy the spare to it and return
     * to the scanning state.
     */
    if (snum_in_spare == last_touched_snum) {
        int ssize;
        uchar *sbase;

        *activesnum = snum_in_spare;

        if (!defragRestart(SCANNING_ACTIVE_SECTOR_5,*activesnum))
            return(SECTOR_DEFRAG_ABORT_RESTART);

        if (defragSerase(3,snum_in_spare) < 0) {
            errstate = 56;
            goto state_error;
        }
        if (sectortoaddr(snum_in_spare,&ssize,&sbase) < 0) {
            errstate = 57;
            goto state_error;
        }
        if (defragFwrite(1,sbase,(uchar *)tdp->spare,ssize) < 0) {
            errstate = 58;
            goto state_error;
        }
        if (defragEraseSpare(tdp) < 0) {
            errstate = 59;
            goto state_error;
        }
        return(SCANNING_ACTIVE_SECTOR_5);
    }

    /* If we got here, then we are confused, so don't do any defrag
     * continuation...
     */
    errstate = 90;

state_error:
    printf("DEFRAG_STATE_ERROR: #%d.\n",errstate);
    return(SECTOR_DEFRAG_INACTIVE);
}

/* inSector():
 * We are trying to figure out if the address space that we want to copy
 * from is within the active sector.  If it is, then we need to adjust
 * our pointers so that we retrieve the at least some of data from the
 * spare. 
 * If the range specified by 'i_base' and 'i_size' overlays (in any way)
 * the address space used by the sector specified by 'snum', 
 * then return the address in the spare and the size of the overlay.
 */
static int
inSector(TDEV *tdp,int snum,uchar *i_base,int i_size,uchar **saddr,int *ovlysz)
{
    int     s_size;
    uchar   *s_base, *s_end, *i_end;

    /* Retrieve information about the sector: */
    if (sectortoaddr(snum,&s_size,&s_base) == -1)
        return(TFSERR_MEMFAIL);

    i_end = i_base + i_size;
    s_end = s_base + s_size;

    if ((i_end < s_base) || (i_base > s_end)) {
        *ovlysz = 0;
        return(0);
    }

    if (i_base < s_base) {
        if (i_end > s_end) {
            *ovlysz = s_size;
        }
        else {
            *ovlysz = (i_size - (s_base - i_base));
        }
        *saddr = (uchar *)tdp->spare;
    }
    else {
        if (i_end > s_end) {
            *ovlysz = (i_size - (i_end - s_end));
        }
        else {
            *ovlysz = i_size;
        }
        *saddr = (uchar *)tdp->spare + (i_base - s_base);
    }
    return(0);
}

/* struct fillinfo & FILLMODE definitions:
 * Structure used by the "Fill" functions below.
 */
#define FILLMODE_FWRITE         1   /* Do the flash write */
#define FILLMODE_SPAREOVERLAP   2   /* Determine if there is SPARE overlap */
#define FILLMODE_CRCONLY        3   /* Calculate a 32-bit crc on the data */

struct fillinfo {
    struct defraghdr *dhp;  /* pointer to defrag header table */
    TDEV    *tdp;           /* pointer to TFS device */
    ulong   crc;            /* used in FILLMODE_CRCONLY mode */
    int     crcsz;          /* size of crc calculation */
    int     fhdr;           /* set if we're working on a file header */
    int     asnum;          /* the active sector */
    int     mode;           /* see FILLMODE_xxx definitions */
};

/* defragFillFlash():
 * This function is called by the defragFillActiveSector() function
 * below.  It covers the four different cases of a file spanning over
 * the active sector, plus it deals with the possibility that the source
 * of the file data may be the same sector as the active one (meaning that
 * the source is taken from the spare).  It is within this function that
 * the active sector is actually modified and it assumes that the portion
 * of the active sector to be written to is already erased.
 * 
 * 
 * SPANTYPE_BCEC:
 * In this case, the file starts in a sector prior to the currently active
 * sector and ends in the active sector...
 * -----------|----------|----------|----------|---------|---------|----------
 * |          |          |          |          |         |         |         |
 * |          |          |<-active->|          |         |         |  SPARE  |
 * |          |          |  sector  |          |         |         |  SECTOR |
 * |          |          |          |          |         |         |         |
 * |          |          | newfile  |          |         |         |         |
 * |          |          | |<-->|   |          |         |         |         |
 * -----------|----------|----------|----------|---------|---------|----------
 *
 * 
 * SPANTYPE_BPEC:
 * In this case, the file starts in a sector prior to the currently active
 * sector and ends in the active sector...
 * -----------|----------|----------|----------|---------|---------|----------
 * |          |          |          |          |         |         |         |
 * |          |          |          |<-active->|         |         |  SPARE  |
 * |          |          |          |  sector  |         |         |  SECTOR |
 * |          |          |          |          |         |         |         |
 * |          |      |<----newfile----->|      |         |         |         |
 * |          |          |          |          |         |         |         |
 * -----------|----------|----------|----------|---------|---------|----------
 *
 * 
 * SPANTYPE_BPEL:
 * In this case, the file starts in some sector prior to the currently
 * active sector and ends in some sector after the currently active
 * sector...
 * -----------|----------|----------|----------|---------|---------|----------
 * |          |          |          |          |         |         |         |
 * |          |          |<-active->|          |         |         |  SPARE  |
 * |          |          |  sector  |          |         |         |  SECTOR |
 * |          |          |          |          |         |         |         |
 * |       |<---------- newfile------------------->|     |         |         |
 * |          |          |          |          |         |         |         |
 * -----------|----------|----------|----------|---------|---------|----------
 *
 *
 * SPANTYPE_BCEL:
 * In this case, the file starts in the active sector and ends in
 * a later sector.
 * -----------|----------|----------|----------|---------|---------|----------
 * |          |          |          |          |         |         |         |
 * |          |<-active->|          |          |         |         |  SPARE  |
 * |          |  sector  |          |          |         |         |  SECTOR |
 * |          |          |          |          |         |         |         |
 * |          |      |<----newfile----->|      |         |         |         |
 * |          |      ****|          |          |         |         |         |
 * -----------|----------|----------|----------|---------|---------|----------
 */
static int
defragFillFlash(struct fillinfo *fip,int spantype,char **activeaddr,int verbose)
{
    char    *hp;
    TFILE   nfhdr;
    struct  defraghdr *dhp; 
    int     ohdroffset, nhdroffset;
    uchar   *ovly, *src, *activesbase;
    int     ovlysz, srcsz, activessize;

    src = 0;
    srcsz = 0;
    nhdroffset = ohdroffset = 0;
    dhp = fip->dhp;

    if (verbose >= 2) {
        printf("   defragFillFlash %s %s\n",fip->fhdr ? "hdr" : "dat",
            defragGetSpantypeStr(spantype));
    }

    if (spantype == SPANTYPE_BCEC) {
        if (fip->fhdr) {
            src = (uchar *)dhp->ohdr;
            srcsz = TFSHDRSIZ;
        }
        else {
            src = (uchar *)dhp->ohdr+TFSHDRSIZ;
            srcsz = dhp->filsize;
        }
    }
    else if (spantype == SPANTYPE_BPEC) {
        if (fip->fhdr) {
            /* Calculate the offset into the header at which point a
             * sector boundary occurs.  Do this for both the old (before
             * defrag relocation) and new (after defrag relocation)
             * location of the header.
             */
            nhdroffset = TFSHDRSIZ - (dhp->neso - dhp->filsize);
            ohdroffset = TFSHDRSIZ - (dhp->oeso - dhp->filsize);
            srcsz = dhp->oeso - dhp->filsize + (ohdroffset - nhdroffset);
            src = (uchar *)dhp->ohdr + nhdroffset;
        }
        else {
            src = (uchar *)dhp->ohdr + TFSHDRSIZ + (dhp->filsize - dhp->neso);
            srcsz = dhp->neso;
        }
    }
    else if (spantype == SPANTYPE_BCEL) {
        if (sectortoaddr(fip->asnum,&activessize,&activesbase) == -1)
            return(TFSERR_MEMFAIL);

        if (fip->fhdr) {
            src = (uchar *)dhp->ohdr;
        }
        else {
            src = (uchar *)dhp->ohdr+TFSHDRSIZ;
        }
        srcsz = (activesbase + activessize) - (uchar *)*activeaddr;
    }
    else if (spantype == SPANTYPE_BPEL) {
        if (sectortoaddr(fip->asnum,&activessize,0) == -1)
            return(TFSERR_MEMFAIL);

        if (fip->fhdr) {
            src = (uchar *)dhp->ohdr;
        }
        else {
            src = (uchar *)dhp->ohdr+TFSHDRSIZ;
        }

        src += ((*activeaddr - dhp->nda) - TFSHDRSIZ);
        srcsz = activessize; 
    }
    else {
        return(0);
    }

    /* Determine if any portion of the source was part of the sector that
     * is now the active sector..  If yes (ovlysz > 0), then we must
     * deal with the fact that some (or all) of the fill source is in the
     * spare sector...
     */
    if (inSector(fip->tdp,fip->asnum,src,srcsz,&ovly,&ovlysz) < 0)
        return(TFSERR_MEMFAIL);

    /* If the mode is not FILLMODE_FWRITE, then we don't do any of the
     * flash operations.  We are in this function only to determine
     * if we need to copy the active sector to the spare prior to 
     * starting the modification of the active sector.
     */
    if (fip->mode == FILLMODE_FWRITE) {
        if (fip->fhdr) {
            hp = (char *)&nfhdr;
            if (ovlysz) {
                memcpy(hp+nhdroffset,ovly,ovlysz);
                if (ovlysz != srcsz) {
                    memcpy(hp+nhdroffset+ovlysz,(char *)src+ovlysz,
                        srcsz-ovlysz);
                }
            }
            else {
                nfhdr = *dhp->ohdr;
            }
            nfhdr.next = dhp->nextfile;
            if (defragFwrite(2,*activeaddr,hp+nhdroffset,srcsz) == -1)
                return(TFSERR_FLASHFAILURE);
        }
        else {
            if (ovlysz) {
                if (defragFwrite(3,*activeaddr,(char *)ovly,ovlysz) == -1)
                    return(TFSERR_FLASHFAILURE);
                if (ovlysz != srcsz) {
                    if (defragFwrite(4,*activeaddr+ovlysz,(char *)src+ovlysz,
                        srcsz-ovlysz) == -1)
                        return(TFSERR_FLASHFAILURE);
                }
            }
            else {
                if (defragFwrite(5,*activeaddr,(char *)src,srcsz) == -1)
                    return(TFSERR_FLASHFAILURE);
            }
        }
    }
    else if (fip->mode == FILLMODE_CRCONLY) {
        register uchar  *bp;
        int     sz, temp;

        if (fip->fhdr) {
            hp = (char *)&nfhdr;
            nfhdr = *dhp->ohdr;
            nfhdr.next = dhp->nextfile;
            bp = hp + nhdroffset;
        }
        else {
            bp = (uchar *)src;
        }
        sz = srcsz;
        fip->crcsz += sz;
        while(sz) {
            temp = (fip->crc ^ *bp++) & 0x000000FFL;
            fip->crc = ((fip->crc >> 8) & 0x00FFFFFFL) ^ crc32tab[temp];
            sz--;
        }
    }
    *activeaddr += srcsz;

    if ((spantype == SPANTYPE_BCEC || spantype == SPANTYPE_BPEC) &&
        (!fip->fhdr) && ((ulong)*activeaddr & 0xf)) {
        int     sz, temp, modfixsize;

        modfixsize = (TFS_FSIZEMOD - ((ulong)*activeaddr & (TFS_FSIZEMOD-1)));
        *activeaddr += modfixsize;
        if (fip->mode == FILLMODE_CRCONLY) {
            sz = modfixsize;
            fip->crcsz += sz;
            while(sz) {
                temp = (fip->crc ^ 0xff) & 0x000000FFL;
                fip->crc = ((fip->crc >> 8) & 0x00FFFFFFL) ^ crc32tab[temp];
                sz--;
            }
        }
    }

    /* Return ovlysz so that the caller will know if this function
     * needed the spare sector.  This is used in the "mode = SPARE_OVERLAP"
     * pass of defragFillActiveSector().
     */
    return(ovlysz);
}

/* defragFillActiveSector():
 * This and defragFillFlash() are the workhorses of the tfsclean() function.
 * The bulk of this function is used to determine if we need to do anything
 * to the active sector and if so, do we need to copy the active sector to
 * the spare prior to erasing it.
 * The first loop in this function determines whether we need to do anything
 * at all with this sector (it may not be touched by the defragmentation).
 * The second loop determines if we have to copy the active sector to the
 * spare prior to erasing the active sector.
 * The final loop in this function does the call to defragFillFlash()
 * to do the actual flash writes.
 */
static int
defragFillActiveSector(TDEV *tdp, int ftot, int snum, int verbose)
{
    int     firstsnum;      /* number of first TFS sector */
    int     activesnum;     /* number of sector currently being written to */
    int     activessize;    /* size of active sector */
    char    *activeaddr;    /* offset being written to in the active sector */
    char    *activesbase;   /* base address of active sector */
    char    *activesend;    /* end address of active sector */
    struct  defraghdr *sdhp;/* pointer into defrag hdr table in spare */
    struct  defraghdr *dhp; /* pointer into defrag header table */
    int     fullsize;       /* size of file and header */
    char    *new_dend;      /* new end of data */
    char    *new_dbase;     /* new base of data */
    char    *new_hend;      /* new end of header */
    char    *new_hbase;     /* new base of header */
    char    *new_fend;      /* new end of file */
    char    *new_fbase;     /* new base of file */
    char    *orig_fend;     /* original end of file */
    int     new_fspan;      /* span type for new file */
    int     new_hspan;      /* span type for new header */
    int     new_dspan;      /* span type for new data */
    int     fillstat;       /* result of defragFillFlash() function call */
    int     noreloctot;     /* number of files spanning the active sector */
                            /* that do not have to be relocated */
    int     tmptot;         /* temps used for the "SPARE_OVERLAP" mode */
    char    *tmpactiveaddr;
    struct  defraghdr *tmpdhp;
    struct  fillinfo finfo;
    struct  sectorcrc   *crctbl;
    int     lastsnum, tot;
    int     copytospare;
    int     lastfileisnotrelocated;

    /* Retrieve number of first TFS sector: */
    if (addrtosector((char *)tdp->start,&firstsnum,0,0) < 0)
        return(TFSERR_MEMFAIL);

    activesnum = snum + firstsnum;
    crctbl = defragCrcTable(tdp);

    /* Retrieve information about active sector: */
    if (sectortoaddr(activesnum,&activessize,(uchar **)&activesbase) == -1)
        return(TFSERR_MEMFAIL);

    if (verbose)
        printf(" Active sector: %3d @ 0x%lx\n",activesnum,(ulong)activesbase);

    if (addrtosector((char *)tdp->end,&lastsnum,0,0) < 0)
        return(TFSERR_MEMFAIL);

    /* Establish a pointer to the defrag header table.
     * For the case when the active sector is the last sector, the defrag
     * header table will be in the spare, so we also establish a pointer
     * to that space...
     */
    dhp = (struct defraghdr *)crctbl;
    dhp -= ftot;
    sdhp = (struct defraghdr *)(tdp->spare+activessize -
            (tdp->sectorcount * sizeof(struct sectorcrc)));
    sdhp -= ftot;

    activeaddr = activesbase;
    tmpactiveaddr = activesbase;
    activesend = activesbase + activessize - 1;

    /* FIRST LOOP:
     * See if we need to do anything...
     * In this state, we are simply checking to see if anything is going
     * to cause the currently active sector to be written to.
     * If yes, then we need to copy it to the spare and start the
     * modification process; else, we just return and do nothing
     * to this sector.
     * For each file in the defrag header table that is destined for
     * the address space occupied by the currently active sector, copy
     * that file (header and data) to the active sector...
     * Note that it may only be a partial copy, depending on the size
     * of the file and the amount of space left in the active sector.
     */

    noreloctot = 0;
    new_fspan = SPANTYPE_UNDEF;
    lastfileisnotrelocated = 0;
    for(tot=0;totfilsize;
        new_fbase = dhp->nda;
        new_fend = (new_fbase + fullsize);

        /* We must figure out how the new version of the file will
         * span across the active sector.
         * See defragGetSpantype() for details.
         */
        new_fspan = defragGetSpantype(activesbase,activesend,
            new_fbase,new_fend);

        /* If the file we are looking at entirely spans a sector that is
         * prior to the currently active sector, then we just continue
         * through the list.
         * If the file entirely spans a sector that is after the
         * currently active sector, then we are done with this active sector.  
         * If the file falls within the active sector in any way, and its
         * new location does not match its old location, then we
         * break out of this loop and begin the modification of this
         * active sector...
         */
        if (new_fspan == SPANTYPE_BLEL)
            return(0);
        
        if (new_fspan != SPANTYPE_BPEP) {
            if (dhp->nda == (char *)dhp->ohdr) {
                noreloctot++;
                if (tot == ftot-1) {
                    lastfileisnotrelocated = 1;
                    if (verbose > 1)
                        printf("  last file not relocated\n");
                }
            }
            else
                break;
        }
    }

    /* If tot == ftot, then we got through the entire loop above without
     * finding a file that needs to be relocated into this active sector.
     * This means one of two things: either all the files fall into a 
     * sector prior to this active sector, or the files in this active
     * sector do not need to be relocated.  In either case, we simply
     * return without touching this sector.
     * Note that we also keep track of the possibility that the last file
     * may not be relocated.  If this ends up to be the case, then we are
     * simply cleaning up one or more dead files after a full set of active
     * files, so we should clean up the sector.
     */
    if ((tot == ftot) && (lastfileisnotrelocated == 0))
        return(0);

    /* If tot != ftot, then we must subtract noreloctot from tot so that
     * we establish 'tot' as the index into the first file that must be
     * copied to the active sector...
     */
    if (noreloctot) {
        tot -= noreloctot;
        dhp -= noreloctot;
        sdhp -= noreloctot;
    }

    /* Exit immediately before cleaning up the spare... */
    defragExitTestPoint(10000+activesnum);

    /* Since we got here, we know that we have to do some work on the
     * currently active sector.  We may not have to copy it to the spare,
     * but we will erase the spare anyway because the sector erase is
     * supposed to be smart enough to avoid the erase if it is already 
     * erased.  This should be handled by the flash driver because in
     * ALL cases the erase should be avoided if possible.
     */
    if (defragEraseSpare(tdp) < 0)
        return(TFSERR_FLASHFAILURE);

    /* Exit immediately after cleaning up the spare... */
    defragExitTestPoint(10001+activesnum);

    /* If the active sector is the last sector (which would contain the
     * defrag header table), then we reference the copy of the table that
     * is in the spare...
     * Also, if this is the last sector, then we HAVE to copy it to 
     * spare, so we can skip the 2nd loop that attempts to determine
     * if we need to do it.
     */
    if (activesnum == lastsnum) {
        dhp = sdhp;
        copytospare = 1;
    }
    else {
        /* SECOND LOOP:
         * See if we need to copy the active sector to the spare...
         * We do this by continuing the loop we started above.  Notice that
         * we do an almost identical loop again below this.
         * On this pass through the loop we are only checking to see if it is
         * necessary to copy this active sector to the spare.
         */
        tmptot = tot;
        tmpdhp = dhp;
        copytospare = 0;
        finfo.mode = FILLMODE_SPAREOVERLAP;
        for(;tmptotfilsize;
            new_fbase = tmpdhp->nda;
            new_fend = (new_fbase + fullsize);
    
            new_fspan = defragGetSpantype(activesbase,activesend,
                new_fbase,new_fend);
    
            if (new_fspan == SPANTYPE_BPEP) 
                continue;
            else if (new_fspan == SPANTYPE_BLEL)
                break;
    
            /* Now retrieve span information about header and data
             * portions of the file (new and orig)...
             */
            new_hbase = new_fbase;
            new_hend = new_hbase + TFSHDRSIZ;
            new_dbase = new_hbase + TFSHDRSIZ;
            new_dend = new_fend;
            orig_fend = ((char *)tmpdhp->ohdr + fullsize);
    
            new_hspan = defragGetSpantype(activesbase,activesend,
                new_hbase,new_hend);
            new_dspan = defragGetSpantype(activesbase,activesend,
                new_dbase,new_dend);
    
            /* If defragFillFlash() returns positive (with mode ==
             * FILLMODE_SPAREOVERLAP set above), then we know that the
             * spare sector must be loaded with a copy of this active
             * sector, so we can break out of this loop at that point...
             */
            finfo.fhdr = 1;
            fillstat = defragFillFlash(&finfo,new_hspan,&tmpactiveaddr,0);
            if (fillstat < 0)
                return(fillstat);   
            if (fillstat > 0) {
                copytospare = 1;
                break;
            }
            if (new_hspan == SPANTYPE_BCEL)
                break;
    
            finfo.fhdr = 0;
            fillstat = defragFillFlash(&finfo,new_dspan,&tmpactiveaddr,0);
            if (fillstat < 0)
                return(fillstat);   
            if (fillstat > 0) {
                copytospare = 1;
                break;
            }
            if (new_dspan == SPANTYPE_BCEL || new_dspan == SPANTYPE_BPEL)
                break;
        }
    }

    finfo.mode = FILLMODE_FWRITE;

    defragExitTestPoint(10002+activesnum);

    if (copytospare) {
        defragTick(verbose);
#if DEFRAG_TEST_ENABLED
        printf("     copying sector %d to spare\n",activesnum);
#endif
        if (defragFwrite(6,(uchar *)tdp->spare,activesbase,activessize) == -1) {
            printf("Failed to copy active %d to spare\n",activesnum);
            return(TFSERR_FLASHFAILURE);
        }
    }
#if DEFRAG_TEST_ENABLED
    else {
        printf("     copy saved\n");
    }
#endif

    defragTick(verbose);

    /* We can now begin actual modification of the active sector,
     * so start off by eraseing it...
     */
    defragExitTestPoint(10003+activesnum);

    if (defragSerase(4,activesnum) < 0)
        return(TFSERR_FLASHFAILURE);

    defragExitTestPoint(10004+activesnum);

    /* THIRD LOOP:
     * Now we pass through the loop to do the real flash modifications...
     */
    for(;totfilsize;
        new_fbase = dhp->nda;
        new_fend = (new_fbase + fullsize);

        new_fspan = defragGetSpantype(activesbase,activesend,
            new_fbase,new_fend);

        if (new_fspan == SPANTYPE_BPEP) 
            continue;
        else if (new_fspan == SPANTYPE_BLEL)
            break;

        if (verbose)
            printf("  File: %s\n",dhp->fname);

        /* Now retrieve span information about header and data
         * portions of the file (new and orig)...
         */
        new_hbase = new_fbase;
        new_hend = new_hbase + TFSHDRSIZ;
        new_dbase = new_hbase + TFSHDRSIZ;
        new_dend = new_fend;
        orig_fend = ((char *)dhp->ohdr + fullsize);

        new_hspan = defragGetSpantype(activesbase,activesend,
            new_hbase,new_hend);
        new_dspan = defragGetSpantype(activesbase,activesend,
            new_dbase,new_dend);

        /* At this point we have all the information we need to copy
         * the appropriate amount of the file from orignal space
         * to new space.
         * We have to break the write up into two parts, the header
         * (new_hspan) and the data (new_dspan) so we have to look
         * at the spantype for each to determine what part of the
         * header and/or data we are going to copy.
         *
         * Also, we must consider the possibility that the source
         * data may be in the spare sector.  This would be the case
         * if the active sector is the same sector that the original
         * data was in.  If the source data is in the spare sector,
         * then an added complication is the fact that it may not
         * all be there, we may have to copy some from the spare,
         * then some from the original space.
         */
        finfo.fhdr = 1;
        fillstat = defragFillFlash(&finfo,new_hspan,&activeaddr,verbose);
        if (fillstat < 0)
            return(fillstat);   
        if (new_hspan == SPANTYPE_BCEL)
            break;

        finfo.fhdr = 0;
        fillstat = defragFillFlash(&finfo,new_dspan,&activeaddr,verbose);
        if (fillstat < 0)
            return(fillstat);   
        if (new_dspan == SPANTYPE_BCEL || new_dspan == SPANTYPE_BPEL)
            break;
        defragTick(verbose);
    }
    return(0);
}

static int
defragNewSectorCrc(TDEV *tdp, struct defraghdr *dht, int snum, ulong *newcrc)
{
    int     firstsnum;      /* number of first TFS sector */
    int     activesnum;     /* number of sector currently being written to */
    int     activessize;    /* size of active sector */
    char    *activeaddr;    /* offset being written to in the active sector */
    char    *activesbase;   /* base address of active sector */
    char    *activesend;    /* end address of active sector */
    struct  defraghdr *dhp; /* pointer into defrag header table */
    int     fullsize;       /* size of file and header */
    char    *new_dend;      /* new end of data */
    char    *new_dbase;     /* new base of data */
    char    *new_hend;      /* new end of header */
    char    *new_hbase;     /* new base of header */
    char    *new_fend;      /* new end of file */
    char    *new_fbase;     /* new base of file */
    char    *orig_fend;     /* original end of file */
    int     new_fspan;      /* span type for new file */
    int     new_hspan;      /* span type for new header */
    int     new_dspan;      /* span type for new data */
    int     fillstat;       /* result of defragFillFlash() function call */
    int     ftot;
    char    *tmpactiveaddr;
    struct  fillinfo finfo;
    struct  sectorcrc   *crctbl;
    int     lastsnum, tot, sz, temp;

    /* Retrieve number of first TFS sector: */
    if (addrtosector((char *)tdp->start,&firstsnum,0,0) < 0)
        return(TFSERR_MEMFAIL);

    activesnum = snum + firstsnum;
    crctbl = defragCrcTable(tdp);

    /* Retrieve information about active sector: */
    if (sectortoaddr(activesnum,&activessize,(uchar **)&activesbase) == -1)
        return(TFSERR_MEMFAIL);

    if (addrtosector((char *)tdp->end,&lastsnum,0,0) < 0)
        return(TFSERR_MEMFAIL);

    dhp = (struct defraghdr *)crctbl - 1;
    ftot = dhp->idx + 1;
    dhp = dht;
    activeaddr = activesbase;
    tmpactiveaddr = activesbase;
    activesend = activesbase + activessize - 1;

    finfo.tdp = tdp;
    finfo.crcsz = 0;
    finfo.crc = 0xffffffff;
    finfo.asnum = activesnum;
    finfo.mode = FILLMODE_CRCONLY;

    for(tot=0;totfilsize;
        new_fbase = dhp->nda;
        new_fend = (new_fbase + fullsize);

        new_fspan = defragGetSpantype(activesbase,activesend,
            new_fbase,new_fend);

        if (new_fspan == SPANTYPE_BPEP) 
            continue;
        else if (new_fspan == SPANTYPE_BLEL)
            break;

        /* Now retrieve span information about header and data
         * portions of the file (new and orig)...
         */
        new_hbase = new_fbase;
        new_hend = new_hbase + TFSHDRSIZ;
        new_dbase = new_hbase + TFSHDRSIZ;
        new_dend = new_fend;
        orig_fend = ((char *)dhp->ohdr + fullsize);

        new_hspan = defragGetSpantype(activesbase,activesend,
            new_hbase,new_hend);
        new_dspan = defragGetSpantype(activesbase,activesend,
            new_dbase,new_dend);

        finfo.fhdr = 1;
        fillstat = defragFillFlash(&finfo,new_hspan,&activeaddr,0);
        if (fillstat < 0)
            return(fillstat);   
        if (new_hspan == SPANTYPE_BCEL)
            break;

        finfo.fhdr = 0;
        fillstat = defragFillFlash(&finfo,new_dspan,&activeaddr,0);
        if (fillstat < 0)
            return(fillstat);   
        if (new_dspan == SPANTYPE_BCEL || new_dspan == SPANTYPE_BPEL)
            break;
    }
    sz = activessize - finfo.crcsz;

    /* If this is the last sector, then we must not include the space used
     * for defrag state storage in the crc calculation.
     * We deduct size of CRC table, DHT table and the crc of the DSI space...
     */
    if (activesnum == lastsnum) {
        sz -= (tdp->sectorcount * sizeof(struct sectorcrc));    
        sz -= (ftot * DEFRAGHDRSIZ);
        sz -= 4;
    }
    while(sz) {
        temp = (finfo.crc ^ 0xff) & 0x000000FFL;
        finfo.crc = ((finfo.crc >> 8) & 0x00FFFFFFL) ^ crc32tab[temp];
        sz--;
    }
    *newcrc = ~finfo.crc;
    return(0);
}

/* defragBuildCrcTable():
 * Build the table of sector crcs.
 * This consists of a set of CRCs representing each sector
 * before defrag starts and after defrag has completed.  
 * One set for each sector.  This table is then used if the
 * defrag process is interrupted to determine the state of
 * the interrupted defragmentation process.
 */
int
defragBuildCrcTable(TDEV *tdp, struct defraghdr *dht, int verbose)
{
    ulong   crc;
    uchar   *sbase;
    int     i, ssize, dhstsize;
    struct  sectorcrc *crctbl;

    dhstsize = (ulong)(tdp->end+1) - (ulong)dht + 4;
    crctbl = defragCrcTable(tdp);

    /* The pre-defrag crc table...
     * This one's easy because it is simply a crc for each of the current
     * sectors.
     */
    sbase = (uchar *)tdp->start;
    for(i=0;isectorcount;i++) {
        if (addrtosector(sbase,0,&ssize,0) < 0)
            return(-1);

        if (i == tdp->sectorcount-1)
            ssize -= dhstsize;
        
        /* The pre-defrag crc: */
        crc = crc32(sbase,ssize);
        if (defragFwrite(7,(uchar *)&crctbl[i].precrc,
            (uchar *)&crc,4) == -1) {
            return(-1);
        }

        /* The post-defrag crc: */
        if (defragNewSectorCrc(tdp,dht,i,&crc) < 0)
            return(-1);

        if (defragFwrite(8,(uchar *)&crctbl[i].postcrc,
            (uchar *)&crc,4) == -1) {
            return(-1);
        }

        sbase += ssize;

        defragTick(0);
    }
    return(0);
}

/* _tfsclean():
 * This is the front-end of the defragmentation process, following are the
 * basic steps of defragmentation...
 *
 * Build the Defrag State Information (DSI) area:
 * 1. Create a table of 32-bit CRCs, two for each sector.  One is the CRC
 *    of the sector prior to beginning defragmentation and the other is
 *    what will be the CRC of the sector after defragmentation has completed.
 *    These CRCs are used to help recover from an interrupted defragmentation.
 * 2. Create a table of struct defraghdr structures, one for each file in
 *    TFS that is currently active (not dead).
 * 3. Create a CRC of the tables created in steps 1 & 2.
 *
 * The data created in steps 1-3 is stored at the end of the last sector
 * used by TFS for file storage.  After this is created, the actual flash
 * defragmentation process starts.
 *
 * File relocation:
 * 4. Step through each sector in TFS flash space, process each file whose
 *    relocated space overlaps with that sector.  As each sector is being
 *    re-built, the original version of that sector is stored in the spare.
 *
 * End of flash cleanup:
 * 5. Run through the remaining, now unsused, space in TFS flash and make
 *    sure it is erased.
 *
 * File check:
 * 6. Run a check of all of the relocated files to make sure everything is
 *    still sane.
 * 
 * Defragmentation success depends on some coordination with tfsadd()...
 * Whenever a file is added to TFS, tfsadd() must verify that the space
 * needed for defrag overhead (defrag state & header tables) will be
 * available.  Also, tfsadd() must make sure that the defrag overhead will
 * always fit into one sector (the sector just prior to the spare).
 */

static int
_tfsclean(TDEV *tdp, int restart, int verbose)
{
    int     dhstsize;       /* Size of state table overhead */
    int     firstsnum;      /* Number of first sector in TFS device. */
    int     lastsnum;       /* Number of last sector in TFS device. */
    int     lastssize;      /* Size of last sector in TFS device. */
    char    *lastsbase;     /* Base address of last sector in TFS device. */
    int     sectorcheck;    /* Used to verify proper TFS configuration. */
    struct  defraghdr *dht; /* Pointer to defrag header table. */
    int     chkstat;        /* Result of tfscheck() after defrag is done. */
    int     ftot;           /* Total number of active files in TFS. */
    int     dtot;           /* Total number of deleted files in TFS. */
    int     fcnt;           /* Running file total, used in hdrtbl build. */
    TFILE   *tfp;           /* Misc file pointer */
    char    *newaddress;    /* Used to calculate "new" location of file. */
    struct  defraghdr   defrag; /* Used to build defrag header table. */
    int     activesnum;     /* Sector being worked on restarted defrag. */
    struct  sectorcrc   *crctbl;    /* Pointer to table of per-sector crcs. */
    int     defrag_state;
    int     sidx, snum;
    char    *end;
    
    if (TfsCleanEnable < 0)
        return(TFSERR_CLEANOFF);

    /* If incoming TFS device pointer is NULL, return error
     */
    if (!tdp)
        return(TFSERR_BADARG);

    /* If the 'restart' flag is set, then we only want to do a defrag if
     * we determine that one is already in progress; so we have to look at
     * the current state of the defrag state table to figure out if a defrag
     * was active.  If not, just return.
     */
    if (restart) {
        defrag_state = defragGetState(tdp,&activesnum);
        switch(defrag_state) {
            case SECTOR_DEFRAG_INACTIVE:
            case SECTOR_DEFRAG_ABORT_RESTART:
                return(TFS_OKAY);
            case SCANNING_ACTIVE_SECTOR_1:
            case SCANNING_ACTIVE_SECTOR_2:
            case SCANNING_ACTIVE_SECTOR_3:
            case SCANNING_ACTIVE_SECTOR_4:
            case SCANNING_ACTIVE_SECTOR_5:
                defrag_state = SCANNING_ACTIVE_SECTOR;
                break;
        }
    }
    else {
        defrag_state = SECTOR_DEFRAG_INACTIVE;
    }

    if ((verbose) || (!MFLAGS_NODEFRAGPRN()))
        printf("TFS device '%s' defragmentation\n",tdp->prefix);

    if (addrtosector((char *)tdp->start,&firstsnum,0,0) < 0)
        return(TFSERR_MEMFAIL);
    lastsnum = firstsnum + tdp->sectorcount - 1;
    if (addrtosector((char *)tdp->end,§orcheck,0,0) < 0)
        return(TFSERR_MEMFAIL);
    if (lastsnum != sectorcheck) {
        printf("%s: SECTORCOUNT != TFSSTART <-> TFSEND\n", tdp->prefix);
        printf("First TFS sector = %d, last = %d\n",firstsnum,sectorcheck);
        return(TFSERR_MEMFAIL);
    }

    if (defrag_state == SECTOR_DEFRAG_INACTIVE) {
        activesnum = firstsnum;
    }

    /* Retrieve information about last sector:
     */
    if (sectortoaddr(lastsnum,&lastssize,(uchar **)&lastsbase) == -1)
        return(TFSERR_MEMFAIL);

    /* Establish a pointer to a table of CRCs that will contain
     * one 32-bit CRC for each sector (prior to starting the defrag).
     */
    crctbl = defragCrcTable(tdp);

    /* Retrieve the number of "dead" and "living" files:
     * If there are no dead files, then there is no need to defrag.
     * If there are no "living" files, then we can just init the flash.
     */
    ftot = dtot = 0;
    if (restart && defragValidDSI(tdp,0)) {
        dht = (struct defraghdr *)crctbl - 1;
        ftot = dht->idx + 1;
    }
    else {
        tfp = (TFILE *)tdp->start;
        while(validtfshdr(tfp)) {
            if (TFS_FILEEXISTS(tfp))
                ftot++;
            else
                dtot++;
            tfp = nextfp(tfp,tdp);
        }
        if (dtot == 0) {
            if (verbose)
                printf("No dead files in %s.\n",tdp->prefix);
            if (tfsflasherased(tdp,verbose))
                return(0);
            if (verbose)
                printf("Cleaning up end of flash...\n");
        }
    }
    if (ftot == 0) {
        if (verbose)
            printf("No active files detected, erasing all %s flash...\n",
                tdp->prefix);
        _tfsinit(tdp);
        return(0);
    }

    /* Now that we know how many files are in TFS, we can establish 
     * a pointer to the defrag header table, and the size of the table...
     */
    dht = (struct defraghdr *)crctbl - ftot;
    dhstsize = (ulong)(tdp->end+1) - (ulong)dht;
    dhstsize += 4; /* Account for the CRC of the state tables. */

    if (defrag_state == SECTOR_DEFRAG_INACTIVE) {
        ulong   crc;

        if (verbose) {
            printf("TFS defrag: building DSI space...\n");
        }

        /* We start by making sure that the space needed by the
         * defrag header and state table at the end of the last
         * sector is clear...
         */
        if (!flasherased((uchar *)dht, (uchar *)(tdp->end))) {
            if (defragEraseSpare(tdp) < 0)
                return(TFSERR_FLASHFAILURE);

            if (defragFwrite(9,(uchar *)(tdp->spare),lastsbase,
                lastssize-dhstsize) == -1) {
                return(TFSERR_FLASHFAILURE);
            }
            if (defragSerase(5,lastsnum) < 0) {
                return(TFSERR_FLASHFAILURE);
            }
            if (defragFwrite(10,lastsbase,(uchar *)(tdp->spare),
                lastssize) == -1) {
                return(TFSERR_FLASHFAILURE);
            }
        }

        /* Erase the spare then copy the portion of the last TFS
         * sector that does not overlap with the defrag header and
         * state table area to the spare.  We do this so that the spare
         * sector contains a defrag header and state table area that
         * is erased.
         */
        if (defragEraseSpare(tdp) < 0)
            return(TFSERR_FLASHFAILURE);
    
        if (defragFwrite(11,(uchar *)tdp->spare,
            lastsbase,lastssize-dhstsize) == -1) {
            return(TFSERR_FLASHFAILURE);
        }

        /* At this point we have a valid copy of the last sector in
         * the spare.  If any portion of the last sector is not identical
         * to what is in the spare, then we need to erase the last sector
         * and re-copy what is in the spare to the last sector.  This is
         * necessary because an interrupt may have occurred while writing
         * to the last sector, and it may have corrupted something.
         */

        if ((memcmp(lastsbase,(char *)(tdp->spare),lastssize-dhstsize)) ||
            (!flasherased((uchar *)dht,(uchar *)tdp->end))) {
            if (defragSerase(6,lastsnum) < 0) {
                return(TFSERR_FLASHFAILURE);
            }
            if (defragFwrite(12,lastsbase,(uchar *)(tdp->spare),
                lastssize) == -1) {
                return(TFSERR_FLASHFAILURE);
            }
        }
        /* Build the header table:
         */
        fcnt = 0;
        tfp = (TFILE *)tdp->start;
        newaddress = (char *)tdp->start;

        if (verbose > 2) {
            printf("\nDEFRAG HEADER DATA (dht=0x%lx, ftot=%d):\n",
                (ulong)dht,ftot);
        }
    
        while(validtfshdr(tfp)) {
            if (TFS_FILEEXISTS(tfp)) {
                uchar   *base, *eof, *neof, *nbase;
                int     size, slot;
                struct  tfsdat *slotptr;
    
                strcpy(defrag.fname,TFS_NAME(tfp));
                defrag.ohdr = tfp;
                defrag.ohdrcrc = tfp->hdrcrc;
                defrag.filsize = TFS_SIZE(tfp);
                if (addrtosector((char *)tfp,0,0,&base) < 0)
                    return(TFSERR_MEMFAIL);

                eof = (uchar *)(tfp+1)+TFS_SIZE(tfp)-1;
                if (addrtosector((char *)eof,0,0,&base) < 0)
                    return(TFSERR_MEMFAIL);
                defrag.oeso = eof - base + 1;

                neof = newaddress+TFSHDRSIZ+TFS_SIZE(tfp)-1;
                if (addrtosector((char *)neof,(int *)&defrag.nesn,0,&nbase) < 0)
                    return(TFSERR_MEMFAIL);
                defrag.neso = neof - nbase + 1;

                defrag.crc = 0;
                defrag.idx = fcnt;
                defrag.nda = newaddress;
    
                /* If the file is currently opened, adjust the base address. */
                slotptr = tfsSlots;
                for (slot=0;slotoffset != -1) {
                        if (slotptr->base == (uchar *)(TFS_BASE(tfp))) {
                            slotptr->base = (uchar *)(newaddress+TFSHDRSIZ);
                        }
                    }
                }
                size = TFS_SIZE(tfp) + TFSHDRSIZ;
                if (size & 0xf) {
                    size += TFS_FSIZEMOD;
                    size &= ~(TFS_FSIZEMOD-1);
                }
                newaddress += size;
                defrag.nextfile = (TFILE *)newaddress;
                defrag.crc = crc32((uchar *)&defrag,DEFRAGHDRSIZ);
                if (verbose > 2) {
                    printf(" File %s:\n",TFS_NAME(tfp));
                    printf("     nda=  0x%08lx, ohdr= 0x%08lx, nxt=  0x%08lx\n",
                        (ulong)(defrag.nda),(ulong)(defrag.ohdr),
                        (ulong)(defrag.nextfile));
                    printf("     oeso= 0x%08lx, nesn= 0x%08lx, neso= 0x%08lx\n",
                        (ulong)(defrag.oeso),(ulong)(defrag.nesn),
                        (ulong)(defrag.neso));
                }
                if (defragFwrite(13,(uchar *)(&dht[fcnt]),
                    (uchar *)(&defrag),DEFRAGHDRSIZ) == -1) {
                    return(TFSERR_FLASHFAILURE);
                }
                fcnt++;
                defragTick(0);
            }
            tfp = nextfp(tfp,tdp);
        }

        if (defragBuildCrcTable(tdp,dht,verbose) < 0)
            return(TFSERR_FLASHFAILURE);

        /* Now, the last part of the state table build is to store a
         * 32-bit crc of the data we just wrote...
         */
        crc = crc32((uchar *)dht,(uchar *)tdp->end - (uchar *)dht);
        if (defragFwrite(14,(uchar *)((ulong *)dht-1),(uchar *)&crc,4) == -1) {
            return(TFSERR_FLASHFAILURE);
        }

        defrag_state = SCANNING_ACTIVE_SECTOR;
    }

    /* Exit here to have a complete defrag header installed. */
    defragExitTestPoint(1000);

    if (defrag_state == SCANNING_ACTIVE_SECTOR) {

        if (verbose) {
            printf("TFS: updating sectors %d-%d...\n",
                activesnum,lastsnum);
        }

        /* Now we begin the actual defragmentation.  We have built enough
         * state information (defrag header and state table) into the last
         * TFS sector, so now we can start the cleanup.
         */
        for(sidx = activesnum - firstsnum; sidx < tdp->sectorcount; sidx++) {
            if (defragFillActiveSector(tdp,ftot,sidx,verbose) < 0)
                return(TFSERR_FLASHFAILURE);
        }

        defrag_state = SECTOR_DEFRAG_ALMOST_DONE;
    }

    /* Exit here to test "almost-done" state detection. */
    defragExitTestPoint(1001);

    if (defrag_state == SECTOR_DEFRAG_ALMOST_DONE) {

        /* We've completed the relocation of all files into a defragmented
         * area of TFS flash space.  Now we have to erase all sectors after
         * the sector used by the last file in TFS (including the spare)...
         * If the last file in TFS uses the last sector, then the defrag
         * header table will be erased and there is nothing left to do
         * except erase the spare.
         */
        if (verbose) {
            printf("TFS: clearing available space...\n");
        }
        if (dht[ftot-1].crc != ERASED32) {
            end = (dht[ftot-1].nda + dht[ftot-1].filsize + TFSHDRSIZ) - 1;
            if (addrtosector(end,&snum,0,0) < 0)
                return(TFSERR_FLASHFAILURE);
            snum++;
            while(snum <= lastsnum) {
                if (defragSerase(7,snum) < 0)
                    return(TFSERR_FLASHFAILURE);
                snum++;
            }
            defragTick(0);
        }
        if (defragEraseSpare(tdp) < 0)
            return(TFSERR_FLASHFAILURE);

        /* All defragmentation is done, so verify sanity of files... */
        chkstat = tfscheck(tdp,verbose);
    }
    else {
        chkstat = TFS_OKAY;
    }

    if ((verbose) || (!MFLAGS_NODEFRAGPRN()))
        printf("Defragmentation complete\n");

    return(chkstat);
}

/* tfsfixup():
 *  Called at system startup to finish up a TFS defragmentation if one
 *  was in progress.
 */
int
tfsfixup(int verbose, int dontquery)
{
    TDEV    *tdp;

    /* Clear test data... */
    DefragTestType = 0;
    DefragTestPoint = 0;
    DefragTestSector = 0;

#if !DEFRAG_TEST_ENABLED
    tfsTrace = 99;
#endif

    /* For each TFS device, run defrag with "fixup" flag set to let
     * the defragger know that it should only defrag if a defrag was
     * in progress. 
     */
    for(tdp=tfsDeviceTbl;tdp->start != TFSEOT;tdp++) {
        /* Call tfsclean() with fixup flag set... */
        _tfsclean(tdp,1,99);
    }

#if !DEFRAG_TEST_ENABLED
    tfsTrace = 0;
#endif
    return(0);
}

#if DEFRAG_TEST_ENABLED
int
dumpDhdr(DEFRAGHDR *dhp)
{
    printf("ohdr: 0x%08lx\n",(ulong)dhp->ohdr);
    printf("nextfile: 0x%08lx\n",(ulong)dhp->nextfile);
    printf("filsize: 0x%08lx\n",(ulong)dhp->filsize);
    printf("crc: 0x%08lx\n",(ulong)dhp->crc);
    printf("idx: 0x%08lx\n",(ulong)dhp->idx);
    printf("nesn: 0x%08lx\n",(ulong)dhp->nesn);
    printf("neso: 0x%08lx\n",(ulong)dhp->neso);
    printf("nda: 0x%08lx\n",(ulong)dhp->nda);
    printf("fname: %s\n",dhp->fname);
    return(TFS_OKAY);
}

int
dumpDhdrTbl(DEFRAGHDR *dhp, int ftot)
{
    TDEV    *tdp;
    uchar   *sbase;
    ulong   *crc, calccrc;
    int     i, ssize, snum;
    struct  sectorcrc *crctbl;

    for(tdp=tfsDeviceTbl;tdp->start != TFSEOT;tdp++) {

        if (!dhp) {
            crctbl = defragCrcTable(tdp);
            printf("Device %s...\n",tdp->prefix);
            dhp = (struct defraghdr *)crctbl - 1;
            ftot = dhp->idx + 1;
            printf("(%d files):\n",ftot);
            dhp = (struct defraghdr *)crctbl - ftot;
            crc = (ulong *)dhp-1;
            if (*crc != crc32((uchar *)dhp,(uchar *)tdp->end - (uchar *)dhp)) {
                printf("Table CRC failure\n");
                return(TFS_OKAY);
            }
        }
        else
            crctbl = (struct sectorcrc *)(dhp + ftot);

        printf("dhp=0x%lx, ftot=%d\n",(ulong)dhp,ftot);
        
        while(dhp < (struct defraghdr *)crctbl) {
            printf(" %s (dhp=0x%lx, idx=%ld):\n",
                dhp->fname,(ulong)dhp,dhp->idx);
            printf("   nda: 0x%08lx, ohdr: 0x%08lx\n",
                (ulong)dhp->nda,(ulong)dhp->ohdr);
            dhp++;
        }

        sbase = (uchar *)tdp->start;
        printf("crctbl at 0x%lx\n",(ulong)crctbl);
        for(i=0;isectorcount;i++) {
            if (addrtosector(sbase,&snum,&ssize,0) < 0)
                return(0);
            if (i == tdp->sectorcount-1) {
                ssize -=                            /* CRC table */
                    (tdp->sectorcount * sizeof(struct sectorcrc));  
                ssize -= (ftot * DEFRAGHDRSIZ);     /* DHT table */
                ssize -= 4;                         /* Crc of the tables */
            }
            calccrc = crc32(sbase,ssize);
            if (calccrc == crctbl[i].precrc)
                printf("crctbl[%d] (snum=%d) pre-pass\n",i,snum);
            else if (calccrc == crctbl[i].postcrc)
                printf("crctbl[%d] (snum=%d) post-pass\n",i,snum);
            else {
                printf("crctbl[%d] (snum=%d) test failed\n",i,snum);
                printf("pre: 0x%lx, post: 0x%lx,calc: 0x%lx\n",
                    crctbl[i].precrc,crctbl[i].postcrc,calccrc);
            }
            sbase += ssize;
        }
    }
    return(TFS_OKAY);
}
#endif

#else

int
tfsfixup(int verbose, int dontquery)
{
    return(TFSERR_NOTAVAILABLE);
}

int
dumpDhdr(DEFRAGHDR *dhp)
{
    return(TFSERR_NOTAVAILABLE);
}

int
dumpHdrTbl(void)
{
    return(TFSERR_NOTAVAILABLE);
}

/* _tfsclean():
 *  This is an alternative to the complicated defragmentation above.
 *  It simply scans through the file list and copies all valid files
 *  to RAM; then flash is erased and the RAM is copied back to flash.
 *  <<< WARNING >>>
 *  THIS FUNCTION SHOULD NOT BE INTERRUPTED AND IT WILL BLOW AWAY
 *  ANY APPLICATION CURRENTLY IN CLIENT RAM SPACE.
 */
int
_tfsclean(TDEV *tdp, int notused, int verbose)
{
    TFILE   *tfp;
    uchar   *tbuf;
    ulong   appramstart;
    int     dtot, nfadd, len, err;

    if (TfsCleanEnable < 0)
        return(TFSERR_CLEANOFF);

    appramstart = getAppRamStart();

    /* Determine how many "dead" files exist. */
    dtot = 0;
    tfp = (TFILE *)tdp->start;
    while(validtfshdr(tfp)) {
        if (!TFS_FILEEXISTS(tfp))
            dtot++;
        tfp = nextfp(tfp,tdp);
    }

    if (dtot == 0)
        return(TFS_OKAY);

    printf("Reconstructing device %s with %d dead file%s removed...\n",
        tdp->prefix, dtot,dtot>1 ? "s":"");

    tbuf = (uchar *)appramstart;
    tfp = (TFILE *)(tdp->start);
    nfadd = tdp->start;
    while(validtfshdr(tfp)) {
        if (TFS_FILEEXISTS(tfp)) {
            len = TFS_SIZE(tfp) + sizeof(struct tfshdr);
            if (len % TFS_FSIZEMOD)
                len += TFS_FSIZEMOD - (len % TFS_FSIZEMOD);
            nfadd += len;
            err = tfsmemcpy(tbuf,(uchar *)tfp,len,0,0);
            if (err != TFS_OKAY)
                return(err);
            
            ((struct tfshdr *)tbuf)->next = (struct tfshdr *)nfadd;
            tbuf += len;
        }
        tfp = nextfp(tfp,tdp);
    }

    /* Erase the flash device: */
    err = _tfsinit(tdp);
    if (err != TFS_OKAY)
        return(err);

    /* Copy data placed in RAM back to flash: */
    err = AppFlashWrite((ulong *)(tdp->start),(ulong *)appramstart,
        (tbuf-(uchar*)appramstart));
    if (err < 0)
        return(TFSERR_FLASHFAILURE);

    return(TFS_OKAY);
}

#endif /* INCLUDE_TFSSAFEDEFRAG */

int
tfsclean_on(void)
{
    TfsCleanEnable++;
    return(TfsCleanEnable);
}

int
tfsclean_off(void)
{
    TfsCleanEnable--;
    return(TfsCleanEnable);
}

/* If INCLUDE_TFSSAFEDEFRAG is not defined, then we do not want to do any
 * automatic cleanup of the flash...
 */
int
tfsautoclean(TDEV *tdp,int verbose)
{
#if INCLUDE_TFSSAFEDEFRAG
    return(_tfsclean(tdp,0,verbose));
#else
    return(TFSERR_NOTAVAILABLE);
#endif
}

/* tfsclean():
 *  Wrapper for _tfsclean so that we can enable a call to appexit() if there
 *  is an error returned by tfsclean().  This is used for testing TFS...
 *  If a script is running to try to cause tfsclean to break, then we want
 *  it to halt if tfsclean does return an error.
 */
int
tfsclean(TDEV *tdp,int verbose)
{
    int cleanresult;

    cleanresult = _tfsclean(tdp,0,verbose);
    if (cleanresult != TFS_OKAY) {
        if (getenv("APP_EXITONCLEANERROR"))
            return -1;
            //appexit(0);
#if INCLUDE_TFSSCRIPT
        if (getenv("SCR_EXITONCLEANERROR"))
            ScriptExitFlag = EXIT_SCRIPT;
#endif
    }
    return(cleanresult);
}
#endif /* INCLUDE_TFS */