#
# File spooler. To build file systems that accept
# files to be given to underlying host commands.
# BUGS: This needs improvements:
# -> listen to endc to detect when the file has been processed, and remove it.
# -> accept general ctl requests via ctl, and pass them to the underlying spooler
# module.
# -> create FILE.status stream file, which reports (via read) any message sent
# through endc until receiving nil, in which case it reports eof.
implement Spool;
include "sys.m";
sys: Sys;
Dir, pctl, NEWPGRP, DMDIR, open, OREAD, FD, OWRITE, ORCLOSE, FORKFD,
ORDWR, FORKNS, NEWFD, MREPL, MBEFORE, MAFTER, MCREATE, pipe, mount,
fprint, sprint, create, pwrite, read, QTDIR, QTFILE, fildes, Qid: import sys;
include "draw.m";
include "styx.m";
styx: Styx;
Rmsg, Tmsg: import styx;
include "error.m";
err: Error;
checkload, stderr, panic, kill, error: import err;
include "styxservers.m";
styxs: Styxservers;
Styxserver, readbytes, readstr, Navigator, Fid: import styxs;
nametree: Nametree;
Tree: import nametree;
include "daytime.m";
daytime: Daytime;
now: import daytime;
include "arg.m";
arg: Arg;
usage: import arg;
include "tbl.m";
tbl: Tbl;
Table: import tbl;
include "netget.m";
netget: Netget;
include "string.m";
str: String;
splitr: import str;
include "env.m";
env: Env;
getenv: import env;
include "spooler.m";
spooler: Spooler;
Sfile: import spooler;
Spool: module {
init: fn(nil: ref Draw->Context, argv: list of string);
};
File: adt {
name: string;
path: string;
fd: ref FD;
vers: int;
sf: ref Sfile;
};
files: ref Table[ref File]; # indexed by qid.
Qroot, Qctl, Qndb: con big iota;
qgen:= Qndb;
debug := 0;
user: string;
argv0 := "spool";
readstarts := 0; # reading a file starts the spooler as well
readall(fname: string) : string
{
fd := open(fname, OREAD);
if (fd == nil)
return "none";
max : con int 1024;
data := array[max] of byte;
tot := nr := 0;
do {
nr = read(fd, data[tot:], len data - tot);
if (nr > 0)
tot += nr;
} while(nr > 0 && tot < len data);
if (tot == 0)
return "none";
return string data[0:tot];
}
newdir(name: string, perm: int, qid: big): Dir
{
d := sys->zerodir;
d.name = name;
d.uid = user;
d.gid = user;
d.qid.path = qid;
if (perm & DMDIR)
d.qid.qtype = QTDIR;
else
d.qid.qtype = QTFILE;
d.mode = perm;
return d;
}
fsreq(srv: ref Styxserver, tree: ref Tree, req: ref Tmsg) : ref Rmsg
{
pick m := req {
Create =>
(fid, mode, d, e) := srv.cancreate(m);
if (e != nil)
return ref Rmsg.Error(m.tag, e);
if (mode&DMDIR)
return ref Rmsg.Error(m.tag, "can't handle directories");
fpath := sprint("/tmp/%s.%d.%s", argv0, int qgen, d.name);
f := ref File(d.name, fpath, nil, 0, nil);
f.fd = create(f.path, OWRITE|ORCLOSE, 8r664);
if (f.fd == nil)
return ref Rmsg.Error(m.tag, sprint("tmpfile: %r"));
d.qid = Qid(++qgen, 0, 0);
d.atime = d.mtime = now();
e = tree.create(Qroot, *d);
if (e != nil)
return ref Rmsg.Error(m.tag, e);
fid.open(mode, d.qid);
files.add(int fid.path, f);
return ref Rmsg.Create(m.tag, d.qid, srv.iounit());
Remove =>
(fid, nil, e) := srv.canremove(m);
srv.delfid(fid);
if (e != nil)
return ref Rmsg.Error(m.tag, e);
if (fid.path == Qctl || fid.path == Qndb)
return ref Rmsg.Error(m.tag, "permission denied");
e = tree.remove(fid.path);
if (e != nil)
return ref Rmsg.Error(m.tag, e);
f := files.del(int fid.path);
if (f != nil && f.sf != nil)
f.sf.stop();
return ref Rmsg.Remove(m.tag);
Read =>
(fid, e) := srv.canread(m);
if (e != nil)
return ref Rmsg.Error(m.tag, e);
if (fid.qtype&QTDIR)
return nil;
data := "";
case fid.path {
Qctl =>
data = spooler->status() + "\n";
Qndb =>
data = netget->ndb() + "\n";
}
return readstr(m, data);
Write =>
(fid, e) := srv.canwrite(m);
if (e != nil)
return ref Rmsg.Error(m.tag, e);
if (fid.path == Qctl || fid.path == Qndb)
return ref Rmsg.Error(m.tag, "permission denied");
f := files.find(int fid.path);
if (f == nil || f.fd == nil)
return ref Rmsg.Error(m.tag, "file not found");
nw := pwrite(f.fd, m.data, len m.data, m.offset);
if (nw < 0)
return ref Rmsg.Error(m.tag, sprint("%r"));
f.vers++;
return ref Rmsg.Write(m.tag, nw);
Clunk =>
fid := srv.getfid(m.fid);
if (fid == nil)
return ref Rmsg.Error(m.tag, "bad fid");
if (fid.path != Qctl && fid.path != Qroot && fid.path != Qndb){
f := files.find(int fid.path);
if (f != nil && f.fd != nil && f.vers > 0 && fid.isopen){
if (fid.mode == OWRITE || fid.mode == ORDWR || readstarts)
(f.sf, nil) = Sfile.start(f.path, nil);
# BUG: should listen through endc (nil above)
# and remove the file or report diagnostics
# sent through it.
}
}
srv.delfid(fid);
return ref Rmsg.Clunk(m.tag);
Wstat =>
return ref Rmsg.Wstat(m.tag);
* =>
return nil;
}
}
fs(pidc: chan of int, fd: ref FD)
{
styx->init();
styxs->init(styx);
user = getenv("user");
if (user == nil)
user = readall("/dev/user");
if (pidc != nil)
pidc <-= pctl(FORKNS|NEWPGRP|NEWFD, list of {0,1,2,fd.fd});
else
pctl(NEWPGRP, nil);
stderr = fildes(2);
netget->announce(argv0, sprint("path /%s", argv0));
(tree, navc) := nametree->start();
nav := Navigator.new(navc);
(reqc, srv) := Styxserver.new(fd, nav, Qroot);
tree.create(Qroot, newdir(".", DMDIR|8r775, Qroot));
tree.create(Qroot, newdir("ctl", 8r444, Qctl));
tree.create(Qroot, newdir("ndb", 8r444, Qndb));
nullfile: ref File;
files = Table[ref File].new(103, nullfile);
for (;;) {
req := <-reqc;
if (req == nil)
break;
rep := fsreq(srv, tree, req);
if (rep == nil)
srv.default(req);
else
srv.reply(rep);
}
tree.quit();
netget->terminate();
kill(pctl(0, nil),"killgrp"); # be sure to quit
}
init(nil: ref Draw->Context, args: list of string)
{
sys = load Sys Sys->PATH;
err = load Error Error->PATH;
err->init();
str = checkload(load String String->PATH, String->PATH);
styx = checkload(load Styx Styx->PATH, Styx->PATH);
styxs = checkload(load Styxservers Styxservers->PATH, Styxservers->PATH);
nametree = checkload(load Nametree Nametree->PATH, Nametree->PATH);
nametree->init();
daytime = checkload(load Daytime Daytime->PATH, Daytime->PATH);
tbl = checkload(load Tbl Tbl->PATH, Tbl->PATH);
env = checkload(load Env Env->PATH, Env->PATH);
netget = checkload(load Netget Netget->PATH, Netget->PATH);
arg = checkload(load Arg Arg->PATH, Arg->PATH);
arg->init(args);
arg->setusage("spool [-abcdr] [-m mnt] module");
mnt: string;
flag := MREPL|MCREATE;
while((opt := arg->opt()) != 0) {
case opt{
'b' =>
flag = MBEFORE;
'a' =>
flag = MAFTER;
'c' =>
flag |= MCREATE;
'm' =>
mnt = arg->earg();
'd' =>
debug = 1;
styxs->traceset(1);
'r' =>
readstarts = 1;
* =>
usage();
}
}
args = arg->argv();
if (len args < 1)
usage();
argv0 = hd args;
(nil, s2) := splitr(argv0, "/");
if (s2 != nil && s2 != "")
argv0 = s2;
dis := "/dis/" + (hd args) + ".dis";
spooler = checkload(load Spooler dis, dis);
spooler->init(tl args);
spooler->debug = debug;
if (mnt == nil)
fs(nil, fildes(0));
else {
pfds := array[2] of ref FD;
if (pipe(pfds) < 0)
error(sprint("%s: pipe: %r", argv0));
pidc := chan of int;
spawn fs(pidc, pfds[0]);
<-pidc;
if (mount(pfds[1], nil, mnt, flag, nil) < 0)
error(sprint("%s: mount: %r", argv0));
pfds[0] = nil;
}
}
|