shithub: unionfs

ref: ce768a70c40c96c6e6dbd1018306a122a4eeec43
dir: /unionfs.c/

View raw version
#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include "unionfs.h"

Srv thefs;
Branch *branch;
usize nbranch;
QLock mtptlock;
Mtpt *mtpt;

Mtpt*
mtptgrab(void)
{
	static int mtptnext = 0;
	Mtpt *m;

	qlock(&mtptlock);
	if(mtpt == nil){
		mtpt = emalloc(sizeof(Mtpt));
		mtpt->path = smprint("/mnt/mtpt%d", mtptnext++);
	}
	m = mtpt;
	mtpt = m->next;
	qunlock(&mtptlock);
	unmount(nil, m->path);
	return m;
}

void
mtptfree(Mtpt *m)
{
	qlock(&mtptlock);
	m->next = mtpt;
	mtpt = m;
	qunlock(&mtptlock);
}

FILE*
filenew(void)
{
	FILE *f;

	f = emalloc(sizeof(FILE));
	f->fd = -1;
	return f;
}

void
filefree(FILE *f)
{
	if(f == nil)
		return;
	if(f->name) free(f->name);
	if(f->uid) free(f->uid);
	if(f->gid) free(f->gid);
	if(f->muid) free(f->muid);
	if(f->path) free(f->path);
	if(f->realpath) free(f->realpath);
	if(f->fd != -1) close(f->fd);
	if(f->dirs) free(f->dirs);
	if(f->mtpt) mtptfree(f->mtpt);
	free(f);
}

void
dircopy(Dir *a, Dir *b)
{
	a->type = b->type;
	a->dev = b->dev;
	a->qid = b->qid;
	a->mode = b->mode;
	a->mtime = b->mtime;
	a->atime = b->atime;
	a->name = estrdup(b->name);
	a->uid = estrdup(b->uid);
	a->gid = estrdup(b->gid);
	a->muid = estrdup(b->muid);
}

void
fsattach(Req *r)
{
	FILE *f;
	char *user;
	
	f = filenew();
	f->name = estrdup("/");
	f->mode = 0777|DMDIR;
	user = getuser();
	f->uid = estrdup(user);
	f->gid = estrdup(user);
	f->muid = estrdup(user);
	f->mtime = f->atime = time(0);
	f->type = 0xFFFFFFFF;
	f->dev = 0xFFFFFFFFFFFFFFFF;
	f->qid = (Qid){0, 0, QTDIR};
	f->qid = qencode(f);
	f->path = estrdup(f->name);
	f->realpath = estrdup(f->name);
	
	r->fid->aux = f;
	r->fid->qid = f->qid;
	r->ofcall.qid = f->qid;
	respond(r, nil);
}

char*
mkpath(char *a0, ...)
{
	va_list args;
	int i;
	char *a;
	char *ap[] = {a0, "", ""};

	va_start(args, a0);
	for(i = 1; (a = va_arg(args, char*)) != nil && i < 3; i++)
		ap[i] = a;
	va_end(args);
	if((a = smprint("%s/%s/%s", ap[0], ap[1], ap[2])) == nil)
		sysfatal("smprint: %r");

	return cleanname(a);
}

char*
clone(Fid *fid, Fid *newfid, void*)
{
	FILE *f;
	FILE *parent = fid->aux;
	
	f = filenew();
	dircopy(f, parent);
	f->qid = parent->qid;
	f->path = estrdup(parent->path);
	f->realpath = estrdup(parent->realpath);
	newfid->aux = f;
	return nil;
}

char*
walkto(Fid *fid, char *name, void *aux)
{
	Req *r;
	Dir *d;
	FILE *f;
	char *path, *realpath;
	int i, *nwalk;
	
	r = aux;
	f = fid->aux;
	nwalk = r->aux;
	path = mkpath(f->path, name, nil);
	for(i = 0; i < nbranch; i++){
		realpath = mkpath(branch[i].root, path, nil);
		if((d = dirstat(realpath)) == nil)
			continue;
		if(*nwalk == r->ifcall.nwname){
			filefree(f);
			f = filenew();
			dircopy(f, d);
		}else{
			free(f->path);
			free(f->realpath);
		}
		f->qid = qencode(d);
		free(d);
		f->path = path;
		f->realpath = realpath;
		fid->aux = f;
		fid->qid = f->qid;
		*nwalk = *nwalk + 1;
		return nil;
	}
	return "not found";
}

void
fswalk(Req *r)
{
	int nwalk;

	nwalk = 1;
	r->aux = &nwalk;
	walkandclone(r, walkto, clone, r);
}

void
destroyfid(Fid *fid)
{
	if(fid->aux)
		filefree(fid->aux);
	fid->aux = nil;
}

void
fsopen(Req *r)
{
	Fcall *T, *R;
	FILE *f;
	usize i;
	char *path;
	Dir *d;
	
	T = &r->ifcall;
	R = &r->ofcall;
	f = r->fid->aux;

	srvrelease(&thefs);
	if(f->mode & DMDIR){
		f->mtpt = mtptgrab();
		for(i = 0; i < nbranch; i++){
			path = mkpath(branch[i].root, f->path, nil);
			if((d = dirstat(path)) != nil){
				if(d->mode & DMDIR)
				if(bind(path, f->mtpt->path, MAFTER) == -1)
					sysfatal("bind: %r");
				free(d);
			}
			free(path);
		}
		if((f->fd = open(f->mtpt->path, T->mode)) < 0){
			responderror(r);
			goto done;
		}
	}else{
		if((f->fd = open(f->realpath, T->mode)) < 0){
			responderror(r);
			goto done;
		}
	}
	R->iounit = iounit(f->fd);
	respond(r, nil);
done:
	srvacquire(&thefs);
}

void
fsremove(Req *r)
{
	FILE *f;
	
	f = r->fid->aux;
	srvrelease(&thefs);
	if(remove(f->path) < 0){
		responderror(r);
		goto done;
	}
	respond(r, nil);
done:
	srvacquire(&thefs);
}

int
dirgen(int i, Dir *d, void *aux)
{
	FILE *f = aux;
	
	if(i == 0){
		if(f->dirs){
			free(f->dirs);
			f->dirs = nil;
			f->ndirs = 0;
		}
		f->ndirs = dirreadall(f->fd, &f->dirs);
		if(f->ndirs == -1)
			sysfatal("dirreadall: %r");
	}
	if(f->ndirs == i)
		return -1;
	dircopy(d, &f->dirs[i]);
	d->qid = qencode(d);
	return 0;
}

void
fsread(Req *r)
{
	long n;
	Fcall *T, *R;
	FILE *f;
	
	T = &r->ifcall;
	R = &r->ofcall;
	f = r->fid->aux;

	srvrelease(&thefs);
	if(f->mode&DMDIR){
		dirread9p(r, dirgen, f);
	}else{
		if((n = pread(f->fd, R->data, T->count, T->offset)) < 0){
			responderror(r);
			goto done;
		}
		r->ofcall.count = n;
	}
	respond(r, nil);
done:
	srvacquire(&thefs);
}

void
fswrite(Req *r)
{
	Fcall *T, *R;
	FILE *f;
	
	T = &r->ifcall;
	R = &r->ofcall;
	f = r->fid->aux;
	
	srvrelease(&thefs);
	if((R->count = pwrite(f->fd, T->data, T->count, T->offset)) != T->count){
		responderror(r);
		goto done;
	}
	respond(r, nil);
done:
	srvacquire(&thefs);
}

int
mkdirp(char *path)
{
	int fd;
	char *p;
	Dir *d;
	
	assert(path != nil);
	if((d = dirstat(path)) != nil){
		free(d);
		return 1;
	}
	path = p = estrdup(path);
	for(; p != nil;){
		if(p[0] == '/')
			p++;
		if(p = strchr(p, '/'))
			*p = 0;
		if((d = dirstat(path)) == nil){
			if((fd = create(path, 0, 0777|DMDIR)) < 0){
				free(path);
				return -1;
			}
			close(fd);
		}
		free(d);
		if(p != nil)
			*p++ = '/';
	}
	free(path);
	return 1;
}

void
fscreate(Req *r)
{
	char *path, *realpath;
	usize i;
	Dir *d;
	Fcall *T, *R;
	FILE *parent, *f;
	int fd;
	
	T = &r->ifcall;
	R = &r->ofcall;
	parent = r->fid->aux;
	
	srvrelease(&thefs);
	for(i = 0; i < nbranch; i++)
		if(branch[i].create == 1)
			break;
	path = mkpath(branch[i].root, parent->path, nil);
	if(mkdirp(path) < 0){
		responderror(r);
		goto done;
	}
	realpath = mkpath(path, T->name, nil);
	free(path);
	if((fd = create(realpath, T->mode, T->perm)) < 0){
		free(realpath);
		responderror(r);
		goto done;
	}
	if((d = dirfstat(fd)) == nil){
		free(realpath);
		responderror(r);
		goto done;
	}
	f = filenew();
	dircopy(f, d);
	f->fd = fd;
	f->qid = qencode(d);
	f->path = mkpath(parent->path, T->name, nil);
	f->realpath = realpath;
	free(d);
	filefree(parent);
	
	r->fid->aux = f;
	R->qid = f->qid;
	respond(r, nil);
done:
	srvacquire(&thefs);
}


void
fsstat(Req *r)
{
	FILE *f = r->fid->aux;
	
	dircopy(&r->d, f);
	respond(r, nil);
}

void
fswstat(Req *r)
{
	FILE *f = r->fid->aux;
	
	srvrelease(&thefs);
	if(dirwstat(f->realpath, &r->d) < 0){
		responderror(r);
		goto done;
	}
	respond(r, nil);
done:
	srvacquire(&thefs);
}

char*
pivot(char *p)
{
	static n = 0;
	char *q;

	if((q = smprint("/mnt/union.%d.%d", getpid(), n++)) == nil)
		sysfatal("smprint: %r");
	if(bind(p, q, MREPL) < 0)
		sysfatal("bind: %r");
	return q;
}

void
main(int argc, char *argv[])
{
	int c, i, mflag, stdio;
	char *mountat, *srvname, *path, *p;
	Dir *d;
	Branch *b;

	c = 0;
	mflag = MREPL|MCREATE;
	mountat = nil;
	srvname = nil;
	stdio = 0;
	ARGBEGIN{
	case 'a':
		mflag |= MAFTER;
		break;
	case 'b':
		mflag |= MBEFORE;
		break;
	case 'c':
		c = 1;
		break;
	case 'C':
		mflag |= MCACHE;
		break;
	case 'D':
		chatty9p++;
		break;
	case 'm':
		mountat = EARGF(usage());
		break;
	case 's':
		srvname = EARGF(usage());
		break;
	case 'i':
		stdio = 1;
		break;
	default:
		usage();
	}ARGEND;
	if(argc < 1)
		usage();
	if((mountat || srvname) == 0)
		mountat = "/mnt/union";
	nbranch = argc;
	branch = b = emalloc(nbranch * sizeof(Branch));
	for(i = 0; i < argc; i++){
		if(strcmp(argv[i], "-c") == 0){
			nbranch--;
			c++;
			continue;
		}

		path = mkpath(argv[i], nil);
		if((d = dirstat(path)) == nil){
			fprint(2, "%s: %s does not exist, skipping\n", argv0, path);
			free(path);
			continue;
		}
		free(d);
		if(mountat && strcmp(path, mountat) == 0){
			p = pivot(path);
			free(path);
			path = p;
		}
		b->root = path;
		b->create = c == 1 ? c : 0;
		b++;
	}
	if(branch[0].root == nil)
		sysfatal("empty branch list");
	if(c == 0)
		branch[0].create = 1;
	
	thefs.attach = fsattach;
	thefs.walk = fswalk;
	thefs.open = fsopen;
	thefs.create = fscreate;
	thefs.remove = fsremove;
	thefs.read = fsread;
	thefs.write = fswrite;
	thefs.stat = fsstat;
	thefs.wstat = fswstat;
	thefs.destroyfid = destroyfid;
	if(stdio == 0){
		postmountsrv(&thefs, srvname, mountat, mflag);
		exits(nil);
	}
	thefs.infd = 0;
	thefs.outfd = 1;
	srv(&thefs);
	exits(nil);
}