ref: a2793fe2305db73c1c1395877fd4b7dd978a9297
parent: 00f653695f2970fb94987baf4ecbb82d5e4b19fd
author: Sigrid Haflínudóttir <[email protected]>
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