/*static char *SCCSID = "@(#)mfs.c      6.14 92/03/08";*/
#define BSWAP1 BSWAP1

#define INCL_DOSERRORS
#include <os2def.h>
#include <bse.h>
#include <fsd.h>
#include <fsh.h>
#include <strat2.h>
#include "heap.h"
#include "api.h"
#include "chain.h"
#include "const.h"
#include "misc.h"
#include "buf.h"
#include "dir.h"
#include "storage.h"
#include "fnode.h"
#include "volume.h"
#include "tables.h"
#include "pb_ifs.h"
#include "pbmacros.h"
#include "superb.h"
#include "lw.h"
#include "frame.h"
#include "fsdata.h"
#include "pbmsg.h"
#include "find.h"
#include "log.h"                                                 // @76193
#include "pr_CACHE.h"
#include "pr_DEBUG.h"
#include "pr_DEL.h"
#include "pr_DSKIO.h"
#include "pr_FCN.h"
#include "pr_FSINI.h"
#include "pr_HEAP.h"
#include "pr_MISC.h"

/* There is a certain amount of disk space that is not reported
 * in the volume's space available.  This is the total number of
 * sectors on the volume / RESERVED_FRACTION.  However, no more than
 * MAX_RESERVED sectors can be reserved.
 */
#define RESERVED_FRACTION       ((ULONG)50)
#define MAX_RESERVED            4096           /* max # of sectors reserved */
#define CCH_MAX_HEAP_OBJ        4096           /* max size band in heap     */

#ifdef DISK_READ_TEST
void Test (PFRAME pfrm);
#endif

#ifdef MINIFSD
extern USHORT BootStage;
extern VOL_REOPEN_DATA far avrdList [];
extern USHORT cHVPB;
#endif

extern BOOL fDirtyMount;         // Allow dirty mounts @129233

/*
 ***    MFS - Mount File System
 *
 *      Handles all 4 operations of FS_Mount
 *
 *      MOUNT_MOUNT       (0)   Mount the filesystem
 *      MOUNT_VOL_REMOVED (1)   Volume has been removed
 *      MOUNT_RELEASE     (2)   Volume has been released
 *      MOUNT_ACCEPT      (3)   Mount for format
 *
 *      The code does the following:
 *
 *      MOUNT:
 *
 *          1) Use RdBuf to read in superblock
 *          2) Allocate VOLTAB entry
 *          3) Setup bit map
 *          4) Setup root directory in SBDIR
 *
 *      VOL_REMOVED:
 *
 *          1) Return (no error) - don't care about this case
 *
 *      RELEASE:
 *
 *          1) Find hVPB in aVolHVPB
 *          2) Flush & discard (all) buffers
 *          3) Cleanup LookAside items for this volume
 *          4) Release entire SBDIR tree
 *          5) Cleanup FindList items for this volume
 *          6) Cleanup VOLTAB entry
 *
 *      ACCEPT:
 *
 *          1) Obtain & initialize VolTab
 *          2) Leave 'Mounting' flag set
 *
 *      ENTRY:  hVPB    - hVPB from kernel
 *              fsOper  - Operation to perform
 *              vpfsi   - file sys independent part of VPB
 *              csecMax - number of sectors on volume (0 if unknown)
 *
 *      Returns:    0 if ok, error code otherwise.
 *
 *     HISTORY
 *  @76193  23-Nov-1993 by D.Azzarito - Change holds for LOG support
 *  @129233 10-Jul-1995 by D.Azzarito - change dirty mount switch from
 *                                      compile time to runtime
 */

STATIC BYTE cBitsInNibble [] = {
        0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4
};

BOOL fUseStrat2 = TRUE;         /* Cleared by OurFSIni/GetCmdLine for -n. */

INT FAR
MFS(SHANDLE hVPB,               /* handle of volume being mounted */
    USHORT  fsOper,             /* Mount Operation to perform */
    struct  vpfsi far *pvpfsi,  /* Pointer to file sys ind. part of VPB. */
                                /* (May be null pointer.) */
    ULONG csecMax,              /* sectors on volume (0 if unknown) */
    PFRAME pfrm)
{
    USHORT usRet = NO_ERROR;

    switch (fsOper) {
        case MOUNT_ACCEPT:      /* forced mount */
        case MOUNT_MOUNT: {     /* normal mount */
            PBUFNODE pbuf;          /* used for return parm from RdBuf */
            ULONG ulVolBits;
            INT inxVol;
            LSN lsnRoot, lsnBMap;
            PVOLTAB pvol = NULL;
            PVOID pBandMem = NULL;

#ifdef MINIFSD
            if (BootStage > 1) {
#endif
                if (pvpfsi != NULL) {   /* not special mount from mini-FSD */

                    /*
                     * Check for NULL vpi_vid.
                     *
                     * BUGBUG: is this a neccessary check?
                     * After a begin format the hvpb is nulled out so the
                     * FSH_FINDDUPHVPB won't find this handle.  It does this
                     * by nulling the vpi_vid field.  So this check is
                     * guaranteed to return an error for the
                     * MOUNT_ACCEPT case.
                     */
                    if (fsOper == MOUNT_MOUNT && pvpfsi->vpi_vid == 0L) {
                        return ERROR_INVALID_HANDLE;
                    }

                    {
                        SHANDLE hVPBTemp;

                        if (FSH_FINDDUPHVPB (hVPB, &hVPBTemp) == 0) {
#ifdef DEBUG
                            dprintf ("MFS: dup hVPB=0x%x\n", hVPB);
#endif
                            /* This volume already mounted */
                            return NO_ERROR;
                        }
                    }
                }

#ifdef MINIFSD
                pvpfsi->vpi_flags |= VPB_NONCONTIG_ALLOWED;

                avrdList [cHVPB].hVPB = hVPB;

                if (pvpfsi->vpi_pDCS != NULL) {

                   if ((avrdList[cHVPB].pDCS =
                       (P_DriverCaps)GHS(sizeof(DriverCaps),pfrm)) == NULL) {

                      return ERROR_NOT_ENOUGH_MEMORY;

                   } else {

                      memcpy(avrdList[cHVPB].pDCS,pvpfsi->vpi_pDCS,sizeof(DriverCaps));

                   }

                } else {

                   avrdList [cHVPB].pDCS = (P_DriverCaps)NULL;

                }

                if (pvpfsi->vpi_pVCS != NULL) {
                   if ((avrdList[cHVPB].pVCS =
                       (P_VolChars)GHS(sizeof(VolChars),pfrm)) == NULL) {

                      return ERROR_NOT_ENOUGH_MEMORY;

                   } else {

                      memcpy(avrdList[cHVPB].pVCS,pvpfsi->vpi_pVCS,sizeof(VolChars));

                   }

                } else {

                   avrdList[cHVPB].pVCS = (P_VolChars)NULL;

                }
                cHVPB++;

#endif

#ifdef DEBUG
                /* search entire volume table - even dirty mounts */
                if (FindAnyVol(hVPB) != NULL)
                    INTERR (__FILE__": Volume already mounted");
#endif /* DEBUG */

                /* NOTE:  Once we get here, we can't block until we have
                 *        stored our hVPB in aVolHVPB.  This prevents other
                 *        mount operations from using the same VolTab entry.
                 */

                /*  Find a slot in volume map to mount this guy with,
                 *  get a temp VolTab (with default 1 band), copy info
                 *  into temp VolTab and temporarily point our new slot
                 *  at temp VolTab.  We have to do this so we can read
                 *  in the super block.
                 *
                 *  Start at 1 for now so we can detect when someone
                 *  doesn't set the vol bits when calling dovolio.
                 *
                 */
                for (inxVol = 1 ; inxVol < VOLMNTMAX ; ++inxVol) {
                    if (aVolHVPB[inxVol] == 0)
                        goto haveSlot;
                }

                return ERROR_TOO_MANY_OPEN_FILES;
#ifdef MINIFSD
            } /* if (BootStage > 1) */
            else {
                inxVol = hVPB;
            }
#endif

haveSlot:
            aVolHVPB[inxVol] = hVPB;

            /* now we have reserved our slot - ok if we block now */

            /*
             * get temp VolTab (minimum) - This is just so that RdBuf will
             * work until we figure out how many band tables we have.
             */
            if ((pvol = GHS(sizeof(VOLTAB),pfrm)) == NULL) {
                usRet = ERROR_NOT_ENOUGH_MEMORY;
                VolPtr[inxVol] = NULL;
                aVolHVPB[inxVol] = 0;       /* forget the volume for now */
                return GetHardErr (usRet);
            }
            memset (pvol, 0, sizeof (VOLTAB));
            pvol->hVPB = hVPB;
            pvol->ulVolBits = ulVolBits = ((ULONG) inxVol) << VOLRSHIFT;

            /* Make sure this is null so don't try to use in fs_fsinfo */
            pvol->pbVolLabel = NULL;

            /* initialize serial num */
            pvol->ulSerial = 0;

            /* mark this VOLTAB mounting */
            pvol->bStatusFlags |= VS_MOUNTING;

            if (pvpfsi != NULL) {
                pvol->chDriveLetter = pvpfsi->vpi_drive + (BYTE)'A';

                if (fUseStrat2) {
                    /* fill in the fields for asyncio stuff */

                   pvpfsi->vpi_flags |= VPB_NONCONTIG_ALLOWED;
                   pvol->idLogDisk = pvpfsi->vpi_unit;

                   if (pvpfsi->vpi_pVCS != NULL) {

                     if (pvpfsi->vpi_pDCS != NULL) {
                       pvol->pfncStrategy2 =
                         (PFN)(ULONG)(((P_DriverCaps)
                               (pvpfsi->vpi_pDCS))->Strategy2);
                       pvol->csgMax =
                            ((P_VolChars)(pvpfsi->vpi_pVCS))->MaxSGList;

                       /* Use full Strategy2 capability
                        * if volume device supports SCB protocol
                        * else default to a quasi-Strategy1 type
                        * of operation
                        */

                       if ((((P_VolChars)(pvpfsi->vpi_pVCS))->VolDescriptor)
                            && VC_SCB) {
                          pvol->SCBcapable = TRUE;
                       } else {
#ifndef MINIFSD
//test DCF2
                          if ((pvpfsi->vpi_vid >> 16) == 0xdcf2)
                            pvol->VDU = TRUE;

                          fStrat1DDPresent = TRUE;
#endif
//test end.dcf2
                          pvol->SCBcapable = FALSE;
                          pvpfsi->vpi_flags &= ~VPB_NONCONTIG_ALLOWED;

                       }
                          } else {   /* pvpfsi->vpi_pDCS = NULL */
                             pvol->pfncStrategy2 = (PFN)NULL;
                             pvol->SCBcapable = FALSE;
                             pvol->csgMax = 0;
                             pvpfsi->vpi_flags &= ~VPB_NONCONTIG_ALLOWED;
#ifndef MINIFSD
//test DCF2
                           if ((pvpfsi->vpi_vid >> 16) == 0xdcf2)
                               pvol->VDU = TRUE;

                           fStrat1DDPresent = TRUE;
#endif
//test end.dcf2
                          }
                   } else {  /* pvpfsi->vpi_pVCS = NULL */
                      pvol->csgMax = 0;
                      pvol->pfncStrategy2 = (PFN)NULL;
                      pvol->SCBcapable = FALSE;
                      pvpfsi->vpi_flags &= ~VPB_NONCONTIG_ALLOWED;
#ifndef MINIFSD
//test DCF2
                      if ((pvpfsi->vpi_vid >> 16) == 0xdcf2)
                        pvol->VDU = TRUE;

                      fStrat1DDPresent = TRUE;
#endif
//test end.dcf2
                   }

#ifdef DEBUG
                    if (pvol->pfncStrategy2) {
                        dprintf (__FILE__": Strategy2 is supported\n");
                    }
#endif /* DEBUG */
                }
#ifdef DEBUG
                else {
                    dprintf ("MFS: ignoring possible strat2.\n");
                }

                dprintf (__FILE__": pvpfsi=%p, pDCS=%p, csgMax=%d\n",
                         pvpfsi, pvpfsi->vpi_pDCS, pvol->csgMax);
#endif /* DEBUG */
            }
#ifdef DEBUG
              else {
                /* so we don't print out "drive :" */
                pvol->chDriveLetter = '?';
            }
#endif /* DEBUG */

            VolPtr[inxVol] = pvol;
            pfrm->pvol = pvol;          /* copy here for RdBuf and friends */


            if (fsOper == MOUNT_ACCEPT) {
#ifdef DEBUG
                dprintf ("MFS: MOUNT_ACCEPT, inxVol=%d, pvol=%p, hVPB=0x%x\n",
                         inxVol, pvol, hVPB);
#endif
                /*
                 * There are no fields in the pvol that we
                 * are interested in, except the size in sectors (which is
                 * used by Read in a DASD opened file, e.g. by CHKDSK).
                 */
                pvol->cSectsOnVol = csecMax;

                /*
                 * Note that we have set the VS_MOUNTING status for this
                 * volume.  Leave this set as an indication that
                 * this volume was force mounted (i.e. MOUNT_ACCEPT).
                 * Later, when we are asked to release this volume,
                 * we check this bit.  We also use it to disallow anything but
                 * DASD opens.
                 *
                 * We'll also set the dirty flag for this volume, which will
                 * be used by FindPVol
                 *
                 */

                afVolDirty[inxVol] = TRUE;

                return (NO_ERROR);
            }

            /* This is a normal mount, so clear the dirty flag */

            afVolDirty[inxVol] = FALSE;

            /*
             * Read the superblock, compute the size of the new VOLTAB
             * and allocate it.  Copy info from the super block into
             * the VOLTAB.  Switch to the new VOLTAB.
             */
            {
                PSUPERB psb;

#if defined(DEBUG) || defined (ASSERT)
                /* set up volume info so VBS doesn't croak */
                pfrm->pvol->cSectsOnVol = SEC_SUPERB + 1;
#endif
                if ((psb = RdBuf(SEC_SUPERB,BP_TOSS,&pbuf,pfrm)) == NULL) {
                    goto mfs120;                        /* got a hard error */
                }
#ifdef DEBUG
                dprintf ("%s: MOUNT_MOUNT, drive=%c:, vol=%d, VolBits=0x%lx, hVPB=0x%x, ",
                         __FILE__, pvol->chDriveLetter, inxVol,
                         pvol->ulVolBits, hVPB);

                /* print out the version and functional version */
                dprintf ("ver=%d, func ver=%d\n",
                         (USHORT) psb->bVersion, (USHORT) psb->bFuncVersion);
#endif

//#114986
#ifndef MINIFSD

                if (pbuf->fFlag & BFL_LZREAD) {
                  if (LazyRead (pbuf, pfrm)) {
                    usRet = GetHardErr(ERROR_READ_FAULT);
                    goto mfs120;
                  }
                }
#endif

                /*
                 * At this point we have a buffer for this volume.  If we fail
                 * to mount this volume, we have to flush & discard all buffers,
                 * so we don't have any buffers belonging to unmounted volumes.
                 */

                if (psb->sig1 != SBSIG1 || psb->sig2 != SBSIG2) {

                    /*
                     * We need to accept the mount request but refuse to
                     * allow access to the volume except for Chkdsk and Format.
                     * In the future, save the reason for not validly mounting
                     * in the volume table.  When an app does a path based
                     * operation on this volume (other than a dasd open) we can
                     * generate a pop-up indicating the correct reason for
                     * disallowing the operation.
                     */

                    usRet = ERROR_INVALID_HANDLE;  /* any non-zero error code */
                    goto mfs290;
                }

                /*
                 * Check to make sure that the version numbers are correct.
                 */
                if (psb->bFuncVersion > SBBASEFV) {

                    /*
                     * It is not okay to touch the volume, mount the volume as
                     * a dirty volume, only allowing DASD opens.  If something
                     * else comes in, do a pop-up.  Remember the reason for the
                     * dirty mount in the pvol.
                     */
#ifdef DEBUG
                    dprintf ("MFS:psb->bFuncVersion > SBBASEFV (%d)\n",
                             SBBASEFV);
#endif
                    goto mfs290;
                }
                if (psb->bVersion < SBBASEV) {

                    /*
                     * Do the same as above except use a different reason code
                     * in the pvol.
                     */
#ifdef DEBUG
                    dprintf ("MFS:psb->bFuncVersion < SBBASEV (%d)\n", SBBASEV);
#endif

#ifdef TEMPORARY
                    {
                        PCHAR pcWrongVersion =
                            "Reformat with new HPFS format\n";

                        MAYBLOCK (FSH_CRITERROR (30, pcWrongVersion,
                                                 0, NULL, CE_ALLFAIL));
                    }
#endif
                    goto mfs290;
                }

                /* From now on we know we recognize the volume.  If we detect a
                 * problem, we will shift to a MOUNT_ACCEPT.  This will allow
                 * ChkDsk to try to fix the volume, but normal operations won't
                 * find the volume.
                 */

                /*
                 * copy info from super block into static VOLTAB just in case
                 * we block.
                 */

                pvol->cSectsOnVol = psb->culSectsOnVol & ~(ULONG)SPBMASK;
                if (pvol->cSectsOnVol > SECMASK) {
                    goto mfs290;                        /* too many sectors */
                }
                pvol->csecDirBlkBand = psb->clsnDirBlkBand;
                pvol->bndDirBand.lsnBase = psb->lsnFirstDirBlk;
                pvol->bndDirBand.scpMap.lsn = psb->lsnDirBlkMap | ulVolBits;

                pvol->bndDirBand.cbMapLen =
                    (USHORT)(psb->clsnDirBlkBand + 28)/32;
                lsnRoot = psb->lsnRootFNode;
                lsnBMap = psb->rspBitMapIndBlk.lsnMain;
            } /* scope of psb */

            /*  Read in Spares block and get data from it */

            {   PSPAREB pspb;

                if ((pspb = RdBuf((ULONG)SEC_SPAREB, BP_TOSS,
                                      &pbuf, pfrm)) == NULL) {
                    goto mfs290;                    /* got a hard error */
                }

//#114986
#ifndef MINIFSD
                if (pbuf->fFlag & BFL_LZREAD) {
                  if (LazyRead (pbuf, pfrm)) {
                    usRet = GetHardErr(ERROR_READ_FAULT);
                    goto mfs290;
                  }
                }
#endif

                /*
                 * Get everything we need out of the spares block
                 * since it may go away if we block.  Be especially
                 * careful when we enter the hot fix initialization
                 * section.  It may cause us to loose the pspb!
                 */
                pvol->cCodePage = (USHORT) pspb->culCP;
                /* pvol->paiCodePage = NULL;        already zeroed */

                /*
                 * Copy spares block flags (e.g. volume dirty bit) into volume
                 * table, for use by everybody.
                 */
                pvol->bSpareFlags = pspb->bFlag;

                /*
                 * If disk is really dirty, remember that fact.  We need to
                 * know this so we don't clear the dirty bit on disk unless we
                 * dirtied it during this mount.
                 */
                if (pspb->bFlag & SPF_DIRT) {
                    pvol->bStatusFlags |= VS_WAS_DIRTY;
                }

#ifdef DEBUG
                if (pspb->cdbSpares > 0xFFFF) {
                    dprintf (__FILE__": cdbSpares > 64 KB (0x%lx)\n",
                             pspb->cdbSpares);
                    goto mfs290;
                }
#endif
                pvol->cSpareDirblks = (UINT) pspb->cdbSpares;

#ifdef DEBUG
                if (pspb->culMaxHotFixes * (ULONG) sizeof (LSN) > 0xFFFF) {
                    dprintf (__FILE__": culMaxHotFixes too big (0x%lx)\n",
                             pspb->culMaxHotFixes);
                    goto mfs290;
                }
#endif

                /* Handle dirty volume. */
                if (pspb->bFlag & SPF_DIRT) {
#ifdef DEBUG
                    dprintf(__FILE__": volume is dirty on MOUNT_MOUNT.\n");
#endif

#ifndef MINIFSD
                    IncrHoldsBUF(pbuf);  // @76193
                    /* Warn user that volume is dirty. */
                    DirtyMountMessage(pvol,pfrm);
                    DecrHoldsBUF(pbuf);  // @76193
                    if (!fDirtyMount)    // No dirty mounts allowed? @129233
                    /* Force mount this dirty volume. */
                      goto mfs290;
                    /* Continue (allow regular mount of dirty volume). */
#else /* MINIFSD */
                    /* In MINIFSD, silently mount dirty volume anyway. */
#endif /* MINIFSD */
                }

                /* set up the hot fix lists */
                IncrHoldsBUF(pbuf);  // @76193
                if ((pvol->pHotFixArray =
                     GHS((USHORT) pspb->culMaxHotFixes * sizeof (LSN),pfrm))
                                                                    == NULL) {
                    usRet = ERROR_NOT_ENOUGH_MEMORY;
                    DecrHoldsBUF(pbuf);  // @76193
                    goto mfs290;
                }

                if ((pvol->pHotFixNew =
                     GHS((USHORT) pspb->culMaxHotFixes * sizeof (LSN),pfrm))
                                                                    == NULL) {
                    usRet = ERROR_NOT_ENOUGH_MEMORY;
                    DecrHoldsBUF(pbuf);  // @76193
                    goto mfs280;
                }

                DecrHoldsBUF(pbuf);  // @76193
#ifdef DEBUG
                if (pspb->culHotFixes > 0xFFFF) {
                    dprintf (__FILE__": culHotFixes > USHORT (0x%lx)\n",
                             pspb->culHotFixes);
                    goto mfs270;
                }

                if (pspb->culMaxHotFixes > 0xFFFF) {
                    dprintf (__FILE__": culMaxHotFixes > USHORT (0x%lx)\n",
                             pspb->culMaxHotFixes);
                    goto mfs270;
                }
#endif

                pvol->cHotFixInUse = (UINT) pspb->culHotFixes;

                if ((pvol->cHotFixMax = (UINT) pspb->culMaxHotFixes) != 0) {

                    UINT cHotFix;
                    USHORT cRemain = 0;
                    LSN    lsnNext = pspb->lsnHotFix;
                    PLSN   pNextHotFix;
                    PLSN   plsnHotFix;
#ifdef DEBUG
                    dprintf ("%s: Initializing HotFix list: Max:%d, InUse:%d\n",
                             __FILE__, pvol->cHotFixMax, pvol->cHotFixInUse);
#endif
                    /* Set up array of 'old sectors' */
                    cHotFix = pvol->cHotFixMax;
                    plsnHotFix = pvol->pHotFixArray;
                    do {
                        if (!RHD (&cRemain, &lsnNext,
                                  &pNextHotFix, plsnHotFix, pfrm)) {
                            PANIC(PBMSG_MFS_RHD_ERROR);
                        }

                        if ( *plsnHotFix == 0 )
                            *plsnHotFix = LSN_BOGUS;

                        plsnHotFix += 1;

                    } while (--cHotFix);

                    /* Set up array of 'new sectors' */
                    cHotFix = pvol->cHotFixMax;
                    plsnHotFix = pvol->pHotFixNew;
                    do {
                        if (!RHD (&cRemain, &lsnNext,
                                  &pNextHotFix, plsnHotFix++, pfrm)) {
                            PANIC(PBMSG_MFS_RHD_ERROR);
                        }
                    } while (--cHotFix);
                }   /* we have HotFixes to set up */

            }   /* end of scope of pspb */

            /*
             * Process bitmaps and DIRBLKs...
             */
            {
                UINT     cBands;
                PBANDTAB pbnd;
                PLSN     plsn;
                PSECTOR  psecBitMap;    /* used for return param from RdBuf */
                UINT     uiBand;
                ULONG    ulSpace;

                /* compute number of bitmap bands */

                cBands = (UINT)((pvol->cSectsOnVol + BANDSIZE-1) >> BANDSHIFT);

                if (cBands < 1) {
#ifdef DEBUG
                    dprintf (__FILE__": cBands invalid (%ld)\n", cBands);
#endif
                    goto mfs270;
                }
                if ((pvol = GHS (sizeof(VOLTAB) +
                                  (cBands-1)*sizeof(PBANDTAB),pfrm)) == NULL) {
                    usRet = ERROR_NOT_ENOUGH_MEMORY;
                    goto mfs270;
                }

                memset (pvol, 0, sizeof(VOLTAB) + (cBands-1)*sizeof(PBANDTAB));

                *pvol = *VolPtr[inxVol];              /* copy all fields */
                RetHeap (VolPtr[inxVol]);             /* return temp VolTab */
                pfrm->pvol = VolPtr[inxVol] = pvol;   /* switch to new VOLTAB */

                pvol->cBands = cBands;

                /* allocate heap space to hold the BANDTABs */

                ulSpace = 0L;
                plsn = NULL;

                for (uiBand = 0 ; uiBand < cBands ; ++uiBand) {

                    if( (uiBand % C_BANDS_IN_CBLOCK) == 0 ) {

                        /*
                         * Okay, Read the next 4 sectors in the bitmap
                         * indirection table.
                         */
                        if( uiBand != 0 ) {
                            /*
                             * If not 1st time through loop, release
                             * the old BitMap IB cache block
                             */
                            DecrHoldsBUF( pbuf );  // @76193
                        }

                        if( (plsn = RdBuf(lsnBMap,
                                          BP_NOOPINION,
                                          &pbuf,pfrm)) == NULL ) {
                            /* got a hard error */
                            pvol->cBands = uiBand;
                            goto mfs260;
                        }

                        IncrHoldsBUF (pbuf);  // @76193

                        /*
                         * Bump lsnBMap to point to the next sector in
                         * bitmap indirection list run
                         */
                        lsnBMap += SPB;
                    }

                    if( (uiBand % C_BANDS_IN_BUFFER) == 0 ) {
                        /*
                         * Allocate space for this set of bandtabs
                         */
                        pbnd = AllocBandTabBuff(MIN(C_BANDS_IN_BUFFER,
                                                cBands - uiBand) *
                                                sizeof(BANDTAB),pfrm);

                        if( pbnd == NULL ) {
                            DecrHoldsBUF( pbuf );  // @76193
                            pvol->cBands = uiBand;
                            goto mfs260;
                        }

                    memset (pbnd, 0, (MIN(C_BANDS_IN_BUFFER,cBands - uiBand))
                            * sizeof(BANDTAB));
                    }

                    pvol->apbndList[uiBand] = pbnd;    // save in ptr array

                    pbnd->lsnBase = (ULONG)uiBand << BANDSHIFT;
                    pbnd->scpMap.lsn = *plsn | ulVolBits;

                    /* read in the bitmap and set hint pointer to it */
                    {
                        PBUFNODE pbufBitMap;    /* used for return from RdBuf */
                        if ((psecBitMap = RdBuf(*plsn++, BP_KEEP,
                                                &pbufBitMap,pfrm)) == NULL) {
                            /*
                             * Got a hard error, release the buffer
                             * and set pvol->cBands to the total
                             * number of bands successfully allocated
                             * so we can free them.
                             */
                            DecrHoldsBUF(pbuf);  // @76193
                            pvol->cBands = uiBand + 1;
                            goto mfs260;      // got a hard error
                        }

                        /* BUGBUG - MarkH - pbufBitMap->fFlag |= BFL_BITMAP;
                         * I don't really know why the following bit is set.
                         * PIA sets it in this function & in LSDBM, but it
                         * is never checked.
                         */
//                        pbufBitMap->fFlag |= BFL_BITMAP;

                        pbnd->scpMap.pbufHint = pbufBitMap;
                        pbnd->cFilesInBand = 0;
                    } /* scope of pbufBitMap */

                    pbnd->cbMapLen = (USHORT)MIN((ULONG)(SECSIZE * SPB),
                                                 ((pvol->cSectsOnVol -
                                                 pbnd->lsnBase) + 7) >> 3);

                    CountBit((PBYTE)psecBitMap,pbnd->cbMapLen,pbnd);

                    ulSpace += pbnd->csecFree;      /* count total free space */

                    /* bump the band table pointer to the next one */
                    pbnd++;
                }

                /* release bitmap indirect block's buffer */
                DecrHoldsBUF (pbuf);  // @76193

                /* compute space allocation limit. */
                /* NOTE: this value is allowed to go negative */

                pvol->csecSpace = ulSpace -
                                  MIN (ULongDivideFar (pvol->cSectsOnVol,
                                                       RESERVED_FRACTION),
                                       MAX_RESERVED);

                /* Read the BANDTAB for the special DIRBLK band */

                if ((psecBitMap = RdBuf(pvol->bndDirBand.scpMap.lsn,
                                        BP_KEEP, &pbuf, pfrm)) == NULL) {
                    goto mfs260;                    /* got a hard error */
                }

//                pbuf->fFlag |= BFL_BITMAP;

                pvol->bndDirBand.scpMap.pbufHint = pbuf; /* save hint pointer */

                /*
                 * Sum the available bits in the map.
                 */
                CountBit ((PBYTE) psecBitMap, pvol->bndDirBand.cbMapLen,
                                          &pvol->bndDirBand);
            }

            /*
             * Okay, now set up the root directory in the SUBDIR structure.
             */
            {
                PSUBDIR psbd = NULL;

                /* initialize the currently unimportant fields to 0 */
                if ((psbd = GHS (sizeof (SUBDIR),pfrm)) == NULL) {
                    usRet = ERROR_NOT_ENOUGH_MEMORY;
                    goto mfs260;
                }

                /* null out all the fields */
                memset (psbd, 0, sizeof (SUBDIR));

                /* allocate enough name space for the root */
                if ((psbd->dh.pspName =
                     GHS (FIELD_SIZE (PASCALSTRING, c) + sizeof (BYTE),pfrm)
                    ) == NULL) {
                    RetHeap (psbd);
                    usRet = ERROR_NOT_ENOUGH_MEMORY;
                    goto mfs260;
                }

                pvol->psbdRoot = psbd;      /* save root SUBDIR in VOLTAB */

                psbd->dcpSib.pFwd = psbd;
                psbd->dcpSib.pBak = psbd;
                psbd->psbdKids = NULL;
                psbd->psbdParent = NULL;
                psbd->dcpNotifyChain.pFwd = NULL;
                psbd->dcpNotifyChain.pBak = NULL;
                psbd->scpTopDirBlkHint.pbufHint = NULL;

                psbd->lsnFNode = lsnRoot;

                {
                    PSECTOR psec;       /* used for return param from RdBuf */
                    if ((psec = RdBuf(lsnRoot,BP_KEEP,&pbuf,pfrm)) ==
                         NULL) {
                        goto mfs250;                /* got a hard error */
                    }

//#114986
#ifndef MINIFSD
                    if (pbuf->fFlag & BFL_LZREAD) {
                      if (LazyRead (pbuf, pfrm)) {
                        usRet = GetHardErr(ERROR_READ_FAULT);
                        goto mfs250;
                      }
                    }
#endif

                    psbd->scpTopDirBlkHint.lsn =
                        ((PFNODE) psec) -> fst.a.aall [0].lsnPhys | ulVolBits;
                }

                psbd->dh.pspName->c = 2;
                psbd->dh.pspName->b [0] = '.';

                /*
                 * Compute the checksum on the count char as well as the
                 * characters themselves.  Then assign into the root subdir
                 * struct.
                 */
                psbd->cksName = CCS ((PBYTE) psbd->dh.pspName,
                                        (ULONG) psbd->dh.pspName->c);

                /*
                 * just in case someone gets the brilliant idea of calling
                 * chksbd...
                 */
                pfrm->psbd = psbd;

                /*
                 *      'Mount' the code pages on this volume.  (Also handles
                 *      saving required code pages on the volume.)
                 */

                if ((usRet = MountCP (pvol,pfrm)) != NO_ERROR) {
                    goto mfs250;
                }

                /* Volume is completely mounted now - so remove mounting flag */
                pvol->bStatusFlags &= ~VS_MOUNTING;

#ifdef DEBUG
                ChkHashChain ();
#endif

#ifdef DISK_READ_TEST
                Test (pfrm);
#endif

                return (NO_ERROR);

                /* Some kind of error was detected before we recognized the
                 * volume, clean up and exit with error
                 */

                /* flush and discard all buffers
                 *
                 *      (we don't have any easy way to clean up
                 *      buffers for one volume)
                 */
/* mfs110: */
                FlushVol(pvol, TRUE, pfrm);

mfs120:
                RetHeap(pvol);

                /* usRet = error code, or else HardErr */
                VolPtr[inxVol] = NULL;
                aVolHVPB[inxVol] = 0;       /* forget the volume for now */
                return GetHardErr (usRet);

                /* Some kind of error was detected after we recognized the vol,
                 * clean up the extra stuff and convert to a dirty/format mount
                 * (in the upper end of the VolTable).
                 */

mfs250:
                RetHeap (psbd->dh.pspName);
                RetHeap (psbd);
            }

mfs260:
            /*
             * Step through the band table list and call FreeMFSBuff
             * on the first address of each memory buffer
             */
            {
                UINT uiBand;

                for (uiBand = 0 ; uiBand < pvol->cBands ;
                                  uiBand += C_BANDS_IN_BUFFER ) {
                    FreeBandTabBuff(pvol->apbndList[uiBand]);
                }
            }

mfs270:
            RetHeap (pvol->pHotFixNew);

mfs280:
            RetHeap (pvol->pHotFixArray);

mfs290:     /* now convert to a DASD-only mount (dirty volume/error) */

            /* flush and discard all buffers */
            FlushVol(pvol, TRUE, pfrm);

            /* clean up slot we reserved earlier */
            VolPtr[inxVol] = NULL;
            aVolHVPB[inxVol] = 0;       /* forget the volume for now */

#ifdef DEBUG
            dprintf ("MFS: MOUNT_MOUNT releasing ivol=%d, pvol=%p, hVPB=0x%x\n",
                     inxVol, pvol, hVPB);
#endif

#ifdef OLD
            /* zero out *pvol (we don't care if *pvol contains any pbandtabs */
            memset (pvol, 0, sizeof (VOLTAB));
#else /* !OLD */
            RetHeap (pvol);
#endif  /* !OLD */

            /*
             * Modify volume ID in volume parameter block, so this volume will
             * be unmounted once it's cleaned up.
             */
            if (pvpfsi != (void far *)0) {
                pvpfsi->vpi_vid = DAMAGED_ID;
            }

#ifdef DEBUG
            dprintf ("MFS: calling MFS again to force mount\n");
#endif
#ifdef MINIFSD
            /*
             *  Since the recursive call to MFS will add the
             *  HVPB to the remount list (again), we will remove it.
             *  The easy way to do this is to decrement the count
             *  of HVPBs.
             */
            cHVPB -= 1;
#endif
            usRet = MFS (hVPB, MOUNT_ACCEPT, pvpfsi, csecMax, pfrm);
            return (usRet);

#ifdef OLD
            for (inxVol = 1; inxVol < VOLMNTMAX; ++inxVol) {
                if (aVolHVPB [inxVol] == 0) {
                    /* reserve new slot */
                    aVolHVPB [inxVol]  = hVPB;
                    VolPtr [inxVol]    = pvol;
                    afVolDirty[inxVol] = TRUE;

                    /* set up new VolBits and hVPB */
                    pvol->ulVolBits = ((ULONG) inxVol) << VOLRSHIFT;
                    pvol->hVPB = hVPB;

                    /* mark this VOLTAB mounting */
                    pvol->bStatusFlags |= VS_MOUNTING;
#ifdef DEBUG
                    dprintf ("MFS: %s, vol=%d, VolBits=0x%lx, hVPB=0x%x\n",
                             "MOUNT_MOUNT dirty", inxVol, pvol->ulVolBits,
                             hVPB);
#endif
                    return (NO_ERROR);
                }
            }

            /* don't have available (special) slot in volume table */
            RetHeap (pvol);
            return (ERROR_TOO_MANY_OPEN_FILES);
#endif
        }

        case MOUNT_VOL_REMOVED:
            /* don't care that a volume was removed */
            return NO_ERROR;

        case MOUNT_RELEASE: {
            USHORT ivol;
            PVOLTAB pvol;


            /* mfs400: */

            /* scan hVPB table for this hVPB */
            for (ivol=0; ivol < VOLMNTMAX; ivol++) {
                if (aVolHVPB [ivol] == hVPB) {
                    break;
                }
            }

            /* if we did not find it, then return error */
            if (ivol == VOLMNTMAX) {
                return ERROR_INVALID_HANDLE;
            }

            /* ivol = index in VolPtr for this hVPB */
            pvol = VolPtr [ivol];

#ifdef DEBUG
            dprintf ("MFS: MOUNT_RELEASE, ivol=%d, pvol=%p, hVPB=0x%x\n",
                     ivol,
                     pvol,
                     hVPB);
#endif

            /* flush and discard all buffers */
            FlushVol(pvol, TRUE, pfrm);

            /*
             * If this volume was not a force mount then do the rest
             * of the stuff.  If it was force mounted, then none of the
             * fields in the volume table entry are valid.
             */
            if ((pvol->bStatusFlags & VS_MOUNTING) == 0) {

                /* remove lookaside items for this volume */
                {
                    USHORT i;
                    PDIRLOOK pdl = (PDIRLOOK) dcpDLHead.pFwd;
                    ULONG VolBits = pvol->ulVolBits;

                    for (i=0; i < DLCNT; i++) {
                        if (pdl->ulVolBits == VolBits) {
                            /* matches the volume bits */

                            /* destroy the checksum */
                            pdl->cks = 0;

                            /* return the name space and set pointer to NULL */
                            if (pdl->pcpName == NULL) {
                                RetHeap (pdl->pcpName);
                                pdl->pcpName = NULL;
                                if (OFFSETOF(pdl->pcpName) != 0)
                                  {
                                  INTERR (PBMSG_RDHF_DOVOLIO);
                                  }
                            }
                        }
                        /* link to the next lookaside entry */
                        pdl = pdl->dcpLinks.pFwd;
                    }
                }

                /* take out all SBDIRs for this volume */

                /* I structured this after the 386 MASM code (trying to explain
                 * all the 'goto's).  I didn't like jumping into while loops.
                 * MarkH - 08-Dec-1988
                 */

                /* we don't want to recurse and have an arbitrarily deep stack,
                 * so we'll just traverse to a bottom, delete the bottom most
                 * group there, then start over at the top as necessary.
                 */
                {
                    PSUBDIR psbdParent;
                    PSUBDIR psbdVictim;

                    psbdVictim = pvol->psbdRoot;
            mfs420:
                    if (psbdVictim->cSubDirs == 0) {
                        goto mfs425;
                    }

                    psbdParent = psbdVictim;
            mfs422:
                    psbdVictim = psbdParent->psbdKids;
                    goto mfs420;

                    /* The SBDIR we're to purge has no descendants, so we can
                     * purge it now.  If we're the subroot guy we were called
                     * with we're done, else we start over again.
                     */
            mfs425:
                    if (psbdVictim == pvol->psbdRoot) {
                        goto mfs430;
                    }

                    DelSBDIR (psbdVictim, psbdParent,pfrm);

                    if (psbdParent->cSubDirs != 0) {
                        goto mfs422;
                    }

                    psbdVictim = pvol->psbdRoot;
                    goto mfs420;

                    /* Have only the root SBDIR left.  Release it. */
            mfs430:
                    RetHeap (psbdVictim->dh.pspName);
                    RetHeap (psbdVictim);
                    pvol->psbdRoot = NULL;
                }

                /* Now we remove any FIND records that are outstanding */
                {
                    PFWORK pfw = (PFWORK) dcpFindListHead.pFwd;
                    PFWORK pfwTmp;
                    SHANDLE hVPB = pvol->hVPB;

                    while (pfw != (PFWORK) dcpFindListHead.pFwd) {
                        if (pfw->cpSearchPath.hVPB == hVPB) {
                            /* found a match */

                            /* remember this entry */
                            pfwTmp = pfw;

                            /* find next entry */
                            pfw = pfw->dcpWorkList.pFwd;

                            /* unlink from FindList */
                            REMOVE_FWORK (pfwTmp);

                            if (pfwTmp->fFindDone == FALSE &&
                                pfwTmp->pspLastName != NULL)
                                /* release last name */
                                RetHeap (pfwTmp->pspLastName);

                            /* return Find guy to heap */
                            RetHeap (pfwTmp);
                        }
                        else
                            /* advance to next entry */
                            pfw = pfw->dcpWorkList.pFwd;
                    }
                } /* remove find records... */

#ifndef MINIFSD
                /* If we marked disk as dirty, then mark it as clean again. */
                if ( (pvol->bSpareFlags & SPF_DIRT) &&
                     !(pvol->bStatusFlags & VS_WAS_DIRTY) ) {

                    /*
                     * Clear the volume dirty bit by updating the spares block.
                     * (Block until it's rewritten.)
                     */
                    ClearSparesFlag(pvol, SPF_DIRT, pfrm);

                    /*
                     * Flush and discard all buffers again, to get rid of spares
                     * block buffer we just created.  BUGBUG: Is there a better
                     * way to do this, or avoid doing this?  If we don't, we can
                     * get errors when VBS calls ASSERT_BUF.
                     */
                    FlushVol(pvol, TRUE, pfrm);

                } /* we marked disk as dirty. */

#endif /* MINIFSD */

            } /* not force mounted. */

            /* Clear the VolTab entry */
            RetVolTab (VolPtr [ivol]);
            aVolHVPB [ivol] = 0;
            VolPtr [ivol] = NULL;

            return NO_ERROR;
        }
    }
}

/*
 *** CountBit - Count BitMap Bits
 *
 *      CountBit is called while mounting a file system.  It setups the
 *      offset past the initial zeros in the map, and it counts the number
 *      of one bits in the area.
 *
 *      This info is placed into the BandTab record
 *
 *      ENTRY   pbBitMap = address of start of map bytes
 *              cusBytes = # of bytes to count
 *              pbtb = bandtab address
 *
 *      EXIT
 *
 */

void far
CountBit (pbBitMap, cusBytes, pbtb)
PBYTE pbBitMap;
USHORT cusBytes;
PBANDTAB pbtb;
{
    PBYTE pb;
    ULONG culBits;

    /*
     * Sum the available bits in this map.
     */

    /* while there are zero bytes keep looking */
    pb = pbBitMap;
    while ((cusBytes != 0) && (*pb == 0)) {
            cusBytes--;
            pb++;
    }

    /* either pb points to non-zero or cusBytes is 0 */

    pbtb->ibHighWaterOffset = pb - pbBitMap;

    /*
     * Now count the 1's (non-allocated sectors) in the remainder
     * of the band.
     */
    culBits = 0;
    while (cusBytes--) {
            culBits += cBitsInNibble [*pb >> 4];
            culBits += cBitsInNibble [*(pb++) & 0x0f];
    }
    pbtb->csecFree = (USHORT)culBits;
}


#ifdef DISK_READ_TEST

void
Test (PFRAME pfrm)
{
        ULONG ul;
        PBYTE pb;
        USHORT usRet, cSec = 1;

        if ((pb = GHS(SECSIZE,pfrm)) == NULL)
            return ;

        for (ul = 0; ul < sb.culSectsOnVol; ul++) {

                usRet = DoVolIO(DVIO_OPREAD, ul, &cSec, pb, pfrm);

                if ((usRet != NO_ERROR) || (cSec != 1)) {
                        dprintf ("Test: bad read, sec=0x%lx, ret=%d, cSec=%d\n",
                                  ul, usRet, cSec);
                        cSec = 1;
                        Int3 ();
                }
        }
        RetHeap (pb);
}
#endif


/*
 ** PVOID AllocBandTabBuff( UINT cb )
 *
 * Allocate a memory buffer for MFS to use for BandTab's
 *
 * ENTRY
 *     cb == number of bytes to allocate
 *
 * RETURNS
 *    address of allocated memory, or NULL if error occured
 *
 */
PVOID AllocBandTabBuff(UINT cb, PFRAME pfrm)
{
    if( cb > CCH_MAX_HEAP_OBJ ) {
      USHORT sel;
      USHORT usRet;

      MAYBLOCK (usRet = FSH_SEGALLOC (SA_FRING0, (ULONG) cb, &sel));

      if( usRet != NO_ERROR ) {
          return NULL;
      }


      return MAKEP( sel, 0 );
    } else
      return GHS( cb,pfrm );
}

/*
 ** void FreeBandTabBuff( PVOID pv )
 *
 * Free a BandTab buffer allocated with AllocBandTabBuff.
 *
 * ENTRY
 *     pv == starting address of buffer
 *
 * EXIT
 *     memory pointed to by pv is freed.
 *
 * NOTES
 *     pv MUST point to the BEGINNING of the memory area to be freed.
 *
 */
void FreeBandTabBuff( PVOID pv ) {

    if( OFFSETOF( pv ) == 0 ) {
      /*
       * Must have been SEGALLOCed since GHS segments always
       * start with a heap header
       */
      FSH_SEGFREE( SELECTOROF( pv ) );

    } else {

      /* heap object */
      RetHeap( pv );
    }


    return;
}
