/******************************************************************************* 
 *                                                                             * 
 *                         M-Systems Confidential                              * 
 *           Copyright (C) M-Systems Flash Disk Pioneers Ltd. 1995-2001        * 
 *                         All Rights Reserved                                 * 
 *                                                                             * 
 ******************************************************************************* 
 *                                                                             * 
 *                         NOTICE OF M-SYSTEMS OEM                             * 
 *                         SOFTWARE LICENSE AGREEMENT                          * 
 *                                                                             * 
 *  THE USE OF THIS SOFTWARE IS GOVERNED BY A SEPARATE LICENSE AGREEMENT       * 
 *  BETWEEN THE OEM AND M-SYSTEMS. REFER TO THAT AGREEMENT FOR THE SPECIFIC    * 
 *  TERMS AND CONDITIONS OF USE, OR CONTACT M-SYSTEMS FOR LICENSE              * 
 *  ASSISTANCE:                                                                * 
 *  E-MAIL = info@m-sys.com                                                    * 
 *                                                                             * 
 *******************************************************************************
 *                                                                             * 
 *                                                                             * 
 *                         Module: FATFILT                                     * 
 *                                                                             * 
 *  This module implements installable FAT12/16 filters. It supports up to     *
 *  SOCKETS sockets, with up to MAX_TL_PARTITIONS disks per socket.      * 
 *  Each disk can contain up to FL_MAX_PARTS_PER_DISK partitions on it, with   * 
 *  maximum depth of partition nesting in extended partitions equal to         * 
 *  MAX_PARTITION_DEPTH.                                                       *
 *                                                                             * 
 *  In order for this module to work, disks must be abs.mounted rather then    * 
 *  mounted. In latter case, this module won't detect any of disk's            * 
 *  partitions, and won't install FAT filters.                                 * 
 *                                                                             * 
 *  This module uses more then 512 bytes of stack space in case if MALLOC is   * 
 *  not enabled.                                                               * 
 *                                                                             * 
 *******************************************************************************/

/*
 * $Log:   V:/Flite/archives/TrueFFS5/Src/FATFILT.C_V  $
 * 
 *    Rev 1.10   Jan 17 2002 23:00:14   oris
 * Changed debug print added \r.
 * 
 *    Rev 1.9   Sep 15 2001 23:45:50   oris
 * Changed BIG_ENDIAN to FL_BIG_ENDIAN
 * 
 *    Rev 1.8   Jun 17 2001 16:39:10   oris
 * Improved documentation and remove warnings.
 * 
 *    Rev 1.7   May 16 2001 21:17:18   oris
 * Added the FL_ prefix to the following defines: MALLOC and FREE.
 * 
 *    Rev 1.6   May 01 2001 14:21:02   oris
 * Remove warnings.
 * 
 *    Rev 1.5   Apr 30 2001 18:00:10   oris
 * Added casting to remove warrnings.
 * 
 *    Rev 1.4   Apr 24 2001 17:07:32   oris
 * Missing casting of MALLOC calls.
 * 
 *    Rev 1.3   Apr 10 2001 23:54:24   oris
 * FL_MAX_DISKS_PER_SOCKET changed to MAX_TL_PARTITIONS.
 * 
 *    Rev 1.2   Apr 09 2001 15:00:42   oris
 * Change static allocation to dynamic allocations.
 * Renamed flffCheck back to ffCheckBeforeWrite to be backwards compatible with OSAK 4.2.
 * 
 *    Rev 1.1   Apr 01 2001 07:51:16   oris
 * New implementation of fat filter.
 * 
 *    Rev 1.0   19 Feb 2001 21:14:14   andreyk
 * Initial revision.
 */




/* 
 * Includes
 */

#include "fatfilt.h"
#include "blockdev.h"
#include "flflash.h"
#include "bddefs.h"


#if defined(ABS_READ_WRITE) && !defined(FL_READ_ONLY)




/*
 * Module configuration
 */

#define  FL_INCLUDE_FAT_MONITOR     /* undefine it to remove FAT filter code */




/*
 * Defines
 */

/* extract pointer to user's buffer from IOreq */

#ifdef SCATTER_GATHER
#define  FLBUF(ioreq,i)  (*((char FAR1 **)((ioreq)->irData) + (int)(i)))
#else
#define  FLBUF(ioreq,i)  ((char FAR1 *)(ioreq->irData) + (SECTOR_SIZE * ((int)(i))))
#endif

/* extract socket# and disk# from TFFS handle */

#define  H2S(handle)     (((int)(handle)) & 0xf)
#define  H2D(handle)     ((((int)(handle)) >> 4) & 0xf)

/* construct TFFS handle from socket# and disk# */

#define  SD2H(socNo,diskNo)  ((int)((((diskNo) & 0xf) << 4) | ((socNo) & 0xf)))

/* unformatted ("raw") disk partition */

#define  FL_RAW_PART  (-1)




/*
 * Local routines 
 */

static FLStatus   reset (void);
static FLStatus   discardDisk (int handle);
static FLStatus   newDisk (int handle);
static FLStatus   parseDisk (int handle);
static FLStatus   discardDiskParts (FLffDisk *pd);
static FLStatus   addDiskPart (FLffDisk *pd, int partNo);
static FLStatus   addNewDiskPart (FLffDisk *pd);
static FLBoolean  isPartTableWrite (FLffDisk *pd, IOreq FAR2 *ioreq);
static FLStatus   isExtPartPresent (char FAR1 *buf, SectorNo *nextExtPartSec);


#ifdef FL_INCLUDE_FAT_MONITOR

static FLStatus   partEnableFF (FLffVol* pv);
static FLStatus   partFreeDelClusters (FLffVol *pv, SectorNo secNo, char FAR1 *newFAT);

#endif




/*
 * Local data
 */

/* module reset flag */

static FLBoolean  resetDone = FALSE; 

/* disks (BDTL partitions in OSAK terminology) */

static FLffDisk*  ffDisk[SOCKETS][MAX_TL_PARTITIONS] = { { NULL } };


#ifndef FL_MALLOC

/*          
 *        WARNING: Large static arrays ! 
 *
 *  sizeof(ffAllDisks[x][y])    is 64 bytes. 
 *  sizeof(ffAllParts[x][y][z]) is 40 bytes.
 *
 */

static FLffDisk ffAllDisks[SOCKETS][MAX_TL_PARTITIONS];
static FLffVol  ffAllParts[SOCKETS][MAX_TL_PARTITIONS][FL_MAX_PARTS_PER_DISK];

#endif /* FL_MALLOC */

static const char zeroes[SECTOR_SIZE] = {0};




/* --------------------------------------------------------------------------- *
 *                                                                             *
 *                    d i s c a r d D i s k P a r t s                          *
 *                                                                             * 
 *  Discard all the partition info (if any) associated with particular disk.   * 
 *                                                                             * 
 *  Parameters:                                                                * 
 *      pd                 : disk (BDTL volume)                                   * 
 *                                                                             * 
 *  Returns:                                                                   * 
 *      Always flOK.                                                           * 
 *                                                                             * 
 * --------------------------------------------------------------------------- */

static FLStatus  discardDiskParts ( FLffDisk * pd )
{
    register int  i;

    if (pd != NULL) {

        for (i = 0; i < FL_MAX_PARTS_PER_DISK; i++) {

#ifdef FL_MALLOC
        if (pd->part[i] != NULL)
            FL_FREE(pd->part[i]);
#endif

            pd->part[i] = NULL;
        }

        pd->parts = 0;
    }

    return flOK;
}




/* --------------------------------------------------------------------------- *
 *                                                                             *
 *                      a d d D i s k P a r t                                  *
 *                                                                             * 
 *  If there is partition record #partNo associated with the disk, discard     * 
 *  this info. Attach new partition record #partNo.                            * 
 *                                                                             * 
 *  Parameters:                                                                * 
 *      pd                 : disk (BDTL volume)                                   * 
 *      partNo             : partition (0 ... FL_MAX_PARTS_PER_DISK-1)            * 
 *                                                                             * 
 *  Returns:                                                                   * 
 *      flOK if success, otherwise respective error code                       * 
 *                                                                             * 
 * --------------------------------------------------------------------------- */

static FLStatus  addDiskPart ( FLffDisk * pd, 
                               int        partNo )
{
    FLffVol  * pv;    
    FLStatus   status;
    int        socNo, diskNo;

    /* arg. sanity check */

    if ((pd == NULL) || (partNo >= FL_MAX_PARTS_PER_DISK))
        return flBadDriveHandle;

    /* break TFFS handle into socket# and disk#, and do sanity check */

    socNo  = H2S(pd->handle);
    diskNo = H2D(pd->handle);
 
    if ((socNo >= SOCKETS) || (diskNo >= MAX_TL_PARTITIONS))
        return flBadDriveHandle;

    status = flNotEnoughMemory;

#ifdef FL_MALLOC
    pv = (FLffVol *)FL_MALLOC( sizeof(FLffVol) );
#else
    pv = &ffAllParts[socNo][diskNo][partNo];
#endif

    if (pv != NULL) {

        /* initialize fields in struct FLffDisk to safe values */
 
        pv->handle         = pd->handle; 
        pv->type           = FL_RAW_PART;
        pv->flags          = 0;          
        pv->ffEnabled      = FALSE;                  /* turn off FAT minitor */
        pv->sectors        = (SectorNo) 0;
        pv->firstFATsecNo  = (SectorNo) -1;          /* none */
        pv->lastFATsecNo   = pv->firstFATsecNo;      /* none */
        pv->firstDataSecNo = (SectorNo) 0;
        pv->clusterSize    = (unsigned) 0;

#ifdef FL_MALLOC
        if( pd->part[partNo] != NULL )
        FL_FREE(pd->part[partNo]);
#endif
        
        pd->part[partNo] = pv;

        status = flOK;
    }

    return status;    
}




/* --------------------------------------------------------------------------- *
 *                                                                             *
 *                    a d d N e w D i s k P a r t                              *
 *                                                                             * 
 *  Add one more partition record to the disk.                                 * 
 *                                                                             * 
 *  Parameters:                                                                * 
 *      pd                 : disk (BDTL volume)                                   * 
 *                                                                             * 
 *  Returns:                                                                   * 
 *      flOK if success, otherwise respective error code                       * 
 *                                                                             * 
 * --------------------------------------------------------------------------- */

static FLStatus  addNewDiskPart ( FLffDisk * pd )
{
    if (pd->parts < FL_MAX_PARTS_PER_DISK) {

        checkStatus( addDiskPart (pd, pd->parts) );
    pd->parts++;
    }

    return flOK;
}




/* --------------------------------------------------------------------------- *
 *                                                                             *
 *                       d i s c a r d D i s k                                 *
 *                                                                             * 
 *  Remove disk record (with all the associated partition records).            * 
 *                                                                             * 
 *  Parameters:                                                                * 
 *      handle             : TFFS handle                                          * 
 *                                                                             * 
 *  Returns:                                                                   * 
 *      flOK if success, otherwise respective error code                       * 
 *                                                                             * 
 * --------------------------------------------------------------------------- */

static FLStatus  discardDisk ( int  handle )
{
    int  socNo, diskNo;

    /* break TFFS handle into socket# and disk#, and do sanity check */

    socNo  = H2S(handle);
    diskNo = H2D(handle);
 
    if ((socNo >= SOCKETS) || (diskNo >= MAX_TL_PARTITIONS))
        return flBadDriveHandle;

    if( ffDisk[socNo][diskNo] != NULL ) {

    /* discard associated partition info */

    (void) discardDiskParts( ffDisk[socNo][diskNo] );

#ifdef FL_MALLOC

        /* release disk's scratch buffer */
 
        if( (ffDisk[socNo][diskNo])->buf != NULL)
            FL_FREE( (ffDisk[socNo][diskNo])->buf );

        FL_FREE( ffDisk[socNo][diskNo] );

#endif

        ffDisk[socNo][diskNo] = NULL;
    }

    return flOK;    
}




/* --------------------------------------------------------------------------- *
 *                                                                             *
 *                           n e w D i s k                                     *
 *                                                                             * 
 *  Discard existing disk record (if any), and create new one.                 * 
 *                                                                             * 
 *  Parameters:                                                                * 
 *      handle             : TFFS handle                                       * 
 *                                                                             * 
 *  Returns:                                                                   * 
 *      flOK if success, otherwise respective error code                       * 
 *                                                                             * 
 * --------------------------------------------------------------------------- */

static FLStatus  newDisk ( int  handle )
{
    int        socNo, diskNo;
    int        i;
    FLffDisk * pd;

    /* break TFFS handle into socket# and disk#, and do sanity check */

    socNo  = H2S(handle);
    diskNo = H2D(handle);
 
    if ((socNo >= SOCKETS) || (diskNo >= MAX_TL_PARTITIONS))
        return flBadDriveHandle;

    /* discard current disk and associated partition info (if any) */

    checkStatus( discardDisk(handle) );

#ifdef FL_MALLOC

    pd = (FLffDisk *) FL_MALLOC( sizeof(FLffDisk) );

    if (pd == NULL)
        return flNotEnoughMemory;

    /* allocate and attach disk's scratch buffer */

    pd->buf = (char *)FL_MALLOC( SECTOR_SIZE );

    if (pd->buf == NULL) {

        FL_FREE (pd);
        return flNotEnoughMemory;
    }

#else /* !FL_MALLOC */

    pd = &ffAllDisks[socNo][diskNo];

#endif /* FL_MALLOC */


    pd->handle  = handle;
    pd->ffstate = flStateNotInitialized;

    /* don't know partition layout yet */

    pd->parts   = 0;
    for (i = 0; i < FL_MAX_PARTS_PER_DISK; i++)
        pd->part[i] = NULL;

    /* watch Master Boot Record for update */

    pd->secToWatch = (SectorNo) 0;

    ffDisk[socNo][diskNo] = pd;

    return flOK;
}




/* --------------------------------------------------------------------------- *
 *                                                                             *
 *                   i s P a r t T a b l e W r i t e                           *
 *                                                                             * 
 *  Check if any of the sectors specified by 'ioreq' points to Master Boot     * 
 *  Record or next extended partition in the extended partitions list.         * 
 *                                                                             * 
 *  Parameters:                                                                * 
 *      pd                 : pointer to disk structure                         * 
 *      ioreq              : standard I/O request                              * 
 *                                                                             * 
 *  Returns:                                                                   * 
 *      TRUE if write to MBR or extended partition list is detected, otherwise * 
 *      FALSE                                                                  * 
 *                                                                             * 
 * --------------------------------------------------------------------------- */

static FLBoolean isPartTableWrite ( FLffDisk   * pd, 
                                    IOreq FAR2 * ioreq )
{
    register long  i;

    if (pd != NULL) {

        for (i = (long)0; i < ioreq->irSectorCount; i++) {

            if( (ioreq->irSectorNo + i) == (long)pd->secToWatch )
                return TRUE;
        }
    }

    return FALSE;
}




/* --------------------------------------------------------------------------- *
 *                                                                             *
 *                   i s E x t P a r t P r e s e n t                           *
 *                                                                             * 
 *  Check if extended partition persent in the partition table. If it is,      * 
 *  calculate the sector # where next partition table will be written to.      * 
 *                                                                             * 
 *  Parameters:                                                                * 
 *      buf                : partition table                                   * 
 *      nextExtPartSec  : sector where next partition table will be written to * 
 *                                                                             * 
 *  Returns:                                                                   * 
 *      flOK on success, otherwise error code                                  * 
 *                                                                             * 
 * --------------------------------------------------------------------------- */

static FLStatus  isExtPartPresent ( char FAR1 * buf, 
                                    SectorNo  * nextExtPartSec )
{
    Partition FAR1 * p;
    register int     i;
  
    /* does it look like partition table ? */

    if (LE2(((PartitionTable FAR1 *) buf)->signature) != PARTITION_SIGNATURE)
        return flBadFormat;   

    /* if extended. part. present, get sector# that will contain next part. in list */

    p = &( ((PartitionTable FAR1 *) buf)->ptEntry[0] );

    for (i = 0;  i < FL_PART_TBL_ENTRIES;  i++, p++) {

        if (p->type == EX_PARTIT) {

            *nextExtPartSec = (SectorNo) UNAL4( (void *) p[i].startingSectorOfPartition );
            return flOK;
        }
    }

    /* no extended partition found */

    return flFileNotFound;
}




/* --------------------------------------------------------------------------- *
 *                                                                             *
 *                           r e s e t                                         *
 *                                                                             * 
 *  Resets this software module to it's initial state upon boot.               * 
 *                                                                             * 
 *  Parameters:                                                                * 
 *    none                                                                   * 
 *                                                                             * 
 *  Returns:                                                                   * 
 *      flOK in case of success, otherwise respective error code               * 
 *                                                                             * 
 * --------------------------------------------------------------------------- */

static FLStatus  reset (void)
{
    int iSoc, iDisk;

    for (iSoc = 0; iSoc < SOCKETS; iSoc++) {

        /* discard existing disk structures for that socket */

        for (iDisk = 0; iDisk < MAX_TL_PARTITIONS; iDisk++)
        (void) discardDisk( SD2H(iSoc, iDisk) );

        /* pre-allocate disk structure for first disk of every socket */

        checkStatus( newDisk(SD2H(iSoc, 0)) );
    }

    resetDone = TRUE;

    return flOK;
}




/* --------------------------------------------------------------------------- *
 *                                                                             *
 *                          p a r s e D i s k                                  *
 *                                                                             * 
 *  Read partition table(s), install and enable FAT filters on all FAT12/16    * 
 *  partitions.                                                                * 
 *                                                                             * 
 *  Parameters:                                                                * 
 *    handle         : TFFS handle                                          * 
 *                                                                             * 
 *  Returns:                                                                   * 
 *      flOK on success, otherwise error code                                  * 
 *                                                                             * 
 *  NOTE:  This routine uses disk's scratch buffer.                            * 
 *                                                                             * 
 * --------------------------------------------------------------------------- */

static FLStatus  parseDisk ( int handle )
{
    int          socNo, diskNo;
    SectorNo     extPartStartSec, sec;
    int          i, depth;
    int          type;
    FLffDisk   * pd;
    FLffVol    * pv;
    Partition  * pp;
    IOreq        ioreq;

#ifdef  FL_MALLOC
    char       * buf;
#else
    char         buf[SECTOR_SIZE];
#endif

    /* break TFFS handle into socket# and disk#, and do sanity check */

    socNo  = H2S(handle);
    diskNo = H2D(handle);
 
    if ((socNo >= ((int) noOfSockets)) || (diskNo >= MAX_TL_PARTITIONS))
        return flBadDriveHandle;

    /* if disk structure hasn't been allocated yet, do it now */

    if (ffDisk[socNo][diskNo] == NULL)
        checkStatus( newDisk(handle) );

    pd = ffDisk[socNo][diskNo];
    
#ifdef  FL_MALLOC

    /* make sure scratch buffer is available */

    if (pd->buf == NULL)
        return flBufferingError;

    buf = pd->buf;

#endif /* FL_MALLOC */ 
 
    /* discard obsolete disk's partition info */

    (void) discardDiskParts (pd);

    /* read Master Boot Record */

    ioreq.irHandle      = handle;
    ioreq.irSectorNo    = (SectorNo) 0;
    ioreq.irSectorCount = (SectorNo) 1;
    ioreq.irData        = (void FAR1 *) buf;
    checkStatus( flAbsRead(&ioreq) );

    /* is it MBR indeed ? */

    if (LE2(((PartitionTable *) buf)->signature) != PARTITION_SIGNATURE)
        return flPartitionNotFound;                         

    /* do primary partitions only (we will do extended partitions later) */

    pp = &( ((PartitionTable *) buf)->ptEntry[0] );

    for (i = 0; i < FL_PART_TBL_ENTRIES; i++, pp++) {

        if( pp->type == ((char)0) )          /* skip empty slot */
            continue;

        if( pp->type == ((char)EX_PARTIT) )  /* skip extended partition */
        continue;

    /* primary partition found (not necessarily FAT12/16) */

        if( addNewDiskPart(pd) != flOK )
        break;

        pv = pd->part[pd->parts - 1];

        /* remember partition's type, and where it starts */

        pv->type       = (int) pp->type;
        pv->startSecNo = (SectorNo) UNAL4( (void *) pp->startingSectorOfPartition );
    } 

    /* do extended partitions in depth */

    for (i = 0; i < FL_PART_TBL_ENTRIES; i++) {

        /* re-read Master Boot Record */

        ioreq.irHandle      = handle;
        ioreq.irSectorNo    = (SectorNo) 0;
        ioreq.irSectorCount = (SectorNo) 1;
        ioreq.irData        = (void FAR1 *) buf;
        checkStatus( flAbsRead(&ioreq) );

        /* is it MBR indeed ? */

        if (LE2(((PartitionTable *) buf)->signature) != PARTITION_SIGNATURE)
            return flOK;

        /* pick up next extended partition in MBR */

        pp = &( ((PartitionTable *) buf)->ptEntry[i] );

        if( pp->type == ((char)EX_PARTIT) ) {

            /* remember where extended partition starts */

            extPartStartSec = (SectorNo) UNAL4( (void *) pp->startingSectorOfPartition );   

            /* follow the list of partition tables */

            sec = extPartStartSec;

            for (depth = 0;  depth < MAX_PARTITION_DEPTH;  depth++) {

                /* read next partition table in the list */

                ioreq.irHandle      = handle;
                ioreq.irSectorNo    = sec;
                ioreq.irSectorCount = (SectorNo) 1;
                ioreq.irData        = (void FAR1 *) buf;
                checkStatus( flAbsRead(&ioreq) );

                /* is it valid partition table ? */

                if (LE2(((PartitionTable *) buf)->signature) != PARTITION_SIGNATURE)
                    break;

                /* if 1st entry is zero, it's the end of part. table list */

                pp = &( ((PartitionTable *) buf)->ptEntry[0] );
                if( pp->type == ((char)0) )
                    break;

                /* Take this partition. Remember it's type, and where it starts */

                if( addNewDiskPart(pd) != flOK )
                break;

                pv = pd->part[pd->parts - 1];

                pv->type       = (int) pp->type;
                pv->startSecNo = 
                    (SectorNo) UNAL4( (void *) pp->startingSectorOfPartition) + sec;

                /* 2nd entry must be extended partition */

                pp = &( ((PartitionTable *) buf)->ptEntry[1] );
                if( pp->type != ((char)EX_PARTIT) )
              break;

                /* sector where next part. table in the list resides */

                sec = extPartStartSec + 
                      (SectorNo) UNAL4( (void *) pp->startingSectorOfPartition );

        }   /* for(depth) */
        }
    }   /* for(i) */ 

#ifdef FL_INCLUDE_FAT_MONITOR

    /* turn on FAT filters on FAT12/16 partition(s) */

    if (pd->parts > 0) {

        for (i = 0;  i < pd->parts;  i++) {

            pv   = pd->part[i];
            type = pv->type;

            /*
             *  WARNING : Routine partEnableFF() uses disk's scratch buffer !
             */

        if((type == FAT12_PARTIT) || (type == FAT16_PARTIT) || (type == DOS4_PARTIT))
                partEnableFF (pv);
    }
    }

#endif /* FL_INCLUDE_FAT_MONITOR */

    /* watch for MBR (sector #0) update */

    pd->secToWatch = (SectorNo) 0;

    pd->ffstate    = flStateInitialized;

    return flOK;
}




#ifdef FL_INCLUDE_FAT_MONITOR

/* --------------------------------------------------------------------------- *
 *                                                                             *
 *                     p a r t E n a b l e F F                                 *
 *                                                                             * 
 *  Installs and enables FAT filter on partition.                              * 
 *                                                                             * 
 *  Parameters:                                                                * 
 *    pv            : disk partition (filesystem volume)                   * 
 *                                                                             * 
 *  Returns:                                                                   * 
 *      flOK on success, otherwise error code                                  * 
 *                                                                             * 
 *  NOTE:  This routine uses disk's scratch buffer.                            * 
 *                                                                             * 
 * --------------------------------------------------------------------------- */

static FLStatus  partEnableFF ( FLffVol * pv )
{
    int        socNo, diskNo;
    FLffDisk * pd;
    BPB      * bpb;
    SectorNo   sectors;
    SectorNo   rootDirSecNo;
    SectorNo   rootDirSectors;
    SectorNo   sectorsPerFAT;
    unsigned   maxCluster;
    int        partNo;
    IOreq      ioreq;

#ifdef  FL_MALLOC
    char     * buf;
#else
    char       buf[SECTOR_SIZE];
#endif

    /* arg. sanity check */

    if (pv == NULL)
        return flBadDriveHandle;
 
    /* break TFFS handle into socket# and disk#, and do sanity check */

    socNo  = H2S(pv->handle);
    diskNo = H2D(pv->handle);
 
    if ((socNo >= ((int) noOfSockets)) || (diskNo >= MAX_TL_PARTITIONS))
        return flBadDriveHandle;

    /* check if 'pv' belongs to this disk */

    pd = ffDisk[socNo][diskNo];

    if (pd == NULL)
        return flBadDriveHandle;

    for (partNo = 0; partNo < pd->parts; partNo++) {

        if (pd->part[partNo] == pv)
        break;
    }

    if (partNo >= pd->parts)
        return flBadDriveHandle;

#ifdef  FL_MALLOC

    /* make sure scratch buffer is available */

    if (pd->buf == NULL)
        return flBufferingError;

    buf = pd->buf;
 
#endif /* FL_MALLOC */ 

    /* make sure FAT filter is off on this partition */

    pv->ffEnabled       = FALSE;

    pv->firstFATsecNo   = (SectorNo) -1;
    pv->lastFATsecNo    = pv->firstFATsecNo;
    pv->clusterSize     = (unsigned) 0;

    /* read the BPB */

    ioreq.irHandle      = pv->handle;
    ioreq.irSectorNo    = pv->startSecNo;
    ioreq.irSectorCount = (SectorNo) 1;
    ioreq.irData        = (void FAR1 *) buf;
    checkStatus( flAbsRead(&ioreq) );

    /* Does it look like DOS bootsector ? */

    bpb = &( ((DOSBootSector *) buf)->bpb );

    if( !((bpb->jumpInstruction[0] == 0xe9) 
            || 
         ((bpb->jumpInstruction[0] == 0xeb) && (bpb->jumpInstruction[2] == 0x90)))) {

        return flNonFATformat;
    }

    /* Do we handle this sector size ? */

    if (UNAL2(bpb->bytesPerSector) != SECTOR_SIZE)
        return flFormatNotSupported;

    /* 
     * Is it a bogus BPB (leftover from previous disk partitioning) ? 
     * Check that there is no overlap with next partition (if one exists).
     */

    sectors = UNAL2(bpb->totalSectorsInVolumeDOS3);
    if (sectors == (SectorNo)0)
        sectors = (SectorNo) LE4(bpb->totalSectorsInVolume);

    if ((partNo+1 < pd->parts) && (pd->part[partNo+1] != NULL)) {

        if( sectors > (pd->part[partNo+1])->startSecNo - pv->startSecNo )
            return flNonFATformat;
    }

    /* number of sectors in partition as reported by BPB */

    pv->sectors        = sectors;

    /* valid BPB; get the rest of partition info from it */

    pv->firstFATsecNo  = pv->startSecNo + (SectorNo)( LE2(bpb->reservedSectors) );
    sectorsPerFAT      = (SectorNo) LE2(bpb->sectorsPerFAT);
    pv->lastFATsecNo   = pv->firstFATsecNo + sectorsPerFAT - (SectorNo)1;
    rootDirSecNo       = pv->firstFATsecNo + (sectorsPerFAT * bpb->noOfFATS);
    rootDirSectors     = (SectorNo)1 + (SectorNo)
        (((UNAL2(bpb->rootDirectoryEntries) * DIRECTORY_ENTRY_SIZE) - 1) / SECTOR_SIZE);
    pv->firstDataSecNo = rootDirSecNo + rootDirSectors;

    pv->clusterSize    = bpb->sectorsPerCluster;

    /* decide which type of FAT is it */

    maxCluster         = (unsigned)1 + (unsigned) 
        ((pv->sectors - (pv->firstDataSecNo - pv->startSecNo)) / pv->clusterSize);

    if (maxCluster < 4085) {
        pv->flags |= VOLUME_12BIT_FAT;    /* 12-bit FAT */

#ifndef FAT_12BIT
        DEBUG_PRINT(("Debug: FAT_12BIT must be defined.\r\n"));
        return flFormatNotSupported;
#endif
    }

    /* turn on FAT filter on this partition */

    pv->ffEnabled = TRUE;

    return flOK;
}




/* --------------------------------------------------------------------------- *
 *                                                                             *
 *                   p a r t F r e e D e l C l u s t e r s                     *
 *                                                                             * 
 *  Compare the new contents of the specified FAT sector against the old       * 
 *  one on the disk. If any freed clusters are found, issue 'sector delete'    * 
 *  calls for all sectors that are occupied by these freed clusters.           * 
 *                                                                             * 
 *  Parameters:                                                                * 
 *    pv            : disk partition (filesystem volume)                   * 
 *      secNo           : abs. sector # of the FAT sector                      * 
 *      newFAT          : new contents of this FAT sector                      * 
 *                                                                             * 
 *  Returns:                                                                   * 
 *      flOK on success, otherwise error code                                  * 
 *                                                                             * 
 *  NOTE:  This routine uses disk's scratch buffer.                            * 
 *                                                                             * 
 * --------------------------------------------------------------------------- */

static FLStatus  partFreeDelClusters ( FLffVol   * pv,
                                       SectorNo    secNo,
                                       char FAR1 * newFAT)
{
    FLffDisk * pd;
    int        socNo, diskNo;
    unsigned   short oldFATentry, newFATentry;
    SectorNo   iSec;
    unsigned   firstCluster;
    IOreq      ioreq;
    int        offset;
    int        iPart;

#ifdef FAT_12BIT
    int        halfBytes;
#endif

#ifdef  FL_MALLOC
    char     * oldFAT;
#else
    char       oldFAT[SECTOR_SIZE];
#endif

    /* arg. sanity check */

    if (pv == NULL)
        return flBadDriveHandle;
 
    /* break TFFS handle into socket# and disk#, and do sanity check */

    socNo  = H2S(pv->handle);
    diskNo = H2D(pv->handle);
 
    if ((socNo >= ((int) noOfSockets)) || (diskNo >= MAX_TL_PARTITIONS))
        return flBadDriveHandle;

    /* check if 'pv' belongs to this disk */

    pd = ffDisk[socNo][diskNo];

    if (pd == NULL)
        return flBadDriveHandle;

    for (iPart = 0; iPart < pd->parts; iPart++) {

        if (pd->part[iPart] == pv)
        break;
    }

    if (iPart >= pd->parts)
        return flBadDriveHandle;

#ifdef  FL_MALLOC

    /* make sure scratch buffer is available */

    if (pd->buf == NULL)
        return flBufferingError;

    oldFAT = pd->buf;
 
#endif /* FL_MALLOC */

    /* read in the FAT sector from the disk */

    ioreq.irHandle      = pv->handle;
    ioreq.irSectorNo    = secNo;
    ioreq.irSectorCount = 1;
    ioreq.irData        = (void FAR1 *) oldFAT;
    checkStatus( flAbsRead(&ioreq) );

#ifdef FAT_12BIT

    /* size of FAT entry in half-bytes */

    halfBytes = ((pv->flags & VOLUME_12BIT_FAT) ? 3 : 4);

    /* starting cluster */

    if (halfBytes == 3) {

    firstCluster = 
            ((((unsigned)(secNo - pv->firstFATsecNo)) * (2 * SECTOR_SIZE)) + 2) / 3;
    }
    else {
        firstCluster = ((unsigned)(secNo - pv->firstFATsecNo)) * (SECTOR_SIZE / 2);
    }

    /* staring data sector */

    iSec = (((SectorNo)firstCluster - 2) * pv->clusterSize) + pv->firstDataSecNo;

    offset = (firstCluster * ((unsigned) halfBytes)) & ((2 * SECTOR_SIZE) - 1);

    /* 
     *  Find if any clusters were logically deleted, and if so, delete them.
     *
     *  NOTE: We are skipping over 12-bit FAT entries which span more than
     *        one sector.
     */

    for (; offset < ((2 * SECTOR_SIZE) - 2); 
               offset += halfBytes, iSec += pv->clusterSize) {

#ifdef FL_BIG_ENDIAN
        oldFATentry = LE2( *(LEushort FAR0 *)(oldFAT + (offset / 2)) );
        newFATentry = LE2( *(LEushort FAR1 *)(newFAT + (offset / 2)) );
#else
        oldFATentry = UNAL2( *(Unaligned FAR0 *)(oldFAT + (offset / 2)) );
        newFATentry = UNAL2( *(Unaligned FAR1 *)(newFAT + (offset / 2)) );
#endif /* FL_BIG_ENDIAN */

        if (offset & 1) {
            oldFATentry >>= 4;
            newFATentry >>= 4;
        }
        else { 
            if (halfBytes == 3) {
                oldFATentry &= 0xfff;
                newFATentry &= 0xfff;
        }
        }

#else /* !FAT_12BIT */

    firstCluster = ((unsigned) (secNo - pv->firstFATsecNo) * (SECTOR_SIZE / 2));
    iSec  = pv->firstDataSecNo + 
            (((SectorNo)(firstCluster - (unsigned)2)) * pv->clusterSize);

    /* Find if any clusters were logically deleted, and if so, delete them */

    for (offset = 0;  offset < SECTOR_SIZE;  offset += 2, iSec += pv->clusterSize) {

        oldFATentry = LE2( *(LEushort FAR0 *)(oldFAT + offset) );
        newFATentry = LE2( *(LEushort FAR1 *)(newFAT + offset) );

#endif /* FAT_12BIT */

        if ((oldFATentry != FAT_FREE) && (newFATentry == FAT_FREE)) {

            ioreq.irHandle      = pv->handle;
            ioreq.irSectorNo    = iSec;
            ioreq.irSectorCount = pv->clusterSize;
            checkStatus( flAbsDelete(&ioreq) );
        }
    }

    return flOK;
}

#endif /* FL_INCLUDE_FAT_MONITOR */




/* --------------------------------------------------------------------------- *
 *                                                                             *
 *                   f f C h e c k B e f o r e W r i t e                       *
 *                                                                             *
 *  Catch all the FAT updates. Detect disk partitioning operation, track it    *
 *  to completion, re-read partition tables, and re-install FAT filters on     *
 *  all FAT12/16 partitions.                                                   *
 *                                                                             *
 *  Parameters:                                                                *
 *      ioreq              : standard I/O request to be checked                   *
 *                                                                             *
 *  Returns:                                                                   *
 *      flOK on success, otherwise error code                                  *
 *                                                                             *
 * --------------------------------------------------------------------------- */

FLStatus  ffCheckBeforeWrite ( IOreq FAR2 * ioreq )
{
    int         socNo, diskNo;
    FLffDisk  * pd;
    FLffVol   * pv;
    long          iSec;
    int         iPart;
    IOreq       ioreq2;
    char FAR1 * usrBuf;

    /* if module hasn't been reset yet, do it now */

    if (resetDone == FALSE)
        (void) reset();

    /* break TFFS handle into socket# and disk#, and do sanity check */

    socNo  = H2S(ioreq->irHandle);
    diskNo = H2D(ioreq->irHandle);
 
    if ((socNo >= ((int) noOfSockets)) || (diskNo >= MAX_TL_PARTITIONS))
        return flBadDriveHandle;

    /* if disk structure hasn't been allocated yet, do it now */

    if (ffDisk[socNo][diskNo] == NULL)
        checkStatus( newDisk((int)ioreq->irHandle) );

    pd = ffDisk[socNo][diskNo];

    /* read partition table(s) and install FAT filters is needed */

    if (pd->ffstate == flStateNotInitialized)
        checkStatus( parseDisk((int)ioreq->irHandle) );

    /* catch writes to MBR, and track the whole disk partitioning operations */

    while( isPartTableWrite(pd, ioreq) == TRUE ) {

        /* disk re-partitioning is in progress */

        if( pd->secToWatch == (SectorNo)0 ) {

            /* it's write to MBR, so trash BPBs in all disk's partitions */

            if (pd->parts > 0) {

                for (iPart = 0;  iPart < pd->parts;  iPart++) {

                    ioreq2.irHandle      = ioreq->irHandle;
                    ioreq2.irSectorNo    = (pd->part[iPart])->startSecNo;
                    ioreq2.irSectorCount = (SectorNo) 1;
                    ioreq2.irData        = (void FAR1 *) zeroes;
                    (void) flAbsWrite(&ioreq2);
        }
        }
        }

        /* keep FAT filters disabled while disk partitioning is in progress */

        pd->ffstate = flStateInitInProgress;

        /* partition table which is about to be written to disk */

        usrBuf = FLBUF( ioreq, (pd->secToWatch - ioreq->irSectorNo) );

        switch( isExtPartPresent(usrBuf, &(pd->secToWatch)) ) {

            case flOK: 

                /* 
                 * Found valid partition table with extended partition.
                 * The pd->secToWatch has been updated to point to the
                 * sector where next partition table will be written to. 
                 */
                continue;

            case flFileNotFound:

                /* 
                 * Valid partition table, but no extended partition in it. 
                 * Partitioning has been completed. Set pd->ffstate to 
                 * 'flStateNotInitialized' to initiate parsing of partition
                 * table(s) and FAT filter installation next time this routine
                 * is called. 
                 */

                pd->ffstate = flStateNotInitialized;
                break;

            case flBadFormat:
        default:

                /* No valid partition table. */

                break;
        }

        return flOK;
    }

#ifdef FL_INCLUDE_FAT_MONITOR

    /* check for FAT update */

    if (pd->ffstate == flStateInitialized) {

        /* NOTE: We can handle 'write' request that spans disk partition boundaries */

        for (iSec = ioreq->irSectorNo; 
             iSec < (ioreq->irSectorNo + ioreq->irSectorCount); iSec++) {

            for (iPart = 0; iPart < pd->parts; iPart++) {

                pv = pd->part[iPart];

                /* we monitor only FAT12/16 partitions */

                if ((pv->type != FAT12_PARTIT) && (pv->type != FAT16_PARTIT) && 
                                                  (pv->type != DOS4_PARTIT))
            continue;

                /* FAT filters can be disabled on individual partitions */

                if (pv->ffEnabled != TRUE)
                    continue;

                if ((iSec >= (long)pv->firstFATsecNo) && (iSec <= (long)pv->lastFATsecNo)) {

                    /* compare new and old contents of FAT sectors(s) */

                    usrBuf = FLBUF( ioreq, (iSec - ioreq->irSectorNo) );

                    checkStatus( partFreeDelClusters(pv, iSec, usrBuf) ); 
            }
            }
        }
    }

#endif /* FL_INCLUDE_FAT_MONITOR */

    return flOK;
}




/* --------------------------------------------------------------------------- *
 *                                                                             *
 *                   f l f f C o n t r o l                                     *
 *                                                                             * 
 *  Enable/disable/install FAT filters. See comments inside the routine for    * 
 *  the list of supported operations.                                          * 
 *                                                                             * 
 *  Parameters:                                                                * 
 *    handle         : TFFS handle                                          * 
 *      partNo             : partition # (0 .. FL_MAX_PARTS_PER_DISK)             * 
 *      state              : see list of supported operations below               * 
 *                                                                             * 
 *  Returns:                                                                   * 
 *      flOK on success, otherwise error code                                  * 
 *                                                                             * 
 * --------------------------------------------------------------------------- *
 *                                                                             *
 *  The following FAT monitor control requests are supported:                  *
 *                                                                             *
 *      state  : flStateNotInitialized                                         *
 *      partNo : [0 ... pd->parts-1]                                           *
 *      action : turn off FAT monitor on specified partition                   *
 *                                                                             *
 *      state  : flStateNotInitialized                                         *
 *      partNo : < 0                                                           *
 *      action : turn off FAT monitor on all partitions                        *
 *                                                                             *
 *      state  : flStateInitialized                                            *
 *      partNo : [0 ... pd->parts-1]                                           *
 *      action : if FAT monitor has been installed on specified partition,     *
 *               turn it on                                                    *
 *                                                                             *
 *      state  : flStateInitInProgress                                         *
 *      partNo : ignored                                                       *
 *      action : re-read partition table(s), and install FAT filters on all    *
 *               partitions                                                    *
 *                                                                             *
 * --------------------------------------------------------------------------- */

FLStatus  flffControl ( int      handle,
                        int      partNo, 
                        FLState  state )
{
    int        socNo, diskNo;
    FLffDisk * pd;
    int        i;
    FLStatus   status;

    /* if module hasn't been reset yet, do it now */

    if (resetDone == FALSE)
        (void) reset();

    /* break TFFS handle into socket# and disk#, and do sanity check */

    socNo  = H2S(handle);
    diskNo = H2D(handle);
 
    if ((socNo >= ((int) noOfSockets)) || (diskNo >= MAX_TL_PARTITIONS))
        return flBadDriveHandle;

    /* if disk structure hasn't been allocated yet, do it now */

    if (ffDisk[socNo][diskNo] == NULL)
        checkStatus( newDisk(handle) );

    pd = ffDisk[socNo][diskNo];

    /* abort if disk re-partitioning is in progress */

    if (pd->ffstate == flStateInitInProgress)
        return flDriveNotReady;

    /* read partition table(s) and install FAT filters is needed */

    if (pd->ffstate == flStateNotInitialized) {

        if (state == flStateNotInitialized)
          return flOK;

        checkStatus( parseDisk(handle) );
    }

    /* check 'partNo' arguement for sanity */
 
    if ((partNo >= pd->parts) || (partNo >= FL_MAX_PARTS_PER_DISK))
        return flBadDriveHandle;

    /* do requested operation */

    status = flBadParameter;

    switch (state) {

        case flStateInitInProgress: 

            /* read partition table(s), install FAT filters on all partitions */

        pd->ffstate = flStateNotInitialized; 
            status = parseDisk(handle);
            break;

        case flStateNotInitialized:         

            /* turn off FAT monitor */

        if (partNo < 0) {                      /* all partitions */

            for (i = 0; i < FL_MAX_PARTS_PER_DISK; i++) {

            if (pd->part[i] != NULL)
                    (pd->part[i])->ffEnabled = FALSE;
        }
        }
        else {                                 /* specified partition */

        if (pd->part[partNo] != NULL)
                (pd->part[partNo])->ffEnabled = FALSE;
        }
            status = flOK;
            break;

#ifdef FL_INCLUDE_FAT_MONITOR

        case flStateInitialized:            

            /* turn on FAT monitor */

        if ((pd->ffstate == flStateInitialized) && (partNo >= 0)) {

            if (pd->part[partNo] != NULL) {

                switch( (pd->part[partNo])->type ) {

                        case FAT12_PARTIT:
                    case FAT16_PARTIT:
                    case DOS4_PARTIT:
                        (pd->part[partNo])->ffEnabled = TRUE;
                            status = flOK;
                            break;

            case FL_RAW_PART:
                            DEBUG_PRINT(("Debug: can't ctrl non-existent partition.\r\n"));
                            break;

            default:
                            DEBUG_PRINT(("Debug: can't ctrl non-FAT12/16 partition.\r\n"));
                            break;
            }
        }
        }
            break;

#endif /* FL_INCLUDE_FAT_MONITOR */

    }  /* switch(state) */ 

    return status;
}




/* --------------------------------------------------------------------------- *
 *                                                                             *
 *                   f l f f I n f o                                           *
 *                                                                             * 
 *  Obtain complete partition info for specified disk.                         *
 *                                                                             * 
 *  Parameters:                                                                * 
 *    handle         : TFFS handle                                          * 
 *                                                                             * 
 *  Returns:                                                                   * 
 *      NULL if error, otherwise pointer to partitioning info                  * 
 *                                                                             * 
 * --------------------------------------------------------------------------- */

FLffDisk * flffInfo ( int  handle )
{
    int        socNo, diskNo;
    FLffDisk * pd;

    /* if module hasn't been reset yet, do it now */

    if (resetDone == FALSE)
        (void) reset();

    /* break TFFS handle into socket# and disk#, and do sanity check */

    socNo  = H2S(handle);
    diskNo = H2D(handle);
 
    if ((socNo >= ((int) noOfSockets)) || (diskNo >= MAX_TL_PARTITIONS))
        return NULL;

    /* if disk structure hasn't been allocated yet, do it now */

    if (ffDisk[socNo][diskNo] == NULL) {

        if( newDisk(handle) != flOK )
        return NULL;
    }

    pd = ffDisk[socNo][diskNo];

    /* read partition table(s) and install FAT filters is needed */

    if (pd->ffstate == flStateNotInitialized) {

        if( parseDisk(handle) != flOK )
            return NULL;
    }

    return pd;
}




#endif /* ABS_READ_WRITE && FL_READ_ONLY */