xref: /plan9/sys/src/cmd/ramfs.c (revision a6a9e07217f318acf170f99684a55fba5200524f)
1 #include <u.h>
2 #include <libc.h>
3 #include <auth.h>
4 #include <fcall.h>
5 
6 /*
7  * Rather than reading /adm/users, which is a lot of work for
8  * a toy program, we assume all groups have the form
9  *	NNN:user:user:
10  * meaning that each user is the leader of his own group.
11  */
12 
13 enum
14 {
15 	OPERM	= 0x3,		/* mask of all permission types in open mode */
16 	Nram	= 2048,
17 	Maxsize	= 512*1024*1024,
18 	Maxfdata	= 8192,
19 };
20 
21 typedef struct Fid Fid;
22 typedef struct Ram Ram;
23 
24 struct Fid
25 {
26 	short	busy;
27 	short	open;
28 	short	rclose;
29 	int	fid;
30 	Fid	*next;
31 	char	*user;
32 	Ram	*ram;
33 };
34 
35 struct Ram
36 {
37 	short	busy;
38 	short	open;
39 	long	parent;		/* index in Ram array */
40 	Qid	qid;
41 	long	perm;
42 	char	*name;
43 	ulong	atime;
44 	ulong	mtime;
45 	char	*user;
46 	char	*group;
47 	char	*muid;
48 	char	*data;
49 	long	ndata;
50 };
51 
52 enum
53 {
54 	Pexec =		1,
55 	Pwrite = 	2,
56 	Pread = 	4,
57 	Pother = 	1,
58 	Pgroup = 	8,
59 	Powner =	64,
60 };
61 
62 ulong	path;		/* incremented for each new file */
63 Fid	*fids;
64 Ram	ram[Nram];
65 int	nram;
66 int	mfd[2];
67 char	*user;
68 uchar	mdata[IOHDRSZ+Maxfdata];
69 uchar	rdata[Maxfdata];	/* buffer for data in reply */
70 uchar statbuf[STATMAX];
71 Fcall thdr;
72 Fcall	rhdr;
73 int	messagesize = sizeof mdata;
74 
75 Fid *	newfid(int);
76 uint	ramstat(Ram*, uchar*, uint);
77 void	error(char*);
78 void	io(void);
79 void	*erealloc(void*, ulong);
80 void	*emalloc(ulong);
81 char	*estrdup(char*);
82 void	usage(void);
83 int	perm(Fid*, Ram*, int);
84 
85 char	*rflush(Fid*), *rversion(Fid*), *rauth(Fid*),
86 	*rattach(Fid*), *rwalk(Fid*),
87 	*ropen(Fid*), *rcreate(Fid*),
88 	*rread(Fid*), *rwrite(Fid*), *rclunk(Fid*),
89 	*rremove(Fid*), *rstat(Fid*), *rwstat(Fid*);
90 
91 char 	*(*fcalls[])(Fid*) = {
92 	[Tversion]	rversion,
93 	[Tflush]	rflush,
94 	[Tauth]	rauth,
95 	[Tattach]	rattach,
96 	[Twalk]		rwalk,
97 	[Topen]		ropen,
98 	[Tcreate]	rcreate,
99 	[Tread]		rread,
100 	[Twrite]	rwrite,
101 	[Tclunk]	rclunk,
102 	[Tremove]	rremove,
103 	[Tstat]		rstat,
104 	[Twstat]	rwstat,
105 };
106 
107 char	Eperm[] =	"permission denied";
108 char	Enotdir[] =	"not a directory";
109 char	Enoauth[] =	"ramfs: authentication not required";
110 char	Enotexist[] =	"file does not exist";
111 char	Einuse[] =	"file in use";
112 char	Eexist[] =	"file exists";
113 char	Eisdir[] =	"file is a directory";
114 char	Enotowner[] =	"not owner";
115 char	Eisopen[] = 	"file already open for I/O";
116 char	Excl[] = 	"exclusive use file already open";
117 char	Ename[] = 	"illegal name";
118 char	Eversion[] =	"unknown 9P version";
119 
120 int debug;
121 int private;
122 
123 void
124 notifyf(void *a, char *s)
125 {
126 	USED(a);
127 	if(strncmp(s, "interrupt", 9) == 0)
128 		noted(NCONT);
129 	noted(NDFLT);
130 }
131 
132 void
133 main(int argc, char *argv[])
134 {
135 	Ram *r;
136 	char *defmnt;
137 	int p[2];
138 	int fd;
139 	int stdio = 0;
140 	char *service;
141 
142 	service = "ramfs";
143 	defmnt = "/tmp";
144 	ARGBEGIN{
145 	case 'D':
146 		debug = 1;
147 		break;
148 	case 'i':
149 		defmnt = 0;
150 		stdio = 1;
151 		mfd[0] = 0;
152 		mfd[1] = 1;
153 		break;
154 	case 's':
155 		defmnt = 0;
156 		break;
157 	case 'm':
158 		defmnt = ARGF();
159 		break;
160 	case 'p':
161 		private++;
162 		break;
163 	case 'S':
164 		defmnt = 0;
165 		service = EARGF(usage());
166 		break;
167 	default:
168 		usage();
169 	}ARGEND
170 
171 	if(pipe(p) < 0)
172 		error("pipe failed");
173 	if(!stdio){
174 		mfd[0] = p[0];
175 		mfd[1] = p[0];
176 		if(defmnt == 0){
177 			char buf[64];
178 			snprint(buf, sizeof buf, "#s/%s", service);
179 			fd = create(buf, OWRITE, 0666);
180 			if(fd < 0)
181 				error("create failed");
182 			sprint(buf, "%d", p[1]);
183 			if(write(fd, buf, strlen(buf)) < 0)
184 				error("writing service file");
185 		}
186 	}
187 
188 	user = getuser();
189 	notify(notifyf);
190 	nram = 1;
191 	r = &ram[0];
192 	r->busy = 1;
193 	r->data = 0;
194 	r->ndata = 0;
195 	r->perm = DMDIR | 0775;
196 	r->qid.type = QTDIR;
197 	r->qid.path = 0LL;
198 	r->qid.vers = 0;
199 	r->parent = 0;
200 	r->user = user;
201 	r->group = user;
202 	r->muid = user;
203 	r->atime = time(0);
204 	r->mtime = r->atime;
205 	r->name = estrdup(".");
206 
207 	if(debug)
208 		fmtinstall('F', fcallfmt);
209 	switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG)){
210 	case -1:
211 		error("fork");
212 	case 0:
213 		close(p[1]);
214 		io();
215 		break;
216 	default:
217 		close(p[0]);	/* don't deadlock if child fails */
218 		if(defmnt && mount(p[1], -1, defmnt, MREPL|MCREATE, "") < 0)
219 			error("mount failed");
220 	}
221 	exits(0);
222 }
223 
224 char*
225 rversion(Fid*)
226 {
227 	Fid *f;
228 
229 	for(f = fids; f; f = f->next)
230 		if(f->busy)
231 			rclunk(f);
232 	if(thdr.msize > sizeof mdata)
233 		rhdr.msize = sizeof mdata;
234 	else
235 		rhdr.msize = thdr.msize;
236 	messagesize = rhdr.msize;
237 	if(strncmp(thdr.version, "9P2000", 6) != 0)
238 		return Eversion;
239 	rhdr.version = "9P2000";
240 	return 0;
241 }
242 
243 char*
244 rauth(Fid*)
245 {
246 	return "ramfs: no authentication required";
247 }
248 
249 char*
250 rflush(Fid *f)
251 {
252 	USED(f);
253 	return 0;
254 }
255 
256 char*
257 rattach(Fid *f)
258 {
259 	/* no authentication! */
260 	f->busy = 1;
261 	f->rclose = 0;
262 	f->ram = &ram[0];
263 	rhdr.qid = f->ram->qid;
264 	if(thdr.uname[0])
265 		f->user = estrdup(thdr.uname);
266 	else
267 		f->user = "none";
268 	if(strcmp(user, "none") == 0)
269 		user = f->user;
270 	return 0;
271 }
272 
273 char*
274 clone(Fid *f, Fid **nf)
275 {
276 	if(f->open)
277 		return Eisopen;
278 	if(f->ram->busy == 0)
279 		return Enotexist;
280 	*nf = newfid(thdr.newfid);
281 	(*nf)->busy = 1;
282 	(*nf)->open = 0;
283 	(*nf)->rclose = 0;
284 	(*nf)->ram = f->ram;
285 	(*nf)->user = f->user;	/* no ref count; the leakage is minor */
286 	return 0;
287 }
288 
289 char*
290 rwalk(Fid *f)
291 {
292 	Ram *r, *fram;
293 	char *name;
294 	Ram *parent;
295 	Fid *nf;
296 	char *err;
297 	ulong t;
298 	int i;
299 
300 	err = nil;
301 	nf = nil;
302 	rhdr.nwqid = 0;
303 	if(rhdr.newfid != rhdr.fid){
304 		err = clone(f, &nf);
305 		if(err)
306 			return err;
307 		f = nf;	/* walk the new fid */
308 	}
309 	fram = f->ram;
310 	if(thdr.nwname > 0){
311 		t = time(0);
312 		for(i=0; i<thdr.nwname && i<MAXWELEM; i++){
313 			if((fram->qid.type & QTDIR) == 0){
314 				err = Enotdir;
315  				break;
316 			}
317 			if(fram->busy == 0){
318 				err = Enotexist;
319 				break;
320 			}
321 			fram->atime = t;
322 			name = thdr.wname[i];
323 			if(strcmp(name, ".") == 0){
324     Found:
325 				rhdr.nwqid++;
326 				rhdr.wqid[i] = fram->qid;
327 				continue;
328 			}
329 			parent = &ram[fram->parent];
330 			if(!perm(f, parent, Pexec)){
331 				err = Eperm;
332 				break;
333 			}
334 			if(strcmp(name, "..") == 0){
335 				fram = parent;
336 				goto Found;
337 			}
338 			for(r=ram; r < &ram[nram]; r++)
339 				if(r->busy && r->parent==fram-ram && strcmp(name, r->name)==0){
340 					fram = r;
341 					goto Found;
342 				}
343 			break;
344 		}
345 		if(i==0 && err == nil)
346 			err = Enotexist;
347 	}
348 	if(nf != nil && (err!=nil || rhdr.nwqid<thdr.nwname)){
349 		/* clunk the new fid, which is the one we walked */
350 		f->busy = 0;
351 		f->ram = nil;
352 	}
353 	if(rhdr.nwqid == thdr.nwname)	/* update the fid after a successful walk */
354 		f->ram = fram;
355 	return err;
356 }
357 
358 char *
359 ropen(Fid *f)
360 {
361 	Ram *r;
362 	int mode, trunc;
363 
364 	if(f->open)
365 		return Eisopen;
366 	r = f->ram;
367 	if(r->busy == 0)
368 		return Enotexist;
369 	if(r->perm & DMEXCL)
370 		if(r->open)
371 			return Excl;
372 	mode = thdr.mode;
373 	if(r->qid.type & QTDIR){
374 		if(mode != OREAD)
375 			return Eperm;
376 		rhdr.qid = r->qid;
377 		return 0;
378 	}
379 	if(mode & ORCLOSE){
380 		/* can't remove root; must be able to write parent */
381 		if(r->qid.path==0 || !perm(f, &ram[r->parent], Pwrite))
382 			return Eperm;
383 		f->rclose = 1;
384 	}
385 	trunc = mode & OTRUNC;
386 	mode &= OPERM;
387 	if(mode==OWRITE || mode==ORDWR || trunc)
388 		if(!perm(f, r, Pwrite))
389 			return Eperm;
390 	if(mode==OREAD || mode==ORDWR)
391 		if(!perm(f, r, Pread))
392 			return Eperm;
393 	if(mode==OEXEC)
394 		if(!perm(f, r, Pexec))
395 			return Eperm;
396 	if(trunc && (r->perm&DMAPPEND)==0){
397 		r->ndata = 0;
398 		if(r->data)
399 			free(r->data);
400 		r->data = 0;
401 		r->qid.vers++;
402 	}
403 	rhdr.qid = r->qid;
404 	rhdr.iounit = messagesize-IOHDRSZ;
405 	f->open = 1;
406 	r->open++;
407 	return 0;
408 }
409 
410 char *
411 rcreate(Fid *f)
412 {
413 	Ram *r;
414 	char *name;
415 	long parent, prm;
416 
417 	if(f->open)
418 		return Eisopen;
419 	if(f->ram->busy == 0)
420 		return Enotexist;
421 	parent = f->ram - ram;
422 	if((f->ram->qid.type&QTDIR) == 0)
423 		return Enotdir;
424 	/* must be able to write parent */
425 	if(!perm(f, f->ram, Pwrite))
426 		return Eperm;
427 	prm = thdr.perm;
428 	name = thdr.name;
429 	if(strcmp(name, ".")==0 || strcmp(name, "..")==0)
430 		return Ename;
431 	for(r=ram; r<&ram[nram]; r++)
432 		if(r->busy && parent==r->parent)
433 		if(strcmp((char*)name, r->name)==0)
434 			return Einuse;
435 	for(r=ram; r->busy; r++)
436 		if(r == &ram[Nram-1])
437 			return "no free ram resources";
438 	r->busy = 1;
439 	r->qid.path = ++path;
440 	r->qid.vers = 0;
441 	if(prm & DMDIR)
442 		r->qid.type |= QTDIR;
443 	r->parent = parent;
444 	free(r->name);
445 	r->name = estrdup(name);
446 	r->user = f->user;
447 	r->group = f->ram->group;
448 	r->muid = f->ram->muid;
449 	if(prm & DMDIR)
450 		prm = (prm&~0777) | (f->ram->perm&prm&0777);
451 	else
452 		prm = (prm&(~0777|0111)) | (f->ram->perm&prm&0666);
453 	r->perm = prm;
454 	r->ndata = 0;
455 	if(r-ram >= nram)
456 		nram = r - ram + 1;
457 	r->atime = time(0);
458 	r->mtime = r->atime;
459 	f->ram->mtime = r->atime;
460 	f->ram = r;
461 	rhdr.qid = r->qid;
462 	rhdr.iounit = messagesize-IOHDRSZ;
463 	f->open = 1;
464 	if(thdr.mode & ORCLOSE)
465 		f->rclose = 1;
466 	r->open++;
467 	return 0;
468 }
469 
470 char*
471 rread(Fid *f)
472 {
473 	Ram *r;
474 	uchar *buf;
475 	long off;
476 	int n, m, cnt;
477 
478 	if(f->ram->busy == 0)
479 		return Enotexist;
480 	n = 0;
481 	rhdr.count = 0;
482 	off = thdr.offset;
483 	buf = rdata;
484 	cnt = thdr.count;
485 	if(cnt > messagesize)	/* shouldn't happen, anyway */
486 		cnt = messagesize;
487 	if(f->ram->qid.type & QTDIR){
488 		for(r=ram+1; off > 0; r++){
489 			if(r->busy && r->parent==f->ram-ram)
490 				off -= ramstat(r, statbuf, sizeof statbuf);
491 			if(r == &ram[nram-1])
492 				return 0;
493 		}
494 		for(; r<&ram[nram] && n < cnt; r++){
495 			if(!r->busy || r->parent!=f->ram-ram)
496 				continue;
497 			m = ramstat(r, buf+n, cnt-n);
498 			if(m == 0)
499 				break;
500 			n += m;
501 		}
502 		rhdr.data = (char*)rdata;
503 		rhdr.count = n;
504 		return 0;
505 	}
506 	r = f->ram;
507 	if(off >= r->ndata)
508 		return 0;
509 	r->atime = time(0);
510 	n = cnt;
511 	if(off+n > r->ndata)
512 		n = r->ndata - off;
513 	rhdr.data = r->data+off;
514 	rhdr.count = n;
515 	return 0;
516 }
517 
518 char*
519 rwrite(Fid *f)
520 {
521 	Ram *r;
522 	ulong off;
523 	int cnt;
524 
525 	r = f->ram;
526 	if(r->busy == 0)
527 		return Enotexist;
528 	off = thdr.offset;
529 	if(r->perm & DMAPPEND)
530 		off = r->ndata;
531 	cnt = thdr.count;
532 	if(r->qid.type & QTDIR)
533 		return Eisdir;
534 	if(off+cnt >= Maxsize)		/* sanity check */
535 		return "write too big";
536 	if(off+cnt > r->ndata)
537 		r->data = erealloc(r->data, off+cnt);
538 	if(off > r->ndata)
539 		memset(r->data+r->ndata, 0, off-r->ndata);
540 	if(off+cnt > r->ndata)
541 		r->ndata = off+cnt;
542 	memmove(r->data+off, thdr.data, cnt);
543 	r->qid.vers++;
544 	r->mtime = time(0);
545 	rhdr.count = cnt;
546 	return 0;
547 }
548 
549 void
550 realremove(Ram *r)
551 {
552 	r->ndata = 0;
553 	if(r->data)
554 		free(r->data);
555 	r->data = 0;
556 	r->parent = 0;
557 	memset(&r->qid, 0, sizeof r->qid);
558 	free(r->name);
559 	r->name = nil;
560 	r->busy = 0;
561 }
562 
563 char *
564 rclunk(Fid *f)
565 {
566 	if(f->open)
567 		f->ram->open--;
568 	if(f->rclose)
569 		realremove(f->ram);
570 	f->busy = 0;
571 	f->open = 0;
572 	f->ram = 0;
573 	return 0;
574 }
575 
576 char *
577 rremove(Fid *f)
578 {
579 	Ram *r;
580 
581 	if(f->open)
582 		f->ram->open--;
583 	f->busy = 0;
584 	f->open = 0;
585 	r = f->ram;
586 	f->ram = 0;
587 	if(r->qid.path == 0 || !perm(f, &ram[r->parent], Pwrite))
588 		return Eperm;
589 	ram[r->parent].mtime = time(0);
590 	realremove(r);
591 	return 0;
592 }
593 
594 char *
595 rstat(Fid *f)
596 {
597 	if(f->ram->busy == 0)
598 		return Enotexist;
599 	rhdr.nstat = ramstat(f->ram, statbuf, sizeof statbuf);
600 	rhdr.stat = statbuf;
601 	return 0;
602 }
603 
604 char *
605 rwstat(Fid *f)
606 {
607 	Ram *r, *s;
608 	Dir dir;
609 
610 	if(f->ram->busy == 0)
611 		return Enotexist;
612 	convM2D(thdr.stat, thdr.nstat, &dir, (char*)statbuf);
613 	r = f->ram;
614 
615 	/*
616 	 * To change length, must have write permission on file.
617 	 */
618 	if(dir.length!=~0 && dir.length!=r->ndata){
619 	 	if(!perm(f, r, Pwrite))
620 			return Eperm;
621 	}
622 
623 	/*
624 	 * To change name, must have write permission in parent
625 	 * and name must be unique.
626 	 */
627 	if(dir.name[0]!='\0' && strcmp(dir.name, r->name)!=0){
628 	 	if(!perm(f, &ram[r->parent], Pwrite))
629 			return Eperm;
630 		for(s=ram; s<&ram[nram]; s++)
631 			if(s->busy && s->parent==r->parent)
632 			if(strcmp(dir.name, s->name)==0)
633 				return Eexist;
634 	}
635 
636 	/*
637 	 * To change mode, must be owner or group leader.
638 	 * Because of lack of users file, leader=>group itself.
639 	 */
640 	if(dir.mode!=~0 && r->perm!=dir.mode){
641 		if(strcmp(f->user, r->user) != 0)
642 		if(strcmp(f->user, r->group) != 0)
643 			return Enotowner;
644 	}
645 
646 	/*
647 	 * To change group, must be owner and member of new group,
648 	 * or leader of current group and leader of new group.
649 	 * Second case cannot happen, but we check anyway.
650 	 */
651 	if(dir.gid[0]!='\0' && strcmp(r->group, dir.gid)!=0){
652 		if(strcmp(f->user, r->user) == 0)
653 		if(strcmp(f->user, dir.gid) == 0)
654 			goto ok;
655 		if(strcmp(f->user, r->group) == 0)
656 		if(strcmp(f->user, dir.gid) == 0)
657 			goto ok;
658 		return Enotowner;
659 		ok:;
660 	}
661 
662 	/* all ok; do it */
663 	if(dir.mode != ~0){
664 		dir.mode &= ~DMDIR;	/* cannot change dir bit */
665 		dir.mode |= r->perm&DMDIR;
666 		r->perm = dir.mode;
667 	}
668 	if(dir.name[0] != '\0'){
669 		free(r->name);
670 		r->name = estrdup(dir.name);
671 	}
672 	if(dir.gid[0] != '\0')
673 		r->group = estrdup(dir.gid);
674 	if(dir.length!=~0 && dir.length!=r->ndata){
675 		r->data = erealloc(r->data, dir.length);
676 		if(r->ndata < dir.length)
677 			memset(r->data+r->ndata, 0, dir.length-r->ndata);
678 		r->ndata = dir.length;
679 	}
680 	ram[r->parent].mtime = time(0);
681 	return 0;
682 }
683 
684 uint
685 ramstat(Ram *r, uchar *buf, uint nbuf)
686 {
687 	int n;
688 	Dir dir;
689 
690 	dir.name = r->name;
691 	dir.qid = r->qid;
692 	dir.mode = r->perm;
693 	dir.length = r->ndata;
694 	dir.uid = r->user;
695 	dir.gid = r->group;
696 	dir.muid = r->muid;
697 	dir.atime = r->atime;
698 	dir.mtime = r->mtime;
699 	n = convD2M(&dir, buf, nbuf);
700 	if(n > 2)
701 		return n;
702 	return 0;
703 }
704 
705 Fid *
706 newfid(int fid)
707 {
708 	Fid *f, *ff;
709 
710 	ff = 0;
711 	for(f = fids; f; f = f->next)
712 		if(f->fid == fid)
713 			return f;
714 		else if(!ff && !f->busy)
715 			ff = f;
716 	if(ff){
717 		ff->fid = fid;
718 		return ff;
719 	}
720 	f = emalloc(sizeof *f);
721 	f->ram = nil;
722 	f->fid = fid;
723 	f->next = fids;
724 	fids = f;
725 	return f;
726 }
727 
728 void
729 io(void)
730 {
731 	char *err, buf[20];
732 	int n, pid, ctl;
733 
734 	pid = getpid();
735 	if(private){
736 		snprint(buf, sizeof buf, "/proc/%d/ctl", pid);
737 		ctl = open(buf, OWRITE);
738 		if(ctl < 0){
739 			fprint(2, "can't protect ramfs\n");
740 		}else{
741 			fprint(ctl, "noswap\n");
742 			fprint(ctl, "private\n");
743 			close(ctl);
744 		}
745 	}
746 
747 	for(;;){
748 		/*
749 		 * reading from a pipe or a network device
750 		 * will give an error after a few eof reads.
751 		 * however, we cannot tell the difference
752 		 * between a zero-length read and an interrupt
753 		 * on the processes writing to us,
754 		 * so we wait for the error.
755 		 */
756 		n = read9pmsg(mfd[0], mdata, messagesize);
757 		if(n < 0)
758 			error("mount read");
759 		if(n == 0)
760 			continue;
761 		if(convM2S(mdata, n, &thdr) == 0)
762 			continue;
763 
764 		if(debug)
765 			fprint(2, "ramfs %d:<-%F\n", pid, &thdr);
766 
767 		if(!fcalls[thdr.type])
768 			err = "bad fcall type";
769 		else
770 			err = (*fcalls[thdr.type])(newfid(thdr.fid));
771 		if(err){
772 			rhdr.type = Rerror;
773 			rhdr.ename = err;
774 		}else{
775 			rhdr.type = thdr.type + 1;
776 			rhdr.fid = thdr.fid;
777 		}
778 		rhdr.tag = thdr.tag;
779 		if(debug)
780 			fprint(2, "ramfs %d:->%F\n", pid, &rhdr);/**/
781 		n = convS2M(&rhdr, mdata, messagesize);
782 		if(n == 0)
783 			error("convS2M error on write");
784 		if(write(mfd[1], mdata, n) != n)
785 			error("mount write");
786 	}
787 }
788 
789 int
790 perm(Fid *f, Ram *r, int p)
791 {
792 	if((p*Pother) & r->perm)
793 		return 1;
794 	if(strcmp(f->user, r->group)==0 && ((p*Pgroup) & r->perm))
795 		return 1;
796 	if(strcmp(f->user, r->user)==0 && ((p*Powner) & r->perm))
797 		return 1;
798 	return 0;
799 }
800 
801 void
802 error(char *s)
803 {
804 	fprint(2, "%s: %s: %r\n", argv0, s);
805 	exits(s);
806 }
807 
808 void *
809 emalloc(ulong n)
810 {
811 	void *p;
812 
813 	p = malloc(n);
814 	if(!p)
815 		error("out of memory");
816 	memset(p, 0, n);
817 	return p;
818 }
819 
820 void *
821 erealloc(void *p, ulong n)
822 {
823 	p = realloc(p, n);
824 	if(!p)
825 		error("out of memory");
826 	return p;
827 }
828 
829 char *
830 estrdup(char *q)
831 {
832 	char *p;
833 	int n;
834 
835 	n = strlen(q)+1;
836 	p = malloc(n);
837 	if(!p)
838 		error("out of memory");
839 	memmove(p, q, n);
840 	return p;
841 }
842 
843 void
844 usage(void)
845 {
846 	fprint(2, "usage: %s [-is] [-m mountpoint]\n", argv0);
847 	exits("usage");
848 }
849