shithub: unionfs

Download patch

ref: 62b4c820ffb7561ca397d46a2b895d8c98726ec7
author: kvik <kvik@a-b.xyz>
date: Mon Apr 22 17:12:51 EDT 2019

let there be unions

--- /dev/null
+++ b/mkfile
@@ -1,0 +1,7 @@
+</$objtype/mkfile
+
+TARG=unionfs
+OFILES=$TARG.$O
+BIN=$home/bin/$objtype
+
+</sys/src/cmd/mkone
--- /dev/null
+++ b/unionfs.c
@@ -1,0 +1,716 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+typedef struct Union Union;
+typedef struct Fil Fil;
+typedef struct Flist Flist;
+typedef struct Fstate Fstate;
+typedef struct Qidmap Qidmap;
+
+struct Union {
+	char *root;
+	int create;
+	Union *prev, *next;
+};
+
+enum {
+	Nqidbits = 5,
+	Nqidmap = 1 << Nqidbits,
+};
+
+struct Qidmap {
+	Ref;
+	ushort type;
+	uint dev;
+	uvlong path;
+	uvlong qpath;
+	Qidmap *next;
+};
+
+struct Fil {
+	Ref;
+	Dir;
+	Qidmap *qmap;
+	char *path;	/* real path */
+	char *fspath;	/* internal path */
+};
+
+struct Flist {
+	Fil *file;
+	Flist *next;
+};
+
+struct Fstate {
+	int fd;
+	Fil *file;
+	Flist *dir, *idx;
+};
+
+Union u0 = {.next = &u0, .prev = &u0};
+Union *unionlist = &u0;
+uvlong qidnext;
+Qidmap *qidmap[Nqidmap];
+
+void
+LOG(char *fmt, ...)
+{
+	static int fd = -1;
+	va_list args;
+
+	if(fd < 0 && (fd = open("/mnt/log/log", OWRITE)) < 0){
+		fprint(2, "failed to open log pipe\n");
+		return;
+	}
+	va_start(args, fmt);
+	vfprint(fd, fmt, args);
+	va_end(args);
+}
+
+void*
+emalloc(ulong sz)
+{
+	void *v;
+
+	if((v = malloc(sz)) == nil)
+		sysfatal("emalloc: %r");
+	memset(v, 0, sz);
+
+	setmalloctag(v, getcallerpc(&sz));
+	return v;
+}
+
+char*
+estrdup(char *s)
+{
+	char *p;
+
+	if((p = strdup(s)) == nil)
+		sysfatal("estrdup: %r");
+	setmalloctag(p, getcallerpc(&s));
+	return p;
+}
+
+char*
+esmprint(char *fmt, ...)
+{
+	char *s;
+	va_list arg;
+	
+	va_start(arg, fmt);
+	s = vsmprint(fmt, arg);
+	va_end(arg);
+	if(s == nil)
+		sysfatal("esmprint: %r");
+	return s;
+}
+
+Ref*
+copyref(Ref *r)
+{
+	incref(r);
+	return r;
+}
+
+int
+qidhash(uvlong path)
+{
+	int h, n;
+	
+	h = 0;
+	for(n = 0; n < 64; n += Nqidbits){
+		h ^= path;
+		path >>= Nqidbits;
+	}
+	return h & (Nqidmap-1);
+}
+
+Qidmap*
+qidlookup(Dir *d)
+{
+	int h;
+	Qidmap *q;
+	
+	h = qidhash(d->qid.path);
+	for(q = qidmap[h]; q != nil; q = q->next)
+		if(q->type == d->type && q->dev == d->dev && q->path == d->qid.path)
+			return q;
+	return nil;
+}
+
+int
+qidexists(uvlong path)
+{
+	int h;
+	Qidmap *q;
+	
+	for(h = 0; h < Nqidmap; h++)
+		for(q = qidmap[h]; q != nil; q = q->next)
+			if(q->qpath == path)
+				return 1;
+	return 0;
+}
+
+Qidmap*
+qidnew(Dir *d)
+{
+	int h;
+	uvlong path;
+	Qidmap *q;
+	
+	q = qidlookup(d);
+	if(q != nil)
+		return (Qidmap*)copyref(q);
+	path = d->qid.path;
+	while(qidexists(path)){
+		path &= (1LL<<48)-1;
+		if(++qidnext >= 1<<16)
+			qidnext = 1;
+		path |= qidnext<<48;
+	}
+	q = emalloc(sizeof(*q));
+	q->type = d->type;
+	q->dev = d->dev;
+	q->path = d->qid.path;
+	q->qpath = path;
+	h = qidhash(q->path);
+	q->next = qidmap[h];
+	qidmap[h] = q;
+	return (Qidmap*)copyref(q);
+}
+
+void
+qidfree(Qidmap *q)
+{
+	int h;
+	Qidmap *l;
+	
+	if(q == nil)
+		return;
+	if(decref(q))
+		return;
+	h = qidhash(q->path);
+	if(qidmap[h] == q)
+		qidmap[h] = q->next;
+	else{
+		for(l = qidmap[h]; l->next != q; l = l->next)
+			;
+		l->next = q->next;
+	}
+	free(q);
+}
+
+void
+unionlink(Union *p, Union *n)
+{
+	p = p->prev;
+	n->next = p->next;
+	n->prev = p;
+	p->next->prev = n;
+	p->next = n;
+}
+
+Fil*
+filenew(Dir *d)
+{
+	Fil *f;
+	
+	f = emalloc(sizeof(*f));
+	f->ref = 1;
+	f->qmap = qidnew(d);
+	f->Dir = *d;
+	f->qid.path = f->qmap->qpath;
+	f->name = estrdup(d->name);
+	f->uid = estrdup(d->uid);
+	f->gid = estrdup(d->gid);
+	f->muid = estrdup(d->muid);
+	return f;
+}
+
+void
+filefree(Fil *f)
+{
+	if(f == nil)
+		return;
+	if(decref(f))
+		return;
+//	qidfree(f->qmap);
+	free(f->name);
+	free(f->uid);
+	free(f->gid);
+	free(f->muid);
+	free(f->path);
+	free(f->fspath);
+	free(f);
+}
+
+void
+flistadd(Flist **list, Fil *f)
+{
+	Flist *p;
+	
+	p = emalloc(sizeof(*p));
+	p->file = f;
+	p->next = *list;
+	*list = p;
+}
+
+void
+flistfree(Flist *l)
+{
+	Flist *lp;
+	
+	for(lp = l; lp != nil; l = lp){
+		lp = lp->next;
+		filefree(l->file);
+		free(l);
+	}
+}
+
+int
+flisthas(Flist *list, char *name)
+{
+	for(; list != nil; list = list->next)
+		if(strcmp(list->file->name, name) == 0)
+			return 1;
+	return 0;
+}
+
+Fstate*
+fstatenew(Fil *f)
+{
+	Fstate *st;
+	
+	st = emalloc(sizeof(*st));
+	st->fd = -1;
+	st->file = (Fil*)copyref(f);
+	return st;
+}
+
+void
+fstatefree(Fstate *st)
+{
+	if(st == nil)
+		return;
+	filefree(st->file);
+	flistfree(st->dir);
+	close(st->fd);
+	free(st);
+}
+
+void
+fsattach(Req *r)
+{
+	Dir d;
+	Fil *root;
+	Fstate *st;
+	
+	nulldir(&d);
+	d.qid = (Qid){0, 0, QTDIR};
+	d.name = ".";
+	d.mode = 0777|DMDIR;
+	d.uid = r->fid->uid;
+	d.gid = r->fid->uid;
+	d.muid = r->fid->uid;
+	d.mtime = time(0);
+	d.atime = time(0);
+	d.length = 0;
+	
+	root = filenew(&d);
+	root->fspath = estrdup(d.name);
+	root->path = estrdup(d.name);
+	
+	st = fstatenew(root);
+	decref(root);
+
+	r->fid->aux = st;
+	r->fid->qid = root->qid;
+	r->ofcall.qid = root->qid;
+	respond(r, nil);
+}
+
+Fil*
+filewalk(Fil *p, char *name)
+{
+	char *path;
+	char *s;
+	Dir *d;
+	Fil *f;
+	Union *u;
+	
+	if(strcmp(name, "..") == 0){
+		s = strrchr(p->fspath, '/');
+		if(s == nil)
+			return p;
+		*s = 0;
+		return filewalk(p, "");
+	}
+	for(u = unionlist->next; u != unionlist; u = u->next){
+		path = esmprint("%s/%s/%s", u->root, p->fspath, name);
+		d = dirstat(path);
+		if(d != nil){
+			f = filenew(d);
+			free(d);
+			f->fspath = cleanname(esmprint("%s/%s", p->fspath, name));
+			f->path = cleanname(path);
+			return f;
+		}
+		free(path);
+	}
+	return nil;
+}
+
+char*
+walk1(Fid *fid, char *name, void *)
+{
+	Fil *p, *f;
+	Fstate *st;
+	
+	st = fid->aux;
+	p = st->file;
+	f = filewalk(p, name);
+	if(f == nil)
+		return "no file";
+	st->file = f;
+	filefree(p);
+
+	fid->qid = f->qid;
+	return nil;
+}
+
+char*
+clone(Fid *old, Fid *new, void*)
+{
+	Fstate *fs;
+	
+	fs = old->aux;
+	new->aux = fstatenew(fs->file);
+	return nil;
+}
+
+void
+destroyfid(Fid *fid)
+{
+	fstatefree((Fstate*)fid->aux);
+}
+
+void
+fswalk(Req *r)
+{
+	walkandclone(r, walk1, clone, nil);
+}
+
+Flist*
+filereaddir(Fil *p)
+{
+	int fd;
+	long i, n;
+	Dir *dir, *d;
+	char *path;
+	Union *u;
+	Fil *f;
+	Flist *list;
+
+	list = nil;
+	for(u = unionlist->next; u != unionlist; u = u->next){
+		path = esmprint("%s/%s", u->root, p->fspath);
+		d = dirstat(path);
+		if(d == nil){
+			free(path);
+			continue;
+		}
+		free(d);
+		fd = open(path, OREAD);
+		if(fd < 0){
+			free(path);
+			continue;
+		}
+		free(path);
+		n = dirreadall(fd, &dir);
+		close(fd);
+		if(n < 0)
+			continue;
+		for(i = 0; i < n; i++){
+			if(flisthas(list, dir[i].name))
+				continue;
+			f = filenew(&dir[i]);
+			flistadd(&list, f);
+		}
+		free(dir);
+	}
+	return list;
+}
+
+void
+fsopen(Req *r)
+{
+	Fcall *i;
+	Fstate *st;
+	Fil *f;
+	
+	i = &r->ifcall;
+	st = r->fid->aux;
+	f = st->file;
+	if(f->mode&DMDIR){
+		st->dir = filereaddir(f);
+		st->idx = st->dir;
+	}else{
+		st->fd = open(f->path, i->mode);
+		if(st->fd < 0){
+			responderror(r);
+			return;
+		}
+	}
+	respond(r, nil);
+}
+
+void
+fscreate(Req *r)
+{
+	char *path, *npath;
+	Dir *d;
+	Fcall *i, *o;
+	Union *u;
+	Fstate *st;
+	Fil *f, *nf;
+	
+	i = &r->ifcall;
+	o = &r->ofcall;
+	st = r->fid->aux;
+	f = st->file;
+	
+	for(u = unionlist->next; u != unionlist; u = u->next)
+		if(u->create == 1)
+			break;
+	assert(u != unionlist);
+	path = cleanname(esmprint("%s/%s", u->root, f->fspath));
+	d = dirstat(path);
+	if(d != nil)
+		goto work;
+	for(u = unionlist->next; u != unionlist; u = u->next){
+		if(u->create == 1)
+			continue;
+		free(path);
+		path = cleanname(esmprint("%s/%s", u->root, f->fspath));
+		d = dirstat(path);
+		if(d != nil){
+			free(d);
+			goto work;
+		}
+	}
+	sysfatal("something's fucked");
+	
+work:
+	npath = cleanname(esmprint("%s/%s", path, i->name));
+	free(path);
+	st = emalloc(sizeof(*st));
+	st->fd = create(npath, i->mode, i->perm);
+	if(st->fd < 0){
+		responderror(r);
+		return;
+	}
+	d = dirfstat(st->fd);
+	if(d == nil){
+		fstatefree(st);
+		responderror(r);
+		return;
+	}
+	nf = filenew(d);
+	free(d);
+	nf->path = npath;
+	nf->fspath = estrdup(f->fspath);
+	st->file = nf;
+	
+	r->fid->aux = st;
+	r->fid->qid = f->qid;
+	o->qid = f->qid;
+	respond(r, nil);
+}
+
+void
+fsremove(Req *r)
+{
+	Fstate *st;
+	Fil *f;
+	
+	st = r->fid->aux;
+	f = st->file;
+	if(remove(f->path) < 0){
+		responderror(r);
+		return;
+	}
+	respond(r, nil);
+}
+
+void
+dirfill(Dir *dir, Fil *f)
+{
+	*dir = f->Dir;
+	dir->qid = f->qid;
+	dir->name = estrdup(f->name);
+	dir->uid = estrdup(f->uid);
+	dir->gid = estrdup(f->gid);
+	dir->muid = estrdup(f->muid);
+}
+
+int
+dirgen(int, Dir *dir, void *aux)
+{
+	Fstate *fs;
+	Flist *l;
+	
+	fs = aux;
+	l = fs->idx;
+	if(l == nil)
+		return -1;
+	dirfill(dir, l->file);
+	fs->idx = l->next;
+	return 0;
+}
+
+void
+fsread(Req *r)
+{
+	long n;
+	Fcall *i, *o;
+	Fil *f;
+	Fstate *st;
+	
+	i = &r->ifcall;
+	o = &r->ofcall;
+	st = r->fid->aux;
+	f = st->file;
+
+	if(f->mode&DMDIR){
+		dirread9p(r, dirgen, st);
+		respond(r, nil);
+		return;
+	}
+	if((n = pread(st->fd, o->data, i->count, i->offset)) < 0){
+		responderror(r);
+		return;
+	}
+	r->ofcall.count = n;
+	respond(r, nil);
+}
+
+void
+fswrite(Req *r)
+{
+	Fcall *i, *o;
+	Fstate *fs;
+	
+	i = &r->ifcall;
+	o = &r->ofcall;
+	fs = r->fid->aux;
+
+	if((o->count = pwrite(fs->fd, i->data, i->count, i->offset)) != i->count){
+		responderror(r);
+		return;
+	}
+	respond(r, nil);
+}
+
+void
+fsstat(Req *r)
+{
+	Fstate *st;
+	
+	st = r->fid->aux;
+	dirfill(&r->d, st->file);
+	respond(r, nil);
+}
+
+void
+fswstat(Req *r)
+{
+	Fstate *st;
+	Fil *f;
+	
+	st = r->fid->aux;
+	f = st->file;
+	if(dirwstat(f->path, &r->d) < 0){
+		responderror(r);
+		return;
+	}
+	respond(r, nil);
+}
+
+Srv fs = {
+	.attach = fsattach,
+	.walk = fswalk,
+	.open = fsopen,
+	.create = fscreate,
+	.remove = fsremove,
+	.read = fsread,
+	.write = fswrite,
+	.stat = fsstat,
+	.wstat = fswstat,
+	.destroyfid = destroyfid,
+};
+
+void
+usage(void)
+{
+	fprint(2, "%s [-D] [-m mtpt] [-s srv] [-c] path ...\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	int c, i;
+	int mflag;
+	char *mtpt, *srv;
+	Union *u;
+
+	c = 0;
+	mflag = MREPL|MCREATE;
+	mtpt = "/n/un";
+	srv = nil;
+	ARGBEGIN{
+	case 'a':
+		mflag = MAFTER;
+		break;
+	case 'b':
+		mflag = MBEFORE;
+		break;
+	case 'm':
+		mtpt = EARGF(usage());
+		break;
+	case 's':
+		srv = EARGF(usage());
+		break;
+	case 'D':
+		chatty9p++;
+		break;
+	case 'c':
+		c++;
+		break;
+	case 'C':
+		mflag |= MCACHE;
+		break;
+	default:
+		usage();
+	}ARGEND;
+	if(argc < 1)
+		usage();
+	for(i = 0; i < argc; i++){
+		if(strncmp(argv[i], "-c", 2) == 0){
+			c++;
+			continue;
+		}
+		u = emalloc(sizeof(*u));
+		u->create = c == 1 ? c : 0;
+		u->root = estrdup(cleanname(argv[i]));
+		unionlink(unionlist, u);
+	}
+	if(c == 0)
+		unionlist->next->create = 1;
+	
+	postmountsrv(&fs, srv, mtpt, mflag);
+	
+	exits(nil);
+}