ref: 6dc0bc759e48e1fc98cd5faf7237c128dd8eb914
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; } static char* ultostr(ulong n) { char buf[32]; snprint(buf, sizeof(buf), "%0ulx", n); return buf; } static ulong strtoid(char *s) { return strtoul(s, nil, 16); } char *srcdir; char *idxfile; Ndb *index = nil; typedef struct Task Task; struct Task { Task* next; 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 nextid; 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; Bprint(bout, "key=config " "nextid=\"%s\"\n" "\n", ultostr(nextid)); for (s = statuses; s; s = s->next) { Bprint(bout, "key=status name=\"%s\"\n", s->name); } Bprint(bout, "\n"); tid = 0; while (tid < nextid) { t = getgtask(tid, &s); if (t) { Bprint(bout, "key=task id=\"%s\" status=\"%s\"", ultostr(t->id), 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; ulong l; s = getstatus(status, nil); if (!s) return nil; l = strtoul(name, nil, 16); t = s->tasks; i = 0; while (t && l != t->id) { 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); t->cassignee = 0; t->cgroup = 0; t->cfname = 0; t->title = nil; t->dir = nil; } static int settasktitle(Task *t, char *title) { char buf[64], *c; if (t->title) free(t->title); if (t->cfname && t->dir->name) free(t->dir->name); if (title && title[0]) { t->title = strdup(title); snprint(buf, sizeof(buf), "%s", title); for (c = buf; *c; c++) { if (*c == ' ' || *c == '\t') *c = '_'; } t->dir->name = smprint("%s-%s", ultostr(t->id), buf); } else { t->dir->name = smprint("%s", ultostr(t->id)); } t->cfname = 1; return 1; } static int updatetask(Task *t, char *name, char *assignee, char *group, char *title) { freetask(t); t->dir = dirstat(name); t->dir->uid = strdup(assignee ? assignee : "na"); t->cassignee = 1; t->dir->gid = strdup(group ? group : "ng"); t->cgroup = 1; return settasktitle(t, title); } 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); tuple = ndbsearch(index, &ns, "key", "config"); if (!tuple) sysfatal("bad index: missing config"); val = ndbfindattr(tuple, ns.t, "nextid"); if (!val) sysfatal("bad config: missing nextid"); nextid = strtoul(val->val, nil, 16); 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"); id = strtoul(val->val, nil, 16); addtask(sval->val, val->val, assignee ? assignee->val : nil, group ? group->val : nil, tval ? tval->val : nil, id); } else sysfatal("invalid index"); } } static ulong newtask(char *name, char *status) { int fd; char *id; ulong newid; readstatuses(); readtasks(); id = ultostr(nextid); fd = create(id, OREAD, 0666); if (fd < 0) { werrstr("unable to open task file '%s'", name); return 0; } close(fd); if (!addtask(status, id, nil, nil, name, nextid)) { werrstr("cannot add task: %r"); return 0; } newid = nextid++; return savedata() ? newid : 0; } 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; Biobuf *bin; long n; t = getgtask(qidnum(r->fid->qid.path), nil); if (!t) { werrstr("task not found"); return 0; } bin = Bopen(ultostr(t->id), 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; Biobuf *bout; long n; t = getgtask(qidnum(r->fid->qid.path), nil); if (!t) { werrstr("task not found"); return 0; } bout = Bopen(ultostr(t->id), 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"); } } int taskmv(Task *t, Status *from, Status *to) { Task *prev; if (from->tasks == t) { from->tasks = t->next; goto Chain; } for (prev = from->tasks; prev->next && prev->next != t; prev = prev->next) ; prev->next = t->next; Chain: t->next = nil; if (!to->tasks) { to->tasks = t; return 1; } for (prev = to->tasks; prev->next; prev = prev->next) ; prev->next = t; return 1; } void fscreate(Req *r) { Status *s, *s2; Task *t; int tid, sid; ulong id; switch (qidtype(r->fid->qid.path)) { case Qstatus: s = getnstatus(qidnum(r->fid->qid.path)); if (!s) { respond(r, "status not found"); return; } id = strtoid(r->ifcall.name); fprint(2, "id of task: %ulx\n", id); t = getgtask(id, &s2); if (t) { /* move existing task */ if (sid == qidnum(r->fid->qid.path)) { respond(r, "task already has status"); return; } if (!taskmv(t, s2, s)) { responderror(r); return; } r->fid->qid = (Qid){mkqid(Qtask, t->id), 0, 0}; r->ofcall.qid = r->fid->qid; } else { /* create new task */ id = newtask(r->ifcall.name, s->name); if (!id) { responderror(r); return; } t = getgtask(id, nil); if (!t) { respond(r, "error creating new task"); return; } r->fid->qid = (Qid){mkqid(Qtask, t->id), 0, 0}; r->ofcall.qid = r->fid->qid; } savedata(); break; default: respond(r, "not allowed"); return; } 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); } static void fswstat(Req *r) { Task *t; int save = 0; switch (qidtype(r->fid->qid.path)) { case Qtask: t = getgtask(qidnum(r->fid->qid.path), nil); if (!t) { respond(r, "invalid task"); return; } if (r->d.uid && r->d.uid[0]) { if (t->cassignee && t->dir->uid) free(t->dir->uid); t->dir->uid = strdup(r->d.uid); t->cassignee = 1; save++; } if (r->d.gid && r->d.gid[0]) { if (t->cgroup && t->dir->gid) free(t->dir->gid); t->dir->gid = strdup(r->d.gid); t->cgroup = 1; save++; } if (r->d.name && r->d.name[0]) { settasktitle(t, r->d.name); save++; } if (save) savedata(); break; default: respond(r, "invalid operation"); return; } respond(r, nil); } Srv fs = { .attach = fsattach, .open = fsopen, .read = fsread, .write = fswrite, .create = fscreate, .walk1 = fswalk1, .stat = fsstat, .wstat = fswstat, }; 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; case '9': chatty9p++; break; default: usage(); }ARGEND; if (argc != 1) usage(); quotefmtinstall(); srcdir = *argv; if (chdir(srcdir)) sysfatal("unable to chdir: %r"); idxfile = "index"; assert(idxfile); index = ndbopen(idxfile); if (!index) sysfatal("unable to open index file: %r"); postmountsrv(&fs, srvname, mtpt, MREPL|MCREATE); exits(0); }