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