blob: 6e00c894b9d001cce3e1c7572d3c8b0e5f8a286e [file] [log] [blame]
#include <u.h>
#include <libc.h>
#include <fcall.h>
/*
* 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 = 2048,
Maxsize = 512*1024*1024,
Maxfdata = 8192
};
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 */
Qid qid;
long perm;
char *name;
ulong atime;
ulong mtime;
char *user;
char *group;
char *muid;
char *data;
long ndata;
};
enum
{
Pexec = 1,
Pwrite = 2,
Pread = 4,
Pother = 1,
Pgroup = 8,
Powner = 64
};
ulong path; /* incremented for each new file */
Fid *fids;
Ram ram[Nram];
int nram;
int mfd[2];
char *user;
uchar mdata[IOHDRSZ+Maxfdata];
uchar rdata[Maxfdata]; /* buffer for data in reply */
uchar statbuf[STATMAX];
Fcall thdr;
Fcall rhdr;
int messagesize = sizeof mdata;
Fid * newfid(int);
uint ramstat(Ram*, uchar*, uint);
void error(char*);
void io(void);
void *erealloc(void*, ulong);
void *emalloc(ulong);
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*);
char *(*fcalls[Tmax])(Fid*);
static void
initfcalls(void)
{
fcalls[Tversion]= rversion;
fcalls[Tflush]= rflush;
fcalls[Tauth]= rauth;
fcalls[Tattach]= rattach;
fcalls[Twalk]= rwalk;
fcalls[Topen]= ropen;
fcalls[Tcreate]= rcreate;
fcalls[Tread]= rread;
fcalls[Twrite]= rwrite;
fcalls[Tclunk]= rclunk;
fcalls[Tremove]= rremove;
fcalls[Tstat]= rstat;
fcalls[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";
char Ebadfid[] = "bad fid";
int debug;
int private;
void
notifyf(void *a, char *s)
{
USED(a);
if(strncmp(s, "interrupt", 9) == 0)
noted(NCONT);
noted(NDFLT);
}
void
main(int argc, char *argv[])
{
Ram *r;
char *defmnt;
int p[2];
int stdio = 0;
char *service;
initfcalls();
service = "ramfs";
defmnt = nil;
ARGBEGIN{
case 'D':
debug = 1;
break;
case 'i':
defmnt = 0;
stdio = 1;
mfd[0] = 0;
mfd[1] = 1;
break;
case 's':
defmnt = nil;
break;
case 'm':
defmnt = ARGF();
break;
case 'p':
private++;
break;
case 'S':
defmnt = 0;
service = EARGF(usage());
break;
default:
usage();
}ARGEND
USED(defmnt);
if(pipe(p) < 0)
error("pipe failed");
if(!stdio){
mfd[0] = p[0];
mfd[1] = p[0];
if(post9pservice(p[1], service, nil) < 0)
sysfatal("post9pservice %s: %r", service);
}
user = getuser();
notify(notifyf);
nram = 2;
r = &ram[0];
r->busy = 1;
r->data = 0;
r->ndata = 0;
r->perm = DMDIR | 0775;
r->qid.type = QTDIR;
r->qid.path = 0;
r->qid.vers = 0;
r->parent = 0;
r->user = user;
r->group = user;
r->muid = user;
r->atime = time(0);
r->mtime = r->atime;
r->name = estrdup(".");
r = &ram[1];
r->busy = 1;
r->data = 0;
r->ndata = 0;
r->perm = 0666;
r->qid.type = 0;
r->qid.path = 1;
r->qid.vers = 0;
r->parent = 0;
r->user = user;
r->group = user;
r->muid = user;
r->atime = time(0);
r->mtime = r->atime;
r->name = estrdup("file");
if(debug)
fmtinstall('F', fcallfmt);
switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG)){
case -1:
error("fork");
case 0:
close(p[1]);
io();
break;
default:
close(p[0]); /* don't deadlock if child fails */
}
exits(0);
}
char*
rversion(Fid *x)
{
Fid *f;
USED(x);
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 *x)
{
if(x->busy)
return Ebadfid;
return "ramfs: no authentication required";
}
char*
rflush(Fid *f)
{
USED(f);
return 0;
}
char*
rattach(Fid *f)
{
/* no authentication! */
if(f->busy)
return Ebadfid;
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*
xclone(Fid *f, Fid **nf)
{
if(!f->busy)
return Ebadfid;
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;
ulong t;
int i;
if(!f->busy)
return Ebadfid;
err = nil;
nf = nil;
rhdr.nwqid = 0;
if(thdr.newfid != thdr.fid){
err = xclone(f, &nf);
if(err)
return err;
f = nf; /* walk the new fid */
}
fram = f->ram;
if(thdr.nwname > 0){
t = 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 == nil)
err = Enotexist;
}
if(nf != nil && (err!=nil || rhdr.nwqid<thdr.nwname)){
/* clunk the new fid, which is the one we walked */
fprint(2, "f %d zero busy\n", f->fid);
f->busy = 0;
f->ram = nil;
}
if(rhdr.nwqid == thdr.nwname) /* update the fid after a successful walk */
f->ram = fram;
assert(f->busy);
return err;
}
char *
ropen(Fid *f)
{
Ram *r;
int mode, trunc;
if(!f->busy)
return Ebadfid;
if(f->open)
return Eisopen;
r = f->ram;
if(r->busy == 0)
return Enotexist;
if(r->perm & DMEXCL)
if(r->open)
return Excl;
mode = thdr.mode;
if(r->qid.type & QTDIR){
if(mode != OREAD)
return Eperm;
rhdr.qid = r->qid;
return 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;
}
trunc = mode & OTRUNC;
mode &= OPERM;
if(mode==OWRITE || mode==ORDWR || trunc)
if(!perm(f, r, Pwrite))
return Eperm;
if(mode==OREAD || mode==ORDWR)
if(!perm(f, r, Pread))
return Eperm;
if(mode==OEXEC)
if(!perm(f, r, Pexec))
return Eperm;
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;
if(!f->busy)
return Ebadfid;
if(f->open)
return Eisopen;
if(f->ram->busy == 0)
return Enotexist;
parent = f->ram - ram;
if((f->ram->qid.type&QTDIR) == 0)
return Enotdir;
/* must be able to write parent */
if(!perm(f, f->ram, Pwrite))
return Eperm;
prm = thdr.perm;
name = thdr.name;
if(strcmp(name, ".")==0 || strcmp(name, "..")==0)
return Ename;
for(r=ram; r<&ram[nram]; r++)
if(r->busy && parent==r->parent)
if(strcmp((char*)name, r->name)==0)
return Einuse;
for(r=ram; r->busy; r++)
if(r == &ram[Nram-1])
return "no free ram resources";
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 = 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(thdr.mode & ORCLOSE)
f->rclose = 1;
r->open++;
return 0;
}
char*
rread(Fid *f)
{
Ram *r;
uchar *buf;
long off;
int n, m, cnt;
if(!f->busy)
return Ebadfid;
if(f->ram->busy == 0)
return Enotexist;
n = 0;
rhdr.count = 0;
off = thdr.offset;
buf = rdata;
cnt = thdr.count;
if(cnt > messagesize) /* shouldn't happen, anyway */
cnt = messagesize;
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 = 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;
ulong off;
int cnt;
r = f->ram;
if(!f->busy)
return Ebadfid;
if(r->busy == 0)
return Enotexist;
off = thdr.offset;
if(r->perm & DMAPPEND)
off = r->ndata;
cnt = thdr.count;
if(r->qid.type & QTDIR)
return Eisdir;
if(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 = nil;
r->busy = 0;
return nil;
}
char *
rclunk(Fid *f)
{
char *e = nil;
if(f->open)
f->ram->open--;
if(f->rclose)
e = realremove(f->ram);
fprint(2, "clunk fid %d busy=%d\n", f->fid, f->busy);
fprint(2, "f %d zero busy\n", f->fid);
f->busy = 0;
f->open = 0;
f->ram = 0;
return e;
}
char *
rremove(Fid *f)
{
Ram *r;
if(f->open)
f->ram->open--;
fprint(2, "f %d zero busy\n", f->fid);
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->busy)
return Ebadfid;
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;
Dir dir;
if(!f->busy)
return Ebadfid;
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;
}
uint
ramstat(Ram *r, uchar *buf, uint nbuf)
{
int n;
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 = nil;
f->fid = fid;
f->next = fids;
fids = f;
return f;
}
void
io(void)
{
char *err, buf[20];
int n, pid, ctl;
pid = getpid();
if(private){
snprint(buf, sizeof buf, "/proc/%d/ctl", pid);
ctl = open(buf, OWRITE);
if(ctl < 0){
fprint(2, "can't protect ramfs\n");
}else{
fprint(ctl, "noswap\n");
fprint(ctl, "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)
error("mount read");
if(n == 0)
error("mount eof");
if(convM2S(mdata, n, &thdr) == 0)
continue;
if(debug)
fprint(2, "ramfs %d:<-%F\n", pid, &thdr);
if(!fcalls[thdr.type])
err = "bad fcall type";
else
err = (*fcalls[thdr.type])(newfid(thdr.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)
fprint(2, "ramfs %d:->%F\n", pid, &rhdr);/**/
n = convS2M(&rhdr, mdata, messagesize);
if(n == 0)
error("convS2M error on write");
if(write(mfd[1], mdata, n) != n)
error("mount write");
}
}
int
perm(Fid *f, Ram *r, int p)
{
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
error(char *s)
{
fprint(2, "%s: %s: %r\n", argv0, s);
exits(s);
}
void *
emalloc(ulong n)
{
void *p;
p = malloc(n);
if(!p)
error("out of memory");
memset(p, 0, n);
return p;
}
void *
erealloc(void *p, ulong n)
{
p = realloc(p, n);
if(!p)
error("out of memory");
return p;
}
char *
estrdup(char *q)
{
char *p;
int n;
n = strlen(q)+1;
p = malloc(n);
if(!p)
error("out of memory");
memmove(p, q, n);
return p;
}
void
usage(void)
{
fprint(2, "usage: %s [-is] [-m mountpoint]\n", argv0);
exits("usage");
}