/*
 * This file is part of Jehanne.
 *
 * Copyright (C) 2015-2016 Giacomo Tesio <giacomo@tesio.it>
 *
 * Jehanne is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 2 of the License.
 *
 * Jehanne 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 Jehanne.  If not, see <http://www.gnu.org/licenses/>.
 */
#include	"u.h"
#include	"../port/lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"../port/error.h"

#include	<authsrv.h>

enum
{
	Nconsdevs	= 64,		/* max number of consoles */

	/* Consdev flags */
	Ciprint		= 2,		/* call this fn from iprint */
	Cntorn		= 4,		/* change \n to \r\n */
};

typedef struct Consdev Consdev;

struct Consdev
{
	Chan*	c;			/* external file */
	Queue*	q;			/* i/o queue, if any */
	void	(*fn)(char*, int);	/* i/o function when no queue */
	int	flags;
};

void	(*consdebug)(void) = nil;
void	(*screenputs)(char*, int) = nil;

static void kmesgputs(char *, int);
static void kprintputs(char*, int);

static	Lock	consdevslock;
static	int	nconsdevs = 3;
static	Consdev	consdevs[Nconsdevs] =			/* keep this order */
{
	{nil, nil,	kmesgputs,	0},			/* kmesg */
	{nil, nil,	kprintputs,	Ciprint},		/* kprint */
	{nil, nil,	uartputs,	Ciprint|Cntorn},	/* serial */
};

static	int	nkbdqs;
static	int	nkbdprocs;
static	Queue*	kbdqs[Nconsdevs];
static	int	kbdprocs[Nconsdevs];
static	Queue*	kbdq;		/* unprocessed console input */
static	Queue*	lineq;		/* processed console input */
	Queue*	serialoq;	/* serial console output */
static	Queue*	kprintoq;	/* console output, for /dev/kprint */
static	uint32_t	kprintinuse;	/* test and set whether /dev/kprint is open */

int	panicking;

static struct
{
	QLock;

	int	raw;		/* true if we shouldn't process input */
	Ref	ctl;		/* number of opens to the control file */
	int	x;		/* index into line */
	char	line[1024];	/* current input line */

	int	count;
	int	ctlpoff;

	/*
	 * A place to save up characters at interrupt time
	 * before dumping them in the queue
	 */
	Lock	lockputc;
	char	istage[1024];
	char*	iw;
	char*	ir;
	char*	ie;
} kbd = {
	.iw	= kbd.istage,
	.ir	= kbd.istage,
	.ie	= kbd.istage + sizeof(kbd.istage),
};

char	*sysname;
int64_t	fasthz;

static void	seedrand(void);
static int	readtime(uint32_t, char*, int);
static int	readbintime(char*, int);
static int	writetime(char*, int);
static int	writebintime(char*, int);

enum
{
	CMhalt,
	CMreboot,
	CMpanic,
};

Cmdtab rebootmsg[] =
{
	CMhalt,		"halt",		1,
	CMreboot,	"reboot",	0,
	CMpanic,	"panic",	0,
};

/* To keep the rest of the kernel unware of new consdevs for now */
static void
kprintputs(char *s, int n)
{
	if(screenputs != nil)
		screenputs(s, n);
}

int
addconsdev(Queue *q, void (*fn)(char*,int), int i, int flags)
{
	Consdev *c;

	ilock(&consdevslock);
	if(i < 0)
		i = nconsdevs;
	else
		flags |= consdevs[i].flags;
	if(nconsdevs == Nconsdevs)
		panic("Nconsdevs too small");
	c = &consdevs[i];
	c->flags = flags;
	c->q = q;
	c->fn = fn;
	if(i == nconsdevs)
		nconsdevs++;
	iunlock(&consdevslock);
	return i;
}

void
delconsdevs(void)
{
	nconsdevs = 2;	/* throw away serial consoles and kprint */
	consdevs[1].q = nil;
}

static void
conskbdqproc(void *a)
{
	char buf[64];
	Queue *q;
	int nr;

	q = a;
	while((nr = qread(q, buf, sizeof(buf))) > 0)
		qwrite(kbdq, buf, nr);
	pexit("hangup", 1);
}

static void
kickkbdq(void)
{
	int i;

	if(up != nil && nkbdqs > 1 && nkbdprocs != nkbdqs){
		lock(&consdevslock);
		if(nkbdprocs == nkbdqs){
			unlock(&consdevslock);
			return;
		}
		for(i = 0; i < nkbdqs; i++)
			if(kbdprocs[i] == 0){
				kbdprocs[i] = 1;
				kproc("conskbdq", conskbdqproc, kbdqs[i]);
			}
		unlock(&consdevslock);
	}
}

int
addkbdq(Queue *q, int i)
{
	int n;

	ilock(&consdevslock);
	if(i < 0)
		i = nkbdqs++;
	if(nkbdqs == Nconsdevs)
		panic("Nconsdevs too small");
	kbdqs[i] = q;
	n = nkbdqs;
	iunlock(&consdevslock);
	switch(n){
	case 1:
		/* if there's just one, pull directly from it. */
		kbdq = q;
		break;
	case 2:
		/* later we'll merge bytes from all kbdqs into a single kbdq */
		kbdq = qopen(4*1024, 0, 0, 0);
		if(kbdq == nil)
			panic("no kbdq");
		/* fall */
	default:
		kickkbdq();
	}
	return i;
}

void
printinit(void)
{
	lineq = qopen(2*1024, 0, nil, nil);
	if(lineq == nil)
		panic("printinit");
	qnoblock(lineq, 1);
}

int
consactive(void)
{
	int i;
	Queue *q;

	for(i = 0; i < nconsdevs; i++)
		if((q = consdevs[i].q) != nil && qlen(q) > 0)
			return 1;
	return 0;
}

void
prflush(void)
{
	unsigned long now;

	now = sys->ticks;
	while(consactive())
		if(sys->ticks - now >= 30*HZ)
			break;
}

/*
 * Log console output so it can be retrieved via /dev/kmesg.
 * This is good for catching boot-time messages after the fact.
 */
struct {
	Lock lk;
	char buf[16384];
	uint32_t n;
} kmesg;

static void
kmesgputs(char *str, int n)
{
	uint32_t nn, d;

	ilock(&kmesg.lk);
	/* take the tail of huge writes */
	if(n > sizeof kmesg.buf){
		d = n - sizeof kmesg.buf;
		str += d;
		n -= d;
	}

	/* slide the buffer down to make room */
	nn = kmesg.n;
	if(nn + n >= sizeof kmesg.buf){
		d = nn + n - sizeof kmesg.buf;
		if(d)
			jehanne_memmove(kmesg.buf, kmesg.buf+d, sizeof kmesg.buf-d);
		nn -= d;
	}

	/* copy the data in */
	jehanne_memmove(kmesg.buf+nn, str, n);
	nn += n;
	kmesg.n = nn;
	iunlock(&kmesg.lk);
}

static void
consdevputs(Consdev *c, char *s, int n, int usewrite)
{
	Chan *cc;
	Queue *q;

	if((cc = c->c) != nil && usewrite)
		cc->dev->write(cc, s, n, 0);
	else if((q = c->q) != nil && !qisclosed(q))
		if(usewrite)
			qwrite(q, s, n);
		else
			qiwrite(q, s, n);
	else if(c->fn != nil)
		c->fn(s, n);
}

/*
 *   Print a string on the console.  Convert \n to \r\n for serial
 *   line consoles.  Locking of the queues is left up to the screen
 *   or uart code.  Multi-line messages to serial consoles may get
 *   interspersed with other messages.
 */
static void
putstrn0(char *str, int n, int usewrite)
{
	Consdev *c;
	char *s, *t;
	int i, len, m;

	if(!islo())
		usewrite = 0;

	for(i = 0; i < nconsdevs; i++){
		c = &consdevs[i];
		len = n;
		s = str;
		while(len > 0){
			t = nil;
			if((c->flags&Cntorn) && !kbd.raw)
				t = jehanne_memchr(s, '\n', len);
			if(t != nil && !kbd.raw){
				m = t-s;
				consdevputs(c, s, m, usewrite);
				consdevputs(c, "\r\n", 2, usewrite);
				len -= m+1;
				s = t+1;
			}else{
				consdevputs(c, s, len, usewrite);
				break;
			}
		}
	}
}

void
putstrn(char *str, int n)
{
	putstrn0(str, n, 0);
}

int
jehanne_print(char *fmt, ...)
{
	int n;
	va_list arg;
	char buf[PRINTSIZE];

	va_start(arg, fmt);
	n = jehanne_vseprint(buf, buf+sizeof(buf), fmt, arg) - buf;
	va_end(arg);
	putstrn(buf, n);

	return n;
}

/*
 * Want to interlock iprints to avoid interlaced output on
 * multiprocessor, but don't want to deadlock if one processor
 * dies during print and another has something important to say.
 * Make a good faith effort.
 */
static Lock iprintlock;

static int
iprintcanlock(Lock *l)
{
	int i;

	for(i=0; i<1000; i++){
		if(canlock(l))
			return 1;
		if(ownlock(l))
			return 0;
		microdelay(100);
	}
	return 0;
}

int
iprint(char *fmt, ...)
{
	Mreg s;
	int i, n, locked; //, mlocked;
	va_list arg;
	char buf[PRINTSIZE];

	s = splhi();
	va_start(arg, fmt);
	n = jehanne_vseprint(buf, buf+sizeof(buf), fmt, arg) - buf;
	va_end(arg);
//	mlocked = malloclocked();
	locked = iprintcanlock(&iprintlock);
	for(i = 0; i < nconsdevs; i++){
		if((consdevs[i].flags&Ciprint) != 0){
//			if(consdevs[i].q != nil && !mlocked)
//				qiwrite(consdevs[i].q, buf, n);
//			else
				consdevs[i].fn(buf, n);
		}
	}
	if(locked)
		unlock(&iprintlock);
	splx(s);

	return n;
}

#pragma profile 0
void
panic(char *fmt, ...)
{
	int n;
	va_list arg;
	char buf[PRINTSIZE];

	if(panicking)
		exit(1);

	panicking = 1;

//	if(malloclocked())
//		consdevs[2].q = nil;	/* force direct uart output */
	consdevs[1].q = nil;	/* don't try to write to /dev/kprint */

	splhi();
	jehanne_strcpy(buf, "panic: ");
	va_start(arg, fmt);
	n = jehanne_vseprint(buf+jehanne_strlen(buf), buf+sizeof(buf), fmt, arg) - buf;
	va_end(arg);
	iprint("%s\n", buf);
	if(consdebug)
		(*consdebug)();
	prflush();
//	splx(s);
	buf[n] = '\n';
	putstrn(buf, n+1);
//	dumpstack();
//	prflush();
//	dump();

	exit(1);
}

#pragma profile 1
/* libmp at least contains a few calls to sysfatal; simulate with panic */
void
jehanne_sysfatal(char *fmt, ...)
{
	char err[256];
	va_list arg;

	va_start(arg, fmt);
	jehanne_vseprint(err, err + sizeof err, fmt, arg);
	va_end(arg);
	panic("sysfatal: %s", err);
}

void
jehanne__assert(const char *fmt)
{
	panic("assert failed at %#p: %s", getcallerpc(), fmt);
}

int
pprint(char *fmt, ...)
{
	int n;
	Chan *c;
	va_list arg;
	char buf[2*PRINTSIZE];

	if(up == nil || up->fgrp == nil)
		return 0;

	c = up->fgrp->fd[2];
	if(c==0 || (c->mode!=OWRITE && c->mode!=ORDWR))
		return 0;
	n = jehanne_snprint(buf, sizeof buf, "%s %d: ", up->text, up->pid);
	va_start(arg, fmt);
	n = jehanne_vseprint(buf+n, buf+sizeof(buf), fmt, arg) - buf;
	va_end(arg);

	if(waserror())
		return 0;
	c->dev->write(c, buf, n, c->offset);
	poperror();

	lock(&c->l);
	c->offset += n;
	unlock(&c->l);

	return n;
}

static void
echo(char *buf, int n)
{
	Mreg s;
	static int ctrlt;
	char *e, *p;

	if(n == 0)
		return;

	e = buf+n;
	for(p = buf; p < e; p++){
		switch(*p){
		case 0x10:	/* ^P */
			if(cpuserver && !kbd.ctlpoff){
				active.exiting = 1;
				return;
			}
			break;
		case 0x14:	/* ^T */
			ctrlt++;
			if(ctrlt > 2)
				ctrlt = 2;
			continue;
		}

		if(ctrlt != 2)
			continue;

		/* ^T escapes */
		ctrlt = 0;
		switch(*p){
		case 'S':
			s = splhi();
			dumpstack();
			procdump();
			splx(s);
			return;
		case 's':
			dumpstack();
			return;
		case 'x':
			mallocsummary();
			return;
		case 'd':
			if(consdebug == nil)
				consdebug = rdb;
			else
				consdebug = nil;
			jehanne_print("consdebug now %#p\n", consdebug);
			return;
		case 'D':
			if(consdebug == nil)
				consdebug = rdb;
			consdebug();
			return;
		case 'p':
			s = spllo();
			procdump();
			splx(s);
			return;
		case 'q':
			scheddump();
			return;
		case 'k':
			killbig("^t ^t k");
			return;
		case 'r':
			exit(0);
			return;
		}
	}

	if(kbdq != nil)
		qproduce(kbdq, buf, n);
}

/*
 *  Called by a uart interrupt for console input.
 *
 *  turn '\r' into '\n' before putting it into the queue.
 */
int
kbdcr2nl(Queue* _1, int ch)
{
	char *next;

	ilock(&kbd.lockputc);		/* just a mutex */
	if(ch == '\r' && !kbd.raw)
		ch = '\n';
	next = kbd.iw+1;
	if(next >= kbd.ie)
		next = kbd.istage;
	if(next != kbd.ir){
		*kbd.iw = ch;
		kbd.iw = next;
	}
	iunlock(&kbd.lockputc);
	return 0;
}

/*
 *  Put character, possibly a rune, into read queue at interrupt time.
 *  Called at interrupt time to process a character.
 */
int
kbdputc(Queue* _1, int ch)
{
	int i, n;
	char buf[UTFmax];
	Rune r;
	char *next;

	if(kbd.ir == nil)
		return 0;		/* in case we're not inited yet */

	ilock(&kbd.lockputc);		/* just a mutex */
	r = ch;
	n = jehanne_runetochar(buf, &r);
	for(i = 0; i < n; i++){
		next = kbd.iw+1;
		if(next >= kbd.ie)
			next = kbd.istage;
		if(next == kbd.ir)
			break;
		*kbd.iw = buf[i];
		kbd.iw = next;
	}
	iunlock(&kbd.lockputc);
	return 0;
}

/*
 *  we save up input characters till clock time to reduce
 *  per character interrupt overhead.
 */
static void
kbdputcclock(void)
{
	char *iw;

	/* this amortizes cost of qproduce */
	if(kbd.iw != kbd.ir){
		iw = kbd.iw;
		if(iw < kbd.ir){
			echo(kbd.ir, kbd.ie-kbd.ir);
			kbd.ir = kbd.istage;
		}
		if(kbd.ir != iw){
			echo(kbd.ir, iw-kbd.ir);
			kbd.ir = iw;
		}
	}
}

enum{
	Qdir,
	Qbintime,
//	Qconfig,
	Qcons,
	Qconsctl,
	Qcputime,
	Qdrivers,
	Qkmesg,
	Qkprint,
	Qhostdomain,
	Qhostowner,
	Qosversion,
	Qrandom,
	Qreboot,
	Qswap,
	Qsysname,
	Qsysstat,
	Qtime,
	Quser,
	Qusers,
};

enum
{
	VLNUMSIZE=	22,
};

static Dirtab consdir[]={
	".",	{Qdir, 0, QTDIR},	0,		DMDIR|0555,
	"bintime",	{Qbintime},	24,		0664,
//	"config",	{Qconfig},	0,		0444,
	"cons",		{Qcons},	0,		0660,
	"consctl",	{Qconsctl},	0,		0220,
	"cputime",	{Qcputime},	6*NUMSIZE,	0444,
	"drivers",	{Qdrivers},	0,		0444,
	"hostdomain",	{Qhostdomain},	DOMLEN,		0664,
	"hostowner",	{Qhostowner},	0,		0664,
	"kmesg",	{Qkmesg},	0,		0440,
	"kprint",	{Qkprint, 0, QTEXCL},	0,	DMEXCL|0440,
	"osversion",	{Qosversion},	0,		0444,
	"random",	{Qrandom},	0,		0444,
	"reboot",	{Qreboot},	0,		0664,
	"swap",		{Qswap},	0,		0664,
	"sysname",	{Qsysname},	0,		0664,
	"sysstat",	{Qsysstat},	0,		0666,
	"time",		{Qtime},	NUMSIZE+3*VLNUMSIZE,	0664,
	"user",		{Quser},	0,		0666,
	"users",		{Qusers},	0,		0644,
};

int
readnum(uint32_t off, char *buf, uint32_t n, uint32_t val, int size)
{
	char tmp[64];

	jehanne_snprint(tmp, sizeof(tmp), "%*lud", size-1, val);
	tmp[size-1] = ' ';
	if(off >= size)
		return 0;
	if(off+n > size)
		n = size-off;
	jehanne_memmove(buf, tmp+off, n);
	return n;
}

long
readstr(long offset, char *buf, long n, char *str)
{
	long size;

	size = jehanne_strlen(str);
	if(offset >= size)
		return 0;
	if(offset+n > size)
		n = size-offset;
	jehanne_memmove(buf, str+offset, n);
	return n;
}

static void
consinit(void)
{
	todinit();
	randominit();
	/*
	 * at 115200 baud, the 1024 char buffer takes 56 ms to process,
	 * processing it every 22 ms should be fine
	 */
	addclock0link(kbdputcclock, 22);
	kickkbdq();
}

static Chan*
consattach(Chan *c, Chan *ac, char *spec, int flags)
{
	return devattach('c', spec);
}

static Walkqid*
conswalk(Chan *c, Chan *nc, char **name, int nname)
{
	return devwalk(c, nc, name,nname, consdir, nelem(consdir), devgen);
}

static long
consstat(Chan *c, uint8_t *dp, long n)
{
	return devstat(c, dp, n, consdir, nelem(consdir), devgen);
}

static Chan*
consopen(Chan *c, unsigned long omode)
{
	c->aux = nil;
	c = devopen(c, omode, consdir, nelem(consdir), devgen);
	switch((uint32_t)c->qid.path){
	case Qconsctl:
		incref(&kbd.ctl);
		break;

	case Qkprint:
		if(TAS(&kprintinuse) != 0){
			c->flag &= ~COPEN;
			error(Einuse);
		}
		if(kprintoq == nil){
			kprintoq = qopen(8*1024, Qcoalesce, 0, 0);
			if(kprintoq == nil){
				c->flag &= ~COPEN;
				error(Enomem);
			}
			qnoblock(kprintoq, 1);
			consdevs[1].q = kprintoq;
		}else
			qreopen(kprintoq);
		c->iounit = qiomaxatomic;
		break;
	}
	return c;
}

static void
consclose(Chan *c)
{
	switch((uint32_t)c->qid.path){
	/* last close of control file turns off raw */
	case Qconsctl:
		if(c->flag&COPEN){
			if(decref(&kbd.ctl) == 0)
				kbd.raw = 0;
		}
		break;

	/* close of kprint allows other opens */
	case Qkprint:
		if(c->flag & COPEN){
			kprintinuse = 0;
			qhangup(kprintoq, nil);
		}
		break;
	}
}

static long
consread(Chan *c, void *buf, long n, int64_t off)
{
	uint64_t l;
	Mach *mp;
	MemoryStats mstats;
	char *b, *bp, ch;
	char tmp[6*NUMSIZE+1];		/* must be >= 6*NUMSIZE (Qcputime) */
	int i, id, send;
	long offset;
//	extern char configfile[];

	if(n <= 0)
		return n;

	offset = off;
	switch((uint32_t)c->qid.path){
	case Qdir:
		return devdirread(c, buf, n, consdir, nelem(consdir), devgen);

	case Qcons:
		qlock(&kbd);
		if(waserror()) {
			qunlock(&kbd);
			nexterror();
		}
		while(!qcanread(lineq)){
			if(qread(kbdq, &ch, 1) == 0)
				continue;
			send = 0;
			if(ch == 0){
				/* flush output on rawoff -> rawon */
				if(kbd.x > 0)
					send = !qcanread(kbdq);
			}else if(kbd.raw){
				kbd.line[kbd.x++] = ch;
				send = !qcanread(kbdq);
			}else{
				switch(ch){
				case '\b':
					i = kbd.x;
					b = kbd.line;
					if(i > 0){
						i--;
						while(i > 0 && (b[i-1]&0xc0) == 0x80)
							i--;
						kbd.x = i;
					}
					break;
				case 0x15:	/* ^U */
					kbd.x = 0;
					jehanne_print("\b^U\n");
					break;
				case '\n':
				case 0x04:	/* ^D */
					send = 1;
				default:
					if(ch != 0x04)
						kbd.line[kbd.x++] = ch;
					break;
				}
			}
			if(send || kbd.x == sizeof kbd.line){
				qwrite(lineq, kbd.line, kbd.x);
				kbd.x = 0;
			}
		}
		n = qread(lineq, buf, n);
		qunlock(&kbd);
		poperror();
		return n;

	case Qcputime:
		/* easiest to format in a separate buffer and copy out */
		for(i=0; i<6; i++){
			l = up->time[i];
			if(i == TReal)
				l = sys->ticks - l;
			l = TK2MS(l);
			readnum(0, tmp+NUMSIZE*i, NUMSIZE, l, NUMSIZE);
		}
		tmp[sizeof(tmp)-1] = 0;
		return readstr(offset, buf, n, tmp);

	case Qkmesg:
		/*
		 * This is unlocked to avoid tying up a process
		 * that's writing to the buffer.  kmesg.n never
		 * gets smaller, so worst case the reader will
		 * see a slurred buffer.
		 */
		if(off >= kmesg.n)
			n = 0;
		else{
			if(off+n > kmesg.n)
				n = kmesg.n - off;
			jehanne_memmove(buf, kmesg.buf+off, n);
		}
		return n;

	case Qkprint:
		return qread(kprintoq, buf, n);

	case Qtime:
		return readtime(offset, buf, n);

	case Qbintime:
		return readbintime(buf, n);

	case Qhostowner:
		return readstr(offset, buf, n, eve);

	case Qhostdomain:
		return readstr(offset, buf, n, hostdomain);

	case Quser:
		return readstr(offset, buf, n, up->user);

	case Qusers:
		b = usersread();
		if(!waserror()){
			n = readstr(offset, buf, n, b);
			poperror();
		}
		jehanne_free(b);
		return n;

//	case Qconfig:
//		return readstr((uint32_t)offset, buf, n, configfile);

	case Qsysstat:
		b = smalloc(MACHMAX*(NUMSIZE*11+1) + 1);	/* +1 for NUL */
		bp = b;
		for(id = 0; id < MACHMAX; id++){
			if((mp = sys->machptr[id]) == nil || !mp->online)
				continue;
			readnum(0, bp, NUMSIZE, id, NUMSIZE);
			bp += NUMSIZE;
			readnum(0, bp, NUMSIZE, mp->cs, NUMSIZE);
			bp += NUMSIZE;
			readnum(0, bp, NUMSIZE, mp->intr, NUMSIZE);
			bp += NUMSIZE;
			readnum(0, bp, NUMSIZE, mp->syscall, NUMSIZE);
			bp += NUMSIZE;
			readnum(0, bp, NUMSIZE, mp->pfault, NUMSIZE);
			bp += NUMSIZE;
			readnum(0, bp, NUMSIZE, mp->tlbfault, NUMSIZE);
			bp += NUMSIZE;
			readnum(0, bp, NUMSIZE, mp->tlbpurge, NUMSIZE);
			bp += NUMSIZE;
			readnum(0, bp, NUMSIZE, mp->load, NUMSIZE);
			bp += NUMSIZE;
			readnum(0, bp, NUMSIZE,
				(mp->perf.avg_inidle*100)/mp->perf.period,
				NUMSIZE);
			bp += NUMSIZE;
			readnum(0, bp, NUMSIZE,
				(mp->perf.avg_inintr*100)/mp->perf.period,
				NUMSIZE);
			bp += NUMSIZE;
			*bp++ = '\n';
		}
		if(waserror()){
			jehanne_free(b);
			nexterror();
		}
		n = readstr(offset, buf, n, b);
		jehanne_free(b);
		poperror();
		return n;

	case Qswap:
		memory_stats(&mstats);
		jehanne_snprint(tmp, sizeof tmp,
			"%llud memory\n"
			"%llud pagesize\n"
			"%lud kernel\n"
			"%lud/%lud user\n",
			mstats.memory,
			(unsigned long)PGSZ,
			mstats.kernel,
			mstats.user, mstats.user_available);

		return readstr(offset, buf, n, tmp);

	case Qsysname:
		if(sysname == nil)
			return 0;
		return readstr(offset, buf, n, sysname);

	case Qrandom:
		return randomread(buf, n);

	case Qdrivers:
		return devtabread(c, buf, n, off);

	case Qosversion:
		return readstr(offset, buf, n, "2000");

	default:
		jehanne_print("consread %#llux\n", c->qid.path);
		error(Egreg);
	}
	return -1;		/* never reached */
}

static void
consremove(Chan* c)
{
	switch((uint32_t)c->qid.path){
	default:
		error(Eperm);
	case Qtime:
		errorl("fugit irreparabile tempus", todget(nil));
	}
}

static long
conswrite(Chan *c, void *va, long n, int64_t off)
{
	char buf[256], ch;
	long l, bp;
	char *a;
	Mach *mp;
	int i;
	uint32_t offset;
	Cmdbuf *cb;
	Cmdtab *ct;

	a = va;
	offset = off;

	switch((uint32_t)c->qid.path){
	case Qcons:
		/*
		 * Can't page fault in putstrn, so copy the data locally.
		 */
		l = n;
		while(l > 0){
			bp = l;
			if(bp > sizeof buf)
				bp = sizeof buf;
			jehanne_memmove(buf, a, bp);
			putstrn0(buf, bp, 1);
			a += bp;
			l -= bp;
		}
		break;

	case Qconsctl:
		if(n >= sizeof(buf))
			n = sizeof(buf)-1;
		jehanne_strncpy(buf, a, n);
		buf[n] = 0;
		for(a = buf; a;){
			if(jehanne_strncmp(a, "rawon", 5) == 0){
				kbd.raw = 1;
				/* clumsy hack - wake up reader */
				ch = 0;
				qwrite(kbdq, &ch, 1);
			}
			else if(jehanne_strncmp(a, "rawoff", 6) == 0)
				kbd.raw = 0;
			else if(jehanne_strncmp(a, "ctlpon", 6) == 0)
				kbd.ctlpoff = 0;
			else if(jehanne_strncmp(a, "ctlpoff", 7) == 0)
				kbd.ctlpoff = 1;
			if(a = jehanne_strchr(a, ' '))
				a++;
		}
		break;

	case Qtime:
		if(!iseve())
			error(Eperm);
		return writetime(a, n);

	case Qbintime:
		if(!iseve())
			error(Eperm);
		return writebintime(a, n);

	case Qhostowner:
		return hostownerwrite(a, n);

	case Qhostdomain:
		return hostdomainwrite(a, n);

	case Quser:
		return userwrite(a, n);

	case Qusers:
		return userswrite(a, n);

//	case Qconfig:
//		error(Eperm);
//		break;

	case Qreboot:
		if(!iseve())
			error(Eperm);
		cb = parsecmd(a, n);

		if(waserror()) {
			jehanne_free(cb);
			nexterror();
		}
		ct = lookupcmd(cb, rebootmsg, nelem(rebootmsg));
		switch(ct->index) {
		case CMhalt:
			reboot(nil, 0, 0);
			break;
		case CMreboot:
			rebootcmd(cb->nf-1, cb->f+1);
			break;
		case CMpanic:
			*(uint32_t*)0=0;
			panic("/dev/reboot");
		}
		poperror();
		jehanne_free(cb);
		break;

	case Qsysstat:
		for(i = 0; i < MACHMAX; i++){
			if((mp = sys->machptr[i]) == nil || !mp->online)
				continue;
			mp->cs = 0;
			mp->intr = 0;
			mp->syscall = 0;
			mp->pfault = 0;
			mp->tlbfault = 0;
			mp->tlbpurge = 0;
		}
		break;

	case Qswap:
		/* no more */
		break;

	case Qsysname:
		if(offset != 0)
			error(Ebadarg);
		if(n <= 0 || n >= sizeof buf)
			error(Ebadarg);
		jehanne_strncpy(buf, a, n);
		buf[n] = 0;
		if(buf[n-1] == '\n')
			buf[n-1] = 0;
		kstrdup(&sysname, buf);
		break;

	default:
		jehanne_print("conswrite: %#llux\n", c->qid.path);
		error(Egreg);
	}
	return n;
}

Dev consdevtab = {
	'c',
	"cons",

	devreset,
	consinit,
	devshutdown,
	consattach,
	conswalk,
	consstat,
	consopen,
	devcreate,
	consclose,
	consread,
	devbread,
	conswrite,
	devbwrite,
	consremove,
	devwstat,
};

static	uint32_t	randn;

static void
seedrand(void)
{
	if(!waserror()){
		randomread((void*)&randn, sizeof(randn));
		poperror();
	}
}

int
nrand(int n)
{
	if(randn == 0)
		seedrand();
	randn = randn*1103515245 + 12345 + sys->ticks;
	return (randn>>16) % n;
}

int
rand(void)
{
	nrand(1);
	return randn;
}

static uint64_t uvorder = 0x0001020304050607ULL;

static uint8_t*
le2vlong(int64_t *to, uint8_t *f)
{
	uint8_t *t, *o;
	int i;

	t = (uint8_t*)to;
	o = (uint8_t*)&uvorder;
	for(i = 0; i < sizeof(int64_t); i++)
		t[o[i]] = f[i];
	return f+sizeof(int64_t);
}

static uint8_t*
vlong2le(uint8_t *t, int64_t from)
{
	uint8_t *f, *o;
	int i;

	f = (uint8_t*)&from;
	o = (uint8_t*)&uvorder;
	for(i = 0; i < sizeof(int64_t); i++)
		t[i] = f[o[i]];
	return t+sizeof(int64_t);
}

static long order = 0x00010203;

static uint8_t*
le2long(long *to, uint8_t *f)
{
	uint8_t *t, *o;
	int i;

	t = (uint8_t*)to;
	o = (uint8_t*)&order;
	for(i = 0; i < sizeof(long); i++)
		t[o[i]] = f[i];
	return f+sizeof(long);
}

#if 0
static uint8_t*
long2le(uint8_t *t, long from)
{
	uint8_t *f, *o;
	int i;

	f = (uint8_t*)&from;
	o = (uint8_t*)&order;
	for(i = 0; i < sizeof(long); i++)
		t[i] = f[o[i]];
	return t+sizeof(long);
}
#endif

char *Ebadtimectl = "bad time control";

/*
 *  like the old #c/time but with added info.  Return
 *
 *	secs	nanosecs	fastticks	fasthz
 */
static int
readtime(uint32_t off, char *buf, int n)
{
	int64_t nsec, ticks;
	long sec;
	char str[7*NUMSIZE];

	nsec = todget(&ticks);
	if(fasthz == 0LL)
		fastticks((uint64_t*)&fasthz);
	sec = nsec/1000000000ULL;
	jehanne_snprint(str, sizeof(str), "%*lud %*llud %*llud %*llud ",
		NUMSIZE-1, sec,
		VLNUMSIZE-1, nsec,
		VLNUMSIZE-1, ticks,
		VLNUMSIZE-1, fasthz);
	return readstr(off, buf, n, str);
}

/*
 *  set the time in seconds
 */
static int
writetime(char *buf, int n)
{
	char b[13];
	long i;
	int64_t now;

	if(n >= sizeof(b))
		error(Ebadtimectl);
	jehanne_strncpy(b, buf, n);
	b[n] = 0;
	i = jehanne_strtol(b, 0, 0);
	if(i <= 0)
		error(Ebadtimectl);
	now = i*1000000000LL;
	todset(now, 0, 0);
	return n;
}

/*
 *  read binary time info.  all numbers are little endian.
 *  ticks and nsec are syncronized.
 */
static int
readbintime(char *buf, int n)
{
	int i;
	int64_t nsec, ticks;
	uint8_t *b = (uint8_t*)buf;

	i = 0;
	if(fasthz == 0LL)
		fastticks((uint64_t*)&fasthz);
	nsec = todget(&ticks);
	if(n >= 3*sizeof(uint64_t)){
		vlong2le(b+2*sizeof(uint64_t), fasthz);
		i += sizeof(uint64_t);
	}
	if(n >= 2*sizeof(uint64_t)){
		vlong2le(b+sizeof(uint64_t), ticks);
		i += sizeof(uint64_t);
	}
	if(n >= 8){
		vlong2le(b, nsec);
		i += sizeof(int64_t);
	}
	return i;
}

/*
 *  set any of the following
 *	- time in nsec
 *	- nsec trim applied over some seconds
 *	- clock frequency
 */
static int
writebintime(char *buf, int n)
{
	uint8_t *p;
	int64_t delta;
	long period;

	n--;
	p = (uint8_t*)buf + 1;
	switch(*buf){
	case 'n':
		if(n < sizeof(int64_t))
			error(Ebadtimectl);
		le2vlong(&delta, p);
		todset(delta, 0, 0);
		break;
	case 'd':
		if(n < sizeof(int64_t)+sizeof(long))
			error(Ebadtimectl);
		p = le2vlong(&delta, p);
		le2long(&period, p);
		todset(-1, delta, period);
		break;
	case 'f':
		if(n < sizeof(uint64_t))
			error(Ebadtimectl);
		le2vlong(&fasthz, p);
		todsetfreq(fasthz);
		break;
	}
	return n;
}
