Commit Diff


commit - b3994ec5c78e6c18885079b58abb7fb997899c3f
commit + 57ccfb9e8f51138c33ad5f58e14c0e54246cf5c4
blob - /dev/null
blob + 2c1658b01831a69d5a50ec37ce78e0e55ef6c259 (mode 644)
--- /dev/null
+++ 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");
+}
+