#define VERSION       1.12
#define LAST_CHANGED  901212

/*--------------------------------------*
 | File: DT.c - Rev. 1.12 901212        |
 +--------------------------------------+
 | DT: disk test, a la Norton Utilities |
 +--------------------------------------+---------------*
 | Author: Maurizio Loreti, aka MLO or I3NOO.           |
 | Address: University of Padova - Deparment of Physics |
 |          Via F. Marzolo, 8 - 35131 PADOVA - Italy    |
 | Phone: (39)(49) 844-313        FAX: (39)(49) 844-245 |
 | E-Mail: LORETI at IPDINFN (BITNET) or VAXFPD::LORETI |
 |         (DECnet). VAXFPD is node 38.257, i.e. 39169. |
 | Home: Via G. Donizetti, 6 - 35010 CADONEGHE (Padova) |
 *------------------------------------------------------*/

/**
 | #include's
**/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <dos.h>
#include <exec/types.h>
#include <exec/exec.h>
#include <devices/trackdisk.h>
#include "mlo.h"

/**
 | #define's. The first two should be in TRACKDISK.H, the fourth in
 | STDIO.H for ANSI compilers - but are not there in the Lattice
 | Include files. TD_CYL is the number of bytes per cylinder.
**/

#define NUMCYLS       80
#define NUMHEADS      2
#define TD_CYL        (TD_SECTOR * NUMSECS)
#define FILENAME_MAX  108
#define ON            1
#define OFF           0

/**
 | A structure definition for directory entries; for Lattice C, only one
 | call to dfind/dnext can be active at a time: so, scanning directories,
 | we check all files first and, only then, all subdirectories one by one.
**/

typedef struct sdirEntry {
  char *name;
  struct sdirEntry *next;
} dirEntry;

/**
 | Global variables
**/

struct MsgPort *diskPort = NULL;      /* Various Intuition pointers */
struct IOExtTD *diskReq = NULL;       /*   for disk input */
BYTE *diskBuffer = NULL;              /* Disk input buffer */
ULONG diskChangeCount;                /* Disk change count */
int nErFil = 0;                       /* Total number of errors */
int nDirs = 0;                        /* Nr. of checked directories */
int nFiles = 0;                       /* Nr. of checked files */
Boolean fromWorkBench;                /* Scheduled from WB or CLI ? */
Boolean abortDT = False;              /* True when CTRL-C hit */

/**
 | The ANSI procedure prototypes
**/

Boolean checkBreak(void);
void checkDir(char *path, Boolean root);
void checkFile(char *name);
void cleanup(int code);
void motor(int action);
void pcl(int n, char *fmt, ...);
void readCyl(int cyl, int hd);
void seekFullRange(SHORT howmany);
void syntax(void);

main(
  int argc,
  char **argv
){
  int drive, cyl, head;
  SHORT error;
  char driveName[5];

/**
 | To be called from CLI, with DT DFx[:] ; if called from the
 | Workbench, a prompt for the floppy unit is sent to the console
 | window (created from the Lattice initialisation routine).
 |  Pass 1: a seek over full range;
 |  Pass 2: read all cylinders;
 |  Pass 3: read all files record by record.
 | But first, check the input arguments and open Intuition.
**/

  fprintf(stdout, "\n\t\"DT\" - v%.2f - MLO %d\n", VERSION, LAST_CHANGED);

  if (argc == 0) {
    do {
      fprintf(stdout, "\nDrive to test (DF0-DF4) ? ");
      fgets(driveName, sizeof(driveName), stdin);
    } while (strnicmp(driveName, "df", 2)   ||
             (drive = atoi(driveName + 2)) < 0   ||   drive > 4);
    fromWorkBench = True;
  } else {
    fromWorkBench = False;
    if (argc != 2   ||   strnicmp(*++argv, "df", 2))    syntax();
    if ((drive = atoi(*argv+2)) < 0   ||   drive > 4)   syntax();
  }

  if ((diskBuffer = (BYTE *) AllocMem(TD_CYL, MEMF_CHIP)) == NULL) {
    fprintf(stderr, "Can't allocate chip memory ...\n");
    cleanup(SYS_ABORT_CODE);
  }

/**
 | Pass 1
**/

  if (!(diskPort = (struct MsgPort *) CreatePort(0, 0))) {
    fprintf(stderr, "Can't create I/O port ...\n");
    cleanup(SYS_ABORT_CODE);
  }

  if (!(diskReq = (struct IOExtTD *)
      CreateExtIO(diskPort, sizeof(struct IOExtTD))) ) {
    fprintf(stderr, "Can't obtain I/O request block ...\n");
    cleanup(SYS_ABORT_CODE);
  }

  sprintf(driveName, "DF%d:", drive);
  if (error = OpenDevice(TD_NAME, drive, diskReq, 0)) {
    fprintf(stderr,
            "Error 0x%X returned by OpenDevice for drive %s ...\n",
            error, driveName);
    cleanup(SYS_ABORT_CODE);
  }

  diskReq->iotd_Req.io_Command = TD_CHANGENUM;
  DoIO(diskReq);
  diskChangeCount = diskReq->iotd_Req.io_Actual;
  fprintf(stdout, "\nChange number for drive %s is %d;\n",
          driveName, diskChangeCount);
  motor(ON);
  seekFullRange(1);

/**
 | Pass 2
**/

  fprintf(stdout, "Checking all disk tracks:\n");
  for (cyl=0; cyl<NUMCYLS; cyl++) {
    for (head=0; head<NUMHEADS; head++) {
      fprintf(stdout, "  reading cylinder %d, head %d ...\r", cyl, head);
      readCyl(cyl, head);
        if (error = diskReq->iotd_Req.io_Error) {
        fprintf(stdout,
                "  Error 0x%X detected for cylinder %d, head %d\n",
                error, cyl, head);
        nErFil++;
      }
    }

/**
 | Test for CTRL-C
**/

    if (checkBreak()) {
      fprintf(stdout, "\n*** DT: BREAK ***\n");
      motor(OFF);
      cleanup(SYS_NORMAL_CODE);
    }
  }
  motor(OFF);
  if (nErFil) {
    fprintf(stdout, "%d hard errors detected reading drive %s.\n",
            nErFil, driveName);
    cleanup(SYS_ABORT_CODE);
  } else {
    fprintf(stdout, "  no errors detected reading drive %s.\n", driveName);
    nErFil = 0;
  }

/**
 | Pass 3
**/

  fprintf(stdout, "Checking all files in drive %s\n", driveName);
  checkDir(driveName, True);
  pcl(2, "%d director%s and %d file%s checked: %d error%s detected.",
      nDirs, (nDirs == 1 ? "y" : "ies"),
      nFiles, (nFiles == 1 ? "" : "s"),
      nErFil, (nErFil == 1 ? "" : "s"));
  cleanup(SYS_NORMAL_CODE);
}

Boolean checkBreak(void)
{
  long SetSignal();

  if (SetSignal(0L, 0x1000L)  &  0x1000L)   return True;
    else                                    return False;
}

void checkDir(
  char *path,
  Boolean root
){
  struct FILEINFO info;
  char fileName[FILENAME_MAX];
  int error;
  dirEntry *rdE = NULL;
  dirEntry *pdE;

/**
 | Check a directory; path contains the full directory name, and root is
 | non zero for the root directory. We scan all directory files, checking
 | 'true' files at first and all subdirectories at the end, one by one.
**/

  nDirs++;
  pcl(1, "  checking files in%s directory %s ...",
      (root ? " root" : ""), path);
  strcpy(fileName, path);
  strcat(fileName, (root ? "#?" : "/#?"));

  error = dfind(&info, fileName, True);
  while (!error   &&   !abortDT) {
    strcpy(fileName, path);
    if (!root) strcat(fileName, "/");
    strcat(fileName, info.fib_FileName);
    if (info.fib_DirEntryType < 0) {
      checkFile(fileName);
    } else {
      if ((pdE = malloc(sizeof(dirEntry))) == NULL) {
        fprintf(stderr, "Can't allocate heap memory ...\n");
        cleanup(SYS_ABORT_CODE);
      }
      if ((pdE->name = malloc(strlen(fileName) + 1)) == NULL) {
        fprintf(stderr, "Can't allocate heap memory ...\n");
        cleanup(SYS_ABORT_CODE);
      }
      strcpy(pdE->name, fileName);
      pdE->next = rdE;
      rdE = pdE;
    }
    error = dnext(&info);
    if (abortDT = checkBreak()) {
      pcl(1, "*** DT: BREAK ***");
    }
  }

  while (rdE != NULL) {
    if (!abortDT) checkDir(rdE->name, False);
    free(rdE->name);
    pdE = rdE->next;
    free(rdE);
    rdE = pdE;
  }
}

void checkFile(
  char *name
){
  struct FileHandle *pFH;
  long ier;

/**
 | Check a file, opening and reading record by record; this procedure
 | is performed using the standard AmigaDOS disk file interface.
**/

  nFiles++;
  pcl(0, "    file %s ...", name);
  if ((pFH = (struct FileHandle *) Open(name, MODE_OLDFILE)) == NULL) {
    pcl(1, "* Error opening file \"%s\": file not found.", name);
    nErFil++;
  } else {
    while ((ier = Read(pFH, diskBuffer, TD_CYL)) > 0) { }
    if (ier < 0) {
      pcl(1, "* AmigaDOS error %d reading file \"%s\".", IoErr(), name);
      nErFil++;
    }
  }
  Close(pFH);
}

void cleanup(
  int code
){
  if (diskBuffer != NULL) {
    FreeMem(diskBuffer, TD_CYL);
  }
  if (diskReq != NULL) {
    CloseDevice(diskReq);
    DeleteExtIO(diskReq, sizeof(struct IOExtTD));
  }
  if (diskPort != NULL)   DeletePort(diskPort);

  if (fromWorkBench) {
    int i;
    fprintf(stdout, "Strike <CR> to continue ...");
    while ( (i = getchar()) != '\n'   &&   i != EOF)  { }
  }
  exit(code);
}

void motor(
  int action
){
  diskReq->iotd_Req.io_Length = action;
  diskReq->iotd_Req.io_Command = TD_MOTOR;
  DoIO(diskReq);
}

void pcl(
  int n,
  char *fmt,
  ...
){
  va_list vl;
  static length = 0;
  int nc;

/**
 | What the hell is the delete-to-end-of-line sequence on the Amiga?
 | The AmigaDOS manual refers to the ANSI sequence <ESC>[1K - that do
 | not work in my NewCon windows; so I wrote this simple interface. When
 | overprinting, we check if the length of the new line is greater than
 | the old one - if not, we output some blanks. "n" is the number of
 | newlines at the end, or zero for no newline but carriage return.
**/

  va_start(vl, fmt);
  nc = vfprintf(stdout, fmt, vl);
  va_end(vl);
  length -= nc;
  if (length > 0) fprintf(stdout, "%*s", length, " ");
  if (n) {
    while (n--) fprintf(stdout, "\n");
    length = 0;
  } else {
    fprintf(stdout, "\r");
    length = nc;
  }
}

void readCyl(
  int cyl,
  int hd
){
  LONG offset;

  diskReq->iotd_Req.io_Length = TD_CYL;
  diskReq->iotd_Req.io_Data = (APTR) diskBuffer;
  diskReq->iotd_Req.io_Command = ETD_READ;
  diskReq->iotd_Count = diskChangeCount;
  offset = TD_SECTOR * (NUMSECS * (hd + NUMHEADS * cyl));
  diskReq->iotd_Req.io_Offset = offset;
  DoIO(diskReq);
}

void seekFullRange(
  SHORT howmany
){
  int i;
  SHORT error;

  for (i=1; i<=howmany; i++) {
    diskReq->iotd_Req.io_Offset =
          ((NUMCYLS - 1) * NUMSECS * NUMHEADS - 1) * TD_SECTOR;
    diskReq->iotd_Req.io_Command = TD_SEEK;
    DoIO(diskReq);
    if (error = diskReq -> iotd_Req.io_Error) {
      fprintf(stdout, "\nSeek cycle %d, error 0x%X ...\n", i, error);
      cleanup(SYS_ABORT_CODE);
    }

    diskReq->iotd_Req.io_Offset = 0;
    diskReq->iotd_Req.io_Command = TD_SEEK;
    DoIO(diskReq);
    if (error = diskReq->iotd_Req.io_Error) {
      fprintf(stdout, "\nSeek cycle %d, error 0x%X ...\n", i, error);
      cleanup(SYS_ABORT_CODE);
    }
  }
  fprintf(stdout, "no errors detected seeking over full disk range.\n");
}

void syntax(void)
{
  fprintf(stdout,
        "\n\tUsage:\t\tDT DFn, where 'n' (0-4) is the drive number.\n");
  fprintf(stdout,
        "\tPurpose:\tDisk test.\n\n");
  cleanup(SYS_NORMAL_CODE);
}
