shithub: 9pro

Download patch

ref: a2793fe2305db73c1c1395877fd4b7dd978a9297
parent: 00f653695f2970fb94987baf4ecbb82d5e4b19fd
author: Sigrid Haflínudóttir <ftrvxmtrx@gmail.com>
date: Sat Dec 21 18:38:56 EST 2019

first

--- /dev/null
+++ b/.gitignore
@@ -1,0 +1,1 @@
+9pex
--- /dev/null
+++ b/9pex.c
@@ -1,0 +1,722 @@
+#define _FILE_OFFSET_BITS 64
+#include <errno.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <aio.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+#include "c9.h"
+
+#define max(a,b) ((a)>(b)?(a):(b))
+#define used(x) ((void)(x))
+#define nelem(x) (int)(sizeof(x)/sizeof((x)[0]))
+
+enum
+{
+	Canrd = 1<<0,
+	Canwr = 1<<1,
+};
+
+typedef struct
+{
+	char *path; /* full path */
+	char *name; /* base name */
+
+	DIR *dir; /* set when it's an opened directory */
+	uint64_t diroffset; /* to read dirs correctly */
+
+	C9qid qid;
+	C9fid fid;
+	C9mode mode; /* mode in which the file was opened */
+	uint32_t iounit;
+
+	int fd; /* set when it's an opened file */
+}Fid;
+
+typedef struct
+{
+	C9tag tag;
+}Tag;
+
+static char *t2s[] = {
+	[Tversion-Tversion] "Tversion",
+	[Tauth-Tversion]    "Tauth",
+	[Tattach-Tversion]  "Tattach",
+	[Tflush-Tversion]   "Tflush",
+	[Twalk-Tversion]    "Twalk",
+	[Topen-Tversion]    "Topen",
+	[Tcreate-Tversion]  "Tcreate",
+	[Tread-Tversion]    "Tread",
+	[Twrite-Tversion]   "Twrite",
+	[Tclunk-Tversion]   "Tclunk",
+	[Tremove-Tversion]  "Tremove",
+	[Tstat-Tversion]    "Tstat",
+	[Twstat-Tversion]   "Twstat",
+};
+
+static char *Enoauth = "authentication not required";
+static char *Eunknownfid = "unknown fid";
+static char *Enowstat = "wstat prohibited";
+static char *Eperm = "permission denied";
+static char *Enowrite = "write prohibited";
+static char *Enomem = "out of memory";
+static char *Edupfid = "duplicate fid";
+static char *Ewalknodir = "walk in non-directory";
+static char *Enotfound = "file not found";
+static char *Eduptag = "duplicate tag";
+static char *Ebotch = "9P protocol botch";
+static char *Enocreate = "create prohibited";
+static char *Eisdir = "is a directory";
+static char *Ebadoffset = "bad offset";
+
+static int in, out;
+static C9ctx ctx;
+static int debug = 1;
+static Fid **fids;
+static int numfids;
+static Tag **tags;
+static int numtags;
+static char *rootpath;
+static size_t rootlen;
+static C9qid walkqids[C9maxpathel];
+static uint8_t *rdbuf;
+static uint8_t *wrbuf;
+static int wroff;
+
+static void
+trace(const char *fmt, ...)
+{
+	va_list ap;
+
+	if (debug == 0)
+		return;
+
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+}
+
+static int
+canrw(int rdonly)
+{
+	struct timeval t;
+	fd_set r, w, e;
+	int n, fl;
+
+	FD_ZERO(&r);
+	FD_SET(in, &r);
+	if (rdonly == 0) {
+		FD_ZERO(&w);
+		FD_SET(out, &w);
+	}
+	FD_ZERO(&e);
+	FD_SET(in, &e);
+	FD_SET(out, &e);
+	memset(&t, 0, sizeof(t));
+	t.tv_usec = 1000;
+	if ((n = select(max(in, out) + 1, &r, &w, &e, rdonly ? NULL : &t)) < 0 || FD_ISSET(in, &e) || FD_ISSET(out, &e))
+		return -1;
+
+	fl = 0;
+	if (FD_ISSET(in, &r))
+		fl |= Canrd;
+	if (FD_ISSET(out, &w))
+		fl |= Canwr;
+
+	return fl;
+}
+
+static int
+hastag(C9tag tag)
+{
+	int i;
+
+	for (i = 0; i < numtags; i++) {
+		if (tags[i] != NULL && tags[i]->tag == tag)
+			return 1;
+	}
+
+	return 0;
+}
+
+static int
+statpath(char *path, struct stat *st, char **err)
+{
+	if (stat(path, st) == 0)
+		return 0;
+
+	if (errno == EACCES)
+		*err = Eperm;
+	else if (errno == ENOMEM)
+		*err = Enomem;
+	else if (errno == ENOTDIR)
+		*err = Ewalknodir;
+	else if (errno == ENOENT)
+		*err = Enotfound;
+	else
+		*err = strerror(errno);
+
+	return -1;
+}
+
+static void
+stat2qid(struct stat *st, C9qid *qid, uint32_t *iounit)
+{
+	int fmt;
+
+	qid->path = st->st_ino;
+	qid->version = st->st_mtime; /* no. */
+	fmt = st->st_mode & S_IFMT;
+	if (fmt == S_IFDIR)
+		qid->type |= C9qtdir;
+	if (fmt == S_IFCHR || fmt == S_IFCHR || fmt == S_IFSOCK || fmt == S_IFIFO)
+		qid->type |= C9qtappend;
+	if (iounit != NULL)
+		*iounit = st->st_blksize;
+}
+
+static Fid *
+newfid(C9fid fid, char *path, char **err)
+{
+	Fid *f, **newfids;
+	struct stat st;
+	int i;
+
+	for (i = 0; i < numfids; i++) {
+		if (fids[i] != NULL && fids[i]->fid == fid) {
+			*err = Edupfid;
+			return NULL;
+		}
+	}
+
+	if (statpath(path, &st, err) != 0)
+		return NULL;
+
+	if ((f = calloc(1, sizeof(*f))) == NULL) {
+		*err = Enomem;
+		return NULL;
+	}
+	f->fd = -1;
+	f->path = strdup(path);
+	f->name = strrchr(f->path, '/');
+	if (f->name == NULL)
+		f->name = f->path;
+	else
+		f->name++;
+
+	for (i = 0; i < numfids; i++) {
+		if (fids[i] == NULL) {
+			fids[i] = f;
+			break;
+		}
+	}
+	if (i >= numfids) {
+		if ((newfids = realloc(fids, (numfids+1)*sizeof(*fids))) == NULL) {
+			*err = Enomem;
+			return NULL;
+		}
+		fids = newfids;
+		fids[numfids++] = f;
+	}
+
+	f->fid = fid;
+	stat2qid(&st, &f->qid, &f->iounit);
+
+	return f;
+}
+
+static Fid *
+findfid(C9fid fid, char **err)
+{
+	int i;
+
+	for (i = 0; i < numfids; i++) {
+		if (fids[i] != NULL && fids[i]->fid == fid) {
+			return fids[i];
+		}
+	}
+
+	*err = Eunknownfid;
+
+	return NULL;
+}
+
+static int
+delfid(C9fid fid, char **err)
+{
+	Fid *f;
+	int i;
+
+	for (i = 0; i < numfids; i++) {
+		f = fids[i];
+		if (f != NULL && f->fid == fid) {
+			if (f->dir != NULL)
+				closedir(f->dir);
+			else if (f->fd >= 0)
+				close(f->fd);
+			free(f->path);
+			free(f);
+			fids[i] = NULL;
+			return 0;
+		}
+	}
+
+	*err = Eunknownfid;
+
+	return -1;
+}
+
+static Fid *
+walk(C9fid fid, C9fid nfid, char *el[], C9qid *qids[], char **err)
+{
+	Fid *f;
+	char *path, *real, *p;
+	struct stat st;
+	int i, plen, ellen;
+
+	if ((f = findfid(fid, err)) == NULL)
+		return NULL;
+
+	if (el[0] == NULL) { /* nwname = 0 */
+		qids[0] = NULL;
+		if (fid == nfid)
+			return f;
+		return newfid(nfid, f->path, err);
+	}
+
+	if ((f->qid.type & C9qtdir) == 0) { /* has to be a dir */
+		*err = Ewalknodir;
+		return NULL;
+	}
+
+	p = strdup(f->path);
+	f = NULL;
+	for (i = 0; el[i] != NULL; i++) {
+		plen = strlen(p);
+		ellen = strlen(el[i]);
+		path = malloc(plen + 1 + ellen + 1);
+		memmove(path, p, plen);
+		path[plen] = '/';
+		memmove(path+plen+1, el[i], ellen);
+		path[plen+1+ellen] = 0;
+
+		if ((real = realpath(path, NULL)) == NULL)
+			break;
+		if (strlen(real) < rootlen) { /* don't escape root */
+			free(real);
+			real = strdup(rootpath);
+		}
+		free(p);
+		p = real;
+
+		if (statpath(p, &st, err) != 0)
+			break;
+		qids[i] = &walkqids[i];
+		stat2qid(&st, qids[i], NULL);
+	}
+
+	qids[i] = NULL;
+	if (el[i] == NULL) { /* could walk all the way */
+		f = newfid(nfid, p, err);
+		if (f != NULL && f->name[0] == '/' && f->name[1] == 0) /* root */
+			f->name = "/";
+	} else if (i != 0) { /* didn't fail on the first one */
+		*err = NULL;
+	}
+	free(p);
+
+	return f;
+}
+
+static int
+openfid(Fid *f, C9mode mode, char **err)
+{
+	struct stat st;
+	int omode;
+
+	if ((f->qid.type & C9qtdir) != 0) {
+		if ((f->dir = opendir(f->path)) == NULL) {
+			*err = strerror(errno);
+			return -1;
+		}
+		f->fd = dirfd(f->dir);
+	} else {
+		omode = O_RDONLY;
+		if ((f->qid.type & C9qtappend) != 0)
+			omode |= O_APPEND;
+		f->fd = open(f->path, omode);
+	}
+
+	if (f->fd < 0 || fstat(f->fd, &st) != 0) {
+		*err = strerror(errno);
+
+		if (f->dir != NULL)
+			closedir(f->dir);
+		else if (f->fd >= 0)
+			close(f->fd);
+
+		f->dir = NULL;
+		f->fd = -1;
+		return -1;
+	}
+	stat2qid(&st, &f->qid, &f->iounit);
+	f->mode = mode;
+
+	return 0;
+}
+
+static char *
+uid2str(uid_t uid, char **err)
+{
+	struct passwd *p;
+
+	if ((p = getpwuid(uid)) == NULL)
+		*err = strerror(errno);
+	else
+		return p->pw_name;
+
+	return NULL;
+}
+
+static char *
+gid2str(gid_t gid, char **err)
+{
+	struct group *g;
+
+	if ((g = getgrgid(gid)) == NULL)
+		*err = strerror(errno);
+	else
+		return g->gr_name;
+
+	return NULL;
+}
+
+static int
+stat2c9stat(char *name, struct stat *st, C9stat *out, char **err)
+{
+	int fmt;
+
+	memset(out, 0, sizeof(*out));
+	out->size = st->st_size;
+	stat2qid(st, &out->qid, NULL);
+	out->name = name;
+	out->atime = st->st_atime;
+	out->mtime = st->st_ctime;
+
+	fmt = st->st_mode & S_IFMT;
+	if (fmt == S_IFDIR)
+		out->mode |= C9stdir;
+	if (fmt == S_IFCHR || fmt == S_IFCHR || fmt == S_IFSOCK || fmt == S_IFIFO)
+		out->mode |= C9stappend;
+	out->mode |= st->st_mode & 0x1ff;
+	if ((out->uid = uid2str(st->st_uid, err)) == NULL)
+		return -1;
+	if ((out->gid = gid2str(st->st_gid, err)) == NULL)
+		return -1;
+
+	return 0;
+}
+
+static int
+statfid(Fid *f, C9stat *out, char **err)
+{
+	struct stat st;
+	int r;
+
+	if (f->fd >= 0)
+		r = fstat(f->fd, &st);
+	else
+		r = stat(f->path, &st);
+	if (r != 0) {
+		*err = strerror(errno);
+		return -1;
+	}
+
+	return stat2c9stat(f->name, &st, out, err);
+}
+
+static uint8_t *
+ctxread(C9ctx *c, uint32_t size, int *err)
+{
+	int n;
+
+	used(c);
+	*err = 0;
+	if ((n = read(in, rdbuf, size)) != (int)size) { /* unexpected eof */
+		*err = 1;
+		return NULL;
+	}
+
+	return rdbuf;
+}
+
+static uint8_t *
+ctxbegin(C9ctx *c, uint32_t size)
+{
+	uint8_t *b;
+
+	used(c);
+	b = wrbuf + wroff;
+	wroff += size;
+
+	return b;
+}
+
+static int
+ctxend(C9ctx *c)
+{
+	used(c);
+
+	if (write(out, wrbuf, wroff) != wroff)
+		return -1;
+	wroff = 0;
+
+	return 0;
+}
+
+static int
+readf(C9ctx *c, C9tag tag, Fid *f, uint64_t offset, uint32_t size, char **err)
+{
+	struct stat st;
+	void *p;
+	struct dirent *e;
+	ssize_t n;
+	C9stat c9st[16], *c9stp[16];
+	long dirpos[16];
+	int i, num;
+
+	if (size > c->msize - 12) /* make sure it fits */
+		size = c->msize - 12;
+
+	if (f->dir == NULL) { /* a file */
+		if ((p = malloc(size)) == NULL) {
+			*err = Enomem;
+			return -1;
+		}
+		if ((n = pread(f->fd, p, size, offset)) < 0) {
+			*err = strerror(errno);
+			return -1;
+		}
+		s9read(c, tag, p, n);
+		return 0;
+	}
+
+	/* dir */
+	if (offset != f->diroffset) {
+		if (offset == 0) {
+			rewinddir(f->dir);
+			f->diroffset = 0;
+		} else {
+			*err = Ebadoffset;
+			return -1;
+		}
+	}
+
+	for (i = 0; i < nelem(c9st); i++) {
+		dirpos[i] = telldir(f->dir); /* so we can rewind in case another stat doesn't fit */
+
+		errno = 0;
+		if ((e = readdir(f->dir)) == NULL && errno != 0) {
+			*err = strerror(errno);
+			return -1;
+		}
+		if (e == NULL) /* eof */
+			break;
+
+		if (fstatat(f->fd, e->d_name, &st, 0) != 0) {
+			*err = strerror(errno);
+			return -1;
+		}
+		if (stat2c9stat(e->d_name, &st, &c9st[i], err) != 0)
+			return -1;
+		c9stp[i] = &c9st[i];
+	}
+
+	num = i;
+	if (s9readdir(c, tag, c9stp, &num, &f->diroffset, size) != 0)
+		return -1;
+	trace("<- Rread tag=%d ...\n", tag);
+	seekdir(f->dir, dirpos[num]);
+
+	return 0;
+}
+
+static int
+s9do(C9error e, char **err)
+{
+	if (e == 0) {
+		*err = NULL;
+		return 0;
+	}
+
+	switch (e) {
+	case C9Einit: *err = "c9: initialization failed"; break;
+	case C9Ever: *err = "c9: protocol version doesn't match"; break;
+	case C9Epkt: *err = "c9: incoming packet error"; break;
+	case C9Etag: *err = "c9: no free tags or bad tag"; break;
+	case C9Ebuf: *err = Enomem; break;
+	case C9Epath: *err = "c9: path is too long or just invalid"; break;
+	case C9Eflush: *err = "c9: limit of outstanding flushes reached"; break;
+	case C9Esize: *err = "c9: can't fit data in one message"; break;
+	case C9Estr: *err = "c9: bad string"; break;
+	default: *err = "c9: unknown error"; break;
+	}
+
+	return -1;
+}
+
+static void
+ctxt(C9ctx *c, C9t *t)
+{
+	Fid *f;
+	C9qid *qids[C9maxpathel+1];
+	char *err;
+	C9stat st;
+	int i;
+
+	trace("-> %s tag=%d", t2s[t->type-Tversion], t->tag);
+
+	err = NULL;
+	if (hastag(t->tag)) {
+		err = Eduptag;
+	} else {
+		switch (t->type){
+		case Tversion:
+			trace("\n");
+			if (s9do(s9version(c), &err) == 0)
+				trace("<- Rversion\n");
+			break;
+		case Tauth:
+			trace(" afid=%d uname=\"%s\" aname=\"%s\"\n", t->auth.afid, t->auth.uname, t->auth.aname);
+			err = Enoauth;
+			break;
+		case Tattach:
+			trace(" afid=%d fid=%d uname=\"%s\" aname=\"%s\"\n", t->attach.afid, t->fid, t->attach.uname, t->attach.aname);
+			if (t->attach.afid != C9nofid) {
+				err = Eunknownfid;
+			} else if ((f = newfid(t->fid, ".", &err)) != NULL) {
+				f->name = "/";
+				if (s9do(s9attach(c, t->tag, &f->qid), &err) == 0)
+					trace("<- Rattach\n");
+			}
+			break;
+		case Tflush:
+			trace(" oldtag=%d\n", t->flush.oldtag);
+			/* FIXME flush it for realz */
+			if (s9do(s9flush(c, t->tag), &err) == 0)
+				trace("<- Rflush tag=%d\n", t->tag);
+			break;
+		case Twalk:
+			trace(" fid=%d newfid=%d", t->fid, t->walk.newfid);
+			for (i = 0; t->walk.wname[i] != NULL; i++)
+				trace(" \"%s\"", t->walk.wname[i]);
+			trace("\n");
+			walk(t->fid, t->walk.newfid, t->walk.wname, qids, &err);
+			if (err == NULL && s9do(s9walk(c, t->tag, qids), &err) == 0)
+				trace("<- Rwalk tag=%d ...\n", t->tag);
+			break;
+		case Topen:
+			trace(" fid=%d mode=0x%02x\n", t->fid, t->open.mode);
+			if ((f = findfid(t->fid, &err)) != NULL) {
+				if (f->fd >= 0)
+					err = Ebotch;
+				else if (t->open.mode != C9read && t->open.mode != C9exec)
+					err = Eperm;
+				else if (t->open.mode != C9read && (f->qid.type & C9qtdir) != 0)
+					err = Eisdir;
+				else if (openfid(f, t->open.mode, &err) == 0 && s9do(s9open(c, t->tag, &f->qid, f->iounit), &err) == 0)
+					trace("<- Ropen tag=%d iounit=%d ...\n", t->tag, f->iounit);
+			}
+			break;
+		case Tcreate:
+			trace("...\n");
+			err = Enocreate;
+			break;
+		case Tread:
+			trace(" fid=%d offset=%lld count=%d\n", t->fid, t->read.offset, t->read.size);
+			if ((f = findfid(t->fid, &err)) != NULL) {
+				if ((f->dir == NULL && f->fd < 0) || (f->mode & 0xf) == C9write)
+					err = Ebotch;
+				else if (readf(c, t->tag, f, t->read.offset, t->read.size, &err) != 0)
+					trace("readf failed\n");
+			}
+			break;
+		case Twrite:
+			trace("...\n");
+			err = Enowrite;
+			break;
+		case Tclunk:
+			trace("\n");
+			if (delfid(t->fid, &err) == 0 && s9do(s9clunk(c, t->tag), &err) == 0)
+				trace("<- Rclunk tag=%d\n", t->tag);
+			break;
+		case Tremove:
+			trace("\n");
+			err = Eperm;
+			break;
+		case Tstat:
+			trace(" fid=%d\n", t->fid);
+			if ((f = findfid(t->fid, &err)) != NULL && statfid(f, &st, &err) == 0 && s9do(s9stat(c, t->tag, &st), &err) == 0)
+				trace("<- Rstat tag=%d ...\n", t->tag);
+			break;
+		case Twstat:
+			trace("...\n");
+			err = Enowstat;
+			break;
+		}
+	}
+
+	if (err != NULL) {
+		if (s9error(c, t->tag, err) == 0)
+			trace("<- Rerror tag=%d \"%s\"\n", t->tag, err);
+		else
+			trace("failed to send an error response\n");
+	}
+}
+
+int
+main(int argc, char **argv)
+{
+	int can;
+
+	used(argc); used(argv);
+
+	rootpath = realpath(".", NULL);
+	rootlen = strlen(rootpath);
+
+	in = 0;
+	out = 1;
+	fids = NULL;
+	numfids = 0;
+	tags = NULL;
+	numtags = 0;
+
+	memset(&ctx, 0, sizeof(ctx));
+	ctx.msize = 64*1024;
+	ctx.read = ctxread;
+	ctx.begin = ctxbegin;
+	ctx.end = ctxend;
+	ctx.t = ctxt;
+	ctx.error = trace;
+
+	rdbuf = malloc(ctx.msize);
+	wrbuf = malloc(ctx.msize);
+	wroff = 0;
+
+	for (;;) {
+		if ((can = canrw(1)) < 0)
+			break;
+		if ((can & Canrd) != 0 && s9proc(&ctx) != 0)
+			break;
+	}
+
+	return 0;
+}
--- /dev/null
+++ b/LICENSE
@@ -1,0 +1,1 @@
+Public domain.
--- /dev/null
+++ b/README.md
@@ -1,0 +1,8 @@
+# 9pro
+
+9p-related tools for Unix-like operating systems.
+
+ * 9pex - share current directory over stdin/stdout, can be used with socat/inetd
+
+This is all _WIP_ still.  9pex is working in read-only mode so far but
+lacks proper auth, async IO, some more error control etc.
--- /dev/null
+++ b/build.sh
@@ -1,0 +1,2 @@
+#!/bin/sh
+gcc -lrt -O -ggdb -Wall -Wextra -Werror ../c9/*.c -I../c9 9pex.c -o 9pex