xref: /plan9/sys/src/cmd/fossil/9fsys.c (revision f9e1cf08d3be51592e03e639fc848a68dc31a55e)
1 #include "stdinc.h"
2 #include "dat.h"
3 #include "fns.h"
4 #include "9.h"
5 
6 typedef struct Fsys Fsys;
7 
8 struct Fsys {
9 	VtLock* lock;
10 
11 	char*	name;		/* copy here & Fs to ease error reporting */
12 	char*	dev;
13 	char*	venti;
14 
15 	Fs*	fs;
16 	VtSession* session;
17 	int	ref;
18 
19 	int	noauth;
20 	int	noperm;
21 	int	wstatallow;
22 
23 	Fsys*	next;
24 };
25 
26 static struct {
27 	VtLock*	lock;
28 	Fsys*	head;
29 	Fsys*	tail;
30 
31 	char*	curfsys;
32 } sbox;
33 
34 static char *_argv0;
35 #define argv0 _argv0
36 
37 static char FsysAll[] = "all";
38 
39 static char EFsysBusy[] = "fsys: '%s' busy";
40 static char EFsysExists[] = "fsys: '%s' already exists";
41 static char EFsysNoCurrent[] = "fsys: no current fsys";
42 static char EFsysNotFound[] = "fsys: '%s' not found";
43 static char EFsysNotOpen[] = "fsys: '%s' not open";
44 
45 static Fsys*
46 _fsysGet(char* name)
47 {
48 	Fsys *fsys;
49 
50 	if(name == nil || name[0] == '\0')
51 		name = "main";
52 
53 	vtRLock(sbox.lock);
54 	for(fsys = sbox.head; fsys != nil; fsys = fsys->next){
55 		if(strcmp(name, fsys->name) == 0){
56 			fsys->ref++;
57 			break;
58 		}
59 	}
60 	vtRUnlock(sbox.lock);
61 	if(fsys == nil)
62 		vtSetError(EFsysNotFound, name);
63 	return fsys;
64 }
65 
66 static int
67 cmdPrintConfig(int argc, char* argv[])
68 {
69 	Fsys *fsys;
70 	char *usage = "usage: printconfig";
71 
72 	ARGBEGIN{
73 	default:
74 		return cliError(usage);
75 	}ARGEND
76 
77 	if(argc)
78 		return cliError(usage);
79 
80 	vtRLock(sbox.lock);
81 	for(fsys = sbox.head; fsys != nil; fsys = fsys->next){
82 		consPrint("\tfsys %s config %s\n", fsys->name, fsys->dev);
83 		if(fsys->venti && fsys->venti[0])
84 			consPrint("\tfsys %s venti %q\n", fsys->name,
85 				fsys->venti);
86 	}
87 	vtRUnlock(sbox.lock);
88 	return 1;
89 }
90 
91 Fsys*
92 fsysGet(char* name)
93 {
94 	Fsys *fsys;
95 
96 	if((fsys = _fsysGet(name)) == nil)
97 		return nil;
98 
99 	vtLock(fsys->lock);
100 	if(fsys->fs == nil){
101 		vtSetError(EFsysNotOpen, fsys->name);
102 		vtUnlock(fsys->lock);
103 		fsysPut(fsys);
104 		return nil;
105 	}
106 	vtUnlock(fsys->lock);
107 
108 	return fsys;
109 }
110 
111 char*
112 fsysGetName(Fsys* fsys)
113 {
114 	return fsys->name;
115 }
116 
117 Fsys*
118 fsysIncRef(Fsys* fsys)
119 {
120 	vtLock(sbox.lock);
121 	fsys->ref++;
122 	vtUnlock(sbox.lock);
123 
124 	return fsys;
125 }
126 
127 void
128 fsysPut(Fsys* fsys)
129 {
130 	vtLock(sbox.lock);
131 	assert(fsys->ref > 0);
132 	fsys->ref--;
133 	vtUnlock(sbox.lock);
134 }
135 
136 Fs*
137 fsysGetFs(Fsys* fsys)
138 {
139 	assert(fsys != nil && fsys->fs != nil);
140 
141 	return fsys->fs;
142 }
143 
144 void
145 fsysFsRlock(Fsys* fsys)
146 {
147 	vtRLock(fsys->fs->elk);
148 }
149 
150 void
151 fsysFsRUnlock(Fsys* fsys)
152 {
153 	vtRUnlock(fsys->fs->elk);
154 }
155 
156 int
157 fsysNoAuthCheck(Fsys* fsys)
158 {
159 	return fsys->noauth;
160 }
161 
162 int
163 fsysNoPermCheck(Fsys* fsys)
164 {
165 	return fsys->noperm;
166 }
167 
168 int
169 fsysWstatAllow(Fsys* fsys)
170 {
171 	return fsys->wstatallow;
172 }
173 
174 static char modechars[] = "YUGalLdHSATs";
175 static ulong modebits[] = {
176 	ModeSticky,
177 	ModeSetUid,
178 	ModeSetGid,
179 	ModeAppend,
180 	ModeExclusive,
181 	ModeLink,
182 	ModeDir,
183 	ModeHidden,
184 	ModeSystem,
185 	ModeArchive,
186 	ModeTemporary,
187 	ModeSnapshot,
188 	0
189 };
190 
191 char*
192 fsysModeString(ulong mode, char *buf)
193 {
194 	int i;
195 	char *p;
196 
197 	p = buf;
198 	for(i=0; modebits[i]; i++)
199 		if(mode & modebits[i])
200 			*p++ = modechars[i];
201 	sprint(p, "%luo", mode&0777);
202 	return buf;
203 }
204 
205 int
206 fsysParseMode(char* s, ulong* mode)
207 {
208 	ulong x, y;
209 	char *p;
210 
211 	x = 0;
212 	for(; *s < '0' || *s > '9'; s++){
213 		if(*s == 0)
214 			return 0;
215 		p = strchr(modechars, *s);
216 		if(p == nil)
217 			return 0;
218 		x |= modebits[p-modechars];
219 	}
220 	y = strtoul(s, &p, 8);
221 	if(*p != '\0' || y > 0777)
222 		return 0;
223 	*mode = x|y;
224 	return 1;
225 }
226 
227 File*
228 fsysGetRoot(Fsys* fsys, char* name)
229 {
230 	File *root, *sub;
231 
232 	assert(fsys != nil && fsys->fs != nil);
233 
234 	root = fsGetRoot(fsys->fs);
235 	if(name == nil || strcmp(name, "") == 0)
236 		return root;
237 
238 	sub = fileWalk(root, name);
239 	fileDecRef(root);
240 
241 	return sub;
242 }
243 
244 static Fsys*
245 fsysAlloc(char* name, char* dev)
246 {
247 	Fsys *fsys;
248 
249 	vtLock(sbox.lock);
250 	for(fsys = sbox.head; fsys != nil; fsys = fsys->next){
251 		if(strcmp(fsys->name, name) != 0)
252 			continue;
253 		vtSetError(EFsysExists, name);
254 		vtUnlock(sbox.lock);
255 		return nil;
256 	}
257 
258 	fsys = vtMemAllocZ(sizeof(Fsys));
259 	fsys->lock = vtLockAlloc();
260 	fsys->name = vtStrDup(name);
261 	fsys->dev = vtStrDup(dev);
262 
263 	fsys->ref = 1;
264 
265 	if(sbox.tail != nil)
266 		sbox.tail->next = fsys;
267 	else
268 		sbox.head = fsys;
269 	sbox.tail = fsys;
270 	vtUnlock(sbox.lock);
271 
272 	return fsys;
273 }
274 
275 static int
276 fsysClose(Fsys* fsys, int argc, char* argv[])
277 {
278 	char *usage = "usage: [fsys name] close";
279 
280 	ARGBEGIN{
281 	default:
282 		return cliError(usage);
283 	}ARGEND
284 	if(argc)
285 		return cliError(usage);
286 
287 	return cliError("close isn't working yet; halt %s and then kill fossil",
288 		fsys->name);
289 
290 	/*
291 	 * Oooh. This could be hard. What if fsys->ref != 1?
292 	 * Also, fsClose() either does the job or panics, can we
293 	 * gracefully detect it's still busy?
294 	 *
295 	 * More thought and care needed here.
296 	fsClose(fsys->fs);
297 	fsys->fs = nil;
298 	vtClose(fsys->session);
299 	fsys->session = nil;
300 
301 	if(sbox.curfsys != nil && strcmp(fsys->name, sbox.curfsys) == 0){
302 		sbox.curfsys = nil;
303 		consPrompt(nil);
304 	}
305 
306 	return 1;
307 	 */
308 }
309 
310 static int
311 fsysVac(Fsys* fsys, int argc, char* argv[])
312 {
313 	uchar score[VtScoreSize];
314 	char *usage = "usage: [fsys name] vac path";
315 
316 	ARGBEGIN{
317 	default:
318 		return cliError(usage);
319 	}ARGEND
320 	if(argc != 1)
321 		return cliError(usage);
322 
323 	if(!fsVac(fsys->fs, argv[0], score))
324 		return 0;
325 
326 	consPrint("vac:%V\n", score);
327 	return 1;
328 }
329 
330 static int
331 fsysSnap(Fsys* fsys, int argc, char* argv[])
332 {
333 	int doarchive;
334 	char *usage = "usage: [fsys name] snap [-a] [-s /active] [-d /archive/yyyy/mmmm]";
335 	char *src, *dst;
336 
337 	src = nil;
338 	dst = nil;
339 	doarchive = 0;
340 	ARGBEGIN{
341 	default:
342 		return cliError(usage);
343 	case 'a':
344 		doarchive = 1;
345 		break;
346 	case 'd':
347 		if((dst = ARGF()) == nil)
348 			return cliError(usage);
349 		break;
350 	case 's':
351 		if((src = ARGF()) == nil)
352 			return cliError(usage);
353 		break;
354 	}ARGEND
355 	if(argc)
356 		return cliError(usage);
357 
358 	if(!fsSnapshot(fsys->fs, src, dst, doarchive))
359 		return 0;
360 
361 	return 1;
362 }
363 
364 static int
365 fsysSnapClean(Fsys *fsys, int argc, char* argv[])
366 {
367 	u32int arch, snap, life;
368 	char *usage = "usage: [fsys name] snapclean [maxminutes]\n";
369 
370 	ARGBEGIN{
371 	default:
372 		return cliError(usage);
373 	}ARGEND
374 
375 	if(argc > 1)
376 		return cliError(usage);
377 	if(argc == 1)
378 		life = atoi(argv[0]);
379 	else
380 		snapGetTimes(fsys->fs->snap, &arch, &snap, &life);
381 
382 	fsSnapshotCleanup(fsys->fs, life);
383 	return 1;
384 }
385 
386 static int
387 fsysSnapTime(Fsys* fsys, int argc, char* argv[])
388 {
389 	char buf[128], *x;
390 	int hh, mm, changed;
391 	u32int arch, snap, life;
392 	char *usage = "usage: [fsys name] snaptime [-a hhmm] [-s snapminutes] [-t maxminutes]";
393 
394 	changed = 0;
395 	snapGetTimes(fsys->fs->snap, &arch, &snap, &life);
396 	ARGBEGIN{
397 	case 'a':
398 		changed = 1;
399 		x = ARGF();
400 		if(x == nil)
401 			return cliError(usage);
402 		if(strcmp(x, "none") == 0){
403 			arch = ~(u32int)0;
404 			break;
405 		}
406 		if(strlen(x) != 4 || strspn(x, "0123456789") != 4)
407 			return cliError(usage);
408 		hh = (x[0]-'0')*10 + x[1]-'0';
409 		mm = (x[2]-'0')*10 + x[3]-'0';
410 		if(hh >= 24 || mm >= 60)
411 			return cliError(usage);
412 		arch = hh*60+mm;
413 		break;
414 	case 's':
415 		changed = 1;
416 		x = ARGF();
417 		if(x == nil)
418 			return cliError(usage);
419 		if(strcmp(x, "none") == 0){
420 			snap = ~(u32int)0;
421 			break;
422 		}
423 		snap = atoi(x);
424 		break;
425 	case 't':
426 		changed = 1;
427 		x = ARGF();
428 		if(x == nil)
429 			return cliError(usage);
430 		if(strcmp(x, "none") == 0){
431 			life = ~(u32int)0;
432 			break;
433 		}
434 		life = atoi(x);
435 		break;
436 	default:
437 		return cliError(usage);
438 	}ARGEND
439 	if(argc > 0)
440 		return cliError(usage);
441 
442 	if(changed){
443 		snapSetTimes(fsys->fs->snap, arch, snap, life);
444 		return 1;
445 	}
446 	snapGetTimes(fsys->fs->snap, &arch, &snap, &life);
447 	if(arch != ~(u32int)0)
448 		sprint(buf, "-a %02d%02d", arch/60, arch%60);
449 	else
450 		sprint(buf, "-a none");
451 	if(snap != ~(u32int)0)
452 		sprint(buf+strlen(buf), " -s %d", snap);
453 	else
454 		sprint(buf+strlen(buf), " -s none");
455 	if(life != ~(u32int)0)
456 		sprint(buf+strlen(buf), " -t %ud", life);
457 	else
458 		sprint(buf+strlen(buf), " -t none");
459 	consPrint("\tsnaptime %s\n", buf);
460 	return 1;
461 }
462 
463 static int
464 fsysSync(Fsys* fsys, int argc, char* argv[])
465 {
466 	char *usage = "usage: [fsys name] sync";
467 	int n;
468 
469 	ARGBEGIN{
470 	default:
471 		return cliError(usage);
472 	}ARGEND
473 	if(argc > 0)
474 		return cliError(usage);
475 
476 	n = cacheDirty(fsys->fs->cache);
477 	fsSync(fsys->fs);
478 	consPrint("\t%s sync: wrote %d blocks\n", fsys->name, n);
479 	return 1;
480 }
481 
482 static int
483 fsysHalt(Fsys *fsys, int argc, char* argv[])
484 {
485 	char *usage = "usage: [fsys name] halt";
486 
487 	ARGBEGIN{
488 	default:
489 		return cliError(usage);
490 	}ARGEND
491 	if(argc > 0)
492 		return cliError(usage);
493 
494 	fsHalt(fsys->fs);
495 	return 1;
496 }
497 
498 static int
499 fsysUnhalt(Fsys *fsys, int argc, char* argv[])
500 {
501 	char *usage = "usage: [fsys name] unhalt";
502 
503 	ARGBEGIN{
504 	default:
505 		return cliError(usage);
506 	}ARGEND
507 	if(argc > 0)
508 		return cliError(usage);
509 
510 	if(!fsys->fs->halted)
511 		return cliError("file system %s not halted", fsys->name);
512 
513 	fsUnhalt(fsys->fs);
514 	return 1;
515 }
516 
517 static int
518 fsysRemove(Fsys* fsys, int argc, char* argv[])
519 {
520 	File *file;
521 	char *usage = "usage: [fsys name] remove path ...";
522 
523 	ARGBEGIN{
524 	default:
525 		return cliError(usage);
526 	}ARGEND
527 	if(argc == 0)
528 		return cliError(usage);
529 
530 	vtRLock(fsys->fs->elk);
531 	while(argc > 0){
532 		if((file = fileOpen(fsys->fs, argv[0])) == nil)
533 			consPrint("%s: %R\n", argv[0]);
534 		else{
535 			if(!fileRemove(file, uidadm))
536 				consPrint("%s: %R\n", argv[0]);
537 			fileDecRef(file);
538 		}
539 		argc--;
540 		argv++;
541 	}
542 	vtRUnlock(fsys->fs->elk);
543 
544 	return 1;
545 }
546 
547 static int
548 fsysClri(Fsys* fsys, int argc, char* argv[])
549 {
550 	char *usage = "usage: [fsys name] clri path ...";
551 
552 	ARGBEGIN{
553 	default:
554 		return cliError(usage);
555 	}ARGEND
556 	if(argc == 0)
557 		return cliError(usage);
558 
559 	vtRLock(fsys->fs->elk);
560 	while(argc > 0){
561 		if(!fileClriPath(fsys->fs, argv[0], uidadm))
562 			consPrint("clri %s: %R\n", argv[0]);
563 		argc--;
564 		argv++;
565 	}
566 	vtRUnlock(fsys->fs->elk);
567 
568 	return 1;
569 }
570 
571 /*
572  * Inspect and edit the labels for blocks on disk.
573  */
574 static int
575 fsysLabel(Fsys* fsys, int argc, char* argv[])
576 {
577 	Fs *fs;
578 	Label l;
579 	int n, r;
580 	u32int addr;
581 	Block *b, *bb;
582 	char *usage = "usage: [fsys name] label addr [type state epoch epochClose tag]";
583 
584 	ARGBEGIN{
585 	default:
586 		return cliError(usage);
587 	}ARGEND
588 	if(argc != 1 && argc != 6)
589 		return cliError(usage);
590 
591 	r = 0;
592 	vtRLock(fsys->fs->elk);
593 
594 	fs = fsys->fs;
595 	addr = strtoul(argv[0], 0, 0);
596 	b = cacheLocal(fs->cache, PartData, addr, OReadOnly);
597 	if(b == nil)
598 		goto Out0;
599 
600 	l = b->l;
601 	consPrint("%slabel %#ux %ud %ud %ud %ud %#x\n",
602 		argc==6 ? "old: " : "", addr, l.type, l.state,
603 		l.epoch, l.epochClose, l.tag);
604 
605 	if(argc == 6){
606 		if(strcmp(argv[1], "-") != 0)
607 			l.type = atoi(argv[1]);
608 		if(strcmp(argv[2], "-") != 0)
609 			l.state = atoi(argv[2]);
610 		if(strcmp(argv[3], "-") != 0)
611 			l.epoch = strtoul(argv[3], 0, 0);
612 		if(strcmp(argv[4], "-") != 0)
613 			l.epochClose = strtoul(argv[4], 0, 0);
614 		if(strcmp(argv[5], "-") != 0)
615 			l.tag = strtoul(argv[5], 0, 0);
616 
617 		consPrint("new: label %#ux %ud %ud %ud %ud %#x\n",
618 			addr, l.type, l.state, l.epoch, l.epochClose, l.tag);
619 		bb = _blockSetLabel(b, &l);
620 		if(bb == nil)
621 			goto Out1;
622 		n = 0;
623 		for(;;){
624 			if(blockWrite(bb)){
625 				while(bb->iostate != BioClean){
626 					assert(bb->iostate == BioWriting);
627 					vtSleep(bb->ioready);
628 				}
629 				break;
630 			}
631 			consPrint("blockWrite: %R\n");
632 			if(n++ >= 5){
633 				consPrint("giving up\n");
634 				break;
635 			}
636 			sleep(5*1000);
637 		}
638 		blockPut(bb);
639 	}
640 	r = 1;
641 Out1:
642 	blockPut(b);
643 Out0:
644 	vtRUnlock(fs->elk);
645 
646 	return r;
647 }
648 
649 /*
650  * Inspect and edit the blocks on disk.
651  */
652 static int
653 fsysBlock(Fsys* fsys, int argc, char* argv[])
654 {
655 	Fs *fs;
656 	char *s;
657 	Block *b;
658 	uchar *buf;
659 	u32int addr;
660 	int c, count, i, offset;
661 	char *usage = "usage: [fsys name] block addr offset [count [data]]";
662 
663 	ARGBEGIN{
664 	default:
665 		return cliError(usage);
666 	}ARGEND
667 	if(argc < 2 || argc > 4)
668 		return cliError(usage);
669 
670 	fs = fsys->fs;
671 	addr = strtoul(argv[0], 0, 0);
672 	offset = strtoul(argv[1], 0, 0);
673 	if(offset < 0 || offset >= fs->blockSize){
674 		vtSetError("bad offset");
675 		return 0;
676 	}
677 	if(argc > 2)
678 		count = strtoul(argv[2], 0, 0);
679 	else
680 		count = 100000000;
681 	if(offset+count > fs->blockSize)
682 		count = fs->blockSize - count;
683 
684 	vtRLock(fs->elk);
685 
686 	b = cacheLocal(fs->cache, PartData, addr, argc==4 ? OReadWrite : OReadOnly);
687 	if(b == nil){
688 		vtSetError("cacheLocal %#ux: %R", addr);
689 		vtRUnlock(fs->elk);
690 		return 0;
691 	}
692 
693 	consPrint("\t%sblock %#ux %ud %ud %.*H\n",
694 		argc==4 ? "old: " : "", addr, offset, count, count, b->data+offset);
695 
696 	if(argc == 4){
697 		s = argv[3];
698 		if(strlen(s) != 2*count){
699 			vtSetError("bad data count");
700 			goto Out;
701 		}
702 		buf = vtMemAllocZ(count);
703 		for(i = 0; i < count*2; i++){
704 			if(s[i] >= '0' && s[i] <= '9')
705 				c = s[i] - '0';
706 			else if(s[i] >= 'a' && s[i] <= 'f')
707 				c = s[i] - 'a' + 10;
708 			else if(s[i] >= 'A' && s[i] <= 'F')
709 				c = s[i] - 'A' + 10;
710 			else{
711 				vtSetError("bad hex");
712 				vtMemFree(buf);
713 				goto Out;
714 			}
715 			if((i & 1) == 0)
716 				c <<= 4;
717 			buf[i>>1] |= c;
718 		}
719 		memmove(b->data+offset, buf, count);
720 		consPrint("\tnew: block %#ux %ud %ud %.*H\n",
721 			addr, offset, count, count, b->data+offset);
722 		blockDirty(b);
723 	}
724 
725 Out:
726 	blockPut(b);
727 	vtRUnlock(fs->elk);
728 
729 	return 1;
730 }
731 
732 /*
733  * Free a disk block.
734  */
735 static int
736 fsysBfree(Fsys* fsys, int argc, char* argv[])
737 {
738 	Fs *fs;
739 	Label l;
740 	char *p;
741 	Block *b;
742 	u32int addr;
743 	char *usage = "usage: [fsys name] bfree addr ...";
744 
745 	ARGBEGIN{
746 	default:
747 		return cliError(usage);
748 	}ARGEND
749 	if(argc == 0)
750 		return cliError(usage);
751 
752 	fs = fsys->fs;
753 	vtRLock(fs->elk);
754 	while(argc > 0){
755 		addr = strtoul(argv[0], &p, 0);
756 		if(*p != '\0'){
757 			consPrint("bad address - '%ud'\n", addr);
758 			/* syntax error; let's stop */
759 			vtRUnlock(fs->elk);
760 			return 0;
761 		}
762 		b = cacheLocal(fs->cache, PartData, addr, OReadOnly);
763 		if(b == nil){
764 			consPrint("loading %#ux: %R\n", addr);
765 			continue;
766 		}
767 		l = b->l;
768 		if(l.state == BsFree)
769 			consPrint("%#ux is already free\n", addr);
770 		else{
771 			consPrint("label %#ux %ud %ud %ud %ud %#x\n",
772 				addr, l.type, l.state, l.epoch, l.epochClose, l.tag);
773 			l.state = BsFree;
774 			l.type = BtMax;
775 			l.tag = 0;
776 			l.epoch = 0;
777 			l.epochClose = 0;
778 			if(!blockSetLabel(b, &l, 0))
779 				consPrint("freeing %#ux: %R\n", addr);
780 		}
781 		blockPut(b);
782 		argc--;
783 		argv++;
784 	}
785 	vtRUnlock(fs->elk);
786 
787 	return 1;
788 }
789 
790 static int
791 fsysDf(Fsys *fsys, int argc, char* argv[])
792 {
793 	char *usage = "usage: [fsys name] df";
794 	u32int used, tot, bsize;
795 	Fs *fs;
796 
797 	ARGBEGIN{
798 	default:
799 		return cliError(usage);
800 	}ARGEND
801 	if(argc != 0)
802 		return cliError(usage);
803 
804 	fs = fsys->fs;
805 	cacheCountUsed(fs->cache, fs->elo, &used, &tot, &bsize);
806 	consPrint("\t%s: %,llud used + %,llud free = %,llud (%llud%% used)\n",
807 		fsys->name, used*(vlong)bsize, (tot-used)*(vlong)bsize,
808 		tot*(vlong)bsize, used*100LL/tot);
809 	return 1;
810 }
811 
812 /*
813  * Zero an entry or a pointer.
814  */
815 static int
816 fsysClrep(Fsys* fsys, int argc, char* argv[], int ch)
817 {
818 	Fs *fs;
819 	Entry e;
820 	Block *b;
821 	u32int addr;
822 	int i, max, offset, sz;
823 	uchar zero[VtEntrySize];
824 	char *usage = "usage: [fsys name] clr%c addr offset ...";
825 
826 	ARGBEGIN{
827 	default:
828 		return cliError(usage, ch);
829 	}ARGEND
830 	if(argc < 2)
831 		return cliError(usage, ch);
832 
833 	fs = fsys->fs;
834 	vtRLock(fsys->fs->elk);
835 
836 	addr = strtoul(argv[0], 0, 0);
837 	b = cacheLocal(fs->cache, PartData, addr, argc==4 ? OReadWrite : OReadOnly);
838 	if(b == nil){
839 		vtSetError("cacheLocal %#ux: %R", addr);
840 	Err:
841 		vtRUnlock(fsys->fs->elk);
842 		return 0;
843 	}
844 
845 	switch(ch){
846 	default:
847 		vtSetError("clrep");
848 		goto Err;
849 	case 'e':
850 		if(b->l.type != BtDir){
851 			vtSetError("wrong block type");
852 			goto Err;
853 		}
854 		sz = VtEntrySize;
855 		memset(&e, 0, sizeof e);
856 		entryPack(&e, zero, 0);
857 		break;
858 	case 'p':
859 		if(b->l.type == BtDir || b->l.type == BtData){
860 			vtSetError("wrong block type");
861 			goto Err;
862 		}
863 		sz = VtScoreSize;
864 		memmove(zero, vtZeroScore, VtScoreSize);
865 		break;
866 	}
867 	max = fs->blockSize/sz;
868 
869 	for(i = 1; i < argc; i++){
870 		offset = atoi(argv[i]);
871 		if(offset >= max){
872 			consPrint("\toffset %d too large (>= %d)\n", i, max);
873 			continue;
874 		}
875 		consPrint("\tblock %#ux %d %d %.*H\n", addr, offset*sz, sz, sz, b->data+offset*sz);
876 		memmove(b->data+offset*sz, zero, sz);
877 	}
878 	blockDirty(b);
879 	blockPut(b);
880 	vtRUnlock(fsys->fs->elk);
881 
882 	return 1;
883 }
884 
885 static int
886 fsysClre(Fsys* fsys, int argc, char* argv[])
887 {
888 	return fsysClrep(fsys, argc, argv, 'e');
889 }
890 
891 static int
892 fsysClrp(Fsys* fsys, int argc, char* argv[])
893 {
894 	return fsysClrep(fsys, argc, argv, 'p');
895 }
896 
897 static int
898 fsysEsearch1(File* f, char* s, u32int elo)
899 {
900 	int n, r;
901 	DirEntry de;
902 	DirEntryEnum *dee;
903 	File *ff;
904 	Entry e, ee;
905 	char *t;
906 
907 	dee = deeOpen(f);
908 	if(dee == nil)
909 		return 0;
910 
911 	n = 0;
912 	for(;;){
913 		r = deeRead(dee, &de);
914 		if(r < 0){
915 			consPrint("\tdeeRead %s/%s: %R\n", s, de.elem);
916 			break;
917 		}
918 		if(r == 0)
919 			break;
920 		if(de.mode & ModeSnapshot){
921 			if((ff = fileWalk(f, de.elem)) == nil)
922 				consPrint("\tcannot walk %s/%s: %R\n", s, de.elem);
923 			else{
924 				if(!fileGetSources(ff, &e, &ee))
925 					consPrint("\tcannot get sources for %s/%s: %R\n", s, de.elem);
926 				else if(e.snap != 0 && e.snap < elo){
927 					consPrint("\t%ud\tclri %s/%s\n", e.snap, s, de.elem);
928 					n++;
929 				}
930 				fileDecRef(ff);
931 			}
932 		}
933 		else if(de.mode & ModeDir){
934 			if((ff = fileWalk(f, de.elem)) == nil)
935 				consPrint("\tcannot walk %s/%s: %R\n", s, de.elem);
936 			else{
937 				t = smprint("%s/%s", s, de.elem);
938 				n += fsysEsearch1(ff, t, elo);
939 				vtMemFree(t);
940 				fileDecRef(ff);
941 			}
942 		}
943 		deCleanup(&de);
944 		if(r < 0)
945 			break;
946 	}
947 	deeClose(dee);
948 
949 	return n;
950 }
951 
952 static int
953 fsysEsearch(Fs* fs, char* path, u32int elo)
954 {
955 	int n;
956 	File *f;
957 	DirEntry de;
958 
959 	f = fileOpen(fs, path);
960 	if(f == nil)
961 		return 0;
962 	if(!fileGetDir(f, &de)){
963 		consPrint("\tfileGetDir %s failed: %R\n", path);
964 		fileDecRef(f);
965 		return 0;
966 	}
967 	if((de.mode & ModeDir) == 0){
968 		fileDecRef(f);
969 		deCleanup(&de);
970 		return 0;
971 	}
972 	deCleanup(&de);
973 	n = fsysEsearch1(f, path, elo);
974 	fileDecRef(f);
975 	return n;
976 }
977 
978 static int
979 fsysEpoch(Fsys* fsys, int argc, char* argv[])
980 {
981 	Fs *fs;
982 	int force, n, remove;
983 	u32int low, old;
984 	char *usage = "usage: [fsys name] epoch [[-ry] low]";
985 
986 	force = 0;
987 	remove = 0;
988 	ARGBEGIN{
989 	case 'y':
990 		force = 1;
991 		break;
992 	case 'r':
993 		remove = 1;
994 		break;
995 	default:
996 		return cliError(usage);
997 	}ARGEND
998 	if(argc > 1)
999 		return cliError(usage);
1000 	if(argc > 0)
1001 		low = strtoul(argv[0], 0, 0);
1002 	else
1003 		low = ~(u32int)0;
1004 
1005 	if(low == 0)
1006 		return cliError("low epoch cannot be zero");
1007 
1008 	fs = fsys->fs;
1009 
1010 	vtRLock(fs->elk);
1011 	consPrint("\tlow %ud hi %ud\n", fs->elo, fs->ehi);
1012 	if(low == ~(u32int)0){
1013 		vtRUnlock(fs->elk);
1014 		return 1;
1015 	}
1016 	n = fsysEsearch(fsys->fs, "/archive", low);
1017 	n += fsysEsearch(fsys->fs, "/snapshot", low);
1018 	consPrint("\t%d snapshot%s found with epoch < %ud\n", n, n==1 ? "" : "s", low);
1019 	vtRUnlock(fs->elk);
1020 
1021 	/*
1022 	 * There's a small race here -- a new snapshot with epoch < low might
1023 	 * get introduced now that we unlocked fs->elk.  Low has to
1024 	 * be <= fs->ehi.  Of course, in order for this to happen low has
1025 	 * to be equal to the current fs->ehi _and_ a snapshot has to
1026 	 * run right now.  This is a small enough window that I don't care.
1027 	 */
1028 	if(n != 0 && !force){
1029 		consPrint("\tnot setting low epoch\n");
1030 		return 1;
1031 	}
1032 	old = fs->elo;
1033 	if(!fsEpochLow(fs, low))
1034 		consPrint("\tfsEpochLow: %R\n");
1035 	else{
1036 		consPrint("\told: epoch%s %ud\n", force ? " -y" : "", old);
1037 		consPrint("\tnew: epoch%s %ud\n", force ? " -y" : "", fs->elo);
1038 		if(fs->elo < low)
1039 			consPrint("\twarning: new low epoch < old low epoch\n");
1040 		if(force && remove)
1041 			fsSnapshotRemove(fs);
1042 	}
1043 
1044 	return 1;
1045 }
1046 
1047 static int
1048 fsysCreate(Fsys* fsys, int argc, char* argv[])
1049 {
1050 	int r;
1051 	ulong mode;
1052 	char *elem, *p, *path;
1053 	char *usage = "usage: [fsys name] create path uid gid perm";
1054 	DirEntry de;
1055 	File *file, *parent;
1056 
1057 	ARGBEGIN{
1058 	default:
1059 		return cliError(usage);
1060 	}ARGEND
1061 	if(argc != 4)
1062 		return cliError(usage);
1063 
1064 	if(!fsysParseMode(argv[3], &mode))
1065 		return cliError(usage);
1066 	if(mode&ModeSnapshot)
1067 		return cliError("create - cannot create with snapshot bit set");
1068 
1069 	if(strcmp(argv[1], uidnoworld) == 0)
1070 		return cliError("permission denied");
1071 
1072 	vtRLock(fsys->fs->elk);
1073 	path = vtStrDup(argv[0]);
1074 	if((p = strrchr(path, '/')) != nil){
1075 		*p++ = '\0';
1076 		elem = p;
1077 		p = path;
1078 		if(*p == '\0')
1079 			p = "/";
1080 	}
1081 	else{
1082 		p = "/";
1083 		elem = path;
1084 	}
1085 
1086 	r = 0;
1087 	if((parent = fileOpen(fsys->fs, p)) == nil)
1088 		goto out;
1089 
1090 	file = fileCreate(parent, elem, mode, argv[1]);
1091 	fileDecRef(parent);
1092 	if(file == nil){
1093 		vtSetError("create %s/%s: %R", p, elem);
1094 		goto out;
1095 	}
1096 
1097 	if(!fileGetDir(file, &de)){
1098 		vtSetError("stat failed after create: %R");
1099 		goto out1;
1100 	}
1101 
1102 	if(strcmp(de.gid, argv[2]) != 0){
1103 		vtMemFree(de.gid);
1104 		de.gid = vtStrDup(argv[2]);
1105 		if(!fileSetDir(file, &de, argv[1])){
1106 			vtSetError("wstat failed after create: %R");
1107 			goto out2;
1108 		}
1109 	}
1110 	r = 1;
1111 
1112 out2:
1113 	deCleanup(&de);
1114 out1:
1115 	fileDecRef(file);
1116 out:
1117 	vtMemFree(path);
1118 	vtRUnlock(fsys->fs->elk);
1119 
1120 	return r;
1121 }
1122 
1123 static void
1124 fsysPrintStat(char *prefix, char *file, DirEntry *de)
1125 {
1126 	char buf[64];
1127 
1128 	if(prefix == nil)
1129 		prefix = "";
1130 	consPrint("%sstat %q %q %q %q %s %llud\n", prefix,
1131 		file, de->elem, de->uid, de->gid, fsysModeString(de->mode, buf), de->size);
1132 }
1133 
1134 static int
1135 fsysStat(Fsys* fsys, int argc, char* argv[])
1136 {
1137 	int i;
1138 	File *f;
1139 	DirEntry de;
1140 	char *usage = "usage: [fsys name] stat files...";
1141 
1142 	ARGBEGIN{
1143 	default:
1144 		return cliError(usage);
1145 	}ARGEND
1146 
1147 	if(argc == 0)
1148 		return cliError(usage);
1149 
1150 	vtRLock(fsys->fs->elk);
1151 	for(i=0; i<argc; i++){
1152 		if((f = fileOpen(fsys->fs, argv[i])) == nil){
1153 			consPrint("%s: %R\n", argv[i]);
1154 			continue;
1155 		}
1156 		if(!fileGetDir(f, &de)){
1157 			consPrint("%s: %R\n", argv[i]);
1158 			fileDecRef(f);
1159 			continue;
1160 		}
1161 		fsysPrintStat("\t", argv[i], &de);
1162 		deCleanup(&de);
1163 		fileDecRef(f);
1164 	}
1165 	vtRUnlock(fsys->fs->elk);
1166 	return 1;
1167 }
1168 
1169 static int
1170 fsysWstat(Fsys *fsys, int argc, char* argv[])
1171 {
1172 	File *f;
1173 	char *p;
1174 	DirEntry de;
1175 	char *usage = "usage: [fsys name] wstat file elem uid gid mode length\n"
1176 		"\tuse - for any field to mean don't change";
1177 
1178 	ARGBEGIN{
1179 	default:
1180 		return cliError(usage);
1181 	}ARGEND
1182 
1183 	if(argc != 6)
1184 		return cliError(usage);
1185 
1186 	vtRLock(fsys->fs->elk);
1187 	if((f = fileOpen(fsys->fs, argv[0])) == nil){
1188 		vtSetError("console wstat - walk - %R");
1189 		vtRUnlock(fsys->fs->elk);
1190 		return 0;
1191 	}
1192 	if(!fileGetDir(f, &de)){
1193 		vtSetError("console wstat - stat - %R");
1194 		fileDecRef(f);
1195 		vtRUnlock(fsys->fs->elk);
1196 		return 0;
1197 	}
1198 	fsysPrintStat("\told: w", argv[0], &de);
1199 
1200 	if(strcmp(argv[1], "-") != 0){
1201 		if(!validFileName(argv[1])){
1202 			vtSetError("console wstat - bad elem");
1203 			goto error;
1204 		}
1205 		vtMemFree(de.elem);
1206 		de.elem = vtStrDup(argv[1]);
1207 	}
1208 	if(strcmp(argv[2], "-") != 0){
1209 		if(!validUserName(argv[2])){
1210 			vtSetError("console wstat - bad uid");
1211 			goto error;
1212 		}
1213 		vtMemFree(de.uid);
1214 		de.uid = vtStrDup(argv[2]);
1215 	}
1216 	if(strcmp(argv[3], "-") != 0){
1217 		if(!validUserName(argv[3])){
1218 			vtSetError("console wstat - bad gid");
1219 			goto error;
1220 		}
1221 		vtMemFree(de.gid);
1222 		de.gid = vtStrDup(argv[3]);
1223 	}
1224 	if(strcmp(argv[4], "-") != 0){
1225 		if(!fsysParseMode(argv[4], &de.mode)){
1226 			vtSetError("console wstat - bad mode");
1227 			goto error;
1228 		}
1229 	}
1230 	if(strcmp(argv[5], "-") != 0){
1231 		de.size = strtoull(argv[5], &p, 0);
1232 		if(argv[5][0] == '\0' || *p != '\0' || (vlong)de.size < 0){
1233 			vtSetError("console wstat - bad length");
1234 			goto error;
1235 		}
1236 	}
1237 
1238 	if(!fileSetDir(f, &de, uidadm)){
1239 		vtSetError("console wstat - %R");
1240 		goto error;
1241 	}
1242 	deCleanup(&de);
1243 
1244 	if(!fileGetDir(f, &de)){
1245 		vtSetError("console wstat - stat2 - %R");
1246 		goto error;
1247 	}
1248 	fsysPrintStat("\tnew: w", argv[0], &de);
1249 	deCleanup(&de);
1250 	fileDecRef(f);
1251 	vtRUnlock(fsys->fs->elk);
1252 
1253 	return 1;
1254 
1255 error:
1256 	deCleanup(&de);	/* okay to do this twice */
1257 	fileDecRef(f);
1258 	vtRUnlock(fsys->fs->elk);
1259 	return 0;
1260 }
1261 
1262 static void
1263 fsckClri(Fsck *fsck, char *name, MetaBlock *mb, int i, Block *b)
1264 {
1265 	USED(name);
1266 
1267 	if((fsck->flags&DoClri) == 0)
1268 		return;
1269 
1270 	mbDelete(mb, i);
1271 	mbPack(mb);
1272 	blockDirty(b);
1273 }
1274 
1275 static void
1276 fsckClose(Fsck *fsck, Block *b, u32int epoch)
1277 {
1278 	Label l;
1279 
1280 	if((fsck->flags&DoClose) == 0)
1281 		return;
1282 	l = b->l;
1283 	if(l.state == BsFree || (l.state&BsClosed)){
1284 		consPrint("%#ux is already closed\n", b->addr);
1285 		return;
1286 	}
1287 	if(epoch){
1288 		l.state |= BsClosed;
1289 		l.epochClose = epoch;
1290 	}else
1291 		l.state = BsFree;
1292 
1293 	if(!blockSetLabel(b, &l, 0))
1294 		consPrint("%#ux setlabel: %R\n", b->addr);
1295 }
1296 
1297 static void
1298 fsckClre(Fsck *fsck, Block *b, int offset)
1299 {
1300 	Entry e;
1301 
1302 	if((fsck->flags&DoClre) == 0)
1303 		return;
1304 	if(offset<0 || offset*VtEntrySize >= fsck->bsize){
1305 		consPrint("bad clre\n");
1306 		return;
1307 	}
1308 	memset(&e, 0, sizeof e);
1309 	entryPack(&e, b->data, offset);
1310 	blockDirty(b);
1311 }
1312 
1313 static void
1314 fsckClrp(Fsck *fsck, Block *b, int offset)
1315 {
1316 	if((fsck->flags&DoClrp) == 0)
1317 		return;
1318 	if(offset<0 || offset*VtScoreSize >= fsck->bsize){
1319 		consPrint("bad clre\n");
1320 		return;
1321 	}
1322 	memmove(b->data+offset*VtScoreSize, vtZeroScore, VtScoreSize);
1323 	blockDirty(b);
1324 }
1325 
1326 static int
1327 fsysCheck(Fsys *fsys, int argc, char *argv[])
1328 {
1329 	int i, halting;
1330 	char *usage = "usage: [fsys name] check [-v] [options]";
1331 	Fsck fsck;
1332 	Block *b;
1333 	Super super;
1334 
1335 	memset(&fsck, 0, sizeof fsck);
1336 	fsck.fs = fsys->fs;
1337 	fsck.clri = fsckClri;
1338 	fsck.clre = fsckClre;
1339 	fsck.clrp = fsckClrp;
1340 	fsck.close = fsckClose;
1341 	fsck.print = consPrint;
1342 
1343 	ARGBEGIN{
1344 	default:
1345 		return cliError(usage);
1346 	}ARGEND
1347 
1348 	for(i=0; i<argc; i++){
1349 		if(strcmp(argv[i], "pblock") == 0)
1350 			fsck.printblocks = 1;
1351 		else if(strcmp(argv[i], "pdir") == 0)
1352 			fsck.printdirs = 1;
1353 		else if(strcmp(argv[i], "pfile") == 0)
1354 			fsck.printfiles = 1;
1355 		else if(strcmp(argv[i], "bclose") == 0)
1356 			fsck.flags |= DoClose;
1357 		else if(strcmp(argv[i], "clri") == 0)
1358 			fsck.flags |= DoClri;
1359 		else if(strcmp(argv[i], "clre") == 0)
1360 			fsck.flags |= DoClre;
1361 		else if(strcmp(argv[i], "clrp") == 0)
1362 			fsck.flags |= DoClrp;
1363 		else if(strcmp(argv[i], "fix") == 0)
1364 			fsck.flags |= DoClose|DoClri|DoClre|DoClrp;
1365 		else if(strcmp(argv[i], "venti") == 0)
1366 			fsck.useventi = 1;
1367 		else if(strcmp(argv[i], "snapshot") == 0)
1368 			fsck.walksnapshots = 1;
1369 		else{
1370 			consPrint("unknown option '%s'\n", argv[i]);
1371 			return cliError(usage);
1372 		}
1373 	}
1374 
1375 	halting = fsys->fs->halted==0;
1376 	if(halting)
1377 		fsHalt(fsys->fs);
1378 	if(fsys->fs->arch){
1379 		b = superGet(fsys->fs->cache, &super);
1380 		if(b == nil){
1381 			consPrint("could not load super block\n");
1382 			goto Out;
1383 		}
1384 		blockPut(b);
1385 		if(super.current != NilBlock){
1386 			consPrint("cannot check fs while archiver is running; wait for it to finish\n");
1387 			goto Out;
1388 		}
1389 	}
1390 	fsCheck(&fsck);
1391 	consPrint("fsck: %d clri, %d clre, %d clrp, %d bclose\n",
1392 		fsck.nclri, fsck.nclre, fsck.nclrp, fsck.nclose);
1393 Out:
1394 	if(halting)
1395 		fsUnhalt(fsys->fs);
1396 	return 1;
1397 }
1398 
1399 static int
1400 fsysVenti(char* name, int argc, char* argv[])
1401 {
1402 	int r;
1403 	char *host;
1404 	char *usage = "usage: [fsys name] venti [address]";
1405 	Fsys *fsys;
1406 
1407 	ARGBEGIN{
1408 	default:
1409 		return cliError(usage);
1410 	}ARGEND
1411 
1412 	if(argc == 0)
1413 		host = nil;
1414 	else if(argc == 1)
1415 		host = argv[0];
1416 	else
1417 		return cliError(usage);
1418 
1419 	if((fsys = _fsysGet(name)) == nil)
1420 		return 0;
1421 
1422 	vtLock(fsys->lock);
1423 	if(host == nil)
1424 		host = fsys->venti;
1425 	else{
1426 		vtMemFree(fsys->venti);
1427 		if(host[0])
1428 			fsys->venti = vtStrDup(host);
1429 		else{
1430 			host = nil;
1431 			fsys->venti = nil;
1432 		}
1433 	}
1434 
1435 	/* already open: do a redial */
1436 	if(fsys->fs != nil){
1437 		if(fsys->session == nil){
1438 			vtSetError("file system was opened with -V");
1439 			r = 0;
1440 			goto out;
1441 		}
1442 		r = 1;
1443 		if(!vtRedial(fsys->session, host)
1444 		|| !vtConnect(fsys->session, 0))
1445 			r = 0;
1446 		goto out;
1447 	}
1448 
1449 	/* not yet open: try to dial */
1450 	if(fsys->session)
1451 		vtClose(fsys->session);
1452 	r = 1;
1453 	if((fsys->session = vtDial(host, 0)) == nil
1454 	|| !vtConnect(fsys->session, 0))
1455 		r = 0;
1456 out:
1457 	vtUnlock(fsys->lock);
1458 	fsysPut(fsys);
1459 	return r;
1460 }
1461 
1462 static int
1463 fsysOpen(char* name, int argc, char* argv[])
1464 {
1465 	char *p, *host;
1466 	Fsys *fsys;
1467 	long ncache;
1468 	int noauth, noventi, noperm, rflag, wstatallow;
1469 	char *usage = "usage: fsys name open [-APVWr] [-c ncache]";
1470 
1471 	ncache = 1000;
1472 	noauth = noperm = wstatallow = noventi = 0;
1473 	rflag = OReadWrite;
1474 
1475 	ARGBEGIN{
1476 	default:
1477 		return cliError(usage);
1478 	case 'A':
1479 		noauth = 1;
1480 		break;
1481 	case 'P':
1482 		noperm = 1;
1483 		break;
1484 	case 'V':
1485 		noventi = 1;
1486 		break;
1487 	case 'W':
1488 		wstatallow = 1;
1489 		break;
1490 	case 'c':
1491 		p = ARGF();
1492 		if(p == nil)
1493 			return cliError(usage);
1494 		ncache = strtol(argv[0], &p, 0);
1495 		if(ncache <= 0 || p == argv[0] || *p != '\0')
1496 			return cliError(usage);
1497 		break;
1498 	case 'r':
1499 		rflag = OReadOnly;
1500 		break;
1501 	}ARGEND
1502 	if(argc)
1503 		return cliError(usage);
1504 
1505 	if((fsys = _fsysGet(name)) == nil)
1506 		return 0;
1507 
1508 	vtLock(fsys->lock);
1509 	if(fsys->fs != nil){
1510 		vtSetError(EFsysBusy, fsys->name);
1511 		vtUnlock(fsys->lock);
1512 		fsysPut(fsys);
1513 		return 0;
1514 	}
1515 
1516 	if(noventi){
1517 		if(fsys->session){
1518 			vtClose(fsys->session);
1519 			fsys->session = nil;
1520 		}
1521 	}
1522 	else if(fsys->session == nil){
1523 		if(fsys->venti && fsys->venti[0])
1524 			host = fsys->venti;
1525 		else
1526 			host = nil;
1527 		fsys->session = vtDial(host, 1);
1528 		if(!vtConnect(fsys->session, nil) && !noventi)
1529 			fprint(2, "warning: connecting to venti: %R\n");
1530 	}
1531 	if((fsys->fs = fsOpen(fsys->dev, fsys->session, ncache, rflag)) == nil){
1532 		vtSetError("fsOpen: %R");
1533 		vtUnlock(fsys->lock);
1534 		fsysPut(fsys);
1535 		return 0;
1536 	}
1537 	fsys->fs->name = fsys->name;	/* for better error messages */
1538 	fsys->noauth = noauth;
1539 	fsys->noperm = noperm;
1540 	fsys->wstatallow = wstatallow;
1541 	vtUnlock(fsys->lock);
1542 	fsysPut(fsys);
1543 
1544 	if(strcmp(name, "main") == 0)
1545 		usersFileRead(nil);
1546 
1547 	return 1;
1548 }
1549 
1550 static int
1551 fsysUnconfig(char* name, int argc, char* argv[])
1552 {
1553 	Fsys *fsys, **fp;
1554 	char *usage = "usage: fsys name unconfig";
1555 
1556 	ARGBEGIN{
1557 	default:
1558 		return cliError(usage);
1559 	}ARGEND
1560 	if(argc)
1561 		return cliError(usage);
1562 
1563 	vtLock(sbox.lock);
1564 	fp = &sbox.head;
1565 	for(fsys = *fp; fsys != nil; fsys = fsys->next){
1566 		if(strcmp(fsys->name, name) == 0)
1567 			break;
1568 		fp = &fsys->next;
1569 	}
1570 	if(fsys == nil){
1571 		vtSetError(EFsysNotFound, name);
1572 		vtUnlock(sbox.lock);
1573 		return 0;
1574 	}
1575 	if(fsys->ref != 0 || fsys->fs != nil){
1576 		vtSetError(EFsysBusy, fsys->name);
1577 		vtUnlock(sbox.lock);
1578 		return 0;
1579 	}
1580 	*fp = fsys->next;
1581 	vtUnlock(sbox.lock);
1582 
1583 	if(fsys->session != nil){
1584 		vtClose(fsys->session);
1585 		vtFree(fsys->session);
1586 	}
1587 	if(fsys->venti != nil)
1588 		vtMemFree(fsys->venti);
1589 	if(fsys->dev != nil)
1590 		vtMemFree(fsys->dev);
1591 	if(fsys->name != nil)
1592 		vtMemFree(fsys->name);
1593 	vtMemFree(fsys);
1594 
1595 	return 1;
1596 }
1597 
1598 static int
1599 fsysConfig(char* name, int argc, char* argv[])
1600 {
1601 	Fsys *fsys;
1602 	char *usage = "usage: fsys name config dev";
1603 
1604 	ARGBEGIN{
1605 	default:
1606 		return cliError(usage);
1607 	}ARGEND
1608 	if(argc != 1)
1609 		return cliError(usage);
1610 
1611 	if((fsys = _fsysGet(argv[0])) != nil){
1612 		vtLock(fsys->lock);
1613 		if(fsys->fs != nil){
1614 			vtSetError(EFsysBusy, fsys->name);
1615 			vtUnlock(fsys->lock);
1616 			fsysPut(fsys);
1617 			return 0;
1618 		}
1619 		vtMemFree(fsys->dev);
1620 		fsys->dev = vtStrDup(argv[0]);
1621 		vtUnlock(fsys->lock);
1622 	}
1623 	else if((fsys = fsysAlloc(name, argv[0])) == nil)
1624 		return 0;
1625 
1626 	fsysPut(fsys);
1627 
1628 	return 1;
1629 }
1630 
1631 static struct {
1632 	char*	cmd;
1633 	int	(*f)(Fsys*, int, char**);
1634 	int	(*f1)(char*, int, char**);
1635 } fsyscmd[] = {
1636 	{ "close",	fsysClose, },
1637 	{ "config",	nil, fsysConfig, },
1638 	{ "open",	nil, fsysOpen, },
1639 	{ "unconfig",	nil, fsysUnconfig, },
1640 	{ "venti",	nil, fsysVenti, },
1641 
1642 	{ "bfree",	fsysBfree, },
1643 	{ "block",	fsysBlock, },
1644 	{ "check",	fsysCheck, },
1645 	{ "clre",	fsysClre, },
1646 	{ "clri",	fsysClri, },
1647 	{ "clrp",	fsysClrp, },
1648 	{ "create",	fsysCreate, },
1649 	{ "df",		fsysDf, },
1650 	{ "epoch",	fsysEpoch, },
1651 	{ "halt",	fsysHalt, },
1652 	{ "label",	fsysLabel, },
1653 	{ "remove",	fsysRemove, },
1654 	{ "snap",	fsysSnap, },
1655 	{ "snaptime",	fsysSnapTime, },
1656 	{ "snapclean",	fsysSnapClean, },
1657 	{ "stat",	fsysStat, },
1658 	{ "sync",	fsysSync, },
1659 	{ "unhalt",	fsysUnhalt, },
1660 	{ "wstat",	fsysWstat, },
1661 	{ "vac",	fsysVac, },
1662 
1663 	{ nil,		nil, },
1664 };
1665 
1666 static int
1667 fsysXXX1(Fsys *fsys, int i, int argc, char* argv[])
1668 {
1669 	int r;
1670 
1671 	vtLock(fsys->lock);
1672 	if(fsys->fs == nil){
1673 		vtUnlock(fsys->lock);
1674 		vtSetError(EFsysNotOpen, fsys->name);
1675 		return 0;
1676 	}
1677 
1678 	if(fsys->fs->halted
1679 	&& fsyscmd[i].f != fsysUnhalt && fsyscmd[i].f != fsysCheck){
1680 		vtSetError("file system %s is halted", fsys->name);
1681 		vtUnlock(fsys->lock);
1682 		return 0;
1683 	}
1684 
1685 	r = (*fsyscmd[i].f)(fsys, argc, argv);
1686 	vtUnlock(fsys->lock);
1687 	return r;
1688 }
1689 
1690 static int
1691 fsysXXX(char* name, int argc, char* argv[])
1692 {
1693 	int i, r;
1694 	Fsys *fsys;
1695 
1696 	for(i = 0; fsyscmd[i].cmd != nil; i++){
1697 		if(strcmp(fsyscmd[i].cmd, argv[0]) == 0)
1698 			break;
1699 	}
1700 
1701 	if(fsyscmd[i].cmd == nil){
1702 		vtSetError("unknown command - '%s'", argv[0]);
1703 		return 0;
1704 	}
1705 
1706 	/* some commands want the name... */
1707 	if(fsyscmd[i].f1 != nil){
1708 		if(strcmp(name, FsysAll) == 0){
1709 			vtSetError("cannot use fsys %#q with %#q command", FsysAll, argv[0]);
1710 			return 0;
1711 		}
1712 		return (*fsyscmd[i].f1)(name, argc, argv);
1713 	}
1714 
1715 	/* ... but most commands want the Fsys */
1716 	if(strcmp(name, FsysAll) == 0){
1717 		r = 1;
1718 		vtRLock(sbox.lock);
1719 		for(fsys = sbox.head; fsys != nil; fsys = fsys->next){
1720 			fsys->ref++;
1721 			r = fsysXXX1(fsys, i, argc, argv) && r;
1722 			fsys->ref--;
1723 		}
1724 		vtRUnlock(sbox.lock);
1725 	}else{
1726 		if((fsys = _fsysGet(name)) == nil)
1727 			return 0;
1728 		r = fsysXXX1(fsys, i, argc, argv);
1729 		fsysPut(fsys);
1730 	}
1731 	return r;
1732 }
1733 
1734 static int
1735 cmdFsysXXX(int argc, char* argv[])
1736 {
1737 	char *name;
1738 
1739 	if((name = sbox.curfsys) == nil){
1740 		vtSetError(EFsysNoCurrent, argv[0]);
1741 		return 0;
1742 	}
1743 
1744 	return fsysXXX(name, argc, argv);
1745 }
1746 
1747 static int
1748 cmdFsys(int argc, char* argv[])
1749 {
1750 	Fsys *fsys;
1751 	char *usage = "usage: fsys [name ...]";
1752 
1753 	ARGBEGIN{
1754 	default:
1755 		return cliError(usage);
1756 	}ARGEND
1757 
1758 	if(argc == 0){
1759 		vtRLock(sbox.lock);
1760 		for(fsys = sbox.head; fsys != nil; fsys = fsys->next)
1761 			consPrint("\t%s\n", fsys->name);
1762 		vtRUnlock(sbox.lock);
1763 		return 1;
1764 	}
1765 	if(argc == 1){
1766 		fsys = nil;
1767 		if(strcmp(argv[0], FsysAll) != 0 && (fsys = fsysGet(argv[0])) == nil)
1768 			return 0;
1769 		sbox.curfsys = vtStrDup(argv[0]);
1770 		consPrompt(sbox.curfsys);
1771 		if(fsys)
1772 			fsysPut(fsys);
1773 		return 1;
1774 	}
1775 
1776 	return fsysXXX(argv[0], argc-1, argv+1);
1777 }
1778 
1779 int
1780 fsysInit(void)
1781 {
1782 	int i;
1783 
1784 	fmtinstall('H', encodefmt);
1785 	fmtinstall('V', scoreFmt);
1786 	fmtinstall('R', vtErrFmt);
1787 	fmtinstall('L', labelFmt);
1788 
1789 	sbox.lock = vtLockAlloc();
1790 
1791 	cliAddCmd("fsys", cmdFsys);
1792 	for(i = 0; fsyscmd[i].cmd != nil; i++){
1793 		if(fsyscmd[i].f != nil)
1794 			cliAddCmd(fsyscmd[i].cmd, cmdFsysXXX);
1795 	}
1796 	/* the venti cmd is special: the fs can be either open or closed */
1797 	cliAddCmd("venti", cmdFsysXXX);
1798 	cliAddCmd("printconfig", cmdPrintConfig);
1799 
1800 	return 1;
1801 }
1802