/* $Header$ */

/* Running a process and communication */

#include <signal.h>
#include <stdio.h>
#include <assert.h>
#include <alloc.h>

#include "ops.h"
#include "message.h"
#include "position.h"
#include "tree.h"
#include "file.h"
#include "symbol.h"
#include "idf.h"
#include "scope.h"

#define MAXARG	128

extern char	*strncpy();
extern struct idf *str2idf();

extern char	*AObj;
extern FILE	*db_out;
extern int	debug;
extern long	pointer_size;

static int	child_pid;		/* process id of child */
static int	to_child, from_child;	/* file descriptors for communication */
static int	child_status;
static int	restoring;
static int	fild1[2], fild2[2];	/* pipe file descriptors */

int		db_ss;
t_lineno	currline, listline;

static int	catch_sigpipe();
static int	stopped();
static int	uputm(), ugetm();

int
init_run()
{
  /* take file descriptors so that listing cannot take them */
  int i;

  for (i = IN_FD; i <= OUT_FD; i++) close(i);
  if (pipe(fild1) < 0 ||
      pipe(fild2) < 0 ||
      fild1[0] != IN_FD ||
      fild2[1] != OUT_FD) {
	return 0;
  }
  to_child = fild1[1];
  from_child = fild2[0];
  return 1;
}

int
start_child(p)
  p_tree	p;
{
  /* start up the process to be debugged and set up communication */

  char *argp[MAXARG];				/* argument list */
  register p_tree pt = p->t_args[0], pt1;
  unsigned int	nargs = 1;			/* #args */
  char	*in_redirect = 0;			/* standard input redirected */
  char	*out_redirect = 0;			/* standard output redirected */

  signal_child(SIGKILL); /* like families in China, this debugger is only
			    allowed one child
			 */

  /* first check arguments and redirections and build argument list */
  while (pt) {
  	switch(pt->t_oper) {
	case OP_LINK:
		pt1 = pt->t_args[1];
		pt = pt->t_args[0];
		continue;
	case OP_NAME:
		if (nargs < (MAXARG-1)) {
			argp[nargs++] = pt->t_str;
		}
		else {
			error("too many arguments");
			return 0;
		}
		break;
	case OP_INPUT:
		if (in_redirect) {
			error("input redirected twice?");
			return 0;
		}
		in_redirect = pt->t_str;
		break;
	case OP_OUTPUT:
		if (out_redirect) {
			error("output redirected twice?");
			return 0;
		}
		out_redirect = pt->t_str;
		break;
  	}
	if (pt != pt1) pt = pt1;
	else break;
  }
  argp[0] = AObj;
  argp[nargs] = 0;

  /* create child process */
  child_pid = fork();
  if (child_pid < 0) {
	error("could not create child");
	return 0;
  }
  if (child_pid == 0) {
	/* this is the child process */
	close(fild1[1]);
	close(fild2[0]);

	signal(SIGINT, SIG_IGN);

	/* I/O redirection */
	if (in_redirect) {
		int fd;
		close(0);
		if ((fd = open(in_redirect, 0)) < 0) {
			error("could not open input file");
			exit(-1);
		}
		if (fd != 0) {
			dup2(fd, 0);
			close(fd);
		}
	}
	if (out_redirect) {
		int fd;
		close(1);
		if ((fd = creat(in_redirect, 0666)) < 0) {
			error("could not open output file");
			exit(-1);
		}
		if (fd != 1) {
			dup2(fd, 1);
			close(fd);
		}
	}

	/* and run process to be debugged */
	execv(AObj, argp);
	error("could not exec %s", AObj);
	exit(-1);
  }

  /* debugger */
  close(fild1[0]);
  close(fild2[1]);

  pipe(fild1);		/* to occupy file descriptors */
  signal(SIGPIPE, catch_sigpipe);
  if (! wait_for_child((char *) 0)) {
	error("child not responding");
	return 0;
  }
  do_items();
  if (! restoring) send_cont(1);
  return 1;
}

int
wait_for_child(s)
  char	*s;		/* to pass on to 'stopped' */
{
  struct message_hdr m;

  if (child_pid) {
  	if (ugetm(&m)) {
		return stopped(s, (t_addr) m.m_size);
  	}
  	return 0;
  }
  return 1;
}

signal_child(sig)
{
  if (child_pid) {
	kill(child_pid, sig);
	if (sig == SIGKILL) {
		wait(&child_status);
		init_run();
	}
  }
}

static int
catch_sigpipe()
{
  child_pid = 0;
}


static int
ureceive(p, c)
  char	*p;
  long	c;
{
  int	i;

  if (! child_pid) return 0;

  while (c >= 0x1000) {
	i = read(from_child, p, 0x1000);
	if (i <= 0) {
		if (i == 0) child_pid = 0;
		return 0;
	}
	p += i;
	c -= i;
  }
  while (c > 0) {
	i = read(from_child, p, (int)c);
	if (i <= 0) {
		if (i == 0) child_pid = 0;
		return 0;
	}
	p += i;
	c -= i;
  }
  return c == 0;
}

static int
usend(p, c)
  char	*p;
  long	c;
{
  int	i;

  while (c >= 0x1000) {
	i = write(to_child, p, 0x1000);
	if (i < 0) return 0;
	p += i;
	c -= i;
  }
  while (c > 0) {
	i = write(to_child, p, (int)c);
	if (i < 0) return 0;
	p += i;
	c -= i;
  }
  return 1;
}

static int
ugetm(message)
  struct message_hdr *message;
{
  if (! ureceive((char *) message, (long) sizeof(struct message_hdr))) {
  	return 0;
  }
  if (debug) printf("Got %d\n", message->m_type);
  return 1;
}

static int
uputm(message)
  struct message_hdr *message;
{
  if (! usend((char *) message, (long) sizeof(struct message_hdr))) {
  	return 0;
  }
  if (debug) printf("Sent %d\n", message->m_type);
  return 1;
}

static struct message_hdr	answer;
static int	single_stepping;

static int
stopped(s, a)
  char	*s;	/* stop message */
  t_addr a;	/* address where stopped */
{
  p_position pos;

  if (s) {
	fprintf(db_out, "%s ", s);
	pos = print_position((t_addr) a, 1);
	fputs("\n", db_out);
	list_position(pos);
  }
  return 1;
}

static int
could_send(m, stop_message)
  struct message_hdr	*m;
{
  int	type;
  t_addr a;
  for (;;) {
  	if (child_pid) {
		if (! uputm(m) ||
		    ! ugetm(&answer)) {
			if (child_pid) {
				error("something wrong!");
				return 1;
			}
			wait(&child_status);
			init_run();
			if (child_status & 0177) {
				fprintf(db_out,
					"Child died with signal %d\n",
					child_status & 0177);
			}
			else {
				fprintf(db_out,
					"Child terminated, exit status %d\n",
					child_status >> 8);
			}
			return 1;
		}
		a = answer.m_size;
		type = answer.m_type;
		if (m->m_type & DB_RUN) {
			/* run command */
			CurrentScope = get_scope_from_addr((t_addr) a);
		    	if (! item_addr_actions(a) &&
		            ( type == DB_SS || type == OK)) {
				/* no explicit breakpoints at this position.
				   Also, child did not stop because of
				   SETSS or SETSSF, otherwise we would
				   have gotten END_SS.
				   So, continue.
				*/
				if ((m->m_type & ~ DB_SS) != CONT) {
					m->m_type = CONT | (m->m_type & DB_SS);
				}
				continue;
			}
			if (type != END_SS && single_stepping) {
				m->m_type = CLRSS;
				uputm(m) && ugetm(&answer);
			}
			single_stepping = 0;
		}
		if (stop_message) {
			stopped("stopped", a);
			handle_displays();
		}
		return 1;
	}
	return 0;
  }
  /*NOTREACHED*/
}

int
get_bytes(size, from, to)
  long	size;
  t_addr from;
  char	*to;
{
  struct message_hdr	m;

  m.m_type = GETBYTES;
  m.m_size = size;
  ATOBUF(m.m_buf, (char *) from);

  if (! could_send(&m, 0)) {
	return 0;
  }

  if (answer.m_type == FAIL) {
	return 0;
  }

  assert(answer.m_type == DATA && answer.m_size == m.m_size);

  return ureceive(to, answer.m_size);
}

int
set_bytes(size, from, to)
  long	size;
  char	*from;
  t_addr to;
{
  struct message_hdr	m;

  m.m_type = SETBYTES;
  m.m_size = size;
  ATOBUF(m.m_buf, (char *) to);

  return uputm(&m)
	 && usend(from, size)
	 && ugetm(&m)
	 && m.m_type != FAIL;
}

int
get_dump(globmessage, globbuf, stackmessage, stackbuf)
  struct message_hdr *globmessage, *stackmessage;
  char **globbuf, **stackbuf;
{
  struct message_hdr	m;

  m.m_type = DUMP;
  if (! could_send(&m, 0)) {
	return 0;
  }
  if (answer.m_type == FAIL) return 0;
  assert(answer.m_type == DGLOB);
  *globmessage = answer;
  *globbuf = Malloc((unsigned) answer.m_size);
  if (! ureceive(*globbuf, answer.m_size) || ! ugetm(stackmessage)) {
	free(*globbuf);
	return 0;
  }
  assert(stackmessage->m_type == DSTACK);
  *stackbuf = Malloc((unsigned) stackmessage->m_size);
  if (! ureceive(*stackbuf, stackmessage->m_size)) {
	free(*globbuf);
	free(*stackbuf);
	return 0;
  }
  ATOBUF(globmessage->m_buf+SP_OFF*pointer_size,
	 BUFTOA(stackmessage->m_buf+SP_OFF*pointer_size));
  return 1;
}

int
put_dump(globmessage, globbuf, stackmessage, stackbuf)
  struct message_hdr *globmessage, *stackmessage;
  char *globbuf, *stackbuf;
{
  struct message_hdr m;

  if (! child_pid) {
	restoring = 1;
	start_child(run_command);
	restoring = 0;
  }
  return	uputm(globmessage) &&
		usend(globbuf, globmessage->m_size) &&
		uputm(stackmessage) &&
		usend(stackbuf, stackmessage->m_size) &&
		ugetm(&m) && stopped("restored", m.m_size);
}

t_addr *
get_EM_regs(level)
  int	level;
{
  struct message_hdr	m;
  static t_addr buf[5];
  register t_addr *to = &buf[0];

  m.m_type = GETEMREGS;
  m.m_size = level;

  if (! could_send(&m, 0)) {
	return 0;
  }
  if (answer.m_type == FAIL) return 0;
  *to++ = (t_addr) BUFTOA(answer.m_buf);
  *to++ = (t_addr) BUFTOA(answer.m_buf+pointer_size);
  *to++ = (t_addr) BUFTOA(answer.m_buf+2*pointer_size);
  *to++ = (t_addr) BUFTOA(answer.m_buf+3*pointer_size);
  *to++ = (t_addr) BUFTOA(answer.m_buf+4*pointer_size);
  return buf;
}

int
set_pc(PC)
  t_addr	PC;
{
  struct message_hdr	m;

  m.m_type = SETEMREGS;
  m.m_size = 0;
  ATOBUF(m.m_buf+PC_OFF*pointer_size, (char *)PC);
  return could_send(&m, 0) && answer.m_type != FAIL;
}

int
send_cont(stop_message)
  int	stop_message;
{
  struct message_hdr	m;

  m.m_type = (CONT | (db_ss ? DB_SS : 0));
  m.m_size = 0;
  return could_send(&m, stop_message) && answer.m_type != FAIL;
}

int
do_single_step(type, count)
  int	type;
  long	count;
{
  struct message_hdr	m;

  m.m_type = type | (db_ss ? DB_SS : 0);
  m.m_size = count;
  single_stepping = 1;
  if (could_send(&m, 1) && answer.m_type != FAIL) {
	return 1;
  }
  single_stepping = 0;
  return 0;
}

int
set_or_clear_breakpoint(a, type)
  t_addr	a;
  int	type;
{
  struct message_hdr m;

  if (a == ILL_ADDR || a == NO_ADDR) return 0;

  m.m_type = type;
  m.m_size = a;
  if (debug) printf("%s breakpoint at 0x%lx\n", type == SETBP ? "setting" : "clearing", (long) a);
  if (! could_send(&m, 0)) { }

  return 1;
}

int
set_or_clear_trace(start, end, type)
  t_addr start, end;
  int	type;
{
  struct message_hdr m;

  m.m_type = type;
  ATOBUF(m.m_buf, (char *) start);
  ATOBUF(m.m_buf+pointer_size, (char *) end);
  if (debug) printf("%s trace at [0x%lx,0x%lx]\n", type == SETTRACE ? "setting" : "clearing", (long) start, (long) end);
  if (! could_send(&m, 0)) { }

  return 1;
}