#include	"u.h"
#include	"../port/lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"../port/error.h"

#include	<dtracy.h>

static Lock *machlocks;

typedef struct DTKChan DTKChan;
typedef struct DTKAux DTKAux;
QLock dtracylock;

struct DTKChan {
	DTChan *ch;
	int ref;
	int idx;
};
struct DTKAux {
	char *str;
};

static void
prog(DTKChan *p, char *s)
{
	DTClause *c;
	int rc;

	dtcrun(p->ch, DTCSTOP);
	dtcreset(p->ch);
	while(*s != 0){
		s = dtclunpack(s, &c);
		if(s == nil)
			error("invalid program");
		rc = dtcaddcl(p->ch, c);
		dtclfree(c);
		if(rc < 0){
			dtcreset(p->ch);
			error(up->syserrstr);
		}
	}
}

enum {
	/* Qdir */
	Qclone = 1,
	Qprobes = 2,
};

enum {
	Qdir,
	Qctl,
	Qprog,
	Qbuf,
	Qepid,
	Qaggbuf,
};

static Dirtab dtracydir[] = {
	"ctl",	{ Qctl, 0, 0 }, 0,	0660,
	"prog", { Qprog, 0, 0 }, 0,	0660,
	"buf",	{ Qbuf, 0, 0, }, 0,	0440,
	"epid",	{ Qepid, 0, 0 }, 0,	0440,
	"aggbuf",	{ Qaggbuf, 0, 0 }, 0,	0440,
};

enum {
	CMstop,
	CMgo,
};

static Cmdtab dtracyctlmsg[] = {
	CMstop,		"stop",		1,
	CMgo,		"go",		1,
};

static DTKChan **dtktab;
static int ndtktab;

static DTKChan *
dtklook(vlong n)
{
	if((uvlong)n >= ndtktab) return nil;
	return dtktab[n];
}
#define QIDPATH(q,e) ((q) + 1 << 8 | (e))
#define SLOT(q) ((vlong)((q).path >> 8) - 1)
#define FILE(q) ((int)(q).path & 0xff)

static DTKChan *
dtknew(void)
{
	DTKChan *p;
	DTKChan **newtab;
	int i;
	
	p = malloc(sizeof(DTKChan));
	if(p == nil) error(Enomem);
	for(i = 0; i < ndtktab; i++)
		if(dtktab[i] == nil){
			dtktab[i] = p;
			p->idx = i;
			break;
		}
	if(i == ndtktab){
		newtab = realloc(dtktab, (ndtktab + 1) * sizeof(DTKChan *));
		if(newtab == nil) error(Enomem);
		dtktab = newtab;
		dtktab[ndtktab] = p;
		p->idx = ndtktab++;
	}
	p->ch = dtcnew();
	return p;
}

static void
dtkfree(DTKChan *p)
{
	int idx;
	
	idx = p->idx;
	dtcfree(p->ch);
	free(p);
	dtktab[idx] = nil;
}

static int dtracyen;

static void
dtracyinit(void)
{
	dtracyen = getconf("*dtracy") != nil;
	if(!dtracyen) return;
	machlocks = smalloc(sizeof(Lock) * conf.nmach);
	dtinit(conf.nmach);
}

static Chan*
dtracyattach(char *spec)
{
	if(!dtracyen)
		error("*dtracy= not set");
	return devattach(L'Δ', spec);
}

static int
dtracygen(Chan *c, char *, Dirtab *, int, int s, Dir *dp)
{
	Dirtab *tab;
	uvlong path;

	if(s == DEVDOTDOT){
		devdir(c, (Qid){Qdir, 0, QTDIR}, "#Δ", 0, eve, 0555, dp);
		return 1;
	}
	if(c->qid.path == Qdir){
		if(s-- == 0) goto clone;
		if(s-- == 0) goto probes;
		if(s >= ndtktab) return -1;
		if(dtklook(s) == nil) return 0;
		sprint(up->genbuf, "%d", s);
		devdir(c, (Qid){QIDPATH(s, Qdir), 0, QTDIR}, up->genbuf, 0, eve, DMDIR|0555, dp);
		return 1;
	}
	if(c->qid.path == Qclone){
	clone:
		strcpy(up->genbuf, "clone");
		devdir(c, (Qid){Qclone, 0, QTFILE}, up->genbuf, 0, eve, 0444, dp);
		return 1;
	}
	if(c->qid.path == Qprobes){
	probes:
		strcpy(up->genbuf, "probes");
		devdir(c, (Qid){Qprobes, 0, QTFILE}, up->genbuf, 0, eve, 0444, dp);
		return 1;		
	}
	if(s >= nelem(dtracydir))
		return -1;
	tab = &dtracydir[s];
	path = QIDPATH(SLOT(c->qid), 0);
	devdir(c, (Qid){tab->qid.path|path, tab->qid.vers, tab->qid.type}, tab->name, tab->length, eve, tab->perm, dp);
	return 1;
}

static Walkqid*
dtracywalk(Chan *c, Chan *nc, char **name, int nname)
{
	Walkqid *rc;

	eqlock(&dtracylock);
	if(waserror()){
		qunlock(&dtracylock);
		nexterror();
	}
	rc = devwalk(c, nc, name, nname, nil, 0, dtracygen);
	qunlock(&dtracylock);
	poperror();
	return rc;
}

static int
dtracystat(Chan *c, uchar *dp, int n)
{
	int rc;

	eqlock(&dtracylock);
	if(waserror()){
		qunlock(&dtracylock);
		nexterror();
	}
	rc = devstat(c, dp, n, nil, 0, dtracygen);
	qunlock(&dtracylock);
	poperror();
	return rc;	
}

static Chan*
dtracyopen(Chan *c, int omode)
{
	DTKChan *p;
	Chan *ch;

	eqlock(&dtracylock);
	if(waserror()){
		qunlock(&dtracylock);
		nexterror();
	}
	if(c->qid.path == Qclone){
		if(!iseve()) error(Eperm);
		p = dtknew();
		c->qid.path = QIDPATH(p->idx, Qctl);
	}
	if(c->qid.path == Qprobes){
		p = nil;
	}else{
		p = dtklook(SLOT(c->qid));
		if(SLOT(c->qid) >= 0 && p == nil) error(Enonexist);
		if(FILE(c->qid) != Qdir && !iseve()) error(Eperm);
	}
	ch = devopen(c, omode, nil, 0, dtracygen);
	if(p != nil) p->ref++;
	qunlock(&dtracylock);
	poperror();
	ch->aux = smalloc(sizeof(DTKAux));
	return ch;
}

static void
dtracyclose(Chan *ch)
{
	DTKAux *aux;
	DTKChan *p;

	if(ch->aux != nil){
		eqlock(&dtracylock);
		p = dtklook(SLOT(ch->qid));
		if(p != nil && --p->ref == 0)
			dtkfree(p);
		qunlock(&dtracylock);
		aux = ch->aux;
		free(aux->str);
		free(ch->aux);
		ch->aux = nil;
	}
}

static int
epidread(DTKAux *aux, DTChan *c, char *a, long n, vlong off)
{
	Fmt f;
	DTEnab *e;

	if(off == 0){
		free(aux->str);
		aux->str = nil;
	}
	if(aux->str == nil){
		fmtstrinit(&f);
		for(e = c->enab; e != nil; e = e->channext)
			fmtprint(&f, "%d %d %d %s\n", e->epid, e->gr->id, e->gr->reclen, e->prob->name);
		aux->str = fmtstrflush(&f);
	}
	return readstr(off, a, n, aux->str);
}

static long
lockedread(DTChan *c, void *a, long n, int(*readf)(DTChan *, void *, int))
{
	long rc;

	if(waserror()){
		qunlock(&dtracylock);
		nexterror();
	}
	eqlock(&dtracylock);
	rc = readf(c, a, n);
	qunlock(&dtracylock);
	poperror();
	return rc;
}

static long
handleread(DTChan *c, void *a, long n, int(*readf)(DTChan *, void *, int))
{
	long rc, m;
	int i;

	for(;;){
		rc = lockedread(c, a, n, readf);
		if(rc < 0) return -1;
		if(rc > 0) break;
		tsleep(&up->sleep, return0, 0, 250);
	}
	m = rc;
	for(i = 0; i < 3 && m < n/2; i++){
		tsleep(&up->sleep, return0, 0, 50);
		rc = lockedread(c, (uchar *)a + m, n - m, readf);
		if(rc < 0) break;
		m += rc;
	}
	return m;
}

static long
probesread(DTKAux *aux, char *a, long n, vlong off)
{
	Fmt f;
	DTProbe **l;
	int i, nl;
	
	if(aux->str == nil){
		fmtstrinit(&f);
		nl = dtplist(&l);
		for(i = 0; i < nl; i++)
			fmtprint(&f, "%s\n", l[i]->name);
		dtfree(l);
		aux->str = fmtstrflush(&f);
	}
	return readstr(off, a, n, aux->str);
}

static long
dtracyread(Chan *c, void *a, long n, vlong off)
{
	int rc;
	DTKChan *p;
	DTChan *ch;

	eqlock(&dtracylock);
	if(waserror()){
		qunlock(&dtracylock);
		nexterror();
	}
	if(SLOT(c->qid) == -1)
		switch((int)c->qid.path){
		case Qdir:
			rc = devdirread(c, a, n, nil, 0, dtracygen);
			goto out;
		case Qprobes:
			rc = probesread(c->aux, a, n, off);
			goto out;
		default:
			error(Egreg);
		}
	p = dtklook(SLOT(c->qid));
	if(p == nil) error(Enonexist);
	switch(FILE(c->qid)){
	case Qdir:
		rc = devdirread(c, a, n, nil, 0, dtracygen);
		break;
	case Qctl:
		sprint(up->genbuf, "%d", p->idx);
		rc = readstr(off, a, n, up->genbuf);
		break;
	case Qbuf:
		ch = p->ch;
		qunlock(&dtracylock);
		poperror();
		return handleread(ch, a, n, dtcread);
	case Qaggbuf:
		ch = p->ch;
		qunlock(&dtracylock);
		poperror();
		return handleread(ch, a, n, dtcaggread);
	case Qepid:
		rc = epidread(c->aux, p->ch, a, n, off);
		break;
	default:
		error(Egreg);
	}
out:
	qunlock(&dtracylock);
	poperror();
	return rc;
}

static long
dtracywrite(Chan *c, void *a, long n, vlong)
{
	int rc;
	DTKChan *p;
	Cmdbuf *cb;
	Cmdtab *ct;

	eqlock(&dtracylock);
	if(waserror()){
		qunlock(&dtracylock);
		nexterror();
	}
	if(SLOT(c->qid) == -1)
		switch((int)c->qid.path){
		case Qdir:
			error(Eperm);
		default:
			error(Egreg);
		}
	p = dtklook(SLOT(c->qid));
	if(p == nil) error(Enonexist);
	switch(FILE(c->qid)){
	case Qdir:
		error(Eperm);
	case Qctl:
		cb = parsecmd(a, n);
		if(waserror()){
			free(cb);
			nexterror();
		}
		ct = lookupcmd(cb, dtracyctlmsg, nelem(dtracyctlmsg));
		switch(ct->index){
		case CMstop: dtcrun(p->ch, DTCSTOP); break;
		case CMgo: dtcrun(p->ch, DTCGO); break;
		default:
			error(Egreg);
		}
		poperror();
		free(cb);
		rc = n;
		break;
	case Qprog:
		{
			char *buf;
			
			buf = smalloc(n+1);
			if(waserror()){
				free(buf);
				nexterror();
			}
			memmove(buf, a, n);
			prog(p, buf);
			free(buf);
			poperror();
			rc = n;
			break;
		}
	default:
		error(Egreg);
	}
	qunlock(&dtracylock);
	poperror();
	return rc;
}


Dev dtracydevtab = {
	L'Δ',
	"dtracy",
	
	devreset,
	dtracyinit,
	devshutdown,
	dtracyattach,
	dtracywalk,
	dtracystat,
	dtracyopen,
	devcreate,
	dtracyclose,
	dtracyread,
	devbread,
	dtracywrite,
	devbwrite,
	devremove,
	devwstat,
};

void *
dtmalloc(ulong n)
{
	void *v;

	v = smalloc(n);
	setmalloctag(v, getcallerpc(&n));
	return v;
}

void
dtfree(void *v)
{
	free(v);
}

void *
dtrealloc(void *v, ulong n)
{
	v = realloc(v, n);
	if(v != nil)
		setrealloctag(v, getcallerpc(&v));
	return v;
}

int
dtmachlock(int i)
{
	while(i < 0) {
		i = dtmachlock(m->machno);
		if(i == m->machno)
			return i;
		dtmachunlock(i);
		i = -1;
	}
	ilock(&machlocks[i]);
	return i;
}

void
dtmachunlock(int i)
{
	iunlock(&machlocks[i]);
}

void
dtcoherence(void)
{
	coherence();
}

uvlong
dttime(void)
{
	return fastticks(nil);
}

uvlong
dtgetvar(int v)
{
	switch(v){
	case DTV_PID:
		return up != nil ? up->pid : 0;
	default:
		return 0;
	}
}

extern int peek(char *, char *, int);

int
dtpeek(uvlong addr, void *buf, int len)
{
	uintptr a;
	
	a = addr;
	if(len == 0) return 0;
	if(a != addr || a > -(uintptr)len || len < 0) return -1;
	if(up == nil || up->privatemem || a >= KZERO) return -1;
	return peek((void *)a, buf, len) > 0 ? -1 : 0;
}
