shithub: riscv

ref: 9861f1a92b373a16939fd6f3c167e5dad9f253d3
dir: /sys/src/cmd/cwfs/config.c/

View raw version
#include "all.h"
#include "io.h"

static void	dowormcopy(void);
static int	dodevcopy(void);

struct {
	char*	icharp;
	char*	charp;
	int	error;
	int	newconf;	/* clear before start */
	int	modconf;	/* write back when done */
	int	nextiter;
	int	lastiter;
	int	diriter;
	Device*	lastcw;
	Device*	devlist;
} f;

static Device* confdev;
static int copyworm = 0, copydev = 0;
static char *src, *dest;

static int nononeset;
static int noauthset;
static int readonlyset;
static int resetparams;

Fspar fspar[] = {
	{ "blocksize",	RBUFSIZE, },
	{ "daddrbits",	sizeof(Off)*8, },
	{ "indirblks",	NIBLOCK, },
	{ "dirblks",	NDBLOCK, },
	{ "namelen",	NAMELEN, },
	{ nil, 0, },
};

int
devcmpr(Device *d1, Device *d2)
{
	while (d1 != d2) {
		if(d1 == nil || d2 == nil || d1->type != d2->type)
			return 1;

		switch(d1->type) {
		default:
			fprint(2, "can't compare dev: %Z\n", d1);
			panic("devcmp");
			return 1;

		case Devmcat:
		case Devmlev:
		case Devmirr:
			d1 = d1->cat.first;
			d2 = d2->cat.first;
			while(d1 && d2) {
				if(devcmpr(d1, d2))
					return 1;
				d1 = d1->link;
				d2 = d2->link;
			}
			break;

		case Devnone:
			return 0;

		case Devro:
			d1 = d1->ro.parent;
			d2 = d2->ro.parent;
			break;

		case Devjuke:
		case Devcw:
			if(devcmpr(d1->cw.c, d2->cw.c))
				return 1;
			d1 = d1->cw.w;
			d2 = d2->cw.w;
			break;

		case Devfworm:
			d1 = d1->fw.fw;
			d2 = d2->fw.fw;
			break;

		case Devwren:
		case Devworm:
		case Devlworm:
			if(d1->wren.file || d2->wren.file){
				if(d1->wren.file == nil || d2->wren.file == nil)
					return 1;
				return !!strcmp(d1->wren.file, d2->wren.file);
			}
			if(d1->wren.ctrl == d2->wren.ctrl)
			if(d1->wren.targ == d2->wren.targ)
			if(d1->wren.lun == d2->wren.lun)
				return 0;
			return 1;

		case Devpart:
			if(d1->part.base == d2->part.base)
			if(d1->part.size == d2->part.size) {
				d1 = d1->part.d;
				d2 = d2->part.d;
				break;
			}
			return 1;
		}
	}
	return 0;
}

void
cdiag(char *s, int c1)
{

	f.charp--;
	if(f.error == 0) {
		print("config diag: %s -- <%c>\n", s, c1);
		f.error = 1;
	}
}

int
cnumb(void)
{
	int c, n;

	c = *f.charp++;
	if(c == '<') {
		n = f.nextiter;
		if(n >= 0) {
			f.nextiter = n+f.diriter;
			if(n == f.lastiter) {
				f.nextiter = -1;
				f.lastiter = -1;
			}
			do {
				c = *f.charp++;
			} while (c != '>');
			return n;
		}
		n = cnumb();
		if(*f.charp++ != '-') {
			cdiag("- expected", f.charp[-1]);
			return 0;
		}
		c = cnumb();
		if(*f.charp++ != '>') {
			cdiag("> expected", f.charp[-1]);
			return 0;
		}
		f.lastiter = c;
		f.diriter = 1;
		if(n > c)
			f.diriter = -1;
		f.nextiter = n+f.diriter;
		return n;
	}
	if(!isascii(c) || !isdigit(c)) {
		cdiag("number expected", c);
		return 0;
	}
	n = 0;
	while(isascii(c) && isdigit(c)) {
		n = n*10 + (c-'0');
		c = *f.charp++;
	}
	f.charp--;
	return n;
}

Device*
config1(int c)
{
	Device *d, *t;
	int m;

	d = ialloc(sizeof(Device), 0);
	do {
		t = config();
		if(d->cat.first == 0)
			d->cat.first = t;
		else
			d->cat.last->link = t;
		d->cat.last = t;
		if(f.error)
			return devnone;
		m = *f.charp;
		if(c == '(' && m == ')')
			d->type = Devmcat;
		else if(c == '[' && m == ']')
			d->type = Devmlev;
		else if(c == '{' && m == '}')
			d->type = Devmirr;
	} while (d->type == 0);
	f.charp++;
	if(d->cat.first == d->cat.last)
		d = d->cat.first;
	return d;
}

static void
map(Device *d)
{
	Map *map;

	if (d->type != Devwren || d->wren.mapped)
		return;
	for (map = devmap; map != nil; map = map->next)
		if (devcmpr(d, map->fdev) == 0)
			break;
	if (map == nil)
		return;
	if (access(map->to, AEXIST) >= 0){
		if(chatty)
			print("map: mapped wren %Z to existing file %s\n", d, map->to);
		d->wren.file = map->to;		/* wren -> file mapping */
	} else if (map->tdev != nil){
		if(chatty)
			print("map: mapped wren %Z to dev %Z\n", d, map->tdev);
		*d = *map->tdev;		/* wren -> wren mapping */
	} else
		fprint(2, "bad mapping %Z to %s; no such file or device", d, map->to);
	d->wren.mapped = 1;
}

Device*
config(void)
{
	int c, m;
	Device *d;
	char *icp, *s, *e;

	if(f.error)
		return devnone;
	d = ialloc(sizeof(Device), 0);
	c = *f.charp++;
	switch(c) {
	default:
		cdiag("unknown type", c);
		return devnone;

	case '(':	/* (d+) one or multiple cat */
	case '[':	/* [d+] one or multiple interleave */
	case '{':	/* {d+} a mirrored device and optional mirrors */
		return config1(c);

	case 'f':	/* fd fake worm */
		d->type = Devfworm;
		d->fw.fw = config();
		break;

	case 'n':
		d->type = Devnone;
		break;

	case '/':	/* /path/to/file	mapped file */
	case '"':	/* "/path/to/file"	mapped file */
	case '\'':	/* '/path/to/file'	mapped file */
	Mapped:
		d->type = Devwren;
		if(c == '/'){
			s = f.charp-1;
			for(e = s+1; *e; e++)
				if(*e == ')' || *e == ']' || *e == '}')
					break;
			f.charp = e;
		} else {
			s = f.charp;
			if((e = strchr(s, c)) == nil){
				cdiag("unterminated string", c);
				return devnone;
			}
			f.charp = e+1;
		}
		d->wren.ctrl = -1;
		d->wren.targ = -1;
		d->wren.lun = -1;
		d->wren.file = ialloc((e - s) + 1, 0);
		memmove(d->wren.file, s, e - s);
		d->wren.file[e - s] = 0;
		break;

	case 'w':	/* w[#.]#[.#] wren	[ctrl] unit [lun] */
		switch(*f.charp){
		case '/':
		case '"':
		case '\'':
			c = *f.charp++;
			goto Mapped;
		}
	case 'r':	/* r# worm side */
	case 'l':	/* l# labelled-worm side */
		icp = f.charp;
		d->type = Devwren;
		d->wren.ctrl = 0;
		d->wren.targ = cnumb();
		d->wren.lun = 0;
		m = *f.charp;
		if(m == '.') {
			f.charp++;
			d->wren.lun = cnumb();
			m = *f.charp;
			if(m == '.') {
				f.charp++;
				d->wren.ctrl = d->wren.targ;
				d->wren.targ = d->wren.lun;
				d->wren.lun = cnumb();
			}
		}
		if(f.nextiter >= 0)
			f.charp = icp-1;
		if(c == 'r')		/* worms are virtual and not uniqued */
			d->type = Devworm;
		else if(c == 'l')
			d->type = Devlworm;
		else
			map(d);		/* subject wrens to optional mapping */
		break;

	case 'o':	/* o ro part of last cw */
		if(f.lastcw == 0) {
			cdiag("no cw to match", c);
			return devnone;
		}
		return f.lastcw->cw.ro;

	case 'j':	/* DD jukebox */
		d->type = Devjuke;
		d->j.j = config();
		d->j.m = config();
		break;

	case 'c':	/* cache/worm */
		d->type = Devcw;
		d->cw.c = config();
		d->cw.w = config();
		d->cw.ro = ialloc(sizeof(Device), 0);
		d->cw.ro->type = Devro;
		d->cw.ro->ro.parent = d;
		f.lastcw = d;
		break;

	case 'p':	/* pd#.# partition base% size% */
		d->type = Devpart;
		d->part.d = config();
		d->part.base = cnumb();
		c = *f.charp++;
		if(c != '.')
			cdiag("dot expected", c);
		d->part.size = cnumb();
		break;

	case 'x':	/* xD swab a device's metadata */
		d->type = Devswab;
		d->swab.d = config();
		break;
	}
	d->dlink = f.devlist;
	f.devlist = d;
	return d;
}

Device*
iconfig(char *s)
{
	Device *d;

	f.nextiter = -1;
	f.lastiter = -1;
	f.error = 0;
	f.icharp = s;
	f.charp = f.icharp;
	d = config();
	if(*f.charp) {
		cdiag("junk on end", *f.charp);
		f.error = 1;
	}
	return d;
}

int
testconfig(char *s)
{
	iconfig(s);
	return f.error;
}

/*
 * if b is a prefix of a, return 0.
 */
int
astrcmp(char *a, char *b)
{
	int n, c;

	n = strlen(b);
	if(memcmp(a, b, n) != 0)
		return 1;
	c = a[n];
	if(c == '\0')
		return 0;
	if(a[n+1])
		return 1;
	if(isascii(c) && isdigit(c))
		return 0;
	return 1;
}

static Fspar *
getpar(char *name)
{
	Fspar *fsp;

	for (fsp = fspar; fsp->name != nil; fsp++)
		if (strcmp(name, fsp->name) == 0)
			return fsp;
	return nil;
}

/*
 * continue to parse obsolete keywords so that old configurations can
 * still work.
 */
void
mergeconf(Iobuf *p)
{
	char word[Maxword+1];
	char *cp;
	Filsys *fs;
	Fspar *fsp;

	if(!noauthset)
		noauth = 0;
	if(!nononeset)
		nonone = 0;
	if(!noatimeset)
		noatime = 0;
	if(!readonlyset)
		readonly = 0;
	for (cp = p->iobuf; *cp != '\0'; cp++) {
		cp = getwrd(word, cp);
		if(word[0] == '\0')
			break;
		else if (word[0] == '#')
			while (*cp != '\n' && *cp != '\0')
				cp++;
		else if(strcmp(word, "service") == 0) {
			cp = getwrd(word, cp);
			if(service[0] == 0)
				strncpy(service, word, sizeof service);
		} else if(strcmp(word, "noauth") == 0){
			if(!noauthset)
				noauth = 1;
		} else if(strcmp(word, "nonone") == 0){
			if(!nononeset)
				nonone = 1;
		} else if(strcmp(word, "noatime") == 0){
			if(!noatimeset)
				noatime = 1;
		} else if(strcmp(word, "readonly") == 0){
			if(!readonlyset)
				readonly = 1;
		} else if(strcmp(word, "newcache") == 0){
			conf.newcache = 1;
		} else if(strcmp(word, "ipauth") == 0)	/* obsolete */
			cp = getwrd(word, cp);
		else if(astrcmp(word, "ip") == 0)	/* obsolete */
			cp = getwrd(word, cp);
		else if(astrcmp(word, "ipgw") == 0)	/* obsolete */
			cp = getwrd(word, cp);
		else if(astrcmp(word, "ipsntp") == 0)	/* obsolete */
			cp = getwrd(word, cp);
		else if(astrcmp(word, "ipmask") == 0)	/* obsolete */
			cp = getwrd(word, cp);
		else if(strcmp(word, "filsys") == 0) {
			cp = getwrd(word, cp);
			for(fs = filsys; fs < filsys + nelem(filsys) - 1 &&
			    fs->name; fs++)
				if(strcmp(fs->name, word) == 0)
					break;
			if (fs >= filsys + nelem(filsys) - 1)
				panic("out of filsys structures");
			if (fs->name && strcmp(fs->name, word) == 0 &&
			    fs->flags & FEDIT)
				cp = getwrd(word, cp);	/* swallow conf */
			else {
				fs->name = strdup(word);
				cp = getwrd(word, cp);
				if (word[0] == '\0')
					fs->conf = nil;
				else
					fs->conf = strdup(word);
			}
		} else if ((fsp = getpar(word)) != nil) {
			cp = getwrd(word, cp);
			if (!isascii(word[0]) || !isdigit(word[0]))
				fprint(2, "bad %s value: %s", fsp->name, word);
			else
				fsp->declared = atol(word);
		} else {
			putbuf(p);
			panic("unknown keyword in config block: %s", word);
		}

		if(*cp != '\n') {
			putbuf(p);
			panic("syntax error in config block at `%s'", word);
		}
	}
}

void
cmd_printconf(int, char *[])
{
	char *p, *s;
	Iobuf *iob;

	iob = getbuf(confdev, 0, Brd);
	if(iob == nil)
		return;
	if(checktag(iob, Tconfig, 0)){
		putbuf(iob);
		return;
	}

	print("config %s\n", nvrgetconfig());
	for(s = p = iob->iobuf; *p != 0 && p < iob->iobuf+BUFSIZE; ){
		if(*p++ != '\n')
			continue;
		if (strncmp(s, "ip", 2) != 0)	/* don't print obsolete cmds */
			print("%.*s", utfnlen(s, p-s), s);
		s = p;
	}
	if(p != s)
		print("%.*s", utfnlen(s, p-s), s);
	print("end\n");

	putbuf(iob);
}

void
sysinit(void)
{
	int error;
	char *cp, *ep;
	Device *d;
	Filsys *fs;
	Fspar *fsp;
	Iobuf *p;

	cons.chan = fs_chaninit(1, 0);

start:
	/*
	 * part 1 -- read the config file
	 */
	devnone = iconfig("n");

	cp = nvrgetconfig();
	if(chatty)
		print("config %s\n", cp);

	confdev = d = iconfig(cp);
	devinit(d);
	if(f.newconf) {
		p = getbuf(d, 0, Bmod);
		memset(p->iobuf, 0, RBUFSIZE);
		settag(p, Tconfig, 0);
	} else
		p = getbuf(d, 0, Brd|Bmod);
	if(!p || checktag(p, Tconfig, 0))
		panic("config io");

	mergeconf(p);

	if (resetparams) {
		for (fsp = fspar; fsp->name != nil; fsp++)
			fsp->declared = 0;
		resetparams = 0;
	}

	for (fsp = fspar; fsp->name != nil; fsp++) {
		/* supply defaults from this cwfs instance */
		if (fsp->declared == 0) {
			fsp->declared = fsp->actual;
			f.modconf = 1;
		}
		/* warn if declared value is not our compiled-in value */
		if (fsp->declared != fsp->actual)
			fprint(2, "warning: config %s %ld != compiled-in %ld\n",
				fsp->name, fsp->declared, fsp->actual);
	}

	if(f.modconf) {
		memset(p->iobuf, 0, BUFSIZE);
		p->flags |= Bmod|Bimm;
		cp = p->iobuf;
		ep = p->iobuf + RBUFSIZE - 1;
		if(service[0])
			cp = seprint(cp, ep, "service %s\n", service);
		for(fs=filsys; fs->name; fs++)
			if(fs->conf && fs->conf[0] != '\0')
				cp = seprint(cp, ep, "filsys %s %s\n", fs->name,
					fs->conf);
		if(noauth)
			cp = seprint(cp, ep, "noauth\n");
		if(nonone)
			cp = seprint(cp, ep, "nonone\n");
		if(noatime)
			cp = seprint(cp, ep, "noatime\n");
		if(readonly)
			cp = seprint(cp, ep, "readonly\n");
		if(conf.newcache)
			cp = seprint(cp, ep, "newcache\n");
		for (fsp = fspar; fsp->name != nil; fsp++)
			cp = seprint(cp, ep, "%s %ld\n",
				fsp->name, fsp->declared);

		putbuf(p);
		f.modconf = f.newconf = 0;
		goto start;
	}
	putbuf(p);

	if(chatty)
		print("service %s\n", service);

loop:
	/*
	 * part 2 -- squeeze out the deleted filesystems
	 */
	for(fs=filsys; fs->name; fs++)
		if(fs->conf == nil || fs->conf[0] == '\0') {
			for(; fs->name; fs++)
				*fs = *(fs+1);
			goto loop;
		}
	if(filsys[0].name == nil)
		panic("no filsys");

	/*
	 * part 3 -- compile the device expression
	 */
	error = 0;
	for(fs=filsys; fs->name; fs++) {
		if(chatty)
			print("filsys %s %s\n", fs->name, fs->conf);
		fs->dev = iconfig(fs->conf);
		if(f.error) {
			error = 1;
			continue;
		}
	}
	if(error)
		panic("fs config");

	/*
	 * part 4 -- initialize the devices
	 */
	for(fs=filsys; fs->name; fs++) {
		if(chatty)
			print("sysinit: %s\n", fs->name);
		if(fs->flags & FREAM)
			devream(fs->dev, 1);
		if(fs->flags & FRECOVER)
			devrecover(fs->dev);
		devinit(fs->dev);
	}

	/*
	 * part 5 -- optionally copy devices or worms
	 */
	if (copyworm) {
		dowormcopy();		/* can return if user quits early */
		panic("copyworm bailed out!");
	}
	if (copydev)
		if (dodevcopy() < 0)
			panic("copydev failed!");
		else
			panic("copydev done.");
}

/* an unfinished idea.  a non-blocking rawchar() would help. */
static int
userabort(char *msg)
{
	USED(msg);
	return 0;
}

static int
blockok(Device *d, Off a)
{
	Iobuf *p = getbuf(d, a, Brd);

	if (p == 0) {
		fprint(2, "i/o error reading %Z block %lld\n", d, (Wideoff)a);
		return 0;
	}
	putbuf(p);
	return 1;
}

/*
 * special case for fake worms only:
 * we need to size the inner cw's worm device.
 * in particular, we want to avoid copying the fake-worm bitmap
 * at the end of the device.
 *
 * N.B.: for real worms (e.g. cw jukes), we need to compute devsize(cw(juke)),
 * *NOT* devsize(juke).
 */
static Device *
wormof(Device *dev)
{
	Device *worm = dev, *cw;

	if (dev->type == Devfworm) {
		cw = dev->fw.fw;
		if (cw != nil && cw->type == Devcw)
			worm = cw->cw.w;
	}
	return worm;
}

/*
 * return the number of the highest-numbered block actually written, plus 1.
 * 0 indicates an error.
 */
static Devsize
writtensize(Device *worm)
{
	Devsize lim = devsize(worm);
	Iobuf *p;

	if(chatty)
		print("devsize(%Z) = %lld\n", worm, (Wideoff)lim);
	if (!blockok(worm, 0) || !blockok(worm, lim-1))
		return 0;
	delay(5*1000);
	if(userabort("sanity checks"))
		return 0;

	/* find worm's last valid block in case "worm" is an (f)worm */
	while (lim > 0) {
		if (userabort("sizing")) {
			lim = 0;		/* you lose */
			break;
		}
		--lim;
		p = getbuf(worm, lim, Brd);
		if (p != 0) {			/* actually read one okay? */
			putbuf(p);
			break;
		}
	}
	if(chatty)
		print("limit(%Z) = %lld\n", worm, (Wideoff)lim);
	if (lim <= 0)
		return 0;
	return lim + 1;
}

/* copy worm fs from "main"'s inner worm to "output" */
static void
dowormcopy(void)
{
	Filsys *f1, *f2;
	Device *fdev, *from, *to = nil;
	Iobuf *p;
	Off a;
	Devsize lim;

	/*
	 * convert file system names into Filsyss and Devices.
	 */

	f1 = fsstr("main");
	if(f1 == nil)
		panic("main file system missing");
	fdev = f1->dev;
	from = wormof(fdev);			/* fake worm special */
	if (from->type != Devfworm && from->type != Devcw) {
		print("main file system is not a worm; copyworm may not do what you want!\n");
		print("waiting for 20 seconds...\n");
		delay(20000);
	}

	f2 = fsstr("output");
	if(f2 == nil) {
		print("no output file system - check only\n\n");
		print("reading worm from %Z (worm %Z)\n", fdev, from);
	} else {
		to = f2->dev;
		print("\ncopying worm from %Z (worm %Z) to %Z, starting in 8 seconds\n",
			fdev, from, to);
		delay(8000);
	}
	if (userabort("preparing to copy"))
		return;

	/*
	 * initialise devices, size them, more sanity checking.
	 */

	devinit(from);
	if (0 && fdev != from) {
		devinit(fdev);
		print("debugging, sizing %Z first\n", fdev);
		writtensize(fdev);
	}
	lim = writtensize(from);
	if(lim == 0)
		panic("no blocks to copy on %Z", from);
	if (to) {
		print("reaming %Z in 8 seconds\n", to);
		delay(8000);
		if (userabort("preparing to ream & copy"))
			return;
		devream(to, 0);
		devinit(to);
		print("copying worm: %lld blocks from %Z to %Z\n",
			(Wideoff)lim, from, to);
	}
	/* can't read to's blocks in case to is a real WORM device */

	/*
	 * Copy written fs blocks, a block at a time (or just read
	 * if no "output" fs).
	 */

	for (a = 0; a < lim; a++) {
		if (userabort("copy"))
			break;
		p = getbuf(from, a, Brd);
		/*
		 * if from is a real WORM device, we'll get errors trying to
		 * read unwritten blocks, but the unwritten blocks need not
		 * be contiguous.
		 */
		if (p == 0) {
			print("%lld not written yet; can't read\n", (Wideoff)a);
			continue;
		}
		if (to != 0 && devwrite(to, p->addr, p->iobuf) != 0) {
			print("out block %lld: write error; bailing",
				(Wideoff)a);
			break;
		}
		putbuf(p);
		if(a % 20000 == 0)
			print("block %lld %T\n", (Wideoff)a, time(nil));
	}

	/*
	 * wrap up: sync target, loop
	 */
	print("copied %lld blocks from %Z to %Z\n", (Wideoff)a, from, to);
	sync("wormcopy");
	
	print("looping; reset the machine at any time.\n");
	for(;;)
		delay(10000);		/* await reset */
}

/* copy device from src to dest */
static int
dodevcopy(void)
{
	Device *from, *to;
	Iobuf *p;
	Off a;
	Devsize lim, tosize;

	/*
	 * convert config strings into Devices.
	 */
	from = iconfig(src);
	if(f.error || from == nil) {
		print("bad src device %s\n", src);
		return -1;
	}
	to = iconfig(dest);
	if(f.error || to == nil) {
		print("bad dest device %s\n", dest);
		return -1;
	}

	/*
	 * initialise devices, size them, more sanity checking.
	 */

	devinit(from);
	lim = devsize(from);
	if(lim == 0)
		panic("no blocks to copy on %Z", from);
	devinit(to);
	tosize = devsize(to);
	if(tosize == 0)
		panic("no blocks to copy on %Z", to);

	/* use smaller of the device sizes */
	if (tosize < lim)
		lim = tosize;

	print("copy %Z to %Z in 8 seconds\n", from, to);
	delay(8000);
	if (userabort("preparing to copy"))
		return -1;
	print("copying dev: %lld blocks from %Z to %Z\n", (Wideoff)lim,
		from, to);

	/*
	 * Copy all blocks, a block at a time.
	 */

	for (a = 0; a < lim; a++) {
		if (userabort("copy"))
			break;
		p = getbuf(from, a, Brd);
		/*
		 * if from is a real WORM device, we'll get errors trying to
		 * read unwritten blocks, but the unwritten blocks need not
		 * be contiguous.
		 */
		if (p == 0) {
			fprint(2, "%lld not written yet; can't read\n", (Wideoff)a);
			continue;
		}
		if (to != 0 && devwrite(to, p->addr, p->iobuf) != 0) {
			print("out block %lld: write error; bailing",
				(Wideoff)a);
			break;
		}
		putbuf(p);
		if(a % 20000 == 0)
			print("block %lld %T\n", (Wideoff)a, time(nil));
	}

	/*
	 * wrap up: sync target
	 */
	print("copied %lld blocks from %Z to %Z\n", (Wideoff)a, from, to);
	sync("devcopy");
	return 0;
}

static int
setconfig(char *dev)
{
	if (dev && !testconfig(dev)){
		nvrsetconfig(dev);	/* if it fails, it will complain */
		return 0;
	}
	return -1;
}

void
arginit(void)
{
	int verb;
	char *line;
	char word[Maxword+1], *cp;
	Filsys *fs;

	if(!setconfig(conf.confdev) && !conf.configfirst)
		return;

	for (;;) {
		print("config: ");
		if ((line = Brdline(&bin, '\n')) == nil)
			return;
		line[Blinelen(&bin)-1] = '\0';

		cp = getwrd(word, line);
		if (word[0] == '\0' || word[0] == '#')
			continue;
		if(strcmp(word, "end") == 0)
			return;
		if(strcmp(word, "halt") == 0)
			exit();
		if(strcmp(word, "queryjuke") == 0) {
			getwrd(word, cp);
			if(testconfig(word) == 0)
				querychanger(iconfig(word));
			continue;
		}
		if(strcmp(word, "copyworm") == 0) {
			copyworm = 1;
			continue;
		}
		if(strcmp(word, "copydev") == 0) {
			cp = getwrd(word, cp);
			if(testconfig(word))
				continue;
			src = strdup(word);
			getwrd(word, cp);
			if(testconfig(word))
				continue;
			dest = strdup(word);
			copydev = 1;
			continue;
		}
		if(strcmp(word, "noattach") == 0) {
			noattach = !noattach;
			print("attach %s\n", noattach ? "disallowed" : "allowed");
			continue;
		}
		if(strcmp(word, "noauth") == 0) {
			noauth = !noauth;
			print("auth %s\n", noauth ? "disabled" : "enabled");
			noauthset++;
			f.modconf = 1;
			continue;
		}
		if(strcmp(word, "nonone") == 0) {
			nonone = !nonone;
			print("none %s\n", nonone ? "disabled" : "enabled");
			nononeset++;
			f.modconf = 1;
			continue;
		}
		if(strcmp(word, "noatime") == 0) {
			noatime = !noatime;
			print("atime %s\n", noatime ? "disabled" : "enabled");
			noatimeset++;
			f.modconf = 1;
			continue;
		}
		if(strcmp(word, "readonly") == 0) {
			readonly = !readonly;
			print("filesystem %s\n", readonly ? "readonly" : "writable");
			readonlyset++;
			f.modconf = 1;
			continue;
		}

		if(strcmp(word, "ream") == 0) {
			verb = FREAM;
			goto gfsname;
		}
		if(strcmp(word, "recover") == 0) {
			verb = FRECOVER;
			goto gfsname;
		}
		if(strcmp(word, "filsys") == 0) {
			verb = FEDIT;
			goto gfsname;
		}

		if(strcmp(word, "nvram") == 0) {
			getwrd(word, cp);
			if(testconfig(word))
				continue;
			/* if it fails, it will complain */
			nvrsetconfig(word);
			continue;
		}
		if(strcmp(word, "config") == 0) {
			getwrd(word, cp);
			if(!testconfig(word) && nvrsetconfig(word) == 0)
				f.newconf = 1;
			continue;
		}
		if(strcmp(word, "service") == 0) {
			getwrd(word, cp);
			strncpy(service, word, sizeof service);
			f.modconf = 1;
			continue;
		}
		if (strcmp(word, "resetparams") == 0) {
			resetparams++;
			continue;
		}

		/*
		 * continue to parse obsolete keywords so that old
		 * configurations can still work.
		 */
		if (strcmp(word, "ipauth") != 0 &&
		    astrcmp(word, "ip") != 0 &&
		    astrcmp(word, "ipgw") != 0 &&
		    astrcmp(word, "ipmask") != 0 &&
		    astrcmp(word, "ipsntp") != 0) {
			print("unknown config command\n");
			print("\ttype end to get out\n");
			continue;
		}

		getwrd(word, cp);
		f.modconf = 1;
		continue;

	gfsname:
		cp = getwrd(word, cp);
		for(fs=filsys; fs->name; fs++)
			if(strcmp(word, fs->name) == 0)
				break;
		if (fs->name == nil) {
			memset(fs, 0, sizeof *fs);
			fs->name = strdup(word);
		}
		switch(verb) {
		case FREAM:
		case FRECOVER:
			fs->flags |= verb;
			break;
		case FEDIT:
			f.modconf = 1;
			getwrd(word, cp);
			fs->flags |= verb;
			if(word[0] == 0)
				fs->conf = nil;
			else if(!testconfig(word))
				fs->conf = strdup(word);
			break;
		}
	}
}