ref: 5e39bdc9f4f8117890494e8a8ce632e77c6aaf46
dir: /vcardfs.c/
#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 Vlinenumber Vlinenumber; struct Vlinenumber { char *name; int value; Vlinenumber *next; }; typedef struct Vfile Vfile; struct Vfile { int level; Vcard *card; Vline *line; Vparam *param; Vfile *cardfile; File *file; char *serialized; Vlinenumber *linenumbers; }; static Vlinenumber* findlinenumber(Vfile *f, char *line) { Vlinenumber *l, *last; if (!f->linenumbers) { f->linenumbers = emalloc(sizeof(Vlinenumber)); last = f->linenumbers; goto Out; } last = nil; for (l = f->linenumbers; l; l = l->next) { if (cistrcmp(l->name, line) == 0) return l; last = l; } if (!last) return nil; last->next = emalloc(sizeof(Vlinenumber)); last = last->next; Out: last->name = estrdup(line); last->value = 0; return last; } static int getcurrentlinenumber(Vfile *f, char *line) { Vlinenumber *l; l = findlinenumber(f, line); return l ? l->value : -1; } static void inclinenumber(Vfile *f, char *line) { Vlinenumber *l; l = findlinenumber(f, line); if (!l) return; l->value++; } 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 char* ecreatelinename(Vfile *cf, Vline *l) { char *s; s = smprint("%d:%s%s%s", getcurrentlinenumber(cf, l->name), l->group ? l->group : "", l->group ? "." : "", l->name); if (!s) sysfatal("createlinename: %r"); return s; } 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; char *s; if (!vf->card) { respond(r, "invalid card file"); return nil; } nl = mallocz(sizeof(Vline), 1); nl->name = estrdup(r->ifcall.name); nl->value = estrdup(""); s = ecreatelinename(vf, nl); dirf = createfile(r->fid->file, s, user, DMDIR|0777, nil); free(s); if (!dirf) { responderror(r); free(nl->name); free(nl->value); free(nl); return nil; } 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); s = ecreatelinename(cf, l); inclinenumber(cf, l->name); fl = createfile(fc, s, user, DMDIR|0777, vf); 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); }