/*
 * (C) Copyright 2002, Brian Knittel.
 * You may freely use this program, but: it offered strictly on an AS-IS, AT YOUR OWN
 * RISK basis, there is no warranty of fitness for any purpose, and the rest of the
 * usual yada-yada. Please keep this notice and the copyright in any distributions
 * or modifications.
 *
 * This is not a supported product, but I welcome bug reports and fixes.
 * Mail to sim@ibm1130.org
 */

// checkdisk - validates and optionally dumps an IBM1130 DMS2 disk image file
//
// Usage:
//      checkdisk [-f] [-d cyl.sec|abssec] [-n count] filename
//
// Examples:
//      checkdisk file.dsk
//              report any misnumbered sectors in file.dsk
//
//      checkdisk -f file.dsk
//              report and fix any misnumbered sectors
//
//      checkdisk -d 198.0 file.dsk
//              dump cylinder 198 sector 0
//
//      checkdisk -d 0 file.dsk
//              dump absolute sector 0
//
//      checkdisk -d 198.0 -n 4 file.dsk
//              dump 4 sectors starting at m.n
// -----------------------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "util_io.h"

#ifdef _WIN32
#  include <io.h> 
#else
    long filelength (int fno);
#   include <sys/types.h>
#   include <sys/stat.h>
#endif

#ifndef TRUE
#  define BOOL  int
#  define TRUE  1
#  define FALSE 0
#endif

#define DSK_NUMWD   321             /* words/sector */
#define DSK_NUMSC   4               /* sectors/surface */
#define DSK_NUMSF   2               /* surfaces/cylinder */
#define DSK_NUMCY   203             /* cylinders/drive */
#define DSK_NUMDR   5               /* drives/controller */
#define DSK_SIZE (DSK_NUMCY * DSK_NUMSF * DSK_NUMSC * DSK_NUMWD)  /* words/drive */

char *usestr  = "Usage: checkdisk [-f] [-d cyl.sec|abssec] [-n count] diskfile";
char *baddisk = "Cannot fix this";

void bail (char *msg);
char *lowcase (char *str);

int main (int argc, char **argv)
{
    FILE *fp;
    char *fname = NULL, *arg, *argval;
    int i, j, cyl, sec, pos, asec, retry, nbad = 0, nfixed = 0, nline;
    BOOL fixit = FALSE, dump = FALSE;
    int dsec, nsec = 1;
    unsigned short wd, buf[DSK_NUMWD];

    for (i = 1; i < argc;) {
        arg = argv[i++];
        if (*arg == '-') {
            arg++;
            lowcase(arg);
            while (*arg) {
                switch (*arg++) {
                    case 'f':
                        fixit = TRUE;
                        break;

                    case 'd':
                        dump = TRUE;

                        if (i >= argc)
                            bail(usestr);

                        argval = argv[i++];
                        if (strchr(argval, '.') != NULL) {
                            if (sscanf(argval, "%d.%d", &cyl, &sec) != 2)
                                bail(usestr);

                            dsec = cyl*(DSK_NUMSF*DSK_NUMSC) + sec;
                        }
                        else if (sscanf(argval, "%d", &dsec) != 1)
                            bail(usestr);

                        if (dsec < 0 || dsec >= (DSK_NUMCY*DSK_NUMSF*DSK_NUMSC))
                            bail("No such sector");

                        break;

                    case 'n':
                        if (i >= argc)
                            bail(usestr);

                        argval = argv[i++];
                        if (sscanf(argval, "%d", &nsec) != 1)
                            bail(usestr);

                        if (nsec <= 0)
                            bail(usestr);

                        break;

                    default:
                        bail(usestr);
                }
            }
        }
        else if (fname == NULL)
            fname = arg;
        else
            bail(usestr);
    }

    if (fname == NULL)
        bail(usestr);

    if ((fp = fopen(fname, "rb+")) == NULL) {
        perror(fname);
        return 1;
    }

    if (filelength(fileno(fp)) != 2*DSK_SIZE) {
        fprintf(stderr, "File is wrong length, expected %d\n", DSK_SIZE);
        bail(baddisk);
    }

    for (cyl = 0; cyl < DSK_NUMCY; cyl++) {
        for (sec = 0; sec < (DSK_NUMSF*DSK_NUMSC); sec++) {
            retry = 1;
again:
            asec = cyl*(DSK_NUMSF*DSK_NUMSC) + sec;
            pos  = asec*2*DSK_NUMWD;

            if (fseek(fp, pos, SEEK_SET) != 0) {
                fprintf(stderr, "Error seeking to pos %x\n", pos);
                bail(baddisk);
            }

            if (fxread(&wd, sizeof(wd), 1, fp) != 1) {
                fprintf(stderr, "Error reading word at abs sec %x, cyl %x, sec %x at offset %x\n", asec, cyl, sec, pos);
                bail(baddisk);
            }

            if (wd != asec) {
                fprintf(stderr, "Bad sector #%x at abs sec %x, cyl %x, sec %x at offset %x\n", wd, asec, cyl, sec, pos);
                nbad++;

                if (fixit) {
                    if (fseek(fp, pos, SEEK_SET) != 0) {
                        fprintf(stderr, "Error seeking to pos %x\n", pos);
                        bail(baddisk);
                    }

                    if (fxwrite(&asec, sizeof(wd), 1, fp) != 1) {
                        fprintf(stderr, "Error writing sector # to abs sec %x, cyl %x, sec %x at offset %x\n", asec, cyl, sec, pos);
                        bail(baddisk);
                    }

                    if (retry) {
                        retry = 0;
                        nfixed++;
                        goto again;
                    }
    
                    fprintf(stderr, "Failed after retry\n");
                    bail(baddisk);
                }
            }
        }
    }

    if (nbad)
        printf("%d bad sector mark%s %s\n", nbad, (nbad == 1) ? "" : "s", fixit ? "fixed" : "found");
    else if (! dump)
        printf("All sector marks OK\n");

    if (! dump)
        return 0;

    pos  = dsec*2*DSK_NUMWD;
    if (fseek(fp, pos, SEEK_SET) != 0) {
        fprintf(stderr, "Error seeking to pos %x\n", pos);
        bail(baddisk);
    }

    for (i = 0; i < nsec; i++) {
        cyl = dsec / (DSK_NUMSF*DSK_NUMSC);
        sec = dsec - cyl*(DSK_NUMSF*DSK_NUMSC);

        if (fxread(&buf, sizeof(buf[0]), DSK_NUMWD, fp) != DSK_NUMWD) {
            fprintf(stderr, "Error reading abs sec %x, cyl %x, sec %x at offset %x\n", dsec, cyl, sec, pos);
            bail(baddisk);
        }

        printf("\nSector %d.%d - %d - /%04x label %04x\n", cyl, sec, dsec, dsec, buf[0]);
        for (nline = 0, j = 1; j < DSK_NUMWD; j++) {
            printf("%04x", buf[j]);
            if (++nline == 16) {
                putchar('\n');
                nline = 0;
            }
            else
                putchar(' ');
        }

        dsec++;
    }

    return 0;
}

void bail (char *msg)
{
    fprintf(stderr, "%s\n", msg);
    exit(1);
}

/* ------------------------------------------------------------------------ 
 * lowcase - force a string to lower case (ASCII)
 * ------------------------------------------------------------------------ */

char *lowcase (char *str)
{
    char *s;

    for (s = str; *s; s++) {
        if (*s >= 'A' && *s <= 'Z')
            *s += 32;
    } 

    return str;
}

#ifndef _WIN32

long filelength (int fno)
{
    struct stat sb;
    
    if (fstat(fno, &sb) != 0)
        return 0;

    return (long) sb.st_size;
}
#endif