ref: f4a258fdbe0f2b82de6de38f491e31a4bb1a4c3f
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,
};
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
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);
dataf = createfile(dirf, "data", user, 0666, nil);
if (!dataf) {
responderror(r);
return dirf;
}
dataf->aux = emkvfile(Qdata, vf->card, nl, nil, vf->cardfile);
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;
}
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;
}
Srv fs = {
.read = fsread,
.write = fswrite,
.create = fscreate,
.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, *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);
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, 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;
createfile(fs.tree->root, "export", user, 0444, f);
initcardfiles(cards);
postmountsrv(&fs, service, mtpt, MREPL);
exits(nil);
}