| #include "stdinc.h" | 
 | #include <bio.h> | 
 | #include "dat.h" | 
 | #include "fns.h" | 
 | #include "9.h" | 
 |  | 
 | struct Fsys { | 
 | 	QLock	lock; | 
 |  | 
 | 	char*	name;		/* copy here & Fs to ease error reporting */ | 
 | 	char*	dev; | 
 | 	char*	venti; | 
 |  | 
 | 	Fs*	fs; | 
 | 	VtConn* session; | 
 | 	int	ref; | 
 |  | 
 | 	int	noauth; | 
 | 	int	noperm; | 
 | 	int	wstatallow; | 
 |  | 
 | 	Fsys*	next; | 
 | }; | 
 |  | 
 | int mempcnt;			/* from fossil.c */ | 
 |  | 
 | int	fsGetBlockSize(Fs *fs); | 
 |  | 
 | static struct { | 
 | 	RWLock	lock; | 
 | 	Fsys*	head; | 
 | 	Fsys*	tail; | 
 |  | 
 | 	char*	curfsys; | 
 | } sbox; | 
 |  | 
 | static char *_argv0; | 
 | #define argv0 _argv0 | 
 |  | 
 | static char FsysAll[] = "all"; | 
 |  | 
 | static char EFsysBusy[] = "fsys: '%s' busy"; | 
 | static char EFsysExists[] = "fsys: '%s' already exists"; | 
 | static char EFsysNoCurrent[] = "fsys: no current fsys"; | 
 | static char EFsysNotFound[] = "fsys: '%s' not found"; | 
 | static char EFsysNotOpen[] = "fsys: '%s' not open"; | 
 |  | 
 | static char * | 
 | ventihost(char *host) | 
 | { | 
 | 	if(host != nil) | 
 | 		return vtstrdup(host); | 
 | 	host = getenv("venti"); | 
 | 	if(host == nil) | 
 | 		host = vtstrdup("$venti"); | 
 | 	return host; | 
 | } | 
 |  | 
 | static void | 
 | prventihost(char *host) | 
 | { | 
 | 	char *vh; | 
 |  | 
 | 	vh = ventihost(host); | 
 | 	fprint(2, "%s: dialing venti at %s\n", | 
 | 		argv0, netmkaddr(vh, 0, "venti")); | 
 | 	free(vh); | 
 | } | 
 |  | 
 | static VtConn * | 
 | myDial(char *host) | 
 | { | 
 | 	prventihost(host); | 
 | 	return vtdial(host); | 
 | } | 
 |  | 
 | static int | 
 | myRedial(VtConn *z, char *host) | 
 | { | 
 | 	prventihost(host); | 
 | 	return vtredial(z, host); | 
 | } | 
 |  | 
 | static Fsys* | 
 | _fsysGet(char* name) | 
 | { | 
 | 	Fsys *fsys; | 
 |  | 
 | 	if(name == nil || name[0] == '\0') | 
 | 		name = "main"; | 
 |  | 
 | 	rlock(&sbox.lock); | 
 | 	for(fsys = sbox.head; fsys != nil; fsys = fsys->next){ | 
 | 		if(strcmp(name, fsys->name) == 0){ | 
 | 			fsys->ref++; | 
 | 			break; | 
 | 		} | 
 | 	} | 
 | 	runlock(&sbox.lock); | 
 | 	if(fsys == nil) | 
 | 		werrstr(EFsysNotFound, name); | 
 | 	return fsys; | 
 | } | 
 |  | 
 | static int | 
 | cmdPrintConfig(int argc, char* argv[]) | 
 | { | 
 | 	Fsys *fsys; | 
 | 	char *usage = "usage: printconfig"; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 |  | 
 | 	if(argc) | 
 | 		return cliError(usage); | 
 |  | 
 | 	rlock(&sbox.lock); | 
 | 	for(fsys = sbox.head; fsys != nil; fsys = fsys->next){ | 
 | 		consPrint("\tfsys %s config %s\n", fsys->name, fsys->dev); | 
 | 		if(fsys->venti && fsys->venti[0]) | 
 | 			consPrint("\tfsys %s venti %q\n", fsys->name, | 
 | 				fsys->venti); | 
 | 	} | 
 | 	runlock(&sbox.lock); | 
 | 	return 1; | 
 | } | 
 |  | 
 | Fsys* | 
 | fsysGet(char* name) | 
 | { | 
 | 	Fsys *fsys; | 
 |  | 
 | 	if((fsys = _fsysGet(name)) == nil) | 
 | 		return nil; | 
 |  | 
 | 	qlock(&fsys->lock); | 
 | 	if(fsys->fs == nil){ | 
 | 		werrstr(EFsysNotOpen, fsys->name); | 
 | 		qunlock(&fsys->lock); | 
 | 		fsysPut(fsys); | 
 | 		return nil; | 
 | 	} | 
 | 	qunlock(&fsys->lock); | 
 |  | 
 | 	return fsys; | 
 | } | 
 |  | 
 | char* | 
 | fsysGetName(Fsys* fsys) | 
 | { | 
 | 	return fsys->name; | 
 | } | 
 |  | 
 | Fsys* | 
 | fsysIncRef(Fsys* fsys) | 
 | { | 
 | 	wlock(&sbox.lock); | 
 | 	fsys->ref++; | 
 | 	wunlock(&sbox.lock); | 
 |  | 
 | 	return fsys; | 
 | } | 
 |  | 
 | void | 
 | fsysPut(Fsys* fsys) | 
 | { | 
 | 	wlock(&sbox.lock); | 
 | 	assert(fsys->ref > 0); | 
 | 	fsys->ref--; | 
 | 	wunlock(&sbox.lock); | 
 | } | 
 |  | 
 | Fs* | 
 | fsysGetFs(Fsys* fsys) | 
 | { | 
 | 	assert(fsys != nil && fsys->fs != nil); | 
 |  | 
 | 	return fsys->fs; | 
 | } | 
 |  | 
 | void | 
 | fsysFsRlock(Fsys* fsys) | 
 | { | 
 | 	rlock(&fsys->fs->elk); | 
 | } | 
 |  | 
 | void | 
 | fsysFsRUnlock(Fsys* fsys) | 
 | { | 
 | 	runlock(&fsys->fs->elk); | 
 | } | 
 |  | 
 | int | 
 | fsysNoAuthCheck(Fsys* fsys) | 
 | { | 
 | 	return fsys->noauth; | 
 | } | 
 |  | 
 | int | 
 | fsysNoPermCheck(Fsys* fsys) | 
 | { | 
 | 	return fsys->noperm; | 
 | } | 
 |  | 
 | int | 
 | fsysWstatAllow(Fsys* fsys) | 
 | { | 
 | 	return fsys->wstatallow; | 
 | } | 
 |  | 
 | static char modechars[] = "YUGalLdHSATs"; | 
 | static ulong modebits[] = { | 
 | 	ModeSticky, | 
 | 	ModeSetUid, | 
 | 	ModeSetGid, | 
 | 	ModeAppend, | 
 | 	ModeExclusive, | 
 | 	ModeLink, | 
 | 	ModeDir, | 
 | 	ModeHidden, | 
 | 	ModeSystem, | 
 | 	ModeArchive, | 
 | 	ModeTemporary, | 
 | 	ModeSnapshot, | 
 | 	0 | 
 | }; | 
 |  | 
 | char* | 
 | fsysModeString(ulong mode, char *buf) | 
 | { | 
 | 	int i; | 
 | 	char *p; | 
 |  | 
 | 	p = buf; | 
 | 	for(i=0; modebits[i]; i++) | 
 | 		if(mode & modebits[i]) | 
 | 			*p++ = modechars[i]; | 
 | 	sprint(p, "%luo", mode&0777); | 
 | 	return buf; | 
 | } | 
 |  | 
 | int | 
 | fsysParseMode(char* s, ulong* mode) | 
 | { | 
 | 	ulong x, y; | 
 | 	char *p; | 
 |  | 
 | 	x = 0; | 
 | 	for(; *s < '0' || *s > '9'; s++){ | 
 | 		if(*s == 0) | 
 | 			return 0; | 
 | 		p = strchr(modechars, *s); | 
 | 		if(p == nil) | 
 | 			return 0; | 
 | 		x |= modebits[p-modechars]; | 
 | 	} | 
 | 	y = strtoul(s, &p, 8); | 
 | 	if(*p != '\0' || y > 0777) | 
 | 		return 0; | 
 | 	*mode = x|y; | 
 | 	return 1; | 
 | } | 
 |  | 
 | File* | 
 | fsysGetRoot(Fsys* fsys, char* name) | 
 | { | 
 | 	File *root, *sub; | 
 |  | 
 | 	assert(fsys != nil && fsys->fs != nil); | 
 |  | 
 | 	root = fsGetRoot(fsys->fs); | 
 | 	if(name == nil || strcmp(name, "") == 0) | 
 | 		return root; | 
 |  | 
 | 	sub = fileWalk(root, name); | 
 | 	fileDecRef(root); | 
 |  | 
 | 	return sub; | 
 | } | 
 |  | 
 | static Fsys* | 
 | fsysAlloc(char* name, char* dev) | 
 | { | 
 | 	Fsys *fsys; | 
 |  | 
 | 	wlock(&sbox.lock); | 
 | 	for(fsys = sbox.head; fsys != nil; fsys = fsys->next){ | 
 | 		if(strcmp(fsys->name, name) != 0) | 
 | 			continue; | 
 | 		werrstr(EFsysExists, name); | 
 | 		wunlock(&sbox.lock); | 
 | 		return nil; | 
 | 	} | 
 |  | 
 | 	fsys = vtmallocz(sizeof(Fsys)); | 
 | 	fsys->name = vtstrdup(name); | 
 | 	fsys->dev = vtstrdup(dev); | 
 |  | 
 | 	fsys->ref = 1; | 
 |  | 
 | 	if(sbox.tail != nil) | 
 | 		sbox.tail->next = fsys; | 
 | 	else | 
 | 		sbox.head = fsys; | 
 | 	sbox.tail = fsys; | 
 | 	wunlock(&sbox.lock); | 
 |  | 
 | 	return fsys; | 
 | } | 
 |  | 
 | static int | 
 | fsysClose(Fsys* fsys, int argc, char* argv[]) | 
 | { | 
 | 	char *usage = "usage: [fsys name] close"; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 | 	if(argc) | 
 | 		return cliError(usage); | 
 |  | 
 | 	return cliError("close isn't working yet; halt %s and then kill fossil", | 
 | 		fsys->name); | 
 |  | 
 | 	/* | 
 | 	 * Oooh. This could be hard. What if fsys->ref != 1? | 
 | 	 * Also, fsClose() either does the job or panics, can we | 
 | 	 * gracefully detect it's still busy? | 
 | 	 * | 
 | 	 * More thought and care needed here. | 
 | 	fsClose(fsys->fs); | 
 | 	fsys->fs = nil; | 
 | 	vtfreeconn(fsys->session); | 
 | 	fsys->session = nil; | 
 |  | 
 | 	if(sbox.curfsys != nil && strcmp(fsys->name, sbox.curfsys) == 0){ | 
 | 		sbox.curfsys = nil; | 
 | 		consPrompt(nil); | 
 | 	} | 
 |  | 
 | 	return 1; | 
 | 	 */ | 
 | } | 
 |  | 
 | static int | 
 | fsysVac(Fsys* fsys, int argc, char* argv[]) | 
 | { | 
 | 	uchar score[VtScoreSize]; | 
 | 	char *usage = "usage: [fsys name] vac path"; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 | 	if(argc != 1) | 
 | 		return cliError(usage); | 
 |  | 
 | 	if(!fsVac(fsys->fs, argv[0], score)) | 
 | 		return 0; | 
 |  | 
 | 	consPrint("vac:%V\n", score); | 
 | 	return 1; | 
 | } | 
 |  | 
 | static int | 
 | fsysSnap(Fsys* fsys, int argc, char* argv[]) | 
 | { | 
 | 	int doarchive; | 
 | 	char *usage = "usage: [fsys name] snap [-a] [-s /active] [-d /archive/yyyy/mmmm]"; | 
 | 	char *src, *dst; | 
 |  | 
 | 	src = nil; | 
 | 	dst = nil; | 
 | 	doarchive = 0; | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	case 'a': | 
 | 		doarchive = 1; | 
 | 		break; | 
 | 	case 'd': | 
 | 		if((dst = ARGF()) == nil) | 
 | 			return cliError(usage); | 
 | 		break; | 
 | 	case 's': | 
 | 		if((src = ARGF()) == nil) | 
 | 			return cliError(usage); | 
 | 		break; | 
 | 	}ARGEND | 
 | 	if(argc) | 
 | 		return cliError(usage); | 
 |  | 
 | 	if(!fsSnapshot(fsys->fs, src, dst, doarchive)) | 
 | 		return 0; | 
 |  | 
 | 	return 1; | 
 | } | 
 |  | 
 | static int | 
 | fsysSnapClean(Fsys *fsys, int argc, char* argv[]) | 
 | { | 
 | 	u32int arch, snap, life; | 
 | 	char *usage = "usage: [fsys name] snapclean [maxminutes]\n"; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 |  | 
 | 	if(argc > 1) | 
 | 		return cliError(usage); | 
 | 	if(argc == 1) | 
 | 		life = atoi(argv[0]); | 
 | 	else | 
 | 		snapGetTimes(fsys->fs->snap, &arch, &snap, &life); | 
 |  | 
 | 	fsSnapshotCleanup(fsys->fs, life); | 
 | 	return 1; | 
 | } | 
 |  | 
 | static int | 
 | fsysSnapTime(Fsys* fsys, int argc, char* argv[]) | 
 | { | 
 | 	char buf[128], *x; | 
 | 	int hh, mm, changed; | 
 | 	u32int arch, snap, life; | 
 | 	char *usage = "usage: [fsys name] snaptime [-a hhmm] [-s snapminutes] [-t maxminutes]"; | 
 |  | 
 | 	changed = 0; | 
 | 	snapGetTimes(fsys->fs->snap, &arch, &snap, &life); | 
 | 	ARGBEGIN{ | 
 | 	case 'a': | 
 | 		changed = 1; | 
 | 		x = ARGF(); | 
 | 		if(x == nil) | 
 | 			return cliError(usage); | 
 | 		if(strcmp(x, "none") == 0){ | 
 | 			arch = ~(u32int)0; | 
 | 			break; | 
 | 		} | 
 | 		if(strlen(x) != 4 || strspn(x, "0123456789") != 4) | 
 | 			return cliError(usage); | 
 | 		hh = (x[0]-'0')*10 + x[1]-'0'; | 
 | 		mm = (x[2]-'0')*10 + x[3]-'0'; | 
 | 		if(hh >= 24 || mm >= 60) | 
 | 			return cliError(usage); | 
 | 		arch = hh*60+mm; | 
 | 		break; | 
 | 	case 's': | 
 | 		changed = 1; | 
 | 		x = ARGF(); | 
 | 		if(x == nil) | 
 | 			return cliError(usage); | 
 | 		if(strcmp(x, "none") == 0){ | 
 | 			snap = ~(u32int)0; | 
 | 			break; | 
 | 		} | 
 | 		snap = atoi(x); | 
 | 		break; | 
 | 	case 't': | 
 | 		changed = 1; | 
 | 		x = ARGF(); | 
 | 		if(x == nil) | 
 | 			return cliError(usage); | 
 | 		if(strcmp(x, "none") == 0){ | 
 | 			life = ~(u32int)0; | 
 | 			break; | 
 | 		} | 
 | 		life = atoi(x); | 
 | 		break; | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 | 	if(argc > 0) | 
 | 		return cliError(usage); | 
 |  | 
 | 	if(changed){ | 
 | 		snapSetTimes(fsys->fs->snap, arch, snap, life); | 
 | 		return 1; | 
 | 	} | 
 | 	snapGetTimes(fsys->fs->snap, &arch, &snap, &life); | 
 | 	if(arch != ~(u32int)0) | 
 | 		sprint(buf, "-a %02d%02d", arch/60, arch%60); | 
 | 	else | 
 | 		sprint(buf, "-a none"); | 
 | 	if(snap != ~(u32int)0) | 
 | 		sprint(buf+strlen(buf), " -s %d", snap); | 
 | 	else | 
 | 		sprint(buf+strlen(buf), " -s none"); | 
 | 	if(life != ~(u32int)0) | 
 | 		sprint(buf+strlen(buf), " -t %ud", life); | 
 | 	else | 
 | 		sprint(buf+strlen(buf), " -t none"); | 
 | 	consPrint("\tsnaptime %s\n", buf); | 
 | 	return 1; | 
 | } | 
 |  | 
 | static int | 
 | fsysSync(Fsys* fsys, int argc, char* argv[]) | 
 | { | 
 | 	char *usage = "usage: [fsys name] sync"; | 
 | 	int n; | 
 | 	 | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 | 	if(argc > 0) | 
 | 		return cliError(usage); | 
 |  | 
 | 	n = cacheDirty(fsys->fs->cache); | 
 | 	fsSync(fsys->fs); | 
 | 	consPrint("\t%s sync: wrote %d blocks\n", fsys->name, n); | 
 | 	return 1; | 
 | } | 
 |  | 
 | static int | 
 | fsysHalt(Fsys *fsys, int argc, char* argv[]) | 
 | { | 
 | 	char *usage = "usage: [fsys name] halt"; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 | 	if(argc > 0) | 
 | 		return cliError(usage); | 
 |  | 
 | 	fsHalt(fsys->fs); | 
 | 	return 1; | 
 | } | 
 |  | 
 | static int | 
 | fsysUnhalt(Fsys *fsys, int argc, char* argv[]) | 
 | { | 
 | 	char *usage = "usage: [fsys name] unhalt"; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 | 	if(argc > 0) | 
 | 		return cliError(usage); | 
 |  | 
 | 	if(!fsys->fs->halted) | 
 | 		return cliError("file system %s not halted", fsys->name); | 
 |  | 
 | 	fsUnhalt(fsys->fs); | 
 | 	return 1; | 
 | } | 
 |  | 
 | static int | 
 | fsysRemove(Fsys* fsys, int argc, char* argv[]) | 
 | { | 
 | 	File *file; | 
 | 	char *usage = "usage: [fsys name] remove path ..."; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 | 	if(argc == 0) | 
 | 		return cliError(usage); | 
 |  | 
 | 	rlock(&fsys->fs->elk); | 
 | 	while(argc > 0){ | 
 | 		if((file = fileOpen(fsys->fs, argv[0])) == nil) | 
 | 			consPrint("%s: %r\n", argv[0]); | 
 | 		else{ | 
 | 			if(!fileRemove(file, uidadm)) | 
 | 				consPrint("%s: %r\n", argv[0]); | 
 | 			fileDecRef(file); | 
 | 		} | 
 | 		argc--; | 
 | 		argv++; | 
 | 	} | 
 | 	runlock(&fsys->fs->elk); | 
 |  | 
 | 	return 1; | 
 | } | 
 |  | 
 | static int | 
 | fsysClri(Fsys* fsys, int argc, char* argv[]) | 
 | { | 
 | 	char *usage = "usage: [fsys name] clri path ..."; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 | 	if(argc == 0) | 
 | 		return cliError(usage); | 
 |  | 
 | 	rlock(&fsys->fs->elk); | 
 | 	while(argc > 0){ | 
 | 		if(!fileClriPath(fsys->fs, argv[0], uidadm)) | 
 | 			consPrint("clri %s: %r\n", argv[0]); | 
 | 		argc--; | 
 | 		argv++; | 
 | 	} | 
 | 	runlock(&fsys->fs->elk); | 
 |  | 
 | 	return 1; | 
 | } | 
 |  | 
 | /* | 
 |  * Inspect and edit the labels for blocks on disk. | 
 |  */ | 
 | static int | 
 | fsysLabel(Fsys* fsys, int argc, char* argv[]) | 
 | { | 
 | 	Fs *fs; | 
 | 	Label l; | 
 | 	int n, r; | 
 | 	u32int addr; | 
 | 	Block *b, *bb; | 
 | 	char *usage = "usage: [fsys name] label addr [type state epoch epochClose tag]"; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 | 	if(argc != 1 && argc != 6) | 
 | 		return cliError(usage); | 
 |  | 
 | 	r = 0; | 
 | 	rlock(&fsys->fs->elk); | 
 |  | 
 | 	fs = fsys->fs; | 
 | 	addr = strtoul(argv[0], 0, 0); | 
 | 	b = cacheLocal(fs->cache, PartData, addr, OReadOnly); | 
 | 	if(b == nil) | 
 | 		goto Out0; | 
 |  | 
 | 	l = b->l; | 
 | 	consPrint("%slabel %#ux %ud %ud %ud %ud %#x\n", | 
 | 		argc==6 ? "old: " : "", addr, l.type, l.state, | 
 | 		l.epoch, l.epochClose, l.tag); | 
 |  | 
 | 	if(argc == 6){ | 
 | 		if(strcmp(argv[1], "-") != 0) | 
 | 			l.type = atoi(argv[1]); | 
 | 		if(strcmp(argv[2], "-") != 0) | 
 | 			l.state = atoi(argv[2]); | 
 | 		if(strcmp(argv[3], "-") != 0) | 
 | 			l.epoch = strtoul(argv[3], 0, 0); | 
 | 		if(strcmp(argv[4], "-") != 0) | 
 | 			l.epochClose = strtoul(argv[4], 0, 0); | 
 | 		if(strcmp(argv[5], "-") != 0) | 
 | 			l.tag = strtoul(argv[5], 0, 0); | 
 |  | 
 | 		consPrint("new: label %#ux %ud %ud %ud %ud %#x\n", | 
 | 			addr, l.type, l.state, l.epoch, l.epochClose, l.tag); | 
 | 		bb = _blockSetLabel(b, &l); | 
 | 		if(bb == nil) | 
 | 			goto Out1; | 
 | 		n = 0; | 
 | 		for(;;){ | 
 | 			if(blockWrite(bb, Waitlock)){ | 
 | 				while(bb->iostate != BioClean){ | 
 | 					assert(bb->iostate == BioWriting); | 
 | 					rsleep(&bb->ioready); | 
 | 				} | 
 | 				break; | 
 | 			} | 
 | 			consPrint("blockWrite: %r\n"); | 
 | 			if(n++ >= 5){ | 
 | 				consPrint("giving up\n"); | 
 | 				break; | 
 | 			} | 
 | 			sleep(5*1000); | 
 | 		} | 
 | 		blockPut(bb); | 
 | 	} | 
 | 	r = 1; | 
 | Out1: | 
 | 	blockPut(b); | 
 | Out0: | 
 | 	runlock(&fs->elk); | 
 |  | 
 | 	return r; | 
 | } | 
 |  | 
 | /* | 
 |  * Inspect and edit the blocks on disk. | 
 |  */ | 
 | static int | 
 | fsysBlock(Fsys* fsys, int argc, char* argv[]) | 
 | { | 
 | 	Fs *fs; | 
 | 	char *s; | 
 | 	Block *b; | 
 | 	uchar *buf; | 
 | 	u32int addr; | 
 | 	int c, count, i, offset; | 
 | 	char *usage = "usage: [fsys name] block addr offset [count [data]]"; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 | 	if(argc < 2 || argc > 4) | 
 | 		return cliError(usage); | 
 |  | 
 | 	fs = fsys->fs; | 
 | 	addr = strtoul(argv[0], 0, 0); | 
 | 	offset = strtoul(argv[1], 0, 0); | 
 | 	if(offset < 0 || offset >= fs->blockSize){ | 
 | 		werrstr("bad offset"); | 
 | 		return 0; | 
 | 	} | 
 | 	if(argc > 2) | 
 | 		count = strtoul(argv[2], 0, 0); | 
 | 	else | 
 | 		count = 100000000; | 
 | 	if(offset+count > fs->blockSize) | 
 | 		count = fs->blockSize - count; | 
 |  | 
 | 	rlock(&fs->elk); | 
 |  | 
 | 	b = cacheLocal(fs->cache, PartData, addr, argc==4 ? OReadWrite : OReadOnly); | 
 | 	if(b == nil){ | 
 | 		werrstr("cacheLocal %#ux: %r", addr); | 
 | 		runlock(&fs->elk); | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	consPrint("\t%sblock %#ux %ud %ud %.*H\n", | 
 | 		argc==4 ? "old: " : "", addr, offset, count, count, b->data+offset); | 
 |  | 
 | 	if(argc == 4){ | 
 | 		s = argv[3]; | 
 | 		if(strlen(s) != 2*count){ | 
 | 			werrstr("bad data count"); | 
 | 			goto Out; | 
 | 		} | 
 | 		buf = vtmallocz(count); | 
 | 		for(i = 0; i < count*2; i++){ | 
 | 			if(s[i] >= '0' && s[i] <= '9') | 
 | 				c = s[i] - '0'; | 
 | 			else if(s[i] >= 'a' && s[i] <= 'f') | 
 | 				c = s[i] - 'a' + 10; | 
 | 			else if(s[i] >= 'A' && s[i] <= 'F') | 
 | 				c = s[i] - 'A' + 10; | 
 | 			else{ | 
 | 				werrstr("bad hex"); | 
 | 				vtfree(buf); | 
 | 				goto Out; | 
 | 			} | 
 | 			if((i & 1) == 0) | 
 | 				c <<= 4; | 
 | 			buf[i>>1] |= c; | 
 | 		} | 
 | 		memmove(b->data+offset, buf, count); | 
 | 		consPrint("\tnew: block %#ux %ud %ud %.*H\n", | 
 | 			addr, offset, count, count, b->data+offset); | 
 | 		blockDirty(b); | 
 | 	} | 
 |  | 
 | Out: | 
 | 	blockPut(b); | 
 | 	runlock(&fs->elk); | 
 |  | 
 | 	return 1; | 
 | } | 
 |  | 
 | /* | 
 |  * Free a disk block. | 
 |  */ | 
 | static int | 
 | fsysBfree(Fsys* fsys, int argc, char* argv[]) | 
 | { | 
 | 	Fs *fs; | 
 | 	Label l; | 
 | 	char *p; | 
 | 	Block *b; | 
 | 	u32int addr; | 
 | 	char *usage = "usage: [fsys name] bfree addr ..."; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 | 	if(argc == 0) | 
 | 		return cliError(usage); | 
 |  | 
 | 	fs = fsys->fs; | 
 | 	rlock(&fs->elk); | 
 | 	while(argc > 0){ | 
 | 		addr = strtoul(argv[0], &p, 0); | 
 | 		if(*p != '\0'){ | 
 | 			consPrint("bad address - '%ud'\n", addr); | 
 | 			/* syntax error; let's stop */ | 
 | 			runlock(&fs->elk); | 
 | 			return 0; | 
 | 		} | 
 | 		b = cacheLocal(fs->cache, PartData, addr, OReadOnly); | 
 | 		if(b == nil){ | 
 | 			consPrint("loading %#ux: %r\n", addr); | 
 | 			continue; | 
 | 		} | 
 | 		l = b->l; | 
 | 		if(l.state == BsFree) | 
 | 			consPrint("%#ux is already free\n", addr); | 
 | 		else{ | 
 | 			consPrint("label %#ux %ud %ud %ud %ud %#x\n", | 
 | 				addr, l.type, l.state, l.epoch, l.epochClose, l.tag); | 
 | 			l.state = BsFree; | 
 | 			l.type = BtMax; | 
 | 			l.tag = 0; | 
 | 			l.epoch = 0; | 
 | 			l.epochClose = 0; | 
 | 			if(!blockSetLabel(b, &l, 0)) | 
 | 				consPrint("freeing %#ux: %r\n", addr); | 
 | 		} | 
 | 		blockPut(b); | 
 | 		argc--; | 
 | 		argv++; | 
 | 	} | 
 | 	runlock(&fs->elk); | 
 |  | 
 | 	return 1; | 
 | } | 
 |  | 
 | static int | 
 | fsysDf(Fsys *fsys, int argc, char* argv[]) | 
 | { | 
 | 	char *usage = "usage: [fsys name] df"; | 
 | 	u32int used, tot, bsize; | 
 | 	Fs *fs; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 | 	if(argc != 0) | 
 | 		return cliError(usage); | 
 |  | 
 | 	fs = fsys->fs; | 
 | 	cacheCountUsed(fs->cache, fs->elo, &used, &tot, &bsize); | 
 | 	consPrint("\t%s: %,llud used + %,llud free = %,llud (%.1f%% used)\n", | 
 | 		fsys->name, used*(vlong)bsize, (tot-used)*(vlong)bsize, | 
 | 		tot*(vlong)bsize, used*100.0/tot); | 
 | 	return 1; | 
 | } | 
 |  | 
 | /* | 
 |  * Zero an entry or a pointer. | 
 |  */ | 
 | static int | 
 | fsysClrep(Fsys* fsys, int argc, char* argv[], int ch) | 
 | { | 
 | 	Fs *fs; | 
 | 	Entry e; | 
 | 	Block *b; | 
 | 	u32int addr; | 
 | 	int i, max, offset, sz; | 
 | 	uchar zero[VtEntrySize]; | 
 | 	char *usage = "usage: [fsys name] clr%c addr offset ..."; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage, ch); | 
 | 	}ARGEND | 
 | 	if(argc < 2) | 
 | 		return cliError(usage, ch); | 
 |  | 
 | 	fs = fsys->fs; | 
 | 	rlock(&fsys->fs->elk); | 
 |  | 
 | 	addr = strtoul(argv[0], 0, 0); | 
 | 	b = cacheLocal(fs->cache, PartData, addr, argc==4 ? OReadWrite : OReadOnly); | 
 | 	if(b == nil){ | 
 | 		werrstr("cacheLocal %#ux: %r", addr); | 
 | 	Err: | 
 | 		runlock(&fsys->fs->elk); | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	switch(ch){ | 
 | 	default: | 
 | 		werrstr("clrep"); | 
 | 		goto Err; | 
 | 	case 'e': | 
 | 		if(b->l.type != BtDir){ | 
 | 			werrstr("wrong block type"); | 
 | 			goto Err; | 
 | 		} | 
 | 		sz = VtEntrySize; | 
 | 		memset(&e, 0, sizeof e); | 
 | 		entryPack(&e, zero, 0); | 
 | 		break; | 
 | 	case 'p': | 
 | 		if(b->l.type == BtDir || b->l.type == BtData){ | 
 | 			werrstr("wrong block type"); | 
 | 			goto Err; | 
 | 		} | 
 | 		sz = VtScoreSize; | 
 | 		memmove(zero, vtzeroscore, VtScoreSize); | 
 | 		break; | 
 | 	} | 
 | 	max = fs->blockSize/sz; | 
 |  | 
 | 	for(i = 1; i < argc; i++){ | 
 | 		offset = atoi(argv[i]); | 
 | 		if(offset >= max){ | 
 | 			consPrint("\toffset %d too large (>= %d)\n", i, max); | 
 | 			continue; | 
 | 		} | 
 | 		consPrint("\tblock %#ux %d %d %.*H\n", addr, offset*sz, sz, sz, b->data+offset*sz); | 
 | 		memmove(b->data+offset*sz, zero, sz); | 
 | 	} | 
 | 	blockDirty(b); | 
 | 	blockPut(b); | 
 | 	runlock(&fsys->fs->elk); | 
 |  | 
 | 	return 1; | 
 | } | 
 |  | 
 | static int | 
 | fsysClre(Fsys* fsys, int argc, char* argv[]) | 
 | { | 
 | 	return fsysClrep(fsys, argc, argv, 'e'); | 
 | } | 
 |  | 
 | static int | 
 | fsysClrp(Fsys* fsys, int argc, char* argv[]) | 
 | { | 
 | 	return fsysClrep(fsys, argc, argv, 'p'); | 
 | } | 
 |  | 
 | static int | 
 | fsysEsearch1(File* f, char* s, u32int elo) | 
 | { | 
 | 	int n, r; | 
 | 	DirEntry de; | 
 | 	DirEntryEnum *dee; | 
 | 	File *ff; | 
 | 	Entry e, ee; | 
 | 	char *t; | 
 |  | 
 | 	dee = deeOpen(f); | 
 | 	if(dee == nil) | 
 | 		return 0; | 
 |  | 
 | 	n = 0; | 
 | 	for(;;){ | 
 | 		r = deeRead(dee, &de); | 
 | 		if(r < 0){ | 
 | 			consPrint("\tdeeRead %s/%s: %r\n", s, de.elem); | 
 | 			break; | 
 | 		} | 
 | 		if(r == 0) | 
 | 			break; | 
 | 		if(de.mode & ModeSnapshot){ | 
 | 			if((ff = fileWalk(f, de.elem)) == nil) | 
 | 				consPrint("\tcannot walk %s/%s: %r\n", s, de.elem); | 
 | 			else{ | 
 | 				if(!fileGetSources(ff, &e, &ee)) | 
 | 					consPrint("\tcannot get sources for %s/%s: %r\n", s, de.elem); | 
 | 				else if(e.snap != 0 && e.snap < elo){ | 
 | 					consPrint("\t%ud\tclri %s/%s\n", e.snap, s, de.elem); | 
 | 					n++; | 
 | 				} | 
 | 				fileDecRef(ff); | 
 | 			} | 
 | 		} | 
 | 		else if(de.mode & ModeDir){ | 
 | 			if((ff = fileWalk(f, de.elem)) == nil) | 
 | 				consPrint("\tcannot walk %s/%s: %r\n", s, de.elem); | 
 | 			else{ | 
 | 				t = smprint("%s/%s", s, de.elem); | 
 | 				n += fsysEsearch1(ff, t, elo); | 
 | 				vtfree(t); | 
 | 				fileDecRef(ff); | 
 | 			} | 
 | 		} | 
 | 		deCleanup(&de); | 
 | 		if(r < 0) | 
 | 			break; | 
 | 	} | 
 | 	deeClose(dee); | 
 |  | 
 | 	return n; | 
 | } | 
 |  | 
 | static int | 
 | fsysEsearch(Fs* fs, char* path, u32int elo) | 
 | { | 
 | 	int n; | 
 | 	File *f; | 
 | 	DirEntry de; | 
 |  | 
 | 	f = fileOpen(fs, path); | 
 | 	if(f == nil) | 
 | 		return 0; | 
 | 	if(!fileGetDir(f, &de)){ | 
 | 		consPrint("\tfileGetDir %s failed: %r\n", path); | 
 | 		fileDecRef(f); | 
 | 		return 0; | 
 | 	} | 
 | 	if((de.mode & ModeDir) == 0){ | 
 | 		fileDecRef(f); | 
 | 		deCleanup(&de); | 
 | 		return 0; | 
 | 	} | 
 | 	deCleanup(&de); | 
 | 	n = fsysEsearch1(f, path, elo); | 
 | 	fileDecRef(f); | 
 | 	return n; | 
 | } | 
 |  | 
 | static int | 
 | fsysEpoch(Fsys* fsys, int argc, char* argv[]) | 
 | { | 
 | 	Fs *fs; | 
 | 	int force, n, remove; | 
 | 	u32int low, old; | 
 | 	char *usage = "usage: [fsys name] epoch [[-ry] low]"; | 
 |  | 
 | 	force = 0; | 
 | 	remove = 0; | 
 | 	ARGBEGIN{ | 
 | 	case 'y': | 
 | 		force = 1; | 
 | 		break; | 
 | 	case 'r': | 
 | 		remove = 1; | 
 | 		break; | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 | 	if(argc > 1) | 
 | 		return cliError(usage); | 
 | 	if(argc > 0) | 
 | 		low = strtoul(argv[0], 0, 0); | 
 | 	else | 
 | 		low = ~(u32int)0; | 
 |  | 
 | 	if(low == 0) | 
 | 		return cliError("low epoch cannot be zero"); | 
 |  | 
 | 	fs = fsys->fs; | 
 |  | 
 | 	rlock(&fs->elk); | 
 | 	consPrint("\tlow %ud hi %ud\n", fs->elo, fs->ehi); | 
 | 	if(low == ~(u32int)0){ | 
 | 		runlock(&fs->elk); | 
 | 		return 1; | 
 | 	} | 
 | 	n = fsysEsearch(fsys->fs, "/archive", low); | 
 | 	n += fsysEsearch(fsys->fs, "/snapshot", low); | 
 | 	consPrint("\t%d snapshot%s found with epoch < %ud\n", n, n==1 ? "" : "s", low); | 
 | 	runlock(&fs->elk); | 
 |  | 
 | 	/* | 
 | 	 * There's a small race here -- a new snapshot with epoch < low might | 
 | 	 * get introduced now that we unlocked fs->elk.  Low has to | 
 | 	 * be <= fs->ehi.  Of course, in order for this to happen low has | 
 | 	 * to be equal to the current fs->ehi _and_ a snapshot has to | 
 | 	 * run right now.  This is a small enough window that I don't care. | 
 | 	 */ | 
 | 	if(n != 0 && !force){ | 
 | 		consPrint("\tnot setting low epoch\n"); | 
 | 		return 1; | 
 | 	} | 
 | 	old = fs->elo; | 
 | 	if(!fsEpochLow(fs, low)) | 
 | 		consPrint("\tfsEpochLow: %r\n"); | 
 | 	else{ | 
 | 		consPrint("\told: epoch%s %ud\n", force ? " -y" : "", old); | 
 | 		consPrint("\tnew: epoch%s %ud\n", force ? " -y" : "", fs->elo); | 
 | 		if(fs->elo < low) | 
 | 			consPrint("\twarning: new low epoch < old low epoch\n"); | 
 | 		if(force && remove) | 
 | 			fsSnapshotRemove(fs); | 
 | 	} | 
 |  | 
 | 	return 1; | 
 | } | 
 |  | 
 | static int | 
 | fsysCreate(Fsys* fsys, int argc, char* argv[]) | 
 | { | 
 | 	int r; | 
 | 	ulong mode; | 
 | 	char *elem, *p, *path; | 
 | 	char *usage = "usage: [fsys name] create path uid gid perm"; | 
 | 	DirEntry de; | 
 | 	File *file, *parent; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 | 	if(argc != 4) | 
 | 		return cliError(usage); | 
 |  | 
 | 	if(!fsysParseMode(argv[3], &mode)) | 
 | 		return cliError(usage); | 
 | 	if(mode&ModeSnapshot) | 
 | 		return cliError("create - cannot create with snapshot bit set"); | 
 |  | 
 | 	if(strcmp(argv[1], uidnoworld) == 0) | 
 | 		return cliError("permission denied"); | 
 |  | 
 | 	rlock(&fsys->fs->elk); | 
 | 	path = vtstrdup(argv[0]); | 
 | 	if((p = strrchr(path, '/')) != nil){ | 
 | 		*p++ = '\0'; | 
 | 		elem = p; | 
 | 		p = path; | 
 | 		if(*p == '\0') | 
 | 			p = "/"; | 
 | 	} | 
 | 	else{ | 
 | 		p = "/"; | 
 | 		elem = path; | 
 | 	} | 
 |  | 
 | 	r = 0; | 
 | 	if((parent = fileOpen(fsys->fs, p)) == nil) | 
 | 		goto out; | 
 |  | 
 | 	file = fileCreate(parent, elem, mode, argv[1]); | 
 | 	fileDecRef(parent); | 
 | 	if(file == nil){ | 
 | 		werrstr("create %s/%s: %r", p, elem); | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	if(!fileGetDir(file, &de)){ | 
 | 		werrstr("stat failed after create: %r"); | 
 | 		goto out1; | 
 | 	} | 
 |  | 
 | 	if(strcmp(de.gid, argv[2]) != 0){ | 
 | 		vtfree(de.gid); | 
 | 		de.gid = vtstrdup(argv[2]); | 
 | 		if(!fileSetDir(file, &de, argv[1])){ | 
 | 			werrstr("wstat failed after create: %r"); | 
 | 			goto out2; | 
 | 		} | 
 | 	} | 
 | 	r = 1; | 
 |  | 
 | out2: | 
 | 	deCleanup(&de); | 
 | out1: | 
 | 	fileDecRef(file); | 
 | out: | 
 | 	vtfree(path); | 
 | 	runlock(&fsys->fs->elk); | 
 |  | 
 | 	return r; | 
 | } | 
 |  | 
 | static void | 
 | fsysPrintStat(char *prefix, char *file, DirEntry *de) | 
 | { | 
 | 	char buf[64]; | 
 |  | 
 | 	if(prefix == nil) | 
 | 		prefix = ""; | 
 | 	consPrint("%sstat %q %q %q %q %s %llud\n", prefix, | 
 | 		file, de->elem, de->uid, de->gid, fsysModeString(de->mode, buf), de->size); | 
 | } | 
 |  | 
 | static int | 
 | fsysStat(Fsys* fsys, int argc, char* argv[]) | 
 | { | 
 | 	int i; | 
 | 	File *f; | 
 | 	DirEntry de; | 
 | 	char *usage = "usage: [fsys name] stat files..."; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 |  | 
 | 	if(argc == 0) | 
 | 		return cliError(usage); | 
 |  | 
 | 	rlock(&fsys->fs->elk); | 
 | 	for(i=0; i<argc; i++){ | 
 | 		if((f = fileOpen(fsys->fs, argv[i])) == nil){ | 
 | 			consPrint("%s: %r\n", argv[i]); | 
 | 			continue; | 
 | 		} | 
 | 		if(!fileGetDir(f, &de)){ | 
 | 			consPrint("%s: %r\n", argv[i]); | 
 | 			fileDecRef(f); | 
 | 			continue; | 
 | 		} | 
 | 		fsysPrintStat("\t", argv[i], &de); | 
 | 		deCleanup(&de); | 
 | 		fileDecRef(f); | 
 | 	} | 
 | 	runlock(&fsys->fs->elk); | 
 | 	return 1; | 
 | } | 
 |  | 
 | static int | 
 | fsysWstat(Fsys *fsys, int argc, char* argv[]) | 
 | { | 
 | 	File *f; | 
 | 	char *p; | 
 | 	DirEntry de; | 
 | 	char *usage = "usage: [fsys name] wstat file elem uid gid mode length\n" | 
 | 		"\tuse - for any field to mean don't change"; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 |  | 
 | 	if(argc != 6) | 
 | 		return cliError(usage); | 
 |  | 
 | 	rlock(&fsys->fs->elk); | 
 | 	if((f = fileOpen(fsys->fs, argv[0])) == nil){ | 
 | 		werrstr("console wstat - walk - %r"); | 
 | 		runlock(&fsys->fs->elk); | 
 | 		return 0; | 
 | 	} | 
 | 	if(!fileGetDir(f, &de)){ | 
 | 		werrstr("console wstat - stat - %r"); | 
 | 		fileDecRef(f); | 
 | 		runlock(&fsys->fs->elk); | 
 | 		return 0; | 
 | 	} | 
 | 	fsysPrintStat("\told: w", argv[0], &de); | 
 |  | 
 | 	if(strcmp(argv[1], "-") != 0){ | 
 | 		if(!validFileName(argv[1])){ | 
 | 			werrstr("console wstat - bad elem"); | 
 | 			goto error; | 
 | 		} | 
 | 		vtfree(de.elem); | 
 | 		de.elem = vtstrdup(argv[1]); | 
 | 	} | 
 | 	if(strcmp(argv[2], "-") != 0){ | 
 | 		if(!validUserName(argv[2])){ | 
 | 			werrstr("console wstat - bad uid"); | 
 | 			goto error; | 
 | 		} | 
 | 		vtfree(de.uid); | 
 | 		de.uid = vtstrdup(argv[2]); | 
 | 	} | 
 | 	if(strcmp(argv[3], "-") != 0){ | 
 | 		if(!validUserName(argv[3])){ | 
 | 			werrstr("console wstat - bad gid"); | 
 | 			goto error; | 
 | 		} | 
 | 		vtfree(de.gid); | 
 | 		de.gid = vtstrdup(argv[3]); | 
 | 	} | 
 | 	if(strcmp(argv[4], "-") != 0){ | 
 | 		if(!fsysParseMode(argv[4], &de.mode)){ | 
 | 			werrstr("console wstat - bad mode"); | 
 | 			goto error; | 
 | 		} | 
 | 	} | 
 | 	if(strcmp(argv[5], "-") != 0){ | 
 | 		de.size = strtoull(argv[5], &p, 0); | 
 | 		if(argv[5][0] == '\0' || *p != '\0' || (vlong)de.size < 0){ | 
 | 			werrstr("console wstat - bad length"); | 
 | 			goto error; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if(!fileSetDir(f, &de, uidadm)){ | 
 | 		werrstr("console wstat - %r"); | 
 | 		goto error; | 
 | 	} | 
 | 	deCleanup(&de); | 
 |  | 
 | 	if(!fileGetDir(f, &de)){ | 
 | 		werrstr("console wstat - stat2 - %r"); | 
 | 		goto error; | 
 | 	} | 
 | 	fsysPrintStat("\tnew: w", argv[0], &de); | 
 | 	deCleanup(&de); | 
 | 	fileDecRef(f); | 
 | 	runlock(&fsys->fs->elk); | 
 |  | 
 | 	return 1; | 
 |  | 
 | error: | 
 | 	deCleanup(&de);	/* okay to do this twice */ | 
 | 	fileDecRef(f); | 
 | 	runlock(&fsys->fs->elk); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void | 
 | fsckClri(Fsck *fsck, char *name, MetaBlock *mb, int i, Block *b) | 
 | { | 
 | 	USED(name); | 
 |  | 
 | 	if((fsck->flags&DoClri) == 0) | 
 | 		return; | 
 |  | 
 | 	mbDelete(mb, i); | 
 | 	mbPack(mb); | 
 | 	blockDirty(b);	 | 
 | } | 
 |  | 
 | static void | 
 | fsckClose(Fsck *fsck, Block *b, u32int epoch) | 
 | { | 
 | 	Label l; | 
 |  | 
 | 	if((fsck->flags&DoClose) == 0) | 
 | 		return; | 
 | 	l = b->l; | 
 | 	if(l.state == BsFree || (l.state&BsClosed)){ | 
 | 		consPrint("%#ux is already closed\n", b->addr); | 
 | 		return; | 
 | 	} | 
 | 	if(epoch){	 | 
 | 		l.state |= BsClosed; | 
 | 		l.epochClose = epoch; | 
 | 	}else | 
 | 		l.state = BsFree; | 
 | 		 | 
 | 	if(!blockSetLabel(b, &l, 0)) | 
 | 		consPrint("%#ux setlabel: %r\n", b->addr); | 
 | } | 
 |  | 
 | static void | 
 | fsckClre(Fsck *fsck, Block *b, int offset) | 
 | { | 
 | 	Entry e; | 
 |  | 
 | 	if((fsck->flags&DoClre) == 0) | 
 | 		return; | 
 | 	if(offset<0 || offset*VtEntrySize >= fsck->bsize){ | 
 | 		consPrint("bad clre\n"); | 
 | 		return; | 
 | 	} | 
 | 	memset(&e, 0, sizeof e); | 
 | 	entryPack(&e, b->data, offset); | 
 | 	blockDirty(b); | 
 | } | 
 |  | 
 | static void | 
 | fsckClrp(Fsck *fsck, Block *b, int offset) | 
 | { | 
 | 	if((fsck->flags&DoClrp) == 0) | 
 | 		return; | 
 | 	if(offset<0 || offset*VtScoreSize >= fsck->bsize){ | 
 | 		consPrint("bad clre\n"); | 
 | 		return; | 
 | 	} | 
 | 	memmove(b->data+offset*VtScoreSize, vtzeroscore, VtScoreSize); | 
 | 	blockDirty(b); | 
 | } | 
 |  | 
 | static int | 
 | fsysCheck(Fsys *fsys, int argc, char *argv[]) | 
 | { | 
 | 	int i, halting; | 
 | 	char *usage = "usage: [fsys name] check [-v] [options]"; | 
 | 	Fsck fsck; | 
 | 	Block *b; | 
 | 	Super super; | 
 |  | 
 | 	memset(&fsck, 0, sizeof fsck); | 
 | 	fsck.fs = fsys->fs; | 
 | 	fsck.clri = fsckClri; | 
 | 	fsck.clre = fsckClre; | 
 | 	fsck.clrp = fsckClrp; | 
 | 	fsck.close = fsckClose; | 
 | 	fsck.print = consPrint; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 |  | 
 | 	for(i=0; i<argc; i++){ | 
 | 		if(strcmp(argv[i], "pblock") == 0) | 
 | 			fsck.printblocks = 1; | 
 | 		else if(strcmp(argv[i], "pdir") == 0) | 
 | 			fsck.printdirs = 1; | 
 | 		else if(strcmp(argv[i], "pfile") == 0) | 
 | 			fsck.printfiles = 1; | 
 | 		else if(strcmp(argv[i], "bclose") == 0) | 
 | 			fsck.flags |= DoClose; | 
 | 		else if(strcmp(argv[i], "clri") == 0) | 
 | 			fsck.flags |= DoClri; | 
 | 		else if(strcmp(argv[i], "clre") == 0) | 
 | 			fsck.flags |= DoClre; | 
 | 		else if(strcmp(argv[i], "clrp") == 0) | 
 | 			fsck.flags |= DoClrp; | 
 | 		else if(strcmp(argv[i], "fix") == 0) | 
 | 			fsck.flags |= DoClose|DoClri|DoClre|DoClrp; | 
 | 		else if(strcmp(argv[i], "venti") == 0) | 
 | 			fsck.useventi = 1; | 
 | 		else if(strcmp(argv[i], "snapshot") == 0) | 
 | 			fsck.walksnapshots = 1; | 
 | 		else{ | 
 | 			consPrint("unknown option '%s'\n", argv[i]); | 
 | 			return cliError(usage); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	halting = fsys->fs->halted==0; | 
 | 	if(halting) | 
 | 		fsHalt(fsys->fs); | 
 | 	if(fsys->fs->arch){ | 
 | 		b = superGet(fsys->fs->cache, &super); | 
 | 		if(b == nil){ | 
 | 			consPrint("could not load super block\n"); | 
 | 			goto Out; | 
 | 		} | 
 | 		blockPut(b); | 
 | 		if(super.current != NilBlock){ | 
 | 			consPrint("cannot check fs while archiver is running; " | 
 | 				"wait for it to finish\n"); | 
 | 			goto Out; | 
 | 		} | 
 | 	} | 
 | 	fsCheck(&fsck); | 
 | 	consPrint("fsck: %d clri, %d clre, %d clrp, %d bclose\n", | 
 | 		fsck.nclri, fsck.nclre, fsck.nclrp, fsck.nclose); | 
 | Out: | 
 | 	if(halting) | 
 | 		fsUnhalt(fsys->fs); | 
 | 	return 1; | 
 | } | 
 |  | 
 | static int | 
 | fsysVenti(char* name, int argc, char* argv[]) | 
 | { | 
 | 	int r; | 
 | 	char *host; | 
 | 	char *usage = "usage: [fsys name] venti [address]"; | 
 | 	Fsys *fsys; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 |  | 
 | 	if(argc == 0) | 
 | 		host = nil; | 
 | 	else if(argc == 1) | 
 | 		host = argv[0]; | 
 | 	else | 
 | 		return cliError(usage); | 
 |  | 
 | 	if((fsys = _fsysGet(name)) == nil) | 
 | 		return 0; | 
 |  | 
 | 	qlock(&fsys->lock); | 
 | 	if(host == nil) | 
 | 		host = fsys->venti; | 
 | 	else{ | 
 | 		vtfree(fsys->venti); | 
 | 		if(host[0]) | 
 | 			fsys->venti = vtstrdup(host); | 
 | 		else{ | 
 | 			host = nil; | 
 | 			fsys->venti = nil; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* already open: do a redial */ | 
 | 	if(fsys->fs != nil){ | 
 | 		if(fsys->session == nil){ | 
 | 			werrstr("file system was opened with -V"); | 
 | 			r = 0; | 
 | 			goto out; | 
 | 		} | 
 | 		r = 1; | 
 | 		if(myRedial(fsys->session, host) < 0 | 
 | 		|| vtconnect(fsys->session) < 0) | 
 | 			r = 0; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	/* not yet open: try to dial */ | 
 | 	if(fsys->session) | 
 | 		vtfreeconn(fsys->session); | 
 | 	r = 1; | 
 | 	if((fsys->session = myDial(host)) == nil | 
 | 	|| vtconnect(fsys->session) < 0) | 
 | 		r = 0; | 
 | out: | 
 | 	qunlock(&fsys->lock); | 
 | 	fsysPut(fsys); | 
 | 	return r; | 
 | } | 
 |  | 
 | static ulong | 
 | freemem(void) | 
 | { | 
 | 	int nf, pgsize = 0; | 
 | 	uvlong size, userpgs = 0, userused = 0; | 
 | 	char *ln, *sl; | 
 | 	char *fields[2]; | 
 | 	Biobuf *bp; | 
 |  | 
 | 	size = 64*1024*1024; | 
 | 	bp = Bopen("#c/swap", OREAD); | 
 | 	if (bp != nil) { | 
 | 		while ((ln = Brdline(bp, '\n')) != nil) { | 
 | 			ln[Blinelen(bp)-1] = '\0'; | 
 | 			nf = tokenize(ln, fields, nelem(fields)); | 
 | 			if (nf != 2) | 
 | 				continue; | 
 | 			if (strcmp(fields[1], "pagesize") == 0) | 
 | 				pgsize = atoi(fields[0]); | 
 | 			else if (strcmp(fields[1], "user") == 0) { | 
 | 				sl = strchr(fields[0], '/'); | 
 | 				if (sl == nil) | 
 | 					continue; | 
 | 				userpgs = atoll(sl+1); | 
 | 				userused = atoll(fields[0]); | 
 | 			} | 
 | 		} | 
 | 		Bterm(bp); | 
 | 		if (pgsize > 0 && userpgs > 0) | 
 | 			size = (userpgs - userused) * pgsize; | 
 | 	} | 
 | 	/* cap it to keep the size within 32 bits */ | 
 | 	if (size >= 3840UL * 1024 * 1024) | 
 | 		size = 3840UL * 1024 * 1024; | 
 | 	return size; | 
 | } | 
 |  | 
 | static int | 
 | fsysOpen(char* name, int argc, char* argv[]) | 
 | { | 
 | 	char *p, *host; | 
 | 	Fsys *fsys; | 
 | 	int noauth, noventi, noperm, rflag, wstatallow, noatimeupd; | 
 | 	long ncache; | 
 | 	char *usage = "usage: fsys name open [-APVWr] [-c ncache]"; | 
 |  | 
 | 	ncache = 1000; | 
 | 	noauth = noperm = wstatallow = noventi = noatimeupd = 0; | 
 | 	rflag = OReadWrite; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	case 'A': | 
 | 		noauth = 1; | 
 | 		break; | 
 | 	case 'P': | 
 | 		noperm = 1; | 
 | 		break; | 
 | 	case 'V': | 
 | 		noventi = 1; | 
 | 		break; | 
 | 	case 'W': | 
 | 		wstatallow = 1; | 
 | 		break; | 
 | 	case 'a': | 
 | 		noatimeupd = 1; | 
 | 		break; | 
 | 	case 'c': | 
 | 		p = ARGF(); | 
 | 		if(p == nil) | 
 | 			return cliError(usage); | 
 | 		ncache = strtol(argv[0], &p, 0); | 
 | 		if(ncache <= 0 || p == argv[0] || *p != '\0') | 
 | 			return cliError(usage); | 
 | 		break; | 
 | 	case 'r': | 
 | 		rflag = OReadOnly; | 
 | 		break; | 
 | 	}ARGEND | 
 | 	if(argc) | 
 | 		return cliError(usage); | 
 |  | 
 | 	if((fsys = _fsysGet(name)) == nil) | 
 | 		return 0; | 
 |  | 
 | 	/* automatic memory sizing? */ | 
 | 	if(mempcnt > 0) { | 
 | 		/* TODO: 8K is a hack; use the actual block size */ | 
 | 		ncache = (((vlong)freemem() * mempcnt) / 100) / (8*1024); | 
 | 		if (ncache < 100) | 
 | 			ncache = 100; | 
 | 	} | 
 |  | 
 | 	qlock(&fsys->lock); | 
 | 	if(fsys->fs != nil){ | 
 | 		werrstr(EFsysBusy, fsys->name); | 
 | 		qunlock(&fsys->lock); | 
 | 		fsysPut(fsys); | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	if(noventi){ | 
 | 		if(fsys->session){ | 
 | 			vtfreeconn(fsys->session); | 
 | 			fsys->session = nil; | 
 | 		} | 
 | 	} | 
 | 	else if(fsys->session == nil){ | 
 | 		if(fsys->venti && fsys->venti[0]) | 
 | 			host = fsys->venti; | 
 | 		else | 
 | 			host = nil; | 
 |  | 
 | 		if((fsys->session = myDial(host)) == nil | 
 | 		|| vtconnect(fsys->session) < 0 && !noventi) | 
 | 			fprint(2, "warning: connecting to venti: %r\n"); | 
 | 	} | 
 | 	if((fsys->fs = fsOpen(fsys->dev, fsys->session, ncache, rflag)) == nil){ | 
 | 		werrstr("fsOpen: %r"); | 
 | 		qunlock(&fsys->lock); | 
 | 		fsysPut(fsys); | 
 | 		return 0; | 
 | 	} | 
 | 	fsys->fs->name = fsys->name;	/* for better error messages */ | 
 | 	fsys->noauth = noauth; | 
 | 	fsys->noperm = noperm; | 
 | 	fsys->wstatallow = wstatallow; | 
 | 	fsys->fs->noatimeupd = noatimeupd; | 
 | 	qunlock(&fsys->lock); | 
 | 	fsysPut(fsys); | 
 |  | 
 | 	if(strcmp(name, "main") == 0) | 
 | 		usersFileRead(nil); | 
 |  | 
 | 	return 1; | 
 | } | 
 |  | 
 | static int | 
 | fsysUnconfig(char* name, int argc, char* argv[]) | 
 | { | 
 | 	Fsys *fsys, **fp; | 
 | 	char *usage = "usage: fsys name unconfig"; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 | 	if(argc) | 
 | 		return cliError(usage); | 
 |  | 
 | 	wlock(&sbox.lock); | 
 | 	fp = &sbox.head; | 
 | 	for(fsys = *fp; fsys != nil; fsys = fsys->next){ | 
 | 		if(strcmp(fsys->name, name) == 0) | 
 | 			break; | 
 | 		fp = &fsys->next; | 
 | 	} | 
 | 	if(fsys == nil){ | 
 | 		werrstr(EFsysNotFound, name); | 
 | 		wunlock(&sbox.lock); | 
 | 		return 0; | 
 | 	} | 
 | 	if(fsys->ref != 0 || fsys->fs != nil){ | 
 | 		werrstr(EFsysBusy, fsys->name); | 
 | 		wunlock(&sbox.lock); | 
 | 		return 0; | 
 | 	} | 
 | 	*fp = fsys->next; | 
 | 	wunlock(&sbox.lock); | 
 |  | 
 | 	if(fsys->session != nil) | 
 | 		vtfreeconn(fsys->session); | 
 | 	if(fsys->venti != nil) | 
 | 		vtfree(fsys->venti); | 
 | 	if(fsys->dev != nil) | 
 | 		vtfree(fsys->dev); | 
 | 	if(fsys->name != nil) | 
 | 		vtfree(fsys->name); | 
 | 	vtfree(fsys); | 
 |  | 
 | 	return 1; | 
 | } | 
 |  | 
 | static int | 
 | fsysConfig(char* name, int argc, char* argv[]) | 
 | { | 
 | 	Fsys *fsys; | 
 | 	char *part; | 
 | 	char *usage = "usage: fsys name config [dev]"; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 | 	if(argc > 1) | 
 | 		return cliError(usage); | 
 |  | 
 | 	if(argc == 0) | 
 | 		part = foptname; | 
 | 	else | 
 | 		part = argv[0]; | 
 |  | 
 | 	if((fsys = _fsysGet(part)) != nil){ | 
 | 		qlock(&fsys->lock); | 
 | 		if(fsys->fs != nil){ | 
 | 			werrstr(EFsysBusy, fsys->name); | 
 | 			qunlock(&fsys->lock); | 
 | 			fsysPut(fsys); | 
 | 			return 0; | 
 | 		} | 
 | 		vtfree(fsys->dev); | 
 | 		fsys->dev = vtstrdup(part); | 
 | 		qunlock(&fsys->lock); | 
 | 	} | 
 | 	else if((fsys = fsysAlloc(name, part)) == nil) | 
 | 		return 0; | 
 |  | 
 | 	fsysPut(fsys); | 
 | 	return 1; | 
 | } | 
 |  | 
 | static struct { | 
 | 	char*	cmd; | 
 | 	int	(*f)(Fsys*, int, char**); | 
 | 	int	(*f1)(char*, int, char**); | 
 | } fsyscmd[] = { | 
 | 	{ "close",	fsysClose, }, | 
 | 	{ "config",	nil, fsysConfig, }, | 
 | 	{ "open",	nil, fsysOpen, }, | 
 | 	{ "unconfig",	nil, fsysUnconfig, }, | 
 | 	{ "venti",	nil, fsysVenti, }, | 
 |  | 
 | 	{ "bfree",	fsysBfree, }, | 
 | 	{ "block",	fsysBlock, }, | 
 | 	{ "check",	fsysCheck, }, | 
 | 	{ "clre",	fsysClre, }, | 
 | 	{ "clri",	fsysClri, }, | 
 | 	{ "clrp",	fsysClrp, }, | 
 | 	{ "create",	fsysCreate, }, | 
 | 	{ "df",		fsysDf, }, | 
 | 	{ "epoch",	fsysEpoch, }, | 
 | 	{ "halt",	fsysHalt, }, | 
 | 	{ "label",	fsysLabel, }, | 
 | 	{ "remove",	fsysRemove, }, | 
 | 	{ "snap",	fsysSnap, }, | 
 | 	{ "snaptime",	fsysSnapTime, }, | 
 | 	{ "snapclean",	fsysSnapClean, }, | 
 | 	{ "stat",	fsysStat, }, | 
 | 	{ "sync",	fsysSync, }, | 
 | 	{ "unhalt",	fsysUnhalt, }, | 
 | 	{ "wstat",	fsysWstat, }, | 
 | 	{ "vac",	fsysVac, }, | 
 |  | 
 | 	{ nil,		nil, }, | 
 | }; | 
 |  | 
 | static int | 
 | fsysXXX1(Fsys *fsys, int i, int argc, char* argv[]) | 
 | { | 
 | 	int r; | 
 |  | 
 | 	qlock(&fsys->lock); | 
 | 	if(fsys->fs == nil){ | 
 | 		qunlock(&fsys->lock); | 
 | 		werrstr(EFsysNotOpen, fsys->name); | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	if(fsys->fs->halted | 
 | 	&& fsyscmd[i].f != fsysUnhalt && fsyscmd[i].f != fsysCheck){ | 
 | 		werrstr("file system %s is halted", fsys->name); | 
 | 		qunlock(&fsys->lock); | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	r = (*fsyscmd[i].f)(fsys, argc, argv); | 
 | 	qunlock(&fsys->lock); | 
 | 	return r; | 
 | } | 
 |  | 
 | static int | 
 | fsysXXX(char* name, int argc, char* argv[]) | 
 | { | 
 | 	int i, r; | 
 | 	Fsys *fsys; | 
 |  | 
 | 	for(i = 0; fsyscmd[i].cmd != nil; i++){ | 
 | 		if(strcmp(fsyscmd[i].cmd, argv[0]) == 0) | 
 | 			break; | 
 | 	} | 
 |  | 
 | 	if(fsyscmd[i].cmd == nil){ | 
 | 		werrstr("unknown command - '%s'", argv[0]); | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	/* some commands want the name... */ | 
 | 	if(fsyscmd[i].f1 != nil){ | 
 | 		if(strcmp(name, FsysAll) == 0){ | 
 | 			werrstr("cannot use fsys %#q with %#q command", FsysAll, argv[0]); | 
 | 			return 0; | 
 | 		} | 
 | 		return (*fsyscmd[i].f1)(name, argc, argv); | 
 | 	} | 
 |  | 
 | 	/* ... but most commands want the Fsys */ | 
 | 	if(strcmp(name, FsysAll) == 0){ | 
 | 		r = 1; | 
 | 		rlock(&sbox.lock); | 
 | 		for(fsys = sbox.head; fsys != nil; fsys = fsys->next){ | 
 | 			fsys->ref++; | 
 | 			r = fsysXXX1(fsys, i, argc, argv) && r; | 
 | 			fsys->ref--; | 
 | 		} | 
 | 		runlock(&sbox.lock); | 
 | 	}else{ | 
 | 		if((fsys = _fsysGet(name)) == nil) | 
 | 			return 0; | 
 | 		r = fsysXXX1(fsys, i, argc, argv); | 
 | 		fsysPut(fsys); | 
 | 	} | 
 | 	return r; | 
 | } | 
 |  | 
 | static int | 
 | cmdFsysXXX(int argc, char* argv[]) | 
 | { | 
 | 	char *name; | 
 |  | 
 | 	if((name = sbox.curfsys) == nil){ | 
 | 		werrstr(EFsysNoCurrent, argv[0]); | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	return fsysXXX(name, argc, argv); | 
 | } | 
 |  | 
 | static int | 
 | cmdFsys(int argc, char* argv[]) | 
 | { | 
 | 	Fsys *fsys; | 
 | 	char *usage = "usage: fsys [name ...]"; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	default: | 
 | 		return cliError(usage); | 
 | 	}ARGEND | 
 |  | 
 | 	if(argc == 0){ | 
 | 		rlock(&sbox.lock); | 
 | 		currfsysname = sbox.head->name; | 
 | 		for(fsys = sbox.head; fsys != nil; fsys = fsys->next) | 
 | 			consPrint("\t%s\n", fsys->name); | 
 | 		runlock(&sbox.lock); | 
 | 		return 1; | 
 | 	} | 
 | 	if(argc == 1){ | 
 | 		fsys = nil; | 
 | 		if(strcmp(argv[0], FsysAll) != 0 && (fsys = fsysGet(argv[0])) == nil) | 
 | 			return 0; | 
 | 		sbox.curfsys = vtstrdup(argv[0]); | 
 | 		consPrompt(sbox.curfsys); | 
 | 		if(fsys) | 
 | 			fsysPut(fsys); | 
 | 		return 1; | 
 | 	} | 
 |  | 
 | 	return fsysXXX(argv[0], argc-1, argv+1); | 
 | } | 
 |  | 
 | int | 
 | fsysInit(void) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	fmtinstall('H', encodefmt); | 
 | 	fmtinstall('V', scoreFmt); | 
 | 	fmtinstall('L', labelFmt); | 
 |  | 
 | 	cliAddCmd("fsys", cmdFsys); | 
 | 	for(i = 0; fsyscmd[i].cmd != nil; i++){ | 
 | 		if(fsyscmd[i].f != nil) | 
 | 			cliAddCmd(fsyscmd[i].cmd, cmdFsysXXX); | 
 | 	} | 
 | 	/* the venti cmd is special: the fs can be either open or closed */ | 
 | 	cliAddCmd("venti", cmdFsysXXX); | 
 | 	cliAddCmd("printconfig", cmdPrintConfig); | 
 |  | 
 | 	return 1; | 
 | } |