ref: e0cac8cff0fad58496578de0bf11f111f765d3a8
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);
}