ref: d916a4c3823f55227ffae35738c2497256e307b5
dir: /appl/cmd/ftpfs.b/
implement Ftpfs; include "sys.m"; sys: Sys; FD, 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 "dial.m"; dial: Dial; Connection: import dial; 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: ref 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; } error(s: string) { sys->fprint(sys->fildes(2), "ftpfs: %s\n", s); raise "fail:"+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->fprint(sys->fildes(2), "ftpfs: 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(sys->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, nil, 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 (;;) { data := dial->dial(firewall, nil); if(data == nil) { 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); d := dial->dial(a, nil); if(d == nil) 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: ref Connection; m: string; listenport = dial->announce("tcp!" + net + "!0"); if(listenport == nil) return nil; (x1, x2) := getnet(listenport.dir); (nil, 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; dataport = dial->listen(listenport); if(dataport == nil) { 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; sys = load Sys Sys->PATH; dial = load Dial Dial->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); ftp = dial->dial(firewall, nil); if(ftp == nil) { 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 := dial->netmkaddr(hostname, "tcp", "ftp"); ftp = dial->dial(d, nil); if(ftp == nil) error(sys->sprint("dial %s failed: %r", d)); if(debug) sys->print("localdir %s\n", ftp.dir); 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) { if (debug) sys->print("killing %d\n", pid); fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); if(fd != nil) sys->fprint(fd, "kill"); } 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: %r\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); }