shithub: riscv

Download patch

ref: bc1ff6985c03402497a052d2b4e2a299b3cdf218
parent: 8fa679d04171417d9420b91471cf45e148b4cc50
author: cinap_lenrek <cinap_lenrek@localhost>
date: Sun Jun 26 03:03:12 EDT 2011

add hgfs, a mercurial filesystem

--- /dev/null
+++ b/sys/src/cmd/hgfs/dat.h
@@ -1,0 +1,84 @@
+enum {
+	MAXPATH = 1024,
+	BUFSZ = 1024,
+	HASHSZ = 20,
+};
+
+typedef struct Revlog Revlog;
+typedef struct Revmap Revmap;
+typedef struct Revinfo Revinfo;
+typedef struct Revtree Revtree;
+typedef struct Revnode Revnode;
+typedef struct Revfile Revfile;
+
+struct Revmap
+{
+	int	rev;
+	int	p1rev;
+	int	p2rev;
+	int	baserev;
+	int	linkrev;
+
+	uchar	hash[HASHSZ];
+
+	int	flags;
+
+	vlong	hoff;
+	vlong	hlen;
+
+	vlong	flen;
+
+	void	*aux;
+};
+
+struct Revlog
+{
+	int	ifd;
+	int	dfd;
+
+	int	nmap;
+	Revmap	*map;
+};
+
+struct Revnode
+{
+	char	*name;
+	uchar	*hash;
+	uvlong	path;
+
+	Revnode	*up;
+	Revnode	*next;
+	Revnode	*down;
+};
+
+struct Revinfo
+{
+	uchar	chash[HASHSZ];
+	uchar	mhash[HASHSZ];
+
+	char	*who;
+	char	*why;
+	long	when;
+};
+
+struct Revtree
+{
+	Ref;
+
+	int	level;
+
+	Revinfo	*info;
+	Revnode	*root;
+};
+
+struct Revfile
+{
+	int	level;
+
+	Revinfo	*info;
+	Revtree	*tree;
+	Revnode	*node;
+
+	char	*buf;
+	int	fd;
+};
--- /dev/null
+++ b/sys/src/cmd/hgfs/fns.h
@@ -1,0 +1,30 @@
+/* hash */
+int Hfmt(Fmt *f);
+int strhash(char *s, uchar *h);
+int fhash(int fd, uchar p1[], uchar p2[], uchar h[]);
+
+/* patch */
+int fpatchmark(int pfd, char *mark);
+int fpatch(int ofd, int bfd, int pfd);
+
+/* zip */
+int funzip(int ofd, int zfd, int len);
+
+/* revlog */
+int fmktemp(void);
+int revlogopen(Revlog *r, char *path, int mode);
+void revlogclose(Revlog *r);
+int revlogextract(Revlog *r, int rev, int ofd);
+uchar *revhash(Revlog *r, int rev);
+int hashrev(Revlog *r, uchar hash[]);
+int revlogopentemp(Revlog *r, int rev);
+
+/* info */
+Revinfo *loadrevinfo(Revlog *changelog, int rev);
+
+/* tree */
+char *nodepath(char *s, char *e, Revnode *nd);
+Revtree *loadfilestree(Revlog *changelog, Revlog *manifest, Revinfo *ri);
+Revtree *loadchangestree(Revlog *changelog, Revlog *manifest, Revinfo *ri);
+void closerevtree(Revtree *t);
+
--- /dev/null
+++ b/sys/src/cmd/hgfs/fs.c
@@ -1,0 +1,587 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "dat.h"
+#include "fns.h"
+
+#include <ctype.h>
+#include <flate.h>
+#include <auth.h>
+#include <fcall.h>
+#include <9p.h>
+
+enum {
+	Qroot,
+		Qrev,
+			Qrev1,
+			Qrev2,
+			Qlog,
+			Qwho,
+			Qwhy,
+			Qfiles,
+			Qchanges,
+				Qtree,
+};
+
+static char *nametab[Qtree+1] = {
+	"/",
+		nil,
+			"rev1",
+			"rev2",
+			"log",
+			"who",
+			"why",
+			"files",
+			"changes",
+				nil,
+};
+
+static Revlog changelog;
+static Revlog manifest;
+
+static Revinfo*
+getrevinfo(int rev)
+{
+	Revinfo *ri;
+
+	if(rev < 0 || rev >= changelog.nmap)
+		return nil;
+	if(ri = changelog.map[rev].aux)
+		return ri;
+	if(ri = loadrevinfo(&changelog, rev))
+		changelog.map[rev].aux = ri;
+	return ri;
+}
+
+static char*
+fsmkuid(char *s)
+{
+	if(s){
+		char *x;
+
+		while(x = strchr(s, '<'))
+			s = x+1;
+		s = estrdup9p(s);
+		if(x = strchr(s, '>'))
+			*x = 0;
+		if(x = strchr(s, '@'))
+			*x = 0;
+		if(x = strchr(s, '\n'))
+			*x = 0;
+	}
+	if(s == nil || *s == 0){
+		free(s);
+		s = estrdup9p("hgfs");
+	}
+	return s;
+}
+
+static void
+fsmkqid(Qid *q, int level, void *aux)
+{
+	Revnode *nd;
+	Revinfo *ri;
+
+	switch(level){
+	case Qroot:
+		q->type = QTDIR;
+		q->path = Qroot;
+		q->vers = 0;
+		break;
+	case Qrev:
+	case Qfiles:
+	case Qchanges:
+		q->type = QTDIR;
+		if(0){
+	case Qrev1:
+	case Qrev2:
+	case Qlog:
+	case Qwho:
+	case Qwhy:
+		q->type = 0;
+		}
+		ri = aux;
+		q->path = *((uvlong*)ri->chash) + (level - Qrev);
+		q->vers = 0;
+		break;
+	case Qtree:
+		nd = aux;
+		if(nd->hash){
+			q->type = 0;
+		} else {
+			q->type = QTDIR;
+		}
+		q->path = nd->path;
+		q->vers = 0;
+		break;
+	}
+}
+
+static void
+fsmkdir(Dir *d, int level, void *aux)
+{
+	char buf[64], *s;
+	Revnode *nd;
+	Revinfo *ri;
+	int rev;
+
+	memset(d, 0, sizeof(*d));
+
+	fsmkqid(&d->qid, level, aux);
+
+	d->mode = 0444;
+	if(d->qid.type == QTDIR)
+		d->mode |= DMDIR | 0111;
+
+	s = nil;
+	ri = nil;
+	switch(level){
+	case Qroot:
+		goto Namegen;
+	case Qrev:
+	case Qrev1:
+	case Qrev2:
+		ri = aux;
+		rev = hashrev(&changelog, ri->chash);
+		if(level == Qrev1)
+			rev = changelog.map[rev].p1rev;
+		else if(level == Qrev2)
+			rev = changelog.map[rev].p2rev;
+		if(rev >= 0)
+			snprint(s = buf, sizeof(buf), "%d.%H", rev, changelog.map[rev].hash);
+		if(level == Qrev){
+			d->name = estrdup9p(buf);
+			break;
+		}
+		goto Strgen;
+	case Qlog:
+		ri = aux;
+		if((rev = hashrev(&changelog, ri->chash)) >= 0)
+			d->length = changelog.map[rev].flen;
+		goto Namegen;
+	case Qwho:
+		ri = aux;
+		s = ri->who;
+		goto Strgen;
+	case Qwhy:
+		ri = aux;
+		s = ri->why;
+	Strgen:
+		d->length = s ? strlen(s)+1 : 0;
+	case Qfiles:
+	case Qchanges:
+		ri = aux;
+		/* no break */
+	Namegen:
+		d->name = estrdup9p(nametab[level]);
+		break;
+	case Qtree:
+		nd = aux;
+		d->name = estrdup9p(nd->name);
+		if(nd->hash){
+			char path[MAXPATH];
+			Revlog rl;
+
+			nodepath(seprint(path, path+MAXPATH, ".hg/store/data"), path+MAXPATH, nd);
+			if(revlogopen(&rl, path, OREAD) < 0)
+				break;
+			if((rev = hashrev(&rl, nd->hash)) >= 0){
+				d->length = rl.map[rev].flen;
+				ri = getrevinfo(rl.map[rev].linkrev);
+			}
+			revlogclose(&rl);
+		}
+		break;
+	}
+
+	if(ri){
+		d->atime = d->mtime = ri->when;
+		d->muid = fsmkuid(ri->who);
+		d->uid = fsmkuid(ri->who);
+	} else
+		d->atime = d->mtime = time(0);
+
+	if(d->uid == nil)
+		d->uid = fsmkuid(nil);
+	if(d->gid == nil)
+		d->gid = fsmkuid(nil);
+	if(d->muid == nil)
+		d->muid = fsmkuid(nil);
+}
+
+static void
+fsattach(Req *r)
+{
+	Revfile *rf;
+
+	if(r->ifcall.aname && r->ifcall.aname[0]){
+		respond(r, "invalid attach specifier");
+		return;
+	}
+	r->fid->qid.path = Qroot;
+	r->fid->qid.type = QTDIR;
+	r->fid->qid.vers = 0;
+	r->ofcall.qid = r->fid->qid;
+
+	rf = emalloc9p(sizeof(*rf));
+	rf->level = Qroot;
+	rf->info = nil;
+	rf->tree = nil;
+	rf->node = nil;
+
+	rf->fd = -1;
+	rf->buf = nil;
+
+	r->fid->aux = rf;
+
+	respond(r, nil);
+}
+
+static void
+fsstat(Req *r)
+{
+	Revfile *rf;
+
+	rf = r->fid->aux;
+	if(rf->level < Qtree)
+		fsmkdir(&r->d, rf->level,  rf->info);
+	else
+		fsmkdir(&r->d, rf->level,  rf->node);
+	respond(r, nil);
+}
+
+static int
+findrev(Revlog *rl, char *name)
+{
+	uchar hash[HASHSZ];
+	int n, i, rev;
+	char *s;
+
+	rev = strtol(name, &s, 10);
+	if(s > name && (*s == 0 || ispunct(*s)))
+		return rev;
+	rev = -1;
+	if(s = strchr(name, '.'))
+		name = s+1;
+	if((n = strhash(name, hash)) > 0){
+		for(i=0; i<rl->nmap; i++){
+			if(memcmp(rl->map[i].hash, hash, n) == 0){
+				if(rev < 0)
+					rev = i;
+				else {
+					rev = -1;
+					break;
+				}
+			}
+		}
+	}
+	return rev;
+}
+
+static char*
+fswalk1(Fid *fid, char *name, Qid *qid)
+{
+	Revfile *rf;
+	Revnode *nd;
+	int i;
+
+	if(!(fid->qid.type&QTDIR))
+		return "walk in non-directory";
+
+	rf = fid->aux;
+	if(strcmp(name, "..") == 0){
+		switch(rf->level){
+		case Qroot:
+			break;
+		case Qrev:
+			rf->info = nil;
+			rf->level = Qroot;
+			break;
+		case Qfiles:
+		case Qchanges:
+			closerevtree(rf->tree);
+			rf->tree = nil;
+			rf->level = Qrev;
+			break;
+		case Qtree:
+			if((rf->node = rf->node->up) == rf->tree->root)
+				rf->level = rf->tree->level;
+			break;
+		}
+	} else {
+		switch(rf->level){
+		case Qroot:
+			i = findrev(&changelog, name);
+			if(rf->info = getrevinfo(i)){
+				rf->level = Qrev;
+				break;
+			}
+		Notfound:
+			return "directory entry not found";
+			break;
+		case Qrev:
+			for(i = Qrev+1; i < Qtree; i++){
+				if(nametab[i] == nil)
+					continue;
+				if(strcmp(name, nametab[i]) == 0)
+					break;
+			}
+			switch(i){
+			case Qtree:
+				goto Notfound;
+			case Qfiles:
+				if((rf->tree = loadfilestree(&changelog, &manifest, rf->info)) == nil)
+					goto Notfound;
+				break;
+			case Qchanges:
+				if((rf->tree = loadchangestree(&changelog, &manifest, rf->info)) == nil)
+					goto Notfound;
+				break;
+			}
+			if(rf->tree){
+				rf->node = rf->tree->root;
+				rf->tree->level = i;
+			}
+			rf->level = i;
+			break;
+		case Qtree:
+		case Qfiles:
+		case Qchanges:
+			for(nd = rf->node->down; nd; nd = nd->next)
+				if(strcmp(nd->name, name) == 0)
+					break;
+			if(nd == nil)
+				goto Notfound;
+			rf->node = nd;
+			rf->level = Qtree;
+			break;
+		}
+	}
+
+	if(rf->level < Qtree)
+		fsmkqid(qid, rf->level, rf->info);
+	else
+		fsmkqid(qid, rf->level, rf->node);
+	fid->qid = *qid;
+
+	return nil;
+}
+
+static char*
+fsclone(Fid *oldfid, Fid *newfid)
+{
+	Revfile *orf, *rf;
+
+	rf = nil;
+	if(orf = oldfid->aux){
+		rf = emalloc9p(sizeof(*rf));
+		*rf = *orf;
+		if(rf->tree)
+			incref(rf->tree);
+		if(rf->fd >= 0)
+			rf->fd = dup(rf->fd, -1);
+		if(rf->buf)
+			rf->buf = estrdup9p(rf->buf);
+	}
+	newfid->aux = rf;
+	return nil;
+}
+
+static void
+fsdestroyfid(Fid *fid)
+{
+	Revfile *rf;
+
+	if(rf = fid->aux){
+		closerevtree(rf->tree);
+		if(rf->fd >= 0)
+			close(rf->fd);
+		free(rf->buf);
+		free(rf);
+	}
+}
+
+static void
+fsopen(Req *r)
+{
+	respond(r, nil);
+}
+
+static int
+rootgen(int i, Dir *d, void *)
+{
+	Revinfo *ri;
+
+	if((ri = getrevinfo(i)) == nil)
+		return -1;
+	fsmkdir(d, Qrev, ri);
+	return 0;
+}
+
+static int
+revgen(int i, Dir *d, void *aux)
+{
+	i += Qrev+1;
+	if(i >= Qtree)
+		return -1;
+	fsmkdir(d, i, aux);
+	return 0;
+}
+
+static int
+treegen(int i, Dir *d, void *aux)
+{
+	Revnode *nd = aux;
+
+	while(i > 0 && nd){
+		nd = nd->next;
+		i--;
+	}
+	if(i || nd == nil)
+		return -1;
+	fsmkdir(d, Qtree, nd);
+	return 0;
+}
+
+static void
+fsread(Req *r)
+{
+	Revfile *rf;
+
+	rf = r->fid->aux;
+	if(r->fid->qid.type == QTDIR){
+		switch(rf->level){
+		default:
+			respond(r, "bug in fsread");
+			return;
+		case Qroot:
+			dirread9p(r, rootgen, nil);
+			respond(r, nil);
+			return;
+		case Qrev:
+			dirread9p(r, revgen, rf->info);
+			respond(r, nil);
+			return;
+		case Qtree:
+		case Qfiles:
+		case Qchanges:
+			dirread9p(r, treegen, rf->node->down);
+			respond(r, nil);
+			return;
+		}
+	} else {
+		char buf[MAXPATH];
+		Revlog rl;
+		char *s;
+		int i, n;
+
+		switch(rf->level){
+		default:
+			respond(r, "bug in fsread");
+			return;
+		case Qlog:
+			if(rf->fd >= 0)
+				break;
+			if((rf->fd = revlogopentemp(&changelog, hashrev(&changelog, rf->info->chash))) < 0){
+				responderror(r);
+				return;
+			}
+			break;
+		case Qrev1:
+		case Qrev2:
+			s = nil;
+			if((i = hashrev(&changelog, rf->info->chash)) >= 0){
+				if(rf->level == Qrev1)
+					i = changelog.map[i].p1rev;
+				else
+					i = changelog.map[i].p2rev;
+				if(i >= 0)
+					snprint(s = buf, sizeof(buf), "%d.%H", i, changelog.map[i].hash);
+			}
+			goto Strgen;
+		case Qwho:
+			s = rf->info->who;
+			goto Strgen;
+		case Qwhy:
+			s = rf->info->why;
+		Strgen:
+			if(rf->buf == nil)
+				rf->buf = s ? smprint("%s\n", s) : estrdup9p("");
+			readstr(r, rf->buf);
+			respond(r, nil);
+			return;
+		case Qtree:
+			if(rf->fd >= 0)
+				break;
+			nodepath(seprint(buf, buf+sizeof(buf), ".hg/store/data"), buf+sizeof(buf), rf->node);
+			if(revlogopen(&rl, buf, OREAD) < 0){
+				responderror(r);
+				return;
+			}
+			if((rf->fd = revlogopentemp(&rl, hashrev(&rl, rf->node->hash))) < 0){
+				responderror(r);
+				revlogclose(&rl);
+				return;
+			}
+			revlogclose(&rl);
+			break;
+		}
+		if((n = pread(rf->fd, r->ofcall.data, r->ifcall.count, r->ifcall.offset)) < 0){
+			responderror(r);
+			return;
+		}
+		r->ofcall.count = n;
+		respond(r, nil);
+	}
+}
+
+Srv fs = 
+{
+	.attach=fsattach,
+	.stat=fsstat,
+	.walk1=fswalk1,
+	.clone=fsclone,
+	.destroyfid=fsdestroyfid,
+	.open=fsopen,
+	.read=fsread,
+};
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-D] [-m mtpt] [-s srv]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	char *srv, *mtpt;
+
+	inflateinit();
+	fmtinstall('H', Hfmt);
+
+	srv = nil;
+	mtpt = "/n/hg";
+
+	ARGBEGIN {
+	case 'D':
+		chatty9p++;
+		break;
+	case 'm':
+		mtpt = EARGF(usage());
+		break;
+	case 's':
+		srv = EARGF(usage());
+		break;
+	default:
+		usage();
+	} ARGEND;
+
+	if(revlogopen(&changelog, ".hg/store/00changelog", OREAD) < 0)
+		sysfatal("can't open changelog: %r\n");
+	if(revlogopen(&manifest, ".hg/store/00manifest", OREAD) < 0)
+		sysfatal("can't open menifest: %r\n");
+
+	postmountsrv(&fs, srv, mtpt, MREPL);
+}
--- /dev/null
+++ b/sys/src/cmd/hgfs/hash.c
@@ -1,0 +1,69 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "dat.h"
+#include "fns.h"
+
+#include <mp.h>
+#include <libsec.h>
+
+int
+Hfmt(Fmt *f)
+{
+	uchar *p;
+
+	p = va_arg(f->args, uchar*);
+	return fmtprint(f, 
+		"%.2x%.2x%.2x%.2x%.2x%.2x",
+		p[0], p[1], p[2], p[3], p[4], p[5]);
+}
+
+int
+fhash(int fd, uchar p1[], uchar p2[], uchar h[])
+{
+	DigestState *ds;
+	uchar buf[BUFSZ];
+	int n;
+
+	ds = nil;
+	memset(h, 0, HASHSZ);
+	if(memcmp(p1, p2, HASHSZ) > 0){
+		ds = sha1(p2, HASHSZ, nil, ds);
+		sha1(p1, HASHSZ, nil, ds);
+	} else {
+		ds = sha1(p1, HASHSZ, nil, ds);
+		sha1(p2, HASHSZ, nil, ds);
+	}
+	while((n = read(fd, buf, BUFSZ)) > 0)
+		sha1(buf, n, nil, ds);
+	sha1(buf, 0, h, ds);
+
+	return 0;
+}
+
+int
+strhash(char *s, uchar *h)
+{
+	uchar *b;
+	int n;
+
+	b = h;
+	memset(h, 0, HASHSZ);
+	n = HASHSZ*2;
+	while(*s && n > 0){
+		if(*s >= '0' && *s <= '9')
+			*h |= *s - '0';
+		else if(*s >= 'a' && *s <= 'f')
+			*h |= 10 + *s - 'a';
+		else if(*s >= 'A' && *s <= 'F')
+			*h |= 10 + *s - 'A';
+		else
+			break;
+		if(n-- & 1)
+			h++;
+		else
+			*h <<= 4;
+		s++;
+	}
+	return h - b;
+}
--- /dev/null
+++ b/sys/src/cmd/hgfs/info.c
@@ -1,0 +1,64 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "dat.h"
+#include "fns.h"
+
+Revinfo*
+loadrevinfo(Revlog *changelog, int rev)
+{
+	char buf[BUFSZ], *p, *e;
+	int fd, line, inmsg, n;
+	Revinfo *ri;
+
+	if((fd = revlogopentemp(changelog, rev)) < 0)
+		return nil;
+
+	seek(fd, 0, 2);
+	write(fd, "\n", 1);
+	seek(fd, 0, 0);
+
+	ri = malloc(sizeof(*ri));
+	memset(ri, 0, sizeof(*ri));
+
+	memmove(ri->chash, changelog->map[rev].hash, HASHSZ);
+
+	line = 0;
+	inmsg = 0;
+	p = buf;
+	e = buf + BUFSZ;
+	while((n = read(fd, p, e - p)) > 0){
+		p += n;
+		while((p > buf) && (e = memchr(buf, '\n', p - buf))){
+			*e++ = 0;
+
+			switch(line++){
+			case 0:
+				strhash(buf, ri->mhash);
+				break;
+			case 1:
+				ri->who = strdup(buf);
+				break;
+			case 2:
+				ri->when = strtol(buf, nil, 10);
+				break;
+			default:
+				if(!inmsg){
+					if(*buf == 0)
+						inmsg = 1;
+				} else {
+					n = ri->why ? strlen(ri->why) : 0;
+					ri->why = realloc(ri->why, n + strlen(buf)+1);
+					strcpy(ri->why + n, buf);
+				}
+			}
+			p -= e - buf;
+			if(p > buf)
+				memmove(buf, e, p - buf);
+		}
+		e = buf + BUFSZ;
+	}
+	close(fd);
+
+	return ri;
+}
--- /dev/null
+++ b/sys/src/cmd/hgfs/mkfile
@@ -1,0 +1,10 @@
+</$objtype/mkfile
+
+TARG=hgfs
+CFLAGS=-FTVw
+
+HFILES=dat.h fns.h
+
+OFILES=zip.$O patch.$O hash.$O revlog.$O tree.$O info.$O fs.$O
+
+</sys/src/cmd/mkone
--- /dev/null
+++ b/sys/src/cmd/hgfs/patch.c
@@ -1,0 +1,159 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "dat.h"
+#include "fns.h"
+
+int
+fcopy(int dfd, int sfd, vlong len)
+{
+	uchar buf[BUFSZ];
+	int n;
+
+	while(len > 0){
+		if((n = BUFSZ) > len)
+			n = len;
+		if((n = read(sfd, buf, n)) < 0)
+			return -1;
+		if(write(dfd, buf, n) != n)
+			return -1;
+		len -= n;
+	}
+	return 0;
+}
+
+static uchar patchmark[12] = {
+	0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff,
+};
+
+int
+fpatchmark(int pfd, char *)
+{
+	if(write(pfd, patchmark, 12) != 12)
+		return -1;
+	return 0;
+}
+
+typedef struct Frag Frag;
+struct Frag
+{
+	Frag	*next;
+	int	fd;
+	vlong	len;
+	vlong	off;
+};
+
+int
+fpatch(int ofd, int bfd, int pfd)
+{
+	vlong off, fstart, fend, start, end, len;
+	int err, front, back;
+	Frag *h, *f, *p;
+	uchar buf[12];
+
+	h = nil;
+	err = -1;
+
+	if(bfd >= 0){
+		h = malloc(sizeof(Frag));
+		h->next = nil;
+		h->off = 0;
+		h->fd = bfd;
+		h->len = seek(h->fd, 0, 2);
+		if(h->len < 0)
+			goto errout;
+	}
+
+	off = 0;
+	while(pfd >= 0){
+		if(readn(pfd, buf, 12) != 12)
+			break;
+
+		if(memcmp(buf, patchmark, 12) == 0){
+			off = 0;
+			continue;
+		}
+
+		start = buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3];
+		end = buf[4]<<24 | buf[5]<<16 | buf[6]<<8 | buf[7];
+		len = buf[8]<<24 | buf[9]<<16 | buf[10]<<8 | buf[11];
+
+		if(start > end){
+			werrstr("bad patch: start > end");
+			goto errout;
+		}
+
+		start += off;
+		end += off;
+		off += start + len - end;
+
+		fstart = 0;
+		for(f = h; f; f = f->next, fstart = fend){
+			fend = fstart + f->len;
+			if(fend <= start)
+				continue;
+			if(fstart >= end)
+				break;
+
+			front = start > fstart;
+			back = end < fend;
+			if(front && back){
+				p = malloc(sizeof(Frag));
+				*p = *f;
+				f->next = p;
+				f->len = start - fstart;
+				p->off += end - fstart;
+				p->len -= end - fstart;
+				break;
+			} else if(back){
+				f->off += end - fstart;
+				f->len -= end - fstart;
+				break;
+			} else if(front){
+				f->len = start - fstart;
+			} else {
+				f->len = 0;
+			}
+		}
+
+		fstart = 0;
+		for(p = nil, f = h; f && fstart < start; p = f, f = f->next)
+			fstart += f->len;
+
+		f = malloc(sizeof(Frag));
+		f->fd = pfd;
+		f->len = len;
+		f->off = seek(f->fd, 0, 1);
+
+		if(p){
+			f->next = p->next;
+			p->next = f;
+		} else {
+			f->next = h;
+			h = f;
+		}
+
+		if(f->off < 0)
+			goto errout;
+		if(seek(pfd, f->len, 1) < 0)
+			goto errout;
+	}
+
+	for(f = h; f; f = f->next){
+		if(seek(f->fd, f->off, 0) < 0)
+			goto errout;
+		if(fcopy(ofd, f->fd, f->len) < 0)
+			goto errout;
+	}
+	err = 0;
+
+errout:
+	while(f = h){
+		h = f->next;
+		free(f);
+	}
+
+	return err;
+}
--- /dev/null
+++ b/sys/src/cmd/hgfs/revlog.c
@@ -1,0 +1,272 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "dat.h"
+#include "fns.h"
+
+int
+fmktemp(void)
+{
+	char temp[MAXPATH];
+
+	snprint(temp, sizeof(temp), "/tmp/hgXXXXXXXXXXX");
+	return create(mktemp(temp), OTRUNC|ORCLOSE|ORDWR, 0666);
+}
+
+static void
+readindex(Revlog *r)
+{
+	uchar buf[64];
+	Revmap *m;
+	int rev;
+
+	seek(r->ifd, 0, 0);
+	for(rev=0;;rev++){
+		if(readn(r->ifd, buf, sizeof(buf)) != sizeof(buf))
+			break;
+		if((rev % 16) == 0)
+			r->map = realloc(r->map, sizeof(r->map[0])*(rev+16));
+		m = &r->map[rev];
+		memset(m, 0, sizeof(*m));
+		m->hoff = (vlong)buf[0]<<40 | 
+			(vlong)buf[1]<<32 |
+			(vlong)buf[2]<<24 | 
+			(vlong)buf[3]<<16 | 
+			(vlong)buf[4]<<8 | 
+			(vlong)buf[5];
+		if(rev == 0)
+			m->hoff &= 0xFFFF;
+		m->rev = rev;
+		m->flags = buf[6]<<8 | buf[7];
+		m->hlen = buf[8]<<24 | buf[9]<<16 | buf[10]<<8 | buf[11];
+		m->flen = buf[12]<<24 | buf[13]<<16 | buf[14]<<8 | buf[15];
+		m->baserev = buf[16]<<24 | buf[17]<<16 | buf[18]<<8 | buf[19];
+		m->linkrev = buf[20]<<24 | buf[21]<<16 | buf[22]<<8 | buf[23];
+		m->p1rev = buf[24]<<24 | buf[25]<<16 | buf[26]<<8 | buf[27];
+		m->p2rev = buf[28]<<24 | buf[29]<<16 | buf[30]<<8 | buf[31];
+		memmove(m->hash, buf+32, HASHSZ);
+
+		if(r->dfd < 0){
+			m->hoff = seek(r->ifd, 0, 1);
+			seek(r->ifd, m->hlen, 1);
+		}
+	}
+	r->nmap = rev;
+}
+
+int
+revlogopen(Revlog *r, char *path, int mode)
+{
+	r->ifd = -1;
+	r->dfd = -1;
+	path = smprint("%s.i", path);
+	if((r->ifd = open(path, mode)) < 0){
+		free(path);
+		return -1;
+	}
+	path[strlen(path)-1] = 'd';
+	r->dfd = open(path, mode);
+	free(path);
+	r->nmap = 0;
+	r->map = nil;
+	readindex(r);
+	return 0;
+}
+
+void
+revlogclose(Revlog *r)
+{
+	if(r->ifd >= 0){
+		close(r->ifd);
+		r->ifd = -1;
+	}
+	if(r->dfd >= 0){
+		close(r->dfd);
+		r->dfd = -1;
+	}
+	free(r->map);
+	r->map = nil;
+	r->nmap = 0;
+}
+
+uchar*
+revhash(Revlog *r, int rev)
+{
+	static uchar nullid[HASHSZ];
+	if(rev < 0 || rev >= r->nmap)
+		return nullid;
+	return r->map[rev].hash;
+}
+
+int
+hashrev(Revlog *r, uchar hash[])
+{
+	int rev;
+
+	for(rev=0; rev<r->nmap; rev++)
+		if(memcmp(r->map[rev].hash, hash, HASHSZ) == 0)
+			return rev;
+	return -1;
+}
+
+static int
+prevlogrev(Revlog *r, int rev)
+{
+	if(r->map[rev].baserev == rev)
+		return -1;
+	return rev-1;
+}
+
+static int
+prevbundlerev(Revlog *r, int rev)
+{
+	if(r->map[rev].baserev == rev)
+		return -1;
+	return r->map[rev].p1rev;
+}
+
+static Revmap**
+getchain1(Revlog *r, int rev, int *count, int (*next)(Revlog *, int))
+{
+	Revmap **chain;
+
+	if(rev < 0 || rev >= r->nmap){
+		if(*count <= 0)
+			return nil;
+		chain = malloc(sizeof(chain[0]) * ((*count)+1));
+		chain[*count] = nil;
+		*count = 0;
+	}else{
+		(*count)++;
+		if(chain = getchain1(r, (*next)(r, rev), count, next))
+			chain[(*count)++] = &r->map[rev];
+	}
+	return chain;
+}
+
+static Revmap**
+getchain(Revlog *r, int rev, int (*next)(Revlog *, int))
+{
+	int count = 0;
+	return getchain1(r, rev, &count, next);
+}
+
+int
+revlogextract(Revlog *r, int rev, int ofd)
+{
+	int err, hfd, pfd, bfd;
+	uchar hash[HASHSZ];
+	Revmap **chain, *m;
+	char buf[32];
+	vlong off;
+	int i;
+
+	err = -1;
+	bfd = -1;
+	pfd = -1;
+
+	if((chain = getchain(r, rev, prevlogrev)) == nil){
+		werrstr("bad patch chain");
+		goto errout;
+	}
+
+	off = seek(ofd, 0, 1);
+	if(off < 0){
+		werrstr("seek outfile: %r");
+		goto errout;
+	}
+	hfd = r->dfd < 0 ? r->ifd : r->dfd;
+	for(i=0; m = chain[i]; i++){
+		if(seek(hfd, m->hoff, 0) < 0){
+			werrstr("seek index: %r");
+			goto errout;
+		}
+		if(m == chain[0] && m->baserev == m->rev){
+			if(chain[1]){
+				if(bfd >= 0 && bfd != ofd)
+					close(bfd);
+				if((bfd = fmktemp()) < 0){
+					werrstr("create basefile: %r");
+					goto errout;
+				}
+			} else
+				bfd = ofd;
+			if(funzip(bfd, hfd, m->hlen) < 0){
+				werrstr("unzip basefile: %r");
+				goto errout;
+			}
+		} else {
+			if(pfd < 0){
+				if((pfd = fmktemp()) < 0){
+					werrstr("create patchfile: %r");
+					goto errout;
+				}
+			}
+
+			/* put a mark before the patch data */
+			snprint(buf, sizeof(buf), "%H", m->hash);
+			if(fpatchmark(pfd, buf) < 0){
+				werrstr("patchmark: %r");
+				goto errout;
+			}
+
+			if(funzip(pfd, hfd, m->hlen) < 0){
+				werrstr("unzip patchfile: %r");
+				goto errout;
+			}
+		}
+	}
+	m = chain[i-1];
+
+	if(pfd >= 0 && bfd >= 0 && bfd != ofd){
+		if(seek(pfd, 0, 0) < 0){
+			werrstr("seek patchfile: %r");
+			goto errout;
+		}
+		if(fpatch(ofd, bfd, pfd) < 0){
+			werrstr("patch: %r");
+			goto errout;
+		}
+	}
+
+	if(seek(ofd, off, 0) < 0){
+		werrstr("seek outfile: %r");
+		goto errout;
+	}
+	if(fhash(ofd, revhash(r, m->p1rev), revhash(r, m->p2rev), hash) < 0){
+		werrstr("hash outfile: %r");
+		goto errout;
+	}
+	if(memcmp(m->hash, hash, HASHSZ)){
+		werrstr("got bad hash");
+		goto errout;
+	}
+	err = 0;
+
+errout:
+	if(pfd >= 0)
+		close(pfd);
+	if(bfd >= 0 && bfd != ofd)
+		close(bfd);
+	free(chain);
+
+	return err;
+}
+
+int
+revlogopentemp(Revlog *r, int rev)
+{
+	int fd;
+
+	if((fd = fmktemp()) < 0)
+		return -1;
+	if(revlogextract(r, rev, fd) < 0){
+		close(fd);
+		return -1;
+	}
+	if(seek(fd, 0, 0) < 0){
+		close(fd);
+		return -1;
+	}
+	return fd;
+}
--- /dev/null
+++ b/sys/src/cmd/hgfs/tree.c
@@ -1,0 +1,267 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "dat.h"
+#include "fns.h"
+
+char*
+nodepath(char *s, char *e, Revnode *nd)
+{
+	static char *frogs[] = {
+		"con", "prn", "aux", "nul",
+		"com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", "com9",
+		"lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9",
+	};
+	char *p;
+	int i;
+
+	if(nd == nil || nd->name == nil)
+		return s;
+
+	s = seprint(nodepath(s, e, nd->up), e, "/");
+
+	p = nd->name;
+	for(i=0; i<nelem(frogs); i++)
+		if(strcmp(frogs[i], p) == 0)
+			return seprint(s, e, "%.2s~%.2x%s", p, p[2], p+3);
+
+	for(; s+4 < e && *p; p++){
+		if(*p == '_'){
+			*s++ = '_';
+			*s++ = '_';
+		} else if(*p >= 'A' && *p <= 'Z'){
+			*s++ = '_';
+			*s++ = 'a' + (*p - 'A');
+		} else if(*p >= 126 || strchr("\\:*?\"<>|", *p)){
+			*s++ = '~';
+			s = seprint(s, e, "%.2x", *p);
+		} else
+			*s++ = *p;
+	}
+	*s = 0;
+
+	return s;
+}
+
+static void
+addnode(Revnode *d, char *path, uchar *hash)
+{
+	char *slash, *x;
+	Revnode *c, *p;
+
+	while(path && *path){
+		if(slash = strchr(path, '/'))
+			*slash++ = 0;
+		p = nil;
+		for(c = d->down; c; p = c, c = c->next)
+			if(strcmp(c->name, path) == 0)
+				break;
+		if(c == nil){
+			c = malloc(sizeof(*c) + (!slash ? HASHSZ : 0) + strlen(path)+1);
+			c->path = 1;
+			x = (char*)&c[1];
+			if(!slash){
+				memmove(c->hash = (uchar*)x, hash, HASHSZ);
+				x += HASHSZ;
+			} else
+				c->hash = nil;
+			strcpy(c->name = x, path);
+			c->up = d;
+			c->down = nil;
+			if(p){
+				c->next = p->next;
+				p->next = c;
+			} else {
+				c->next = d->down;
+				d->down = c;
+			}
+
+			if(c->hash){
+				p = c;
+				p->path = *((uvlong*)c->hash);
+				while(d->up){
+					d->path += p->path;
+					p = d;
+					d = d->up;
+				}
+			}
+		}
+		d = c;
+		path = slash;
+	}
+}
+
+typedef struct Hashstr Hashstr;
+struct Hashstr
+{
+	Hashstr	*next;
+	char	str[];
+};
+
+static ulong
+hashstr(char *s)
+{
+	ulong h, t;
+	char c;
+
+	h = 0;
+	while(c = *s++){
+		t = h & 0xf8000000;
+		h <<= 5;
+		h ^= t>>27;
+		h ^= (ulong)c;
+	}
+	return h;
+}
+
+static int
+loadmanifest(Revnode *root, int fd, Hashstr **ht, int nh)
+{
+	char buf[BUFSZ], *p, *e;
+	uchar hash[HASHSZ];
+	int n;
+
+	p = buf;
+	e = buf + BUFSZ;
+	while((n = read(fd, p, e - p)) > 0){
+		p += n;
+		while((p > buf) && (e = memchr(buf, '\n', p - buf))){
+			*e++ = 0;
+
+			strhash(buf + strlen(buf) + 1, hash);
+			if(ht == nil)
+				addnode(root, buf, hash);
+			else {
+				Hashstr *he;
+
+				for(he = ht[hashstr(buf) % nh]; he; he = he->next){
+					if(strcmp(he->str, buf) == 0){
+						addnode(root, buf, hash);
+						break;
+					}
+				}
+			}
+
+			p -= e - buf;
+			if(p > buf)
+				memmove(buf, e, p - buf);
+		}
+		e = buf + BUFSZ;
+	}
+	return 0;
+}
+
+static Revtree*
+loadtree(Revlog *manifest, Revinfo *ri, Hashstr **ht, int nh)
+{
+	Revtree *t;
+	int fd;
+
+	if((fd = revlogopentemp(manifest, hashrev(manifest, ri->mhash))) < 0)
+		return nil;
+
+	t = malloc(sizeof(*t));
+	memset(t, 0, sizeof(*t));
+	incref(t);
+
+	t->info = ri;
+
+	t->root = malloc(sizeof(Revnode));
+	t->root->path = 0;
+	t->root->name = 0;
+	t->root->up = nil;
+	t->root->down = nil;
+	t->root->next = nil;
+	t->root->hash = nil;
+
+	if(loadmanifest(t->root, fd, ht, nh) < 0){
+		close(fd);
+		closerevtree(t);
+		return nil;
+	}
+
+	close(fd);
+
+	return t;
+}
+
+Revtree*
+loadfilestree(Revlog *, Revlog *manifest, Revinfo *ri)
+{
+	return loadtree(manifest, ri, nil, 0);
+}
+
+Revtree*
+loadchangestree(Revlog *changelog, Revlog *manifest, Revinfo *ri)
+{
+	char buf[BUFSZ], *p, *e;
+	Hashstr *ht[256], *he, **hp;
+	int fd, done, line, n;
+	Revtree *t;
+
+	if((fd = revlogopentemp(changelog, hashrev(changelog, ri->chash))) < 0)
+		return nil;
+
+	done = 0;
+	line = 0;
+	memset(ht, 0, sizeof(ht));
+
+	p = buf;
+	e = buf + BUFSZ;
+	while((n = read(fd, p, e - p)) > 0){
+		p += n;
+		while((p > buf) && (e = memchr(buf, '\n', p - buf))){
+			*e++ = 0;
+
+			if(++line >= 4){
+				if(*buf == 0){
+					done = 1;
+					break;
+				}
+
+				he = malloc(sizeof(*he) + strlen(buf)+1);
+				hp = &ht[hashstr(strcpy(he->str, buf)) % nelem(ht)];
+				he->next = *hp;
+				*hp = he;
+			}
+
+			p -= e - buf;
+			if(p > buf)
+				memmove(buf, e, p - buf);
+		}
+		if(done)
+			break;
+		e = buf + BUFSZ;
+	}
+	close(fd);
+
+	t = loadtree(manifest, ri, ht, nelem(ht));
+
+	for(hp = ht; hp != &ht[nelem(ht)]; hp++){
+		while(he = *hp){
+			*hp = he->next;
+			free(he);
+		}
+	}
+
+	return t;
+}
+
+static void
+freenode(Revnode *nd)
+{
+	if(nd == nil)
+		return;
+	freenode(nd->down);
+	freenode(nd->next);
+	free(nd);
+}
+
+void
+closerevtree(Revtree *t)
+{
+	if(t == nil || decref(t))
+		return;
+	freenode(t->root);
+	free(t);
+}
--- /dev/null
+++ b/sys/src/cmd/hgfs/zip.c
@@ -1,0 +1,97 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "dat.h"
+#include "fns.h"
+
+#include <flate.h>
+
+struct zbuf {
+	int	fd;
+	int	len;
+
+	uchar	*b, *p, *e;
+};
+
+static int
+zwrite(void *a, void *p, int n)
+{
+	int *ofd = a;
+
+	if(write(*ofd, p, n) != n)
+		return -1;
+	return n;
+}
+
+static int
+zgetc(void *a)
+{
+	struct zbuf *z = a;
+	int n;
+
+	for(;;){
+		if(z->p < z->e)
+			return *z->p++;
+		if(z->len <= 0)
+			return -1;
+		if((n = BUFSZ) > z->len)
+			n = z->len;
+		if((n = read(z->fd, z->p = z->b, n)) <= 0)
+			return -1;
+		z->len -= n;
+		z->e = z->p + n;
+	}
+}
+
+int
+funzip(int ofd, int zfd, int len)
+{
+	uchar buf[BUFSZ];
+	struct zbuf z;
+	int wlen, n;
+	uchar *p;
+
+	if(len == 0)
+		return 0;
+
+	wlen = 0;
+	if((n = BUFSZ) > len)
+		n = len;
+	if((n = read(zfd, p = buf, n)) <= 0)
+		return -1;
+	len -= n;
+	switch(*p){
+	default:
+		return -1;
+	case 'u':
+		p++;
+		n--;
+		/* no break */
+	case '\0':
+		while((n > 0) && (write(ofd, p, n) == n)){
+			wlen += n;
+			if(len <= 0)
+				break;
+			if((n = BUFSZ) > len)
+				n = len;
+			if((n = read(zfd, p = buf, n)) <= 0)
+				break;
+			len -= n;
+		}
+		break;
+	case 'x':
+		if(n < 2)
+			return -1;
+		z.fd = zfd;
+		z.len = len;
+		z.b = buf;
+		z.p = p + 2;
+		z.e = p + n;
+		if((wlen = inflate(&ofd, zwrite, &z, zgetc)) < 0){
+			werrstr("%s", flateerr(wlen));
+			return -1;
+		}
+		break;
+	}
+	return wlen;
+}