blob: bced145650da9e2118ea29e76cc2fc9d38a5e1e8 [file] [log] [blame] [edit]
#include <stdlib.h>
#include <stdio.h>
#include <parlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <pthread.h>
#include <fcntl.h>
#include <ctype.h>
#include <error.h>
#include <nixip.h>
#include <dir.h>
#include <ndb.h>
#include <fcall.h>
#include <printf-ext.h>
#warning "you really need to fix the include directory tree structure!"
#define DMDIR 0x80000000 /* mode bit for directories */
#define QTDIR 0x80 /* type bit for directories */
/*
* Rather than reading /adm/users, which is a lot of work for
* a toy program, we assume all groups have the form
* NNN:user:user:
* meaning that each user is the leader of his own group.
*/
enum {
OPERM = 0x3, /* mask of all permission types in open mode */
Nram = 4096,
Maxsize = 768 * 1024 * 1024,
Maxfdata = 8192,
Maxulong = (1ULL << 32) - 1,
};
typedef struct Fid Fid;
typedef struct Ram Ram;
struct Fid {
short busy;
short open;
short rclose;
int fid;
Fid *next;
char *user;
Ram *ram;
};
struct Ram {
short busy;
short open;
long parent; /* index in Ram array */
struct qid qid;
long perm;
char *name;
uint32_t atime;
uint32_t mtime;
char *user;
char *group;
char *muid;
char *data;
long ndata;
};
enum {
Pexec = 1,
Pwrite = 2,
Pread = 4,
Pother = 1,
Pgroup = 8,
Powner = 64,
};
uint32_t path; /* incremented for each new file */
Fid *fids;
Ram ram[Nram];
int nram;
int mfd[2];
char *user;
uint8_t mdata[IOHDRSZ + Maxfdata];
uint8_t rdata[Maxfdata]; /* buffer for data in reply */
#define STATMAX 65536
uint8_t statbuf[STATMAX];
struct fcall thdr;
struct fcall rhdr;
int messagesize = sizeof mdata;
Fid *newfid(int);
unsigned int ramstat(Ram *, uint8_t *, unsigned int);
void io(void);
void *erealloc(void *, uint32_t);
void *emalloc(uint32_t);
char *estrdup(char *);
void usage(void);
int perm(Fid *, Ram *, int);
char *rflush(Fid *), *rversion(Fid *), *rauth(Fid *),
*rattach(Fid *), *rwalk(Fid *),
*ropen(Fid *), *rcreate(Fid *),
*rread(Fid *), *rwrite(Fid *), *rclunk(Fid *),
*rremove(Fid *), *rstat(Fid *), *rwstat(Fid *);
int needfid[] = {
[Tversion] 0,
[Tflush] 0,
[Tauth] 0,
[Tattach] 0,
[Twalk] 1,
[Topen] 1,
[Tcreate] 1,
[Tread] 1,
[Twrite] 1,
[Tclunk] 1,
[Tremove] 1,
[Tstat] 1,
[Twstat] 1,
};
char *(*fcalls[]) (Fid *) = {
[Tversion] rversion,
[Tflush] rflush,
[Tauth] rauth,
[Tattach] rattach,
[Twalk] rwalk,
[Topen] ropen,
[Tcreate] rcreate,
[Tread] rread,
[Twrite] rwrite,
[Tclunk] rclunk,[Tremove] rremove,[Tstat] rstat,[Twstat] rwstat,};
char Eperm[] = "permission denied";
char Enotdir[] = "not a directory";
char Enoauth[] = "ramfs: authentication not required";
char Enotexist[] = "file does not exist";
char Einuse[] = "file in use";
char Eexist[] = "file exists";
char Eisdir[] = "file is a directory";
char Enotowner[] = "not owner";
char Eisopen[] = "file already open for I/O";
char Excl[] = "exclusive use file already open";
char Ename[] = "illegal name";
char Eversion[] = "unknown 9P version";
char Enotempty[] = "directory not empty";
int debug = 0;
int private;
static int memlim = 1;
#warning "need signal handling in ramfs"
#if 0
void notifyf(void *a, char *s)
{
if (strncmp(s, "interrupt", 9) == 0) {
ignore(NCONT);
fprintf(stderr, "noted\n");
exit(1);
}
ignore(NDFLT);
fprintf(stderr, "noted\n");
exit(1);
}
#endif
char *argv0;
#define ARGBEGIN for((argv0||(argv0=*argv)),argv++,argc--;\
argv[0] && argv[0][0]=='-' && argv[0][1];\
argc--, argv++) {\
char *_args, *_argt;\
char _argc; \
_args = &argv[0][1];\
if(_args[0]=='-' && _args[1]==0){\
argc--; argv++; break;\
}\
_argc = _args[0];\
while(*_args && _args++)\
switch(_argc)
#define ARGEND /*SET(_argt);USED(_argt,_argc,_args);}USED(argv, argc);*/}
#define ARGF() (_argt=_args, _args="",\
(*_argt? _argt: argv[1]? (argc--, *++argv): 0))
#define EARGF(x) (_argt=_args, _args="",\
(*_argt? _argt: argv[1]? (argc--, *++argv): ((x), abort(), (char*)0)))
#define ARGC() _argc
void main(int argc, char *argv[])
{
Ram *r;
char *defmnt, *service;
int p[2];
int fd;
int stdio = 0;
int ret;
service = "ramfs";
defmnt = NULL; //"/tmp";
/* blame lindent. */
ARGBEGIN {
case 'i':
defmnt = 0;
stdio = 1;
mfd[0] = 0;
mfd[1] = 1;
break;
case 'm':
defmnt = EARGF(usage());
break;
case 'p':
private++;
break;
case 's':
defmnt = 0;
break;
case 'u':
memlim = 0; /* unlimited memory consumption */
break;
case 'D':
debug = 1;
break;
case 'S':
defmnt = 0;
service = EARGF(usage());
break;
default:
usage();
}
ARGEND ret = syscall(SYS_npipe, (unsigned long)p);
if (ret < 0) {
error(1, 0, "pipe: %r");
exit(1);
}
if (!stdio) {
mfd[0] = p[0];
mfd[1] = p[0];
if (defmnt == 0) {
char buf[64];
snprintf(buf, sizeof buf, "#s/%s", service);
#warning "would be nice to have remove on close"
fd = open(buf, O_WRONLY | O_CREAT /*ORCLOSE*/, 0666);
if (fd < 0)
error(1, 0, "create failed: %r");
snprintf(buf, sizeof(buf), "%d", p[1]);
if (write(fd, buf, strlen(buf)) < 0)
error(1, 0, "writing service file: %r");
}
}
user = "eve"; //getuser();
//notify(notifyf);
nram = 1;
r = &ram[0];
r->busy = 1;
r->data = 0;
r->ndata = 0;
r->perm = DMDIR | 0775;
r->qid.type = QTDIR;
r->qid.path = 0LL;
r->qid.vers = 0;
r->parent = 0;
r->user = user;
r->group = user;
r->muid = user;
r->atime = 0; //time(0);
r->mtime = r->atime;
r->name = estrdup(".");
if (debug) {
register_printf_specifier('i', printf_ipaddr, printf_ipaddr_info);
register_printf_specifier('F', printf_fcall, printf_fcall_info);
register_printf_specifier('M', printf_dir, printf_dir_info);
}
#if 0
switch (rfork(RFFDG | RFPROC | RFNAMEG | RFNOTEG)) {
case -1:
error("fork");
case 0:
#endif
close(p[1]);
io();
#if 0
break;
default:
close(p[0]); /* don't deadlock if child fails */
if (defmnt && mount(p[1], -1, defmnt, MREPL | MCREATE, "") < 0)
error("mount failed");
}
exits(0);
#endif
}
char *rversion(Fid * unused)
{
Fid *f;
for (f = fids; f; f = f->next)
if (f->busy)
rclunk(f);
if (thdr.msize > sizeof mdata)
rhdr.msize = sizeof mdata;
else
rhdr.msize = thdr.msize;
messagesize = rhdr.msize;
if (strncmp(thdr.version, "9P2000", 6) != 0)
return Eversion;
rhdr.version = "9P2000";
return 0;
}
char *rauth(Fid * unused)
{
return "ramfs: no authentication required";
}
char *rflush(Fid * f)
{
return 0;
}
char *rattach(Fid * f)
{
/* no authentication! */
f->busy = 1;
f->rclose = 0;
f->ram = &ram[0];
rhdr.qid = f->ram->qid;
if (thdr.uname[0])
f->user = estrdup(thdr.uname);
else
f->user = "none";
if (strcmp(user, "none") == 0)
user = f->user;
return 0;
}
char *clone(Fid * f, Fid ** nf)
{
if (f->open)
return Eisopen;
if (f->ram->busy == 0)
return Enotexist;
*nf = newfid(thdr.newfid);
(*nf)->busy = 1;
(*nf)->open = 0;
(*nf)->rclose = 0;
(*nf)->ram = f->ram;
(*nf)->user = f->user; /* no ref count; the leakage is minor */
return 0;
}
char *rwalk(Fid * f)
{
Ram *r, *fram;
char *name;
Ram *parent;
Fid *nf;
char *err;
uint32_t t;
int i;
err = NULL;
nf = NULL;
rhdr.nwqid = 0;
if (thdr.newfid != thdr.fid) {
err = clone(f, &nf);
if (err)
return err;
f = nf; /* walk the new fid */
}
fram = f->ram;
if (thdr.nwname > 0) {
t = 0; //time(0);
for (i = 0; i < thdr.nwname && i < MAXWELEM; i++) {
if ((fram->qid.type & QTDIR) == 0) {
err = Enotdir;
break;
}
if (fram->busy == 0) {
err = Enotexist;
break;
}
fram->atime = t;
name = thdr.wname[i];
if (strcmp(name, ".") == 0) {
Found:
rhdr.nwqid++;
rhdr.wqid[i] = fram->qid;
continue;
}
parent = &ram[fram->parent];
if (!perm(f, parent, Pexec)) {
err = Eperm;
break;
}
if (strcmp(name, "..") == 0) {
fram = parent;
goto Found;
}
for (r = ram; r < &ram[nram]; r++)
if (r->busy && r->parent == fram - ram
&& strcmp(name, r->name) == 0) {
fram = r;
goto Found;
}
break;
}
if (i == 0 && err == NULL)
err = Enotexist;
}
if (nf != NULL && (err != NULL || rhdr.nwqid < thdr.nwname)) {
/* clunk the new fid, which is the one we walked */
f->busy = 0;
f->ram = NULL;
}
if (rhdr.nwqid > 0)
err = NULL; /* didn't get everything in 9P2000 right! */
if (rhdr.nwqid == thdr.nwname) /* update the fid after a successful walk */
f->ram = fram;
return err;
}
char *ropen(Fid * f)
{
Ram *r;
int mode, trunc;
if (f->open)
return Eisopen;
r = f->ram;
if (r->busy == 0)
return Enotexist;
#warning "DMEXCL would be nice"
#if 0
if (r->perm & DMEXCL)
if (r->open)
return Excl;
#endif
mode = thdr.mode;
if (r->qid.type & QTDIR) {
if (mode != O_RDONLY)
return Eperm;
rhdr.qid = r->qid;
return 0;
}
#if 0
if (mode & ORCLOSE) {
/* can't remove root; must be able to write parent */
if (r->qid.path == 0 || !perm(f, &ram[r->parent], Pwrite))
return Eperm;
f->rclose = 1;
}
#endif
trunc = mode & O_TRUNC;
mode &= 0777; //OPERM;
if (mode == O_WRONLY || mode == O_RDWR || trunc)
if (!perm(f, r, Pwrite))
return Eperm;
if (mode == O_RDONLY || mode == O_RDWR)
if (!perm(f, r, Pread))
return Eperm;
#warning "O_EXEC?"
#if 0
if (mode == O_EXEC)
if (!perm(f, r, Pexec))
return Eperm;
#endif
if (trunc /*&& (r->perm&DMAPPEND)==0 */ ) {
r->ndata = 0;
if (r->data)
free(r->data);
r->data = 0;
r->qid.vers++;
}
rhdr.qid = r->qid;
rhdr.iounit = messagesize - IOHDRSZ;
f->open = 1;
r->open++;
return 0;
}
char *rcreate(Fid * f)
{
Ram *r;
char *name;
long parent, prm;
char *ret;
if (debug) printf("%s\n", __func__);
if (f->open) {
ret = Eisopen;
goto bad;
}
if (f->ram->busy == 0) {
ret = Enotexist;
goto bad;
}
parent = f->ram - ram;
if ((f->ram->qid.type & QTDIR) == 0) {
ret = Enotdir;
goto bad;
}
/* must be able to write parent */
if (!perm(f, f->ram, Pwrite)) {
ret = Eperm;
goto bad;
}
prm = thdr.perm;
name = thdr.name;
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) {
ret = Ename;
goto bad;
}
for (r = ram; r < &ram[nram]; r++)
if (r->busy && parent == r->parent)
if (strcmp((char *)name, r->name) == 0) {
ret = Einuse;
goto bad;
}
for (r = ram; r->busy; r++)
if (r == &ram[Nram - 1]) {
ret = "no free ram resources";
goto bad;
}
r->busy = 1;
r->qid.path = ++path;
r->qid.vers = 0;
if (prm & DMDIR)
r->qid.type |= QTDIR;
r->parent = parent;
free(r->name);
r->name = estrdup(name);
r->user = f->user;
r->group = f->ram->group;
r->muid = f->ram->muid;
if (prm & DMDIR)
prm = (prm & ~0777) | (f->ram->perm & prm & 0777);
else
prm = (prm & (~0777 | 0111)) | (f->ram->perm & prm & 0666);
r->perm = prm;
r->ndata = 0;
if (r - ram >= nram)
nram = r - ram + 1;
r->atime = 0; //time(0);
r->mtime = r->atime;
f->ram->mtime = r->atime;
f->ram = r;
rhdr.qid = r->qid;
rhdr.iounit = messagesize - IOHDRSZ;
f->open = 1;
#if 0
if (thdr.mode & ORCLOSE)
f->rclose = 1;
#endif
r->open++;
bad:
if (debug && ret)
printf("%s: returning :%s:\n", __func__, ret);
return ret;
}
char *rread(Fid * f)
{
Ram *r;
uint8_t *buf;
int64_t off;
int n, m, cnt;
if (f->ram->busy == 0)
return Enotexist;
n = 0;
rhdr.count = 0;
rhdr.data = (char *)rdata;
if (thdr.offset < 0)
return "negative seek offset";
off = thdr.offset;
buf = rdata;
cnt = thdr.count;
if (cnt > messagesize) /* shouldn't happen, anyway */
cnt = messagesize;
if (cnt < 0)
return "negative read count";
if (f->ram->qid.type & QTDIR) {
for (r = ram + 1; off > 0; r++) {
if (r->busy && r->parent == f->ram - ram)
off -= ramstat(r, statbuf, sizeof statbuf);
if (r == &ram[nram - 1])
return 0;
}
for (; r < &ram[nram] && n < cnt; r++) {
if (!r->busy || r->parent != f->ram - ram)
continue;
m = ramstat(r, buf + n, cnt - n);
if (m == 0)
break;
n += m;
}
rhdr.data = (char *)rdata;
rhdr.count = n;
return 0;
}
r = f->ram;
if (off >= r->ndata)
return 0;
r->atime = 0; //time(0);
n = cnt;
if (off + n > r->ndata)
n = r->ndata - off;
rhdr.data = r->data + off;
rhdr.count = n;
return 0;
}
char *rwrite(Fid * f)
{
Ram *r;
int64_t off;
int cnt;
r = f->ram;
rhdr.count = 0;
if (r->busy == 0)
return Enotexist;
if (thdr.offset < 0)
return "negative seek offset";
off = thdr.offset;
#if 0
if (r->perm & DMAPPEND)
off = r->ndata;
#endif
cnt = thdr.count;
if (cnt < 0)
return "negative write count";
if (r->qid.type & QTDIR)
return Eisdir;
if (memlim && off + cnt >= Maxsize) /* sanity check */
return "write too big";
if (off + cnt > r->ndata)
r->data = erealloc(r->data, off + cnt);
if (off > r->ndata)
memset(r->data + r->ndata, 0, off - r->ndata);
if (off + cnt > r->ndata)
r->ndata = off + cnt;
memmove(r->data + off, thdr.data, cnt);
r->qid.vers++;
r->mtime = time(0);
rhdr.count = cnt;
return 0;
}
static int emptydir(Ram * dr)
{
long didx = dr - ram;
Ram *r;
for (r = ram; r < &ram[nram]; r++)
if (r->busy && didx == r->parent)
return 0;
return 1;
}
char *realremove(Ram * r)
{
if (r->qid.type & QTDIR && !emptydir(r))
return Enotempty;
r->ndata = 0;
if (r->data)
free(r->data);
r->data = 0;
r->parent = 0;
memset(&r->qid, 0, sizeof r->qid);
free(r->name);
r->name = NULL;
r->busy = 0;
return NULL;
}
char *rclunk(Fid * f)
{
char *e = NULL;
if (f->open)
f->ram->open--;
if (f->rclose)
e = realremove(f->ram);
f->busy = 0;
f->open = 0;
f->ram = 0;
return e;
}
char *rremove(Fid * f)
{
Ram *r;
if (f->open)
f->ram->open--;
f->busy = 0;
f->open = 0;
r = f->ram;
f->ram = 0;
if (r->qid.path == 0 || !perm(f, &ram[r->parent], Pwrite))
return Eperm;
ram[r->parent].mtime = time(0);
return realremove(r);
}
char *rstat(Fid * f)
{
if (f->ram->busy == 0)
return Enotexist;
rhdr.nstat = ramstat(f->ram, statbuf, sizeof statbuf);
rhdr.stat = statbuf;
return 0;
}
char *rwstat(Fid * f)
{
Ram *r, *s;
struct dir dir;
if (f->ram->busy == 0)
return Enotexist;
convM2D(thdr.stat, thdr.nstat, &dir, (char *)statbuf);
r = f->ram;
/*
* To change length, must have write permission on file.
*/
if (dir.length != ~0 && dir.length != r->ndata) {
if (!perm(f, r, Pwrite))
return Eperm;
}
/*
* To change name, must have write permission in parent
* and name must be unique.
*/
if (dir.name[0] != '\0' && strcmp(dir.name, r->name) != 0) {
if (!perm(f, &ram[r->parent], Pwrite))
return Eperm;
for (s = ram; s < &ram[nram]; s++)
if (s->busy && s->parent == r->parent)
if (strcmp(dir.name, s->name) == 0)
return Eexist;
}
/*
* To change mode, must be owner or group leader.
* Because of lack of users file, leader=>group itself.
*/
if (dir.mode != ~0 && r->perm != dir.mode) {
if (strcmp(f->user, r->user) != 0)
if (strcmp(f->user, r->group) != 0)
return Enotowner;
}
/*
* To change group, must be owner and member of new group,
* or leader of current group and leader of new group.
* Second case cannot happen, but we check anyway.
*/
if (dir.gid[0] != '\0' && strcmp(r->group, dir.gid) != 0) {
if (strcmp(f->user, r->user) == 0)
// if(strcmp(f->user, dir.gid) == 0)
goto ok;
if (strcmp(f->user, r->group) == 0)
if (strcmp(f->user, dir.gid) == 0)
goto ok;
return Enotowner;
ok: ;
}
/* all ok; do it */
if (dir.mode != ~0) {
dir.mode &= ~DMDIR; /* cannot change dir bit */
dir.mode |= r->perm & DMDIR;
r->perm = dir.mode;
}
if (dir.name[0] != '\0') {
free(r->name);
r->name = estrdup(dir.name);
}
if (dir.gid[0] != '\0')
r->group = estrdup(dir.gid);
if (dir.length != ~0 && dir.length != r->ndata) {
r->data = erealloc(r->data, dir.length);
if (r->ndata < dir.length)
memset(r->data + r->ndata, 0, dir.length - r->ndata);
r->ndata = dir.length;
}
ram[r->parent].mtime = time(0);
return 0;
}
unsigned int ramstat(Ram * r, uint8_t * buf, unsigned int nbuf)
{
int n;
struct dir dir;
dir.name = r->name;
dir.qid = r->qid;
dir.mode = r->perm;
dir.length = r->ndata;
dir.uid = r->user;
dir.gid = r->group;
dir.muid = r->muid;
dir.atime = r->atime;
dir.mtime = r->mtime;
n = convD2M(&dir, buf, nbuf);
if (n > 2)
return n;
return 0;
}
Fid *newfid(int fid)
{
Fid *f, *ff;
ff = 0;
for (f = fids; f; f = f->next)
if (f->fid == fid)
return f;
else if (!ff && !f->busy)
ff = f;
if (ff) {
ff->fid = fid;
return ff;
}
f = emalloc(sizeof *f);
f->ram = NULL;
f->fid = fid;
f->next = fids;
fids = f;
return f;
}
void io(void)
{
char *err, buf[40];
int n, pid, ctl;
Fid *fid;
pid = getpid();
if (private) {
snprintf(buf, sizeof buf, "/proc/%d/ctl", pid);
ctl = open(buf, O_WRONLY);
if (ctl < 0) {
fprintf(stderr, "can't protect ramfs\n");
} else {
fprintf(stderr, "noswap\n");
fprintf(stderr, "private\n");
close(ctl);
}
}
for (;;) {
/*
* reading from a pipe or a network device
* will give an error after a few eof reads.
* however, we cannot tell the difference
* between a zero-length read and an interrupt
* on the processes writing to us,
* so we wait for the error.
*/
n = read9pmsg(mfd[0], mdata, messagesize);
if (n < 0) {
#warning "not handling hungup case on pipe"
#if 0
rerrstr(buf, sizeof buf);
if (buf[0] == '\0' || strstr(buf, "hungup")) {
fprintf(stderr, "");
exit(1);
}
#endif
error(1, 0, "mount read: %r");
}
if (n == 0)
continue;
if (convM2S(mdata, n, &thdr) == 0)
continue;
if (debug)
fprintf(stderr, "ramfs %d:<-%F\n", pid, &thdr);
if (thdr.type < 0 || thdr.type >= ARRAY_SIZE(fcalls)
|| !fcalls[thdr.type])
err = "bad fcall type";
else if (((fid = newfid(thdr.fid)) == NULL || !fid->ram)
&& needfid[thdr.type])
err = "fid not in use";
else
err = (*fcalls[thdr.type]) (fid);
if (err) {
rhdr.type = Rerror;
rhdr.ename = err;
} else {
rhdr.type = thdr.type + 1;
rhdr.fid = thdr.fid;
}
rhdr.tag = thdr.tag;
if (debug)
fprintf(stderr, "ramfs %d:->%F\n", pid, &rhdr);
/**/ n = convS2M(&rhdr, mdata, messagesize);
if (n == 0)
error(1, 0, "convS2M error on write: %r");
if (write(mfd[1], mdata, n) != n)
error(1, 0, "mount write: %r");
}
}
int perm(Fid * f, Ram * r, int p)
{
/* No rules! */
return 1;
if ((p * Pother) & r->perm)
return 1;
if (strcmp(f->user, r->group) == 0 && ((p * Pgroup) & r->perm))
return 1;
if (strcmp(f->user, r->user) == 0 && ((p * Powner) & r->perm))
return 1;
return 0;
}
void *emalloc(uint32_t n)
{
void *p;
p = calloc(n, 1);
if (!p)
error(1, 0, "out of memory: %r");
memset(p, 0, n);
return p;
}
void *erealloc(void *p, uint32_t n)
{
p = realloc(p, n);
if (!p)
error(1, 0, "out of memory: %r");
return p;
}
char *estrdup(char *q)
{
char *p;
int n;
n = strlen(q) + 1;
p = calloc(n, 1);
if (!p)
error(1, 0, "out of memory: %r");
memmove(p, q, n);
return p;
}
void usage(void)
{
fprintf(stderr, "usage: %s [-Dipsu] [-m mountpoint] [-S srvname]\n", argv0);
fprintf(stderr, "usage");
exit(1);
}