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