ref: a05459a8d6a5a190db2fe52a89ed22bf515834b4
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, Qtitle, Qdata, Qassignee, Qgroup, }; char *qnames[] = { [Qdir] "/", [Qctl] "ctl", [Qstatus] nil, [Qtask] nil, [Qtitle] "title", [Qdata] "data", [Qassignee] "assignee", [Qgroup] "group", }; int qidtype(ulong path) { // -----0000 return path & 15; } ulong qidnum(ulong path) { // 000000---- return path >> 4; } ulong mkqid(int type, int num) { return (num << 4) | type & 15; } 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; char *dataname; char *ext; 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 && strcmp(t->dir->uid, "na") != 0) { Bprint(bout, " assignee=\"%s\"", t->dir->uid); } if (t->cgroup && strcmp(t->dir->gid, "ng") != 0) { 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; } static int getsid(Status *s) { int i = 0; for (Status *n = statuses; n; n = n->next) { if (s == n) return i; i++; } return -1; } 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 = t->id; 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; } } werrstr("task not found"); 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); if (t->dataname) free(t->dataname); if (t->ext) free(t->ext); free(t->dir); t->cassignee = 0; t->cgroup = 0; t->cfname = 0; t->title = nil; t->dir = nil; t->dataname = nil; t->ext = nil; } static void settaskassignee(Task *t, char *assignee) { assert(t); if (t->cassignee) free(t->dir->uid); t->dir->uid = strdup(assignee && assignee[0] ? assignee : "na"); t->cassignee = 1; } static void settaskgroup(Task *t, char *group) { assert(t); if (t->cgroup) free(t->dir->uid); t->dir->gid = strdup(group && group[0] ? group : "ng"); t->cgroup = 1; } 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->title = nil; 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) { char buf[32]; char *ext; Dir *dir; int fd; int n, i; freetask(t); fd = open(".", OREAD); if (fd < 0) sysfatal("unable to open dir: %r"); ext = nil; i = 0; while (n = dirread(fd, &dir)) { for (i = 0; i < n; i++) { if (strcmp(dir[i].name, "..") == 0) continue; ext = strchr(dir[i].name, '.'); if (ext) { snprint(buf, sizeof(buf), "%s.", name); if (strncmp(buf, dir[i].name, strlen(buf)) == 0) { goto Break; } } else { if (strcmp(name, dir[i].name) == 0) goto Break; } } free(dir); } Break: close(fd); if (!dir) { werrstr("task not found: %s", name); return 0; } t->dir = dirstat(dir[i].name); free(dir); if (!ext) { t->dataname = strdup(qnames[Qdata]); } else { t->dataname = smprint("%s%s", qnames[Qdata], ext); } t->ext = ext ? strdup(ext) : nil; settaskassignee(t, assignee); settaskgroup(t, group); 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 filltaskstat(Dir *d, Task *t) { d->uid = estrdup9p(t->dir->uid); d->gid = estrdup9p(t->dir->gid); d->atime = t->dir->atime; d->mtime = t->dir->mtime; } static void fillstat(Dir *d, uvlong path) { int type = 0; Task *t; // memset(d, 0, sizeof(Dir)); d->uid = estrdup9p("todo"); d->gid = estrdup9p("todo"); switch (qidtype(path)) { case Qdir: case Qstatus: case Qtask: type = QTDIR; break; case Qctl: type = 0; break; } d->qid = (Qid){path, 0, type}; d->atime = d->mtime = 0; d->length = 0; if (qidtype(path) == Qtask) { d->length = 999; } t = getgtask(qidnum(path), nil); 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 Qtask: d->name = estrdup9p(t->dir->name); d->mode = DMDIR|0555; case Qtitle: d->name = estrdup9p(qnames[Qtitle]); d->mode = 0666; goto Commontask; case Qdata: d->name = estrdup9p(t->dataname); d->mode = 0666; goto Commontask; case Qassignee: d->name = estrdup9p(qnames[Qassignee]); d->mode = 0666; goto Commontask; case Qgroup: d->name = estrdup9p(qnames[Qgroup]); d->mode = 0666; /* falls through */ Commontask: filltaskstat(d, t); 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; Task *t; t = getntask(s->name, i); if (!t) return -1; d->name = strdup(t->dir->name); d->qid = (Qid){mkqid(Qtask, t->id), 0, QTDIR}; d->mode = DMDIR|0555; 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; } static int taskgen(int i, Dir *d, void *aux) { Task *t = (Task*)aux; switch (i + Qtitle) { case Qtitle: d->name = strdup(qnames[Qtitle]); d->qid = (Qid){mkqid(Qtitle, t->id), 0, 0}; d->length = t->title ? strlen(t->title) : 0; break; case Qdata: d->name = strdup(t->dataname); d->qid = (Qid){mkqid(Qdata, t->id), 0, 0}; d->length = t->dir->length; break; case Qassignee: d->name = strdup(qnames[Qassignee]); d->qid = (Qid){mkqid(Qassignee, t->id), 0, 0}; d->length = t->dir->uid ? strlen(t->dir->uid) : 0; break; case Qgroup: d->name = strdup(qnames[Qgroup]); d->qid = (Qid){mkqid(Qgroup, t->id), 0, 0}; d->length = t->dir->gid ? strlen(t->dir->gid) : 0; break; default: return -1; } d->mode = 0666; filltaskstat(d, t); return 0; } int taskread(Req *r) { Task *t; char *f; Biobuf *bin; long n; t = getgtask(qidnum(r->fid->qid.path), nil); if (!t) { werrstr("task not found"); return 0; } f = t->ext ? smprint("%s%s", ultostr(t->id), t->ext) : strdup(ultostr(t->id)); bin = Bopen(f, OREAD); free(f); 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; Task *t; ulong qnum = qidnum(r->fid->qid.path); switch (qidtype(r->fid->qid.path)) { case Qdir: readstatuses(); dirread9p(r, rootgen, nil); respond(r, nil); break; case Qstatus: readstatuses(); s = getnstatus(qnum); readtasks(); dirread9p(r, statusgen, s); respond(r, nil); break; case Qtask: readtasks(); t = getgtask(qnum, nil); if (!t) { respond(r, "task not found"); return; } dirread9p(r, taskgen, t); respond(r, nil); break; case Qtitle: readtasks(); t = getgtask(qnum, nil); if (!t) { respond(r, "task not found"); return; } if (t->title) readstr(r, t->title); respond(r, nil); break; case Qdata: if (!taskread(r)) responderror(r); else respond(r, nil); break; case Qassignee: readtasks(); t = getgtask(qnum, nil); if (!t) { respond(r, "task not found"); return; } if (t->cassignee) readstr(r, t->dir->uid); respond(r, nil); break; case Qgroup: readtasks(); t = getgtask(qnum, nil); if (!t) { respond(r, "task not found"); return; } if (t->cgroup) readstr(r, t->dir->gid); 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]) { if (addstatus(args[1])) return savedata(); 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; } static int taskwritefield(Task *t, int field, Req *r) { char *s; if (r->ifcall.offset != 0) { werrstr("write offset not 0"); return 0; } if (r->ifcall.count == 0) { switch(field) { case Qtitle: settasktitle(t, nil); break; case Qassignee: settaskassignee(t, nil); break; case Qgroup: settaskgroup(t, nil); break; } return 1; } s = r->ifcall.data; r->ifcall.data[r->ifcall.count-1] = 0; // last byte 0 for (int i = 0; i < r->ifcall.count; i++) { if (s[i] == '\n') { s[i] = 0; break; } } switch (field) { case Qtitle: settasktitle(t, s); break; case Qassignee: settaskassignee(t, s); break; case Qgroup: settaskgroup(t, s); break; default: sysfatal("cannot happen"); } return savedata(); } void fswrite(Req *r) { Task *t; ulong qnum; int qtype; qnum = qidnum(r->fid->qid.path); qtype = qidtype(r->fid->qid.path); switch (qtype) { case Qctl: if (!ctlwrite(r)) responderror(r); else respond(r, nil); break; case Qtitle: case Qassignee: case Qgroup: t = getgtask(qnum, nil); if (!t) { responderror(r); break; } if (!taskwritefield(t, qtype, r)) { responderror(r); break; } r->ofcall.count = r->ifcall.count; respond(r, nil); break; case Qdata: if (!taskwrite(r)) responderror(r); else respond(r, nil); break; case Qtask: 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, *sfrom; Task *t; int sid; ulong id, qnum; qnum = qidnum(r->fid->qid.path); switch (qidtype(r->fid->qid.path)) { case Qstatus: s = getnstatus(qnum); if (!s) { respond(r, "status not found"); return; } id = strtoid(r->ifcall.name); t = getgtask(id, &sfrom); sid = getsid(sfrom); if (t) { /* move existing task */ if (sid == qnum) { respond(r, "task already has status"); return; } if (!taskmv(t, sfrom, s)) { responderror(r); return; } r->fid->qid = (Qid){mkqid(Qtask, t->id), 0, QTDIR}; 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, QTDIR}; 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; ulong qnum; isdotdot = strcmp(name, "..") == 0; qnum = qidnum(fid->qid.path); 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)) { if (qnum != sid) { return "task has a different status"; } *qid = (Qid){mkqid(Qtask, t->id), 0, QTDIR}; return nil; } return "task not found"; case Qtask: t = getgtask(qnum, &s); if (!t) { return "task not found"; } sid = getsid(s); if (isdotdot) { *qid = (Qid){mkqid(Qstatus, sid), 0, QTDIR}; return nil; } if (strcmp(name, qnames[Qtitle]) == 0) { *qid = (Qid){mkqid(Qtitle, qnum), 0, 0}; return nil; } if (strcmp(name, qnames[Qassignee]) == 0) { *qid = (Qid){mkqid(Qassignee, qnum), 0, 0}; return nil; } if (strcmp(name, qnames[Qgroup]) == 0) { *qid = (Qid){mkqid(Qgroup, qnum), 0, 0}; return nil; } if (strcmp(name, t->dataname) == 0) { *qid = (Qid){mkqid(Qdata, qnum), 0, 0}; return nil; } if (strcmp(name, qnames[Qdata]) == 0) { *qid = (Qid){mkqid(Qdata, qnum), 0, 0}; return nil; } } 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"; int gen = 0; int fd; ARGBEGIN{ case 's': srvname = EARGF(usage()); break; case 'm': mtpt = EARGF(usage()); break; case 'c': gen++; 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"; if (gen) { fd = create(idxfile, OWRITE, 0666); if (fd < 0) sysfatal("unable to create index file: %r"); fprint(fd, "key=config nextid=\"1\"\n"); close(fd); exits(0); } assert(idxfile); index = ndbopen(idxfile); if (!index) sysfatal("unable to open index file: %r"); postmountsrv(&fs, srvname, mtpt, MREPL|MCREATE); exits(0); }