shithub: vcardfs

ref: 614e174dca25e4fa70b5a79be5e08a3b3ba3474a
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,
};

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

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
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;
	
	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;
		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
fswrite(Req *r)
{
	Vfile *f;
	f = r->fid->file->aux;
	switch (f->level) {
	case Qctl:
		break;
	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 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)
		goto Nil;
	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;
	}
	
	if (nf) {
		r->fid->file = nf;
		r->ofcall.qid = nf->qid;
	}
	
	if (vf->cardfile->serialized)
		free(vf->cardfile->serialized);
	vf->cardfile->serialized = nil;
	
	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;
	
	fprint(2, "serialized:\n%p", ncards);
	
	initcardfiles(ncards);
	
	for (nc = cards; nc->next; nc = nc->next)
		continue;
	
	nc->next = ncards;
}

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

/* TODO: LOOKAT:
	/sys/src/cmd/webcookies.c:/createfile
	/sys/src/cmd/aux/gps/gpsfs.c:/createfile
*/
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*
getcardname(Vcard *c)
{
	Vline *l;
	Vline *fn = nil, *n = nil;
	
	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)
		return safename(n->value);
	return nil;
}

static void
initcardfiles(Vcard *chain)
{
	File *fc, *fl, *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->file = fc;
		free(s);
		if (!fc)
			sysfatal("%r");
		vf = emkvfile(Qexport, c, nil, nil, cf);
		createfile(fc, "export", user, 0444, vf);
		
		for (l = c->content; l; l = l->next) {
			vf = emkvfile(Qline, c, l, nil, cf);
			fl = createfile(fc, l->name, user, DMDIR|0777, vf);
			vf = emkvfile(Qdata, c, l, nil, cf);
			createfile(fl, "data", user, 0666, vf);
			if (l->group) {
				vf = emkvfile(Qgroup, c, l, nil, cf);
				createfile(fl, "group", user, 0666, vf);
			}
			vf = emkvfile(Qparams, c, l, nil, cf);
			f = createfile(fl, "params", user, DMDIR|0777, vf);
			
			for (p = l->params; p; p = p->next) {
				vf = emkvfile(Qparamdata, c, l, p, cf);
				createfile(f, p->name, user, 0666, 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("vcf", "vcf", DMDIR|0555, nil);
	createfile(fs.tree->root, "ctl", user, 0666,
		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;
	createfile(fs.tree->root, "export", user, 0444, f);
	initcardfiles(cards);
	
	postmountsrv(&fs, service, mtpt, MREPL);
	exits(nil);
}