/*static char *SCCSID = "@(#)main.c     6.4 91/11/06";*/
/***    MAIN.C - Top level routine for Pinball CHKDSK.
 *
 *      CHKDSK
 *      Gregory A. Jones
 *      Copyright 1988 Microsoft Corporation
 *
 *      Modification history:
 *      G.A. Jones      05/23/88        Original for Pinball 1.0.
 *      P.A. Williams   05/28/89        Set SPF_DIRT flag during check and
 *                                      reset after for >= level 1 check.
 *                                      PTR B783764
 *      P.A. Williams   07/11/89        Added redetermine media call to dismount
 *                                      volume.
 *      56340   11/05/92 BJH    Math error computing the amount of free space.
 *                              Error in building the filename to write out OS2BOOT.
 * @d01 73790   93SEP03   GY    <Support V4FORMAT.  GHASSAN@AUSVM1>
 *     @83390   19May94  DA   Allow CHKDSK if MAJOR OS version matches.
 *     @90910   26Jul94  DA   Warn user about /F:3 on fast-formatted drives.
 *
 */

#include <setjmp.h>
#define INCL_DOSDEVICES
#define INCL_DOSFILEMGR
#include "ldefs.h"
#include "ltypes.h"
#include "globals.h"
#include <pr_chk.h>
#include <conio.h>


extern void align_fnode (ULONG, struct FNODE *);
extern void sys_write_file (PUCHAR, PUCHAR, USHORT);

extern void FAR PASCAL C_TERM( void );

extern jmp_buf chk_error;

/* the following figures are defined and used in mfs.c in hpfs code
*  and when it is moved to some global hpfs header file this can be
* deleted. These figures are used to calculate (and display) the free space
* available on the disk
*/

#define RESERVE_PCENT   50
                /* 2% of total space is maximum reserved */
#define MAX_RESERVED_SECTORS    4096
                /* But it cannot exceed 4096 sectors */


/***    chk_show_statistics -- display statistical messages
 *
 *      This routine is called after all checking and recovery is
 *      performed.  It indicates how much space is taken by directories,
 *      file data, bad blocks, and free blocks.  Variables totaling the
 *      sectors used by these various portions are kept up-to-date by
 *      the checking and recovery routines.
 *
 *      chk_show_statistics (void)
 *
 *      ENTRY           No parameters
 *                      Global tallies set by checking and recovery routines
 *
 *      EXIT            No return value
 *
 *      CALLS           printf
 */
void chk_show_statistics (void)
{
  UCHAR *array [2];
  long v_reserved_sectors;
  long v_free_space;
  struct _FSALLOCATE fsinfo_buf;

  array [0] = outbuf;
  array [1] = outbuf+40;

#ifdef TRACE
  fprintf (stderr, "chk_show_statistics ()\n");
  fflush (stderr);
#endif

  sprintf (outbuf, "%10ld", number_of_sectors / 2L);
  CHK_COMPUTMSG( SYSTEM_MSG, array, 1, CHK_TOTAL_SPACE_STR, msgfilename, STDOUT_CODE,
                     NO_RESPONSE, NULL, 0);

  sprintf (outbuf, "%10ld", (chk_dirblk_sectors + chk_subdir_sectors) / 2L);
  ltoa (chk_subdir_sectors, outbuf+40, 10);
  CHK_COMPUTMSG( SYSTEM_MSG, array, 2, CHK_USER_DIRECTORIES_STR, msgfilename, STDOUT_CODE,
                     NO_RESPONSE, NULL, 0);

  if (chk_fnode_sectors) {
    sprintf (outbuf, "%10ld", (chk_data_sectors + chk_fnode_sectors +
                               chk_ab_sectors) / 2L);
    ltoa (chk_fnode_sectors, outbuf+40, 10);
    CHK_COMPUTMSG( SYSTEM_MSG, array, 2, CHK_USER_FILES_STR, msgfilename, STDOUT_CODE,
                       NO_RESPONSE, NULL, 0);
  }

  if (sb.culNumBadSects) {
    sprintf (outbuf, "%10ld", sb.culNumBadSects / 2L);
    CHK_COMPUTMSG( SYSTEM_MSG, array, 1, CHK_BAD_SECTORS_STR, msgfilename, STDOUT_CODE,
                       NO_RESPONSE, NULL, 0);
  }
  if (chk_ea_sectors) {
    sprintf (outbuf, "%10ld", chk_ea_sectors / 2L);
    CHK_COMPUTMSG( SYSTEM_MSG, array, 1, CHK_EA_SPACE_STR, msgfilename, STDOUT_CODE,
                       NO_RESPONSE, NULL, 0);
  }

 /*calculate free space available on disk */
/* Use dosqfsinfo to get free space iff a) there are no problems in the
* disk, or b) if /F switch is given. (in case b, we would have set the
* disk right, and called redetermine media, by the time we call qfsinfo
* so pinball should respond to our query
*/

   if (!chk_fatboot && (chk_delete_inconsistency || !chk_error_found)) {
        if ( DosQFSInfo( (USHORT ) (toupper(disk_name[0]) -'A'+1),0x0001,
                (char far *) &fsinfo_buf,
                sizeof(struct _FSALLOCATE) ))
                chk_exit_error (WRITE_ERROR);
        v_free_space = ( fsinfo_buf.cSectorUnit * fsinfo_buf.cUnitAvail ) /           //56340
                              ( 1024 / fsinfo_buf.cbSector );                         //56340
    v_reserved_sectors = ( number_of_sectors - chk_data_sectors -
                chk_fnode_sectors - chk_subdir_sectors - chk_dirblk_sectors -
                chk_ab_sectors - sb.culNumBadSects -chk_ea_sectors -
                v_free_space * 2L + 1)/2L;
    }
    else {
        v_free_space = ( number_of_sectors - chk_data_sectors -
                chk_fnode_sectors - chk_subdir_sectors - chk_dirblk_sectors -
                chk_ab_sectors - sb.culNumBadSects - chk_ea_sectors )/2L ;

        v_free_space = (v_free_space >0L)?v_free_space:0L;

        v_reserved_sectors = number_of_sectors / RESERVE_PCENT;

        if (v_reserved_sectors > MAX_RESERVED_SECTORS)
                v_reserved_sectors = MAX_RESERVED_SECTORS;

        v_reserved_sectors = (v_reserved_sectors +1)/2L;
        if (v_free_space < v_reserved_sectors)
                v_reserved_sectors = 0;

        v_free_space -=v_reserved_sectors;
    }
  sprintf (outbuf, "%10ld", v_reserved_sectors );

  CHK_COMPUTMSG( SYSTEM_MSG, array, 1, CHK_RESERV_SPACE_STR, msgfilename, STDOUT_CODE,
                     NO_RESPONSE, NULL, 0);

  sprintf (outbuf, "%10ld", v_free_space);

  CHK_COMPUTMSG( SYSTEM_MSG, array, 1, CHK_FREE_SPACE_STR, msgfilename, STDOUT_CODE,
                     NO_RESPONSE, NULL, 0);
}

/***    chkdsk -- top level function for CHKDSK
 *
 *      This routine invokes lower-level routines to parse the command
 *      line, open the disk, validate the file system, repair the file
 *      system, and close the disk.
 *
 *      chkdsk (argc, argv, envp)
 *
 *      ENTRY           argc - count of command line arguments, incl. "CHKDSK"
 *                      argv - list of pointers to command line arguments
 *                      envp - list of pointers to environment strings
 *
 *      EXIT            Returns 0 or uses chk_exit_error in case of error.
 *
 *      CALLS           chk_parse_options
 *                      open_disk
 *                      lock_disk
 *                      chk_allocate_maps
 *                      chk_validate_elementary
 *                      chk_rebuild_system
 *                      chk_check_system
 *                      chk_show_statistics
 *                      close_disk
 *                      chk_exit_error
 *                      setjmp
 *
 *      EFFECTS         Checks out the consistency of the file system
 *                      May delete inconsistent structures, if /D, /R, or /F
 *                      May recover lost data, if /R or /F
 *                      May search free space for lost data, if /F
 */
USHORT far pascal _loadds chkdsk (argc, argv, envp)
  USHORT argc;
  UCHAR **argv;
  UCHAR **envp;
{
  ULONG l;
  USHORT s=0;
  USHORT usDosVersion = 0;

#ifdef ALIGN
 PFNODE f;
  USHORT attr;
  extern UCHAR os2bootname [];
  extern UCHAR os2boot [];
  extern USHORT os2bootsize;
#endif

  if (setjmp (chk_error)) {             /* exit_error returns to here */
    C_TERM ();
    return (ERROR_CODE);                /* return exit code to frontend */
  }

/*
 * Find out whether the standard output handle has been redirected
 * to a file.  This information is later used to control whether
 * the percent-scanned displays are used.
 */

  check_stdout_redir ();

#ifdef TIMESTAMPS
  printf (chk_version_str);
  printf (chk_timestamp_str, __DATE__, __TIME__);
#endif

  if (!chk_parse_options (argc, argv))  /* parse command line options */
    chk_exit_error (ARG_ERROR);         /* an invalid option was encountered */

  if ( chk_usepipe )
       usPipeMgr( SUI_MSGTYPE_PERCENT_DONE, NULL, 0, usChkPercentDone,
                  NULL, hfileChkPipe, 0, NULL, 0 );
                                    // percent returned via msg parm
  if (!open_disk (disk_name))           /* open the disk */
    chk_exit_error (OPEN_ERROR);        /* cannot open disk */

/*
 * Make sure the boot block, superblock, and spareblock are readable.
 * If any are not, get out before locking the drive so that the user
 * can back up his data.
 */

  if (read_scratch (0L, scratchbuf, 1) ||
      read_scratch ((ULONG)SEC_SUPERB, &sb, 1) ||
      read_scratch ((ULONG)SEC_SPAREB, &spb, 1)) {
    CHK_ERROR_MSG( CHK_BAD_SUPERB_STR);
    close_disk ();
    chk_exit_error (READ_ERROR);
  }

  if (chk_delete_inconsistency) {
    dismount ();
    read_scratch ((ULONG)SEC_SUPERB, &sb, 1);
    read_scratch ((ULONG)SEC_SPAREB, &spb, 1);
  }

#ifdef  V4FORMAT              /* @d01 */
  chk_repair_disk = 0;        /* disk not yet repaired */
#endif

/*
 * Version checks.  Make sure that we understand everything on this disk.
 *
 *  There are 2 main cases: Incorrect OS version and incorrect FS version.
 *
 *  Operating System:
 *
 *      If the current OS version is < CHKDSK version, error (mainly for
 *      compatibility with other utilities, but also for assuring we'll not
 *      try to use a non-existing API)
 *
 *  File System:
 *
 *      There are five cases, two involving the major version and two
 *      involving the functional version:
 *
 *      CHKDSK version < FS version
 *          Error, CHKDSK doesn't understand the structures at all.
 *
 *      CHKDSK version > FS version
 *          OK, since between versions 1 and 2 all that changed was FNODE
 *          alignment, and CHKDSK knows how to convert that.
 *
 *      CHKDSK minor version < FS minor version
 *          Also an error.  Some fields which CHKDSK thinks are not used may
 *          actually have been used by the FS to point to sectors.  CHKDSK
 *          would think those sectors orphaned and free them.
 *
 *      CHKDSK minor version > FS minor version
 *          This is OK.  It just means that some fields which have meaning
 *          for CHKDSK have been left zeroed by the FS.  Since they're
 *          zeroed, CHKDSK will ignore them.
 */

  DosGetVersion(&usDosVersion);
  /*
   * Note that if DosGetVersion fails then usDosVersion is zero
   * (its initial value), so the following code will handle it
   * as an incorrect dos version error.
   *
   */

// @83390 Allow CHKDSK on any .X version of OS/2 (just match MAJOR versions)

  if (usDosVersion>>8 != DOS_VERSION>>8) {    /* DOS_VERSION defined in dosver.h */
    error_msg(CHK_BAD_DOS_VERSION_STR); /* Incorrect dos version msg */
    close_disk();
    C_TERM ();
    return(ERROR_CODE);
  }

  if ((SBBASEV < sb.bVersion) || (SBBASEFV < sb.bFuncVersion)) {
    CHK_ERROR_MSG( CHK_BAD_HPFS_VERSION_STR);    /* display incorrect version msg */
    close_disk ();
    C_TERM ();
    return (ERROR_CODE);                /* return an error */
  }

// @90910 - If the user specified /F:3 on a fast-formatted drive, warn him

  if ((spb.bFlag & SPF_FASTFMT) &&               // fast formatted drive? @90910
      (chk_search_free))                         // user wants /F:3?      @90910
  {                                              // Yes, warn about /F:3  @90910
     CHK_COMPUTMSG ( SYSTEM_MSG                                        // @90910
                   , NULL                                              // @90910
                   , 0                                                 // @90910
                   , MSG_HPFS_CHKDSK_F3_WARNING                        // @90910
                   , msgfilename                                       // @90910
                   , STDOUT_CODE                                       // @90910
                   , YES_NO_RESPONSE                                   // @90910
                   , scratchbuf                                        // @90910
                   , 1                                                 // @90910
                   );                                                  // @90910
     if (scratchbuf[0] == NO_ANS)                                      // @90910
     {                                                                 // @90910
        close_disk ();                                                 // @90910
        C_TERM ();                                                     // @90910
        return (SUCCESS_CODE);                    // return w/o errorr    @90910
     }                                                                 // @90910
  }                                                                    // @90910


/*
 * Process the /C switch -- only checks if the file system is dirty.
 */

#ifdef ALIGN
  f = ALLOCATE_BLOCK (SECTORS_PER_FNODE);
  read_scratch (sb.lsnRootFNode, f, SECTORS_PER_FNODE);
#endif

  if (chk_check_if_dirty &&             /* check only if filesystem dirty */
#ifdef ALIGN
      f->sig == FNSIGVAL &&             /* and FNODEs don't need alignment */
#endif
      !spb.bFlag) {                     /* and no flags set */
    close_disk ();                      /* not dirty -- just exit */
    C_TERM ();
    return (SUCCESS_CODE);              /* with a successful error code */
  }

  if (chk_autocheck) {                  /* find out which FSD we're using */
    PFSQBUFFER pfsq;
    USHORT size = sizeof (FSQBUFFER) + 20;

    pfsq = malloc (size);

    DosQFSAttach (disk_name, 0, 0x0001, (PBYTE)pfsq, &size, 0L);
    chk_fatboot = strcmp (
        ((PCHAR)pfsq->szName) + pfsq->cbName + 1 + sizeof (pfsq->cbFSDName),
        "HPFS");
    free (pfsq);
  }
  else                                  /* only lock disk if no autochecking */
    if (chk_delete_inconsistency && lock_disk ()) { /* lock the disk */
#ifdef ALIGN
      FREE_BLOCK (f);
#endif
      chk_exit_error (LOCK_ERROR);
    }

/*
 * Make sure the filesystem's cache is invalidated right away by
 * dismounting the volume.  Then reread the superblock and spareblock
 * to make sure we get the disk versions of them.
 */

  if (chk_delete_inconsistency) {
    dismount ();
    read_scratch ((ULONG)SEC_SUPERB, &sb, 1);
    read_scratch ((ULONG)SEC_SPAREB, &spb, 1);
  }

#ifdef PAW13
  if (chk_delete_inconsistency &&       /* make sure the DIRTY flag is set */
      !(spb.bFlag & SPF_DIRT)) {        /* if we might change the disk */
    spb.bFlag |= SPF_DIRT;              /* PTR B783764 */
    if ((spb.sig1 != SPSIG1) || (spb.sig2 != SPSIG2))
    {
       write_scratch ((ULONG)SEC_SPAREB, &spb, 1);
    }
  }
#endif


#ifdef ALIGN
  if (f->sig == OLDFNSIGVAL)
    align_fnode (sb.lsnRootFNode, f);
  FREE_BLOCK (f);
#endif

#ifdef  V4FORMAT
  /*
   * If DASD Limits is both 'RUNNING' and 'DIRTY' then the volume
   * was not shutdown correctly (e.g. lightning!)
   */
  if( (spb.bFlag2 & SPF2_DASD_RUNNING) && (spb.bFlag2 & SPF2_DASD_DIRTY) )
    chk_repair_disk++;      /* DASD Limits needs resynchronization */
#endif

  chk_allocate_maps (number_of_sectors);  /* allocate bit maps and disk map */

  if (!chk_validate_elementary ())      /* elementary structures are bad */
    chk_rebuild_system ();              /* reconstruct the file system */

  else                                  /* elementary structure are OK */
    chk_check_system ();                /* check out existing system */

#ifdef PAW13
  if (chk_delete_inconsistency) {       /* if we can change the disk, */
//86635
    spb.bFlag &= (SPF_VER | SPF_FASTFMT); /* reset almost all flags B784677 */

#ifdef  V4FORMAT
/*
 *  If DASD LIMITS are enabled for the volume, then spb.bFlag2
 *  will indicate that.
 *
 *  We reset the status of DASD Limits for the volume IF we are allowed
 *  to change the disk (chk_delete_inconsistency) AND repairs were made
 *  (chk_repair_disk) AND DASD Limits are active on the volume.
 *
 */
    #define  DLIM_ENABLED  (SPF2_DASD_SYNCH | SPF2_DASD_RUNNING)

    if( chk_delete_inconsistency &&
                        chk_repair_disk &&
                             (spb.bFlag2 & DLIM_ENABLED) )
    {
        spb.bFlag2 &= ~SPF2_DASD_SUPPORT;    /* Clear DASD LIMITS bits */
        spb.bFlag2 |= SPF2_DASD_SYNCH;       /* Set to synchronize     */
    }
#endif

#ifdef ALIGN
    if (!spb.SPB_SUPERBSUM || !spb.SPB_SPAREBSUM) {
      /* checksums not in place; need to realign the boot block */
      struct boot_sector *boot;

      boot = ALLOCATE_BLOCK (16);

      read_scratch (0L, boot, 1);       /* read 1st sector of boot block */
      pinboot.Extended_Boot.Boot_BPB = boot->Extended_Boot.Boot_BPB; /* copy volume params into pinboot */
      pinboot.Extended_Boot.Boot_Sig = boot->Extended_Boot.Boot_Sig;
      pinboot.Extended_Boot.Boot_Serial = boot->Extended_Boot.Boot_Serial;
      memcpy (pinboot.Extended_Boot.Boot_Vol_label, boot->Extended_Boot.Boot_Vol_label, sizeof (boot->Extended_Boot.Boot_Vol_label));
      memcpy (pinboot.Extended_Boot.Boot_System_ID, boot->Extended_Boot.Boot_System_ID, sizeof (boot->Extended_Boot.Boot_System_ID));
#ifdef CHECK_ZERO
      fCheckZero = FALSE;
      write_scratch (0L, &pinboot, 16); /* write entire new boot block */
      fCheckZero = TRUE;
#else
      write_scratch (0L, &pinboot, 16); /* write entire new boot block */
#endif
      s = 1;                            /* we'll need to align os2boot too */
      FREE_BLOCK (boot);
    }
#endif

    /* Calculate the superblock and spareblock checksums.  The spareblock
     * checksum must be cleared before recalculating it, so it doesn't
     * perturb itself.  It is supposed to include the superblock checksum,
     * though.  We store the spareblock checksum into a temp variable to
     * make sure that the compiler doesn't filter out the zeroing statement
     * as having no effect.
     */

    sb.bVersion = SBBASEV;

#ifdef  V4FORMAT
    /*
     * If the format of the disk supports our new extensions
     * then we keep it as is.
     */

    if( sb.bFuncVersion < SBBASEFV ) {

       if (sb.culSectsOnVol < CSEC_BIG_DISK) {
          /* normal sized disk, use old version number */
         sb.bFuncVersion = SBBASEOLDFV;
       } else {
         /* disk is too big for old hpfs, set new version number */
         sb.bFuncVersion = SBBASEOLDFV_BIGDISK;
       }

    }

#else

    if (sb.culSectsOnVol < CSEC_BIG_DISK) {                         /*BIGDISK */
       /* normal sized disk, use old version number */              /*BIGDISK */
      sb.bFuncVersion = SBBASEOLDFV;                                /*BIGDISK */
    } else {                                                        /*BIGDISK */
      /* disk is too big for old hpfs, set new version number */    /*BIGDISK */
      sb.bFuncVersion = SBBASEFV;                                   /*BIGDISK */
    }                                                               /*BIGDISK */
#endif

    spb.SPB_SUPERBSUM = ccs ((PUCHAR)&sb, sizeof (struct SUPERB));
    spb.SPB_SPAREBSUM = 0L;
    l = ccs ((PUCHAR)&spb, sizeof (struct SpareB));
    spb.SPB_SPAREBSUM = l;

    write_scratch ((ULONG)SEC_SUPERB, &sb, 1);
    write_scratch ((ULONG)SEC_SPAREB, &spb, 1);
    if (!chk_autocheck)
      dismount ();
  }
#endif

  close_disk ();                        /* unlock and close the disk */

#ifdef ALIGN
  if (s) {                              /* we're realigning things... */
    UCHAR target[CCHMAXPATH];                                           //56340
                                                                        //56340
    sprintf (target,                     /* create target file name */  //56340
             os2bootname,                                               //56340
             disk_name);                                                //56340

    if (!DosQFileMode (target, &attr, 0L)) {       /* if os2boot exists */      //56340
      sys_write_file (target, os2boot, os2bootsize); /* recreate os2boot */     //56340
      open_disk (disk_name);
      read_scratch ((ULONG)SEC_SPAREB, &spb, 1);        /* mark disk clean again */
//86635
      spb.bFlag &= (SPF_VER | SPF_FASTFMT);
      spb.SPB_SPAREBSUM = 0L;
      l = ccs ((PUCHAR)&spb, sizeof (struct SpareB));
      spb.SPB_SPAREBSUM = l;
      write_scratch ((ULONG)SEC_SPAREB, &spb, 1);
      if (!chk_autocheck)
        dismount ();
      close_disk ();
    }
  }
#endif

  if ( ( chk_usepipe ) && ( usChkPercentDone < PERCENT_FENCE_MAX ) ){
      do {
          usChkPercentDone += PERCENT_FENCE_1 * 100 *
                              (double)(PERCENT_FENCE_2 +
                                       PERCENT_FENCE_3);
          if ( usChkPercentDone <= PERCENT_FENCE_MAX )
              usPipeMgr( SUI_MSGTYPE_PERCENT_DONE, NULL, 0, usChkPercentDone,
                         NULL, hfileChkPipe, 0, NULL, 0 );

      } while ( usChkPercentDone < PERCENT_FENCE_MAX );
  }
  chk_show_statistics ();               /* display numbers of files, etc. */

  hfree (bitmap);
  hfree (chk_disk_map);

  if ( chk_usepipe ) {
                usPipeMgr( SUI_MSGTYPE_PERCENT_DONE, NULL, 0, (USHORT)100,
                   NULL, hfileChkPipe, 0, NULL, 0 );
  }
  C_TERM ();
  return (SUCCESS_CODE);                /* return successful error code */
  envp;                                 /* Chkdsk doesn't need this but */
                                        /* is passed it so must remove */
                                        /* it from the stack. */
}
