summaryrefslogtreecommitdiff
path: root/appl/cmd/fs
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
commit37da2899f40661e3e9631e497da8dc59b971cbd0 (patch)
treecbc6d4680e347d906f5fa7fca73214418741df72 /appl/cmd/fs
parent54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff)
20060303a
Diffstat (limited to 'appl/cmd/fs')
-rw-r--r--appl/cmd/fs/and.b65
-rw-r--r--appl/cmd/fs/bundle.b195
-rw-r--r--appl/cmd/fs/chstat.b185
-rw-r--r--appl/cmd/fs/compose.b100
-rw-r--r--appl/cmd/fs/depth.b49
-rw-r--r--appl/cmd/fs/entries.b86
-rw-r--r--appl/cmd/fs/eval.b648
-rw-r--r--appl/cmd/fs/exec.b162
-rw-r--r--appl/cmd/fs/filter.b64
-rw-r--r--appl/cmd/fs/ls.b97
-rw-r--r--appl/cmd/fs/match.b79
-rw-r--r--appl/cmd/fs/merge.b187
-rw-r--r--appl/cmd/fs/mergewrite.b186
-rw-r--r--appl/cmd/fs/mkfile60
-rw-r--r--appl/cmd/fs/mode.b120
-rw-r--r--appl/cmd/fs/not.b48
-rw-r--r--appl/cmd/fs/or.b65
-rw-r--r--appl/cmd/fs/path.b77
-rw-r--r--appl/cmd/fs/pipe.b223
-rw-r--r--appl/cmd/fs/print.b51
-rw-r--r--appl/cmd/fs/proto.b388
-rw-r--r--appl/cmd/fs/query.b130
-rw-r--r--appl/cmd/fs/readfile.b144
-rw-r--r--appl/cmd/fs/run.b60
-rw-r--r--appl/cmd/fs/select.b56
-rw-r--r--appl/cmd/fs/setroot.b104
-rw-r--r--appl/cmd/fs/size.b54
-rw-r--r--appl/cmd/fs/template.b35
-rw-r--r--appl/cmd/fs/unbundle.b259
-rw-r--r--appl/cmd/fs/void.b33
-rw-r--r--appl/cmd/fs/walk.b233
-rw-r--r--appl/cmd/fs/write.b111
32 files changed, 4354 insertions, 0 deletions
diff --git a/appl/cmd/fs/and.b b/appl/cmd/fs/and.b
new file mode 100644
index 00000000..ff867409
--- /dev/null
+++ b/appl/cmd/fs/and.b
@@ -0,0 +1,65 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+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.P(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/cmd/fs/bundle.b b/appl/cmd/fs/bundle.b
new file mode 100644
index 00000000..a4b1cee5
--- /dev/null
+++ b/appl/cmd/fs/bundle.b
@@ -0,0 +1,195 @@
+implement Bundle;
+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 "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, report, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+include "bundle.m";
+
+# 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 "vx";
+}
+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!
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Readdir->PATH);
+}
+
+run(nil: ref Draw->Context, report: ref Report,
+ nil: list of Option, args: list of ref Value): ref Value
+{
+ return ref Value.V(
+ bundle(
+ report,
+ bufio->fopen(sys->fildes(1), Sys->OWRITE),
+ (hd args).x().i
+ )
+ );
+}
+
+bundle(r: ref Report, iob: ref Iobuf, c: Fschan): chan of int
+{
+ sync := chan of int;
+ spawn bundleproc(c, sync, iob, r.start("bundle"));
+ return sync;
+}
+
+bundleproc(c: Fschan, sync: chan of int, iob: ref Iobuf, errorc: chan of string)
+{
+ if(sync != nil && <-sync == 0){
+ (<-c).t1 <-= Quit;
+ quit(errorc);
+ }
+ (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();
+ quit(errorc);
+ exit;
+}
+
+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" =>
+ 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/cmd/fs/chstat.b b/appl/cmd/fs/chstat.b
new file mode 100644
index 00000000..e549527e
--- /dev/null
+++ b/appl/cmd/fs/chstat.b
@@ -0,0 +1,185 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fsfilter: Fsfilter;
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ fsfilter = load Fsfilter Fsfilter->PATH;
+ if(fsfilter == nil)
+ badmod(Fsfilter->PATH);
+}
+
+run(nil: ref Draw->Context, nil: ref 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.discard();
+ 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.discard();
+ 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.X(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/cmd/fs/compose.b b/appl/cmd/fs/compose.b
new file mode 100644
index 00000000..69187d6b
--- /dev/null
+++ b/appl/cmd/fs/compose.b
@@ -0,0 +1,100 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Cmpchan,
+ Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+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.M(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/cmd/fs/depth.b b/appl/cmd/fs/depth.b
new file mode 100644
index 00000000..19c03b2d
--- /dev/null
+++ b/appl/cmd/fs/depth.b
@@ -0,0 +1,49 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+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.P(c);
+}
+
+depthgate(c: Gatechan, d: int)
+{
+ while((((dir, nil, depth), reply) := <-c).t0.t0 != nil)
+ reply <-= depth <= d;
+}
diff --git a/appl/cmd/fs/entries.b b/appl/cmd/fs/entries.b
new file mode 100644
index 00000000..56aac67f
--- /dev/null
+++ b/appl/cmd/fs/entries.b
@@ -0,0 +1,86 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+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.T(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/cmd/fs/eval.b b/appl/cmd/fs/eval.b
new file mode 100644
index 00000000..5eaf9291
--- /dev/null
+++ b/appl/cmd/fs/eval.b
@@ -0,0 +1,648 @@
+implement Eval;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ sh: Sh;
+ Context: import sh;
+include "readdir.m";
+#include "env.m";
+# env: Env;
+#include "string.m";
+# str: String;
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Quit: import Fslib;
+
+# more general:
+# eval: fn[V, M](ctxt: ref Context, r: ref Report, expr: string, args:...) with {
+# V =>
+# typec: fn(t: self V): int;
+# cvt: fn(t: self V, tc: int): V;
+# cvt2s: fn(t: self V): (int, string);
+# cvt2v: fn(t: self V): chan of int;
+# mkstring: fn(s: string): V;
+# mkcmd: fn(c: ref Sh->Cmd): V;
+# discard: fn(t: self V);
+# type2s: fn(c: int): string;
+# loadmod: fn(cmd: string): M;
+# M =>
+# types: fn(): string;
+# init: fn();
+# run: fn(ctxt: ref Draw->Context, r: ref Report, cmd: string,
+# opts: list of (int, list of V), args: list of V): V;
+# }
+# how to call eval?
+# (eval with [V=>ref Value, M=>Fsmodule])(
+#
+# sort out error reporting; stderr is not good.
+
+
+# possible things to do:
+# pipe [-1pP] [-t command] command fs -> void
+# pipe all files in fs through command.
+# extract [-r root] gate fs -> fs
+# extract the first entry within fs which
+# passes through the gate.
+# if -r is specified, the entry is placed
+# within the given root, and may be a file,
+# otherwise files are not allowed.
+# apply string fs
+# for each file in fs, evaluates string as an fs expression
+# (which should yield fs), and replace the file in the
+# original hierarchy with the result.
+# e.g.
+# fs apply '{unbundle $file}' {filter {or {mode +d} *.bundle} .}
+# a bit fanciful this...
+# merge could take an optional boolean operator
+#
+# venti?
+#
+# Cmpgate: chan of Cmpgatequery;
+# Cmpgatequery: type (Entry, Entry, chan of int);
+# returns 00, 01, 10 or 11
+# used by merge to decide what to do when merging
+# used by write to decide what to do when writing
+#
+# cmpdate [-u] '>'
+# cmpquery command
+
+Eval: module {
+ types: fn(): string;
+ init: fn();
+ run: fn(ctxt: ref Draw->Context, r: ref Fslib->Report,
+ opts: list of Fslib->Option, args: list of ref Fslib->Value): ref Fslib->Value;
+ eval: fn(ctxt: ref Draw->Context, r: ref Fslib->Report,
+ expr: string, args: list of ref Fslib->Value, ret: int): ref Fslib->Value;
+};
+
+WORD, SHCMD, VAR: con iota;
+
+Evalstate: adt {
+ s: string;
+ spos: int;
+ drawctxt: ref Draw->Context;
+ report: ref Report;
+ args: array of ref Value;
+ verbose: int;
+
+ expr: fn(p: self ref Evalstate): ref Value;
+ getc: fn(p: self ref Evalstate): int;
+ ungetc: fn(p: self ref Evalstate);
+ gettok: fn(p: self ref Evalstate): (int, string);
+};
+
+ops: list of (string, Fsmodule);
+lock: chan of int;
+
+# to do:
+# - change value letters to more appropriate (e.g. fs->f, entries->e, gate->g).
+# - allow shell $variable expansions
+
+types(): string
+{
+ return "as-v";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: eval: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ fslib->init();
+# env = load Env Env->PATH;
+# if(env == nil)
+# badmod(Env->PATH);
+# str = load String String->PATH;
+# if(str == nil)
+# badmod(String->PATH);
+ lock = chan[1] of int;
+}
+
+run(ctxt: ref Draw->Context, report: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ return (ref Evalstate((hd args).s().i, 0, ctxt, report, nil, opts != nil)).expr();
+}
+
+eval(ctxt: ref Draw->Context, report: ref Report,
+ expr: string, args: list of ref Value, rtype: int): ref Value
+{
+ a := array[len args] of ref Value;
+ for(i := 0; args != nil; args = tl args)
+ a[i++] = hd args;
+ e := ref Evalstate(expr, 0, ctxt, report, a, 0);
+ v := e.expr();
+ vl: list of ref Value;
+ for(i = 0; i < len a; i++)
+ if(a[i] != nil)
+ vl = a[i] :: vl;
+ nv := cvt(e, v, rtype);
+ if(nv == nil){
+ vl = v :: vl;
+ sys->fprint(stderr(), "fs: eval fn: %s cannot be converted to %s\n",
+ type2s(v.typec()), type2s(rtype));
+ }
+ if(vl != nil)
+ spawn discard(nil, vl);
+ return nv;
+}
+
+tok2s(t: int, s: string): string
+{
+ case t {
+ WORD =>
+ return s;
+ SHCMD =>
+ return "@";
+ VAR =>
+ return "$" + s;
+ }
+ return sys->sprint("%c", t);
+}
+
+# expr: WORD exprs
+# exprs:
+# | exprs '{' expr '}'
+# | exprs WORD
+# | exprs SHCMD
+# | exprs VAR
+Evalstate.expr(p: self ref Evalstate): ref Value
+{
+ args: list of ref Value;
+ t: int;
+ s: string;
+ {
+ (t, s) = p.gettok();
+ } exception e {
+ "parse error" =>
+ return nil;
+ }
+ if(t != WORD){
+ sys->fprint(stderr(), "fs: eval: syntax error (char %d), expected word, found %#q\n",
+ p.spos, tok2s(t, s));
+ return nil;
+ }
+ cmd := s;
+loop:
+ for(;;){
+ {
+ (t, s) = p.gettok();
+ } exception e {
+ "parse error" =>
+ spawn discard(nil, args);
+ return nil;
+ }
+ case t {
+ '{' =>
+ v := p.expr();
+ if(v == nil){
+ spawn discard(nil, args);
+ return nil;
+ }
+ args = v :: args;
+ '}' =>
+ break loop;
+ WORD =>
+ args = ref Value.S(s) :: args;
+ VAR =>
+ n := int s;
+ if(n < 0 || n >= len p.args){
+ sys->fprint(stderr(), "fs: eval: invalid arg reference $%s\n", s);
+ spawn discard(nil, args);
+ return nil;
+ }
+ if(p.args[n] == nil){
+ sys->fprint(stderr(), "fs: eval: cannot use $%d twice\n", n);
+ spawn discard(nil, args);
+ return nil;
+ }
+ args = p.args[n] :: args;
+ p.args[n] = nil;
+ SHCMD =>
+ if(sh == nil && (sh = load Sh Sh->PATH) == nil){
+ sys->fprint(stderr(), "fs: eval: cannot load %s: %r\n", Sh->PATH);
+ spawn discard(nil, args);
+ return nil;
+ }
+ (c, err) := sh->parse(s);
+ if(c == nil){
+ sys->fprint(stderr(), "fs: eval: cannot parse shell command @%s: %s\n", s, err);
+ spawn discard(nil, args);
+ return nil;
+ }
+ args = ref Value.C(c) :: args;
+ -1 =>
+ break loop;
+ * =>
+ spawn discard(nil, args);
+ sys->fprint(stderr(), "fs: eval: syntax error; unexpected token %d before char %d\n", t, p.spos);
+ return nil;
+ }
+ }
+ return runcmd(p, cmd, rev(args));
+}
+
+runcmd(p: ref Evalstate, cmd: string, args: list of ref Value): ref Value
+{
+ m := loadmodule(cmd);
+ if(m == nil){
+ spawn discard(nil, args);
+ return nil;
+ }
+ otype := m->types();
+ ok: int;
+ opts: list of Option;
+ (ok, opts, args) = cvtargs(p, args, cmd, otype);
+ if(ok == -1){
+ sys->fprint(stderr(), "fs: eval: usage: %s\n", fslib->cmdusage(cmd, otype));
+ spawn discard(opts, args);
+ return nil;
+ }
+ r := m->run(p.drawctxt, p.report, opts, args);
+ if(r == nil)
+ spawn discard(opts, args);
+ return r;
+}
+
+cvtargs(e: ref Evalstate, args: list of ref Value, cmd, otype: string): (int, list of Option, list of ref Value)
+{
+ ok: int;
+ opts: list of Option;
+ (nil, at, t) := fslib->splittype(otype);
+ (ok, opts, args) = cvtopts(e, t, cmd, args);
+ if(ok == -1)
+ return (-1, opts, args);
+ if(len at < 1 || at[0] == '*'){
+ sys->fprint(stderr(), "fs: eval: invalid type descriptor %#q for %#q\n", at, cmd);
+ return (-1, opts, args);
+ }
+ n := len args;
+ if(at[len at - 1] == '*'){
+ tc := at[len at - 2];
+ at = at[0:len at - 2];
+ for(i := len at; i < n; i++)
+ at[i] = tc;
+ }
+ if(n != len at){
+ sys->fprint(stderr(), "fs: eval: wrong number of arguments to %#q\n", cmd);
+ return (-1, opts, args);
+ }
+ d: list of ref Value;
+ (ok, args, d) = cvtvalues(e, at, cmd, args);
+ if(ok == -1)
+ args = join(args, d);
+ return (ok, opts, args);
+}
+
+cvtvalues(e: ref Evalstate, t: string, cmd: string, args: list of ref Value): (int, list of ref Value, list of ref Value)
+{
+ cargs: list of ref Value;
+ for(i := 0; i < len t; i++){
+ tc := t[i];
+ if(args == nil){
+ sys->fprint(stderr(), "fs: eval: %q missing argument of type %s\n", cmd, type2s(tc));
+ return (-1, cargs, args);
+ }
+ v := cvt(e, hd args, tc);
+ if(v == nil){
+ sys->fprint(stderr(), "fs: eval: %q: %s cannot be converted to %s\n",
+ cmd, type2s((hd args).typec()), type2s(tc));
+ return (-1, cargs, args);
+ }
+ cargs = v :: cargs;
+ args = tl args;
+ }
+ return (0, rev(cargs), args);
+}
+
+cvtopts(e: ref Evalstate, opttype: string, cmd: string, args: list of ref Value): (int, list of Option, list of ref Value)
+{
+ if(opttype == nil)
+ return (0, nil, args);
+ opts: list of Option;
+getopts:
+ while(args != nil){
+ s := "";
+ pick v := hd args {
+ S =>
+ s = v.i;
+ if(s == nil || s[0] != '-' || len s == 1)
+ s = nil;
+ else if(s == "--"){
+ args = tl args;
+ s = nil;
+ }
+ }
+ if(s == nil)
+ return (0, opts, args);
+ s = s[1:];
+ while(len s > 0){
+ opt := s[0];
+ if(((ok, t) := fslib->opttypes(opt, opttype)).t0 == -1){
+ sys->fprint(stderr(), "fs: eval: %s: unknown option -%c\n", cmd, opt);
+ return (-1, opts, args);
+ }
+ if(t == nil){
+ s = s[1:];
+ opts = (opt, nil) :: opts;
+ }else{
+ if(len s > 1)
+ args = ref Value.S(s[1:]) :: tl args;
+ else
+ args = tl args;
+ vl: list of ref Value;
+ (ok, vl, args) = cvtvalues(e, t, cmd, args);
+ if(ok == -1)
+ return (-1, opts, join(vl, args));
+ opts = (opt, vl) :: opts;
+ continue getopts;
+ }
+ }
+ args = tl args;
+ }
+ return (0, opts, args);
+}
+
+discard(ol: list of (int, list of ref Value), vl: list of ref Value)
+{
+ for(; ol != nil; ol = tl ol)
+ for(ovl := (hd ol).t1; ovl != nil; ovl = tl ovl)
+ vl = (hd ovl) :: vl;
+ for(; vl != nil; vl = tl vl)
+ (hd vl).discard();
+}
+
+loadmodule(cmd: string): Fsmodule
+{
+ lock <-= 0;
+ for(ol := ops; ol != nil; ol = tl ol)
+ if((hd ol).t0 == cmd)
+ break;
+ if(ol != nil){
+ <-lock;
+ return (hd ol).t1;
+ }
+ p := cmd + ".dis";
+ if(p[0] != '/' && !(p[0] == '.' && p[1] == '/'))
+ p = "/dis/fs/" + p;
+ m := load Fsmodule p;
+ if(m == nil){
+ sys->fprint(stderr(), "fs: eval: cannot load %s: %r\n", p);
+ sys->fprint(stderr(), "fs: eval: unknown verb %#q\n", cmd);
+ sys->werrstr(sys->sprint("cannot load module %q", cmd));
+ <-lock;
+ return nil;
+ }
+ {
+ m->init();
+ } exception e {
+ "fail:*" =>
+ <-lock;
+ sys->werrstr(sys->sprint("module init failed: %s", e[5:]));
+ return nil;
+ }
+ ops = (cmd, m) :: ops;
+ <-lock;
+ return m;
+}
+
+runexternal(p: ref Evalstate, cmd: string, t: string, opts: list of Option, args: list of ref Value): ref Value
+{
+ m := loadmodule(cmd);
+ if(m == nil)
+ return nil;
+ if(!fslib->typecompat(t, m->types())){
+ sys->fprint(stderr(), "fs: eval: %s has incompatible type\n", cmd);
+ sys->fprint(stderr(), "fs: eval: expected usage: %s\n", fslib->cmdusage(cmd, t));
+ sys->fprint(stderr(), "fs: eval: actually usage: %s\n", fslib->cmdusage(cmd, m->types()));
+ return nil;
+ }
+ return m->run(p.drawctxt, p.report, opts, args);
+}
+
+cvt(e: ref Evalstate, v: ref Value, t: int): ref Value
+{
+ {
+ return cvt1(e, v, t);
+ } exception {
+ "type conversion" =>
+ return nil;
+ }
+}
+
+cvt1(e: ref Evalstate, v: ref Value, t: int): ref Value
+{
+ if(v.typec() == t)
+ return v;
+ r: ref Value;
+ case t {
+ 't' =>
+ r = runexternal(e, "entries", "tx", nil, cvt1(e, v, 'x') :: nil);
+ 'x' =>
+ r = runexternal(e, "walk", "xs", nil, cvt1(e, v, 's') :: nil);
+ 'p' =>
+ r = runexternal(e, "match", "ps", nil, cvt1(e, v, 's') :: nil);
+ 's' =>
+ r = runexternal(e, "run", "sc", nil, cvt1(e, v, 'c') :: nil);
+ 'v' =>
+ r = runexternal(e, "print", "vt", nil, cvt1(e, v, 't') :: nil);
+ }
+ if(r == nil)
+ raise "type conversion";
+ return r;
+}
+
+Evalstate.getc(p: self ref Evalstate): int
+{
+ c := -1;
+ if(p.spos < len p.s)
+ c = p.s[p.spos];
+ p.spos++;
+ return c;
+}
+
+Evalstate.ungetc(p: self ref Evalstate)
+{
+ p.spos--;
+}
+
+# XXX backslash escapes newline?
+Evalstate.gettok(p: self ref Evalstate): (int, string)
+{
+ while ((c := p.getc()) == ' ' || c == '\t')
+ ;
+ t: int;
+ s: string;
+
+ case c {
+ -1 =>
+ t = -1;
+ '\n' =>
+ t = '\n';
+ '{' =>
+ t = '{';
+ '}' =>
+ t = '}';
+ '@' => # embedded shell command
+ while((nc := p.getc()) == ' ' || nc == '\t')
+ ;
+ if(nc != '{'){
+ sys->fprint(stderr(), "fs: eval: expected '{' after '@'\n");
+ raise "parse error";
+ }
+ s = "{";
+ d := 1;
+ getcmd:
+ while((nc = p.getc()) != -1){
+ s[len s] = nc;
+ case nc {
+ '{' =>
+ d++;
+ '}' =>
+ if(--d == 0)
+ break getcmd;
+ '\'' =>
+ s += getqword(p, 1);
+ }
+ }
+ if(nc == -1){
+ sys->fprint(stderr(), "fs: eval: unbalanced '{' in shell command\n");
+ raise "parse error";
+ }
+ t = SHCMD;
+ '$' =>
+ t = VAR;
+ s = getvar(p);
+ '\'' =>
+ s = getqword(p, 0);
+ t = WORD;
+ * =>
+ do {
+ s[len s] = c;
+ c = p.getc();
+ if (in(c, " \t{}\n")){
+ p.ungetc();
+ break;
+ }
+ } while (c >= 0);
+ t = WORD;
+ }
+ return (t, s);
+}
+
+getvar(p: ref Evalstate): string
+{
+ c := p.getc();
+ if(c == -1){
+ sys->fprint(stderr(), "fs: eval: unexpected eof after '$'\n");
+ raise "parse error";
+ }
+ v: string;
+ while(in(c, " \t\n@{}'") == 0){
+ v[len v] = c;
+ c = p.getc();
+ }
+ p.ungetc();
+ for(i := 0; i < len v; i++)
+ if(v[i] < '0' || v[i] > '9')
+ break;
+ if(i < len v || v == nil){
+ sys->fprint(stderr(), "fs: eval: invalid $ reference $%q\n", v);
+ raise "parse error";
+ }
+ return v;
+}
+# v: string;
+# if(c == '\''){
+# v = getqword(p, 0);
+# c = p.getc();
+# } else{
+# v[0] = c;
+# while((c = p.getc()) != -1){
+# if(in(c, "a-zA-Z0-9*_") == 0) # heuristic stolen from rc
+# break;
+# v[len v] = c;
+# }
+# }
+# vl := str->unquoted(env->getenv(v));
+# if(vl == nil){
+# sys->fprint(stderr(), "fs: eval: shell variable $%q has %d elements\n", v, len vl);
+# raise "parse error";
+# }
+# val := hd vl;
+# if(c == -1 || in(c, " \t@{}\n")){
+# p.ungetc();
+# return (WORD, val);
+# }
+# (t, s) = p.gettok();
+# if(t != WORD){
+# sys->fprint(stderr(), "fs: eval: expected word after $%q\n", v);
+# raise "parse error";
+# }
+# s = val + s;
+#}
+
+in(c: int, s: string): int
+{
+ for(i := 0; i < len s; i++)
+ if(s[i] == c)
+ return 1;
+ return 0;
+}
+
+# get a quoted word; the starting quote has already been seen
+getqword(p: ref Evalstate, keepq: int): string
+{
+ s := "";
+ for(;;) {
+ while ((nc := p.getc()) != '\'' && nc >= 0)
+ s[len s] = nc;
+ if (nc == -1){
+ sys->fprint(stderr(), "fs: eval: unterminated quote\n");
+ raise "parse error";
+ }
+ if (p.getc() != '\'') {
+ p.ungetc();
+ if(keepq)
+ s[len s] = '\'';
+ return s;
+ }
+ s[len s] = '\''; # 'xxx''yyy' becomes WORD(xxx'yyy)
+ if(keepq)
+ s[len 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;
+}
+
+# join x to y, leaving result in arbitrary order.
+join[T](x, y: list of T): list of T
+{
+ if(len x > len y)
+ (x, y) = (y, x);
+ for(; x != nil; x = tl x)
+ y = hd x :: y;
+ return y;
+}
+
+stderr(): ref Sys->FD
+{
+ return sys->fildes(2);
+}
diff --git a/appl/cmd/fs/exec.b b/appl/cmd/fs/exec.b
new file mode 100644
index 00000000..60beb74e
--- /dev/null
+++ b/appl/cmd/fs/exec.b
@@ -0,0 +1,162 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ sh: Sh;
+ Context: import sh;
+include "fslib.m";
+ fslib: Fslib;
+ Option, Value, Entrychan, Report: import fslib;
+
+# usage: exec [-n nfiles] [-t endcmd] [-pP] command entries
+types(): string
+{
+ return "vct-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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->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 args).c().i;
+ c := (hd tl args).t().i;
+ sync := chan of int;
+ spawn execproc(drawctxt, sync, n, pflag, c, cmd, tcmd, report.start("exec"));
+ sync <-= 1;
+ return ref Value.V(sync);
+}
+
+execproc(drawctxt: ref Draw->Context, sync: chan of int, 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 == 0){
+ 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;
+}
+
+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/cmd/fs/filter.b b/appl/cmd/fs/filter.b
new file mode 100644
index 00000000..9275cc7f
--- /dev/null
+++ b/appl/cmd/fs/filter.b
@@ -0,0 +1,64 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fsfilter: Fsfilter;
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+
+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 "xpx-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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ fsfilter = load Fsfilter Fsfilter->PATH;
+ if(fsfilter == nil)
+ badmod(Fsfilter->PATH);
+}
+
+run(nil: ref Draw->Context, nil: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ dst := chan of (Fsdata, chan of int);
+ spawn filterproc((hd tl args).x().i, dst, (hd args).p().i, opts != nil);
+ return ref Value.X(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/cmd/fs/ls.b b/appl/cmd/fs/ls.b
new file mode 100644
index 00000000..70beae48
--- /dev/null
+++ b/appl/cmd/fs/ls.b
@@ -0,0 +1,97 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "daytime.m";
+ daytime: Daytime;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Option, Value, Entrychan, Report: import fslib;
+
+types(): string
+{
+ return "vt-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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ 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
+{
+ sync := chan of int;
+ spawn lsproc(sync, opts, (hd args).t().i, daytime, report.start("ls"));
+ return ref Value.V(sync);
+}
+
+lsproc(sync: chan of int, opts: list of Option, c: Entrychan, daytime: Daytime, errorc: chan of string)
+{
+ now := daytime->now();
+ mflag := uflag := 0;
+ if(<-sync == 0){
+ c.sync <-= 0;
+ errorc <-= nil;
+ }
+ 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->print("%s", s);
+ }
+ errorc <-= 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/cmd/fs/match.b b/appl/cmd/fs/match.b
new file mode 100644
index 00000000..331867a9
--- /dev/null
+++ b/appl/cmd/fs/match.b
@@ -0,0 +1,79 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "filepat.m";
+ filepat: Filepat;
+include "regex.m";
+ regex: Regex;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ 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.P(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/cmd/fs/merge.b b/appl/cmd/fs/merge.b
new file mode 100644
index 00000000..977102b2
--- /dev/null
+++ b/appl/cmd/fs/merge.b
@@ -0,0 +1,187 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s: import fslib;
+ Fschan, Fsdata, Entrychan, Cmpchan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+# 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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil){
+ sys->fprint(sys->fildes(2), "fs: cannot load %s: %r\n", Fslib->PATH);
+ raise "fail:bad module";
+ }
+}
+
+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.X(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 = fslib->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/cmd/fs/mergewrite.b b/appl/cmd/fs/mergewrite.b
new file mode 100644
index 00000000..3ff1b1f1
--- /dev/null
+++ b/appl/cmd/fs/mergewrite.b
@@ -0,0 +1,186 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "readdir.m";
+ readdir: Readdir;
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, quit, report: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Cmpchan, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+types(): string
+{
+ return "vmsx"; # XXX bad argument ordering...
+}
+
+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);
+
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil){
+ sys->fprint(sys->fildes(2), "fs: mergewrite: cannot load %s: %r\n", Fslib->PATH);
+ raise "fail:bad module";
+ }
+}
+
+run(nil: ref Draw->Context, report: ref Report,
+ nil: list of Option, args: list of ref Value): ref Value
+{
+ sync := chan of int;
+ spawn fswriteproc(sync, (hd args).m().i, (hd tl args).s().i, (hd tl tl args).x().i, report.start("mergewrite"));
+ <-sync;
+ return ref Value.V(sync);
+}
+
+fswriteproc(sync: chan of int, cmp: Cmpchan, root: string, c: Fschan, errorc: chan of string)
+{
+ sys->pctl(Sys->FORKNS, nil);
+ sync <-= 1;
+ if(<-sync == 0){
+ (<-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);
+ quit(errorc);
+}
+
+fswritedir(path: string, cmp: Cmpchan, dir: ref Sys->Dir, dreply: chan of int, c: Fschan, errorc: chan of string)
+{
+ fd: ref Sys->FD;
+ if(dir.mode & Sys->DMDIR){
+ 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 && (r & 2r10) == 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);
+ d0 = nil;
+ }
+ }
+ sys->chdir("..");
+ 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{
+ 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/cmd/fs/mkfile b/appl/cmd/fs/mkfile
new file mode 100644
index 00000000..37fb5e12
--- /dev/null
+++ b/appl/cmd/fs/mkfile
@@ -0,0 +1,60 @@
+<../../../mkconfig
+# fs write /n/local/n/fossil/usr/inferno {filter {and {not {or *.dis *.sbl}} {path /appl/cmd/fs /module/fslib.m /appl/lib/fslib.b /appl/cmd/fs.b /man/1/fs}} /}
+TARG=\
+ and.dis\
+ bundle.dis\
+ chstat.dis\
+ compose.dis\
+ depth.dis\
+ entries.dis\
+ eval.dis\
+ exec.dis\
+ filter.dis\
+ ls.dis\
+ match.dis\
+ merge.dis\
+ mergewrite.dis\
+ mode.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\
+ void.dis\
+
+
+INS= ${TARG:%=$ROOT/dis/fs/%}
+
+SYSMODULES=\
+ bufio.m\
+ draw.m\
+ sh.m\
+ sys.m\
+ bundle.m\
+ fslib.m\
+
+DISBIN=$ROOT/dis/fs
+
+<$ROOT/mkfiles/mkdis
+
+all:V: $TARG
+
+install:V: $INS
+
+nuke:V: clean
+ rm -f $INS
+
+clean:V:
+ rm -f *.dis *.sbl
+
+uninstall:V:
+ rm -f $INS
diff --git a/appl/cmd/fs/mode.b b/appl/cmd/fs/mode.b
new file mode 100644
index 00000000..83d385d7
--- /dev/null
+++ b/appl/cmd/fs/mode.b
@@ -0,0 +1,120 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+# 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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+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.P(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/cmd/fs/not.b b/appl/cmd/fs/not.b
new file mode 100644
index 00000000..e318f855
--- /dev/null
+++ b/appl/cmd/fs/not.b
@@ -0,0 +1,48 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+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.P(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/cmd/fs/or.b b/appl/cmd/fs/or.b
new file mode 100644
index 00000000..ca6668d1
--- /dev/null
+++ b/appl/cmd/fs/or.b
@@ -0,0 +1,65 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+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.P(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/cmd/fs/path.b b/appl/cmd/fs/path.b
new file mode 100644
index 00000000..2b8c98a0
--- /dev/null
+++ b/appl/cmd/fs/path.b
@@ -0,0 +1,77 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+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.P(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/cmd/fs/pipe.b b/appl/cmd/fs/pipe.b
new file mode 100644
index 00000000..665abdeb
--- /dev/null
+++ b/appl/cmd/fs/pipe.b
@@ -0,0 +1,223 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ sh: Sh;
+ Context: import sh;
+include "fslib.m";
+ fslib: Fslib;
+ Option, Value, Fschan, Report, quit: import fslib;
+ Skip, Next, Down, Quit: import fslib;
+
+
+# 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 "vcx-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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->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;
+ 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;
+ }
+ cmd := (hd args).c().i;
+ c := (hd tl args).x().i;
+ sync := chan of int;
+ spawn execproc(drawctxt, sync, oneflag, pflag, c, cmd, report.start("exec"));
+ sync <-= 1;
+ return ref Value.V(sync);
+}
+
+execproc(drawctxt: ref Draw->Context, sync: chan of int, oneflag, pflag: int,
+ c: Fschan, cmd: ref Sh->Cmd, errorc: chan of string)
+{
+ sys->pctl(Sys->NEWFD, 0::1::2::nil);
+ ctxt := Context.new(drawctxt);
+ <-sync;
+ if(<-sync == 0){
+ (<-c).t1 <-= Quit;
+ quit(errorc);
+ }
+ 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;
+ quit(errorc);
+ }
+ }
+
+ 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;
+ quit(errorc);
+ }
+ (<-c).t1 <-= Skip;
+ break;
+ }
+ }
+ if(!oneflag){
+ fd = nil;
+ <-result;
+ }
+ }
+ fd = nil;
+ if(oneflag)
+ <-result;
+ quit(errorc);
+}
+
+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/cmd/fs/print.b b/appl/cmd/fs/print.b
new file mode 100644
index 00000000..21761e0e
--- /dev/null
+++ b/appl/cmd/fs/print.b
@@ -0,0 +1,51 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+types(): string
+{
+ return "vt";
+}
+
+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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+run(nil: ref Draw->Context, report: ref Report,
+ nil: list of Option, args: list of ref Value): ref Value
+{
+ sync := chan of int;
+ spawn printproc(sync, (hd args).t().i, report.start("print"));
+ return ref Value.V(sync);
+}
+
+printproc(sync: chan of int, c: Entrychan, errorc: chan of string)
+{
+ if(<-sync == 0){
+ c.sync <-= 0;
+ quit(errorc);
+ exit;
+ }
+ c.sync <-= 1;
+ while(((d, p, nil) := <-c.c).t0 != nil)
+ sys->print("%s\n", p);
+ quit(errorc);
+}
diff --git a/appl/cmd/fs/proto.b b/appl/cmd/fs/proto.b
new file mode 100644
index 00000000..bc836f44
--- /dev/null
+++ b/appl/cmd/fs/proto.b
@@ -0,0 +1,388 @@
+implement 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 "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, report, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+File: adt {
+ name: string;
+ mode: int;
+ owner: string;
+ group: string;
+ old: string;
+ flags: int;
+ sub: cyclic array of ref File;
+};
+
+Proto: adt {
+ indent: int;
+ lastline: string;
+ iob: ref Iobuf;
+};
+
+Star, Plus: con 1<<iota;
+
+types(): string
+{
+ return "xs-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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->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
+{
+ protofile := (hd args).s().i;
+ rootpath: string;
+ if(opts != nil)
+ rootpath = (hd (hd opts).args).s().i;
+ if(rootpath == nil)
+ rootpath = "/";
+
+ proto := ref Proto(0, nil, nil);
+ if((proto.iob = bufio->open(protofile, Sys->OREAD)) == nil){
+ sys->fprint(sys->fildes(2), "fs: proto: cannot open %q: %r\n", protofile);
+ return nil;
+ }
+ root := ref File(rootpath, ~0, nil, nil, nil, 0, nil);
+ (root.flags, root.sub) = readproto(proto, -1);
+ c := chan of (Fsdata, chan of int);
+ spawn protowalk(c, root, report.start("proto"));
+ return ref Value.X(c);
+}
+
+protowalk(c: Fschan, root: ref File, errorc: chan of string)
+{
+ protowalk1(c, root.flags, root.name, file2dir(root, nil), 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;
+ prevsub: string;
+ for(i := 0; i < n; i++){
+ for(; j < len sub; j++){
+ s := sub[j].name;
+ if(s == prevsub){
+ report(errorc, sys->sprint("duplicate entry %s", pathconcat(path, s)));
+ continue; # eliminate duplicates in proto
+ }
+ if(s >= a[i].name || sub[j].old != nil)
+ 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++];
+ prevsub = 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 Proto, 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 Proto, 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/cmd/fs/query.b b/appl/cmd/fs/query.b
new file mode 100644
index 00000000..421be1d9
--- /dev/null
+++ b/appl/cmd/fs/query.b
@@ -0,0 +1,130 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ sh: Sh;
+ Context: import sh;
+include "fslib.m";
+ fslib: Fslib;
+ Option, Value, Gatechan, Gatequery, Report, Nilentry: import fslib;
+
+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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ 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.P(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/cmd/fs/readfile.b b/appl/cmd/fs/readfile.b
new file mode 100644
index 00000000..4a52ae08
--- /dev/null
+++ b/appl/cmd/fs/readfile.b
@@ -0,0 +1,144 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, report, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+this is a bad idea, i think
+i think walk + filter + setroot is good enough.
+
+types(): string
+{
+ # usage: readfile [-f file] name
+ return "xs-fs";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: readfile: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+run(nil: ref Draw->Context, report: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ path: string;
+ f := (hd args).s().i;
+ fd: ref Sys->FD;
+ seekable: int;
+ if(f == "-"){
+ if(opts == nil){
+ sys->fprint(sys->fildes(2), "fs: readfile: must specify a path when reading stdin\n");
+ return nil;
+ }
+ fd = sys->fildes(0);
+ seekable = 0;
+ }else{
+ fd = sys->open(f, Sys->OREAD);
+ seekable = isseekable(fd);
+ }
+ if(fd == nil){
+ sys->fprint(sys->fildes(2), "fs: readfile: cannot open %s: %r\n", f);
+ return nil;
+ }
+ if(opts != nil)
+ path = (hd (hd opts).args).s().i;
+ else
+ path = f;
+
+ (root, file) := pathsplit(path);
+ if(file == nil || file == "." || file == ".."){
+ sys->fprint(sys->fildes(2), "fs: readfile: invalid filename %q\n", fname);
+ return nil;
+ }
+ d.name = file;
+ v := ref Value.X(chan of (Fsdata, chan of int));
+ spawn readproc(v.i, fd, root, ref d, seekable, report.start("read"));
+ return v;
+}
+
+readproc(c: Fschan, fd: ref Sys->FD, root: string, d: ref Sys->Dir, seekable: int, errorc: chan of string)
+{
+ reply := chan of int;
+ rd := ref Sys->nulldir;
+ rd.name = root;
+ c <-= ((rd, nil), reply);
+ if(<-reply != Down)
+ quit(errorc);
+
+ c <-= ((d, nil), reply);
+ case <-reply {
+ Down =>
+ sendfile(c, fd, errorc);
+ Skip or
+ Quit =>
+ quit(errorc);
+ }
+ c <-= ((nil, nil), reply);
+ <-reply;
+ quit(errorc);
+}
+
+sendfile(c: Fschan, data: list of array of byte, length: big, errorc: chan of string)
+{
+ reply := chan of int;
+ for(;;){
+ buf: array of byte;
+ if(fd != nil){
+ buf := array[Sys->ATOMICIO] of byte;
+ if((n := sys->read(fd, buf, len buf)) <= 0){
+ if(n < 0)
+ report(errorc, sys->sprint("read error: %r"));
+ c <-= ((nil, nil), reply);
+ if(<-reply == Quit)
+ quit(errorc);
+ return;
+ }
+ c <-= ((nil, buf), reply);
+ case <-reply {
+ Quit =>
+ quit(errorc);
+ Skip =>
+ return;
+ }
+ }
+}
+
+pathsplit(p: string): (string, string)
+{
+ for (i := len p - 1; i >= 0; i--)
+ if (p[i] != '/')
+ break;
+ if (i < 0)
+ return (p, nil);
+ p = p[0:i+1];
+ for (i = len p - 1; i >=0; i--)
+ if (p[i] == '/')
+ break;
+ if (i < 0)
+ return (".", p);
+ return (p[0:i+1], p[i+1:]);
+}
+
+# dodgy heuristic... avoid, or using the stat-length of pipes and net connections
+isseekable(fd: ref Sys->FD): int
+{
+ (ok, stat) := sys->stat(iob.fd);
+ if(ok != -1 && stat.dtype == '|' || stat.dtype == 'I')
+ return 0;
+ return 1;
+}
diff --git a/appl/cmd/fs/run.b b/appl/cmd/fs/run.b
new file mode 100644
index 00000000..a5734d7c
--- /dev/null
+++ b/appl/cmd/fs/run.b
@@ -0,0 +1,60 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ sh: Sh;
+ Context: import sh;
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ 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.S(s);
+}
diff --git a/appl/cmd/fs/select.b b/appl/cmd/fs/select.b
new file mode 100644
index 00000000..9fe5e4b0
--- /dev/null
+++ b/appl/cmd/fs/select.b
@@ -0,0 +1,56 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+types(): string
+{
+ return "tpt";
+}
+
+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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+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 tl args).t().i, dst, (hd args).p().i);
+ return ref Value.T(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/cmd/fs/setroot.b b/appl/cmd/fs/setroot.b
new file mode 100644
index 00000000..d39a4cf4
--- /dev/null
+++ b/appl/cmd/fs/setroot.b
@@ -0,0 +1,104 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+# set the root
+types(): string
+{
+ return "xsx-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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+run(nil: ref Draw->Context, nil: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ root := (hd args).s().i;
+ if(root == nil && opts == nil){
+ sys->fprint(sys->fildes(2), "fs: setroot: empty path\n");
+ return nil;
+ }
+ v := ref Value.X(chan of (Fsdata, chan of int));
+ spawn setroot((hd tl 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;
+ fslib->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(fslib->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/cmd/fs/size.b b/appl/cmd/fs/size.b
new file mode 100644
index 00000000..d72edd99
--- /dev/null
+++ b/appl/cmd/fs/size.b
@@ -0,0 +1,54 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+types(): string
+{
+ return "vt";
+}
+
+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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+run(nil: ref Draw->Context, report: ref Report,
+ nil: list of Option, args: list of ref Value): ref Value
+{
+ sync := chan of int;
+ spawn sizeproc(sync, (hd args).t().i, report.start("size"));
+ return ref Value.V(sync);
+}
+
+sizeproc(sync: chan of int, c: Entrychan, errorc: chan of string)
+{
+ if(<-sync == 0){
+ c.sync <-= 0;
+ quit(errorc);
+ exit;
+ }
+ c.sync <-= 1;
+
+ size := big 0;
+ while(((d, nil, nil) := <-c.c).t0 != nil)
+ size += d.length;
+ sys->print("%bd\n", size);
+ quit(errorc);
+}
diff --git a/appl/cmd/fs/template.b b/appl/cmd/fs/template.b
new file mode 100644
index 00000000..9b08f589
--- /dev/null
+++ b/appl/cmd/fs/template.b
@@ -0,0 +1,35 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+types(): string
+{
+ return "nil";
+}
+
+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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+run(nil: ref Draw->Context, report: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+}
diff --git a/appl/cmd/fs/unbundle.b b/appl/cmd/fs/unbundle.b
new file mode 100644
index 00000000..ad500e8e
--- /dev/null
+++ b/appl/cmd/fs/unbundle.b
@@ -0,0 +1,259 @@
+implement Unbundle;
+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 "fslib.m";
+ fslib: Fslib;
+ Report, Value,quit, report: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Quit, Next, Skip, Down,
+ Option: import Fslib;
+include "unbundle.m";
+
+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 Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->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);
+ return ref Value.X(unbundle(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;
+}
+
+unbundle(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/cmd/fs/void.b b/appl/cmd/fs/void.b
new file mode 100644
index 00000000..0f8c72fa
--- /dev/null
+++ b/appl/cmd/fs/void.b
@@ -0,0 +1,33 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, Option: import fslib;
+
+types(): string
+{
+ return "vv";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: void: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+}
+
+run(nil: ref Draw->Context, nil: ref Report,
+ nil: list of Option, args: list of ref Value): ref Value
+{
+ return (hd args).v();
+}
diff --git a/appl/cmd/fs/walk.b b/appl/cmd/fs/walk.b
new file mode 100644
index 00000000..1c5016bd
--- /dev/null
+++ b/appl/cmd/fs/walk.b
@@ -0,0 +1,233 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "readdir.m";
+ readdir: Readdir;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, report, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+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;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->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.X(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));
+ fslib->sendnulldir(c);
+ quit(errorc);
+ }
+ (ok, d) := sys->stat(".");
+ if(ok == -1){
+ report(errorc, sys->sprint("cannot stat %q: %r", path));
+ fslib->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/cmd/fs/write.b b/appl/cmd/fs/write.b
new file mode 100644
index 00000000..934d4d67
--- /dev/null
+++ b/appl/cmd/fs/write.b
@@ -0,0 +1,111 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, quit, report: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+types(): string
+{
+ return "vsx";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil){
+ sys->fprint(sys->fildes(2), "fs: write: cannot load %s: %r\n", Fslib->PATH);
+ raise "fail:bad module";
+ }
+}
+
+run(nil: ref Draw->Context, report: ref Report,
+ nil: list of Option, args: list of ref Value): ref Value
+{
+ sync := chan of int;
+ spawn fswriteproc(sync, (hd args).s().i, (hd tl args).x().i, report.start("fswrite"));
+ <-sync;
+ return ref Value.V(sync);
+}
+
+fswriteproc(sync: chan of int, root: string, c: Fschan, errorc: chan of string)
+{
+ sys->pctl(Sys->FORKNS, nil);
+ sync <-= 1;
+ if(<-sync == 0){
+ (<-c).t1 <-= Quit;
+ quit(errorc);
+ }
+
+ (d, reply) := <-c;
+ if(root != nil){
+ d.dir = ref *d.dir;
+ d.dir.name = root;
+ }
+ fswritedir(d.dir.name, d, reply, c, errorc);
+ quit(errorc);
+}
+
+fswritedir(path: string, d: Fsdata, dreply: chan of int, c: Fschan, errorc: chan of string)
+{
+ fd: ref Sys->FD;
+ if(d.dir.mode & Sys->DMDIR){
+ fd = sys->create(d.dir.name, Sys->OREAD, d.dir.mode|8r300);
+ if(fd == nil && (fd = sys->open(d.dir.name, Sys->OREAD)) == nil){
+ dreply <-= Next;
+ report(errorc, sys->sprint("cannot create %q, mode %uo: %r", path, d.dir.mode|8r300));
+ return;
+ }
+ 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);
+ }
+ sys->chdir("..");
+ if((d.dir.mode & 8r300) != 8r300){
+ 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;
+ }
+}