From e5888a1ffdae813d7575f5fb02275c6bb07e5199 Mon Sep 17 00:00:00 2001 From: Taru Karttunen Date: Wed, 30 Mar 2011 15:46:40 +0300 Subject: Import sources from 2011-03-30 iso image --- sys/src/cmd/cwfs/config.c | 1037 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1037 insertions(+) create mode 100755 sys/src/cmd/cwfs/config.c (limited to 'sys/src/cmd/cwfs/config.c') diff --git a/sys/src/cmd/cwfs/config.c b/sys/src/cmd/cwfs/config.c new file mode 100755 index 000000000..bc0d118ea --- /dev/null +++ b/sys/src/cmd/cwfs/config.c @@ -0,0 +1,1037 @@ +#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 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: + print("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.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 = malloc(sizeof(Device)); + 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) +{ print("map: mapped wren %Z to existing file %s\n", d, map->to); // DEBUG + d->wren.file = map->to; /* wren -> file mapping */ +} + else if (map->tdev != nil) +{ print("map: mapped wren %Z to dev %Z\n", d, map->tdev); // DEBUG + *d = *map->tdev; /* wren -> wren mapping */ +} + else + print("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; + + if(f.error) + return devnone; + d = malloc(sizeof(Device)); + + 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 'w': /* w[#.]#[.#] wren [ctrl] unit [lun] */ + 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 = malloc(sizeof(Device)); + 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; + + 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, "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])) + print("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", (int)(p-s), s); + s = p; + } + if(p != s) + print("%.*s", (int)(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(Devcon, 1, 0); + +start: + /* + * part 1 -- read the config file + */ + devnone = iconfig("n"); + + cp = nvrgetconfig(); + 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) + print("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); + + 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; + print("config block written\n"); + goto start; + } + putbuf(p); + + 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++) { + 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++) { + delay(3000); + 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) { + print("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; + } + // print("wormof(%Z)=%Z\n", dev, worm); + 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; + + 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; + } + } + 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"); + delay(2000); + print("looping; reset the machine at any time.\n"); + for (; ; ) + continue; /* 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) { + 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 + */ + print("copied %lld blocks from %Z to %Z\n", (Wideoff)a, from, to); + sync("devcopy"); + return 0; +} + +static void +setconfig(char *dev) +{ + if (dev != nil && !testconfig(dev)) + nvrsetconfig(dev); /* if it fails, it will complain */ +} + +void +arginit(void) +{ + int verb; + char *line; + char word[Maxword+1], *cp; + Filsys *fs; + + if(nvrcheck() == 0) { + setconfig(conf.confdev); + if (!conf.configfirst) + return; + } + + /* nvr was bad or invoker requested configuration step */ + setconfig(conf.confdev); + 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, "allow") == 0) { + wstatallow = 1; + writeallow = 1; + 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, "noauth") == 0) { + noauth = !noauth; + continue; + } + if(strcmp(word, "noattach") == 0) { + noattach = !noattach; + continue; + } + if(strcmp(word, "readonly") == 0) { + readonly = 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: + if(strcmp(fs->name, "main") == 0) + wstatallow = 1; /* only set, never reset */ + /* fallthrough */ + 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; + } + } +} -- cgit v1.2.3