/*
 * process interface for Linux.
 * 
 * Uses ptrace for registers and data,
 * /proc for some process status.
 * There's not much point to worrying about
 * byte order here -- using ptrace means
 * we're running on the architecture we're debugging,
 * unless truly weird stuff is going on.
 *
 * It is tempting to use /proc/%d/mem along with
 * the sp and pc in the stat file to get a stack trace
 * without attaching to the program, but unfortunately
 * you can't read the mem file unless you've attached.
 */

#include <u.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
#include <libc.h>
#include <mach.h>
#include "ureg386.h"

Mach *machcpu = &mach386;

typedef struct PtraceRegs PtraceRegs;

struct PtraceRegs
{
	Regs r;
	int pid;
};

static int ptracerw(Map*, Seg*, ulong, void*, uint, int);
static int ptraceregrw(Regs*, char*, ulong*, int);

static int attachedpids[1000];
static int nattached;

void
unmapproc(Map *map)
{
	int i;

	if(map == nil)
		return;
	for(i=0; i<map->nseg; i++)
		while(i<map->nseg && map->seg[i].pid){
			map->nseg--;
			memmove(&map->seg[i], &map->seg[i+1], 
				(map->nseg-i)*sizeof(map->seg[0]));
		}
}

int
mapproc(int pid, Map *map, Regs **rp)
{
	int i;
	Seg s;
	PtraceRegs *r;

	if(nattached==1 && attachedpids[0] == pid)
		goto already;
	if(nattached)
		detachproc(attachedpids[0]);

	for(i=0; i<nattached; i++)
		if(attachedpids[i]==pid)
			goto already;
	if(nattached == nelem(attachedpids)){
		werrstr("attached to too many processes");
		return -1;
	}

	if(ptrace(PTRACE_ATTACH, pid, 0, 0) < 0){
		werrstr("ptrace attach %d: %r", pid);
		return -1;
	}
	
	if(ctlproc(pid, "waitstop") < 0){
		fprint(2, "waitstop: %r");
		ptrace(PTRACE_DETACH, pid, 0, 0);
		return -1;
	}
	attachedpids[nattached++] = pid;

already:
	memset(&s, 0, sizeof s);
	s.base = 0;
	s.size = 0xFFFFFFFF;
	s.offset = 0;
	s.name = "data";
	s.file = nil;
	s.rw = ptracerw;
	s.pid = pid;
	if(addseg(map, s) < 0){
		fprint(2, "addseg: %r\n");
		return -1;
	}

	if((r = mallocz(sizeof(PtraceRegs), 1)) == nil){
		fprint(2, "mallocz: %r\n");
		return -1;
	}
	r->r.rw = ptraceregrw;
	r->pid = pid;
	*rp = (Regs*)r;
	return 0;
}

int
detachproc(int pid)
{
	int i;

	for(i=0; i<nattached; i++){
		if(attachedpids[i] == pid){
			attachedpids[i] = attachedpids[--nattached];
			break;
		}
	}
	return ptrace(PTRACE_DETACH, pid, 0, 0);
}

static int
ptracerw(Map *map, Seg *seg, ulong addr, void *v, uint n, int isr)
{
	int i;
	u32int u;
	uchar buf[4];

	addr += seg->base;
	for(i=0; i<n; i+=4){
		if(isr){
			errno = 0;
			u = ptrace(PTRACE_PEEKDATA, seg->pid, addr+i, 0);
			if(errno)
				goto ptraceerr;
			if(n-i >= 4)
				*(u32int*)((char*)v+i) = u;
			else{
				*(u32int*)buf = u;
				memmove((char*)v+i, buf, n-i);
			}
		}else{
			if(n-i >= 4)
				u = *(u32int*)((char*)v+i);
			else{
				errno = 0;
				u = ptrace(PTRACE_PEEKDATA, seg->pid, addr+i, 0);
				if(errno)
					return -1;
				*(u32int*)buf = u;
				memmove(buf, (char*)v+i, n-i);
				u = *(u32int*)buf;
			}
			if(ptrace(PTRACE_POKEDATA, seg->pid, addr+i, &u) < 0)
				goto ptraceerr;
		}
	}
	return 0;

ptraceerr:
	werrstr("ptrace: %r");
	return -1;
}

static char* linuxregs[] = {
	"BX",
	"CX",
	"DX",
	"SI",
	"DI",
	"BP",
	"AX",
	"DS",
	"ES",
	"FS",
	"GS",
	"OAX",
	"PC",
	"CS",
	"EFLAGS",
	"SP",
	"SS",
};

static ulong
reg2linux(char *reg)
{
	int i;

	for(i=0; i<nelem(linuxregs); i++)
		if(strcmp(linuxregs[i], reg) == 0)
			return 4*i;
	return ~(ulong)0;
}

static int
ptraceregrw(Regs *regs, char *name, ulong *val, int isr)
{
	int pid;
	ulong addr;
	u32int u;

	pid = ((PtraceRegs*)regs)->pid;
	addr = reg2linux(name);
	if(~addr == 0){
		if(isr){
			*val = ~(ulong)0;
			return 0;
		}
		werrstr("register not available");
		return -1;
	}
	if(isr){
		errno = 0;
		u = ptrace(PTRACE_PEEKUSER, pid, addr, 0);
		if(errno)
			goto ptraceerr;
		*val = u;
	}else{
		u = *val;
		if(ptrace(PTRACE_POKEUSER, pid, addr, &u) < 0)
			goto ptraceerr;
	}
	return 0;

ptraceerr:
	werrstr("ptrace: %r");
	return -1;
}

static int
isstopped(int pid)
{
	char buf[1024];
	int fd, n;
	char *p;

	snprint(buf, sizeof buf, "/proc/%d/stat", pid);
	if((fd = open(buf, OREAD)) < 0)
		return 0;
	n = read(fd, buf, sizeof buf-1);
	close(fd);
	if(n <= 0)
		return 0;
	buf[n] = 0;

	/* command name is in parens, no parens afterward */
	p = strrchr(buf, ')');
	if(p == nil || *++p != ' ')
		return 0;
	++p;

	/* next is state - T is stopped for tracing */
	return *p == 'T';
}

/* /proc/pid/stat contains 
	pid
	command in parens
	0. state
	1. ppid
	2. pgrp
	3. session
	4. tty_nr
	5. tpgid
	6. flags (math=4, traced=10)
	7. minflt
	8. cminflt
	9. majflt
	10. cmajflt
	11. utime
	12. stime
	13. cutime
	14. cstime
	15. priority
	16. nice
	17. 0
	18. itrealvalue
	19. starttime
	20. vsize
	21. rss
	22. rlim
	23. startcode
	24. endcode
	25. startstack
	26. kstkesp
	27. kstkeip
	28. pending signal bitmap
	29. blocked signal bitmap
	30. ignored signal bitmap
	31. caught signal bitmap
	32. wchan
	33. nswap
	34. cnswap
	35. exit_signal
	36. processor
*/


int
procnotes(int pid, char ***pnotes)
{
	char buf[1024], *f[40];
	int fd, i, n, nf;
	char *p, *s, **notes;
	ulong sigs;
	extern char *_p9sigstr(int, char*);

	*pnotes = nil;
	snprint(buf, sizeof buf, "/proc/%d/stat", pid);
	if((fd = open(buf, OREAD)) < 0){
		fprint(2, "open %s: %r\n", buf);
		return -1;
	}
	n = read(fd, buf, sizeof buf-1);
	close(fd);
	if(n <= 0){
		fprint(2, "read %s: %r\n", buf);
		return -1;
	}
	buf[n] = 0;

	/* command name is in parens, no parens afterward */
	p = strrchr(buf, ')');
	if(p == nil || *++p != ' '){
		fprint(2, "bad format in /proc/%d/stat\n", pid);
		return -1;
	}
	++p;

	nf = tokenize(p, f, nelem(f));
	if(0) print("code 0x%lux-0x%lux stack 0x%lux kstk 0x%lux keip 0x%lux pending 0x%lux\n",
		strtoul(f[23], 0, 0), strtoul(f[24], 0, 0), strtoul(f[25], 0, 0),
		strtoul(f[26], 0, 0), strtoul(f[27], 0, 0), strtoul(f[28], 0, 0));
	if(nf <= 28)
		return -1;

	sigs = strtoul(f[28], 0, 0) & ~(1<<SIGCONT);
	if(sigs == 0){
		*pnotes = nil;
		return 0;
	}

	notes = mallocz(32*sizeof(char*), 0);
	if(notes == nil)
		return -1;
	n = 0;
	for(i=0; i<32; i++){
		if((sigs&(1<<i)) == 0)
			continue;
		if((s = _p9sigstr(i, nil)) == nil)
			continue;
		notes[n++] = s;
	}
	*pnotes = notes;
	return n;
}

#undef waitpid

int
ctlproc(int pid, char *msg)
{
	int p, status;

	if(strcmp(msg, "hang") == 0){
		if(pid == getpid())
			return ptrace(PTRACE_TRACEME, 0, 0, 0);
		werrstr("can only hang self");
		return -1;
	}
	if(strcmp(msg, "kill") == 0)
		return ptrace(PTRACE_KILL, pid, 0, 0);
	if(strcmp(msg, "startstop") == 0){
		if(ptrace(PTRACE_CONT, pid, 0, 0) < 0)
			return -1;
		goto waitstop;
	}
	if(strcmp(msg, "sysstop") == 0){
		if(ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0)
			return -1;
		goto waitstop;
	}
	if(strcmp(msg, "stop") == 0){
		if(kill(pid, SIGSTOP) < 0)
			return -1;
		goto waitstop;
	}
	if(strcmp(msg, "waitstop") == 0){
	waitstop:
		if(isstopped(pid))
			return 0;
		for(;;){
			p = waitpid(pid, &status, WUNTRACED|__WALL);
			if(p <= 0){
				if(errno == ECHILD){
					if(isstopped(pid))
						return 0;
				}
				return -1;
			}
			if(WIFEXITED(status) || WIFSTOPPED(status))
				return 0;
		}
	}
	if(strcmp(msg, "start") == 0)
		return ptrace(PTRACE_CONT, pid, 0, 0);
	werrstr("unknown control message '%s'", msg);
	return -1;
}

char*
proctextfile(int pid)
{
	static char buf[1024], pbuf[128];

	snprint(pbuf, sizeof pbuf, "/proc/%d/exe", pid);
	if(readlink(pbuf, buf, sizeof buf) >= 0)
		return buf;
	if(access(pbuf, AEXIST) >= 0)
		return pbuf;
	return nil;
}


#if 0
	snprint(buf, sizeof buf, "/proc/%d/maps", pid);
	if((b = Bopen(buf, OREAD)) == nil){
		werrstr("open %s: %r", buf);
		return -1;
	}

/*
        08048000-08056000 r-xp 00000000 03:0c 64593      /usr/sbin/gpm
        08056000-08058000 rw-p 0000d000 03:0c 64593      /usr/sbin/gpm
        08058000-0805b000 rwxp 00000000 00:00 0
        40000000-40013000 r-xp 00000000 03:0c 4165       /lib/ld-2.2.4.so
        40013000-40015000 rw-p 00012000 03:0c 4165       /lib/ld-2.2.4.so
        4001f000-40135000 r-xp 00000000 03:0c 45494      /lib/libc-2.2.4.so
        40135000-4013e000 rw-p 00115000 03:0c 45494      /lib/libc-2.2.4.so
        4013e000-40142000 rw-p 00000000 00:00 0
        bffff000-c0000000 rwxp 00000000 00:00 0
*/

	file = nil;
	while((p = Brdline(b, '\n')) != nil){
		p[Blinelen(b)-1] = 0;
		memset(f, 0, sizeof f);
		if((nf = getfields(p, f, 6, 1, " ")) < 5)
			continue;
		base = strtoul(f[0], &p, 16);
		if(*p != '-')
			continue;
		end = strtoul(p+1, &p, 16);
		if(*p != 0)
			continue;
		offset = strtoul(f[2], &p, 16);
		if(*p != 0)
			continue;
		if(nf == 6)
			file = f[5];
		zero = atoi(f[4]) == 0;
		print("%lux-%lux %lux %s %s\n", base, end, offset, zero ? "data" : "text", file ? file : "");
		s.base = base;
		s.size = end - base;
		s.offset = offset;
		s.name = zero ? "data" : "text";
		s.file = strdup(file);
		s.rw = ptracerw;
		s.pid = pid;
		if(addseg(map, s) < 0){
			Bterm(b);
			ptrace(PTRACE_DETACH, pid, 0, 0);
			return -1;
		}
	}
	Bterm(b);
#endif