xref: /inferno-os/appl/cmd/9660srv.b (revision c93eaa9a36d5071c19f42debcef978afd89ec994)
1implement ISO9660;
2
3include "sys.m";
4	sys: Sys;
5	Dir, Qid, QTDIR, QTFILE, DMDIR: import sys;
6
7include "draw.m";
8
9include "daytime.m";
10	daytime:	Daytime;
11
12include "string.m";
13	str: String;
14
15include "styx.m";
16	styx: Styx;
17	Rmsg, Tmsg: import styx;
18
19include "arg.m";
20
21ISO9660: module
22{
23	init:	fn(nil: ref Draw->Context, nil: list of string);
24};
25
26Sectorsize: con 2048;
27Maxname: con 256;
28
29Enonexist:	con "file does not exist";
30Eperm:	con "permission denied";
31Enofile:	con "no file system specified";
32Eauth:	con "authentication failed";
33Ebadfid:	con	"invalid fid";
34Efidinuse:	con	"fid already in use";
35Enotdir:	con	"not a directory";
36Esyntax:	con	"file name syntax";
37
38devname: string;
39
40chatty := 0;
41showstyx := 0;
42progname := "9660srv";
43stderr: ref Sys->FD;
44noplan9 := 0;
45nojoliet := 0;
46norock := 0;
47
48usage()
49{
50	sys->fprint(sys->fildes(2), "usage: %s [-rabc] [-9JR] [-s] cd_device dir\n", progname);
51	raise "fail:usage";
52}
53
54init(nil: ref Draw->Context, args: list of string)
55{
56	sys = load Sys Sys->PATH;
57
58	sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);
59	stderr = sys->fildes(2);
60
61	if(args != nil)
62		progname = hd args;
63	styx = load Styx Styx->PATH;
64	if(styx == nil)
65		noload(Styx->PATH);
66	styx->init();
67
68	if(args != nil)
69		progname = hd args;
70	mountopt := Sys->MREPL;
71	copt := 0;
72	stdio := 0;
73
74	arg := load Arg Arg->PATH;
75	if(arg == nil)
76		noload(Arg->PATH);
77	arg->init(args);
78	while((c := arg->opt()) != 0)
79		case c {
80		'v' or 'D' => chatty = 1; showstyx = 1;
81		'r' => mountopt = Sys->MREPL;
82		'a' => mountopt = Sys->MAFTER;
83		'b' => mountopt = Sys->MBEFORE;
84		'c' => copt = Sys->MCREATE;
85		's' => stdio = 1;
86		'9' => noplan9 = 1;
87		'J' => nojoliet = 1;
88		'R' => norock = 1;
89		* => usage();
90		}
91	args = arg->argv();
92	arg = nil;
93
94	if(args == nil || tl args == nil)
95		usage();
96	what := hd args;
97	mountpt := hd tl args;
98
99	daytime = load Daytime Daytime->PATH;
100	if(daytime == nil)
101		noload(Daytime->PATH);
102
103	iobufinit(Sectorsize);
104
105	pip := array[2] of ref Sys->FD;
106	if(stdio){
107		pip[0] = sys->fildes(0);
108		pip[1] = sys->fildes(1);
109	}else
110		if(sys->pipe(pip) < 0)
111			error(sys->sprint("can't create pipe: %r"));
112
113	devname = what;
114
115	sync := chan of int;
116	spawn fileserve(pip[1], sync);
117	<-sync;
118
119	if(sys->mount(pip[0], nil, mountpt, mountopt|copt, nil) < 0) {
120		sys->fprint(sys->fildes(2), "%s: mount %s %s failed: %r\n", progname, what, mountpt);
121		exit;
122	}
123}
124
125noload(s: string)
126{
127	sys->fprint(sys->fildes(2), "%s: can't load %s: %r\n", progname, s);
128	raise "fail:load";
129}
130
131error(p: string)
132{
133	sys->fprint(sys->fildes(2), "9660srv: %s\n", p);
134	raise "fail:error";
135}
136
137fileserve(rfd: ref Sys->FD, sync: chan of int)
138{
139	sys->pctl(Sys->NEWFD|Sys->FORKNS, list of {2, rfd.fd});
140	rfd = sys->fildes(rfd.fd);
141	stderr = sys->fildes(2);
142	sync <-= 1;
143	while((m := Tmsg.read(rfd, 0)) != nil){
144		if(showstyx)
145			chat(sys->sprint("%s...", m.text()));
146		r: ref Rmsg;
147		pick t := m {
148		Readerror =>
149			error(sys->sprint("mount read error: %s", t.error));
150		Version =>
151 			r = rversion(t);
152		Auth =>
153			r = rauth(t);
154		Flush =>
155 			r = rflush(t);
156		Attach =>
157 			r = rattach(t);
158		Walk =>
159 			r = rwalk(t);
160		Open =>
161 			r = ropen(t);
162		Create =>
163 			r = rcreate(t);
164		Read =>
165 			r = rread(t);
166		Write =>
167 			r = rwrite(t);
168		Clunk =>
169 			r = rclunk(t);
170		Remove =>
171 			r = rremove(t);
172		Stat =>
173 			r = rstat(t);
174		Wstat =>
175 			r = rwstat(t);
176		* =>
177			error(sys->sprint("invalid T-message tag: %d", tagof m));
178		}
179		pick e := r {
180		Error =>
181			r.tag = m.tag;
182		}
183		rbuf := r.pack();
184		if(rbuf == nil)
185			error("bad R-message conversion");
186		if(showstyx)
187			chat(r.text()+"\n");
188		if(sys->write(rfd, rbuf, len rbuf) != len rbuf)
189			error(sys->sprint("connection write error: %r"));
190	}
191
192	if(chatty)
193		chat("server end of file\n");
194}
195
196E(s: string): ref Rmsg.Error
197{
198	return ref Rmsg.Error(0, s);
199}
200
201rversion(t: ref Tmsg.Version): ref Rmsg
202{
203	(msize, version) := styx->compatible(t, Styx->MAXRPC, Styx->VERSION);
204	return ref Rmsg.Version(t.tag, msize, version);
205}
206
207rauth(t: ref Tmsg.Auth): ref Rmsg
208{
209	return ref Rmsg.Error(t.tag, "authentication not required");
210}
211
212rflush(t: ref Tmsg.Flush): ref Rmsg
213{
214	return ref Rmsg.Flush(t.tag);
215}
216
217rattach(t: ref Tmsg.Attach): ref Rmsg
218{
219	dname := devname;
220	if(t.aname != "")
221		dname = t.aname;
222	(dev, err) := devattach(dname, Sys->OREAD, Sectorsize);
223	if(dev == nil)
224		return E(err);
225
226	xf := Xfs.new(dev);
227	root := cleanfid(t.fid);
228	root.qid = Sys->Qid(big 0, 0, Sys->QTDIR);
229	root.xf = xf;
230	err = root.attach();
231	if(err != nil){
232		clunkfid(t.fid);
233		return E(err);
234	}
235	xf.rootqid = root.qid;
236	return ref Rmsg.Attach(t.tag, root.qid);
237}
238
239walk1(f: ref Xfile, name: string): string
240{
241	if(!(f.qid.qtype & Sys->QTDIR))
242		return Enotdir;
243	case name {
244	"." =>
245		return nil;	# nop, but shouldn't happen
246	".." =>
247		if(f.qid.path==f.xf.rootqid.path)
248			return nil;
249		return f.walkup();
250	* =>
251		return f.walk(name);
252	}
253}
254
255rwalk(t: ref Tmsg.Walk): ref Rmsg
256{
257	f:=findfid(t.fid);
258	if(f == nil)
259		return E(Ebadfid);
260	nf, sf: ref Xfile;
261	if(t.newfid != t.fid){
262		nf = cleanfid(t.newfid);
263		if(nf == nil)
264			return E(Efidinuse);
265		f.clone(nf);
266		f = nf;
267	}else
268		sf = f.save();
269
270	qids: array of Sys->Qid;
271	if(len t.names > 0){
272		qids = array[len t.names] of Sys->Qid;
273		for(i := 0; i < len t.names; i++){
274			e := walk1(f, t.names[i]);
275			if(e != nil){
276				if(nf != nil){
277					nf.clunk();
278					clunkfid(t.newfid);
279				}else
280					f.restore(sf);
281				if(i == 0)
282					return E(e);
283				return ref Rmsg.Walk(t.tag, qids[0:i]);
284			}
285			qids[i] = f.qid;
286		}
287	}
288	return ref Rmsg.Walk(t.tag, qids);
289}
290
291ropen(t: ref Tmsg.Open): ref Rmsg
292{
293	f := findfid(t.fid);
294	if(f == nil)
295		return E(Ebadfid);
296	if(f.flags&Omodes)
297		return E("open on open file");
298	e := f.open(t.mode);
299	if(e != nil)
300		return E(e);
301	f.flags = openflags(t.mode);
302	return ref Rmsg.Open(t.tag, f.qid, Styx->MAXFDATA);
303}
304
305rcreate(t: ref Tmsg.Create): ref Rmsg
306{
307	name := t.name;
308	if(name == "." || name == "..")
309		return E(Esyntax);
310	f := findfid(t.fid);
311	if(f == nil)
312		return E(Ebadfid);
313	if(f.flags&Omodes)
314		return E("create on open file");
315	if(!(f.qid.qtype&Sys->QTDIR))
316		return E("create in non-directory");
317	e := f.create(name, t.perm, t.mode);
318	if(e != nil)
319		return E(e);
320	f.flags = openflags(t.mode);
321	return ref Rmsg.Create(t.tag, f.qid, Styx->MAXFDATA);
322}
323
324rread(t: ref Tmsg.Read): ref Rmsg
325{
326	err: string;
327
328	f := findfid(t.fid);
329	if(f == nil)
330		return E(Ebadfid);
331	if (!(f.flags&Oread))
332		return E("file not opened for reading");
333	if(t.count < 0 || t.offset < big 0)
334		return E("negative offset or count");
335	b := array[Styx->MAXFDATA] of byte;
336	count: int;
337	if(f.qid.qtype & Sys->QTDIR)
338		(count, err) = f.readdir(b, int t.offset, t.count);
339	else
340		(count, err) = f.read(b, int t.offset, t.count);
341	if(err != nil)
342		return E(err);
343	if(count != len b)
344		b = b[0:count];
345	return ref Rmsg.Read(t.tag, b);
346}
347
348rwrite(nil: ref Tmsg.Write): ref Rmsg
349{
350	return E(Eperm);
351}
352
353rclunk(t: ref Tmsg.Clunk): ref Rmsg
354{
355	f := findfid(t.fid);
356	if(f == nil)
357		return E(Ebadfid);
358	f.clunk();
359	clunkfid(t.fid);
360	return ref Rmsg.Clunk(t.tag);
361}
362
363rremove(t: ref Tmsg.Remove): ref Rmsg
364{
365	f := findfid(t.fid);
366	if(f == nil)
367		return E(Ebadfid);
368	f.clunk();
369	clunkfid(t.fid);
370	return E(Eperm);
371}
372
373rstat(t: ref Tmsg.Stat): ref Rmsg
374{
375	f := findfid(t.fid);
376	if(f == nil)
377		return E(Ebadfid);
378	(dir, nil) := f.stat();
379	return ref Rmsg.Stat(t.tag, *dir);
380}
381
382rwstat(nil: ref Tmsg.Wstat): ref Rmsg
383{
384	return E(Eperm);
385}
386
387openflags(mode: int): int
388{
389	flags := 0;
390	case mode & ~(Sys->OTRUNC|Sys->ORCLOSE) {
391	Sys->OREAD =>
392		flags = Oread;
393	Sys->OWRITE =>
394		flags = Owrite;
395	Sys->ORDWR =>
396		flags = Oread|Owrite;
397	}
398	if(mode & Sys->ORCLOSE)
399		flags |= Orclose;
400	return flags;
401}
402
403chat(s: string)
404{
405	if(chatty)
406		sys->fprint(stderr, "%s", s);
407}
408
409Fid: adt {
410	fid:	int;
411	file:	ref Xfile;
412};
413
414FIDMOD: con 127;	# prime
415fids := array[FIDMOD] of list of ref Fid;
416
417hashfid(fid: int): (ref Fid, array of list of ref Fid)
418{
419	nl: list of ref Fid;
420
421	hp := fids[fid%FIDMOD:];
422	nl = nil;
423	for(l := hp[0]; l != nil; l = tl l){
424		f := hd l;
425		if(f.fid == fid){
426			l = tl l;	# excluding f
427			for(; nl != nil; nl = tl nl)
428				l = (hd nl) :: l;	# put examined ones back, in order
429			hp[0] = l;
430			return (f, hp);
431		} else
432			nl = f :: nl;
433	}
434	return (nil, hp);
435}
436
437findfid(fid: int): ref Xfile
438{
439	(f, hp) := hashfid(fid);
440	if(f == nil){
441		chat("unassigned fid");
442		return nil;
443	}
444	hp[0] = f :: hp[0];
445	return f.file;
446}
447
448cleanfid(fid: int): ref Xfile
449{
450	(f, hp) := hashfid(fid);
451	if(f != nil){
452		chat("fid in use");
453		return nil;
454	}
455	f = ref Fid;
456	f.fid = fid;
457	f.file = Xfile.new();
458	hp[0] = f :: hp[0];
459	return f.file.clean();
460}
461
462clunkfid(fid: int)
463{
464	(f, nil) := hashfid(fid);
465	if(f != nil)
466		f.file.clean();
467}
468
469#
470#
471#
472
473Xfs: adt {
474	d:	ref Device;
475	inuse:	int;
476	issusp:	int;	# system use sharing protocol in use?
477	suspoff:	int;	# LEN_SKP, if so
478	isplan9:	int;	# has Plan 9-specific directory info
479	isrock:	int;	# is rock ridge
480	rootqid:	Sys->Qid;
481	ptr:	int;	# tag for private data
482
483	new:	fn(nil: ref Device): ref Xfs;
484	incref:	fn(nil: self ref Xfs);
485	decref:	fn(nil: self ref Xfs);
486};
487
488Xfile:	adt {
489	xf:	ref Xfs;
490	flags:	int;
491	qid:	Sys->Qid;
492	ptr:	ref Isofile;	# tag for private data
493
494	new:		fn(): ref Xfile;
495	clean:	fn(nil: self ref Xfile): ref Xfile;
496
497	save:		fn(nil: self ref Xfile): ref Xfile;
498	restore:	fn(nil: self ref Xfile, s: ref Xfile);
499
500	attach:	fn(nil: self ref Xfile): string;
501	clone:	fn(nil: self ref Xfile, nil: ref Xfile);
502	walkup:	fn(nil: self ref Xfile): string;
503	walk:	fn(nil: self ref Xfile, nil: string): string;
504	open:	fn(nil: self ref Xfile, nil: int): string;
505	create:	fn(nil: self ref Xfile, nil: string, nil: int, nil: int): string;
506	readdir:	fn(nil: self ref Xfile, nil: array of byte, nil: int, nil: int): (int, string);
507	read:		fn(nil: self ref Xfile, nil: array of byte, nil: int, nil: int): (int, string);
508	write:	fn(nil: self ref Xfile, nil: array of byte, nil: int, nil: int): (int, string);
509	clunk:	fn(nil: self ref Xfile);
510	remove:	fn(nil: self ref Xfile): string;
511	stat:		fn(nil: self ref Xfile): (ref Sys->Dir, string);
512	wstat:	fn(nil: self ref Xfile, nil: ref Sys->Dir): string;
513};
514
515Oread, Owrite, Orclose: con 1<<iota;
516Omodes: con 3;	# mask
517
518VOLDESC: con 16;	# sector number
519
520Drec: adt {
521	reclen:	int;
522	attrlen:	int;
523	addr:	int;	# should be big?
524	size:	int;	# should be big?
525	date:	array of byte;
526	time:	int;
527	tzone:	int;	# not in high sierra
528	flags:	int;
529	unitsize:	int;
530	gapsize:	int;
531	vseqno:	int;
532	name:	array of byte;
533	data:	array of byte;	# system extensions
534};
535
536Isofile: adt {
537	fmt:	int;	# 'z' if iso, 'r' if high sierra
538	blksize:	int;
539	offset:	int;	# true offset when reading directory
540	doffset:	int;	# styx offset when reading directory
541	d:	ref Drec;
542};
543
544Xfile.new(): ref Xfile
545{
546	f := ref Xfile;
547	return f.clean();
548}
549
550Xfile.clean(f: self ref Xfile): ref Xfile
551{
552	if(f.xf != nil){
553		f.xf.decref();
554		f.xf = nil;
555	}
556	f.ptr = nil;
557	f.flags = 0;
558	f.qid = Qid(big 0, 0, 0);
559	return f;
560}
561
562Xfile.save(f: self ref Xfile): ref Xfile
563{
564	s := ref Xfile;
565	*s = *f;
566	s.ptr = ref *f.ptr;
567	s.ptr.d = ref *f.ptr.d;
568	return s;
569}
570
571Xfile.restore(f: self ref Xfile, s: ref Xfile)
572{
573	f.flags = s.flags;
574	f.qid = s.qid;
575	*f.ptr = *s.ptr;
576}
577
578Xfile.attach(root: self ref Xfile): string
579{
580	fmt := 0;
581	blksize := 0;
582	haveplan9 := 0;
583	dirp: ref Block;
584	dp := ref Drec;
585	for(a:=VOLDESC;a<VOLDESC+100;a++){
586		p := Block.get(root.xf.d, a);
587		if(p == nil){
588			if(dirp != nil)
589				dirp.put();
590			return "can't read volume descriptor";
591		}
592		v := p.data;	# Voldesc
593		if(eqs(v[0:7], "\u0001CD001\u0001")){		# ISO
594			if(dirp != nil)
595				dirp.put();
596			dirp = p;
597			fmt = 'z';
598			convM2Drec(v[156:], dp, 0);	# v.z.desc.rootdir
599			blksize = l16(v[128:]);	# v.z.desc.blksize
600			if(chatty)
601				chat(sys->sprint("iso, blksize=%d...", blksize));
602			haveplan9 = eqs(v[8:8+6], "PLAN 9");	# v.z.boot.sysid
603			if(haveplan9){
604				if(noplan9) {
605					chat("ignoring plan9");
606					haveplan9 = 0;
607				}else{
608					fmt = '9';
609					chat("plan9 iso...");
610				}
611			}
612			continue;
613		}
614		if(eqs(v[8:8+7], "\u0001CDROM\u0001")){	# high sierra
615			if(dirp != nil)
616				dirp.put();
617			dirp = p;
618			fmt = 'r';
619			convM2Drec(v[180:], dp, 1);	# v.r.desc.rootdir
620			blksize = l16(v[136:]);	# v.r.desc.blksize
621			if(chatty)
622				chat(sys->sprint("high sierra, blksize=%d...", blksize));
623			continue;
624		}
625		if(haveplan9==0 && !nojoliet && eqs(v[0:7], "\u0002CD001\u0001")){
626			q := v[88:];	# v.z.desc.escapes
627			if(q[0] == byte 16r25 && q[1] == byte 16r2F &&
628			   (q[2] == byte 16r40 || q[2] == byte 16r43 || q[2] == byte 16r45)){	# joliet, it appears
629				if(dirp != nil)
630					dirp.put();
631				dirp = p;
632				fmt = 'J';
633				convM2Drec(v[156:], dp, 0);	# v.z.desc.rootdir
634				if(blksize != l16(v[128:]))	# v.z.desc.blksize
635					sys->fprint(stderr, "9660srv: warning: suspicious Joliet block size: %d\n", l16(v[128:]));
636				chat("joliet...");
637				continue;
638			}
639		}else{
640			p.put();
641			if(v[0] == byte 16rFF)
642				break;
643		}
644	}
645
646	if(fmt ==  0){
647		if(dirp != nil)
648			dirp.put();
649		return "CD format not recognised";
650	}
651
652	if(chatty)
653		showdrec(stderr, fmt, dp);
654	if(blksize > Sectorsize){
655		dirp.put();
656		return "blocksize too big";
657	}
658	fp := iso(root);
659	root.xf.isplan9 = haveplan9;
660	fp.fmt = fmt;
661	fp.blksize = blksize;
662	fp.offset = 0;
663	fp.doffset = 0;
664	fp.d = dp;
665	root.qid.path = big dp.addr;
666	root.qid.qtype = QTDIR;
667	root.qid.vers = 0;
668	dirp.put();
669	dp = ref Drec;
670	if(getdrec(root, dp) >= 0){
671		s := dp.data;
672		n := len s;
673		if(n >= 7 && s[0] == byte 'S' && s[1] == byte 'P' && s[2] == byte 7 &&
674		   s[3] == byte 1 && s[4] == byte 16rBE && s[5] == byte 16rEF){
675			root.xf.issusp = 1;
676			root.xf.suspoff = int s[6];
677			n -= root.xf.suspoff;
678			s = s[root.xf.suspoff:];
679			while(n >= 4){
680				l := int s[2];
681				if(s[0] == byte 'E' && s[1] == byte 'R'){
682					if(int s[4] == 10 && eqs(s[8:18], "RRIP_1991A"))
683						root.xf.isrock = 1;
684					break;
685				} else if(s[0] == byte 'C' && s[1] == byte 'E' && int s[2] >= 28){
686					(s, n) = getcontin(root.xf.d, s);
687					continue;
688				} else if(s[0] == byte 'R' && s[1] == byte 'R'){
689					if(!norock)
690						root.xf.isrock = 1;
691					break;	# can skip search for ER
692				} else if(s[0] == byte 'S' && s[1] == byte 'T')
693					break;
694				s = s[l:];
695				n -= l;
696			}
697		}
698	}
699	if(root.xf.isrock)
700		chat("Rock Ridge...");
701	fp.offset = 0;
702	fp.doffset = 0;
703	return nil;
704}
705
706Xfile.clone(oldf: self ref Xfile, newf: ref Xfile)
707{
708	*newf = *oldf;
709	newf.ptr = nil;
710	newf.xf.incref();
711	ip := iso(oldf);
712	np := iso(newf);
713	*np = *ip;	# might not be right; shares ip.d
714}
715
716Xfile.walkup(f: self ref Xfile): string
717{
718	pf := Xfile.new();
719	ppf := Xfile.new();
720	e := walkup(f, pf, ppf);
721	pf.clunk();
722	ppf.clunk();
723	return e;
724}
725
726walkup(f, pf, ppf: ref Xfile): string
727{
728	e := opendotdot(f, pf);
729	if(e != nil)
730		return sys->sprint("can't open pf: %s", e);
731	paddr := iso(pf).d.addr;
732	if(iso(f).d.addr == paddr)
733		return nil;
734	e = opendotdot(pf, ppf);
735	if(e != nil)
736		return sys->sprint("can't open ppf: %s", e);
737	d := ref Drec;
738	while(getdrec(ppf, d) >= 0){
739		if(d.addr == paddr){
740			newdrec(f, d);
741			f.qid.path = big paddr;
742			f.qid.qtype = QTDIR;
743			f.qid.vers = 0;
744			return nil;
745		}
746	}
747	return "can't find addr of ..";
748}
749
750Xfile.walk(f: self ref Xfile, name: string): string
751{
752	ip := iso(f);
753	if(!f.xf.isplan9){
754		for(i := 0; i < len name; i++)
755			if(name[i] == ';')
756				break;
757		if(i >= Maxname)
758			i = Maxname-1;
759		name = name[0:i];
760	}
761	if(chatty)
762		chat(sys->sprint("%d \"%s\"...", len name, name));
763	ip.offset = 0;
764	dir := ref Dir;
765	d := ref Drec;
766	while(getdrec(f, d) >= 0) {
767		dvers := rzdir(f.xf, dir, ip.fmt, d);
768		if(name != dir.name)
769			continue;
770		newdrec(f, d);
771		f.qid.path = dir.qid.path;
772		f.qid.qtype = dir.qid.qtype;
773		f.qid.vers = dir.qid.vers;
774		if(dvers){
775			# versions ignored
776		}
777		return nil;
778	}
779	return Enonexist;
780}
781
782Xfile.open(f: self ref Xfile, mode: int): string
783{
784	if(mode != Sys->OREAD)
785		return Eperm;
786	ip := iso(f);
787	ip.offset = 0;
788	ip.doffset = 0;
789	return nil;
790}
791
792Xfile.create(nil: self ref Xfile, nil: string, nil: int, nil: int): string
793{
794	return Eperm;
795}
796
797Xfile.readdir(f: self ref Xfile, buf: array of byte, offset: int, count: int): (int, string)
798{
799	ip := iso(f);
800	d := ref Dir;
801	drec := ref Drec;
802	if(offset < ip.doffset){
803		ip.offset = 0;
804		ip.doffset = 0;
805	}
806	rcnt := 0;
807	while(rcnt < count && getdrec(f, drec) >= 0){
808		if(len drec.name == 1){
809			if(drec.name[0] == byte 0)
810				continue;
811			if(drec.name[0] == byte 1)
812				continue;
813		}
814		rzdir(f.xf, d, ip.fmt, drec);
815		d.qid.vers = f.qid.vers;
816		a := styx->packdir(*d);
817		if(ip.doffset < offset){
818			ip.doffset += len a;
819			continue;
820		}
821		if(rcnt+len a > count)
822			break;
823		buf[rcnt:] = a;		# BOTCH: copy
824		rcnt += len a;
825	}
826	ip.doffset += rcnt;
827	return (rcnt, nil);
828}
829
830Xfile.read(f: self ref Xfile, buf: array of byte, offset: int, count: int): (int, string)
831{
832	ip := iso(f);
833	if(offset >= ip.d.size)
834		return (0, nil);
835	if(offset+count > ip.d.size)
836		count = ip.d.size - offset;
837	addr := (ip.d.addr+ip.d.attrlen)*ip.blksize + offset;
838	o := addr % Sectorsize;
839	addr /= Sectorsize;
840	if(chatty)
841		chat(sys->sprint("d.addr=0x%x, addr=0x%x, o=0x%x...", ip.d.addr, addr, o));
842	n := Sectorsize - o;
843	rcnt := 0;
844	while(count > 0){
845		if(n > count)
846			n = count;
847		p := Block.get(f.xf.d, addr);
848		if(p == nil)
849			return (-1, "i/o error");
850		buf[rcnt:] = p.data[o:o+n];
851		p.put();
852		count -= n;
853		rcnt += n;
854		addr++;
855		o = 0;
856		n = Sectorsize;
857	}
858	return (rcnt, nil);
859}
860
861Xfile.write(nil: self ref Xfile, nil: array of byte, nil: int, nil: int): (int, string)
862{
863	return (-1, Eperm);
864}
865
866Xfile.clunk(f: self ref Xfile)
867{
868	f.ptr = nil;
869}
870
871Xfile.remove(nil: self ref Xfile): string
872{
873	return Eperm;
874}
875
876Xfile.stat(f: self ref Xfile): (ref Dir, string)
877{
878	ip := iso(f);
879	d := ref Dir;
880	rzdir(f.xf, d, ip.fmt, ip.d);
881	d.qid.vers = f.qid.vers;
882	if(d.qid.path==f.xf.rootqid.path){
883		d.qid.path = big 0;
884		d.qid.qtype = QTDIR;
885	}
886	return (d, nil);
887}
888
889Xfile.wstat(nil: self ref Xfile, nil: ref Dir): string
890{
891	return Eperm;
892}
893
894Xfs.new(d: ref Device): ref Xfs
895{
896	xf := ref Xfs;
897	xf.inuse = 1;
898	xf.d = d;
899	xf.isplan9 = 0;
900	xf.issusp = 0;
901	xf.isrock = 0;
902	xf.suspoff = 0;
903	xf.ptr = 0;
904	xf.rootqid = Qid(big 0, 0, QTDIR);
905	return xf;
906}
907
908Xfs.incref(xf: self ref Xfs)
909{
910	xf.inuse++;
911}
912
913Xfs.decref(xf: self ref Xfs)
914{
915	xf.inuse--;
916	if(xf.inuse == 0){
917		if(xf.d != nil)
918			xf.d.detach();
919	}
920}
921
922showdrec(fd: ref Sys->FD, fmt: int, d: ref Drec)
923{
924	if(d.reclen == 0)
925		return;
926	sys->fprint(fd, "%d %d %d %d ",
927		d.reclen, d.attrlen, d.addr, d.size);
928	sys->fprint(fd, "%s 0x%2.2x %d %d %d ",
929		rdate(d.date, fmt), d.flags,
930		d.unitsize, d.gapsize, d.vseqno);
931	sys->fprint(fd, "%d %s", len d.name, nstr(d.name));
932	syslen := len d.data;
933	if(syslen != 0)
934		sys->fprint(fd, " %s", nstr(d.data));
935	sys->fprint(fd, "\n");
936}
937
938newdrec(f: ref Xfile, dp: ref Drec)
939{
940	x := iso(f);
941	n := ref Isofile;
942	n.fmt = x.fmt;
943	n.blksize = x.blksize;
944	n.offset = 0;
945	n.doffset = 0;
946	n.d = dp;
947	f.ptr = n;
948}
949
950getdrec(f: ref Xfile, d: ref Drec): int
951{
952	if(f.ptr == nil)
953		return -1;
954	boff := 0;
955	ip := iso(f);
956	size := ip.d.size;
957	while(ip.offset<size){
958		addr := (ip.d.addr+ip.d.attrlen)*ip.blksize + ip.offset;
959		boff = addr % Sectorsize;
960		if(boff > Sectorsize-34){
961			ip.offset += Sectorsize-boff;
962			continue;
963		}
964		p := Block.get(f.xf.d, addr/Sectorsize);
965		if(p == nil)
966			return -1;
967		nb := int p.data[boff];
968		if(nb >= 34) {
969			convM2Drec(p.data[boff:], d, ip.fmt=='r');
970			#chat(sys->sprint("off %d", ip.offset));
971			#showdrec(stderr, ip.fmt, d);
972			p.put();
973			ip.offset += nb + (nb&1);
974			return 0;
975		}
976		p.put();
977		p = nil;
978		ip.offset += Sectorsize-boff;
979	}
980	return -1;
981}
982
983# getcontin returns a slice of the Iobuf, valid until next i/o call
984getcontin(d: ref Device, a: array of byte): (array of byte, int)
985{
986	bn := l32(a[4:]);
987	off := l32(a[12:]);
988	n := l32(a[20:]);
989	p := Block.get(d, bn);
990	if(p == nil)
991		return (nil, 0);
992	return (p.data[off:off+n], n);
993}
994
995iso(f: ref Xfile): ref Isofile
996{
997	if(f.ptr == nil){
998		f.ptr = ref Isofile;
999		f.ptr.d = ref Drec;
1000	}
1001	return f.ptr;
1002}
1003
1004opendotdot(f: ref Xfile, pf: ref Xfile): string
1005{
1006	d := ref Drec;
1007	ip := iso(f);
1008	ip.offset = 0;
1009	if(getdrec(f, d) < 0)
1010		return "opendotdot: getdrec(.) failed";
1011	if(len d.name != 1 || d.name[0] != byte 0)
1012		return "opendotdot: no . entry";
1013	if(d.addr != ip.d.addr)
1014		return "opendotdot: bad . address";
1015	if(getdrec(f, d) < 0)
1016		return "opendotdot: getdrec(..) failed";
1017	if(len d.name != 1 || d.name[0] != byte 1)
1018		return "opendotdot: no .. entry";
1019
1020	pf.xf = f.xf;
1021	pip := iso(pf);
1022	pip.fmt = ip.fmt;
1023	pip.blksize = ip.blksize;
1024	pip.offset = 0;
1025	pip.doffset = 0;
1026	pip.d = d;
1027	return nil;
1028}
1029
1030rzdir(fs: ref Xfs, d: ref Dir, fmt: int, dp: ref Drec): int
1031{
1032	Hmode, Hname: con 1<<iota;
1033	vers := -1;
1034	have := 0;
1035	d.qid.path = big dp.addr;
1036	d.qid.vers = 0;
1037	d.qid.qtype = QTFILE;
1038	n := len dp.name;
1039	if(n == 1) {
1040		case int dp.name[0] {
1041		0 => d.name = "."; have |= Hname;
1042		1 =>	d.name = ".."; have |= Hname;
1043		* =>	d.name = ""; d.name[0] = tolower(int dp.name[0]);
1044		}
1045	} else {
1046		if(fmt == 'J'){	# Joliet, 16-bit Unicode
1047			d.name = "";
1048			for(i:=0; i<n; i+=2){
1049				r := (int dp.name[i]<<8) | int dp.name[i+1];
1050				d.name[len d.name] = r;
1051			}
1052		}else{
1053			if(n >= Maxname)
1054				n = Maxname-1;
1055			d.name = "";
1056			for(i:=0; i<n && int dp.name[i] != '\r'; i++)
1057				d.name[i] = tolower(int dp.name[i]);
1058		}
1059	}
1060
1061	if(fs.isplan9 && dp.reclen>34+len dp.name) {
1062		#
1063		# get gid, uid, mode and possibly name
1064		# from plan9 directory extension
1065		#
1066		s := dp.data;
1067		n = int s[0];
1068		if(n)
1069			d.name = string s[1:1+n];
1070		l := 1+n;
1071		n = int s[l++];
1072		d.uid = string s[l:l+n];
1073		l += n;
1074		n = int s[l++];
1075		d.gid = string s[l:l+n];
1076		l += n;
1077		if(l & 1)
1078			l++;
1079		d.mode = l32(s[l:]);
1080		if(d.mode & DMDIR)
1081			d.qid.qtype = QTDIR;
1082	} else {
1083		d.mode = 8r444;
1084		case fmt {
1085		'z' =>
1086			if(fs.isrock)
1087				d.gid = "ridge";
1088			else
1089				d.gid = "iso";
1090		'r' =>
1091			d.gid = "sierra";
1092		'J' =>
1093			d.gid = "joliet";
1094		* =>
1095			d.gid = "???";
1096		}
1097		flags := dp.flags;
1098		if(flags & 2){
1099			d.qid.qtype = QTDIR;
1100			d.mode |= DMDIR|8r111;
1101		}
1102		d.uid = "cdrom";
1103		for(i := 0; i < len d.name; i++)
1104			if(d.name[i] == ';') {
1105				vers = int string d.name[i+1:];	# inefficient
1106				d.name = d.name[0:i];	# inefficient
1107				break;
1108			}
1109		n = len dp.data - fs.suspoff;
1110		if(fs.isrock && n >= 4){
1111			s := dp.data[fs.suspoff:];
1112			nm := 0;
1113			while(n >= 4 && have != (Hname|Hmode)){
1114				l := int s[2];
1115				if(s[0] == byte 'P' && s[1] == byte 'X' && s[3] == byte 1){
1116					# posix file attributes
1117					mode := l32(s[4:12]);
1118					d.mode = mode & 8r777;
1119					if((mode & 8r170000) == 8r0040000){
1120						d.mode |= DMDIR;
1121						d.qid.qtype = QTDIR;
1122					}
1123					have |= Hmode;
1124				} else if(s[0] == byte 'N' && s[1] == byte 'M' && s[3] == byte 1){
1125					# alternative name
1126					flags = int s[4];
1127					if((flags & ~1) == 0){
1128						if(nm == 0){
1129							d.name = string s[5:l];
1130							nm = 1;
1131						} else
1132							d.name += string s[5:l];
1133						if(flags == 0)
1134							have |= Hname;	# no more
1135					}
1136				} else if(s[0] == byte 'C' && s[1] == byte 'E' && int s[2] >= 28){
1137					(s, n) = getcontin(fs.d, s);
1138					continue;
1139				} else if(s[0] == byte 'S' && s[1] == byte 'T')
1140					break;
1141				n -= l;
1142				s = s[l:];
1143			}
1144		}
1145	}
1146	d.length = big 0;
1147	if((d.mode & DMDIR) == 0)
1148		d.length = big dp.size;
1149	d.dtype = 0;
1150	d.dev = 0;
1151	d.atime = dp.time;
1152	d.mtime = d.atime;
1153	return vers;
1154}
1155
1156convM2Drec(a: array of byte, d: ref Drec, highsierra: int)
1157{
1158	d.reclen = int a[0];
1159	d.attrlen = int a[1];
1160	d.addr = int l32(a[2:10]);
1161	d.size = int l32(a[10:18]);
1162	d.time = gtime(a[18:24]);
1163	d.date = array[7] of byte;
1164	d.date[0:] = a[18:25];
1165	if(highsierra){
1166		d.tzone = 0;
1167		d.flags = int a[24];
1168		d.unitsize = 0;
1169		d.gapsize = 0;
1170		d.vseqno = 0;
1171	} else {
1172		d.tzone = int a[24];
1173		d.flags = int a[25];
1174		d.unitsize = int a[26];
1175		d.gapsize = int a[27];
1176		d.vseqno = l32(a[28:32]);
1177	}
1178	n := int a[32];
1179	d.name = array[n] of byte;
1180	d.name[0:] = a[33:33+n];
1181	n += 33;
1182	if(n & 1)
1183		n++;	# check this
1184	syslen := d.reclen - n;
1185	if(syslen > 0){
1186		d.data = array[syslen] of byte;
1187		d.data[0:] = a[n:n+syslen];
1188	} else
1189		d.data = nil;
1190}
1191
1192nstr(p: array of byte): string
1193{
1194	q := "";
1195	n := len p;
1196	for(i := 0; i < n; i++){
1197		if(int p[i] == '\\')
1198			q[len q] = '\\';
1199		if(' ' <= int p[i] && int p[i] <= '~')
1200			q[len q] = int p[i];
1201		else
1202			q += sys->sprint("\\%2.2ux", int p[i]);
1203	}
1204	return q;
1205}
1206
1207rdate(p: array of byte, fmt: int): string
1208{
1209	c: int;
1210
1211	s := sys->sprint("%2.2d.%2.2d.%2.2d %2.2d:%2.2d:%2.2d",
1212		int p[0], int p[1], int p[2], int p[3], int p[4], int p[5]);
1213	if(fmt == 'z'){
1214		htz := int p[6];
1215		if(htz >= 128){
1216			htz = 256-htz;
1217			c = '-';
1218		}else
1219			c = '+';
1220		s += sys->sprint(" (%c%.1f)", c, real htz/2.0);
1221	}
1222	return s;
1223}
1224
1225dmsize := array[] of {
1226	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
1227};
1228
1229dysize(y: int): int
1230{
1231	if((y%4) == 0)
1232		return 366;
1233	return 365;
1234}
1235
1236gtime(p: array of byte): int	# yMdhms
1237{
1238	y:=int p[0]; M:=int p[1]; d:=int p[2];
1239	h:=int p[3]; m:=int p[4]; s:=int p[5];;
1240	if(y < 70)
1241		return 0;
1242	if(M < 1 || M > 12)
1243		return 0;
1244	if(d < 1 || d > dmsize[M-1])
1245		return 0;
1246	if(h > 23)
1247		return 0;
1248	if(m > 59)
1249		return 0;
1250	if(s > 59)
1251		return 0;
1252	y += 1900;
1253	t := 0;
1254	for(i:=1970; i<y; i++)
1255		t += dysize(i);
1256	if(dysize(y)==366 && M >= 3)
1257		t++;
1258	M--;
1259	while(M-- > 0)
1260		t += dmsize[M];
1261	t += d-1;
1262	t = 24*t + h;
1263	t = 60*t + m;
1264	t = 60*t + s;
1265	return t;
1266}
1267
1268l16(p: array of byte): int
1269{
1270	v := (int p[1]<<8)| int p[0];
1271	if (v >= 16r8000)
1272		v -= 16r10000;
1273	return v;
1274}
1275
1276l32(p: array of byte): int
1277{
1278	return (((((int p[3]<<8)| int p[2])<<8)| int p[1])<<8)| int p[0];
1279}
1280
1281eqs(a: array of byte, b: string): int
1282{
1283	if(len a != len b)
1284		return 0;
1285	for(i := 0; i < len a; i++)
1286		if(int a[i] != b[i])
1287			return 0;
1288	return 1;
1289}
1290
1291tolower(c: int): int
1292{
1293	if(c >= 'A' && c <= 'Z')
1294		return c-'A' + 'a';
1295	return c;
1296}
1297
1298#
1299# I/O buffers
1300#
1301
1302Device: adt {
1303	inuse:	int;	# attach count
1304	name:	string;	# of underlying file
1305	fd:	ref Sys->FD;
1306	sectorsize:	int;
1307	qid:	Sys->Qid;	# (qid,dtype,dev) identify uniquely
1308	dtype:	int;
1309	dev:	int;
1310
1311	detach:	fn(nil: self ref Device);
1312};
1313
1314Block: adt {
1315	dev:	ref Device;
1316	addr:	int;
1317	data:	array of byte;
1318
1319	# internal
1320	next:	cyclic ref Block;
1321	prev:	cyclic ref Block;
1322	busy:	int;
1323
1324	get:	fn(nil: ref Device, addr: int): ref Block;
1325	put:	fn(nil: self ref Block);
1326};
1327
1328devices:	list of ref Device;
1329
1330NIOB:	con 100;	# for starters
1331HIOB:	con 127;	# prime
1332
1333hiob := array[HIOB] of list of ref Block;	# hash buckets
1334iohead:	ref Block;
1335iotail:	ref Block;
1336bufsize := 0;
1337
1338iobufinit(bsize: int)
1339{
1340	bufsize = bsize;
1341	for(i:=0; i<NIOB; i++)
1342		newblock();
1343}
1344
1345newblock(): ref Block
1346{
1347	p := ref Block;
1348	p.busy = 0;
1349	p.addr = -1;
1350	p.dev = nil;
1351	p.data = array[bufsize] of byte;
1352	p.next = iohead;
1353	if(iohead != nil)
1354		iohead.prev = p;
1355	iohead = p;
1356	if(iotail == nil)
1357		iotail = p;
1358	return p;
1359}
1360
1361Block.get(dev: ref Device, addr: int): ref Block
1362{
1363	p: ref Block;
1364
1365	dh := hiob[addr%HIOB:];
1366	for(l := dh[0]; l != nil; l = tl l) {
1367		p = hd l;
1368		if(p.addr == addr && p.dev == dev) {
1369			p.busy++;
1370			return p;
1371		}
1372	}
1373	# Find a non-busy buffer from the tail
1374	for(p = iotail; p != nil && p.busy; p = p.prev)
1375		;
1376	if(p == nil)
1377		p = newblock();
1378
1379	# Delete from hash chain
1380	if(p.addr >= 0) {
1381		hp := hiob[p.addr%HIOB:];
1382		l = nil;
1383		for(f := hp[0]; f != nil; f = tl f)
1384			if(hd f != p)
1385				l = (hd f) :: l;
1386		hp[0] = l;
1387	}
1388
1389	# Hash and fill
1390	p.addr = addr;
1391	p.dev = dev;
1392	p.busy++;
1393	sys->seek(dev.fd, big addr*big dev.sectorsize, 0);
1394	if(sys->read(dev.fd, p.data, dev.sectorsize) != dev.sectorsize){
1395		p.addr = -1;	# stop caching
1396		p.put();
1397		purge(dev);
1398		return nil;
1399	}
1400	dh[0] = p :: dh[0];
1401	return p;
1402}
1403
1404Block.put(p: self ref Block)
1405{
1406	p.busy--;
1407	if(p.busy < 0)
1408		panic("Block.put");
1409
1410	if(p == iohead)
1411		return;
1412
1413	# Link onto head for lru
1414	if(p.prev != nil)
1415		p.prev.next = p.next;
1416	else
1417		iohead = p.next;
1418
1419	if(p.next != nil)
1420		p.next.prev = p.prev;
1421	else
1422		iotail = p.prev;
1423
1424	p.prev = nil;
1425	p.next = iohead;
1426	iohead.prev = p;
1427	iohead = p;
1428}
1429
1430purge(dev: ref Device)
1431{
1432	for(i := 0; i < HIOB; i++){
1433		l := hiob[i];
1434		hiob[i] = nil;
1435		for(; l != nil; l = tl l){	# reverses bucket's list, but never mind
1436			p := hd l;
1437			if(p.dev == dev)
1438				p.busy = 0;
1439			else
1440				hiob[i] = p :: hiob[i];
1441		}
1442	}
1443}
1444
1445devattach(name: string, mode: int, sectorsize: int): (ref Device, string)
1446{
1447	if(sectorsize > bufsize)
1448		return (nil, "sector size too big");
1449	fd := sys->open(name, mode);
1450	if(fd == nil)
1451		return(nil, sys->sprint("%s: can't open: %r", name));
1452	(rc, dir) := sys->fstat(fd);
1453	if(rc < 0)
1454		return (nil, sys->sprint("%r"));
1455	for(dl := devices; dl != nil; dl = tl dl){
1456		d := hd dl;
1457		if(d.qid.path != dir.qid.path || d.qid.vers != dir.qid.vers)
1458			continue;
1459		if(d.dtype != dir.dtype || d.dev != dir.dev)
1460			continue;
1461		d.inuse++;
1462		if(chatty)
1463			sys->print("inuse=%d, \"%s\", dev=%H...\n", d.inuse, d.name, d.fd);
1464		return (d, nil);
1465	}
1466	if(chatty)
1467		sys->print("alloc \"%s\", dev=%H...\n", name, fd);
1468	d := ref Device;
1469	d.inuse = 1;
1470	d.name = name;
1471	d.qid = dir.qid;
1472	d.dtype = dir.dtype;
1473	d.dev = dir.dev;
1474	d.fd = fd;
1475	d.sectorsize = sectorsize;
1476	devices = d :: devices;
1477	return (d, nil);
1478}
1479
1480Device.detach(d: self ref Device)
1481{
1482	d.inuse--;
1483	if(d.inuse < 0)
1484		panic("putxdata");
1485	if(chatty)
1486		sys->print("decref=%d, \"%s\", dev=%H...\n", d.inuse, d.name, d.fd);
1487	if(d.inuse == 0){
1488		if(chatty)
1489			sys->print("purge...\n");
1490		purge(d);
1491		dl := devices;
1492		devices = nil;
1493		for(; dl != nil; dl = tl dl)
1494			if((hd dl) != d)
1495				devices = (hd dl) :: devices;
1496	}
1497}
1498
1499panic(s: string)
1500{
1501	sys->print("panic: %s\n", s);
1502	a: array of byte;
1503	a[5] = byte 0; # trap
1504}
1505