shithub: unionfs

ref: c1eed337afadb15156a5364767c3e3755586e537
dir: /unionfs.c/

View raw version
#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/exportfs/%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) s_free(f->path);
	if(f->realpath) s_free(f->realpath);
	if(f->fd != -1) close(f->fd);
	if(f->dl) dirlistfree(f->dl);
	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->length = b->length;
	if(a->name)
		free(a->name);
	a->name = estrdup(b->name);
	if(a->uid)
		free(a->uid);
	a->uid = estrdup(b->uid);
	if(a->gid)
		free(a->gid);
	a->gid = estrdup(b->gid);
	if(a->muid)
		free(a->muid);
	a->muid = estrdup(b->muid);
}

int
catchflush(void*, char *note)
{
	if(strcmp(note, "flush") == 0){
		atnotify(catchflush, 0);
		return 1;
	}
	return 0;
}

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 = s_copy(f->name);
	f->realpath = s_new();
	
	r->fid->aux = f;
	r->fid->qid = f->qid;
	r->ofcall.qid = f->qid;
	respond(r, nil);
}

String*
walk(String *s, char *n0, char *n1)
{
	s_append(s, "/");
	s_append(s, n0);
	s_append(s, "/");
	s_append(s, n1);
	cleanname(s->base);
	s->ptr = s->base + strlen(s->base);
	return s;
}

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

char*
walkto(Fid *fid, char *name, void *)
{
	Dir *d;
	FILE *f;
	int i;
	
	f = fid->aux;
	walk(f->path, name, nil);
	for(i = 0; i < nbranch; i++){
		s_reset(f->realpath);
		walk(f->realpath, branch[i].root, s_to_c(f->path));
		if((d = dirstat(s_to_c(f->realpath))) == nil)
			continue;
		dircopy(f, d);
		f->qid = qencode(d);
		free(d);
		fid->qid = f->qid;
		return nil;
	}
	return "not found";
}

void
fswalk(Req *r)
{
	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;
	String *path;
	Dir *d;
	
	T = &r->ifcall;
	R = &r->ofcall;
	f = r->fid->aux;

	f->pid = getpid();
	atnotify(catchflush, 1);
	srvrelease(&thefs);
	if(f->mode & DMDIR){
		f->mtpt = mtptgrab();
		path = s_new();
		for(i = 0; i < nbranch; i++){
			s_reset(path);
			walk(path, branch[i].root, s_to_c(f->path));
			if((d = dirstat(s_to_c(path))) != nil){
				if(d->mode & DMDIR)
					bind(s_to_c(path), f->mtpt->path, MAFTER);
				free(d);
			}
		}
		s_free(path);
		f->fd = open(f->mtpt->path, T->mode);
		mtptfree(f->mtpt);
		if(f->fd == -1)
			goto error;
	}else
		if((f->fd = open(s_to_c(f->realpath), T->mode)) == -1)
			goto error;
	R->iounit = iounit(f->fd);
	if(f->flushed == 0)
		respond(r, nil);
	srvacquire(&thefs);
	atnotify(catchflush, 0);
	return;
error:
	if(f->flushed == 0)
		responderror(r);
	srvacquire(&thefs);
	atnotify(catchflush, 0);
}

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

int
dirgen(int i, Dir *d, void *aux)
{
	Dirlist *dl = aux;
	Dir *dd;
	
	if(dl->ndirs == i)
		return -1;
	dd = dl->dirs[i];
	dircopy(d, dd);
	d->qid = qencode(dd);
	return 0;
}

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

	f->pid = getpid();
	atnotify(catchflush, 1);
	srvrelease(&thefs);
	if(f->mode&DMDIR){
		if(T->offset == 0){
			if(seek(f->fd, 0, 0) == -1)
				goto error;
			if(f->dl != nil)
				dirlistfree(f->dl);
			if((f->dl = dirlist(f->fd)) == nil)
				goto error;
		}
		dirread9p(r, dirgen, f->dl);
	}else{
		if((n = pread(f->fd, R->data, T->count, T->offset)) == -1)
			goto error;
		r->ofcall.count = n;
	}
	if(f->flushed == 0)
		respond(r, nil);
	srvacquire(&thefs);
	atnotify(catchflush, 0);
	return;
error:
	if(f->flushed == 0)
		responderror(r);
	srvacquire(&thefs);
	atnotify(catchflush, 0);
}

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

void
fsflush(Req *r)
{
	FILE *f = r->oldreq->fid->aux;
	
	if(f->pid == 0){
		respond(r, nil);
		return;
	}
	switch(r->oldreq->type){
	case Topen:
	case Tread:
	case Twrite:
		f->flushed = 1;
		while(postnote(PNPROC, f->pid, "flush") != 0)
			sleep(100);
		respond(r->oldreq, "interrupted");
	}
	respond(r, nil);
}

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)) == -1){
				free(path);
				return -1;
			}
			close(fd);
		}
		free(d);
		if(p != nil)
			*p++ = '/';
	}
	free(path);
	return 1;
}

void
fscreate(Req *r)
{
	String *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;
	fd = -1;
	realpath = s_new();
	walk(realpath, branch[i].root, s_to_c(parent->path));
	if(mkdirp(s_to_c(realpath)) == -1){
error:
		s_free(realpath);
		if(fd != -1)
			close(fd);
		responderror(r);
		srvacquire(&thefs);
		return;
	}
	walk(realpath, T->name, nil);
	if((fd = create(s_to_c(realpath), T->mode, T->perm)) == -1)
		goto error;
	if((d = dirfstat(fd)) == nil)
		goto error;
	f = filenew();
	dircopy(f, d);
	f->fd = fd;
	f->qid = qencode(d);
	free(d);
	f->path = walk(s_clone(parent->path), T->name, nil);
	f->realpath = realpath;
	filefree(parent);
	
	r->fid->aux = f;
	R->qid = f->qid;
	respond(r, nil);
	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(s_to_c(f->realpath), &r->d) == -1){
		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) == -1)
		sysfatal("bind: %r");
	return q;
}

void
main(int argc, char *argv[])
{
	int c, i, mflag, stdio;
	char pwd[512];
	char *mountat, *srvname, *path;
	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";
	if(getwd(pwd, sizeof pwd) == nil)
		sysfatal("getwd: %r");
	nbranch = argc;
	branch = b = emalloc(nbranch * sizeof(Branch));
	for(i = 0; i < argc; i++){
		if(strcmp(argv[i], "-c") == 0){
			nbranch--;
			c++;
			continue;
		}
		path = cleanname(argv[i]);
		if(!(path[0] == '/' || path[0] == '#'))
			path = cleanname(smprint("%s/%s", pwd, path));
		if((d = dirstat(path)) == nil){
			fprint(2, "%s: %s does not exist, skipping\n", argv0, path);
			continue;
		}
		free(d);
		if(mountat && strcmp(path, mountat) == 0)
			path = pivot(path);
		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.flush = fsflush;
	thefs.destroyfid = destroyfid;
	if(stdio == 0){
		postmountsrv(&thefs, srvname, mountat, mflag);
		exits(nil);
	}
	thefs.infd = 0;
	thefs.outfd = 1;
	srv(&thefs);
	exits(nil);
}