/* cached-worm file server */
#include "all.h"
#include "io.h"
#include "9p1.h"

extern int oldcachefmt;

Map *devmap;

Biobuf bin;

void
machinit(void)
{
	active.exiting = 0;
}

/*
 * Put a string on the console.
 */
void
puts(char *s, int n)
{
	print("%.*s", n, s);
}

void
prflush(void)
{
}

/*
 * Print a string on the console.
 */
void
putstrn(char *str, int n)
{
	puts(str, n);
}

/*
 * get a character from the console
 */
int
getc(void)
{
	return Bgetrune(&bin);
}

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

	va_start(arg, fmt);
	n = vseprint(buf, buf + sizeof buf, fmt, arg) - buf;
	va_end(arg);
	buf[n] = '\0';
	print("panic: %s\n", buf);
	exit();
}

int
okay(char *quest)
{
	char *ln;

	print("okay to %s? ", quest);
	if ((ln = Brdline(&bin, '\n')) == nil)
		return 0;
	ln[Blinelen(&bin)-1] = '\0';
	if (isascii(*ln) && isupper(*ln))
		*ln = tolower(*ln);
	return *ln == 'y';
}

static void
mapinit(char *mapfile)
{
	int nf;
	char *ln;
	char *fields[2];
	Biobuf *bp;
	Map *map;

	if (mapfile == nil)
		return;
	bp = Bopen(mapfile, OREAD);
	if (bp == nil)
		sysfatal("can't read %s", mapfile);
	devmap = nil;
	while ((ln = Brdline(bp, '\n')) != nil) {
		ln[Blinelen(bp)-1] = '\0';
		if (*ln == '\0' || *ln == '#')
			continue;
		nf = tokenize(ln, fields, nelem(fields));
		if (nf != 2)
			continue;
		if(testconfig(fields[0]) != 0) {
			print("bad `from' device %s in %s\n",
				fields[0], mapfile);
			continue;
		}
		map = malloc(sizeof *map);
		map->from = strdup(fields[0]);
		map->to =   strdup(fields[1]);
		map->fdev = iconfig(fields[0]);
		map->tdev = nil;
		if (access(map->to, AEXIST) < 0) {
			/*
			 * map->to isn't an existing file, so it had better be
			 * a config string for a device.
			 */
			if(testconfig(fields[1]) == 0)
				map->tdev = iconfig(fields[1]);
		}
		/* else map->to is the replacement file name */
		map->next = devmap;
		devmap = map;
	}
	Bterm(bp);
}

static void
confinit(void)
{
	conf.nmach = 1;

	conf.mem = meminit();

	conf.nuid = 1000;
	conf.nserve = 15;		/* tunable */
	conf.nfile = 30000;
	conf.nlgmsg = 100;
	conf.nsmmsg = 500;

	localconfinit();

	conf.nwpath = conf.nfile*8;
	conf.nauth =  conf.nfile/10;
	conf.gidspace = conf.nuid*3;

	cons.flags = 0;

	if (conf.devmap)
		mapinit(conf.devmap);
}

/*
 * compute BUFSIZE*(NDBLOCK+INDPERBUF+INDPERBUF²+INDPERBUF³+INDPERBUF⁴)
 * while watching for overflow; in that case, return 0.
 */

static uvlong
adduvlongov(uvlong a, uvlong b)
{
	uvlong r = a + b;

	if (r < a || r < b)
		return 0;
	return r;
}

static uvlong
muluvlongov(uvlong a, uvlong b)
{
	uvlong r = a * b;

	if (a != 0 && r/a != b || r < a || r < b)
		return 0;
	return r;
}

static uvlong
maxsize(void)
{
	int i;
	uvlong max = NDBLOCK, ind = 1;

	for (i = 0; i < NIBLOCK; i++) {
		ind = muluvlongov(ind, INDPERBUF);	/* power of INDPERBUF */
		if (ind == 0)
			return 0;
		max = adduvlongov(max, ind);
		if (max == 0)
			return 0;
	}
	return muluvlongov(max, BUFSIZE);
}

enum {
	INDPERBUF² = ((uvlong)INDPERBUF *INDPERBUF),
	INDPERBUF⁴ = ((uvlong)INDPERBUF²*INDPERBUF²),
};

static void
printsizes(void)
{
	uvlong max = maxsize();

	print("\tblock size = %d; ", RBUFSIZE);
	if (max == 0)
		print("max file size exceeds 2⁶⁴ bytes\n");
	else {
		uvlong offlim = 1ULL << (sizeof(Off)*8 - 1);

		if (max >= offlim)
			max = offlim - 1;
		print("max file size = %,llud\n", (Wideoff)max);
	}
	if (INDPERBUF²/INDPERBUF != INDPERBUF)
		print("overflow computing INDPERBUF²\n");
	if (INDPERBUF⁴/INDPERBUF² != INDPERBUF²)
		print("overflow computing INDPERBUF⁴\n");
	print("\tINDPERBUF = %d, INDPERBUF^4 = %,lld, ", INDPERBUF,
		(Wideoff)INDPERBUF⁴);
	print("CEPERBK = %d\n", CEPERBK);
	print("\tsizeofs: Dentry = %d, Cache = %d\n",
		sizeof(Dentry), sizeof(Cache));
}

void
usage(void)
{
	fprint(2, "usage: %s [-cf][-a ann-str][-m dev-map] config-dev\n",
		argv0);
	exits("usage");
}

void
main(int argc, char **argv)
{
	int i, nets = 0;
	char *ann;

	rfork(RFNOTEG);
	formatinit();
	machinit();
	conf.confdev = "n";		/* Devnone */

	ARGBEGIN{
	case 'a':			/* announce on this net */
		ann = EARGF(usage());
		if (nets >= Maxnets) {
			fprint(2, "%s: too many networks to announce: %s\n",
				argv0, ann);
			exits("too many nets");
		}
		annstrs[nets++] = ann;
		break;
	case 'c':			/* use new, faster cache layout */
		oldcachefmt = 0;
		break;
	case 'f':			/* enter configuration mode first */
		conf.configfirst++;
		break;
	case 'm':			/* name device-map file */
		conf.devmap = EARGF(usage());
		break;
	default:
		usage();
		break;
	}ARGEND

	if (argc != 1)
		usage();
	conf.confdev = argv[0];	/* config string for dev holding full config */

	Binit(&bin, 0, OREAD);
	confinit();

	print("\nPlan 9 %d-bit cached-worm file server with %d-deep indir blks\n",
		sizeof(Off)*8 - 1, NIBLOCK);
	printsizes();

	qlock(&reflock);
	qunlock(&reflock);
	serveq = newqueue(1000, "9P service");	/* tunable */
	raheadq = newqueue(1000, "readahead");	/* tunable */

	mbinit();
	netinit();
	scsiinit();

	files = malloc(conf.nfile * sizeof *files);
	for(i=0; i < conf.nfile; i++) {
		qlock(&files[i]);
		qunlock(&files[i]);
	}

	wpaths = malloc(conf.nwpath * sizeof(*wpaths));
	uid = malloc(conf.nuid * sizeof(*uid));
	gidspace = malloc(conf.gidspace * sizeof(*gidspace));
	authinit();

	print("iobufinit\n");
	iobufinit();

	arginit();
	boottime = time(nil);

	print("sysinit\n");
	sysinit();

	/*
	 * Ethernet i/o processes
	 */
	netstart();

	/*
	 * read ahead processes
	 */
	newproc(rahead, 0, "rah");

	/*
	 * server processes
	 */
	for(i=0; i < conf.nserve; i++)
		newproc(serve, 0, "srv");

	/*
	 * worm "dump" copy process
	 */
	newproc(wormcopy, 0, "wcp");

	/*
	 * processes to read the console
	 */
	consserve();

	/*
	 * "sync" copy process
	 * this doesn't return.
	 */
	procsetname("scp");
	synccopy();
}

/*
 * read ahead processes.
 * read message from q and then
 * read the device.
 */
int
rbcmp(void *va, void *vb)
{
	Rabuf *ra, *rb;

	ra = *(Rabuf**)va;
	rb = *(Rabuf**)vb;
	if(rb == 0)
		return 1;
	if(ra == 0)
		return -1;
	if(ra->dev > rb->dev)
		return 1;
	if(ra->dev < rb->dev)
		return -1;
	if(ra->addr > rb->addr)
		return 1;
	if(ra->addr < rb->addr)
		return -1;
	return 0;
}

void
rahead(void *)
{
	Rabuf *rb[50];
	Iobuf *p;
	int i, n;

	for (;;) {
		rb[0] = fs_recv(raheadq, 0);
		for(n = 1; n < nelem(rb); n++) {
			if(raheadq->count <= 0)
				break;
			rb[n] = fs_recv(raheadq, 0);
		}
		qsort(rb, n, sizeof rb[0], rbcmp);
		for(i = 0; i < n; i++) {
			if(rb[i] == 0)
				continue;
			p = getbuf(rb[i]->dev, rb[i]->addr, Brd);
			if(p)
				putbuf(p);
			lock(&rabuflock);
			rb[i]->link = rabuffree;
			rabuffree = rb[i];
			unlock(&rabuflock);
		}
	}
}

/*
 * main filesystem server loop.
 * entered by many processes.
 * they wait for message buffers and
 * then process them.
 */
void
serve(void *)
{
	int i;
	Chan *cp;
	Msgbuf *mb;

	for (;;) {
		qlock(&reflock);
		/* read 9P request from a network input process */
		mb = fs_recv(serveq, 0);
		assert(mb->magic == Mbmagic);
		/* fs kernel sets chan in /sys/src/fs/ip/il.c:/^getchan */
		cp = mb->chan;
		if (cp == nil)
			panic("serve: nil mb->chan");
		rlock(&cp->reflock);
		qunlock(&reflock);

		rlock(&mainlock);

		if (mb->data == nil)
			panic("serve: nil mb->data");
		/* better sniffing code in /sys/src/cmd/disk/kfs/9p12.c */
		if(cp->protocol == nil){
			/* do we recognise the protocol in this packet? */
			/* better sniffing code: /sys/src/cmd/disk/kfs/9p12.c */
			for(i = 0; fsprotocol[i] != nil; i++)
				if(fsprotocol[i](mb) != 0) {
					cp->protocol = fsprotocol[i];
					break;
				}
			if(cp->protocol == nil){
				print("no protocol for message\n");
				for(i = 0; i < 12; i++)
					print(" %2.2uX", mb->data[i]);
				print("\n");
			}
		} else
			/* process the request, generate an answer and reply */
			cp->protocol(mb);

		mbfree(mb);
		runlock(&mainlock);
		runlock(&cp->reflock);
	}
}

void
exit(void)
{
	lock(&active);
	active.exiting = 1;
	unlock(&active);

	print("halted at %T.\n", time(nil));
	postnote(PNGROUP, getpid(), "die");
	exits(nil);
}

enum {
	DUMPTIME = 5,	/* 5 am */
	WEEKMASK = 0,	/* every day (1=sun, 2=mon, 4=tue, etc.) */
};

/*
 * calculate the next dump time.
 * minimum delay is 100 minutes.
 */
Timet
nextdump(Timet t)
{
	Timet nddate = nextime(t+MINUTE(100), DUMPTIME, WEEKMASK);

	if(!conf.nodump)
		print("next dump at %T\n", nddate);
	return nddate;
}

/*
 * process to copy dump blocks from
 * cache to worm. it runs flat out when
 * it gets work, but only looks for
 * work every 10 seconds.
 */
void
wormcopy(void *)
{
	int f, dorecalc = 1;
	Timet dt, t = 0, nddate = 0, ntoytime = 0;
	Filsys *fs;

	for (;;) {
		if (dorecalc) {
			dorecalc = 0;
			t = time(nil);
			nddate = nextdump(t);		/* chatters */
			ntoytime = time(nil);
		}
		dt = time(nil) - t;
		if(dt < 0 || dt > MINUTE(100)) {
			if(dt < 0)
				print("time went back\n");
			else
				print("time jumped ahead\n");
			dorecalc = 1;
			continue;
		}
		t += dt;
		f = 0;
		if(t > ntoytime)
			ntoytime = time(nil) + HOUR(1);
		else if(t > nddate) {
			if(!conf.nodump) {
				print("automatic dump %T\n", t);
				for(fs=filsys; fs->name; fs++)
					if(fs->dev->type == Devcw)
						cfsdump(fs);
			}
			dorecalc = 1;
		} else {
			rlock(&mainlock);
			for(fs=filsys; fs->name; fs++)
				if(fs->dev->type == Devcw)
					f |= dumpblock(fs->dev);
			runlock(&mainlock);
			if(!f)
				delay(10000);
			wormprobe();
		}
	}
}

/*
 * process to synch blocks
 * it puts out a block/cache-line every second
 * it waits 10 seconds if caught up.
 * in both cases, it takes about 10 seconds
 * to get up-to-date.
 */
void
synccopy(void)
{
	int f;

	for (;;) {
		rlock(&mainlock);
		f = syncblock();
		runlock(&mainlock);
		if(!f)
			delay(10000);
		else
			delay(1000);
	}
}

Devsize
inqsize(char *file)
{
	int nf;
	char *ln, *end, *data = malloc(strlen(file) + 5 + 1);
	char *fields[4];
	Devsize rv = -1;
	Biobuf *bp;

	strcpy(data, file);
	end = strstr(data, "/data");
	if (end == nil)
		strcat(data, "/ctl");
	else
		strcpy(end, "/ctl");
	bp = Bopen(data, OREAD);
	if (bp) {
		while (rv < 0 && (ln = Brdline(bp, '\n')) != nil) {
			ln[Blinelen(bp)-1] = '\0';
			nf = tokenize(ln, fields, nelem(fields));
			if (nf == 3 && strcmp(fields[0], "geometry") == 0)
				rv = atoi(fields[2]);
		}
		Bterm(bp);
	}
	free(data);
	return rv;
}
