summaryrefslogtreecommitdiff
path: root/appl/lib/fslib.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/lib/fslib.b')
-rw-r--r--appl/lib/fslib.b400
1 files changed, 400 insertions, 0 deletions
diff --git a/appl/lib/fslib.b b/appl/lib/fslib.b
new file mode 100644
index 00000000..20dd6990
--- /dev/null
+++ b/appl/lib/fslib.b
@@ -0,0 +1,400 @@
+implement Fslib;
+
+#
+# Copyright © 2003 Vita Nuova Holdings Limited
+#
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+
+# Fsdata stream conventions:
+#
+# Fsdata: adt {
+# dir: ref Sys->Dir;
+# data: array of byte;
+# };
+# Fschan: type chan of (Fsdata, chan of int);
+# c: Fschan;
+#
+# a stream of values sent on c represent the contents of a directory
+# hierarchy. after each value has been received, the associated reply
+# channel must be used to prompt the sender how next to proceed.
+#
+# the first item sent on an fsdata channel represents the root directory
+# (it must be a directory), and its name holds the full path of the
+# hierarchy that's being transferred. the items that follow represent
+# the contents of the root directory.
+#
+# the set of valid sequences of values can be described by a yacc-style
+# grammar, where the terminal tokens describe data values (Fsdata adts)
+# passed down the channel. this grammar describes the case where the
+# entire fs tree is traversed in its entirety:
+#
+# dir: DIR dircontents NIL
+# | DIR NIL
+# dircontents: entry
+# | dircontents entry
+# entry: FILE filecontents NIL
+# | FILE NIL
+# | dir
+# filecontents: DATA
+# | filecontents DATA
+#
+# the tests for the various terminal token types, given a token (of type
+# Fsdata) t:
+#
+# FILE t.dir != nil && (t.dir.mode & Sys->DMDIR) == 0
+# DIR t.dir != nil && (t.dir.mode & Sys->DMDIR)
+# DATA t.data != nil
+# NIL t.data == nil && t.dir == nil
+#
+# when a token is received, there are four possible replies:
+# Quit
+# terminate the stream immediately. no more tokens will
+# be on the channel.
+#
+# Down
+# descend one level in the hierarchy, if possible. the next tokens
+# will represent the contents of the current entry.
+#
+# Next
+# get the next entry in a directory, or the next data
+# block in a file, or travel one up the hierarchy if
+# it's the last entry or data block in that directory or file.
+#
+# Skip
+# skip to the end of a directory or file's contents.
+# if we're already at the end, this is a no-op (same as Next)
+#
+# grammar including replies is different. a token is the tuple (t, reply),
+# where reply is the value that was sent over the reply channel. Quit
+# always causes the grammar to terminate, so it is omitted for clarity.
+# thus there are 12 possible tokens (DIR_DOWN, DIR_NEXT, DIR_SKIP, FILE_DOWN, etc...)
+#
+# dir: DIR_DOWN dircontents NIL_NEXT
+# | DIR_DOWN dircontents NIL_SKIP
+# | DIR_DOWN dircontents NIL_DOWN
+# | DIR_NEXT
+# dircontents:
+# | FILE_SKIP
+# | DIR_SKIP
+# | file dircontents
+# | dir dircontents
+# file: FILE_DOWN filecontents NIL_NEXT
+# | FILE_DOWN filecontents NIL_SKIP
+# | FILE_DOWN filecontents NIL_DOWN
+# | FILE_NEXT
+# filecontents:
+# | data
+# | data DATA_SKIP
+# data: DATA_NEXT
+# | data DATA_NEXT
+#
+# both the producer and consumer of fs data on the channel must between
+# them conform to the second grammar. if a stream of fs data
+# is sent with no reply channel, the stream must conform to the first grammar.
+
+valuec := array[] of {
+ tagof(Value.V) => 'v',
+ tagof(Value.X) => 'x',
+ tagof(Value.P) => 'p',
+ tagof(Value.S) => 's',
+ tagof(Value.C) => 'c',
+ tagof(Value.T) => 't',
+ tagof(Value.M) => 'm',
+};
+
+init()
+{
+ sys = load Sys Sys->PATH;
+}
+
+# copy the contents (not the entry itself) of a directory from src to dst.
+copy(src, dst: Fschan): int
+{
+ indent := 1;
+ myreply := chan of int;
+ for(;;){
+ (d, reply) := <-src;
+ dst <-= (d, myreply);
+ r := <-myreply;
+ case reply <-= r {
+ Quit =>
+ return Quit;
+ Next =>
+ if(d.dir == nil && d.data == nil)
+ if(--indent == 0)
+ return Next;
+ Skip =>
+ if(--indent == 0)
+ return Next;
+ Down =>
+ if(d.dir != nil || d.data != nil)
+ indent++;
+ }
+ }
+}
+
+Report.new(): ref Report
+{
+ r := ref Report(chan of string, chan of (string, chan of string), chan of int);
+ spawn reportproc(r.startc, r.enablec, r.reportc);
+ return r;
+}
+
+Report.start(r: self ref Report, name: string): chan of string
+{
+ if(r == nil)
+ return nil;
+ errorc := chan of string;
+ r.startc <-= (name, errorc);
+ return errorc;
+}
+
+Report.enable(r: self ref Report)
+{
+ r.enablec <-= 0;
+}
+
+reportproc(startc: chan of (string, chan of string), startreports: chan of int, errorc: chan of string)
+{
+ realc := array[2] of chan of string;
+ p := array[len realc] of string;
+ a := array[0] of chan of string;;
+
+ n := 0;
+ for(;;) alt{
+ (prefix, c) := <-startc =>
+ if(n == len realc){
+ realc = (array[n * 2] of chan of string)[0:] = realc;
+ p = (array[n * 2] of string)[0:] = p;
+ }
+ realc[n] = c;
+ p[n] = prefix;
+ n++;
+ <-startreports =>
+ if(n == 0){
+ errorc <-= nil;
+ exit;
+ }
+ a = realc;
+ (x, report) := <-a =>
+ if(report == nil){
+# errorc <-= "exit " + p[x];
+ --n;
+ if(n != x){
+ a[x] = a[n];
+ a[n] = nil;
+ p[x] = p[n];
+ p[n] = nil;
+ }
+ if(n == 0){
+ errorc <-= nil;
+ exit;
+ }
+ }else if(a == realc)
+ errorc <-= p[x] + ": " + report;
+ }
+}
+
+type2s(c: int): string
+{
+ case c{
+ 'a' =>
+ return "any";
+ 'x' =>
+ return "fs";
+ 's' =>
+ return "string";
+ 'v' =>
+ return "void";
+ 'p' =>
+ return "gate";
+ 'c' =>
+ return "command";
+ 't' =>
+ return "entries";
+ 'm' =>
+ return "selector";
+ * =>
+ return sys->sprint("unknowntype('%c')", c);
+ }
+}
+
+typeerror(tc: int, v: ref Value): string
+{
+ sys->fprint(sys->fildes(2), "fs: bad type conversion, expected %s, was actually %s\n", type2s(tc), type2s(valuec[tagof v]));
+ return "type conversion error";
+}
+
+Value.t(v: self ref Value): ref Value.T
+{
+ pick xv :=v {T => return xv;}
+ raise typeerror('t', v);
+}
+Value.c(v: self ref Value): ref Value.C
+{
+ pick xv :=v {C => return xv;}
+ raise typeerror('c', v);
+}
+Value.s(v: self ref Value): ref Value.S
+{
+ pick xv :=v {S => return xv;}
+ raise typeerror('s', v);
+}
+Value.p(v: self ref Value): ref Value.P
+{
+ pick xv :=v {P => return xv;}
+ raise typeerror('p', v);
+}
+Value.x(v: self ref Value): ref Value.X
+{
+ pick xv :=v {X => return xv;}
+ raise typeerror('x', v);
+}
+Value.v(v: self ref Value): ref Value.V
+{
+ pick xv :=v {V => return xv;}
+ raise typeerror('v', v);
+}
+Value.m(v: self ref Value): ref Value.M
+{
+ pick xv :=v {M => return xv;}
+ raise typeerror('m', v);
+}
+
+Value.typec(v: self ref Value): int
+{
+ return valuec[tagof v];
+}
+
+Value.discard(v: self ref Value)
+{
+ if(v == nil)
+ return;
+ pick xv := v {
+ X =>
+ (<-xv.i).t1 <-= Quit;
+ P =>
+ xv.i <-= (Nilentry, nil);
+ M =>
+ xv.i <-= (nil, nil, nil);
+ V =>
+ xv.i <-= 0;
+ T =>
+ xv.i.sync <-= 0;
+ }
+}
+
+sendnulldir(c: Fschan): int
+{
+ reply := chan of int;
+ c <-= ((ref Sys->nulldir, nil), reply);
+ if((r := <-reply) == Down){
+ c <-= ((nil, nil), reply);
+ if(<-reply != Quit)
+ return Quit;
+ return Next;
+ }
+ return r;
+}
+
+quit(errorc: chan of string)
+{
+ if(errorc != nil)
+ errorc <-= nil;
+ exit;
+}
+
+report(errorc: chan of string, err: string)
+{
+ if(errorc != nil)
+ errorc <-= err;
+}
+
+# true if a module with type sig t1 is compatible with a caller that expects t0
+typecompat(t0, t1: string): int
+{
+ (rt0, at0, ot0) := splittype(t0);
+ (rt1, at1, ot1) := splittype(t1);
+ if((rt0 != rt1 && rt0 != 'a') || at0 != at1) # XXX could do better for repeated args.
+ return 0;
+ for(i := 1; i < len ot0; i++){
+ for(j := i; j < len ot0; j++)
+ if(ot0[j] == '-')
+ break;
+ (ok, t) := opttypes(ot0[i], ot1);
+ if(ok == -1 || ot0[i:j] != t)
+ return 0;
+ i = j + 1;
+ }
+ return 1;
+}
+
+splittype(t: string): (int, string, string)
+{
+ if(t == nil)
+ return (-1, nil, nil);
+ for(i := 1; i < len t; i++)
+ if(t[i] == '-')
+ break;
+ return (t[0], t[1:i], t[i:]);
+}
+
+opttypes(opt: int, opts: string): (int, string)
+{
+ for(i := 1; i < len opts; i++){
+ if(opts[i] == opt && opts[i-1] == '-'){
+ for(j := i+1; j < len opts; j++)
+ if(opts[j] == '-')
+ break;
+ return (0, opts[i+1:j]);
+ }
+ }
+ return (-1, nil);
+}
+
+cmdusage(s, t: string): string
+{
+ if(s == nil)
+ return nil;
+ for(oi := 0; oi < len t; oi++)
+ if(t[oi] == '-')
+ break;
+ if(oi < len t){
+ single, multi: string;
+ for(i := oi; i < len t - 1;){
+ for(j := i + 1; j < len t; j++)
+ if(t[j] == '-')
+ break;
+
+ optargs := t[i+2:j];
+ if(optargs == nil)
+ single[len single] = t[i+1];
+ else{
+ multi += sys->sprint(" [-%c", t[i+1]);
+ for (k := 0; k < len optargs; k++)
+ multi += " " + type2s(optargs[k]);
+ multi += "]";
+ }
+ i = j;
+ }
+ if(single != nil)
+ s += " [-" + single + "]";
+ s += multi;
+ }
+ multi := 0;
+ if(oi > 2 && t[oi - 1] == '*'){
+ multi = 1;
+ oi -= 2;
+ }
+ for(k := 1; k < oi; k++)
+ s += " " + type2s(t[k]);
+ if(multi)
+ s += " [" + type2s(t[k]) + "...]";
+ s += " -> " + type2s(t[0]);
+ return s;
+}