Blob


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