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