/*static char *SCCSID = "@(#)autochk.c  6.2 92/01/31";*/
#define INIT1 INIT1

/***    autochk.c - auto check disk support
 *
 *      Copyright Microsoft, 1988.
 *
 *      This module contains the routines which are needed to run CHKDSK
 *      on a volume at system initialization time.
 *
 *      HISTORY
 *          @90910 27Jul94  D.Azzarito - Allow /F:1, but not /F:3 during AC
 *          @88993 15Jul94  D.Azzarito - bring back old AC password (CHKDSK
 *                                       now handles improper use).
 *          @83390 5May1994 D.Azzarito - Added FORCE option to Autocheck
 *          @82381 19-Apr-94 by D.Azzarito - Fix AUTOCHECK for FAT MiniFSD
 *          @80712 14-Mar-94 by F. Miro - Enable autochk when boot drive is FAT
 *          19-Sep-89 by JR (John Rogers, JohnRo@Microsoft)
 *              - Added SCCSID.
 *              - Avoid warnings from Microsoft C 5.2 (on slash-slash comments).
 */

#define INCL_DOS
#define INCL_DOSERRORS                                               // @82381

#include <os2def.h>
#include <bse.h>
#include <memory.h>

#include <const.h>
#include <misc.h>
#include "chain.h"
#include "buf.h"
#include "dir.h"
#include "volume.h"
#include "storage.h"
#include "tables.h"
#include "frame.h"
#include <superb.h>
#include "boot.h"

#include "pr_fsini.h"
#include "pr_debug.h"


                /* BUGBUG - get this memory from near scratch space */
UCHAR scratch[2*SECSIZE];  /* working buffer for boot block & super block */


/* miscelaneous manifests and variables for auto CHKDSK handling */

#define CHK_STK_SIZE    0xf000          /* nice and BIG */

HMODULE hMod = 0;

pChkFN pfnChk = 0;
SEL ChkdskStk = 0;

char ChkName[] = "CHKDSK";      /* disk repair entry point in utility DLL */
char szChkLevel[] = "/F:2";     // @82381 Level of CHKDSK to use for AutoChk

/*
 * Apply CHKDSK to all of the volumes listed in the AutoCheckList and to
 * the boot volumes as needed.
 */
void near
AutoCheck(unsigned long chkmask, unsigned long forcemask)            // @83390
{
    register int drive;

    /*
     * this loop is based upon the maximum number of driver letters possible
     * in a system and has no relationship to VOLMNTMAX.  It should remain 26
     * until such time as OS/2 supports more than 26 drive letters then the
     * code which sets the mask in ourfsini.c must also be changed.
     */

    for (drive = 0 ; drive < 26 ; ++drive, chkmask >>= 1, forcemask >>=1) //@83390
    {
      if (chkmask & 1)
         CheckDirty(drive,(BOOL)forcemask & 1);   /* @83390 check to see if volume is dirty */
    }

    UnloadChkdsk(1);    /* free up system resources used by CHKDSK */
}


/*
 * CheckDirty(drive, force) - check if a volume needs to be repaired
 *
 *      This routine will check if a volume is HPFS and if CHKDSK needs to
 *      be run on the volume.  If required, it will invoke the CHKDSK routine
 *      in the HPFS utility DLL to repair the disk.
 */

void near
CheckDirty(int drive, BOOL force)                                   // @83390
{
   int err, action;
   unsigned infd;
   unsigned rdcnt;
   static char far * far chkArgs[] =
      { ChkName, 0, szChkLevel, "/aUtOcHeCk", 0 };                  // @88993
   struct SUPERB *psuperB;
   struct SpareB *pspareB;
   struct EXTENDED_BOOT *pBoot;
   char drvname[4];

   drvname[0] = (char)(drive + 'A');
   drvname[1] = ':';
   drvname[2] = 0;
   chkArgs[1] = drvname;

        /* open mode: read only, deny write, DASD */
   if ((err = DosOpen((PSZ)drvname, (PHFILE)&infd, (PUSHORT)&action,
                       0L, 0, 1, 0x8020, 0L)) != 0)
   {
#ifdef DEBUG
      dprintf("Can't open disk drive %Fs  Error=%d\n",
              (char far *)drvname, err);
#endif
      return;
   }

   rdcnt = 0;

   /* read in the boot sector @82381 */

   if (err = CheckDirtyRdSec(infd, 0L, (void far *)scratch,
                             1, (PUSHORT)&rdcnt) || rdcnt != SECSIZE)
   {
      DosClose(infd);
      return;
   }

    /* check for our signature in the boot sector */
   pBoot = (struct EXTENDED_BOOT *)scratch;
   if (memcmp(pBoot->Boot_System_ID, "HPFS    ", 8) != 0)
   {

        /* not an HPFS volume, ignore it! */
      DosClose(infd);
      return;
   }

   /* read in the Super & Spare Block @82381 */

   if (err = CheckDirtyRdSec(infd, (long)SEC_SUPERB, (void far *)scratch,
                             2, (PUSHORT)&rdcnt) || rdcnt != SECSIZE * 2)
   {
      DosClose(infd);
      return;
   }

   DosClose(infd);
   psuperB = (struct SUPERB *)scratch;
   pspareB = (struct SpareB *)(scratch + SECSIZE);

   if (force || (pspareB->bFlag &                                    // @83390
               (SPF_DIRT|SPF_SPARE|SPF_HFUSED|SPF_BADSEC|SPF_BADBM)) != 0
               || pspareB->sig1 != SPSIG1 || pspareB->sig2 != SPSIG2
               || psuperB->sig1 != SBSIG1 || psuperB->sig2 != SBSIG2
               || psuperB->bVersion < SBBASEV)
   {

        /*
         * This disk is dirty, or has a bad signature in the Super/Spare
         * Block.  We'll try to run our CHKDSK dll
         * to repair it before continuing the boot process.
         */

      if (LoadChkdsk() != 0)
         return;

//@90910 - Allow /F:1 always, but only allow /F:3 if FORMAT /L was used.
//#86635
      if ((pspareB->bFlag & SPF_FASTFMT) &&   /* fast formatted drive */
          (szChkLevel[3] == 0x33))            /* and /F:3 autocheck */
      {
         szChkLevel[3] = 0x32;  /* change CHKDSK level to /F:2 */
      }

      CallChkdsk(MAKEP(ChkdskStk, CHK_STK_SIZE), pfnChk,
                 4, chkArgs, (UCHAR far * far *)0);

      UnloadChkdsk(0);        /* free up module */
   }
}

/*
 * CheckDirtyRdSec - Sector Read worker for CheckDirty
 *
 *      This routine reads a sector for the CheckDirty routine.  Why a
 *      separate worker?  Because of the way the different mini FSD's work.
 *      FAT's FSD only understands "byte" mode (all sizes are specified in
 *      bytes).  HPFS's FSD supports byte and SECTOR mode (all sizes are
 *      specified in SECTORS).  Here's the rub - on HPFS partitions >2G,
 *      SECTOR mode is REQUIRED (byte mode returns an error).  We've got
 *      a problem, because we won't know how big the partition is until
 *      after we get to the SuperBlock.
 *
 *      This routine will try switching to SECTOR mode for the HPFS mini,
 *      but if it fails, we do the read in BYTE mode.
 *
 * INPUT:  unsigned  uHnd     - handle of drive to read from
 *         ULONG     ulSector - sector # to read (boot sector is 0)
 *         void far *pBuffer  - address of buffer to place sector(s)
 *         USHORT    usSecCount - count of sectors to read
 *         PUSHORT   pusBytesRead - total BYTES read
 *
 * OUTPUT: function retuns 0 if successful, otherwise error number.
 *         pusBytesRead will contain the count of bytes put into pBuffer.
 *
 * HISTORY:
 * --WHEN--      --WHO--     --WHY--
 * 19 Apr 1994   D.Azzarito  defect 82381 - wrote this function
 */

int near
CheckDirtyRdSec(unsigned uHnd,               // Handle to volume
                ULONG    ulSector,           // sector number to read
                void far *pBuffer,           // ptr to buffer for read data
                USHORT   usSecCount,         // sectors to read
                PUSHORT  pusBytesRead)       // count of BYTES read
{
   BOOL fSectorIO;              // Do we need sectorIO mode?
   ULONG ulPasswd;              // password for FSCTL
   ULONG ulPosition;            // new file postion from DosChgFilePtr
   USHORT usParmLengthInOut;    // count of parms sent to/from FSCTL
   USHORT usDataLengthInOut;    // count of bytes sent to/from FSCTL
   int err;                     // error return code

   ulPasswd = FSCP_PASSWD;      // secret password for SECIO access

/*
 * We'd like to be in SECTOR mode, but the FAT MiniFSD doesn't support it.
 * If the FSCtl fails, we're probably dealing with the FAT MiniFSD, so we
 * have to use BYTE mode.
 */

   if (err=DosFSCtl (NULL,               // data area
                     0,                  // max data length
                     &usDataLengthInOut, // data area in/out
                     (PVOID)&ulPasswd,   // parm list
                     sizeof(ulPasswd),   // max parm list len
                     &usParmLengthInOut, // size of parm list in/out
                     FSC_SECTORIO,       // function code
                     NULL,               // route name
                     uHnd,               // file handle
                     1,                  // route method
                     0L))                // reserved
      fSectorIO = FALSE;
   else
      fSectorIO = TRUE;

/*
 *  First we position the read pointer to the desired sector
 */

   if (!(err = DosChgFilePtr(uHnd,
                             fSectorIO ? ulSector : ulSector * SECSIZE,
                             0,
                             &ulPosition)))
   {
/*
 * Now we do the read (1st time through we're in BYTE mode, 2nd time
 * we should be in SECTOR mode)
 */

      err = DosRead(uHnd,
                    pBuffer,
                    fSectorIO ? usSecCount : usSecCount * SECSIZE,
                    pusBytesRead);

      if (fSectorIO)                   // If we're in sector IO mode,
         *pusBytesRead *= SECSIZE;     // cvt SECTORS read into BYTES
   }                                   // End if !err from DosChgFilePtr
   return(err);
}

/*
 * LoadChkdsk - load in the HPFS utility DLL
 *
 *      This routine gets the address of the CHKDSK routine in the file system
 *      utility .dll and allocates a stack for CHKDSK to use since the
 *      init time stack that we run on isn't nearly large enough.
 *
 *      NOTE: this routine must be called for each invokation of CHKDSK so
 *      the libraries initialized data will be re-read from disk.
 */

int near
LoadChkdsk()
{
    unsigned int err;
    char failName[128];

    if ((err = DosLoadModule(failName, sizeof failName, "UHPFS", &hMod))
                != 0) {
#ifdef DEBUG
        dprintf("Can't load utility .dll.  Error=%d\n", err);
#endif
        return err;
    }
    if ((err = DosGetProcAddr(hMod, ChkName, (PFN far *)&pfnChk)) != 0) {
#ifdef DEBUG
        dprintf("Can't get CHKDSK address.  Error=%d\n", err);
#endif
        pfnChk = 0;
        return err;
    }
    if ((err = DosAllocSeg(CHK_STK_SIZE, &ChkdskStk, 0)) != 0) {
#ifdef DEBUG
        dprintf("Can't get memory for CHKDSK stack.  Error=%d\n", err);
#endif
        pfnChk = 0;
        return err;
    }
    return 0;
}

/*
 * UnloadChkdsk - release the HPFS utility DLL
 *
 *      This routine frees up the utility module and the chkdsk stack.
 */

void near
UnloadChkdsk(int fAll)  /* fAll != 0, if free stack also */
{
    if (hMod != 0) {
        DosFreeModule(hMod);
        hMod = 0;
    }
    if (fAll && ChkdskStk != 0) {
        DosFreeSeg(ChkdskStk);
        ChkdskStk = 0;
    }
}
