ref: 423f4db8e2a87e2319834ea9896a3d568cb64334
author: sirjofri <[email protected]>
date: Fri May 31 17:01:07 EDT 2024
adds todofs
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,6 @@
+</$objtype/mkfile
+
+TARG=todofs
+OFILES=todofs.$O
+
+</sys/src/cmd/mkone
--- /dev/null
+++ b/todofs.c
@@ -1,0 +1,804 @@
+#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);
+}