xref: /inferno-os/appl/cmd/memfs.b (revision 3f1f06c5d12b24c4061e5123acabf72348ff45a2)
1implement MemFS;
2
3include "sys.m";
4	sys: Sys;
5	OTRUNC, ORCLOSE, OREAD, OWRITE: import Sys;
6include "styx.m";
7	styx: Styx;
8	Tmsg, Rmsg: import styx;
9include "styxlib.m";
10	styxlib: Styxlib;
11	Styxserver: import styxlib;
12include "draw.m";
13include "arg.m";
14
15MemFS: module {
16	init: fn(ctxt: ref Draw->Context, args: list of string);
17};
18
19
20blksz : con 512;
21Efull : con "filesystem full";
22
23Memfile : adt {
24	name : string;
25	owner : string;
26	qid : Sys->Qid;
27	perm : int;
28	atime : int;
29	mtime : int;
30	nopen : int;
31	data : array of array of byte;			# allocated in blks, no holes
32	length : int;
33	parent : cyclic ref Memfile;	# Dir entry linkage
34	kids : cyclic ref Memfile;
35	prev : cyclic ref Memfile;
36	next : cyclic ref Memfile;
37	hashnext : cyclic ref Memfile;	# Qid hash linkage
38};
39
40Qidhash : adt {
41	buckets : array of ref Memfile;
42	nextqid : int;
43	new : fn () : ref Qidhash;
44	add : fn (h : self ref Qidhash, mf : ref Memfile);
45	remove : fn (h : self ref Qidhash, mf : ref Memfile);
46	lookup : fn (h : self ref Qidhash, qid : Sys->Qid) : ref Memfile;
47};
48
49timefd: ref Sys->FD;
50
51init(nil: ref Draw->Context, argv: list of string)
52{
53	sys = load Sys Sys->PATH;
54	styx = checkload(load Styx Styx->PATH, Styx->PATH);
55	styxlib = checkload(load Styxlib Styxlib->PATH, Styxlib->PATH);
56	arg := checkload(load Arg Arg->PATH, Arg->PATH);
57
58	amode := Sys->MREPL;
59	maxsz := 16r7fffffff;
60	srv := 0;
61	mntpt := "/tmp";
62
63	arg->init(argv);
64	arg->setusage("memfs [-s] [-rab] [-m size] [mountpoint]");
65	while((opt := arg->opt()) != 0) {
66		case opt{
67		's' =>
68			srv = 1;
69		'r' =>
70			amode = Sys->MREPL;
71		'a' =>
72			amode = Sys->MAFTER;
73		'b' =>
74			amode = Sys->MBEFORE;
75		'm' =>
76			maxsz = int arg->earg();
77		* =>
78			arg->usage();
79		}
80	}
81	argv = arg->argv();
82	arg = nil;
83	if (argv != nil)
84		mntpt = hd argv;
85
86	srvfd: ref Sys->FD;
87	mntfd: ref Sys->FD;
88	if (srv)
89		srvfd = sys->fildes(0);
90	else {
91		p := array [2] of ref Sys->FD;
92		if (sys->pipe(p) == -1)
93			error(sys->sprint("cannot create pipe: %r"));
94		mntfd = p[0];
95		srvfd = p[1];
96	}
97	styx->init();
98	styxlib->init(styx);
99	timefd = sys->open("/dev/time", sys->OREAD);
100
101	(tc, styxsrv) := Styxserver.new(srvfd);
102	if (srv)
103		memfs(maxsz, tc, styxsrv, nil);
104	else {
105		sync := chan of int;
106		spawn memfs(maxsz, tc, styxsrv, sync);
107		<-sync;
108		if (sys->mount(mntfd, nil, mntpt, amode | Sys->MCREATE, nil) == -1)
109			error(sys->sprint("failed to mount onto %s: %r", mntpt));
110	}
111}
112
113checkload[T](x: T, p: string): T
114{
115	if(x == nil)
116		error(sys->sprint("cannot load %s: %r", p));
117	return x;
118}
119
120stderr(): ref Sys->FD
121{
122	return sys->fildes(2);
123}
124
125error(e: string)
126{
127	sys->fprint(stderr(), "memfs: %s\n", e);
128	raise "fail:error";
129}
130
131freeblks: int;
132
133memfs(maxsz : int, tc : chan of ref Tmsg, srv : ref Styxserver, sync: chan of int)
134{
135	sys->pctl(Sys->NEWNS, nil);
136	if (sync != nil)
137		sync <-= 1;
138	freeblks = (maxsz / blksz);
139	qhash := Qidhash.new();
140
141	# init root
142	root := newmf(qhash, nil, "memfs", srv.uname, 8r755 | Sys->DMDIR);
143	root.parent = root;
144
145	while((tmsg := <-tc) != nil) {
146#		sys->print("%s\n", tmsg.text());
147	Msg:
148		pick tm := tmsg {
149		Readerror =>
150			break;
151		Version =>
152			srv.devversion(tm);
153		Auth =>
154			srv.devauth(tm);
155		Flush =>
156			srv.reply(ref Rmsg.Flush(tm.tag));
157		Walk =>
158			(err, c, mf) := fidtomf(srv, qhash, tm.fid);
159			if (err != "") {
160				srv.reply(ref Rmsg.Error(tm.tag, err));
161				continue;
162			}
163			nc: ref styxlib->Chan;
164			if (tm.newfid != tm.fid) {
165				nc = srv.clone(c, tm.newfid);
166				if (nc == nil) {
167					srv.reply(ref Rmsg.Error(tm.tag, "fid in use"));
168					continue;
169				}
170				c = nc;
171			}
172			qids: array of Sys->Qid;
173			if (len tm.names > 0) {
174				oqid := c.qid;
175				opath := c.path;
176				qids = array[len tm.names] of Sys->Qid;
177				wmf := mf;
178				for (i := 0; i < len tm.names; i++) {
179					wmf = dirlookup(wmf, tm.names[i]);
180					if (wmf == nil) {
181						if (nc == nil) {
182							c.qid = oqid;
183							c.path = opath;
184						} else
185							srv.chanfree(nc);
186						if (i == 0)
187							srv.reply(ref Rmsg.Error(tm.tag, Styxlib->Enotfound));
188						else
189							srv.reply(ref Rmsg.Walk(tm.tag, qids[0:i]));
190						break Msg;
191					}
192					c.qid = wmf.qid;
193					qids[i] = wmf.qid;
194				}
195			}
196			srv.reply(ref Rmsg.Walk(tm.tag, qids));
197		Open =>
198			(err, c, mf) := fidtomf(srv, qhash, tm.fid);
199			if (err == "" && c.open)
200				err = Styxlib->Eopen;
201			if (err == "" && !modeok(tm.mode, mf.perm, c.uname, mf.owner))
202				err = Styxlib->Eperm;
203			if (err == "" && (mf.perm & Sys->DMDIR) && (tm.mode & (OTRUNC|OWRITE|ORCLOSE)))
204				err = Styxlib->Eperm;
205			if (err == "" && (tm.mode & ORCLOSE)) {
206				p := mf.parent;
207				if (p == nil || !modeok(OWRITE, p.perm, c.uname, p.owner))
208					err = Styxlib->Eperm;
209			}
210
211			if (err != "") {
212				srv.reply(ref Rmsg.Error(tm.tag, err));
213				continue;
214			}
215
216			c.open = 1;
217			c.mode = tm.mode;
218			c.qid.vers = mf.qid.vers;
219			mf.nopen++;
220			if ((tm.mode & OTRUNC) && !(mf.perm & Sys->DMAPPEND)) {
221				# OTRUNC cannot be set for a directory
222				# always at least one blk so don't need to check fs limit
223				freeblks += (len mf.data);
224				mf.data = nil;
225				freeblks--;
226				mf.data = array[1] of {* => array [blksz] of byte};
227				mf.length = 0;
228				mf.mtime = now();
229			}
230			srv.reply(ref Rmsg.Open(tm.tag, mf.qid, Styx->MAXFDATA));
231		Create =>
232			(err, c, parent) := fidtomf(srv, qhash, tm.fid);
233			if (err == "" && c.open)
234				err = Styxlib->Eopen;
235			if (err == "" && !(parent.qid.qtype & Sys->QTDIR))
236				err = Styxlib->Enotdir;
237			if (err == "" && !modeok(OWRITE, parent.perm, c.uname, parent.owner))
238				err = Styxlib->Eperm;
239			if (err == "" && (tm.perm & Sys->DMDIR) && (tm.mode & (OTRUNC|OWRITE|ORCLOSE)))
240				err = Styxlib->Eperm;
241			if (err == "" && dirlookup(parent, tm.name) != nil)
242				err = Styxlib->Eexists;
243
244			if (err != "") {
245				srv.reply(ref Rmsg.Error(tm.tag, err));
246				continue;
247			}
248
249			isdir := tm.perm & Sys->DMDIR;
250			if (!isdir && freeblks <= 0) {
251				srv.reply(ref Rmsg.Error(tm.tag, Efull));
252				continue;
253			}
254
255			# modify perms as per Styx specification...
256			perm : int;
257			if (isdir)
258				perm = (tm.perm&~8r777) | (parent.perm&tm.perm&8r777);
259			else
260				perm = (tm.perm&(~8r777|8r111)) | (parent.perm&tm.perm& 8r666);
261
262			nmf := newmf(qhash, parent, tm.name, c.uname, perm);
263			if (!isdir) {
264				freeblks--;
265				nmf.data = array[1] of {* => array [blksz] of byte};
266			}
267
268			# link in the new MemFile
269			nmf.next = parent.kids;
270			if (parent.kids != nil)
271				parent.kids.prev = nmf;
272			parent.kids = nmf;
273
274			c.open = 1;
275			c.mode = tm.mode;
276			c.qid = nmf.qid;
277			nmf.nopen = 1;
278			srv.reply(ref Rmsg.Create(tm.tag, nmf.qid, Styx->MAXFDATA));
279		Read =>
280			(err, c, mf) := fidtomf(srv, qhash, tm.fid);
281			if (err == "" && !c.open)
282				err = Styxlib->Ebadfid;
283
284			if (err != "") {
285				srv.reply(ref Rmsg.Error(tm.tag, err));
286				continue;
287			}
288			data: array of byte = nil;
289			if (mf.perm & Sys->DMDIR)
290				data = dirdata(mf, int tm.offset, tm.count);
291			else
292				data = filedata(mf, int tm.offset, tm.count);
293			mf.atime = now();
294			srv.reply(ref Rmsg.Read(tm.tag, data));
295		Write =>
296			(err, c, mf) := fidtomf(srv, qhash, tm.fid);
297			if (c != nil && !c.open)
298				err = Styxlib->Ebadfid;
299			if (err == nil && (mf.perm & Sys->DMDIR))
300				err = Styxlib->Eperm;
301			if (err == nil)
302				err = writefile(mf, int tm.offset, tm.data);
303			if (err != nil) {
304				srv.reply(ref Rmsg.Error(tm.tag, err));
305				continue;
306			}
307			srv.reply(ref Rmsg.Write(tm.tag, len tm.data));
308		Clunk =>
309			(err, c, mf) := fidtomf(srv, qhash, tm.fid);
310			if (c != nil)
311				srv.chanfree(c);
312			if (err != nil) {
313				srv.reply(ref Rmsg.Error(tm.tag, err));
314				continue;
315			}
316			if (c.open) {
317				if (c.mode & ORCLOSE)
318					unlink(mf);
319				mf.nopen--;
320				freeblks += delfile(qhash, mf);
321			}
322			srv.reply(ref Rmsg.Clunk(tm.tag));
323		Stat =>
324			(err, nil, mf) := fidtomf(srv, qhash, tm.fid);
325			if (err != nil) {
326				srv.reply(ref Rmsg.Error(tm.tag, err));
327				continue;
328			}
329			srv.reply(ref Rmsg.Stat(tm.tag, fileinfo(mf)));
330		Remove =>
331			(err, c, mf) := fidtomf(srv, qhash, tm.fid);
332			if (err != nil) {
333				srv.reply(ref Rmsg.Error(tm.tag, err));
334				continue;
335			}
336			srv.chanfree(c);
337			parent := mf.parent;
338			if (!modeok(OWRITE, parent.perm, c.uname, parent.owner))
339				err = Styxlib->Eperm;
340			if (err == "" && (mf.perm & Sys->DMDIR) && mf.kids != nil)
341				err = "directory not empty";
342			if (err == "" && mf == root)
343				err = "root directory";
344			if (err != nil) {
345				srv.reply(ref Rmsg.Error(tm.tag, err));
346				continue;
347			}
348
349			unlink(mf);
350			if (c.open)
351				mf.nopen--;
352			freeblks += delfile(qhash, mf);
353			srv.reply(ref Rmsg.Remove(tm.tag));
354		Wstat =>
355			(err, c, mf) := fidtomf(srv, qhash, tm.fid);
356			stat := tm.stat;
357
358			if (err == nil && stat.name != mf.name) {
359				parent := mf.parent;
360				if (!modeok(OWRITE, parent.perm, c.uname, parent.owner))
361					err = Styxlib->Eperm;
362				else if (dirlookup(parent, stat.name) != nil)
363					err = Styxlib->Eexists;
364			}
365			if (err == nil && (stat.mode != mf.perm || stat.mtime != mf.mtime)) {
366				if (c.uname != mf.owner)
367					err = Styxlib->Eperm;
368			}
369			if (err != nil) {
370				srv.reply(ref Rmsg.Error(tm.tag, err));
371				continue;
372			}
373			isdir := mf.perm & Sys->DMDIR;
374			if(stat.name != nil)
375				mf.name = stat.name;
376			if(stat.mode != ~0)
377				mf.perm = stat.mode | isdir;
378			if(stat.mtime != ~0)
379				mf.mtime = stat.mtime;
380 			if(stat.uid != nil)
381 				mf.owner = stat.uid;
382			t := now();
383			mf.atime = t;
384			mf.parent.mtime = t;
385			# not supporting group id at the moment
386			srv.reply(ref Rmsg.Wstat(tm.tag));
387		Attach =>
388			c := srv.newchan(tm.fid);
389			if (c == nil) {
390				srv.reply(ref Rmsg.Error(tm.tag, Styxlib->Einuse));
391				continue;
392			}
393			c.uname = tm.uname;
394			c.qid = root.qid;
395			srv.reply(ref Rmsg.Attach(tm.tag, c.qid));
396		}
397	}
398}
399
400writefile(mf: ref Memfile, offset: int, data: array of byte): string
401{
402	if(mf.perm & Sys->DMAPPEND)
403		offset = mf.length;
404	startblk := offset/blksz;
405	nblks := ((len data + offset) - (startblk * blksz))/blksz;
406	lastblk := startblk + nblks;
407	need := lastblk + 1 - len mf.data;
408	if (need > 0) {
409		if (need > freeblks)
410			return Efull;
411		mf.data = (array [lastblk+1] of array of byte)[:] = mf.data;
412		freeblks -= need;
413	}
414	mf.length = max(mf.length, offset + len data);
415
416	# handle (possibly incomplete first block) separately
417	offset %= blksz;
418	end := min(blksz-offset, len data);
419	if (mf.data[startblk] == nil)
420		mf.data[startblk] = array [blksz] of byte;
421	mf.data[startblk++][offset:] = data[:end];
422
423	ix := blksz - offset;
424	while (ix < len data) {
425		if (mf.data[startblk] == nil)
426			mf.data[startblk] = array [blksz] of byte;
427		end = min(ix+blksz,len data);
428		mf.data[startblk++][:] = data[ix:end];
429		ix += blksz;
430	}
431	mf.mtime = now();
432	return nil;
433}
434
435filedata(mf: ref Memfile, offset, n: int): array of byte
436{
437	if (offset +n > mf.length)
438		n = mf.length - offset;
439	if (n == 0)
440		return nil;
441
442	data := array [n] of byte;
443	startblk := offset/blksz;
444	offset %= blksz;
445	rn := min(blksz - offset, n);
446	data[:] = mf.data[startblk++][offset:offset+rn];
447	ix := blksz - offset;
448	while (ix < n) {
449		rn = blksz;
450		if (ix+rn > n)
451			rn = n - ix;
452		data[ix:] = mf.data[startblk++][:rn];
453		ix += blksz;
454	}
455	return data;
456}
457
458QHSIZE: con 256;
459QHMASK: con QHSIZE-1;
460
461Qidhash.new() : ref Qidhash
462{
463	qh := ref Qidhash;
464	qh.buckets = array [QHSIZE] of ref Memfile;
465	qh.nextqid = 0;
466	return qh;
467}
468
469Qidhash.add(h : self ref Qidhash, mf : ref Memfile)
470{
471	path := h.nextqid++;
472	mf.qid = Sys->Qid(big path, 0, Sys->QTFILE);
473	bix := path & QHMASK;
474	mf.hashnext = h.buckets[bix];
475	h.buckets[bix] = mf;
476}
477
478Qidhash.remove(h : self ref Qidhash, mf : ref Memfile)
479{
480
481	bix := int mf.qid.path & QHMASK;
482	prev : ref Memfile;
483	for (cur := h.buckets[bix]; cur != nil; cur = cur.hashnext) {
484		if (cur == mf)
485			break;
486		prev = cur;
487	}
488	if (cur != nil) {
489		if (prev != nil)
490			prev.hashnext = cur.hashnext;
491		else
492			h.buckets[bix] = cur.hashnext;
493		cur.hashnext = nil;
494	}
495}
496
497Qidhash.lookup(h : self ref Qidhash, qid : Sys->Qid) : ref Memfile
498{
499	bix := int qid.path & QHMASK;
500	for (mf := h.buckets[bix]; mf != nil; mf = mf.hashnext)
501		if (mf.qid.path == qid.path)
502			break;
503	return mf;
504}
505
506newmf(qh : ref Qidhash, parent : ref Memfile, name, owner : string, perm : int) : ref Memfile
507{
508	# qid gets set by Qidhash.add()
509	t := now();
510	mf := ref Memfile (name, owner, Sys->Qid(big 0,0,Sys->QTFILE), perm, t, t, 0, nil, 0, parent, nil, nil, nil, nil);
511	qh.add(mf);
512	if(perm & Sys->DMDIR)
513		mf.qid.qtype = Sys->QTDIR;
514	return mf;
515}
516
517fidtomf(srv : ref Styxserver, qh : ref Qidhash, fid : int) : (string, ref Styxlib->Chan, ref Memfile)
518{
519	c := srv.fidtochan(fid);
520	if (c == nil)
521		return (Styxlib->Ebadfid, nil, nil);
522	mf := qh.lookup(c.qid);
523	if (mf == nil)
524		return (Styxlib->Enotfound, c, nil);
525	return (nil, c, mf);
526}
527
528unlink(mf : ref Memfile)
529{
530	parent := mf.parent;
531	if (parent == nil)
532		return;
533	if (mf.next != nil)
534		mf.next.prev = mf.prev;
535	if (mf.prev != nil)
536		mf.prev.next = mf.next;
537	else
538		mf.parent.kids = mf.next;
539	mf.parent = nil;
540	mf.prev = nil;
541	mf.next = nil;
542}
543
544delfile(qh : ref Qidhash, mf : ref Memfile) : int
545{
546	if (mf.nopen <= 0 && mf.parent == nil && mf.kids == nil
547	&& mf.prev == nil && mf.next == nil) {
548		qh.remove(mf);
549		nblks := len mf.data;
550		mf.data = nil;
551		return nblks;
552	}
553	return 0;
554}
555
556dirlookup(dir : ref Memfile, name : string) : ref Memfile
557{
558	if (name == ".")
559		return dir;
560	if (name == "..")
561		return dir.parent;
562	for (mf := dir.kids; mf != nil; mf = mf.next) {
563		if (mf.name == name)
564			break;
565	}
566	return mf;
567}
568
569access := array[] of {8r400, 8r200, 8r600, 8r100};
570modeok(mode, perm : int, user, owner : string) : int
571{
572	if(mode >= (OTRUNC|ORCLOSE|OREAD|OWRITE))
573		return 0;
574
575	# not handling groups!
576	if (user != owner)
577		perm <<= 6;
578
579	if ((mode & OTRUNC) && !(perm & 8r200))
580		return 0;
581
582	a := access[mode &3];
583	if ((a & perm) != a)
584		return 0;
585	return 1;
586}
587
588dirdata(dir : ref Memfile, start, n : int) : array of byte
589{
590	data := array[Styx->MAXFDATA] of byte;
591	for (k := dir.kids; start > 0 && k != nil; k = k.next) {
592		a := styx->packdir(fileinfo(k));
593		start -= len a;
594	}
595	r := 0;
596	for (; r < n && k != nil; k = k.next) {
597		a := styx->packdir(fileinfo(k));
598		if(r+len a > n)
599			break;
600		data[r:] = a;
601		r += len a;
602	}
603	return data[0:r];
604}
605
606fileinfo(f : ref Memfile) : Sys->Dir
607{
608	dir := sys->zerodir;
609	dir.name = f.name;
610	dir.uid = f.owner;
611	dir.gid = "memfs";
612	dir.qid = f.qid;
613	dir.mode = f.perm;
614	dir.atime = f.atime;
615	dir.mtime = f.mtime;
616	dir.length = big f.length;
617	dir.dtype = 0;
618	dir.dev = 0;
619	return dir;
620}
621
622min(a, b : int) : int
623{
624	if (a < b)
625		return a;
626	return b;
627}
628
629max(a, b : int) : int
630{
631	if (a > b)
632		return a;
633	return b;
634}
635
636now(): int
637{
638	if (timefd == nil)
639		return 0;
640	buf := array[128] of byte;
641	sys->seek(timefd, big 0, 0);
642	n := sys->read(timefd, buf, len buf);
643	if(n < 0)
644		return 0;
645
646	t := (big string buf[0:n]) / big 1000000;
647	return int t;
648}
649