xref: /inferno-os/appl/cmd/lockfs.b (revision 62d7827bc358c000db9ff48fe61bd28ac352a884)
1implement Lockfs;
2include "sys.m";
3	sys: Sys;
4	stderr: ref Sys->FD;
5include "draw.m";
6include "styx.m";
7	styx: Styx;
8	Tmsg, Rmsg: import styx;
9include "styxlib.m";
10	styxlib: Styxlib;
11	Dirtab, Styxserver, Chan,
12	devdir,
13	Eperm, Ebadfid, Eexists, Enotdir, Enotfound, Einuse: import styxlib;
14include "arg.m";
15include "keyring.m";
16	keyring: Keyring;
17include "security.m";
18	auth: Auth;
19include "dial.m";
20	dial: Dial;
21
22Lockfs: module {
23	init: fn(nil: ref Draw->Context, argv: list of string);
24	dirgen: fn(srv: ref Styxlib->Styxserver, c: ref Styxlib->Chan,
25			tab: array of Styxlib->Dirtab, i: int): (int, Sys->Dir);
26};
27
28Elocked: con "file is locked";
29
30devgen: Dirgenmod;
31
32Openreq: adt {
33	srv: ref Styxserver;
34	tag: int;
35	omode: int;
36	c: ref Chan;
37	uproc: Uproc;
38};
39
40Lockqueue: adt {
41	h: list of ref Openreq;
42	t: list of ref Openreq;
43	put: fn(q: self ref Lockqueue, s: ref Openreq);
44	get: fn(q: self ref Lockqueue): ref Openreq;
45	peek: fn(q: self ref Lockqueue): ref Openreq;
46	flush: fn(q: self ref Lockqueue, srv: ref Styxserver, tag: int);
47};
48
49Lockfile: adt {
50	waitq: ref Lockqueue;
51	fd: ref Sys->FD;
52	readers: int;
53	writers: int;
54	d: Sys->Dir;
55};
56
57Ureq: adt {
58	fname: string;
59	pick {
60	Open =>
61		omode: int;
62	Create =>
63		omode: int;
64		perm: int;
65	Remove =>
66	Wstat =>
67		dir: Sys->Dir;
68	}
69};
70
71Uproc: type chan of (ref Ureq, chan of (ref Sys->FD, string));
72
73maxqidpath := big 1;
74locks: list of ref Lockfile;
75lockdir: string;
76authinfo: ref Keyring->Authinfo;
77timefd: ref Sys->FD;
78
79MAXCONN: con 20;
80
81verbose := 0;
82
83usage()
84{
85	sys->fprint(stderr, "usage: lockfs [-A] [-a alg]... [-p addr] dir [mountpoint]\n");
86	raise "fail:usage";
87}
88
89badmodule(p: string)
90{
91	sys->fprint(stderr, "lockfs: cannot load %s: %r\n", p);
92	raise "fail:bad module";
93}
94
95init(nil: ref Draw->Context, argv: list of string)
96{
97	sys = load Sys Sys->PATH;
98	stderr = sys->fildes(2);
99	styx = load Styx Styx->PATH;
100	if (styx == nil)
101		badmodule(Styx->PATH);
102	dial = load Dial Dial->PATH;
103	if (dial == nil)
104		badmodule(Dial->PATH);
105	styx->init();
106	styxlib = load Styxlib Styxlib->PATH;
107	if (styxlib == nil)
108		badmodule(Styxlib->PATH);
109	styxlib->init(styx);
110	devgen = load Dirgenmod "$self";
111	if (devgen == nil)
112		badmodule("self as Dirgenmod");
113	timefd = sys->open("/dev/time", sys->OREAD);
114	if (timefd == nil) {
115		sys->fprint(stderr, "lockfs: cannot open /dev/time: %r\n");
116		raise "fail:no time";
117	}
118	arg := load Arg Arg->PATH;
119	if (arg == nil)
120		badmodule(Arg->PATH);
121	arg->init(argv);
122
123	addr := "";
124	doauth := 1;
125	algs: list of string;
126	while ((opt := arg->opt()) != 0) {
127		case opt {
128		'p' =>
129			addr = arg->arg();
130		'a' =>
131			alg := arg->arg();
132			if (alg == nil)
133				usage();
134			algs = alg :: algs;
135		'A' =>
136			doauth = 0;
137		'v' =>
138			verbose = 1;
139		* =>
140			usage();
141		}
142	}
143	argv = arg->argv();
144	if (argv == nil || (addr != nil && tl argv != nil))
145		usage();
146	if (addr == nil)
147		doauth = 0;		# no authentication necessary for local mount
148	if (doauth) {
149		auth = load Auth Auth->PATH;
150		if (auth == nil)
151			badmodule(Auth->PATH);
152		if ((e := auth->init()) != nil) {
153			sys->fprint(stderr, "lockfs: cannot init auth: %s\n", e);
154			raise "fail:errors";
155		}
156		keyring = load Keyring Keyring->PATH;
157		if (keyring == nil)
158			badmodule(Keyring->PATH);
159		authinfo = keyring->readauthinfo("/usr/" + user() + "/keyring/default");
160	}
161
162	mountpoint := lockdir = hd argv;
163	if (tl argv != nil)
164		mountpoint = hd tl argv;
165	if (addr != nil) {
166		if (doauth && algs == nil)
167			algs = "none" :: nil;		# XXX is this default a bad idea?
168		srvrq := chan of (ref Sys->FD, string, Uproc);
169		srvsync := chan of (int, string);
170		spawn listener(addr, srvrq, srvsync, algs);
171		(srvpid, err) := <-srvsync;
172		srvsync = nil;
173		if (srvpid == -1) {
174			sys->fprint(stderr, "lockfs: failed to start listener: %s\n", err);
175			raise "fail:errors";
176		}
177		sync := chan of int;
178		spawn server(srvrq, sync);
179		<-sync;
180	} else {
181		rq := chan of (ref Sys->FD, string, Uproc);
182		fds := array[2] of ref Sys->FD;
183		sys->pipe(fds);
184		sync := chan of int;
185		spawn server(rq, sync);
186		<-sync;
187		rq <-= (fds[0], "lock", nil);
188		rq <-= (nil, nil, nil);
189		if (sys->mount(fds[1], nil, mountpoint, Sys->MREPL | Sys->MCREATE, nil) == -1) {
190			sys->fprint(stderr, "lockfs: cannot mount: %r\n");
191			raise "fail:cannot mount";
192		}
193	}
194}
195
196server(srvrq: chan of (ref Sys->FD, string, Uproc), sync: chan of int)
197{
198	sys->pctl(Sys->FORKNS, nil);
199	sync <-= 1;
200	down := 0;
201	nclient := 0;
202	tchans := array[MAXCONN] of chan of ref Tmsg;
203	srv := array[MAXCONN] of ref Styxserver;
204	uprocs := array[MAXCONN] of Uproc;
205	lockinit();
206Service:
207	for (;;) alt {
208	(fd, reqstr, uprocch) := <-srvrq =>
209		if (fd == nil) {
210			if (verbose && reqstr != nil)
211				sys->print("lockfs: localserver going down (reason: %s)\n", reqstr);
212			down = 1;
213		} else {
214			if (verbose)
215				sys->print("lockfs: got new connection (s == '%s')\n", reqstr);
216			for (i := 0; i < len tchans; i++)
217				if (tchans[i] == nil) {
218					(tchans[i], srv[i]) = Styxserver.new(fd);
219					if(verbose)
220						sys->print("svc started\n");
221					uprocs[i] = uprocch;
222					break;
223				}
224			if (i == len tchans) {
225				sys->fprint(stderr, "lockfs: too many clients\n");	# XXX expand arrays
226				if (uprocch != nil)
227					uprocch <-= (nil, nil);
228			} else
229				nclient++;
230		}
231	(n, gm) := <-tchans =>
232		if (handletmsg(srv[n], gm, uprocs[n]) == -1) {
233			tchans[n] = nil;
234			srv[n] = nil;
235			if (uprocs[n] != nil) {
236				uprocs[n] <-= (nil, nil);
237				uprocs[n] = nil;
238			}
239			if (nclient-- <= 1 && down)
240				break Service;
241		}
242	}
243	if (verbose)
244		sys->print("lockfs: finished\n");
245}
246
247dirgen(nil: ref Styxserver, nil: ref Styxlib->Chan,
248				nil: array of Dirtab, s: int): (int, Sys->Dir)
249{
250	d: Sys->Dir;
251	ll := locks;
252	for (i := 0; i < s && ll != nil; i++)
253		ll = tl ll;
254	if (ll == nil)
255		return (-1, d);
256	return (1, (hd ll).d);
257}
258
259handletmsg(srv:  ref Styxserver, gm: ref Tmsg, uproc: Uproc): int
260{
261{
262	if (gm == nil)
263		gm = ref Tmsg.Readerror(-1, "eof");
264	if(verbose)
265		sys->print("<- %s\n", gm.text());
266	pick m := gm {
267	Readerror =>
268		# could be more efficient...
269		for (cl := srv.chanlist(); cl != nil; cl = tl cl) {
270			c := hd cl;
271			for (ll := locks; ll != nil; ll = tl ll) {
272				if ((hd ll).d.qid.path == c.qid.path) {
273					l := hd ll;
274					l.waitq.flush(srv, -1);
275					if (c.open)
276						unlocked(l);
277					break;
278				}
279			}
280		}
281		if (m.error != "eof")
282			sys->fprint(stderr, "lockfs: read error: %s\n", m.error);
283		return -1;
284	Version =>
285		srv.devversion(m);
286	Auth =>
287		srv.devauth(m);
288	Walk =>
289		c := fid2chan(srv, m.fid);
290		qids: array of Sys->Qid;
291		cc := ref *c;
292		if (len m.names > 0) {
293			qids = array[1] of Sys->Qid;	# it's just one level
294			if ((cc.qid.qtype & Sys->QTDIR) == 0) {
295				srv.reply(ref Rmsg.Error(m.tag, Enotdir));
296				break;
297			}
298			for (ll := locks; ll != nil; ll = tl ll)
299				if ((hd ll).d.name == m.names[0])
300					break;
301			if (ll == nil) {
302				srv.reply(ref Rmsg.Error(m.tag, Enotfound));
303				break;
304			}
305			d := (hd ll).d;
306			cc.qid = d.qid;
307			cc.path = d.name;
308			qids[0] = c.qid;
309		}
310		if(m.newfid != m.fid){
311			nc := srv.clone(cc, m.newfid);
312			if(nc == nil){
313				srv.reply(ref Rmsg.Error(m.tag, Einuse));
314				break;
315			}
316		}else{
317			c.qid = cc.qid;
318			c.path = cc.path;
319		}
320		srv.reply(ref Rmsg.Walk(m.tag, qids));
321	Open =>
322		c := fid2chan(srv, m.fid);
323		if (c.qid.qtype & Sys->QTDIR) {
324			srv.reply(ref Rmsg.Open(m.tag, c.qid, Styx->MAXFDATA));
325			break;
326		}
327		for (ll := locks; ll != nil; ll = tl ll)
328			if ((hd ll).d.qid.path == c.qid.path)
329				break;
330		if (ll == nil) {
331			srv.reply(ref Rmsg.Error(m.tag, Enotfound));
332			break;
333		}
334		l := hd ll;
335		req := ref Openreq(srv, m.tag, m.mode, c, uproc);
336		if (l.fd == nil || (m.mode == Sys->OREAD && l.writers == 0)) {
337			openlockfile(l, req);
338		} else {
339			l.waitq.put(req);
340		}
341		req = nil;
342	Create =>
343		c := fid2chan(srv, m.fid);
344		if ((c.qid.qtype & Sys->QTDIR) == 0) {
345			srv.reply(ref Rmsg.Error(m.tag, Enotdir));
346			break;
347		}
348		if (m.perm & Sys->DMDIR) {
349			srv.reply(ref Rmsg.Error(m.tag, Eperm));
350			break;
351		}
352		for (ll := locks; ll != nil; ll = tl ll)
353			if ((hd ll).d.name == m.name)
354				break;
355		if (ll != nil) {
356			srv.reply(ref Rmsg.Error(m.tag, Eexists));
357			break;
358		}
359		(fd, err) := create(uproc, lockdir + "/" + m.name, m.mode, m.perm);
360		if (fd == nil) {
361			srv.reply(ref Rmsg.Error(m.tag, err));
362			break;
363		}
364		(ok, d) := sys->fstat(fd);
365		if (ok == -1) {
366			srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r")));
367			break;
368		}
369		l := ref Lockfile(ref Lockqueue, fd, 0, 0, d);
370		l.d.qid = (maxqidpath++, 0, Sys->QTFILE);
371		l.d.mtime = l.d.atime = now();
372		if (m.mode == Sys->OREAD)
373			l.readers = 1;
374		else
375			l.writers = 1;
376		locks = l :: locks;
377		c.qid.path = (hd locks).d.qid.path;
378		c.open = 1;
379		srv.reply(ref Rmsg.Create(m.tag, c.qid, Styx->MAXFDATA));
380	Read =>
381		c := fid2chan(srv, m.fid);
382		if (c.qid.qtype & Sys->QTDIR)
383			srv.devdirread(m, devgen, nil);
384		else {
385			l := qid2lock(c.qid);
386			if (l == nil)
387				srv.reply(ref Rmsg.Error(m.tag, Enotfound));
388			else {
389				d := array[m.count] of byte;
390				sys->seek(l.fd, m.offset, Sys->SEEKSTART);
391				n := sys->read(l.fd, d, m.count);
392				if (n == -1)
393					srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r")));
394				else {
395					srv.reply(ref Rmsg.Read(m.tag, d[0:n]));
396					l.d.atime = now();
397				}
398			}
399		}
400	Write =>
401		c := fid2chan(srv, m.fid);
402		if (c.qid.qtype & Sys->QTDIR) {
403			srv.reply(ref Rmsg.Error(m.tag, Eperm));
404			break;
405		}
406		l := qid2lock(c.qid);
407		if (l == nil) {
408			srv.reply(ref Rmsg.Error(m.tag, Enotfound));
409			break;
410		}
411		sys->seek(l.fd, m.offset, Sys->SEEKSTART);
412		n := sys->write(l.fd, m.data, len m.data);
413		if (n == -1)
414			srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r")));
415		else {
416			srv.reply(ref Rmsg.Write(m.tag, n));
417			nlength := m.offset + big n;
418			if (nlength > l.d.length)
419				l.d.length = nlength;
420			l.d.mtime = now();
421			l.d.qid.vers++;
422		}
423	Clunk =>
424		c := srv.devclunk(m);
425		if (c != nil && c.open && (l := qid2lock(c.qid)) != nil)
426			unlocked(l);
427	Flush =>
428		for (ll := locks; ll != nil; ll = tl ll)
429			(hd ll).waitq.flush(srv, m.tag);
430		srv.reply(ref Rmsg.Flush(m.tag));
431	Stat =>
432		srv.devstat(m, devgen, nil);
433	Remove =>
434		c := fid2chan(srv, m.fid);
435		srv.chanfree(c);
436		if (c.qid.qtype & Sys->QTDIR) {
437			srv.reply(ref Rmsg.Error(m.tag, Eperm));
438			break;
439		}
440		l := qid2lock(c.qid);
441		if (l == nil) {
442			srv.reply(ref Rmsg.Error(m.tag, Enotfound));
443			break;
444		}
445		if (l.fd != nil) {
446			srv.reply(ref Rmsg.Error(m.tag, Elocked));
447			break;
448		}
449		if ((err := remove(uproc, lockdir + "/" + l.d.name)) == nil) {
450			srv.reply(ref Rmsg.Error(m.tag, err));
451			break;
452		}
453		ll: list of ref Lockfile;
454		for (; locks != nil; locks = tl locks)
455			if (hd locks != l)
456				ll = hd locks :: ll;
457		locks = ll;
458		srv.reply(ref Rmsg.Remove(m.tag));
459	Wstat =>
460		c := fid2chan(srv, m.fid);
461		if (c.qid.qtype & Sys->QTDIR) {
462			srv.reply(ref Rmsg.Error(m.tag, Eperm));
463			break;
464		}
465		l := qid2lock(c.qid);
466		if (l == nil) {
467			srv.reply(ref Rmsg.Error(m.tag, Enotfound));
468			break;
469		}
470		if ((err := wstat(uproc, lockdir + "/" + l.d.name, m.stat)) != nil) {
471			srv.reply(ref Rmsg.Error(m.tag, err));
472			break;
473		}
474		(ok, d) := sys->stat(lockdir + "/" + m.stat.name);
475		if (ok == -1) {
476			srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r")));
477			break;
478		}
479		d.qid = l.d.qid;
480		l.d = d;
481		srv.reply(ref Rmsg.Wstat(m.tag));
482	Attach =>
483		srv.devattach(m);
484	}
485	return 0;
486}
487exception e{
488	"panic:*" =>
489		sys->fprint(stderr, "lockfs: %s\n", e);
490		srv.reply(ref Rmsg.Error(gm.tag, e[len "panic:":]));
491		return 0;
492}
493}
494
495unlocked(l: ref Lockfile)
496{
497	if (l.readers > 0)
498		l.readers--;
499	else
500		l.writers--;
501	if (l.readers > 0)
502		return;
503	l.fd = nil;
504
505	# unblock all readers at the head of the queue.
506	# XXX should we queuejump other readers?
507	while ((nreq := l.waitq.peek()) != nil && l.writers == 0) {
508		if (nreq.omode != Sys->OREAD && l.readers > 0)
509			break;
510		openlockfile(l, nreq);
511		l.waitq.get();
512	}
513}
514
515openlockfile(l: ref Lockfile, req: ref Openreq): int
516{
517	err: string;
518	(l.fd, err) = open(req.uproc, lockdir + "/" + l.d.name, req.omode);
519	if (l.fd == nil) {
520		req.srv.reply(ref Rmsg.Error(req.tag, err));
521		return -1;
522	}
523	req.c.open = 1;
524	if (req.omode & Sys->OTRUNC)
525		l.d.length = big 0;
526	req.srv.reply(ref Rmsg.Open(req.tag, l.d.qid, Styx->MAXFDATA));
527	if (req.omode == Sys->OREAD)
528		l.readers++;
529	else
530		l.writers++;
531	return 0;
532}
533
534qid2lock(q: Sys->Qid): ref Lockfile
535{
536	for (ll := locks; ll != nil; ll = tl ll)
537		if ((hd ll).d.qid.path == q.path)
538			return hd ll;
539	return nil;
540}
541
542lockinit()
543{
544	fd := sys->open(lockdir, Sys->OREAD);
545	if (fd == nil)
546		return;
547
548	lockl: list of ref Lockfile;
549	# XXX if O(n²) behaviour is a problem, use Readdir module
550	for(;;){
551		(n, e) := sys->dirread(fd);
552		if(n <= 0)
553			break;
554		for (i := 0; i < n; i++) {
555			for (l := lockl; l != nil; l = tl l)
556				if ((hd l).d.name == e[i].name)
557					break;
558			if (l == nil) {
559				e[i].qid = (maxqidpath++, 0, Sys->QTFILE);
560				lockl = ref Lockfile(ref Lockqueue, nil, 0, 0, e[i]) :: lockl;
561			}
562		}
563	}
564	# remove all directories from list
565	for (locks = nil; lockl != nil; lockl = tl lockl)
566		if (((hd lockl).d.mode & Sys->DMDIR) == 0)
567			locks = hd lockl :: locks;
568}
569
570
571fid2chan(srv: ref Styxserver, fid: int): ref Chan
572{
573	c := srv.fidtochan(fid);
574	if (c == nil)
575		raise "panic:bad fid";
576	return c;
577}
578
579Lockqueue.put(q: self ref Lockqueue, s: ref Openreq)
580{
581        q.t = s :: q.t;
582}
583
584Lockqueue.get(q: self ref Lockqueue): ref Openreq
585{
586        s: ref Openreq;
587        if(q.h == nil)
588                (q.h, q.t) = (revrqlist(q.t), nil);
589
590        if(q.h != nil)
591                (s, q.h) = (hd q.h, tl q.h);
592
593        return s;
594}
595
596Lockqueue.peek(q: self ref Lockqueue): ref Openreq
597{
598	s := q.get();
599	if (s != nil)
600		q.h = s :: q.h;
601	return s;
602}
603
604doflush(l: list of ref Openreq, srv: ref Styxserver, tag: int): list of ref Openreq
605{
606	oldl := l;
607	nl: list of ref Openreq;
608	doneone := 0;
609	while (l != nil) {
610		oreq := hd l;
611		if (oreq.srv != srv || (tag != -1 && oreq.tag != tag))
612			nl = oreq :: nl;
613		else
614			doneone = 1;
615		l = tl l;
616	}
617	if (doneone)
618		return revrqlist(nl);
619	else
620		return oldl;
621}
622
623Lockqueue.flush(q: self ref Lockqueue, srv: ref Styxserver, tag: int)
624{
625	q.h = doflush(q.h, srv, tag);
626	q.t = doflush(q.t, srv, tag);
627}
628
629# or inline
630revrqlist(ls: list of ref Openreq) : list of ref Openreq
631{
632        rs: list of ref Openreq;
633        while(ls != nil){
634                rs = hd ls :: rs;
635                ls = tl ls;
636        }
637        return rs;
638}
639
640# addr should be, e.g. tcp!*!2345
641listener(addr: string, ch: chan of (ref Sys->FD, string, Uproc),
642		sync: chan of (int, string), algs: list of string)
643{
644	addr = dial->netmkaddr(addr, "tcp", "33234");
645	c := dial->announce(addr);
646	if (c == nil) {
647		sync <-= (-1, sys->sprint("cannot anounce on %s: %r", addr));
648		return;
649	}
650	sync <-= (sys->pctl(0, nil), nil);
651	for (;;) {
652		nc := dial->listen(c);
653		if (nc == nil) {
654			ch <-= (nil, sys->sprint("listen failed: %r"), nil);
655			return;
656		}
657		dfd := sys->open(nc.dir + "/data", Sys->ORDWR);
658		if (dfd != nil) {
659			if (algs == nil)
660				ch <-= (dfd, nil, nil);
661			else
662				spawn authenticator(dfd, ch, algs);
663		}
664	}
665}
666
667# authenticate a connection, setting the user id appropriately,
668# and then act as a server, performing file operations
669# on behalf of the central process.
670authenticator(dfd: ref Sys->FD, ch: chan of (ref Sys->FD, string, Uproc), algs: list of string)
671{
672	(fd, err) := auth->server(algs, authinfo, dfd, 1);
673	if (fd == nil) {
674		if (verbose)
675			sys->fprint(stderr, "lockfs: authentication failed: %s\n", err);
676		return;
677	}
678	uproc := chan of (ref Ureq, chan of (ref Sys->FD, string));
679	ch <-= (fd, err, uproc);
680	for (;;) {
681		(req, reply) := <-uproc;
682		if (req == nil)
683			exit;
684		reply <-= doreq(req);
685	}
686}
687
688create(uproc: Uproc, file: string, omode: int, perm: int): (ref Sys->FD, string)
689{
690	return proxydoreq(uproc, ref Ureq.Create(file, omode, perm));
691}
692
693open(uproc: Uproc, file: string, omode: int): (ref Sys->FD, string)
694{
695	return proxydoreq(uproc, ref Ureq.Open(file, omode));
696}
697
698remove(uproc: Uproc, file: string): string
699{
700	return proxydoreq(uproc, ref Ureq.Remove(file)).t1;
701}
702
703wstat(uproc: Uproc, file: string, d: Sys->Dir): string
704{
705	return proxydoreq(uproc, ref Ureq.Wstat(file, d)).t1;
706}
707
708proxydoreq(uproc: Uproc, req: ref Ureq): (ref Sys->FD, string)
709{
710	if (uproc == nil)
711		return doreq(req);
712	reply := chan of (ref Sys->FD, string);
713	uproc <-= (req, reply);
714	return <-reply;
715}
716
717doreq(greq: ref Ureq): (ref Sys->FD, string)
718{
719	fd: ref Sys->FD;
720	err: string;
721	pick req := greq {
722	Open =>
723		if ((fd = sys->open(req.fname, req.omode)) == nil)
724			err = sys->sprint("%r");
725	Create =>
726		if ((fd = sys->create(req.fname, req.omode, req.perm)) == nil)
727			err = sys->sprint("%r");
728	Remove =>
729		if (sys->remove(req.fname) == -1)
730			err = sys->sprint("%r");
731	Wstat =>
732		if (sys->wstat(req.fname, req.dir) == -1)
733			err = sys->sprint("%r");
734	}
735	return (fd, err);
736}
737
738user(): string
739{
740	fd := sys->open("/dev/user", sys->OREAD);
741	if(fd == nil){
742		sys->fprint(stderr, "lockfs: can't open /dev/user: %r\n");
743		raise "fail:no user";
744	}
745
746	buf := array[Sys->NAMEMAX] of byte;
747	n := sys->read(fd, buf, len buf);
748	if(n < 0) {
749		sys->fprint(stderr, "lockfs: failed to read /dev/user: %r\n");
750		raise "fail:no user";
751	}
752
753	return string buf[0:n];
754}
755
756now(): int
757{
758	buf := array[128] of byte;
759	sys->seek(timefd, big 0, 0);
760	if ((n := sys->read(timefd, buf, len buf)) < 0)
761		return 0;
762	return int (big string buf[0:n] / big 1000000);
763}
764