xref: /inferno-os/appl/cmd/dossrv.b (revision c93eaa9a36d5071c19f42debcef978afd89ec994)
1implement Dossrv;
2
3include "sys.m";
4	sys: Sys;
5
6include "draw.m";
7
8include "arg.m";
9
10include "daytime.m";
11	daytime: Daytime;
12
13include "styx.m";
14	styx: Styx;
15	Tmsg, Rmsg: import styx;
16
17Dossrv: module
18{
19        init:   fn(ctxt: ref Draw->Context, args: list of string);
20        system:   fn(ctxt: ref Draw->Context, args: list of string): string;
21};
22
23arg0 := "dossrv";
24
25deffile: string;
26pflag := 0;
27debug := 0;
28
29usage(iscmd: int): string
30{
31	sys->fprint(sys->fildes(2), "usage: %s [-v] [-s] [-F] [-c] [-S secpertrack] [-f devicefile] [-m mountpoint]\n", arg0);
32	if(iscmd)
33		raise "fail:usage";
34	return "usage";
35}
36
37init(nil: ref Draw->Context, args: list of string)
38{
39	e := init2(nil, args, 1);
40	if(e != nil){
41		sys->fprint(sys->fildes(2), "%s: %s\n", arg0, e);
42		raise "fail:error";
43	}
44}
45
46system(nil: ref Draw->Context, args: list of string): string
47{
48	e := init2(nil, args, 0);
49	if(e != nil)
50		sys->fprint(sys->fildes(2), "%s: %s\n", arg0, e);
51	return e;
52}
53
54nomod(s: string): string
55{
56	return sys->sprint("can't load %s: %r", s);
57}
58
59init2(nil: ref Draw->Context, args: list of string, iscmd: int): string
60{
61	sys = load Sys Sys->PATH;
62
63	pipefd := array[2] of ref Sys->FD;
64
65	srvfile := "/n/dos";
66	deffile = "";	# no default, for safety
67	sectors := 0;
68	stdin := 0;
69
70	arg := load Arg Arg->PATH;
71	if(arg == nil)
72		return nomod(Arg->PATH);
73	arg->init(args);
74	arg0 = arg->progname();
75	while((o := arg->opt()) != 0) {
76		case o {
77		'v' =>
78			if(debug & STYX_MESS)
79				debug |= VERBOSE;
80			debug |= STYX_MESS;
81		'F' =>
82			debug |= FAT_INFO;
83		'c' =>
84			debug |= CLUSTER_INFO;
85			iodebug = 1;
86		'S' =>
87			s := arg->arg();
88			if(s != nil && s[0]>='0' && s[0]<='9')
89				sectors = int s;
90			else
91				return usage(iscmd);
92		's' =>
93			stdin = 1;
94		'f' =>
95			deffile = arg->arg();
96			if(deffile == nil)
97				return usage(iscmd);
98		'm' =>
99			srvfile = arg->arg();
100			if(srvfile == nil)
101				return usage(iscmd);
102		'p' =>
103			pflag++;
104		* =>
105			return usage(iscmd);
106		}
107	}
108	args = arg->argv();
109	arg = nil;
110
111	if(deffile == "" || !stdin && srvfile == "")
112		return usage(iscmd);
113
114	styx = load Styx Styx->PATH;
115	if(styx == nil)
116		return nomod(Styx->PATH);
117	styx->init();
118
119	daytime = load Daytime Daytime->PATH;
120	if(daytime == nil)
121		return nomod(Daytime->PATH);
122
123	iotrackinit(sectors);
124
125	if(!stdin) {
126		if(sys->pipe(pipefd) < 0)
127			return sys->sprint("can't create pipe: %r");
128	}else{
129		pipefd[0] = nil;
130		pipefd[1] = sys->fildes(1);
131	}
132
133	dossetup();
134
135	spawn dossrv(pipefd[1]);
136
137	if(!stdin) {
138		if(sys->mount(pipefd[0], nil, srvfile, sys->MREPL|sys->MCREATE, deffile) < 0)
139			return sys->sprint("mount %s: %r", srvfile);
140	}
141
142	return nil;
143}
144
145#
146# Styx server
147#
148
149	Enevermind,
150	Eformat,
151	Eio,
152	Enomem,
153	Enonexist,
154	Enotdir,
155	Enofid,
156	Efidopen,
157	Efidinuse,
158	Eexist,
159	Eperm,
160	Enofilsys,
161	Eauth,
162	Econtig,
163	Efull,
164	Eopen,
165	Ephase: con iota;
166
167errmsg := array[] of {
168	Enevermind	=> "never mind",
169	Eformat		=> "unknown format",
170	Eio		=> "I/O error",
171	Enomem		=> "server out of memory",
172	Enonexist	=> "file does not exist",
173	Enotdir => "not a directory",
174	Enofid => "no such fid",
175	Efidopen => "fid already open",
176	Efidinuse => "fid in use",
177	Eexist		=> "file exists",
178	Eperm		=> "permission denied",
179	Enofilsys	=> "no file system device specified",
180	Eauth		=> "authentication failed",
181	Econtig =>	"out of contiguous disk space",
182	Efull =>	"file system full",
183	Eopen =>	"invalid open mode",
184	Ephase => "phase error -- directory entry not found",
185};
186
187e(n: int): ref Rmsg.Error
188{
189	if(n < 0 || n >= len errmsg)
190		return ref Rmsg.Error(0, "it's thermal problems");
191	return ref Rmsg.Error(0, errmsg[n]);
192}
193
194dossrv(rfd: ref Sys->FD)
195{
196	sys->pctl(Sys->NEWFD, rfd.fd :: 2 :: nil);
197	rfd = sys->fildes(rfd.fd);
198	while((t := Tmsg.read(rfd, 0)) != nil){
199		if(debug & STYX_MESS)
200			chat(sys->sprint("%s...", t.text()));
201
202		r: ref Rmsg;
203		pick m := t {
204		Readerror =>
205			panic(sys->sprint("mount read error: %s", m.error));
206		Version =>
207 			r = rversion(m);
208		Auth =>
209			r = rauth(m);
210		Flush =>
211 			r = rflush(m);
212		Attach =>
213 			r = rattach(m);
214		Walk =>
215 			r = rwalk(m);
216		Open =>
217 			r = ropen(m);
218		Create =>
219 			r = rcreate(m);
220		Read =>
221 			r = rread(m);
222		Write =>
223 			r = rwrite(m);
224		Clunk =>
225 			r = rclunk(m);
226		Remove =>
227 			r = rremove(m);
228		Stat =>
229 			r = rstat(m);
230		Wstat =>
231 			r = rwstat(m);
232		* =>
233			panic("Styx mtype");
234		}
235		pick m := r {
236		Error =>
237			r.tag = t.tag;
238		}
239		rbuf := r.pack();
240		if(rbuf == nil)
241			panic("Rmsg.pack");
242		if(debug & STYX_MESS)
243			chat(sys->sprint("%s\n", r.text()));
244		if(sys->write(rfd, rbuf, len rbuf) != len rbuf)
245			panic("mount write");
246	}
247
248	if(debug & STYX_MESS)
249		chat("server EOF\n");
250}
251
252rversion(t: ref Tmsg.Version): ref Rmsg
253{
254	(msize, version) := styx->compatible(t, Styx->MAXRPC, Styx->VERSION);
255	return ref Rmsg.Version(t.tag, msize, version);
256}
257
258rauth(t: ref Tmsg.Auth): ref Rmsg
259{
260	return ref Rmsg.Error(t.tag, "authentication not required");
261}
262
263rflush(t: ref Tmsg.Flush): ref Rmsg
264{
265	return ref Rmsg.Flush(t.tag);
266}
267
268rattach(t: ref Tmsg.Attach): ref Rmsg
269{
270	root := xfile(t.fid, Clean);
271	if(root == nil)
272		return e(Eio);
273	if(t.aname == nil)
274		t.aname = deffile;
275	(xf, ec) := getxfs(t.aname);
276	root.xf = xf;
277	if(xf == nil) {
278		if(root!=nil)
279			xfile(t.fid, Clunk);
280		return ref Rmsg.Error(t.tag, ec);
281	}
282	if(xf.fmt == 0 && dosfs(xf) < 0){
283		if(root!=nil)
284			xfile(t.fid, Clunk);
285		return e(Eformat);
286	}
287
288	root.qid = Sys->Qid(big 0, 0, Sys->QTDIR);
289	root.xf.rootqid = root.qid;
290	return ref Rmsg.Attach(t.tag, root.qid);
291}
292
293clone(ofl: ref Xfile, newfid: int): ref Xfile
294{
295	nfl := xfile(newfid, Clean);
296	next := nfl.next;
297	*nfl = *ofl;
298	nfl.ptr = nil;
299	nfl.next = next;
300	nfl.fid = newfid;
301	refxfs(nfl.xf, 1);
302	if(ofl.ptr != nil){
303		dp := ref *ofl.ptr;
304		dp.p = nil;
305		dp.d = nil;
306		nfl.ptr = dp;
307	}
308	return nfl;
309}
310
311walk1(f: ref Xfile, name: string): ref Rmsg.Error
312{
313	if((f.qid.qtype & Sys->QTDIR) == 0){
314		if(debug)
315			chat(sys->sprint("qid.path=0x%bx...", f.qid.path));
316		return e(Enotdir);
317	}
318
319	if(name == ".")	# can't happen
320		return nil;
321
322	if(name== "..") {
323		if(f.qid.path == f.xf.rootqid.path) {
324			if (debug)
325				chat("walkup from root...");
326			return nil;
327		}
328		(r,dp) := walkup(f);
329		if(r < 0)
330			return e(Enonexist);
331
332		f.ptr = dp;
333		if(dp.addr == 0) {
334			f.qid.path = f.xf.rootqid.path;
335			f.qid.qtype = Sys->QTFILE;
336		} else {
337			f.qid.path = QIDPATH(dp);
338			f.qid.qtype = Sys->QTDIR;
339		}
340	} else {
341		if(getfile(f) < 0)
342			return e(Enonexist);
343		(r,dp) := searchdir(f, name, 0,1);
344		putfile(f);
345		if(r < 0)
346			return e(Enonexist);
347
348		f.ptr = dp;
349		f.qid.path = QIDPATH(dp);
350		f.qid.qtype = Sys->QTFILE;
351		if(dp.addr == 0)
352			f.qid.path = f.xf.rootqid.path;
353		else {
354			d := Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]);
355			if((int d.attr & DDIR) !=  0)
356				f.qid.qtype = Sys->QTDIR;
357		}
358		putfile(f);
359	}
360	return nil;
361}
362
363rwalk(t: ref Tmsg.Walk): ref Rmsg
364{
365	f := xfile(t.fid, Asis);
366	if(f==nil) {
367		if(debug)
368			chat("no xfile...");
369		return e(Enofid);
370	}
371	nf: ref Xfile;
372	if(t.newfid != t.fid)
373		f = nf = clone(f, t.newfid);
374	qids: array of Sys->Qid;
375	if(len t.names > 0){
376		savedqid := f.qid;
377		savedptr := f.ptr;
378		qids = array[len t.names] of Sys->Qid;
379		for(i := 0; i < len t.names; i++){
380			e := walk1(f, t.names[i]);
381			if(e != nil){
382				f.qid = savedqid;
383				f.ptr = savedptr;
384				if(nf != nil)
385					xfile(t.newfid, Clunk);
386				if(i == 0)
387					return e;
388				return ref Rmsg.Walk(t.tag, qids[0:i]);
389			}
390			qids[i] = f.qid;
391		}
392	}
393	return ref Rmsg.Walk(t.tag, qids);
394}
395
396ropen(t: ref Tmsg.Open): ref Rmsg
397{
398	attr: int;
399
400	omode := 0;
401	f := xfile(t.fid, Asis);
402	if(f == nil)
403		return e(Enofid);
404	if((f.flags&Omodes) != 0)
405		return e(Efidopen);
406
407	dp := f.ptr;
408	if(dp.paddr && (t.mode & Styx->ORCLOSE) != 0) {
409		# check on parent directory of file to be deleted
410		p := getsect(f.xf, dp.paddr);
411		if(p == nil)
412			return e(Eio);
413		# 11 is the attr byte offset in a FAT directory entry
414		attr = int p.iobuf[dp.poffset+11];
415		putsect(p);
416		if((attr & int DRONLY) != 0)
417			return e(Eperm);
418		omode |= Orclose;
419	} else if(t.mode & Styx->ORCLOSE)
420		omode |= Orclose;
421
422	if(getfile(f) < 0)
423		return e(Enonexist);
424
425	if(dp.addr != 0) {
426		d := Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]);
427		attr = int d.attr;
428	} else
429		attr = int DDIR;
430
431	case t.mode & 7 {
432	Styx->OREAD or
433	Styx->OEXEC =>
434		omode |= Oread;
435	Styx->ORDWR =>
436		omode |= Oread;
437		omode |= Owrite;
438		if(attr & int (DRONLY|DDIR)) {
439			putfile(f);
440			return e(Eperm);
441		}
442	Styx->OWRITE =>
443		omode |= Owrite;
444		if(attr & int (DRONLY|DDIR)) {
445			putfile(f);
446			return e(Eperm);
447		}
448	* =>
449		putfile(f);
450		return e(Eopen);
451	}
452
453	if(t.mode & Styx->OTRUNC) {
454		if((attr & int DDIR)!=0 || (attr & int DRONLY) != 0) {
455			putfile(f);
456			return e(Eperm);
457		}
458
459		if(truncfile(f) < 0) {
460			putfile(f);
461			return e(Eio);
462		}
463	}
464
465	f.flags |= omode;
466	putfile(f);
467	return ref Rmsg.Open(t.tag, f.qid, Styx->MAXFDATA);
468}
469
470mkdentry(xf: ref Xfs, ndp: ref Dosptr, name: string, sname: string, islong: int, nattr: byte, start: array of byte, length: array of byte): int
471{
472	ndp.p = getsect(xf, ndp.addr);
473	if(ndp.p == nil)
474		return Eio;
475	if(islong && (r := putlongname(xf, ndp, name, sname)) < 0){
476		putsect(ndp.p);
477		if(r == -2)
478			return Efull;
479		return Eio;
480	}
481
482	nd := ref Dosdir(".       ","   ",byte 0,array[10] of { * => byte 0},
483			array[2] of { * => byte 0}, array[2] of { * => byte 0},
484			array[2] of { * => byte 0},array[4] of { * => byte 0});
485
486	nd.attr = nattr;
487	puttime(nd);
488	nd.start[0: ] = start[0: 2];
489	nd.length[0: ] = length[0: 4];
490
491	if(islong)
492		putname(sname[0:8]+"."+sname[8:11], nd);
493	else
494		putname(name, nd);
495	ndp.p.iobuf[ndp.offset: ] = Dosdir.Dd2arr(nd);
496	ndp.p.flags |= BMOD;
497	return 0;
498}
499
500rcreate(t: ref Tmsg.Create): ref Rmsg
501{
502	bp: ref Dosbpb;
503	omode:=0;
504	start:=0;
505	sname := "";
506	islong :=0;
507
508	f := xfile(t.fid, Asis);
509	if(f == nil)
510		return e(Enofid);
511	if((f.flags&Omodes) != 0)
512		return e(Efidopen);
513	if(getfile(f)<0)
514		return e(Eio);
515
516	pdp := f.ptr;
517	if(pdp.addr != 0)
518		pd := Dosdir.arr2Dd(pdp.p.iobuf[pdp.offset:pdp.offset+DOSDIRSIZE]);
519	else
520		pd = nil;
521
522	if(pd != nil)
523		attr := int pd.attr;
524	else
525		attr = DDIR;
526
527	if(!(attr & DDIR) || (attr & DRONLY)) {
528		putfile(f);
529		return e(Eperm);
530	}
531
532	if(t.mode & Styx->ORCLOSE)
533		omode |= Orclose;
534
535	case (t.mode & 7) {
536	Styx->OREAD or
537	Styx->OEXEC =>
538		omode |= Oread;
539	Styx->OWRITE or
540	Styx->ORDWR =>
541		if ((t.mode & 7) == Styx->ORDWR)
542			omode |= Oread;
543		omode |= Owrite;
544		if(t.perm & Sys->DMDIR){
545			putfile(f);
546			return e(Eperm);
547		}
548	* =>
549		putfile(f);
550		return e(Eopen);
551	}
552
553	if(t.name=="." || t.name=="..") {
554		putfile(f);
555		return e(Eperm);
556	}
557
558	(r,ndp) := searchdir(f, t.name, 1, 1);
559	if(r < 0) {
560		putfile(f);
561		if(r == -2)
562			return e(Efull);
563		return e(Eexist);
564	}
565
566	nds := name2de(t.name);
567	if(nds > 0) {
568		# long file name, find "new" short name
569		i := 1;
570		for(;;) {
571			sname = long2short(t.name, i);
572			(r1, tmpdp) := searchdir(f, sname, 0, 0);
573			if(r1 < 0)
574				break;
575			putsect(tmpdp.p);
576			i++;
577		}
578		islong = 1;
579	}
580
581	# allocate first cluster, if making directory
582	if(t.perm & Sys->DMDIR) {
583		bp = f.xf.ptr;
584		start = falloc(f.xf);
585		if(start <= 0) {
586			putfile(f);
587			return e(Efull);
588		}
589	}
590
591	 # now we're committed
592	if(pd != nil) {
593		puttime(pd);
594		pdp.p.flags |= BMOD;
595	}
596
597	f.ptr = ndp;
598	ndp.p = getsect(f.xf, ndp.addr);
599	if(ndp.p == nil ||
600	   islong && putlongname(f.xf, ndp, t.name, sname) < 0){
601		putsect(pdp.p);
602		if(ndp.p != nil)
603			putsect(ndp.p);
604		return e(Eio);
605	}
606
607	nd := ref Dosdir(".       ","   ",byte 0,array[10] of { * => byte 0},
608			array[2] of { * => byte 0}, array[2] of { * => byte 0},
609			array[2] of { * => byte 0},array[4] of { * => byte 0});
610
611	if((t.perm & 8r222) == 0)
612		nd.attr |= byte DRONLY;
613
614	puttime(nd);
615	nd.start[0] = byte start;
616	nd.start[1] = byte (start>>8);
617
618	if(islong)
619		putname(sname[0:8]+"."+sname[8:11], nd);
620	else
621		putname(t.name, nd);
622
623	f.qid.path = QIDPATH(ndp);
624	if(t.perm & Sys->DMDIR) {
625		nd.attr |= byte DDIR;
626		f.qid.qtype |= Sys->QTDIR;
627		xp := getsect(f.xf, bp.dataaddr+(start-2)*bp.clustsize);
628		if(xp == nil) {
629			if(ndp.p!=nil)
630				putfile(f);
631			putsect(pdp.p);
632			return e(Eio);
633		}
634		xd := ref *nd;
635		xd.name = ".       ";
636		xd.ext = "   ";
637		xp.iobuf[0:] = Dosdir.Dd2arr(xd);
638		if(pd!=nil)
639			xd = ref *pd;
640		else{
641			xd = ref Dosdir("..      ","   ",byte 0,
642				array[10] of { * => byte 0},
643				array[2] of { * => byte 0},
644				array[2] of { * => byte 0},
645				array[2] of { * => byte 0},
646				array[4] of { * => byte 0});
647
648			puttime(xd);
649			xd.attr = byte DDIR;
650		}
651		xd.name="..      ";
652		xd.ext="   ";
653		xp.iobuf[DOSDIRSIZE:] = Dosdir.Dd2arr(xd);
654		xp.flags |= BMOD;
655		putsect(xp);
656	}else
657		f.qid.qtype = Sys->QTFILE;
658
659	ndp.p.flags |= BMOD;
660	tmp := Dosdir.Dd2arr(nd);
661	ndp.p.iobuf[ndp.offset:]= tmp;
662	putfile(f);
663	putsect(pdp.p);
664
665	f.flags |= omode;
666	return ref Rmsg.Create(t.tag, f.qid, Styx->MAXFDATA);
667}
668
669rread(t: ref Tmsg.Read): ref Rmsg
670{
671	r: int;
672	data: array of byte;
673
674	if(((f:=xfile(t.fid, Asis))==nil) ||
675	    (f.flags&Oread == 0))
676		return e(Eio);
677
678	if((f.qid.qtype & Sys->QTDIR) != 0) {
679		if(getfile(f) < 0)
680			return e(Eio);
681		(r, data) = readdir(f, int t.offset, t.count);
682	} else {
683		if(getfile(f) < 0)
684			return e(Eio);
685		(r,data) = readfile(f, int t.offset, t.count);
686	}
687	putfile(f);
688
689	if(r < 0)
690		return e(Eio);
691	return ref Rmsg.Read(t.tag, data[0:r]);
692}
693
694rwrite(t: ref Tmsg.Write): ref Rmsg
695{
696	if(((f:=xfile(t.fid, Asis))==nil) ||
697	   !(f.flags&Owrite))
698		return e(Eio);
699	if(getfile(f) < 0)
700		return e(Eio);
701	r := writefile(f, t.data, int t.offset, len t.data);
702	putfile(f);
703	if(r < 0){
704		if(r == -2)
705			return e(Efull);
706		return e(Eio);
707	}
708	return ref Rmsg.Write(t.tag, r);
709}
710
711rclunk(t: ref Tmsg.Clunk): ref Rmsg
712{
713	xfile(t.fid, Clunk);
714	sync();
715	return ref Rmsg.Clunk(t.tag);
716}
717
718doremove(f: ref Xfs, dp: ref Dosptr)
719{
720	dp.p.iobuf[dp.offset] = byte DOSEMPTY;
721	dp.p.flags |= BMOD;
722	for(prevdo := dp.offset-DOSDIRSIZE; prevdo >= 0; prevdo-=DOSDIRSIZE){
723		if (dp.p.iobuf[prevdo+11] != byte DLONG)
724			break;
725		dp.p.iobuf[prevdo] = byte DOSEMPTY;
726	}
727
728	if (prevdo <= 0 && dp.prevaddr != -1){
729		p := getsect(f,dp.prevaddr);
730		for(prevdo = f.ptr.sectsize-DOSDIRSIZE; prevdo >= 0; prevdo-=DOSDIRSIZE) {
731			if(p.iobuf[prevdo+11] != byte DLONG)
732				break;
733			p.iobuf[prevdo] = byte DOSEMPTY;
734			p.flags |= BMOD;
735		}
736		putsect(p);
737	}
738}
739
740rremove(t: ref Tmsg.Remove): ref Rmsg
741{
742	f := xfile(t.fid, Asis);
743	if(f == nil)
744		return e(Enofid);
745
746	if(!f.ptr.addr) {
747		if(debug)
748			chat("root...");
749		xfile(t.fid, Clunk);
750		sync();
751		return e(Eperm);
752	}
753
754	# check on parent directory of file to be deleted
755	parp := getsect(f.xf, f.ptr.paddr);
756	if(parp == nil) {
757		xfile(t.fid, Clunk);
758		sync();
759		return e(Eio);
760	}
761
762	pard := Dosdir.arr2Dd(parp.iobuf[f.ptr.poffset:f.ptr.poffset+DOSDIRSIZE]);
763	if(f.ptr.paddr && (int pard.attr & DRONLY)) {
764		if(debug)
765			chat("parent read-only...");
766		putsect(parp);
767		xfile(t.fid, Clunk);
768		sync();
769		return e(Eperm);
770	}
771
772	if(getfile(f) < 0){
773		if(debug)
774			chat("getfile failed...");
775		putsect(parp);
776		xfile(t.fid, Clunk);
777		sync();
778		return e(Eio);
779	}
780
781	dattr := int f.ptr.p.iobuf[f.ptr.offset+11];
782	if(dattr & DDIR && emptydir(f) < 0){
783		if(debug)
784			chat("non-empty dir...");
785		putfile(f);
786		putsect(parp);
787		xfile(t.fid, Clunk);
788		sync();
789		return e(Eperm);
790	}
791	if(f.ptr.paddr == 0 && dattr&DRONLY) {
792		if(debug)
793			chat("read-only file in root directory...");
794		putfile(f);
795		putsect(parp);
796		xfile(t.fid, Clunk);
797		sync();
798		return e(Eperm);
799	}
800
801	doremove(f.xf, f.ptr);
802
803	if(f.ptr.paddr) {
804		puttime(pard);
805		parp.flags |= BMOD;
806	}
807
808	parp.iobuf[f.ptr.poffset:] = Dosdir.Dd2arr(pard);
809	putsect(parp);
810	err := 0;
811	if(truncfile(f) < 0)
812		err = Eio;
813
814	putfile(f);
815	xfile(t.fid, Clunk);
816	sync();
817	if(err)
818		return e(err);
819	return ref Rmsg.Remove(t.tag);
820}
821
822rstat(t: ref Tmsg.Stat): ref Rmsg
823{
824	f := xfile(t.fid, Asis);
825	if(f == nil)
826		return e(Enofid);
827	if(getfile(f) < 0)
828		return e(Eio);
829	dir := dostat(f);
830	putfile(f);
831	return ref Rmsg.Stat(t.tag, *dir);
832}
833
834dostat(f: ref Xfile): ref Sys->Dir
835{
836	islong :=0;
837	prevdo: int;
838	longnamebuf:="";
839
840	# get file info.
841	dir := getdir(f.ptr.p.iobuf[f.ptr.offset:f.ptr.offset+DOSDIRSIZE],
842					f.ptr.addr, f.ptr.offset);
843	# get previous entry
844	if(f.ptr.prevaddr == -1) {
845		# maybe extended, but will never cross sector boundary...
846		# short filename at beginning of sector..
847		if(f.ptr.offset!=0) {
848			for(prevdo = f.ptr.offset-DOSDIRSIZE; prevdo >=0; prevdo-=DOSDIRSIZE) {
849				prevdattr := f.ptr.p.iobuf[prevdo+11];
850				if(prevdattr != byte DLONG)
851					break;
852				islong = 1;
853				longnamebuf += getnamesect(f.ptr.p.iobuf[prevdo:prevdo+DOSDIRSIZE]);
854			}
855		}
856	} else {
857		# extended and will cross sector boundary.
858		for(prevdo = f.ptr.offset-DOSDIRSIZE; prevdo >=0; prevdo-=DOSDIRSIZE) {
859			prevdattr := f.ptr.p.iobuf[prevdo+11];
860			if(prevdattr != byte DLONG)
861				break;
862			islong = 1;
863			longnamebuf += getnamesect(f.ptr.p.iobuf[prevdo:prevdo+DOSDIRSIZE]);
864		}
865		if (prevdo < 0) {
866			p := getsect(f.xf,f.ptr.prevaddr);
867			for(prevdo = f.xf.ptr.sectsize-DOSDIRSIZE; prevdo >=0; prevdo-=DOSDIRSIZE){
868				prevdattr := p.iobuf[prevdo+11];
869				if(prevdattr != byte DLONG)
870					break;
871				islong = 1;
872				longnamebuf += getnamesect(p.iobuf[prevdo:prevdo+DOSDIRSIZE]);
873			}
874			putsect(p);
875		}
876	}
877	if(islong)
878		dir.name = longnamebuf;
879	return dir;
880}
881
882nameok(elem: string): int
883{
884	isfrog := array[256] of {
885	# NUL
886	1, 1, 1, 1, 1, 1, 1, 1,
887	# BKS
888	1, 1, 1, 1, 1, 1, 1, 1,
889	# DLE
890	1, 1, 1, 1, 1, 1, 1, 1,
891	# CAN
892	1, 1, 1, 1, 1, 1, 1, 1,
893#	' ' =>	1,
894	'/' =>	1, 16r7f =>	1, * => 0
895	};
896
897	for(i:=0; i < len elem; i++) {
898		if(isfrog[elem[i]])
899			return -1;
900	}
901	return 0;
902}
903
904rwstat(t: ref Tmsg.Wstat): ref Rmsg
905{
906	f := xfile(t.fid, Asis);
907	if(f == nil)
908		return e(Enofid);
909
910	if(getfile(f) < 0)
911		return e(Eio);
912
913	dp := f.ptr;
914
915	if(dp.addr == 0){	# root
916		putfile(f);
917		return e(Eperm);
918	}
919
920	changes := 0;
921	dir := dostat(f);
922	wdir := ref t.stat;
923
924	if(dir.uid != wdir.uid || dir.gid != wdir.gid){
925		putfile(f);
926		return e(Eperm);
927	}
928
929	if(dir.mtime != wdir.mtime || ((dir.mode^wdir.mode) & 8r777))
930		changes = 1;
931
932	if((wdir.mode & 7) != ((wdir.mode >> 3) & 7)
933	|| (wdir.mode & 7) != ((wdir.mode >> 6) & 7)){
934		putfile(f);
935		return e(Eperm);
936	}
937
938	if(dir.name != wdir.name){
939		# temporarily disable this
940		# g.errno = Eperm;
941		# putfile(f);
942		# return;
943
944		#
945		# grab parent directory of file to be changed and check for write perm
946		# rename also disallowed for read-only files in root directory
947		#
948		parp := getsect(f.xf, dp.paddr);
949		if(parp == nil){
950			putfile(f);
951			return e(Eio);
952		}
953		# pard := Dosdir.arr2Dd(parp.iobuf[dp.poffset: dp.poffset+DOSDIRSIZE]);
954		pardattr := int parp.iobuf[dp.poffset+11];
955		dpd := Dosdir.arr2Dd(dp.p.iobuf[dp.offset: dp.offset+DOSDIRSIZE]);
956		if(dp.paddr != 0 && int pardattr & DRONLY
957		|| dp.paddr == 0 && int dpd.attr & DRONLY){
958			putsect(parp);
959			putfile(f);
960			return e(Eperm);
961		}
962
963		#
964		# retrieve info from old entry
965		#
966		oaddr := dp.addr;
967		ooffset := dp.offset;
968		d := dpd;
969#		od := *d;
970		# start := getstart(f.xf, d);
971		start := d.start;
972		length := d.length;
973		attr := d.attr;
974
975		#
976		# temporarily release file to allow other directory ops:
977		# walk to parent, validate new name
978		# then remove old entry
979		#
980		putfile(f);
981		pf := ref *f;
982		pdp := ref Dosptr(dp.paddr, dp.poffset, 0, 0, 0, 0, -1, -1, parp, nil);
983		# if(pdp.addr != 0)
984		# 	pdpd := Dosdir.arr2Dd(parp.iobuf[pdp.offset: pdp.offset+DOSDIRSIZE]);
985		# else
986		# 	pdpd = nil;
987		pf.ptr = pdp;
988		if(wdir.name == "." || wdir.name == ".."){
989			putsect(parp);
990			return e(Eperm);
991		}
992		islong := 0;
993		sname := "";
994		nds := name2de(wdir.name);
995		if(nds > 0) {
996			# long file name, find "new" short name
997			i := 1;
998			for(;;) {
999				sname = long2short(wdir.name, i);
1000				(r1, tmpdp) := searchdir(f, sname, 0, 0);
1001				if(r1 < 0)
1002					break;
1003				putsect(tmpdp.p);
1004				i++;
1005			}
1006			islong = 1;
1007		}else{
1008			(b, e) := dosname(wdir.name);
1009			sname = b+e;
1010		}
1011		# (r, ndp) := searchdir(pf, wdir.name, 1, 1);
1012		# if(r < 0){
1013		#	putsect(parp);
1014		#	g.errno = Eperm;
1015		#	return;
1016		# }
1017		if(getfile(f) < 0){
1018			putsect(parp);
1019			return e(Eio);
1020		}
1021		doremove(f.xf, dp);
1022		putfile(f);
1023
1024		#
1025		# search for dir entry again, since we may be able to use the old slot,
1026		# and we need to set up the naddr field if a long name spans the block.
1027		# create new entry.
1028		#
1029		r := 0;
1030		(r, dp) = searchdir(pf, sname, 1, islong);
1031		if(r < 0){
1032			putsect(parp);
1033			return e(Ephase);
1034		}
1035		if((r = mkdentry(pf.xf, dp, wdir.name, sname, islong, attr, start, length)) != 0){
1036			putsect(parp);
1037			return e(r);
1038		}
1039		putsect(parp);
1040
1041		#
1042		# relocate up other fids to the same file, if it moved
1043		#
1044		f.ptr = dp;
1045		f.qid.path = QIDPATH(dp);
1046		if(oaddr != dp.addr || ooffset != dp.offset)
1047			dosptrreloc(f, dp, oaddr, ooffset);
1048		changes = 1;
1049		# f = nil;
1050	}
1051
1052	if(changes){
1053		d := Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]);
1054		putdir(d, wdir);
1055		dp.p.iobuf[dp.offset: ] = Dosdir.Dd2arr(d);
1056		dp.p.flags |= BMOD;
1057	}
1058	if(f != nil)
1059		putfile(f);
1060	sync();
1061	return ref Rmsg.Wstat(t.tag);
1062}
1063
1064#
1065# FAT file system format
1066#
1067
1068Dospart: adt {
1069	active: byte;
1070	hstart: byte;
1071	cylstart: array of byte;
1072	typ: byte;
1073	hend: byte;
1074	cylend: array of byte;
1075	start: array of byte;
1076	length: array of byte;
1077};
1078
1079Dosboot: adt {
1080	arr2Db:	fn(arr: array of byte): ref Dosboot;
1081	magic:	array of byte;
1082	version:	array of byte;
1083	sectsize:	array of byte;
1084	clustsize:	byte;
1085	nresrv:	array of byte;
1086	nfats:	byte;
1087	rootsize:	array of byte;
1088	volsize:	array of byte;
1089	mediadesc:	byte;
1090	fatsize:	array of byte;
1091	trksize:	array of byte;
1092	nheads:	array of byte;
1093	nhidden:	array of byte;
1094	bigvolsize:	array of byte;
1095	driveno:	byte;
1096	bootsig:	byte;
1097	volid:	array of byte;
1098	label:	array of byte;
1099};
1100
1101Dosbpb: adt {
1102	sectsize: int;	# in bytes
1103	clustsize: int;	# in sectors
1104	nresrv: int;	# sectors
1105	nfats: int;	# usually 2
1106	rootsize: int;	# number of entries
1107	volsize: int;	# in sectors
1108	mediadesc: int;
1109	fatsize: int;	# in sectors
1110	fatclusters: int;
1111	fatbits: int;	# 12 or 16
1112	fataddr: int; #big;	# sector number
1113	rootaddr: int; #big;
1114	dataaddr: int; #big;
1115	freeptr: int; #big;	# next free cluster candidate
1116};
1117
1118Dosdir: adt {
1119	Dd2arr:	fn(d: ref Dosdir): array of byte;
1120	arr2Dd:	fn(arr: array of byte): ref Dosdir;
1121	name:	string;
1122	ext:		string;
1123	attr:		byte;
1124	reserved:	array of byte;
1125	time:		array of byte;
1126	date:		array of byte;
1127	start:		array of byte;
1128	length:	array of byte;
1129};
1130
1131Dosptr: adt {
1132	addr:	int;	# of file's directory entry
1133	offset:	int;
1134	paddr:	int;	# of parent's directory entry
1135	poffset:	int;
1136	iclust:	int;	# ordinal within file
1137	clust:	int;
1138	prevaddr:	int;
1139	naddr:	int;
1140	p:	ref Iosect;
1141	d:	ref Dosdir;
1142};
1143
1144Asis, Clean, Clunk: con iota;
1145
1146FAT12: con byte 16r01;
1147FAT16: con byte 16r04;
1148FATHUGE: con byte 16r06;
1149DMDDO: con 16r54;
1150DRONLY: con 16r01;
1151DHIDDEN: con 16r02;
1152DSYSTEM: con 16r04;
1153DVLABEL: con 16r08;
1154DDIR: con 16r10;
1155DARCH: con 16r20;
1156DLONG: con DRONLY | DHIDDEN | DSYSTEM | DVLABEL;
1157DMLONG: con DLONG | DDIR | DARCH;
1158
1159DOSDIRSIZE: con 32;
1160DOSEMPTY: con 16rE5;
1161DOSRUNES: con 13;
1162
1163FATRESRV: con 2;
1164
1165Oread: con  1;
1166Owrite: con  2;
1167Orclose: con  4;
1168Omodes: con  3;
1169
1170VERBOSE, STYX_MESS, FAT_INFO, CLUSTER_INFO: con (1 << iota);
1171
1172nowt, nowt1: int;
1173tzoff: int;
1174
1175#
1176# because we map all incoming short names from all upper to all lower case,
1177# and FAT cannot store mixed case names in short name form,
1178# we'll declare upper case as unacceptable to decide whether a long name
1179# is needed on output.  thus, long names are always written in the case
1180# in the system call, and are always read back as written; short names
1181# are produced by the common case of writing all lower case letters
1182#
1183isdos := array[256] of {
1184	'a' to 'z' => 1, 'A' to 'Z' => 0, '0' to '9' => 1,
1185	' ' => 1, '$' => 1, '%' => 1, '"' => 1, '-' => 1, '_' => 1, '@' => 1,
1186	'~' => 1, '`' => 1, '!' => 1, '(' => 1, ')' => 1, '{' => 1, '}' => 1, '^' => 1,
1187	'#' => 1, '&' => 1,
1188	* => 0
1189};
1190
1191dossetup()
1192{
1193	nowt = daytime->now();
1194	nowt1 = sys->millisec();
1195	tzoff = daytime->local(0).tzoff;
1196}
1197
1198# make xf into a Dos file system... or die trying to.
1199dosfs(xf: ref Xfs): int
1200{
1201	mbroffset := 0;
1202	i: int;
1203	p: ref Iosect;
1204
1205Dmddo:
1206	for(;;) {
1207		for(i=2; i>0; i--) {
1208			p = getsect(xf, 0);
1209			if(p == nil)
1210				return -1;
1211
1212			if((mbroffset == 0) && (p.iobuf[0] == byte 16re9))
1213				break;
1214
1215			# Check if the jump displacement (magic[1]) is too
1216			# short for a FAT. DOS 4.0 MBR has a displacement of 8.
1217			if(p.iobuf[0] == byte 16reb &&
1218			   p.iobuf[2] == byte 16r90 &&
1219			   p.iobuf[1] != byte 16r08)
1220				break;
1221
1222			if(i < 2 ||
1223			   p.iobuf[16r1fe] != byte 16r55 ||
1224			   p.iobuf[16r1ff] != byte 16raa) {
1225				i = 0;
1226				break;
1227			}
1228
1229			dp := 16r1be;
1230			for(j:=4; j>0; j--) {
1231				if(debug) {
1232					chat(sys->sprint("16r%2.2ux (%d,%d) 16r%2.2ux (%d,%d) %d %d...",
1233					int p.iobuf[dp], int p.iobuf[dp+1],
1234					bytes2short(p.iobuf[dp+2: dp+4]),
1235					int p.iobuf[dp+4], int p.iobuf[dp+5],
1236					bytes2short(p.iobuf[dp+6: dp+8]),
1237					bytes2int(p.iobuf[dp+8: dp+12]),
1238					bytes2int(p.iobuf[dp+12:dp+16])));
1239				}
1240
1241				# Check for a disc-manager partition in the MBR.
1242				# Real MBR is at lba 63. Unfortunately it starts
1243				# with 16rE9, hence the check above against magic.
1244				if(int p.iobuf[dp+4] == DMDDO) {
1245					mbroffset = 63*Sectorsize;
1246					putsect(p);
1247					purgebuf(xf);
1248					xf.offset += mbroffset;
1249					break Dmddo;
1250				}
1251
1252				# Make sure it really is the right type, other
1253				# filesystems can look like a FAT
1254				# (e.g. OS/2 BOOT MANAGER).
1255				if(p.iobuf[dp+4] == FAT12 ||
1256				   p.iobuf[dp+4] == FAT16 ||
1257				   p.iobuf[dp+4] == FATHUGE)
1258					break;
1259				dp+=16;
1260			}
1261
1262			if(j <= 0) {
1263				if(debug)
1264					chat("no active partition...");
1265				putsect(p);
1266				return -1;
1267			}
1268
1269			offset := bytes2int(p.iobuf[dp+8:dp+12])* Sectorsize;
1270			putsect(p);
1271			purgebuf(xf);
1272			xf.offset = mbroffset+offset;
1273		}
1274		break;
1275	}
1276	if(i <= 0) {
1277		if(debug)
1278			chat("bad magic...");
1279		putsect(p);
1280		return -1;
1281	}
1282
1283	b := Dosboot.arr2Db(p.iobuf);
1284	if(debug & FAT_INFO)
1285		bootdump(b);
1286
1287	bp := ref Dosbpb;
1288	xf.ptr = bp;
1289	xf.fmt = 1;
1290
1291	bp.sectsize = bytes2short(b.sectsize);
1292	bp.clustsize = int b.clustsize;
1293	bp.nresrv = bytes2short(b.nresrv);
1294	bp.nfats = int b.nfats;
1295	bp.rootsize = bytes2short(b.rootsize);
1296	bp.volsize = bytes2short(b.volsize);
1297	if(bp.volsize == 0)
1298		bp.volsize = bytes2int(b.bigvolsize);
1299	bp.mediadesc = int b.mediadesc;
1300	bp.fatsize = bytes2short(b.fatsize);
1301
1302	bp.fataddr = int bp.nresrv;
1303	bp.rootaddr = bp.fataddr + bp.nfats*bp.fatsize;
1304	i = bp.rootsize*DOSDIRSIZE + bp.sectsize-1;
1305	i /= bp.sectsize;
1306	bp.dataaddr = bp.rootaddr + i;
1307	bp.fatclusters = FATRESRV+(bp.volsize - bp.dataaddr)/bp.clustsize;
1308	if(bp.fatclusters < 4087)
1309		bp.fatbits = 12;
1310	else
1311		bp.fatbits = 16;
1312	bp.freeptr = 2;
1313	if(debug & FAT_INFO){
1314		chat(sys->sprint("fatbits=%d (%d clusters)...",
1315			bp.fatbits, bp.fatclusters));
1316		for(i=0; i< int b.nfats; i++)
1317			chat(sys->sprint("fat %d: %d...",
1318				i, bp.fataddr+i*bp.fatsize));
1319		chat(sys->sprint("root: %d...", bp.rootaddr));
1320		chat(sys->sprint("data: %d...", bp.dataaddr));
1321	}
1322	putsect(p);
1323	return 0;
1324}
1325
1326QIDPATH(dp: ref Dosptr): big
1327{
1328	return big (dp.addr*(Sectorsize/DOSDIRSIZE) + dp.offset/DOSDIRSIZE);
1329}
1330
1331isroot(addr: int): int
1332{
1333	return addr == 0;
1334}
1335
1336getfile(f: ref Xfile): int
1337{
1338	dp := f.ptr;
1339	if(dp.p!=nil)
1340		panic("getfile");
1341	if(dp.addr < 0)
1342		panic("getfile address");
1343	p := getsect(f.xf, dp.addr);
1344	if(p == nil)
1345		return -1;
1346
1347	dp.d = nil;
1348	if(!isroot(dp.addr)) {
1349		if(f.qid.path != QIDPATH(dp)){
1350			if(debug) {
1351				chat(sys->sprint("qid mismatch f=0x%x d=0x%x...",
1352					int f.qid.path, int QIDPATH(dp)));
1353			}
1354			putsect(p);
1355			return -1;
1356		}
1357	#	dp.d = Dosdir.arr2Dd(p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]);
1358	}
1359	dp.p = p;
1360	return 0;
1361}
1362
1363putfile(f: ref Xfile)
1364{
1365	dp := f.ptr;
1366	if(dp.p==nil)
1367		panic("putfile");
1368	putsect(dp.p);
1369	dp.p = nil;
1370	dp.d = nil;
1371}
1372
1373getstart(nil: ref Xfs, d: ref Dosdir): int
1374{
1375	start := bytes2short(d.start);
1376#	if(xf.isfat32)
1377#		start |= bytes2short(d.hstart)<<16;
1378	return start;
1379}
1380
1381putstart(nil: ref Xfs, d: ref Dosdir, start: int)
1382{
1383	d.start[0] = byte start;
1384	d.start[1] = byte (start>>8);
1385#	if(xf.isfat32){
1386#		d.hstart[0] = start>>16;
1387#		d.hstart[1] = start>>24;
1388#	}
1389}
1390
1391#
1392# return the disk cluster for the iclust cluster in f
1393#
1394fileclust(f: ref Xfile, iclust: int, cflag: int): int
1395{
1396
1397#	bp := f.xf.ptr;
1398	dp := f.ptr;
1399	if(isroot(dp.addr))
1400		return -1;		# root directory for old FAT format does not start on a cluster boundary
1401	d := dp.d;
1402	if(d == nil){
1403		if(dp.p == nil)
1404			panic("fileclust");
1405		d = Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]);
1406	}
1407	next := 0;
1408	start := getstart(f.xf, d);
1409	if(start == 0) {
1410		if(!cflag)
1411			return -1;
1412		start = falloc(f.xf);
1413		if(start <= 0)
1414			return -1;
1415		puttime(d);
1416		putstart(f.xf, d, start);
1417		dp.p.iobuf[dp.offset:] = Dosdir.Dd2arr(d);
1418		dp.p.flags |= BMOD;
1419		dp.clust = 0;
1420	}
1421
1422	clust, nskip: int;
1423	if(dp.clust == 0 || iclust < dp.iclust) {
1424		clust = start;
1425		nskip = iclust;
1426	} else {
1427		clust = dp.clust;
1428		nskip = iclust - dp.iclust;
1429	}
1430
1431	if(debug & CLUSTER_INFO  && nskip > 0)
1432		chat(sys->sprint("clust %d, skip %d...", clust, nskip));
1433
1434	if(clust <= 0)
1435		return -1;
1436
1437	if(nskip > 0) {
1438		while(--nskip >= 0) {
1439			next = getfat(f.xf, clust);
1440			if(debug & CLUSTER_INFO)
1441				chat(sys->sprint(".%d", next));
1442			if(next <= 0){
1443				if(!cflag)
1444					break;
1445				next = falloc(f.xf);
1446				if(next <= 0)
1447					return -1;
1448				putfat(f.xf, clust, next);
1449			}
1450			clust = next;
1451		}
1452		if(next <= 0)
1453			return -1;
1454		dp.clust = clust;
1455		dp.iclust = iclust;
1456	}
1457	if(debug & CLUSTER_INFO)
1458		chat(sys->sprint(" clust(%d)=0x%x...", iclust, clust));
1459	return clust;
1460}
1461
1462#
1463# return the disk sector for the isect disk sector in f,
1464# allocating space if necessary and cflag is set
1465#
1466fileaddr(f: ref Xfile, isect: int, cflag: int): int
1467{
1468	bp := f.xf.ptr;
1469	dp := f.ptr;
1470	if(isroot(dp.addr)) {
1471		if(isect*bp.sectsize >= bp.rootsize*DOSDIRSIZE)
1472			return -1;
1473		return bp.rootaddr + isect;
1474	}
1475	clust := fileclust(f, isect/bp.clustsize, cflag);
1476	if(clust < 0)
1477		return -1;
1478	return clust2sect(bp, clust) + isect%bp.clustsize;
1479}
1480
1481#
1482# look for a directory entry matching name
1483# always searches for long names which match a short name
1484#
1485# if creating (cflag is set), set address of available slot and allocate next cluster if necessary
1486#
1487searchdir(f: ref Xfile, name: string, cflag: int, lflag: int): (int, ref Dosptr)
1488{
1489	xf := f.xf;
1490	bp := xf.ptr;
1491	addr1 := -1;
1492	addr2 := -1;
1493	prevaddr1 := -1;
1494	o1 := 0;
1495	dp :=  ref Dosptr(0,0,0,0,0,0,-1,-1,nil,nil);	# prevaddr and naddr are -1
1496	dp.paddr = f.ptr.addr;
1497	dp.poffset = f.ptr.offset;
1498	islong :=0;
1499	buf := "";
1500
1501	need := 1;
1502	if(lflag && cflag)
1503		need += name2de(name);
1504	if(!lflag) {
1505		name = name[0:8]+"."+name[8:11];
1506		i := len name -1;
1507		while(i >= 0 && (name[i]==' ' || name[i] == '.'))
1508			i--;
1509		name = name[0:i+1];
1510	}
1511
1512	addr := -1;
1513	prevaddr: int;
1514	have := 0;
1515	for(isect:=0;; isect++) {
1516		prevaddr = addr;
1517		addr = fileaddr(f, isect, cflag);
1518		if(addr < 0)
1519			break;
1520		p := getsect(xf, addr);
1521		if(p == nil)
1522			break;
1523		for(o:=0; o<bp.sectsize; o+=DOSDIRSIZE) {
1524			dattr := int p.iobuf[o+11];
1525			dname0 := p.iobuf[o];
1526			if(dname0 == byte 16r00) {
1527				if(debug)
1528					chat("end dir(0)...");
1529				putsect(p);
1530				if(!cflag)
1531					return (-1, nil);
1532
1533				#
1534				# addr1 and o1 are the start of the dirs
1535				# addr2 is the optional second cluster used if the long name
1536				# entry does not fit within the addr1 cluster
1537				# have tells us the number of contiguous free dirs
1538				# starting at addr1.o1; need is the number needed to hold the long name
1539				#
1540				if(addr1 < 0){
1541					addr1 = addr;
1542					prevaddr1 = prevaddr;
1543					o1 = o;
1544				}
1545				nleft := (bp.sectsize-o)/DOSDIRSIZE;
1546				if(addr2 < 0 && nleft+have < need){
1547					addr2 = fileaddr(f, isect+1, cflag);
1548					if(addr2 < 0){
1549						if(debug)
1550							chat("end dir(2)...");
1551						return (-2, nil);
1552					}
1553				}else if(addr2 < 0)
1554					addr2 = addr;
1555				if(addr2 == addr1)
1556					addr2 = -1;
1557				if(debug)
1558					chat(sys->sprint("allocate addr1=%d,%d addr2=%d for %s nleft=%d have=%d need=%d", addr1, o1, addr2, name, nleft, have, need));
1559				dp.addr = addr1;
1560				dp.offset = o1;
1561				dp.prevaddr = prevaddr1;
1562				dp.naddr = addr2;
1563				return (0, dp);
1564			}
1565
1566			if(dname0 == byte DOSEMPTY) {
1567				if(debug)
1568					chat("empty...");
1569				have++;
1570				if(addr1 == -1){
1571					addr1 = addr;
1572					o1 = o;
1573					prevaddr1 = prevaddr;
1574				}
1575				if(addr2 == -1 && have >= need)
1576					addr2 = addr;
1577				continue;
1578			}
1579			have = 0;
1580			if(addr2 == -1)
1581				addr1 = -1;
1582
1583			if(0 && lflag && debug)
1584				dirdump(p.iobuf[o:o+DOSDIRSIZE],addr,o);
1585
1586			if((dattr & DMLONG) == DLONG) {
1587				if(!islong)
1588					buf = "";
1589				islong = 1;
1590				buf = getnamesect(p.iobuf[o:o+DOSDIRSIZE]) + buf;	# getnamesect should return sum
1591				continue;
1592			}
1593			if(dattr & DVLABEL) {
1594				islong = 0;
1595				continue;
1596			}
1597
1598			if(!islong || !lflag)
1599				buf = getname(p.iobuf[o:o+DOSDIRSIZE]);
1600			islong = 0;
1601
1602			if(debug)
1603				chat(sys->sprint("cmp: [%s] [%s]", buf, name));
1604			if(mystrcmp(buf, name) != 0) {
1605				buf="";
1606				continue;
1607			}
1608			if(debug)
1609				chat("found\n");
1610
1611			if(cflag) {
1612				putsect(p);
1613				return (-1,nil);
1614			}
1615
1616			dp.addr = addr;
1617			dp.prevaddr = prevaddr;
1618			dp.offset = o;
1619			dp.p = p;
1620			#dp.d = Dosdir.arr2Dd(p.iobuf[o:o+DOSDIRSIZE]);
1621			return (0, dp);
1622		}
1623		putsect(p);
1624	}
1625	if(debug)
1626		chat("end dir(1)...");
1627	if(!cflag)
1628		return (-1, nil);
1629	#
1630	# end of root directory or end of non-root directory on cluster boundary
1631	#
1632	if(addr1 < 0){
1633		addr1 = fileaddr(f, isect, 1);
1634		if(addr1 < 0)
1635			return (-2, nil);
1636		prevaddr1 = prevaddr;
1637		o1 = 0;
1638	}else{
1639		if(addr2 < 0 && have < need){
1640			addr2 = fileaddr(f, isect, 1);
1641			if(addr2 < 0)
1642				return (-2, nil);
1643		}
1644	}
1645	if(addr2 == addr1)
1646		addr2 = -1;
1647	dp.addr = addr1;
1648	dp.offset = o1;
1649	dp.prevaddr = prevaddr1;
1650	dp.naddr = addr2;
1651	return (0, dp);
1652}
1653
1654emptydir(f: ref Xfile): int
1655{
1656	for(isect:=0;; isect++) {
1657		addr := fileaddr(f, isect, 0);
1658		if(addr < 0)
1659			break;
1660
1661		p := getsect(f.xf, addr);
1662		if(p == nil)
1663			return -1;
1664
1665		for(o:=0; o<f.xf.ptr.sectsize; o+=DOSDIRSIZE) {
1666			dname0 := p.iobuf[o];
1667			dattr := int p.iobuf[o+11];
1668
1669			if(dname0 == byte 16r00) {
1670				putsect(p);
1671				return 0;
1672			}
1673
1674			if(dname0 == byte DOSEMPTY || dname0 == byte '.')
1675				continue;
1676
1677			if(dattr & DVLABEL)
1678				continue;		# ignore any long name entries: it's empty if there are no short ones
1679
1680			putsect(p);
1681			return -1;
1682		}
1683		putsect(p);
1684	}
1685	return 0;
1686}
1687
1688readdir(f:ref Xfile, offset: int, count: int): (int, array of byte)
1689{
1690	xf := f.xf;
1691	bp := xf.ptr;
1692	rcnt := 0;
1693	buf := array[Styx->MAXFDATA] of byte;
1694	islong :=0;
1695	longnamebuf:="";
1696
1697	if(count <= 0)
1698		return (0, nil);
1699
1700Read:
1701	for(isect:=0;; isect++) {
1702		addr := fileaddr(f, isect, 0);
1703		if(addr < 0)
1704			break;
1705		p := getsect(xf, addr);
1706		if(p == nil)
1707			return (-1,nil);
1708
1709		for(o:=0; o<bp.sectsize; o+=DOSDIRSIZE) {
1710			dname0 := int p.iobuf[o];
1711			dattr := int p.iobuf[o+11];
1712
1713			if(dname0 == 16r00) {
1714				putsect(p);
1715				break Read;
1716			}
1717
1718			if(dname0 == DOSEMPTY)
1719				continue;
1720
1721			if(dname0 == '.') {
1722				dname1 := int p.iobuf[o+1];
1723				if(dname1 == ' ' || dname1 == 0)
1724					continue;
1725				dname2 := int p.iobuf[o+2];
1726				if(dname1 == '.' &&
1727				  (dname2 == ' ' || dname2 == 0))
1728					continue;
1729			}
1730
1731			if((dattr & DMLONG) == DLONG) {
1732				if(!islong)
1733					longnamebuf = "";
1734				longnamebuf = getnamesect(p.iobuf[o:o+DOSDIRSIZE]) + longnamebuf;
1735				islong = 1;
1736				continue;
1737			}
1738			if(dattr & DVLABEL) {
1739				islong = 0;
1740				continue;
1741			}
1742
1743			dir := getdir(p.iobuf[o:o+DOSDIRSIZE], addr, o);
1744			if(islong) {
1745				dir.name = longnamebuf;
1746				longnamebuf = "";
1747				islong = 0;
1748			}
1749			d := styx->packdir(*dir);
1750			if(offset > 0) {
1751				offset -= len d;
1752				islong = 0;
1753				continue;
1754			}
1755			if(rcnt+len d > count){
1756				putsect(p);
1757				break Read;
1758			}
1759			buf[rcnt:] = d;
1760			rcnt += len d;
1761			if(rcnt >= count) {
1762				putsect(p);
1763				break Read;
1764			}
1765		}
1766		putsect(p);
1767	}
1768
1769	return (rcnt, buf[0:rcnt]);
1770}
1771
1772walkup(f: ref Xfile): (int, ref Dosptr)
1773{
1774	bp := f.xf.ptr;
1775	dp := f.ptr;
1776	o: int;
1777	ndp:= ref Dosptr(0,0,0,0,0,0,-1,-1,nil,nil);
1778	ndp.addr = dp.paddr;
1779	ndp.offset = dp.poffset;
1780
1781	if(debug)
1782		chat(sys->sprint("walkup: paddr=0x%x...", dp.paddr));
1783
1784	if(dp.paddr == 0)
1785		return (0,ndp);
1786
1787	p := getsect(f.xf, dp.paddr);
1788	if(p == nil)
1789		return (-1,nil);
1790
1791	if(debug)
1792		dirdump(p.iobuf[dp.poffset:dp.poffset+DOSDIRSIZE],dp.paddr,dp.poffset);
1793
1794	xd := Dosdir.arr2Dd(p.iobuf[dp.poffset:dp.poffset+DOSDIRSIZE]);
1795	start := getstart(f.xf, xd);
1796	if(debug & CLUSTER_INFO)
1797		if(debug)
1798			chat(sys->sprint("start=0x%x...", start));
1799	putsect(p);
1800	if(start == 0)
1801		return (-1,nil);
1802
1803	#
1804	# check that parent's . points to itself
1805	#
1806	p = getsect(f.xf, bp.dataaddr + (start-2)*bp.clustsize);
1807	if(p == nil)
1808		return (-1,nil);
1809
1810	if(debug)
1811		dirdump(p.iobuf,0,0);
1812
1813	xd = Dosdir.arr2Dd(p.iobuf);
1814	if(p.iobuf[0]!= byte '.' ||
1815	   p.iobuf[1]!= byte ' ' ||
1816	   start != getstart(f.xf, xd)) {
1817 		if(p!=nil)
1818			putsect(p);
1819		return (-1,nil);
1820	}
1821
1822	if(debug)
1823		dirdump(p.iobuf[DOSDIRSIZE:],0,0);
1824
1825	#
1826	# parent's .. is the next entry, and has start of parent's parent
1827	#
1828	xd = Dosdir.arr2Dd(p.iobuf[DOSDIRSIZE:]);
1829	if(p.iobuf[32] != byte '.' || p.iobuf[33] != byte '.') {
1830 		if(p != nil)
1831			putsect(p);
1832		return (-1,nil);
1833	}
1834
1835	#
1836	# we're done if parent is root
1837	#
1838	pstart := getstart(f.xf, xd);
1839	putsect(p);
1840	if(pstart == 0)
1841		return (0, ndp);
1842
1843	#
1844	# check that parent's . points to itself
1845	#
1846	p = getsect(f.xf, clust2sect(bp, pstart));
1847	if(p == nil) {
1848		if(debug)
1849			chat(sys->sprint("getsect %d failed\n", pstart));
1850		return (-1,nil);
1851	}
1852	if(debug)
1853		dirdump(p.iobuf,0,0);
1854	xd = Dosdir.arr2Dd(p.iobuf);
1855	if(p.iobuf[0]!= byte '.' ||
1856	   p.iobuf[1]!=byte ' ' ||
1857	   pstart!=getstart(f.xf, xd)) {
1858 		if(p != nil)
1859			putsect(p);
1860		return (-1,nil);
1861	}
1862
1863	#
1864	# parent's parent's .. is the next entry, and has start of parent's parent's parent
1865	#
1866	if(debug)
1867		dirdump(p.iobuf[DOSDIRSIZE:],0,0);
1868
1869	xd = Dosdir.arr2Dd(p.iobuf[DOSDIRSIZE:]);
1870	if(xd.name[0] != '.' || xd.name[1] !=  '.') {
1871 		if(p != nil)
1872			putsect(p);
1873		return (-1,nil);
1874	}
1875	ppstart :=getstart(f.xf, xd);
1876	putsect(p);
1877
1878	#
1879	# open parent's parent's parent, and walk through it until parent's paretn is found
1880	# need this to find parent's parent's addr and offset
1881	#
1882	ppclust := ppstart;
1883	# TO DO: FAT32
1884	if(ppclust != 0)
1885		k := clust2sect(bp, ppclust);
1886	else
1887		k = bp.rootaddr;
1888	p = getsect(f.xf, k);
1889	if(p == nil) {
1890		if(debug)
1891			chat(sys->sprint("getsect %d failed\n", k));
1892		return (-1,nil);
1893	}
1894
1895	if(debug)
1896		dirdump(p.iobuf,0,0);
1897
1898	if(ppstart) {
1899		xd = Dosdir.arr2Dd(p.iobuf);
1900		if(p.iobuf[0]!= byte '.' ||
1901		   p.iobuf[1]!= byte ' ' ||
1902		   ppstart!=getstart(f.xf, xd)) {
1903 			if(p!=nil)
1904				putsect(p);
1905			return (-1,nil);
1906		}
1907	}
1908
1909	for(so:=1; ;so++) {
1910		for(o=0; o<bp.sectsize; o+=DOSDIRSIZE) {
1911			xdname0 := p.iobuf[o];
1912			if(xdname0 == byte 16r00) {
1913				if(debug)
1914					chat("end dir\n");
1915 				if(p != nil)
1916					putsect(p);
1917				return (-1,nil);
1918			}
1919
1920			if(xdname0 == byte DOSEMPTY)
1921				continue;
1922
1923			#xd = Dosdir.arr2Dd(p.iobuf[o:o+DOSDIRSIZE]);
1924			xdstart:= p.iobuf[o+26:o+28];	# TO DO: getstart
1925			if(bytes2short(xdstart) == pstart) {
1926				putsect(p);
1927				ndp.paddr = k;
1928				ndp.poffset = o;
1929				return (0,ndp);
1930			}
1931		}
1932		if(ppclust) {
1933			if(so%bp.clustsize == 0) {
1934				ppstart = getfat(f.xf, ppstart);
1935				if(ppstart < 0){
1936					if(debug)
1937						chat(sys->sprint("getfat %d fail\n",
1938							ppstart));
1939 					if(p != nil)
1940						putsect(p);
1941					return (-1,nil);
1942				}
1943			}
1944			k = clust2sect(bp, ppclust) +
1945				so%bp.clustsize;
1946		}
1947		else {
1948			if(so*bp.sectsize >= bp.rootsize*DOSDIRSIZE) {
1949 				if(p != nil)
1950					putsect(p);
1951				return (-1,nil);
1952			}
1953			k = bp.rootaddr + so;
1954		}
1955		putsect(p);
1956		p = getsect(f.xf, k);
1957		if(p == nil) {
1958			if(debug)
1959				chat(sys->sprint("getsect %d failed\n", k));
1960			return (-1,nil);
1961		}
1962	}
1963	putsect(p);
1964	ndp.paddr = k;
1965	ndp.poffset = o;
1966	return (0,ndp);
1967}
1968
1969readfile(f: ref Xfile, offset: int, count: int): (int, array of byte)
1970{
1971	xf := f.xf;
1972	bp := xf.ptr;
1973	dp := f.ptr;
1974
1975	length := bytes2int(dp.p.iobuf[dp.offset+28:dp.offset+32]);
1976	rcnt := 0;
1977	if(offset >= length)
1978		return (0,nil);
1979 	buf := array[Styx->MAXFDATA] of byte;
1980	if(offset+count >= length)
1981		count = length - offset;
1982	isect := offset/bp.sectsize;
1983	o := offset%bp.sectsize;
1984	while(count > 0) {
1985		addr := fileaddr(f, isect++, 0);
1986		if(addr < 0)
1987			break;
1988		c := bp.sectsize - o;
1989		if(c > count)
1990			c = count;
1991		p := getsect(xf, addr);
1992		if(p == nil)
1993			return (-1, nil);
1994		buf[rcnt:] = p.iobuf[o:o+c];
1995		putsect(p);
1996		count -= c;
1997		rcnt += c;
1998		o = 0;
1999	}
2000	return (rcnt, buf[0:rcnt]);
2001}
2002
2003writefile(f: ref Xfile, buf: array of byte, offset,count: int): int
2004{
2005	xf := f.xf;
2006	bp := xf.ptr;
2007	dp := f.ptr;
2008	addr := 0;
2009	c: int;
2010	rcnt := 0;
2011	p: ref Iosect;
2012
2013	d := dp.d;
2014	if(d == nil)
2015		d = Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]);
2016	isect := offset/bp.sectsize;
2017
2018	o := offset%bp.sectsize;
2019	while(count > 0) {
2020		addr = fileaddr(f, isect++, 1);
2021		if(addr < 0)
2022			break;
2023		c = bp.sectsize - o;
2024		if(c > count)
2025			c = count;
2026		if(c == bp.sectsize){
2027			p = getosect(xf, addr);
2028			if(p == nil)
2029				return -1;
2030			p.flags = 0;
2031		}else{
2032			p = getsect(xf, addr);
2033			if(p == nil)
2034				return -1;
2035		}
2036		p.iobuf[o:] = buf[rcnt:rcnt+c];
2037		p.flags |= BMOD;
2038		putsect(p);
2039		count -= c;
2040		rcnt += c;
2041		o = 0;
2042	}
2043	if(rcnt <= 0 && addr < 0)
2044		return -2;
2045	length := 0;
2046	dlen := bytes2int(d.length);
2047	if(rcnt > 0)
2048		length = offset+rcnt;
2049	else if(dp.addr && dp.clust) {
2050		c = bp.clustsize*bp.sectsize;
2051		if(dp.iclust > (dlen+c-1)/c)
2052			length = c*dp.iclust;
2053	}
2054	if(length > dlen) {
2055		d.length[0] = byte length;
2056		d.length[1] = byte (length>>8);
2057		d.length[2] = byte (length>>16);
2058		d.length[3] = byte (length>>24);
2059	}
2060	puttime(d);
2061	dp.p.flags |= BMOD;
2062	dp.p.iobuf[dp.offset:] = Dosdir.Dd2arr(d);
2063	return rcnt;
2064}
2065
2066truncfile(f: ref Xfile): int
2067{
2068	xf := f.xf;
2069#	bp := xf.ptr;
2070	dp := f.ptr;
2071	d := Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]);
2072
2073	clust := getstart(f.xf, d);
2074	putstart(f.xf, d, 0);
2075	while(clust > 0) {
2076		next := getfat(xf, clust);
2077		putfat(xf, clust, 0);
2078		clust = next;
2079	}
2080
2081	d.length[0] = byte 0;
2082	d.length[1] = byte 0;
2083	d.length[2] = byte 0;
2084	d.length[3] = byte 0;
2085
2086	dp.p.iobuf[dp.offset:] = Dosdir.Dd2arr(d);
2087	dp.iclust = 0;
2088	dp.clust = 0;
2089	dp.p.flags |= BMOD;
2090
2091	return 0;
2092}
2093
2094getdir(arr: array of byte, addr,offset: int) :ref Sys->Dir
2095{
2096	dp := ref Sys->Dir;
2097
2098	if(arr == nil || addr == 0) {
2099		dp.name = "";
2100		dp.qid.path = big 0;
2101		dp.qid.qtype = Sys->QTDIR;
2102		dp.length = big 0;
2103		dp.mode = Sys->DMDIR|8r777;
2104	}
2105	else {
2106		dp.name = getname(arr);
2107		for(i:=0; i < len dp.name; i++)
2108			if(dp.name[i]>='A' && dp.name[i]<='Z')
2109				dp.name[i] = dp.name[i]-'A'+'a';
2110
2111		# dp.qid.path = bytes2short(d.start);
2112		dp.qid.path = big (addr*(Sectorsize/DOSDIRSIZE) + offset/DOSDIRSIZE);
2113		dattr := int arr[11];
2114
2115		if(dattr & DRONLY)
2116			dp.mode = 8r444;
2117		else
2118			dp.mode = 8r666;
2119
2120		dp.atime = gtime(arr);
2121		dp.mtime = dp.atime;
2122		if(dattr & DDIR) {
2123			dp.length = big 0;
2124			dp.qid.qtype |= Styx->QTDIR;
2125			dp.mode |= Sys->DMDIR|8r111;
2126		}
2127		else
2128			dp.length = big bytes2int(arr[28:32]);
2129
2130		if(dattr & DSYSTEM){
2131			dp.mode |= Styx->DMEXCL;
2132			dp.qid.qtype |= Styx->QTEXCL;
2133		}
2134	}
2135
2136	dp.qid.vers = 0;
2137	dp.dtype = 0;
2138	dp.dev = 0;
2139	dp.uid = "dos";
2140	dp.gid = "srv";
2141
2142	return dp;
2143}
2144
2145putdir(d: ref Dosdir, dp: ref Sys->Dir)
2146{
2147	if(dp.mode & 2)
2148		d.attr &= byte ~DRONLY;
2149	else
2150		d.attr |= byte DRONLY;
2151
2152	if(dp.mode & Styx->DMEXCL)
2153		d.attr |= byte DSYSTEM;
2154	else
2155		d.attr &= byte ~DSYSTEM;
2156	xputtime(d, dp.mtime);
2157}
2158
2159getname(arr: array of byte): string
2160{
2161	p: string;
2162	for(i:=0; i<8; i++) {
2163		c := int arr[i];
2164		if(c == 0 || c == ' ')
2165			break;
2166		if(i == 0 && c == 16r05)
2167			c = 16re5;
2168		p[len p] = c;
2169	}
2170	for(i=8; i<11; i++) {
2171		c := int arr[i];
2172		if(c == 0 || c == ' ')
2173			break;
2174		if(i == 8)
2175			p[len p] = '.';
2176		p[len p] = c;
2177	}
2178
2179	return p;
2180}
2181
2182dosname(p: string): (string, string)
2183{
2184	name := "        ";
2185	for(i := 0; i < len p && i < 8; i++) {
2186		c := p[i];
2187		if(c >= 'a' && c <= 'z')
2188			c += 'A'-'a';
2189		else if(c == '.')
2190			break;
2191		name[i] = c;
2192	}
2193	ext := "   ";
2194	for(j := len p - 1; j >= i; j--) {
2195		if(p[j] == '.') {
2196			q := 0;
2197			for(j++; j < len p && q < 3; j++) {
2198				c := p[j];
2199				if(c >= 'a' && c <= 'z')
2200					c += 'A'-'a';
2201				ext[q++] = c;
2202			}
2203			break;
2204		}
2205	}
2206	return (name, ext);
2207}
2208
2209putname(p: string, d: ref Dosdir)
2210{
2211	if ((int d.attr & DLONG) == DLONG)
2212		panic("putname of long name");
2213	(d.name, d.ext) = dosname(p);
2214}
2215
2216mystrcmp(s1, s2: string): int
2217{
2218	n := len s1;
2219	if(n != len s2)
2220		return 1;
2221
2222	for(i := 0; i < n; i++) {
2223		c := s1[i];
2224		if(c >= 'A' && c <= 'Z')
2225			c -= 'A'-'a';
2226		d := s2[i];
2227		if(d >= 'A' && d <= 'Z')
2228			d -= 'A'-'a';
2229		if(c != d)
2230			return 1;
2231	}
2232	return 0;
2233}
2234
2235#
2236# return the length of a long name in directory
2237# entries or zero if it's normal dos
2238#
2239name2de(p: string): int
2240{
2241	ext := 0;
2242	name := 0;
2243
2244	for(end := len p; --end >= 0 && p[end] != '.';)
2245		ext++;
2246
2247	if(end > 0) {
2248		name = end;
2249		for(i := 0; i < end; i++) {
2250			if(p[i] == '.')
2251				return (len p+DOSRUNES-1)/DOSRUNES;
2252		}
2253	}
2254	else {
2255		name = ext;
2256		ext = 0;
2257	}
2258
2259	if(name <= 8 && ext <= 3 && isvalidname(p))
2260		return 0;
2261
2262	return (len p+DOSRUNES-1)/DOSRUNES;
2263}
2264
2265isvalidname(s: string): int
2266{
2267	dot := 0;
2268	for(i := 0; i < len s; i++)
2269		if(s[i] == '.') {
2270			if(++dot > 1 || i == len s-1)
2271				return 0;
2272		} else if(s[i] > len isdos || isdos[s[i]] == 0)
2273			return 0;
2274	return 1;
2275}
2276
2277getnamesect(arr: array of byte): string
2278{
2279	s: string;
2280	c: int;
2281
2282	for(i := 1; i < 11; i += 2) {
2283		c = int arr[i] | (int arr[i+1] << 8);
2284		if(c == 0)
2285			return s;
2286		s[len s] = c;
2287	}
2288	for(i = 14; i < 26; i += 2) {
2289		c = int arr[i] | (int arr[i+1] << 8);
2290		if(c == 0)
2291			return s;
2292		s[len s] = c;
2293	}
2294	for(i = 28; i < 32; i += 2) {
2295		c = int arr[i] | (int arr[i+1] << 8);
2296		if(c == 0)
2297			return s;
2298		s[len s] = c;
2299	}
2300	return s;
2301}
2302
2303# takes a long filename and converts to a short dos name, with a tag number.
2304long2short(src: string,val: int): string
2305{
2306	dst :="           ";
2307	skip:=0;
2308	xskip:=0;
2309	ext:=len src-1;
2310	while(ext>=0 && src[ext]!='.')
2311		ext--;
2312
2313	if (ext < 0)
2314		ext=len src -1;
2315
2316	# convert name eliding periods
2317	j:=0;
2318	for(name := 0; name < ext && j<8; name++){
2319		c := src[name];
2320		if(c!='.' && c!=' ' && c!='\t') {
2321			if(c>='a' && c<='z')
2322				dst[j++] = c-'a'+'A';
2323			else
2324				dst[j++] = c;
2325		}
2326		else
2327			skip++;
2328	}
2329
2330	# convert extension
2331	j=8;
2332	for(xname := ext+1; xname < len src && j<11; xname++) {
2333		c := src[xname];
2334		if(c!=' ' && c!='\t'){
2335			if (c>='a' && c<='z')
2336				dst[j++] = c-'a'+'A';
2337			else
2338				dst[j++] = c;
2339		}else
2340			xskip++;
2341	}
2342
2343	# add tag number
2344	j =1;
2345	for(i:=val; i > 0; i/=10)
2346		j++;
2347
2348	if (8-j<name)
2349		name = 8-j;
2350	else
2351		name -= skip;
2352
2353	dst[name]='~';
2354	for(; val > 0; val /= 10)
2355		dst[name+ --j] = (val%10)+'0';
2356
2357	if(debug)
2358		chat(sys->sprint("returning dst [%s] src [%s]\n",dst,src));
2359
2360	return dst;
2361}
2362
2363getfat(xf: ref Xfs, n: int): int
2364{
2365	bp := xf.ptr;
2366	k := 0;
2367
2368	if(n < 2 || n >= bp.fatclusters)
2369		return -1;
2370	fb := bp.fatbits;
2371	k = (fb*n) >> 3;
2372	if(k < 0 || k >= bp.fatsize*bp.sectsize)
2373		panic("getfat");
2374
2375	sect := k/bp.sectsize + bp.fataddr;
2376	o := k%bp.sectsize;
2377	p := getsect(xf, sect);
2378	if(p == nil)
2379		return -1;
2380	k = int p.iobuf[o++];
2381	if(o >= bp.sectsize) {
2382		putsect(p);
2383		p = getsect(xf, sect+1);
2384		if(p == nil)
2385			return -1;
2386		o = 0;
2387	}
2388	k |= int p.iobuf[o++]<<8;
2389	if(fb == 32){
2390		# fat32 is really fat28
2391		k |= int p.iobuf[o++] << 16;
2392		k |= (int p.iobuf[o] & 16r0F) << 24;
2393		fb = 28;
2394	}
2395	putsect(p);
2396	if(fb == 12) {
2397		if(n&1)
2398			k >>= 4;
2399		else
2400			k &= 16rfff;
2401	}
2402
2403	if(debug & FAT_INFO)
2404		chat(sys->sprint("fat(0x%x)=0x%x...", n, k));
2405
2406	#
2407	# check for out of range
2408	#
2409	if(k >= (1<<fb) - 8)
2410		return -1;
2411	return k;
2412}
2413
2414putfat(xf: ref Xfs, n, val: int)
2415{
2416	bp := xf.ptr;
2417	if(n < 2 || n >= bp.fatclusters)
2418		panic(sys->sprint("putfat n=%d", n));
2419	k := (bp.fatbits*n) >> 3;
2420	if(k >= bp.fatsize*bp.sectsize)
2421		panic("putfat");
2422	sect := k/bp.sectsize + bp.fataddr;
2423	for(; sect<bp.rootaddr; sect+=bp.fatsize) {
2424		o := k%bp.sectsize;
2425		p := getsect(xf, sect);
2426		if(p == nil)
2427			continue;
2428		case bp.fatbits {
2429		12 =>
2430			if(n&1) {
2431				p.iobuf[o] &= byte 16r0f;
2432				p.iobuf[o++] |= byte (val<<4);
2433				if(o >= bp.sectsize) {
2434					p.flags |= BMOD;
2435					putsect(p);
2436					p = getsect(xf, sect+1);
2437					if(p == nil)
2438						continue;
2439					o = 0;
2440				}
2441				p.iobuf[o] = byte (val>>4);
2442			}
2443			else {
2444				p.iobuf[o++] = byte val;
2445				if(o >= bp.sectsize) {
2446					p.flags |= BMOD;
2447					putsect(p);
2448					p = getsect(xf, sect+1);
2449					if(p == nil)
2450						continue;
2451					o = 0;
2452				}
2453				p.iobuf[o] &= byte 16rf0;
2454				p.iobuf[o] |= byte ((val>>8)&16r0f);
2455			}
2456		16 =>
2457			p.iobuf[o++] = byte val;
2458			p.iobuf[o] = byte (val>>8);
2459		32 =>	# fat32 is really fat28
2460			p.iobuf[o++] = byte val;
2461			p.iobuf[o++] = byte (val>>8);
2462			p.iobuf[o++] = byte (val>>16);
2463			p.iobuf[o] = byte ((int p.iobuf[o] & 16rF0) | ((val>>24) & 16r0F));
2464		* =>
2465			panic("putfat fatbits");
2466		}
2467
2468		p.flags |= BMOD;
2469		putsect(p);
2470	}
2471}
2472
2473falloc(xf: ref Xfs): int
2474{
2475	bp := xf.ptr;
2476	n := bp.freeptr;
2477	for(;;) {
2478		if(getfat(xf, n) == 0)
2479			break;
2480		if(++n >= bp.fatclusters)
2481			n = FATRESRV;
2482		if(n == bp.freeptr)
2483			return 0;
2484	}
2485	bp.freeptr = n+1;
2486	if(bp.freeptr >= bp.fatclusters)
2487		bp.freeptr = FATRESRV;
2488	putfat(xf, n, int 16rffffffff);
2489	k := clust2sect(bp, n);
2490	for(i:=0; i<bp.clustsize; i++) {
2491		p := getosect(xf, k+i);
2492		if(p == nil)
2493			return -1;
2494		for(j:=0; j<len p.iobuf; j++)
2495			p.iobuf[j] = byte 0;
2496		p.flags = BMOD;
2497		putsect(p);
2498	}
2499	return n;
2500}
2501
2502clust2sect(bp: ref Dosbpb, clust: int): int
2503{
2504	return bp.dataaddr + (clust - FATRESRV)*bp.clustsize;
2505}
2506
2507sect2clust(bp: ref Dosbpb, sect: int): int
2508{
2509	c := (sect - bp.dataaddr) / bp.clustsize + FATRESRV;
2510	# assert(sect == clust2sect(bp, c));
2511	return c;
2512}
2513
2514bootdump(b: ref Dosboot)
2515{
2516	chat(sys->sprint("magic: 0x%2.2x 0x%2.2x 0x%2.2x\n",
2517		int b.magic[0], int b.magic[1], int b.magic[2]));
2518	chat(sys->sprint("version: \"%8.8s\"\n", string b.version));
2519	chat(sys->sprint("sectsize: %d\n", bytes2short(b.sectsize)));
2520	chat(sys->sprint("allocsize: %d\n", int b.clustsize));
2521	chat(sys->sprint("nresrv: %d\n", bytes2short(b.nresrv)));
2522	chat(sys->sprint("nfats: %d\n", int b.nfats));
2523	chat(sys->sprint("rootsize: %d\n", bytes2short(b.rootsize)));
2524	chat(sys->sprint("volsize: %d\n", bytes2short(b.volsize)));
2525	chat(sys->sprint("mediadesc: 0x%2.2x\n", int b.mediadesc));
2526	chat(sys->sprint("fatsize: %d\n", bytes2short(b.fatsize)));
2527	chat(sys->sprint("trksize: %d\n", bytes2short(b.trksize)));
2528	chat(sys->sprint("nheads: %d\n", bytes2short(b.nheads)));
2529	chat(sys->sprint("nhidden: %d\n", bytes2int(b.nhidden)));
2530	chat(sys->sprint("bigvolsize: %d\n", bytes2int(b.bigvolsize)));
2531	chat(sys->sprint("driveno: %d\n", int b.driveno));
2532	chat(sys->sprint("bootsig: 0x%2.2x\n", int b.bootsig));
2533	chat(sys->sprint("volid: 0x%8.8x\n", bytes2int(b.volid)));
2534	chat(sys->sprint("label: \"%11.11s\"\n", string b.label));
2535}
2536
2537xputtime(d: ref Dosdir, s: int)
2538{
2539	if(s == 0)
2540		t := daytime->local((sys->millisec() - nowt1)/1000 + nowt);
2541	else
2542		t = daytime->local(s);
2543	x := (t.hour<<11) | (t.min<<5) | (t.sec>>1);
2544	d.time[0] = byte x;
2545	d.time[1] = byte (x>>8);
2546	x = ((t.year-80)<<9) | ((t.mon+1)<<5) | t.mday;
2547	d.date[0] = byte x;
2548	d.date[1] = byte (x>>8);
2549}
2550
2551puttime(d: ref Dosdir)
2552{
2553	xputtime(d, 0);
2554}
2555
2556gtime(a: array of byte): int
2557{
2558	tm := ref Daytime->Tm;
2559	i := bytes2short(a[22:24]);	# dos time
2560	tm.hour = i >> 11;
2561	tm.min = (i>>5) & 63;
2562	tm.sec = (i & 31) << 1;
2563	i = bytes2short(a[24:26]);	# dos date
2564	tm.year = 80 + (i>>9);
2565	tm.mon = ((i>>5) & 15) - 1;
2566	tm.mday = i & 31;
2567	tm.tzoff = tzoff;	# DOS time is local time
2568	return daytime->tm2epoch(tm);
2569}
2570
2571dirdump(arr: array of byte, addr, offset: int)
2572{
2573	if(!debug)
2574		return;
2575	attrchar:= "rhsvda67";
2576	d := Dosdir.arr2Dd(arr);
2577	buf := sys->sprint("\"%.8s.%.3s\" ", d.name, d.ext);
2578	p_i:=7;
2579
2580	for(i := 16r80; i != 0; i >>= 1) {
2581		if((d.attr & byte i) ==  byte i)
2582			ch := attrchar[p_i];
2583		else
2584			ch = '-';
2585		buf += sys->sprint("%c", ch);
2586		p_i--;
2587	}
2588
2589	i = bytes2short(d.time);
2590	buf += sys->sprint(" %2.2d:%2.2d:%2.2d", i>>11, (i>>5)&63, (i&31)<<1);
2591	i = bytes2short(d.date);
2592	buf += sys->sprint(" %2.2d.%2.2d.%2.2d", 80+(i>>9), (i>>5)&15, i&31);
2593	buf += sys->sprint(" %d %d", bytes2short(d.start), bytes2short(d.length));
2594	buf += sys->sprint(" %d %d\n",addr,offset);
2595	chat(buf);
2596}
2597
2598putnamesect(longname: string, curslot: int, first: int, sum: int, a: array of byte)
2599{
2600	for(i := 0; i < DOSDIRSIZE; i++)
2601		a[i] = byte 16rFF;
2602	if(first)
2603		a[0] = byte (16r40 | curslot);
2604	else
2605		a[0] = byte curslot;
2606	a[11] = byte DLONG;
2607	a[12] = byte 0;
2608	a[13] = byte sum;
2609	a[26] = byte 0;
2610	a[27] = byte 0;
2611	# a[1:1+10] = characters 1 to 5
2612	n := len longname;
2613	j := (curslot-1)*DOSRUNES;
2614	for(i = 1; i < 1+10; i += 2){
2615		c := 0;
2616		if(j < n)
2617			c = longname[j++];
2618		a[i] = byte c;
2619		a[i+1] = byte (c >> 8);
2620		if(c == 0)
2621			return;
2622	}
2623	# a[14:14+12] = characters 6 to 11
2624	for(i = 14; i < 14+12; i += 2){
2625		c := 0;
2626		if(j < n)
2627			c = longname[j++];
2628		a[i] = byte c;
2629		a[i+1] = byte (c >> 8);
2630		if(c == 0)
2631			return;
2632	}
2633	# a[28:28+4] characters 12 to 13
2634	for(i = 28; i < 28+4; i += 2){
2635		c := 0;
2636		if(j < n)
2637			c = longname[j++];
2638		a[i] = byte c;
2639		a[i+1] = byte (c>>8);
2640		if(c == 0)
2641			return;
2642	}
2643}
2644
2645putlongname(xf: ref Xfs, ndp: ref Dosptr, name: string, sname: string): int
2646{
2647	bp := xf.ptr;
2648	first := 1;
2649	sum := aliassum(sname);
2650	for(nds := (len name+DOSRUNES-1)/DOSRUNES; nds > 0; nds--) {
2651		putnamesect(name, nds, first, sum, ndp.p.iobuf[ndp.offset:]);
2652		first = 0;
2653		ndp.offset += DOSDIRSIZE;
2654		if(ndp.offset == bp.sectsize) {
2655			if(debug)
2656				chat(sys->sprint("long name %s entry %d/%d crossing sector, addr=%d, naddr=%d", name, nds, (len name+DOSRUNES-1)/DOSRUNES, ndp.addr, ndp.naddr));
2657			ndp.p.flags |= BMOD;
2658			putsect(ndp.p);
2659			ndp.p = nil;
2660			ndp.d = nil;
2661
2662			# switch to the next cluster for the next long entry or the subsequent normal dir. entry
2663			# naddr must be set up correctly by searchdir because we'll need one or the other
2664
2665			ndp.prevaddr = ndp.addr;
2666			ndp.addr = ndp.naddr;
2667			ndp.naddr = -1;
2668			if(ndp.addr < 0)
2669				return -1;
2670			ndp.p = getsect(xf, ndp.addr);
2671			if(ndp.p == nil)
2672				return -1;
2673			ndp.offset = 0;
2674		}
2675	}
2676	return 0;
2677}
2678
2679bytes2int(a: array of byte): int
2680{
2681	return (((((int a[3] << 8) | int a[2]) << 8) | int a[1]) << 8) | int a[0];
2682}
2683
2684bytes2short(a: array of byte): int
2685{
2686	return (int a[1] << 8) | int a[0];
2687}
2688
2689chat(s: string)
2690{
2691	if(debug)
2692		sys->fprint(sys->fildes(2), "%s", s);
2693}
2694
2695panic(s: string)
2696{
2697	sys->fprint(sys->fildes(2), "dosfs: panic: %s\n", s);
2698	if(pflag)
2699		<-chan of int;	# hang here
2700	raise "fail:panic";
2701}
2702
2703Dosboot.arr2Db(arr: array of byte): ref Dosboot
2704{
2705	db := ref Dosboot;
2706	db.magic = arr[0:3];
2707	db.version = arr[3:11];
2708	db.sectsize = arr[11:13];
2709	db.clustsize = arr[13];
2710	db.nresrv = arr[14:16];
2711	db.nfats = arr[16];
2712	db.rootsize = arr[17:19];
2713	db.volsize = arr[19:21];
2714	db.mediadesc = arr[21];
2715	db.fatsize = arr[22:24];
2716	db.trksize = arr[24:26];
2717	db.nheads = arr[26:28];
2718	db.nhidden = arr[28:32];
2719	db.bigvolsize = arr[32:36];
2720	db.driveno = arr[36];
2721	db.bootsig = arr[38];
2722	db.volid = arr[39:43];
2723	db.label = arr[43:54];
2724	return db;
2725}
2726
2727Dosdir.arr2Dd(arr: array of byte): ref Dosdir
2728{
2729	dir := ref Dosdir;
2730	for(i := 0; i < 8; i++)
2731		dir.name[len dir.name] = int arr[i];
2732	for(; i < 11; i++)
2733		dir.ext[len dir.ext] = int arr[i];
2734	dir.attr = arr[11];
2735	dir.reserved = arr[12:22];
2736	dir.time = arr[22:24];
2737	dir.date = arr[24:26];
2738	dir.start = arr[26:28];
2739	dir.length = arr[28:32];
2740	return dir;
2741}
2742
2743Dosdir.Dd2arr(d: ref Dosdir): array of byte
2744{
2745	a := array[32] of byte;
2746	i:=0;
2747	for(j := 0; j < len d.name; j++)
2748		a[i++] = byte d.name[j];
2749	for(; j<8; j++)
2750		a[i++]= byte 0;
2751	for(j=0; j<len d.ext; j++)
2752		a[i++] = byte d.ext[j];
2753	for(; j<3; j++)
2754		a[i++]= byte 0;
2755	a[i++] = d.attr;
2756	for(j=0; j<10; j++)
2757		a[i++] = d.reserved[j];
2758	for(j=0; j<2; j++)
2759		a[i++] = d.time[j];
2760	for(j=0; j<2; j++)
2761		a[i++] = d.date[j];
2762	for(j=0; j<2; j++)
2763		a[i++] = d.start[j];
2764	for(j=0; j<4; j++)
2765		a[i++] = d.length[j];
2766	return a;
2767}
2768
2769#
2770# checksum of short name for use in long name directory entries
2771# assumes sname is already padded correctly to 8+3
2772#
2773aliassum(sname: string): int
2774{
2775	i := 0;
2776	for(sum:=0; i<11; i++)
2777		sum = (((sum&1)<<7)|((sum&16rfe)>>1))+sname[i];
2778	return sum;
2779}
2780
2781#
2782# track i/o
2783#
2784
2785# An Xfs represents the root of an external file system, anchored
2786# to the server and the client
2787Xfs: adt {
2788	next:cyclic ref Xfs;
2789	name: string;	# of file containing external f.s.
2790	qid: Sys->Qid;	# of file containing external f.s.
2791	refn: int;		# attach count
2792	rootqid: Sys->Qid;	# of inferno constructed root directory
2793	dev: ref Sys->FD;  # FD of the file containing external f.s.
2794	fmt: int;		# successfully read format
2795	offset: int;		# offset in sectors to file system
2796	ptr: ref Dosbpb;
2797};
2798
2799# An Xfile represents the mapping of fid's & qid's to the server.
2800Xfile: adt {
2801	next: cyclic ref Xfile;		# in hash bucket
2802	client: int;
2803	fid: int;
2804	flags: int;
2805	qid: Sys->Qid;
2806	xf: ref Xfs;
2807	ptr: ref Dosptr;
2808};
2809
2810Iosect: adt
2811{
2812	next: cyclic ref Iosect;
2813	flags: int;
2814	t: cyclic ref Iotrack;
2815	iobuf: array of byte;
2816};
2817
2818Iotrack: adt
2819{
2820	flags: int;
2821	xf: ref Xfs;
2822	addr: int;
2823	next: cyclic ref Iotrack;		# in lru list
2824	prev: cyclic ref Iotrack;
2825	hnext: cyclic ref Iotrack;		# in hash list
2826	hprev: cyclic ref Iotrack;
2827	refn: int;
2828	tp: cyclic ref Track;
2829};
2830
2831Track: adt
2832{
2833	create: fn(): ref Track;
2834	p: cyclic array of ref Iosect;
2835	buf: array of byte;
2836};
2837
2838BMOD: con	1<<0;
2839BIMM: con	1<<1;
2840BSTALE: con	1<<2;
2841
2842HIOB: con 31;	# a prime
2843NIOBUF: con 20;
2844
2845Sectorsize: con 512;
2846Sect2trk: con 9;	# default
2847
2848hiob := array[HIOB+1] of ref Iotrack;		# hash buckets + lru list
2849iobuf := array[NIOBUF] of ref Iotrack;		# the real ones
2850freelist: ref Iosect;
2851sect2trk := Sect2trk;
2852trksize := Sect2trk*Sectorsize;
2853
2854FIDMOD: con 127;	# prime
2855xhead:		ref Xfs;
2856client:		int;
2857
2858xfiles := array[FIDMOD] of ref Xfile;
2859iodebug := 0;
2860
2861iotrackinit(sectors: int)
2862{
2863	if(sectors <= 0)
2864		sectors = 9;
2865	sect2trk = sectors;
2866	trksize = sect2trk*Sectorsize;
2867
2868	freelist = nil;
2869
2870	for(i := 0;i < FIDMOD; i++)
2871		xfiles[i] = ref Xfile(nil,0,0,0,Sys->Qid(big 0,0,0),nil,nil);
2872
2873	for(i = 0; i <= HIOB; i++)
2874		hiob[i] = ref Iotrack;
2875
2876	for(i = 0; i < HIOB; i++) {
2877		hiob[i].hprev = hiob[i];
2878		hiob[i].hnext = hiob[i];
2879		hiob[i].refn = 0;
2880		hiob[i].addr = 0;
2881	}
2882	hiob[i].prev = hiob[i];
2883	hiob[i].next = hiob[i];
2884	hiob[i].refn = 0;
2885	hiob[i].addr = 0;
2886
2887	for(i=0;i<NIOBUF;i++)
2888		iobuf[i] = ref Iotrack;
2889
2890	for(i=0; i<NIOBUF; i++) {
2891		iobuf[i].hprev = iobuf[i].hnext = iobuf[i];
2892		iobuf[i].prev = iobuf[i].next = iobuf[i];
2893		iobuf[i].refn=iobuf[i].addr=0;
2894		iobuf[i].flags = 0;
2895		if(hiob[HIOB].next != iobuf[i]) {
2896			iobuf[i].prev.next = iobuf[i].next;
2897			iobuf[i].next.prev = iobuf[i].prev;
2898			iobuf[i].next = hiob[HIOB].next;
2899			iobuf[i].prev = hiob[HIOB];
2900			hiob[HIOB].next.prev = iobuf[i];
2901			hiob[HIOB].next = iobuf[i];
2902		}
2903		iobuf[i].tp =  Track.create();
2904	}
2905}
2906
2907Track.create(): ref Track
2908{
2909	t := ref Track;
2910	t.p = array[sect2trk] of ref Iosect;
2911	t.buf = array[trksize] of byte;
2912	return t;
2913}
2914
2915getsect(xf: ref Xfs, addr: int): ref Iosect
2916{
2917	return getiosect(xf, addr, 1);
2918}
2919
2920getosect(xf: ref Xfs, addr: int): ref Iosect
2921{
2922	return getiosect(xf, addr, 0);
2923}
2924
2925# get the sector corresponding to the address addr.
2926getiosect(xf: ref Xfs, addr , rflag: int): ref Iosect
2927{
2928	# offset from beginning of track.
2929	toff := addr %  sect2trk;
2930
2931	# address of beginning of track.
2932	taddr := addr -  toff;
2933	t := getiotrack(xf, taddr);
2934
2935	if(rflag && t.flags&BSTALE) {
2936		if(tread(t) < 0)
2937			return nil;
2938
2939		t.flags &= ~BSTALE;
2940	}
2941
2942	t.refn++;
2943	if(t.tp.p[toff] == nil) {
2944		p := newsect();
2945		t.tp.p[toff] = p;
2946		p.flags = t.flags&BSTALE;
2947		p.t = t;
2948		p.iobuf = t.tp.buf[toff*Sectorsize:(toff+1)*Sectorsize];
2949	}
2950	return t.tp.p[toff];
2951}
2952
2953putsect(p: ref Iosect)
2954{
2955	t: ref Iotrack;
2956
2957	t = p.t;
2958	t.flags |= p.flags;
2959	p.flags = 0;
2960	t.refn--;
2961	if(t.refn < 0)
2962		panic("putsect: refcount");
2963
2964	if(t.flags & BIMM) {
2965		if(t.flags & BMOD)
2966			twrite(t);
2967		t.flags &= ~(BMOD|BIMM);
2968	}
2969}
2970
2971# get the track corresponding to addr
2972# (which is the address of the beginning of a track
2973getiotrack(xf: ref Xfs, addr: int): ref Iotrack
2974{
2975	p: ref Iotrack;
2976	mp := hiob[HIOB];
2977
2978	if(iodebug)
2979		chat(sys->sprint("iotrack %d,%d...", xf.dev.fd, addr));
2980
2981	# find bucket in hash table.
2982	h := (xf.dev.fd<<24) ^ addr;
2983	if(h < 0)
2984		h = ~h;
2985	h %= HIOB;
2986	hp := hiob[h];
2987
2988	out: for(;;){
2989		loop: for(;;) {
2990		 	# look for it in the active list
2991			for(p = hp.hnext; p != hp; p=p.hnext) {
2992				if(p.addr != addr || p.xf != xf)
2993					continue;
2994				if(p.addr == addr && p.xf == xf) {
2995					break out;
2996				}
2997				continue loop;
2998			}
2999
3000		 	# not found
3001		 	# take oldest unref'd entry
3002			for(p = mp.prev; p != mp; p=p.prev)
3003				if(p.refn == 0 )
3004					break;
3005			if(p == mp) {
3006				if(iodebug)
3007					chat("iotrack all ref'd\n");
3008				continue loop;
3009			}
3010
3011			if((p.flags & BMOD)!= 0) {
3012				twrite(p);
3013				p.flags &= ~(BMOD|BIMM);
3014				continue loop;
3015			}
3016			purgetrack(p);
3017			p.addr = addr;
3018			p.xf = xf;
3019			p.flags = BSTALE;
3020			break out;
3021		}
3022	}
3023
3024	if(hp.hnext != p) {
3025		p.hprev.hnext = p.hnext;
3026		p.hnext.hprev = p.hprev;
3027		p.hnext = hp.hnext;
3028		p.hprev = hp;
3029		hp.hnext.hprev = p;
3030		hp.hnext = p;
3031	}
3032	if(mp.next != p) {
3033		p.prev.next = p.next;
3034		p.next.prev = p.prev;
3035		p.next = mp.next;
3036		p.prev = mp;
3037		mp.next.prev = p;
3038		mp.next = p;
3039	}
3040	return p;
3041}
3042
3043purgetrack(t: ref Iotrack)
3044{
3045	refn := sect2trk;
3046	for(i := 0; i < sect2trk; i++) {
3047		if(t.tp.p[i] == nil) {
3048			--refn;
3049			continue;
3050		}
3051		freesect(t.tp.p[i]);
3052		--refn;
3053		t.tp.p[i]=nil;
3054	}
3055	if(t.refn != refn)
3056		panic("purgetrack");
3057	if(refn!=0)
3058		panic("refn not 0");
3059}
3060
3061twrite(t: ref Iotrack): int
3062{
3063	if(iodebug)
3064		chat(sys->sprint("[twrite %d...", t.addr));
3065
3066	if((t.flags & BSTALE)!= 0) {
3067		refn:=0;
3068		for(i:=0; i<sect2trk; i++)
3069			if(t.tp.p[i]!=nil)
3070				++refn;
3071
3072		if(refn < sect2trk) {
3073			if(tread(t) < 0) {
3074				if (iodebug)
3075					chat("error]");
3076				return -1;
3077			}
3078		}
3079		else
3080			t.flags &= ~BSTALE;
3081	}
3082
3083	if(devwrite(t.xf, t.addr, t.tp.buf) < 0) {
3084		if(iodebug)
3085			chat("error]");
3086		return -1;
3087	}
3088
3089	if(iodebug)
3090		chat(" done]");
3091
3092	return 0;
3093}
3094
3095tread(t: ref Iotrack): int
3096{
3097	refn := 0;
3098	rval: int;
3099
3100	for(i := 0; i < sect2trk; i++)
3101		if(t.tp.p[i] != nil)
3102			++refn;
3103
3104	if(iodebug)
3105		chat(sys->sprint("[tread %d...", t.addr));
3106
3107	tbuf := t.tp.buf;
3108	if(refn != 0)
3109		tbuf = array[trksize] of byte;
3110
3111	rval = devread(t.xf, t.addr, tbuf);
3112	if(rval < 0) {
3113		if(iodebug)
3114			chat("error]");
3115		return -1;
3116	}
3117
3118	if(refn != 0) {
3119		for(i=0; i < sect2trk; i++) {
3120			if(t.tp.p[i] == nil) {
3121				t.tp.buf[i*Sectorsize:]=tbuf[i*Sectorsize:(i+1)*Sectorsize];
3122				if(iodebug)
3123					chat(sys->sprint("%d ", i));
3124			}
3125		}
3126	}
3127
3128	if(iodebug)
3129		chat("done]");
3130
3131	t.flags &= ~BSTALE;
3132	return 0;
3133}
3134
3135purgebuf(xf: ref Xfs)
3136{
3137	for(p := 0; p < NIOBUF; p++) {
3138		if(iobuf[p].xf != xf)
3139			continue;
3140		if(iobuf[p].xf == xf) {
3141			if((iobuf[p].flags & BMOD) != 0)
3142				twrite(iobuf[p]);
3143
3144			iobuf[p].flags = BSTALE;
3145			purgetrack(iobuf[p]);
3146		}
3147	}
3148}
3149
3150sync()
3151{
3152	for(p := 0; p < NIOBUF; p++) {
3153		if(!(iobuf[p].flags & BMOD))
3154			continue;
3155
3156		if(iobuf[p].flags & BMOD){
3157			twrite(iobuf[p]);
3158			iobuf[p].flags &= ~(BMOD|BIMM);
3159		}
3160	}
3161}
3162
3163
3164newsect(): ref Iosect
3165{
3166	if((p := freelist)!=nil)	{
3167		freelist = p.next;
3168		p.next = nil;
3169	} else
3170		p = ref Iosect(nil, 0, nil,nil);
3171
3172	return p;
3173}
3174
3175freesect(p: ref Iosect)
3176{
3177	p.next = freelist;
3178	freelist = p;
3179}
3180
3181
3182# devio from here
3183deverror(name: string, xf: ref Xfs, addr,n,nret: int): int
3184{
3185	if(nret < 0) {
3186		if(iodebug)
3187			chat(sys->sprint("%s errstr=\"%r\"...", name));
3188		xf.dev = nil;
3189		return -1;
3190	}
3191	if(iodebug)
3192		chat(sys->sprint("dev %d sector %d, %s: %d, should be %d\n",
3193			xf.dev.fd, addr, name, nret, n));
3194
3195	panic(name);
3196	return -1;
3197}
3198
3199devread(xf: ref Xfs, addr: int, buf: array of byte): int
3200{
3201	if(xf.dev==nil)
3202		return -1;
3203
3204	sys->seek(xf.dev, big (xf.offset+addr*Sectorsize), sys->SEEKSTART);
3205	nread := sys->read(xf.dev, buf, trksize);
3206	if(nread != trksize)
3207		return deverror("read", xf, addr, trksize, nread);
3208
3209	return 0;
3210}
3211
3212devwrite(xf: ref Xfs, addr: int, buf: array of byte): int
3213{
3214	if(xf.dev == nil)
3215		return -1;
3216
3217	sys->seek(xf.dev, big (xf.offset+addr*Sectorsize), 0);
3218	nwrite := sys->write(xf.dev, buf, trksize);
3219	if(nwrite != trksize)
3220		return deverror("write", xf, addr, trksize , nwrite);
3221
3222	return 0;
3223}
3224
3225devcheck(xf: ref Xfs): int
3226{
3227	buf := array[Sectorsize] of byte;
3228
3229	if(xf.dev == nil)
3230		return -1;
3231
3232	sys->seek(xf.dev, big 0, sys->SEEKSTART);
3233	if(sys->read(xf.dev, buf, Sectorsize) != Sectorsize){
3234		xf.dev = nil;
3235		return -1;
3236	}
3237
3238	return 0;
3239}
3240
3241# setup and return the Xfs associated with "name"
3242
3243getxfs(name: string): (ref Xfs, string)
3244{
3245	if(name == nil)
3246		return (nil, "no file system device specified");
3247
3248
3249	 # If the name passed is of the form 'name:offset' then
3250	 # offset is used to prime xf->offset. This allows accessing
3251	 # a FAT-based filesystem anywhere within a partition.
3252	 # Typical use would be to mount a filesystem in the presence
3253	 # of a boot manager programm at the beginning of the disc.
3254
3255	offset := 0;
3256	for(i := 0;i < len name; i++)
3257		if(name[i]==':')
3258			break;
3259
3260	if(i < len name) {
3261		offset = int name[i+1:];
3262		if(offset < 0)
3263			return (nil, "invalid device offset to file system");
3264		offset *= Sectorsize;
3265		name = name[0:i];
3266	}
3267
3268	fd := sys->open(name, Sys->ORDWR);
3269	if(fd == nil) {
3270		if(iodebug)
3271			chat(sys->sprint("getxfs: open(%s) failed: %r\n", name));
3272		return (nil, sys->sprint("can't open %s: %r", name));
3273	}
3274
3275	(rval,dir) := sys->fstat(fd);
3276	if(rval < 0)
3277		return (nil, sys->sprint("can't stat %s: %r", name));
3278
3279	# lock down the list of xf's.
3280	fxf: ref Xfs;
3281	for(xf := xhead; xf != nil; xf = xf.next) {
3282		if(xf.refn == 0) {
3283			if(fxf == nil)
3284				fxf = xf;
3285			continue;
3286		}
3287		if(xf.qid.path != dir.qid.path || xf.qid.vers != dir.qid.vers)
3288			continue;
3289
3290		if(xf.name!= name || xf.dev == nil)
3291			continue;
3292
3293		if(devcheck(xf) < 0) # look for media change
3294			continue;
3295
3296		if(offset && xf.offset != offset)
3297			continue;
3298
3299		if(iodebug)
3300			chat(sys->sprint("incref \"%s\", dev=%d...",
3301				xf.name, xf.dev.fd));
3302
3303		++xf.refn;
3304		return (xf, nil);
3305	}
3306
3307	# this xf doesn't exist, make a new one and stick it on the list.
3308	if(fxf == nil){
3309		fxf = ref Xfs;
3310		fxf.next = xhead;
3311		xhead = fxf;
3312	}
3313
3314	if(iodebug)
3315		chat(sys->sprint("alloc \"%s\", dev=%d...", name, fd.fd));
3316
3317	fxf.name = name;
3318	fxf.refn = 1;
3319	fxf.qid = dir.qid;
3320	fxf.dev = fd;
3321	fxf.fmt = 0;
3322	fxf.offset = offset;
3323	return (fxf, nil);
3324}
3325
3326refxfs(xf: ref Xfs, delta: int)
3327{
3328	xf.refn += delta;
3329	if(xf.refn == 0) {
3330		if (iodebug)
3331			chat(sys->sprint("free \"%s\", dev=%d...",
3332				xf.name, xf.dev.fd));
3333
3334		purgebuf(xf);
3335		if(xf.dev !=nil)
3336			xf.dev = nil;
3337	}
3338}
3339
3340xfile(fid, flag: int): ref Xfile
3341{
3342	pf: ref Xfile;
3343
3344	# find hashed file list in LRU? table.
3345	k := (fid^client)%FIDMOD;
3346
3347	# find if this fid is in the hashed file list.
3348	f:=xfiles[k];
3349	for(pf = nil; f != nil; f = f.next) {
3350		if(f.fid == fid && f.client == client)
3351			break;
3352		pf=f;
3353	}
3354
3355	# move this fid to the front of the list if it was further down.
3356	if(f != nil && pf != nil){
3357		pf.next = f.next;
3358		f.next = xfiles[k];
3359		xfiles[k] = f;
3360	}
3361
3362	case flag {
3363	* =>
3364		panic("xfile");
3365	Asis =>
3366		if(f != nil && f.xf != nil && f.xf.dev == nil)
3367			return nil;
3368		return f;
3369	Clean =>
3370		break;
3371	Clunk =>
3372		if(f != nil) {
3373			xfiles[k] = f.next;
3374			clean(f);
3375		}
3376		return nil;
3377	}
3378
3379	# clean it up ..
3380	if(f != nil)
3381		return clean(f);
3382
3383	# f wasn't found in the hashtable, make a new one and add it
3384	f = ref Xfile;
3385	f.next = xfiles[k];
3386	xfiles[k] = f;
3387	# sort out the fid, etc.
3388	f.fid = fid;
3389	f.client = client;
3390	f.flags = 0;
3391	f.qid = Sys->Qid(big 0, 0, Styx->QTFILE);
3392	f.xf = nil;
3393	f.ptr = ref Dosptr(0,0,0,0,0,0,-1,-1,nil,nil);
3394	return f;
3395}
3396
3397clean(f: ref Xfile): ref Xfile
3398{
3399	f.ptr = nil;
3400	if(f.xf != nil) {
3401		refxfs(f.xf, -1);
3402		f.xf = nil;
3403	}
3404	f.flags = 0;
3405	f.qid = Sys->Qid(big 0, 0, 0);
3406	return f;
3407}
3408
3409#
3410# the file at <addr, offset> has moved
3411# relocate the dos entries of all fids in the same file
3412#
3413dosptrreloc(f: ref Xfile, dp: ref Dosptr, addr: int, offset: int)
3414{
3415	i: int;
3416	p: ref Xfile;
3417	xdp: ref Dosptr;
3418
3419	for(i=0; i < FIDMOD; i++){
3420		for(p = xfiles[i]; p != nil; p = p.next){
3421			xdp = p.ptr;
3422			if(p != f && p.xf == f.xf
3423			&& xdp != nil && xdp.addr == addr && xdp.offset == offset){
3424				*xdp = *dp;
3425				xdp.p = nil;
3426				# xdp.d = nil;
3427				p.qid.path = big QIDPATH(xdp);
3428			}
3429		}
3430	}
3431}
3432