summaryrefslogtreecommitdiff
path: root/appl/cmd/ftpfs.b
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
commit37da2899f40661e3e9631e497da8dc59b971cbd0 (patch)
treecbc6d4680e347d906f5fa7fca73214418741df72 /appl/cmd/ftpfs.b
parent54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff)
20060303a
Diffstat (limited to 'appl/cmd/ftpfs.b')
-rw-r--r--appl/cmd/ftpfs.b1959
1 files changed, 1959 insertions, 0 deletions
diff --git a/appl/cmd/ftpfs.b b/appl/cmd/ftpfs.b
new file mode 100644
index 00000000..010fe9ee
--- /dev/null
+++ b/appl/cmd/ftpfs.b
@@ -0,0 +1,1959 @@
+implement Ftpfs;
+
+include "sys.m";
+ sys: Sys;
+ FD, Connection, Dir: import Sys;
+
+include "draw.m";
+
+include "arg.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "daytime.m";
+ time: Daytime;
+ Tm: import time;
+
+include "string.m";
+ str: String;
+
+include "styx.m";
+ styx: Styx;
+ Tmsg, Rmsg: import styx;
+
+include "factotum.m";
+
+Ftpfs: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+#
+# File system node. Refers to parent and file structure.
+# Siblings are linked. The head is parent.children.
+#
+
+Node: adt
+{
+ dir: Dir;
+ uniq: int;
+ parent: cyclic ref Node;
+ sibs: cyclic ref Node;
+ children: cyclic ref Node;
+ file: cyclic ref File;
+ depth: int;
+ remname: string;
+ cached: int;
+ valid: int;
+
+ extendpath: fn(parent: self ref Node, elem: string): ref Node;
+ fixsymbolic: fn(n: self ref Node);
+ invalidate: fn(n: self ref Node);
+ markcached: fn(n: self ref Node);
+ uncache: fn(n: self ref Node);
+ uncachedir: fn(parent: self ref Node, child: ref Node);
+
+ stat: fn(n: self ref Node): array of byte;
+ qid: fn(n: self ref Node): Sys->Qid;
+
+ fileget: fn(n: self ref Node): ref File;
+ filefree: fn(n: self ref Node);
+ fileclean: fn(n: self ref Node);
+ fileisdirty: fn(n: self ref Node): int;
+ filedirty: fn(n: self ref Node);
+ fileread: fn(n: self ref Node, b: array of byte, off, c: int): int;
+ filewrite: fn(n: self ref Node, b: array of byte, off, c: int): int;
+
+ action: fn(n: self ref Node, cmd: string): int;
+ createdir: fn(n: self ref Node): int;
+ createfile: fn(n: self ref Node): int;
+ changedir: fn(n: self ref Node): int;
+ docreate: fn(n: self ref Node): int;
+ pathname: fn(n: self ref Node): string;
+ readdir: fn(n: self ref Node): int;
+ readfile: fn(n: self ref Node): int;
+ removedir: fn(n: self ref Node): int;
+ removefile: fn(n: self ref Node): int;
+};
+
+#
+# Styx protocol file identifier.
+#
+
+Fid: adt
+{
+ fid: int;
+ node: ref Node;
+ busy: int;
+};
+
+#
+# Foreign file with cache.
+#
+
+File: adt
+{
+ cache: array of byte;
+ length: int;
+ offset: int;
+ fd: ref FD;
+ inuse, dirty: int;
+ atime: int;
+ node: cyclic ref Node;
+ tempname: string;
+
+ createtmp: fn(f: self ref File): ref FD;
+};
+
+ftp: Connection;
+dfid: ref FD;
+dfidiob: ref Iobuf;
+buffresidue: int = 0;
+tbuff: array of byte;
+rbuff: array of byte;
+ccfd: ref FD;
+stdin, stderr: ref FD;
+
+fids: list of ref Fid;
+
+BSZ: con 8192;
+Chunk: con 1024;
+Nfiles: con 128;
+
+CHSYML: con 16r40000000;
+
+mountpoint: string = "/n/ftp";
+user: string = nil;
+password: string;
+hostname: string = "kremvax";
+anon: string = "anon";
+
+firewall: string = "tcp!$proxy!402";
+myname: string = "anon";
+myhost: string = "lucent.com";
+proxyid: string;
+proxyhost: string;
+
+errstr: string;
+net: string;
+port: int;
+
+Enosuchfile: con "file does not exist";
+Eftpproto: con "ftp protocol error";
+Eshutdown: con "remote shutdown";
+Eioerror: con "io error";
+Enotadirectory: con "not a directory";
+Eisadirectory: con "is a directory";
+Epermission: con "permission denied";
+Ebadoffset: con "bad offset";
+Ebadlength: con "bad length";
+Enowstat: con "wstat not implemented";
+Emesgmismatch: con "message size mismatch";
+
+remdir: ref Node;
+remroot: ref Node;
+remrootpath: string;
+
+heartbeatpid: int;
+
+#
+# FTP protocol codes are 3 digits >= 100.
+# The code type is obtained by dividing by 100.
+#
+
+Syserr: con -2;
+Syntax: con -1;
+Shutdown: con 0;
+Extra: con 1;
+Success: con 2;
+Incomplete: con 3;
+TempFail: con 4;
+PermFail: con 5;
+Impossible: con 6;
+Err: con 7;
+
+debug: int = 0;
+quiet: int = 0;
+active: int = 0;
+cdtoroot: int = 0;
+
+proxy: int = 0;
+
+mountfd: ref FD;
+styxfd: ref FD;
+
+#
+# Set up FDs for service.
+#
+
+connect(): string
+{
+ pip := array[2] of ref Sys->FD;
+ if(sys->pipe(pip) < 0)
+ return sys->sprint("can't create pipe: %r");
+ mountfd = pip[0];
+ styxfd = pip[1];
+ return nil;
+}
+
+#shut(s: string)
+#{
+# sys->print("ftpfs: %s shutdown\n", s);
+#}
+
+#
+# Mount server. Must be spawned because it does
+# an attach transaction.
+#
+
+mount(mountpoint: string)
+{
+ if (sys->mount(mountfd, nil, mountpoint, sys->MREPL | sys->MCREATE, nil) < 0) {
+ sys->print("mount %s failed: %r\n", mountpoint);
+ shutdown();
+ }
+ mountfd = nil;
+}
+
+#
+# Keep the link alive.
+#
+
+beatquanta: con 10;
+beatlimit: con 10;
+beatcount: int;
+activity: int;
+transfer: int;
+
+heartbeat(pidc: chan of int)
+{
+ pid := sys->pctl(0, nil);
+ pidc <-= pid;
+ for (;;) {
+ sys->sleep(beatquanta * 1000);
+ if (activity || transfer) {
+ beatcount = 0;
+ activity = 0;
+ continue;
+ }
+ beatcount++;
+ if (beatcount == beatlimit) {
+ acquire();
+ if (sendrequest("NOOP", 0) == Success)
+ getreply(0);
+ release();
+ beatcount = 0;
+ activity = 0;
+ }
+ }
+}
+
+#
+# Control lock.
+#
+
+ctllock: chan of int;
+
+acquire()
+{
+ ctllock <-= 1;
+}
+
+release()
+{
+ <-ctllock;
+}
+
+#
+# Data formatting routines.
+#
+
+sendreply(r: ref Rmsg)
+{
+ if (debug)
+ sys->print("> %s\n", r.text());
+ a := r.pack();
+ if(styx->write(styxfd, a, len a) != len a)
+ sys->print("ftpfs: error replying: %r\n");
+}
+
+rerror(tag: int, s: string)
+{
+ if (debug)
+ sys->print("error: %s\n", s);
+ sendreply(ref Rmsg.Error(tag, s));
+}
+
+seterr(e: int, s: string): int
+{
+ case e {
+ Syserr =>
+ errstr = Eioerror;
+ Syntax =>
+ errstr = Eftpproto;
+ Shutdown =>
+ errstr = Eshutdown;
+ * =>
+ errstr = s;
+ }
+ return -1;
+}
+
+#
+# Node routines.
+#
+
+anode: Node;
+npath: int = 1;
+
+newnode(parent: ref Node, name: string): ref Node
+{
+ n := ref anode;
+ n.dir.name = name;
+ n.dir.atime = time->now();
+ n.children = nil;
+ n.remname = name;
+ if (parent != nil) {
+ n.parent = parent;
+ n.sibs = parent.children;
+ parent.children = n;
+ n.depth = parent.depth + 1;
+ n.valid = 0;
+ } else {
+ n.parent = n;
+ n.sibs = nil;
+ n.depth = 0;
+ n.valid = 1;
+ n.dir.uid = anon;
+ n.dir.gid = anon;
+ n.dir.mtime = n.dir.atime;
+ }
+ n.file = nil;
+ n.uniq = npath++;
+ n.cached = 0;
+ return n;
+}
+
+Node.extendpath(parent: self ref Node, elem: string): ref Node
+{
+ n: ref Node;
+
+ for (n = parent.children; n != nil; n = n.sibs)
+ if (n.dir.name == elem)
+ return n;
+ return newnode(parent, elem);
+}
+
+Node.markcached(n: self ref Node)
+{
+ n.cached = 1;
+ n.dir.atime = time->now();
+}
+
+Node.uncache(n: self ref Node)
+{
+ if (n.fileisdirty())
+ n.createfile();
+ n.filefree();
+ n.cached = 0;
+}
+
+Node.uncachedir(parent: self ref Node, child: ref Node)
+{
+ sp: ref Node;
+
+ if (parent == nil || parent == child)
+ return;
+ for (sp = parent.children; sp != nil; sp = sp.sibs)
+ if (sp != child && sp.file != nil && !sp.file.dirty && sp.file.fd != nil) {
+ sp.filefree();
+ sp.cached = 0;
+ }
+}
+
+Node.invalidate(node: self ref Node)
+{
+ n: ref Node;
+
+ node.uncachedir(nil);
+ for (n = node.children; n != nil; n = n.sibs) {
+ n.cached = 0;
+ n.invalidate();
+ n.valid = 0;
+ }
+}
+
+Node.fixsymbolic(n: self ref Node)
+{
+ if (n.changedir() == 0) {
+ n.dir.mode |= Sys->DMDIR;
+ n.dir.qid.qtype = Sys->QTDIR;
+ } else
+ n.dir.qid.qtype = Sys->QTFILE;
+ n.dir.mode &= ~CHSYML;
+}
+
+Node.stat(n: self ref Node): array of byte
+{
+ return styx->packdir(n.dir);
+}
+
+Node.qid(n: self ref Node): Sys->Qid
+{
+ if(n.dir.mode & Sys->DMDIR)
+ return Sys->Qid(big n.uniq, 0, Sys->QTDIR);
+ return Sys->Qid(big n.uniq, 0, Sys->QTFILE);
+}
+
+#
+# File routines.
+#
+
+ntmp: int;
+files: list of ref File;
+nfiles: int;
+afile: File;
+atime: int;
+
+#
+# Allocate a file structure for a node. If too many
+# are already allocated discard the oldest.
+#
+
+Node.fileget(n: self ref Node): ref File
+{
+ f, o: ref File;
+ l: list of ref File;
+
+ if (n.file != nil)
+ return n.file;
+ o = nil;
+ for (l = files; l != nil; l = tl l) {
+ f = hd l;
+ if (f.inuse == 0)
+ break;
+ if (!f.dirty && (o == nil || o.atime > f.atime))
+ o = f;
+ }
+ if (l == nil) {
+ if (nfiles == Nfiles && o != nil) {
+ o.node.uncache();
+ f = o;
+ }
+ else {
+ f = ref afile;
+ files = f :: files;
+ nfiles++;
+ }
+ }
+ n.file = f;
+ f.node = n;
+ f.atime = atime++;
+ f.inuse = 1;
+ f.dirty = 0;
+ f.length = 0;
+ f.fd = nil;
+ return f;
+}
+
+#
+# Create a temporary file for a local copy of a file.
+# If too many are open uncache parent.
+#
+
+File.createtmp(f: self ref File): ref FD
+{
+ t := "/tmp/ftp." + string time->now() + "." + string ntmp;
+ if (ntmp >= 16)
+ f.node.parent.uncachedir(f.node);
+ f.fd = sys->create(t, Sys->ORDWR | Sys->ORCLOSE, 8r600);
+ f.tempname = t;
+ f.offset = 0;
+ ntmp++;
+ return f.fd;
+}
+
+#
+# Read 'c' bytes at offset 'off' from a file into buffer 'b'.
+#
+
+Node.fileread(n: self ref Node, b: array of byte, off, c: int): int
+{
+ f: ref File;
+ t, i: int;
+
+ f = n.file;
+ if (off + c > f.length)
+ c = f.length - off;
+ for (t = 0; t < c; t += i) {
+ if (off >= f.length)
+ return t;
+ if (off < Chunk) {
+ i = c;
+ if (off + i > Chunk)
+ i = Chunk - off;
+ b[t:] = f.cache[off: off + i];
+ }
+ else {
+ if (f.offset != off) {
+ if (sys->seek(f.fd, big off, Sys->SEEKSTART) < big 0) {
+ f.offset = -1;
+ return seterr(Err, sys->sprint("seek temp failed: %r"));
+ }
+ }
+ if (t == 0)
+ i = sys->read(f.fd, b, c - t);
+ else
+ i = sys->read(f.fd, rbuff, c - t);
+ if (i < 0) {
+ f.offset = -1;
+ return seterr(Err, sys->sprint("read temp failed: %r"));
+ }
+ if (i == 0)
+ break;
+ if (t > 0)
+ b[t:] = rbuff[0: i];
+ f.offset = off + i;
+ }
+ off += i;
+ }
+ return t;
+}
+
+#
+# Write 'c' bytes at offset 'off' to a file from buffer 'b'.
+#
+
+Node.filewrite(n: self ref Node, b: array of byte, off, c: int): int
+{
+ f: ref File;
+ t, i: int;
+
+ f = n.fileget();
+ if (f.cache == nil)
+ f.cache = array[Chunk] of byte;
+ for (t = 0; t < c; t += i) {
+ if (off < Chunk) {
+ i = c;
+ if (off + i > Chunk)
+ i = Chunk - off;
+ f.cache[off:] = b[t: t + i];
+ }
+ else {
+ if (f.fd == nil) {
+ if (f.createtmp() == nil)
+ return seterr(Err, sys->sprint("temp file: %r"));
+ if (sys->write(f.fd, f.cache, Chunk) != Chunk) {
+ f.offset = -1;
+ return seterr(Err, sys->sprint("write temp failed: %r"));
+ }
+ f.offset = Chunk;
+ f.length = Chunk;
+ }
+ if (f.offset != off) {
+ if (off > f.length) {
+ # extend the file with zeroes
+ # sparse files may not be supported
+ }
+ if (sys->seek(f.fd, big off, Sys->SEEKSTART) < big 0) {
+ f.offset = -1;
+ return seterr(Err, sys->sprint("seek temp failed: %r"));
+ }
+ }
+ i = sys->write(f.fd, b[t:len b], c - t);
+ if (i != c - t) {
+ f.offset = -1;
+ return seterr(Err, sys->sprint("write temp failed: %r"));
+ }
+ }
+ off += i;
+ f.offset = off;
+ }
+ if (off > f.length)
+ f.length = off;
+ return t;
+}
+
+Node.filefree(n: self ref Node)
+{
+ f: ref File;
+
+ f = n.file;
+ if (f == nil)
+ return;
+ if (f.fd != nil) {
+ ntmp--;
+ f.fd = nil;
+ f.tempname = nil;
+ }
+ f.cache = nil;
+ f.length = 0;
+ f.inuse = 0;
+ f.dirty = 0;
+ n.file = nil;
+}
+
+Node.fileclean(n: self ref Node)
+{
+ if (n.file != nil)
+ n.file.dirty = 0;
+}
+
+Node.fileisdirty(n: self ref Node): int
+{
+ return n.file != nil && n.file.dirty;
+}
+
+Node.filedirty(n: self ref Node)
+{
+ f: ref File;
+
+ f = n.fileget();
+ f.dirty = 1;
+}
+
+#
+# Fid management.
+#
+
+afid: Fid;
+
+getfid(fid: int): ref Fid
+{
+ l: list of ref Fid;
+ f, ff: ref Fid;
+
+ ff = nil;
+ for (l = fids; l != nil; l = tl l) {
+ f = hd l;
+ if (f.fid == fid) {
+ if (f.busy)
+ return f;
+ else {
+ ff = f;
+ break;
+ }
+ } else if (ff == nil && !f.busy)
+ ff = f;
+ }
+ if (ff == nil) {
+ ff = ref afid;
+ fids = ff :: fids;
+ }
+ ff.node = nil;
+ ff.fid = fid;
+ return ff;
+}
+
+#
+# FTP protocol.
+#
+
+fail(s: int, l: string)
+{
+ case s {
+ Syserr =>
+ sys->print("read fail: %r\n");
+ Syntax =>
+ sys->print("%s\n", Eftpproto);
+ Shutdown =>
+ sys->print("%s\n", Eshutdown);
+ * =>
+ sys->print("unexpected response: %s\n", l);
+ }
+ exit;
+}
+
+getfullreply(echo: int): (int, int, string)
+{
+ reply := "";
+ s: string;
+ code := -1;
+ do{
+ s = dfidiob.gets('\n');
+ if(s == nil)
+ return (Shutdown, 0, nil);
+ if(len s >= 2 && s[len s-1] == '\n'){
+ if (s[len s - 2] == '\r')
+ s = s[0: len s - 2];
+ else
+ s = s[0: len s - 1];
+ }
+ if (debug || echo)
+ sys->print("%s\n", s);
+ reply = reply+s;
+ if(code < 0){
+ if(len s < 3)
+ return (Syntax, 0, nil);
+ code = int s[0:3];
+ if(s[3] != '-')
+ break;
+ }
+ }while(len s < 4 || int s[0:3] != code || s[3] != ' ');
+
+ if(code < 100)
+ return (Syntax, 0, nil);
+ return (code / 100, code, reply);
+}
+
+getreply(echo: int): (int, string)
+{
+ (c, code, s) := getfullreply(echo);
+ return (c, s);
+}
+
+sendrequest2(req: string, echo: int, figleaf: string): int
+{
+ activity = 1;
+ if (debug || echo) {
+ if (figleaf == nil)
+ figleaf = req;
+ sys->print("%s\n", figleaf);
+ }
+ b := array of byte (req + "\r\n");
+ n := sys->write(dfid, b, len b);
+ if (n < 0)
+ return Syserr;
+ if (n != len b)
+ return Shutdown;
+ return Success;
+}
+
+sendrequest(req: string, echo: int): int
+{
+ return sendrequest2(req, echo, req);
+}
+
+sendfail(s: int)
+{
+ case s {
+ Syserr =>
+ sys->print("write fail: %r\n");
+ Shutdown =>
+ sys->print("%s\n", Eshutdown);
+ * =>
+ sys->print("internal error\n");
+ }
+ exit;
+}
+
+dataport(l: list of string): string
+{
+ s := "tcp!" + hd l;
+ l = tl l;
+ s = s + "." + hd l;
+ l = tl l;
+ s = s + "." + hd l;
+ l = tl l;
+ s = s + "." + hd l;
+ l = tl l;
+ return s + "!" + string ((int hd l * 256) + (int hd tl l));
+}
+
+commas(l: list of string): string
+{
+ s := hd l;
+ l = tl l;
+ while (l != nil) {
+ s = s + "," + hd l;
+ l = tl l;
+ }
+ return s;
+}
+
+third(cmd: string): ref FD
+{
+ acquire();
+ for (;;) {
+ (n, data) := sys->dial(firewall, nil);
+ if (n < 0) {
+ if (debug)
+ sys->print("dial %s failed: %r\n", firewall);
+ break;
+ }
+ t := sys->sprint("\n%s!*\n\n%s\n%s\n1\n-1\n-1\n", proxyhost, myhost, myname);
+ b := array of byte t;
+ n = sys->write(data.dfd, b, len b);
+ if (n < 0) {
+ if (debug)
+ sys->print("firewall write failed: %r\n");
+ break;
+ }
+ b = array[256] of byte;
+ n = sys->read(data.dfd, b, len b);
+ if (n < 0) {
+ if (debug)
+ sys->print("firewall read failed: %r\n");
+ break;
+ }
+ (c, k) := sys->tokenize(string b[:n], "\n");
+ if (c < 2) {
+ if (debug)
+ sys->print("bad response from firewall\n");
+ break;
+ }
+ if (hd k != "0") {
+ if (debug)
+ sys->print("firewall connect: %s\n", hd tl k);
+ break;
+ }
+ p := hd tl k;
+ if (debug)
+ sys->print("portid %s\n", p);
+ (c, k) = sys->tokenize(p, "!");
+ if (c < 3) {
+ if (debug)
+ sys->print("bad portid from firewall\n");
+ break;
+ }
+ n = int hd tl tl k;
+ (c, k) = sys->tokenize(hd tl k, ".");
+ if (c != 4) {
+ if (debug)
+ sys->print("bad portid ip address\n");
+ break;
+ }
+ t = sys->sprint("PORT %s,%d,%d", commas(k), n / 256, n & 255);
+ r := sendrequest(t, 0);
+ if (r != Success)
+ break;
+ (r, nil) = getreply(0);
+ if (r != Success)
+ break;
+ r = sendrequest(cmd, 0);
+ if (r != Success)
+ break;
+ (r, nil) = getreply(0);
+ if (r != Extra)
+ break;
+ n = sys->read(data.dfd, b, len b);
+ if (n < 0) {
+ if (debug)
+ sys->print("firewall read failed: %r\n");
+ break;
+ }
+ b = array of byte "0\n?\n";
+ n = sys->write(data.dfd, b, len b);
+ if (n < 0) {
+ if (debug)
+ sys->print("firewall write failed: %r\n");
+ break;
+ }
+ release();
+ return data.dfd;
+ }
+ release();
+ return nil;
+}
+
+passive(cmd: string): ref FD
+{
+ acquire();
+ if (sendrequest("PASV", 0) != Success) {
+ release();
+ return nil;
+ }
+ (r, m) := getreply(0);
+ release();
+ if (r != Success)
+ return nil;
+ (nil, p) := str->splitl(m, "(");
+ if (p == nil)
+ str->splitl(m, "0-9");
+ else
+ p = p[1:len p];
+ (c, l) := sys->tokenize(p, ",");
+ if (c < 6) {
+ sys->print("data: %s\n", m);
+ return nil;
+ }
+ a := dataport(l);
+ if (debug)
+ sys->print("data dial %s\n", a);
+ (s, d) := sys->dial(a, nil);
+ if (s < 0)
+ return nil;
+ acquire();
+ r = sendrequest(cmd, 0);
+ if (r != Success) {
+ release();
+ return nil;
+ }
+ (r, m) = getreply(0);
+ release();
+ if (r != Extra)
+ return nil;
+ return d.dfd;
+}
+
+getnet(dir: string): (string, int)
+{
+ buf := array[50] of byte;
+ n := dir + "/local";
+ lfd := sys->open(n, Sys->OREAD);
+ if (lfd == nil) {
+ if (debug)
+ sys->fprint(stderr, "open %s: %r\n", n);
+ return (nil, 0);
+ }
+ length := sys->read(lfd, buf, len buf);
+ if (length < 0) {
+ if (debug)
+ sys->fprint(stderr, "read%s: %r\n", n);
+ return (nil, 0);
+ }
+ (r, l) := sys->tokenize(string buf[0:length], "!");
+ if (r != 2) {
+ if (debug)
+ sys->fprint(stderr, "tokenize(%s) returned (%d)\n", string buf[0:length], r);
+ return (nil, 0);
+ }
+ if (debug)
+ sys->print("net is %s!%d\n", hd l, int hd tl l);
+ return (hd l, int hd tl l);
+}
+
+activate(cmd: string): ref FD
+{
+ r: int;
+
+ listenport, dataport: Connection;
+ m: string;
+
+ (r, listenport) = sys->announce("tcp!" + net + "!0");
+ if (r < 0)
+ return nil;
+ (x1, x2) := getnet(listenport.dir);
+ (x3, x4) := sys->tokenize(x1, ".");
+ t := sys->sprint("PORT %s,%d,%d", commas(x4), int x2 / 256, int x2&255);
+ acquire();
+ r = sendrequest(t, 0);
+ if (r != Success) {
+ release();
+ return nil;
+ }
+ (r, m) = getreply(0);
+ if (r != Success) {
+ release();
+ return nil;
+ }
+ r = sendrequest(cmd, 0);
+ if (r != Success) {
+ release();
+ return nil;
+ }
+ (r, m) = getreply(0);
+ release();
+ if (r != Extra)
+ return nil;
+ (r, dataport) = sys->listen(listenport);
+ if (r < 0) {
+ sys->fprint(stderr, "activate: listen failed: %r\n");
+ return nil;
+ }
+ fd := sys->open(dataport.dir + "/data", sys->ORDWR);
+ if (debug)
+ sys->print("activate: data connection on %s\n", dataport.dir);
+ if (fd == nil) {
+ sys->fprint(stderr, "activate: open of %s failed: %r\n", dataport.dir);
+ return nil;
+ }
+ return fd;
+}
+
+data(cmd: string): ref FD
+{
+ if (proxy)
+ return third(cmd);
+ else if (active)
+ return activate(cmd);
+ else
+ return passive(cmd);
+}
+
+#
+# File list cracking routines.
+#
+
+fields(l: list of string, n: int): array of string
+{
+ a := array[n] of string;
+ for (i := 0; i < n; i++) {
+ a[i] = hd l;
+ l = tl l;
+ }
+ return a;
+}
+
+now: ref Tm;
+months: con "janfebmaraprmayjunjulaugsepoctnovdec";
+
+cracktime(month, day, year, hms: string): int
+{
+ tm: Tm;
+
+ if (now == nil)
+ now = time->local(time->now());
+ tm = *now;
+ if (month[0] >= '0' && month[0] <= '9') {
+ tm.mon = int month - 1;
+ if (tm.mon < 0 || tm.mon > 11)
+ tm.mon = 5;
+ }
+ else if (len month >= 3) {
+ month = str->tolower(month[0:3]);
+ for (i := 0; i < 36; i += 3)
+ if (month == months[i:i+3]) {
+ tm.mon = i / 3;
+ break;
+ }
+ }
+ tm.mday = int day;
+ if (hms != nil) {
+ (h, z) := str->splitl(hms, "apAP");
+ (a, b) := str->splitl(h, ":");
+ tm.hour = int a;
+ if (b != nil) {
+ (c, d) := str->splitl(b[1:len b], ":");
+ tm.min = int c;
+ if (d != nil)
+ tm.sec = int d[1:len d];
+ }
+ if (z != nil && str->tolower(z)[0] == 'p')
+ tm.hour += 12;
+ }
+ if (year != nil) {
+ tm.year = int year;
+ if (tm.year >= 1900)
+ tm.year -= 1900;
+ }
+ else {
+ if (tm.mon > now.mon || (tm.mon == now.mon && tm.mday > now.mday+1))
+ tm.year--;
+ }
+ return time->tm2epoch(ref tm);
+}
+
+crackmode(p: string): int
+{
+ flags := 0;
+ case len p {
+ 10 => # unix and new style plan 9
+ case p[0] {
+ 'l' =>
+ return CHSYML | 0777;
+ 'd' =>
+ flags = Sys->DMDIR;
+ }
+ p = p[1:10];
+ 11 => # old style plan 9
+ if (p[0] == 'l')
+ flags = Sys->DMDIR;
+ p = p[2:11];
+ * =>
+ return Sys->DMDIR | 0777;
+ }
+ mode := 0;
+ n := 0;
+ for (i := 0; i < 3; i++) {
+ mode <<= 3;
+ if (p[n] == 'r')
+ mode |= 4;
+ if (p[n+1] == 'w')
+ mode |= 2;
+ case p[n+2] {
+ 'x' or 's' or 'S' =>
+ mode |= 1;
+ }
+ n += 3;
+ }
+ return mode | flags;
+}
+
+crackdir(p: string): (string, Dir)
+{
+ d: Dir;
+ ln, a: string;
+
+ (n, l) := sys->tokenize(p, " \t\r\n");
+ f := fields(l, n);
+ if (n > 2 && f[n - 2] == "->")
+ n -= 2;
+ case n {
+ 8 => # ls -l
+ ln = f[7];
+ d.uid = f[2];
+ d.gid = f[2];
+ d.mode = crackmode(f[0]);
+ d.length = big f[3];
+ (a, nil) = str->splitl(f[6], ":");
+ if (len a != len f[6])
+ d.atime = cracktime(f[4], f[5], nil, f[6]);
+ else
+ d.atime = cracktime(f[4], f[5], f[6], nil);
+ 9 => # ls -lg
+ ln = f[8];
+ d.uid = f[2];
+ d.gid = f[3];
+ d.mode = crackmode(f[0]);
+ d.length = big f[4];
+ (a, nil) = str->splitl(f[7], ":");
+ if (len a != len f[7])
+ d.atime = cracktime(f[5], f[6], nil, f[7]);
+ else
+ d.atime = cracktime(f[5], f[6], f[7], nil);
+ 10 => # plan 9
+ ln = f[9];
+ d.uid = f[3];
+ d.gid = f[4];
+ d.mode = crackmode(f[0]);
+ d.length = big f[5];
+ (a, nil) = str->splitl(f[8], ":");
+ if (len a != len f[8])
+ d.atime = cracktime(f[6], f[7], nil, f[8]);
+ else
+ d.atime = cracktime(f[6], f[7], f[8], nil);
+ 4 => # NT
+ ln = f[3];
+ d.uid = anon;
+ d.gid = anon;
+ if (f[2] == "<DIR>") {
+ d.length = big 0;
+ d.mode = Sys->DMDIR | 8r777;
+ }
+ else {
+ d.mode = 8r666;
+ d.length = big f[2];
+ }
+ (n, l) = sys->tokenize(f[0], "/-");
+ if (n == 3)
+ d.atime = cracktime(hd l, hd tl l, f[2], f[1]);
+ 1 => # ls
+ ln = f[0];
+ d.uid = anon;
+ d.gid = anon;
+ d.mode = 0777;
+ d.atime = 0;
+ * =>
+ return (nil, d);
+ }
+ if (ln == "." || ln == "..")
+ return (nil, d);
+ d.mtime = d.atime;
+ d.name = ln;
+ return (ln, d);
+}
+
+longls := 1;
+
+Node.readdir(n: self ref Node): int
+{
+ f: ref FD;
+ p: ref Node;
+
+ if (n.changedir() < 0)
+ return -1;
+ transfer = 1;
+ for (;;) {
+ if (longls) {
+ f = data("LIST -la");
+ if (f == nil) {
+ longls = 0;
+ continue;
+ }
+ }
+ else {
+ f = data("LIST");
+ if (f == nil) {
+ transfer = 0;
+ return seterr(Err, Enosuchfile);
+ }
+ }
+ break;
+ }
+ b := bufio->fopen(f, sys->OREAD);
+ if (b == nil) {
+ transfer = 0;
+ return seterr(Err, Eioerror);
+ }
+ while ((s := b.gets('\n')) != nil) {
+ if (debug)
+ sys->print("%s", s);
+ (l, d) := crackdir(s);
+ if (l == nil)
+ continue;
+ p = n.extendpath(l);
+ p.dir = d;
+ p.valid = 1;
+ }
+ b = nil;
+ f = nil;
+ (r, nil) := getreply(0);
+ transfer = 0;
+ if (r != Success)
+ return seterr(Err, Enosuchfile);
+ return 0;
+}
+
+Node.readfile(n: self ref Node): int
+{
+ c: int;
+
+ if (n.parent.changedir() < 0)
+ return -1;
+ transfer = 1;
+ f := data("RETR " + n.remname);
+ if (f == nil) {
+ transfer = 0;
+ return seterr(Err, Enosuchfile);
+ }
+ off := 0;
+ while ((c = sys->read(f, tbuff, BSZ)) > 0) {
+ if (n.filewrite(tbuff, off, c) != c) {
+ off = -1;
+ break;
+ }
+ off += c;
+ }
+ if (c < 0) {
+ transfer = 0;
+ return seterr(Err, Eioerror);
+ }
+ f = nil;
+ if(off == 0)
+ n.filewrite(tbuff, off, 0);
+ (s, nil) := getreply(0);
+ transfer = 0;
+ if (s != Success)
+ return seterr(s, Enosuchfile);
+ return off;
+}
+
+path(a, b: string): string
+{
+ if (a == nil)
+ return b;
+ if (b == nil)
+ return a;
+ if (a[len a - 1] == '/')
+ return a + b;
+ else
+ return a + "/" + b;
+}
+
+Node.pathname(n: self ref Node): string
+{
+ s: string;
+
+ while (n != n.parent) {
+ s = path(n.remname, s);
+ n = n.parent;
+ }
+ return path(remrootpath, s);
+}
+
+Node.changedir(n: self ref Node): int
+{
+ t: ref Node;
+ d: string;
+
+ t = n;
+ if (t == remdir)
+ return 0;
+ if (n.depth == 0)
+ d = remrootpath;
+ else
+ d = n.pathname();
+ remdir.uncachedir(nil);
+ acquire();
+ r := sendrequest("CWD " + d, 0);
+ if (r == Success)
+ (r, nil) = getreply(0);
+ release();
+ case r {
+ Success
+# or Incomplete
+ =>
+ remdir = n;
+ return 0;
+ * =>
+ return seterr(r, Enosuchfile);
+ }
+}
+
+Node.docreate(n: self ref Node): int
+{
+ f: ref FD;
+
+ transfer = 1;
+ f = data("STOR " + n.remname);
+ if (f == nil) {
+ transfer = 0;
+ return -1;
+ }
+ off := 0;
+ for (;;) {
+ r := n.fileread(tbuff, off, BSZ);
+ if (r <= 0)
+ break;
+ if (sys->write(f, tbuff, r) < 0) {
+ off = -1;
+ break;
+ }
+ off += r;
+ }
+ transfer = 0;
+ return off;
+}
+
+Node.createfile(n: self ref Node): int
+{
+ if (n.parent.changedir() < 0)
+ return -1;
+ off := n.docreate();
+ if (off < 0)
+ return -1;
+ (r, nil) := getreply(0);
+ if (r != Success)
+ return -1;
+ return off;
+}
+
+Node.action(n: self ref Node, cmd: string): int
+{
+ if (n.parent.changedir() < 0)
+ return -1;
+ acquire();
+ r := sendrequest(cmd + " " + n.dir.name, 0);
+ if (r == Success)
+ (r, nil) = getreply(0);
+ release();
+ if (r != Success)
+ return -1;
+ return 0;
+}
+
+Node.createdir(n: self ref Node): int
+{
+ return n.action("MKD");
+}
+
+Node.removefile(n: self ref Node): int
+{
+ return n.action("DELE");
+}
+
+Node.removedir(n: self ref Node): int
+{
+ return n.action("RMD");
+}
+
+pwd(s: string): string
+{
+ (nil, s) = str->splitl(s, "\"");
+ if (s == nil || len s < 2)
+ return "/";
+ (s, nil) = str->splitl(s[1:len s], "\"");
+ return s;
+}
+
+#
+# User info for firewall.
+#
+getuser()
+{
+ b := array[Sys->NAMEMAX] of byte;
+ f := sys->open("/dev/user", Sys->OREAD);
+ if (f != nil) {
+ n := sys->read(f, b, len b);
+ if (n > 0)
+ myname = string b[:n];
+ else if (n == 0)
+ sys->print("warning: empty /dev/user\n");
+ else
+ sys->print("warning: could not read /dev/user: %r\n");
+ } else
+ sys->print("warning: could not open /dev/user: %r\n");
+ f = sys->open("/dev/sysname", Sys->OREAD);
+ if (f != nil) {
+ n := sys->read(f, b, len b);
+ if (n > 0)
+ myhost = string b[:n];
+ else if (n == 0)
+ sys->print("warning: empty /dev/sysname\n");
+ else
+ sys->print("warning: could not read /dev/sysname: %r\n");
+ } else
+ sys->print("warning: could not open /dev/sysname: %r\n");
+ if (debug)
+ sys->print("proxy %s for %s@%s\n", firewall, myname, myhost);
+}
+
+server()
+{
+ while((t := Tmsg.read(styxfd, 0)) != nil){
+ if (debug)
+ sys->print("< %s\n", t.text());
+ pick x := t {
+ Readerror =>
+ sys->print("ftpfs: read error on mount point: %s\n", x.error);
+ kill(heartbeatpid);
+ exit;
+ Version =>
+ versionT(x);
+ Auth =>
+ authT(x);
+ Attach =>
+ attachT(x);
+ Clunk =>
+ clunkT(x);
+ Create =>
+ createT(x);
+ Flush =>
+ flushT(x);
+ Open =>
+ openT(x);
+ Read =>
+ readT(x);
+ Remove =>
+ removeT(x);
+ Stat =>
+ statT(x);
+ Walk =>
+ walkT(x);
+ Write =>
+ writeT(x);
+ Wstat =>
+ wstatT(x);
+ * =>
+ rerror(t.tag, "unimp");
+ }
+ }
+ if (debug)
+ sys->print("ftpfs: server: exiting\n");
+ kill(heartbeatpid);
+}
+
+raw(on: int)
+{
+ if(ccfd == nil) {
+ ccfd = sys->open("/dev/consctl", Sys->OWRITE);
+ if(ccfd == nil) {
+ sys->fprint(stderr, "ftpfs: cannot open /dev/consctl: %r\n");
+ return;
+ }
+ }
+ if(on)
+ sys->fprint(ccfd, "rawon");
+ else
+ sys->fprint(ccfd, "rawoff");
+}
+
+prompt(p: string, def: string, echo: int): string
+{
+ if (def == nil)
+ sys->print("%s: ", p);
+ else
+ sys->print("%s[%s]: ", p, def);
+ if (!echo)
+ raw(1);
+ b := bufio->fopen(stdin, Sys->OREAD);
+ s := b.gets(int '\n');
+ if (!echo) {
+ raw(0);
+ sys->print("\n");
+ }
+ if(s != nil)
+ s = s[0:len s - 1];
+ if (s == "")
+ return def;
+ return s;
+}
+
+#
+# Entry point. Load modules and initiate protocol.
+#
+
+nomod(s: string)
+{
+ sys->fprint(sys->fildes(2), "ftpfs: can't load %s: %r\n", s);
+ raise "fail:load";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ l: string;
+ rv: int;
+ code: int;
+
+ if (sys == nil)
+ sys = load Sys Sys->PATH;
+ stdin = sys->fildes(0);
+ stderr = sys->fildes(2);
+
+ time = load Daytime Daytime->PATH;
+ if (time == nil)
+ nomod(Daytime->PATH);
+ str = load String String->PATH;
+ if (str == nil)
+ nomod(String->PATH);
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil)
+ nomod(Bufio->PATH);
+ styx = load Styx Styx->PATH;
+ if (styx == nil)
+ nomod(Styx->PATH);
+ styx->init();
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ nomod(Arg->PATH);
+
+ # parse arguments
+ # [-/dpq] [-m mountpoint] [-a password] host
+ arg->init(args);
+ arg->setusage("ftpfs [-/dpq] [-m mountpoint] [-a password] ftphost");
+ keyspec := "";
+ while((op := arg->opt()) != 0)
+ case op {
+ 'd' =>
+ debug++;
+ '/' =>
+ cdtoroot = 1;
+ 'p' =>
+ active = 1;
+ 'q' =>
+ quiet = 1;
+ 'm' =>
+ mountpoint = arg->earg();
+ 'a' =>
+ password = arg->earg();
+ user = "anonymous";
+ 'k' =>
+ keyspec = arg->earg();
+ * =>
+ arg->usage();
+ }
+ argv := arg->argv();
+ if (len argv != 1)
+ arg->usage();
+ arg = nil;
+ hostname = hd argv;
+
+ if (len hostname > 6 && hostname[:6] == "proxy!") {
+ hostname = hostname[6:];
+ proxy = 1;
+ }
+
+ if (proxy) {
+ if (!quiet)
+ sys->print("dial firewall service %s\n", firewall);
+ (rv, ftp) = sys->dial(firewall, nil);
+ if (rv < 0) {
+ sys->print("dial %s failed: %r\n", firewall);
+ exit;
+ }
+ dfid = ftp.dfd;
+ getuser();
+ t := sys->sprint("\ntcp!%s!tcp.21\n\n%s\n%s\n0\n-1\n-1\n", hostname, myhost, myname);
+ if (debug)
+ sys->print("request%s\n", t);
+ b := array of byte t;
+ rv = sys->write(dfid, b, len b);
+ if (rv < 0) {
+ sys->print("firewall write failed: %r\n");
+ exit;
+ }
+ b = array[256] of byte;
+ rv = sys->read(dfid, b, len b);
+ if (rv < 0) {
+ sys->print("firewall read failed: %r\n");
+ return;
+ }
+ (c, k) := sys->tokenize(string b[:rv], "\n");
+ if (c < 2) {
+ sys->print("bad response from firewall\n");
+ exit;
+ }
+ if (hd k != "0") {
+ sys->print("firewall connect: %s\n", hd tl k);
+ exit;
+ }
+ proxyid = hd tl k;
+ if (debug)
+ sys->print("proxyid %s\n", proxyid);
+ (c, k) = sys->tokenize(proxyid, "!");
+ if (c < 3) {
+ sys->print("bad proxyid from firewall\n");
+ exit;
+ }
+ proxyhost = (hd k) + "!" + (hd tl k);
+ if (debug)
+ sys->print("proxyhost %s\n", proxyhost);
+ } else {
+ d := "tcp!" + hostname + "!ftp";
+ (rv, ftp) = sys->dial(d, nil);
+ if (debug)
+ sys->print("localdir %s\n", ftp.dir);
+ if (rv < 0) {
+ sys->print("dial %s failed: %r\n", d);
+ exit;
+ }
+ dfid = ftp.dfd;
+ }
+ dfidiob = bufio->fopen(dfid, sys->OREAD);
+ (net, port) = getnet(ftp.dir);
+ tbuff = array[BSZ] of byte;
+ rbuff = array[BSZ] of byte;
+ (rv, l) = getreply(!quiet);
+ if (rv != Success)
+ fail(rv, l);
+ if (user == nil) {
+ getuser();
+ user = myname;
+ user = prompt("User", user, 1);
+ }
+ rv = sendrequest("USER " + user, 0);
+ if (rv != Success)
+ sendfail(rv);
+ (rv, code, l) = getfullreply(!quiet);
+ if (rv != Success) {
+ if (rv != Incomplete)
+ fail(rv, l);
+ if (code == 331) {
+ if(password == nil){
+ factotum := load Factotum Factotum->PATH;
+ if(factotum != nil){
+ factotum->init();
+ if(user != nil && keyspec == nil)
+ keyspec = sys->sprint("user=%q", user);
+ (nil, password) = factotum->getuserpasswd(sys->sprint("proto=pass server=%s service=ftp %s", hostname, keyspec));
+ }
+ if(password == nil)
+ password = prompt("Password", nil, 0);
+ }
+ rv = sendrequest2("PASS " + password, 0, "PASS XXXX");
+ if (rv != Success)
+ sendfail(rv);
+ (rv, l) = getreply(0);
+ if (rv != Success)
+ fail(rv, l);
+ }
+ }
+ if (cdtoroot) {
+ rv = sendrequest("CWD /", 0);
+ if (rv != Success)
+ sendfail(rv);
+ (rv, l) = getreply(0);
+ if (rv != Success)
+ fail(rv, l);
+ }
+ rv = sendrequest("TYPE I", 0);
+ if (rv != Success)
+ sendfail(rv);
+ (rv, l) = getreply(0);
+ if (rv != Success)
+ fail(rv, l);
+ rv = sendrequest("PWD", 0);
+ if (rv != Success)
+ sendfail(rv);
+ (rv, l) = getreply(0);
+ if (rv != Success)
+ fail(rv, l);
+ remrootpath = pwd(l);
+ remroot = newnode(nil, "/");
+ remroot.dir.mode = Sys->DMDIR | 8r777;
+ remroot.dir.qid.qtype = Sys->QTDIR;
+ remdir = remroot;
+ l = connect();
+ if (l != nil) {
+ sys->print("%s\n", l);
+ exit;
+ }
+ ctllock = chan[1] of int;
+ spawn mount(mountpoint);
+ pidc := chan of int;
+ spawn heartbeat(pidc);
+ heartbeatpid = <-pidc;
+ if (debug)
+ sys->print("heartbeatpid %d\n", heartbeatpid);
+ spawn server(); # dies when receive on chan fails
+}
+
+kill(pid: int): int
+{
+ if (debug)
+ sys->print("killing %d\n", pid);
+ fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE);
+ if (fd == nil) {
+ sys->print("kill: open failed\n");
+ return -1;
+ }
+ if (sys->write(fd, array of byte "kill", 4) != 4) {
+ sys->print("kill: write failed\n");
+ return -1;
+ }
+ return 0;
+}
+
+shutdown()
+{
+ mountfd = nil;
+}
+
+#
+# Styx transactions.
+#
+
+versionT(t: ref Tmsg.Version)
+{
+ (msize, version) := styx->compatible(t, Styx->MAXRPC, Styx->VERSION);
+ sendreply(ref Rmsg.Version(t.tag, msize, version));
+}
+
+authT(t: ref Tmsg.Auth)
+{
+ sendreply(ref Rmsg.Error(t.tag, "authentication not required"));
+}
+
+flushT(t: ref Tmsg.Flush)
+{
+ sendreply(ref Rmsg.Flush(t.tag));
+}
+
+attachT(t: ref Tmsg.Attach)
+{
+ f := getfid(t.fid);
+ f.busy = 1;
+ f.node = remroot;
+ sendreply(ref Rmsg.Attach(t.tag, remroot.qid()));
+}
+
+walkT(t: ref Tmsg.Walk)
+{
+ f := getfid(t.fid);
+ qids: array of Sys->Qid;
+ node := f.node;
+ if(len t.names > 0){
+ qids = array[len t.names] of Sys->Qid;
+ for(i := 0; i < len t.names; i++) {
+ if ((node.dir.mode & Sys->DMDIR) == 0){
+ if(i == 0)
+ return rerror(t.tag, Enotadirectory);
+ break;
+ }
+ if (t.names[i] == "..")
+ node = node.parent;
+ else if (t.names[i] != ".") {
+ if (t.names[i] == ".flush.ftpfs") {
+ node.invalidate();
+ node.readdir();
+ qids[i] = node.qid();
+ continue;
+ }
+ node = node.extendpath(t.names[i]);
+ if (node.parent.cached) {
+ if (!node.valid) {
+ if(i == 0)
+ return rerror(t.tag, Enosuchfile);
+ break;
+ }
+ if ((node.dir.mode & CHSYML) != 0)
+ node.fixsymbolic();
+ } else if (!node.valid) {
+ if (node.changedir() == 0){
+ node.dir.qid.qtype = Sys->QTDIR;
+ node.dir.mode |= Sys->DMDIR;
+ }else{
+ node.dir.qid.qtype = Sys->QTFILE;
+ node.dir.mode &= ~Sys->DMDIR;
+ }
+ }
+ qids[i] = node.qid();
+ }
+ }
+ if(i < len t.names){
+ sendreply(ref Rmsg.Walk(t.tag, qids[0:i]));
+ return;
+ }
+ }
+ if(t.newfid != t.fid){
+ n := getfid(t.newfid);
+ if(n.busy)
+ return rerror(t.tag, "fid in use");
+ n.busy = 1;
+ n.node = node;
+ }else
+ f.node = node;
+ sendreply(ref Rmsg.Walk(t.tag, qids));
+}
+
+openT(t: ref Tmsg.Open)
+{
+ f := getfid(t.fid);
+ if ((f.node.dir.mode & Sys->DMDIR) != 0 && t.mode != Sys->OREAD) {
+ rerror(t.tag, Epermission);
+ return;
+ }
+ if ((t.mode & Sys->OTRUNC) != 0) {
+ f.node.uncache();
+ f.node.parent.uncache();
+ f.node.filedirty();
+ } else if (!f.node.cached) {
+ f.node.filefree();
+ if ((f.node.dir.mode & Sys->DMDIR) != 0) {
+ f.node.invalidate();
+ if (f.node.readdir() < 0) {
+ rerror(t.tag, Enosuchfile);
+ return;
+ }
+ }
+ else {
+ if (f.node.readfile() < 0) {
+ rerror(t.tag, errstr);
+ return;
+ }
+ }
+ f.node.markcached();
+ }
+ sendreply(ref Rmsg.Open(t.tag, f.node.qid(), Styx->MAXFDATA));
+}
+
+createT(t: ref Tmsg.Create)
+{
+ f := getfid(t.fid);
+ if ((f.node.dir.mode & Sys->DMDIR) == 0) {
+ rerror(t.tag, Enotadirectory);
+ return;
+ }
+ f.node = f.node.extendpath(t.name);
+ f.node.uncache();
+ if ((t.perm & Sys->DMDIR) != 0) {
+ if (f.node.createdir() < 0) {
+ rerror(t.tag, Epermission);
+ return;
+ }
+ }
+ else
+ f.node.filedirty();
+ f.node.parent.invalidate();
+ f.node.parent.uncache();
+ sendreply(ref Rmsg.Create(t.tag, f.node.qid(), Styx->MAXFDATA));
+}
+
+readT(t: ref Tmsg.Read)
+{
+ f := getfid(t.fid);
+ count := t.count;
+
+ if (count < 0)
+ return rerror(t.tag, Ebadlength);
+ if (count > Styx->MAXFDATA)
+ count = Styx->MAXFDATA;
+ if (t.offset < big 0)
+ return rerror(t.tag, Ebadoffset);
+ rv := 0;
+ if ((f.node.dir.mode & Sys->DMDIR) != 0) {
+ offset := int t.offset;
+ for (p := f.node.children; offset > 0 && p != nil; p = p.sibs)
+ if (p.valid)
+ offset -= len p.stat();
+ for (; rv < count && p != nil; p = p.sibs) {
+ if (p.valid) {
+ if ((p.dir.mode & CHSYML) != 0)
+ p.fixsymbolic();
+ a := p.stat();
+ size := len a;
+ if(rv+size > count)
+ break;
+ tbuff[rv:] = a;
+ rv += size;
+ }
+ }
+ } else {
+ if (!f.node.cached && f.node.readfile() < 0) {
+ rerror(t.tag, errstr);
+ return;
+ }
+ f.node.markcached();
+ rv = f.node.fileread(tbuff, int t.offset, count);
+ if (rv < 0) {
+ rerror(t.tag, errstr);
+ return;
+ }
+ }
+ sendreply(ref Rmsg.Read(t.tag, tbuff[0:rv]));
+}
+
+writeT(t: ref Tmsg.Write)
+{
+ f := getfid(t.fid);
+ if ((f.node.dir.mode & Sys->DMDIR) != 0) {
+ rerror(t.tag, Eisadirectory);
+ return;
+ }
+ count := f.node.filewrite(t.data, int t.offset, len t.data);
+ if (count < 0) {
+ rerror(t.tag, errstr);
+ return;
+ }
+ f.node.filedirty();
+ sendreply(ref Rmsg.Write(t.tag, count));
+}
+
+clunkT(t: ref Tmsg.Clunk)
+{
+ f := getfid(t.fid);
+ if (f.node.fileisdirty()) {
+ if (f.node.createfile() < 0)
+ sys->print("ftpfs: could not create %s\n", f.node.pathname());
+ f.node.fileclean();
+ f.node.uncache();
+ }
+ f.busy = 0;
+ sendreply(ref Rmsg.Clunk(t.tag));
+}
+
+removeT(t: ref Tmsg.Remove)
+{
+ f := getfid(t.fid);
+ if ((f.node.dir.mode & Sys->DMDIR) != 0) {
+ if (f.node.removedir() < 0) {
+ rerror(t.tag, errstr);
+ return;
+ }
+ }
+ else {
+ if (f.node.removefile() < 0) {
+ rerror(t.tag, errstr);
+ return;
+ }
+ }
+ f.node.parent.uncache();
+ f.node.uncache();
+ f.node.valid = 0;
+ f.busy = 0;
+ sendreply(ref Rmsg.Remove(t.tag));
+}
+
+statT(t: ref Tmsg.Stat)
+{
+ f := getfid(t.fid);
+ n := f.node.parent;
+ if (!n.cached) {
+ n.invalidate();
+ n.readdir();
+ n.markcached();
+ }
+ if (!f.node.valid) {
+ rerror(t.tag, Enosuchfile);
+ return;
+ }
+ sendreply(ref Rmsg.Stat(t.tag, f.node.dir));
+}
+
+wstatT(t: ref Tmsg.Wstat)
+{
+ rerror(t.tag, Enowstat);
+}