/*
 * File:	int.c
 *
 * Synopsis:	PC interrupt caller, memory R/W, I/O port R/W
 *
 * System:	MSDOS - Turbo C or Borland C (other compilers need some work)
 *
 * A utility for PC systems programmers to:
 *	- perform and visualise the results of interrupt calls
 *	- use buffers (inc files) to set register pairs
 *	- view the interrupt vector table
 *	- read & write I/O ports
 *
 * NB: This utility must be used with EXTREME CARE. Using bad interrupt and/or
 * I/O calls can destroy data on your memory/disk(s) and might crash your
 * machine.
 *
 * Compatible with int.c ditributed with Ralf Brown's interrupt lists
 * Comments/suggestions welcome on the email address below.
 *
 *
 * Copyright (c) 1992-1994 Angelo Haritsis <ah@doc.ic.ac.uk>
 *
 * Redistribution and use in source and binary forms are permitted provided
 * that the above copyright notice and this paragraph are duplicated in all
 * such forms and that any documentation, advertising materials, and other
 * materials related to such distribution and use acknowledge that the
 * software was developed by Angelo Haritsis.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * TODO:
 * > -fbuf filename (write mem with vals from a file)
 */

#ifndef lint
static char rcsid[] = "$Header: E:/SRC/MISC\RCS\int.c 1.2 1994/04/11 20:11:36 ah Exp ah $";
#endif

#include <stdio.h>
#include <io.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include <dos.h>

#define PROG "int"
#define VERSION "1.0"
#define NOTREACHED 0


#define PARSE_JUSTARGS	0x01
#define PARSE_NOACTION	0x02

/*
 * Types
 */
typedef int (*fun_ptr) (char **, int *);

typedef struct s_opt {
	char *opts;		/* option name */
	int size;		/* bytes for the ptr element (1, 2, 4);
				 * 0 means execute the function following
				 * parsing (argv, &opt_now)
				 */
	void *ptr;		/* pointer to the data element to be loaded or a function if size = 0 */
} OPTION;

typedef struct s_dbuff {
	unsigned *ptr_to_seg;	/* the area where the segment is held */
	unsigned *ptr_to_off;	/* the area where the offset is held */
	void far *addr; 	/* the address (when explicitly given) */
	char logo[20];
#define DBUFF_HEX 0x01
#define DBUFF_ASC 0x02
#define DBUFF_BIN 0x04
	int mode;		/* display mode */
	long bytes;		/* # of bytes to show */
} DBUFFER;

typedef struct s_doserr {
	int errno;
	int class;
	int action;
	int locus;
} DOS_ERROR;

/*
 * Globals
 */
char switchchar[] = "-+/";

char usage[] =
"usage: " PROG " [-h] [-q] [int=# | int# | -int #] [[-]reg[=]val] ...\n"
"       [-buf sreg:reg=\"str\"]\n"
"       [-d[a|b|hx] sreg:reg[:#bytes]] [-d[a|hx] faraddr[:#bytes]]\n"
"       [-divt int#-int#]]\n"
"       [-in[w] port#] [-out[w] port#,val]\n"
"       str: printf fmt (%#s allowed to setup buffers)\n";

char help[] =
" eg.\n"
"     o Interrupts (+ buffer setup)\n"
"       " PROG " 0x10 -ax 0x12 same as: int int=0x10 ax=0x12 (set VGA graphics mode)\n"
"       " PROG " 0x10 ah=0x10 al=1 bh=9 (set VGA border color; use to adjust display)\n"
"       " PROG " 0x21 -ah 9 -buf ds:dx=\"hello world\\n$\" -q (INT 21,9 'hello world')\n"
"       " PROG " an_int -buf es:bx=\"%512s\" (creates a scratch buffer of 512 bytes)\n"
"       " PROG " 0x33 ax=9 bx=0 cx=0 -fbuf es:bx=cursor.dat (mouse cursor from file)\n"
"     o Memory (-d: display: called after interrupt)\n"
"       " PROG " -q -dhx 0x400049:18 (BIOS video area data)\n"
"       " PROG " -q es=0x40 -bx 0x49 -dhx es:bx:18 (same as above)\n"
"       " PROG " -q -db 0xF8000000:512 >file (binary dump of AMIBIOS serial data)\n"
"     o IVT\n"
"       " PROG " -q -divt (display vectors from interrupt vector table = IVT)\n"
"       " PROG " -q -divt 0x10-0x1F (display IVT slots 0x10 to 0x1F)\n"
"     o I/O Ports\n"
"       " PROG " -out 0x70,0 -in 0x71 (read seconds from CMOS)\n"
"       " PROG " -out 0x70,0 -out 0x71,0 (zero-out seconds in CMOS)\n";

char *int_error[] = {
	NULL,
	"Parse",
};

char str_flags[] = "0N..ODITSZ0A0P1C";  /* 80x86 flag register */

union REGS reg;
struct SREGS sreg;
unsigned int reg_bp, reg_sp, flags;
int int_call;			/* the interrupt to be called */
int dos_errno;
int quiet_mode = 0;		/* quiet means just execute int - no reg display * /* does not apply on display
				 * opts */
int buff_now = 0;
char *buff[10]; 	    /* array of read buffer pointers */

int dbuff_now = 0;
DBUFFER dbuff[10];		/* the buffers to show at end of intr */


/* --- Prototypes --- */
OPTION *parse_an_opt(char *argv[], int *opt_now, int mode);
void doit(void);
void reg_display(void);
int set_int_val(char **argv, int *opt_now);
int ivt_display(char **argv, int *opt_now);
int ioport_out(char **argv, int *opt_now);
int ioport_in(char **argv, int *opt_now);
int read_escape(char **p);
int set_buff(char **argv, int *opt_now);
int set_fbuff(char **argv, int *opt_now);
int set_dbuff(char **argv, int *opt_now);
int dbuff_parse(void **ptr, char *tok);
int set_dbuff(char **argv, int *opt_now);
int show_help(char **argv, int *opt_now);
int set_quiet(char **argv, int *opt_now);
void error_exit(int err, char *s);

/* --- */

/*
 * Structure with all the `action' to be done for an option
 * NB: Care with matching prefixes (longer must come first)
 */
OPTION Opt[] = {

	/* NB: put the longer strings first ! */

	{"$DEFAULT", 0, (void *) set_int_val},
	{"DIVT", 0, (void *) ivt_display},      /* display int vector table */
	{"INT", 2, (void *) &int_call},

	{"OUTW", 0, (void *) ioport_out},       /* I/O port write, read */
	{"OUT", 0, (void *) ioport_out},
	{"INW", 0, (void *) ioport_in},
	{"IN", 0, (void *) ioport_in},

	{"DBUF", 0, (void *) set_dbuff},
	{"FBUF", 0, (void *) set_fbuff},
	{"BUF", 0, (void *) set_buff},
	{"DHX", 0, (void *) set_dbuff},         /* display mem contents (hex) */

	{"FL", 2, (void *) &(reg.x.flags)},     /* set flags (won't affect anything) */

	{"AX", 2, (void *) &(reg.x.ax)},        /* set general registers */
	{"BX", 2, (void *) &(reg.x.bx)},
	{"CX", 2, (void *) &(reg.x.cx)},
	{"DX", 2, (void *) &(reg.x.dx)},
	{"AH", 1, (void *) &(reg.h.ah)},
	{"BH", 1, (void *) &(reg.h.bh)},
	{"CH", 1, (void *) &(reg.h.ch)},
	{"DH", 1, (void *) &(reg.h.dh)},
	{"AL", 1, (void *) &(reg.h.al)},
	{"BL", 1, (void *) &(reg.h.bl)},
	{"CL", 1, (void *) &(reg.h.cl)},
	{"DL", 1, (void *) &(reg.h.dl)},

	{"SI", 2, (void *) &(reg.x.si)},        /* set index, stack registers */
	{"DI", 2, (void *) &(reg.x.di)},
	{"BP", 0, 0},
	{"SP", 0, 0},

	{"CS", 2, (void *) &(sreg.cs)},         /* set segment registers */
	{"DS", 2, (void *) &(sreg.ds)},
	{"ES", 2, (void *) &(sreg.es)},
	{"SS", 2, (void *) &(sreg.ss)},

	{"DA", 0, (void *) set_dbuff},          /* display mem contents (ascii) */
	{"D", 0, (void *) set_dbuff},           /* display mem contents (ascii+hex) */
	{"Q", 0, (void *) set_quiet},           /* quiet (no disply of reg contents) */
	{"H", 0, (void *) show_help},           /* help */
};


int cdecl
main(int argc, char *argv[])
{
	int opt_now;

	if (argc == 1)
		error_exit(0, NULL);

	/* parse the arguments and do proper action */
	for (opt_now = 1; opt_now < argc; opt_now++)
		if (parse_an_opt(argv, &opt_now, 0) == NULL)
			error_exit(1, NULL);
	doit();
	return (0);
}


/*
 * Parses an argument and calls proper function or assigns numbers
 * accordingly. Reentrant.
 */
OPTION *
parse_an_opt(char *argv[], int *opt_now, int mode)
{
	int i, arg_len, get_next_arg;
	char *opts, *optarg, *p;
	long val;

	opts = argv[*opt_now];
	if (strchr(switchchar, opts[0]))	/* option starts with a switch char, skip it */
		opts++, argv[*opt_now]++;
	for (i = 0; i < (sizeof(Opt) / sizeof(OPTION)); i++) {
		arg_len = strlen(Opt[i].opts);
		get_next_arg = opts[arg_len] == 0;
		if (strncmpi(opts, Opt[i].opts, arg_len) == 0) {
			if (mode & PARSE_NOACTION)	/* do not perform action */
				return (&Opt[i]);	/* just return ptr to Opt slot */
			switch (Opt[i].size) {

			case 0: /* call the function */
				if (!(mode & PARSE_JUSTARGS) && Opt[i].ptr != (void *) 0)
					if ((*((fun_ptr) Opt[i].ptr)) (argv, opt_now) == 0)
						return (NULL);	/* error */
				return (&Opt[i]);

			case 1: /* ptr is a byte, short, int */
			case 2:
			case 4:
				if (get_next_arg)
					optarg = argv[++(*opt_now)];	/* get next option */
				else
					optarg = opts + arg_len + 1;	/* skip a separator (eg =) */
				val = strtol(optarg, &p, 0);
				if (p == optarg)
					return (NULL);
				switch (Opt[i].size) {
				case 1:
					*((char *) Opt[i].ptr) = (char) val;
					break;
				case 2:
					*((short *) Opt[i].ptr) = (short) val;
					break;
				case 4:
					*((long *) Opt[i].ptr) = (long) val;
					break;
				}
				return (&Opt[i]);

			default:
				assert(NOTREACHED);
			}
		}
	}
	if (mode & PARSE_JUSTARGS)
		return (&Opt[0]);	/* default */
	else {
		i = (*((fun_ptr) Opt[0].ptr)) (argv, opt_now);	/* default */
		return (i == 0 ? NULL : &Opt[0]);
	}
}

/*
 * Call the interrupt if asked and display the result buffers
 */
void
doit(void)
{
	int i;
	long j;
	unsigned char far *ptr;
	unsigned char b;

	dos_errno = 0;
	if (int_call != -1) {
		reg_bp = _BP;
		reg_sp = _SP;
		flags = _FLAGS;
		reg_display();
	}
	if (int_call > 0 && int_call < 256) {
		quiet_mode || printf("\nINT: 0x%02X\n", int_call);
		int86x(int_call, &reg, &reg, &sreg);	/**/
		if (reg.x.cflag != 0)	/* error occured */
			dos_errno = _doserrno;
		reg_bp = _BP;
		reg_sp = _SP;
		quiet_mode || printf("\n");
		flags = reg.x.flags;
		reg_display();
		if (!quiet_mode && (int_call == 0x21 || int_call == 0x24))	/* dos call */
			printf("DOSERR: %04X (%u)\n", dos_errno, dos_errno);
	}
	/* display dbuffers */

	for (i = 0; i < dbuff_now; i++) {
		ptr = (unsigned char far *) MK_FP(*(dbuff[i].ptr_to_seg), *(dbuff[i].ptr_to_off));

		if (dbuff[i].mode & DBUFF_BIN)		/* binary */
			setmode(1, O_BINARY);
		else
			printf("\n*<%s> {\n", dbuff[i].logo);
		for (j = 0; j < dbuff[i].bytes; j++, ptr++) {
			b = *ptr & 0x00FF;		/* byte to display */
			if (dbuff[i].mode & DBUFF_BIN) {	/* binary */
				putchar(b);
				continue;	/* nothing else */
			}
			if (dbuff[i].mode & DBUFF_HEX)
				printf("%02X", b);
			if (dbuff[i].mode == DBUFF_ASC)
				putchar(iscntrl(b) ? '.' : b);
			else if (dbuff[i].mode & DBUFF_ASC)
				printf("(%c)", iscntrl(b) ? '.' : b);
			if (dbuff[i].mode != DBUFF_ASC)
				printf(" ");
		}
		fflush(stdout);
		if (dbuff[i].mode & DBUFF_BIN)	       /* binary */
			setmode(1, O_TEXT);
		else
			printf("}\n");
	}
	/* free the read buffers allocated */
	for (i = 0; i < buff_now; i++)
		free(buff[i]);
}

void
reg_display(void)
{
	char s[32];
	int i, bit_on;

	if (quiet_mode)
		return;
	printf(
	       "AX=%04X   BX=%04X   CX=%04X   DX=%04X\n"
	       "SI=%04X   DI=%04X   BP=%04X   SP=%04X\n"
	       "CS=%04X   DS=%04X   ES=%04X   SS=%04X",
	       reg.x.ax, reg.x.bx, reg.x.cx, reg.x.dx,
	       reg.x.si, reg.x.di, reg_bp, reg_sp,
	       sreg.cs, sreg.ds, sreg.es, sreg.ss);
	strncpy(s, str_flags, 32);	/* use a scratch copy */
	/* and now the flags */
	flags = reg.x.flags;
	for (i = 0; i < 16; i++) {
		bit_on = (flags & ((unsigned) 0x8000 >> i)) != 0;
		if (s[i] == '.')
			s[i] = bit_on ? '1' : '0';
		else
			s[i] = bit_on ? s[i] : tolower(s[i]);
	}
	printf("   CPU Flags: %16s\n", s);
}

/*
 * 'default' argument function - see if it is an interrupt
 */
int
set_int_val(char **argv, int *opt_now)
{
	long val;
	char *p;

	val = strtol(argv[*opt_now], &p, 0);
	if (val <= 0 || val > 255 || p - argv[*opt_now] != strlen(argv[*opt_now]))
		return (0);	/* problems */
	int_call = (int) val;
	return (1);
}

/*
 * Display a slot of the Interrupt Vector Table
 */
int
ivt_display(char **argv, int *opt_now)
{
	char sfrom[20], sto[20];
	int from, to, i;
	void far *p;

	if ((i = sscanf(argv[*opt_now + 1], "%[0-9xX]-%s", sfrom, sto)) == 2) { /* is a range given ? */
		(*opt_now)++;	/* consume next arg */
		from = (int) strtol(sfrom, (char **) &sfrom, 0);
		to = (int) strtol(sto, (char **) &sto, 0);
	} else {
		from = 0;
		to = 255;
	}
	/* do it now */
	printf("Interrupt Vector Table (0x%02X to 0x%02X)\n", from, to);
	for (i = from; i <= to; i++) {
		disable();	/* just in case ... */
		p = (void far *) *((long far *) (4L * i));
		enable();
		printf(" * 0x%02X (%3u):  %Fp\n", i, i, p);
	}
	printf("\n");
	return (1);
}

int
ioport_out(char **argv, int *opt_now)
{
	char *optarg, sport[10], sval[10];
	int word_op, port, val;

	optarg = argv[*opt_now];
	word_op = (toupper(optarg[3]) == 'W') ? 1 : 0;
	if (isdigit(optarg[3 + word_op]))	/* arg follows with no delimiter */
		optarg += 3 + word_op;
	else
		optarg = argv[++(*opt_now)];
	if (sscanf(optarg, "%[^ ,;]%*[ ,;]%s", sport, sval) != 2)
		return (0);
	port = (int) strtol(sport, (char **) &sport, 0);
	val = (int) strtol(sval, (char **) &sval, 0);
	if (word_op)
		outport(port, (unsigned) val);
	else
		outportb(port, val);
	int_call = -1;
	return (1);
}

int
ioport_in(char **argv, int *opt_now)
{
	char *optarg, sport[10];
	int word_op, port, val;

	optarg = argv[*opt_now];
	word_op = (toupper(optarg[2]) == 'W') ? 1 : 0;
	if (isdigit(optarg[2 + word_op]))	/* arg follows with no delimiter */
		optarg += 2 + word_op;
	else
		optarg = argv[++(*opt_now)];
	if (sscanf(optarg, "%s", sport) != 1)
		return (0);
	port = (int) strtol(sport, (char **) &sport, 0);
	if (word_op) {
		val = inport(port);
		quiet_mode || printf("INW 0x%04X (%5u):  0x%04X (%5u)\n", port, port, val, val);
	} else {
		val = inportb(port);
		quiet_mode || printf("IN 0x%04X (%5u):  0x%02X (%3u)\n", port, port, val, val);
	}
	quiet_mode || printf("\n");
	int_call = -1;
	return (1);
}

#define ESCAPES 10
static int esc_to_code[ESCAPES][2] = {
	{'n', '\n'},
	{'t', '\t'},
	{'v', '\v'},
	{'b', 'b'},
	{'r', '\r'},
	{'f', '\f'},
	{'a', '\a'},
	{'\\', '\\'},
	{'\?', '?'},
	{'\'', '\''},
};

/*
 * returns with *p pointing to char after the one(s) consumed
 */
int
read_escape(char **p)
{
	int i;

	if (isdigit(**p))	/* octal */
		return ((int) strtol(*p, p, 8));
	else if (**p == 'x')    /* hex */
		return ((int) strtol(*p + 1, p, 16));
	for (i = 0; i < ESCAPES; i++)
		if (**p == esc_to_code[i][0]) {
			(*p)++; /* consume it */
			return (esc_to_code[i][1]);
		}
	/* otherwise, return the character un-translated */
	(*p)++; 		/* consume it */
	return (**p);
}


/*
 * load seg register values to point ot the created buffer
 */
void
load_regpair(char *s_seg, char *s_off, void far *buff)
{
	int len;
	char stmp[50], *argv_fake[3];

	/* load the regs */
	argv_fake[0] = stmp;
	argv_fake[1] = "";
	len = 0;
	sprintf(stmp, "-%s 0x%X", s_seg, FP_SEG(buff));
	parse_an_opt(argv_fake, (int *) &len, PARSE_JUSTARGS);	/* make it think it's an option */
	sprintf(stmp, "-%s 0x%X", s_off, FP_OFF(buff));
	parse_an_opt(argv_fake, (int *) &len, PARSE_JUSTARGS);	/* and again for offs register */
}

/*
 * set registers accordingly
 */
int
set_buff(char **argv, int *opt_now)
{
	char *optarg, *p, *dp;
	char s_seg[10], s_off[10];
	char stmp[50];
	static char dummy[] = "";       /* for case of %s in fmt str */
	unsigned int len;

	optarg = argv[++(*opt_now)];	/* s_off pair */
	sscanf(optarg, "%[^:]:%s", &s_seg, &s_off);
	if (s_off[2] == '=')
		s_off[2] = 0;
	optarg = argv[++(*opt_now)];	/* printf like string */
	/* how big a buffer ? */
	len = strlen(optarg);
	/* add the %# lengths (extra buffer space) */
	for (p = strchr(optarg, '%'); p != NULL; p = strchr(p + 1, '%'))
		len += atoi(p + 1);

	if ((buff[buff_now] = (char *) malloc(len)) == NULL)
		return (0);
	/* create escape chars again (since cmd processing makes \ into \\) */
	p = optarg, dp = stmp;
	while (*p)
		if (*p == '\\') {
			p++;	/* consume \ */
			*dp++ = read_escape(&p);
		} else
			*dp++ = *p++;

	/* load the buffer; 5 % fields are enough (XXX ..f func problem if \0 appears before end of fmt) */
	sprintf(buff[buff_now], stmp, dummy, dummy, dummy, dummy, dummy);

	load_regpair(s_seg, s_off, (void far *) buff[buff_now]);
	buff_now++;
	return (1);
}

/*
 * set register pair to point to buffer with data from a file
 */
int
set_fbuff(char **argv, int *opt_now)
{
	char *optarg, *fname;
	char s_seg[10], s_off[80];
	long len;
	FILE *f;

	optarg = argv[++(*opt_now)];	/* s_off pair */
	sscanf(optarg, "%[^:]:%s", &s_seg, &s_off);
	if (s_off[2] == '=') {
		s_off[2] = 0;
		fname = &s_off[3];
	} else
		return (0);

	if ((f = fopen(fname, "rb")) == NULL)
		return (0);
	len = filelength(fileno(f));
	if (len > 65500L || (buff[buff_now] = (char *) malloc((unsigned int)len)) == NULL)
		return (0);
	if (fread(buff[buff_now], (int) len, 1, f) != 1)
		return (0);
	load_regpair(s_seg, s_off, (void far *) buff[buff_now]);
	buff_now++;
	fclose(f);
	return (1);
}

int
dbuff_parse(void **ptr, char *tok)
{
	char stmp[50], *argv_fake[3];
	OPTION *p_opt;
	int len = 0;

	argv_fake[0] = stmp;
	argv_fake[1] = "";
	sprintf(stmp, "-%s 0", tok);
	p_opt = parse_an_opt(argv_fake, &len, PARSE_JUSTARGS | PARSE_NOACTION);
	if (p_opt == NULL || p_opt == &Opt[0])
		return (0);
	*ptr = (unsigned *) p_opt->ptr;
	return (1);
}

/*
 * add to the list of the buffers to be displayed at end
 */
int
set_dbuff(char **argv, int *opt_now)
{
	char mode_char;
	char *optarg, *p;
	char tok[3][15];	/* max 3 tokens of 15 chars each */
	int i;
	long addr;
	unsigned long num = 1L; /* number of bytes to display */
	DBUFFER *dpb;

	dpb = &dbuff[dbuff_now];
	dpb->mode = DBUFF_HEX | DBUFF_ASC;
	mode_char = toupper(argv[*opt_now][1]);
	dpb->mode &= (mode_char == 'A') ? ~DBUFF_HEX : ~0;
	dpb->mode &= (mode_char == 'H') ? ~DBUFF_ASC : ~0;
	if (mode_char == 'B') {         /* binary mode */
		dpb->mode &= ~(DBUFF_HEX | DBUFF_ASC);
		dpb->mode |= DBUFF_BIN;
	}
	optarg = argv[++(*opt_now)];	/* reg pair */
	strncpy(dpb->logo, optarg, 20);
	/* collect tokens */
	for (i = 0, p = strtok(optarg, ":="); p; p = strtok(NULL, ":="))
		strcpy(tok[i++], p);
	if (i > 3)
		return (0);
	/* process them */
	addr = strtoul(tok[0], &p, 0);
	if ((p - tok[0]) > 0) { /* first is addr */
		if (i > 1) {	/* there's a 2nd token */
			num = strtoul(tok[1], &p, 0);
			if ((p - tok[1]) == 0 || num == 0)
				return (0);	/* wrong argument */
		}
		dpb->addr = (void far *) addr;
		dpb->ptr_to_off = (unsigned *) &(dpb->addr);
		dpb->ptr_to_seg = ((unsigned *) &(dpb->addr)) + 1;
	} else {		/* should be Reg:Reg[:#] format */
		if (dbuff_parse((void **) &(dpb->ptr_to_seg), tok[0]) == 0)
			return (0);
		if (dbuff_parse((void **) &(dpb->ptr_to_off), tok[1]) == 0)
			return (0);
		if (i > 2) {	/* num argument */
			num = strtoul(tok[2], &p, 0);
			if ((p - tok[2]) == 0 || num == 0)
				return (0);	/* wrong argument */
		}
	}
	dpb->bytes = num;
	dbuff_now++;		/* have inserted an element */
	return (1);
}

int
set_quiet(char **argv, int *opt_now)
{
	argv = argv, opt_now = opt_now; /* eliminate warning */
	return (quiet_mode = 1);
}

#define fmsg stdout		/* DOS stderr cannot be redir'ed >:-{ */

int
show_help(char **argv, int *opt_now)
{
	argv = argv, opt_now = opt_now; /* eliminate warning */
	fprintf(fmsg,
		PROG ": Execute and investigate interrupts/system data (ver " VERSION ")\n"
		"Copyright (c) 1992-1994 A. Haritsis <ah@doc.ic.ac.uk>. Distribute freely.\n");
	error_exit(0, help);
	return (1);
}

void
error_exit(int err, char *s)
{
	if (err > 0)
		fprintf(fmsg, PROG ": %s error\n", int_error[err]);
	fprintf(fmsg, "%s", usage);
	if (s != NULL)
		fprintf(fmsg, "%s", s);
	exit(err);
}
