/*
 * trace - trace DOS system calls
 *
 * (C) Copyright 1991-1994 Diomidis Spinellis.  All rights reserved.
 *
 * $Id: trace.c%v 1.28 1994/10/01 11:32:56 dds Exp $
 *
 * Permission to use, copy, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 * 
 * 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.
 */

#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <fcntl.h>
#include <io.h>
#include <dos.h>
#include <process.h>
#include <ctype.h>
#include <time.h>
#include <signal.h>
#include <bios.h>
#include <errno.h>

#ifndef lint
static char rcsid[] = "$Id: trace.c%v 1.28 1994/10/01 11:32:56 dds Exp $";
#endif

#define MAXBUFF 1025

/*
 * Program options.  See main() for explanation.
 */
static stringprint, hexprint, otherprint, nameprint, regdump, tracechildren;
static timeprint, count, tracetsr, tsrpsp, numprint, worderror, pspprint;
static branchprint, append;
static unsigned datalen = 15;
static someprefix;
static char *fname = "trace.log";

static mypsp, tracedpsp;		/* PSP of us and traced program */
static execed;				/* True after we run */
static char _far *critsectflag;		/* DOS critical section flag */
static char _far *criterrflag;		/* DOS citical error flag */

/* DOS interrupt */
#define DOS_INT	0x21

/* Old dos handler to chain to */
static void (_interrupt _far _cdecl *old_dos_handler)();

/* Output file descriptor */
static int fd;

#pragma check_stack(off)
#pragma intrinsic(strlen)

/*
 * Output a string to the trace file
 */
static void
tputs(char *s)
{
	int len = strlen(s);
	int sseg, soff;
	char far *p = s;

	sseg = FP_SEG(p);
	soff = FP_OFF(p);
	_asm {
		mov bx, fd
		mov cx, len
		mov ax, sseg
		mov dx, soff
		push ds
		mov ds, ax
		mov ah, 40h
		int 21h
		pop ds
	}
}

int _doprnt(char *fmt, va_list marker, FILE *f);

/*
 * Print a formated string to the trace file
 */
int
tprintf(char *fmt, ...)
{
	va_list marker;
	int result;
	static char msg[200];
	static FILE f;

	f._ptr = f._base = msg;
	f._cnt = 32000;
	f._flag = _IOWRT | _IOFBF;
	va_start(marker, fmt);
	result = _doprnt(fmt, marker, &f);
	*f._ptr = '\0';
	va_end(marker);
	tputs(msg);
	return result;
}

/*
 * Convert a high low pair into a long
 */
static long
makelong(unsigned high, unsigned low)
{
	union {
		struct {
			unsigned low;
			unsigned high;
		} hl;
		long l;
	} v;

	v.hl.high = high;
	v.hl.low = low;
	return v.l;
}


/*
 * Convert a segment offset pair into a far pointer.
 */
static void _far *
makefptr(unsigned seg, unsigned off)
{
	union {
		struct {
			unsigned off;
			unsigned seg;
		} so;
		void _far *ptr;
	} v;

	v.so.seg = seg;
	v.so.off = off;
	return v.ptr;
}

static char buff[MAXBUFF];

/*
 * Convert a $ terminated string to a 0 terminated one
 */
static char *
makestring(unsigned sseg, unsigned soff)
{
	char _far *p, *s;
	unsigned count = 0;

	p = makefptr(sseg, soff);
	s = buff;
	while (*p != '$' && count < datalen) {
		*s++ = *p++;
		count++;
	}
	*s = '\0';
	return buff;
}

/*
 * Return day of week
 */
static char *
weekday(int n)
{
	switch (n) {
		case 0: return "Sun";
		case 1: return "Mon";
		case 2: return "Tue";
		case 3: return "Wed";
		case 4: return "Thu";
		case 5: return "Fri";
		case 6: return "Sat";
	}
}

/*
 * Decode device information as for ioctl 0
 */
static void
devinfo(unsigned n)
{
	if (n & 0x80) {
		tputs("\tCHARDEV: ");
		if (n & 0x01) tputs("STDIN ");
		if (n & 0x02) tputs("STDOUT ");
		if (n & 0x04) tputs("NUL ");
		if (n & 0x08) tputs("CLOCK$ ");
		if (n & 0x10) tputs("SPECIAL ");
		if (n & 0x20) tputs("RAW ");
		if (n & 0x40) tputs("NOT_EOF "); else tputs("EOF ");
		if (n & 0x1000) tputs("REMOTE "); else tputs("LOCAL ");
		if (n & 0x4000) tputs("CAN_IOCTL"); else tputs("NO_IOCTL");
	} else {
		tputs("\tFILE: ");
		tprintf("device=%u ", n & 0x1f);
		if (n & 0x40) tputs("NOT_WRITTEN "); else tputs("WRITTEN ");
		if (n & 0x800) tputs("FIXED "); else tputs("REMOVABLE ");
		if (n & 0x4000) tputs("KEEP_DATE "); else tputs("UPDATE_DATE ");
		if (n & 0x8000) tputs("REMOTE"); else tputs("LOCAL");
	}
	tputs("\r\n");
}

/*
 * Convert a boolean variable to on / off.
 */
static char *
makeonoff(int n)
{
	switch (n) {
	case 0:
		return "off";
	case 1:
		return "on";
	default:
		return itoa(n, buff, 10);
	}
}


/*
 * Print the string contained in the buffer specified of len bytes.
 */
static void
outbuff(unsigned sseg, unsigned soff, unsigned len)
{
	char _far *p;
	unsigned l, i;
	char *s = buff;
	int hex = 0;
	static char hexnum[] = "0123456789abcdef";

	p = makefptr(sseg, soff);
	l = min(datalen, len);
	for (i = 0; i < l; i++)
		if (!isascii(p[i]))
			if (hexprint)
				hex = 1;
			else
				return;
	*s++ = '\t';
	if (hex) {
		*s++ = '{';
		for (i = 0; i < l; i++) {
			*s++ = '0';
			*s++ = 'x';
			*s++ = hexnum[(unsigned char)p[i]>>4];
			*s++ = hexnum[p[i] & 0xf];
			*s++ = ',';
		}
		if (l < len) { *s++ = '.'; *s++ = '.'; *s++ = '.';}
		*s++ = '}';
	} else {
		*s++ = '"';
		for (i = 0; i < l; i++)
			switch (p[i]) {
			case '\n': *s++ = '\\'; *s++ = 'n'; break;
			case '\t': *s++ = '\\'; *s++ = 't'; break;
			case '\b': *s++ = '\\'; *s++ = 'b'; break;
			case '\r': *s++ = '\\'; *s++ = 'r'; break;
			case '\f': *s++ = '\\'; *s++ = 'f'; break;
			case '\v': *s++ = '\\'; *s++ = 'v'; break;
			case '\a': *s++ = '\\'; *s++ = 'a'; break;
			default:
				if (iscntrl(p[i])) {
					*s++ = '\\'; *s++ = 'x';
					*s++ = hexnum[(unsigned)p[i]>>4];
					*s++ = hexnum[p[i] & 0xf];
				} else
					*s++ = p[i];
			}
		if (l < len) { *s++ = '.'; *s++ = '.'; *s++ = '.';}
		*s++ = '"';
	}
	*s++ = '\0';
	tputs(buff);
}

	
/*
 * Return executing programs PSP
 */
static unsigned
getpsp(void)
{
	_asm mov ah, 51h
	_asm int 21h
	_asm mov ax, bx
}

/*
 * Set executing programs PSP
 */
static void
setpsp(unsigned psp)
{
	_asm mov ah, 50h
	_asm mov bx, psp
	_asm int 21h
}

/*
 * Return the DOS critical section flag pointer.
 */
static char _far *
getcritsectflag(void)
{
	unsigned sseg, soff;

	_asm mov ah, 34h
	_asm int 21h
	_asm mov ax, es
	_asm mov sseg, ax
	_asm mov soff, bx
	return makefptr(sseg, soff);
}

/*
 * Return the DOS Data Transfer Address (DTA)
 */
static void _far *
getdta(void)
{
	unsigned sseg, soff;

	_asm mov ah, 2fh
	_asm int 21h
	_asm mov ax, es
	_asm mov sseg, ax
	_asm mov soff, bx
	return makefptr(sseg, soff);
}

/*
 * Convert a file mode number into a Unix ls -l like string.
 */
static char *
strmode(unsigned mode)
{
	char *p = buff;

	if (!stringprint)
		return "";
	*p++ = '[';
	if (mode & 0x20) *p++ = 'a'; else *p++ = '-';
	if (mode & 0x10) *p++ = 'd'; else *p++ = '-';
	if (mode & 0x08) *p++ = 'v'; else *p++ = '-';
	if (mode & 0x04) *p++ = 's'; else *p++ = '-';
	if (mode & 0x02) *p++ = 'h'; else *p++ = '-';
	if (mode & 0x01) *p++ = 'r'; else *p++ = '-';
	*p++ = ']';
	*p++ = '\0';
	return buff;
}

/*
 * Print the DTA contents as filled by the find first / find next functions
 */
static void
outdta(void)
{
	struct s_dta {
		char reserve[21];
		unsigned char mode;
		unsigned short time;
		unsigned short date;
		long size;
		char name[13];
	} _far *d;
	int psp;

	psp = getpsp();
	setpsp(tracedpsp);
	d = getdta();
	setpsp(psp);
	tprintf("ok\t(%-12Fs %10lu %2u/%2u/%2u %2u:%02u.%-2u %s)\r\n", d->name, d->size, (d->date >> 9) + 80, (d->date >> 5) & 0xf, d->date & 0x1f, d->time >> 11, (d->time >> 5) & 0x3f, d->time & 0x1f, strmode(d->mode));
}

char *errcodes[] = {
	"Error 0",
	"Invalid function code",
	"File not found",
	"Path not found",
	"Too many open files",
	"Access denied",
	"Invalid handle",
	"Memory control blocks destroyed",
	"Insufficient memory",
	"Invalid memory block address",
	"Invalid environment",
	"Invalid format",
	"Invalid access code",
	"Invalid data",
	"Reserved 14",
	"Invalid drive",
	"Attempt to remove current directory",
	"Not same device",
	"No more files",
	"Disk is write-protected",
	"Bad disk unit",
	"Drive not ready",
	"Invalid disk command",
	"CRC error",
	"Invalid length",
	"Seek error",
	"Not an MS-DOS disk",
	"Sector not found",
	"Out of paper",
	"Write fault",
	"Read fault",
	"General failure",
	"Sharing violation",
	"Lock violation",
	"Wrong disk",
	"FCB unavailable",
};

/*
 * Print error without newline
 */
static void
errprint(int err)
{
	if (worderror && err <= 35)
		tprintf("Error (%s)", errcodes[err]);
	else
		tprintf("Error (%u)", err);
}

/*
 * Print error with newline
 */
static void
errprintln(int err)
{
	if (worderror && err <= 35)
		tprintf("Error (%s)\r\n", errcodes[err]);
	else
		tprintf("Error (%u)\r\n", err);
}

/*
 * Print error or ok
 */
static void
okerrorproc(unsigned flags, unsigned ax)
{
	if (flags & 1)
		errprintln(ax);
	else
		tputs("ok\r\n");
}

/*
 * Print error
 */
static void
nerrorproc(unsigned flags, unsigned ax)
{
	if (flags & 1)
		errprintln(ax);
	else
		tprintf("%u\r\n", ax);
}

/*
 * Print memory allocation strategy
 */
static void
print_memory_strategy(unsigned str)
{
	if (str & 0x40)
		tputs("high memory ");
	else if (str & 0x80)
		tputs("high then low memory ");
	else 
		tputs("low memory ");
	switch (str & 7) {
	case 0: tputs("first fit"); break;
	case 1: tputs("best fit"); break;
	case 2: tputs("last fit"); break;
	}
}

/*
 * Print date and time as packed in DOS format
 */
static void
print_dtim(int date, int time)
{
	tprintf("%2u/%2u/%2u %2u:%02u.%-2u",
		(date >> 9) + 80, (date >> 5) & 0xf, date & 0x1f,
		time >> 11, (time >> 5) & 0x3f, time & 0x1f);
}

/*
 * Print time of day.
 */
static void
print_time(void)
{
	_strtime(buff);
	buff[8] = ' ';
	buff[9] = '\0';
	tputs(buff);
}

#pragma check_stack()

struct s_func {
	char *name;
	unsigned count;
} funcs[256] =
{
	{"terminate", 0},		/* 00H	DOS1 - TERMINATE PROGRAM */
	{"key_in_echo", 0},		/* 01H	DOS1 - KEYBOARD INPUT WITH ECHO */
	{"disp_out", 0},		/* 02H	DOS1 - DISPLAY OUTPUT */
	{"serial_in", 0},		/* 03H	DOS1 - SERIAL INPUT */
	{"serial_out", 0},		/* 04H	DOS1 - SERIAL OUTPUT */
	{"printer_out", 0},		/* 05H	DOS1 - PRINTER OUTPUT */
	{"direct_out", 0},		/* 06H	DOS1 - DIRECT CONSOLE OUT */
	{"dir_key_in", 0},		/* 07H	DOS1 - DIRECT KEYBOARD INPUT */
	{"key_in", 0},			/* 08H	DOS1 - KEYBOARD INPUT WITHOUT ECHO */
	{"disp_string", 0},		/* 09H	DOS1 - DISPLAY STRING */
	{"buf_key_in", 0},		/* 0AH	DOS1 - BUFFERED KEYBOARD INPUT */
	{"chk_key_stat", 0},		/* 0BH	DOS1 - CHECK KEYBOARD STATUS */
	{"clr_key_func", 0},		/* 0CH	DOS1 - CLEAR KEY BUFFER AND PERFORM FUNCTION */
	{"flush", 0},			/* 0DH	DOS1 - DISK RESET */
	{"sel_drive", 0},		/* 0EH	DOS1 - SELECT CURRENT DRIVE */
	{"open_file", 0},		/* 0FH	DOS1 - OPEN FILE */
	{"close_file", 0},		/* 10H	DOS1 - CLOSE FILE */
	{"search_first", 0},		/* 11H	DOS1 - SEARCH FOR FIRST MATCHING FILE */
	{"search_next", 0},		/* 12H	DOS1 - SEARCH FOR NEXT MATCHING FILE */
	{"delete_file", 0},		/* 13H	DOS1 - DELETE FILE */
	{"rd_seq_rec", 0},		/* 14H	DOS1 - READ SEQUENTIAL RECORD */
	{"wr_seq_rec", 0},		/* 15H	DOS1 - WRITE SEQUENTIAL RECORD */
	{"create_file", 0},		/* 16H	DOS1 - CREATE FILE */
	{"rename_file", 0},		/* 17H	DOS1 - RENAME FILE */
	{"reserved 0x18", 0},		/* 18h */
	{"rd_cur_drive", 0},		/* 19H	DOS1 - REPORT CURRENT DRIVE */
	{"set_dta", 0},			/* 1AH	DOS1 - SET DISK TRANSFER AREA FUCNTION */
	{"rd_fat_1", 0},		/* 1BH	DOS1 - READ CURRENT DRIVE'S FAT */
	{"drive_info", 0},		/* 1CH	DOS1 - READ ANY DRIVE'S FAT */
	{"reserved 0x1d", 0},		/* 1dh */
	{"reserved 0x1e", 0},		/* 1eh */
	{"reserved 0x1f", 0},		/* 1fh */
	{"reserved 0x20", 0},		/* 20h */
	{"rd_ran_rec1", 0},		/* 21H	DOS1 - READ RANDOM FILE RECORD */
	{"wr_ran_rec1", 0},		/* 22H	DOS1 - WRITE RANDOM FILE RECORD */
	{"rd_file_size", 0},		/* 23H	DOS1 - REPORT FILE SIZE */
	{"set_ran_rec", 0},		/* 24H	DOS1 - SET RANDOM RECORD FIELD SIZE */
	{"set_int_vec", 0},		/* 25H	DOS1 - SET INTERRUPT VECTOR */
	{"create_seg", 0},		/* 26H	DOS1 - CREATE PROGRAM SEGMENT FUCNTION */
	{"rd_ran_rec2", 0},		/* 27H	DOS1 - READ RANDOM FILE RECORD */
	{"wr_ran_rec2", 0},		/* 28H	DOS1 - WRITE RANDOM FILE RECORD FUCNTION */
	{"parse_name", 0},		/* 29H	DOS1 - PARSE FILENAME */
	{"get_date", 0},		/* 2AH	DOS1 - GET DATE */
	{"set_date", 0},		/* 2BH	DOS1 - SET DATE */
	{"get_time", 0},		/* 2CH	DOS1 - GET TIME */
	{"set_time", 0},		/* 2DH	DOS1 - SET TIME */
	{"set_verify", 0},		/* 2EH	DOS1 - SET DISK WRITE VERIFICATION MODE */
	{"get_dta", 0},			/* 2FH	DOS2 - GET DISK TRANSFER AREA ADDRESS */
	{"get_ver", 0},			/* 30H	DOS2 - GET DOS VERSION NUMBER */
	{"keep", 0},			/* 31H	DOS2 - ADVANCED TERMINATE BUT STAY RESIDENT */
	{"reserved 0x32", 0},		/* 32h */
	{"cntrl_brk", 0},		/* 33H	DOS2 - GET/SET CONTROL BREAK STATUS */
	{"critical_flag", 0},		/* 34h */
	{"get_int_vec", 0},		/* 35H	DOS2 - GET INTERRUPT VECTOR */
	{"get_space", 0},		/* 36H	DOS2 - GET DISK FREE SPACE */
	{"switchar", 0},			/* 37h */
	{"get_country", 0},		/* 38H	DOS2 - GET COUNTRY INFORMATION */
	{"mkdir", 0},			/* 39H	DOS2 - MAKE DIRECTORY */
	{"rmdir", 0},			/* 3AH	DOS2 - REMOVE DIRECTORY */
	{"chdir", 0},			/* 3BH	DOS2 - CHANGE CURRENT DIRECTORY FUCNTION */
	{"create", 0},			/* 3CH	DOS2 - CREATE FILE */
	{"open", 0},			/* 3DH	DOS2 - OPEN FILE */
	{"close", 0},			/* 3EH	DOS2 - CLOSE FILE */
	{"read", 0},			/* 3FH	DOS2 - READ FILE/DEVICE */
	{"write", 0},			/* 40H	DOS2 - WRITE FILE/DEVICE */
	{"delete", 0},			/* 41H	DOS2 - DELETE FILE */
	{"lseek", 0},			/* 42H	DOS2 - MOVE FILE POINTER */
	{"chmod", 0},			/* 43H	DOS2 - CHANGE FILE MODE */
	{"ioctl", 0},			/* 44H	DOS2 - DEVICE I/O CONTROL */
	{"dup", 0},			/* 45H	DOS2 - DUPLICATE FILE HANDLE */
	{"cdup", 0},			/* 46H	DOS2 - FORCE FILE HANDLE DUPLICATION */
	{"get_dir", 0},			/* 47H	DOS2 - GET CURRENT DIRECTORY */
	{"allocate", 0},		/* 48H	DOS2 - ALLOCATE MEMORY */
	{"free", 0},			/* 49H	DOS2 - FREE MEMORY */
	{"set_block", 0},		/* 4AH	DOS2 - MODIFY ALLOCATED MEMORY BLOCK */
	{"exec", 0},			/* 4BH	DOS2 - LOAD/EXECUTE PROGRAM */
	{"term_proc", 0},		/* 4CH	DOS2 - TERMINATE PROCESS */
	{"get_code", 0},		/* 4DH	DOS2 - GET SUBPROGRAM RETURN CODE */
	{"find_first", 0},		/* 4EH	DOS2 - FIND FIRST FILE MATCH */
	{"find_next", 0},		/* 4FH	DOS2 - FIND NEXT FILE MATCH */
	{"set_psp", 0},			/* 50h */
	{"get_psp", 0},			/* 51h */
	{"sysvars", 0},			/* 52h GET LIST OF LISTS */
	{"reserved 0x53", 0},		/* 53h */
	{"get_verify", 0},		/* 54H	DOS2 - GET FILE WRITE VERIFY STATE */
	{"reserved 0x55", 0},		/* 55h */
	{"rename", 0},			/* 56H	DOS2 - RENAME FILE */
	{"date_time", 0},		/* 57H	DOS2 - GET/SET FILE DATE/TIME */
	{"alloc_strategy", 0},		/* 58h */
	{"get_err", 0},			/* 59H	DOS3 - GET EXTENDED RETURN CODE */
	{"create_temp", 0},		/* 5AH	DOS3 - CREATE TEMPORARY FILE */
	{"create_new", 0},		/* 5BH	DOS3 - CREATE NEW FILE */
	{"file_lock", 0},		/* 5CH	DOS3 - LOCK/UNLOCK FILE ACCESS */
	{"reserved 0x5d", 0},		/* 5dh */
	{"machine_name", 0},		/* 5eh */
	{"assign_list", 0},		/* 5fh */
	{"reserved 0x60", 0},		/* 60h */
	{"reserved 0x61", 0},		/* 61h */
	{"get_psp", 0},			/* 62H	DOS3 - GET PROGRAM SEGMENT PREFIX ADDRESS */
};


/*
 * Print any prefixes specified by the user before the actual function call.
 */
static void
prefix(int fun, unsigned cs, unsigned ip)
{
	if (timeprint)
		print_time();
	if (pspprint)
		tprintf("%04x ", tracedpsp);
	if (numprint)
		tprintf("%02x ", fun);
	if (branchprint)
		tprintf("%04X:%04X ", cs, ip - 2);
}


/* 
 * The new DOS interrupt handler 
 */
static void _cdecl _interrupt _far
dos_handler(
	unsigned _es,
	unsigned _ds,
	unsigned _di,
	unsigned _si,
	unsigned _bp,
	unsigned _sp,
	unsigned _bx,
	unsigned _dx,
	unsigned _cx,
	unsigned _ax,
	unsigned _ip,
	unsigned _cs,
	unsigned _flags)
{
	static trace = 1;
	static int fun, funl;

	/* Filter out interrupts generated by us */
	if (!execed) {
		if ((_ax >>8) == 0x4b)		/* Exec */
			execed = 1;
		_chain_intr(old_dos_handler);
	}
	if (!trace || *critsectflag || *criterrflag)
		_chain_intr(old_dos_handler);
	trace = 0;
	tracedpsp = getpsp();
	if (tracetsr && tracedpsp != tsrpsp) {
		trace = 1;
		_chain_intr(old_dos_handler);
	}
	fun = _ax >> 8;
	if (count) {
		funcs[fun].count++;
		trace = 1;
		_chain_intr(old_dos_handler);
	}
	setpsp(mypsp);
	if (someprefix)
		prefix(fun, _cs, _ip);
	switch (fun) {
	case 0x02:				/* disp_out */
		tprintf("display_char('%c')\r\n", _dx & 0xff);
		break;
	case 0x06:				/* direct_out */
		tprintf("direct_out('%c')\r\n", _dx & 0xff);
		break;
	case 0x09:				/* disp_string */
		if (stringprint)
			tprintf("display(\"%s\")\r\n", makestring(_ds, _dx));
		else
			tprintf("display(%04X:%04X)\r\n", _ds, _dx);
		break;
	case 0x0d:
		tputs("flush()\r\n");
		break;
	case 0x0e:				/* set_current_disk */
		tprintf("set_current_disk(%c:) = ", (_dx & 0xff) < 27 ? (_dx & 0xff) + 'A' : '.');
		break;
	case 0x19:				/* get_current_disk */
		break;
	case 0x1a:				/* set_dta */
		tprintf("set_dta(%04X:%04X)\r\n", _ds, _dx);
		break;
	case 0x1c:				/* drive information */
		tprintf("drive_info(%d) = ", _dx & 0xff);
		break;
	case 0x25:				/* set_vector */
		tprintf("set_vector(%#x, %04X:%04X)\r\n", _ax & 0xff, _ds, _dx);
		break;
	case 0x29:				/* parse_name */
		tprintf("parse_name(%04X:%04X, %d, %04X:%04X) = ",
			_ds, _si, _ax & 0xff, _es, _di);
		break;
	case 0x2a:				/* get_date */
	case 0x2c:				/* get_time */
		break;
	case 0x2d:				/* set_time */
		tprintf("set_time(%02d:%02d:%02d.%d) = ", _cx >> 8, _cx & 0xff, _dx >> 8, _dx & 0xff);
		break;
	case 0x2f:				/* get_dta */
	case 0x30:				/* get_version */
		break;
	case 0x33:				/* cntrl_brk */
		switch (funl = (_ax & 0xff)) {
		case 0:
			break;
		case 1:
			tprintf("set_break(%s)\r\n", makeonoff(_dx & 0xff));
			break;
		case 2:
			tprintf("getset_break(%s) = ", makeonoff(_dx & 0xff));
			break;
		default:
			goto aka_default;
		}
		break;
	case 0x34:				/* critical_flag */
		break;
	case 0x35:				/* get_vector */
		tprintf("get_vector(%#x) = ", _ax & 0xff);
		break;
	case 0x39:				/* Mkdir */
		tprintf("mkdir(\"%Fs\") = ", makefptr(_ds, _dx));
		break;
	case 0x3a:				/* Rmdir */
		tprintf("rmdir(\"%Fs\") = ", makefptr(_ds, _dx));
		break;
	case 0x3b:				/* Chdir */
		tprintf("chdir(\"%Fs\") = ", makefptr(_ds, _dx));
		break;
	case 0x3c:				/* Creat */
		tprintf("creat(\"%Fs\", %02x%s) = ", makefptr(_ds, _dx), _cx, strmode(_cx));
		break;
	case 0x3d:				/* Open */
		tprintf("open(\"%Fs\", %d) = ", makefptr(_ds, _dx), _ax & 0xff);
		break;
	case 0x3e:				/* Close */
		tprintf("close(%u) = ", _bx);
		break;
	case 0x3f:				/* Read */
		tprintf("read(%u, %04X:%04X, %u) = ", _bx, _ds, _dx, _cx);
		break;
	case 0x40:				/* Write */
		tprintf("write(%u, %04X:%04X, %u) = ", _bx, _ds, _dx, _cx);
		break;
	case 0x41:				/* Unlink */
		tprintf("chdir(\"%Fs\") = ", makefptr(_ds, _dx));
		break;
	case 0x42:				/* Lseek */
		tprintf("lseek(%u, %ld, %d) = ",_bx, makelong(_cx, _dx), _ax & 0xff);
		break;
	case 0x43:				/* Chmod / getmod */
		switch (funl = (_ax & 0xff)) {
		case 0:
			tputs("getmod(\"");
			break;
		case 1:
			tputs("chmod(\"");
			break;
		default:
			goto aka_default;
		}
		tprintf("%Fs", makefptr(_ds, _dx));
		if ((_ax & 0xff) == 1)
			tprintf("\", %#x%s", _cx, strmode(_cx));
		tputs(") = ");
		break;
	case 0x44:				/* ioctl */
		switch (funl = (_ax & 0xff)) {
		default:
			goto aka_default;
		case 0x00:			/* get device info */
			tprintf("ioctl(GET_DEV_INFO, %d) = ", _bx);
			break;
		case 0x02:			/* read from chdev control */
			tprintf("ioctl(READ_CHDEV_CCHAN, %d, %u, %04X:%04X) = ",
				_bx, _cx, _ds, _dx);
			break;
		case 0x07:			/* get output status */
			tprintf("ioctl(GET_OUTPUT_STATUS, %d) = ", _bx);
			break;
		case 0x08:			/* check if removable */
			tprintf("ioctl(BD_ISREMOVABLE, %d) = ", _bx & 0xff);
			break;
		case 0x09:			/* check if remote */
			tprintf("ioctl(BD_ISREMOTE, %d) = ", _bx & 0xff);
			break;
		case 0x0a:			/* check if remote */
			tprintf("ioctl(HAN_ISREMOTE, %d) = ", _bx);
			break;
		case 0x0e:			/* get logical map */
			tprintf("ioctl(GET_LMAP, %d) = ", _bx & 0xff);
			break;
		case 0x0f:			/* set logical map */
			tprintf("ioctl(SET_LMAP, %d) = ", _bx & 0xff);
			break;
		}
		break;
	case 0x45:				/* Dup */
		tprintf("dup(%u) = ", _bx);
		break;
	case 0x46:				/* Dup2 */
		tprintf("dup2(%u, %u) = ", _bx, _cx);
		break;
	case 0x47:				/* Getcwd */
		tprintf("getcwd(%d, %04X:%04X) = ", _dx & 0xff, _ds, _si);
		break;
	case 0x48:				/* Alloc */
		tprintf("alloc(%#x0)= ", _bx);
		break;
	case 0x49:				/* Free */
		tprintf("free(%04X:0000) = ", _es);
		break;
	case 0x4a:				/* Realloc */
		tprintf("realloc(%04X:0000, %#x0) = ", _es, _bx);
		break;
	case 0x4b:				/* Exec */
		tprintf("exec(\"%Fs", makefptr(_ds, _dx));
		if (tracechildren)
			tputs("\") = ...\r\n");
		else
			tputs("\") = ");
		if (tracechildren) {
			trace = 1;
			setpsp(tracedpsp);
			_chain_intr(old_dos_handler);
		}
		break;
	case 0x4c:				/* Exit */
		tprintf("exit(%d)\r\n", _ax & 0xff);
		if (tracechildren) {
			trace = 1;
			setpsp(tracedpsp);
			_chain_intr(old_dos_handler);
		}
		break;
	case 0x4d:				/* Get return code */
		break;
	case 0x4e:				/* Findfirst */
		tprintf("findfirst(\"%Fs\", %#x%s) = ", makefptr(_ds, _dx), _cx, strmode(_cx));
		break;
	case 0x4f:				/* Findnext */
		tputs("findnext() = ");
		break;
	case 0x50:				/* set_psp */
		tprintf("set_psp(%04X)\r\n", _bx);
		break;
	case 0x51:				/* get_psp */
		break;
	case 0x52:				/* sysvars */
		tputs("sysvars() = ");
		break;
	case 0x57:				/* get/set date time */
		switch (funl = (_ax & 0xff)) {
		case 0:				/* get time */
			tprintf("get_time(%d) = ", _bx);
			break;
		case 1:				/* set time */
			tprintf("set_time(%d, ");
			print_dtim(_dx, _cx);
			tputs(") = ");
			break;
		default:
			goto aka_default;
		}
		break;
	case 0x58:				/* memory allocation */
		switch (funl = (_ax & 0xff)) {
		case 0:				/* get strategy */
			tputs("get_alloc_str() = ");
			break;
		case 1:				/* set strategy */
			tputs("set_alloc_str(");
			print_memory_strategy(_bx & 0xff);
			tputs(") = ");
			break;
		case 2:				/* get umb state */
			tputs("get_umb_state() = ");
			break;
		case 3:				/* set umb state */
			tprintf("set_umb_state(%d) = ", _bx);
			break;
		default:
			goto aka_default;
		}
		break;
	case 0x5b:				/* Creat new */
		tprintf("creat_new(\"%Fs\", %02x%s) = ", makefptr(_ds, _dx),
			 _cx, strmode(_cx));
		break;
	case 0x62:				/* get_psp */
		break;
	case 0x55:				/* Create child PSP */
		tprintf("child_psp(%04X, %04X) = ", _dx, _si);
		break;
	case 0x5a:				/* Tmpfile */
		tprintf("tmpfile(\"%Fs\", %#x) = ", makefptr(_ds, _dx),
			_cx & 0xff);
		break;
	case 0x6c:				/* Open */
		tprintf("open4(\"%Fs\", %d, %s, %d) = ", makefptr(_ds, _si), _bx, strmode(_cx), _dx);
		break;

	aka_default:			/* All unknown functions arrive here */
	default:
		if (otherprint) {
			if (nameprint && fun <= 0x62)
				tputs(funcs[fun].name);
			else
				tprintf("%02x", fun);
			if (regdump)
				tprintf(" :ax=%04X bx=%04X cx=%04X dx=%04X si=%04X di=%04X ds=%04X es=%04X\r\n", _ax, _bx, _cx, _dx, _si, _di, _ds, _es);
			else
				tputs("\r\n");
		}
		setpsp(tracedpsp);
		trace = 1;
		_chain_intr(old_dos_handler);
	}
	/*
	 * This is the best place to flush the file.
	 * We have printed the function and its parameters, so if the
	 * systems crashes the information will be saved.
	 */
	if (append) {
		close(fd);
		if ((fd = open(fname, O_TEXT | O_APPEND | O_WRONLY, 0666)) == -1) {
			fd = 1;
			tprintf("ERROR %s \r\n", sys_errlist[errno]);
		}
		lseek(fd, 0l, SEEK_END);
	}
	/*
	 * Call the DOS interrupt.  Transfer all function variables to the
	 * machine registers, call DOS and transfer the machine registers
	 * back to the function variables.
	 */
	setpsp(tracedpsp);
	_asm {
		pushf
		push ds
		mov ax, _flags
		push ax
		popf
		mov ax, _es
		mov es, ax
		mov ax, _ds
		mov ds, ax
		mov ax, _ax
		mov bx, _bx
		mov cx, _cx
		mov dx, _dx
		mov si, _si
		mov di, _di
		int 21h
		mov _ax, ax
		mov _bx, bx
		mov _cx, cx
		mov _dx, dx
		mov _si, si
		mov _di, di
		mov ax, es
		mov _es, ax
		mov ax, ds
		mov es, ax
		pushf
		pop ax
		mov _flags, ax
		pop ds
		popf
	}
	setpsp(mypsp);
	switch (fun) {
	case 0x02:				/* disp_out */
	case 0x06:				/* direct_out */
	case 0x09:				/* disp_string */
	case 0x0d:				/* flush */
		break;
	case 0x0e:				/* set_current_disk */
		tprintf("%d\r\n", _ax & 0xff);
		break;
	case 0x19:				/* get_current_disk */
		tprintf("get_current_disk() = %c:\r\n", (_ax & 0xff) + 'A');
		break;
	case 0x1a:				/* set_dta */
		break;
	case 0x1c:				/* drive information */
		tprintf("s/c=%u b/s=%u nc=%u ty=", _ax & 0xff, _cx, _dx);
		switch (*(unsigned char *)makefptr(_ds, _bx)) {
		case 0xff: tputs("320K\r\n"); break;
		case 0xfe: tputs("160K\r\n"); break;
		case 0xfd: tputs("360K\r\n"); break;
		case 0xfc: tputs("180K\r\n"); break;
		case 0xf9: tputs("1.2M\r\n"); break;
		case 0xf8: tputs("HD\r\n"); break;
		default: tputs("other\r\n"); break;
		}
		break;
	case 0x25:				/* set_vector */
		break;
	case 0x29:				/* parse_name */
		tprintf("%d (%04X:%04X)\r\n", _ax & 0xff, _ds, _si);
		break;
	case 0x2a:				/* get_date */
		tprintf("get_date() = %2u/%2u/%2u (%s)\r\n", _cx, _dx >> 8,  _dx & 0xff, weekday(_ax & 0xff));
		break;
	case 0x2c:				/* get_time */
		tprintf("get_time() = %02d:%02d:%02d.%d\r\n", _cx >> 8, _cx & 0xff, _dx >> 8, _dx & 0xff);
		break;
	case 0x2d:				/* set_time */
		if ((_ax & 0xff) == 0)
			tputs("ok\r\n");
		else
			tputs("invalid\r\n");
		break;
	case 0x2f:				/* get_dta */
		tprintf("get_dta() = %04X:%04X\r\n", _es, _bx);
		break;
	case 0x30:				/* get_version */
		tprintf("get_version() = %u.%u\r\n", _ax & 0xff, _ax >> 8);
		break;
	case 0x33:				/* cntrl_brk */
		switch (funl) {
		case 0:
			tprintf("get_break() = %s\r\n", makeonoff(_dx & 0xff));
			break;
		case 1:
			break;
		case 2:
			tprintf("%s\r\n", makeonoff(_dx & 0xff));
			break;
		}
		break;
	case 0x34:				/* critical_flag */
		tprintf("critical_flag() = %04X:%04X\r\n", _es, _bx);
		break;
	case 0x35:				/* get_vector */
		tprintf("%04X:%04X\r\n", _es, _bx);
		break;
	case 0x39:				/* Mkdir */
	case 0x3a:				/* Rmdir */
	case 0x3b:				/* Chdir */
		okerrorproc(_flags, _ax);
		break;
	case 0x3c:				/* Creat */
	case 0x3d:				/* Open */
		nerrorproc(_flags, _ax);
		break;
	case 0x3e:				/* Close */
		okerrorproc(_flags, _ax);
		break;
	case 0x3f:				/* Read */
	case 0x40:				/* Write */
		if (_flags & 1)
			errprint(_ax);
		else
			tprintf("%u", _ax);
		if (stringprint)
			outbuff(_ds, _dx, _ax);
		tputs("\r\n");
		break;
	case 0x41:				/* Unlink */
		okerrorproc(_flags, _ax);
		break;
	case 0x42:				/* Lseek */
		if (_flags & 1)
			errprintln(_ax);
		else
			tprintf("%ld\r\n", makelong(_dx, _ax));
		break;
	case 0x43:				/* Chmod / getmod */
		if (_flags & 1)
			errprint(_ax);
		else {
			if ((_ax & 0xff) == 0)
				tprintf("%u%s", _cx, strmode(_cx));
			else
				tputs("ok");
		}
		tputs("\r\n");
		break;
	case 0x44:				/* ioctl */
		switch (funl) {
		case 0x00:			/* get device info */
			if (_flags & 1)
				errprintln(_ax);
			else {
				if (stringprint)
					devinfo(_dx);
				else
					tprintf("%04X\r\n", _dx);
			}
			break;
		case 0x02:			/* read from chdev control */
			if (_flags & 1)
				errprint(_ax);
			else
				tprintf("%u", _ax);
			if (stringprint)
				outbuff(_ds, _dx, _ax);
			tputs("\r\n");
			break;
		case 0x07:			/* get output status */
			if (_flags & 1)
				errprint(_ax);
			else
				tputs((_ax & 0xff) ? "NOT_READY" : "READY");
			tputs("\r\n");
			break;
		case 0x08:			/* check if removable */
			if (_flags & 1)
				errprint(_ax);
			else
				tputs(_ax ? "FIXED" : "REMOVABLE");
			tputs("\r\n");
			break;
		case 0x09:			/* check if remote */
			if (_flags & 1)
				errprint(_ax);
			else {
				tputs((_dx & 0x8000) ? "SUBST " : "NO_SUBST ");
				tputs((_dx & 0x1000) ? "REMOTE " : "LOCAL ");
				tputs((_dx & 0x0200) ? "NOIO" : "IO");
			}
			tputs("\r\n");
			break;
		case 0x0a:			/* check if remote */
			if (_flags & 1)
				errprint(_ax);
			else
				tputs((_dx & 0x8000) ? "REMOTE" : "LOCAL");
			tputs("\r\n");
			break;
		case 0x0e:			/* get logical map */
			if (_flags & 1)
				errprint(_ax);
			else
				tprintf("%d", _ax & 0xff);
			tputs("\r\n");
			break;
		case 0x0f:			/* set logical map */
			okerrorproc(_flags, _ax);
			break;
		}
		break;
	case 0x45:				/* Dup */
		nerrorproc(_flags, _ax);
		break;
	case 0x46:				/* Dup2 */
		okerrorproc(_flags, _ax);
		break;
	case 0x47:				/* Getcwd */
		if (_flags & 1)
			errprintln(_ax);
		else {
			if (stringprint)
				tprintf("ok\t\"%Fs\"\r\n", makefptr(_ds, _si));
			else
				tputs("ok\r\n");
		}
		break;
	case 0x48:				/* Alloc */
		if (_flags & 1) {
			errprint(_ax);
			tprintf("\t(free = %#x0 bytes)\r\n", _bx);
		} else
			tprintf("%04X:0000\r\n", _ax);
		break;
	case 0x49:				/* Free */
		okerrorproc(_flags, _ax);
		break;
	case 0x4a:				/* Realloc */
		if (_flags & 1) {
			errprint(_ax);
			tprintf("\t(free = %#x0 bytes)\r\n", _bx);
		} else
			tputs("ok\r\n");
		break;
	case 0x4b:				/* Exec */
		okerrorproc(_flags, _ax);
		break;
	case 0x4c:				/* Exit */
		/* NOTREACHED */
		break;
	case 0x4d:				/* Get return code */
		tprintf("get_code() = %d %d\r\n", _ax >> 8, _ax & 0xff);
		break;
	case 0x4e:				/* Findfirst */
		if (_flags & 1)
			errprintln(_ax);
		else {
			if (stringprint)
				outdta();
			else
				tputs("ok\r\n");
		}
		break;
	case 0x4f:				/* Findnext */
		if (_flags & 1)
			errprintln(_ax);
		else {
			if (stringprint)
				outdta();
			else
				tputs("ok\r\n");
		}
		break;
	case 0x50:				/* set_psp */
		tracedpsp = _bx;
		break;
	case 0x52:				/* sysvars */
		tprintf("%04X:%04X\r\n", _es, _bx);
		break;
	case 0x51:				/* get_psp */
	case 0x62:				/* get_psp */
		tprintf("get_psp() = %04X\r\n", _bx);
		break;
	case 0x55:				/* Create child PSP */
		if (_flags & 1)
			errprintln(_ax);
		else {
			tputs("ok\r\n");
			tracedpsp = _dx;
		}
		break;
	case 0x57:				/* get/set date time */
		switch (funl = (_ax & 0xff)) {
		case 0:				/* get time */
			if (_flags & 1)
				errprint(_ax);
			else
				print_dtim(_dx, _cx);
			tputs("\r\n");
			break;
		case 1:				/* set time */
			okerrorproc(_flags, _ax);
			break;
		}
		break;
	case 0x58:				/* memory allocation */
		switch (funl) {
		case 0:				/* get strategy */
			if (_flags & 1)
				errprint(_ax);
			else
				print_memory_strategy(_ax);
			tputs("\r\n");
			break;
		case 1:				/* set strategy */
			okerrorproc(_flags, _ax);
			break;
		case 2:				/* get umb state */
			nerrorproc(_flags, _ax & 0xff);
			break;
		case 3:				/* set umb state */
			okerrorproc(_flags, _ax);
			break;
		}
		break;
	case 0x5a:				/* Tmpfile */
		nerrorproc(_flags, _ax);
		break;
	case 0x5b:				/* Creat new */
		nerrorproc(_flags, _ax);
		break;
	case 0x6c:				/* Open 4 */
		if (_flags & 1)
			errprint(_ax);
		else {
			tprintf("%u ", _ax);
			if (stringprint)
				switch (_cx) {
				case 0: tputs("(OPEN)"); break;
				case 1: tputs("(CREAT)"); break;
				case 2: tputs("(TRUNC)"); break;
				}
		}
		tputs("\r\n");
	}
	setpsp(tracedpsp);
	trace = 1;
}

/*
 * Restore the prevuious handler.
 * Called by signal handlers.
 */
void
restore_handler(int sig)
{
	/* Restore old handler */
	_dos_setvect(DOS_INT, old_dos_handler);
}

int getopt(int, char **, char *);

/*
 * Main program starts here.
 */
int
main(int argc, char *argv[])
{
	int status = 0;
	extern int optind;
	extern char *optarg;
	int errflag = 0;
	char *usagestring = "usage: %s [-o fname] [-l len] [-help] [-abcfinrstvwxy] [-p psp] [command options ...]\n";
	int c;
	static char revstring[] = "$Revision: 1.28 $", revision[30];
	char *p;

	strcpy(revision, strchr(revstring, ':') + 2);
	*strchr(revision, '$') = '\0';
	strlwr(argv[0]);
	if (p = strrchr(argv[0], '\\'))
		argv[0] = p + 1;
	if (p = strrchr(argv[0], '/'))
		argv[0] = p + 1;
	if (p = strrchr(argv[0], '.'))
		*p = '\0';
	if (!*argv[0])
		argv[0] = "trace";
	while ((c = getopt(argc, argv, "abfo:h:sxl:nitrevcp:wy")) != EOF)
		switch (c) {
		case 'i':			/* Print PSP */
			pspprint = 1;
			break;
		case 'p':			/* Trace with given PSP */
			if (optarg) {
				tsrpsp = atoi(optarg);
				tracetsr = 1;
			} else {
				fprintf(stderr, "%s: -p needs a PSP parameter\n", argv[0]);
				errflag = 1;
			}
			break;
		case 'e':			/* Trace children */
			tracechildren = 1;
			break;
		case 'v':			/* Verbose */
			branchprint = pspprint = worderror = numprint = timeprint = tracechildren = regdump = stringprint = hexprint = otherprint = nameprint = 1;
			break;
		case 'f':			/* Print function number */
			numprint = 1;
			break;
		case 'b':			/* Print interrupt branch address */
			branchprint = 1;
			break;
		case 't':			/* Print time of each call */
			timeprint = 1;
			break;
		case 'r':			/* Dump registers */
			regdump = 1;
			break;
		case 'o':			/* Output file */
			if (optarg)
				fname = optarg ;
			else {
				fprintf(stderr, "%s: -o needs a file parameter\n", argv[0]);
				errflag = 1;
			}
			break ;
		case 'w':			/* Print errors as words */
			worderror = 1;
			break;
		case 'c':			/* Produce summary count */
			count = 1;
			break;
		case 's':			/* Print strings in I/O */
			stringprint = 1;
			break;
		case 'y':			/* Close file after every write */
			append = 1;
			break;
		case 'x':			/* Print binary data in I/O */
			hexprint = 1;
			break;
		case 'a':			/* Print all DOS functions */
			otherprint = 1;
			break;
		case 'n':			/* Print names of other DOS functions */
			nameprint = 1;
			break;
		case 'l':			/* Specify length */
			if (optarg) {
				datalen = atoi(optarg);
				if (datalen >= MAXBUFF) {
					fprintf(stderr, "%s: Data length %u is greater than maximum length %u.  %u used.\n", argv[0], datalen, MAXBUFF - 1, MAXBUFF - 1);
					datalen = MAXBUFF - 1;
				}
			} else {
				fprintf(stderr, "%s: -l needs a length parameter\n", argv[0]);
				errflag = 1;
			}
			break;
		case 'h':			/* Help */
			fprintf(stdout, "Trace Version %s (C) Copyright 1991-1994 D. Spinellis.  All rights reserved.\n", revision);
			fprintf(stdout, usagestring, argv[0]);
			fputs("-a\tTrace all DOS functions\n", stdout);
			fputs("-b\tPrint interrupt branch address\n", stdout);
			fputs("-c\tProduce a count summary only\n", stdout);
			fputs("-e\tTrace across exec calls\n", stdout);
			fputs("-f\tPrefix calls with function number\n", stdout);
			fputs("-h\tPrint this list\n", stdout);
			fputs("-i\tPrefix calls with process id (psp address)\n", stdout);
			fputs("-l L\tSpecify I/O printed data length\n", stdout);
			fputs("-n\tPrint other functions by name\n", stdout);
			fputs("-o F\tSpecify output file name\n", stdout);
			fputs("-p P\tTrace resident process with PSP P\n", stdout);
			fputs("-r\tDump registers on other functions\n", stdout);
			fputs("-s\tPrint I/O strings\n", stdout);
			fputs("-t\tPrefix calls with time\n", stdout);
			fputs("-v\tVerbose (-abefinrstwx)\n", stdout);
			fputs("-w\tPrint errors as words\n", stdout);
			fputs("-x\tPrint I/O binary data (needs -s)\n", stdout);
			fputs("-y\tClose file after everY write\n", stdout);
			return 0;
		case '?':			/* Error */
			errflag = 1;
			break ;
		}
	if (errflag) {
		fprintf(stderr, usagestring, argv[0]);
		return 2;
	}
	if ((fd = open(fname, O_CREAT | O_TRUNC | O_TEXT | O_WRONLY, 0666)) == -1) {
		perror(fname);
		return 1;
	}
	someprefix = pspprint | timeprint | numprint | branchprint;
	mypsp = getpsp();
	critsectflag = getcritsectflag();
	if (_osmajor == 2)
		criterrflag = critsectflag + 1;
	else
		criterrflag = critsectflag - 1;
	/* Save old handler and install new one */
	old_dos_handler = _dos_getvect(DOS_INT);
	(void)signal(SIGABRT, restore_handler);
	(void)signal(SIGINT, restore_handler);
	(void)signal(SIGTERM, restore_handler);
	_dos_setvect(DOS_INT, dos_handler);
	if (tracetsr) {
		printf("Tracing process with psp %d (0x%04x)\n", tsrpsp, tsrpsp);
		puts("Press any key to exit.");
		execed = 1;
		(void)_bios_keybrd(_KEYBRD_READ);
	} else if (optind != argc) {
		status=spawnvp(P_WAIT, argv[optind], argv + optind);
	} else {
		puts("Tracing system activity.\nPress any key to exit.");
		execed = 1;
		(void)_bios_keybrd(_KEYBRD_READ);
	}
	execed = 0;
	restore_handler(SIGTERM);
	if (count)
		for (c = 0; c < 256; c++)
			if (funcs[c].count)
				tprintf("%02X %20s %10u\r\n", c, funcs[c].name ? funcs[c].name : "???", funcs[c].count);

	close(fd);
	if (status == -1) {
		perror(argv[optind]);
		return 1;
	} else
		return 0;
}
