xref: /plan9/sys/src/cmd/fossil/view.c (revision a84536681645e23c630ce4ef2e5c3b284d4c590b)
1 #include "stdinc.h"
2 #include "dat.h"
3 #include "fns.h"
4 #include <draw.h>
5 #include <event.h>
6 
7 /* --- tree.h */
8 typedef struct Tree Tree;
9 typedef struct Tnode Tnode;
10 
11 struct Tree
12 {
13 	Tnode *root;
14 	Point offset;
15 	Image *clipr;
16 };
17 
18 struct Tnode
19 {
20 	Point offset;
21 
22 	char *str;
23 //	char *(*strfn)(Tnode*);
24 //	uint (*draw)(Tnode*, Image*, Image*, Point);
25 	void (*expand)(Tnode*);
26 	void (*collapse)(Tnode*);
27 
28 	uint expanded;
29 	Tnode **kid;
30 	int nkid;
31 	void *aux;
32 };
33 
34 typedef struct Atree Atree;
35 struct Atree
36 {
37 	int resizefd;
38 	Tnode *root;
39 };
40 
41 Atree *atreeinit(char*);
42 
43 /* --- visfossil.c */
44 Tnode *initxheader(void);
45 Tnode *initxcache(char *name);
46 Tnode *initxsuper(void);
47 Tnode *initxlocalroot(char *name, u32int addr);
48 Tnode *initxentry(Entry);
49 Tnode *initxsource(Entry, int);
50 Tnode *initxentryblock(Block*, Entry*);
51 Tnode *initxdatablock(Block*, uint);
52 Tnode *initxroot(char *name, uchar[VtScoreSize]);
53 
54 int fd;
55 Header h;
56 Super super;
57 VtSession *z;
58 VtRoot vac;
59 int showinactive;
60 
61 /*
62  * dumbed down versions of fossil routines
63  */
64 char*
65 bsStr(int state)
66 {
67 	static char s[100];
68 
69 	if(state == BsFree)
70 		return "Free";
71 	if(state == BsBad)
72 		return "Bad";
73 
74 	sprint(s, "%x", state);
75 	if(!(state&BsAlloc))
76 		strcat(s, ",Free");	/* should not happen */
77 	if(state&BsVenti)
78 		strcat(s, ",Venti");
79 	if(state&BsClosed)
80 		strcat(s, ",Closed");
81 	return s;
82 }
83 
84 char *bttab[] = {
85 	"BtData",
86 	"BtData+1",
87 	"BtData+2",
88 	"BtData+3",
89 	"BtData+4",
90 	"BtData+5",
91 	"BtData+6",
92 	"BtData+7",
93 	"BtDir",
94 	"BtDir+1",
95 	"BtDir+2",
96 	"BtDir+3",
97 	"BtDir+4",
98 	"BtDir+5",
99 	"BtDir+6",
100 	"BtDir+7",
101 };
102 
103 char*
104 btStr(int type)
105 {
106 	if(type < nelem(bttab))
107 		return bttab[type];
108 	return "unknown";
109 }
110 #pragma varargck argpos stringnode 1
111 
112 Block*
113 allocBlock(void)
114 {
115 	Block *b;
116 
117 	b = mallocz(sizeof(Block)+h.blockSize, 1);
118 	b->data = (void*)&b[1];
119 	return b;
120 }
121 
122 void
123 blockPut(Block *b)
124 {
125 	free(b);
126 }
127 
128 static u32int
129 partStart(int part)
130 {
131 	switch(part){
132 	default:
133 		assert(0);
134 	case PartSuper:
135 		return h.super;
136 	case PartLabel:
137 		return h.label;
138 	case PartData:
139 		return h.data;
140 	}
141 }
142 
143 
144 static u32int
145 partEnd(int part)
146 {
147 	switch(part){
148 	default:
149 		assert(0);
150 	case PartSuper:
151 		return h.super+1;
152 	case PartLabel:
153 		return h.data;
154 	case PartData:
155 		return h.end;
156 	}
157 }
158 
159 Block*
160 readBlock(int part, u32int addr)
161 {
162 	u32int start, end;
163 	u64int offset;
164 	int n, nn;
165 	Block *b;
166 	uchar *buf;
167 
168 	start = partStart(part);
169 	end = partEnd(part);
170 	if(addr >= end-start){
171 		werrstr("bad addr 0x%.8ux; wanted 0x%.8ux - 0x%.8ux", addr, start, end);
172 		return nil;
173 	}
174 
175 	b = allocBlock();
176 	b->addr = addr;
177 	buf = b->data;
178 	offset = ((u64int)(addr+start))*h.blockSize;
179 	n = h.blockSize;
180 	while(n > 0){
181 		nn = pread(fd, buf, n, offset);
182 		if(nn < 0){
183 			blockPut(b);
184 			return nil;
185 		}
186 		if(nn == 0){
187 			werrstr("short read");
188 			blockPut(b);
189 			return nil;
190 		}
191 		n -= nn;
192 		offset += nn;
193 		buf += nn;
194 	}
195 	return b;
196 }
197 
198 int vtType[BtMax] = {
199 	VtDataType,		/* BtData | 0  */
200 	VtPointerType0,		/* BtData | 1  */
201 	VtPointerType1,		/* BtData | 2  */
202 	VtPointerType2,		/* BtData | 3  */
203 	VtPointerType3,		/* BtData | 4  */
204 	VtPointerType4,		/* BtData | 5  */
205 	VtPointerType5,		/* BtData | 6  */
206 	VtPointerType6,		/* BtData | 7  */
207 	VtDirType,		/* BtDir | 0  */
208 	VtPointerType0,		/* BtDir | 1  */
209 	VtPointerType1,		/* BtDir | 2  */
210 	VtPointerType2,		/* BtDir | 3  */
211 	VtPointerType3,		/* BtDir | 4  */
212 	VtPointerType4,		/* BtDir | 5  */
213 	VtPointerType5,		/* BtDir | 6  */
214 	VtPointerType6,		/* BtDir | 7  */
215 };
216 
217 Block*
218 ventiBlock(uchar score[VtScoreSize], uint type)
219 {
220 	int n;
221 	Block *b;
222 
223 	b = allocBlock();
224 	memmove(b->score, score, VtScoreSize);
225 	b->addr = NilBlock;
226 
227 	n = vtRead(z, b->score, vtType[type], b->data, h.blockSize);
228 	if(n < 0){
229 		fprint(2, "vtRead returns %d: %R\n", n);
230 		blockPut(b);
231 		return nil;
232 	}
233 	vtZeroExtend(vtType[type], b->data, n, h.blockSize);
234 	b->l.type = type;
235 	b->l.state = 0;
236 	b->l.tag = 0;
237 	b->l.epoch = 0;
238 	return b;
239 }
240 
241 Block*
242 dataBlock(uchar score[VtScoreSize], uint type, uint tag)
243 {
244 	Block *b, *bl;
245 	int lpb;
246 	Label l;
247 	u32int addr;
248 
249 	addr = globalToLocal(score);
250 	if(addr == NilBlock)
251 		return ventiBlock(score, type);
252 
253 	lpb = h.blockSize/LabelSize;
254 	bl = readBlock(PartLabel, addr/lpb);
255 	if(bl == nil)
256 		return nil;
257 	if(!labelUnpack(&l, bl->data, addr%lpb)){
258 		werrstr("%R");
259 		blockPut(bl);
260 		return nil;
261 	}
262 	blockPut(bl);
263 	if(l.type != type){
264 		werrstr("type mismatch; got %d (%s) wanted %d (%s)",
265 			l.type, btStr(l.type), type, btStr(type));
266 		return nil;
267 	}
268 	if(tag && l.tag != tag){
269 		werrstr("tag mismatch; got 0x%.8ux wanted 0x%.8ux",
270 			l.tag, tag);
271 		return nil;
272 	}
273 	b = readBlock(PartData, addr);
274 	if(b == nil)
275 		return nil;
276 	b->l = l;
277 	return b;
278 }
279 
280 Entry*
281 copyEntry(Entry e)
282 {
283 	Entry *p;
284 
285 	p = mallocz(sizeof *p, 1);
286 	*p = e;
287 	return p;
288 }
289 
290 MetaBlock*
291 copyMetaBlock(MetaBlock mb)
292 {
293 	MetaBlock *p;
294 
295 	p = mallocz(sizeof mb, 1);
296 	*p = mb;
297 	return p;
298 }
299 
300 /*
301  * visualizer
302  */
303 
304 Tnode*
305 stringnode(char *fmt, ...)
306 {
307 	va_list arg;
308 	Tnode *t;
309 
310 	t = mallocz(sizeof(Tnode), 1);
311 	va_start(arg, fmt);
312 	t->str = vsmprint(fmt, arg);
313 	va_end(arg);
314 	t->nkid = -1;
315 	return t;
316 }
317 
318 void
319 xcacheexpand(Tnode *t)
320 {
321 	if(t->nkid >= 0)
322 		return;
323 
324 	t->nkid = 1;
325 	t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
326 	t->kid[0] = initxheader();
327 }
328 
329 Tnode*
330 initxcache(char *name)
331 {
332 	Tnode *t;
333 
334 	if((fd = open(name, OREAD)) < 0)
335 		sysfatal("cannot open %s: %r", name);
336 
337 	t = stringnode("%s", name);
338 	t->expand = xcacheexpand;
339 	return t;
340 }
341 
342 void
343 xheaderexpand(Tnode *t)
344 {
345 	if(t->nkid >= 0)
346 		return;
347 
348 	t->nkid = 1;
349 	t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
350 	t->kid[0] = initxsuper();
351 	//t->kid[1] = initxlabel(h.label);
352 	//t->kid[2] = initxdata(h.data);
353 }
354 
355 Tnode*
356 initxheader(void)
357 {
358 	u8int buf[HeaderSize];
359 	Tnode *t;
360 
361 	if(pread(fd, buf, HeaderSize, HeaderOffset) < HeaderSize)
362 		return stringnode("error reading header: %r");
363 	if(!headerUnpack(&h, buf))
364 		return stringnode("error unpacking header: %R");
365 
366 	t = stringnode("header "
367 		"version=%#ux (%d) "
368 		"blockSize=%#ux (%d) "
369 		"super=%#lux (%ld) "
370 		"label=%#lux (%ld) "
371 		"data=%#lux (%ld) "
372 		"end=%#lux (%ld)",
373 		h.version, h.version, h.blockSize, h.blockSize,
374 		h.super, h.super,
375 		h.label, h.label, h.data, h.data, h.end, h.end);
376 	t->expand = xheaderexpand;
377 	return t;
378 }
379 
380 void
381 xsuperexpand(Tnode *t)
382 {
383 	if(t->nkid >= 0)
384 		return;
385 
386 	t->nkid = 1;
387 	t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
388 	t->kid[0] = initxlocalroot("active", super.active);
389 //	t->kid[1] = initxlocalroot("next", super.next);
390 //	t->kid[2] = initxlocalroot("current", super.current);
391 }
392 
393 Tnode*
394 initxsuper(void)
395 {
396 	Block *b;
397 	Tnode *t;
398 
399 	b = readBlock(PartSuper, 0);
400 	if(b == nil)
401 		return stringnode("reading super: %r");
402 	if(!superUnpack(&super, b->data)){
403 		blockPut(b);
404 		return stringnode("unpacking super: %R");
405 	}
406 	blockPut(b);
407 	t = stringnode("super "
408 		"version=%#ux "
409 		"epoch=[%#ux,%#ux) "
410 		"qid=%#llux "
411 		"active=%#x "
412 		"next=%#x "
413 		"current=%#x "
414 		"last=%V "
415 		"name=%s",
416 		super.version, super.epochLow, super.epochHigh,
417 		super.qid, super.active, super.next, super.current,
418 		super.last, super.name);
419 	t->expand = xsuperexpand;
420 	return t;
421 }
422 
423 void
424 xvacrootexpand(Tnode *t)
425 {
426 	if(t->nkid >= 0)
427 		return;
428 
429 	t->nkid = 1;
430 	t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
431 	t->kid[0] = initxroot("root", vac.score);
432 }
433 
434 Tnode*
435 initxvacroot(uchar score[VtScoreSize])
436 {
437 	Tnode *t;
438 	uchar buf[VtRootSize];
439 	int n;
440 
441 	if((n = vtRead(z, score, VtRootType, buf, VtRootSize)) < 0)
442 		return stringnode("reading root %V: %R", score);
443 
444 	if(!vtRootUnpack(&vac, buf))
445 		return stringnode("unpack %d-byte root: %R", n);
446 
447 	h.blockSize = vac.blockSize;
448 	t = stringnode("vac version=%#ux name=%s type=%s blockSize=%ud score=%V prev=%V",
449 		vac.version, vac.name, vac.type, vac.blockSize, vac.score, vac.prev);
450 	t->expand = xvacrootexpand;
451 	return t;
452 }
453 
454 Tnode*
455 initxlabel(Label l)
456 {
457 	return stringnode("label type=%s state=%s epoch=%#ux tag=%#ux",
458 		btStr(l.type), bsStr(l.state), l.epoch, l.tag);
459 }
460 
461 typedef struct Xblock Xblock;
462 struct Xblock
463 {
464 	Tnode;
465 	Block *b;
466 	int (*gen)(void*, Block*, int, Tnode**);
467 	void *arg;
468 	int printlabel;
469 };
470 
471 void
472 xblockexpand(Tnode *tt)
473 {
474 	int i, j;
475 	enum { Q = 32 };
476 	Xblock *t = (Xblock*)tt;
477 	Tnode *nn;
478 
479 	if(t->nkid >= 0)
480 		return;
481 
482 	j = 0;
483 	if(t->printlabel){
484 		t->kid = mallocz(Q*sizeof(t->kid[0]), 1);
485 		t->kid[0] = initxlabel(t->b->l);
486 		j = 1;
487 	}
488 
489 	for(i=0;; i++){
490 		switch((*t->gen)(t->arg, t->b, i, &nn)){
491 		case -1:
492 			t->nkid = j;
493 			return;
494 		case 0:
495 			break;
496 		case 1:
497 			if(j%Q == 0)
498 				t->kid = realloc(t->kid, (j+Q)*sizeof(t->kid[0]));
499 			t->kid[j++] = nn;
500 			break;
501 		}
502 	}
503 }
504 
505 int
506 nilgen(void*, Block*, int, Tnode**)
507 {
508 	return -1;
509 }
510 
511 Tnode*
512 initxblock(Block *b, char *s, int (*gen)(void*, Block*, int, Tnode**), void *arg)
513 {
514 	Xblock *t;
515 
516 	if(gen == nil)
517 		gen = nilgen;
518 	t = mallocz(sizeof(Xblock), 1);
519 	t->b = b;
520 	t->gen = gen;
521 	t->arg = arg;
522 	if(b->addr == NilBlock)
523 		t->str = smprint("Block %V: %s", b->score, s);
524 	else
525 		t->str = smprint("Block %#ux: %s", b->addr, s);
526 	t->printlabel = 1;
527 	t->nkid = -1;
528 	t->expand = xblockexpand;
529 	return t;
530 }
531 
532 int
533 xentrygen(void *v, Block *b, int o, Tnode **tp)
534 {
535 	Entry e;
536 	Entry *ed;
537 
538 	ed = v;
539 	if(o >= ed->dsize/VtEntrySize)
540 		return -1;
541 
542 	entryUnpack(&e, b->data, o);
543 	if(!showinactive && !(e.flags & VtEntryActive))
544 		return 0;
545 	*tp = initxentry(e);
546 	return 1;
547 }
548 
549 Tnode*
550 initxentryblock(Block *b, Entry *ed)
551 {
552 	return initxblock(b, "entry", xentrygen, ed);
553 }
554 
555 typedef struct Xentry Xentry;
556 struct Xentry
557 {
558 	Tnode;
559 	Entry e;
560 };
561 
562 void
563 xentryexpand(Tnode *tt)
564 {
565 	Xentry *t = (Xentry*)tt;
566 
567 	if(t->nkid >= 0)
568 		return;
569 
570 	t->nkid = 1;
571 	t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
572 	t->kid[0] = initxsource(t->e, 1);
573 }
574 
575 Tnode*
576 initxentry(Entry e)
577 {
578 	Xentry *t;
579 
580 	t = mallocz(sizeof *t, 1);
581 	t->nkid = -1;
582 	t->str = smprint("Entry gen=%#ux psize=%d dsize=%d depth=%d flags=%#ux size=%lld score=%V",
583 		e.gen, e.psize, e.dsize, e.depth, e.flags, e.size, e.score);
584 	if(e.flags & VtEntryLocal)
585 		t->str = smprint("%s archive=%d snap=%d tag=%#ux", t->str, e.archive, e.snap, e.tag);
586 	t->expand = xentryexpand;
587 	t->e = e;
588 	return t;
589 }
590 
591 int
592 ptrgen(void *v, Block *b, int o, Tnode **tp)
593 {
594 	Entry *ed;
595 	Entry e;
596 
597 	ed = v;
598 	if(o >= ed->psize/VtScoreSize)
599 		return -1;
600 
601 	e = *ed;
602 	e.depth--;
603 	memmove(e.score, b->data+o*VtScoreSize, VtScoreSize);
604 	if(memcmp(e.score, vtZeroScore, VtScoreSize) == 0)
605 		return 0;
606 	*tp = initxsource(e, 0);
607 	return 1;
608 }
609 
610 static int
611 etype(int flags, int depth)
612 {
613 	uint t;
614 
615 	if(flags&VtEntryDir)
616 		t = BtDir;
617 	else
618 		t = BtData;
619 	return t+depth;
620 }
621 
622 Tnode*
623 initxsource(Entry e, int dowrap)
624 {
625 	Block *b;
626 	Tnode *t, *tt;
627 
628 	b = dataBlock(e.score, etype(e.flags, e.depth), e.tag);
629 	if(b == nil)
630 		return stringnode("dataBlock: %r");
631 
632 	if((e.flags & VtEntryActive) == 0)
633 		return stringnode("inactive Entry");
634 
635 	if(e.depth == 0){
636 		if(e.flags & VtEntryDir)
637 			tt = initxentryblock(b, copyEntry(e));
638 		else
639 			tt = initxdatablock(b, e.dsize);
640 	}else{
641 		tt = initxblock(b, smprint("%s+%d pointer", (e.flags & VtEntryDir) ? "BtDir" : "BtData", e.depth),
642 			ptrgen, copyEntry(e));
643 	}
644 
645 	/*
646 	 * wrap the contents of the Source in a Source node,
647 	 * just so it's closer to what you see in the code.
648 	 */
649 	if(dowrap){
650 		t = stringnode("Source");
651 		t->nkid = 1;
652 		t->kid = mallocz(sizeof(Tnode*)*1, 1);
653 		t->kid[0] = tt;
654 		tt = t;
655 	}
656 	return tt;
657 }
658 
659 int
660 xlocalrootgen(void*, Block *b, int o, Tnode **tp)
661 {
662 	Entry e;
663 
664 	if(o >= 1)
665 		return -1;
666 	entryUnpack(&e, b->data, o);
667 	*tp = initxentry(e);
668 	return 1;
669 }
670 
671 Tnode*
672 initxlocalroot(char *name, u32int addr)
673 {
674 	uchar score[VtScoreSize];
675 	Block *b;
676 
677 	localToGlobal(addr, score);
678 	b = dataBlock(score, BtDir, RootTag);
679 	if(b == nil)
680 		return stringnode("read data block %#ux: %R", addr);
681 	return initxblock(b, smprint("'%s' fs root", name), xlocalrootgen, nil);
682 }
683 
684 int
685 xvacrootgen(void*, Block *b, int o, Tnode **tp)
686 {
687 	Entry e;
688 
689 	if(o >= 3)
690 		return -1;
691 	entryUnpack(&e, b->data, o);
692 	*tp = initxentry(e);
693 	return 1;
694 }
695 
696 Tnode*
697 initxroot(char *name, uchar score[VtScoreSize])
698 {
699 	Block *b;
700 
701 	b = dataBlock(score, BtDir, RootTag);
702 	if(b == nil)
703 		return stringnode("read data block %V: %R", score);
704 	return initxblock(b, smprint("'%s' fs root", name), xvacrootgen, nil);
705 }
706 Tnode*
707 initxdirentry(MetaEntry *me)
708 {
709 	DirEntry dir;
710 	Tnode *t;
711 
712 	if(!deUnpack(&dir, me))
713 		return stringnode("deUnpack: %R");
714 
715 	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);
716 	t->nkid = 1;
717 	t->kid = mallocz(sizeof(t->kid[0])*1, 1);
718 	t->kid[0] = stringnode(
719 		"qid=%#llux\n"
720 		"uid=%s gid=%s mid=%s\n"
721 		"mtime=%lud mcount=%lud ctime=%lud atime=%lud\n"
722 		"mode=%luo\n"
723 		"plan9 %d p9path %#llux p9version %lud\n"
724 		"qidSpace %d offset %#llux max %#llux",
725 		dir.qid,
726 		dir.uid, dir.gid, dir.mid,
727 		dir.mtime, dir.mcount, dir.ctime, dir.atime,
728 		dir.mode,
729 		dir.plan9, dir.p9path, dir.p9version,
730 		dir.qidSpace, dir.qidOffset, dir.qidMax);
731 	return t;
732 }
733 
734 int
735 metaentrygen(void *v, Block*, int o, Tnode **tp)
736 {
737 	Tnode *t;
738 	MetaBlock *mb;
739 	MetaEntry me;
740 
741 	mb = v;
742 	if(o >= mb->nindex)
743 		return -1;
744 	meUnpack(&me, mb, o);
745 
746 	t = stringnode("MetaEntry %d bytes", mb->size);
747 	t->kid = mallocz(sizeof(t->kid[0])*1, 1);
748 	t->kid[0] = initxdirentry(&me);
749 	t->nkid = 1;
750 	*tp = t;
751 	return 1;
752 }
753 
754 int
755 metablockgen(void *v, Block *b, int o, Tnode **tp)
756 {
757 	Xblock *t;
758 	MetaBlock *mb;
759 
760 	if(o >= 1)
761 		return -1;
762 
763 	/* hack: reuse initxblock as a generic iterator */
764 	mb = v;
765 	t = (Xblock*)initxblock(b, "", metaentrygen, mb);
766 	t->str = smprint("MetaBlock %d/%d space used, %d add'l free %d/%d table used%s",
767 		mb->size, mb->maxsize, mb->free, mb->nindex, mb->maxindex,
768 		mb->botch ? " [BOTCH]" : "");
769 	t->printlabel = 0;
770 	*tp = t;
771 	return 1;
772 }
773 
774 /*
775  * attempt to guess at the type of data in the block.
776  * it could just be data from a file, but we're hoping it's MetaBlocks.
777  */
778 Tnode*
779 initxdatablock(Block *b, uint n)
780 {
781 	MetaBlock mb;
782 
783 	if(n > h.blockSize)
784 		n = h.blockSize;
785 
786 	if(mbUnpack(&mb, b->data, n))
787 		return initxblock(b, "metadata", metablockgen, copyMetaBlock(mb));
788 
789 	return initxblock(b, "data", nil, nil);
790 }
791 
792 int
793 parseScore(uchar *score, char *buf, int n)
794 {
795 	int i, c;
796 
797 	memset(score, 0, VtScoreSize);
798 
799 	if(n < VtScoreSize*2)
800 		return 0;
801 	for(i=0; i<VtScoreSize*2; i++){
802 		if(buf[i] >= '0' && buf[i] <= '9')
803 			c = buf[i] - '0';
804 		else if(buf[i] >= 'a' && buf[i] <= 'f')
805 			c = buf[i] - 'a' + 10;
806 		else if(buf[i] >= 'A' && buf[i] <= 'F')
807 			c = buf[i] - 'A' + 10;
808 		else{
809 			return 0;
810 		}
811 
812 		if((i & 1) == 0)
813 			c <<= 4;
814 
815 		score[i>>1] |= c;
816 	}
817 	return 1;
818 }
819 
820 int
821 scoreFmt(Fmt *f)
822 {
823 	uchar *v;
824 	int i;
825 	u32int addr;
826 
827 	v = va_arg(f->args, uchar*);
828 	if(v == nil){
829 		fmtprint(f, "*");
830 	}else if((addr = globalToLocal(v)) != NilBlock)
831 		fmtprint(f, "0x%.8ux", addr);
832 	else{
833 		for(i = 0; i < VtScoreSize; i++)
834 			fmtprint(f, "%2.2ux", v[i]);
835 	}
836 
837 	return 0;
838 }
839 
840 Atree*
841 atreeinit(char *arg)
842 {
843 	Atree *a;
844 	uchar score[VtScoreSize];
845 
846 	vtAttach();
847 
848 	fmtinstall('V', scoreFmt);
849 	fmtinstall('R', vtErrFmt);
850 
851 	z = vtDial(nil, 1);
852 	if(z == nil)
853 		fprint(2, "warning: cannot dial venti: %R\n");
854 	if(!vtConnect(z, 0)){
855 		fprint(2, "warning: cannot connect to venti: %R\n");
856 		z = nil;
857 	}
858 	a = mallocz(sizeof(Atree), 1);
859 	if(strncmp(arg, "vac:", 4) == 0){
860 		if(!parseScore(score, arg+4, strlen(arg+4))){
861 			fprint(2, "cannot parse score\n");
862 			return nil;
863 		}
864 		a->root = initxvacroot(score);
865 	}else
866 		a->root = initxcache(arg);
867 	a->resizefd = -1;
868 	return a;
869 }
870 
871 /* --- tree.c */
872 enum
873 {
874 	Nubwidth = 11,
875 	Nubheight = 11,
876 	Linewidth = Nubwidth*2+4,
877 };
878 
879 uint
880 drawtext(char *s, Image *m, Image *clipr, Point o)
881 {
882 	char *t, *nt, *e;
883 	uint dy;
884 
885 	if(s == nil)
886 		s = "???";
887 
888 	dy = 0;
889 	for(t=s; t&&*t; t=nt){
890 		if(nt = strchr(t, '\n')){
891 			e = nt;
892 			nt++;
893 		}else
894 			e = t+strlen(t);
895 
896 		_string(m, Pt(o.x, o.y+dy), display->black, ZP, display->defaultfont,
897 			t, nil, e-t, clipr->clipr, nil, ZP, SoverD);
898 		dy += display->defaultfont->height;
899 	}
900 	return dy;
901 }
902 
903 void
904 drawnub(Image *m, Image *clipr, Point o, Tnode *t)
905 {
906 	clipr = nil;
907 
908 	if(t->nkid == 0)
909 		return;
910 	if(t->nkid == -1 && t->expand == nil)
911 		return;
912 
913 	o.y += (display->defaultfont->height-Nubheight)/2;
914 	draw(m, rectaddpt(Rect(0,0,1,Nubheight), o), display->black, clipr, ZP);
915 	draw(m, rectaddpt(Rect(0,0,Nubwidth,1), o), display->black, clipr, o);
916 	draw(m, rectaddpt(Rect(Nubwidth-1,0,Nubwidth,Nubheight), o),
917 		display->black, clipr, addpt(o, Pt(Nubwidth-1, 0)));
918 	draw(m, rectaddpt(Rect(0, Nubheight-1, Nubwidth, Nubheight), o),
919 		display->black, clipr, addpt(o, Pt(0, Nubheight-1)));
920 
921 	draw(m, rectaddpt(Rect(0, Nubheight/2, Nubwidth, Nubheight/2+1), o),
922 		display->black, clipr, addpt(o, Pt(0, Nubheight/2)));
923 	if(!t->expanded)
924 		draw(m, rectaddpt(Rect(Nubwidth/2, 0, Nubwidth/2+1, Nubheight), o),
925 			display->black, clipr, addpt(o, Pt(Nubwidth/2, 0)));
926 
927 }
928 
929 uint
930 drawnode(Tnode *t, Image *m, Image *clipr, Point o)
931 {
932 	int i;
933 	char *fs, *s;
934 	uint dy;
935 	Point oo;
936 
937 	if(t == nil)
938 		return 0;
939 
940 	t->offset = o;
941 
942 	oo = Pt(o.x+Nubwidth+2, o.y);
943 //	if(t->draw)
944 //		dy = (*t->draw)(t, m, clipr, oo);
945 //	else{
946 		fs = nil;
947 		if(t->str)
948 			s = t->str;
949 	//	else if(t->strfn)
950 	//		fs = s = (*t->strfn)(t);
951 		else
952 			s = "???";
953 		dy = drawtext(s, m, clipr, oo);
954 		free(fs);
955 //	}
956 
957 	if(t->expanded){
958 		if(t->nkid == -1 && t->expand)
959 			(*t->expand)(t);
960 		oo = Pt(o.x+Nubwidth+(Linewidth-Nubwidth)/2, o.y+dy);
961 		for(i=0; i<t->nkid; i++)
962 			oo.y += drawnode(t->kid[i], m, clipr, oo);
963 		dy = oo.y - o.y;
964 	}
965 	drawnub(m, clipr, o, t);
966 	return dy;
967 }
968 
969 void
970 drawtree(Tree *t, Image *m, Rectangle r)
971 {
972 	Point p;
973 
974 	draw(m, r, display->white, nil, ZP);
975 
976 	replclipr(t->clipr, 1, r);
977 	p = addpt(t->offset, r.min);
978 	drawnode(t->root, m, t->clipr, p);
979 }
980 
981 Tnode*
982 findnode(Tnode *t, Point p)
983 {
984 	int i;
985 	Tnode *tt;
986 
987 	if(ptinrect(p, rectaddpt(Rect(0,0,Nubwidth, Nubheight), t->offset)))
988 		return t;
989 	if(!t->expanded)
990 		return nil;
991 	for(i=0; i<t->nkid; i++)
992 		if(tt = findnode(t->kid[i], p))
993 			return tt;
994 	return nil;
995 }
996 
997 void
998 usage(void)
999 {
1000 	fprint(2, "usage: vtree /dev/sdC0/fossil\n");
1001 	exits("usage");
1002 }
1003 
1004 Tree t;
1005 
1006 void
1007 eresized(int new)
1008 {
1009 	Rectangle r;
1010 	r = screen->r;
1011 	if(new && getwindow(display, Refnone) < 0)
1012 		fprint(2,"can't reattach to window");
1013 	drawtree(&t, screen, screen->r);
1014 }
1015 
1016 enum
1017 {
1018 	Left = 1<<0,
1019 	Middle = 1<<1,
1020 	Right = 1<<2,
1021 
1022 	MMenu = 2,
1023 };
1024 
1025 char *items[] = { "exit", 0 };
1026 enum { IExit, };
1027 
1028 Menu menu;
1029 
1030 void
1031 main(int argc, char **argv)
1032 {
1033 	int n;
1034 	char *dir;
1035 	Event e;
1036 	Point op, p;
1037 	Tnode *tn;
1038 	Mouse m;
1039 	int Eready;
1040 	Atree *fs;
1041 
1042 	ARGBEGIN{
1043 	case 'a':
1044 		showinactive = 1;
1045 		break;
1046 	default:
1047 		usage();
1048 	}ARGEND
1049 
1050 	switch(argc){
1051 	default:
1052 		usage();
1053 	case 1:
1054 		dir = argv[0];
1055 		break;
1056 	}
1057 
1058 	fs = atreeinit(dir);
1059 	initdraw(0, "/lib/font/bit/lucidasans/unicode.8.font", "tree");
1060 	t.root = fs->root;
1061 	t.offset = ZP;
1062 	t.clipr = allocimage(display, Rect(0,0,1,1), GREY1, 1, DOpaque);
1063 
1064 	eresized(0);
1065 	flushimage(display, 1);
1066 
1067 	einit(Emouse);
1068 
1069 	menu.item = items;
1070 	menu.gen = 0;
1071 	menu.lasthit = 0;
1072 	if(fs->resizefd > 0){
1073 		Eready = 1<<3;
1074 		estart(Eready, fs->resizefd, 1);
1075 	}else
1076 		Eready = 0;
1077 
1078 	for(;;){
1079 		switch(n=eread(Emouse|Eready, &e)){
1080 		default:
1081 			if(Eready && n==Eready)
1082 				eresized(0);
1083 			break;
1084 		case Emouse:
1085 			m = e.mouse;
1086 			switch(m.buttons){
1087 			case Left:
1088 				op = t.offset;
1089 				p = m.xy;
1090 				do {
1091 					t.offset = addpt(t.offset, subpt(m.xy, p));
1092 					p = m.xy;
1093 					eresized(0);
1094 					m = emouse();
1095 				}while(m.buttons == Left);
1096 				if(m.buttons){
1097 					t.offset = op;
1098 					eresized(0);
1099 				}
1100 				break;
1101 			case Middle:
1102 				n = emenuhit(MMenu, &m, &menu);
1103 				if(n == -1)
1104 					break;
1105 				switch(n){
1106 				case IExit:
1107 					exits(nil);
1108 				}
1109 				break;
1110 			case Right:
1111 				do
1112 					m = emouse();
1113 				while(m.buttons == Right);
1114 				if(m.buttons)
1115 					break;
1116 				tn = findnode(t.root, m.xy);
1117 				if(tn){
1118 					tn->expanded = !tn->expanded;
1119 					eresized(0);
1120 				}
1121 				break;
1122 			}
1123 		}
1124 	}
1125 }
1126