ref: 423f4db8e2a87e2319834ea9896a3d568cb64334
dir: /todofs.c/
#include <u.h> #include <libc.h> #include <fcall.h> #include <thread.h> #include <9p.h> #include <bio.h> #include <ndb.h> /* owner = assignee */ /* group = optional group */ /* filename = UID_short_description_based_on_title */ /* file contents = plain read from source file UID */ /* source directory contents: index - index file, ndb format UID1 UID2 UID3 */ void usage(void) { fprint(2, "usage: %s [-s srvname] [-m mountpoint] directory\n", argv0); exits("usage"); } #define HASHLEN 10 enum { Qdir, Qctl, Qstatus, Qtask, }; char *qnames[] = { [Qdir] "/", [Qctl] "ctl", [Qstatus] nil, [Qtask] nil, }; int qidtype(ulong path) { // -----00 return path & 3; } ulong qidnum(ulong path) { // 000000-- return path >> 2; } ulong mkqid(int type, int num) { return (num << 2) | type & 3; } char *srcdir; char *idxfile; Ndb *index = nil; typedef struct Task Task; struct Task { Task* next; char hash[HASHLEN]; char *title; ulong id; Dir *dir; int cassignee; int cgroup; int cfname; }; typedef struct Status Status; struct Status { Status *next; char *name; Task *tasks; }; Status *statuses = nil; ulong maxtask = 0; Task* getgtask(ulong id, Status **status); int savedata(void) { Biobuf *bout; Status *s; ulong tid; Task *t; ndbclose(index); index = nil; bout = Bopen(idxfile, OWRITE); if (!bout) goto Errout; for (s = statuses; s; s = s->next) { Bprint(bout, "key=status name=\"%s\"\n", s->name); } Bprint(bout, "\n"); tid = 0; while (tid <= maxtask) { t = getgtask(tid, &s); if (t) { Bprint(bout, "key=task id=\"%s\" status=\"%s\"", t->hash, s->name); if (t->cassignee) { Bprint(bout, " assignee=\"%s\"", t->dir->uid); } if (t->cgroup) { Bprint(bout, " group=\"%s\"", t->dir->gid); } if (t->title) { Bprint(bout, " title=\"%s\"", t->title); } Bprint(bout, "\n"); } tid++; } Bterm(bout); index = ndbopen(idxfile); return 1; Errout: index = ndbopen(idxfile); return 0; } Status* getstatus(char *name, int *id) { Status *s; int i; if (!statuses) return nil; i = 0; s = statuses; while (s && (strcmp(s->name, name) != 0)) { i++; s = s->next; } if (id) *id = i; return s; /* found or nil */ } Status* getnstatus(int id) { Status *s; int i; i = 0; s = statuses; while (i != id && s->next) { i++; s = s->next; } return i == id ? s : nil; } int addstatus(char *name) { Status *s; if (!statuses) { statuses = mallocz(sizeof(Status), 1); statuses->name = strdup(name); return 1; } if (getstatus(name, nil)) { werrstr("status '%s' already exists", name); return 0; } for (s = statuses; s->next; s = s->next) ; s->next = mallocz(sizeof(Status), 1); s = s->next; s->name = strdup(name); return 1; } Task* gettask(char *status, char *name, int *task) { Status *s; Task *t; int i; s = getstatus(status, nil); if (!s) return nil; t = s->tasks; i = 0; while (t && (strcmp(t->hash, name) != 0)) { t = t->next; i++; } if (task) *task = i; return t; /* found or nil */ } Task* getstask(char *name, int *status, int *task) { Status *s; Task *t; int i, j; i = 0; for (s = statuses; s; s = s->next, i++) { t = gettask(s->name, name, &j); if (t) { if (status) *status = i; if (task) *task = j; return t; } } return nil; } Task* getftask(char *fname, int *status, int *task) { Task *t; Status *s; int i, j; i = 0; for (s = statuses; s; s = s->next, i++) { t = s->tasks; j = 0; while (t) { if (strcmp(t->dir->name, fname) == 0) { if (status) *status = i; if (task) *task = j; return t; } t = t->next; j++; } } return nil; } Task* getgtask(ulong id, Status **status) { Task *t; Status *s; for (s = statuses; s; s = s->next) { t = s->tasks; while (t) { if (t->id == id) { if (status) *status = s; return t; } t = t->next; } } return nil; } Task* getntask(char *status, int id) { Status *s; int i; Task *t; s = getstatus(status, nil); if (!s) return nil; i = 0; t = s->tasks; while (i != id && t->next) { i++; t = t->next; } return i == id ? t : nil; } void freetask(Task *t) { if (t->cassignee) free(t->dir->uid); if (t->cgroup) free(t->dir->gid); if (t->cfname) free(t->dir->name); if (t->title) free(t->title); free(t->dir); } static int updatetask(Task *t, char *name, char *assignee, char *group, char *title) { char path[256]; char *c, buf[64]; snprint(path, sizeof(path), "%s/%s", srcdir, name); snprint(t->hash, sizeof(t->hash), "%s", name); freetask(t); if (title) t->title = strdup(title); t->dir = dirstat(path); if (assignee) { t->dir->uid = strdup(assignee); t->cassignee = 1; } if (group) { t->dir->gid = strdup(group); t->cgroup = 1; } if (title) { snprint(buf, sizeof(buf), "%s", title); for (c = buf; *c; c++) { if (*c == ' ' || *c == '\t') { *c = '_'; } } t->dir->name = smprint("%s-%s", t->hash, buf); } else t->dir->name = strdup(t->hash); t->cfname = 1; return 1; } int addtask(char *status, char *name, char *assignee, char *group, char *title, ulong id) { Status *s; Task *t; s = getstatus(status, nil); if (!s) { werrstr("status %s not found", status); return 0; } t = getstask(name, nil, nil); if (t) { t->id = id; return updatetask(t, name, assignee, group, title); } if (!s->tasks) { s->tasks = mallocz(sizeof(Task), 1); t = s->tasks; } else { for (t = s->tasks; t->next; t = t->next) ; t->next = mallocz(sizeof(Task), 1); t = t->next; } t->id = id; return updatetask(t, name, assignee, group, title); } void readstatuses(void) { Ndbtuple *tuple, *val; Ndbs s; if (ndbchanged(index)) ndbreopen(index); for (tuple = ndbsearch(index, &s, "key", "status"); tuple != nil; tuple = ndbsnext(&s, "key", "status")) { if (val = ndbfindattr(tuple, s.t, "name")) { addstatus(val->val); } else sysfatal("invalid index file"); } } static void readtasks(void) { Ndbtuple *tuple, *val, *sval; Ndbtuple *assignee, *group, *tval; Ndbs ns; ulong id; if (ndbchanged(index)) ndbreopen(index); id = 0; for (tuple = ndbsearch(index, &ns, "key", "task"); tuple != nil; tuple = ndbsnext(&ns, "key", "task")) { if ((val = ndbfindattr(tuple, ns.t, "id")) && (sval = ndbfindattr(tuple, ns.t, "status"))) { assignee = ndbfindattr(tuple, ns.t, "assignee"); group = ndbfindattr(tuple, ns.t, "group"); tval = ndbfindattr(tuple, ns.t, "title"); addtask(sval->val, val->val, assignee ? assignee->val : nil, group ? group->val : nil, tval ? tval->val : nil, id); id++; } else sysfatal("invalid index"); } maxtask = id; } void fsopen(Req *r) { respond(r, nil); } static char* statusname(int id) { Status *s = getnstatus(id); return s ? strdup(s->name) : nil; } static void fillstat(Dir *d, uvlong path) { int type = 0; // memset(d, 0, sizeof(Dir)); d->uid = estrdup9p("todo"); d->gid = estrdup9p("todo"); switch (qidtype(path)) { case Qdir: case Qstatus: type = QTDIR; break; case Qctl: case Qtask: type = 0; break; } d->qid = (Qid){path, 0, type}; d->atime = d->mtime = 0; d->length = 0; if (qidtype(path) == Qtask) { d->length = 999; } switch (qidtype(path)) { case Qdir: d->name = estrdup9p(qnames[Qdir]); d->mode = DMDIR|0555; break; case Qstatus: d->name = statusname(qidnum(path)); d->mode = DMDIR|0555; break; case Qctl: d->name = estrdup9p(qnames[Qctl]); d->mode = 0666; break; } } static int rootgen(int i, Dir *d, void *aux) { Status *s; USED(aux); switch (i) { case 0: /* ctl */ fillstat(d, mkqid(Qctl, 0)); return 0; default: /* statuses */ i -= 1; s = getnstatus(i); if (!s) return -1; fillstat(d, mkqid(Qstatus, i)); return 0; } } static int statusgen(int i, Dir *d, void *aux) { Status *s = (Status*)aux; int snum; Task *t; getstatus(s->name, &snum); t = getntask(s->name, i); if (!t) return -1; d->name = strdup(t->dir->name); d->qid = (Qid){mkqid(Qtask, t->id), 0, 0}; d->mode = 0666; d->atime = t->dir->atime; d->mtime = t->dir->mtime; d->length = t->dir->length; d->uid = strdup(t->dir->uid); d->gid = strdup(t->dir->gid); return 0; } int taskread(Req *r) { Task *t; char path[256]; Biobuf *bin; long n; t = getgtask(qidnum(r->fid->qid.path), nil); if (!t) { werrstr("task not found"); return 0; } snprint(path, sizeof(path), "%s/%s", srcdir, t->hash); bin = Bopen(path, OREAD); if (!bin) return 0; Bseek(bin, r->ifcall.offset, 0); n = Bread(bin, r->ofcall.data, r->ifcall.count); if (n < 0) { Bterm(bin); return 0; } r->ofcall.count = n; Bterm(bin); return 1; } void fsread(Req *r) { Status *s; switch (qidtype(r->fid->qid.path)) { case Qdir: readstatuses(); dirread9p(r, rootgen, nil); respond(r, nil); break; case Qstatus: readstatuses(); s = getnstatus(qidnum(r->fid->qid.path)); readtasks(); dirread9p(r, statusgen, s); respond(r, nil); break; case Qtask: readtasks(); if (!taskread(r)) responderror(r); else respond(r, nil); break; case Qctl: respond(r, nil); break; default: respond(r, "error"); } } int taskwrite(Req *r) { Task *t; char path[256]; Biobuf *bout; long n; t = getgtask(qidnum(r->fid->qid.path), nil); if (!t) { werrstr("task not found"); return 0; } snprint(path, sizeof(path), "%s/%s", srcdir, t->hash); bout = Bopen(path, OWRITE); if (!bout) return 0; Bseek(bout, r->ifcall.offset, 0); n = Bwrite(bout, r->ifcall.data, r->ifcall.count); if (n < 0) { Bterm(bout); return 0; } r->ofcall.count = n; Bterm(bout); return 1; } int ctlwrite(Req *r) { char str[256]; char *args[5]; int n; memset(str, 0, sizeof(str)); memcpy(str, r->ifcall.data, r->ifcall.count); n = tokenize(str, args, 5); if (n < 1) return 1; if (strcmp(args[0], "addstatus") == 0) { if (n != 2) goto Addstatuserr; if (args[1] && *args[1]) { addstatus(args[1]); return 1; } Addstatuserr: werrstr("usage: addstatus status"); return 0; } if (strcmp(args[0], "dump") == 0) { if (n != 1) { werrstr("usage: dump"); return 0; } readtasks(); return savedata(); } werrstr("invalid command: '%s'", args[0]); return 0; } void fswrite(Req *r) { switch (qidtype(r->fid->qid.path)) { case Qctl: if (!ctlwrite(r)) responderror(r); else respond(r, nil); break; case Qtask: if (!taskwrite(r)) responderror(r); else respond(r, nil); break; case Qdir: case Qstatus: respond(r, nil); break; default: respond(r, "error"); } } void fscreate(Req *r) { respond(r, nil); } static void fsattach(Req *r) { r->ofcall.qid = (Qid){Qdir, 0, QTDIR}; r->fid->qid = r->ofcall.qid; r->fid->aux = 0; respond(r, nil); } static char* fswalk1(Fid *fid, char *name, Qid *qid) { int isdotdot; Status *s; Task *t; int sid, tid; isdotdot = strcmp(name, "..") == 0; switch (qidtype(fid->qid.path)) { case Qdir: if (isdotdot) { *qid = fid->qid; return nil; } if (strcmp(name, qnames[Qctl]) == 0) { *qid = (Qid){mkqid(Qctl, 0), 0, 0}; return nil; } s = getstatus(name, &sid); if (!s) return "file not found"; *qid = (Qid){mkqid(Qstatus, sid), 0, QTDIR}; return nil; case Qstatus: if (isdotdot) { *qid = (Qid){mkqid(Qdir, 0), 0, QTDIR}; return nil; } if (t = getftask(name, &sid, &tid)) { *qid = (Qid){mkqid(Qtask, t->id), 0, 0}; return nil; } return "file not found"; } return "error"; } static void fsstat(Req *r) { fillstat(&r->d, r->fid->qid.path); respond(r, nil); } Srv fs = { .attach = fsattach, .open = fsopen, .read = fsread, .write = fswrite, .create = fscreate, .walk1 = fswalk1, .stat = fsstat, }; void main(int argc, char **argv) { char *srvname = nil; char *mtpt = "/mnt/todo"; ARGBEGIN{ case 's': srvname = EARGF(usage()); break; case 'm': mtpt = EARGF(usage()); break; default: usage(); }ARGEND; if (argc != 1) usage(); quotefmtinstall(); srcdir = *argv; idxfile = smprint("%s/index", srcdir); assert(idxfile); index = ndbopen(idxfile); if (!index) sysfatal("unable to open index file: %r"); postmountsrv(&fs, srvname, mtpt, MREPL|MCREATE); exits(0); }