/*
 * (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