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