shithub: todofs

Download patch

ref: 423f4db8e2a87e2319834ea9896a3d568cb64334
author: sirjofri <sirjofri@sirjofri.de>
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);
+}