/*
 * Copyright (c) 1991, 1992 Paul Kranenburg <pk@cs.few.eur.nl>
 * Copyright (c) 1993 Branko Lankester <branko@hacktic.nl>
 * Copyright (c) 1993, 1994 Rick Sladkey <jrs@world.std.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Paul Kranenburg,
 *      Branko Lankester and Rick Sladkey.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *	syscall.c,v 2.25 1994/03/12 23:11:57 jrs Exp
 */

#include "defs.h"

#include <signal.h>
#include <time.h>
#include <errno.h>
#include <sys/user.h>
#include <sys/syscall.h>
#include <sys/param.h>

#ifndef LINUX
extern char *sys_errlist[];
extern int sys_nerr;
#endif /* !LINUX */

#include "syscall.h"

struct sysent sysent[] = {
#include "syscallent.h"
};
int nsyscalls = sizeof sysent / sizeof sysent[0];

char *errnoent[] = {
#include "errnoent.h"
};
int nerrnos = sizeof errnoent / sizeof errnoent[0];

int qual_flags[MAX_QUALS];

static int call_count[MAX_QUALS];
static int error_count[MAX_QUALS];
static struct timeval tv_count[MAX_QUALS];
static int sorted_count[MAX_QUALS];

static struct timeval shortest = { 1000000, 0 };

static int lookup_syscall(), lookup_signal(), lookup_fault(), lookup_desc();

static struct qual_options {
	int bitflag;
	char *option_name;
	int (*lookup)();
	char *argument_name;
} qual_options[] = {
	{ QUAL_TRACE,	"trace",	lookup_syscall,	"system call"	},
	{ QUAL_TRACE,	"t",		lookup_syscall,	"system call"	},
	{ QUAL_ABBREV,	"abbrev",	lookup_syscall,	"system call"	},
	{ QUAL_ABBREV,	"a",		lookup_syscall,	"system call"	},
	{ QUAL_VERBOSE,	"verbose",	lookup_syscall,	"system call"	},
	{ QUAL_VERBOSE,	"v",		lookup_syscall,	"system call"	},
	{ QUAL_RAW,	"raw",		lookup_syscall,	"system call"	},
	{ QUAL_RAW,	"x",		lookup_syscall,	"system call"	},
	{ QUAL_SIGNAL,	"signal",	lookup_signal,	"signal"	},
	{ QUAL_SIGNAL,	"signals",	lookup_signal,	"signal"	},
	{ QUAL_SIGNAL,	"s",		lookup_signal,	"signal"	},
	{ QUAL_FAULT,	"fault",	lookup_fault,	"fault"		},
	{ QUAL_FAULT,	"faults",	lookup_fault,	"fault"		},
	{ QUAL_FAULT,	"m",		lookup_fault,	"fault"		},
	{ QUAL_READ,	"read",		lookup_desc,	"descriptor"	},
	{ QUAL_READ,	"reads",	lookup_desc,	"descriptor"	},
	{ QUAL_READ,	"r",		lookup_desc,	"descriptor"	},
	{ QUAL_WRITE,	"write",	lookup_desc,	"descriptor"	},
	{ QUAL_WRITE,	"writes",	lookup_desc,	"descriptor"	},
	{ QUAL_WRITE,	"w",		lookup_desc,	"descriptor"	},
	{ 0,		NULL,		NULL,		NULL		},
};

static int
lookup_syscall(s)
char *s;
{
	int i;

	for (i = 0; i < nsyscalls; i++) {
		if (strcmp(s, sysent[i].sys_name) == 0)
			return i;
	}
	return -1;
}

static int
lookup_signal(s)
char *s;
{
	int i;
	char buf[32];

	if (s && *s && isdigit(*s))
		return atoi(s);
	strcpy(buf, s);
	s = buf;
	for (i = 0; s[i]; i++)
		s[i] = toupper(s[i]);
	if (strncmp(s, "SIG", 3) == 0)
		s += 3;
	for (i = 0; i <= NSIG; i++) {
		if (strcmp(s, signalent[i] + 3) == 0)
			return i;
	}
	return -1;
}

static int
lookup_fault(s)
char *s;
{
	return -1;
}

static int
lookup_desc(s)
char *s;
{
	if (s && *s && isdigit(*s))
		return atoi(s);
	return -1;
}

void
qualify(s)
char *s;
{
	struct qual_options *opt;
	int not;
	char *p;
	int i, n;

	opt = &qual_options[0];
	for (i = 0; (p = qual_options[i].option_name); i++) {
		n = strlen(p);
		if (strncmp(s, p, n) == 0 && s[n] == '=') {
			opt = &qual_options[i];
			s += n + 1;
			break;
		}
	}
	not = 0;
	if (*s == '!') {
		not = 1;
		s++;
	}
	if (strcmp(s, "none") == 0) {
		not = 1 - not;
		s = "all";
	}
	if (strcmp(s, "all") == 0) {
		for (i = 0; i < MAX_QUALS; i++) {
			if (not)
				qual_flags[i] &= ~opt->bitflag;
			else
				qual_flags[i] |= opt->bitflag;
		}
		return;
	}
	for (i = 0; i < MAX_QUALS; i++) {
		if (not)
			qual_flags[i] |= opt->bitflag;
		else
			qual_flags[i] &= ~opt->bitflag;
	}
	for (p = strtok(s, ","); p; p = strtok(NULL, ",")) {
		if ((n = (*opt->lookup)(p)) < 0) {
			fprintf(stderr, "strace: invalid %s `%s'\n",
				opt->argument_name, p);
			exit(1);
		}
		if (not)
			qual_flags[n] &= ~opt->bitflag;
		else
			qual_flags[n] |= opt->bitflag;
	}
	return;
}

static void
dumpio(tcp)
struct tcb *tcp;
{
	if (syserror(tcp))
		return;
	switch (tcp->scno) {
	case SYS_read:
#ifdef SYS_recv
	case SYS_recv:
#endif
#ifdef SYS_recvfrom
	case SYS_recvfrom:
#endif
		if (qual_flags[tcp->u_arg[0]] & QUAL_READ)
			dumpstr(tcp, tcp->u_arg[1], tcp->u_rval);
		break;
	case SYS_write:
#ifdef SYS_send
	case SYS_send:
#endif
#ifdef SYS_sendto
	case SYS_sendto:
#endif
		if (qual_flags[tcp->u_arg[0]] & QUAL_WRITE)
			dumpstr(tcp, tcp->u_arg[1], tcp->u_arg[2]);
		break;
	}
}

#ifndef SUNOS4

enum subcall_style { shift_style, deref_style, mask_style };

static void
decode_subcall(tcp, subcall, nsubcalls, style)
struct tcb *tcp;
int subcall;
int nsubcalls;
enum subcall_style style;
{
	int i, addr, mask, arg;

	if (tcp->u_arg[0] < 0 || tcp->u_arg[0] >= nsubcalls)
		return;
	switch (style) {
	case shift_style:
		tcp->scno = subcall + tcp->u_arg[0];
		if (sysent[tcp->scno].nargs != -1)
			tcp->u_nargs = sysent[tcp->scno].nargs;
		else
			tcp->u_nargs--;
		for (i = 0; i < tcp->u_nargs; i++)
			tcp->u_arg[i] = tcp->u_arg[i + 1];
		break;
	case deref_style:
		tcp->scno = subcall + tcp->u_arg[0];
		addr = tcp->u_arg[1];
		for (i = 0; i < sysent[tcp->scno].nargs; i++) {
			if (umove(tcp, addr, &arg) < 0)
				arg = 0;
			tcp->u_arg[i] = arg;
			addr += sizeof(arg);
		}
		tcp->u_nargs = sysent[tcp->scno].nargs;
		break;
	case mask_style:
		mask = (tcp->u_arg[0] >> 8) & 0xff;
		tcp->u_arg[0] &= 0xff;
		for (i = 0; mask; i++)
			mask >>= 1;
		tcp->scno = subcall + i;
		if (sysent[tcp->scno].nargs != -1)
			tcp->u_nargs = sysent[tcp->scno].nargs;
		break;
	}
}

#endif /* !SUNOS4 */

struct tcb *tcp_last = NULL;

int
syscall(tcp)
struct tcb *tcp;
{
	int i;
	int sys_res;
	struct timeval tv;
	int scno;
#ifdef LINUX
	int eax;
#endif /* LINUX */
#ifndef SVR4
	int pid = tcp->pid;
#endif /* !SVR4 */

	/* Measure the exit time as early as possible to avoid errors. */
	if (dtime && (tcp->flags & TCB_INSYSCALL))
		gettimeofday(&tv, NULL);
#ifdef LINUX
	if (upeek(pid, 4*ORIG_EAX, &scno) < 0)
		return -1;
#endif
#ifdef SUNOS4
	if (upeek(pid, uoff(u_arg[7]), &scno) < 0)
		return -1;
#endif
#ifdef SVR4
#ifdef HAVE_PR_SYSCALL
	scno = tcp->status.pr_syscall;
#else /* !HAVE_PR_SYSCALL */
	scno = tcp->status.pr_what;
#endif /* !HAVE_PR_SYSCALL */
	if (!(tcp->flags & TCB_INSYSCALL)) {
		if (tcp->status.pr_why != PR_SYSENTRY) {
			if (scno == SYS_fork || scno == SYS_vfork) {
				/* We are returning in the child, fake it. */
				tcp->status.pr_why = PR_SYSENTRY;
				syscall(tcp);
				tcp->status.pr_why = PR_SYSEXIT;
			}
			else {
				fprintf(stderr, "syscall: missing entry\n");
				tcp->flags |= TCB_INSYSCALL;
			}
		}
	}
	else {
		if (tcp->status.pr_why != PR_SYSEXIT) {
			fprintf(stderr, "syscall: missing exit\n");
			tcp->flags &= ~TCB_INSYSCALL;
		}
	}
#endif /* SVR4 */
#ifdef SUNOS4
	if (!(tcp->flags & TCB_INSYSCALL)) {
		if (scno == 0) {
			fprintf(stderr, "syscall: missing entry\n");
			tcp->flags |= TCB_INSYSCALL;
		}
	}
	else {
		if (scno != 0) {
			if (debug) {
				/*
				 * This happens when a signal handler
				 * for a signal which interrupted a
				 * a system call makes another system call.
				 */
				fprintf(stderr, "syscall: missing exit\n");
			}
			tcp->flags &= ~TCB_INSYSCALL;
		}
	}
#endif /* SUNOS4 */
#ifdef LINUX
	if (upeek(pid, 4*EAX, &eax) < 0)
		return -1;
	if (eax != -ENOSYS && !(tcp->flags & TCB_INSYSCALL)) {
		if (debug)
			fprintf(stderr, "stray syscall exit: eax = %d\n", eax);
		return 0;
	}
#endif /* LINUX */
	if (tcp->flags & TCB_INSYSCALL) {
		unsigned int u_error;

		if (!(qual_flags[tcp->scno] & QUAL_TRACE)) {
			tcp->flags &= ~TCB_INSYSCALL;
			return 0;
		}
		if (tcp->flags & TCB_REPRINT) {
			printleader(tcp);
			tprintf("<... ");
			if (tcp->scno >= nsyscalls)
				tprintf("syscall_%u", tcp->scno);
			else
				tprintf("%s", sysent[tcp->scno].sys_name);
			tprintf(" resumed> ");
		}
#ifdef LINUX
		if (eax && (unsigned) -eax < nerrnos) {
			tcp->u_rval = -1;
			u_error = -eax;
		}
		else {
			tcp->u_rval = eax;
			u_error = 0;
		}
#endif /* LINUX */
#ifdef SUNOS4
		/* get error code from user struct */
		if (upeek(pid, uoff(u_error), &u_error) < 0)
			return -1;
		u_error >>= 24; /* u_error is a char */

		/* get system call return value */
		if (upeek(pid, uoff(u_rval1), &tcp->u_rval) < 0)
			return -1;
#endif /* SUNOS4 */
#ifdef SVR4
#ifdef SPARC
		/* Judicious guessing goes a long way. */
		if (tcp->status.pr_reg[R_PSR] & 0x100000) {
			tcp->u_rval = -1;
			u_error = tcp->status.pr_reg[R_O0];
		}
		else {
			tcp->u_rval = tcp->status.pr_reg[R_O0];
			u_error = 0;
		}
#endif /* SPARC */
#ifdef I386
		/* Wanna know how to kill an hour single-stepping? */
		if (tcp->status.pr_reg[EFL] & 0x1) {
			tcp->u_rval = -1;
			u_error = tcp->status.pr_reg[EAX];
		}
		else {
			tcp->u_rval = tcp->status.pr_reg[EAX];
			u_error = 0;
		}
#endif /* I386 */
#endif /* SVR4 */
		tcp->u_error = u_error;
		if (cflag) {
			call_count[tcp->scno]++;
			if (u_error)
				error_count[tcp->scno]++;
			tv_sub(&tv, &tv, &tcp->etime);
#ifdef LINUX
			if (tv_cmp(&tv, &tcp->dtime) > 0) {
				static struct timeval one_tick =
					{ 0, 1000000 / HZ };

				if (tv_nz(&tcp->dtime))
					tv = tcp->dtime;
				else if (tv_cmp(&tv, &one_tick) > 0) {
					if (tv_cmp(&shortest, &one_tick) < 0)
						tv = shortest;
					else
						tv = one_tick;
				}
			}
#endif /* LINUX */
			if (tv_cmp(&tv, &shortest) < 0)
				shortest = tv;
			tv_add(&tv_count[tcp->scno],
				&tv_count[tcp->scno], &tv);
			tcp->flags &= ~TCB_INSYSCALL;
			return 0;
		}

		if (tcp->scno >= nsyscalls
		    || (qual_flags[tcp->scno] & QUAL_RAW))
			sys_res = printargs(tcp);
		else
			sys_res = (*sysent[tcp->scno].sys_func)(tcp);
		u_error = tcp->u_error;
		tprintf(") ");
		tabto(acolumn);
		if (qual_flags[tcp->scno] & QUAL_RAW) {
			if (u_error)
				tprintf("= -1 (errno %d)", u_error);
			else
				tprintf("= %#x", tcp->u_rval);
		}
		else if (!(sys_res & RVAL_NONE) && u_error) {
#ifdef LINUX
			switch (u_error) {
			case ERESTARTSYS:
				tprintf("= ? ERESTARTSYS (To be restarted)");
				break;
			case ERESTARTNOINTR:
				tprintf("= ? ERESTARTNOINTR (To be restarted)");
				break;
			case ERESTARTNOHAND:
				tprintf("= ? ERESTARTNOHAND (To be restarted)");
				break;
			default:
#endif /* LINUX */
				tprintf("= -1 ");
				if (u_error < nerrnos && u_error < sys_nerr)
					tprintf("%s (%s)", errnoent[u_error],
						sys_errlist[u_error]);
				else if (u_error < nerrnos)
					tprintf("%s (errno %d)",
						errnoent[u_error], u_error);
				else if (u_error < sys_nerr)
					tprintf("ERRNO_%d (%s)", u_error,
						sys_errlist[u_error]);
				else
					tprintf("E??? (errno %d)", u_error);
#ifdef LINUX
				break;
			}
#endif /* LINUX */
		}
		else {
			if (sys_res & RVAL_NONE)
				tprintf("= ?");
			else {
				switch (sys_res & RVAL_MASK) {
				case RVAL_HEX:
					tprintf("= %#x", tcp->u_rval);
					break;
				case RVAL_OCTAL:
					tprintf("= %#o", tcp->u_rval);
					break;
				case RVAL_UDECIMAL:
					tprintf("= %u", tcp->u_rval);
					break;
				case RVAL_DECIMAL:
					tprintf("= %d", tcp->u_rval);
					break;
				default:
					fprintf(stderr,
						"invalid rval format\n");
					break;
				}
			}
			if ((sys_res & RVAL_STR) && tcp->auxstr)
				tprintf(" (%s)", tcp->auxstr);
		}
		if (dtime) {
			tv_sub(&tv, &tv, &tcp->etime);
			tprintf(" <%ld.%06ld>", tv.tv_sec, tv.tv_usec);
		}
		printtrailer(tcp);

		dumpio(tcp);
		if (fflush(tcp->outf) == EOF)
			return -1;
		tcp->flags &= ~TCB_INSYSCALL;
		return 0;
	}

	/* Entering system call */
	tcp->scno = scno;
#ifdef LINUX
	tcp->u_nargs = sysent[tcp->scno].nargs;
	for (i = 0; i < tcp->u_nargs; i++) {
		if (upeek(pid, i*4, &tcp->u_arg[i]) < 0)
			return -1;
	}
#endif /* LINUX */
#ifdef SUNOS4
	tcp->u_nargs = sysent[tcp->scno].nargs;
	for (i = 0; i < tcp->u_nargs; i++) {
		struct user *u;

		if (upeek(pid, uoff(u_arg[0]) + (i*sizeof(u->u_arg[0])),
		    &tcp->u_arg[i]) < 0)
			return -1;
	}
#endif /* SUNOS4 */
#ifdef SVR4
#ifdef HAVE_PR_SYSCALL
	if (sysent[tcp->scno].nargs != -1)
		tcp->u_nargs = sysent[tcp->scno].nargs;
	else
		tcp->u_nargs = tcp->status.pr_nsysarg;
	for (i = 0; i < tcp->u_nargs; i++)
		tcp->u_arg[i] = tcp->status.pr_sysarg[i];
#else /* !HAVE_PR_SYSCALL */
#ifdef I386
	if (sysent[tcp->scno].nargs != -1)
		tcp->u_nargs = sysent[tcp->scno].nargs;
	else
		tcp->u_nargs = 5;
	umoven(tcp, tcp->status.pr_reg[UESP] + 4,
		tcp->u_nargs*sizeof(tcp->u_arg[0]), (char *) tcp->u_arg);
#endif /* I386 */
#endif /* !HAVE_PR_SYSCALL */
#endif /* SVR4 */
	switch (tcp->scno) {
#ifdef LINUX
	case SYS_socketcall:
		decode_subcall(tcp, SYS_socket_subcall,
			SYS_socket_nsubcalls, deref_style);
		break;
	case SYS_ipc:
		decode_subcall(tcp, SYS_ipc_subcall,
			SYS_ipc_nsubcalls, shift_style);
		break;
#endif /* LINUX */
#ifdef SVR4
	case SYS_pgrpsys:
		decode_subcall(tcp, SYS_pgrpsys_subcall,
			SYS_pgrpsys_nsubcalls, shift_style);
		break;
	case SYS_sigcall:
		decode_subcall(tcp, SYS_sigcall_subcall,
			SYS_sigcall_nsubcalls, mask_style);
		break;
	case SYS_msgsys:
		decode_subcall(tcp, SYS_msgsys_subcall,
			SYS_msgsys_nsubcalls, shift_style);
		break;
	case SYS_shmsys:
		decode_subcall(tcp, SYS_shmsys_subcall,
			SYS_shmsys_nsubcalls, shift_style);
		break;
	case SYS_semsys:
		decode_subcall(tcp, SYS_semsys_subcall,
			SYS_semsys_nsubcalls, shift_style);
		break;
#if 0 /* broken */
	case SYS_utssys:
		decode_subcall(tcp, SYS_utssys_subcall,
			SYS_utssys_nsubcalls, shift_style);
		break;
#endif
	case SYS_sysfs:
		decode_subcall(tcp, SYS_sysfs_subcall,
			SYS_sysfs_nsubcalls, shift_style);
		break;
	case SYS_spcall:
		decode_subcall(tcp, SYS_spcall_subcall,
			SYS_spcall_nsubcalls, shift_style);
		break;
	case SYS_context:
		decode_subcall(tcp, SYS_context_subcall,
			SYS_context_nsubcalls, shift_style);
		break;
#endif /* SVR4 */
	}
	if (!(qual_flags[tcp->scno] & QUAL_TRACE)) {
		tcp->flags |= TCB_INSYSCALL;
		return 0;
	}
	if (cflag) {
		gettimeofday(&tcp->etime, NULL);
		tcp->flags |= TCB_INSYSCALL;
		return 0;
	}
	printleader(tcp);
	tcp->flags &= ~TCB_REPRINT;
	tcp_last = tcp;
	if (tcp->scno >= nsyscalls)
		tprintf("syscall_%u(", tcp->scno);
	else
		tprintf("%s(", sysent[tcp->scno].sys_name);
	if (tcp->scno >= nsyscalls ||
	    ((qual_flags[tcp->scno] & QUAL_RAW) && tcp->scno != SYS_exit))
		sys_res = printargs(tcp);
	else
		sys_res = (*sysent[tcp->scno].sys_func)(tcp);
	if (fflush(tcp->outf) == EOF)
		return -1;
	tcp->flags |= TCB_INSYSCALL;
	/* Measure the entrance time as late as possible to avoid errors. */
	if (dtime)
		gettimeofday(&tcp->etime, NULL);
	return sys_res;
}

int
printargs(tcp)
struct tcb *tcp;
{
	if (entering(tcp)) {
		int i;

		for (i = 0; i < tcp->u_nargs; i++)
			tprintf("%s%#x", i ? ", " : "", tcp->u_arg[i]);
	}
	return 0;
}

int
getrval2(tcp)
struct tcb *tcp;
{
	int val = -1;

#ifdef SUNOS4
	if (upeek(tcp->pid, uoff(u_rval2), &val) < 0)
		return -1;
#endif /* SUNOS4 */

#ifdef SVR4
#ifdef SPARC
	val = tcp->status.pr_reg[R_O1];
#endif /* SPARC */
#ifdef I386
	val = tcp->status.pr_reg[EDX];
#endif /* I386 */
#endif /* SVR4 */

	return val;
}

/*
 * Apparently, indirect system calls have already be converted by ptrace(2),
 * so if you see "indir" this program has gone astray.
 */
int
sys_indir(tcp)
struct tcb *tcp;
{
	unsigned int i, scno, nargs;

	if (entering(tcp)) {
		if ((scno = tcp->u_arg[0]) > nsyscalls) {
			fprintf(stderr, "Bogus syscall: %u\n", scno);
			return 0;
		}
		nargs = sysent[scno].nargs;
		tprintf("%s", sysent[scno].sys_name);
		for (i = 0; i < nargs; i++)
			tprintf(", %#x", tcp->u_arg[i+1]);
	}
	return 0;
}

static int
time_cmp(a, b)
void *a;
void *b;
{
	return -tv_cmp(&tv_count[*((int *) a)], &tv_count[*((int *) b)]);
}

static int
syscall_cmp(a, b)
void *a;
void *b;
{
	return strcmp(sysent[*((int *) a)].sys_name,
		sysent[*((int *) b)].sys_name);
}

static int
count_cmp(a, b)
void *a;
void *b;
{
	int m = call_count[*((int *) a)], n = call_count[*((int *) b)];

	return (m < n) ? 1 : (m > n) ? -1 : 0;
}

static int (*sortfun)();
static struct timeval overhead = { -1, -1 };

void
set_sortby(sortby)
char *sortby;
{
	if (strcmp(sortby, "time") == 0)
		sortfun = time_cmp;
	else if (strcmp(sortby, "calls") == 0)
		sortfun = count_cmp;
	else if (strcmp(sortby, "name") == 0)
		sortfun = syscall_cmp;
	else if (strcmp(sortby, "nothing") == 0)
		sortfun = NULL;
	else {
		fprintf(stderr, "invalid sortby: `%s'\n", sortby);
		exit(1);
	}
}

void set_overhead(n)
int n;
{
	overhead.tv_sec = n / 1000000;
	overhead.tv_usec = n % 1000000;
}

void
call_summary(outf)
FILE *outf;
{
	int i, j;
	int call_cum, error_cum;
	struct timeval tv_cum, dtv;
	double percent;
	char *dashes = "-------------------------";
	char error_str[16];

	call_cum = error_cum = tv_cum.tv_sec = tv_cum.tv_usec = 0;
	if (overhead.tv_sec == -1) {
		tv_mul(&overhead, &shortest, 8);
		tv_div(&overhead, &overhead, 10);
	}
	for (i = 0; i < nsyscalls; i++) {
		sorted_count[i] = i;
		if (call_count[i] == 0)
			continue;
		tv_mul(&dtv, &overhead, call_count[i]);
		tv_sub(&tv_count[i], &tv_count[i], &dtv);
		call_cum += call_count[i];
		error_cum += error_count[i];
		tv_add(&tv_cum, &tv_cum, &tv_count[i]);
	}
	if (sortfun)
		qsort((void *) sorted_count, nsyscalls, sizeof(int), sortfun);
	fprintf(outf, "%6.6s %11.11s %11.11s %9.9s %9.9s %s\n",
		"% time", "seconds", "usecs/call",
		"calls", "errors", "syscall");
	fprintf(outf, "%6.6s %11.11s %11.11s %9.9s %9.9s %-16.16s\n",
		dashes, dashes, dashes, dashes, dashes, dashes);
	for (i = 0; i < nsyscalls; i++) {
		j = sorted_count[i];
		if (call_count[j] == 0)
			continue;
		tv_div(&dtv, &tv_count[j], call_count[j]);
		if (error_count[j])
			sprintf(error_str, "%d", error_count[j]);
		else
			error_str[0] = '\0';
		percent = 100.0*tv_float(&tv_count[j])/tv_float(&tv_cum);
		fprintf(outf, "%6.2f %4ld.%06ld %11ld %9d %9.9s %s\n",
			percent, tv_count[j].tv_sec, tv_count[j].tv_usec,
			1000000 * dtv.tv_sec + dtv.tv_usec, call_count[j],
			error_str, sysent[j].sys_name);
	}
	fprintf(outf, "%6.6s %11.11s %11.11s %9.9s %9.9s %-16.16s\n",
		dashes, dashes, dashes, dashes, dashes, dashes);
	if (error_cum)
		sprintf(error_str, "%d", error_cum);
	else
		error_str[0] = '\0';
	fprintf(outf, "%6.6s %4ld.%06ld %11.11s %9d %9.9s %s\n",
		"100.00", tv_cum.tv_sec, tv_cum.tv_usec, "", call_cum,
		error_str, "total");
}

