ref: 84e2e4682e915b10d89bad9712f0e6628ddc888d
dir: /9pex.c/
#define _DEFAULT_SOURCE
#define _FILE_OFFSET_BITS 64
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <inttypes.h>
#include <pwd.h>
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include "c9.h"
#include "parg.h"
#define max(a,b) ((a)>(b)?(a):(b))
#define used(x) ((void)(x))
#define nelem(x) (int)(sizeof(x)/sizeof((x)[0]))
uint32_t crc32(const void *data, int len);
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;
typedef struct
{
char *name;
uint32_t id;
}Id;
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, eof;
static C9ctx ctx;
static int debug;
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 uint32_t wroff, wrend, wrbufsz;
static Id *uids, *gids;
static int numuids, numgids;
__attribute__ ((format (printf, 1, 2)))
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, int block)
{
struct timeval t;
fd_set r, w, e;
int n, fl;
FD_ZERO(&r);
FD_SET(in, &r);
FD_ZERO(&w);
if (rdonly == 0)
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, block ? 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
wrsend(void)
{
uint32_t n;
int w;
if (wrend == 0)
return 0;
for (n = 0; n < wrend && (w = write(out, wrbuf+n, wrend-n)) > 0; n += w);
if (w <= 0) {
if (errno != EPIPE) /* remote end closed */
perror("write");
return -1;
}
if (debug >= 2)
trace("<- %d bytes, %d left\n", wrend, wroff-wrend);
memmove(wrbuf, wrbuf+wrend, wroff-wrend);
wroff = wroff - wrend;
return 0;
}
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 = crc32(&st->st_ctim, sizeof(st->st_ctime));
qid->type = C9qtfile;
fmt = st->st_mode & S_IFMT;
if (fmt == S_IFDIR)
qid->type |= C9qtdir;
if ((st->st_mode & 0222) != 0 && (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 int
hasperm(struct stat *st, C9mode mode, char **err)
{
int m, stm, fmt;
m = mode & 0xf;
stm = st->st_mode & 0777;
*err = Eperm;
if (((stm & 0111) == 0 || (stm & 0444) == 0) && m == C9exec) /* executing needs rx */
return 0;
if ((stm & 0222) == 0 && (m == C9write || m == C9rdwr)) /* writing needs w */
return 0;
if ((stm & 0444) == 0 && m != C9write) /* reading needs r */
return 0;
fmt = st->st_mode & S_IFMT;
if (fmt == S_IFDIR && ((stm & 0111) == 0 || (stm & 0444) == 0)) /* dirs need rx */
return 0;
*err = NULL;
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;
free(path);
if (strlen(real) < rootlen) { /* don't escape root */
free(real);
real = strdup(rootpath);
}
free(p);
p = real;
if (statpath(p, &st, err) != 0)
break;
if (el[i+1] != NULL && !hasperm(&st, C9read, err))
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 || !hasperm(&st, mode, err)) {
if (*err == NULL)
*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;
Id *newuids;
int i;
for (i = 0; i < numuids; i++) {
if (uids[i].id == uid)
return uids[i].name;
}
if ((p = getpwuid(uid)) == NULL) {
*err = strerror(errno);
return NULL;
}
if ((newuids = realloc(uids, sizeof(*uids)*(numuids+1))) == NULL) {
*err = Enomem;
return NULL;
}
uids = newuids;
uids[numuids].id = uid;
uids[numuids].name = strdup(p->pw_name);
return uids[numuids++].name;
}
static char *
gid2str(gid_t gid, char **err)
{
struct group *g;
Id *newgids;
int i;
for (i = 0; i < numgids; i++) {
if (gids[i].id == gid)
return gids[i].name;
}
if ((g = getgrgid(gid)) == NULL) {
*err = strerror(errno);
return NULL;
}
if ((newgids = realloc(gids, sizeof(*gids)*(numgids+1))) == NULL) {
*err = Enomem;
return NULL;
}
gids = newgids;
gids[numgids].id = gid;
gids[numgids].name = strdup(g->gr_name);
return gids[numgids++].name;
}
static int
stat2c9stat(char *name, struct stat *st, C9stat *stout, char **err)
{
int fmt;
memset(stout, 0, sizeof(*stout));
stout->size = st->st_size;
stat2qid(st, &stout->qid, NULL);
stout->name = name;
stout->atime = st->st_atime;
stout->mtime = st->st_ctime;
fmt = st->st_mode & S_IFMT;
if (fmt == S_IFDIR)
stout->mode |= C9stdir;
if (fmt == S_IFCHR || fmt == S_IFCHR || fmt == S_IFSOCK || fmt == S_IFIFO)
stout->mode |= C9stappend;
stout->mode |= st->st_mode & 0x1ff;
if ((stout->uid = uid2str(st->st_uid, err)) == NULL)
return -1;
if ((stout->gid = gid2str(st->st_gid, err)) == NULL)
return -1;
return 0;
}
static int
statfid(Fid *f, C9stat *stout, 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, stout, err);
}
static uint8_t *
ctxread(C9ctx *c, uint32_t size, int *err)
{
uint32_t n;
int r;
used(c);
r = 0;
*err = 0;
for (n = 0; n < size && (r = read(in, rdbuf, size)) > 0; n += r);
if (r <= 0) {
if (r == 0)
eof = 1;
else
*err = 1;
return NULL;
}
return rdbuf;
}
static uint8_t *
ctxbegin(C9ctx *c, uint32_t size)
{
uint8_t *b;
used(c);
if (wroff + size > wrbufsz) {
if (wrsend() != 0 || wroff + size > wrbufsz)
return NULL;
}
b = wrbuf + wroff;
wroff += size;
return b;
}
static int
ctxend(C9ctx *c)
{
used(c);
wrend = wroff;
return 0;
}
__attribute__ ((format (printf, 1, 2)))
static void
ctxerror(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
va_end(ap);
}
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 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;
}
if (s9do(s9read(c, tag, p, n), err) != 0)
return -1;
trace("<- Rread tag=%d count=%zd data=...\n", tag, n);
return 0;
}
/* dir */
if (offset != f->diroffset) {
if (offset == 0) {
rewinddir(f->dir);
f->diroffset = 0;
} else {
*err = Ebadoffset;
return -1;
}
}
num = 0;
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 (e->d_name[0] == '.' && (e->d_name[1] == 0 || ((e->d_name[1] == '.' && e->d_name[2] == 0))))
continue;
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[num++] = &c9st[i];
}
i = num;
if (s9do(s9readdir(c, tag, c9stp, &num, &f->diroffset, size), err) != 0)
return -1;
trace("<- Rread tag=%d count=%"PRIu64" data=...\n", tag, f->diroffset - offset);
if (i != num)
seekdir(f->dir, dirpos[num]);
return 0;
}
static void
ctxt(C9ctx *c, C9t *t)
{
Fid *f;
C9qid *qids[C9maxpathel+1];
char *err, *err2;
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, rootpath, &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 ", t->tag);
for (i = 0; qids[i] != NULL; i++)
trace("qid=[path=%"PRIu64" type=0x%02x version=%"PRIu32"] ", qids[i]->path, qids[i]->type, qids[i]->version);
trace("\n");
}
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 qid=[path=%"PRIu64" type=0x%02x version=%"PRIu32"] iounit=%d\n", t->tag, f->qid.path, f->qid.type, f->qid.version, f->iounit);
}
break;
case Tcreate:
trace("...\n");
err = Enocreate;
break;
case Tread:
trace(" fid=%d offset=%"PRIu64" count=%"PRIu32"\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(" fid=%d\n", t->fid);
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 (s9do(s9error(c, t->tag, err), &err2) == 0)
trace("<- Rerror tag=%d \"%s\"\n", t->tag, err);
else
fprintf(stderr, "s9error: %s\n", err2);
}
}
int
main(int argc, char **argv)
{
const char *dir;
char *err;
Fid *f;
struct parg_state ps;
int can, i, c, rdonly, block;
parg_init(&ps);
debug = 0;
dir = NULL;
while ((c = parg_getopt(&ps, argc, argv, "dh")) >= 0) {
switch (c) {
case 1:
if (dir != NULL) {
fprintf(stderr, "only one dir can be specified\n");
return 1;
}
dir = ps.optarg;
break;
case 'd':
debug++;
break;
case 'h':
fprintf(stderr, "usage: 9pex [-d] DIR\n");
return 0;
break;
case '?':
fprintf(stderr, "unknown option -%c\n", ps.optopt);
return 1;
break;
default:
fprintf(stderr, "unhandled option -%c\n", c);
return 1;
break;
}
}
if (dir == NULL) {
fprintf(stderr, "no dir specified\n");
return 1;
}
if ((rootpath = realpath(dir, NULL)) == NULL) {
trace("%s: %s\n", dir, strerror(errno));
return 1;
}
rootlen = strlen(rootpath);
in = 0;
out = 1;
eof = 0;
fids = NULL;
numfids = 0;
tags = NULL;
numtags = 0;
uids = NULL;
numuids = 0;
gids = NULL;
numgids = 0;
memset(&ctx, 0, sizeof(ctx));
ctx.msize = 64*1024;
ctx.read = ctxread;
ctx.begin = ctxbegin;
ctx.end = ctxend;
ctx.t = ctxt;
ctx.error = ctxerror;
rdbuf = calloc(1, ctx.msize);
wrbufsz = ctx.msize;
wrbuf = calloc(1, wrbufsz);
wroff = wrend = 0;
signal(SIGPIPE, SIG_IGN);
err = NULL;
rdonly = block = 1; /* at first we wait until the client sends in data */
for (; !eof;) {
if ((can = canrw(rdonly, block)) < 0)
break;
if ((can & Canrd) != 0) { /* if there is data, process it */
if (s9do(s9proc(&ctx), &err) != 0)
break;
/* give it a chance to receive all the data first */
rdonly = 1;
block = 0;
} else if (block == 0) { /* got all the data */
if (rdonly != 0) { /* wait until we can send OR we get more data */
rdonly = 0;
block = 1;
}
} else if (rdonly == 0 && (can & Canwr) != 0) { /* can send */
if (wrsend() != 0) /* send all the data */
break;
rdonly = 1; /* and go back to reading */
block = 1;
}
}
if (err != NULL)
trace("s9proc: %s\n", err);
for (i = 0; i < numfids; i++) {
if ((f = fids[i]) != NULL) {
if (f->dir != NULL)
closedir(f->dir);
else if (f->fd >= 0)
close(f->fd);
free(f->path);
free(f);
}
}
for (i = 0; i < numuids; i++)
free(uids[i].name);
free(uids);
for (i = 0; i < numgids; i++)
free(gids[i].name);
free(gids);
memset(wrbuf, 0, ctx.msize);
free(wrbuf);
memset(rdbuf, 0, ctx.msize);
free(rdbuf);
free(fids);
free(rootpath);
return 0;
}