/*
*	rndck
*
*	Exercise read/write random access devices.
*	in a nondestructive manner.
*
*	DataIndustrier DIAB AB, Gunnar Alm 1988.
*/

#include	<stdio.h>
#include	<signal.h>
#include	<fcntl.h>
#include	<time.h>
#include	<sys/types.h>
#include	<sys/stat.h>

#define	VERSION	"V3.21"
#define	USAGE	"[-wrhlfV] [-b beg[kMr]] [-s sz[kM]] [-z blksz[kM]] [-p part] [-e level ] dev"
#define	ERRMAX	50

#define	MAXSZ	8192				/* Maximum sector size */

onintr(), onquit(), onhup(), onterm();		/* Interrupt stuff */

extern	int	errno;
extern	char	*optarg;
extern	int	optind;

char	*PRMPT;			/* Myname */
int	Fd;			/* Exercise path fd */
int	Term = 0;		/* Terminate flag */
int	Hex = 0;		/* Dump in hex flag */
int	Blksz = 0;		/* Global blocksize */
FILE	*logfd;			/* Where to log things */
static unsigned char cmp[MAXSZ];	/* Compare buffer */
static unsigned	char org[MAXSZ];	/* Original sector */
static unsigned char new[MAXSZ];	/* New sector */

main(argc,argv)
int argc;
char **argv;
{
	ulong	i;		/* Chunk variable */
	ulong	loopc = 0;	/* Loop counter */
	ulong	pos = 0;	/* Position */
	ulong	begin = 0;	/* First byte adress */
	ulong	end = 0;	/* Last byte adress */
	ulong	size = 0;	/* Partition size */
	ulong	psize = 0;	/* Physical end of device */
	int	wflag = 0;	/* Write enable flag */
	int	roflag = 0;	/* Read only flag, no write */
	int	logp = 0;	/* Log position info each loop */
	int	force = 0;	/* Force size flag, ignore pend */
	int	part = 2;	/* Number of partitions, default 2 */
	int	extra = 0;	/* Exteneded mode, stop on compare error, don't restore */
	int	tmp;		/* Scratch value */
	uint	maxerr = ERRMAX;/* Number of allowed errors */
	uint	slp;		/* Sleep while forking */
	int	relbeg = 0;	/* begin value is relative */
	uint	errcnt = 0;	/* Accumulated errors */
	short	seed[3];	/* Seed value. */
	long	nblks;		/* Number of blocks from begin to end. */
	char	*logname;	/* Name of log file */
	int	debug;		/* Debug lu */
	int	device;		/* Device flag */

	struct stat filetmp;
				
	PRMPT = *argv;		/* Prog name */

	while ((i = getopt(argc, argv, "b:s:z:p:Vwrhlfe:")) != EOF) {
		switch (i) {
			case 'V':
				printf("%s %s\n", PRMPT, VERSION);
				break;
			case 'w':
				wflag = -1;
				break;
			case 'r':
				roflag = -1;
				break;
			case 'h':
				Hex = -1;
				break;
			case 'l':
				logp = -1;
				break;
			case 'f':
				force = -1;
				break;
			case 'e':
				extra = getvalr(optarg, &tmp);
				if (extra == 0) extra = 1;
				maxerr = 1;
				break;
			case 'b':
				begin = getvalr(optarg, &relbeg);
				break;
			case 's':
				size = getvalr(optarg, &tmp);
				break;
			case 'z':
				Blksz = getvalr(optarg, &tmp);
				break;
			case 'p':
				part = atoi(optarg);
				break;
			case '?':
			default:
				argc = 0;
		}
	}
	argv += optind;
	argc -= optind;

	if(argc < 1) {
		printf("Usage: %s %s\n", PRMPT, USAGE);
		exit(1);
	}
	log("Random read/write disk test program, %s/Ga.", VERSION);

	if (wflag == roflag) {
		if (wflag == 0) {
			log("Confusion, neither read only or write ?");
		} else {
			log("Confusion, both read only and write ?");
		}
		exit(1);
	}
	if (stat(argv[0], &filetmp) != 0) {
		log("Failed to stat on %s", argv[0]);
		exit(1);
	}
	if ((device = S_ISCHR(filetmp.st_mode)) == 0) {
		log("The device is probably a file (at least not a device).");
	}
	if ((Fd = open(argv[0],2)) < NULL) {
		log("Failed to open %s", argv[0]);
		perror(argv[0]);
		exit(1);
 	}
	if (extra >= 9) {
		log("Extended mode uses debugger in the OS");
		if (open("/dev/debug", 2) < NULL) {
			log("Failed to open /dev/debug");
			exit(1);
		}
	}
	/*
	*	Find out the physical block size on the device.
	*/
	if (Blksz == 0) {
		for (Blksz = 128; Blksz <= MAXSZ ; Blksz <<= 1) {
			if (READ(0, new) == Blksz) {
				break;
			}
		}
		if (Blksz > MAXSZ) {
			log("Can't find out physical block size, sorry!");
			exit(1);
		}
	}
	if ((Blksz < 16) || (Blksz > MAXSZ) || (Blksz % 2)) {
		log("Illegal block size, namely %d.");
		exit(1);
	}
	/*
	*	Find out the size of the device.
	*/
	if (force == 0) {
		if (device == 0) {	/* Not a device */
			psize = findsz(Fd, Blksz, wflag);
		} else {		/* Device */
			psize = findsz(Fd, Blksz, 0);
		}
		log("The found number of %d byte blocks is %d, so...", Blksz, psize/Blksz);
		log("the capacity is %d bytes, %dkb or %dMb.", psize, psize/1024, psize/1024/1024);
	} else {
		if (size == 0) {
			log("Size must be specified when -f flag is used.");
			exit(1);
		}
		psize = size;
		log("The specified number of %d byte blocks is %d, so...",Blksz, psize/Blksz);
		log("the capacity is %d bytes, %dkb or %dMb.", psize, psize/1024, psize/1024/1024);
	}
	/*
	*	Don't let the sons use the same lu since they would
	*	get the same seek position and the test garbled.
	*/
	close(Fd);
	/*
	*	Check begin, end, size and psize values.
	*/
	if (relbeg != 0) {
		if (psize == 0) {
			log("Found device size is zero, can't use relative begin.");
			exit(1);
		}
		begin = psize - begin;	/* begin bytes from physend */
	}
	if (size == 0) {
		if (psize == 0) {
			log("Found device size is zero, please specify size.");
			exit(1);
		}
		end = psize;
	} else {
		end = begin + size;
	}
	if (end > psize) {
		log("Exercise area ends outside device, adjusted");
		end = psize;
	}
	begin -= begin % Blksz;			/* Adjust to blocksize */
	end -= end % Blksz;			/* Adjust to blocksize */

	if (end < begin) {
		log("Illegal values, end is less than begin.");
		exit(1);
	}
	if (end - begin < Blksz * part) {
		log("Illegal values, not enough space to test on.");
		exit(1);
	}
	if (part < 1 || part > 64) {
		log("Illegal number of partitions, namely %d.", part);
		exit(1);
	}
	size = (end - begin) / part;
	size -= size % Blksz;			/* Align on blocks. */

	if (wflag != 0) {
		log("Read/Write - begin %dkb, end %dkb and %d partitions.", begin/1024, end/1024, part);
	} else {
		log("Read only - begin %dkb, end %dkb and %d partitions.", begin/1024, end/1024, part);
	}

	/*
	*	Make some son's identical to me.
	*/
	slp = 3;
	while(part && (i = fork()) != NULL) {
		if(i < NULL) {
			kill(-getpgrp(), SIGTERM);
			perror("fork");
			exit(1);
		}
		begin += size;
		sleep(slp);
		slp = 1;
		part--;
	}
	if(part == NULL) {
		exit(0);	/* All son's born, father dies */
	}
	/*
	*	Now I am a successfully born son, do some preprocessing.
	*/
	if(part != 1) {			/* New end for all sons exept the last one. */
		end = begin + size;
	}
	nblks = (end - begin) / Blksz;	/* Remember number of blocks */

	seed[0] = 1234;
	seed[1] = part;
	seed[2] = 5678;
	seed48(seed);			/* Init random generator */		

	if ((Fd = open(argv[0],2)) < NULL) {	/* Open the device */
		log("Failed to open %s", argv[0]);
		perror(argv[0]);
		exit(1);
	}
	if (extra != 0) {
		logname = tmpnam(NULL);
		logfd = freopen(logname, "w", stderr);
		if (extra >= 9) {
			debug = open("/dev/debug", 2);		/* Open the debug */
		}
	}
	log("%s on %s, range from bn %8d to bn %8d", PRMPT, argv[0], begin/Blksz, (end/Blksz) - 1);
	/*
	*	Signal setup, reqired.
	*/
	if(signal(SIGINT, onintr) == SIG_IGN)  signal(SIGINT, SIG_IGN);
	if(signal(SIGHUP, onhup) == SIG_IGN)   signal(SIGHUP, SIG_IGN);
	if(signal(SIGQUIT, onquit) == SIG_IGN) signal(SIGQUIT, SIG_IGN);
	if(signal(SIGTERM, onterm) == SIG_IGN) signal(SIGTERM, SIG_IGN);
	/*
	*	Fill unique buffer with known data
	*/
	for (i = 0; i < MAXSZ/sizeof(long); i++) {
		((long *)new)[i] = 0xaa55aa55;
	}
	/*
	*	Main loop.
	*/	
	for(;;){				/* Infinite loop */

	if (Term != 0 || errcnt >= maxerr) {
		log("Terminating, %d errors, %d loops.", errcnt, loopc);
		(void)close(debug);
  		exit(1);
	}
	/*
	*	Pick a random adress.
	*/
	pos = (lrand48() % nblks) * Blksz + begin;
	/*
	*	Read original sector into buffer
	*/
	if (logp != 0) {
		log("Test position, bn %8d %#x", pos/Blksz, pos/Blksz);
	}
	if ((i = READ(pos, org)) != Blksz) {
		if (i < 0) {
			log("Original read I/O error, errno %d, bn %d, restarted.",
			errno, pos/Blksz);
		} else {
			log("Original read I/O error, count %d, bn %d, restarted.",
			i, pos/Blksz);
		}
		errcnt++;
		continue;
	}
	if(CHECK(pos, org)) {
		log("Original readcheck error, bn %d %#x, byte %d %#x.", pos/Blksz, pos/Blksz, pos, pos);
		errcnt++;
		continue;
	}
	if (extra != 0) {
		if ((((unsigned long *)org)[1] != pos) || ((unsigned long *)org)[2] != 0x55aa55aa) {
			log("Unknown original data, bn %d %#x, byte %d %#x.", pos/Blksz, pos/Blksz, pos, pos);
			if (extra >= 9) {
				(void)read(debug, (char *)&i, sizeof(i));
			}
			phex(stderr, org, Blksz);
			errcnt++;
			continue;	/* Pick a new sector */
		}
	}
	if (wflag != 0) {		/* Write allowed */
		/*
		*	Make new unique sector contents and write.
		*/
		MARK(pos, new);
		/*
		*	Write the new data to the device.
		*/
		if ((i = WRITE(pos, new)) != Blksz) {
			if (i < 0) {
				log("Unique write I/O error, errno %d, bn %d, restarted.", errno, pos/Blksz);
			} else {
				log("Unique write I/O error, count %d, bn %d, restarted.", i, pos/Blksz);
			}
			errcnt++;
			continue;	/* Write failed, pick a new sector */
		}
		/*
		*	Read back and compare the written sector
		*/
		if(CHECK(pos, new)){
			log("Unique cmperr, bn %d %#x, byte %d %#x.", pos/Blksz, pos/Blksz, pos, pos);
			if (extra >= 9) {
				(void)read(debug, (char *)&i, sizeof(i));
			}
			errcnt++;
		}
		/*
		*	Now restore the original sector contents
		*/
restore:
		if (Term > 3) {
			if (Term > 4) {
				log("Forced termination, never restored at bn %d.", pos/Blksz);
				log("Terminating, %d errors, %d loops.", errcnt, loopc);
		  		exit(1);
			}
			printf("One more interrupt will force termination.");
		}
		if ((i = WRITE(pos, org)) != Blksz) {
			if (i < 0) {
				log("Original write I/O error, errno %d, bn %d, restarted.", errno, pos/Blksz);
			} else {
				log("Original write I/O error, count %d, bn %d, restarted.", i, pos/Blksz);
			}
			sleep(1);
			errcnt++;
			goto restore;
		}
		if (CHECK(pos, org)) {
			log("Original cmperr, bn %d %#x, byte %d %#x.",	pos/Blksz, pos/Blksz, pos, pos);
			if (extra >= 9) {
				(void)read(debug, (char *)&i, sizeof(i));
			}
			errcnt++;
			goto restore;
		}
	}
	loopc++;
	}
}
/*
*	Subroutines
*/
MARK(pos,buf)
unsigned long pos;
unsigned char *buf;
{
	*(unsigned long *)buf = pos;
}
READ(pos,buf)
unsigned long pos;
unsigned char *buf;
{
	lseek(Fd, pos, 0);
	return read(Fd, buf, Blksz);
}
WRITE(pos,buf)
unsigned long pos;
unsigned char *buf;
{
	lseek(Fd, pos, 0);
	return write(Fd, buf, Blksz);
}
CHECK(pos,buf)
unsigned char *buf;
{
	unsigned char *p1, *p2;
	int retry, err, i;

	retry = 0;
	do {
	   err = 0;
	   if ((i = READ(pos,cmp)) != Blksz) {
	      if (i < 0) {
		log("Checkread I/O error, errno %d, bn %d, try %d.",
		errno, pos/Blksz, retry);
	      } else {
		log("Checkread I/O error, count %d, bn %d, try %d.",
		i, pos/Blksz, retry);
	      }
	   err++;
	   continue;
	   }
	   for (p1 = buf,p2 = cmp ; p1 < &buf[Blksz] ; p1++,p2++) {
	      if (*p1 != *p2) {
	      	 if (Hex == 0) {
		    log("Got %02x%02x%02x%02x%02x%02x Exp %02x%02x%02x%02x%02x%02x, off %d %#x, try %d",
		    p2[0],p2[1],p2[2],p2[3],p2[4],p2[5],
		    p1[0],p1[1],p1[2],p1[3],p1[4],p1[5],
		    p1-buf, p1-buf, retry);
		 } else {
		    log("Found data, bn %d %#x, byte %d %#x, off %d %#x, try %d.\n",
		    pos/Blksz, pos/Blksz, pos, pos, p1-buf, p1-buf, retry);
		    phex(stderr, cmp, Blksz);
		    log("Exp.  data, bn %d %#x, byte %d %#x, off %d %#x, try %d.\n",
		    pos/Blksz, pos/Blksz, pos, pos, p1-buf, p1-buf, retry);
		    phex(stderr, buf, Blksz);
		    fprintf(stderr,"\n\n");
		 }
		 err++;
		 break;
	      }
	   }
	} while(err && retry++ < 1);
	return(retry);
}
onintr()	{ signal(SIGINT, onintr);  Term++; }
onquit()	{ signal(SIGQUIT, onintr); Term++; }
onhup()		{ signal(SIGHUP, onintr);  Term++; }
onterm()	{ signal(SIGTERM, onintr); Term++; }
/*
*	Get a string value and fix 'k' and 'M'
*	Special version, finds additional character
*	after value and marks this in rel.
*/
getvalr(s, rel)
register char *s;
int *rel;			/* rel flag */
{
	register long x;

	x = atol(s);
	while(*s >= '0' && *s <= '9') s++;
	if(*s == 'M') {
		x *= 1024 * 1024;
		s++;
	} else {
		if (*s == 'k') {
			x *= 1024;
			s++;
		}
	}
	if (*s == 'r') {
		*rel = -1;			/* Mark its relative */
	}
	return(x);
}
/*
*	Log message on stderr with time and date
*/
log(fmt,s1,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12,s13,s14,s15,s16,s17,s18,s19,s20)
{
	register struct tm *tp;
	long clock;
	extern struct tm *localtime();

	clock = time(0);
	tp = localtime(&clock);
	fprintf(stderr, "%d:%02d%02d %02d.%02d.%02d ", getpid(),
		tp->tm_mon+1, tp->tm_mday,
		tp->tm_hour, tp->tm_min, tp->tm_sec);
	fprintf(stderr, fmt,s1,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12,s13,s14,s15,s16,s17,s18,s19,s20);
	fprintf(stderr, "\n");
	fflush(stderr);
}
/*
*	Find out size on a random access device.
*/
findsz(fd, bsize, wflag)
int fd, bsize, wflag;			/* bsize is requested block size */
{
	unsigned char *buf;
	unsigned long pos,rval;
	unsigned long incr;
	
	if((buf = (unsigned char *)malloc(bsize)) == NULL){
		return(0);
	}
	rval = 0;
	pos = 0x40000000;				/* Start at 1Gb */
	incr = pos;
	do {
		incr >>= 1;
		if (wflag != 0) {
	 		if (lseek(fd, pos, 0) >= 0 && read(fd, buf, bsize)  == bsize &&
			   lseek(fd, pos, 0) >= 0 && write(fd, buf, bsize) == bsize ){
				rval = pos + bsize;
				pos += incr;
			} else {
				pos -= incr;
			}
		} else {
			if (lseek(fd, pos, 0) >= 0 && read(fd, buf, bsize)  == bsize) {
				rval = pos + bsize;
				pos += incr;
			} else {
				pos -= incr;
			}
		}
	} while(incr >= bsize);
	free(buf);
	return(rval);
}
/*
*	This routine will produce a hex + ascii layout
*	on a line by line basis and print on output.
*
*	Dataindustrier DIAB AB, Gunnar Alm 880113
*/
#define	ASCOFF	49			/* Offset to ascii part */
#define	LEN	65			/* Max length of output line */

phex(fd, bufp, bufl)
FILE *fd;				/* Fd to print on */
char *bufp;				/* Pointer to data */
int bufl;				/* Length of data */
{
	char *hexp, *ascp;
	int tkn, pos;
	static char hex[] = "0123456789abcdef";
	char line[LEN];
				
	while(bufl > 0) {
		hexp = line; ascp = hexp + ASCOFF;
		for(pos = 0; pos < 16; pos++, bufp++, bufl--) {
			tkn = *bufp & 0xff;
			if(bufl < 1) {
				*hexp++ = ' ';
				*hexp++ = ' ';
				*hexp++ = ' ';
				*ascp++ = ' ';
			} else {
				*hexp++ = hex[(tkn >> 4) & 15];
				*hexp++ = hex[tkn & 15];
				*hexp++ = ' ';
				if((tkn < 0x20) || (tkn > 0x7e)) {
					*ascp++ = '.';
				} else {
					*ascp++ = tkn;
				}
			}
		}
		*hexp++ = ' ';
		*ascp++ = '\0';
		fprintf(fd, "%s\n", line);
	}
	return(0);
}
