Introduction
Introduction Statistics Contact Development Disclaimer Help
tramfs - plan9port - [fork] Plan 9 from user space
git clone git://src.adamsgaard.dk/plan9port
Log
Files
Refs
README
LICENSE
---
commit 57ccfb9e8f51138c33ad5f58e14c0e54246cf5c4
parent b3994ec5c78e6c18885079b58abb7fb997899c3f
Author: rsc <devnull@localhost>
Date: Thu, 11 Dec 2003 17:50:50 +0000
ramfs
Diffstat:
A src/cmd/ramfs.c | 904 +++++++++++++++++++++++++++++…
1 file changed, 904 insertions(+), 0 deletions(-)
---
diff --git a/src/cmd/ramfs.c b/src/cmd/ramfs.c
t@@ -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 …
+ 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(na…
+ 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 success…
+ 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");
+}
+
You are viewing proxied material from mx1.adamsgaard.dk. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.