diff options
Diffstat (limited to 'appl/alphabet/fs')
32 files changed, 4095 insertions, 0 deletions
diff --git a/appl/alphabet/fs/and.b b/appl/alphabet/fs/and.b new file mode 100644 index 00000000..da180978 --- /dev/null +++ b/appl/alphabet/fs/and.b @@ -0,0 +1,70 @@ +implement And,Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; + Report: import Reports; +include "alphabet/fs.m"; + fs: Fs; + Value: import fs; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fs; + +And: module {}; + +types(): string +{ + return "pppp*"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fs->init(); +} + +run(nil: ref Draw->Context, nil: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + c := chan of Gatequery; + spawn andgate(c, args); + return ref Value.Vp(c); +} + +andgate(c: Gatechan, args: list of ref Value) +{ + sub: list of Gatechan; + for(; args != nil; args = tl args) + sub = (hd args).p().i :: sub; + sub = rev(sub); + myreply := chan of int; + while(((d, reply) := <-c).t0.t0 != nil){ + for(l := sub; l != nil; l = tl l){ + (hd l) <-= (d, myreply); + if(<-myreply == 0) + break; + } + reply <-= l == nil; + } + for(; sub != nil; sub = tl sub) + hd sub <-= (Nilentry, nil); +} + +rev[T](x: list of T): list of T +{ + l: list of T; + for(; x != nil; x = tl x) + l = hd x :: l; + return l; +} diff --git a/appl/alphabet/fs/bundle.b b/appl/alphabet/fs/bundle.b new file mode 100644 index 00000000..2c692f1a --- /dev/null +++ b/appl/alphabet/fs/bundle.b @@ -0,0 +1,210 @@ +implement Bundle, Fsmodule; +include "sys.m"; + sys: Sys; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "readdir.m"; + readdir: Readdir; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; + reports: Reports; + Report, quit, report: import reports; +include "alphabet/fs.m"; + fs: Fs; + Value: import fs; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fs; + +Bundle: module {}; + +# XXX if we can't open a directory, is it ever worth passing its metadata +# through anyway? + +EOF: con "end of archive\n"; + +types(): string +{ + return "fx"; +} +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: bundle: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + readdir = load Readdir Readdir->PATH; + if(readdir == nil) + badmod(Readdir->PATH); + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + badmod(Readdir->PATH); + bufio->fopen(nil, Sys->OREAD); # XXX no bufio->init! + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Readdir->PATH); + fs->init(); + reports = load Reports Reports->PATH; + if(reports == nil) + badmod(Reports->PATH); +} + +run(nil: ref Draw->Context, r: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + f := chan of ref Sys->FD; + spawn bundleproc((hd args).x().i, f, r.start("bundle")); + return ref Value.Vf(f); +} + +#bundle(r: ref Report, iob: ref Iobuf, c: Fschan): chan of string +bundle(nil: ref Report, nil: ref Iobuf, nil: Fschan): chan of string +{ + return nil; +# sync := chan[1] of string; +# spawn bundleproc(c, sync, iob, r.start("bundle")); +# return sync; +} + +bundleproc(c: Fschan, f: chan of ref Sys->FD, errorc: chan of string) +{ + f <-= nil; + if((fd := <-f) == nil){ + (<-c).t1 <-= Quit; + quit(errorc); + } + iob := bufio->fopen(fd, Sys->OWRITE); + fd = nil; + (d, reply) := <-c; + if(d.dir == nil){ + report(errorc, "no root directory"); + endarchive(iob, errorc); + } + if(puts(iob, dir2header(d.dir), errorc) == -1){ + reply <-= Quit; + quit(errorc); + } + reply <-= Down; + bundledir(d.dir.name, d, c, iob, errorc); + endarchive(iob, errorc); +} + +endarchive(iob: ref Iobuf, errorc: chan of string) +{ + { + if(puts(iob, EOF, errorc) != -1) + iob.flush(); + sys->fprint(iob.fd, ""); + } exception { + "write on closed pipe" => + ; + } + quit(errorc); +} + +bundledir(path: string, d: Fsdata, + c: Fschan, + iob: ref Iobuf, errorc: chan of string) +{ + if(d.dir.mode & Sys->DMDIR){ + path[len path] = '/'; + for(;;){ + (ent, reply) := <-c; + if(ent.dir == nil){ + reply <-= Skip; + break; + } + if(puts(iob, dir2header(ent.dir), errorc) == -1){ + reply <-= Quit; + quit(errorc); + } + reply <-= Down; + bundledir(path + ent.dir.name, ent, c, iob, errorc); + } + iob.putc('\n'); + }else{ + buf: array of byte; + reply: chan of int; + length := big d.dir.length; + n := big 0; + for(;;){ + ((nil, buf), reply) = <-c; + if(buf == nil){ + reply <-= Skip; + break; + } + if(write(iob, buf, len buf, errorc) != len buf){ + reply <-= Quit; + quit(errorc); + } + n += big len buf; + if(n > length){ # should never happen + report(errorc, sys->sprint("%q is longer than expected (fatal)", path)); + reply <-= Quit; + quit(errorc); + } + if(n == length){ + reply <-= Skip; + break; + } + reply <-= Next; + } + if(n < length){ + report(errorc, sys->sprint("%q is shorter than expected (%bd/%bd); adding null bytes", path, n, length)); + buf = array[Sys->ATOMICIO] of {* => byte 0}; + while(n < length){ + nb := len buf; + if(length - n < big len buf) + nb = int (length - n); + if(write(iob, buf, nb, errorc) != nb){ + (<-c).t1 <-= Quit; + quit(errorc); + } + report(errorc, sys->sprint("added %d null bytes", nb)); + n += big nb; + } + } + } +} + +dir2header(d: ref Sys->Dir): string +{ + return sys->sprint("%q %uo %q %q %ud %bd\n", d.name, d.mode, d.uid, d.gid, d.mtime, d.length); +} + +puts(iob: ref Iobuf, s: string, errorc: chan of string): int +{ + { + if(iob.puts(s) == -1) + report(errorc, sys->sprint("write error: %r")); + return 0; + } exception { + "write on closed pipe" => + report(errorc, sys->sprint("write on closed pipe")); + return -1; + } +} + +write(iob: ref Iobuf, buf: array of byte, n: int, errorc: chan of string): int +{ + { + nw := iob.write(buf, n); + if(nw < n){ + if(nw >= 0) + report(errorc, "short write"); + else{ + report(errorc, sys->sprint("write error: %r")); + } + } + return nw; + } exception { + "write on closed pipe" => + report(errorc, "write on closed pipe"); + return -1; + } +} diff --git a/appl/alphabet/fs/bundle.m b/appl/alphabet/fs/bundle.m new file mode 100644 index 00000000..905b8288 --- /dev/null +++ b/appl/alphabet/fs/bundle.m @@ -0,0 +1,9 @@ +Bundle: module { + PATH: con "/dis/fs/bundle.dis"; + + types: fn(): string; + init: fn(); + run: fn(nil: ref Draw->Context, report: ref Reports->Report, + nil: list of Fs->Option, args: list of ref Fs->Value): ref Fs->Value; + bundle: fn(r: ref Reports->Report, iob: ref Bufio->Iobuf, c: Fs->Fschan): chan of string; +}; diff --git a/appl/alphabet/fs/chstat.b b/appl/alphabet/fs/chstat.b new file mode 100644 index 00000000..b53ca784 --- /dev/null +++ b/appl/alphabet/fs/chstat.b @@ -0,0 +1,189 @@ +implement Chstat, Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; +include "alphabet/fs.m"; + fsfilter: Fsfilter; + fs: Fs; + Value: import fs; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fs; + +Chstat: module {}; + +Query: adt { + gate: Gatechan; + stat: Sys->Dir; + mask: int; + cflag: int; + reply: chan of int; + + query: fn(q: self ref Query, d: ref Sys->Dir, name: string, depth: int): int; +}; + +types(): string +{ + return "xx-pp-ms-us-gs-ts-as-c"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + + fsfilter = load Fsfilter Fsfilter->PATH; + if(fsfilter == nil) + badmod(Fsfilter->PATH); +} + +run(nil: ref Draw->Context, nil: ref Reports->Report, + opts: list of Option, args: list of ref Value): ref Value +{ + ws := Sys->nulldir; + mask := 0; + gate: ref Value; + cflag := 0; + for(; opts != nil; opts = tl opts){ + o := (hd opts).args; + case (hd opts).opt { + 'p' => + gate.free(0); + gate = hd o; + 'm' => + ok: int; + m := (hd o).s().i; + (ok, mask, ws.mode) = parsemode(m); + mask &= ~Sys->DMDIR; + if(ok == 0){ + sys->fprint(sys->fildes(2), "fs: chstat: bad mode %#q\n", m); + gate.free(0); + return nil; + } + 'u' => + ws.uid = (hd o).s().i; + 'g' => + ws.gid = (hd o).s().i; + 't' => + ws.mtime = int (hd o).s().i; + 'a' => + ws.atime = int (hd o).s().i; + 'c' => + cflag++; + } + } + + dst := chan of (Fsdata, chan of int); + p: Gatechan; + if(gate != nil) + p = gate.p().i; + spawn chstatproc((hd args).x().i, dst, p, ws, mask, cflag); + return ref Value.Vx(dst); +} + +chstatproc(src, dst: Fschan, gate: Gatechan, stat: Sys->Dir, mask: int, cflag: int) +{ + fsfilter->filter(ref Query(gate, stat, mask, cflag, chan of int), src, dst); + if(gate != nil) + gate <-= ((nil, nil, 0), nil); +} + +Query.query(q: self ref Query, d: ref Sys->Dir, name: string, depth: int): int +{ + c := 1; + if(q.gate != nil){ + q.gate <-= ((d, name, depth), q.reply); + c = <-q.reply; + } + if(c){ + if(q.cflag){ + m := d.mode & 8r700; + d.mode = (d.mode & ~8r77)|(m>>3)|(m>>6); + } + stat := q.stat; + d.mode = (d.mode & ~q.mask) | (stat.mode & q.mask); + if(stat.uid != nil) + d.uid = stat.uid; + if(stat.gid != nil) + d.gid = stat.gid; + if(stat.mtime != ~0) + d.mtime = stat.mtime; + if(stat.atime != ~0) + d.atime = stat.atime; + } + return 1; +} + +# stolen from /appl/cmd/chmod.b +User: con 8r700; +Group: con 8r070; +Other: con 8r007; +All: con User | Group | Other; + +Read: con 8r444; +Write: con 8r222; +Exec: con 8r111; +parsemode(spec: string): (int, int, int) +{ + mask := Sys->DMAPPEND | Sys->DMEXCL | Sys->DMDIR | Sys->DMAUTH; +loop: + for(i := 0; i < len spec; i++){ + case spec[i] { + 'u' => + mask |= User; + 'g' => + mask |= Group; + 'o' => + mask |= Other; + 'a' => + mask |= All; + * => + break loop; + } + } + if(i == len spec) + return (0, 0, 0); + if(i == 0) + mask |= All; + + op := spec[i++]; + if(op != '+' && op != '-' && op != '=') + return (0, 0, 0); + + mode := 0; + for(; i < len spec; i++){ + case spec[i]{ + 'r' => + mode |= Read; + 'w' => + mode |= Write; + 'x' => + mode |= Exec; + 'a' => + mode |= Sys->DMAPPEND; + 'l' => + mode |= Sys->DMEXCL; + 'd' => + mode |= Sys->DMDIR; + 'A' => + mode |= Sys->DMAUTH; + * => + return (0, 0, 0); + } + } + if(op == '+' || op == '-') + mask &= mode; + if(op == '-') + mode = ~mode; + return (1, mask, mode); +} diff --git a/appl/alphabet/fs/compose.b b/appl/alphabet/fs/compose.b new file mode 100644 index 00000000..09bef812 --- /dev/null +++ b/appl/alphabet/fs/compose.b @@ -0,0 +1,105 @@ +implement Compose, Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; + Report: import Reports; +include "alphabet/fs.m"; + fs: Fs; + Value: import fs; + Cmpchan, + Nilentry, Option, + Next, Down, Skip, Quit: import Fs; + +Compose: module {}; + +AinB: con 1<<3; +BinA: con 1<<2; +AoutB: con 1<<1; +BoutA: con 1<<0; + +A: con AinB|AoutB; +AoverB: con AinB|AoutB|BoutA; +AatopB: con AinB|BoutA; +AxorB: con AoutB|BoutA; + +B: con BinA|BoutA; +BoverA: con BinA|BoutA|AoutB; +BatopA: con BinA|AoutB; +BxorA: con BoutA|AoutB; + +ops := array[] of { + AinB => "AinB", + BinA => "BinA", + AoutB => "AoutB", + BoutA => "BoutA", + A => "A", + AoverB => "AoverB", + AatopB => "AatopB", + AxorB => "AxorB", + B => "B", + BoverA => "BoverA", + BatopA => "BatopA", +}; + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +types(): string +{ + return "ms-d"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fs->init(); +} + +run(nil: ref Draw->Context, nil: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + c := chan of (ref Sys->Dir, ref Sys->Dir, chan of int); + s := (hd args).s().i; + for(i := 0; i < len ops; i++) + if(ops[i] == s) + break; + if(i == len ops){ + sys->fprint(sys->fildes(2), "fs: join: bad op %q\n", s); + return nil; + } + spawn compose(c, i, opts != nil); + return ref Value.Vm(c); +} + +compose(c: Cmpchan, op: int, dflag: int) +{ + t := array[4] of {* => 0}; + if(op & AinB) + t[2r11] = 2r01; + if(op & BinA) + t[2r11] = 2r10; + if(op & AoutB) + t[2r01] = 2r01; + if(op & BoutA) + t[2r10] = 2r10; + if(dflag){ + while(((d0, d1, reply) := <-c).t2 != nil){ + x := (d1 != nil) << 1 | d0 != nil; + r := t[d0 != nil | (d1 != nil) << 1]; + if(r == 0 && x == 2r11 && (d0.mode & d1.mode & Sys->DMDIR)) + r = 2r11; + reply <-= r; + } + }else{ + while(((d0, d1, reply) := <-c).t2 != nil) + reply <-= t[(d1 != nil) << 1 | d0 != nil]; + } +} diff --git a/appl/alphabet/fs/depth.b b/appl/alphabet/fs/depth.b new file mode 100644 index 00000000..9787fd54 --- /dev/null +++ b/appl/alphabet/fs/depth.b @@ -0,0 +1,54 @@ +implement Depth, Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; + Report: import Reports; +include "alphabet/fs.m"; + fs: Fs; + Value: import fs; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fs; + +Depth: module {}; + +types(): string +{ + return "ps"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fs->init(); +} + +run(nil: ref Draw->Context, nil: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + d := int (hd args).s().i; + if(d <= 0){ + sys->fprint(sys->fildes(2), "fs: depth: invalid depth\n"); + return nil; + } + c := chan of Gatequery; + spawn depthgate(c, d); + return ref Value.Vp(c); +} + +depthgate(c: Gatechan, d: int) +{ + while((((dir, nil, depth), reply) := <-c).t0.t0 != nil) + reply <-= depth <= d; +} diff --git a/appl/alphabet/fs/entries.b b/appl/alphabet/fs/entries.b new file mode 100644 index 00000000..6fbf78d0 --- /dev/null +++ b/appl/alphabet/fs/entries.b @@ -0,0 +1,91 @@ +implement Entries, Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; + Report: import Reports; +include "alphabet/fs.m"; + fs: Fs; + Value: import fs; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fs; + +Entries: module {}; + +types(): string +{ + return "tx"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fs->init(); +} + +run(nil: ref Draw->Context, nil: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + sc := Entrychan(chan of int, chan of Entry); + spawn entriesproc((hd args).x().i, sc); + return ref Value.Vt(sc); +} + +entriesproc(c: Fschan, sc: Entrychan) +{ + if(<-sc.sync == 0){ + (<-c).t1 <-= Quit; + exit; + } + indent := 0; + names: list of string; + name: string; +loop: + for(;;){ + (d, reply) := <-c; + if(d.dir != nil){ + p: string; + depth := indent; + if(d.dir.mode & Sys->DMDIR){ + names = name :: names; + if(indent == 0) + name = d.dir.name; + else{ + if(name[len name - 1] != '/') + name[len name] = '/'; + name += d.dir.name; + } + indent++; + reply <-= Down; + p = name; + }else{ + p = name; + if(p[len p - 1] != '/') + p[len p] = '/'; + p += d.dir.name; + reply <-= Next; + } + if(p != nil) + sc.c <-= (d.dir, p, depth); + }else{ + reply <-= Next; + if(d.dir == nil && d.data == nil){ + if(--indent == 0) + break loop; + (name, names) = (hd names, tl names); + } + } + } + sc.c <-= Nilentry; +} diff --git a/appl/alphabet/fs/exec.b b/appl/alphabet/fs/exec.b new file mode 100644 index 00000000..a7aa7460 --- /dev/null +++ b/appl/alphabet/fs/exec.b @@ -0,0 +1,172 @@ +implement Exec, Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; + sh: Sh; + Context: import sh; +include "alphabet/reports.m"; + reports: Reports; + Report: import reports; +include "alphabet/fs.m"; + fs: Fs; + Option, Value, Entrychan: import fs; + +Exec: module {}; + +# usage: exec [-n nfiles] [-t endcmd] [-pP] command entries +types(): string +{ + return "rtc-ns-tc-p-P"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: exec: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fs->init(); + reports = load Reports Reports->PATH; + if(reports == nil) + badmod(Reports->PATH); + sh = load Sh Sh->PATH; + if(sh == nil) + badmod(Sh->PATH); + sh->initialise(); +} + +run(drawctxt: ref Draw->Context, report: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + n := 1; + pflag := 0; + tcmd: ref Sh->Cmd; + for(; opts != nil; opts = tl opts){ + o := hd opts; + case o.opt { + 'n' => + if((n = int (hd o.args).s().i) <= 0){ + sys->fprint(sys->fildes(2), "fs: exec: invalid argument to -n\n"); + return nil; + } + 't' => + tcmd = (hd o.args).c().i; + 'p' => + pflag = 1; + 'P' => + pflag = 2; + } + } + if(pflag && n > 1){ + sys->fprint(sys->fildes(2), "fs: exec: cannot specify -p with -n %d\n", n); + return nil; + } + cmd := (hd tl args).c().i; + c := (hd args).t().i; + sync := chan of string; + spawn execproc(drawctxt, sync, n, pflag, c, cmd, tcmd, report.start("exec")); + sync <-= nil; + return ref Value.Vr(sync); +} + +execproc(drawctxt: ref Draw->Context, sync: chan of string, n, pflag: int, + c: Entrychan, cmd, tcmd: ref Sh->Cmd, errorc: chan of string) +{ + sys->pctl(Sys->NEWFD, 0::1::2::nil); + ctxt := Context.new(drawctxt); + <-sync; + if(<-sync != nil){ + c.sync <-= 0; + errorc <-= nil; + exit; + } + c.sync <-= 1; + argv := ref Sh->Listnode(cmd, nil) :: nil; + + fl: list of ref Sh->Listnode; + nf := 0; + while(((d, p, nil) := <-c.c).t0 != nil){ + fl = ref Sh->Listnode(nil, p) :: fl; + if(++nf >= n){ + ctxt.set("file", rev(fl)); + if(pflag) + setstatenv(ctxt, d, pflag); + fl = nil; + nf = 0; + {ctxt.run(argv, 0);} exception {"fail:*" =>;} + } + } + if(nf > 0){ + ctxt.set("file", rev(fl)); + {ctxt.run(argv, 0);} exception {"fail:*" =>;} + } + if(tcmd != nil){ + ctxt.set("file", nil); + {ctxt.run(ref Sh->Listnode(tcmd, nil) :: nil, 0);} exception {"fail:*" =>;} + } + errorc <-= nil; + sync <-= nil; # XXX should return result here... +} + +setenv(ctxt: ref Context, var: string, val: list of string) +{ + ctxt.set(var, sh->stringlist2list(val)); +} + +setstatenv(ctxt: ref Context, dir: ref Sys->Dir, pflag: int) +{ + setenv(ctxt, "mode", modes(dir.mode) :: nil); + setenv(ctxt, "uid", dir.uid :: nil); + setenv(ctxt, "mtime", string dir.mtime :: nil); + setenv(ctxt, "length", string dir.length :: nil); + + if(pflag > 1){ + setenv(ctxt, "name", dir.name :: nil); + setenv(ctxt, "gid", dir.gid :: nil); + setenv(ctxt, "muid", dir.muid :: nil); + setenv(ctxt, "qid", sys->sprint("16r%ubx", dir.qid.path) :: string dir.qid.vers :: nil); + setenv(ctxt, "atime", string dir.atime :: nil); + setenv(ctxt, "dtype", sys->sprint("%c", dir.dtype) :: nil); + setenv(ctxt, "dev", string dir.dev :: nil); + } +} + +mtab := array[] of { + "---", "--x", "-w-", "-wx", + "r--", "r-x", "rw-", "rwx" +}; + +modes(mode: int): string +{ + s: string; + + if(mode & Sys->DMDIR) + s = "d"; + else if(mode & Sys->DMAPPEND) + s = "a"; + else if(mode & Sys->DMAUTH) + s = "A"; + else + s = "-"; + if(mode & Sys->DMEXCL) + s += "l"; + else + s += "-"; + s += mtab[(mode>>6)&7]+mtab[(mode>>3)&7]+mtab[mode&7]; + return s; +} + +rev[T](x: list of T): list of T +{ + l: list of T; + for(; x != nil; x = tl x) + l = hd x :: l; + return l; +} diff --git a/appl/alphabet/fs/filter.b b/appl/alphabet/fs/filter.b new file mode 100644 index 00000000..af696e2f --- /dev/null +++ b/appl/alphabet/fs/filter.b @@ -0,0 +1,66 @@ +implement Filter, Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; +include "alphabet/fs.m"; + fsfilter: Fsfilter; + fs: Fs; + Value: import fs; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fs; + +Filter: module {}; + +Query: adt { + gate: Gatechan; + dflag: int; + reply: chan of int; + query: fn(q: self ref Query, d: ref Sys->Dir, name: string, depth: int): int; +}; + +types(): string +{ + return "xxp-d"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fsfilter = load Fsfilter Fsfilter->PATH; + if(fsfilter == nil) + badmod(Fsfilter->PATH); +} + +run(nil: ref Draw->Context, nil: ref Reports->Report, + opts: list of Option, args: list of ref Value): ref Value +{ + dst := chan of (Fsdata, chan of int); + spawn filterproc((hd args).x().i, dst, (hd tl args).p().i, opts != nil); + return ref Value.Vx(dst); +} + +filterproc(src, dst: Fschan, gate: Gatechan, dflag: int) +{ + fsfilter->filter(ref Query(gate, dflag, chan of int), src, dst); + gate <-= ((nil, nil, 0), nil); +} + +Query.query(q: self ref Query, d: ref Sys->Dir, name: string, depth: int): int +{ + if(depth == 0 || (q.dflag && (d.mode & Sys->DMDIR))) + return 1; + q.gate <-= ((d, name, depth), q.reply); + return <-q.reply; +} diff --git a/appl/alphabet/fs/ls.b b/appl/alphabet/fs/ls.b new file mode 100644 index 00000000..fdc2ddb0 --- /dev/null +++ b/appl/alphabet/fs/ls.b @@ -0,0 +1,107 @@ +implement Ls, Fsmodule; +include "sys.m"; + sys: Sys; +include "daytime.m"; + daytime: Daytime; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; + reports: Reports; + Report: import reports; +include "alphabet/fs.m"; + fs: Fs; + Option, Value, Entrychan: import fs; + +Ls: module {}; + +types(): string +{ + return "ft-u-m"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: ls: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + reports = load Reports Reports->PATH; + if(reports == nil) + badmod(Reports->PATH); + if(fs == nil) + badmod(Fs->PATH); + fs->init(); + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + badmod(Daytime->PATH); +} + +run(nil: ref Draw->Context, report: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + f := chan of ref Sys->FD; + spawn lsproc(f, opts, (hd args).t().i, report.start("/fs/ls")); + return ref Value.Vf(f); +} + +lsproc(f: chan of ref Sys->FD, opts: list of Option, c: Entrychan, errorc: chan of string) +{ + f <-= nil; + if((fd := <-f) == nil){ + c.sync <-= 0; + reports->quit(errorc); + } + now := daytime->now(); + mflag := uflag := 0; + c.sync <-= 1; + for(; opts != nil; opts = tl opts){ + case (hd opts).opt { + 'm' => + mflag = 1; + 'u' => + uflag = 1; + } + } + while(((dir, p, nil) := <-c.c).t0 != nil){ + t := dir.mtime; + if(uflag) + t = dir.atime; + s := sys->sprint("%s %c %d %s %s %bud %s %s\n", + modes(dir.mode), dir.dtype, dir.dev, + dir.uid, dir.gid, dir.length, + daytime->filet(now, dir.mtime), p); + if(mflag) + s = "[" + dir.muid + "] " + s; + sys->fprint(fd, "%s", s); + } + reports->quit(errorc); +} + +mtab := array[] of { + "---", "--x", "-w-", "-wx", + "r--", "r-x", "rw-", "rwx" +}; + +modes(mode: int): string +{ + s: string; + + if(mode & Sys->DMDIR) + s = "d"; + else if(mode & Sys->DMAPPEND) + s = "a"; + else if(mode & Sys->DMAUTH) + s = "A"; + else + s = "-"; + if(mode & Sys->DMEXCL) + s += "l"; + else + s += "-"; + s += mtab[(mode>>6)&7]+mtab[(mode>>3)&7]+mtab[mode&7]; + return s; +} diff --git a/appl/alphabet/fs/match.b b/appl/alphabet/fs/match.b new file mode 100644 index 00000000..3a82de49 --- /dev/null +++ b/appl/alphabet/fs/match.b @@ -0,0 +1,84 @@ +implement Match, Fsmodule; +include "sys.m"; + sys: Sys; +include "filepat.m"; + filepat: Filepat; +include "regex.m"; + regex: Regex; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; + Report: import Reports; +include "alphabet/fs.m"; + fs: Fs; + Value: import fs; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fs; + +Match: module {}; + +types(): string +{ + return "ps-a-r"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fs->init(); + regex = load Regex Regex->PATH; + if(regex == nil) + badmod(Regex->PATH); + filepat = load Filepat Filepat->PATH; + if(filepat == nil) + badmod(Filepat->PATH); +} + +run(nil: ref Draw->Context, nil: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + pat := (hd args).s().i; + aflag := rflag := 0; + for(; opts != nil; opts = tl opts){ + case (hd opts).opt { + 'a' => + aflag = 1; + 'r' => + rflag = 1; + } + } + v := ref Value.Vp(chan of Gatequery); + re: Regex->Re; + if(rflag){ + err: string; + (re, err) = regex->compile(pat, 0); + if(re == nil){ + sys->fprint(sys->fildes(2), "fs: match: regex error on %#q: %s\n", pat, err); + return nil; + } + } + spawn matchproc(v.i, aflag, pat, re); + return v; +} + +matchproc(c: Gatechan, all: int, pat: string, re: Regex->Re) +{ + while((((d, name, nil), reply) := <-c).t0.t0 != nil){ + if(all == 0) + name = d.name; + if(re != nil) + reply <-= regex->execute(re, name) != nil; # XXX should anchor it? + else + reply <-= filepat->match(pat, name); + } +} diff --git a/appl/alphabet/fs/merge.b b/appl/alphabet/fs/merge.b new file mode 100644 index 00000000..62524de5 --- /dev/null +++ b/appl/alphabet/fs/merge.b @@ -0,0 +1,192 @@ +implement Merge, Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; + Report: import Reports; +include "alphabet/fs.m"; + fs: Fs; + Value: import fs; + Fschan, Fsdata, Entrychan, Cmpchan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fs; + +Merge: module {}; + +# e.g.... +# fs select {mode -d} {merge -c {compose -d AoutB} {filter {not {path /chan /dev /usr/rog /n/local /net}} /} {merge {proto FreeBSD} {proto Hp} {proto Irix} {proto Linux} {proto MacOSX} {proto Nt} {proto Nt.ti} {proto Nt.ti925} {proto Plan9} {proto Plan9.ti} {proto Plan9.ti925} {proto Solaris} {proto authsrv} {proto dl} {proto dlsrc} {proto ep7} {proto inferno} {proto inferno.ti} {proto ipaqfs} {proto minitel} {proto os} {proto scheduler.client} {proto scheduler.server} {proto sds} {proto src} {proto src.ti} {proto sword} {proto ti925.ti} {proto ti925bin} {proto tipaq} {proto umec} {proto utils} {proto utils.ti}}} >[2] /dev/null + +types(): string +{ + return "xxxx*-1-cm"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil){ + sys->fprint(sys->fildes(2), "fs: cannot load %s: %r\n", Fs->PATH); + raise "fail:bad module"; + } + fs->init(); +} + +run(nil: ref Draw->Context, nil: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + recurse := 1; + cmp: Cmpchan; + for(; opts != nil; opts = tl opts){ + case (hd opts).opt { + '1' => + recurse = 0; + 'c' => + cmp = (hd (hd opts).args).m().i; + } + } + dst := chan of (Fsdata, chan of int); + spawn mergeproc((hd args).x().i, (hd tl args).x().i, dst, recurse, cmp, tl tl args == nil); + for(args = tl tl args; args != nil; args = tl args){ + dst1 := chan of (Fsdata, chan of int); + spawn mergeproc(dst, (hd args).x().i, dst1, recurse, cmp, tl args == nil); + dst = dst1; + } + return ref Value.Vx(dst); +} + +# merge two trees; assume directories are alphabetically sorted. +mergeproc(c0, c1, dst: Fschan, recurse: int, cmp: Cmpchan, killcmp: int) +{ + myreply := chan of int; + ((d0, nil), reply0) := <-c0; + ((d1, nil), reply1) := <-c1; + + if(compare(cmp, d0, d1) == 2r10) + dst <-= ((d1, nil), myreply); + else + dst <-= ((d0, nil), myreply); + r := <-myreply; + reply0 <-= r; + reply1 <-= r; + if(r == Down){ + { + mergedir(c0, c1, dst, recurse, cmp); + } exception {"exit" =>;} + } + if(cmp != nil && killcmp) + cmp <-= (nil, nil, nil); +} + +mergedir(c0, c1, dst: Fschan, recurse: int, cmp: Cmpchan) +{ + myreply := chan of int; + reply0, reply1: chan of int; + d0, d1: ref Sys->Dir; + eof0 := eof1 := 0; + for(;;){ + if(!eof0 && d0 == nil){ + ((d0, nil), reply0) = <-c0; + if(d0 == nil){ + reply0 <-= Next; + eof0 = 1; + } + } + if(!eof1 && d1 == nil){ + ((d1, nil), reply1) = <-c1; + if(d1 == nil){ + reply1 <-= Next; + eof1 = 1; + } + } + if(eof0 && eof1) + break; + + (wd0, wd1) := (d0, d1); + if(d0 != nil && d1 != nil && d0.name != d1.name){ + if(d0.name < d1.name) + wd1 = nil; + else + wd0 = nil; + } + + wc0, wc1: Fschan; + wreply0, wreply1: chan of int; + weof0, weof1: int; + + c := compare(cmp, wd0, wd1); + if(wd0 != nil && wd1 != nil){ + if(c != 0 && recurse && (wd0.mode & wd1.mode & Sys->DMDIR) != 0){ + dst <-= ((wd0, nil), myreply); + r := <-myreply; + reply0 <-= r; + reply1 <-= r; + d0 = d1 = nil; + case r { + Quit => + raise "exit"; + Skip => + return; + Down => + mergedir(c0, c1, dst, 1, cmp); + } + continue; + } + # when we can't merge and there's a clash, choose c0 over c1, unless cmp says otherwise + if(c == 2r10){ + reply0 <-= Next; + d0 = nil; + }else{ + reply1 <-= Next; + d1 = nil; + } + } + if(c & 2r01){ + (wd0, wc0, wreply0, weof0) = (d0, c0, reply0, eof0); + (wd1, wc1, wreply1, weof1) = (d1, c1, reply1, eof1); + d0 = nil; + }else if(c & 2r10){ + (wd0, wc0, wreply0, weof0) = (d1, c1, reply1, eof1); + (wd1, wc1, wreply1, weof1) = (d0, c0, reply0, eof0); + d1 = nil; + }else{ + if(wd0 == nil){ + reply1 <-= Next; + d1 = nil; + }else{ + reply0 <-= Next; + d0 = nil; + } + continue; + } + dst <-= ((wd0, nil), myreply); + r := <-myreply; + wreply0 <-= r; + if(r == Down) + r = fs->copy(wc0, dst); # XXX hmm, maybe this should be a mergedir() + case r { + Quit or + Skip => + if(wd1 == nil && !weof1) + (nil, wreply1) = <-wc1; + wreply1 <-= r; + if(r == Quit) + raise "exit"; + return; + } + } + dst <-= ((nil, nil), myreply); + if(<-myreply == Quit) + raise "exit"; +} + +compare(cmp: Cmpchan, d0, d1: ref Sys->Dir): int +{ + mask := (d0 != nil) | (d1 != nil) << 1; + if(cmp == nil) + return mask; + reply := chan of int; + cmp <-= (d0, d1, reply); + return <-reply & mask; +} diff --git a/appl/alphabet/fs/mergewrite.b b/appl/alphabet/fs/mergewrite.b new file mode 100644 index 00000000..04e8cebb --- /dev/null +++ b/appl/alphabet/fs/mergewrite.b @@ -0,0 +1,244 @@ +implement Mergewrite, Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "readdir.m"; + readdir: Readdir; +include "alphabet/reports.m"; + reports: Reports; + Report, report, quit: import reports; +include "alphabet/fs.m"; + fs: Fs; + Value: import fs; + Fschan, Fsdata, Entrychan, Entry, + Cmpchan, Option, + Next, Down, Skip, Quit: import Fs; + +Mergewrite: module {}; + +types(): string +{ + return "rxsm-v-n"; +} + +VERBOSE, NOWRITE, ASSUME: con 1<<iota; + +init() +{ + sys = load Sys Sys->PATH; + readdir = load Readdir Readdir->PATH; + if(readdir == nil){ + sys->fprint(sys->fildes(2), "fs: mergewrite: cannot load %s: %r\n", Readdir->PATH); + raise "fail:bad module"; + } + readdir->init(nil, 0); + + fs = load Fs Fs->PATH; + if(fs == nil){ + sys->fprint(sys->fildes(2), "fs: mergewrite: cannot load %s: %r\n", Fs->PATH); + raise "fail:bad module"; + } + reports = load Reports Reports->PATH; + if(reports == nil){ + sys->fprint(sys->fildes(2), "fs: mergewrite: cannot load %s: %r\n", Reports->PATH); + raise "fail:bad module"; + } +} + +run(nil: ref Draw->Context, report: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + sync := chan of string; + flags := 0; + for(; opts != nil; opts = tl opts){ + case (hd opts).opt { + 'n' => + flags |= NOWRITE; + 'v' => + flags |= VERBOSE; + } + } + + spawn fswriteproc(sync, flags, (hd args).x().i, (hd tl args).s().i, (hd tl tl args).m().i, report.start("mergewrite")); + sync <-= nil; + return ref Value.Vr(sync); +} + +fswriteproc(sync: chan of string, flags: int, c: Fschan, root: string, cmp: Cmpchan, errorc: chan of string) +{ + sys->pctl(Sys->FORKNS, nil); + <-sync; + if(<-sync != nil){ + (<-c).t1 <-= Quit; + quit(errorc); + } + + ((d, nil), reply) := <-c; + if(root != nil){ + d = ref *d; + d.name = root; + } + fswritedir(d.name, cmp, d, reply, c, errorc, flags); + errorc <-= nil; + sync <-= nil; # XXX should return result here... +} + +fswritedir(path: string, cmp: Cmpchan, dir: ref Sys->Dir, dreply: chan of int, c: Fschan, + errorc: chan of string, flags: int) +{ + fd: ref Sys->FD; + if(dir.mode & Sys->DMDIR){ + made := 0; + if(flags&VERBOSE) + report(errorc, sys->sprint("create %q %uo", path, dir.mode)); + if(flags&NOWRITE){ + if(flags&ASSUME) + made = 1; + else{ + fd = sys->open(dir.name, Sys->OREAD); + if(fd == nil){ + made = 1; + flags |= ASSUME; + }else if(sys->chdir(dir.name) == -1){ + dreply <-= Next; + report(errorc, sys->sprint("cannot cd to %q: %r", path)); + return; + } + } + }else{ + fd = sys->create(dir.name, Sys->OREAD, dir.mode|8r300); + made = fd != nil; + if(fd == nil && (fd = sys->open(dir.name, Sys->OREAD)) == nil){ + dreply <-= Next; + report(errorc, sys->sprint("cannot create %q, mode %uo: %r", path, dir.mode|8r300)); + return; + } + # XXX if we haven't just made it, we should chmod the old entry u+w to enable writing. + if(sys->chdir(dir.name) == -1){ # XXX beware of names starting with '#' + dreply <-= Next; + report(errorc, sys->sprint("cannot cd to %q: %r", path)); + fd = nil; + sys->remove(dir.name); + return; + } + } + dreply <-= Down; + entries: array of ref Sys->Dir; + if(made == 0) + entries = readdir->readall(fd, Readdir->NAME|Readdir->COMPACT).t0; + i := 0; + eod := 0; + d0, d1: ref Sys->Dir; + reply: chan of int; + path[len path] = '/'; + for(;;){ + if(!eod && d0 == nil){ + ((d0, nil), reply) = <-c; + if(d0 == nil){ + reply <-= Next; + eod = 1; + } + } + if(d1 == nil && i < len entries) + d1 = entries[i++]; + if(d0 == nil && d1 == nil) + break; + + (wd0, wd1) := (d0, d1); + if(d0 != nil && d1 != nil && d0.name != d1.name){ + if(d0.name < d1.name) + wd1 = nil; + else + wd0 = nil; + } + r := compare(cmp, wd0, wd1); + if(wd1 != nil){ + if((r & 2r10) == 0){ + if(flags&VERBOSE) + report(errorc, "removing "+path+wd1.name); + if((flags&NOWRITE)==0){ + if(wd1.mode & Sys->DMDIR) + rmdir(wd1.name); + else + remove(wd1.name); + } + } + d1 = nil; + } + if(wd0 != nil){ + if((r & 2r01) == 0) + reply <-= Next; + else + fswritedir(path + wd0.name, cmp, d0, reply, c, errorc, flags); + d0 = nil; + } + } + if((flags&ASSUME)==0) + sys->chdir(".."); + if((flags&NOWRITE)==0){ + if((dir.mode & 8r300) != 8r300){ + ws := Sys->nulldir; + ws.mode = dir.mode; + if(sys->fwstat(fd, ws) == -1) + report(errorc, sys->sprint("cannot wstat %q: %r", path)); + } + } + }else{ + if(flags&VERBOSE) + report(errorc, sys->sprint("create %q %uo", path, dir.mode)); + if(flags&NOWRITE){ + dreply <-= Next; + return; + } + fd = sys->create(dir.name, Sys->OWRITE, dir.mode); + if(fd == nil){ + dreply <-= Next; + report(errorc, sys->sprint("cannot create %q, mode %uo: %r", path, dir.mode|8r300)); + return; + } + dreply <-= Down; + while((((nil, buf), reply) := <-c).t0.data != nil){ + nw := sys->write(fd, buf, len buf); + if(nw < len buf){ + if(nw == -1) + errorc <-= sys->sprint("error writing %q: %r", path); + else + errorc <-= sys->sprint("short write"); + reply <-= Skip; + break; + } + reply <-= Next; + } + reply <-= Next; + } +} + +rmdir(name: string) +{ + (d, n) := readdir->init(name, Readdir->NONE|Readdir->COMPACT); + for(i := 0; i < n; i++){ + path := name+"/"+d[i].name; + if(d[i].mode & Sys->DMDIR) + rmdir(path); + else + remove(path); + } + remove(name); +} + +remove(name: string) +{ + if(sys->remove(name) < 0) + sys->fprint(sys->fildes(2), "mergewrite: cannot remove %q: %r\n", name); +} + +compare(cmp: Cmpchan, d0, d1: ref Sys->Dir): int +{ + mask := (d0 != nil) | (d1 != nil) << 1; + if(cmp == nil) + return mask; + reply := chan of int; + cmp <-= (d0, d1, reply); + return <-reply & mask; +} diff --git a/appl/alphabet/fs/mkext.b b/appl/alphabet/fs/mkext.b new file mode 100644 index 00000000..b916aebc --- /dev/null +++ b/appl/alphabet/fs/mkext.b @@ -0,0 +1,266 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "string.m"; + str: String; +include "bundle.m"; + bundle: Bundle; +include "draw.m"; +include "sh.m"; +include "alphabet/fs.m"; + fslib: Fs; + Report, Value,quit, report: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Quit, Next, Skip, Down, + Option: import Fs; + +to do... +read file. if non-seekable, make temporary copy. +record offsets of all files +sort by filename +output in proper order. + + +types(): string +{ + return "xs"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: exec: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fs Fs->PATH; + if(fslib == nil) + badmod(Fs->PATH); + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + badmod(Bufio->PATH); + str = load String String->PATH; + if(str == nil) + badmod(String->PATH); + bundle = load Bundle Bundle->PATH; + if(bundle == nil) + badmod(Bundle->PATH); + bundle->init(); +} + +run(nil: ref Draw->Context, report: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + p := (hd args).s().i; + iob: ref Bufio->Iobuf; + if(p == "-") + iob = bufio->fopen(sys->fildes(0), Sys->OREAD); + else + iob = bufio->open(p, Sys->OREAD); + if(iob == nil){ + sys->fprint(sys->fildes(2), "fs: unbundle: cannot open %q: %r\n", p); + return nil; + } + seekable := p != "-"; + if(seekable) + seekable = isseekable(iob.fd); + if( + return ref Value.Vx(mkext(report, iob, seekable, Sys->ATOMICIO)); +} + +# dodgy heuristic... avoid, or using the stat-length of pipes and net connections +isseekable(fd: ref Sys->FD): int +{ + (ok, stat) := sys->fstat(fd); + if(ok != -1 && stat.dtype == '|' || stat.dtype == 'I') + return 0; + return 1; +} + +mkext(r: ref Report, iob: ref Iobuf, seekable, blocksize: int): Fschan +{ + c := chan of (Fsdata, chan of int); + spawn unbundleproc(iob, c, seekable, blocksize, r.start("bundle")); + return c; +} + +EOF: con "end of archive\n"; + +unbundleproc(iob: ref Iobuf, c: Fschan, seekable, blocksize: int, errorc: chan of string) +{ + reply := chan of int; + p := iob.gets('\n'); + # XXX overall header? + if(p == nil || p == EOF){ + fslib->sendnulldir(c); + quit(errorc); + } + d := header2dir(p); + if(d == nil){ + fslib->sendnulldir(c); + report(errorc, "invalid first header"); + quit(errorc); + } + if((d.mode & Sys->DMDIR) == 0){ + fslib->sendnulldir(c); + report(errorc, "first entry is not a directory"); + quit(errorc); + } + c <-= ((d, nil), reply); + case r := <-reply { + Down => + unbundledir(iob, c, 0, seekable, blocksize, errorc); + c <-= ((nil, nil), reply); + <-reply; + Skip or + Next => + unbundledir(iob, c, 1, seekable, blocksize, errorc); + Quit => + break; + } + quit(errorc); +} + +unbundledir(iob: ref Iobuf, c: Fschan, + skipping, seekable, blocksize: int, errorc: chan of string): int +{ + reply := chan of int; + while((p := iob.gets('\n')) != nil){ + if(p == EOF) + break; + if(p[0] == '\n') + break; + d := header2dir(p); + if(d == nil){ + report(errorc, sys->sprint("invalid bundle header %q", p[0:len p - 1])); + return -1; + } + if(d.mode & Sys->DMDIR){ + if(skipping) + continue; + c <-= ((d, nil), reply); + case <-reply { + Quit => + quit(errorc); + Down => + r := unbundledir(iob, c, 0, seekable, blocksize, errorc); + c <-= ((nil, nil), reply); + if(<-reply == Quit) + quit(errorc); + if(r == -1) + return -1; + Skip => + if(unbundledir(iob, c, 1, seekable, blocksize, errorc) == -1) + return -1; + skipping = 1; + Next => + if(unbundledir(iob, c, 1, seekable, blocksize, errorc) == -1) + return -1; + } + }else{ + if(skipping){ + if(skipdata(iob, d.length, seekable) == -1) + return -1; + }else{ + case unbundlefile(iob, d, c, errorc, seekable, blocksize) { + -1 => + return -1; + Skip => + skipping = 1; + } + } + } + } + if(p == nil) + report(errorc, "unexpected eof"); + return 0; +} + +skipdata(iob: ref Iobuf, length: big, seekable: int): int +{ + if(seekable){ + iob.seek(big length, Sys->SEEKRELA); + return 0; + } + buf := array[Sys->ATOMICIO] of byte; + for(n := big 0; n < length; ){ + nb := Sys->ATOMICIO; + if(length - n < big Sys->ATOMICIO) + nb = int (length - n); + nb = iob.read(buf, nb); + if(nb <= 0) + return -1; + n += big nb; + } + return 0; +} + +unbundlefile(iob: ref Iobuf, d: ref Sys->Dir, + c: Fschan, errorc: chan of string, seekable, blocksize: int): int +{ + reply := chan of int; + c <-= ((d, nil), reply); + case <-reply { + Quit => + quit(errorc); + Skip => + if(skipdata(iob, d.length, seekable) == -1) + return -1; + return Skip; + Next => + if(skipdata(iob, d.length, seekable) == -1) + return -1; + return Next; + } + length := d.length; + for(n := big 0; n < length; ){ + nr := blocksize; + if(n + big blocksize > length) + nr = int (length - n); + buf := array[nr] of byte; + nr = iob.read(buf, nr); + if(nr <= 0){ + if(nr < 0) + report(errorc, sys->sprint("read error: %r")); + else + report(errorc, sys->sprint("premature eof")); + return -1; + }else if(nr < len buf) + buf = buf[0:nr]; + c <-= ((nil, buf), reply); + n += big nr; + case <-reply { + Quit => + quit(errorc); + Skip => + if(skipdata(iob, length - n, seekable) == -1) + return -1; + return Next; + } + } + c <-= ((nil, nil), reply); + if(<-reply == Quit) + quit(errorc); + return Next; +} + +header2dir(s: string): ref Sys->Dir +{ + toks := str->unquoted(s); + nf := len toks; + if(nf != 6) + return nil; + d := ref Sys->nulldir; + (d.name, toks) = (hd toks, tl toks); + (d.mode, toks) = (str->toint(hd toks, 8).t0, tl toks); + (d.uid, toks) = (hd toks, tl toks); + (d.gid, toks) = (hd toks, tl toks); + (d.mtime, toks) = (int hd toks, tl toks); + (d.length, toks) = (big hd toks, tl toks); + return d; +} diff --git a/appl/alphabet/fs/mkfile b/appl/alphabet/fs/mkfile new file mode 100644 index 00000000..684a3650 --- /dev/null +++ b/appl/alphabet/fs/mkfile @@ -0,0 +1,55 @@ +<../../../mkconfig + +TARG=\ + and.dis\ + bundle.dis\ + chstat.dis\ + compose.dis\ + depth.dis\ + entries.dis\ + exec.dis\ + filter.dis\ + ls.dis\ + match.dis\ + merge.dis\ + mergewrite.dis\ + mode.dis\ + newer.dis\ + not.dis\ + or.dis\ + path.dis\ + pipe.dis\ + print.dis\ + proto.dis\ + query.dis\ + run.dis\ + select.dis\ + setroot.dis\ + size.dis\ + unbundle.dis\ + walk.dis\ + write.dis\ + +MODULES=\ + bundle.m\ + unbundle.m\ + +SYSMODULES=\ + alphabet/fs.m\ + alphabet/reports.m\ + bufio.m\ + bundle.m\ + daytime.m\ + draw.m\ + filepat.m\ + readdir.m\ + regex.m\ + sh.m\ + string.m\ + sys.m\ + unbundle.m\ + +DISBIN=$ROOT/dis/alphabet/fs + +<$ROOT/mkfiles/mkdis +LIMBOFLAGS=-F $LIMBOFLAGS diff --git a/appl/alphabet/fs/mode.b b/appl/alphabet/fs/mode.b new file mode 100644 index 00000000..974885c7 --- /dev/null +++ b/appl/alphabet/fs/mode.b @@ -0,0 +1,125 @@ +implement Mode, Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; + Report: import Reports; +include "alphabet/fs.m"; + fs: Fs; + Value: import fs; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fs; + +Mode: module {}; + +# XXX implement octal modes. + +User: con 8r700; +Group: con 8r070; +Other: con 8r007; +All: con User | Group | Other; + +Read: con 8r444; +Write: con 8r222; +Exec: con 8r111; + +types(): string +{ + return "ps"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fs->init(); +} + +run(nil: ref Draw->Context, nil: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + spec := (hd args).s().i; + (ok, mask, mode) := parsemode(spec); + if(ok == 0){ + sys->fprint(sys->fildes(2), "fs: mode: bad mode %#q\n", spec); + return nil; + } + c := chan of Gatequery; + spawn modegate(c, mask, mode); + return ref Value.Vp(c); +} + +modegate(c: Gatechan, mask, mode: int) +{ + m := mode & mask; + while((((d, nil, nil), reply) := <-c).t0.t0 != nil) + reply <-= ((d.mode & mask) ^ m) == 0; +} + +# stolen from /appl/cmd/chmod.b +parsemode(spec: string): (int, int, int) +{ + mask := Sys->DMAPPEND | Sys->DMEXCL | Sys->DMDIR | Sys->DMAUTH; +loop: + for(i := 0; i < len spec; i++){ + case spec[i] { + 'u' => + mask |= User; + 'g' => + mask |= Group; + 'o' => + mask |= Other; + 'a' => + mask |= All; + * => + break loop; + } + } + if(i == len spec) + return (0, 0, 0); + if(i == 0) + mask |= All; + + op := spec[i++]; + if(op != '+' && op != '-' && op != '=') + return (0, 0, 0); + + mode := 0; + for(; i < len spec; i++){ + case spec[i]{ + 'r' => + mode |= Read; + 'w' => + mode |= Write; + 'x' => + mode |= Exec; + 'a' => + mode |= Sys->DMAPPEND; + 'l' => + mode |= Sys->DMEXCL; + 'd' => + mode |= Sys->DMDIR; + 'A' => + mode |= Sys->DMAUTH; + * => + return (0, 0, 0); + } + } + if(op == '+' || op == '-') + mask &= mode; + if(op == '-') + mode = ~mode; + return (1, mask, mode); +} + + diff --git a/appl/alphabet/fs/newer.b b/appl/alphabet/fs/newer.b new file mode 100644 index 00000000..a278f70a --- /dev/null +++ b/appl/alphabet/fs/newer.b @@ -0,0 +1,64 @@ +implement Newer, Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; + Report: import Reports; +include "alphabet/fs.m"; + fs: Fs; + Value: import fs; + Cmpchan, Option: import Fs; + +Newer: module {}; + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +types(): string +{ + return "m-d"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fs->init(); +} + +# select those items in A that are newer than those in B +# or those that exist in A that don't in B. +# if -d flag is given, select all directories in A too. +run(nil: ref Draw->Context, nil: ref Report, + opts: list of Option, nil: list of ref Value): ref Value +{ + c := chan of (ref Sys->Dir, ref Sys->Dir, chan of int); + spawn newer(c, opts != nil); + return ref Value.Vm(c); +} + +newer(c: Cmpchan, dflag: int) +{ + while(((d0, d1, reply) := <-c).t2 != nil){ + r: int; + if(d0 == nil) + r = 2r10; + else if(d1 == nil) + r = 2r01; + else if(dflag && (d0.mode & Sys->DMDIR)) + r = 2r11; + else { + if(d0.mtime > d1.mtime) + r = 2r01; + else + r= 2r10; + } + reply <-= r; + } +} diff --git a/appl/alphabet/fs/not.b b/appl/alphabet/fs/not.b new file mode 100644 index 00000000..571f18a3 --- /dev/null +++ b/appl/alphabet/fs/not.b @@ -0,0 +1,53 @@ +implement Not, Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; + Report: import Reports; +include "alphabet/fs.m"; + fs: Fs; + Value: import fs; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fs; + +Not: module {}; + +types(): string +{ + return "pp"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fs->init(); +} + +run(nil: ref Draw->Context, nil: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + c := chan of Gatequery; + spawn notgate(c, (hd args).p().i); + return ref Value.Vp(c); +} + +notgate(c, sub: Gatechan) +{ + myreply := chan of int; + while(((d, reply) := <-c).t0.t0 != nil){ + sub <-= (d, myreply); + reply <-= !<-myreply; + } + sub <-= (Nilentry, nil); +} diff --git a/appl/alphabet/fs/or.b b/appl/alphabet/fs/or.b new file mode 100644 index 00000000..7a103a0b --- /dev/null +++ b/appl/alphabet/fs/or.b @@ -0,0 +1,70 @@ +implement Or, Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; + Report: import Reports; +include "alphabet/fs.m"; + fs: Fs; + Value: import fs; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fs; + +Or: module {}; + +types(): string +{ + return "pppp*"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fs->init(); +} + +run(nil: ref Draw->Context, nil: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + c := chan of Gatequery; + spawn orgate(c, args); + return ref Value.Vp(c); +} + +orgate(c: Gatechan, args: list of ref Value) +{ + sub: list of Gatechan; + for(; args != nil; args = tl args) + sub = (hd args).p().i :: sub; + sub = rev(sub); + myreply := chan of int; + while(((d, reply) := <-c).t0.t0 != nil){ + for(l := sub; l != nil; l = tl l){ + (hd l) <-= (d, myreply); + if(<-myreply) + break; + } + reply <-= l != nil; + } + for(; sub != nil; sub = tl sub) + hd sub <-= (Nilentry, nil); +} + +rev[T](x: list of T): list of T +{ + l: list of T; + for(; x != nil; x = tl x) + l = hd x :: l; + return l; +} diff --git a/appl/alphabet/fs/path.b b/appl/alphabet/fs/path.b new file mode 100644 index 00000000..1ed48378 --- /dev/null +++ b/appl/alphabet/fs/path.b @@ -0,0 +1,82 @@ +implement Path, Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; + Report: import Reports; +include "alphabet/fs.m"; + fs: Fs; + Value: import fs; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fs; + +Path: module {}; + +types(): string +{ + return "pss*-x"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fs->init(); +} + +run(nil: ref Draw->Context, nil: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + # XXX cleanname all paths? + c := chan of Gatequery; + p: list of string; + for(; args != nil; args = tl args) + p = (hd args).s().i :: p; + spawn pathgate(c, opts != nil, p); + return ref Value.Vp(c); +} + +pathgate(c: Gatechan, xflag: int, paths: list of string) +{ + if(xflag){ + while((((d, path, nil), reply) := <-c).t0.t0 != nil){ + for(q := paths; q != nil; q = tl q){ + r := 1; + p := hd q; + if(len path > len p) + r = path[len p] != '/' || path[0:len p] != p; + else if(len path == len p) + r = path != p; + if(r == 0) + break; + } + reply <-= q == nil; + } + }else{ + while((((d, path, nil), reply) := <-c).t0.t0 != nil){ + for(q := paths; q != nil; q = tl q){ + r := 0; + p := hd q; + if(len path > len p) + r = path[len p] == '/' && path[0:len p] == p; + else if(len path == len p) + r = path == p; + else + r = p[len path] == '/' && p[0:len path] == path; + if(r) + break; + } + reply <-= q != nil; + } + } +} diff --git a/appl/alphabet/fs/pipe.b b/appl/alphabet/fs/pipe.b new file mode 100644 index 00000000..9fe36ec7 --- /dev/null +++ b/appl/alphabet/fs/pipe.b @@ -0,0 +1,230 @@ +implement Pipe, Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; + sh: Sh; + Context: import sh; +include "alphabet/reports.m"; + Report: import Reports; +include "alphabet/fs.m"; + fs: Fs; + Option, Value, Fschan: import fs; + Skip, Next, Down, Quit: import fs; + +Pipe: module {}; + +# pipe the contents of the files in a filesystem through +# a command. -1 causes one command only to be executed. +# -p and -P (exclusive to -1) cause stat modes to be set in the shell environment. +types(): string +{ + return "rxc-1-p-P"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: exec: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fs->init(); + sh = load Sh Sh->PATH; + if(sh == nil) + badmod(Sh->PATH); + sh->initialise(); +} + +run(drawctxt: ref Draw->Context, nil: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + n := 1; + oneflag := pflag := 0; + for(; opts != nil; opts = tl opts){ + o := hd opts; + case o.opt { + '1' => + oneflag = 1; + 'p' => + pflag = 1; + 'P' => + pflag = 2; + } + } + if(pflag && oneflag){ + sys->fprint(sys->fildes(2), "fs: exec: cannot specify -p with -1\n"); + return nil; + } + c := (hd args).x().i; + cmd := (hd tl args).c().i; + sync := chan of string; + spawn execproc(drawctxt, sync, oneflag, pflag, c, cmd); + sync <-= nil; + return ref Value.Vr(sync); +} + +execproc(drawctxt: ref Draw->Context, sync: chan of string, oneflag, pflag: int, + c: Fschan, cmd: ref Sh->Cmd) +{ + sys->pctl(Sys->NEWFD, 0::1::2::nil); + ctxt := Context.new(drawctxt); + <-sync; + if(<-sync != nil){ + (<-c).t1 <-= Quit; + exit; + } + argv := ref Sh->Listnode(cmd, nil) :: nil; + fd: ref Sys->FD; + result := chan of string; + if(oneflag){ + fd = popen(ctxt, argv, result); + if(fd == nil){ + (<-c).t1 <-= Quit; + sync <-= "cannot make pipe"; + exit; + } + } + + names: list of string; + name: string; + indent := 0; + for(;;){ + (d, reply) := <-c; + if(d.dir == nil){ + reply <-= Next; + if(--indent == 0){ + break; + } + (name, names) = (hd names, tl names); + continue; + } + if((d.dir.mode & Sys->DMDIR) != 0){ + reply <-= Down; + names = name :: names; + if(indent > 0 && name != nil && name[len name - 1] != '/') + name[len name] = '/'; + name += d.dir.name; + indent++; + continue; + } + if(!oneflag){ + p := name; + if(p != nil && p[len p - 1] != '/') + p[len p] = '/'; + setenv(ctxt, "file", p + d.dir.name :: nil); + if(pflag) + setstatenv(ctxt, d.dir, pflag); + fd = popen(ctxt, argv, result); + } + if(fd == nil){ + reply <-= Next; + continue; + } + reply <-= Down; + for(;;){ + data: array of byte; + ((nil, data), reply) = <-c; + reply <-= Next; + if(data == nil) + break; + n := -1; + {n = sys->write(fd, data, len data);}exception {"write on closed pipe" => ;} + if(n != len data){ + if(oneflag){ + (<-c).t1 <-= Quit; + sync <-= "truncated write"; + exit; + } + (<-c).t1 <-= Skip; + break; + } + } + if(!oneflag){ + fd = nil; + <-result; + } + } + fd = nil; + if(oneflag) + sync <-= <-result; + else + sync <-= nil; +} + +popen(ctxt: ref Context, argv: list of ref Sh->Listnode, result: chan of string): ref Sys->FD +{ + sync := chan of int; + fds := array[2] of ref Sys->FD; + sys->pipe(fds); + spawn runcmd(ctxt, argv, fds[0], sync, result); + <-sync; + return fds[1]; +} + +runcmd(ctxt: ref Context, argv: list of ref Sh->Listnode, stdin: ref Sys->FD, sync: chan of int, result: chan of string) +{ + sys->pctl(Sys->FORKFD, nil); + sys->dup(stdin.fd, 0); + stdin = nil; + sys->pctl(Sys->NEWFD, 0::1::2::nil); + ctxt = ctxt.copy(0); + sync <-= 0; + r := ctxt.run(argv, 0); + ctxt = nil; + sys->pctl(Sys->NEWFD, nil); + result <-=r; +} + +setenv(ctxt: ref Context, var: string, val: list of string) +{ + ctxt.set(var, sh->stringlist2list(val)); +} + +setstatenv(ctxt: ref Context, dir: ref Sys->Dir, pflag: int) +{ + setenv(ctxt, "mode", modes(dir.mode) :: nil); + setenv(ctxt, "uid", dir.uid :: nil); + setenv(ctxt, "mtime", string dir.mtime :: nil); + setenv(ctxt, "length", string dir.length :: nil); + + if(pflag > 1){ + setenv(ctxt, "name", dir.name :: nil); + setenv(ctxt, "gid", dir.gid :: nil); + setenv(ctxt, "muid", dir.muid :: nil); + setenv(ctxt, "qid", sys->sprint("16r%ubx", dir.qid.path) :: string dir.qid.vers :: nil); + setenv(ctxt, "atime", string dir.atime :: nil); + setenv(ctxt, "dtype", sys->sprint("%c", dir.dtype) :: nil); + setenv(ctxt, "dev", string dir.dev :: nil); + } +} + +mtab := array[] of { + "---", "--x", "-w-", "-wx", + "r--", "r-x", "rw-", "rwx" +}; + +modes(mode: int): string +{ + s: string; + + if(mode & Sys->DMDIR) + s = "d"; + else if(mode & Sys->DMAPPEND) + s = "a"; + else if(mode & Sys->DMAUTH) + s = "A"; + else + s = "-"; + if(mode & Sys->DMEXCL) + s += "l"; + else + s += "-"; + s += mtab[(mode>>6)&7]+mtab[(mode>>3)&7]+mtab[mode&7]; + return s; +} diff --git a/appl/alphabet/fs/print.b b/appl/alphabet/fs/print.b new file mode 100644 index 00000000..4c9bee59 --- /dev/null +++ b/appl/alphabet/fs/print.b @@ -0,0 +1,61 @@ +implement Print, Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; + reports: Reports; + Report: import reports; +include "alphabet/fs.m"; + fs: Fs; + Value: import fs; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fs; + +Print: module {}; + +types(): string +{ + return "ft"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + reports = load Reports Reports->PATH; + if(reports == nil) + badmod(Reports->PATH); + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fs->init(); +} + +run(nil: ref Draw->Context, report: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + f := chan of ref Sys->FD; + spawn printproc(f, (hd args).t().i, report.start("/fs/print")); + return ref Value.Vf(f); +} + +printproc(f: chan of ref Sys->FD, c: Entrychan, errorc: chan of string) +{ + f <-= nil; + if((fd := <-f) == nil){ + c.sync <-= 0; + reports->quit(errorc); + } + c.sync <-= 1; + while(((d, p, nil) := <-c.c).t0 != nil) + sys->fprint(fd, "%s\n", p); + sys->fprint(fd, ""); + reports->quit(errorc); +} diff --git a/appl/alphabet/fs/proto.b b/appl/alphabet/fs/proto.b new file mode 100644 index 00000000..48e46d18 --- /dev/null +++ b/appl/alphabet/fs/proto.b @@ -0,0 +1,416 @@ +implement Proto, Fsmodule; +include "sys.m"; + sys: Sys; +include "readdir.m"; + readdir: Readdir; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "string.m"; + str: String; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; + reports: Reports; + Report, quit, report: import reports; +include "alphabet/fs.m"; + fs: Fs; + Value: import fs; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fs; + +Proto: module {}; + +File: adt { + name: string; + mode: int; + owner: string; + group: string; + old: string; + flags: int; + sub: cyclic array of ref File; +}; + +Protof: adt { + indent: int; + lastline: string; + iob: ref Iobuf; +}; + +Star, Plus: con 1<<iota; + +types(): string +{ + return "xf-rs"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: proto: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fs->init(); + reports = load Reports Reports->PATH; + if(reports == nil) + badmod(Reports->PATH); + readdir = load Readdir Readdir->PATH; + if(readdir == nil) + badmod(Readdir->PATH); + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + badmod(Bufio->PATH); + str = load String String->PATH; + if(str == nil) + badmod(String->PATH); +} + +run(nil: ref Draw->Context, report: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + f := (hd args).f().i; + rootpath: string; + if(opts != nil) + rootpath = (hd (hd opts).args).s().i; + if(rootpath == nil) + rootpath = "/"; + + root := ref File(rootpath, ~0, nil, nil, nil, 0, nil); + c := chan of (Fsdata, chan of int); + spawn protowalk(c, f, root, report.start("proto")); + return ref Value.Vx(c); +} + +protowalk(c: Fschan, f: chan of ref Sys->FD, root: ref File, errorc: chan of string) +{ + fd := <-f; + if(fd != nil) + f <-= nil; + else{ + sys->pipe(p := array[2] of ref Sys->FD); + f <-= p[1]; + fd = p[0]; + } + proto := ref Protof(0, nil, nil); + proto.iob = bufio->fopen(fd, Sys->OREAD); + (root.flags, root.sub) = readproto(proto, -1); + + d: ref Sys->Dir; + (ok, rd) := sys->stat(root.name); + if(ok != -1) + d = ref rd; + + protowalk1(c, root.flags, root.name, file2dir(root, d), root.sub, errorc); + quit(errorc); +} + +protowalk1(c: Fschan, flags: int, path: string, d: ref Sys->Dir, + sub: array of ref File, errorc: chan of string): int +{ + reply := chan of int; + c <-= ((d, nil), reply); + case r := <-reply { + Quit => + quit(errorc); + Next or + Skip => + return r; + } + (a, n) := readdir->init(path, Readdir->NAME|Readdir->COMPACT); + if(len a == 0){ + c <-= ((nil, nil), reply); + if(<-reply == Quit) + quit(errorc); + return Next; + } + j := 0; + preventry: string; + useentry: int; + for(i := 0; i < n; i += useentry){ + useentry = 1; + for(; j < len sub; j++){ + s := sub[j].name; + if(s == preventry){ + report(errorc, sys->sprint("duplicate entry %s", pathconcat(path, s))); + continue; # eliminate duplicates in proto + } + if(s >= a[i].name) + break; + # entry has not been found, but we've got a substitute version, + # so save the rest of the entries to match the rest of sub. + if(sub[j].old != nil){ + useentry = 0; + break; + } + report(errorc, sys->sprint("%s not found", pathconcat(path, s))); + } + foundsub := j < len sub && (sub[j].name == a[i].name || sub[j].old != nil); + if(foundsub || flags&Plus || + (flags&Star && (a[i].mode & Sys->DMDIR)==0)){ + f: ref File; + if(foundsub){ + f = sub[j++]; + preventry = f.name; + } + p: string; + d: ref Sys->Dir; + if(foundsub && f.old != nil){ + p = f.old; + (ok, xd) := sys->stat(p); + if(ok == -1){ + report(errorc, sys->sprint("cannot stat %q: %r", p)); + continue; + } + d = ref xd; + }else{ + p = pathconcat(path, a[i].name); + d = a[i]; + } + + d = file2dir(f, d); + r: int; + if((d.mode & Sys->DMDIR) == 0) + r = walkfile(c, p, d, errorc); + else if(flags & Plus) + r = protowalk1(c, Plus, p, d, nil, errorc); + else + r = protowalk1(c, f.flags, p, d, f.sub, errorc); + if(r == Skip) + return Next; + } + } + c <-= ((nil, nil), reply); + if(<-reply == Quit) + quit(errorc); + return Next; +} + +pathconcat(p, name: string): string +{ + if(p != nil && p[len p - 1] != '/') + p[len p] = '/'; + p += name; + return p; +} + +# from(ish) walk.b +walkfile(c: Fschan, path: string, d: ref Sys->Dir, errorc: chan of string): int +{ + reply := chan of int; + fd := sys->open(path, Sys->OREAD); + if(fd == nil){ + report(errorc, sys->sprint("cannot open %q: %r", path)); + return Next; + } + c <-= ((d, nil), reply); + case r := <-reply { + Quit => + quit(errorc); + Next or + Skip => + return r; + } + length := d.length; + for(n := big 0; n < length; ){ + nr := Sys->ATOMICIO; + if(n + big Sys->ATOMICIO > length) + nr = int (length - n); + buf := array[nr] of byte; + nr = sys->read(fd, buf, nr); + if(nr <= 0){ + if(nr < 0) + report(errorc, sys->sprint("error reading %q: %r", path)); + else + report(errorc, sys->sprint("%q is shorter than expected (%bd/%bd)", + path, n, length)); + break; + }else if(nr < len buf) + buf = buf[0:nr]; + c <-= ((nil, buf), reply); + case <-reply { + Quit => + quit(errorc); + Skip => + return Next; + } + n += big nr; + } + c <-= ((nil, nil), reply); + if(<-reply == Quit) + quit(errorc); + return Next; +} + +readproto(proto: ref Protof, indent: int): (int, array of ref File) +{ + a := array[10] of ref File; + n := 0; + flags := 0; + while((f := readline(proto, indent)) != nil){ + if(f.name == "*") + flags |= Star; + else if(f.name == "+") + flags |= Plus; + else{ + (f.flags, f.sub) = readproto(proto, proto.indent); + if(n == len a) + a = (array[n * 2] of ref File)[0:] = a; + a[n++] = f; + } + } + if(n < len a) + a = (array[n] of ref File)[0:] = a[0:n]; + mergesort(a, array[n] of ref File); + return (flags, a); +} + +readline(proto: ref Protof, indent: int): ref File +{ + s: string; + if(proto.lastline != nil){ + s = proto.lastline; + proto.lastline = nil; + }else if(proto.indent == -1) + return nil; + else if((s = proto.iob.gets('\n')) == nil){ + proto.indent = -1; + return nil; + } + spc := 0; + for(i := 0; i < len s; i++){ + c := s[i]; + if(c == ' ') + spc++; + else if(c == '\t') + spc += 8; + else + break; + } + if(i == len s || s[i] == '#' || s[i] == '\n') + return readline(proto, indent); # XXX sort out tail recursion! + if(spc <= indent){ + proto.lastline = s; + return nil; + } + proto.indent = spc; + (n, toks) := sys->tokenize(s, " \t\n"); + f := ref File(nil, ~0, nil, nil, nil, 0, nil); + (f.name, toks) = (getname(hd toks, 0), tl toks); + if(toks == nil) + return f; + (f.mode, toks) = (getmode(hd toks), tl toks); + if(toks == nil) + return f; + (f.owner, toks) = (getname(hd toks, 1), tl toks); + if(toks == nil) + return f; + (f.group, toks) = (getname(hd toks, 1), tl toks); + if(toks == nil) + return f; + (f.old, toks) = (hd toks, tl toks); + return f; +} + +mergesort(a, b: array of ref File) +{ + r := len a; + if (r > 1) { + m := (r-1)/2 + 1; + mergesort(a[0:m], b[0:m]); + mergesort(a[m:], b[m:]); + b[0:] = a; + for ((i, j, k) := (0, m, 0); i < m && j < r; k++) { + if(b[i].name > b[j].name) + a[k] = b[j++]; + else + a[k] = b[i++]; + } + if (i < m) + a[k:] = b[i:m]; + else if (j < r) + a[k:] = b[j:r]; + } +} + +getname(s: string, allowminus: int): string +{ + if(s == nil) + return nil; + if(allowminus && s == "-") + return nil; + if(s[0] == '$') + return getenv(s[1:]); + return s; +} + +getenv(s: string): string +{ + # XXX implement env variables + return nil; +} + +getmode(s: string): int +{ + s = getname(s, 1); + if(s == nil) + return ~0; + m := 0; + i := 0; + if(s[i] == 'd'){ + m |= Sys->DMDIR; + i++; + } + if(i < len s && s[i] == 'a'){ + m |= Sys->DMAPPEND; + i++; + } + if(i < len s && s[i] == 'l'){ + m |= Sys->DMEXCL; + i++; + } + (xmode, t) := str->toint(s, 8); + if(t != nil){ + # report(aux.errorc, "bad mode specification %q", s); + return ~0; + } + return xmode | m; +} + +file2dir(f: ref File, old: ref Sys->Dir): ref Sys->Dir +{ + d := ref Sys->nulldir; + if(old != nil){ + if(old.dtype != 'M'){ + d.uid = "sys"; + d.gid = "sys"; + xmode := (old.mode >> 6) & 7; + d.mode = old.mode | xmode | (xmode << 3); + }else{ + d.uid = old.uid; + d.gid = old.gid; + d.mode = old.mode; + } + d.length = old.length; + d.mtime = old.mtime; + d.atime = old.atime; + d.muid = old.muid; + d.name = old.name; + } + if(f != nil){ + d.name = f.name; + if(f.owner != nil) + d.uid = f.owner; + if(f.group != nil) + d.gid = f.group; + if(f.mode != ~0) + d.mode = f.mode; + } + return d; +} diff --git a/appl/alphabet/fs/query.b b/appl/alphabet/fs/query.b new file mode 100644 index 00000000..8d230707 --- /dev/null +++ b/appl/alphabet/fs/query.b @@ -0,0 +1,135 @@ +implement Query, Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; + sh: Sh; + Context: import sh; +include "alphabet/reports.m"; + Report: import Reports; +include "alphabet/fs.m"; + fs: Fs; + Option, Value, Gatechan, Gatequery, Nilentry: import fs; + +Query: module {}; + +types(): string +{ + return "pc-p-P"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: query: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fs->init(); + sh = load Sh Sh->PATH; + if(sh == nil) + badmod(Sh->PATH); +} + +run(drawctxt: ref Draw->Context, nil: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + pflag := 0; + for(; opts != nil; opts = tl opts){ + o := hd opts; + case o.opt { + 'p' => + pflag = 1; + 'P' => + pflag = 2; + } + } + + v := ref Value.Vp(chan of Gatequery); + spawn querygate(drawctxt, v.i, (hd args).c().i, pflag); + v.i <-= (Nilentry, nil); + return v; +} + +querygate(drawctxt: ref Draw->Context, c: Gatechan, cmd: ref Sh->Cmd, pflag: int) +{ + sys->pctl(Sys->NEWFD, 0::1::2::nil); + ctxt := Context.new(drawctxt); + <-c; + argv := ref Sh->Listnode(cmd, nil) :: nil; + while((((d, p, nil), reply) := <-c).t0.t0 != nil){ + ctxt.set("file", ref Sh->Listnode(nil, p) :: nil); + if(pflag) + setstatenv(ctxt, d, pflag); + err := ""; + { + err = ctxt.run(argv, 0); + } exception e { + "fail:*" => + err = e; + } + reply <-= (err == nil); + } +} + +# XXX shouldn't duplicate this... + +setenv(ctxt: ref Context, var: string, val: list of string) +{ + ctxt.set(var, sh->stringlist2list(val)); +} + +setstatenv(ctxt: ref Context, dir: ref Sys->Dir, pflag: int) +{ + setenv(ctxt, "mode", modes(dir.mode) :: nil); + setenv(ctxt, "uid", dir.uid :: nil); + setenv(ctxt, "mtime", string dir.mtime :: nil); + setenv(ctxt, "length", string dir.length :: nil); + + if(pflag > 1){ + setenv(ctxt, "name", dir.name :: nil); + setenv(ctxt, "gid", dir.gid :: nil); + setenv(ctxt, "muid", dir.muid :: nil); + setenv(ctxt, "qid", sys->sprint("16r%ubx", dir.qid.path) :: string dir.qid.vers :: nil); + setenv(ctxt, "atime", string dir.atime :: nil); + setenv(ctxt, "dtype", sys->sprint("%c", dir.dtype) :: nil); + setenv(ctxt, "dev", string dir.dev :: nil); + } +} + +start(startc: chan of (string, chan of string), name: string): chan of string +{ + c := chan of string; + startc <-= (name, c); + return c; +} + +mtab := array[] of { + "---", "--x", "-w-", "-wx", + "r--", "r-x", "rw-", "rwx" +}; + +modes(mode: int): string +{ + s: string; + + if(mode & Sys->DMDIR) + s = "d"; + else if(mode & Sys->DMAPPEND) + s = "a"; + else if(mode & Sys->DMAUTH) + s = "A"; + else + s = "-"; + if(mode & Sys->DMEXCL) + s += "l"; + else + s += "-"; + s += mtab[(mode>>6)&7]+mtab[(mode>>3)&7]+mtab[mode&7]; + return s; +} diff --git a/appl/alphabet/fs/run.b b/appl/alphabet/fs/run.b new file mode 100644 index 00000000..6f6a38bb --- /dev/null +++ b/appl/alphabet/fs/run.b @@ -0,0 +1,65 @@ +implement Run, Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; + sh: Sh; + Context: import sh; +include "alphabet/reports.m"; + Report: import Reports; +include "alphabet/fs.m"; + fs: Fs; + Value: import fs; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fs; + +Run: module {}; + +types(): string +{ + return "sc"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fs->init(); + sh = load Sh Sh->PATH; + if(sh == nil) + badmod(Sh->PATH); + sh->initialise(); +} + +run(drawctxt: ref Draw->Context, nil: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + c := (hd args).c().i; + ctxt := Context.new(drawctxt); + ctxt.setlocal("s", nil); + { + ctxt.run(ref Sh->Listnode(c, nil)::nil, 0); + } exception e { + "fail:*" => + sys->fprint(sys->fildes(2), "fs: run: exception %q raised in %s\n", e[5:], sh->cmd2string(c)); + return nil; + } + sl := ctxt.get("s"); + if(sl == nil || tl sl != nil){ + sys->fprint(sys->fildes(2), "fs: run: $s has %d members; exactly one is required\n", len sl); + return nil; + } + s := (hd sl).word; + if(s == nil && (hd sl).cmd != nil) + s = sh->cmd2string((hd sl).cmd); + return ref Value.Vs(s); +} diff --git a/appl/alphabet/fs/select.b b/appl/alphabet/fs/select.b new file mode 100644 index 00000000..8a44104d --- /dev/null +++ b/appl/alphabet/fs/select.b @@ -0,0 +1,60 @@ +implement Select, Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; + Report: import Reports; +include "alphabet/fs.m"; + fs: Fs; + Value: import fs; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fs; + +Select: module {}; +types(): string +{ + return "ttp"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fs->init(); +} + +run(nil: ref Draw->Context, nil: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + dst := Entrychan(chan of int, chan of Entry); + spawn selectproc((hd args).t().i, dst, (hd tl args).p().i); + return ref Value.Vt(dst); +} + +selectproc(src, dst: Entrychan, query: Gatechan) +{ + if(<-dst.sync == 0){ + query <-= (Nilentry, nil); + src.sync <-= 0; + exit; + } + src.sync <-= 1; + reply := chan of int; + while((d := <-src.c).t0 != nil){ + query <-= (d, reply); + if(<-reply) + dst.c <-= d; + } + dst.c <-= Nilentry; + query <-= (Nilentry, nil); +} diff --git a/appl/alphabet/fs/setroot.b b/appl/alphabet/fs/setroot.b new file mode 100644 index 00000000..d04b7de5 --- /dev/null +++ b/appl/alphabet/fs/setroot.b @@ -0,0 +1,109 @@ +implement Setroot, Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; + Report: import Reports; +include "alphabet/fs.m"; + fs: Fs; + Value: import fs; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fs; + +Setroot: module {}; + +# set the root +types(): string +{ + return "xxs-c"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fs->init(); +} + +run(nil: ref Draw->Context, nil: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + root := (hd tl args).s().i; + if(root == nil && opts == nil){ + sys->fprint(sys->fildes(2), "fs: setroot: empty path\n"); + return nil; + } + v := ref Value.Vx(chan of (Fsdata, chan of int)); + spawn setroot((hd args).x().i, v.i, root, opts != nil); + return v; +} + +setroot(src, dst: Fschan, root: string, cflag: int) +{ + ((d, nil), reply) := <-src; + if(cflag){ + createroot(src, dst, root, d, reply); + }else{ + myreply := chan of int; + rd := ref *d; + rd.name = root; + dst <-= ((rd, nil), myreply); + if(<-myreply == Down){ + reply <-= Down; + fs->copy(src, dst); + } + } +} + +createroot(src, dst: Fschan, root: string, d: ref Sys->Dir, reply: chan of int) +{ + if(root == nil) + root = d.name; + (n, elems) := sys->tokenize(root, "/"); # XXX should really do a cleanname first + if(root[0] == '/'){ + elems = "/" :: elems; + n++; + } + myreply := chan of int; + lev := 0; + r := -1; + for(; elems != nil; elems = tl elems){ + rd := ref *d; + rd.name = hd elems; + dst <-= ((rd, nil), myreply); + case r = <-myreply { + Quit => + (<-src).t1 <-= Quit; + exit; + Skip => + break; + Next => + lev++; + break; + } + lev++; + } + if(r == Down){ + reply <-= Down; + if(fs->copy(src, dst) == Quit) + exit; + }else + reply <-= Quit; + while(lev-- > 1){ + dst <-= ((nil, nil), myreply); + if(<-myreply == Quit){ + (<-src).t1 <-= Quit; + exit; + } + } +} diff --git a/appl/alphabet/fs/size.b b/appl/alphabet/fs/size.b new file mode 100644 index 00000000..966bf957 --- /dev/null +++ b/appl/alphabet/fs/size.b @@ -0,0 +1,64 @@ +implement Size, Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; + reports: Reports; + Report: import reports; +include "alphabet/fs.m"; + fs: Fs; + Value: import fs; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fs; + +Size: module {}; + +types(): string +{ + return "ft"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + reports = load Reports Reports->PATH; + if(reports == nil) + badmod(Reports->PATH); + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fs->init(); +} + +run(nil: ref Draw->Context, report: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + f := chan of ref Sys->FD; + spawn sizeproc(f, (hd args).t().i, report.start("size")); + return ref Value.Vf(f); +} + +sizeproc(f: chan of ref Sys->FD, c: Entrychan, errorc: chan of string) +{ + f <-= nil; + if((fd := <-f) == nil){ + c.sync <-= 0; + exit; + } + c.sync <-= 1; + + size := big 0; + while(((d, nil, nil) := <-c.c).t0 != nil) + size += d.length; + sys->fprint(fd, "%bd\n", size); + sys->fprint(fd, ""); + errorc <-= nil; +} diff --git a/appl/alphabet/fs/unbundle.b b/appl/alphabet/fs/unbundle.b new file mode 100644 index 00000000..3de7e7e2 --- /dev/null +++ b/appl/alphabet/fs/unbundle.b @@ -0,0 +1,259 @@ +implement Unbundle, Fsmodule; +include "sys.m"; + sys: Sys; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "string.m"; + str: String; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; + reports: Reports; + Report, quit, report: import reports; +include "alphabet/fs.m"; + fs: Fs; + Value: import fs; + Fschan, Fsdata, Entrychan, Entry, + Quit, Next, Skip, Down, + Option: import Fs; + +Unbundle: module {}; +types(): string +{ + return "xf"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: exec: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fs->init(); + reports = load Reports Reports->PATH; + if(reports == nil) + badmod(Reports->PATH); + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + badmod(Bufio->PATH); + str = load String String->PATH; + if(str == nil) + badmod(String->PATH); +} + +run(nil: ref Draw->Context, report: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + f := (hd args).f().i; + c := ref Value.Vx(chan of (Fsdata, chan of int)); + spawn unbundleproc((hd args).f().i, nil, c.i, -1, Sys->ATOMICIO, report.start("unbundle")); + return c; +} + +# dodgy heuristic... avoid, or using the stat-length of pipes and net connections +isseekable(fd: ref Sys->FD): int +{ + (ok, stat) := sys->fstat(fd); + if(ok != -1 && stat.dtype == '|' || stat.dtype == 'I') + return 0; + return 1; +} + +EOF: con "end of archive\n"; + +unbundleproc(f: chan of ref Sys->FD, iob: ref Iobuf, c: Fschan, + seekable, blocksize: int, errorc: chan of string) +{ + if(f != nil){ + fd := <-f; + if(fd == nil){ + sys->pipe(p := array[2] of ref Sys->FD); + f <-= p[1]; + p[1] = nil; + fd = p[0]; + }else + f <-= nil; + if(seekable == -1) + seekable = isseekable(fd); + iob = bufio->fopen(fd, Sys->OREAD); + f = nil; + } + + reply := chan of int; + p := iob.gets('\n'); + # XXX overall header? + if(p == nil || p == EOF){ + fs->sendnulldir(c); + quit(errorc); + } + d := header2dir(p); + if(d == nil){ + fs->sendnulldir(c); + report(errorc, sys->sprint("invalid first header %q", p[0:len p - 1])); + quit(errorc); + } + if((d.mode & Sys->DMDIR) == 0){ + fs->sendnulldir(c); + report(errorc, "first entry is not a directory"); + quit(errorc); + } + c <-= ((d, nil), reply); + case r := <-reply { + Down => + unbundledir(iob, c, 0, seekable, blocksize, errorc); + c <-= ((nil, nil), reply); + <-reply; + Skip or + Next => + unbundledir(iob, c, 1, seekable, blocksize, errorc); + Quit => + break; + } + quit(errorc); +} + +unbundledir(iob: ref Iobuf, c: Fschan, + skipping, seekable, blocksize: int, errorc: chan of string): int +{ + reply := chan of int; + while((p := iob.gets('\n')) != nil){ + if(p == EOF) + break; + if(p[0] == '\n') + break; + d := header2dir(p); + if(d == nil){ + report(errorc, sys->sprint("invalid bundle header %q", p[0:len p - 1])); + return -1; + } + if(d.mode & Sys->DMDIR){ + if(skipping) + continue; + c <-= ((d, nil), reply); + case <-reply { + Quit => + quit(errorc); + Down => + r := unbundledir(iob, c, 0, seekable, blocksize, errorc); + c <-= ((nil, nil), reply); + if(<-reply == Quit) + quit(errorc); + if(r == -1) + return -1; + Skip => + if(unbundledir(iob, c, 1, seekable, blocksize, errorc) == -1) + return -1; + skipping = 1; + Next => + if(unbundledir(iob, c, 1, seekable, blocksize, errorc) == -1) + return -1; + } + }else{ + if(skipping){ + if(skipdata(iob, d.length, seekable) == -1) + return -1; + }else{ + case unbundlefile(iob, d, c, errorc, seekable, blocksize) { + -1 => + return -1; + Skip => + skipping = 1; + } + } + } + } + if(p == nil) + report(errorc, "unexpected eof"); + return 0; +} + +skipdata(iob: ref Iobuf, length: big, seekable: int): int +{ + if(seekable){ + iob.seek(big length, Sys->SEEKRELA); + return 0; + } + buf := array[Sys->ATOMICIO] of byte; + for(n := big 0; n < length; ){ + nb := Sys->ATOMICIO; + if(length - n < big Sys->ATOMICIO) + nb = int (length - n); + nb = iob.read(buf, nb); + if(nb <= 0) + return -1; + n += big nb; + } + return 0; +} + +unbundlefile(iob: ref Iobuf, d: ref Sys->Dir, + c: Fschan, errorc: chan of string, seekable, blocksize: int): int +{ + reply := chan of int; + c <-= ((d, nil), reply); + case <-reply { + Quit => + quit(errorc); + Skip => + if(skipdata(iob, d.length, seekable) == -1) + return -1; + return Skip; + Next => + if(skipdata(iob, d.length, seekable) == -1) + return -1; + return Next; + } + length := d.length; + for(n := big 0; n < length; ){ + nr := blocksize; + if(n + big blocksize > length) + nr = int (length - n); + buf := array[nr] of byte; + nr = iob.read(buf, nr); + if(nr <= 0){ + if(nr < 0) + report(errorc, sys->sprint("read error: %r")); + else + report(errorc, sys->sprint("premature eof")); + return -1; + }else if(nr < len buf) + buf = buf[0:nr]; + c <-= ((nil, buf), reply); + n += big nr; + case <-reply { + Quit => + quit(errorc); + Skip => + if(skipdata(iob, length - n, seekable) == -1) + return -1; + return Next; + } + } + c <-= ((nil, nil), reply); + if(<-reply == Quit) + quit(errorc); + return Next; +} + +header2dir(s: string): ref Sys->Dir +{ + toks := str->unquoted(s); + nf := len toks; + if(nf != 6) + return nil; + d := ref Sys->nulldir; + (d.name, toks) = (hd toks, tl toks); + (d.mode, toks) = (str->toint(hd toks, 8).t0, tl toks); + (d.uid, toks) = (hd toks, tl toks); + (d.gid, toks) = (hd toks, tl toks); + (d.mtime, toks) = (int hd toks, tl toks); + (d.length, toks) = (big hd toks, tl toks); + return d; +} diff --git a/appl/alphabet/fs/unbundle.m b/appl/alphabet/fs/unbundle.m new file mode 100644 index 00000000..cb93005b --- /dev/null +++ b/appl/alphabet/fs/unbundle.m @@ -0,0 +1,9 @@ +Unbundle: module { + PATH: con "/dis/fs/bundle.dis"; + + types: fn(): string; + init: fn(); + run: fn(nil: ref Draw->Context, report: ref Report, + nil: list of Option, args: list of ref Value): ref Value; + unbundle: fn(r: ref Reports->Report, iob: ref Bufio->Iobuf, seekable: int, blocksize: int): Fs->Fschan; +}; diff --git a/appl/alphabet/fs/walk.b b/appl/alphabet/fs/walk.b new file mode 100644 index 00000000..d99d0e9f --- /dev/null +++ b/appl/alphabet/fs/walk.b @@ -0,0 +1,242 @@ +implement Walk, Fsmodule; +include "sys.m"; + sys: Sys; +include "readdir.m"; + readdir: Readdir; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; + reports: Reports; + Report, quit, report: import reports; +include "alphabet/fs.m"; + fs: Fs; + Value: import fs; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fs; + +Walk: module {}; + +Loopcheck: adt { + a: array of list of ref Sys->Dir; + + new: fn(): ref Loopcheck; + enter: fn(l: self ref Loopcheck, d: ref Sys->Dir): int; + leave: fn(l: self ref Loopcheck, d: ref Sys->Dir); +}; + +types(): string +{ + return "xs-bs"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: walk: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fs->init(); + reports = load Reports Reports->PATH; + if(reports == nil) + badmod(Reports->PATH); + readdir = load Readdir Readdir->PATH; + if(readdir == nil) + badmod(Readdir->PATH); +} + +run(nil: ref Draw->Context, report: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + path := (hd args).s().i; + (ok, d) := sys->stat(path); + if(ok== -1){ + sys->fprint(sys->fildes(2), "fs: walk: cannot stat %q: %r\n", path); + return nil; + } + if((d.mode & Sys->DMDIR) == 0){ + # XXX could produce an fs containing just the single file. + # would have to split the path though. + sys->fprint(sys->fildes(2), "fs: walk: %q is not a directory\n", path); + return nil; + } + sync := chan of int; + c := chan of (Fsdata, chan of int); + spawn fswalkproc(sync, path, c, Sys->ATOMICIO, report.start("walk")); + <-sync; + return ref Value.Vx(c); +} + +# XXX need to avoid loops in the filesystem... +fswalkproc(sync: chan of int, path: string, c: Fschan, blocksize: int, errorc: chan of string) +{ + sys->pctl(Sys->FORKNS, nil); + sync <-= 1; + # XXX could allow a single root file? + if(sys->chdir(path) == -1){ + report(errorc, sys->sprint("cannot cd to %q: %r", path)); + fs->sendnulldir(c); + quit(errorc); + } + (ok, d) := sys->stat("."); + if(ok == -1){ + report(errorc, sys->sprint("cannot stat %q: %r", path)); + fs->sendnulldir(c); + quit(errorc); + } + d.name = path; + reply := chan of int; + c <-= ((ref d, nil), reply); + if(<-reply == Down){ + loopcheck := Loopcheck.new(); + loopcheck.enter(ref d); + if(path[len path - 1] != '/') + path[len path] = '/'; + fswalkdir(path, c, blocksize, loopcheck, errorc); + c <-= ((nil, nil), reply); + <-reply; + } + quit(errorc); +} + +fswalkdir(path: string, c: Fschan, blocksize: int, loopcheck: ref Loopcheck, errorc: chan of string) +{ + reply := chan of int; + (a, n) := readdir->init(".", Readdir->NAME|Readdir->COMPACT); + if(n == -1){ + report(errorc, sys->sprint("cannot readdir %q: %r", path)); + return; + } + for(i := 0; i < n; i++) + if(a[i].mode & Sys->DMDIR) + if(loopcheck.enter(a[i]) == 0) + a[i].dtype = ~0; +directory: + for(i = 0; i < n; i++){ + if(a[i].mode & Sys->DMDIR){ + d := a[i]; + if(d.dtype == ~0){ + report(errorc, sys->sprint("filesystem loop at %#q", path + d.name)); + continue; + } + if(sys->chdir("./" + d.name) == -1){ + report(errorc, sys->sprint("cannot cd to %#q: %r", path + a[i].name)); + continue; + } + c <-= ((d, nil), reply); + case <-reply { + Quit => + quit(errorc); + Down => + fswalkdir(path + a[i].name + "/", c, blocksize, loopcheck, errorc); + c <-= ((nil, nil), reply); + if(<-reply == Quit) + quit(errorc); + Skip => + sys->chdir(".."); + i++; + break directory; + Next => + break; + } + if(sys->chdir("..") == -1) # XXX what should we do if this fails? + report(errorc, sys->sprint("failed to cd .. from %#q: %r\n", path + a[i].name)); + + } else { + if(fswalkfile(path, a[i], c, blocksize, errorc) == Skip) + break directory; + } + } + for(i = n - 1; i >= 0; i--) + if(a[i].mode & Sys->DMDIR && a[i].dtype != ~0) + loopcheck.leave(a[i]); +} + +fswalkfile(path: string, d: ref Sys->Dir, c: Fschan, blocksize: int, errorc: chan of string): int +{ + reply := chan of int; + fd := sys->open(d.name, Sys->OREAD); + if(fd == nil){ + report(errorc, sys->sprint("cannot open %q: %r", path+d.name)); + return Next; + } + c <-= ((d, nil), reply); + case <-reply { + Quit => + quit(errorc); + Skip => + return Skip; + Next => + return Next; + Down => + break; + } + length := d.length; + for(n := big 0; n < length; ){ + nr := blocksize; + if(n + big blocksize > length) + nr = int (length - n); + buf := array[nr] of byte; + nr = sys->read(fd, buf, nr); + if(nr <= 0){ + if(nr < 0) + report(errorc, sys->sprint("error reading %q: %r", path + d.name)); + else + report(errorc, sys->sprint("%q is shorter than expected (%bd/%bd)", + path + d.name, n, length)); + break; + }else if(nr < len buf) + buf = buf[0:nr]; + c <-= ((nil, buf), reply); + case <-reply { + Quit => + quit(errorc); + Skip => + return Next; + } + n += big nr; + } + c <-= ((nil, nil), reply); + if(<-reply == Quit) + quit(errorc); + return Next; +} + +HASHSIZE: con 32; + +issamedir(d0, d1: ref Sys->Dir): int +{ + (q0, q1) := (d0.qid, d1.qid); + return q0.path == q1.path && + q0.qtype == q1.qtype && + d0.dtype == d1.dtype && + d0.dev == d1.dev; +} + +Loopcheck.new(): ref Loopcheck +{ + return ref Loopcheck(array[HASHSIZE] of list of ref Sys->Dir); +} + +# XXX we're assuming no-one modifies the values in d behind our back... +Loopcheck.enter(l: self ref Loopcheck, d: ref Sys->Dir): int +{ + slot := int d.qid.path & (HASHSIZE-1); + for(ll := l.a[slot]; ll != nil; ll = tl ll) + if(issamedir(d, hd ll)) + return 0; + l.a[slot] = d :: l.a[slot]; + return 1; +} + +Loopcheck.leave(l: self ref Loopcheck, d: ref Sys->Dir) +{ + slot := int d.qid.path & (HASHSIZE-1); + l.a[slot] = tl l.a[slot]; +} diff --git a/appl/alphabet/fs/write.b b/appl/alphabet/fs/write.b new file mode 100644 index 00000000..272c2f71 --- /dev/null +++ b/appl/alphabet/fs/write.b @@ -0,0 +1,137 @@ +implement Write, Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "alphabet/reports.m"; + reports: Reports; + Report, report: import reports; +include "alphabet/fs.m"; + fs: Fs; + Value: import fs; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Option, + Next, Down, Skip, Quit: import Fs; + +Write: module {}; +types(): string +{ + return "rxs-v"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: write: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fs = load Fs Fs->PATH; + if(fs == nil) + badmod(Fs->PATH); + fs->init(); + reports = load Reports Reports->PATH; + if(reports == nil) + badmod(Reports->PATH); +} + +run(nil: ref Draw->Context, report: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + sync := chan of string; + spawn fswriteproc(sync, (hd tl args).s().i, (hd args).x().i, report.start("fswrite"), opts!=nil); + <-sync; + return ref Value.Vr(sync); +} + +fswriteproc(sync: chan of string, root: string, c: Fschan, errorc: chan of string, verbose: int) +{ + sys->pctl(Sys->FORKNS, nil); + sync <-= nil; + if(<-sync != nil){ + (<-c).t1 <-= Quit; + quit(sync, errorc); + } + + (d, reply) := <-c; + if(root != nil){ + d.dir = ref *d.dir; + d.dir.name = root; + } + fswritedir(d.dir.name, d, reply, c, errorc, verbose); + quit(sync, errorc); +} + +quit(sync: chan of string, errorc: chan of string) +{ + errorc <-= nil; + sync <-= nil; + exit; +} + +fswritedir(path: string, d: Fsdata, dreply: chan of int, c: Fschan, errorc: chan of string, verbose: int) +{ + fd: ref Sys->FD; + if(verbose) + report(errorc, sys->sprint("create %q %uo", path, d.dir.mode)); + if(d.dir.mode & Sys->DMDIR){ + created := 1; + fd = sys->create(d.dir.name, Sys->OREAD, d.dir.mode|8r777); + if(fd == nil){ + err := sys->sprint("%r"); + if((fd = sys->open(d.dir.name, Sys->OREAD)) == nil){ + dreply <-= Next; + report(errorc, sys->sprint("cannot create %q, mode %uo: %s", path, d.dir.mode|8r300, err)); + return; + }else + created = 0; + } + if(sys->chdir(d.dir.name) == -1){ # XXX beware of names starting with '#' + dreply <-= Next; + report(errorc, sys->sprint("cannot cd to %q: %r", path)); + fd = nil; + sys->remove(d.dir.name); + return; + } + dreply <-= Down; + path[len path] = '/'; + for(;;){ + (ent, reply) := <-c; + if(ent.dir == nil){ + reply <-= Next; + break; + } + fswritedir(path + ent.dir.name, ent, reply, c, errorc, verbose); + } + sys->chdir(".."); + if(created && (d.dir.mode & 8r777) != 8r777){ + ws := Sys->nulldir; + ws.mode = d.dir.mode; + if(sys->fwstat(fd, ws) == -1) + report(errorc, sys->sprint("cannot wstat %q: %r", path)); + } + }else{ + fd = sys->create(d.dir.name, Sys->OWRITE, d.dir.mode); + if(fd == nil){ + dreply <-= Next; + report(errorc, sys->sprint("cannot create %q, mode %uo: %r", path, d.dir.mode|8r300)); + return; + } + dreply <-= Down; + while((((nil, buf), reply) := <-c).t0.data != nil){ + nw := sys->write(fd, buf, len buf); + if(nw < len buf){ + if(nw == -1) + errorc <-= sys->sprint("error writing %q: %r", path); + else + errorc <-= sys->sprint("short write"); + reply <-= Skip; + break; + } + reply <-= Next; + } + reply <-= Next; + } +} |
