xref: /inferno-os/appl/cmd/disk/kfs.b (revision f4ab6bd1adbb9bff11da6885084de2ed31f2a415)
1implement Kfs;
2
3#
4# Copyright © 1991-2003 Lucent Technologies Inc.
5# Limbo version Copyright © 2004 Vita Nuova Holdings Limited
6#
7
8#
9# TO DO:
10#	- sync proc; Bmod; process structure
11#	- swiz?
12
13include "sys.m";
14	sys: Sys;
15	Qid, Dir: import Sys;
16	DMEXCL, DMAPPEND, DMDIR: import Sys;
17	QTEXCL, QTAPPEND, QTDIR: import Sys;
18
19include "draw.m";
20
21include "styx.m";
22	styx: Styx;
23	Tmsg, Rmsg: import styx;
24	NOFID, OEXEC, ORCLOSE, OREAD, OWRITE, ORDWR, OTRUNC: import Styx;
25	IOHDRSZ: import Styx;
26
27include "daytime.m";
28	daytime: Daytime;
29	now: import daytime;
30
31include "arg.m";
32
33Kfs: module
34{
35	init:	fn(nil: ref Draw->Context, nil: list of string);
36};
37
38MAXBUFSIZE:	con 16*1024;
39
40#
41#  fundamental constants
42#
43NAMELEN: con 28;	# size of names, including null byte
44NDBLOCK:	con 6;	# number of direct blocks in Dentry
45MAXFILESIZE:	con big 16r7FFFFFFF;	# Plan 9's limit (kfs's size is signed)
46
47SUPERADDR: con 1;
48ROOTADDR: con 2;
49
50QPDIR:	con int (1<<31);
51QPNONE: con 0;
52QPROOT: con 1;
53QPSUPER: con 2;
54
55#
56# don't change, these are the mode bits on disc
57#
58DALLOC: con 16r8000;
59DDIR:	con 16r4000;
60DAPND:	con 16r2000;
61DLOCK:	con 16r1000;
62DREAD:	con 4;
63DWRITE:	con 2;
64DEXEC:	con 1;
65
66#
67# other constants
68#
69
70MINUTE:	con 60;
71TLOCK:	con 5*MINUTE;
72NTLOCK:	con 200;	# number of active file locks
73
74Buffering: con 1;
75
76FID1, FID2, FID3: con 1+iota;
77
78None: con 0;	# user ID for "none"
79Noworld: con 9999;	# conventional id for "noworld" group
80
81Lock: adt
82{
83	c: chan of int;
84	new:	fn(): ref Lock;
85	lock:	fn(c: self ref Lock);
86	canlock:	fn(c: self ref Lock): int;
87	unlock:	fn(c: self ref Lock);
88};
89
90Dentry: adt
91{
92	name:	string;
93	uid:	int;
94	gid:	int;
95	muid:	int;	# not set by plan 9's kfs
96	mode:	int;	# mode bits on disc: DALLOC etc
97	qid:	Qid;	# 9p1 format on disc
98	size:	big;	# only 32-bits on disc, and Plan 9 limits it to signed
99	atime:	int;
100	mtime:	int;
101
102	iob:	ref Iobuf;	# locked block containing directory entry, when in memory
103	buf:	array of byte;	# pointer into block to packed directory entry, when in memory
104	mod:	int;	# bits of buf that need updating
105
106	unpack:	fn(a: array of byte): ref Dentry;
107	get:	fn(p: ref Iobuf, slot: int): ref Dentry;
108	geta:	fn(d: ref Device, addr: int, slot: int, qpath: int, mode: int): (ref Dentry, string);
109	getd:	fn(f: ref File, mode: int): (ref Dentry, string);
110	put:	fn(d: self ref Dentry);
111	access:	fn(d: self ref Dentry, f: int, uid: int);
112	change:	fn(d: self ref Dentry, f: int);
113	release:	fn(d: self ref Dentry);
114	getblk:	fn(d: self ref Dentry, a: int, tag: int): ref Iobuf;
115	getblk1:	fn(d: self ref Dentry, a: int, tag: int): ref Iobuf;
116	rel2abs:	fn(d: self ref Dentry, a: int, tag: int, putb: int): int;
117	trunc:	fn(d: self ref Dentry, uid: int);
118	update:	fn(d: self ref Dentry);
119	print:	fn(d: self ref Dentry);
120};
121
122Uname, Uids, Umode, Uqid, Usize, Utime: con 1<<iota;	# Dentry.mod
123
124#
125# disc structure:
126#	Tag:	pad[2] tag[2] path[4]
127Tagsize: con 2+2+4;
128
129Tag: adt
130{
131	tag:	int;
132	path:	int;
133
134	unpack:	fn(a: array of byte): Tag;
135	pack:	fn(t: self Tag, a: array of byte);
136};
137
138Superb: adt
139{
140	iob:	ref Iobuf;
141
142	fstart:	int;
143	fsize:	int;
144	tfree:	int;
145	qidgen:	int;		# generator for unique ids
146
147	fsok:	int;
148
149	fbuf:	array of byte;	# nfree[4] free[FEPERBLK*4]; aliased into containing block
150
151	get:	fn(dev: ref Device, flags: int): ref Superb;
152	touched:	fn(s: self ref Superb);
153	put:	fn(s: self ref Superb);
154	print:	fn(s: self ref Superb);
155
156	pack:	fn(s: self ref Superb, a: array of byte);
157	unpack:	fn(a: array of byte): ref Superb;
158};
159
160Device: adt
161{
162	fd:	ref Sys->FD;
163	ronly:	int;
164	# could put locks here if necessary
165	# partitioning by ds(3)
166};
167
168#
169# one for each locked qid
170#
171Tlock: adt
172{
173	dev:	ref Device;
174	time:	int;
175	qpath:	int;
176	file:	cyclic ref File;	# TO DO: probably not needed
177};
178
179File: adt
180{
181	qlock:	chan of int;
182	qid:	Qid;
183	wpath:	ref Wpath;
184	tlock:	cyclic ref Tlock;		# if file is locked
185	fs:	ref Device;
186	addr:	int;
187	slot:	int;
188	lastra:	int;		# read ahead address
189	fid:	int;
190	uid:	int;
191	open:	int;
192	cons:	int;	# if opened by console
193	doffset: big;	# directory reading
194	dvers:	int;
195	dslot:	int;
196
197	new:	fn(fid: int): ref File;
198	access:	fn(f: self ref File, d: ref Dentry, mode: int): int;
199	lock:	fn(f: self ref File);
200	unlock:	fn(f: self ref File);
201};
202
203FREAD, FWRITE, FREMOV, FWSTAT: con 1<<iota;	# File.open
204
205Chan: adt
206{
207	fd:	ref Sys->FD;			# fd request came in on
208#	rlock, wlock: QLock;		# lock for reading/writing messages on cp
209	flags:	int;
210	flist:	list of ref File;			# active files
211	fqlock:	chan of int;
212#	reflock:	RWLock;		# lock for Tflush
213	msize:	int;			# version
214
215	new:	fn(fd: ref Sys->FD): ref Chan;
216	getfid:	fn(c: self ref Chan, fid: int, flag: int): ref File;
217	putfid:	fn(c: self ref Chan, f: ref File);
218	flock: fn(nil: self ref Chan);
219	funlock:	fn(nil: self ref Chan);
220};
221
222Hiob: adt
223{
224	link:	ref Iobuf;	# TO DO: eliminate circular list
225	lk:	ref Lock;
226	niob: int;
227
228	newbuf:	fn(h: self ref Hiob): ref Iobuf;
229};
230
231Iobuf: adt
232{
233	qlock:	chan of int;
234	dev:	ref Device;
235	fore:	cyclic ref Iobuf;		# lru hash chain
236	back:	cyclic ref Iobuf;		# for lru
237	iobuf:	array of byte;		# only active while locked
238	xiobuf:	array of byte;	# "real" buffer pointer
239	addr:	int;
240	flags:	int;
241
242	get:	fn(dev: ref Device, addr: int, flags: int):ref Iobuf;
243	put:	fn(iob: self ref Iobuf);
244	lock:	fn(iob: self ref Iobuf);
245	canlock:	fn(iob: self ref Iobuf): int;
246	unlock:	fn(iob: self ref Iobuf);
247
248	checktag:	fn(iob: self ref Iobuf, tag: int, qpath: int): int;
249	settag:	fn(iob: self ref Iobuf, tag: int, qpath: int);
250};
251
252Wpath: adt
253{
254	up: cyclic ref Wpath;		# pointer upwards in path
255	addr: int;		# directory entry addr
256	slot: int;		# directory entry slot
257};
258
259#
260#  error codes generated from the file server
261#
262Eaccess: con "access permission denied";
263Ealloc: con "phase error -- directory entry not allocated";
264Eauth: con "authentication failed";
265Eauthmsg: con "kfs: authentication not required";
266Ebadspc: con "attach -- bad specifier";
267Ebadu: con "attach -- privileged user";
268Ebroken: con "close/read/write -- lock is broken";
269Echar: con "bad character in directory name";
270Econvert: con "protocol botch";
271Ecount: con "read/write -- count too big";
272Edir1: con "walk -- in a non-directory";
273Edir2: con "create -- in a non-directory";
274Edot: con "create -- . and .. illegal names";
275Eempty: con "remove -- directory not empty";
276Eentry: con "directory entry not found";
277Eexist: con "create -- file exists";
278Efid: con "unknown fid";
279Efidinuse: con "fid already in use";
280Efull: con "file system full";
281Elocked: con "open/create -- file is locked";
282Emode: con "open/create -- unknown mode";
283Ename: con "create/wstat -- bad character in file name";
284Enotd: con "wstat -- attempt to change directory";
285Enotg: con "wstat -- not in group";
286Enotl: con "wstat -- attempt to change length";
287Enotm: con "wstat -- unknown type/mode";
288Enotu: con "wstat -- not owner";
289Eoffset: con "read/write -- offset negative";
290Eopen: con "read/write -- on non open fid";
291Ephase: con "phase error -- cannot happen";
292Eqid: con "phase error -- qid does not match";
293Eqidmode: con "wstat -- qid.qtype/dir.mode mismatch";
294Eronly: con "file system read only";
295Ersc: con "it's russ's fault.  bug him.";
296Esystem: con "kfs system error";
297Etoolong: con "name too long";
298Etoobig: con "write -- file size limit";
299Ewalk: con "walk -- too many (system wide)";
300
301#
302#  tags on block
303#
304Tnone,
305Tsuper,			# the super block
306Tdir,			# directory contents
307Tind1,			# points to blocks
308Tind2,			# points to Tind1
309Tfile,			# file contents
310Tfree,			# in free list
311Tbuck,			# cache fs bucket
312Tvirgo,			# fake worm virgin bits
313Tcache,			# cw cache things
314MAXTAG: con iota;
315
316#
317#  flags to Iobuf.get
318#
319	Bread,	# read the block if miss
320	Bprobe,	# return null if miss
321	Bmod,	# set modified bit in buffer
322	Bimm,	# set immediate bit in buffer
323	Bres:		# never renamed
324	con 1<<iota;
325
326#
327#  check flags
328#
329	Crdall,	# read all files
330	Ctag,	# rebuild tags
331	Cpfile,	# print files
332	Cpdir,	# print directories
333	Cfree,	# rebuild free list
334	Cream,	# clear all bad tags
335	Cbad,	# clear all bad blocks
336	Ctouch,	# touch old dir and indir
337	Cquiet:	# report just nasty things
338	con 1<<iota;
339
340#
341#  buffer size variables, determined by RBUFSIZE
342#
343RBUFSIZE: int;
344BUFSIZE: int;
345DIRPERBUF: int;
346INDPERBUF: int;
347INDPERBUF2: int;
348FEPERBUF: int;
349
350emptyblock: array of byte;
351
352wrenfd: ref Sys->FD;
353thedevice: ref Device;
354devnone: ref Device;
355wstatallow := 0;
356writeallow := 0;
357writegroup := 0;
358
359ream := 0;
360readonly := 0;
361noatime := 0;
362localfs: con 1;
363conschan: ref Chan;
364consuid := -1;
365consgid := -1;
366debug := 0;
367kfsname: string;
368consoleout: chan of string;
369mainlock: ref Lock;
370pids: list of int;
371
372noqid: Qid;
373
374init(nil: ref Draw->Context, args: list of string)
375{
376	sys = load Sys Sys->PATH;
377	styx = load Styx Styx->PATH;
378	daytime = load Daytime Daytime->PATH;
379
380	styx->init();
381
382
383	arg := load Arg Arg->PATH;
384	if(arg == nil)
385		error(sys->sprint("can't load %s: %r", Arg->PATH));
386	arg->init(args);
387	arg->setusage("disk/kfs [-r [-b bufsize]] [-cADPRW] [-n name] kfsfile");
388	bufsize := 1024;
389	nocheck := 0;
390	while((o := arg->opt()) != 0)
391		case o {
392		'c' => nocheck = 1;
393		'r' =>	ream = 1;
394		'b' => bufsize = int arg->earg();
395		'D' => debug = !debug;
396		'P' => writeallow = 1;
397		'W' => wstatallow = 1;
398		'R' => readonly = 1;
399		'A' => noatime = 1;	# mainly useful for flash
400		'n' => kfsname = arg->earg();
401		* =>	arg->usage();
402		}
403	args = arg->argv();
404	if(args == nil)
405		arg->usage();
406	arg = nil;
407
408	devnone = ref Device(nil, 1);
409	mainlock = Lock.new();
410
411	conschan = Chan.new(nil);
412	conschan.msize = Styx->MAXRPC;
413
414	mode := Sys->ORDWR;
415	if(readonly)
416		mode = Sys->OREAD;
417	wrenfd = sys->open(hd args, mode);
418	if(wrenfd == nil)
419		error(sys->sprint("can't open %s: %r", hd args));
420	thedevice = ref Device(wrenfd, readonly);
421	if(ream){
422		if(bufsize <= 0 || bufsize % 512 || bufsize > MAXBUFSIZE)
423			error(sys->sprint("invalid block size %d", bufsize));
424		RBUFSIZE = bufsize;
425		wrenream(thedevice);
426	}else{
427		if(!wreninit(thedevice))
428			error("kfs magic in trouble");
429	}
430	BUFSIZE = RBUFSIZE - Tagsize;
431	DIRPERBUF = BUFSIZE / Dentrysize;
432	INDPERBUF = BUFSIZE / 4;
433	INDPERBUF2 = INDPERBUF * INDPERBUF;
434	FEPERBUF = (BUFSIZE - Super1size - 4) / 4;
435	emptyblock = array[RBUFSIZE] of {* => byte 0};
436
437	iobufinit(30);
438
439	if(ream){
440		superream(thedevice, SUPERADDR);
441		rootream(thedevice, ROOTADDR);
442		wstatallow = writeallow = 1;
443	}
444	if(wrencheck(wrenfd))
445		error("kfs super/root in trouble");
446
447	if(!ream && !readonly && !superok(0)){
448		sys->print("kfs needs check\n");
449		if(!nocheck)
450			check(thedevice, Cquiet|Cfree);
451	}
452
453	(d, e) := Dentry.geta(thedevice, ROOTADDR, 0, QPROOT, Bread);
454	if(d != nil && !(d.mode & DDIR))
455		e = "not a directory";
456	if(e != nil)
457		error("bad root: "+e);
458	if(debug)
459		d.print();
460	d.put();
461
462	sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);
463
464	sys->pctl(Sys->NEWFD, wrenfd.fd :: 0 :: 1 :: 2 :: nil);
465	wrenfd = sys->fildes(wrenfd.fd);
466	thedevice.fd = wrenfd;
467
468	c := chan of int;
469
470	if(Buffering){
471		spawn syncproc(c);
472		pid := <-c;
473		if(pid)
474			pids = pid :: pids;
475	}
476	spawn consinit(c);
477	pid := <- c;
478	if(pid)
479		pids = pid :: pids;
480
481	spawn kfs(sys->fildes(0));
482}
483
484error(s: string)
485{
486	sys->fprint(sys->fildes(2), "kfs: %s\n", s);
487	for(; pids != nil; pids = tl pids)
488		kill(hd pids);
489	raise "fail:error";
490}
491
492panic(s: string)
493{
494	sys->fprint(sys->fildes(2), "kfs: panic: %s\n", s);
495	for(; pids != nil; pids = tl pids)
496		kill(hd pids);
497	raise "panic";
498}
499
500syncproc(c: chan of int)
501{
502	c <-= 0;
503}
504
505shutdown()
506{
507	for(; pids != nil; pids = tl pids)
508		kill(hd pids);
509	# TO DO: when Bmod deferred, must sync
510	# sync super block
511	if(!readonly && superok(1)){
512		# ;
513	}
514	iobufclear();
515}
516
517kill(pid: int)
518{
519	fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
520	if(fd != nil)
521		sys->fprint(fd, "kill");
522}
523
524#
525# limited file system support for console
526#
527kattach(fid: int): string
528{
529	return applycons(ref Tmsg.Attach(1, fid, NOFID, "adm", "")).t1;
530}
531
532kopen(oldfid: int, newfid: int, names: array of string, mode: int): string
533{
534	(r1, e1) := applycons(ref Tmsg.Walk(1, oldfid, newfid, names));
535	if(r1 != nil){
536		pick m := r1 {
537		Walk =>
538			if(len m.qids != len names){
539				kclose(newfid);
540				cprint(Eexist);
541				return Eexist;
542			}
543		* =>
544			return "unexpected reply";
545		}
546		(r1, e1) = applycons(ref Tmsg.Open(1, newfid, mode));
547		if(e1 != nil){
548			kclose(newfid);
549			cprint(sys->sprint("open: %s", e1));
550		}
551	}
552	return e1;
553}
554
555kread(fid: int, offset: int, nbytes: int): (array of byte, string)
556{
557	(r, e) := applycons(ref Tmsg.Read(1, fid, big offset, nbytes));
558	if(r != nil){
559		pick m := r {
560		Read =>
561			return (m.data, nil);
562		* =>
563			return (nil, "unexpected reply");
564		}
565	}
566	cprint(sys->sprint("read error: %s", e));
567	return (nil, e);
568}
569
570kclose(fid: int)
571{
572	applycons(ref Tmsg.Clunk(1, fid));
573}
574
575applycons(t: ref Tmsg): (ref Rmsg, string)
576{
577	r := apply(conschan, t);
578	pick m := r {
579	Error =>
580		if(debug)
581			cprint(sys->sprint("%s: %s\n", t.text(), m.ename));
582		return (nil, m.ename);
583	}
584	return (r, nil);
585}
586
587#
588# always reads /adm/users in userinit(), then
589# optionally serves the command file, if used.
590#
591Req: adt {
592	nbytes:	int;
593	rc:	chan of (array of byte, string);
594};
595
596consinit(c: chan of int)
597{
598	kattach(FID1);
599	userinit();
600	if(kfsname == nil){
601		c <-= 0;
602		exit;
603	}
604	cfname := "kfs."+kfsname+".cmd";
605	sys->bind("#s", "/chan", Sys->MBEFORE);
606	file := sys->file2chan("/chan", cfname);
607	if(file == nil)
608		error(sys->sprint("can't create /chan/%s: %r", cfname));
609	c <-= sys->pctl(0, nil);
610	consc := chan of string;
611	checkend := chan of int;
612	cdata: array of byte;
613	pending: ref Req;
614	cfid := -1;
615	for(;;) alt{
616	(nil, nbytes, fid, rc) := <-file.read =>
617		if(rc == nil)
618			break;
619		if(cfid == -1)
620			cfid = fid;
621		if(fid != cfid || pending != nil){
622			rc <-= (nil, "kfs.cmd is busy");
623			break;
624		}
625		if(cdata != nil){
626			cdata = reply(rc, nbytes, cdata);
627			break;
628		}
629		if(nbytes <= 0 || consoleout == nil){
630			rc <-= (nil, nil);
631			break;
632		}
633		pending = ref Req(nbytes, rc);
634		consc = consoleout;
635	(nil, data, fid, wc) := <-file.write =>
636		if(cfid == -1)
637			cfid = fid;
638		if(wc == nil){
639			if(fid == cfid){
640				cfid = -1;
641				pending = nil;
642				cdata = nil;	# discard unread data from last command
643				if((consc = consoleout) == nil)
644					consc = chan of string;
645			}
646			break;
647		}
648		if(fid != cfid){
649			wc <-= (0, "kfs.cmd is busy");
650			break;
651		}
652		(nf, fld) := sys->tokenize(string data, " \t\n\r");
653		if(nf < 1){
654			wc <-= (0, "illegal kfs request");
655			break;
656		}
657		case hd fld {
658		"check" =>
659			if(consoleout != nil){
660				wc <-= (0, "check in progress");
661				break;
662			}
663			f := 0;
664			if(nf > 1){
665				f = checkflags(hd tl fld);
666				if(f < 0){
667					wc <-= (0, "illegal check flag: "+hd tl fld);
668					break;
669				}
670			}
671			consoleout = chan of string;
672			spawn checkproc(checkend, f);
673			wc <-= (len data, nil);
674			consc = consoleout;
675		"users" or "user" =>
676			cmd_users();
677			wc <-= (len data, nil);
678		"sync" =>
679			# nothing TO DO until writes are buffered
680			wc <-= (len data, nil);
681		"allow" =>
682			wstatallow = writeallow = 1;
683			wc <-= (len data, nil);
684		"allowoff" or "disallow" =>
685			wstatallow = writeallow = 0;
686			wc <-= (len data, nil);
687		* =>
688			wc <-= (0, "unknown kfs request");
689			continue;
690		}
691	<-checkend =>
692		consoleout = nil;
693		consc = chan of string;
694	s := <-consc =>
695		#sys->print("<-%s\n", s);
696		req := pending;
697		pending = nil;
698		if(req != nil)
699			cdata = reply(req.rc, req.nbytes, array of byte s);
700		else
701			cdata = array of byte s;
702		if(cdata != nil && cfid != -1)
703			consc = chan of string;
704	}
705}
706
707reply(rc: chan of (array of byte, string), nbytes: int, a: array of byte): array of byte
708{
709	if(len a < nbytes)
710		nbytes = len a;
711	rc <-= (a[0:nbytes], nil);
712	if(nbytes == len a)
713		return nil;
714	return a[nbytes:];
715}
716
717checkproc(c: chan of int, flags: int)
718{
719	mainlock.lock();
720	check(thedevice, flags);
721	mainlock.unlock();
722	c <-= 1;
723}
724
725#
726# normal kfs service
727#
728kfs(rfd: ref Sys->FD)
729{
730	cp := Chan.new(rfd);
731	while((t := Tmsg.read(rfd, cp.msize)) != nil){
732		if(debug)
733			sys->print("<- %s\n", t.text());
734		r := apply(cp, t);
735		pick m := r {
736		Error =>
737			r.tag = t.tag;
738		}
739		if(debug)
740			sys->print("-> %s\n", r.text());
741		rbuf := r.pack();
742		if(rbuf == nil)
743			panic("Rmsg.pack");
744		if(sys->write(rfd, rbuf, len rbuf) != len rbuf)
745			panic("mount write");
746	}
747	shutdown();
748}
749
750apply(cp: ref Chan, t: ref Tmsg): ref Rmsg
751{
752	mainlock.lock();	# TO DO: this is just to keep console and kfs from colliding
753	r: ref Rmsg;
754	pick m := t {
755	Readerror =>
756		error(sys->sprint("mount read error: %s", m.error));
757	Version =>
758		r = rversion(cp, m);
759	Auth =>
760		r = rauth(cp, m);
761	Flush =>
762		r = rflush(cp, m);
763	Attach =>
764		r = rattach(cp, m);
765	Walk =>
766		r = rwalk(cp, m);
767	Open =>
768		r = ropen(cp, m);
769	Create =>
770		r = rcreate(cp, m);
771	Read =>
772		r = rread(cp, m);
773	Write =>
774		r = rwrite(cp, m);
775	Clunk =>
776		r = rclunk(cp, m);
777	Remove =>
778		r = rremove(cp, m);
779	Stat =>
780		r = rstat(cp, m);
781	Wstat =>
782		r = rwstat(cp, m);
783	* =>
784		panic("Styx mtype");
785		return nil;
786	}
787	mainlock.unlock();
788	return r;
789}
790
791rversion(cp: ref Chan, t: ref Tmsg.Version): ref Rmsg
792{
793	cp.msize = RBUFSIZE+IOHDRSZ;
794	if(cp.msize < Styx->MAXRPC)
795		cp.msize = Styx->MAXRPC;
796	(msize, version) := styx->compatible(t, Styx->MAXRPC, Styx->VERSION);
797	if(msize < 256)
798		return ref Rmsg.Error(t.tag, "message size too small");
799	return ref Rmsg.Version(t.tag, msize, version);
800}
801
802rauth(nil: ref Chan, t: ref Tmsg.Auth): ref Rmsg
803{
804	return ref Rmsg.Error(t.tag, Eauthmsg);
805}
806
807rflush(nil: ref Chan, t: ref Tmsg.Flush): ref Rmsg
808{
809	# runlock(cp.reflock);
810	# wlock(cp.reflock);
811	# wunlock(cp.reflock);
812	# rlock(cp.reflock);
813	return ref Rmsg.Flush(t.tag);
814}
815
816err(t: ref Tmsg, s: string): ref Rmsg.Error
817{
818	return ref Rmsg.Error(t.tag, s);
819}
820
821ferr(t: ref Tmsg, s: string, file: ref File, p: ref Iobuf): ref Rmsg.Error
822{
823	if(p != nil)
824		p.put();
825	if(file != nil)
826		file.unlock();
827	return ref Rmsg.Error(t.tag, s);
828}
829
830File.new(fid: int): ref File
831{
832	f := ref File;
833	f.qlock = chan[1] of int;
834	f.fid = fid;
835	f.cons = 0;
836	f.tlock = nil;
837	f.wpath = nil;
838	f.doffset = big 0;
839	f.dvers = 0;
840	f.dslot = 0;
841	f.uid = None;
842	f.cons = 0;
843#	f.cuid = None;
844	return f;
845}
846
847#
848# returns a locked file structure
849#
850
851Chan.getfid(cp: self ref Chan, fid: int, flag: int): ref File
852{
853	if(fid == NOFID)
854		return nil;
855	cp.flock();
856	for(l := cp.flist; l != nil; l = tl l){
857		f := hd l;
858		if(f.fid == fid){
859			cp.funlock();
860			if(flag)
861				return nil;	# fid in use
862			f.lock();
863			if(f.fid == fid)
864				return f;
865			f.unlock();
866			cp.flock();
867		}
868	}
869	if(flag == 0){
870		sys->print("kfs: cannot find %H.%ud", cp, fid);
871		cp.funlock();
872		return nil;
873	}
874	f := File.new(fid);
875	f.lock();
876	cp.flist = f :: cp.flist;
877	cp.funlock();
878	return f;
879}
880
881Chan.putfid(cp: self ref Chan, f: ref File)
882{
883	cp.flock();
884	nl: list of ref File;
885	for(x := cp.flist; x != nil; x = tl x)
886		if(hd x != f)
887			nl = hd x :: nl;
888	cp.flist = nl;
889	cp.funlock();
890	f.unlock();
891}
892
893File.lock(f: self ref File)
894{
895	f.qlock <-= 1;
896}
897
898File.unlock(f: self ref File)
899{
900	<-f.qlock;
901}
902
903Chan.new(fd: ref Sys->FD): ref Chan
904{
905	c := ref Chan;
906	c.fd = fd;
907	c.fqlock = chan[1] of int;
908#	rlock, wlock: QLock;		# lock for reading/writing messages on cp
909	c.flags = 0;
910#	reflock:	RWLock;		# lock for Tflush
911	c.msize = 0;	# set by rversion
912	return c;
913}
914
915Chan.flock(c: self ref Chan)
916{
917	c.fqlock <-= 1;
918}
919
920Chan.funlock(c: self ref Chan)
921{
922	<-c.fqlock;
923}
924
925rattach(cp: ref Chan, t: ref Tmsg.Attach): ref Rmsg
926{
927	if(t.aname != "" && t.aname != "main")
928		return err(t, Ebadspc);
929	file := cp.getfid(t.fid, 1);
930	if(file == nil)
931		return err(t, Efidinuse);
932	p := Iobuf.get(thedevice, ROOTADDR, Bread);
933	if(p == nil){
934		cp.putfid(file);
935		return err(t, "can't access root block");
936	}
937	d := Dentry.get(p, 0);
938	if(d == nil || p.checktag(Tdir, QPROOT) || (d.mode & DALLOC) == 0 || (d.mode & DDIR) == 0){
939		p.put();
940		cp.putfid(file);
941		return err(t, Ealloc);
942	}
943	if(file.access(d, DEXEC)){
944		p.put();
945		cp.putfid(file);
946		return err(t, Eaccess);
947	}
948	d.access(FREAD, file.uid);
949	file.fs = thedevice;
950	file.qid = d.qid;
951	file.addr = p.addr;
952	file.slot = 0;
953	file.open = 0;
954	file.uid = strtouid(t.uname);
955	file.wpath = nil;
956	p.put();
957	qid := file.qid;
958	file.unlock();
959	return ref Rmsg.Attach(t.tag, qid);
960}
961
962clone(nfile: ref File, file: ref File)
963{
964	nfile.qid = file.qid;
965	nfile.wpath = file.wpath;
966	nfile.fs = file.fs;
967	nfile.addr = file.addr;
968	nfile.slot = file.slot;
969	nfile.uid = file.uid;
970#	nfile.cuid = None;
971	nfile.open = file.open & ~FREMOV;
972}
973
974walkname(file: ref File, wname: string): (string, Qid)
975{
976	#
977	# File must not have been opened for I/O by an open
978	# or create message and must represent a directory.
979	#
980	if(file.open != 0)
981		return (Emode, noqid);
982
983	(d, e) := Dentry.getd(file, Bread);
984	if(d == nil)
985		return (e, noqid);
986	if(!(d.mode & DDIR)){
987		d.put();
988		return (Edir1, noqid);
989	}
990
991	#
992	# For walked elements the implied user must
993	# have permission to search the directory.
994	#
995	if(file.access(d, DEXEC)){
996		d.put();
997		return (Eaccess, noqid);
998	}
999	d.access(FREAD, file.uid);
1000
1001	if(wname == "." || wname == ".." && file.wpath == nil){
1002		d.put();
1003		return (nil, file.qid);
1004	}
1005
1006	d1: ref Dentry;	# entry for wname, if found
1007	slot: int;
1008
1009	if(wname == ".."){
1010		d.put();
1011		addr := file.wpath.addr;
1012		slot = file.wpath.slot;
1013		(d1, e) = Dentry.geta(file.fs, addr, slot, QPNONE, Bread);
1014		if(d1 == nil)
1015			return (e, noqid);
1016		file.wpath = file.wpath.up;
1017	}else{
1018
1019	Search:
1020		for(addr := 0; ; addr++){
1021			if(d.iob == nil){
1022				(d, e) = Dentry.getd(file, Bread);
1023				if(d == nil)
1024					return (e, noqid);
1025			}
1026			p1 := d.getblk1(addr, 0);
1027			if(p1 == nil || p1.checktag(Tdir, int d.qid.path)){
1028				if(p1 != nil)
1029					p1.put();
1030				return (Eentry, noqid);
1031			}
1032			for(slot = 0; slot < DIRPERBUF; slot++){
1033				d1 = Dentry.get(p1, slot);
1034				if(!(d1.mode & DALLOC))
1035					continue;
1036				if(wname != d1.name)
1037					continue;
1038				#
1039				# update walk path
1040				#
1041				file.wpath = ref Wpath(file.wpath, file.addr, file.slot);
1042				slot += DIRPERBUF*addr;
1043				break Search;
1044			}
1045			p1.put();
1046		}
1047		d.put();
1048	}
1049
1050	file.addr = d1.iob.addr;
1051	file.slot = slot;
1052	file.qid = d1.qid;
1053	d1.put();
1054	return (nil, file.qid);
1055}
1056
1057rwalk(cp: ref Chan, t: ref Tmsg.Walk): ref Rmsg
1058{
1059	nfile, tfile: ref File;
1060	q: Qid;
1061
1062	# The file identified by t.fid must be valid in the
1063	# current session and must not have been opened for I/O
1064	# by an open or create message.
1065
1066	if((file := cp.getfid(t.fid, 0)) == nil)
1067		return err(t, Efid);
1068	if(file.open != 0)
1069		return ferr(t, Emode, file, nil);
1070
1071	# If newfid is not the same as fid, allocate a new file;
1072	# a side effect is checking newfid is not already in use (error);
1073	# if there are no names to walk this will be equivalent to a
1074	# simple 'clone' operation.
1075	# Otherwise, fid and newfid are the same and if there are names
1076	# to walk make a copy of 'file' to be used during the walk as
1077	# 'file' must only be updated on success.
1078	# Finally, it's a no-op if newfid is the same as fid and t.nwname
1079	# is 0.
1080
1081	nwqid := 0;
1082	if(t.newfid != t.fid){
1083		if((nfile = cp.getfid(t.newfid, 1)) == nil)
1084			return ferr(t, Efidinuse, file, nil);
1085	}
1086	else if(len t.names != 0)
1087		nfile = tfile = File.new(NOFID);
1088	else{
1089		file.unlock();
1090		return ref Rmsg.Walk(t.tag, nil);
1091	}
1092	clone(nfile, file);
1093
1094	r := ref Rmsg.Walk(t.tag, array[len t.names] of Qid);
1095	error: string;
1096	for(nwname := 0; nwname < len t.names; nwname++){
1097		(error, q) = walkname(nfile, t.names[nwname]);
1098		if(error != nil)
1099			break;
1100		r.qids[nwqid++] = q;
1101	}
1102
1103	if(len t.names == 0){
1104
1105		# Newfid must be different to fid (see above)
1106		# so this is a simple 'clone' operation - there's
1107		# nothing to do except unlock unless there's
1108		# an error.
1109
1110		nfile.unlock();
1111		if(error != nil)
1112			cp.putfid(nfile);
1113	}else if(nwqid < len t.names){
1114		#
1115		# Didn't walk all elements, 'clunk' nfile
1116		# and leave 'file' alone.
1117		# Clear error if some of the elements were
1118		# walked OK.
1119		#
1120		if(nfile != tfile)
1121			cp.putfid(nfile);
1122		if(nwqid != 0)
1123			error = nil;
1124		r.qids = r.qids[0:nwqid];
1125	}else{
1126		#
1127		# Walked all elements. If newfid is the same
1128		# as fid must update 'file' from the temporary
1129		# copy used during the walk.
1130		# Otherwise just unlock (when using tfile there's
1131		# no need to unlock as it's a local).
1132		#
1133		if(nfile == tfile){
1134			file.qid = nfile.qid;
1135			file.wpath = nfile.wpath;
1136			file.addr = nfile.addr;
1137			file.slot = nfile.slot;
1138		}else
1139			nfile.unlock();
1140	}
1141	file.unlock();
1142
1143	if(error != nil)
1144		return err(t, error);
1145	return r;
1146}
1147
1148ropen(cp: ref Chan, f: ref Tmsg.Open): ref Rmsg
1149{
1150	wok := cp == conschan || writeallow;
1151
1152	if((file := cp.getfid(f.fid, 0)) == nil)
1153		return err(f, Efid);
1154
1155	#
1156	# if remove on close, check access here
1157	#
1158	ro := isro(file.fs) || (writegroup && !ingroup(file.uid, writegroup));
1159	if(f.mode & ORCLOSE){
1160		if(ro)
1161			return ferr(f, Eronly, file, nil);
1162		#
1163		# check on parent directory of file to be deleted
1164		#
1165		if(file.wpath == nil || file.wpath.addr == file.addr)
1166			return ferr(f, Ephase, file, nil);
1167		p := Iobuf.get(file.fs, file.wpath.addr, Bread);
1168		if(p == nil || p.checktag(Tdir, QPNONE))
1169			return ferr(f, Ephase, file, p);
1170		if((d := Dentry.get(p, file.wpath.slot)) == nil || !(d.mode & DALLOC))
1171			return ferr(f, Ephase, file, p);
1172		if(file.access(d, DWRITE))
1173			return ferr(f, Eaccess, file, p);
1174		p.put();
1175	}
1176	(d, e) := Dentry.getd(file, Bread);
1177	if(d == nil)
1178		return ferr(f, e, file, nil);
1179	p := d.iob;
1180	qid := d.qid;
1181	fmod: int;
1182	case f.mode & 7 {
1183
1184	OREAD =>
1185		if(file.access(d, DREAD) && !wok)
1186			return ferr(f, Eaccess, file, p);
1187		fmod = FREAD;
1188
1189	OWRITE =>
1190		if((d.mode & DDIR) || (file.access(d, DWRITE) && !wok))
1191			return ferr(f, Eaccess, file, p);
1192		if(ro)
1193			return ferr(f, Eronly, file, p);
1194		fmod = FWRITE;
1195
1196	ORDWR =>
1197		if((d.mode & DDIR)
1198		|| (file.access(d, DREAD) && !wok)
1199		|| (file.access(d, DWRITE) && !wok))
1200			return ferr(f, Eaccess, file, p);
1201		if(ro)
1202			return ferr(f, Eronly, file, p);
1203		fmod = FREAD+FWRITE;
1204
1205	OEXEC =>
1206		if((d.mode & DDIR) || (file.access(d, DEXEC) && !wok))
1207			return ferr(f, Eaccess, file, p);
1208		fmod = FREAD;
1209
1210	* =>
1211		return ferr(f, Emode, file, p);
1212	}
1213	if(f.mode & OTRUNC){
1214		if((d.mode & DDIR) || (file.access(d, DWRITE) && !wok))
1215			return ferr(f, Eaccess, file, p);
1216		if(ro)
1217			return ferr(f, Eronly, file, p);
1218	}
1219	if(d.mode & DLOCK){
1220		if((t := tlocked(file, d)) == nil)
1221			return ferr(f, Elocked, file, p);
1222		file.tlock = t;
1223		t.file = file;
1224	}
1225	if(f.mode & ORCLOSE)
1226		fmod |= FREMOV;
1227	file.open = fmod;
1228	if((f.mode & OTRUNC) && !(d.mode & DAPND)){
1229		d.trunc(file.uid);
1230		qid.vers = d.qid.vers;
1231	}
1232	file.lastra = 1;
1233	p.put();
1234	file.unlock();
1235	return ref Rmsg.Open(f.tag, qid, cp.msize-IOHDRSZ);
1236}
1237
1238rcreate(cp: ref Chan, f: ref Tmsg.Create): ref Rmsg
1239{
1240	wok := cp == conschan || writeallow;
1241
1242	if((file := cp.getfid(f.fid, 0)) == nil)
1243		return err(f, Efid);
1244	if(isro(file.fs) || (writegroup && !ingroup(file.uid, writegroup)))
1245		return ferr(f, Eronly, file, nil);
1246
1247	(d, e) := Dentry.getd(file, Bread);
1248	if(e != nil)
1249		return ferr(f, e, file, nil);
1250	p := d.iob;
1251	if(!(d.mode & DDIR))
1252		return ferr(f, Edir2, file, p);
1253	if(file.access(d, DWRITE) && !wok)
1254		return ferr(f, Eaccess, file, p);
1255	d.access(FREAD, file.uid);
1256
1257	#
1258	# Check the name is valid and will fit in an old
1259	# directory entry.
1260	#
1261	if((l := checkname9p2(f.name)) == 0)
1262		return ferr(f, Ename, file, p);
1263	if(l+1 > NAMELEN)
1264		return ferr(f, Etoolong, file, p);
1265	if(f.name == "." || f.name == "..")
1266		return ferr(f, Edot, file, p);
1267
1268	addr1 := 0;	# block with first empty slot, if any
1269	slot1 := 0;
1270	for(addr := 0; ; addr++){
1271		if((p1 := d.getblk(addr, 0)) == nil){
1272			if(addr1 != 0)
1273				break;
1274			p1 = d.getblk(addr, Tdir);
1275		}
1276		if(p1 == nil)
1277			return ferr(f, Efull, file, p);
1278		if(p1.checktag(Tdir, int d.qid.path)){
1279			p1.put();
1280			return ferr(f, Ephase, file, p);
1281		}
1282		for(slot := 0; slot < DIRPERBUF; slot++){
1283			d1 := Dentry.get(p1, slot);
1284			if(!(d1.mode & DALLOC)){
1285				if(addr1 == 0){
1286					addr1 = p1.addr;
1287					slot1 = slot + addr*DIRPERBUF;
1288				}
1289				continue;
1290			}
1291			if(f.name == d1.name){
1292				p1.put();
1293				return ferr(f, Eexist, file, p);
1294			}
1295		}
1296		p1.put();
1297	}
1298
1299	fmod: int;
1300
1301	case f.mode & 7 {
1302	OEXEC or
1303	OREAD =>		# seems only useful to make directories
1304		fmod = FREAD;
1305
1306	OWRITE =>
1307		fmod = FWRITE;
1308
1309	ORDWR =>
1310		fmod = FREAD+FWRITE;
1311
1312	* =>
1313		return ferr(f, Emode, file, p);
1314	}
1315	if(f.perm & DMDIR)
1316		if((f.mode & OTRUNC) || (f.perm & DMAPPEND) || (fmod & FWRITE))
1317			return ferr(f, Eaccess, file, p);
1318
1319	# do it
1320
1321	path := qidpathgen(file.fs);
1322	if((p1 := Iobuf.get(file.fs, addr1, Bread|Bimm|Bmod)) == nil)
1323		return ferr(f, Ephase, file, p);
1324	d1 := Dentry.get(p1, slot1);
1325	if(d1 == nil || p1.checktag(Tdir, int d.qid.path)){
1326		p.put();
1327		return ferr(f, Ephase, file, p1);
1328	}
1329	if(d1.mode & DALLOC){
1330		p.put();
1331		return ferr(f, Ephase, file, p1);
1332	}
1333
1334	d1.name = f.name;
1335	if(cp == conschan){
1336		d1.uid = consuid;
1337		d1.gid = consgid;
1338	}
1339	else{
1340		d1.uid = file.uid;
1341		d1.gid = d.gid;
1342		f.perm &= d.mode | ~8r666;
1343		if(f.perm & DMDIR)
1344			f.perm &= d.mode | ~8r777;
1345	}
1346	d1.qid.path = big path;
1347	d1.qid.vers = 0;
1348	d1.mode = DALLOC | (f.perm & 8r777);
1349	if(f.perm & DMDIR)
1350		d1.mode |= DDIR;
1351	if(f.perm & DMAPPEND)
1352		d1.mode |= DAPND;
1353	t: ref Tlock;
1354	if(f.perm & DMEXCL){
1355		d1.mode |= DLOCK;
1356		t = tlocked(file, d1);
1357		# if nil, out of tlock structures
1358	}
1359	d1.access(FWRITE, file.uid);
1360	d1.change(~0);
1361	d1.update();
1362	qid := mkqid(path, 0, d1.mode);
1363	p1.put();
1364	d.change(~0);
1365	d.access(FWRITE, file.uid);
1366	d.update();
1367	p.put();
1368
1369	#
1370	# do a walk to new directory entry
1371	#
1372	file.wpath = ref Wpath(file.wpath, file.addr, file.slot);
1373	file.qid = qid;
1374	file.tlock = t;
1375	if(t != nil)
1376		t.file = file;
1377	file.lastra = 1;
1378	if(f.mode & ORCLOSE)
1379		fmod |= FREMOV;
1380	file.open = fmod;
1381	file.addr = addr1;
1382	file.slot = slot1;
1383	file.unlock();
1384	return ref Rmsg.Create(f.tag, qid, cp.msize-IOHDRSZ);
1385}
1386
1387dirread(cp: ref Chan, f: ref Tmsg.Read, file: ref File, d: ref Dentry): ref Rmsg
1388{
1389	p1: ref Iobuf;
1390	d1: ref Dentry;
1391
1392	count := f.count;
1393	data := array[count] of byte;
1394	offset := f.offset;
1395	iounit := cp.msize-IOHDRSZ;
1396	if(count > iounit)
1397		count = iounit;
1398
1399	# Pick up where we left off last time if nothing has changed,
1400	# otherwise must scan from the beginning.
1401
1402	addr, slot: int;
1403	start: big;
1404
1405	if(offset == file.doffset){	# && file.qid.vers == file.dvers
1406		addr = file.dslot/DIRPERBUF;
1407		slot = file.dslot%DIRPERBUF;
1408		start = offset;
1409	}
1410	else{
1411		addr = 0;
1412		slot = 0;
1413		start = big 0;
1414	}
1415
1416	nread := 0;
1417Dread:
1418	for(;;){
1419		if(d.iob == nil){
1420			#
1421			# This is just a check to ensure the entry hasn't
1422			# gone away during the read of each directory block.
1423			#
1424			e: string;
1425			(d, e) = Dentry.getd(file, Bread);
1426			if(d == nil)
1427				return ferr(f, e, file, nil);
1428		}
1429		p1 = d.getblk1(addr, 0);
1430		if(p1 == nil)
1431			break;
1432		if(p1.checktag(Tdir, QPNONE))
1433			return ferr(f, Ephase, file, p1);
1434
1435		for(; slot < DIRPERBUF; slot++){
1436			d1 = Dentry.get(p1, slot);
1437			if(!(d1.mode & DALLOC))
1438				continue;
1439			dir := dir9p2(d1);
1440			n := styx->packdirsize(dir);
1441			if(n > count-nread){
1442				p1.put();
1443				break Dread;
1444			}
1445			data[nread:] = styx->packdir(dir);
1446			start += big n;
1447			if(start < offset)
1448				continue;
1449			if(count < n){
1450				p1.put();
1451				break Dread;
1452			}
1453			count -= n;
1454			nread += n;
1455			offset += big n;
1456		}
1457		p1.put();
1458		slot = 0;
1459		addr++;
1460	}
1461
1462	file.doffset = offset;
1463	file.dvers = file.qid.vers;
1464	file.dslot = slot+DIRPERBUF*addr;
1465
1466	d.put();
1467	file.unlock();
1468	return ref Rmsg.Read(f.tag, data[0:nread]);
1469}
1470
1471rread(cp: ref Chan, f: ref Tmsg.Read): ref Rmsg
1472{
1473	if((file := cp.getfid(f.fid, 0)) == nil)
1474		return err(f, Efid);
1475	if(!(file.open & FREAD))
1476		return ferr(f, Eopen, file, nil);
1477	count := f.count;
1478	iounit := cp.msize-IOHDRSZ;
1479	if(count < 0 || count > iounit)
1480		return ferr(f, Ecount, file, nil);
1481	offset := f.offset;
1482	if(offset < big 0)
1483		return ferr(f, Eoffset, file, nil);
1484
1485	(d, e) := Dentry.getd(file, Bread);
1486	if(d == nil)
1487		return ferr(f, e, file, nil);
1488	if((t := file.tlock) != nil){
1489		tim := now();
1490		if(t.time < tim || t.file != file){
1491			d.put();
1492			return ferr(f, Ebroken, file, nil);
1493		}
1494		# renew the lock
1495		t.time = tim + TLOCK;
1496	}
1497	d.access(FREAD, file.uid);
1498	if(d.mode & DDIR)
1499		return dirread(cp, f, file, d);
1500
1501	if(offset+big count > d.size)
1502		count = int (d.size - offset);
1503	if(count < 0)
1504		count = 0;
1505	data := array[count] of byte;
1506	nread := 0;
1507	while(count > 0){
1508		if(d.iob == nil){
1509			# must check and reacquire entry
1510			(d, e) = Dentry.getd(file, Bread);
1511			if(d == nil)
1512				return ferr(f, e, file, nil);
1513		}
1514		addr := int (offset / big BUFSIZE);
1515		if(addr == file.lastra+1)
1516			;	# dbufread(p, d, addr+1);
1517		file.lastra = addr;
1518		o := int (offset % big BUFSIZE);
1519		n := BUFSIZE - o;
1520		if(n > count)
1521			n = count;
1522		p1 := d.getblk1(addr, 0);
1523		if(p1 != nil){
1524			if(p1.checktag(Tfile, QPNONE)){
1525				p1.put();
1526				return ferr(f, Ephase, file, nil);
1527			}
1528			data[nread:] = p1.iobuf[o:o+n];
1529			p1.put();
1530		}else
1531			data[nread:] = emptyblock[0:n];
1532		count -= n;
1533		nread += n;
1534		offset += big n;
1535	}
1536	d.put();
1537	file.unlock();
1538	return ref Rmsg.Read(f.tag, data[0:nread]);
1539}
1540
1541rwrite(cp: ref Chan, f: ref Tmsg.Write): ref Rmsg
1542{
1543	if((file := cp.getfid(f.fid, 0)) == nil)
1544		return err(f, Efid);
1545	if(!(file.open & FWRITE))
1546		return ferr(f, Eopen, file, nil);
1547	if(isro(file.fs) || (writegroup && !ingroup(file.uid, writegroup)))
1548		return ferr(f, Eronly, file, nil);
1549	count := len f.data;
1550	if(count < 0 || count > cp.msize-IOHDRSZ)
1551		return ferr(f, Ecount, file, nil);
1552	offset := f.offset;
1553	if(offset < big 0)
1554		return ferr(f, Eoffset, file, nil);
1555
1556	(d, e) := Dentry.getd(file, Bread|Bmod);
1557	if(d == nil)
1558		return ferr(f, e, file, nil);
1559	if((t := file.tlock) != nil){
1560		tim := now();
1561		if(t.time < tim || t.file != file){
1562			d.put();
1563			return ferr(f, Ebroken, file, nil);
1564		}
1565		# renew the lock
1566		t.time = tim + TLOCK;
1567	}
1568	d.access(FWRITE, file.uid);
1569	if(d.mode & DAPND)
1570		offset = d.size;
1571	end := offset + big count;
1572	if(end > d.size){
1573		if(end > MAXFILESIZE)
1574			return ferr(f, Etoobig, file, nil);
1575		d.size = end;
1576		d.change(Usize);
1577	}
1578	d.update();
1579
1580	nwrite := 0;
1581	while(count > 0){
1582		if(d.iob == nil){
1583			# must check and reacquire entry
1584			(d, e) = Dentry.getd(file, Bread|Bmod);
1585			if(d == nil)
1586				return ferr(f, e, file, nil);
1587		}
1588		addr := int (offset / big BUFSIZE);
1589		o := int (offset % big BUFSIZE);
1590		n := BUFSIZE - o;
1591		if(n > count)
1592			n = count;
1593		qpath := int d.qid.path;
1594		p1 := d.getblk1(addr, Tfile);
1595		if(p1 == nil)
1596			return ferr(f, Efull, file, nil);
1597		if(p1.checktag(Tfile, qpath)){
1598			p1.put();
1599			return ferr(f, Ealloc, file, nil);
1600		}
1601		p1.iobuf[o:] = f.data[nwrite:nwrite+n];
1602		p1.flags |= Bmod;
1603		p1.put();
1604		count -= n;
1605		nwrite += n;
1606		offset += big n;
1607	}
1608	d.put();
1609	file.unlock();
1610	return ref Rmsg.Write(f.tag, nwrite);
1611}
1612
1613doremove(f: ref File, iscon: int): string
1614{
1615	if(isro(f.fs) || f.cons == 0 && (writegroup && !ingroup(f.uid, writegroup)))
1616		return Eronly;
1617	#
1618	# check permission on parent directory of file to be deleted
1619	#
1620	if(f.wpath == nil || f.wpath.addr == f.addr)
1621		return Ephase;
1622	(d1, e1) := Dentry.geta(f.fs, f.wpath.addr, f.wpath.slot, QPNONE, Bread);
1623	if(e1 != nil)
1624		return e1;
1625	if(!iscon && f.access(d1, DWRITE)){
1626		d1.put();
1627		return Eaccess;
1628	}
1629	d1.access(FWRITE, f.uid);
1630	d1.put();
1631
1632	#
1633	# check on file to be deleted
1634	#
1635	(d, e) := Dentry.getd(f, Bread);
1636	if(e != nil)
1637		return e;
1638
1639	#
1640	# if deleting a directory, make sure it is empty
1641	#
1642	if(d.mode & DDIR)
1643	for(addr:=0; (p1 := d.getblk(addr, 0)) != nil; addr++){
1644		if(p1.checktag(Tdir, int d.qid.path)){
1645			p1.put();
1646			d.put();
1647			return Ephase;
1648		}
1649		for(slot:=0; slot<DIRPERBUF; slot++){
1650			d1 = Dentry.get(p1, slot);
1651			if(!(d1.mode & DALLOC))
1652				continue;
1653			p1.put();
1654			d.put();
1655			return Eempty;
1656		}
1657		p1.put();
1658	}
1659
1660	#
1661	# do it
1662	#
1663	d.trunc(f.uid);
1664	d.buf[0:] = emptyblock[0:Dentrysize];
1665	d.put();
1666	return nil;
1667}
1668
1669clunk(cp: ref Chan, file: ref File, remove: int, wok: int): string
1670{
1671	if((t := file.tlock) != nil){
1672		if(t.file == file)
1673			t.time = 0;		# free the lock
1674		file.tlock = nil;
1675	}
1676	if(remove)
1677		error := doremove(file, wok);
1678	file.open = 0;
1679	file.wpath = nil;
1680	cp.putfid(file);
1681
1682	return error;
1683}
1684
1685rclunk(cp: ref Chan, t: ref Tmsg.Clunk): ref Rmsg
1686{
1687	if((file := cp.getfid(t.fid, 0)) == nil)
1688		return err(t, Efid);
1689	clunk(cp, file, file.open & FREMOV, 0);
1690	return ref Rmsg.Clunk(t.tag);
1691}
1692
1693rremove(cp: ref Chan, t: ref Tmsg.Remove): ref Rmsg
1694{
1695	if((file := cp.getfid(t.fid, 0)) == nil)
1696		return err(t, Efid);
1697	e :=  clunk(cp, file, 1, cp == conschan);
1698	if(e != nil)
1699		return err(t, e);
1700	return ref Rmsg.Remove(t.tag);
1701}
1702
1703rstat(cp: ref Chan, f: ref Tmsg.Stat): ref Rmsg
1704{
1705	if((file := cp.getfid(f.fid, 0)) == nil)
1706		return err(f, Efid);
1707	(d, e) := Dentry.getd(file, Bread);
1708	if(d == nil)
1709		return ferr(f, e, file, nil);
1710	dir := dir9p2(d);
1711	if(d.qid.path == big QPROOT)	# stat of root gives time
1712		dir.atime = now();
1713	d.put();
1714	if(styx->packdirsize(dir) > cp.msize-IOHDRSZ)
1715		return ferr(f, Ersc, file, nil);
1716	file.unlock();
1717
1718	return ref Rmsg.Stat(f.tag, dir);
1719}
1720
1721rwstat(cp: ref Chan, f: ref Tmsg.Wstat): ref Rmsg
1722{
1723	if((file := cp.getfid(f.fid, 0)) == nil)
1724		return err(f, Efid);
1725
1726	# if user none, can't do anything unless in allow mode
1727
1728	if(file.uid == None && !wstatallow)
1729		return ferr(f, Eaccess, file, nil);
1730
1731	if(isro(file.fs) || (writegroup && !ingroup(file.uid, writegroup)))
1732		return ferr(f, Eronly, file, nil);
1733
1734	#
1735	# first get parent
1736	#
1737	p1: ref Iobuf;
1738	d1: ref Dentry;
1739	if(file.wpath != nil){
1740		p1 = Iobuf.get(file.fs, file.wpath.addr, Bread);
1741		if(p1 == nil)
1742			return ferr(f, Ephase, file, p1);
1743		d1 = Dentry.get(p1, file.wpath.slot);
1744		if(d1 == nil || p1.checktag(Tdir, QPNONE) || !(d1.mode & DALLOC))
1745			return ferr(f, Ephase, file, p1);
1746	}
1747
1748	#
1749	# now the file
1750	#
1751	(d, e) := Dentry.getd(file, Bread);
1752	if(d == nil)
1753		return ferr(f, e, file, p1);
1754
1755	#
1756	# Convert the message and fix up
1757	# fields not to be changed.
1758	#
1759	dir := f.stat;
1760	if(dir.uid == nil)
1761		uid := d.uid;
1762	else
1763		uid = strtouid(dir.uid);
1764	if(dir.gid == nil)
1765		gid := d.gid;
1766	else
1767		gid = strtouid(dir.gid);
1768	if(dir.name == nil)
1769		dir.name = d.name;
1770	else{
1771		if((l := checkname9p2(dir.name)) == 0){
1772			d.put();
1773			return ferr(f, Ename, file, p1);
1774		}
1775		if(l+1 > NAMELEN){
1776			d.put();
1777			return ferr(f, Etoolong, file, p1);
1778		}
1779	}
1780
1781	# Before doing sanity checks, find out what the
1782	# new 'mode' should be:
1783	# if 'type' and 'mode' are both defaults, take the
1784	# new mode from the old directory entry;
1785	# else if 'type' is the default, use the new mode entry;
1786	# else if 'mode' is the default, create the new mode from
1787	# 'type' or'ed with the old directory mode;
1788	# else neither are defaults, use the new mode but check
1789	# it agrees with 'type'.
1790
1791	if(dir.qid.qtype == 16rFF && dir.mode == ~0){
1792		dir.mode = d.mode & 8r777;
1793		if(d.mode & DLOCK)
1794			dir.mode |= DMEXCL;
1795		if(d.mode & DAPND)
1796			dir.mode |= DMAPPEND;
1797		if(d.mode & DDIR)
1798			dir.mode |= DMDIR;
1799	}
1800	else if(dir.qid.qtype == 16rFF){
1801		# nothing to do
1802	}
1803	else if(dir.mode == ~0)
1804		dir.mode = (dir.qid.qtype<<24)|(d.mode & 8r777);
1805	else if(dir.qid.qtype != ((dir.mode>>24) & 16rFF)){
1806		d.put();
1807		return ferr(f, Eqidmode, file, p1);
1808	}
1809
1810	# Check for unknown type/mode bits
1811	# and an attempt to change the directory bit.
1812
1813	if(dir.mode & ~(DMDIR|DMAPPEND|DMEXCL|8r777)){
1814		d.put();
1815		return ferr(f, Enotm, file, p1);
1816	}
1817	if(d.mode & DDIR)
1818		mode := DMDIR;
1819	else
1820		mode = 0;
1821	if((dir.mode^mode) & DMDIR){
1822		d.put();
1823		return ferr(f, Enotd, file, p1);
1824	}
1825
1826	if(dir.mtime == ~0)
1827		dir.mtime = d.mtime;
1828	if(dir.length == ~big 0)
1829		dir.length = big d.size;
1830
1831
1832	# Currently, can't change length.
1833
1834	if(dir.length != big d.size){
1835		d.put();
1836		return ferr(f, Enotl, file, p1);
1837	}
1838
1839
1840	# if chown,
1841	# must be god
1842	# wstatallow set to allow chown during boot
1843
1844	if(uid != d.uid && !wstatallow){
1845		d.put();
1846		return ferr(f, Enotu, file, p1);
1847	}
1848
1849	# if chgroup,
1850	# must be either
1851	#	a) owner and in new group
1852	#	b) leader of both groups
1853	# wstatallow and writeallow are set to allow chgrp during boot
1854
1855	while(gid != d.gid){
1856		if(wstatallow || writeallow)
1857			break;
1858		if(d.uid == file.uid && ingroup(file.uid, gid))
1859			break;
1860		if(leadgroup(file.uid, gid))
1861			if(leadgroup(file.uid, d.gid))
1862				break;
1863		d.put();
1864		return ferr(f, Enotg, file, p1);
1865	}
1866
1867	# if rename,
1868	# must have write permission in parent
1869
1870	while(d.name != dir.name){
1871
1872		# drop entry to prevent deadlock, then
1873		# check that destination name is valid and unique
1874
1875		d.put();
1876		if(checkname9p2(dir.name) == 0 || d1 == nil)
1877			return ferr(f, Ename, file, p1);
1878		if(dir.name == "." || dir.name == "..")
1879			return ferr(f, Edot, file, p1);
1880
1881
1882		for(addr := 0; ; addr++){
1883			if((p := d1.getblk(addr, 0)) == nil)
1884				break;
1885			if(p.checktag(Tdir, int d1.qid.path)){
1886				p.put();
1887				continue;
1888			}
1889			for(slot := 0; slot < DIRPERBUF; slot++){
1890				d = Dentry.get(p, slot);
1891				if(!(d.mode & DALLOC))
1892					continue;
1893				if(dir.name == d.name){
1894					p.put();
1895					return ferr(f, Eexist, file, p1);
1896				}
1897			}
1898			p.put();
1899		}
1900
1901		# reacquire entry
1902
1903		(d, nil) = Dentry.getd(file, Bread);
1904		if(d == nil)
1905			return ferr(f, Ephase, file, p1);
1906
1907		if(wstatallow || writeallow) # set to allow rename during boot
1908			break;
1909		if(d1 == nil || file.access(d1, DWRITE)){
1910			d.put();
1911			return ferr(f, Eaccess, file, p1);
1912		}
1913		break;
1914	}
1915
1916	# if mode/time, either
1917	#	a) owner
1918	#	b) leader of either group
1919
1920	mode = dir.mode & 8r777;
1921	if(dir.mode & DMAPPEND)
1922		mode |= DAPND;
1923	if(dir.mode & DMEXCL)
1924		mode |= DLOCK;
1925	while(d.mtime != dir.mtime || ((d.mode^mode) & (DAPND|DLOCK|8r777))){
1926		if(wstatallow)			# set to allow chmod during boot
1927			break;
1928		if(d.uid == file.uid)
1929			break;
1930		if(leadgroup(file.uid, gid))
1931			break;
1932		if(leadgroup(file.uid, d.gid))
1933			break;
1934		d.put();
1935		return ferr(f, Enotu, file, p1);
1936	}
1937	d.mtime = dir.mtime;
1938	d.uid = uid;
1939	d.gid = gid;
1940	d.mode = (mode & (DAPND|DLOCK|8r777)) | (d.mode & (DALLOC|DDIR));
1941
1942	d.name = dir.name;
1943	d.access(FWSTAT, file.uid);
1944	d.change(~0);
1945	d.put();
1946
1947	if(p1 != nil)
1948		p1.put();
1949	file.unlock();
1950
1951	return ref Rmsg.Wstat(f.tag);
1952}
1953
1954superok(set: int): int
1955{
1956	sb := Superb.get(thedevice, Bread|Bmod|Bimm);
1957	ok := sb.fsok;
1958	sb.fsok = set;
1959	if(debug)
1960		sb.print();
1961	sb.touched();
1962	sb.put();
1963	return ok;
1964}
1965
1966# little-endian
1967get2(a: array of byte, o: int): int
1968{
1969	return (int a[o+1]<<8) | int a[o];
1970}
1971
1972get2s(a: array of byte, o: int): int
1973{
1974	v := (int a[o+1]<<8) | int a[o];
1975	if(v & 16r8000)
1976		v |= ~0 << 8;
1977	return v;
1978}
1979
1980get4(a: array of byte, o: int): int
1981{
1982	return (int a[o+3]<<24) | (int a[o+2] << 16) | (int a[o+1]<<8) | int a[o];
1983}
1984
1985put2(a: array of byte, o: int, v: int)
1986{
1987	a[o] = byte v;
1988	a[o+1] = byte (v>>8);
1989}
1990
1991put4(a: array of byte, o: int, v: int)
1992{
1993	a[o] = byte v;
1994	a[o+1] = byte (v>>8);
1995	a[o+2] = byte (v>>16);
1996	a[o+3] = byte (v>>24);
1997}
1998
1999Tag.unpack(a: array of byte): Tag
2000{
2001	return Tag(get2(a,2), get4(a,4));
2002}
2003
2004Tag.pack(t: self Tag, a: array of byte)
2005{
2006	put2(a, 0, 0);
2007	put2(a, 2, t.tag);
2008	if(t.path != QPNONE)
2009		put4(a, 4, t.path & ~QPDIR);
2010}
2011
2012Superb.get(dev: ref Device, flags: int): ref Superb
2013{
2014	p := Iobuf.get(dev, SUPERADDR, flags);
2015	if(p == nil)
2016		return nil;
2017	if(p.checktag(Tsuper, QPSUPER)){
2018		p.put();
2019		return nil;
2020	}
2021	sb := Superb.unpack(p.iobuf);
2022	sb.iob = p;
2023	return sb;
2024}
2025
2026Superb.touched(s: self ref Superb)
2027{
2028	s.iob.flags |= Bmod;
2029}
2030
2031Superb.put(sb: self ref Superb)
2032{
2033	if(sb.iob == nil)
2034		return;
2035	if(sb.iob.flags & Bmod)
2036		sb.pack(sb.iob.iobuf);
2037	sb.iob.put();
2038	sb.iob = nil;
2039}
2040
2041#  this is the disk structure
2042# Superb:
2043#	Super1;
2044#	Fbuf	fbuf;
2045# Fbuf:
2046#	nfree[4]
2047#	free[]	# based on BUFSIZE
2048#  Super1:
2049#	long	fstart;
2050#	long	fsize;
2051#	long	tfree;
2052#	long	qidgen;		# generator for unique ids
2053#	long	fsok;		# file system ok
2054#	long	roraddr;	# dump root addr
2055#	long	last;		# last super block addr
2056#	long	next;		# next super block addr
2057
2058Ofstart: con 0;
2059Ofsize: con Ofstart+4;
2060Otfree: con Ofsize+4;
2061Oqidgen: con Otfree+4;
2062Ofsok: con Oqidgen+4;
2063Ororaddr: con Ofsok+4;
2064Olast: con Ororaddr+4;
2065Onext: con Olast+4;
2066Super1size: con Onext+4;
2067
2068Superb.unpack(a: array of byte): ref Superb
2069{
2070	s := ref Superb;
2071	s.fstart = get4(a, Ofstart);
2072	s.fsize = get4(a, Ofsize);
2073	s.tfree = get4(a, Otfree);
2074	s.qidgen = get4(a, Oqidgen);
2075	s.fsok = get4(a, Ofsok);
2076	s.fbuf = a[Super1size:];
2077	return s;
2078}
2079
2080Superb.pack(s: self ref Superb, a: array of byte)
2081{
2082	put4(a, Ofstart, s.fstart);
2083	put4(a, Ofsize, s.fsize);
2084	put4(a, Otfree, s.tfree);
2085	put4(a, Oqidgen, s.qidgen);
2086	put4(a, Ofsok, s.fsok);
2087}
2088
2089Superb.print(sb: self ref Superb)
2090{
2091	sys->print("fstart=%ud fsize=%ud tfree=%ud qidgen=%ud fsok=%d\n",
2092		sb.fstart, sb.fsize, sb.tfree, sb.qidgen, sb.fsok);
2093}
2094
2095Dentry.get(p: ref Iobuf, slot: int): ref Dentry
2096{
2097	if(p == nil)
2098		return nil;
2099	buf := p.iobuf[(slot%DIRPERBUF)*Dentrysize:];
2100	d := Dentry.unpack(buf);
2101	d.iob = p;
2102	d.buf = buf;
2103	return d;
2104}
2105
2106Dentry.geta(fs: ref Device, addr: int, slot: int, qpath: int, mode: int): (ref Dentry, string)
2107{
2108	p := Iobuf.get(fs, addr, mode);
2109	if(p == nil || p.checktag(Tdir, qpath)){
2110		if(p != nil)
2111			p.put();
2112		return (nil, Ealloc);
2113	}
2114	d := Dentry.get(p, slot);
2115	if(d == nil || !(d.mode & DALLOC)){
2116		p.put();
2117		return (nil, Ealloc);
2118	}
2119	return (d, nil);
2120}
2121
2122Dentry.getd(file: ref File, mode: int): (ref Dentry, string)
2123{
2124	(d, e) := Dentry.geta(file.fs, file.addr, file.slot, QPNONE, mode);	# QPNONE should be file.wpath's path
2125	if(e != nil)
2126		return (nil, e);
2127	if(file.qid.path != d.qid.path || (file.qid.qtype&QTDIR) != (d.qid.qtype&QTDIR)){
2128		d.put();
2129		return (nil, Eqid);
2130	}
2131	return (d, nil);
2132}
2133
2134#  this is the disk structure:
2135#	char	name[NAMELEN];
2136#	short	uid;
2137#	short	gid;		[2*2]
2138#	ushort	mode;
2139#		#define	DALLOC	0x8000
2140#		#define	DDIR	0x4000
2141#		#define	DAPND	0x2000
2142#		#define	DLOCK	0x1000
2143#		#define	DREAD	0x4
2144#		#define	DWRITE	0x2
2145#		#define	DEXEC	0x1
2146#	[ushort muid]		[2*2]
2147#	Qid.path;			[4]
2148#	Qid.version;		[4]
2149#	long	size;			[4]
2150#	long	dblock[NDBLOCK];
2151#	long	iblock;
2152#	long	diblock;
2153#	long	atime;
2154#	long	mtime;
2155
2156Oname: con 0;
2157Ouid: con Oname+NAMELEN;
2158Ogid: con Ouid+2;
2159Omode: con Ogid+2;
2160Omuid: con Omode+2;
2161Opath: con Omuid+2;
2162Overs: con Opath+4;
2163Osize: con Overs+4;
2164Odblock: con Osize+4;
2165Oiblock: con Odblock+NDBLOCK*4;
2166Odiblock: con Oiblock+4;
2167Oatime: con Odiblock+4;
2168Omtime: con Oatime+4;
2169Dentrysize: con Omtime+4;
2170
2171Dentry.unpack(a: array of byte): ref Dentry
2172{
2173	d := ref Dentry;
2174	for(i:=0; i<NAMELEN; i++)
2175		if(int a[i] == 0)
2176			break;
2177	d.name = string a[0:i];
2178	d.uid = get2s(a, Ouid);
2179	d.gid = get2s(a, Ogid);
2180	d.mode = get2(a, Omode);
2181	d.muid = get2(a, Omuid);	# note: not set by Plan 9's kfs
2182	d.qid = mkqid(get4(a, Opath), get4(a, Overs), d.mode);
2183	d.size = big get4(a, Osize) & big 16rFFFFFFFF;
2184	d.atime = get4(a, Oatime);
2185	d.mtime = get4(a, Omtime);
2186	d.mod = 0;
2187	return d;
2188}
2189
2190Dentry.change(d: self ref Dentry, f: int)
2191{
2192	d.mod |= f;
2193}
2194
2195Dentry.update(d: self ref Dentry)
2196{
2197	f := d.mod;
2198	d.mod = 0;
2199	if(d.iob == nil || (d.iob.flags & Bmod) == 0){
2200		if(f != 0)
2201			panic("Dentry.update");
2202		return;
2203	}
2204	a := d.buf;
2205	if(f & Uname){
2206		b := array of byte d.name;
2207		for(i := 0; i < NAMELEN; i++)
2208			if(i < len b)
2209				a[i] = b[i];
2210			else
2211				a[i] = byte 0;
2212	}
2213	if(f & Uids){
2214		put2(a, Ouid, d.uid);
2215		put2(a, Ogid, d.gid);
2216	}
2217	if(f & Umode)
2218		put2(a, Omode, d.mode);
2219	if(f & Uqid){
2220		path := int d.qid.path;
2221		if(d.mode & DDIR)
2222			path |= QPDIR;
2223		put4(a, Opath, path);
2224		put4(a, Overs, d.qid.vers);
2225	}
2226	if(f & Usize)
2227		put4(a, Osize, int d.size);
2228	if(f & Utime){
2229		put4(a, Omtime, d.mtime);
2230		put4(a, Oatime, d.atime);
2231	}
2232	d.iob.flags |= Bmod;
2233}
2234
2235Dentry.access(d: self ref Dentry, f: int, uid: int)
2236{
2237	if((p := d.iob) != nil && !readonly){
2238		if((f & (FWRITE|FWSTAT)) == 0 && noatime)
2239			return;
2240		if(f & (FREAD|FWRITE|FWSTAT)){
2241			d.atime = now();
2242			put4(d.buf, Oatime, d.atime);
2243			p.flags |= Bmod;
2244		}
2245		if(f & FWRITE){
2246			d.mtime = now();
2247			put4(d.buf, Omtime, d.mtime);
2248			d.muid = uid;
2249			put2(d.buf, Omuid, uid);
2250			d.qid.vers++;
2251			put4(d.buf, Overs, d.qid.vers);
2252			p.flags |= Bmod;
2253		}
2254	}
2255}
2256
2257#
2258# release the directory entry buffer and thus the
2259# lock on both buffer and entry, typically during i/o,
2260# to be reacquired later if needed
2261#
2262Dentry.release(d: self ref Dentry)
2263{
2264	if(d.iob != nil){
2265		d.update();
2266		d.iob.put();
2267		d.iob = nil;
2268		d.buf = nil;
2269	}
2270}
2271
2272Dentry.getblk(d: self ref Dentry, a: int, tag: int): ref Iobuf
2273{
2274	addr := d.rel2abs(a, tag, 0);
2275	if(addr == 0)
2276		return nil;
2277	return Iobuf.get(thedevice, addr, Bread);
2278}
2279
2280#
2281# same as Dentry.buf but calls d.release
2282# to reduce interference.
2283#
2284Dentry.getblk1(d: self ref Dentry, a: int, tag: int): ref Iobuf
2285{
2286	addr := d.rel2abs(a, tag, 1);
2287	if(addr == 0)
2288		return nil;
2289	return Iobuf.get(thedevice, addr, Bread);
2290}
2291
2292Dentry.rel2abs(d: self ref Dentry, a: int, tag: int, putb: int): int
2293{
2294	if(a < 0){
2295		sys->print("Dentry.rel2abs: neg\n");
2296		return 0;
2297	}
2298	p := d.iob;
2299	if(p == nil || d.buf == nil)
2300		panic("nil iob");
2301	data := d.buf;
2302	qpath := int d.qid.path;
2303	dev := p.dev;
2304	if(a < NDBLOCK){
2305		addr := get4(data, Odblock+a*4);
2306		if(addr == 0 && tag){
2307			addr = balloc(dev, tag, qpath);
2308			put4(data, Odblock+a*4, addr);
2309			p.flags |= Bmod|Bimm;
2310		}
2311		if(putb)
2312			d.release();
2313		return addr;
2314	}
2315	a -= NDBLOCK;
2316	if(a < INDPERBUF){
2317		addr := get4(data, Oiblock);
2318		if(addr == 0 && tag){
2319			addr = balloc(dev, Tind1, qpath);
2320			put4(data, Oiblock, addr);
2321			p.flags |= Bmod|Bimm;
2322		}
2323		if(putb)
2324			d.release();
2325		return  indfetch(dev, qpath, addr, a, Tind1, tag);
2326	}
2327	a -= INDPERBUF;
2328	if(a < INDPERBUF2){
2329		addr := get4(data, Odiblock);
2330		if(addr == 0 && tag){
2331			addr = balloc(dev, Tind2, qpath);
2332			put4(data, Odiblock, addr);
2333			p.flags |= Bmod|Bimm;
2334		}
2335		if(putb)
2336			d.release();
2337		addr = indfetch(dev, qpath, addr, a/INDPERBUF, Tind2, Tind1);
2338		return indfetch(dev, qpath, addr, a%INDPERBUF, Tind1, tag);
2339	}
2340	if(putb)
2341		d.release();
2342	sys->print("Dentry.buf: trip indirect\n");
2343	return 0;
2344}
2345
2346indfetch(dev: ref Device, path: int, addr: int, a: int, itag: int, tag: int): int
2347{
2348	if(addr == 0)
2349		return 0;
2350	bp := Iobuf.get(dev, addr, Bread);
2351	if(bp == nil){
2352		sys->print("ind fetch bp = nil\n");
2353		return 0;
2354	}
2355	if(bp.checktag(itag, path)){
2356		sys->print("ind fetch tag\n");
2357		bp.put();
2358		return 0;
2359	}
2360	addr = get4(bp.iobuf, a*4);
2361	if(addr == 0 && tag){
2362		addr = balloc(dev, tag, path);
2363		if(addr != 0){
2364			put4(bp.iobuf, a*4, addr);
2365			bp.flags |= Bmod;
2366			if(localfs || tag == Tdir)
2367				bp.flags |= Bimm;
2368			bp.settag(itag, path);
2369		}
2370	}
2371	bp.put();
2372	return addr;
2373}
2374
2375balloc(dev: ref Device, tag: int, qpath: int): int
2376{
2377	# TO DO: cache superblock to reduce pack/unpack
2378	sb := Superb.get(dev, Bread|Bmod);
2379	if(sb == nil)
2380		panic("balloc: super block");
2381	n := get4(sb.fbuf, 0);
2382	n--;
2383	sb.tfree--;
2384	if(n < 0 || n >= FEPERBUF)
2385		panic("balloc: bad freelist");
2386	a := get4(sb.fbuf, 4+n*4);
2387	if(n == 0){
2388		if(a == 0){
2389			sb.tfree = 0;
2390			sb.touched();
2391			sb.put();
2392			return 0;
2393		}
2394		bp := Iobuf.get(dev, a, Bread);
2395		if(bp == nil || bp.checktag(Tfree, QPNONE)){
2396			if(bp != nil)
2397				bp.put();
2398			sb.put();
2399			return 0;
2400		}
2401		sb.fbuf[0:] = bp.iobuf[0:(FEPERBUF+1)*4];
2402		sb.touched();
2403		bp.put();
2404	}else{
2405		put4(sb.fbuf, 0, n);
2406		sb.touched();
2407	}
2408	bp := Iobuf.get(dev, a, Bmod);
2409	bp.iobuf[0:] = emptyblock;
2410	bp.settag(tag, qpath);
2411	if(tag == Tind1 || tag == Tind2 || tag == Tdir)
2412		bp.flags |= Bimm;
2413	bp.put();
2414	sb.put();
2415	return a;
2416}
2417
2418bfree(dev: ref Device, addr: int, d: int)
2419{
2420	if(addr == 0)
2421		return;
2422	if(d > 0){
2423		d--;
2424		p := Iobuf.get(dev, addr, Bread);
2425		if(p != nil){
2426			for(i:=INDPERBUF-1; i>=0; i--){
2427				a := get4(p.iobuf, i*4);
2428				bfree(dev, a, d);
2429			}
2430			p.put();
2431		}
2432	}
2433
2434	# stop outstanding i/o
2435	p := Iobuf.get(dev, addr, Bprobe);
2436	if(p != nil){
2437		p.flags &= ~(Bmod|Bimm);
2438		p.put();
2439	}
2440
2441	s := Superb.get(dev, Bread|Bmod);
2442	if(s == nil)
2443		panic("bfree: super block");
2444	addfree(dev, addr, s);
2445	s.put();
2446}
2447
2448addfree(dev: ref Device, addr: int, sb: ref Superb)
2449{
2450	if(addr >= sb.fsize){
2451		sys->print("addfree: bad addr %ud\n", addr);
2452		return;
2453	}
2454	n := get4(sb.fbuf, 0);
2455	if(n < 0 || n > FEPERBUF)
2456		panic("addfree: bad freelist");
2457	if(n >= FEPERBUF){
2458		p := Iobuf.get(dev, addr, Bmod);
2459		if(p == nil)
2460			panic("addfree: Iobuf.get");
2461		p.iobuf[0:] = sb.fbuf[0:(1+FEPERBUF)*4];
2462		sb.fbuf[0:] = emptyblock[0:(1+FEPERBUF)*4];	# clear it for debugging
2463		p.settag(Tfree, QPNONE);
2464		p.put();
2465		n = 0;
2466	}
2467	put4(sb.fbuf, 4+n*4, addr);
2468	put4(sb.fbuf, 0, n+1);
2469	sb.tfree++;
2470	if(addr >= sb.fsize)
2471		sb.fsize = addr+1;
2472	sb.touched();
2473}
2474
2475qidpathgen(dev: ref Device): int
2476{
2477	sb := Superb.get(dev, Bread|Bmod);
2478	if(sb == nil)
2479		panic("qidpathgen: super block");
2480	sb.qidgen++;
2481	path := sb.qidgen;
2482	sb.touched();
2483	sb.put();
2484	return path;
2485}
2486
2487Dentry.trunc(d: self ref Dentry, uid: int)
2488{
2489	p := d.iob;
2490	data := d.buf;
2491	bfree(p.dev, get4(data, Odiblock), 2);
2492	put4(data, Odiblock, 0);
2493	bfree(p.dev, get4(data, Oiblock), 1);
2494	put4(data, Oiblock, 0);
2495	for(i:=NDBLOCK-1; i>=0; i--){
2496		bfree(p.dev, get4(data, Odblock+i*4), 0);
2497		put4(data, Odblock+i*4, 0);
2498	}
2499	d.size = big 0;
2500	d.change(Usize);
2501	p.flags |= Bmod|Bimm;
2502	d.access(FWRITE, uid);
2503	d.update();
2504}
2505
2506Dentry.put(d: self ref Dentry)
2507{
2508	p := d.iob;
2509	if(p == nil || d.buf == nil)
2510		return;
2511	d.update();
2512	p.put();
2513	d.iob = nil;
2514	d.buf = nil;
2515}
2516
2517Dentry.print(d: self ref Dentry)
2518{
2519	sys->print("name=%#q uid=%d gid=%d mode=#%8.8ux qid.path=#%bux qid.vers=%ud size=%bud\n",
2520		d.name, d.uid, d.gid, d.mode, d.qid.path, d.qid.vers, d.size);
2521	p := d.iob;
2522	if(p != nil && (data := p.iobuf) != nil){
2523		sys->print("\tdblock=");
2524		for(i := 0; i < NDBLOCK; i++)
2525			sys->print(" %d", get4(data, Odblock+i*4));
2526		sys->print(" iblock=%ud diblock=%ud\n", get4(data, Oiblock), get4(data, Odiblock));
2527	}
2528}
2529
2530HWidth: con 5;	# buffers per line
2531
2532hiob: array of ref Hiob;
2533
2534iobufinit(niob: int)
2535{
2536	nhiob := niob/HWidth;
2537	while(!prime(nhiob))
2538		nhiob++;
2539	hiob = array[nhiob] of {* => ref Hiob(nil, Lock.new(), 0)};
2540	# allocate the buffers now
2541	for(i := 0; i < len hiob; i++){
2542		h := hiob[i];
2543		while(h.niob < HWidth)
2544			h.newbuf();
2545	}
2546}
2547
2548iobufclear()
2549{
2550	# eliminate the cyclic references
2551	for(i := 0; i < len hiob; i++){
2552		h := hiob[i];
2553		while(--h.niob >= 0){
2554			p := hiob[i].link;
2555			hiob[i].link = p.fore;
2556			p.fore = p.back = nil;
2557			p = nil;
2558		}
2559	}
2560}
2561
2562prime(n: int): int
2563{
2564	if((n%2) == 0)
2565		return 0;
2566	for(i:=3;; i+=2) {
2567		if((n%i) == 0)
2568			return 0;
2569		if(i*i >= n)
2570			return 1;
2571	}
2572}
2573
2574Hiob.newbuf(hb: self ref Hiob): ref Iobuf
2575{
2576	# hb must be locked
2577	p := ref Iobuf;
2578	p.qlock = chan[1] of int;
2579	q := hb.link;
2580	if(q != nil){
2581		p.fore = q;
2582		p.back = q.back;
2583		q.back = p;
2584		p.back.fore = p;
2585	}else{
2586		hb.link = p;
2587		p.fore = p;
2588		p.back = p;
2589	}
2590	p.dev = devnone;
2591	p.addr = -1;
2592	p.flags = 0;
2593	p.xiobuf = array[RBUFSIZE] of byte;
2594	hb.niob++;
2595	return p;
2596}
2597
2598Iobuf.get(dev: ref Device, addr: int, flags: int): ref Iobuf
2599{
2600	hb := hiob[addr%len hiob];
2601	p: ref Iobuf;
2602Search:
2603	for(;;){
2604		hb.lk.lock();
2605		s := hb.link;
2606
2607		# see if it's active
2608		p = s;
2609		do{
2610			if(p.addr == addr && p.dev == dev){
2611				if(p != s){
2612					p.back.fore = p.fore;
2613					p.fore.back = p.back;
2614					p.fore = s;
2615					p.back = s.back;
2616					s.back = p;
2617					p.back.fore = p;
2618					hb.link = p;
2619				}
2620				hb.lk.unlock();
2621				p.lock();
2622				if(p.addr != addr || p.dev != dev){
2623					# lost race
2624					p.unlock();
2625					continue Search;
2626				}
2627				p.flags |= flags;
2628				p.iobuf = p.xiobuf;
2629				return p;
2630			}
2631		}while((p = p.fore) != s);
2632		if(flags == Bprobe){
2633			hb.lk.unlock();
2634			return nil;
2635		}
2636
2637		# steal the oldest unlocked buffer
2638		do{
2639			p = s.back;
2640			if(p.canlock()){
2641				# TO DO: if Bmod, write it out and restart Hashed
2642				# for now we needn't because Iobuf.put is synchronous
2643				if(p.flags & Bmod)
2644					sys->print("Bmod unexpected (%ud)\n", p.addr);
2645				hb.link = p;
2646				p.dev = dev;
2647				p.addr = addr;
2648				p.flags = flags;
2649				break Search;
2650			}
2651			s = p;
2652		}while(p != hb.link);
2653
2654		# no unlocked blocks available; add a new one
2655		p = hb.newbuf();
2656		p.lock();	# return it locked
2657		break;
2658	}
2659
2660	p.dev = dev;
2661	p.addr = addr;
2662	p.flags = flags;
2663	hb.lk.unlock();
2664	p.iobuf = p.xiobuf;
2665	if(flags & Bread){
2666		if(wrenread(dev.fd, addr, p.iobuf)){
2667			eprint(sys->sprint("error reading block %ud: %r", addr));
2668			p.flags = 0;
2669			p.dev = devnone;
2670			p.addr = -1;
2671			p.iobuf = nil;
2672			p.unlock();
2673			return nil;
2674		}
2675	}
2676	return p;
2677}
2678
2679Iobuf.put(p: self ref Iobuf)
2680{
2681	if(p.flags & Bmod)
2682		p.flags |= Bimm;	# temporary; see comment in Iobuf.get
2683	if(p.flags & Bimm){
2684		if(!(p.flags & Bmod))
2685			eprint(sys->sprint("imm and no mod (%d)", p.addr));
2686		if(!wrenwrite(p.dev.fd, p.addr, p.iobuf))
2687			p.flags &= ~(Bmod|Bimm);
2688		else
2689			panic(sys->sprint("error writing block %ud: %r", p.addr));
2690	}
2691	p.iobuf = nil;
2692	p.unlock();
2693}
2694
2695Iobuf.lock(p: self ref Iobuf)
2696{
2697	p.qlock <-= 1;
2698}
2699
2700Iobuf.canlock(p: self ref Iobuf): int
2701{
2702	alt{
2703	p.qlock <-= 1 =>
2704		return 1;
2705	* =>
2706		return 0;
2707	}
2708}
2709
2710Iobuf.unlock(p: self ref Iobuf)
2711{
2712	<-p.qlock;
2713}
2714
2715File.access(f: self ref File, d: ref Dentry, m: int): int
2716{
2717	if(wstatallow)
2718		return 0;
2719
2720	# none gets only other permissions
2721
2722	if(f.uid != None){
2723		if(f.uid == d.uid)	# owner
2724			if((m<<6) & d.mode)
2725				return 0;
2726		if(ingroup(f.uid, d.gid))	# group membership
2727			if((m<<3) & d.mode)
2728				return 0;
2729	}
2730
2731	#
2732	# other access for everyone except members of group "noworld"
2733	#
2734	if(m & d.mode){
2735		#
2736		# walk directories regardless.
2737		# otherwise it's impossible to get
2738		# from the root to noworld's directories.
2739		#
2740		if((d.mode & DDIR) && (m == DEXEC))
2741			return 0;
2742		if(!ingroup(f.uid, Noworld))
2743			return 0;
2744	}
2745	return 1;
2746}
2747
2748tagname(t: int): string
2749{
2750	case t {
2751	Tnone =>	return "Tnone";
2752	Tsuper =>	return "Tsuper";
2753	Tdir => return "Tdir";
2754	Tind1 => return "Tind1";
2755	Tind2 => return "Tind2";
2756	Tfile => return "Tfile";
2757	Tfree => return "Tfree";
2758	Tbuck => return "Tbuck";
2759	Tvirgo => return "Tvirgo";
2760	Tcache => return "Tcache";
2761	* =>	return sys->sprint("%d", t);
2762	}
2763}
2764
2765Iobuf.checktag(p: self ref Iobuf, tag: int, qpath: int): int
2766{
2767	t := Tag.unpack(p.iobuf[BUFSIZE:]);
2768	if(t.tag != tag){
2769		if(1)
2770			eprint(sys->sprint("	tag = %s; expected %s; addr = %ud\n",
2771				tagname(t.tag), tagname(tag), p.addr));
2772		return 2;
2773	}
2774	if(qpath != QPNONE){
2775		qpath &= ~QPDIR;
2776		if(qpath != t.path){
2777			if(qpath == (t.path&~QPDIR))	# old bug
2778				return 0;
2779			if(1)
2780				eprint(sys->sprint("	tag/path = %ux; expected %s/%ux\n",
2781					t.path, tagname(tag), qpath));
2782			return 1;
2783		}
2784	}
2785	return 0;
2786}
2787
2788Iobuf.settag(p: self ref Iobuf, tag: int, qpath: int)
2789{
2790	Tag(tag, qpath).pack(p.iobuf[BUFSIZE:]);
2791	p.flags |= Bmod;
2792}
2793
2794badmagic := 0;
2795wmagic := "kfs wren device\n";
2796
2797wrenream(dev: ref Device)
2798{
2799	if(RBUFSIZE % 512)
2800		panic(sys->sprint("kfs: bad buffersize(%d): restart a multiple of 512", RBUFSIZE));
2801	if(RBUFSIZE > MAXBUFSIZE)
2802		panic(sys->sprint("kfs: bad buffersize(%d): must be at most %d", RBUFSIZE, MAXBUFSIZE));
2803	sys->print("kfs: reaming the file system using %d byte blocks\n", RBUFSIZE);
2804	buf := array[RBUFSIZE] of {* => byte 0};
2805	buf[256:] = sys->aprint("%s%d\n", wmagic, RBUFSIZE);
2806	if(sys->seek(dev.fd, big 0, 0) < big 0 || sys->write(dev.fd, buf, len buf) != len buf)
2807		panic("can't ream disk");
2808}
2809
2810wreninit(dev: ref Device): int
2811{
2812	(ok, nil) := sys->fstat(dev.fd);
2813	if(ok < 0)
2814		return 0;
2815	buf := array[MAXBUFSIZE] of byte;
2816	sys->seek(dev.fd, big 0, 0);
2817	n := sys->read(dev.fd, buf, len buf);
2818	if(n < len buf)
2819		return 0;
2820	badmagic = 0;
2821	RBUFSIZE = 1024;
2822	if(string buf[256:256+len wmagic] != wmagic){
2823		badmagic = 1;
2824		return 0;
2825	}
2826	RBUFSIZE = int string buf[256+len wmagic:256+len wmagic+12];
2827	if(RBUFSIZE % 512)
2828		error("bad block size");
2829	return 1;
2830}
2831
2832wrenread(fd: ref Sys->FD, addr: int, a: array of byte): int
2833{
2834	return sys->pread(fd, a, len a, big addr * big RBUFSIZE) != len a;
2835}
2836
2837wrenwrite(fd: ref Sys->FD, addr: int, a: array of byte): int
2838{
2839	return sys->pwrite(fd, a, len a, big addr * big RBUFSIZE) != len a;
2840}
2841
2842wrentag(buf: array of byte, tag: int, qpath: int): int
2843{
2844	t := Tag.unpack(buf[BUFSIZE:]);
2845	return t.tag != tag || (qpath&~QPDIR) != t.path;
2846}
2847
2848wrencheck(fd: ref Sys->FD): int
2849{
2850	if(badmagic)
2851		return 1;
2852	buf := array[RBUFSIZE] of byte;
2853	if(wrenread(fd, SUPERADDR, buf) || wrentag(buf, Tsuper, QPSUPER) ||
2854	    wrenread(fd, ROOTADDR, buf) || wrentag(buf, Tdir, QPROOT))
2855		return 1;
2856	d0 := Dentry.unpack(buf);
2857	if(d0.mode & DALLOC)
2858		return 0;
2859	return 1;
2860}
2861
2862wrensize(dev: ref Device): int
2863{
2864	(ok, d) := sys->fstat(dev.fd);
2865	if(ok < 0)
2866		return -1;
2867	return int (d.length / big RBUFSIZE);
2868}
2869
2870checkname9p2(s: string): int
2871{
2872	for(i := 0; i < len s; i++)
2873		if(s[i] <= 8r40)
2874			return 0;
2875	return styx->utflen(s);
2876}
2877
2878isro(d: ref Device): int
2879{
2880	return d == nil || d.ronly;
2881}
2882
2883tlocks: list of ref Tlock;
2884
2885tlocked(f: ref File, d: ref Dentry): ref Tlock
2886{
2887	tim := now();
2888	path := int d.qid.path;
2889	t1: ref Tlock;
2890	for(l := tlocks; l != nil; l = tl l){
2891		t := hd l;
2892		if(t.qpath == path && t.time >= tim && t.dev == f.fs)
2893			return nil;	# it's locked
2894		if(t.file == nil || t1 == nil && t.time < tim)
2895			t1 = t;
2896	}
2897	t := t1;
2898	if(t == nil)
2899		t = ref Tlock;
2900	t.dev = f.fs;
2901	t.qpath = path;
2902	t.time = tim + TLOCK;
2903	tlocks = t :: tlocks;
2904	return t;
2905}
2906
2907mkqid(path: int, vers: int, mode: int): Qid
2908{
2909	qid: Qid;
2910
2911	qid.path = big (path & ~QPDIR);
2912	qid.vers = vers;
2913	qid.qtype = 0;
2914	if(mode & DDIR)
2915		qid.qtype |= QTDIR;
2916	if(mode & DAPND)
2917		qid.qtype |= QTAPPEND;
2918	if(mode & DLOCK)
2919		qid.qtype |= QTEXCL;
2920	return qid;
2921}
2922
2923dir9p2(d: ref Dentry): Sys->Dir
2924{
2925	dir: Sys->Dir;
2926
2927	dir.name = d.name;
2928	dir.uid = uidtostr(d.uid);
2929	dir.gid = uidtostr(d.gid);
2930	dir.muid = uidtostr(d.muid);
2931	dir.qid = d.qid;
2932	dir.mode = d.mode & 8r777;
2933	if(d.mode & DDIR)
2934		dir.mode |= DMDIR;
2935	if(d.mode & DAPND)
2936		dir.mode |= DMAPPEND;
2937	if(d.mode & DLOCK)
2938		dir.mode |= DMEXCL;
2939	dir.atime = d.atime;
2940	dir.mtime = d.mtime;
2941	dir.length = big d.size;
2942	dir.dtype = 0;
2943	dir.dev = 0;
2944	return dir;
2945}
2946
2947rootream(dev: ref Device, addr: int)
2948{
2949	p := Iobuf.get(dev, addr, Bmod|Bimm);
2950	p.iobuf[0:] = emptyblock;
2951	p.settag(Tdir, QPROOT);
2952	d := Dentry.get(p, 0);
2953	d.name = "/";
2954	d.uid = -1;
2955	d.gid = -1;
2956	d.mode = DALLOC | DDIR |
2957		((DREAD|DWRITE|DEXEC) << 6) |
2958		((DREAD|DWRITE|DEXEC) << 3) |
2959		((DREAD|DWRITE|DEXEC) << 0);
2960	d.qid.path = big QPROOT;
2961	d.qid.vers = 0;
2962	d.qid.qtype = QTDIR;
2963	d.atime = now();
2964	d.mtime = d.atime;
2965	d.change(~0);
2966	d.access(FREAD|FWRITE, -1);
2967	d.update();
2968	p.put();
2969}
2970
2971superream(dev: ref Device, addr: int)
2972{
2973	fsize := wrensize(dev);
2974	if(fsize <= 0)
2975		panic("file system device size");
2976	p := Iobuf.get(dev, addr, Bmod|Bimm);
2977	p.iobuf[0:] = emptyblock;
2978	p.settag(Tsuper, QPSUPER);
2979	sb := ref Superb;
2980	sb.iob = p;
2981	sb.fstart = 1;
2982	sb.fsize = fsize;
2983	sb.qidgen = 10;
2984	sb.tfree = 0;
2985	sb.fsok = 0;
2986	sb.fbuf = p.iobuf[Super1size:];
2987	put4(sb.fbuf, 0, 1);	# nfree = 1
2988	for(i := fsize-1; i>=addr+2; i--)
2989		addfree(dev, i, sb);
2990	sb.put();
2991}
2992
2993eprint(s: string)
2994{
2995	sys->print("kfs: %s\n", s);
2996}
2997
2998#
2999# /adm/users
3000#
3001# uid:user:leader:members[,...]
3002
3003User: adt {
3004	uid:	int;
3005	name:	string;
3006	leader:	int;
3007	mem:	list of int;
3008};
3009
3010users: list of ref User;
3011
3012admusers := array[] of {
3013	(-1, "adm", "adm"),
3014	(None, "none", "adm"),
3015	(Noworld, "noworld", nil),
3016	(10000, "sys", nil),
3017	(10001, "upas", "upas"),
3018	(10002, "bootes", "bootes"),
3019	(10006, "inferno", nil),
3020};
3021
3022userinit()
3023{
3024	if(!cmd_users() && users == nil){
3025		cprint("initializing minimal user table");
3026		defaultusers();
3027	}
3028	writegroup = strtouid("write");
3029}
3030
3031cmd_users(): int
3032{
3033	if(kopen(FID1, FID2, array[] of {"adm", "users"}, OREAD) != nil)
3034		return 0;
3035	buf: array of byte;
3036	for(off := 0;;){
3037		(a, e) := kread(FID2, off, Styx->MAXFDATA);
3038		if(e != nil){
3039			cprint("/adm/users read error: "+e);
3040			return 0;
3041		}
3042		if(len a == 0)
3043			break;
3044		off += len a;
3045		if(buf != nil){
3046			c := array[len buf + len a] of byte;
3047			if(buf != nil)
3048				c[0:] = buf;
3049			c[len buf:] = a;
3050			buf = c;
3051		}else
3052			buf = a;
3053	}
3054	kclose(FID2);
3055
3056	# (uid:name:lead:mem,...\n)+
3057	(nl, lines) := sys->tokenize(string buf, "\n");
3058	if(nl == 0){
3059		cprint("empty /adm/users");
3060		return 0;
3061	}
3062	oldusers := users;
3063	users = nil;
3064
3065	# first pass: enter id:name
3066	for(l := lines; l != nil; l = tl l){
3067		uid, name, r: string;
3068		s := hd l;
3069		if(s == "" || s[0] == '#')
3070			continue;
3071		(uid, r) = field(s, ':');
3072		(name, r) = field(r, ':');
3073		if(uid == nil || name == nil || string int uid != uid){
3074			cprint("invalid /adm/users line: "+hd l);
3075			users = oldusers;
3076			return 0;
3077		}
3078		adduser(int uid, name, nil, nil);
3079	}
3080
3081	# second pass: groups and leaders
3082	for(l = lines; l != nil; l = tl l){
3083		s := hd l;
3084		if(s == "" || s[0] == '#')
3085			continue;
3086		name, lead, mem, r: string;
3087		(nil, r) = field(s, ':');	# skip id
3088		(name, r) = field(r, ':');
3089		(lead, mem) = field(r, ':');
3090		(nil, mems) := sys->tokenize(mem, ",\n");
3091		if(name == nil || lead == nil && mems == nil)
3092			continue;
3093		u := finduname(name);
3094		if(lead != nil){
3095			lu := strtouid(lead);
3096			if(lu != None)
3097				u.leader = lu;
3098			else if(lead != nil)
3099				u.leader = u.uid;	# mimic kfs not fs
3100		}
3101		mids: list of int = nil;
3102		for(; mems != nil; mems = tl mems){
3103			lu := strtouid(hd mems);
3104			if(lu != None)
3105				mids = lu :: mids;
3106		}
3107		u.mem = mids;
3108	}
3109
3110	if(debug)
3111	for(x := users; x != nil; x = tl x){
3112		u := hd x;
3113		sys->print("%d : %q : %d :", u.uid, u.name, u.leader);
3114		for(y := u.mem; y != nil; y = tl y)
3115			sys->print(" %d", hd y);
3116		sys->print("\n");
3117	}
3118	return 1;
3119}
3120
3121field(s: string, c: int): (string, string)
3122{
3123	for(i := 0; i < len s; i++)
3124		if(s[i] == c)
3125			return (s[0:i], s[i+1:]);
3126	return (s, nil);
3127}
3128
3129defaultusers()
3130{
3131	for(i := 0; i < len admusers; i++){
3132		(id, name, leader) := admusers[i];
3133		adduser(id, name, leader, nil);
3134	}
3135}
3136
3137finduname(s: string): ref User
3138{
3139	for(l := users; l != nil; l = tl l){
3140		u := hd l;
3141		if(u.name == s)
3142			return u;
3143	}
3144	return nil;
3145}
3146
3147uidtostr(id: int): string
3148{
3149	if(id == None)
3150		return "none";
3151	for(l := users; l != nil; l = tl l){
3152		u := hd l;
3153		if(u.uid == id)
3154			return u.name;
3155	}
3156	return sys->sprint("#%d", id);
3157}
3158
3159leadgroup(ui: int, gi: int): int
3160{
3161	for(l := users; l != nil; l = tl l){
3162		u := hd l;
3163		if(u.uid == gi){
3164			if(u.leader == ui)
3165				return 1;
3166			if(u.leader == 0)
3167				return ingroup(ui, gi);
3168			return 0;
3169		}
3170	}
3171	return 0;
3172}
3173
3174strtouid(s: string): int
3175{
3176	if(s == "none")
3177		return None;
3178	u := finduname(s);
3179	if(u != nil)
3180		return u.uid;
3181	return 0;
3182}
3183
3184ingroup(uid: int, gid: int): int
3185{
3186	if(uid == gid)
3187		return 1;
3188	for(l := users; l != nil; l = tl l){
3189		u := hd l;
3190		if(u.uid == gid){
3191			for(m := u.mem; m != nil; m = tl m)
3192				if(hd m == uid)
3193					return 1;
3194			return 0;
3195		}
3196	}
3197	return 0;
3198}
3199
3200baduname(s: string): int
3201{
3202	n := checkname9p2(s);
3203	if(n == 0 || n+1 > NAMELEN || s == "." || s == ".."){
3204		sys->print("kfs: illegal user name %q\n", s);
3205		return 1;
3206	}
3207	return 0;
3208}
3209
3210adduser(id: int, name: string, leader: string, mem: list of string)
3211{
3212	if(baduname(name))
3213		return;
3214	for(l := users; l != nil; l = tl l){
3215		u := hd l;
3216		if(u.uid == id){
3217			sys->print("kfs: duplicate user ID %d (name %q)\n", id, u.name);
3218			return;
3219		}else if(u.name == name){
3220			sys->print("kfs: duplicate user name %q (id %d)\n", name, u.uid);
3221			return;
3222		}
3223	}
3224	if(name == leader)
3225		lid := id;
3226	else if(leader == nil)
3227		lid = 0;
3228	else if(!baduname(leader))
3229		lid = strtouid(leader);
3230	else
3231		return;
3232	memid: list of int;
3233	for(; mem != nil; mem = tl mem){
3234		if(baduname(hd mem))
3235			return;
3236		x := strtouid(hd mem);
3237		if(x != 0)
3238			memid = x :: memid;
3239	}
3240	u := ref User(id, name, lid, memid);
3241	users = u :: users;
3242}
3243
3244Lock.new(): ref Lock
3245{
3246	return ref Lock(chan[1] of int);
3247}
3248
3249Lock.lock(l: self ref Lock)
3250{
3251	l.c <-= 1;
3252}
3253
3254Lock.canlock(l: self ref Lock): int
3255{
3256	alt{
3257	l.c <-= 1 =>
3258		return 1;
3259	* =>
3260		return 0;
3261	}
3262}
3263
3264Lock.unlock(l: self ref Lock)
3265{
3266	<-l.c;
3267}
3268
3269#
3270# kfs check, could be a separate module if that seemed important
3271#
3272
3273MAXDEPTH: con 100;
3274MAXNAME: con 4000;
3275
3276Map: adt {
3277	lo, hi:	int;
3278	bits:	array of byte;
3279	nbad:	int;
3280	ndup:	int;
3281	nmark:	int;
3282
3283	new:	fn(lo, hi: int): ref Map;
3284	isset:	fn(b: self ref Map, a: int): int;
3285	mark:	fn(b: self ref Map, a: int): string;
3286};
3287
3288Check: adt {
3289	dev:	ref Device;
3290
3291	amap:	ref Map;
3292	qmap:	ref Map;
3293
3294	name:	string;
3295	nfiles:	int;
3296	maxq:	int;
3297
3298	mod:	int;
3299	flags:	int;
3300	oldblock:	int;
3301
3302	depth:	int;
3303	maxdepth:	int;
3304
3305	check:	fn(c: self ref Check);
3306	touch:	fn(c: self ref Check, a: int): int;
3307	checkdir:	fn(c: self ref Check, a: int, qpath: int): int;
3308	checkindir:	fn(c: self ref Check, a: int, d: ref Dentry, qpath: int): int;
3309	maked:	fn(c: self ref Check, a: int, s: int, qpath: int): ref Dentry;
3310	modd:	fn(c: self ref Check, a: int, s: int, d: ref Dentry);
3311	fsck:		fn(c: self ref Check, d: ref Dentry): int;
3312	xread:	fn(c: self ref Check, a: int, qpath: int);
3313	xtag:		fn(c: self ref Check, a: int, tag: int, qpath: int): ref Iobuf;
3314	ckfreelist:	fn(c: self ref Check, sb: ref Superb);
3315	mkfreelist:	fn(c: self ref Check, sb: ref Superb);
3316	amark:	fn(c: self ref Check, a: int): int;
3317	fmark:	fn(c: self ref Check, a: int): int;
3318	missing:	fn(c: self ref Check, sb: ref Superb);
3319	qmark:	fn(c: self ref Check, q: int);
3320};
3321
3322check(dev: ref Device, flag: int)
3323{
3324	#mainlock.wlock();
3325	#mainlock.wunlock();
3326	c := ref Check;
3327	c.dev = dev;
3328	c.nfiles = 0;
3329	c.maxq = 0;
3330	c.mod = 0;
3331	c.flags = flag;
3332	c.oldblock = 0;
3333	c.depth = 0;
3334	c.maxdepth = 0;
3335	c.check();
3336}
3337
3338checkflags(s: string): int
3339{
3340	f := 0;
3341	for(i := 0; i < len s; i++)
3342		case s[i] {
3343		'r' =>	f |= Crdall;
3344		't' => f |= Ctag;
3345		'P' => f |= Cpfile;
3346		'p' => f |= Cpdir;
3347		'f' => f |= Cfree;
3348		'c' => f |= Cream;
3349		'd' => f |= Cbad;
3350		'w' => f |= Ctouch;
3351		'q' => f |= Cquiet;
3352		'v' => ;	# old verbose flag; ignored
3353		* =>	return -1;
3354	}
3355	return f;
3356}
3357
3358Check.check(c: self ref Check)
3359{
3360	sbaddr := SUPERADDR;
3361	p := c.xtag(sbaddr, Tsuper, QPSUPER);
3362	if(p == nil){
3363		cprint(sys->sprint("bad superblock"));
3364		return;
3365	}
3366	sb := Superb.unpack(p.iobuf);
3367	sb.iob = p;
3368
3369	fstart := sb.fstart;
3370	if(fstart != 1){
3371		cprint(sys->sprint("invalid superblock"));
3372		return;
3373	}
3374	fsize := sb.fsize;
3375	if(fsize < fstart || fsize > wrensize(c.dev)){
3376		cprint(sys->sprint("invalid size in superblock"));
3377		return;
3378	}
3379	c.amap = Map.new(fstart, fsize);
3380
3381	nqid := sb.qidgen+100;		# not as much of a botch
3382	if(nqid > 1024*1024*8)
3383		nqid = 1024*1024*8;
3384	if(nqid < 64*1024)
3385		nqid = 64*1024;
3386	c.qmap = Map.new(0, nqid);
3387
3388	c.mod = 0;
3389	c.depth = 0;
3390	c.maxdepth = 0;
3391
3392	if(c.amark(sbaddr))
3393		{}
3394
3395	if(!(c.flags & Cquiet))
3396		cprint(sys->sprint("checking file system: %s", "main"));
3397	c.nfiles = 0;
3398	c.maxq = 0;
3399
3400	d := c.maked(ROOTADDR, 0, QPROOT);
3401	if(d != nil){
3402		if(c.amark(ROOTADDR))
3403			{}
3404		if(c.fsck(d))
3405			c.modd(ROOTADDR, 0, d);
3406		if(--c.depth != 0)
3407			cprint("depth not zero on return");
3408	}
3409	if(sb.qidgen < c.maxq)
3410		cprint(sys->sprint("qid generator low path=%d maxq=%d", sb.qidgen, c.maxq));
3411
3412	nqbad := c.qmap.nbad + c.qmap.ndup;
3413	c.qmap = nil;	# could use to implement resequence
3414
3415	ndup := c.amap.ndup;
3416	nused := c.amap.nmark;
3417
3418	c.amap.ndup = c.amap.nmark = 0;	# reset for free list counts
3419	if(c.flags & Cfree){
3420		c.name = "free list";
3421		c.mkfreelist(sb);
3422		sb.qidgen = c.maxq;
3423		p.settag(Tsuper, QPNONE);
3424	}else
3425		c.ckfreelist(sb);
3426
3427	nbad := c.amap.nbad;
3428	nfdup := c.amap.ndup;
3429	nfree := c.amap.nmark;
3430	# leave amap for missing, below
3431
3432	if(c.mod){
3433		cprint("file system was modified");
3434		p.settag(Tsuper, QPNONE);
3435	}
3436
3437	if(!(c.flags & Cquiet)){
3438		cprint(sys->sprint("%8d files", c.nfiles));
3439		cprint(sys->sprint("%8d blocks in the file system", fsize-fstart));
3440		cprint(sys->sprint("%8d used blocks", nused));
3441		cprint(sys->sprint("%8d free blocks", sb.tfree));
3442	}
3443	if(!(c.flags & Cfree)){
3444		if(nfree != sb.tfree)
3445			cprint(sys->sprint("%8d free blocks found", nfree));
3446		if(nfdup)
3447			cprint(sys->sprint("%8d blocks duplicated in the free list", nfdup));
3448		if(fsize-fstart-nused-nfree)
3449			cprint(sys->sprint("%8d missing blocks", fsize-fstart-nused-nfree));
3450	}
3451	if(ndup)
3452		cprint(sys->sprint("%8d address duplications", ndup));
3453	if(nbad)
3454		cprint(sys->sprint("%8d bad block addresses", nbad));
3455	if(nqbad)
3456		cprint(sys->sprint("%8d bad qids", nqbad));
3457	if(!(c.flags & Cquiet))
3458		cprint(sys->sprint("%8d maximum qid path", c.maxq));
3459	c.missing(sb);
3460
3461	sb.put();
3462}
3463
3464Check.touch(c: self ref Check, a: int): int
3465{
3466	if((c.flags&Ctouch) && a){
3467		p := Iobuf.get(c.dev, a, Bread|Bmod);
3468		if(p != nil)
3469			p.put();
3470		return 1;
3471	}
3472	return 0;
3473}
3474
3475Check.checkdir(c: self ref Check, a: int, qpath: int): int
3476{
3477	ns := len c.name;
3478	dmod := c.touch(a);
3479	for(i:=0; i<DIRPERBUF; i++){
3480		nd := c.maked(a, i, qpath);
3481		if(nd == nil)
3482			break;
3483		if(c.fsck(nd)){
3484			c.modd(a, i, nd);
3485			dmod++;
3486		}
3487		c.depth--;
3488		c.name = c.name[0:ns];
3489	}
3490	c.name = c.name[0:ns];
3491	return dmod;
3492}
3493
3494Check.checkindir(c: self ref Check, a: int, d: ref Dentry, qpath: int): int
3495{
3496	dmod := c.touch(a);
3497	p := c.xtag(a, Tind1, qpath);
3498	if(p == nil)
3499		return dmod;
3500	for(i:=0; i<INDPERBUF; i++){
3501		a = get4(p.iobuf, i*4);
3502		if(a == 0)
3503			continue;
3504		if(c.amark(a)){
3505			if(c.flags & Cbad){
3506				put4(p.iobuf, i*4, 0);
3507				p.flags |= Bmod;
3508			}
3509			continue;
3510		}
3511		if(d.mode & DDIR)
3512			dmod += c.checkdir(a, qpath);
3513		else if(c.flags & Crdall)
3514			c.xread(a, qpath);
3515	}
3516	p.put();
3517	return dmod;
3518}
3519
3520Check.fsck(c: self ref Check, d: ref Dentry): int
3521{
3522	p: ref Iobuf;
3523	i: int;
3524	a, qpath: int;
3525
3526	if(++c.depth >= c.maxdepth){
3527		c.maxdepth = c.depth;
3528		if(c.maxdepth >= MAXDEPTH){
3529			cprint(sys->sprint("max depth exceeded: %s", c.name));
3530			return 0;
3531		}
3532	}
3533	dmod := 0;
3534	if(!(d.mode & DALLOC))
3535		return 0;
3536	c.nfiles++;
3537
3538	ns := len c.name;
3539	i = styx->utflen(d.name);
3540	if(i >= NAMELEN){
3541		d.name[NAMELEN-1] = 0;	# TO DO: not quite right
3542		cprint(sys->sprint("%q.name (%q) not terminated", c.name, d.name));
3543		return 0;
3544	}
3545	ns += i;
3546	if(ns >= MAXNAME){
3547		cprint(sys->sprint("%q.name (%q) name too large", c.name, d.name));
3548		return 0;
3549	}
3550	c.name += d.name;
3551
3552	if(d.mode & DDIR){
3553		if(ns > 1)
3554			c.name += "/";
3555		if(c.flags & Cpdir)
3556			cprint(sys->sprint("%s", c.name));
3557	} else if(c.flags & Cpfile)
3558		cprint(sys->sprint("%s", c.name));
3559
3560	qpath = int d.qid.path & ~QPDIR;
3561	c.qmark(qpath);
3562	if(qpath > c.maxq)
3563		c.maxq = qpath;
3564	for(i=0; i<NDBLOCK; i++){
3565		a = get4(d.buf, Odblock+i*4);
3566		if(a == 0)
3567			continue;
3568		if(c.amark(a)){
3569			put4(d.buf, Odblock+i*4, 0);
3570			dmod++;
3571			continue;
3572		}
3573		if(d.mode & DDIR)
3574			dmod += c.checkdir(a, qpath);
3575		else if(c.flags & Crdall)
3576			c.xread(a, qpath);
3577	}
3578	a = get4(d.buf, Oiblock);
3579	if(a){
3580		if(c.amark(a)){
3581			put4(d.buf, Oiblock, 0);
3582			dmod++;
3583		}
3584		else
3585			dmod += c.checkindir(a, d, qpath);
3586	}
3587
3588	a = get4(d.buf, Odiblock);
3589	if(a && c.amark(a)){
3590		put4(d.buf, Odiblock, 0);
3591		return dmod + 1;
3592	}
3593	dmod += c.touch(a);
3594	p = c.xtag(a, Tind2, qpath);
3595	if(p != nil){
3596		for(i=0; i<INDPERBUF; i++){
3597			a = get4(p.iobuf, i*4);
3598			if(a == 0)
3599				continue;
3600			if(c.amark(a)){
3601				if(c.flags & Cbad){
3602					put4(p.iobuf, i*4, 0);
3603					p.flags |= Bmod;
3604				}
3605				continue;
3606			}
3607			dmod += c.checkindir(a, d, qpath);
3608		}
3609		p.put();
3610	}
3611	return dmod;
3612}
3613
3614Check.ckfreelist(c: self ref Check, sb: ref Superb)
3615{
3616	c.name = "free list";
3617	cprint(sys->sprint("check %s", c.name));
3618	fb := sb.fbuf;
3619	a := SUPERADDR;
3620	p: ref Iobuf;
3621	lo := 0;
3622	hi := 0;
3623	for(;;){
3624		n := get4(fb, 0);		# nfree
3625		if(n < 0 || n > FEPERBUF){
3626			cprint(sys->sprint("check: nfree bad %d", a));
3627			break;
3628		}
3629		for(i:=1; i<n; i++){
3630			a = get4(fb, 4+i*4);	# free[i]
3631			if(a && !c.fmark(a)){
3632				if(!lo || lo > a)
3633					lo = a;
3634				if(!hi || hi < a)
3635					hi = a;
3636			}
3637		}
3638		a = get4(fb, 4);	# free[0]
3639		if(a == 0)
3640			break;
3641		if(c.fmark(a))
3642			break;
3643		if(!lo || lo > a)
3644			lo = a;
3645		if(!hi || hi < a)
3646			hi = a;
3647		if(p != nil)
3648			p.put();
3649		p = c.xtag(a, Tfree, QPNONE);
3650		if(p == nil)
3651			break;
3652		fb = p.iobuf;
3653	}
3654	if(p != nil)
3655		p.put();
3656	cprint(sys->sprint("lo = %d; hi = %d", lo, hi));
3657}
3658
3659#
3660# make freelist from scratch
3661#
3662Check.mkfreelist(c: self ref Check, sb: ref Superb)
3663{
3664	sb.fbuf[0:] = emptyblock[0:(FEPERBUF+1)*4];
3665	sb.tfree = 0;
3666	put4(sb.fbuf, 0, 1);	# nfree = 1
3667	for(a:=sb.fsize-sb.fstart-1; a >= 0; a--){
3668		i := a>>3;
3669		if(i < 0 || i >= len c.amap.bits)
3670			continue;
3671		b := byte (1 << (a&7));
3672		if((c.amap.bits[i] & b) != byte 0)
3673			continue;
3674		addfree(c.dev, sb.fstart+a, sb);
3675		c.amap.bits[i] |= b;
3676	}
3677	sb.iob.flags |= Bmod;
3678}
3679
3680#
3681# makes a copy of a Dentry's representation on disc so that
3682# the rest of the much larger iobuf can be freed.
3683#
3684Check.maked(c: self ref Check, a: int, s: int, qpath: int): ref Dentry
3685{
3686	p := c.xtag(a, Tdir, qpath);
3687	if(p == nil)
3688		return nil;
3689	d := Dentry.get(p, s);
3690	if(d == nil)
3691		return nil;
3692	copy := array[len d.buf] of byte;
3693	copy[0:] = d.buf;
3694	d.put();
3695	d.buf = copy;
3696	return d;
3697}
3698
3699Check.modd(c: self ref Check, a: int, s: int, d1: ref Dentry)
3700{
3701	if(!(c.flags & Cbad))
3702		return;
3703	p := Iobuf.get(c.dev, a, Bread);
3704	d := Dentry.get(p, s);
3705	if(d == nil){
3706		if(p != nil)
3707			p.put();
3708		return;
3709	}
3710	d.buf[0:] = d1.buf;
3711	p.flags |= Bmod;
3712	p.put();
3713}
3714
3715Check.xread(c: self ref Check, a: int, qpath: int)
3716{
3717	p := c.xtag(a, Tfile, qpath);
3718	if(p != nil)
3719		p.put();
3720}
3721
3722Check.xtag(c: self ref Check, a: int, tag: int, qpath: int): ref Iobuf
3723{
3724	if(a == 0)
3725		return nil;
3726	p := Iobuf.get(c.dev, a, Bread);
3727	if(p == nil){
3728		cprint(sys->sprint("check: \"%s\": xtag: p null", c.name));
3729		if(c.flags & (Cream|Ctag)){
3730			p = Iobuf.get(c.dev, a, Bmod);
3731			if(p != nil){
3732				p.iobuf[0:] = emptyblock;
3733				p.settag(tag, qpath);
3734				c.mod++;
3735				return p;
3736			}
3737		}
3738		return nil;
3739	}
3740	if(p.checktag(tag, qpath)){
3741		cprint(sys->sprint("check: \"%s\": xtag: checktag", c.name));
3742		if(c.flags & Cream)
3743			p.iobuf[0:] = emptyblock;
3744		if(c.flags & (Cream|Ctag)){
3745			p.settag(tag, qpath);
3746			c.mod++;
3747		}
3748		return p;
3749	}
3750	return p;
3751}
3752
3753Check.amark(c: self ref Check, a: int): int
3754{
3755	e := c.amap.mark(a);
3756	if(e != nil){
3757		cprint(sys->sprint("check: \"%s\": %s %d", c.name, e, a));
3758		return e != "dup";	# don't clear dup blocks because rm might repair
3759	}
3760	return 0;
3761}
3762
3763Check.fmark(c: self ref Check,a: int): int
3764{
3765	e := c.amap.mark(a);
3766	if(e != nil){
3767		cprint(sys->sprint("check: \"%s\": %s %d", c.name, e, a));
3768		return 1;
3769	}
3770	return 0;
3771}
3772
3773Check.missing(c: self ref Check, sb: ref Superb)
3774{
3775	n := 0;
3776	for(a:=sb.fsize-sb.fstart-1; a>=0; a--){
3777		i := a>>3;
3778		b := byte (1 << (a&7));
3779		if((c.amap.bits[i] & b) == byte 0){
3780			cprint(sys->sprint("missing: %d", sb.fstart+a));
3781			n++;
3782		}
3783		if(n > 10){
3784			cprint(sys->sprint(" ..."));
3785			break;
3786		}
3787	}
3788}
3789
3790Check.qmark(c: self ref Check, qpath: int)
3791{
3792	e := c.qmap.mark(qpath);
3793	if(e != nil){
3794		if(c.qmap.nbad+c.qmap.ndup < 20)
3795			cprint(sys->sprint("check: \"%s\": qid %s 0x%ux", c.name, e, qpath));
3796	}
3797}
3798
3799Map.new(lo, hi: int): ref Map
3800{
3801	m := ref Map;
3802	n := (hi-lo+7)>>3;
3803	m.bits = array[n] of {* => byte 0};
3804	m.lo = lo;
3805	m.hi = hi;
3806	m.nbad = 0;
3807	m.ndup = 0;
3808	m.nmark = 0;
3809	return m;
3810}
3811
3812Map.isset(m: self ref Map, i: int): int
3813{
3814	if(i < m.lo || i >= m.hi)
3815		return -1;	# hard to say
3816	i -= m.lo;
3817	return (m.bits[i>>3] & byte (1<<(i&7))) != byte 0;
3818}
3819
3820Map.mark(m: self ref Map, i: int): string
3821{
3822	if(i < m.lo || i >= m.hi){
3823		m.nbad++;
3824		return "out of range";
3825	}
3826	i -= m.lo;
3827	b := byte (1 << (i&7));
3828	i >>= 3;
3829	if((m.bits[i] & b) != byte 0){
3830		m.ndup++;
3831		return "dup";
3832	}
3833	m.bits[i] |= b;
3834	m.nmark++;
3835	return nil;
3836}
3837
3838cprint(s: string)
3839{
3840	if(consoleout != nil)
3841		consoleout <-= s+"\n";
3842	else
3843		eprint(s);
3844}
3845