aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/ramfs.c
diff options
context:
space:
mode:
authorrsc <devnull@localhost>2003-12-11 17:50:50 +0000
committerrsc <devnull@localhost>2003-12-11 17:50:50 +0000
commit57ccfb9e8f51138c33ad5f58e14c0e54246cf5c4 (patch)
tree4a444cd4a94cfcac19d5926ef40d769f7132ae6d /src/cmd/ramfs.c
parentb3994ec5c78e6c18885079b58abb7fb997899c3f (diff)
downloadplan9port-57ccfb9e8f51138c33ad5f58e14c0e54246cf5c4.tar.gz
plan9port-57ccfb9e8f51138c33ad5f58e14c0e54246cf5c4.tar.bz2
plan9port-57ccfb9e8f51138c33ad5f58e14c0e54246cf5c4.zip
ramfs
Diffstat (limited to 'src/cmd/ramfs.c')
-rw-r--r--src/cmd/ramfs.c904
1 files changed, 904 insertions, 0 deletions
diff --git a/src/cmd/ramfs.c b/src/cmd/ramfs.c
new file mode 100644
index 00000000..2c1658b0
--- /dev/null
+++ b/src/cmd/ramfs.c
@@ -0,0 +1,904 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+
+int post9pservice(int, char*);
+
+/*
+ * 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[])(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";
+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;
+
+ service = "ramfs";
+ defmnt = "/tmp";
+ ARGBEGIN{
+ case 'D':
+ debug = 1;
+ break;
+ case 'i':
+ defmnt = 0;
+ stdio = 1;
+ mfd[0] = 0;
+ mfd[1] = 1;
+ break;
+ case 's':
+ defmnt = 0;
+ break;
+ case 'm':
+ defmnt = ARGF();
+ break;
+ case 'p':
+ private++;
+ break;
+ case 'S':
+ defmnt = 0;
+ service = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(defmnt)
+ sysfatal("cannot mount -- not on plan 9");
+
+ if(pipe(p) < 0)
+ error("pipe failed");
+ if(!stdio){
+ mfd[0] = p[0];
+ mfd[1] = p[0];
+ if(post9pservice(p[1], service) < 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*
+clone(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 = clone(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){
+fprint(2, "got fid %d busy=%d\n", fid, f->busy);
+ 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");
+}
+