/*   This is part of um-ViewOS
 *   The user-mode implementation of OSVIEW -- A Process with a View
 *
 *   capture_um.c : capture and divert system calls
 *   
 *   Copyright 2005 Renzo Davoli University of Bologna - Italy
 *   Modified 2005 Mattia Belletti, Ludovico Gardenghi, Andrea Gasparini
 *   Modified 2006 Renzo Davoli
 *
 *   Some code has been inherited from strace
 *   Copyright (c) 1991, 1992 Paul Kranenburg <pk@cs.few.eur.nl>
 *   Copyright (c) 1993 Branko Lankester <branko@hacktic.nl>
 *   Copyright (c) 1993, 1994, 1995, 1996 Rick Sladkey <jrs@world.std.com>
 *   Copyright (c) 1996-1999 Wichert Akkerman <wichert@cistron.nl>
 *   
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License, version 2, as
 *   published by the Free Software Foundation.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License along
 *   with this program; if not, write to the Free Software Foundation, Inc.,
 *   51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA.
 *
 *   $Id: capture_um.c 1011 2011-10-24 20:10:26Z rd235 $
 *
 */   
#include <assert.h>
#include <stdio.h>
#include <signal.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <stdarg.h>
#include <pthread.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <asm/ptrace.h>
#include <asm/unistd.h>
#include <sched.h>
#include <limits.h>
#include <assert.h>
#include <config.h>
#include "capture_nested.h"

#include "defs.h"
#include "utils.h"
#include "gdebug.h"

#ifdef GDEBUG_ENABLED
#include "syscallnames.h"
#endif

#define PCBSIZE 10

pthread_key_t pcb_key=0; /* key to grab the current thread pcb */

sfun native_syscall=syscall;

/* debugging output, (bypass pure_libc when loaded) */
int vprintk(const char *fmt, va_list ap) {
	char *s;
	int rv=0;
	int level=PRINTK_STANDARD_LEVEL;
	if (fmt[0] == '<' && fmt[1] != 0 && fmt[2] == '>') {
		/*level*/
		switch (fmt[1]) {
			case '0' ... '7':
				level=fmt[1] - '0';
				fmt+=3;
				break;
		}
	}
	if (level <= printk_current_level) {
		rv=vasprintf(&s, fmt, ap);
		if (rv>0)
			rv=r_write(2,s,strlen(s));
		free(s);
	}
	return rv;
}

int printk(const char *fmt, ...) {
	int rv;
	va_list ap;
	va_start(ap,fmt);
	rv=vprintk(fmt,ap);
	va_end(ap);
	return rv;
}

static struct pcb **pcbtab;           /* capture_um pcb table */
int nprocs = 0;                       /* number of active processes */
static int pcbtabsize;                /* actual size of the pcb table */

divfun scdtab[_UM_NR_syscalls];                 /* upcalls */
char scdnarg[_UM_NR_syscalls];	/*nargs*/

/* linux has a single call for all the socket calls 
 * in several architecture (i386, ppc), socket calls are standard
 * system calls in others (x86_64) */
#if __NR_socketcall != __NR_doesnotexist
divfun sockcdtab[19];                 /* upcalls */
static char socketcallnargs[] = {
	0,
	3, /* sys_socket(2)    */
	3, /* sys_bind(2)      */
	3, /* sys_connect(2)   */
	2, /* sys_listen(2)    */
	3, /* sys_accept(2)    */
	3, /* sys_getsockname(2)   */
	3, /* sys_getpeername(2)   */
	4, /* sys_socketpair(2)    */
	4, /* sys_send(2)      */
	4, /* sys_recv(2)      */
	6, /* sys_sendto(2)    */
	6, /* sys_recvfrom(2)    */
	2, /* sys_shutdown(2)    */
	5, /* sys_setsockopt(2)    */
	5, /* sys_getsockopt(2)    */
	3, /* sys_sendmsg(2)   */
	3, /* sys_recvmsg(2)   */
	4  /* sys_msocket new call for multiple stack access */
};
#endif

/* umview have to exit with the exit code of the first child: here we remember
 * what the first child was, and save its exit value */
pid_t first_child_pid;
int first_child_exit_status = -1;

/* just an interface to a hidden value */
int pcbtablesize(void)
{
	return pcbtabsize;
}

/* the "current process" info gets stored as key specific data of the thread */
struct pcb *get_pcb()
{
	return pthread_getspecific(pcb_key);
}

void set_pcb(void *new)
{
	pthread_setspecific(pcb_key,new);
}

/* pcb allocator, it resizes the data structure when needed */
static struct pcb *newpcb (int pid)
{
	register int i,j;
	struct pcb *pcb;

	for (i=0; 1; i++) {
		if (i==pcbtabsize) { /* expand the pcb table */
			/* we double the size, from pcbtabsize to pcbtabsize*2; to do this, we
			 * reallocate the newtab to double the size it was before; then we need
			 * pcbtabsize more pointers; so we allocate a table of pointers of size
			 * pcbtabsize, and the new pointers to pointers now points to that. It's
			 * a bit difficult to understand - graphically:
			 *
			 * newtab:
			 * +---------------------------------------------------------------+
			 * |0123|45678...|                |                                |
			 * +---------------------------------------------------------------+
			 *   |       |             |                         |
			 *   V       V             V                         V
			 * first    second       third                     fourth
			 * calloc   calloc       calloc                    calloc
			 *  of        of           of                        of
			 * newpcbs  newpcbs      newpcbs                   newpcbs
			 *
			 * Messy it can be, this way pointers to pcbs still remain valid after
			 * a reallocation.
			 */
			struct pcb **newtab = (struct pcb **)
				realloc(pcbtab, 2 * pcbtabsize * sizeof pcbtab[0]);
			struct pcb *newpcbs = (struct pcb *) calloc(pcbtabsize, sizeof *newpcbs); 
			if (newtab == NULL || newpcbs == NULL) {
				if (newtab != NULL)
					free(newtab);
				return NULL;
			}
			for (j = pcbtabsize; j < 2 * pcbtabsize; ++j)
				newtab[j] = &newpcbs[j - pcbtabsize];
			pcbtabsize *= 2;
			pcbtab = newtab;
		}
		pcb=pcbtab[i];
		if (! (pcb->flags & PCB_INUSE)) {
			pcb->pid=pid;
			pcb->umpid=i+1; // umpid==0 is reserved for umview itself
			pcb->flags = PCB_INUSE | PCB_STARTING;
			pcb->sysscno = NOSC;
			pcb->pp = NULL;
			nprocs++;
			return pcb;
		}
	}

	/* never reach here! */
	assert(0);
	return NULL;
}

/* this is an iterator on the pcb table */
void forallpcbdo(voidfun f,void *arg)
{
	register int i;
	for (i = 0; i < pcbtabsize; i++) {
		struct pcb *pc = pcbtab[i];
		if (pc->flags & PCB_INUSE)
		{
			GDEBUG(8, "calling @%p with arg %p on pid %d", f, arg, pc->pid);
			f(pc,arg);
			GDEBUG(8, "returning from call");
		}
	}
}

/* pid 2 pcb conversion (by linear search) */
struct pcb *pid2pcb(int pid)
{
	register int i;
	for (i = 0; i < pcbtabsize; i++) {
		struct pcb *pc = pcbtab[i];
		if (pc->pid == pid && pc->flags & PCB_INUSE)
			return pc;
	}
	return NULL;
}

/* orphan processes must NULL-ify their parent process pointer */
static void _cut_pp(struct pcb *pc, struct pcb *delpc)
{
	if (pc->pp == delpc)
		pc->pp = NULL;
}

/* pcb deallocator */
static void droppcb(struct pcb *pc)
{
	/* the last process descriptor should stay "alive" for
	 * the termination of all modules */
	/* otherwise the "nesting" mechanism misunderstands
	 * the pcb by a npcb */
	/* XXX rd235 20090805: it seems not a problem any more
		 in the new version. deleted for dup delproc notication for proc #1 */
#ifdef _PROC_MEM_TEST
	if (pc->memfd >= 0)
		close(pc->memfd);
#endif
	nprocs--;
	forallpcbdo(_cut_pp,pc);
	pcb_destructor(pc,0/*flags*/,0);
#if 0
	if (nprocs > 0)
#endif
		pc->flags = 0; /*NOT PCB_INUSE */;
}

/* initial PCB table allocation */
static void allocatepcbtab()
{
	struct pcb *pc;

	/* Allocate the initial pcbtab.  */
	/* look at newpcb for some explanations about the structure */
	pcbtabsize = PCBSIZE;
	/* allocation of pointers */
	pcbtab = (struct pcb **) malloc (pcbtabsize * sizeof pcbtab[0]);
	/* allocation of PCBs */
	pcbtab[0] = (struct pcb *) calloc (pcbtabsize, sizeof *pcbtab[0]);
	/* each pointer points to the corresponding PCB */
	for (pc = pcbtab[0]; pc < &pcbtab[0][pcbtabsize]; ++pc)
		pcbtab[pc - pcbtab[0]] = &pcbtab[0][pc - pcbtab[0]];
}

static int handle_new_proc(int pid, struct pcb *pp)
{
	struct pcb *oldpc,*pc;
#ifdef LIBC_VFORK_DIRTY_TRICKS
	long saved_regs[VIEWOS_FRAME_SIZE];
#endif /*LIBC_VFORK_DIRTY_TRICKS*/

	//printk("handle_new_proc %d %p\n",pid,pp);
	if ((oldpc=pc=pid2pcb(pid)) == NULL && (pc = newpcb(pid))== NULL) {
		printk("[pcb table full]\n");
		if(r_ptrace(PTRACE_KILL, pid, 0, 0) < 0){
			GPERROR(0, "KILL");
			exit(1);
		}
	}
	if (pp != NULL) {
		//		GDEBUG(2, "handle_new_proc(pid=%d,pp=%d) -- pc->pid: %d oldpc=%d pc=%d",pid,pp,pc->pid,oldpc,pc);
		pc->pp = pp;
		if (oldpc != NULL) {
			pc->flags &= ~PCB_STARTING;
#ifdef LIBC_VFORK_DIRTY_TRICKS
			pc->saved_regs=saved_regs;
			getregs(pc);
			putargn(0,pp->sysargs[0],pc);
			putargn(1,pp->sysargs[1],pc);
			////printk("starting1 pc->pid %d  %x %x was %x %x\n",pc->pid, pp->sysargs[0],pp->sysargs[1],getargn(0,pc),getargn(1,pc));
			if(setregs(pc,PTRACE_SYSCALL,0,0) < 0){
				GPERROR(0, "continuing");
				exit(1);
			}
#else
			if(r_ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0){
				GPERROR(0, "continuing");
				exit(1);
			}
			pc->saved_regs=NULL;

#endif /*LIBC_VFORK_DIRTY_TRICKS*/
		} else {
			pc->sysargs[0]=pp->sysargs[0];
			pc->sysargs[1]=pp->sysargs[1];
		}
#ifdef _PROC_MEM_TEST
		if (!has_ptrace_multi) {
			char *memfile;
			asprintf(&memfile,"/proc/%d/mem",pc->pid);
			pc->memfd=r_open(memfile,O_RDWR,0);
			free(memfile);
		} else
			pc->memfd= -1;
#endif
		pc->signum=0;
		pcb_constructor(pc,pp->sysargs[2],0);
	}
	return 0;
}

#ifdef FAKESIGSTOP
int fakesigstopcont(struct pcb *pc)
{
	long kpid=getargn(0,pc);
	long ksig=getargn(1,pc);
	struct pcb *kpc;

	if ((kpc=pid2pcb(kpid)) != NULL && ksig == SIGCONT &&
			(kpc->flags & PCB_FAKESTOP))
	{
		GDEBUG(1, "FAKECONT %d",kpid);
		kpc->flags &= ~PCB_FAKESTOP;
		if(r_ptrace(PTRACE_SYSCALL, kpid, 0, 0) < 0){
			GPERROR(0, "continuing");
			exit(1);
		}
		return SC_FAKE;
	}
	return STD_BEHAVIOR;
}
#endif

/* FORK/VFORK/CLONE management */
void offspring_enter(struct pcb *pc)
{
	//printf("offspring_enter:%d\n",pc->pid);
	pc->sysargs[0]=getargn(0,pc);
	pc->sysargs[1]=getargn(1,pc);
	//printk("offspring_enter %x %x\n",pc->sysargs[0],pc->sysargs[1]);
	if (pc->sysscno == __NR_fork || pc->sysscno == __NR_vfork) {
		putscno(__NR_clone,pc);
		putargn(0,CLONE_PTRACE|SIGCHLD, pc);
		putargn(1,0, pc);
		pc->sysargs[2]=SIGCHLD;
	}
	else if (pc->sysscno == __NR_clone) {
		putargn(0,pc->sysargs[0] | CLONE_PTRACE, pc);
		pc->sysargs[2]=pc->sysargs[0];
	}
}

void offspring_exit(struct pcb *pc)
{
	putargn(0,pc->sysargs[0],pc);
	putargn(1,pc->sysargs[1],pc);
}

/* Tracer core, executed any time an event occurs*/
void tracehand()
{
	int pid, status, scno=0;
	struct pcb *pc;

	while(nprocs>0){
		/* get the id of the signalling process */
		if((pid =  r_waitpid(-1, &status, WUNTRACED | __WALL | WNOHANG)) < 0)
		{
			GPERROR(0, "wait");
			exit(1);
		}

		/* This is a safe exit if there are spurious chars in the pipe */
		if (pid==0) return;
		if ((pc=pid2pcb(pid))==NULL) {
			/* race condition, new procs can be faster than parents*/
			if(WIFSTOPPED(status) && (WSTOPSIG(status) == SIGSTOP)) {
				/* create the descriptor, block the process
				 * until the parent complete the pcb */
				////printk("RACE CONDITION %d\n",pid);
				handle_new_proc(pid,NULL);
				continue;
			}
			/* error case */
			printk("signal from unknown pid %d: killed\n",pid);
			GDEBUG(0, "signal from unknown pid %d: killed",pid);
			if(r_ptrace(PTRACE_KILL, pid, 0, 0) < 0){
				GPERROR(0, "KILL");
				exit(1);
			}
		}

		/* set the pcb of the signalling (current) process as a
		 * thread private data */
		pthread_setspecific(pcb_key,pc);

		if(WIFSTOPPED(status) && (WSTOPSIG(status) == SIGTRAP)){
			int isreproducing=0;
			long saved_regs[VIEWOS_FRAME_SIZE];
			pc->saved_regs=saved_regs;
			if ( getregs(pc) < 0 ){
				GPERROR(0, "saving register");
				exit(1);
			}
			//printregs(pc);
			scno=getscno(pc);
			/* execve does not return */
			if (
#if __NR_socketcall != __NR_doesnotexist
					pc->sockaddr == 0 && 
#endif
					pc->sysscno == __NR_execve && 
					scno != __NR_execve && 
					(pc->behavior != SC_FAKE || scno != __NR_getpid)){
				pc->sysscno = NOSC;
			}
			isreproducing=(scno == __NR_fork ||
					scno == __NR_vfork ||
					scno == __NR_clone);
			/* sigreturn and rt_sigreturn give random "OUT" values, maybe 0.
			 * this is a workaroud */
#if defined(__x86_64__) //sigreturn and signal aren't defineed in amd64
			/* x86_64 has not the single socketcall */
			if (pc->sysscno == __NR_rt_sigreturn )
				pc->sysscno = NOSC;
#else
			if (
#if __NR_socketcall != __NR_doesnotexist
					pc->sockaddr == 0 && 
#endif
					(pc->sysscno == __NR_rt_sigreturn || pc->sysscno == __NR_sigreturn)) 
				pc->sysscno = NOSC;
			/*0 is READ for x86_84*/
			else if (scno == 0) {
				if (pc->sysscno == __NR_execve)
					pc->sysscno = NOSC;
			}
#endif
			else if (pc->sysscno == NOSC) /* PRE syscall tracing event (IN)*/
			{
				divfun fun;
				pc->signum=0;
				GDEBUG(3, "--> pid %d syscall %d (%s) @ %p", pid, scno, SYSCALLNAME(scno), getpc(pc));
				//printf("IN\n");
				pc->sysscno = scno;
				switch (scdnarg[scno]) {
					case 6:
						pc->sysargs[5]=getargn(5,pc);
					case 5:
						pc->sysargs[4]=getargn(4,pc);
					case 4:
						pc->sysargs[3]=getargn(3,pc);
					case 3:
						pc->sysargs[2]=getargn(2,pc);
					case 2:
						pc->sysargs[1]=getargn(1,pc);
					case 1:
						pc->sysargs[0]=getargn(0,pc);
				}
#if __NR_socketcall != __NR_doesnotexist
				if (scno==__NR_socketcall) {
					//printk("socketcall %d %x\n",pc->sysargs[0],pc->sysargs[1]);
					pc->sysscno=pc->sysargs[0];
					pc->sockaddr=pc->sysargs[1];
					umoven(pc,pc->sockaddr,
							socketcallnargs[pc->sysscno] * sizeof(long), pc->sysargs);
					fun=sockcdtab[pc->sysscno];
				} else {
					pc->sockaddr=0;
					fun=scdtab[pc->sysscno];
				}
#else
				fun=scdtab[pc->sysscno];
#endif
				if (fun != NULL)
					pc->behavior=fun(pc->sysscno,IN,pc);
				else
					pc->behavior=STD_BEHAVIOR;
#ifdef FAKESIGSTOP
				if (scno == __NR_kill && pc->behavior == STD_BEHAVIOR)
					pc->behavior=fakesigstopcont(pc);
#endif
				if (pc->behavior & SC_SKIP_CALL) {
					if (PT_VM_OK) { /* kernel supports System call skip PTRACE_SYSVM */
						if ((fun(scno,OUT,pc) & SC_SUSPENDED)==0)
							pc->sysscno=NOSC;
					} else 
						/* fake syscall with getpid if the kernel does not support
						 * syscall shortcuts */
						putscno(__NR_getpid,pc);
				} else
				{
					/* fork is translated into clone 
					 * offspring management */
					if (isreproducing) {
						offspring_enter(pc);
					}
					if (pc->behavior & SC_SAVEREGS) {
						/* in case the call has been changed, count the 
						 * args for the new call */
						switch (scdnarg[getscno(pc)]) {
							case 6:
								putargn(5,pc->sysargs[5],pc);
							case 5:
								putargn(4,pc->sysargs[4],pc);
							case 4:
								putargn(3,pc->sysargs[3],pc);
							case 3:
								putargn(2,pc->sysargs[2],pc);
							case 2:
								putargn(1,pc->sysargs[1],pc);
							case 1:
								putargn(0,pc->sysargs[0],pc);
						}
					}
				}
			} else { /* POST syscall management (OUT phase) */
				divfun fun;
				GDEBUG(3, "<-- pid %d syscall %d (%s) @ %p", pid, scno, SYSCALLNAME(scno), getpc(pc));
				//printk("OUT\n");
				if (isreproducing) {
					long newpid;
					newpid=getrv(pc);
					if (newpid >= 0) {
						handle_new_proc(newpid,pc);
						offspring_exit(pc);
						putrv(newpid,pc);
					} else {
						////printf("ERESTARTNOINTR scno %d %ld %ld\n",scno, newpid,pc->saved_regs[MY_RAX]);
						offspring_exit(pc);
					}

					GDEBUG(3, "FORK! %d->%d",pid,newpid);

					/* restore original arguments */
				}
				/* It is just for the sake of correctness, this test could be
				 * safely eliminated  to increase the performance*/
				if ((pc->behavior == SC_FAKE && scno != __NR_getpid) && 
#if __NR_socketcall != __NR_doesnotexist
						(scno != __NR_socketcall && pc->sockaddr == 0) &&
#endif
						scno != pc->sysscno)
					GDEBUG(0, "error FAKE != %s",SYSCALLNAME(scno));
#if __NR_socketcall != __NR_doesnotexist
				if (pc->sockaddr == 0) 
					fun=scdtab[pc->sysscno];
				else 
					fun=sockcdtab[pc->sysscno];
#else
				fun=scdtab[pc->sysscno];
#endif
				if (fun != NULL &&
						(pc->behavior == SC_FAKE ||
						 pc->behavior == SC_CALLONXIT ||
						 pc->behavior == SC_TRACEONLY)) {
					pc->behavior = fun(pc->sysscno,OUT,pc);
					if ((pc->behavior & SC_SUSPENDED) == 0)
						pc->sysscno=NOSC;
					else
						pc->behavior=SC_SUSPOUT;
				} else {
					pc->behavior = STD_BEHAVIOR;
					pc->sysscno=NOSC;
				}
			} // end if scno==NOSC (OUT)
			/* resume the caller ONLY IF the syscall is not blocking */
			/* setregs is a macro that resume the execution, too */
			if ((pc->behavior & SC_SUSPENDED) == 0) {
				if ((pc->behavior & SC_SAVEREGS) || isreproducing) {
					if (PT_VM_OK) {
						/*printk("SC %s %d\n",SYSCALLNAME(scno),pc->behavior);*/
						if(setregs(pc,PTRACE_SYSVM, (isreproducing ? 0 : (pc->behavior & SC_VM_MASK)),pc->signum) == -1)
							GPERROR(0, "setregs");
						if(!isreproducing && (pc->behavior & PTRACE_VM_SKIPEXIT))
							pc->sysscno=NOSC; 
					} else
						if( setregs(pc,PTRACE_SYSCALL, 0, pc->signum) < 0)
							GPERROR(0, "setregs");
				} else /* register not modified */
				{
					//printk ("RESTART\n");
					if (PT_VM_OK) {
						if (r_ptrace(PTRACE_SYSVM,pc->pid,pc->behavior & SC_VM_MASK,pc->signum) < 0)
							GPERROR(0, "restart");
						if(pc->behavior & PTRACE_VM_SKIPEXIT)
							pc->sysscno=NOSC; 
					}else {
						if (r_ptrace(PTRACE_SYSCALL,pc->pid,0,pc->signum) < 0)
							GPERROR(0, "restart");
					}
				}
					pc->saved_regs=NULL;
			} else {
				pc->saved_regs=malloc(sizeof(saved_regs));
				memcpy(pc->saved_regs,saved_regs,sizeof(saved_regs));
			}

		} // end if SIGTRAP
		else if(WIFSIGNALED(status)) {
			GDEBUG(3, "%d: signaled %d",pid,WTERMSIG(status));
			/* process killed by a signal */
			droppcb(pc);
		}
		/* Abend and signal management */
		else if(WIFSTOPPED(status)) {
			long saved_regs[VIEWOS_FRAME_SIZE];
			pc->saved_regs=saved_regs;
			GDEBUG(3, "%d: stopped sig=%d",pid,(WSTOPSIG(status)));
			if(WSTOPSIG(status) == SIGSEGV)
			{
				if(getregs(pc) == -1)
					GDEBUG(3, "[err]");
				GDEBUG(3, "%d: stopped sig=SIGSEGV @ %p",
						pc->pid, getpc(pc));
			}
			/*if (!sigishandled(pc,  WSTOPSIG(status))) {
			// also progenie, but for now 
			//r_ptrace(PTRACE_KILL,pid,0,0);
			//printf("KILLED %d %d\n", pid,pc->pid);
			}*/
#ifdef FAKESIGSTOP
			if (WSTOPSIG(status) == SIGTSTP && pc->pp != NULL) {
				pc->flags |= PCB_FAKESTOP;
				GDEBUG(1, "KILL 28 %d",pc->pp->pid);
				//SIGSTOP -> FAKE SIGWINCH
				kill(pc->pp->pid,28);
			} else
#endif
				if (WSTOPSIG(status) == SIGSTOP && (pc->flags & PCB_STARTING)) {
					pc->flags &= ~PCB_STARTING;
#ifdef LIBC_VFORK_DIRTY_TRICKS
					getregs(pc);
					////printk("starting2 %x %x was %x %x\n",pc->sysargs[0],pc->sysargs[1],getargn(0,pc),getargn(1,pc));
					putargn(0,pc->sysargs[0],pc);
					putargn(1,pc->sysargs[1],pc);
					setregs(pc,PTRACE_SYSCALL,0,0);
#else
					r_ptrace(PTRACE_SYSCALL, pid, 0, 0);
#endif
				} else
					/* forward signals to the process */
					if(r_ptrace(PTRACE_SYSCALL, pid, 0, WSTOPSIG(status)) < 0){
						GPERROR(0, "continuing");
						exit(1);
					}
				pc->saved_regs=NULL;
		}
		/* process termination management */
		else if(WIFEXITED(status)) {
			//printf("%d: exited\n",pid);
			/* the process has terminated */
			droppcb(pc);
			/* if it was the "init" process (first child), save its exit status,
			 * since it is also _our_ exit status! */
			if(first_child_pid == pc->pid)
				first_child_exit_status = WEXITSTATUS(status);
		}
		else GDEBUG(1, "wait failed - pid = %d, status = %d", pid, status);
	}
}

/* pc can be resumed: there is data to unblock (maybe) its system call */
void sc_resume(struct pcb *pc)
{
	/* int pid=pc->pid; */
	//printk("RESUME %d\n",pc->pid);
	int scno=pc->sysscno;
	int inout=pc->behavior-SC_SUSPENDED;
	int	isreproducing=(scno == __NR_fork ||
			scno == __NR_vfork ||
			scno == __NR_clone);
	int signum=0;
	divfun fun;
	/* set the current process */
	pthread_setspecific(pcb_key,pc);
#if __NR_socketcall != __NR_doesnotexist
	if (pc->sockaddr == 0)
		fun=scdtab[pc->sysscno];
	else
		fun=sockcdtab[pc->sysscno];
#else
	fun=scdtab[scno];
#endif
	/* try again to execute the mgmt function */
	if (fun != NULL)
		pc->behavior=fun(scno,inout,pc);
	else
		pc->behavior=STD_BEHAVIOR;
	if (inout==IN) { /* resumed in IN phase */
		if (pc->behavior == SC_FAKE) {
			if (PT_VM_OK) { /* in case PTRACE_SYSVM supports syscall skipping */
				if (inout==IN && (fun(scno,OUT,pc) & SC_SUSPENDED)==0)
					pc->sysscno=NOSC;
			} else
				putscno(__NR_getpid,pc);
		} else {
			if (isreproducing)
				offspring_enter(pc);
			if (pc->behavior & SC_SAVEREGS) {
				/* in case the call has been changed, count the 
				 * args for the new call */
				switch (scdnarg[getscno(pc)]) {
					case 6:
						putargn(5,pc->sysargs[5],pc);
					case 5:
						putargn(4,pc->sysargs[4],pc);
					case 4:
						putargn(3,pc->sysargs[3],pc);
					case 3:
						putargn(2,pc->sysargs[2],pc);
					case 2:
						putargn(1,pc->sysargs[1],pc);
					case 1:
						putargn(0,pc->sysargs[0],pc);
				}
			}
		}
	} else { /* inout == OUT */
		if ((pc->behavior & SC_SUSPENDED) == 0)
			pc->sysscno=NOSC;
		else
			pc->behavior = SC_SUSPOUT;
		signum=pc->signum;
	}
	//printk("RESTARTED %d %d\n",pc->pid,pc->behavior);
	/* restore registers and restart ONLY IF the call is not already blocking */
	if ((pc->behavior & SC_SUSPENDED) == 0) {
		if (PT_VM_OK) {
			if(setregs(pc,PTRACE_SYSVM,isreproducing ? 0 : pc->behavior,signum) == -1)
				GPERROR(0, "setregs");
			if(!isreproducing && (pc->behavior & PTRACE_VM_SKIPEXIT))
				pc->sysscno=NOSC;
		} else
			if( setregs(pc,PTRACE_SYSCALL,0,signum) == -1)
				GPERROR(0, "setregs");
		free(pc->saved_regs);
		pc->saved_regs=0;
	}
}

void wake_null(int s)
{
}

static void setsigaction(void)
{
	struct sigaction sa;
	sigset_t blockchild; 

	sa.sa_handler = SIG_IGN;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	r_sigaction(SIGTTOU, &sa, NULL);
	r_sigaction(SIGTTIN, &sa, NULL);
	r_sigaction(SIGHUP, &sa, NULL);
	r_sigaction(SIGINT, &sa, NULL);
	r_sigaction(SIGQUIT, &sa, NULL);
	r_sigaction(SIGPIPE, &sa, NULL);
	r_sigaction(SIGTERM, &sa, NULL);
	////sa.sa_handler = SIG_DFL;
	// SIGCHLD: syscall hadling is interruptable
	//sigaddset(&sa.sa_mask,SIGCHLD);
	// fillset: syscall handling is not interruptable
	sigfillset(&sa.sa_mask);
	/* 
	 * The signal handler is no longer the whole tracehand()
	 * but a smaller function whose only duty is to
	 * wake up the select() in main().
	 * With ppoll there is no need for pipe: in this latter
	 * case SIGCHLD gets blocked. SIGCHLD will unblock ppoll
	 */
	sigemptyset(&blockchild);
	sigaddset(&blockchild,SIGCHLD);
	r_sigprocmask(SIG_BLOCK,&blockchild,NULL);
	sa.sa_handler = wake_null;
	r_sigaction(SIGCHLD, &sa, NULL);
}

/* destructor: the pcb element is flagged as free */
static void vir_pcb_free(void *arg)
{
	struct pcb *pc=arg;
	if (pc->flags & PCB_ALLOCATED) {
		pcb_destructor(pc,0,1);
		free(arg);
	}
}

#if 0
/* execvp implementation (to avoid pure_libc management) */
static int r_execvp(const char *file, char *const argv[]){
	if(strchr(file,'/') != NULL)
		return execve(file,argv,environ);
	else {
		char *path;
		char *envpath;
		char *pathelem;
		char buf[PATH_MAX];
		if ((envpath=getenv("PATH")) == NULL)
			envpath="/bin:/usr/bin";
		path=strdup(envpath);
		while((pathelem=strsep(&path,":")) != NULL){
			if (*pathelem != 0) {
				register int i,j;
				for (i=0; i<PATH_MAX && pathelem[i]; i++)
					buf[i]=pathelem[i];
				if(buf[i-1] != '/' && i<PATH_MAX)
					buf[i++]='/';
				for (j=0; i<PATH_MAX && file[j]; j++,i++)
					buf[i]=file[j];
				buf[i]=0;
				if (r_execve(buf,argv,environ)<0 &&
						((errno != ENOENT) && (errno != ENOTDIR) && (errno != EACCES))) {
					free(path);
					return -1;
				}
			}
		}
		free(path);
		errno = ENOENT;
		return -1;
	}
}
#endif

int capture_attach(struct pcb *pc,pid_t pid)
{
	handle_new_proc(pid,pc);
	if (r_ptrace(PTRACE_ATTACH,pid,0,0) < 0)
		return -errno;
	else {
		int status;
		if(r_waitpid(pid, &status, WUNTRACED) < 0 ||
				r_ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0)
			GPERROR(0, "restarting attached");
		return 0;
	}
}

void capture_execrc(const char *path,const char *argv1)
{
	if (access(path,X_OK)==0) {
		int pid;
		int status;
		switch (pid=fork()) {
			case -1: exit (2);
			case 0: execl(path,path,argv1,(char *)0);
							exit (2);
			default: waitpid(pid,&status,0);
							 if (!WIFEXITED(status))
								 exit (2);
		}
	}
}

/* main capture startup */
int capture_main(char **argv, char *rc)
{
	int status;
#if __NR_socketcall != __NR_doesnotexist
	scdnarg[__NR_socketcall]=2;
#endif

	allocatepcbtab();
	switch (first_child_pid=fork()) {
		case -1:
			GPERROR(0, "strace: fork");
			exit(1);
			break;
		case 0:
			{
				sigset_t unblockall;
				sigemptyset(&unblockall);
				r_sigprocmask(SIG_SETMASK,&unblockall,NULL);
			}
			unsetenv("LD_PRELOAD");
			/* try to set process priority back to standard prio (effective only when 
			 * umview runs in setuid mode), useless call elsewhere */
			r_setpriority(PRIO_PROCESS,0,0);
			if(r_ptrace(PTRACE_TRACEME, 0, 0, 0) < 0){
				GPERROR(0, "ptrace");
				exit(1);
			}
			r_kill(getpid(), SIGSTOP);
			capture_execrc("/etc/viewosrc",(char *)0);
			if (rc != NULL && *rc != 0)
				capture_execrc(rc,(char *)0);
			/* maybe it is better to use execvp instead of r_execvp.
			 * the former permits to load the startup executable through 
			 * a (preloaded) module */
			execvp(argv[0], argv);
			GPERROR(0, "strace: exec");
			_exit(1);
		default:
			/* UMVIEW TRACER startup */
			/* create the thread key */
			pthread_key_create(&pcb_key,vir_pcb_free);
			/* init the nested syscall capturing */
			capture_nested_init();
			/* create (by hand) the first process' pcb */
			handle_new_proc(first_child_pid,pcbtab[0]);
			/* set the pcb_key for this process */
			pthread_setspecific(pcb_key,pcbtab[0]);
			if(r_waitpid(first_child_pid, &status, WUNTRACED) < 0){
				GPERROR(0, "Waiting for stop");
				exit(1);
			}
			/* set up the signal management */
			setsigaction();
			/* okay, the first process can start (traced) */
			if(r_ptrace(PTRACE_SYSCALL, first_child_pid, 0, 0) < 0){
				GPERROR(0, "continuing");
				exit(1);
			}
	}
	return 0;
}

/* vim: set ts=2 shiftwidth=2: */
