/*static char *SCCSID = "@(#)gcode.c    6.6 91/11/06";*/
/***    GCODE.C - Functions shared by Pinball utilities
 *
 *      Pinball Boot_OEM 1.0
 *      Gregory A. Jones
 *      Copyright (C) 1988 Microsoft Corp.
 *
 *      Revision History:
 *      ChangeTeam      08/11/95   128913: Chkdsk failed to find space for
 *                                 corrections if force to search backwards.
 *      D. Azzarito     09/02/92   @53562: Remove Bill McJohn's name from
 *                                 the error message when we try to write
 *                                 to sector 0.  Shut down chkdsk instead
 *                                 of locking up the system.
 *      J.A. Rogers     08/17/89   Invalidate Pinball's cache, so it doesn't
 *                                 get out of date.  Use DosDevIOCtl to read
 *                                 again.  (changed write_scratch and
 *                                 read_scratch)
 *      P.A. Williams   06/07/89   Don't unconditionally clear 8 bits at the
 *                                 at end of BAND anymore in
 *                                 allocate_bitmap_sectors. Also put bitmap at
 *                                 beginning of even numbered band and at end
 *                                 off odd band.
 *      P.A Williams    Allocate_early_sectors now starts search at
 *                                      SEC_START_DISK_SEARCH.
 */

#define INCL_DOSFILEMGR                                        /* BIGDISK */
#define INCL_DOSDEVICES
#define INCL_DOSINFOSEG
#define INCL_DOSSIGNALS
#include <ldefs.h>
#include <pr_com.h>
extern void     chk_exit_error (USHORT);                       /*@53562*/

/* Round "a" down to a multiple of "b". */
#define ROUND_DOWN(a,b)         ( (a) - ( (a) % (b) ) )

extern USHORT chk_fatboot;
extern USHORT chk_autocheck;

/***    allocate_near - get some sectors from the bit map
 *
 *      This routine finds a specified number of free sectors
 *      near a specific sector.  If no space is available after
 *      that sector, the search will start again at the requested
 *      location and regress to the beginning of the disk.
 *
 *      The sectors are allocated on a multiple of the granularity
 *      specified.
 *
 *      The sectors are also not allowed to cross an 8MB band boundary,
 *      since the filesystem cannot handle such runs.
 *
 *      allocate_near (lsn, n, gran)
 *
 *      ENTRY           lsn  - sector to start looking at
 *                      n    - number of sectors to allocate
 *                      gran - sector boundary to allocate on
 *
 *      EXIT            Returns a starting sector number, or 0L if no space
 *
 *      CALLS           clr_bit - to mark the sectors as used in the free map
 *
 *      EFFECTS         Sectors in the bit maps are marked as in use.
 */
ULONG allocate_near (lsn, n, gran)
  ULONG lsn;
  USHORT n;
  USHORT gran;
{
  USHORT count, last_bit, found;
  UCHAR huge *p;
  long first, last, last_byte;

#ifdef  TRACE
  fprintf (stderr, "allocate_near (%lx, %x)\n", lsn, n);
  fflush (stderr);
#endif

  /*
   *  If the requested locality is off the end of the disk,
   *  get something near the middle of the disk.
   */
  if ( lsn >= sb.culSectsOnVol )
    lsn = sb.culSectsOnVol / 2;

  p = bitmap;

  found = FALSE;
  if (gran != 1)
    lsn -= (lsn % gran);        /* start on a "gran" boundary */

  last = first = lsn;

/*
 * Start at the requested sector and look forward.
 */

  while ((last < rec_param.dev_bpb.Ext_totalSectors) && !found) {
    last_byte = last >> 3L;
    last_bit = (USHORT)(last & 7L);

    if (*(p + last_byte) & (0x01 << last_bit))
      if (last-first == n-1)
        if (((last / (SPB * SECSIZE * 8L)) ==
             (first / (SPB * SECSIZE * 8L))) &&
            !(first % gran))
          found = TRUE;
        else
          last = (first += gran);
      else
        last++;
    else
      last = (first += gran);
  }

/*
 * If we got to the end of the disk without finding anything,
 * use the same algorithm starting at the beginning of the disk.
 */

  if (!found) {
    last = first = lsn;                 /* start at request point again */
    while ((first > 0L) && !found) {    /* go until beginning of disk */
/*128913 last_byte = first >> 3L;          */
/*128913 last_bit = (USHORT)(first & 7L);  */
      last_byte = last >> 3L;                                     /*128913*/
      last_bit = (USHORT)(last & 7L);                             /*128913*/

      if (*(p + last_byte) & (0x01 << last_bit))
        if (last-first == n-1)
          if (((last / (SPB * SECSIZE * 8L)) ==
               (first / (SPB * SECSIZE * 8L))) &&
              !(first % gran))
            found = TRUE;
/*
 * 128913 start
 *
 *        else
 *          first = (last -= gran);
 *      else
 *        first--;
 *    else
 *       first = (last -= gran);
 *
 * 128913 end
 */

          else                                                    /*128913*/
            last = (first -= gran);                               /*128913*/
        else                                                      /*128913*/
          last++;                                                 /*128913*/
      else                                                        /*128913*/
        last = (first -= gran);                                   /*128913*/
    }
  }


  if (!found)                           /* no space available! */
    return (0L);

/*
 * Mark the sectors in the bitmap.
 */

  for (count=0; count<n; count++)
    clr_bit (first + count);

#ifdef  TRACE
  fprintf (stderr, "Giving %lx\n", first);
  fflush (stderr);
#endif

  return (first);
}

/***    allocate_early_sectors - get some sectors from the bit map
 *
 *      This routine finds a specified number of free sectors
 *      near the beginning of the disk, allocated on a multiple
 *      of the requested granularity.
 *
 *      allocate_early_sectors (n, gran)
 *
 *      ENTRY           n    - number of sector to allocate
 *                      gran - sector boundary to allocate on
 *
 *      EXIT            returns a starting sector number
 *
 *      CALLS           allocate_near
 *
 *      EFFECTS         Sectors in the bit maps are marked as in use.
 */
ULONG allocate_early_sectors (n, gran)
  USHORT n;
  USHORT gran;
{
  return (allocate_near (SEC_START_DISK_SEARCH, n, gran));
}

/***    allocate_middle_sectors
 *
 *      allocate_middle_sectors is identical to allocate_early_sectors
 *      in all respects, save that the search for available space is
 *      started at the middle of the disk.
 *
 *      allocate_middle_sectors (n, gran)
 *
 *      ENTRY           n - number of sectors to allocate
 *                      gran - sector boundary to allocate on
 *
 *      EXIT            returns a starting sector number
 *
 *      CALLS           allocate_near
 *
 *      EFFECTS         Sectors in the free map are marked as used.
 */
ULONG allocate_middle_sectors (n, gran)
  USHORT n;
  USHORT gran;
{
  return (allocate_near ((rec_param.dev_bpb.Ext_totalSectors >> 1L) & ~7L, n, gran));
}

/***    allocate_bitmap_sectors - allocate disk space for bitmaps
 *
 *      This routine finds four free sectors near the beginning of
 *      a requested 8-megabyte band for an even band no.
 *      and four free sectors near the end of a requested 8-megabyte
 *      band of the disk for an odd band no.
 *
 *      Note that allocate_bitmap_sectors starts at the beginning
 *      or end of the specified band, but will search, if necessary,
 *      even to the ends of the volume.  Similarly, it first
 *      searches in the specified direction, but if that fails
 *      it will go back and try the other direction.
 *
 *      allocate_bitmap_sectors (n)
 *
 *      ENTRY           n - band number to allocate in
 *
 *      EXIT            returns a starting sector number
 *                      returns 0xffffffff if it couldn't allocate
 *                          a block in the specified band.
 *
 *      CALLS           clr_bit
 *
 *      WARNINGS        Due to the cross-track restrictions, no more
 *                      then SectorsPerTrack sectors can be allocated.
 *                      It is known that no more then this will be needed
 *                      for any file system structure, at least while
 *                      formatting.
 *                      BUGBUG - cross-sect code removed
 *
 *      EFFECTS         Sectors in the bit maps are marked as in use.
 */
ULONG allocate_bitmap_sectors (n)
  USHORT n;
{
  USHORT count, found;
  UCHAR huge *p, last_mask;
  ULONG sector, last_byte, offset;
  BOOL fSecondTry = FALSE;  /*  Have we searched both directions?  */

#ifdef TRACE
  fprintf (stderr, "allocating bitmap for band #%d\n", n);
  fflush (stderr);
#endif

  p = bitmap;

  /*
   * The bitmap is allocated in the first available block in an even
   * band and in the last available block in an odd band. This way we
   * allow a large file to be contiguous as it crosses over an even to
   * odd band boundary. The offset variable below is 0 if the bitmap
   * should go at the start of the band and 1 if it should go at the end.
   */

  offset = n & 1;                                 /* are we on even/odd band */

  found = FALSE;


  sector = ((ULONG)(n+offset) * SECTORS_PER_BAND) /* start at first sector  */
               - (offset ? (ULONG)SPB : 0L);      /* for even band and last */
                                                  /* sector-SPD for odd one */
  last_byte = sector / 8L;
  last_mask = (UCHAR)((sector & 7) ? 0xf0 : 0x0f);

  while (!found) {                                /* find a free block  */

    if ((p [last_byte] & last_mask) == last_mask)
            found = TRUE;
    else {
        if (offset) {

           sector -= (ULONG)SPB;

           if (sector <= SEC_SUPERB) {

             /*
              *  We've reached the forbidden zone--we
              *  cannot allocate bitmaps in the area
              *  reserved for the boot code.  If we
              *  haven't used our second chance, reverse
              *  offset and search in the other direction.
              *
              *  Otherwise, return error.
              */
             if ( !fSecondTry ) {

                 fSecondTry = TRUE;
                 offset = 0;
                 sector = (ULONG)(n + 1) * SECTORS_PER_BAND;
                 last_byte = sector / 8L;
                 last_mask = (UCHAR)((sector & 7) ? 0xf0 : 0x0f);
                 continue;

             } else
                 return( 0xffffffffL );

           }
        } else {

           sector += (ULONG)SPB;

           if (sector > number_of_sectors) {

              /*
               *  We fell off the end of the disk.  If we
               *  haven't used our second chance, go back
               *  to where we started and search in the
               *  other direction.  Otherwise
               *  error.
               */
             if ( !fSecondTry ) {

                 fSecondTry = TRUE;
                 offset = 1;
                 sector = (ULONG)(n) * SECTORS_PER_BAND;
                 last_byte = sector / 8L;
                 last_mask = (UCHAR)((sector & 7) ? 0xf0 : 0x0f);
                 continue;

             } else
                return( 0xffffffffL );
           }
        }
        last_byte = sector / 8L;
        last_mask = ~last_mask;                 /* mask flips if we need  */
                                                /* to check again         */
    }
  }


/*
 * Mark these bits in the bitmap.
 */

  for (count=0; count<SPB; count++)
    clr_bit (sector + (ULONG)count);


#ifdef  TRACE
  fprintf (stderr, "Put bitmap in sectors %xl - %xl\n", sector, sector+SPB-1);
  fflush (stderr);
#endif

  return (sector);
}

/***    clr_bit - clear a bit in a bitmap
 *
 *      This routine is called to clear a bit in a bitmap.  This
 *      indicates a sector in use by the file system.
 *
 *      clr_bit (lsn)
 *
 *      ENTRY           lsn - sector number to allocate
 *
 *      EXIT            Return value 0 if success, 1 if no bitmap,
 *                      2 if bad sector number
 *
 *      CALLS           None
 *
 *      EFFECTS         Clears a bit in the global bitmap
 *
 *      WARNINGS        Bitmap should be allocated first
 */
USHORT clr_bit (lsn)
  ULONG lsn;
{
  register ULONG byte;
  register USHORT bit;

#ifdef TRACE_BITS
  fprintf (stderr, "clr_bit (%lx)\n", lsn);
  fflush (stderr);
#endif

  if (!bitmap)                  /* bitmap is not allocated yet */
    return (1);                 /* return error condition */

  if (lsn > number_of_sectors)  /* sector outside bitmap */
    return (2);                 /* return error condition */

  byte = lsn >> 3;              /* divide by 8 without a DIV */
  bit = (USHORT)(lsn & 7);      /* "mod" by 8 without a DIV */
  bitmap [byte] &= ~(1 << bit); /* clear the bit by ANDing with its inverse */

  return (0);
}

/***    set_bit - set a bit in a bitmap
 *
 *      This routine is called to set a bit in a bitmap.  This
 *      indicates a sector not in use by the file system.
 *
 *      set_bit (lsn)
 *
 *      ENTRY           lsn - sector number to free
 *
 *      EXIT            Return value 0 if success, 1 if no bitmap,
 *                      2 if bad sector number
 *
 *      CALLS           None
 *
 *      EFFECTS         Sets a bit in the global bitmap
 *
 *      WARNINGS        Bitmap should be allocated first
 */
USHORT set_bit (lsn)
  ULONG lsn;
{
  register ULONG byte;
  register USHORT bit;

#ifdef TRACE_BITS
  fprintf (stderr, "set_bit (%lx)\n", lsn);
  fflush (stderr);
#endif

  if (!bitmap)                  /* bitmap is not allocated yet */
    return (1);                 /* return error condition */

  if (lsn > number_of_sectors)  /* sector outside bitmap */
    return (2);                 /* return error condition */

  byte = lsn >> 3;              /* divide by 8 without a DIV */
  bit = (USHORT)(lsn & 7);      /* "mod" by 8 without a DIV */
  bitmap [byte] |= (1 << bit);  /* set the bit by ORing with it */

  return (0);
}

/***    clr_db_bit - clear a bit in the dirblk bitmap
 *
 *      This routine is called to clear a bit in the dirblk bitmap.  This
 *      indicates a DIRBLK in use by the file system.
 *
 *      clr_db_bit (db_number)
 *
 *      ENTRY           db_number - dirblk number within band to allocate
 *
 *      EXIT            Return value 0 if success, 1 if no bitmap,
 *                      2 if bad sector number
 *
 *      CALLS           None
 *
 *      EFFECTS         Clears a bit in the dirblk bitmap
 *
 *      WARNINGS        Bitmap should be allocated first
 */
USHORT clr_db_bit (db_number)
  ULONG db_number;
{
  register ULONG byte;
  register USHORT bit;

#ifdef TRACE_BITS
  fprintf (stderr, "clr_db_bit (%lx)\n", db_number);
  fflush (stderr);
#endif

  if (!db_bitmap)               /* bitmap is not allocated yet */
    return (1);                 /* return error condition */

  if (db_number > sb.clsnDirBlkBand / SECTORS_PER_DIRBLK) /* sector outside bitmap */
    return (2);                 /* return error condition */

  byte = db_number >> 3;        /* divide by 8 without a DIV */
  bit = (USHORT)(db_number & 7);        /* "mod" by 8 without a DIV */
  db_bitmap [byte] &= ~(1 << bit); /* clear the bit by ANDing with its inverse */

  return (0);
}

/***    set_db_bit - set a bit in the dirblk bitmap
 *
 *      This routine is called to set a bit in the dirblk bitmap.  This
 *      indicates a DIRBLK not in use by the file system.
 *
 *      set_db_bit (db_number)
 *
 *      ENTRY           db_number - dirblk number within band to free
 *
 *      EXIT            Return value 0 if success, 1 if no bitmap,
 *                      2 if bad sector number
 *
 *      CALLS           None
 *
 *      EFFECTS         Sets a bit in the dirblk bitmap
 *
 *      WARNINGS        Bitmap should be allocated first
 */
USHORT set_db_bit (db_number)
  ULONG db_number;
{
  register ULONG byte;
  register USHORT bit;

#ifdef TRACE_BITS
  fprintf (stderr, "set_db_bit (%lx)\n", db_number);
  fflush (stderr);
#endif

  if (!db_bitmap)               /* bitmap is not allocated yet */
    return (1);                 /* return error condition */

  if (db_number > sb.clsnDirBlkBand / SECTORS_PER_DIRBLK) /* sector outside bitmap */
    return (2);                 /* return error condition */

  byte = db_number >> 3;        /* divide by 8 without a DIV */
  bit = (USHORT)(db_number & 7);        /* "mod" by 8 without a DIV */
  db_bitmap [byte] |= (1 << bit);       /* set the bit */

  return (0);
}

#define CCS_ROT 7

/***    ccs - compute check sum
 *
 *      This function computes a checksum of a range of memory.
 *      It is used to verify the integrity of the superblock,
 *      spareblock, and codepage sectors.
 *
 *      ccs (mem, nbytes)
 *
 *      ENTRY           mem    - pointer to memory to checksum
 *                      nbytes - length of area
 *
 *      EXIT            Returns 32-bit checksum
 *
 *      CALLS           None
 *
 *      WARNINGS        This function must match CCS in both HPFS
 *                      and LAN Manager/386 EXACTLY!
 *
 *      EFFECTS         None
 */
ULONG ccs (PUCHAR mem, USHORT nbytes)
{
  register USHORT i;
  ULONG accum=0L;

  for (i=0; i < nbytes; i++) {
    /* add in the byte, then rotate left by CCS_ROT */
    accum += (ULONG) *mem++;
    accum = (accum << CCS_ROT) + (accum >> (32-CCS_ROT));
  }
  return (accum);
}

/***    open_disk - open and lock the disk to be checked.
 *
 *      This function is called to open the disk for logical access
 *      on a sector basis.  DosOpen is called in DASD mode to accomplish
 *      this.  This function does not lock the volume, since some of the
 *      utilities do not need to do so unless they want to change the disk,
 *      and CHKDSK must make sure it can read the superblock before locking
 *      the volume (so that if it's unreadable, the file system won't be
 *      asked to re-mount).
 *
 *      open_disk (name)
 *
 *      ENTRY           name - name of disk to open
 *
 *      EXIT            Returns TRUE if success, FALSE otherwise
 *
 *      CALLS           DosDevIOCtl
 *                      DosOpen
 *                      DosPhysicalDisk
 *
 *      EFFECTS         Opens a handle to the disk
 *                      Fills in disk_handle variable with a drive handle
 */
USHORT open_disk (name)
  PUCHAR name;
{
/*
 * Use DASD Open to open the Pinball volume.
 */

  USHORT action_taken, ret_code, i;
  ULONG ulFSCtlPasswd = FSCP_PASSWD;     /* BIGDISK */
  ULONG zero_data=0L;
  USHORT usTrashCan = 0;                 /* BIGDISK */
  UCHAR c;

  ret_code = DosOpen (name, &disk_handle,
                      &action_taken, 0L,
                      0, OPEN_FLAG, OPEN_MODE, 0L);
  if (ret_code) {
#ifdef TRACE
    fprintf (stderr, "DosOpen failed: %d.\n", ret_code);
    fflush (stderr);
#endif
    return (FALSE);
  }

  c = GET_RECOMM;
  ret_code = DosDevIOCtl ((char far *)&rec_param, (char far *)&c,
                          IOCTL_GET_PARAMS, IOCTL_CATEGORY, disk_handle);
  if (ret_code) {
#ifdef TRACE
    fprintf (stderr, "GET PARAMS IOCTL failed: %d.\n", ret_code);
    fflush (stderr);
#endif
    return (FALSE);
  }

  if (rec_param.dev_bpb.TotalSectors) {
    rec_param.dev_bpb.Ext_totalSectors = (ULONG)rec_param.dev_bpb.TotalSectors;
    rec_param.dev_bpb.TotalSectors = 0;
  }

  number_of_sectors = rec_param.dev_bpb.Ext_totalSectors;
  partition_offset = rec_param.dev_bpb.HiddenSectors;
  prm.spt = rec_param.dev_bpb.SectorsPerTrack;
  prm.nh  = rec_param.dev_bpb.Heads;
  for (i=0; i<MAX_SECTORS_PER_TRACK; i++) {
    dp.map [i].sectno = i+1;
    dp.map [i].sectsize = SECSIZE;
  }
  dp.cmdinfo = 1;               /* track is consecutive sectors */

  return (TRUE);
}


/***    lock_disk - lock the disk for exclusive access
 *
 *      This function is called to lock the disk for exclusive access,
 *      so that no other processes may change the disk while it is being
 *      repaired.
 *
 *      lock_disk (void)
 *
 *      ENTRY           No parameters
 *                      disk_handle contains a handle to the disk
 *
 *      EXIT            Returns non-zero if error
 *
 *      CALLS           DosDevIOCtl
 *
 *      WARNINGS        Disk should be opened first
 *
 *      EFFECTS         Locks disk
 */
USHORT lock_disk (void)
{
  ULONG zero_data = 0L;
  USHORT ret_code;

  ret_code = DosDevIOCtl ((char far *)&zero_data, (char far *)&zero_data,
                          IOCTL_LOCK, IOCTL_CATEGORY, disk_handle);
#ifdef TRACE
  if (ret_code) {
    fprintf (stderr, "LOCK IOCTL failed: %d.\n", ret_code);
    fflush (stderr);
  }
#endif
  if (!ret_code)
    disk_locked = TRUE;         /* successfully locked the disk */

  return (ret_code);
}

/***    close_disk - close the disk handle
 *
 *      This function is called when a utility is finished accessing
 *      a disk.  It fress up the disk handle allocated by open_disk.
 *      If the disk is locked, it will be unlocked before closing the
 *      handle.
 *
 *      close_disk (void)
 *
 *      ENTRY           No parameters
 *
 *      EXIT            No return value
 *
 *      CALLS           DosOpen
 *
 *      WARNINGS        None
 *
 *      EFFECTS         Closes the disk handle
 *                      Allows other users to access the disk
 */
USHORT close_disk (void)
{

  ULONG zero_data=0L;

  if (disk_locked) {
    DosDevIOCtl ((char far *)&zero_data, (char far *)&zero_data,
                 IOCTL_UNLOCK, IOCTL_CATEGORY, disk_handle);

  }

  return (DosClose (disk_handle));

}

/***    dismount - dismount a volume
 *
 *      This function is called whenever a utility has changed the
 *      contents of a volume using a direct write (e.g. CHKDSK,
 *      FORMAT, or RECOVER).  It issues a Redetermine Media IOCTL,
 *      which instructs the FSD to re-mount the volume from scratch.
 *
 *      dismount ()
 *
 *      ENTRY           No parameters
 *                      disk_handle is open on the volume
 *
 *      EXIT            No return value
 *
 *      CALLS           DosDevIOCtl
 *
 *      WARNINGS        None
 *
 *      EFFECTS         Causes volume to be re-mounted
 */

void dismount (void)

{

  ULONG scratch=0L;

  DosDevIOCtl ((PCHAR)&scratch, (PCHAR)&scratch, IOCTL_REDETERMINE,
               IOCTL_CATEGORY, disk_handle);

}

/***    read_scratch - read a run of sectors into memory.
 *
 *      This function is used to read a run of sectors from the disk.
 *      It is called read_scratch because it reads into an already-
 *      allocated area of memory, and thus may be called over and over
 *      again on the same scratch area.
 *
 *      read_scratch (psn, mem, ns)
 *
 *      ENTRY           psn - sector number to start at
 *                      mem - memory address to store data in
 *                      ns  - number of sectors to read
 *
 *      EXIT            Returns zero if success, or OS/2 error code
 *
 *      CALLS           DosRead
 *
 *      EFFECTS         Fills in memory with disk image
 */
USHORT read_scratch (psn, mem, ns)
ULONG psn;
void *mem;
USHORT ns;
{
  USHORT ret_code;
  ULONG phys_sector;

#ifdef TRACE_READS
  fprintf (stderr, "read_scratch (%lx, [buffer], %x)\n", psn, ns);
  fflush (stderr);
#endif

  if (psn >= number_of_sectors)
    return (ERROR_GEN_FAILURE);

/*
 * Compute physical cylinder/head/sector corresponding to this run.
 */

  phys_sector = partition_offset + psn;

  dp.sect = (USHORT)(phys_sector % prm.spt);
  dp.head = (USHORT)((phys_sector / prm.spt) % prm.nh);
  dp.cyl =  (USHORT)(phys_sector / (prm.spt * prm.nh));
  dp.nsect = ns;

/*
 * Split the read into chunks, if necessary;  the IOCTL we use isn't
 * capable of crossing track boundaries.
 */

  if (dp.sect+dp.nsect >= prm.spt) {    /* track overlap */
    dp.nsect = prm.spt - dp.sect;       /* read up to track boundary */

    ret_code = DosDevIOCtl (mem, (UCHAR *)&dp, IOCTL_READ, IOCTL_CATEGORY,
                            disk_handle);
    if (ret_code) return (ret_code);

    mem = (PUCHAR)((PUCHAR)mem + dp.nsect * SECSIZE);/* skip past the stuff we wrote */
    ns -= dp.nsect;                     /* decrement sectors left to go */
    dp.nsect = prm.spt;                 /* write whole tracks now */
    dp.sect = 0;                        /* write at beginning of each track */

    while (ns > prm.spt) {              /* write the whole tracks */
      dp.head++;                        /* go to next head */
      if (dp.head == prm.nh) {          /* crossed cylinder boundary */
        dp.head = 0;                    /* start at first head again */
        dp.cyl++;                       /* go to next cylinder */
      }
      ret_code = DosDevIOCtl (mem, (UCHAR *)&dp, IOCTL_READ,
                              IOCTL_CATEGORY, disk_handle);
      if (ret_code) return (ret_code);
      mem = (PUCHAR)((PUCHAR)mem + dp.nsect * SECSIZE);/* skip past the stuff we wrote */
      ns -= dp.nsect;                   /* decrement sectors left to go */
    }

    dp.head++;                          /* go to next head */
    if (dp.head == prm.nh) {            /* crossed cylinder boundary */
      dp.head = 0;                      /* start at first head again */
      dp.cyl++;                         /* go to next cylinder */
    }
    dp.nsect = ns;                      /* number of sectors in partial track */
  }

/*
 * If we've got anything left, read the final partial track.
 */

  if (ns)
    return (DosDevIOCtl (mem, (UCHAR *)&dp, IOCTL_READ, IOCTL_CATEGORY,
                         disk_handle));
  else
    return (ret_code);

}



#ifdef CHECK_ZERO

/***    write_scratch_chkdsk - write a run of sectors to disk (chkdsk only)
 *
 *      This function is used to detect writes to sector 0.
 */
USHORT write_scratch_chkdsk (ULONG psn,
                             void *mem,
                             USHORT ns)
{
    if (fCheckZero && psn == 0) {
        USHORT us;
        static char szErroStr[] =
"\r\n CHKDSK FATAL ERROR: attempting to write sector 0\r\n";  /*@53562*/

        DosWrite(1, szErroStr, sizeof( szErroStr ), &us );    /*@53562*/
        chk_exit_error(WRITE_ERROR);                          /*@53562*/

    }

    return write_scratch (psn, mem, ns);
}

#endif

/***    write_scratch - write a run of sectors to disk.
 *
 *      This function is used to write a run of sectors to the disk.
 *      It is called write_scratch because it just writes the contents
 *      of a buffer to disk without performing any memory manipulation.
 *
 *      This function must use DosDevIOCtl instead of DosWrite, since
 *      the file system would hotfix on a hard error if DosWrite was
 *      used;  that would tend to confuse CHKDSK.
 *
 *      write_scratch (psn, mem, ns)
 *
 *      ENTRY           psn - sector number to start at
 *                      mem - address of memory containing data to write
 *                      ns  - number of sectors to write
 *
 *      EXIT            Returns zero if success, or OS/2 error code
 *
 *      CALLS           DosDevIOCtl
 *
 *      EFFECTS         Writes memory contents to disk
 */
USHORT write_scratch (psn, mem, ns)
  ULONG psn;
  void *mem;
  USHORT ns;
{
  USHORT ret_code;
  ULONG phys_sector;

#ifdef TRACE_WRITES
  fprintf (stderr, "write_scratch (%lx, [buffer], %x)\n", psn, ns);
  fflush (stderr);
#endif

/*
 * Some disk drivers/controllers do not range-check sector numbers.
 * To avoid large problems, we check the sector ourselves and return
 * "general failure" if the sector is out-of-range.
 * (PTR B700203)
 */

  if (psn >= number_of_sectors)
    return (ERROR_GEN_FAILURE);

#ifdef DEBUG
  if (psn < SEC_SUPERB)
    i = 1;                      /* place to set a breakpoint for stomping boot */
#endif

/*
 * Invalidate Pinball's cache, because otherwise it'll just get out of date,
 * and screw things up later.
 */
  if (!chk_fatboot) {
    ULONG ulBlock, ulFirstBlock, ulLastBlock;   /* Sector numbers. */
    USHORT usDataLenReturned, usParmLenReturned;

    /*
     * We only need to invalidate one sector per block, so loop for each block.
     */
    ulFirstBlock = ROUND_DOWN(psn, SPB);
    ulLastBlock = ROUND_DOWN(psn + ns, SPB);
    for (ulBlock=ulFirstBlock; ulBlock <= ulLastBlock; ulBlock += SPB) {

      /* Invalidate cache for block containing this sector. */
      ret_code = DosFSCtl(
                  (PVOID) 0,                            /* pData */
                  (USHORT) 0,                           /* cbData */
                  (PVOID) & usDataLenReturned,          /* pcbData */
                  (PVOID) & ulBlock,                    /* pParms */
                  sizeof ulBlock,                       /* cbParms */
                  (PVOID) & usParmLenReturned,          /* pcbParms */
                  FSC_INVALIDATE,                       /* usFunCode */
                  (PVOID) 0,                            /* pszRoute */
                  disk_handle,                          /* hf */
                  (USHORT)FSCTL_HANDLE,                 /* usRouteMethod */
                  0L);                                  /* reserved */

      if (ret_code && (ret_code != ERROR_NOT_SUPPORTED) &&
          (ret_code != ERROR_INVALID_FUNCTION))
        return(ret_code);
    } /* for */
  } /* scope of ulBlock, etc. */

/*
 * Compute physical cylinder/head/sector corresponding to this run.
 */

  phys_sector = partition_offset + psn;

  dp.sect = (USHORT)(phys_sector % prm.spt);
  dp.head = (USHORT)((phys_sector / prm.spt) % prm.nh);
  dp.cyl =  (USHORT)(phys_sector / (prm.spt * prm.nh));
  dp.nsect = ns;

/*
 * Split the read into chunks, if necessary;  the IOCTL we use isn't
 * capable of crossing track boundaries.
 */

  if (dp.sect+dp.nsect >= prm.spt) {    /* track overlap */
    dp.nsect = prm.spt - dp.sect;       /* read up to track boundary */

    ret_code = DosDevIOCtl (mem, (UCHAR *)&dp, IOCTL_WRITE, IOCTL_CATEGORY,
                            disk_handle);
    if (ret_code) return (ret_code);

    mem = (PUCHAR)((PUCHAR)mem + dp.nsect * SECSIZE);/* skip past the stuff we wrote */
    ns -= dp.nsect;                     /* decrement sectors left to go */
    dp.nsect = prm.spt;                 /* write whole tracks now */
    dp.sect = 0;                        /* write at beginning of each track */

    while (ns > prm.spt) {              /* write the whole tracks */
      dp.head++;                        /* go to next head */
      if (dp.head == prm.nh) {          /* crossed cylinder boundary */
        dp.head = 0;                    /* start at first head again */
        dp.cyl++;                       /* go to next cylinder */
      }
      ret_code = DosDevIOCtl (mem, (UCHAR *)&dp, IOCTL_WRITE,
                              IOCTL_CATEGORY, disk_handle);
      if (ret_code) return (ret_code);
      mem = (PUCHAR)((PUCHAR)mem + dp.nsect * SECSIZE);/* skip past the stuff we wrote */
      ns -= dp.nsect;                   /* decrement sectors left to go */
    }

    dp.head++;                          /* go to next head */
    if (dp.head == prm.nh) {            /* crossed cylinder boundary */
      dp.head = 0;                      /* start at first head again */
      dp.cyl++;                         /* go to next cylinder */
    }
    dp.nsect = ns;                      /* number of sectors in partial track */
  }

/*
 * If we've got anything left, read the final partial track.
 */

  if (ns)
    return (DosDevIOCtl (mem, (UCHAR *)&dp, IOCTL_WRITE, IOCTL_CATEGORY,
                         disk_handle));
  else
    return (ret_code);
}

/***    hold_signals - disallow Ctrl-Break interrupts
 *
 *      This function is called to prevent the user from interrupting
 *      FORMAT while it's writing the file system;  the user should
 *      not be able to create a half-filesystem.  It's okay for the
 *      user to break while formatting tracks;  although some data
 *      will be lost, he can run CHKDSK to get back what hasn't been
 *      erased yet.
 *
 *      hold_signals (void)
 *
 *      ENTRY           No parameters
 *
 *      EXIT            No return value
 *
 *      CALLS           DosHoldSignal
 *
 *      WARNINGS        None
 *
 *      EFFECTS         Shuts off Ctrl-Break interrupts
 */
void hold_signals (void)
{
  DosHoldSignal (1);
}

/***    allow_signals - allow Ctrl-Break interrupts
 *
 *      This function is called to re-enable Ctrl-Break interrupts,
 *      when FORMAT is finished writing the file system to disk.
 *
 *      allow_signals (void)
 *
 *      ENTRY           No parameters
 *
 *      EXIT            No return value
 *
 *      CALLS           DosHoldSignal
 *
 *      WARNINGS        None
 *
 *      EFFECTS         Turns on Ctrl-Break interrupts
 */
void allow_signals (void)
{
  DosHoldSignal (0);
}

/***    get_time - get the current time from InfoSeg
 *
 *      This function is called to get the current time (in seconds since
 *      01-01-1970) to be stamped on the new root directory entries.
 *      It calls DosGetInfoSeg and returns the first ULONG in the segment.
 *
 *      get_time ()
 *
 *      ENTRY           No parameters
 *
 *      EXIT            Returns current time
 *
 *      WARNINGS        None
 *
 *      EFFECTS         None
 */
ULONG get_time ()
{
  SEL sel, sel2;

  DosGetInfoSeg (&sel, &sel2);
  return (*(ULONG far *)MAKEP (sel, 0));
}

/***    check_stdout_redir - find out if standard output is redirected
 *
 *      This function calls the DosQHandType API on the standard output
 *      handle to determine whether it has been redirected.  If so, the
 *      global variable stdout_redir is made non-zero.  After this function
 *      is called, that variable can be used to determine whether or not
 *      to output running displays such as FORMAT's and CHKDSK's percent-
 *      done displays.
 *
 *      check_stdout_redir (void)
 *
 *      ENTRY           No parameters
 *
 *      EXIT            No return value
 *                      stdout_redir is set non-zero if stdout is a file
 *
 *      CALLS           DosQHandType
 *
 *      WARNINGS        Call this function before using stdout_redir
 *
 *      EFFECTS         Sets global variable stdout_redir
 */
void check_stdout_redir (void)
{
  USHORT handtype, devattr;

  DosQHandType (1, &handtype, &devattr);
  stdout_redir = (handtype != 1);
}

/***    error_msg - display error message
 *
 *      This function is called to display a message to the standard
 *      error device.  It calls computmsg() to display the message.
 *      To display messages with substitution strings, computmsg()
 *      must be called manually.
 *
 *      This function only works with message IDs for Pinball messages;
 *      it will not work for standard OS/2 messages that live in OSO001.MSG.
 *
 *      error_msg (msg)
 *
 *      ENTRY           msg - message ID
 *
 *      EXIT            No return value
 *
 *      CALLS           computmsg
 */
void error_msg (msg)
  USHORT msg;
{
  computmsg (NULL, 0, msg, msgfilename, STDERR_CODE, NO_RESPONSE, NULL, 0);
}

/***    out_msg - output a message
 *
 *      This function is called to display a message to the standard
 *      output device.  It calls computmsg() to display the message.
 *      A substitution string for the message can be passed.  To display
 *      a message which requires more than one substitution string,
 *      computmsg() must be called manually.
 *
 *      This function only works with message IDs for Pinball messages;
 *      it will not work for standard OS/2 messages that live in OSO001.MSG.
 *
 *      out_msg (msg, subst)
 *
 *      ENTRY           msg   - message ID
 *                      subst - pointer to substitution string, or NULL if none
 *
 *      EXIT            No return value
 *
 *      CALLS           computmsg
 */
void out_msg (msg, subst)
  USHORT msg;
  UCHAR *subst;
{
  computmsg ((subst) ? &subst : NULL,
             (subst) ? 1 : 0, msg, msgfilename, STDOUT_CODE,
             NO_RESPONSE, NULL, 0);
}

/***    exit_insf_mem - global "exit if insufficient memory" routine
 *
 *      This function is called by make_bad if it can't get enough memory
 *      for a bad block list.  It terminates the program, displaying the
 *      message "Insufficient memory".
 *
 *      BUGBUG - check the return code!
 *      BUGBUG - isn't this a little severe?
 *
 *      exit_insf_mem (void)
 *
 *      ENTRY           No parameters
 *
 *      EXIT            No return value
 *
 *      CALLS           error_msg
 *                      exit
 *
 *      WARNINGS        Does not return
 *
 *      EFFECTS         Terminates program
 */
void exit_insf_mem (void)
{
  computmsg (NULL, 0, rctomid (ERROR_NOT_ENOUGH_MEMORY), "OSO001.MSG",
             STDERR_CODE, NO_RESPONSE, NULL, 0);
  exit (1);
}

/***    make_bad - add a sector to the bad block list
 *
 *      This function is called when a bad sector is found.  It is
 *      is passed a physical sector number, and adds that number to
 *      the bad block list in memory.  If the memory list is not long
 *      enough, more memory is allocated via realloc(), extending in
 *      2K chunks (each enough for 512 sector numbers). The global
 *      variable "num_bad_sectors" is updated to reflect the number
 *      of bad sectors found in the file.
 *
 *      If there is not enough memory to hold the list, the sector is
 *      not marked bad.
 *
 *      Also, we write garbage to the bad sector, in the hope that
 *      this will wipe out the error (making the other sectors in
 *      that block easier to access).
 *
 *      make_bad (sect)
 *
 *      ENTRY           sect - physical sector number that is bad
 *
 *      EXIT            No return value
 *
 *      CALLS           glb_exit_error
 *                      realloc
 *                      clr_bit
 *
 *      EFFECTS         Adds "sect" to memory list
 *                      Updates num_bad_sectors
 *                      Allocates memory
 *                      Modifies bitmap
 */
void make_bad (sect)
  ULONG sect;
{
  if (!(num_bad_sectors % 512)) {       /* need more memory */
    bad_list = (PULONG)realloc (bad_list, (size_t)((num_bad_sectors + 512) * 4));
    if (!bad_list)                      /* no more memory available */
      exit_insf_mem ();                 /* terminate program */
  }
  bad_list [num_bad_sectors++] = sect;  /* store phys. sector number */
  clr_bit (sect);
  write_scratch( sect, scratchptr, 1 );
}

/***    add_bad_block - allocate a new block on the bad list
 *
 *      This function is called when the bad block list is full
 *      and needs to be extended.  It looks in the bit map for a
 *      block of free sectors, and tries writing to them to make
 *      sure that they aren't bad.  If they are, those sectors are
 *      remembered as bad and the process is tried again.
 *
 *      If no sectors are available, a value of zero is returned.
 *
 *      add_bad_block (blk, early)
 *
 *      ENTRY           blk   - pointer to block to store new sector number in
 *                      early - non-zero if sectors should be allocated near the
 *                              beginning of the disk;  zero if they should be
 *                              allocated near the middle
 *
 *      EXIT            *blk = sector number of allocated block
 *                      Returns that sector number, or 0L if no disk space
 *
 *      CALLS           allocate_early_sectors
 *                      allocate_middle_sectors
 *                      make_bad
 *                      write_scratch
 *
 *      EFFECTS         Modifies bit map
 *                      May add to bad sector list
 *                      May increment num_bad_sectors
 */
ULONG add_bad_block (blk, early)
  ULONG *blk;
  USHORT early;
{
  ULONG sect;
  USHORT i;

  while (TRUE) {
    sect = early ? allocate_early_sectors (SPB, SPB)
                 : allocate_middle_sectors (SPB, SPB);
    if (!sect)
      return (0L);
    if (write_scratch (sect, zeros, SPB)) {
      for (i=0; i<SPB; i++)
        make_bad (sect+i);
    }
    else
      break;
  }

  *blk = sect;
  return (sect);
}

/***    save_bad - add bad sector numbers to the bad list on disk
 *
 *      This function is called after a file has been recovered.  It
 *      looks through the memory list of bad sectors, and adds those
 *      sectors to the bad block list on disk.  This involves chaining
 *      through the bad block list to the end, and then adding the
 *      sector numbers, allocating further blocks if necessary.  The
 *      superblock must also be updated, to correctly reflect the number
 *      of bad blocks on the disk.  After the sectors are added to the
 *      bad block list, corresponding bits in the bit maps are cleared
 *      to prevent the file system from re-using them.
 *
 *      If insufficient memory or disk space are available, the sectors
 *      are not marked as bad.
 *
 *      save_bad (void)
 *
 *      ENTRY           No parameters
 *                      bad_list points to an array of bad sector numbers
 *                      num_bad_sectors contains the number of bad sectors found
 *
 *      EXIT            No return value
 *
 *      CALLS           add_bad_block
 *                      free
 *                      malloc
 *                      read_scratch
 *                      write_scratch
 *
 *      EFFECTS         Updates disk
 */
void save_bad (void)
{
  ULONG *p, nbad, sect;
  USHORT i;

  if ((p=(ULONG *)malloc (SPB * SECSIZE)) == NULL)
    return;

  if ((zeros=malloc (SPB * SECSIZE)) == NULL) {
    free (p);
    return;
  }

  if (read_scratch ((ULONG)SEC_SUPERB, &sb, 1)) {
    free (p);
    free (zeros);
    return;
  }

  memset (zeros, '\0', SPB * SECSIZE);

  /* Add the bad blocks we found to the filesystem's list */

  sect = sb.rspBadBlkList.lsnMain;                      /* start at top of bad block list */
  read_scratch (sect, p, SPB);          /* read the first block */
  for (nbad = sb.culNumBadSects; nbad > 511L; nbad -= 511L) {   /* while mid-chain */
    sect = *p;                          /* chain to fwd link */
    read_scratch (sect, p, SPB);        /* read fwd block */
  }

  for (i=(USHORT)nbad; i < (USHORT)(num_bad_sectors+nbad); i++) {   /* for all bad blocks we found */
    p [(i % 511) + 1] = bad_list [i-nbad];      /* store one in the list */
    clr_bit (bad_list [i-nbad]);        /* mark as in use */
    if (i % 511 == 510) {               /* just filled up this block */
      add_bad_block (p, FALSE);         /* allocate more space, middle */
      write_scratch (sect, p, SPB);     /* save this block */
      sect = *p;                        /* start on next block */
      memset ((UCHAR *)p, '\0', SPB * SECSIZE);
    }
  }
  write_scratch (sect, p, SPB);         /* save the last one */

#ifdef TWO_MAPS
  /* Do exactly the same thing with the secondary filesystem copy,
   * except allocate sectors early instead of middle.
   */

  sect = sblk.rspBadBlkList.lsnSpare;   /* start at top of secondary bad block list */
  read_scratch (sect, p, SPB);
  for (nbad = sblk.culNumBadSects; nbad > 511L; nbad -= 511L) {
    sect = *p;
    read_scratch (sect, p, SPB);
  }

  for (i=nbad; i<num_bad_sectors+nbad; i++) {   /* for all bad blocks we found */
    p [(i % 511) + 1] = bad_list [i-nbad];      /* store one in the list */
    if (i % 511 == 510) {               /* just filled up this block */
      add_bad_block (p, TRUE);          /* allocate more space, early */
      write_scratch (sect, p, SPB);     /* save this block */
      sect = *p;                        /* start on next block */
      memset ((UCHAR *)p, '\0', SPB * SECSIZE);
    }
  }
  write_scratch (sect, p, SPB);         /* save the last one */
#endif  /*** TWO_MAPS ***/

  sb.culNumBadSects += num_bad_sectors; /* update filesystem's count */
  write_scratch ((ULONG)SEC_SUPERB, &sb, 1);    /* save the superblock */

  free (zeros);
  free (p);
}

#ifdef C52
/***    free_arena - deallocate all "malloc"'d memory
 *
 *      This function is called when a utility exits to deallocate
 *      all memory that was allocated with malloc() during its
 *      execution.  This is done to make sure that all utility-
 *      related memory gets deallocated when the utility is invoked
 *      by the startup process, which never exits.
 *
 *      The function looks at the "_abrktb" table owned by the C
 *      runtime.  This is an array of length/selector pairs.  Each
 *      non-null selector in the array is freed by this function,
 *      except the first (which is the DLL's default data segment,
 *      which will be freed when DosFreeModule is called).
 *
 *      Memory allocated with halloc() (huge memory) is not affected
 *      by this function.
 *
 *      free_arena ()
 *
 *      ENTRY           No parameters
 *
 *      EXIT            No return value
 *
 *      CALLS           DosFreeSeg
 *
 *      WARNINGS        All memory allocated with malloc becomes invalid
 *                      C runtime heap manager cannot be trusted after this call
 *
 *      EFFECTS         Frees all normal heap memory
 */
void free_arena (void)
{
  extern USHORT _abrktb [];
  USHORT i;

  for (i = 3; i < 64; i += 2)           /* skip 1st element, land on selectors */
    if (_abrktb [i])                    /* if non-null selector */
      DosFreeSeg (_abrktb [i]);         /* free the segment */
    else
      break;
}
#endif
