ref: 05d9123a707dec0eaa0ab079b94e69cdb750c6db
dir: /appl/alphabet/fs/walk.b/
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];
}