shithub: vcardfs

ref: ff810eacaefe6dc6544b8f98862961eec8cb490b
dir: /vcardfs.c/

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

/* rough structure

/john-doe/
	export (export single contact as vcard file)
	/fn/
		/data (line value)
		/group (line group)
		/params/
			p1 (param value)
			...
	/tel/
		/data (line value)
		/group (line group)
		/params/
			...
/ctl
	- "write" (save file)
/import
	- write new vcf file to import
/export
	- read all vcards as vcf

*/

void
usage(void)
{
	fprint(2, "usage: %s [-s srv] [-m mtpt] [file]\n", argv0);
	exits("usage");
}

static void*
emalloc(long n)
{
	void *p;
	p = mallocz(n, 1);
	if (!p)
		sysfatal("emalloc: %r");
	return p;
}

static char*
estrdup(char *s)
{
	void *t;
	t = strdup(s);
	if (!t)
		sysfatal("estrdup: %r");
	return t;
}

enum {
	Qroot,
		Qctl,
		Qimport,
		Qgexport,
		Qcard,
			Qexport,
			Qline,
				Qdata,
				Qgroup,
				Qparams,
					Qparamdata,
};

static char *qnames[] = {
	[Qroot] "root",
		[Qctl] "ctl",
		[Qimport] "import",
		[Qgexport] "gexport",
		[Qcard] "card",
			[Qexport] "export",
			[Qline] "line",
				[Qdata] "data",
				[Qgroup] "group",
				[Qparams] "params",
					[Qparamdata] "paramdata",
};

typedef struct Vfile Vfile;
struct Vfile {
	int level;
	Vcard *card;
	Vline *line;
	Vparam *param;
	Vfile *cardfile;
	File *file;
	char *serialized;
};

static char*
vfqn(Vfile *f)
{
	if (f->level > Qparamdata || f->level < 0)
		return "<Q>";
	return qnames[f->level];
}

static char*
fqn(File *f)
{
	Vfile *vf;
	vf = f->aux;
	return vfqn(vf);
}

char *user = nil;
char *mtpt = "/mnt/vcard";
char *service = nil;

char *file = nil;
Vcard *cards = nil;

static char* getcardname(Vcard*);
static void initcardfiles(Vcard *chain);

static Vfile*
emkvfile(int level, Vcard *c, Vline *l, Vparam *p, Vfile *cfile)
{
	Vfile *f;
	f = mallocz(sizeof(Vfile), 1);
	if (!f)
		sysfatal("%r");
	f->level = level;
	f->card = c;
	f->line = l;
	f->param = p;
	f->cardfile = cfile;
	return f;
}

static void
readserialized(Req *r, Vfile *f)
{
	int n;
	char *s;
	Vcard *tmp;
	
	if (f->level == Qgexport) {
		if (f->cardfile->serialized)
			free(f->cardfile->serialized);
		f->cardfile->serialized = vcmserialize(cards);
		if (!f->cardfile->serialized) {
			responderror(r);
			return;
		}
		goto Readin;
	}
	
	if (f->cardfile->serialized)
		goto Readin;
	
	tmp = f->card->next;
	f->card->next = nil;
	f->cardfile->serialized = vcmserialize(f->card);
	f->card->next = tmp;
	
	if (!f->cardfile->serialized) {
		responderror(r);
		return;
	}

Readin:
	s = f->cardfile->serialized + r->ifcall.offset;
	n = strlen(s);
	n = n < r->ifcall.count ? n : r->ifcall.count;
	readbuf(r, s, n);
	respond(r, nil);
}

static void
fsread(Req *r)
{
	Vfile *f;
	f = r->fid->file->aux;
	switch (f->level) {
	case Qctl:
		respond(r, nil);
		return;
	case Qimport:
		respond(r, nil);
		return;
	case Qgexport:
		readserialized(r, f);
		return;
	case Qexport:
		readserialized(r, f);
		return;
	case Qdata:
		readstr(r, f->line->value);
		respond(r, nil);
		return;
	case Qgroup:
		if (!f->line->group) {
			respond(r, "file not found");
			return;
		}
		readstr(r, f->line->group);
		respond(r, nil);
		return;
	case Qparamdata:
		readstr(r, f->param->value);
		respond(r, nil);
		return;
	}
	respond(r, "not implemented");
}

static void
condrenameline(File *f)
{
	Vfile *vf;
	Vline *l;
	char *s;
	
	vf = f->aux;
	l = vf->line;
	
	if (!(l->group && *l->group)) {
		if (f->name)
			free(f->name);
		f->name = strdup(l->name);
		return;
	}
	s = smprint("%s.%s", l->group, l->name);
	if (f->name)
		free(f->name);
	f->name = s;
}

static void
condrenamecard(Vfile *f)
{
	char *s;
	
	if (strcmp(f->line->name, "n") != 0
	 && strcmp(f->line->name, "fn") != 0)
		return;
	
	if (!f->cardfile->file)
		return;
	
	s = getcardname(f->card);
	if (!s)
		return;
	
	if (f->cardfile->file->name)
		free(f->cardfile->file->name);
	f->cardfile->file->name = s;
}

static char*
getsanitized(char *s)
{
	return s;
}

static void
writedata(Req *r, Vfile *f)
{
	char *s;
	File *file;
	
	switch (f->level) {
	case Qparamdata:
		if (!f->param) goto Err;
		s = mallocz(r->ifcall.count + 1, 1);
		if (!s) {
			responderror(r);
			return;
		}
		memcpy(s, r->ifcall.data, r->ifcall.count);
		
		/* chop off last \n, if exists */
		if (s[r->ifcall.count - 1] == '\n')
			s[r->ifcall.count - 1] = 0;
		
		if (f->param->value)
			free(f->param->value);
		f->param->value = s;
		break;
	case Qdata:
		if (!f->line) goto Err;
		s = mallocz(r->ifcall.count + 1, 1);
		if (!s) {
			responderror(r);
			return;
		}
		memcpy(s, r->ifcall.data, r->ifcall.count);
		
		if (s[r->ifcall.count - 1] == '\n')
			s[r->ifcall.count - 1] = 0;
		
		if (f->line->value)
			free(f->line->value);
		f->line->value = getsanitized(s);
		condrenamecard(f);
		break;
	case Qgroup:
		if (!f->line) goto Err;
		s = mallocz(r->ifcall.count + 1, 1);
		if (!s) {
			responderror(r);
			return;
		}
		memcpy(s, r->ifcall.data, r->ifcall.count);
		
		if (s[r->ifcall.count - 1] == '\n')
			s[r->ifcall.count - 1] = 0;
		
		if (f->line->group)
			free(f->line->group);
		f->line->group = s;
		
		/* rename line file to match group */
		file = r->fid->file->parent;
		if (!file || !file->name)
			break;
		condrenameline(file);
		break;
	default:
		respond(r, "file not found");
		return;
	}
	
	if (f->cardfile->serialized)
		free(f->cardfile->serialized);
	f->cardfile->serialized = nil;
	respond(r, nil);
	return;
	
Err:
	respond(r, "error");
}

static void
readimportdata(Req *r, Vfile *f)
{
	char *s;
	long n, m;
	
	if (f->serialized) {
		m = strlen(f->serialized);
		if (r->ifcall.offset + r->ifcall.count + 1 > m)
			n = r->ifcall.offset + r->ifcall.count + 1;
		else
			n = m;
		s = mallocz(n, 1);
		if (!s)
			sysfatal("%r");
		strcpy(s, f->serialized);
		memcpy(s + r->ifcall.offset, r->ifcall.data, r->ifcall.count);
		free(f->serialized);
		f->serialized = s;
	} else {
		s = mallocz(r->ifcall.count + 1, 1);
		if (!s)
			sysfatal("%r");
		memcpy(s, r->ifcall.data, r->ifcall.count);
		f->serialized = s;
	}
	r->ofcall.count = r->ifcall.count;
	respond(r, nil);
}

static void
savedata(Req *r)
{
	char *s;
	int fd;
	
	fd = open(file, OTRUNC|OWRITE);
	if (fd < 0) {
		responderror(r);
		return;
	}
	
	s = vcmserialize(cards);
	if (!s) {
		responderror(r);
		return;
	}
	
	fprint(fd, "%s", s);
	free(s);
	respond(r, nil);
}

static void
addnew(Req *r, char *fn)
{
	Vcard *c, *cc;
	
	c = mallocz(sizeof(Vcard), 1);
	if (!c) {
		responderror(r);
		return;
	}
	c->content = mallocz(sizeof(Vline), 1);
	if (!c->content) {
		responderror(r);
		return;
	}
	c->content->name = estrdup("fn");
	c->content->value = estrdup(fn);
	
	initcardfiles(c);
	
	if (!cards) {
		cards = c;
		respond(r, nil);
		return;
	}
	
	for (cc = cards; cc->next; cc = cc->next)
		continue;
	
	cc->next = c;
	respond(r, nil);
}

static void
ctlcmd(Req *r)
{
	char *s, *t;
	
	s = mallocz(r->ifcall.count + 1, 1);
	if (!s)
		sysfatal("%r");
	memcpy(s, r->ifcall.data, r->ifcall.count);
	
	t = strchr(s, '\n');
	if (t)
		*t = 0;
	
	if (strcmp(s, "write") == 0) {
		savedata(r);
		goto Out;
	}
	
	if (strncmp(s, "new ", 4) == 0) {
		addnew(r, s+4);
		goto Out;
	}
	free(s);
	respond(r, "not implemented");
	return;
Out:
	free(s);
	r->ofcall.count = r->ofcall.count;
}

static void
fswrite(Req *r)
{
	Vfile *f;
	f = r->fid->file->aux;
	switch (f->level) {
	case Qctl:
		ctlcmd(r);
		return;
	case Qimport:
		readimportdata(r, f);
		return;
	case Qexport:
		respond(r, "not a function");
		return;
	case Qparamdata:
	case Qdata:
	case Qgroup:
		writedata(r, f);
		return;
	}
	respond(r, "not implemented");
}

static Vparam*
emkvparam(Vline *line, char *name)
{
	Vparam *vp, *p;
	vp = mallocz(sizeof(Vparam), 1);
	if (!vp)
		sysfatal("%r");
	vp->name = estrdup(name);
	vp->value = estrdup("");
	
	if (!line->params) {
		line->params = vp;
		return vp;
	}
	for (p = line->params; p->next; p = p->next)
		continue;
	p->next = vp;
	return vp;
}

static File*
addline(Req *r, Vfile *vf)
{
	File *dirf, *paramf, *dataf;
	Vline *l, *nl;
	
	if (!vf->card) {
		respond(r, "invalid card file");
		return nil;
	}
	
	dirf = createfile(r->fid->file, r->ifcall.name, user, DMDIR|0777, nil);
	if (!dirf) {
		responderror(r);
		return nil;
	}
	nl = mallocz(sizeof(Vline), 1);
	nl->name = estrdup(r->ifcall.name);
	nl->value = estrdup("");
	
	if (!vf->card->content) {
		vf->card->content = nl;
	} else {
		for (l = vf->card->content; l->next; l = l->next)
			continue;
		l->next = nl;
	}
	
	dirf->aux = emkvfile(Qline, vf->card, nl, nil, vf->cardfile);
	
	paramf = createfile(dirf, "params", user, DMDIR|0777, nil);
	if (!paramf) {
		responderror(r);
		return dirf;
	}
	paramf->aux = emkvfile(Qparams, vf->card, nl, nil, vf->cardfile);
	closefile(paramf);
	
	dataf = createfile(dirf, "data", user, 0666, nil);
	if (!dataf) {
		responderror(r);
		return dirf;
	}
	dataf->aux = emkvfile(Qdata, vf->card, nl, nil, vf->cardfile);
	closefile(dataf);
	closefile(dirf);
	respond(r, nil);
	return dirf;
}

static void
fscreate(Req *r)
{
	File *f, *nf;
	Vfile *vf;
	Vparam *vp;
	
	nf = nil;
	USED(nf);
	f = r->fid->file;
	vf = f->aux;
	if (r->ifcall.perm&DMDIR) {
		if (vf->level != Qcard)
			goto Nil;
		nf = addline(r, vf);
		goto Next;
	}
	switch (vf->level) {
	case Qline:
		if (strcmp(r->ifcall.name, "group") != 0)
			goto Nil;
		nf = createfile(f, r->ifcall.name, user, 0666, nil);
		if (!nf) {
			responderror(r);
			return;
		}
		nf->aux = emkvfile(Qgroup, vf->card, vf->line, nil, vf->cardfile);
		vf->line->group = estrdup("");
		break;
	case Qparams:
		nf = createfile(f, r->ifcall.name, user, 0666, nil);
		if (!nf) {
			responderror(r);
			return;
		}
		vp = emkvparam(vf->line, r->ifcall.name);
		nf->aux = emkvfile(Qparamdata, vf->card, vf->line, vp, vf->cardfile);
		break;
	default:
		goto Nil;
	}
	
Next:
	if (!nf) {
		/* assume already responded */
		return;
	}
	/* not sure if that's needed, but if I close the file, it seems
	to clean up properly */
	closefile(r->fid->file);
	r->fid->file = nf;
	r->ofcall.qid = nf->qid;
	
	if (vf->cardfile->serialized)
		free(vf->cardfile->serialized);
	vf->cardfile->serialized = nil;
	
	if (!r->responded)
		respond(r, nil);
	return;
	
Nil:
	respond(r, "create prohibited");
}

static void
fsdestroyfid(Fid *fid)
{
	File *f;
	Vfile *vf;
	Vcard *ncards, *nc;
	
	f = fid->file;
	if (!f)
		return;
	vf = f->aux;
	if (!vf)
		return;
	
	switch (vf->level) {
	case Qimport:
		break;
	default:
		return;
	}
	
	if (!vf->serialized)
		return;
	
	ncards = vcparse(vf->serialized);
	if (!ncards) {
		fprint(2, "error parsing import: %r");
		return;
	}
	free(vf->serialized);
	vf->serialized = nil;
	
	initcardfiles(ncards);
	
	if (!cards) {
		cards = ncards;
		return;
	}
	
	for (nc = cards; nc->next; nc = nc->next)
		continue;
	
	nc->next = ncards;
}

static void
rmclearlchildren(File *f, Vline *l)
{
	File *file, *subfile;
	Vparam *p;
	
	incref(f);
	file = walkfile(f, "data");
	if (file)
		removefile(file);
	incref(f);
	file = walkfile(f, "group");
	if (file)
		removefile(file);
	incref(f);
	file = walkfile(f, "params");
	if (file) {
		for (p = l->params; p; p = p->next) {
			incref(file);
			subfile = walkfile(file, p->name);
			if (subfile)
				removefile(subfile);
		}
		removefile(file);
	}
}

static void
rmclearcchildren(File *f, Vcard *c)
{
	File *file;
	Vline *l;
	
	for (l = c->content; l; l = l->next) {
		if (!l->name)
			continue;
		incref(f);
		file = walkfile(f, l->name);
		if (file) {
			rmclearlchildren(file, l);
			removefile(file);
		}
	}
	
	incref(f);
	file = walkfile(f, "export");
	if (file)
		removefile(file);
}

static void
rmcard(Req *r, File *f)
{
	Vfile *vf = f->aux;
	Vcard *c = vf->card;
	Vcard *cp;
	
	if (cards == c) {
		cards = c->next;
		goto Out;
	}
	for (cp = cards; cp; cp = cp->next)
		if (cp->next == c)
			break;
	
	if (!cp) {
		respond(r, "card not found!");
		return;
	}
	cp->next = c->next;

Out:
	c->next = nil;
	rmclearcchildren(f, c);
	vcfreecards(c);
	vf->card = nil;
	/* on success, f will be removed automatically */
	respond(r, nil);
}

static void
rmline(Req *r, File *f)
{
	Vfile *vf = f->aux;
	Vcard *c = vf->card;
	Vline *l = vf->line;
	Vline *pl;
	
	if (!l) {
		respond(r, nil);
		return;
	}
	
	if (c->content == l) {
		c->content = l->next;
		goto Out;
	}
	
	for (pl = c->content; pl; pl = pl->next)
		if (pl->next == l)
			break;
	
	if (!pl) {
		respond(r, "property not found");
		return;
	}
	pl->next = l->next;
Out:
	l->next = nil;
	rmclearlchildren(f, l);
	vcfreelines(l);
	vf->line = nil;
	/* on success, f will be removed automatically */
	respond(r, nil);
}

static void
rmparam(Req *r, Vfile *f)
{
	Vline *l = f->line;
	Vparam *p = f->param;
	Vparam *cp;
	
	if (l->params == p) {
		l->params = p->next;
		goto Out;
	}
	
	for (cp = l->params; cp; cp = cp->next)
		if (cp->next == p)
			break;
	
	if (!cp) {
		respond(r, "param not found");
		return;
	}
	cp->next = p->next;
Out:
	p->next = nil;
	vcfreeparams(p);
	f->param = nil;
	/* on success, f will be removed automatically */
	respond(r, nil);
}

static void
rmgroup(Req *r, File *f)
{
	Vfile *vf = f->aux;
	Vfile *cf = vf->cardfile;
	Vline *l = vf->line;
	
	if (cf->serialized)
		free(cf->serialized);
	cf->serialized = nil;
	
	if (l->group)
		free(l->group);
	l->group = nil;
	condrenameline(f->parent);
	vf->line = nil;
	respond(r, nil);
}

static void
fsremove(Req *r)
{
	Vfile *vf;
	
	vf = r->fid->file->aux;
	switch (vf->level) {
	case Qcard:
		rmcard(r, r->fid->file);
		return;
	case Qline:
		rmline(r, r->fid->file);
		return;
	case Qgroup:
		rmgroup(r, r->fid->file);
		return;
	case Qparamdata:
		rmparam(r, vf);
		return;
	default:
		respond(r, "remove prohibited");
		return;
	}
}

Srv fs = {
	.read = fsread,
	.write = fswrite,
	.create = fscreate,
	.remove = fsremove,
	.destroyfid = fsdestroyfid,
};

static char*
safename(char *n)
{
	char *s;
	
	s = estrdup(n);
	n = s;
	while (*n) {
		switch (*n) {
		case ' ':
			*n = '_';
			break;
		case '\t':
			*n = '_';
			break;
		}
		n++;
	}
	return s;
}

static char*
composename(char *oname)
{
	char *s, *t;
	char *args[5];
	
	/* see also: rfc 6350, section 6.2.2 */
	args[0] =      /* last name */
	args[1] =      /* first name */
	args[2] =      /* additional names */
	args[3] =      /* honorific prefixes */
	args[4] = nil; /* honorific suffixes */
	
	s = estrdup(oname);
	getfields(s, args, 5, 0, ";");
	t = smprint(
		"%s"        /* first name */
		"%s%s",     /* last name */
		args[1][0] ? args[1] : "",
		args[0][0] ? " " : "", args[0][0] ? args[0] : ""
		);
	free(s);
	return t;
}

static char*
getcardname(Vcard *c)
{
	Vline *l;
	Vline *fn = nil, *n = nil;
	char *s, *sn;
	
	for (l = c->content; l; l = l->next) {
		if (cistrcmp(l->name, "fn") == 0)
			fn = l;
		else if (cistrcmp(l->name, "n") == 0)
			n = l;
		if (fn && n)
			break;
	}
	
	if (fn)
		return safename(fn->value);
	if (n) {
		s = composename(n->value);
		sn = safename(s);
		free(s);
		return sn;
	}
	return nil;
}

static void
initcardfiles(Vcard *chain)
{
	File *fc, *fl, *fp, *f;
	Vcard *c;
	Vline *l;
	Vparam *p;
	Vfile *vf, *cf;
	char *s;
	
	for (c = chain; c; c = c->next) {
		s = getcardname(c);
		if (!s)
			continue;
		cf = emkvfile(Qcard, c, nil, nil, nil);
		fc = createfile(fs.tree->root, s, user, DMDIR|0777, cf);
		cf->cardfile = cf;
		cf->file = fc;
		free(s);
		if (!fc)
			sysfatal("%r");
		vf = emkvfile(Qexport, c, nil, nil, cf);
		f = createfile(fc, "export", user, 0444, vf);
		closefile(f);
		
		for (l = c->content; l; l = l->next) {
			vf = emkvfile(Qline, c, l, nil, cf);
			if (l->group && *l->group) {
				s = smprint("%s.%s", l->group, l->name);
			}
			fl = createfile(fc, (l->group && *l->group ? s : l->name), user, DMDIR|0777, vf);
			if (l->group) {
				free(s);
				s = nil;
			}
			vf = emkvfile(Qdata, c, l, nil, cf);
			f = createfile(fl, "data", user, 0666, vf);
			closefile(f);
			if (l->group) {
				vf = emkvfile(Qgroup, c, l, nil, cf);
				f = createfile(fl, "group", user, 0666, vf);
				closefile(f);
			}
			vf = emkvfile(Qparams, c, l, nil, cf);
			fp = createfile(fl, "params", user, DMDIR|0777, vf);
			
			for (p = l->params; p; p = p->next) {
				vf = emkvfile(Qparamdata, c, l, p, cf);
				f = createfile(fp, p->name, user, 0666, vf);
				closefile(f);
			}
			closefile(fp);
			closefile(fl);
		}
		closefile(fc);
	}
}

static void
fdestroy(File *f)
{
	Vfile *vf;
	
	vf = f->aux;
	
	/* File*s must be closefile'd properly after creation */
	/* walkfile needs an incref, because it closefile's the dir */
	//fprint(2, "destroy: %s\n", f->name);
	
	if (vf)
		free(vf);
}

void
main(int argc, char **argv)
{
	Vfile *f;
	
	ARGBEGIN{
	case 'D':
		chatty9p++;
		break;
	case 'm':
		mtpt = EARGF(usage());
		break;
	case 's':
		service = EARGF(usage());
		break;
	default:
		usage();
		break;
	}ARGEND;
	
	rfork(RFNOTEG);
	
	user = getuser();
	
	if (argc == 1)
		file = argv[0];
	else {
		file = smprint("/usr/%s/lib/vcarddb.vcf", user);
		if (!file)
			sysfatal("%r");
	}
	
	cards = vcparsefile(file);
	
	if (!cards)
		sysfatal("%r");
	
	fs.tree = alloctree("vcard", "vcard", DMDIR|0777, fdestroy);
	createfile(fs.tree->root, "ctl", user, 0222,
		emkvfile(Qctl, nil, nil, nil, nil));
	createfile(fs.tree->root, "import", user, 0222,
		emkvfile(Qimport, nil, nil, nil, nil));
	f = emkvfile(Qgexport, nil, nil, nil, nil);
	f->cardfile = f;
	closefile(createfile(fs.tree->root, "export", user, 0444, f));
	initcardfiles(cards);
	
	postmountsrv(&fs, service, mtpt, MREPL);
	exits(nil);
}