| #include "stdinc.h" | 
 | #include "dat.h" | 
 | #include "fns.h" | 
 | #include <draw.h> | 
 | #include <event.h> | 
 |  | 
 | /* --- tree.h */ | 
 | typedef struct Tree Tree; | 
 | typedef struct Tnode Tnode; | 
 |  | 
 | struct Tree | 
 | { | 
 | 	Tnode *root; | 
 | 	Point offset; | 
 | 	Image *clipr; | 
 | }; | 
 |  | 
 | struct Tnode | 
 | { | 
 | 	Point offset; | 
 |  | 
 | 	char *str; | 
 | //	char *(*strfn)(Tnode*); | 
 | //	uint (*draw)(Tnode*, Image*, Image*, Point); | 
 | 	void (*expand)(Tnode*); | 
 | 	void (*collapse)(Tnode*); | 
 |  | 
 | 	uint expanded; | 
 | 	Tnode **kid; | 
 | 	int nkid; | 
 | 	void *aux; | 
 | }; | 
 |  | 
 | typedef struct Atree Atree; | 
 | struct Atree | 
 | { | 
 | 	int resizefd; | 
 | 	Tnode *root; | 
 | }; | 
 |  | 
 | Atree *atreeinit(char*); | 
 |  | 
 | /* --- visfossil.c */ | 
 | Tnode *initxheader(void); | 
 | Tnode *initxcache(char *name); | 
 | Tnode *initxsuper(void); | 
 | Tnode *initxlocalroot(char *name, u32int addr); | 
 | Tnode *initxentry(Entry); | 
 | Tnode *initxsource(Entry, int); | 
 | Tnode *initxentryblock(Block*, Entry*); | 
 | Tnode *initxdatablock(Block*, uint); | 
 | Tnode *initxroot(char *name, uchar[VtScoreSize]); | 
 |  | 
 | int fd; | 
 | int mainstacksize = STACK; | 
 | Header h; | 
 | Super super; | 
 | VtConn *z; | 
 | VtRoot vac; | 
 | int showinactive; | 
 |  | 
 | /* | 
 |  * dumbed down versions of fossil routines | 
 |  */ | 
 | char* | 
 | bsStr(int state) | 
 | { | 
 | 	static char s[100]; | 
 |  | 
 | 	if(state == BsFree) | 
 | 		return "Free"; | 
 | 	if(state == BsBad) | 
 | 		return "Bad"; | 
 |  | 
 | 	sprint(s, "%x", state); | 
 | 	if(!(state&BsAlloc)) | 
 | 		strcat(s, ",Free");	/* should not happen */ | 
 | 	if(state&BsVenti) | 
 | 		strcat(s, ",Venti"); | 
 | 	if(state&BsClosed) | 
 | 		strcat(s, ",Closed"); | 
 | 	return s; | 
 | } | 
 |  | 
 | char *bttab[] = { | 
 | 	"BtData", | 
 | 	"BtData+1", | 
 | 	"BtData+2", | 
 | 	"BtData+3", | 
 | 	"BtData+4", | 
 | 	"BtData+5", | 
 | 	"BtData+6", | 
 | 	"BtData+7", | 
 | 	"BtDir", | 
 | 	"BtDir+1", | 
 | 	"BtDir+2", | 
 | 	"BtDir+3", | 
 | 	"BtDir+4", | 
 | 	"BtDir+5", | 
 | 	"BtDir+6", | 
 | 	"BtDir+7", | 
 | }; | 
 |  | 
 | char* | 
 | btStr(int type) | 
 | { | 
 | 	if(type < nelem(bttab)) | 
 | 		return bttab[type]; | 
 | 	return "unknown"; | 
 | } | 
 |  | 
 | Block* | 
 | allocBlock(void) | 
 | { | 
 | 	Block *b; | 
 |  | 
 | 	b = mallocz(sizeof(Block)+h.blockSize, 1); | 
 | 	b->data = (void*)&b[1]; | 
 | 	return b; | 
 | } | 
 |  | 
 | void | 
 | blockPut(Block *b) | 
 | { | 
 | 	free(b); | 
 | } | 
 |  | 
 | static u32int | 
 | partStart(int part) | 
 | { | 
 | 	switch(part){ | 
 | 	default: | 
 | 		assert(0); | 
 | 	case PartSuper: | 
 | 		return h.super; | 
 | 	case PartLabel: | 
 | 		return h.label; | 
 | 	case PartData: | 
 | 		return h.data; | 
 | 	} | 
 | } | 
 |  | 
 |  | 
 | static u32int | 
 | partEnd(int part) | 
 | { | 
 | 	switch(part){ | 
 | 	default: | 
 | 		assert(0); | 
 | 	case PartSuper: | 
 | 		return h.super+1; | 
 | 	case PartLabel: | 
 | 		return h.data; | 
 | 	case PartData: | 
 | 		return h.end; | 
 | 	} | 
 | } | 
 |  | 
 | Block* | 
 | readBlock(int part, u32int addr) | 
 | { | 
 | 	u32int start, end; | 
 | 	u64int offset; | 
 | 	int n, nn; | 
 | 	Block *b; | 
 | 	uchar *buf; | 
 |  | 
 | 	start = partStart(part); | 
 | 	end = partEnd(part); | 
 | 	if(addr >= end-start){ | 
 | 		werrstr("bad addr 0x%.8ux; wanted 0x%.8ux - 0x%.8ux", addr, start, end); | 
 | 		return nil; | 
 | 	} | 
 |  | 
 | 	b = allocBlock(); | 
 | 	b->addr = addr; | 
 | 	buf = b->data; | 
 | 	offset = ((u64int)(addr+start))*h.blockSize; | 
 | 	n = h.blockSize; | 
 | 	while(n > 0){ | 
 | 		nn = pread(fd, buf, n, offset); | 
 | 		if(nn < 0){ | 
 | 			blockPut(b); | 
 | 			return nil; | 
 | 		} | 
 | 		if(nn == 0){ | 
 | 			werrstr("short read"); | 
 | 			blockPut(b); | 
 | 			return nil; | 
 | 		} | 
 | 		n -= nn; | 
 | 		offset += nn; | 
 | 		buf += nn; | 
 | 	} | 
 | 	return b; | 
 | } | 
 |  | 
 | int vtType[BtMax] = { | 
 | 	VtDataType,		/* BtData | 0  */ | 
 | 	VtDataType+1,		/* BtData | 1  */ | 
 | 	VtDataType+2,		/* BtData | 2  */ | 
 | 	VtDataType+3,		/* BtData | 3  */ | 
 | 	VtDataType+4,		/* BtData | 4  */ | 
 | 	VtDataType+5,		/* BtData | 5  */ | 
 | 	VtDataType+6,		/* BtData | 6  */ | 
 | 	VtDataType+7,		/* BtData | 7  */ | 
 | 	VtDirType,		/* BtDir | 0  */ | 
 | 	VtDirType+1,		/* BtDir | 1  */ | 
 | 	VtDirType+2,		/* BtDir | 2  */ | 
 | 	VtDirType+3,		/* BtDir | 3  */ | 
 | 	VtDirType+4,		/* BtDir | 4  */ | 
 | 	VtDirType+5,		/* BtDir | 5  */ | 
 | 	VtDirType+6,		/* BtDir | 6  */ | 
 | 	VtDirType+7,		/* BtDir | 7  */ | 
 | }; | 
 |  | 
 | Block* | 
 | ventiBlock(uchar score[VtScoreSize], uint type) | 
 | { | 
 | 	int n; | 
 | 	Block *b; | 
 |  | 
 | 	b = allocBlock(); | 
 | 	memmove(b->score, score, VtScoreSize); | 
 | 	b->addr = NilBlock; | 
 |  | 
 | 	n = vtread(z, b->score, vtType[type], b->data, h.blockSize); | 
 | 	if(n < 0){ | 
 | 		fprint(2, "vtread returns %d: %r\n", n); | 
 | 		blockPut(b); | 
 | 		return nil; | 
 | 	} | 
 | 	vtzeroextend(vtType[type], b->data, n, h.blockSize); | 
 | 	b->l.type = type; | 
 | 	b->l.state = 0; | 
 | 	b->l.tag = 0; | 
 | 	b->l.epoch = 0; | 
 | 	return b; | 
 | } | 
 |  | 
 | Block* | 
 | dataBlock(uchar score[VtScoreSize], uint type, uint tag) | 
 | { | 
 | 	Block *b, *bl; | 
 | 	int lpb; | 
 | 	Label l; | 
 | 	u32int addr; | 
 |  | 
 | 	addr = globalToLocal(score); | 
 | 	if(addr == NilBlock) | 
 | 		return ventiBlock(score, type); | 
 |  | 
 | 	lpb = h.blockSize/LabelSize; | 
 | 	bl = readBlock(PartLabel, addr/lpb); | 
 | 	if(bl == nil) | 
 | 		return nil; | 
 | 	if(!labelUnpack(&l, bl->data, addr%lpb)){ | 
 | 		werrstr("%r"); | 
 | 		blockPut(bl); | 
 | 		return nil; | 
 | 	} | 
 | 	blockPut(bl); | 
 | 	if(l.type != type){ | 
 | 		werrstr("type mismatch; got %d (%s) wanted %d (%s)", | 
 | 			l.type, btStr(l.type), type, btStr(type)); | 
 | 		return nil; | 
 | 	} | 
 | 	if(tag && l.tag != tag){ | 
 | 		werrstr("tag mismatch; got 0x%.8ux wanted 0x%.8ux", | 
 | 			l.tag, tag); | 
 | 		return nil; | 
 | 	} | 
 | 	b = readBlock(PartData, addr); | 
 | 	if(b == nil) | 
 | 		return nil; | 
 | 	b->l = l; | 
 | 	return b; | 
 | } | 
 |  | 
 | Entry* | 
 | copyEntry(Entry e) | 
 | { | 
 | 	Entry *p; | 
 |  | 
 | 	p = mallocz(sizeof *p, 1); | 
 | 	*p = e; | 
 | 	return p; | 
 | } | 
 |  | 
 | MetaBlock* | 
 | copyMetaBlock(MetaBlock mb) | 
 | { | 
 | 	MetaBlock *p; | 
 |  | 
 | 	p = mallocz(sizeof mb, 1); | 
 | 	*p = mb; | 
 | 	return p; | 
 | } | 
 |  | 
 | /* | 
 |  * visualizer  | 
 |  */ | 
 |  | 
 | #pragma	varargck	argpos	stringnode	1 | 
 |  | 
 | Tnode* | 
 | stringnode(char *fmt, ...) | 
 | { | 
 | 	va_list arg; | 
 | 	Tnode *t; | 
 |  | 
 | 	t = mallocz(sizeof(Tnode), 1); | 
 | 	va_start(arg, fmt); | 
 | 	t->str = vsmprint(fmt, arg); | 
 | 	va_end(arg); | 
 | 	t->nkid = -1; | 
 | 	return t; | 
 | } | 
 |  | 
 | void | 
 | xcacheexpand(Tnode *t) | 
 | { | 
 | 	if(t->nkid >= 0) | 
 | 		return; | 
 |  | 
 | 	t->nkid = 1; | 
 | 	t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1); | 
 | 	t->kid[0] = initxheader(); | 
 | } | 
 |  | 
 | Tnode* | 
 | initxcache(char *name) | 
 | { | 
 | 	Tnode *t; | 
 |  | 
 | 	if((fd = open(name, OREAD)) < 0) | 
 | 		sysfatal("cannot open %s: %r", name); | 
 |  | 
 | 	t = stringnode("%s", name); | 
 | 	t->expand = xcacheexpand; | 
 | 	return t; | 
 | } | 
 |  | 
 | void | 
 | xheaderexpand(Tnode *t) | 
 | { | 
 | 	if(t->nkid >= 0) | 
 | 		return; | 
 |  | 
 | 	t->nkid = 1; | 
 | 	t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1); | 
 | 	t->kid[0] = initxsuper(); | 
 | 	//t->kid[1] = initxlabel(h.label); | 
 | 	//t->kid[2] = initxdata(h.data); | 
 | } | 
 |  | 
 | Tnode* | 
 | initxheader(void) | 
 | { | 
 | 	u8int buf[HeaderSize]; | 
 | 	Tnode *t; | 
 |  | 
 | 	if(pread(fd, buf, HeaderSize, HeaderOffset) < HeaderSize) | 
 | 		return stringnode("error reading header: %r"); | 
 | 	if(!headerUnpack(&h, buf)) | 
 | 		return stringnode("error unpacking header: %r"); | 
 |  | 
 | 	t = stringnode("header " | 
 | 		"version=%#ux (%d) " | 
 | 		"blockSize=%#ux (%d) " | 
 | 		"super=%#lux (%ld) " | 
 | 		"label=%#lux (%ld) " | 
 | 		"data=%#lux (%ld) " | 
 | 		"end=%#lux (%ld)", | 
 | 		h.version, h.version, h.blockSize, h.blockSize, | 
 | 		h.super, h.super, | 
 | 		h.label, h.label, h.data, h.data, h.end, h.end); | 
 | 	t->expand = xheaderexpand; | 
 | 	return t; | 
 | } | 
 |  | 
 | void | 
 | xsuperexpand(Tnode *t) | 
 | { | 
 | 	if(t->nkid >= 0) | 
 | 		return; | 
 |  | 
 | 	t->nkid = 1; | 
 | 	t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1); | 
 | 	t->kid[0] = initxlocalroot("active", super.active); | 
 | //	t->kid[1] = initxlocalroot("next", super.next); | 
 | //	t->kid[2] = initxlocalroot("current", super.current); | 
 | } | 
 |  | 
 | Tnode* | 
 | initxsuper(void) | 
 | { | 
 | 	Block *b; | 
 | 	Tnode *t; | 
 |  | 
 | 	b = readBlock(PartSuper, 0); | 
 | 	if(b == nil) | 
 | 		return stringnode("reading super: %r"); | 
 | 	if(!superUnpack(&super, b->data)){ | 
 | 		blockPut(b); | 
 | 		return stringnode("unpacking super: %r"); | 
 | 	} | 
 | 	blockPut(b); | 
 | 	t = stringnode("super " | 
 | 		"version=%#ux " | 
 | 		"epoch=[%#ux,%#ux) " | 
 | 		"qid=%#llux " | 
 | 		"active=%#x " | 
 | 		"next=%#x " | 
 | 		"current=%#x " | 
 | 		"last=%V " | 
 | 		"name=%s", | 
 | 		super.version, super.epochLow, super.epochHigh, | 
 | 		super.qid, super.active, super.next, super.current, | 
 | 		super.last, super.name); | 
 | 	t->expand = xsuperexpand; | 
 | 	return t; | 
 | } | 
 |  | 
 | void | 
 | xvacrootexpand(Tnode *t) | 
 | { | 
 | 	if(t->nkid >= 0) | 
 | 		return; | 
 |  | 
 | 	t->nkid = 1; | 
 | 	t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1); | 
 | 	t->kid[0] = initxroot("root", vac.score); | 
 | } | 
 |  | 
 | Tnode* | 
 | initxvacroot(uchar score[VtScoreSize]) | 
 | { | 
 | 	Tnode *t; | 
 | 	uchar buf[VtRootSize]; | 
 | 	int n; | 
 |  | 
 | 	if((n = vtread(z, score, VtRootType, buf, VtRootSize)) < 0) | 
 | 		return stringnode("reading root %V: %r", score); | 
 |  | 
 | 	if(vtrootunpack(&vac, buf) < 0) | 
 | 		return stringnode("unpack %d-byte root: %r", n); | 
 |  | 
 | 	h.blockSize = vac.blocksize; | 
 | 	t = stringnode("vac version=%#ux name=%s type=%s blocksize=%lud score=%V prev=%V", | 
 | 		VtRootVersion, vac.name, vac.type, vac.blocksize, vac.score, vac.prev); | 
 | 	t->expand = xvacrootexpand; | 
 | 	return t; | 
 | } | 
 |  | 
 | Tnode* | 
 | initxlabel(Label l) | 
 | { | 
 | 	return stringnode("label type=%s state=%s epoch=%#ux tag=%#ux", | 
 | 		btStr(l.type), bsStr(l.state), l.epoch, l.tag); | 
 | } | 
 |  | 
 | typedef struct Xblock Xblock; | 
 | struct Xblock | 
 | { | 
 | 	Tnode t; | 
 | 	Block *b; | 
 | 	int (*gen)(void*, Block*, int, Tnode**); | 
 | 	void *arg; | 
 | 	int printlabel; | 
 | }; | 
 |  | 
 | void | 
 | xblockexpand(Tnode *tt) | 
 | { | 
 | 	int i, j; | 
 | 	enum { Q = 32 }; | 
 | 	Xblock *t = (Xblock*)tt; | 
 | 	Tnode *nn; | 
 |  | 
 | 	if(t->t.nkid >= 0) | 
 | 		return; | 
 |  | 
 | 	j = 0; | 
 | 	if(t->printlabel){ | 
 | 		t->t.kid = mallocz(Q*sizeof(t->t.kid[0]), 1); | 
 | 		t->t.kid[0] = initxlabel(t->b->l); | 
 | 		j = 1; | 
 | 	} | 
 |  | 
 | 	for(i=0;; i++){ | 
 | 		switch((*t->gen)(t->arg, t->b, i, &nn)){ | 
 | 		case -1: | 
 | 			t->t.nkid = j; | 
 | 			return; | 
 | 		case 0: | 
 | 			break; | 
 | 		case 1: | 
 | 			if(j%Q == 0) | 
 | 				t->t.kid = realloc(t->t.kid, (j+Q)*sizeof(t->t.kid[0])); | 
 | 			t->t.kid[j++] = nn; | 
 | 			break; | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | int | 
 | nilgen(void *v, Block *b, int o, Tnode **tp) | 
 | { | 
 | 	return -1; | 
 | } | 
 |  | 
 | Tnode* | 
 | initxblock(Block *b, char *s, int (*gen)(void *v, Block *b, int o, Tnode **tp), void *arg) | 
 | { | 
 | 	Xblock *t; | 
 |  | 
 | 	if(gen == nil) | 
 | 		gen = nilgen; | 
 | 	t = mallocz(sizeof(Xblock), 1); | 
 | 	t->b = b; | 
 | 	t->gen = gen; | 
 | 	t->arg = arg; | 
 | 	if(b->addr == NilBlock) | 
 | 		t->t.str = smprint("Block %V: %s", b->score, s); | 
 | 	else | 
 | 		t->t.str = smprint("Block %#ux: %s", b->addr, s); | 
 | 	t->printlabel = 1; | 
 | 	t->t.nkid = -1; | 
 | 	t->t.expand = xblockexpand; | 
 | 	return (Tnode*)t; | 
 | } | 
 |  | 
 | int | 
 | xentrygen(void *v, Block *b, int o, Tnode **tp) | 
 | { | 
 | 	Entry e; | 
 | 	Entry *ed; | 
 |  | 
 | 	ed = v; | 
 | 	if(o >= ed->dsize/VtEntrySize) | 
 | 		return -1; | 
 |  | 
 | 	entryUnpack(&e, b->data, o); | 
 | 	if(!showinactive && !(e.flags & VtEntryActive)) | 
 | 		return 0; | 
 | 	*tp = initxentry(e); | 
 | 	return 1; | 
 | } | 
 |  | 
 | Tnode* | 
 | initxentryblock(Block *b, Entry *ed) | 
 | { | 
 | 	return initxblock(b, "entry", xentrygen, ed); | 
 | } | 
 |  | 
 | typedef struct Xentry Xentry; | 
 | struct Xentry  | 
 | { | 
 | 	Tnode t; | 
 | 	Entry e; | 
 | }; | 
 |  | 
 | void | 
 | xentryexpand(Tnode *tt) | 
 | { | 
 | 	Xentry *t = (Xentry*)tt; | 
 |  | 
 | 	if(t->t.nkid >= 0) | 
 | 		return; | 
 |  | 
 | 	t->t.nkid = 1; | 
 | 	t->t.kid = mallocz(sizeof(t->t.kid[0])*t->t.nkid, 1); | 
 | 	t->t.kid[0] = initxsource(t->e, 1); | 
 | } | 
 |  | 
 | Tnode* | 
 | initxentry(Entry e) | 
 | { | 
 | 	Xentry *t; | 
 |  | 
 | 	t = mallocz(sizeof *t, 1); | 
 | 	t->t.nkid = -1; | 
 | 	t->t.str = smprint("Entry gen=%#ux psize=%d dsize=%d depth=%d flags=%#ux size=%lld score=%V", | 
 | 		e.gen, e.psize, e.dsize, e.depth, e.flags, e.size, e.score); | 
 | 	if(e.flags & VtEntryLocal) | 
 | 		t->t.str = smprint("%s archive=%d snap=%d tag=%#ux", t->t.str, e.archive, e.snap, e.tag); | 
 | 	t->t.expand = xentryexpand; | 
 | 	t->e = e; | 
 | 	return (Tnode*)t; | 
 | } | 
 |  | 
 | int | 
 | ptrgen(void *v, Block *b, int o, Tnode **tp) | 
 | { | 
 | 	Entry *ed; | 
 | 	Entry e; | 
 |  | 
 | 	ed = v; | 
 | 	if(o >= ed->psize/VtScoreSize) | 
 | 		return -1; | 
 |  | 
 | 	e = *ed; | 
 | 	e.depth--; | 
 | 	memmove(e.score, b->data+o*VtScoreSize, VtScoreSize); | 
 | 	if(memcmp(e.score, vtzeroscore, VtScoreSize) == 0) | 
 | 		return 0; | 
 | 	*tp = initxsource(e, 0); | 
 | 	return 1; | 
 | } | 
 |  | 
 | static int | 
 | etype(int flags, int depth) | 
 | { | 
 | 	uint t; | 
 |  | 
 | 	if(flags&_VtEntryDir) | 
 | 		t = BtDir; | 
 | 	else | 
 | 		t = BtData; | 
 | 	return t+depth; | 
 | } | 
 |  | 
 | Tnode* | 
 | initxsource(Entry e, int dowrap) | 
 | { | 
 | 	Block *b; | 
 | 	Tnode *t, *tt; | 
 |  | 
 | 	b = dataBlock(e.score, etype(e.flags, e.depth), e.tag); | 
 | 	if(b == nil) | 
 | 		return stringnode("dataBlock: %r"); | 
 |  | 
 | 	if((e.flags & VtEntryActive) == 0) | 
 | 		return stringnode("inactive Entry"); | 
 |  | 
 | 	if(e.depth == 0){ | 
 | 		if(e.flags & _VtEntryDir) | 
 | 			tt = initxentryblock(b, copyEntry(e)); | 
 | 		else | 
 | 			tt = initxdatablock(b, e.dsize); | 
 | 	}else{ | 
 | 		tt = initxblock(b, smprint("%s+%d pointer", (e.flags & _VtEntryDir) ? "BtDir" : "BtData", e.depth), | 
 | 			ptrgen, copyEntry(e)); | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * wrap the contents of the Source in a Source node, | 
 | 	 * just so it's closer to what you see in the code. | 
 | 	 */ | 
 | 	if(dowrap){ | 
 | 		t = stringnode("Source"); | 
 | 		t->nkid = 1; | 
 | 		t->kid = mallocz(sizeof(Tnode*)*1, 1); | 
 | 		t->kid[0] = tt; | 
 | 		tt = t; | 
 | 	} | 
 | 	return tt; | 
 | } | 
 |  | 
 | int | 
 | xlocalrootgen(void *v, Block *b, int o, Tnode **tp) | 
 | { | 
 | 	Entry e; | 
 |  | 
 | 	if(o >= 1) | 
 | 		return -1; | 
 | 	entryUnpack(&e, b->data, o); | 
 | 	*tp = initxentry(e); | 
 | 	return 1; | 
 | } | 
 |  | 
 | Tnode* | 
 | initxlocalroot(char *name, u32int addr) | 
 | { | 
 | 	uchar score[VtScoreSize]; | 
 | 	Block *b; | 
 |  | 
 | 	localToGlobal(addr, score); | 
 | 	b = dataBlock(score, BtDir, RootTag); | 
 | 	if(b == nil) | 
 | 		return stringnode("read data block %#ux: %r", addr); | 
 | 	return initxblock(b, smprint("'%s' fs root", name), xlocalrootgen, nil); | 
 | } | 
 |  | 
 | int | 
 | xvacrootgen(void *v, Block *b, int o, Tnode **tp) | 
 | { | 
 | 	Entry e; | 
 |  | 
 | 	if(o >= 3) | 
 | 		return -1; | 
 | 	entryUnpack(&e, b->data, o); | 
 | 	*tp = initxentry(e); | 
 | 	return 1; | 
 | } | 
 |  | 
 | Tnode* | 
 | initxroot(char *name, uchar score[VtScoreSize]) | 
 | { | 
 | 	Block *b; | 
 |  | 
 | 	b = dataBlock(score, BtDir, RootTag); | 
 | 	if(b == nil) | 
 | 		return stringnode("read data block %V: %r", score); | 
 | 	return initxblock(b, smprint("'%s' fs root", name), xvacrootgen, nil); | 
 | } | 
 | Tnode* | 
 | initxdirentry(MetaEntry *me) | 
 | { | 
 | 	DirEntry dir; | 
 | 	Tnode *t; | 
 |  | 
 | 	if(!deUnpack(&dir, me)) | 
 | 		return stringnode("deUnpack: %r"); | 
 |  | 
 | 	t = stringnode("dirEntry elem=%s size=%llud data=%#lux/%#lux meta=%#lux/%#lux", dir.elem, dir.size, dir.entry, dir.gen, dir.mentry, dir.mgen); | 
 | 	t->nkid = 1; | 
 | 	t->kid = mallocz(sizeof(t->kid[0])*1, 1); | 
 | 	t->kid[0] = stringnode( | 
 | 		"qid=%#llux\n" | 
 | 		"uid=%s gid=%s mid=%s\n" | 
 | 		"mtime=%lud mcount=%lud ctime=%lud atime=%lud\n" | 
 | 		"mode=%luo\n" | 
 | 		"plan9 %d p9path %#llux p9version %lud\n" | 
 | 		"qidSpace %d offset %#llux max %#llux", | 
 | 		dir.qid, | 
 | 		dir.uid, dir.gid, dir.mid, | 
 | 		dir.mtime, dir.mcount, dir.ctime, dir.atime, | 
 | 		dir.mode, | 
 | 		dir.plan9, dir.p9path, dir.p9version, | 
 | 		dir.qidSpace, dir.qidOffset, dir.qidMax); | 
 | 	return t; | 
 | } | 
 |  | 
 | int | 
 | metaentrygen(void *v, Block *b, int o, Tnode **tp) | 
 | { | 
 | 	Tnode *t; | 
 | 	MetaBlock *mb; | 
 | 	MetaEntry me; | 
 |  | 
 | 	mb = v; | 
 | 	if(o >= mb->nindex) | 
 | 		return -1; | 
 | 	meUnpack(&me, mb, o); | 
 |  | 
 | 	t = stringnode("MetaEntry %d bytes", mb->size); | 
 | 	t->kid = mallocz(sizeof(t->kid[0])*1, 1); | 
 | 	t->kid[0] = initxdirentry(&me); | 
 | 	t->nkid = 1; | 
 | 	*tp = t; | 
 | 	return 1; | 
 | } | 
 |  | 
 | int | 
 | metablockgen(void *v, Block *b, int o, Tnode **tp) | 
 | { | 
 | 	Xblock *t; | 
 | 	MetaBlock *mb; | 
 |  | 
 | 	if(o >= 1) | 
 | 		return -1; | 
 |  | 
 | 	/* hack: reuse initxblock as a generic iterator */ | 
 | 	mb = v; | 
 | 	t = (Xblock*)initxblock(b, "", metaentrygen, mb); | 
 | 	t->t.str = smprint("MetaBlock %d/%d space used, %d add'l free %d/%d table used%s", | 
 | 		mb->size, mb->maxsize, mb->free, mb->nindex, mb->maxindex, | 
 | 		mb->botch ? " [BOTCH]" : ""); | 
 | 	t->printlabel = 0; | 
 | 	*tp = (Tnode*)t; | 
 | 	return 1; | 
 | } | 
 |  | 
 | /* | 
 |  * attempt to guess at the type of data in the block. | 
 |  * it could just be data from a file, but we're hoping it's MetaBlocks. | 
 |  */ | 
 | Tnode* | 
 | initxdatablock(Block *b, uint n) | 
 | { | 
 | 	MetaBlock mb; | 
 |  | 
 | 	if(n > h.blockSize) | 
 | 		n = h.blockSize; | 
 |  | 
 | 	if(mbUnpack(&mb, b->data, n)) | 
 | 		return initxblock(b, "metadata", metablockgen, copyMetaBlock(mb)); | 
 |  | 
 | 	return initxblock(b, "data", nil, nil); | 
 | } | 
 |  | 
 | int | 
 | parseScore(uchar *score, char *buf, int n) | 
 | { | 
 | 	int i, c; | 
 |  | 
 | 	memset(score, 0, VtScoreSize); | 
 |  | 
 | 	if(n < VtScoreSize*2) | 
 | 		return 0; | 
 | 	for(i=0; i<VtScoreSize*2; i++){ | 
 | 		if(buf[i] >= '0' && buf[i] <= '9') | 
 | 			c = buf[i] - '0'; | 
 | 		else if(buf[i] >= 'a' && buf[i] <= 'f') | 
 | 			c = buf[i] - 'a' + 10; | 
 | 		else if(buf[i] >= 'A' && buf[i] <= 'F') | 
 | 			c = buf[i] - 'A' + 10; | 
 | 		else{ | 
 | 			return 0; | 
 | 		} | 
 |  | 
 | 		if((i & 1) == 0) | 
 | 			c <<= 4; | 
 | 	 | 
 | 		score[i>>1] |= c; | 
 | 	} | 
 | 	return 1; | 
 | } | 
 |  | 
 | int | 
 | scoreFmt(Fmt *f) | 
 | { | 
 | 	uchar *v; | 
 | 	int i; | 
 | 	u32int addr; | 
 |  | 
 | 	v = va_arg(f->args, uchar*); | 
 | 	if(v == nil){ | 
 | 		fmtprint(f, "*"); | 
 | 	}else if((addr = globalToLocal(v)) != NilBlock) | 
 | 		fmtprint(f, "0x%.8ux", addr); | 
 | 	else{ | 
 | 		for(i = 0; i < VtScoreSize; i++) | 
 | 			fmtprint(f, "%2.2ux", v[i]); | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | Atree* | 
 | atreeinit(char *arg) | 
 | { | 
 | 	Atree *a; | 
 | 	uchar score[VtScoreSize]; | 
 |  | 
 | 	fmtinstall('V', scoreFmt); | 
 |  | 
 | 	z = vtdial(nil); | 
 | 	if(z == nil) | 
 | 		fprint(2, "warning: cannot dial venti: %r\n"); | 
 | 	else if(vtconnect(z) < 0){ | 
 | 		fprint(2, "warning: cannot connect to venti: %r\n"); | 
 | 		z = nil; | 
 | 	} | 
 | 	a = mallocz(sizeof(Atree), 1); | 
 | 	if(strncmp(arg, "vac:", 4) == 0){ | 
 | 		if(!parseScore(score, arg+4, strlen(arg+4))){ | 
 | 			fprint(2, "cannot parse score\n"); | 
 | 			return nil; | 
 | 		} | 
 | 		a->root = initxvacroot(score); | 
 | 	}else | 
 | 		a->root = initxcache(arg); | 
 | 	a->resizefd = -1; | 
 | 	return a; | 
 | } | 
 |  | 
 | /* --- tree.c */ | 
 | enum | 
 | { | 
 | 	Nubwidth = 11, | 
 | 	Nubheight = 11, | 
 | 	Linewidth = Nubwidth*2+4, | 
 | }; | 
 |  | 
 | uint | 
 | drawtext(char *s, Image *m, Image *clipr, Point o) | 
 | { | 
 | 	char *t, *nt, *e; | 
 | 	uint dy; | 
 |  | 
 | 	if(s == nil) | 
 | 		s = "???"; | 
 |  | 
 | 	dy = 0; | 
 | 	for(t=s; t&&*t; t=nt){ | 
 | 		if(nt = strchr(t, '\n')){ | 
 | 			e = nt; | 
 | 			nt++; | 
 | 		}else | 
 | 			e = t+strlen(t); | 
 |  | 
 | 		_string(m, Pt(o.x, o.y+dy), display->black, ZP, display->defaultfont, | 
 | 			t, nil, e-t, clipr->clipr, nil, ZP, SoverD); | 
 | 		dy += display->defaultfont->height; | 
 | 	} | 
 | 	return dy; | 
 | } | 
 |  | 
 | void | 
 | drawnub(Image *m, Image *clipr, Point o, Tnode *t) | 
 | { | 
 | 	clipr = nil; | 
 |  | 
 | 	if(t->nkid == 0) | 
 | 		return; | 
 | 	if(t->nkid == -1 && t->expand == nil) | 
 | 		return; | 
 |  | 
 | 	o.y += (display->defaultfont->height-Nubheight)/2; | 
 | 	draw(m, rectaddpt(Rect(0,0,1,Nubheight), o), display->black, clipr, ZP); | 
 | 	draw(m, rectaddpt(Rect(0,0,Nubwidth,1), o), display->black, clipr, o); | 
 | 	draw(m, rectaddpt(Rect(Nubwidth-1,0,Nubwidth,Nubheight), o),  | 
 | 		display->black, clipr, addpt(o, Pt(Nubwidth-1, 0))); | 
 | 	draw(m, rectaddpt(Rect(0, Nubheight-1, Nubwidth, Nubheight), o), | 
 | 		display->black, clipr, addpt(o, Pt(0, Nubheight-1))); | 
 |  | 
 | 	draw(m, rectaddpt(Rect(0, Nubheight/2, Nubwidth, Nubheight/2+1), o), | 
 | 		display->black, clipr, addpt(o, Pt(0, Nubheight/2))); | 
 | 	if(!t->expanded) | 
 | 		draw(m, rectaddpt(Rect(Nubwidth/2, 0, Nubwidth/2+1, Nubheight), o), | 
 | 			display->black, clipr, addpt(o, Pt(Nubwidth/2, 0))); | 
 |  | 
 | } | 
 |  | 
 | uint | 
 | drawnode(Tnode *t, Image *m, Image *clipr, Point o) | 
 | { | 
 | 	int i; | 
 | 	char *fs, *s; | 
 | 	uint dy; | 
 | 	Point oo; | 
 |  | 
 | 	if(t == nil) | 
 | 		return 0; | 
 |  | 
 | 	t->offset = o; | 
 |  | 
 | 	oo = Pt(o.x+Nubwidth+2, o.y); | 
 | //	if(t->draw) | 
 | //		dy = (*t->draw)(t, m, clipr, oo); | 
 | //	else{ | 
 | 		fs = nil; | 
 | 		if(t->str) | 
 | 			s = t->str; | 
 | 	//	else if(t->strfn) | 
 | 	//		fs = s = (*t->strfn)(t); | 
 | 		else | 
 | 			s = "???"; | 
 | 		dy = drawtext(s, m, clipr, oo); | 
 | 		free(fs); | 
 | //	} | 
 |  | 
 | 	if(t->expanded){ | 
 | 		if(t->nkid == -1 && t->expand) | 
 | 			(*t->expand)(t); | 
 | 		oo = Pt(o.x+Nubwidth+(Linewidth-Nubwidth)/2, o.y+dy); | 
 | 		for(i=0; i<t->nkid; i++) | 
 | 			oo.y += drawnode(t->kid[i], m, clipr, oo); | 
 | 		dy = oo.y - o.y; | 
 | 	} | 
 | 	drawnub(m, clipr, o, t); | 
 | 	return dy; | 
 | } | 
 |  | 
 | void | 
 | drawtree(Tree *t, Image *m, Rectangle r) | 
 | { | 
 | 	Point p; | 
 |  | 
 | 	draw(m, r, display->white, nil, ZP); | 
 |  | 
 | 	replclipr(t->clipr, 1, r); | 
 | 	p = addpt(t->offset, r.min); | 
 | 	drawnode(t->root, m, t->clipr, p); | 
 | } | 
 |  | 
 | Tnode* | 
 | findnode(Tnode *t, Point p) | 
 | { | 
 | 	int i; | 
 | 	Tnode *tt; | 
 |  | 
 | 	if(ptinrect(p, rectaddpt(Rect(0,0,Nubwidth, Nubheight), t->offset))) | 
 | 		return t; | 
 | 	if(!t->expanded) | 
 | 		return nil; | 
 | 	for(i=0; i<t->nkid; i++) | 
 | 		if(tt = findnode(t->kid[i], p)) | 
 | 			return tt; | 
 | 	return nil; | 
 | } | 
 |  | 
 | void | 
 | usage(void) | 
 | { | 
 | 	fprint(2, "usage: fossil/view /dev/sdC0/fossil\n"); | 
 | 	threadexitsall("usage"); | 
 | } | 
 |  | 
 | Tree t; | 
 |  | 
 | void | 
 | eresized(int new) | 
 | { | 
 | 	if(new && getwindow(display, Refnone) < 0) | 
 | 		fprint(2,"can't reattach to window"); | 
 | 	drawtree(&t, screen, screen->r); | 
 | } | 
 |  | 
 | enum | 
 | { | 
 | 	Left = 1<<0, | 
 | 	Middle = 1<<1, | 
 | 	Right = 1<<2, | 
 |  | 
 | 	MMenu = 2, | 
 | }; | 
 |  | 
 | char *items[] = { "exit", 0 }; | 
 | enum { IExit, }; | 
 |  | 
 | Menu menu; | 
 |  | 
 | void | 
 | threadmain(int argc, char **argv) | 
 | { | 
 | 	int n; | 
 | 	char *dir; | 
 | 	Event e; | 
 | 	Point op, p; | 
 | 	Tnode *tn; | 
 | 	Mouse m; | 
 | 	int Eready; | 
 | 	Atree *fs; | 
 |  | 
 | 	ARGBEGIN{ | 
 | 	case 'a': | 
 | 		showinactive = 1; | 
 | 		break; | 
 | 	default: | 
 | 		usage(); | 
 | 	}ARGEND | 
 |  | 
 | 	switch(argc){ | 
 | 	default: | 
 | 		usage(); | 
 | 	case 1: | 
 | 		dir = argv[0]; | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	fs = atreeinit(dir); | 
 | #ifdef PLAN9PORT | 
 | 	initdraw(0, "/lib/font/bit/lucsans/unicode.8.font", "tree"); | 
 | #else | 
 | 	initdraw(0, "/lib/font/bit/lucidasans/unicode.8.font", "tree"); | 
 | #endif | 
 | 	t.root = fs->root; | 
 | 	t.offset = ZP; | 
 | 	t.clipr = allocimage(display, Rect(0,0,1,1), GREY1, 1, DOpaque); | 
 |  | 
 | 	eresized(0); | 
 | 	flushimage(display, 1); | 
 |  | 
 | 	einit(Emouse); | 
 |  | 
 | 	menu.item = items; | 
 | 	menu.gen = 0; | 
 | 	menu.lasthit = 0; | 
 | 	if(fs->resizefd > 0){ | 
 | 		Eready = 1<<3; | 
 | 		estart(Eready, fs->resizefd, 1); | 
 | 	}else | 
 | 		Eready = 0; | 
 |  | 
 | 	for(;;){ | 
 | 		switch(n=eread(Emouse|Eready, &e)){ | 
 | 		default: | 
 | 			if(Eready && n==Eready) | 
 | 				eresized(0); | 
 | 			break; | 
 | 		case Emouse: | 
 | 			m = e.mouse; | 
 | 			switch(m.buttons){ | 
 | 			case Left: | 
 | 				op = t.offset; | 
 | 				p = m.xy; | 
 | 				do { | 
 | 					t.offset = addpt(t.offset, subpt(m.xy, p)); | 
 | 					p = m.xy; | 
 | 					eresized(0); | 
 | 					m = emouse(); | 
 | 				}while(m.buttons == Left); | 
 | 				if(m.buttons){ | 
 | 					t.offset = op; | 
 | 					eresized(0); | 
 | 				} | 
 | 				break; | 
 | 			case Middle: | 
 | 				n = emenuhit(MMenu, &m, &menu); | 
 | 				if(n == -1) | 
 | 					break; | 
 | 				switch(n){ | 
 | 				case IExit: | 
 | 					threadexitsall(nil); | 
 | 				} | 
 | 				break; | 
 | 			case Right: | 
 | 				do | 
 | 					m = emouse(); | 
 | 				while(m.buttons == Right); | 
 | 				if(m.buttons) | 
 | 					break; | 
 | 				tn = findnode(t.root, m.xy); | 
 | 				if(tn){ | 
 | 					tn->expanded = !tn->expanded; | 
 | 					eresized(0); | 
 | 				} | 
 | 				break; | 
 | 			} | 
 | 		} | 
 | 	} | 
 | } |