xref: /inferno-os/appl/cmd/ftpfs.b (revision c93eaa9a36d5071c19f42debcef978afd89ec994)
1implement Ftpfs;
2
3include "sys.m";
4	sys: Sys;
5	FD, Dir: import Sys;
6
7include "draw.m";
8
9include "arg.m";
10
11include "bufio.m";
12	bufio: Bufio;
13	Iobuf: import bufio;
14
15include "daytime.m";
16	time: Daytime;
17	Tm: import time;
18
19include "string.m";
20	str: String;
21
22include "styx.m";
23	styx: Styx;
24	Tmsg, Rmsg: import styx;
25
26include "dial.m";
27	dial: Dial;
28	Connection: import dial;
29
30include "factotum.m";
31
32Ftpfs: module
33{
34	init: fn(nil: ref Draw->Context, argv: list of string);
35};
36
37#
38#	File system node.  Refers to parent and file structure.
39#	Siblings are linked.  The head is parent.children.
40#
41
42Node: adt
43{
44	dir:		Dir;
45	uniq:		int;
46	parent:		cyclic ref Node;
47	sibs:		cyclic ref Node;
48	children:	cyclic ref Node;
49	file:		cyclic ref File;
50	depth:		int;
51	remname:	string;
52	cached:		int;
53	valid:		int;
54
55	extendpath:	fn(parent: self ref Node, elem: string): ref Node;
56	fixsymbolic:	fn(n: self ref Node);
57	invalidate:	fn(n: self ref Node);
58	markcached:	fn(n: self ref Node);
59	uncache:	fn(n: self ref Node);
60	uncachedir:	fn(parent: self ref Node, child: ref Node);
61
62	stat:		fn(n: self ref Node): array of byte;
63	qid:		fn(n: self ref Node): Sys->Qid;
64
65	fileget:	fn(n: self ref Node): ref File;
66	filefree:	fn(n: self ref Node);
67	fileclean:	fn(n: self ref Node);
68	fileisdirty:	fn(n: self ref Node): int;
69	filedirty:	fn(n: self ref Node);
70	fileread:	fn(n: self ref Node, b: array of byte, off, c: int): int;
71	filewrite:	fn(n: self ref Node, b: array of byte, off, c: int): int;
72
73	action:		fn(n: self ref Node, cmd: string): int;
74	createdir:	fn(n: self ref Node): int;
75	createfile:	fn(n: self ref Node): int;
76	changedir:	fn(n: self ref Node): int;
77	docreate:	fn(n: self ref Node): int;
78	pathname:	fn(n: self ref Node): string;
79	readdir:	fn(n: self ref Node): int;
80	readfile:	fn(n: self ref Node): int;
81	removedir:	fn(n: self ref Node): int;
82	removefile:	fn(n: self ref Node): int;
83};
84
85#
86#	Styx protocol file identifier.
87#
88
89Fid: adt
90{
91	fid:	int;
92	node:	ref Node;
93	busy:	int;
94};
95
96#
97#	Foreign file with cache.
98#
99
100File: adt
101{
102	cache:		array of byte;
103	length:		int;
104	offset:		int;
105	fd:		ref FD;
106	inuse, dirty:	int;
107	atime:		int;
108	node:		cyclic ref Node;
109	tempname:	string;
110
111	createtmp:	fn(f: self ref File): ref FD;
112};
113
114ftp:		ref Connection;
115dfid:			ref FD;
116dfidiob:		ref Iobuf;
117buffresidue:	int = 0;
118tbuff:		array of byte;
119rbuff:		array of byte;
120ccfd:			ref FD;
121stdin, stderr:	ref FD;
122
123fids:		list of ref Fid;
124
125BSZ:		con 8192;
126Chunk:		con 1024;
127Nfiles:		con 128;
128
129CHSYML:		con 16r40000000;
130
131mountpoint:	string = "/n/ftp";
132user:			string = nil;
133password:		string;
134hostname:	string = "kremvax";
135anon:		string = "anon";
136
137firewall:		string = "tcp!$proxy!402";
138myname:		string = "anon";
139myhost:		string = "lucent.com";
140proxyid:		string;
141proxyhost:	string;
142
143errstr:		string;
144net:			string;
145port:			int;
146
147Enosuchfile:	con "file does not exist";
148Eftpproto:	con "ftp protocol error";
149Eshutdown:	con "remote shutdown";
150Eioerror:	con "io error";
151Enotadirectory:	con "not a directory";
152Eisadirectory:	con "is a directory";
153Epermission:	con "permission denied";
154Ebadoffset:	con "bad offset";
155Ebadlength:	con "bad length";
156Enowstat:	con "wstat not implemented";
157Emesgmismatch:	con "message size mismatch";
158
159remdir:		ref Node;
160remroot:	ref Node;
161remrootpath:	string;
162
163heartbeatpid: int;
164
165#
166#	FTP protocol codes are 3 digits >= 100.
167#	The code type is obtained by dividing by 100.
168#
169
170Syserr:		con -2;
171Syntax:		con -1;
172Shutdown:	con 0;
173Extra:		con 1;
174Success:	con 2;
175Incomplete:	con 3;
176TempFail:	con 4;
177PermFail:	con 5;
178Impossible:	con 6;
179Err:		con 7;
180
181debug:		int = 0;
182quiet:		int = 0;
183active:		int = 0;
184cdtoroot:		int = 0;
185
186proxy:		int = 0;
187
188mountfd:	ref FD;
189styxfd: ref FD;
190
191#
192#	Set up FDs for service.
193#
194
195connect(): string
196{
197	pip := array[2] of ref Sys->FD;
198	if(sys->pipe(pip) < 0)
199		return sys->sprint("can't create pipe: %r");
200	mountfd = pip[0];
201	styxfd = pip[1];
202	return nil;
203}
204
205error(s: string)
206{
207	sys->fprint(sys->fildes(2), "ftpfs: %s\n", s);
208	raise "fail:"+s;
209}
210
211#
212#	Mount server.  Must be spawned because it does
213#	an attach transaction.
214#
215
216mount(mountpoint: string)
217{
218	if (sys->mount(mountfd, nil, mountpoint, Sys->MREPL | Sys->MCREATE, nil) < 0) {
219		sys->fprint(sys->fildes(2), "ftpfs: mount %s failed: %r\n", mountpoint);
220		shutdown();
221	}
222	mountfd = nil;
223}
224
225#
226#	Keep the link alive.
227#
228
229beatquanta:	con 10;
230beatlimit:	con 10;
231beatcount:	int;
232activity:	int;
233transfer:	int;
234
235heartbeat(pidc: chan of int)
236{
237	pid := sys->pctl(0, nil);
238	pidc <-= pid;
239	for (;;) {
240		sys->sleep(beatquanta * 1000);
241		if (activity || transfer) {
242			beatcount = 0;
243			activity = 0;
244			continue;
245		}
246		beatcount++;
247		if (beatcount == beatlimit) {
248			acquire();
249			if (sendrequest("NOOP", 0) == Success)
250				getreply(0);
251			release();
252			beatcount = 0;
253			activity = 0;
254		}
255	}
256}
257
258#
259#	Control lock.
260#
261
262ctllock: chan of int;
263
264acquire()
265{
266	ctllock <-= 1;
267}
268
269release()
270{
271	<-ctllock;
272}
273
274#
275#	Data formatting routines.
276#
277
278sendreply(r: ref Rmsg)
279{
280	if (debug)
281		sys->print("> %s\n", r.text());
282	a := r.pack();
283	if(sys->write(styxfd, a, len a) != len a)
284		sys->print("ftpfs: error replying: %r\n");
285}
286
287rerror(tag: int, s: string)
288{
289	if (debug)
290		sys->print("error: %s\n", s);
291	sendreply(ref Rmsg.Error(tag, s));
292}
293
294seterr(e: int, s: string): int
295{
296	case e {
297	Syserr =>
298		errstr = Eioerror;
299	Syntax =>
300		errstr = Eftpproto;
301	Shutdown =>
302		errstr = Eshutdown;
303	* =>
304		errstr = s;
305	}
306	return -1;
307}
308
309#
310#	Node routines.
311#
312
313anode:	Node;
314npath:	int	= 1;
315
316newnode(parent: ref Node, name: string): ref Node
317{
318	n := ref anode;
319	n.dir.name = name;
320	n.dir.atime = time->now();
321	n.children = nil;
322	n.remname = name;
323	if (parent != nil) {
324		n.parent = parent;
325		n.sibs = parent.children;
326		parent.children = n;
327		n.depth = parent.depth + 1;
328		n.valid = 0;
329	} else {
330		n.parent = n;
331		n.sibs = nil;
332		n.depth = 0;
333		n.valid = 1;
334		n.dir.uid = anon;
335		n.dir.gid = anon;
336		n.dir.mtime = n.dir.atime;
337	}
338	n.file = nil;
339	n.uniq = npath++;
340	n.cached = 0;
341	return n;
342}
343
344Node.extendpath(parent: self ref Node, elem: string): ref Node
345{
346	n: ref Node;
347
348	for (n = parent.children; n != nil; n = n.sibs)
349		if (n.dir.name == elem)
350			return n;
351	return newnode(parent, elem);
352}
353
354Node.markcached(n: self ref Node)
355{
356	n.cached = 1;
357	n.dir.atime = time->now();
358}
359
360Node.uncache(n: self ref Node)
361{
362	if (n.fileisdirty())
363		n.createfile();
364	n.filefree();
365	n.cached = 0;
366}
367
368Node.uncachedir(parent: self ref Node, child: ref Node)
369{
370	sp: ref Node;
371
372	if (parent == nil || parent == child)
373		return;
374	for (sp = parent.children; sp != nil; sp = sp.sibs)
375		if (sp != child && sp.file != nil && !sp.file.dirty && sp.file.fd != nil) {
376			sp.filefree();
377			sp.cached = 0;
378		}
379}
380
381Node.invalidate(node: self ref Node)
382{
383	n: ref Node;
384
385	node.uncachedir(nil);
386	for (n = node.children; n != nil; n = n.sibs) {
387		n.cached = 0;
388		n.invalidate();
389		n.valid = 0;
390	}
391}
392
393Node.fixsymbolic(n: self ref Node)
394{
395	if (n.changedir() == 0) {
396		n.dir.mode |= Sys->DMDIR;
397		n.dir.qid.qtype = Sys->QTDIR;
398	} else
399		n.dir.qid.qtype = Sys->QTFILE;
400	n.dir.mode &= ~CHSYML;
401}
402
403Node.stat(n: self ref Node): array of byte
404{
405	return styx->packdir(n.dir);
406}
407
408Node.qid(n: self ref Node): Sys->Qid
409{
410	if(n.dir.mode & Sys->DMDIR)
411		return Sys->Qid(big n.uniq, 0, Sys->QTDIR);
412	return Sys->Qid(big n.uniq, 0, Sys->QTFILE);
413}
414
415#
416#	File routines.
417#
418
419ntmp:	int;
420files:	list of ref File;
421nfiles:	int;
422afile:	File;
423atime:	int;
424
425#
426#	Allocate a file structure for a node.  If too many
427#	are already allocated discard the oldest.
428#
429
430Node.fileget(n: self ref Node): ref File
431{
432	f, o: ref File;
433	l: list of ref File;
434
435	if (n.file != nil)
436		return n.file;
437	o = nil;
438	for (l = files; l != nil; l = tl l) {
439		f = hd l;
440		if (f.inuse == 0)
441			break;
442		if (!f.dirty && (o == nil || o.atime > f.atime))
443			o = f;
444	}
445	if (l == nil) {
446		if (nfiles == Nfiles && o != nil) {
447			o.node.uncache();
448			f = o;
449		}
450		else {
451			f = ref afile;
452			files = f :: files;
453			nfiles++;
454		}
455	}
456	n.file = f;
457	f.node = n;
458	f.atime = atime++;
459	f.inuse = 1;
460	f.dirty = 0;
461	f.length = 0;
462	f.fd = nil;
463	return f;
464}
465
466#
467#	Create a temporary file for a local copy of a file.
468#	If too many are open uncache parent.
469#
470
471File.createtmp(f: self ref File): ref FD
472{
473	t := "/tmp/ftp." + string time->now() + "." + string ntmp;
474	if (ntmp >= 16)
475		f.node.parent.uncachedir(f.node);
476	f.fd = sys->create(t, Sys->ORDWR | Sys->ORCLOSE, 8r600);
477	f.tempname = t;
478	f.offset = 0;
479	ntmp++;
480	return f.fd;
481}
482
483#
484#	Read 'c' bytes at offset 'off' from a file into buffer 'b'.
485#
486
487Node.fileread(n: self ref Node, b: array of byte, off, c: int): int
488{
489	f: ref File;
490	t, i: int;
491
492	f = n.file;
493	if (off + c > f.length)
494		c = f.length - off;
495	for (t = 0; t < c; t += i) {
496		if (off >= f.length)
497			return t;
498		if (off < Chunk) {
499			i = c;
500			if (off + i > Chunk)
501				i = Chunk - off;
502			b[t:] = f.cache[off: off + i];
503		}
504		else {
505			if (f.offset != off) {
506				if (sys->seek(f.fd, big off, Sys->SEEKSTART) < big 0) {
507					f.offset = -1;
508					return seterr(Err, sys->sprint("seek temp failed: %r"));
509				}
510			}
511			if (t == 0)
512				i = sys->read(f.fd, b, c - t);
513			else
514				i = sys->read(f.fd, rbuff, c - t);
515			if (i < 0) {
516				f.offset = -1;
517				return seterr(Err, sys->sprint("read temp failed: %r"));
518			}
519			if (i == 0)
520				break;
521			if (t > 0)
522				b[t:] = rbuff[0: i];
523			f.offset = off + i;
524		}
525		off += i;
526	}
527	return t;
528}
529
530#
531#	Write 'c' bytes at offset 'off' to a file from buffer 'b'.
532#
533
534Node.filewrite(n: self ref Node, b: array of byte, off, c: int): int
535{
536	f: ref File;
537	t, i: int;
538
539	f = n.fileget();
540	if (f.cache == nil)
541		f.cache = array[Chunk] of byte;
542	for (t = 0; t < c; t += i) {
543		if (off < Chunk) {
544			i = c;
545			if (off + i > Chunk)
546				i = Chunk - off;
547			f.cache[off:] = b[t: t + i];
548		}
549		else {
550			if (f.fd == nil) {
551				if (f.createtmp() == nil)
552					return seterr(Err, sys->sprint("temp file: %r"));
553				if (sys->write(f.fd, f.cache, Chunk) != Chunk) {
554					f.offset = -1;
555					return seterr(Err, sys->sprint("write temp failed: %r"));
556				}
557				f.offset = Chunk;
558				f.length = Chunk;
559			}
560			if (f.offset != off) {
561				if (off > f.length) {
562					# extend the file with zeroes
563					# sparse files may not be supported
564				}
565				if (sys->seek(f.fd, big off, Sys->SEEKSTART) < big 0) {
566					f.offset = -1;
567					return seterr(Err, sys->sprint("seek temp failed: %r"));
568				}
569			}
570			i = sys->write(f.fd, b[t:len b], c - t);
571			if (i != c - t) {
572				f.offset = -1;
573				return seterr(Err, sys->sprint("write temp failed: %r"));
574			}
575		}
576		off += i;
577		f.offset = off;
578	}
579	if (off > f.length)
580		f.length = off;
581	return t;
582}
583
584Node.filefree(n: self ref Node)
585{
586	f: ref File;
587
588	f = n.file;
589	if (f == nil)
590		return;
591	if (f.fd != nil) {
592		ntmp--;
593		f.fd = nil;
594		f.tempname = nil;
595	}
596	f.cache = nil;
597	f.length = 0;
598	f.inuse = 0;
599	f.dirty = 0;
600	n.file = nil;
601}
602
603Node.fileclean(n: self ref Node)
604{
605	if (n.file != nil)
606		n.file.dirty = 0;
607}
608
609Node.fileisdirty(n: self ref Node): int
610{
611	return n.file != nil && n.file.dirty;
612}
613
614Node.filedirty(n: self ref Node)
615{
616	f: ref File;
617
618	f = n.fileget();
619	f.dirty = 1;
620}
621
622#
623#	Fid management.
624#
625
626afid:	Fid;
627
628getfid(fid: int): ref Fid
629{
630	l: list of ref Fid;
631	f, ff: ref Fid;
632
633	ff = nil;
634	for (l = fids; l != nil; l = tl l) {
635		f = hd l;
636		if (f.fid == fid) {
637			if (f.busy)
638				return f;
639			else {
640				ff = f;
641				break;
642			}
643		} else if (ff == nil && !f.busy)
644			ff = f;
645	}
646	if (ff == nil) {
647		ff = ref afid;
648		fids = ff :: fids;
649	}
650	ff.node = nil;
651	ff.fid = fid;
652	return ff;
653}
654
655#
656#	FTP protocol.
657#
658
659fail(s: int, l: string)
660{
661	case s {
662	Syserr =>
663		sys->print("read fail: %r\n");
664	Syntax =>
665		sys->print("%s\n", Eftpproto);
666	Shutdown =>
667		sys->print("%s\n", Eshutdown);
668	* =>
669		sys->print("unexpected response: %s\n", l);
670	}
671	exit;
672}
673
674getfullreply(echo: int): (int, int, string)
675{
676	reply := "";
677	s: string;
678	code := -1;
679	do{
680		s = dfidiob.gets('\n');
681		if(s == nil)
682			return (Shutdown, 0, nil);
683		if(len s >= 2 && s[len s-1] == '\n'){
684			if (s[len s - 2] == '\r')
685				s = s[0: len s - 2];
686			else
687				s = s[0: len s - 1];
688		}
689		if (debug || echo)
690			sys->print("%s\n", s);
691		reply = reply+s;
692		if(code < 0){
693			if(len s < 3)
694				return (Syntax, 0, nil);
695			code = int s[0:3];
696			if(s[3] != '-')
697				break;
698		}
699	}while(len s < 4 || int s[0:3] != code || s[3] != ' ');
700
701	if(code < 100)
702		return (Syntax, 0, nil);
703	return (code / 100, code, reply);
704}
705
706getreply(echo: int): (int, string)
707{
708	(c, nil, s) := getfullreply(echo);
709	return (c, s);
710}
711
712sendrequest2(req: string, echo: int, figleaf: string): int
713{
714	activity = 1;
715	if (debug || echo) {
716		if (figleaf == nil)
717			figleaf = req;
718		sys->print("%s\n", figleaf);
719	}
720	b := array of byte (req + "\r\n");
721	n := sys->write(dfid, b, len b);
722	if (n < 0)
723		return Syserr;
724	if (n != len b)
725		return Shutdown;
726	return Success;
727}
728
729sendrequest(req: string, echo: int): int
730{
731	return sendrequest2(req, echo, req);
732}
733
734sendfail(s: int)
735{
736	case s {
737	Syserr =>
738		sys->print("write fail: %r\n");
739	Shutdown =>
740		sys->print("%s\n", Eshutdown);
741	* =>
742		sys->print("internal error\n");
743	}
744	exit;
745}
746
747dataport(l: list of string): string
748{
749	s := "tcp!" + hd l;
750	l = tl l;
751	s = s + "." + hd l;
752	l = tl l;
753	s = s + "." + hd l;
754	l = tl l;
755	s = s + "." + hd l;
756	l = tl l;
757	return s + "!" + string ((int hd l * 256) + (int hd tl l));
758}
759
760commas(l: list of string): string
761{
762	s := hd l;
763	l = tl l;
764	while (l != nil) {
765		s = s + "," + hd l;
766		l = tl l;
767	}
768	return s;
769}
770
771third(cmd: string): ref FD
772{
773	acquire();
774	for (;;) {
775		data := dial->dial(firewall, nil);
776		if(data == nil) {
777			if (debug)
778				sys->print("dial %s failed: %r\n", firewall);
779			break;
780		}
781		t := sys->sprint("\n%s!*\n\n%s\n%s\n1\n-1\n-1\n", proxyhost, myhost, myname);
782		b := array of byte t;
783		n := sys->write(data.dfd, b, len b);
784		if (n < 0) {
785			if (debug)
786				sys->print("firewall write failed: %r\n");
787			break;
788		}
789		b = array[256] of byte;
790		n = sys->read(data.dfd, b, len b);
791		if (n < 0) {
792			if (debug)
793				sys->print("firewall read failed: %r\n");
794			break;
795		}
796		(c, k) := sys->tokenize(string b[:n], "\n");
797		if (c < 2) {
798			if (debug)
799				sys->print("bad response from firewall\n");
800			break;
801		}
802		if (hd k != "0") {
803			if (debug)
804				sys->print("firewall connect: %s\n", hd tl k);
805			break;
806		}
807		p := hd tl k;
808		if (debug)
809			sys->print("portid %s\n", p);
810		(c, k) = sys->tokenize(p, "!");
811		if (c < 3) {
812			if (debug)
813				sys->print("bad portid from firewall\n");
814			break;
815		}
816		n = int hd tl tl k;
817		(c, k) = sys->tokenize(hd tl k, ".");
818		if (c != 4) {
819			if (debug)
820				sys->print("bad portid ip address\n");
821			break;
822		}
823		t = sys->sprint("PORT %s,%d,%d", commas(k), n / 256, n & 255);
824		r := sendrequest(t, 0);
825		if (r != Success)
826			break;
827		(r, nil) = getreply(0);
828		if (r != Success)
829			break;
830		r = sendrequest(cmd, 0);
831		if (r != Success)
832			break;
833		(r, nil) = getreply(0);
834		if (r != Extra)
835			break;
836		n = sys->read(data.dfd, b, len b);
837		if (n < 0) {
838			if (debug)
839				sys->print("firewall read failed: %r\n");
840			break;
841		}
842		b = array of byte "0\n?\n";
843		n = sys->write(data.dfd, b, len b);
844		if (n < 0) {
845			if (debug)
846				sys->print("firewall write failed: %r\n");
847			break;
848		}
849		release();
850		return data.dfd;
851	}
852	release();
853	return nil;
854}
855
856passive(cmd: string): ref FD
857{
858	acquire();
859	if (sendrequest("PASV", 0) != Success) {
860		release();
861		return nil;
862	}
863	(r, m) := getreply(0);
864	release();
865	if (r != Success)
866		return nil;
867	(nil, p) := str->splitl(m, "(");
868	if (p == nil)
869		str->splitl(m, "0-9");
870	else
871		p = p[1:len p];
872	(c, l) := sys->tokenize(p, ",");
873	if (c < 6) {
874		sys->print("data: %s\n", m);
875		return nil;
876	}
877	a := dataport(l);
878	if (debug)
879		sys->print("data dial %s\n", a);
880	d := dial->dial(a, nil);
881	if(d == nil)
882		return nil;
883	acquire();
884	r = sendrequest(cmd, 0);
885	if (r != Success) {
886		release();
887		return nil;
888	}
889	(r, m) = getreply(0);
890	release();
891	if (r != Extra)
892		return nil;
893	return d.dfd;
894}
895
896getnet(dir: string): (string, int)
897{
898	buf := array[50] of byte;
899	n := dir + "/local";
900	lfd := sys->open(n, Sys->OREAD);
901	if (lfd == nil) {
902		if (debug)
903			sys->fprint(stderr, "open %s: %r\n", n);
904		return (nil, 0);
905	}
906	length := sys->read(lfd, buf, len buf);
907	if (length < 0) {
908		if (debug)
909			sys->fprint(stderr, "read%s: %r\n", n);
910		return (nil, 0);
911	}
912	(r, l) := sys->tokenize(string buf[0:length], "!");
913	if (r != 2) {
914		if (debug)
915			sys->fprint(stderr, "tokenize(%s) returned (%d)\n", string buf[0:length], r);
916		return (nil, 0);
917	}
918	if (debug)
919		sys->print("net is %s!%d\n", hd l, int hd tl l);
920	return (hd l, int hd tl l);
921}
922
923activate(cmd: string): ref FD
924{
925	r: int;
926
927	listenport, dataport: ref Connection;
928	m: string;
929
930	listenport = dial->announce("tcp!" + net + "!0");
931	if(listenport == nil)
932		return nil;
933	(x1, x2)  := getnet(listenport.dir);
934	(nil, x4) := sys->tokenize(x1, ".");
935	t := sys->sprint("PORT %s,%d,%d", commas(x4), int x2 / 256, int x2&255);
936	acquire();
937	r = sendrequest(t, 0);
938	if (r != Success) {
939		release();
940		return nil;
941	}
942	(r, m) = getreply(0);
943	if (r != Success) {
944		release();
945		return nil;
946	}
947	r = sendrequest(cmd, 0);
948	if (r != Success) {
949		release();
950		return nil;
951	}
952	(r, m) = getreply(0);
953	release();
954	if (r != Extra)
955		return nil;
956	dataport = dial->listen(listenport);
957	if(dataport == nil) {
958		sys->fprint(stderr, "activate: listen failed: %r\n");
959		return nil;
960	}
961	fd := sys->open(dataport.dir + "/data", sys->ORDWR);
962	if (debug)
963		sys->print("activate: data connection on %s\n", dataport.dir);
964	if (fd == nil) {
965		sys->fprint(stderr, "activate: open of %s failed: %r\n", dataport.dir);
966		return nil;
967	}
968	return fd;
969}
970
971data(cmd: string): ref FD
972{
973	if (proxy)
974		return third(cmd);
975	else if (active)
976		return activate(cmd);
977	else
978		return passive(cmd);
979}
980
981#
982#	File list cracking routines.
983#
984
985fields(l: list of string, n: int): array of string
986{
987	a := array[n] of string;
988	for (i := 0; i < n; i++) {
989		a[i] = hd l;
990		l = tl l;
991	}
992	return a;
993}
994
995now:	ref Tm;
996months:	con "janfebmaraprmayjunjulaugsepoctnovdec";
997
998cracktime(month, day, year, hms: string): int
999{
1000	tm: Tm;
1001
1002	if (now == nil)
1003		now = time->local(time->now());
1004	tm = *now;
1005	if (month[0] >= '0' && month[0] <= '9') {
1006		tm.mon = int month - 1;
1007		if (tm.mon < 0 || tm.mon > 11)
1008			tm.mon = 5;
1009	}
1010	else if (len month >= 3) {
1011		month = str->tolower(month[0:3]);
1012		for (i := 0; i < 36; i += 3)
1013			if (month == months[i:i+3]) {
1014				tm.mon = i / 3;
1015				break;
1016			}
1017	}
1018	tm.mday = int day;
1019	if (hms != nil) {
1020		(h, z) := str->splitl(hms, "apAP");
1021		(a, b) := str->splitl(h, ":");
1022		tm.hour = int a;
1023		if (b != nil) {
1024			(c, d) := str->splitl(b[1:len b], ":");
1025			tm.min = int c;
1026			if (d != nil)
1027				tm.sec = int d[1:len d];
1028		}
1029		if (z != nil && str->tolower(z)[0] == 'p')
1030			tm.hour += 12;
1031	}
1032	if (year != nil) {
1033		tm.year = int year;
1034		if (tm.year >= 1900)
1035			tm.year -= 1900;
1036	}
1037	else {
1038		if (tm.mon > now.mon || (tm.mon == now.mon && tm.mday > now.mday+1))
1039			tm.year--;
1040	}
1041	return time->tm2epoch(ref tm);
1042}
1043
1044crackmode(p: string): int
1045{
1046	flags := 0;
1047	case len p {
1048	10 =>	# unix and new style plan 9
1049		case p[0] {
1050		'l' =>
1051			return CHSYML | 0777;
1052		'd' =>
1053			flags = Sys->DMDIR;
1054		}
1055		p = p[1:10];
1056	11 =>	# old style plan 9
1057		if (p[0] == 'l')
1058			flags = Sys->DMDIR;
1059		p = p[2:11];
1060	* =>
1061		return Sys->DMDIR | 0777;
1062	}
1063	mode := 0;
1064	n := 0;
1065	for (i := 0; i < 3; i++) {
1066		mode <<= 3;
1067		if (p[n] == 'r')
1068			mode |= 4;
1069		if (p[n+1] == 'w')
1070			mode |= 2;
1071		case p[n+2] {
1072		'x' or 's' or 'S' =>
1073			mode |= 1;
1074		}
1075		n += 3;
1076	}
1077	return mode | flags;
1078}
1079
1080crackdir(p: string): (string, Dir)
1081{
1082	d: Dir;
1083	ln, a: string;
1084
1085	(n, l) := sys->tokenize(p, " \t\r\n");
1086	f := fields(l, n);
1087	if (n > 2 && f[n - 2] == "->")
1088		n -= 2;
1089	case n {
1090	8 =>	# ls -l
1091		ln = f[7];
1092		d.uid = f[2];
1093		d.gid = f[2];
1094		d.mode = crackmode(f[0]);
1095		d.length = big f[3];
1096		(a, nil) = str->splitl(f[6], ":");
1097		if (len a != len f[6])
1098			d.atime = cracktime(f[4], f[5], nil, f[6]);
1099		else
1100			d.atime = cracktime(f[4], f[5], f[6], nil);
1101	9 =>	# ls -lg
1102		ln = f[8];
1103		d.uid = f[2];
1104		d.gid = f[3];
1105		d.mode = crackmode(f[0]);
1106		d.length = big f[4];
1107		(a, nil) = str->splitl(f[7], ":");
1108		if (len a != len f[7])
1109			d.atime = cracktime(f[5], f[6], nil, f[7]);
1110		else
1111			d.atime = cracktime(f[5], f[6], f[7], nil);
1112	10 =>	# plan 9
1113		ln = f[9];
1114		d.uid = f[3];
1115		d.gid = f[4];
1116		d.mode = crackmode(f[0]);
1117		d.length = big f[5];
1118		(a, nil) = str->splitl(f[8], ":");
1119		if (len a != len f[8])
1120			d.atime = cracktime(f[6], f[7], nil, f[8]);
1121		else
1122			d.atime = cracktime(f[6], f[7], f[8], nil);
1123	4 =>	# NT
1124		ln = f[3];
1125		d.uid = anon;
1126		d.gid = anon;
1127		if (f[2] == "<DIR>") {
1128			d.length = big 0;
1129			d.mode = Sys->DMDIR | 8r777;
1130		}
1131		else {
1132			d.mode = 8r666;
1133			d.length = big f[2];
1134		}
1135		(n, l) = sys->tokenize(f[0], "/-");
1136		if (n == 3)
1137			d.atime = cracktime(hd l, hd tl l, f[2], f[1]);
1138	1 =>	# ls
1139		ln = f[0];
1140		d.uid = anon;
1141		d.gid = anon;
1142		d.mode = 0777;
1143		d.atime = 0;
1144	* =>
1145		return (nil, d);
1146	}
1147	if (ln == "." || ln == "..")
1148		return (nil, d);
1149	d.mtime = d.atime;
1150	d.name = ln;
1151	return (ln, d);
1152}
1153
1154longls := 1;
1155
1156Node.readdir(n: self ref Node): int
1157{
1158	f: ref FD;
1159	p: ref Node;
1160
1161	if (n.changedir() < 0)
1162		return -1;
1163	transfer = 1;
1164	for (;;) {
1165		if (longls) {
1166			f = data("LIST -la");
1167			if (f == nil) {
1168				longls = 0;
1169				continue;
1170			}
1171		}
1172		else {
1173			f = data("LIST");
1174			if (f == nil) {
1175				transfer = 0;
1176				return seterr(Err, Enosuchfile);
1177			}
1178		}
1179		break;
1180	}
1181	b := bufio->fopen(f, sys->OREAD);
1182	if (b == nil) {
1183		transfer = 0;
1184		return seterr(Err, Eioerror);
1185	}
1186	while ((s := b.gets('\n')) != nil) {
1187		if (debug)
1188			sys->print("%s", s);
1189		(l, d) := crackdir(s);
1190		if (l == nil)
1191			continue;
1192		p = n.extendpath(l);
1193		p.dir = d;
1194		p.valid = 1;
1195	}
1196	b = nil;
1197	f = nil;
1198	(r, nil) := getreply(0);
1199	transfer = 0;
1200	if (r != Success)
1201		return seterr(Err, Enosuchfile);
1202	return 0;
1203}
1204
1205Node.readfile(n: self ref Node): int
1206{
1207	c: int;
1208
1209	if (n.parent.changedir() < 0)
1210		return -1;
1211	transfer = 1;
1212	f := data("RETR " + n.remname);
1213	if (f == nil) {
1214		transfer = 0;
1215		return seterr(Err, Enosuchfile);
1216	}
1217	off := 0;
1218	while ((c = sys->read(f, tbuff, BSZ)) > 0) {
1219		if (n.filewrite(tbuff, off, c) != c) {
1220			off = -1;
1221			break;
1222		}
1223		off += c;
1224	}
1225	if (c < 0) {
1226		transfer = 0;
1227		return seterr(Err, Eioerror);
1228	}
1229	f = nil;
1230	if(off == 0)
1231		n.filewrite(tbuff, off, 0);
1232	(s, nil) := getreply(0);
1233	transfer = 0;
1234	if (s != Success)
1235		return seterr(s, Enosuchfile);
1236	return off;
1237}
1238
1239path(a, b: string): string
1240{
1241	if (a == nil)
1242		return b;
1243	if (b == nil)
1244		return a;
1245	if (a[len a - 1] == '/')
1246		return a + b;
1247	else
1248		return a + "/" + b;
1249}
1250
1251Node.pathname(n: self ref Node): string
1252{
1253	s: string;
1254
1255	while (n != n.parent) {
1256		s = path(n.remname, s);
1257		n = n.parent;
1258	}
1259	return path(remrootpath, s);
1260}
1261
1262Node.changedir(n: self ref Node): int
1263{
1264	t: ref Node;
1265	d: string;
1266
1267	t = n;
1268	if (t == remdir)
1269		return 0;
1270	if (n.depth == 0)
1271		d = remrootpath;
1272	else
1273		d = n.pathname();
1274	remdir.uncachedir(nil);
1275	acquire();
1276	r := sendrequest("CWD " + d, 0);
1277	if (r == Success)
1278		(r, nil) = getreply(0);
1279	release();
1280	case r {
1281	Success
1282#	or Incomplete
1283		=>
1284		remdir = n;
1285		return 0;
1286	* =>
1287		return seterr(r, Enosuchfile);
1288	}
1289}
1290
1291Node.docreate(n: self ref Node): int
1292{
1293	f: ref FD;
1294
1295	transfer = 1;
1296	f = data("STOR " + n.remname);
1297	if (f == nil) {
1298		transfer = 0;
1299		return -1;
1300	}
1301	off := 0;
1302	for (;;) {
1303		r := n.fileread(tbuff, off, BSZ);
1304		if (r <= 0)
1305			break;
1306		if (sys->write(f, tbuff, r) < 0) {
1307			off = -1;
1308			break;
1309		}
1310		off += r;
1311	}
1312	transfer = 0;
1313	return off;
1314}
1315
1316Node.createfile(n: self ref Node): int
1317{
1318	if (n.parent.changedir() < 0)
1319		return -1;
1320	off := n.docreate();
1321	if (off < 0)
1322		return -1;
1323	(r, nil) := getreply(0);
1324	if (r != Success)
1325		return -1;
1326	return off;
1327}
1328
1329Node.action(n: self ref Node, cmd: string): int
1330{
1331	if (n.parent.changedir() < 0)
1332		return -1;
1333	acquire();
1334	r := sendrequest(cmd + " " + n.dir.name, 0);
1335	if (r == Success)
1336		(r, nil) = getreply(0);
1337	release();
1338	if (r != Success)
1339		return -1;
1340	return 0;
1341}
1342
1343Node.createdir(n: self ref Node): int
1344{
1345	return n.action("MKD");
1346}
1347
1348Node.removefile(n: self ref Node): int
1349{
1350	return n.action("DELE");
1351}
1352
1353Node.removedir(n: self ref Node): int
1354{
1355	return n.action("RMD");
1356}
1357
1358pwd(s: string): string
1359{
1360	(nil, s) = str->splitl(s, "\"");
1361	if (s == nil || len s < 2)
1362		return "/";
1363	(s, nil) = str->splitl(s[1:len s], "\"");
1364	return s;
1365}
1366
1367#
1368#	User info for firewall.
1369#
1370getuser()
1371{
1372	b := array[Sys->NAMEMAX] of byte;
1373	f := sys->open("/dev/user", Sys->OREAD);
1374	if (f != nil) {
1375		n := sys->read(f, b, len b);
1376		if (n > 0)
1377			myname = string b[:n];
1378		else if (n == 0)
1379			sys->print("warning: empty /dev/user\n");
1380		else
1381			sys->print("warning: could not read /dev/user: %r\n");
1382	} else
1383		sys->print("warning: could not open /dev/user: %r\n");
1384	f = sys->open("/dev/sysname", Sys->OREAD);
1385	if (f != nil) {
1386		n := sys->read(f, b, len b);
1387		if (n > 0)
1388			myhost = string b[:n];
1389		else if (n == 0)
1390			sys->print("warning: empty /dev/sysname\n");
1391		else
1392			sys->print("warning: could not read /dev/sysname: %r\n");
1393	} else
1394		sys->print("warning: could not open /dev/sysname: %r\n");
1395	if (debug)
1396		sys->print("proxy %s for %s@%s\n", firewall, myname, myhost);
1397}
1398
1399server()
1400{
1401	while((t := Tmsg.read(styxfd, 0)) != nil){
1402		if (debug)
1403			sys->print("< %s\n", t.text());
1404		pick x := t {
1405		Readerror =>
1406			sys->print("ftpfs: read error on mount point: %s\n", x.error);
1407			kill(heartbeatpid);
1408			exit;
1409		Version =>
1410			versionT(x);
1411		Auth =>
1412			authT(x);
1413		Attach =>
1414			attachT(x);
1415		Clunk =>
1416			clunkT(x);
1417		Create =>
1418			createT(x);
1419		Flush =>
1420			flushT(x);
1421		Open =>
1422			openT(x);
1423		Read =>
1424			readT(x);
1425		Remove =>
1426			removeT(x);
1427		Stat =>
1428			statT(x);
1429		Walk =>
1430			walkT(x);
1431		Write =>
1432			writeT(x);
1433		Wstat =>
1434			wstatT(x);
1435		* =>
1436			rerror(t.tag, "unimp");
1437		}
1438	}
1439	if (debug)
1440		sys->print("ftpfs: server: exiting\n");
1441	kill(heartbeatpid);
1442}
1443
1444raw(on: int)
1445{
1446	if(ccfd == nil) {
1447		ccfd = sys->open("/dev/consctl", Sys->OWRITE);
1448		if(ccfd == nil) {
1449			sys->fprint(stderr, "ftpfs: cannot open /dev/consctl: %r\n");
1450			return;
1451		}
1452	}
1453	if(on)
1454		sys->fprint(ccfd, "rawon");
1455	else
1456		sys->fprint(ccfd, "rawoff");
1457}
1458
1459prompt(p: string, def: string, echo: int): string
1460{
1461	if (def == nil)
1462		sys->print("%s: ", p);
1463	else
1464		sys->print("%s[%s]: ", p, def);
1465	if (!echo)
1466		raw(1);
1467	b := bufio->fopen(stdin, Sys->OREAD);
1468	s := b.gets(int '\n');
1469	if (!echo) {
1470		raw(0);
1471		sys->print("\n");
1472	}
1473	if(s != nil)
1474		s = s[0:len s - 1];
1475	if (s == "")
1476		return def;
1477	return s;
1478}
1479
1480#
1481#	Entry point.  Load modules and initiate protocol.
1482#
1483
1484nomod(s: string)
1485{
1486	sys->fprint(sys->fildes(2), "ftpfs: can't load %s: %r\n", s);
1487	raise "fail:load";
1488}
1489
1490init(nil: ref Draw->Context, args: list of string)
1491{
1492	l: string;
1493	rv: int;
1494	code: int;
1495
1496	sys = load Sys Sys->PATH;
1497	dial = load Dial Dial->PATH;
1498	stdin = sys->fildes(0);
1499	stderr = sys->fildes(2);
1500
1501	time = load Daytime Daytime->PATH;
1502	if (time == nil)
1503		nomod(Daytime->PATH);
1504	str = load String String->PATH;
1505	if (str == nil)
1506		nomod(String->PATH);
1507	bufio = load Bufio Bufio->PATH;
1508	if (bufio == nil)
1509		nomod(Bufio->PATH);
1510	styx = load Styx Styx->PATH;
1511	if (styx == nil)
1512		nomod(Styx->PATH);
1513	styx->init();
1514	arg := load Arg Arg->PATH;
1515	if(arg == nil)
1516		nomod(Arg->PATH);
1517
1518	# parse arguments
1519	# [-/dpq] [-m mountpoint] [-a password] host
1520	arg->init(args);
1521	arg->setusage("ftpfs [-/dpq] [-m mountpoint] [-a password] ftphost");
1522	keyspec := "";
1523	while((op := arg->opt()) != 0)
1524		case op {
1525		'd' =>
1526			debug++;
1527		'/' =>
1528			cdtoroot = 1;
1529		'p' =>
1530			active = 1;
1531		'q' =>
1532			quiet = 1;
1533		'm' =>
1534			mountpoint = arg->earg();
1535		'a' =>
1536			password = arg->earg();
1537			user = "anonymous";
1538		'k' =>
1539			keyspec = arg->earg();
1540		* =>
1541			arg->usage();
1542		}
1543	argv := arg->argv();
1544	if (len argv != 1)
1545		arg->usage();
1546	arg = nil;
1547	hostname = hd argv;
1548
1549	if (len hostname > 6 && hostname[:6] == "proxy!") {
1550		hostname = hostname[6:];
1551		proxy = 1;
1552	}
1553
1554	if (proxy) {
1555		if (!quiet)
1556			sys->print("dial firewall service %s\n", firewall);
1557		ftp = dial->dial(firewall, nil);
1558		if(ftp == nil) {
1559			sys->print("dial %s failed: %r\n", firewall);
1560			exit;
1561		}
1562		dfid = ftp.dfd;
1563		getuser();
1564		t := sys->sprint("\ntcp!%s!tcp.21\n\n%s\n%s\n0\n-1\n-1\n", hostname, myhost, myname);
1565		if (debug)
1566			sys->print("request%s\n", t);
1567		b := array of byte t;
1568		rv = sys->write(dfid, b, len b);
1569		if (rv < 0) {
1570			sys->print("firewall write failed: %r\n");
1571			exit;
1572		}
1573		b = array[256] of byte;
1574		rv = sys->read(dfid, b, len b);
1575		if (rv < 0) {
1576			sys->print("firewall read failed: %r\n");
1577			return;
1578		}
1579		(c, k) := sys->tokenize(string b[:rv], "\n");
1580		if (c < 2) {
1581			sys->print("bad response from firewall\n");
1582			exit;
1583		}
1584		if (hd k != "0") {
1585			sys->print("firewall connect: %s\n", hd tl k);
1586			exit;
1587		}
1588		proxyid = hd tl k;
1589		if (debug)
1590			sys->print("proxyid %s\n", proxyid);
1591		(c, k) = sys->tokenize(proxyid, "!");
1592		if (c < 3) {
1593			sys->print("bad proxyid from firewall\n");
1594			exit;
1595		}
1596		proxyhost = (hd k) + "!" + (hd tl k);
1597		if (debug)
1598			sys->print("proxyhost %s\n", proxyhost);
1599	} else {
1600		d := dial->netmkaddr(hostname, "tcp", "ftp");
1601		ftp = dial->dial(d, nil);
1602		if(ftp == nil)
1603			error(sys->sprint("dial %s failed: %r", d));
1604		if(debug)
1605			sys->print("localdir %s\n", ftp.dir);
1606		dfid = ftp.dfd;
1607	}
1608	dfidiob = bufio->fopen(dfid, sys->OREAD);
1609	(net, port) = getnet(ftp.dir);
1610	tbuff = array[BSZ] of byte;
1611	rbuff = array[BSZ] of byte;
1612	(rv, l) = getreply(!quiet);
1613	if (rv != Success)
1614		fail(rv, l);
1615	if (user == nil) {
1616		getuser();
1617		user = myname;
1618		user = prompt("User", user, 1);
1619	}
1620	rv = sendrequest("USER " + user, 0);
1621	if (rv != Success)
1622		sendfail(rv);
1623	(rv, code, l) = getfullreply(!quiet);
1624	if (rv != Success) {
1625		if (rv != Incomplete)
1626			fail(rv, l);
1627		if (code == 331) {
1628			if(password == nil){
1629				factotum := load Factotum Factotum->PATH;
1630				if(factotum != nil){
1631					factotum->init();
1632					if(user != nil && keyspec == nil)
1633						keyspec = sys->sprint("user=%q", user);
1634					(nil, password) = factotum->getuserpasswd(sys->sprint("proto=pass server=%s service=ftp %s", hostname, keyspec));
1635				}
1636				if(password == nil)
1637					password = prompt("Password", nil, 0);
1638			}
1639			rv = sendrequest2("PASS " + password, 0, "PASS XXXX");
1640			if (rv != Success)
1641				sendfail(rv);
1642			(rv, l) = getreply(0);
1643			if (rv != Success)
1644				fail(rv, l);
1645		}
1646	}
1647	if (cdtoroot) {
1648		rv = sendrequest("CWD /", 0);
1649		if (rv != Success)
1650			sendfail(rv);
1651		(rv, l) = getreply(0);
1652		if (rv != Success)
1653			fail(rv, l);
1654	}
1655	rv = sendrequest("TYPE I", 0);
1656	if (rv != Success)
1657		sendfail(rv);
1658	(rv, l) = getreply(0);
1659	if (rv != Success)
1660		fail(rv, l);
1661	rv = sendrequest("PWD", 0);
1662	if (rv != Success)
1663		sendfail(rv);
1664	(rv, l) = getreply(0);
1665	if (rv != Success)
1666		fail(rv, l);
1667	remrootpath = pwd(l);
1668	remroot = newnode(nil, "/");
1669	remroot.dir.mode = Sys->DMDIR | 8r777;
1670	remroot.dir.qid.qtype = Sys->QTDIR;
1671	remdir = remroot;
1672	l = connect();
1673	if (l != nil) {
1674		sys->print("%s\n", l);
1675		exit;
1676	}
1677	ctllock = chan[1] of int;
1678	spawn mount(mountpoint);
1679	pidc := chan of int;
1680	spawn heartbeat(pidc);
1681	heartbeatpid = <-pidc;
1682	if (debug)
1683		sys->print("heartbeatpid %d\n", heartbeatpid);
1684	spawn server();				# dies when receive on chan fails
1685}
1686
1687kill(pid: int)
1688{
1689	if (debug)
1690		sys->print("killing %d\n", pid);
1691	fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
1692	if(fd != nil)
1693		sys->fprint(fd, "kill");
1694}
1695
1696shutdown()
1697{
1698	mountfd = nil;
1699}
1700
1701#
1702#	Styx transactions.
1703#
1704
1705versionT(t: ref Tmsg.Version)
1706{
1707	(msize, version) := styx->compatible(t, Styx->MAXRPC, Styx->VERSION);
1708	sendreply(ref Rmsg.Version(t.tag, msize, version));
1709}
1710
1711authT(t: ref Tmsg.Auth)
1712{
1713	sendreply(ref Rmsg.Error(t.tag, "authentication not required"));
1714}
1715
1716flushT(t: ref Tmsg.Flush)
1717{
1718	sendreply(ref Rmsg.Flush(t.tag));
1719}
1720
1721attachT(t: ref Tmsg.Attach)
1722{
1723	f := getfid(t.fid);
1724	f.busy = 1;
1725	f.node = remroot;
1726	sendreply(ref Rmsg.Attach(t.tag, remroot.qid()));
1727}
1728
1729walkT(t: ref Tmsg.Walk)
1730{
1731	f := getfid(t.fid);
1732	qids: array of Sys->Qid;
1733	node := f.node;
1734	if(len t.names > 0){
1735		qids = array[len t.names] of Sys->Qid;
1736		for(i := 0; i < len t.names; i++) {
1737			if ((node.dir.mode & Sys->DMDIR) == 0){
1738				if(i == 0)
1739					return rerror(t.tag, Enotadirectory);
1740				break;
1741			}
1742			if (t.names[i] == "..")
1743				node = node.parent;
1744			else if (t.names[i] != ".") {
1745				if (t.names[i] == ".flush.ftpfs") {
1746					node.invalidate();
1747					node.readdir();
1748					qids[i] = node.qid();
1749					continue;
1750				}
1751				node = node.extendpath(t.names[i]);
1752				if (node.parent.cached) {
1753					if (!node.valid) {
1754						if(i == 0)
1755							return rerror(t.tag, Enosuchfile);
1756						break;
1757					}
1758					if ((node.dir.mode & CHSYML) != 0)
1759						node.fixsymbolic();
1760				} else if (!node.valid) {
1761					if (node.changedir() == 0){
1762						node.dir.qid.qtype = Sys->QTDIR;
1763						node.dir.mode |= Sys->DMDIR;
1764					}else{
1765						node.dir.qid.qtype = Sys->QTFILE;
1766						node.dir.mode &= ~Sys->DMDIR;
1767					}
1768				}
1769				qids[i] = node.qid();
1770			}
1771		}
1772		if(i < len t.names){
1773			sendreply(ref Rmsg.Walk(t.tag, qids[0:i]));
1774			return;
1775		}
1776	}
1777	if(t.newfid != t.fid){
1778		n := getfid(t.newfid);
1779		if(n.busy)
1780			return rerror(t.tag, "fid in use");
1781		n.busy = 1;
1782		n.node = node;
1783	}else
1784		f.node = node;
1785	sendreply(ref Rmsg.Walk(t.tag, qids));
1786}
1787
1788openT(t: ref Tmsg.Open)
1789{
1790	f := getfid(t.fid);
1791	if ((f.node.dir.mode & Sys->DMDIR) != 0 && t.mode != Sys->OREAD) {
1792		rerror(t.tag, Epermission);
1793		return;
1794	}
1795	if ((t.mode & Sys->OTRUNC) != 0) {
1796		f.node.uncache();
1797		f.node.parent.uncache();
1798		f.node.filedirty();
1799	} else if (!f.node.cached) {
1800		f.node.filefree();
1801		if ((f.node.dir.mode & Sys->DMDIR) != 0) {
1802			f.node.invalidate();
1803			if (f.node.readdir() < 0) {
1804				rerror(t.tag, Enosuchfile);
1805				return;
1806			}
1807		}
1808		else {
1809			if (f.node.readfile() < 0) {
1810				rerror(t.tag, errstr);
1811				return;
1812			}
1813		}
1814		f.node.markcached();
1815	}
1816	sendreply(ref Rmsg.Open(t.tag, f.node.qid(), Styx->MAXFDATA));
1817}
1818
1819createT(t: ref Tmsg.Create)
1820{
1821	f := getfid(t.fid);
1822	if ((f.node.dir.mode & Sys->DMDIR) == 0) {
1823		rerror(t.tag, Enotadirectory);
1824		return;
1825	}
1826	f.node = f.node.extendpath(t.name);
1827	f.node.uncache();
1828	if ((t.perm & Sys->DMDIR) != 0) {
1829		if (f.node.createdir() < 0) {
1830			rerror(t.tag, Epermission);
1831			return;
1832		}
1833	}
1834	else
1835		f.node.filedirty();
1836	f.node.parent.invalidate();
1837	f.node.parent.uncache();
1838	sendreply(ref Rmsg.Create(t.tag, f.node.qid(), Styx->MAXFDATA));
1839}
1840
1841readT(t: ref Tmsg.Read)
1842{
1843	f := getfid(t.fid);
1844	count := t.count;
1845
1846	if (count < 0)
1847		return rerror(t.tag, Ebadlength);
1848	if (count > Styx->MAXFDATA)
1849		count = Styx->MAXFDATA;
1850	if (t.offset < big 0)
1851		return rerror(t.tag, Ebadoffset);
1852	rv := 0;
1853	if ((f.node.dir.mode & Sys->DMDIR) != 0) {
1854		offset := int t.offset;
1855		for (p := f.node.children; offset > 0 && p != nil; p = p.sibs)
1856			if (p.valid)
1857				offset -= len p.stat();
1858		for (; rv < count && p != nil; p = p.sibs) {
1859			if (p.valid) {
1860				if ((p.dir.mode & CHSYML) != 0)
1861					p.fixsymbolic();
1862				a := p.stat();
1863				size := len a;
1864				if(rv+size > count)
1865					break;
1866				tbuff[rv:] = a;
1867				rv += size;
1868			}
1869		}
1870	} else {
1871		if (!f.node.cached && f.node.readfile() < 0) {
1872			rerror(t.tag, errstr);
1873			return;
1874		}
1875		f.node.markcached();
1876		rv = f.node.fileread(tbuff, int t.offset, count);
1877		if (rv < 0) {
1878			rerror(t.tag, errstr);
1879			return;
1880		}
1881	}
1882	sendreply(ref Rmsg.Read(t.tag, tbuff[0:rv]));
1883}
1884
1885writeT(t: ref Tmsg.Write)
1886{
1887	f := getfid(t.fid);
1888	if ((f.node.dir.mode & Sys->DMDIR) != 0) {
1889		rerror(t.tag, Eisadirectory);
1890		return;
1891	}
1892	count := f.node.filewrite(t.data, int t.offset, len t.data);
1893	if (count < 0) {
1894		rerror(t.tag, errstr);
1895		return;
1896	}
1897	f.node.filedirty();
1898	sendreply(ref Rmsg.Write(t.tag, count));
1899}
1900
1901clunkT(t: ref Tmsg.Clunk)
1902{
1903	f := getfid(t.fid);
1904	if (f.node.fileisdirty()) {
1905		if (f.node.createfile() < 0)
1906			sys->print("ftpfs: could not create %s: %r\n", f.node.pathname());
1907		f.node.fileclean();
1908		f.node.uncache();
1909	}
1910	f.busy = 0;
1911	sendreply(ref Rmsg.Clunk(t.tag));
1912}
1913
1914removeT(t: ref Tmsg.Remove)
1915{
1916	f := getfid(t.fid);
1917	if ((f.node.dir.mode & Sys->DMDIR) != 0) {
1918		if (f.node.removedir() < 0) {
1919			rerror(t.tag, errstr);
1920			return;
1921		}
1922	}
1923	else {
1924		if (f.node.removefile() < 0) {
1925			rerror(t.tag, errstr);
1926			return;
1927		}
1928	}
1929	f.node.parent.uncache();
1930	f.node.uncache();
1931	f.node.valid = 0;
1932	f.busy = 0;
1933	sendreply(ref Rmsg.Remove(t.tag));
1934}
1935
1936statT(t: ref Tmsg.Stat)
1937{
1938	f := getfid(t.fid);
1939	n := f.node.parent;
1940	if (!n.cached) {
1941		n.invalidate();
1942		n.readdir();
1943		n.markcached();
1944	}
1945	if (!f.node.valid) {
1946		rerror(t.tag, Enosuchfile);
1947		return;
1948	}
1949	sendreply(ref Rmsg.Stat(t.tag, f.node.dir));
1950}
1951
1952wstatT(t: ref Tmsg.Wstat)
1953{
1954	rerror(t.tag, Enowstat);
1955}
1956