summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES3
-rw-r--r--appl/cmd/mkfile6
-rw-r--r--appl/cmd/vacfs.b446
-rw-r--r--appl/cmd/vacget.b199
-rw-r--r--appl/cmd/vacput.b258
-rw-r--r--appl/lib/mkfile2
-rw-r--r--appl/lib/vac.b1094
-rw-r--r--dis/lib/vac.disbin0 -> 17748 bytes
-rw-r--r--dis/vacfs.disbin0 -> 8141 bytes
-rw-r--r--dis/vacget.disbin0 -> 4203 bytes
-rw-r--r--dis/vacput.disbin0 -> 6585 bytes
-rw-r--r--lib/proto/inferno25
-rw-r--r--lib/proto/src14
-rw-r--r--man/1/INDEX2
-rw-r--r--man/1/vacget75
-rw-r--r--man/4/INDEX1
-rw-r--r--man/4/vacfs47
-rw-r--r--man/lib/checkman.awk187
-rw-r--r--man/lib/colophon26
-rw-r--r--man/lib/lookman/junkwords509
-rwxr-xr-xman/lib/lookman/mkindex13
-rw-r--r--man/lib/notes10
-rw-r--r--man/lib/preface73
-rwxr-xr-xman/lib/secindex43
-rw-r--r--man/lib/title77
-rw-r--r--man/lib/trademarks46
-rw-r--r--man/mkfile119
-rw-r--r--module/vac.m188
28 files changed, 3456 insertions, 7 deletions
diff --git a/CHANGES b/CHANGES
index b144d248..c0e478e4 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,5 +1,8 @@
20070614
/appl/lib/venti.b bug fixes, a few more errstrs, remove prints to stderr
+ add initial module/vac.m appl/lib/vac.b from mjl (gsoc project ventivac)
+ add initial /appl/cmd/^(vacfs.b vacget.b vacput.b) /man/4/vacfs /man/1/vacget from mjl (gsoc:ventivac)
+ include omitted man/mkfile man/lib
20070608
update /lib9 functions to use silly va_copy (and then va_end) instead of just assigning, to account for silly C implementations
change /appl/cmd/mc.b not to require Draw or Env (so lc works on smaller systems)
diff --git a/appl/cmd/mkfile b/appl/cmd/mkfile
index 69cf60d0..29c1679f 100644
--- a/appl/cmd/mkfile
+++ b/appl/cmd/mkfile
@@ -160,6 +160,9 @@ TARG=\
unmount.dis\
uudecode.dis\
uuencode.dis\
+ vacfs.dis\
+ vacget.dis\
+ vacput.dis\
wav2iaf.dis\
wc.dis\
webgrab.dis\
@@ -216,3 +219,6 @@ rawdbfs.dis: $MODDIR/styxservers.m
import.dis: $MODDIR/encoding.m $MODDIR/factotum.m
basename.dis: $MODDIR/names.m
cleanname.dis: $MODDIR/names.m
+vacfs.dis: $MODDIR/vac.m $MODDIR/venti.m
+vacget.dis: $MODDIR/vac.m $MODDIR/venti.m
+vacput.dis: $MODDIR/vac.m $MODDIR/venti.m
diff --git a/appl/cmd/vacfs.b b/appl/cmd/vacfs.b
new file mode 100644
index 00000000..5fffc98d
--- /dev/null
+++ b/appl/cmd/vacfs.b
@@ -0,0 +1,446 @@
+implement Vacfs;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "arg.m";
+include "string.m";
+include "daytime.m";
+include "venti.m";
+include "vac.m";
+include "styx.m";
+ styx: Styx;
+ Tmsg, Rmsg: import styx;
+include "styxservers.m";
+
+str: String;
+daytime: Daytime;
+venti: Venti;
+vac: Vac;
+styxservers: Styxservers;
+
+print, sprint, fprint, fildes: import sys;
+Score, Session: import venti;
+Roottype, Dirtype, Pointertype0, Datatype: import venti;
+Root, Entry, Direntry, Metablock, Metaentry, Entrysize, Modeperm, Modeappend, Modeexcl, Modedir, Modesnapshot, Vacdir, Vacfile, Source: import vac;
+Styxserver, Fid, Navigator, Navop, Enotfound: import styxservers;
+
+Vacfs: module {
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+
+addr := "net!$venti!venti";
+dflag := pflag := 0;
+session: ref Session;
+
+ss: ref Styxserver;
+
+Elem: adt {
+ qid: int;
+ de: ref Direntry;
+ size: big;
+ pick {
+ File => vf: ref Vacfile;
+ Dir => vd: ref Vacdir;
+ pqid: int;
+ offset: int;
+ nprev: int;
+ prev: array of ref Sys->Dir;
+ }
+
+ new: fn(nqid: int, vd: ref Vacdir, de: ref Direntry, pqid: int): ref Elem;
+ stat: fn(e: self ref Elem): ref Sys->Dir;
+};
+
+Qdir: adt {
+ qid: int;
+ cqids: list of (big, int);
+};
+
+elems := array[512] of list of ref Elem;
+qids := array[512] of list of ref Qdir;
+lastqid := 0;
+qidscores: list of (string, int);
+
+
+childget(qid: int, vqid: big): ref Elem
+{
+ for(l := qids[qid % len qids]; l != nil; l = tl l) {
+ if((hd l).qid != qid)
+ continue;
+ for(m := (hd l).cqids; m != nil; m = tl m) {
+ (vq, cq) := hd m;
+ if(vq == vqid)
+ return get(cq);
+ }
+ }
+ return nil;
+}
+
+childput(qid: int, vqid: big): int
+{
+ qd: ref Qdir;
+ for(l := qids[qid % len qids]; l != nil; l = tl l)
+ if((hd l).qid == qid) {
+ qd = hd l;
+ break;
+ }
+ if(qd == nil) {
+ qd = ref Qdir(qid, nil);
+ qids[qid % len qids] = qd::nil;
+ }
+ qd.cqids = (vqid, ++lastqid)::qd.cqids;
+ return lastqid;
+}
+
+scoreget(score: string): ref Elem
+{
+ for(l := qidscores; l != nil; l = tl l) {
+ (s, n) := hd l;
+ if(s == score)
+ return get(n);
+ }
+ return nil;
+}
+
+scoreput(score: string): int
+{
+ qidscores = (score, ++lastqid)::qidscores;
+ return lastqid;
+}
+
+
+Elem.new(nqid: int, vd: ref Vacdir, de: ref Direntry, pqid: int): ref Elem
+{
+ (e, me) := vd.open(de);
+ if(e == nil)
+ return nil;
+ if(de.mode & Vac->Modedir)
+ return ref Elem.Dir(nqid, de, e.size, Vacdir.new(session, e, me), pqid, 0, 0, nil);
+ return ref Elem.File(nqid, de, e.size, Vacfile.new(session, e));
+}
+
+Elem.stat(e: self ref Elem): ref Sys->Dir
+{
+ d := e.de.mkdir();
+ d.qid.path = big e.qid;
+ d.length = e.size;
+ return d;
+}
+
+walk(ed: ref Elem.Dir, name: string): (ref Elem, string)
+{
+ if(name == "..")
+ return (get(ed.pqid), nil);
+
+ if(ed.qid == 0) {
+ ne := scoreget(name);
+ if(ne == nil) {
+ (ok, score) := Score.parse(name);
+ if(ok != 0)
+ return (nil, "bad score: "+name);
+
+ (vd, de, err) := vac->vdroot(session, score);
+ if(err != nil)
+ return (nil, err);
+
+ nqid := scoreput(name);
+ ne = ref Elem.Dir(nqid, de, big 0, vd, ed.qid, 0, 0, nil);
+ set(ne);
+ }
+ return (ne, nil);
+ }
+
+ de := ed.vd.walk(name);
+ if(de == nil)
+ return (nil, sprint("%r"));
+ ne := childget(ed.qid, de.qid);
+ if(ne == nil) {
+ nqid := childput(ed.qid, de.qid);
+ ne = Elem.new(nqid, ed.vd, de, ed.qid);
+ set(ne);
+ }
+ return (ne, nil);
+}
+
+get(qid: int): ref Elem
+{
+ for(l := elems[qid % len elems]; l != nil; l = tl l)
+ if((hd l).qid == qid)
+ return hd l;
+ return nil;
+}
+
+set(e: ref Elem)
+{
+ elems[e.qid % len elems] = e::elems[e.qid % len elems];
+}
+
+getfile(qid: int): ref Elem.File
+{
+ pick file := get(qid) {
+ File => return file;
+ }
+ error("internal error, getfile");
+ return nil;
+}
+
+getdir(qid: int): ref Elem.Dir
+{
+ pick d := get(qid) {
+ Dir => return d;
+ }
+ error("internal error, getdir");
+ return nil;
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ arg := load Arg Arg->PATH;
+ str = load String String->PATH;
+ daytime = load Daytime Daytime->PATH;
+ venti = load Venti Venti->PATH;
+ styx = load Styx Styx->PATH;
+ styxservers = load Styxservers Styxservers->PATH;
+ vac = load Vac Vac->PATH;
+ if(venti == nil || vac == nil)
+ error("loading venti,vac");
+ sys->pctl(sys->NEWPGRP, nil);
+ venti->init();
+ vac->init();
+ styx->init();
+ styxservers->init(styx);
+
+ arg->init(args);
+ arg->setusage(arg->progname()+" [-Ddp] [-a addr] [[tag:]score]");
+ while((ch := arg->opt()) != 0)
+ case ch {
+ 'D' => styxservers->traceset(1);
+ 'a' => addr = arg->earg();
+ 'd' => dflag++;
+ vac->dflag++;
+ 'p' => pflag++;
+ * => warn(sprint("bad option: -%c", ch));
+ arg->usage();
+ }
+ args = arg->argv();
+ if(len args > 1)
+ arg->usage();
+
+ score: ref Score;
+ if(len args == 1) {
+ (tag, scorestr) := str->splitstrr(hd args, ":");
+ if(tag != nil)
+ tag = tag[:len tag-1];
+ if(tag == nil)
+ tag = "vac";
+ if(tag != "vac")
+ error("bad score type: "+tag);
+ (ok, s) := Score.parse(scorestr);
+ if(ok != 0)
+ error("bad score: "+scorestr);
+ score = ref s;
+ }
+
+ (cok, conn) := sys->dial(addr, nil);
+ if(cok < 0)
+ error(sprint("dialing %s: %r", addr));
+ say("have connection");
+
+ fd := conn.dfd;
+ session = Session.new(fd);
+ if(session == nil)
+ error(sprint("handshake: %r"));
+ say("have handshake");
+
+ rqid := 0;
+ red: ref Elem;
+ if(args == nil) {
+ de := Direntry.new();
+ de.uid = de.gid = de.mid = user();
+ de.ctime = de.atime = de.mtime = daytime->now();
+ de.mode = Vac->Modedir|8r755;
+ de.emode = Sys->DMDIR|8r755;
+ red = ref Elem.Dir(rqid, de, big 0, nil, rqid, 0, 0, nil);
+ } else {
+ (vd, de, err) := vac->vdroot(session, *score);
+ if(err != nil)
+ error(err);
+ rqid = ++lastqid;
+ red = ref Elem.Dir(rqid, de, big 0, vd, rqid, 0, 0, nil);
+ }
+ set(red);
+ say(sprint("have root, qid=%d", rqid));
+
+ navchan := chan of ref Navop;
+ nav := Navigator.new(navchan);
+ spawn navigator(navchan);
+
+ msgc: chan of ref Tmsg;
+ (msgc, ss) = Styxserver.new(sys->fildes(0), nav, big rqid);
+
+ for(;;) {
+ pick m := <- msgc {
+ Readerror =>
+ say("read error: "+m.error);
+
+ Read =>
+ say(sprint("have read, offset=%ubd count=%d", m.offset, m.count));
+ (c, err) := ss.canread(m);
+ if(c == nil){
+ ss.reply(ref Rmsg.Error(m.tag, err));
+ break;
+ }
+ if(c.qtype & Sys->QTDIR){
+ ss.default(m);
+ break;
+ }
+
+ ef := getfile(int c.path);
+ n := m.count;
+ a := array[n] of byte;
+ have := ef.vf.pread(a, n, m.offset);
+ if(have < 0) {
+ ss.reply(ref Rmsg.Error(m.tag, sprint("%r")));
+ break;
+ }
+ ss.reply(ref Rmsg.Read(m.tag, a[:have]));
+
+ Open =>
+ (c, mode, f, err) := canopen(m);
+ if(c == nil){
+ ss.reply(ref Rmsg.Error(m.tag, err));
+ break;
+ }
+ c.open(mode, f.qid);
+ ss.reply(ref Rmsg.Open(m.tag, f.qid, ss.iounit()));
+
+
+ * =>
+ ss.default(m);
+ }
+ }
+}
+
+canopen(m: ref Tmsg.Open): (ref Fid, int, ref Sys->Dir, string)
+{
+ c := ss.getfid(m.fid);
+ if(c == nil)
+ return (nil, 0, nil, Styxservers->Ebadfid);
+ if(c.isopen)
+ return (nil, 0, nil, Styxservers->Eopen);
+ (f, err) := ss.t.stat(c.path);
+ if(f == nil)
+ return (nil, 0, nil, err);
+ mode := styxservers->openmode(m.mode);
+ if(mode == -1)
+ return (nil, 0, nil, Styxservers->Ebadarg);
+ if(mode != Sys->OREAD && f.qid.qtype & Sys->QTDIR)
+ return (nil, 0, nil, Styxservers->Eperm);
+ if(!pflag && !styxservers->openok(c.uname, m.mode, f.mode, f.uid, f.gid))
+ return (nil, 0, nil, Styxservers->Eperm);
+ if(m.mode & Sys->ORCLOSE)
+ return (nil, 0, nil, Styxservers->Eperm);
+ return (c, mode, f, err);
+}
+
+navigator(c: chan of ref Navop)
+{
+loop:
+ for(;;) {
+ navop := <- c;
+ say(sprint("have navop, path=%bd", navop.path));
+ pick n := navop {
+ Stat =>
+ say(sprint("have stat"));
+ n.reply <-= (get(int n.path).stat(), nil);
+
+ Walk =>
+ say(sprint("have walk, name=%q", n.name));
+ ed := getdir(int n.path);
+ (ne, err) := walk(ed, n.name);
+ if(err != nil) {
+ n.reply <-= (nil, err);
+ break;
+ }
+ n.reply <-= (ne.stat(), nil);
+
+ Readdir =>
+ say(sprint("have readdir path=%bd offset=%d count=%d", n.path, n.offset, n.count));
+ if(n.path == big 0) {
+ n.reply <-= (nil, nil);
+ break;
+ }
+ ed := getdir(int n.path);
+ if(n.offset == 0) {
+ ed.vd.rewind();
+ ed.offset = 0;
+ ed.prev = array[0] of ref Sys->Dir;
+ }
+ skip := n.offset-ed.offset;
+ if(skip > 0) {
+ ed.prev = ed.prev[skip:];
+ ed.nprev -= skip;
+ ed.offset += skip;
+ }
+ if(len ed.prev < n.count) {
+ newprev := array[n.count] of ref Sys->Dir;
+ newprev[:] = ed.prev;
+ ed.prev = newprev;
+ }
+ while(ed.nprev < n.count) {
+ (ok, de) := ed.vd.readdir();
+ if(ok < 0) {
+ say(sprint("readdir error: %r"));
+ n.reply <-= (nil, sprint("reading directory: %r"));
+ continue loop;
+ }
+ if(de == nil)
+ break;
+ ne := childget(ed.qid, de.qid);
+ if(ne == nil) {
+ nqid := childput(ed.qid, de.qid);
+ ne = Elem.new(nqid, ed.vd, de, ed.qid);
+ }
+ d := ne.stat();
+ ed.prev[ed.nprev++] = d;
+ n.reply <-= (d, nil);
+ }
+ n.reply <-= (nil, nil);
+ }
+ }
+}
+
+user(): string
+{
+ if((fd := sys->open("/dev/user", Sys->OREAD)) != nil
+ && (n := sys->read(fd, d := array[128] of byte, len d)) > 0)
+ return string d[:n];
+ return "nobody";
+}
+
+error(s: string)
+{
+ killgrp();
+ fprint(fildes(2), "%s\n", s);
+ raise "fail:"+s;
+}
+
+warn(s: string)
+{
+ fprint(fildes(2), "%s\n", s);
+}
+
+say(s: string)
+{
+ if(dflag)
+ warn(s);
+}
+
+killgrp()
+{
+ fd := sys->open("/prog/"+string sys->pctl(0, nil)+"/ctl", sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "killgrp\n");
+}
diff --git a/appl/cmd/vacget.b b/appl/cmd/vacget.b
new file mode 100644
index 00000000..c14ad28a
--- /dev/null
+++ b/appl/cmd/vacget.b
@@ -0,0 +1,199 @@
+implement Vacget;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "arg.m";
+include "string.m";
+include "venti.m";
+include "vac.m";
+
+str: String;
+venti: Venti;
+vac: Vac;
+
+print, sprint, fprint, fildes: import sys;
+Score, Session: import venti;
+Roottype, Dirtype, Pointertype0, Datatype: import venti;
+Root, Entry, Direntry, Metablock, Metaentry, Entrysize, Modeperm, Modeappend, Modeexcl, Modedir, Modesnapshot, Vacdir, Vacfile, Source: import vac;
+
+Vacget: module {
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+
+addr := "net!$venti!venti";
+dflag := vflag := pflag := tflag := 0;
+session: ref Session;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ arg := load Arg Arg->PATH;
+ str = load String String->PATH;
+ venti = load Venti Venti->PATH;
+ vac = load Vac Vac->PATH;
+ if(venti == nil || vac == nil)
+ error("loading venti,vac");
+ venti->init();
+ vac->init();
+
+ arg->init(args);
+ arg->setusage(sprint("%s [-dtv] [-a addr] [tag:]score", arg->progname()));
+ while((c := arg->opt()) != 0)
+ case c {
+ 'a' => addr = arg->earg();
+ 'd' => dflag++;
+ vac->dflag++;
+ 'p' => pflag++;
+ 't' => tflag++;
+ 'v' => vflag++;
+ * => warn(sprint("bad option: -%c", c));
+ arg->usage();
+ }
+ args = arg->argv();
+ if(len args != 1)
+ arg->usage();
+
+ (tag, scorestr) := str->splitstrr(hd args, ":");
+ if(tag != nil)
+ tag = tag[:len tag-1];
+ if(tag == nil)
+ tag = "vac";
+ if(tag != "vac")
+ error("bad score type: "+tag);
+
+ (sok, score) := Score.parse(scorestr);
+ if(sok != 0)
+ error("bad score: "+scorestr);
+ say("have score");
+
+ (cok, conn) := sys->dial(addr, nil);
+ if(cok < 0)
+ error(sprint("dialing %s: %r", addr));
+ say("have connection");
+
+ fd := conn.dfd;
+ session = Session.new(fd);
+ if(session == nil)
+ error(sprint("handshake: %r"));
+ say("have handshake");
+
+ (vd, nil, err) := vac->vdroot(session, score);
+ if(err != nil)
+ error(err);
+
+ say("starting walk");
+ walk(".", vd);
+}
+
+create(path: string, omode: int, de: ref Direntry): ref Sys->FD
+{
+ perm := Sys->DMDIR | Sys->DMAPPEND | Sys->DMEXCL | Sys->DMTMP;
+ perm &= de.emode;
+ perm |= 8r666;
+ if(de.emode & Sys->DMDIR)
+ perm |= 8r777;
+ fd := sys->create(path, omode, perm);
+ if(fd == nil)
+ return nil;
+ if(pflag) {
+ d := sys->nulldir;
+ d.uid = de.uid;
+ d.gid = de.gid;
+ d.mode = de.emode;
+ if(sys->fwstat(fd, d) != 0) {
+ warn(sprint("fwstat %s for uid/gid/mode: %r", path));
+ d.uid = d.gid = "";
+ sys->fwstat(fd, d);
+ }
+ }
+ return fd;
+}
+
+walk(path: string, vd: ref Vacdir)
+{
+ say("start of walk: "+path);
+ for(;;) {
+ (n, de) := vd.readdir();
+ if(n < 0)
+ error(sprint("reading direntry in %s: %r", path));
+ if(n == 0)
+ break;
+ say("walk: have direntry, elem="+de.elem);
+ newpath := path+"/"+de.elem;
+ (e, me) := vd.open(de);
+ if(e == nil)
+ error(sprint("reading entry for %s: %r", newpath));
+
+ oflags := de.mode&~(Modeperm|Modeappend|Modeexcl|Modedir|Modesnapshot);
+ if(oflags)
+ warn(sprint("%s: not all bits in mode can be set: 0x%x", newpath, oflags));
+
+ if(tflag || vflag)
+ print("%s\n", newpath);
+
+ if(me != nil) {
+ if(!tflag)
+ create(newpath, Sys->OREAD, de);
+ # ignore error, possibly for already existing dir.
+ # if creating really failed, writing files in the dir will fail later on.
+ walk(newpath, Vacdir.new(session, e, me));
+ } else {
+ if(tflag)
+ continue;
+ say("writing file");
+ fd := create(newpath, sys->OWRITE, de);
+ if(fd == nil)
+ error(sprint("creating %s: %r", newpath));
+ bio := bufio->fopen(fd, bufio->OWRITE);
+ if(bio == nil)
+ error(sprint("bufio fopen %s: %r", newpath));
+
+ buf := array[sys->ATOMICIO] of byte;
+ vf := Vacfile.new(session, e);
+ for(;;) {
+ rn := vf.read(buf, len buf);
+ if(rn == 0)
+ break;
+ if(rn < 0)
+ error(sprint("reading vac %s: %r", newpath));
+ wn := bio.write(buf, rn);
+ if(wn != rn)
+ error(sprint("writing local %s: %r", newpath));
+ }
+ bok := bio.flush();
+ bio.close();
+ if(bok == bufio->ERROR || bok == bufio->EOF)
+ error(sprint("bufio close: %r"));
+
+ if(pflag) {
+ d := sys->nulldir;
+ d.mtime = de.mtime;
+ if(sys->fwstat(fd, d) < 0)
+ warn(sprint("fwstat %s for mtime: %r", newpath));
+ }
+ fd = nil;
+ }
+ }
+}
+
+error(s: string)
+{
+ fprint(fildes(2), "%s\n", s);
+ raise "fail:"+s;
+}
+
+warn(s: string)
+{
+ fprint(fildes(2), "%s\n", s);
+}
+
+say(s: string)
+{
+ if(dflag)
+ warn(s);
+}
diff --git a/appl/cmd/vacput.b b/appl/cmd/vacput.b
new file mode 100644
index 00000000..e01bdd36
--- /dev/null
+++ b/appl/cmd/vacput.b
@@ -0,0 +1,258 @@
+implement Vacput;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "daytime.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "arg.m";
+include "string.m";
+include "venti.m";
+include "vac.m";
+
+daytime: Daytime;
+str: String;
+venti: Venti;
+vac: Vac;
+
+print, sprint, fprint, fildes: import sys;
+Score, Session: import venti;
+Roottype, Dirtype, Pointertype0, Datatype: import venti;
+Root, Entry, Direntry, Metablock, Metaentry, Entrysize, File, Sink, MSink: import vac;
+
+Vacput: module {
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+
+addr := "net!$venti!venti";
+dflag := 0;
+vflag := 0;
+blocksize := vac->Dsize;
+session: ref Session;
+name := "vac";
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ daytime = load Daytime Daytime->PATH;
+ bufio = load Bufio Bufio->PATH;
+ arg := load Arg Arg->PATH;
+ str = load String String->PATH;
+ venti = load Venti Venti->PATH;
+ vac = load Vac Vac->PATH;
+ if(venti == nil || vac == nil)
+ error("loading venti,vac");
+ venti->init();
+ vac->init();
+
+ arg->init(args);
+ arg->setusage(sprint("%s [-dtv] [-a addr] [-b blocksize] [-n name] path ...", arg->progname()));
+ while((c := arg->opt()) != 0)
+ case c {
+ 'a' => addr = arg->earg();
+ 'b' => blocksize = int arg->earg();
+ 'n' => name = arg->earg();
+ 'd' => dflag++;
+ vac->dflag++;
+ 'v' => vflag++;
+ * => warn(sprint("bad option: -%c", c));
+ arg->usage();
+ }
+ args = arg->argv();
+ if(len args == 0)
+ arg->usage();
+
+ (cok, conn) := sys->dial(addr, nil);
+ if(cok < 0)
+ error(sprint("dialing %s: %r", addr));
+ say("have connection");
+
+ fd := conn.dfd;
+ session = Session.new(fd);
+ if(session == nil)
+ error(sprint("handshake: %r"));
+ say("have handshake");
+
+ topde: ref Direntry;
+ if(len args == 1 && ((nil, d) := sys->stat(hd args)).t0 == 0 && d.mode&Sys->DMDIR) {
+ topde = Direntry.mk(d);
+ topde.elem = name;
+ } else {
+ topde = Direntry.new();
+ topde.elem = name;
+ topde.uid = topde.gid = user();
+ topde.mode = 8r777|Vac->Modedir;
+ topde.mtime = topde.atime = 0;
+ }
+ topde.ctime = daytime->now();
+
+ s := Sink.new(session, blocksize);
+ ms := MSink.new(session, blocksize);
+ for(; args != nil; args = tl args)
+ writepath(hd args, s, ms);
+ say("tree written");
+
+ e0 := s.finish();
+ if(e0 == nil)
+ error(sprint("writing top entry: %r"));
+ e1 := ms.finish();
+ if(e1 == nil)
+ error(sprint("writing top meta entry: %r"));
+ say(sprint("top entries written (%s, %s)", e0.score.text(), e1.score.text()));
+ s2 := MSink.new(session, blocksize);
+ if(s2.add(topde) < 0)
+ error(sprint("adding direntry for top entries: %r"));
+ e2 := s2.finish();
+ say("top meta entry written, "+e2.score.text());
+
+ td := array[Entrysize*3] of byte;
+ td[0*Entrysize:] = e0.pack();
+ td[1*Entrysize:] = e1.pack();
+ td[2*Entrysize:] = e2.pack();
+ (tok, tscore) := session.write(Dirtype, td);
+ if(tok < 0)
+ error(sprint("writing top-level entries: %r"));
+ say("top entry written, "+tscore.text());
+
+ root := Root.new(name, "vac", tscore, blocksize, nil);
+ rd := root.pack();
+ if(rd == nil)
+ error(sprint("root pack: %r"));
+ (rok, rscore) := session.write(Roottype, rd);
+ if(rok < 0)
+ error(sprint("writing root score: %r"));
+ say("root written, "+rscore.text());
+ print("vac:%s\n", rscore.text());
+ if(session.sync() < 0)
+ error(sprint("syncing server: %r"));
+}
+
+writepath(path: string, s: ref Sink, ms: ref MSink)
+{
+ if(vflag)
+ print("%s\n", path);
+say("writepath "+path);
+ fd := sys->open(path, sys->OREAD);
+ if(fd == nil)
+ error(sprint("opening %s: %r", path));
+ (ok, dir) := sys->fstat(fd);
+ if(ok < 0)
+ error(sprint("fstat %s: %r", path));
+say("writepath: file opened");
+ if(dir.mode&sys->DMAUTH) {
+ warn(path+": is auth file, skipping");
+ return;
+ }
+ if(dir.mode&sys->DMTMP) {
+ warn(path+": is temporary file, skipping");
+ return;
+ }
+
+ e, me: ref Entry;
+ de: ref Direntry;
+ if(dir.mode & sys->DMDIR) {
+say("writepath: file is dir");
+ ns := Sink.new(session, blocksize);
+ nms := MSink.new(session, blocksize);
+ for(;;) {
+ (n, dirs) := sys->dirread(fd);
+ if(n == 0)
+ break;
+ if(n < 0)
+ error(sprint("dirread %s: %r", path));
+ for(i := 0; i < len dirs; i++) {
+ d := dirs[i];
+ npath := path+"/"+d.name;
+ writepath(npath, ns, nms);
+ }
+ }
+ e = ns.finish();
+ if(e == nil)
+ error(sprint("error flushing dirsink for %s: %r", path));
+ me = nms.finish();
+ if(me == nil)
+ error(sprint("error flushing metasink for %s: %r", path));
+ } else {
+say("writepath: file is normale file");
+ e = writefile(path, fd);
+ if(e == nil)
+ error(sprint("error flushing filesink for %s: %r", path));
+ }
+say("writepath: wrote path, "+e.score.text());
+
+ de = Direntry.mk(dir);
+say("writepath: have direntry");
+
+ i := s.add(e);
+ if(i < 0)
+ error(sprint("adding entry to sink: %r"));
+ mi := 0;
+ if(me != nil)
+ mi = s.add(me);
+ if(mi < 0)
+ error(sprint("adding mentry to sink: %r"));
+ de.entry = i;
+ de.mentry = mi;
+ i = ms.add(de);
+ if(i < 0)
+ error(sprint("adding direntry to msink: %r"));
+say("writepath done");
+}
+
+writefile(path: string, fd: ref Sys->FD): ref Entry
+{
+ bio := bufio->fopen(fd, bufio->OREAD);
+ if(bio == nil)
+ error(sprint("bufio opening %s: %r", path));
+ say(sprint("bufio opened path %s", path));
+
+ f := File.new(session, Datatype, blocksize);
+ for(;;) {
+ buf := array[blocksize] of byte;
+ n := 0;
+ while(n < len buf) {
+ want := len buf - n;
+ have := bio.read(buf[n:], want);
+ if(have == 0)
+ break;
+ if(have < 0)
+ error(sprint("reading %s: %r", path));
+ n += have;
+ }
+ say(sprint("have buf, length %d", n));
+
+ if(f.write(buf[:n]) < 0)
+ error(sprint("writing %s: %r", path));
+ if(n != len buf)
+ break;
+ }
+ bio.close();
+ return f.finish();
+}
+
+user(): string
+{
+ if((fd := sys->open("/dev/user", Sys->OREAD)) != nil
+ && (n := sys->read(fd, d := array[128] of byte, len d)) > 0)
+ return string d[:n];
+ return "nobody";
+}
+
+error(s: string)
+{
+ warn(s);
+ raise "fail:"+s;
+}
+
+warn(s: string)
+{
+ fprint(fildes(2), "%s\n", s);
+}
+
+say(s: string)
+{
+ if(dflag)
+ warn(s);
+}
diff --git a/appl/lib/mkfile b/appl/lib/mkfile
index a5736dbf..a6eaf912 100644
--- a/appl/lib/mkfile
+++ b/appl/lib/mkfile
@@ -137,6 +137,7 @@ TARG=\
translate.dis\
ubfa.dis\
url.dis\
+ vac.dis\
venti.dis\
virgil.dis\
volume.dis\
@@ -232,3 +233,4 @@ rfc822.dis: $ROOT/module/rfc822.m
csv.dis: $ROOT/module/csv.m
json.dis: $ROOT/module/json.m
lists.dis: $ROOT/module/lists.m
+vac.dis: $ROOT/module/vac.m $ROOT/module/venti.m
diff --git a/appl/lib/vac.b b/appl/lib/vac.b
new file mode 100644
index 00000000..53967661
--- /dev/null
+++ b/appl/lib/vac.b
@@ -0,0 +1,1094 @@
+implement Vac;
+
+include "sys.m";
+include "venti.m";
+include "vac.m";
+
+sys: Sys;
+venti: Venti;
+
+werrstr, sprint, fprint, fildes: import sys;
+Roottype, Dirtype, Pointertype0, Datatype: import venti;
+Score, Session, Scoresize: import venti;
+
+dflag = 0;
+
+# from venti.b
+BIT8SZ: con 1;
+BIT16SZ: con 2;
+BIT32SZ: con 4;
+BIT48SZ: con 6;
+BIT64SZ: con 8;
+
+Rootnamelen: con 128;
+Rootversion: con 2;
+Direntrymagic: con 16r1c4d9072;
+Metablockmagic: con 16r5656fc79;
+Maxstringsize: con 1000;
+
+blankroot: Root;
+blankentry: Entry;
+blankdirentry: Direntry;
+blankmetablock: Metablock;
+blankmetaentry: Metaentry;
+
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ venti = load Venti Venti->PATH;
+ venti->init();
+}
+
+pstring(a: array of byte, o: int, s: string): int
+{
+ sa := array of byte s; # could do conversion ourselves
+ n := len sa;
+ a[o] = byte (n >> 8);
+ a[o+1] = byte n;
+ a[o+2:] = sa;
+ return o+BIT16SZ+n;
+}
+
+gstring(a: array of byte, o: int): (string, int)
+{
+ if(o < 0 || o+BIT16SZ > len a)
+ return (nil, -1);
+ l := (int a[o] << 8) | int a[o+1];
+ if(l > Maxstringsize)
+ return (nil, -1);
+ o += BIT16SZ;
+ e := o+l;
+ if(e > len a)
+ return (nil, -1);
+ return (string a[o:e], e);
+}
+
+gtstring(a: array of byte, o: int, n: int): string
+{
+ e := o + n;
+ if(e > len a)
+ return nil;
+ for(i := o; i < e; i++)
+ if(a[i] == byte 0)
+ break;
+ return string a[o:i];
+}
+
+
+Root.new(name, rtype: string, score: Score, blocksize: int, prev: ref Score): ref Root
+{
+ return ref Root(Rootversion, name, rtype, score, blocksize, prev);
+}
+
+Root.pack(r: self ref Root): array of byte
+{
+ d := array[Rootsize] of byte;
+ i := 0;
+ i = p16(d, i, r.version);
+ i = ptstring(d, i, r.name, Rootnamelen);
+ if(i < 0)
+ return nil;
+ i = ptstring(d, i, r.rtype, Rootnamelen);
+ if(i < 0)
+ return nil;
+ i = pscore(d, i, r.score);
+ i = p16(d, i, r.blocksize);
+ if(r.prev == nil) {
+ for(j := 0; j < Scoresize; j++)
+ d[i+j] = byte 0;
+ i += Scoresize;
+ } else
+ i = pscore(d, i, *r.prev);
+ if(i != len d) {
+ sys->werrstr("root pack, bad length: "+string i);
+ return nil;
+ }
+ return d;
+}
+
+Root.unpack(d: array of byte): ref Root
+{
+ if(len d != Rootsize){
+ sys->werrstr("root entry is wrong length");
+ return nil;
+ }
+ r := ref blankroot;
+ r.version = g16(d, 0);
+ if(r.version != Rootversion){
+ sys->werrstr("unknown root version");
+ return nil;
+ }
+ o := BIT16SZ;
+ r.name = gtstring(d, o, Rootnamelen);
+ o += Rootnamelen;
+ r.rtype = gtstring(d, o, Rootnamelen);
+ o += Rootnamelen;
+ r.score = gscore(d, o);
+ o += Scoresize;
+ r.blocksize = g16(d, o);
+ o += BIT16SZ;
+ r.prev = ref gscore(d, o);
+ return r;
+}
+
+Entry.new(psize, dsize, flags: int, size: big, score: Venti->Score): ref Entry
+{
+ return ref Entry(0, psize, dsize, (flags&Entrydepthmask)>>Entrydepthshift, flags, size, score);
+}
+
+Entry.pack(e: self ref Entry): array of byte
+{
+ d := array[Entrysize] of byte;
+ i := 0;
+ i = p32(d, i, e.gen);
+ i = p16(d, i, e.psize);
+ i = p16(d, i, e.dsize);
+ e.flags |= e.depth<<Entrydepthshift;
+ d[i++] = byte e.flags;
+ for(j := 0; j < 5; j++)
+ d[i++] = byte 0;
+ i = p48(d, i, e.size);
+ i = pscore(d, i, e.score);
+ if(i != len d) {
+ werrstr(sprint("bad length, have %d, want %d", i, len d));
+ return nil;
+ }
+ return d;
+}
+
+Entry.unpack(d: array of byte): ref Entry
+{
+ if(len d != Entrysize){
+ sys->werrstr("entry is wrong length");
+ return nil;
+ }
+ e := ref blankentry;
+ i := 0;
+ e.gen = g32(d, i);
+ i += BIT32SZ;
+ e.psize = g16(d, i);
+ i += BIT16SZ;
+ e.dsize = g16(d, i);
+ i += BIT16SZ;
+ e.flags = int d[i];
+ e.depth = (e.flags & Entrydepthmask) >> Entrydepthshift;
+ e.flags &= ~Entrydepthmask;
+ i += BIT8SZ;
+ i += 5; # skip something...
+ e.size = g48(d, i);
+ i += BIT48SZ;
+ e.score = gscore(d, i);
+ i += Scoresize;
+ if((e.flags & Entryactive) == 0)
+ return e;
+ if(!checksize(e.psize) || !checksize(e.dsize)){
+ sys->werrstr(sys->sprint("bad blocksize (%d or %d)", e.psize, e.dsize));
+ return nil;
+ }
+ return e;
+}
+
+Direntry.new(): ref Direntry
+{
+ return ref Direntry(9, "", 0, 0, 0, 0, big 0, "", "", "", 0, 0, 0, 0, 0, 0);
+}
+
+Direntry.mk(d: Sys->Dir): ref Direntry
+{
+ atime := 0; # d.atime;
+ mode := d.mode&Modeperm;
+ if(d.mode&sys->DMAPPEND)
+ mode |= Modeappend;
+ if(d.mode&sys->DMEXCL)
+ mode |= Modeexcl;
+ if(d.mode&sys->DMDIR)
+ mode |= Modedir;
+ if(d.mode&sys->DMTMP)
+ mode |= Modetemp;
+ return ref Direntry(9, d.name, 0, 0, 0, 0, d.qid.path, d.uid, d.gid, d.muid, d.mtime, 0, 0, atime, mode, d.mode);
+}
+
+Direntry.mkdir(de: self ref Direntry): ref Sys->Dir
+{
+ d := ref sys->nulldir;
+ d.name = de.elem;
+ d.uid = de.uid;
+ d.gid = de.gid;
+ d.muid = de.mid;
+ d.qid.path = de.qid;
+ d.qid.vers = 0;
+ d.qid.qtype = de.emode>>24;
+ d.mode = de.emode;
+ d.atime = de.atime;
+ d.mtime = de.mtime;
+ d.length = big 0;
+ return d;
+}
+
+strlen(s: string): int
+{
+ return 2+len array of byte s;
+}
+
+Direntry.pack(de: self ref Direntry): array of byte
+{
+ # assume version 9
+ length := 4+2+strlen(de.elem)+4+4+4+4+8+strlen(de.uid)+strlen(de.gid)+strlen(de.mid)+4+4+4+4+4; # + qidspace?
+
+ d := array[length] of byte;
+ i := 0;
+ i = p32(d, i, Direntrymagic);
+ i = p16(d, i, de.version);
+ i = pstring(d, i, de.elem);
+ i = p32(d, i, de.entry);
+ if(de.version == 9) {
+ i = p32(d, i, de.gen);
+ i = p32(d, i, de.mentry);
+ i = p32(d, i, de.mgen);
+ }
+ i = p64(d, i, de.qid);
+ i = pstring(d, i, de.uid);
+ i = pstring(d, i, de.gid);
+ i = pstring(d, i, de.mid);
+ i = p32(d, i, de.mtime);
+ i = p32(d, i, de.mcount);
+ i = p32(d, i, de.ctime);
+ i = p32(d, i, de.atime);
+ i = p32(d, i, de.mode);
+ if(i != len d) {
+ werrstr(sprint("bad length for direntry (expected %d, have %d)", len d, i));
+ return nil;
+ }
+ return d;
+}
+
+Direntry.unpack(d: array of byte): ref Direntry
+{
+ {
+ de := ref blankdirentry;
+ i := 0;
+ magic: int;
+ (magic, i) = eg32(d, i);
+ if(magic != Direntrymagic) {
+ werrstr(sprint("bad magic (%x, want %x)", magic, Direntrymagic));
+ return nil;
+ }
+ (de.version, i) = eg16(d, i);
+ if(de.version != 8 && de.version != 9) {
+ werrstr(sprint("bad version (%d)", de.version));
+ return nil;
+ }
+ (de.elem, i) = egstring(d, i);
+ (de.entry, i) = eg32(d, i);
+ case de.version {
+ 8 =>
+ de.gen = 0;
+ de.mentry = de.entry+1;
+ de.mgen = 0;
+ 9 =>
+ (de.gen, i) = eg32(d, i);
+ (de.mentry, i) = eg32(d, i);
+ (de.mgen, i) = eg32(d, i);
+ }
+ (de.qid, i) = eg64(d, i);
+ (de.uid, i) = egstring(d, i);
+ (de.gid, i) = egstring(d, i);
+ (de.mid, i) = egstring(d, i);
+ (de.mtime, i) = eg32(d, i);
+ (de.mcount, i) = eg32(d, i);
+ (de.ctime, i) = eg32(d, i);
+ (de.atime, i) = eg32(d, i);
+ (de.mode, i) = eg32(d, i);
+ de.emode = de.mode&Modeperm;
+ if(de.mode&Modeappend)
+ de.emode |= sys->DMAPPEND;
+ if(de.mode&Modeexcl)
+ de.emode |= sys->DMEXCL;
+ if(de.mode&Modedir)
+ de.emode |= sys->DMDIR;
+ if(de.mode&Modetemp)
+ de.emode |= sys->DMTMP;
+ if(de.version == 9)
+ ; # xxx handle qid space?, can be in here
+ return de;
+ } exception e {
+ "too small:*" =>
+ werrstr("direntry "+e);
+ return nil;
+ * =>
+ raise e;
+ }
+}
+
+
+Metablock.new(): ref Metablock
+{
+ return ref Metablock(0, 0, 0, 0);
+}
+
+Metablock.pack(mb: self ref Metablock, d: array of byte)
+{
+ i := 0;
+ i = p32(d, i, Metablockmagic);
+ i = p16(d, i, mb.size);
+ i = p16(d, i, mb.free);
+ i = p16(d, i, mb.maxindex);
+ i = p16(d, i, mb.nindex);
+}
+
+Metablock.unpack(d: array of byte): ref Metablock
+{
+ if(len d < Metablocksize) {
+ werrstr(sprint("bad length for metablock (%d, want %d)", len d, Metablocksize));
+ return nil;
+ }
+ i := 0;
+ magic := g32(d, i);
+ if(magic != Metablockmagic && magic != Metablockmagic+1) {
+ werrstr(sprint("bad magic for metablock (%x, need %x)", magic, Metablockmagic));
+ return nil;
+ }
+ i += BIT32SZ;
+
+ mb := ref blankmetablock;
+ mb.size = g16(d, i);
+ i += BIT16SZ;
+ mb.free = g16(d, i);
+ i += BIT16SZ;
+ mb.maxindex = g16(d, i);
+ i += BIT16SZ;
+ mb.nindex = g16(d, i);
+ i += BIT16SZ;
+ if(mb.nindex == 0) {
+ werrstr("bad metablock, nindex=0");
+ return nil;
+ }
+ return mb;
+}
+
+Metaentry.pack(me: self ref Metaentry, d: array of byte)
+{
+ i := 0;
+ i = p16(d, i, me.offset);
+ i = p16(d, i, me.size);
+}
+
+Metaentry.unpack(d: array of byte, i: int): ref Metaentry
+{
+ o := Metablocksize+i*Metaentrysize;
+ if(o+Metaentrysize > len d) {
+ werrstr(sprint("meta entry lies outside meta block, i=%d", i));
+ return nil;
+ }
+
+ me := ref blankmetaentry;
+ me.offset = g16(d, o);
+ o += BIT16SZ;
+ me.size = g16(d, o);
+ o += BIT16SZ;
+ if(me.offset+me.size > len d) {
+ werrstr(sprint("meta entry points outside meta block, i=%d", i));
+ return nil;
+ }
+ return me;
+}
+
+
+Page.new(dsize: int): ref Page
+{
+ psize := (dsize/Scoresize)*Scoresize;
+ return ref Page(array[psize] of byte, 0);
+}
+
+Page.add(p: self ref Page, s: Score)
+{
+ for(i := 0; i < Scoresize; i++)
+ p.d[p.o+i] = s.a[i];
+ p.o += Scoresize;
+}
+
+Page.full(p: self ref Page): int
+{
+ return p.o+Scoresize > len p.d;
+}
+
+Page.data(p: self ref Page): array of byte
+{
+ for(i := p.o; i >= Scoresize; i -= Scoresize)
+ if(!Score(p.d[i-Scoresize:i]).eq(Score.zero()))
+ break;
+ return p.d[:i];
+}
+
+
+File.new(s: ref Session, dtype, dsize: int): ref File
+{
+ p := array[1] of ref Page;
+ p[0] = Page.new(dsize);
+ return ref File(p, dtype, dsize, big 0, s);
+}
+
+fflush(f: ref File, last: int): (int, ref Entry)
+{
+ for(i := 0; i < len f.p; i++) {
+ if(!last && !f.p[i].full())
+ return (0, nil);
+ if(last && f.p[i].o == Scoresize) {
+ flags := Entryactive;
+ if(f.dtype & Dirtype)
+ flags |= Entrydir;
+ flags |= i<<Entrydepthshift;
+ score := Score(f.p[i].data());
+ if(len score.a == 0)
+ score = Score.zero();
+ return (0, Entry.new(len f.p[i].d, f.dsize, flags, f.size, score));
+ }
+ (ok, score) := f.s.write(Pointertype0+i, f.p[i].data());
+ if(ok < 0)
+ return (-1, nil);
+ f.p[i] = Page.new(f.dsize);
+ if(i+1 == len f.p) {
+ newp := array[len f.p+1] of ref Page;
+ newp[:] = f.p;
+ newp[len newp-1] = Page.new(f.dsize);
+ f.p = newp;
+ }
+ f.p[i+1].add(score);
+ }
+ werrstr("internal error in fflush");
+ return (-1, nil);
+}
+
+File.write(f: self ref File, d: array of byte): int
+{
+ (fok, nil) := fflush(f, 0);
+ if(fok < 0)
+ return -1;
+ length := len d;
+ for(i := len d; i > 0; i--)
+ if(d[i-1] != byte 0)
+ break;
+ d = d[:i];
+ (ok, score) := f.s.write(f.dtype, d);
+ if(ok < 0)
+ return -1;
+ f.size += big length;
+ f.p[0].add(score);
+ return 0;
+}
+
+File.finish(f: self ref File): ref Entry
+{
+ (ok, e) := fflush(f, 1);
+ if(ok < 0)
+ return nil;
+ return e;
+}
+
+
+Sink.new(s: ref Venti->Session, dsize: int): ref Sink
+{
+ dirdsize := (dsize/Entrysize)*Entrysize;
+ return ref Sink(File.new(s, Dirtype, dsize), array[dirdsize] of byte, 0, 0);
+}
+
+Sink.add(m: self ref Sink, e: ref Entry): int
+{
+ ed := e.pack();
+ if(ed == nil)
+ return -1;
+ n := len m.d - m.nd;
+ if(n > len ed)
+ n = len ed;
+ m.d[m.nd:] = ed[:n];
+ m.nd += n;
+ if(n < len ed) {
+ if(m.f.write(m.d) < 0)
+ return -1;
+ m.nd = len ed - n;
+ m.d[:] = ed[n:];
+ }
+ return m.ne++;
+}
+
+Sink.finish(m: self ref Sink): ref Entry
+{
+ if(m.nd > 0)
+ if(m.f.write(m.d[:m.nd]) < 0)
+ return nil;
+ e := m.f.finish();
+ e.dsize = len m.d;
+ return e;
+}
+
+
+elemcmp(a, b: array of byte, fossil: int): int
+{
+ for(i := 0; i < len a && i < len b; i++)
+ if(a[i] != b[i])
+ return (int a[i] - int b[i]);
+ if(fossil)
+ return len a - len b;
+ return len b - len a;
+}
+
+Mentry.cmp(a, b: ref Mentry): int
+{
+ return elemcmp(array of byte a.elem, array of byte b.elem, 0);
+}
+
+MSink.new(s: ref Venti->Session, dsize: int): ref MSink
+{
+ return ref MSink(File.new(s, Dirtype, dsize), array[dsize] of byte, 0, nil);
+}
+
+l2a[T](l: list of T): array of T
+{
+ a := array[len l] of T;
+ i := 0;
+ for(; l != nil; l = tl l)
+ a[i++] = hd l;
+ return a;
+}
+
+insertsort[T](a: array of T)
+ for { T => cmp: fn(a, b: T): int; }
+{
+ for(i := 1; i < len a; i++) {
+ tmp := a[i];
+ for(j := i; j > 0 && T.cmp(a[j-1], tmp) > 0; j--)
+ a[j] = a[j-1];
+ a[j] = tmp;
+ }
+}
+
+mflush(m: ref MSink, last: int): int
+{
+ d := array[len m.de] of byte;
+
+ me := l2a(m.l);
+ insertsort(me);
+ o := Metablocksize;
+ deo := o+len m.l*Metaentrysize;
+ for(i := 0; i < len me; i++) {
+ me[i].me.offset += deo;
+ me[i].me.pack(d[o:]);
+ o += Metaentrysize;
+ }
+ d[o:] = m.de[:m.nde];
+ o += m.nde;
+ if(!last)
+ while(o < len d)
+ d[o++] = byte 0;
+
+ mb := Metablock.new();
+ mb.nindex = len m.l;
+ mb.maxindex = mb.nindex;
+ mb.free = 0;
+ mb.size = o;
+ mb.pack(d);
+
+ if(m.f.write(d[:o]) < 0)
+ return -1;
+ m.nde = 0;
+ m.l = nil;
+ return 0;
+}
+
+MSink.add(m: self ref MSink, de: ref Direntry): int
+{
+ d := de.pack();
+ if(d == nil)
+ return -1;
+say(sprint("msink: adding direntry, length %d", len d));
+ if(Metablocksize+len m.l*Metaentrysize+m.nde + Metaentrysize+len d > len m.de)
+ if(mflush(m, 0) < 0)
+ return -1;
+ m.de[m.nde:] = d;
+ m.l = ref Mentry(de.elem, ref Metaentry(m.nde, len d))::m.l;
+ m.nde += len d;
+ return 0;
+}
+
+MSink.finish(m: self ref MSink): ref Entry
+{
+ if(m.nde > 0)
+ mflush(m, 1);
+ return m.f.finish();
+}
+
+Source.new(s: ref Session, e: ref Entry): ref Source
+{
+ return ref Source(s, e);
+}
+
+power(b, e: int): big
+{
+ r := big 1;
+ while(e-- > 0)
+ r *= big b;
+ return r;
+}
+
+blocksize(e: ref Entry): int
+{
+ if(e.psize > e.dsize)
+ return e.psize;
+ return e.dsize;
+}
+
+Source.get(s: self ref Source, i: big, d: array of byte): int
+{
+ npages := (s.e.size+big (s.e.dsize-1))/big s.e.dsize;
+ if(i*big s.e.dsize >= s.e.size)
+ return 0;
+
+ want := s.e.dsize;
+ if(i == npages-big 1)
+ want = int (s.e.size - i*big s.e.dsize);
+ last := s.e.score;
+ bsize := blocksize(s.e);
+ buf: array of byte;
+
+ npp := s.e.psize/Scoresize; # scores per pointer block
+ np := power(npp, s.e.depth-1); # blocks referenced by score at this depth
+ for(depth := s.e.depth; depth >= 0; depth--) {
+ dtype := Pointertype0+depth-1;
+ if(depth == 0) {
+ dtype = Datatype;
+ if(s.e.flags & Entrydir)
+ dtype = Dirtype;
+ bsize = want;
+ }
+ buf = s.session.read(last, dtype, bsize);
+ if(buf == nil)
+ return -1;
+ if(depth > 0) {
+ pi := int (i / np);
+ i %= np;
+ np /= big npp;
+ o := (pi+1)*Scoresize;
+ if(o <= len buf)
+ last = Score(buf[o-Scoresize:o]);
+ else
+ last = Score.zero();
+ }
+ }
+ for(j := len buf; j < want; j++)
+ d[j] = byte 0;
+ d[:] = buf;
+ return want;
+}
+
+
+Vacfile.mk(s: ref Source): ref Vacfile
+{
+ return ref Vacfile(s, big 0);
+}
+
+Vacfile.new(s: ref Session, e: ref Entry): ref Vacfile
+{
+ return Vacfile.mk(Source.new(s, e));
+}
+
+Vacfile.seek(v: self ref Vacfile, offset: big): big
+{
+ v.o += offset;
+ if(v.o > v.s.e.size)
+ v.o = v.s.e.size;
+ return v.o;
+}
+
+Vacfile.read(v: self ref Vacfile, d: array of byte, n: int): int
+{
+ have := v.pread(d, n, v.o);
+ if(have > 0)
+ v.o += big have;
+ return have;
+}
+
+Vacfile.pread(v: self ref Vacfile, d: array of byte, n: int, offset: big): int
+{
+ dsize := v.s.e.dsize;
+ have := v.s.get(big (offset/big dsize), buf := array[dsize] of byte);
+ if(have <= 0)
+ return have;
+say(sprint("vacfile.read: have=%d dsize=%d", have, dsize));
+ o := int (offset % big dsize);
+ have -= o;
+ if(have > n)
+ have = n;
+ if(have <= 0)
+ return 0;
+ d[:] = buf[o:o+have];
+ return have;
+}
+
+
+Vacdir.mk(vf: ref Vacfile, ms: ref Source): ref Vacdir
+{
+ return ref Vacdir(vf, ms, big 0, 0);
+}
+
+Vacdir.new(session: ref Session, e, me: ref Entry): ref Vacdir
+{
+ vf := Vacfile.new(session, e);
+ ms := Source.new(session, me);
+ return Vacdir.mk(vf, ms);
+
+}
+
+mecmp(d: array of byte, i: int, elem: string, fromfossil: int): (int, int)
+{
+ me := Metaentry.unpack(d, i);
+ if(me == nil)
+ return (0, 1);
+ o := me.offset+6;
+ n := g16(d, o);
+ o += BIT16SZ;
+ if(o+n > len d) {
+ werrstr("bad elem in direntry");
+ return (0, 1);
+ }
+ return (elemcmp(d[o:o+n], array of byte elem, fromfossil), 0);
+}
+
+finddirentry(d: array of byte, elem: string): (int, ref Direntry)
+{
+ mb := Metablock.unpack(d);
+ if(mb == nil)
+ return (-1, nil);
+ fromfossil := g32(d, 0) == Metablockmagic+1;
+
+ left := 0;
+ right := mb.nindex;
+ while(left+1 != right) {
+ mid := (left+right)/2;
+ (c, err) := mecmp(d, mid, elem, fromfossil);
+ if(err)
+ return (-1, nil);
+ if(c <= 0)
+ left = mid;
+ else
+ right = mid;
+ if(c == 0)
+ break;
+ }
+ de := readdirentry(d, left);
+ if(de != nil && de.elem == elem)
+ return (1, de);
+ return (0, nil);
+}
+
+Vacdir.walk(v: self ref Vacdir, elem: string): ref Direntry
+{
+ i := big 0;
+ for(;;) {
+ n := v.ms.get(i, buf := array[v.ms.e.dsize] of byte);
+ if(n < 0)
+ return nil;
+ if(n == 0)
+ break;
+ (ok, de) := finddirentry(buf[:n], elem);
+ if(ok < 0)
+ return nil;
+ if(de != nil)
+ return de;
+ i++;
+ }
+ werrstr(sprint("no such file or directory"));
+ return nil;
+}
+
+vfreadentry(vf: ref Vacfile, entry: int): ref Entry
+{
+say(sprint("vfreadentry: reading entry=%d", entry));
+ ebuf := array[Entrysize] of byte;
+ n := vf.pread(ebuf, len ebuf, big entry*big Entrysize);
+ if(n < 0)
+ return nil;
+ if(n != len ebuf) {
+ werrstr(sprint("bad archive, entry=%d not present", entry));
+ return nil;
+ }
+ e := Entry.unpack(ebuf);
+ if(~e.flags&Entryactive) {
+ werrstr("entry not active");
+ return nil;
+ }
+ if(e.flags&Entrylocal) {
+ werrstr("entry is local");
+ return nil;
+ }
+say(sprint("vreadentry: have entry, score=%s", e.score.text()));
+ return e;
+}
+
+Vacdir.open(vd: self ref Vacdir, de: ref Direntry): (ref Entry, ref Entry)
+{
+say(sprint("vacdir.open: opening entry=%d", de.entry));
+ e := vfreadentry(vd.vf, de.entry);
+ if(e == nil)
+ return (nil, nil);
+ isdir1 := de.mode & Modedir;
+ isdir2 := e.flags & Entrydir;
+ if(isdir1 && !isdir2 || !isdir1 && isdir2) {
+ werrstr("direntry directory bit does not match entry directory bit");
+ return (nil, nil);
+ }
+say(sprint("vacdir.open: have entry, score=%s size=%bd", e.score.text(), e.size));
+ me: ref Entry;
+ if(de.mode&Modedir) {
+ me = vfreadentry(vd.vf, de.mentry);
+ if(me == nil)
+ return (nil, nil);
+say(sprint("vacdir.open: have mentry, score=%s size=%bd", me.score.text(), e.size));
+ }
+ return (e, me);
+}
+
+readdirentry(buf: array of byte, i: int): ref Direntry
+{
+ me := Metaentry.unpack(buf, i);
+ if(me == nil)
+ return nil;
+ o := me.offset;
+ de := Direntry.unpack(buf[o:o+me.size]);
+ if(badelem(de.elem)) {
+ werrstr(sprint("bad direntry: %s", de.elem));
+ return nil;
+ }
+ return de;
+}
+
+has(c: int, s: string): int
+{
+ for(i := 0; i < len s; i++)
+ if(s[i] == c)
+ return 1;
+ return 0;
+}
+
+badelem(elem: string): int
+{
+ return elem == "" || elem == "." || elem == ".." || has('/', elem) || has(0, elem);
+}
+
+Vacdir.readdir(vd: self ref Vacdir): (int, ref Direntry)
+{
+say(sprint("vacdir.readdir: ms.e.size=%bd vd.p=%bd vd.i=%d", vd.ms.e.size, vd.p, vd.i));
+ dsize := vd.ms.e.dsize;
+ n := vd.ms.get(vd.p, buf := array[dsize] of byte);
+ if(n <= 0)
+ return (n, nil);
+say(sprint("vacdir.readdir: have buf, length=%d e.size=%bd", n, vd.ms.e.size));
+ mb := Metablock.unpack(buf);
+ if(mb == nil)
+ return (-1, nil);
+ de := readdirentry(buf, vd.i);
+ if(de == nil)
+ return (-1, nil);
+ vd.i++;
+ if(vd.i >= mb.nindex) {
+ vd.p++;
+ vd.i = 0;
+ }
+say("vacdir.readdir: have entry");
+ return (1, de);
+}
+
+Vacdir.rewind(vd: self ref Vacdir)
+{
+ vd.p = big 0;
+ vd.i = 0;
+}
+
+
+vdroot(session: ref Session, score: Venti->Score): (ref Vacdir, ref Direntry, string)
+{
+ d := session.read(score, Roottype, Rootsize);
+ if(d == nil)
+ return (nil, nil, sprint("reading vac score: %r"));
+ r := Root.unpack(d);
+ if(r == nil)
+ return (nil, nil, sprint("bad vac root block: %r"));
+ say("have root");
+ topscore := r.score;
+
+ d = session.read(topscore, Dirtype, 3*Entrysize);
+ if(d == nil)
+ return (nil, nil, sprint("reading rootdir score: %r"));
+ if(len d != 3*Entrysize) {
+ say("top entries not in directory of 3 elements, assuming it's from fossil");
+ if(len d % Entrysize != 0 && len d == 2*Entrysize != 0) # what's in the second 40 bytes? looks like 2nd 20 bytes of it is zero score
+ return (nil, nil, sprint("bad fossil rootdir, have %d bytes, need %d or %d", len d, Entrysize, 2*Entrysize));
+ e := Entry.unpack(d[:Entrysize]);
+ if(e == nil)
+ return (nil, nil, sprint("unpacking fossil top-level entry: %r"));
+ topscore = e.score;
+ d = session.read(topscore, Dirtype, 3*Entrysize);
+ if(d == nil)
+ return (nil, nil, sprint("reading fossil rootdir block: %r"));
+ say("have fossil top entries");
+ }
+ say("have top entries");
+
+ e := array[3] of ref Entry;
+ j := 0;
+ for(i := 0; i+Entrysize <= len d; i += Entrysize) {
+ e[j] = Entry.unpack(d[i:i+Entrysize]);
+ if(e[j] == nil)
+ return (nil, nil, sprint("reading root entry %d: %r", j));
+ j++;
+ }
+ say("top entries unpacked");
+
+ mroot := Vacdir.new(session, nil, e[2]);
+ (ok, de) := mroot.readdir();
+ if(ok <= 0)
+ return (nil, nil, sprint("reading root meta entry: %r"));
+
+say(sprint("vdroot: new score=%s", score.text()));
+ return (Vacdir.new(session, e[0], e[1]), de, nil);
+}
+
+
+checksize(n: int): int
+{
+ if(n < 256 || n > Venti->Maxlumpsize) {
+ sys->werrstr("bad block size");
+ return 0;
+ }
+ return 1;
+}
+
+gscore(f: array of byte, i: int): Score
+{
+ s := Score(array[Scoresize] of byte);
+ s.a[0:] = f[i:i+Scoresize];
+ return s;
+}
+
+g16(f: array of byte, i: int): int
+{
+ return (int f[i] << 8) | int f[i+1];
+}
+
+g32(f: array of byte, i: int): int
+{
+ return (((((int f[i+0] << 8) | int f[i+1]) << 8) | int f[i+2]) << 8) | int f[i+3];
+}
+
+g48(f: array of byte, i: int): big
+{
+ b1 := (((((int f[i+0] << 8) | int f[i+1]) << 8) | int f[i+2]) << 8) | int f[i+3];
+ b0 := (int f[i+4] << 8) | int f[i+5];
+ return (big b1 << 16) | big b0;
+}
+
+g64(f: array of byte, i: int): big
+{
+ b0 := (((((int f[i+3] << 8) | int f[i+2]) << 8) | int f[i+1]) << 8) | int f[i];
+ b1 := (((((int f[i+7] << 8) | int f[i+6]) << 8) | int f[i+5]) << 8) | int f[i+4];
+ return (big b1 << 32) | (big b0 & 16rFFFFFFFF);
+}
+
+p16(d: array of byte, i: int, v: int): int
+{
+ d[i+0] = byte (v>>8);
+ d[i+1] = byte v;
+ return i+BIT16SZ;
+}
+
+p32(d: array of byte, i: int, v: int): int
+{
+ p16(d, i+0, v>>16);
+ p16(d, i+2, v);
+ return i+BIT32SZ;
+}
+
+p48(d: array of byte, i: int, v: big): int
+{
+ p16(d, i+0, int (v>>32));
+ p32(d, i+2, int v);
+ return i+BIT48SZ;
+}
+
+p64(d: array of byte, i: int, v: big): int
+{
+ p32(d, i+0, int (v>>32));
+ p32(d, i+0, int v);
+ return i+BIT64SZ;
+}
+
+ptstring(d: array of byte, i: int, s: string, l: int): int
+{
+ a := array of byte s;
+ if(len a > l) {
+ sys->werrstr("string too long: "+s);
+ return -1;
+ }
+ for(j := 0; j < len a; j++)
+ d[i+j] = a[j];
+ while(j < l)
+ d[i+j++] = byte 0;
+ return i+l;
+}
+
+pscore(d: array of byte, i: int, s: Score): int
+{
+ for(j := 0; j < Scoresize; j++)
+ d[i+j] = s.a[j];
+ return i+Scoresize;
+}
+
+echeck(f: array of byte, i: int, l: int)
+{
+ if(i+l > len f)
+ raise sprint("too small: buffer length is %d, requested %d bytes starting at offset %d", len f, l, i);
+}
+
+egscore(f: array of byte, i: int): (Score, int)
+{
+ echeck(f, i, Scoresize);
+ return (gscore(f, i), i+Scoresize);
+}
+
+egstring(a: array of byte, o: int): (string, int)
+{
+ (s, no) := gstring(a, o);
+ if(no == -1)
+ raise sprint("too small: string runs outside buffer (length %d)", len a);
+ return (s, no);
+}
+
+eg16(f: array of byte, i: int): (int, int)
+{
+ echeck(f, i, BIT16SZ);
+ return (g16(f, i), i+BIT16SZ);
+}
+
+eg32(f: array of byte, i: int): (int, int)
+{
+ echeck(f, i, BIT32SZ);
+ return (g32(f, i), i+BIT32SZ);
+}
+
+eg48(f: array of byte, i: int): (big, int)
+{
+ echeck(f, i, BIT48SZ);
+ return (g48(f, i), i+BIT48SZ);
+}
+
+eg64(f: array of byte, i: int): (big, int)
+{
+ echeck(f, i, BIT64SZ);
+ return (g64(f, i), i+BIT64SZ);
+}
+
+say(s: string)
+{
+ if(dflag)
+ fprint(fildes(2), "%s\n", s);
+}
diff --git a/dis/lib/vac.dis b/dis/lib/vac.dis
new file mode 100644
index 00000000..7c7d558c
--- /dev/null
+++ b/dis/lib/vac.dis
Binary files differ
diff --git a/dis/vacfs.dis b/dis/vacfs.dis
new file mode 100644
index 00000000..45c1da64
--- /dev/null
+++ b/dis/vacfs.dis
Binary files differ
diff --git a/dis/vacget.dis b/dis/vacget.dis
new file mode 100644
index 00000000..c68f1c98
--- /dev/null
+++ b/dis/vacget.dis
Binary files differ
diff --git a/dis/vacput.dis b/dis/vacput.dis
new file mode 100644
index 00000000..2b45397c
--- /dev/null
+++ b/dis/vacput.dis
Binary files differ
diff --git a/lib/proto/inferno b/lib/proto/inferno
index 00cbf765..93c252b9 100644
--- a/lib/proto/inferno
+++ b/lib/proto/inferno
@@ -397,6 +397,7 @@ appl
broke.b
bytes.b
cal.b
+ calc.b
cat.b
cd.b
cddb.b
@@ -543,7 +544,6 @@ appl
ppptest.b
script.b
script.m
- obootpd.b
ping.b
ppp
mkfile
@@ -565,7 +565,6 @@ appl
itest.b
itreplay.b
kill.b
- lc.b
lego
clock.b
clockface.b
@@ -632,7 +631,6 @@ appl
symb.b
tk.b
xeq.b
- mathcalc.b
mc.b
md5sum.b
mdb.b
@@ -756,6 +754,9 @@ appl
mkfile
uuencode.b
uudecode.b
+ vacfs.b
+ vacget.b
+ vacput.b
wav2iaf.b
wc.b
webgrab.b
@@ -925,6 +926,7 @@ appl
ibm866.b
iso8859-1.b
iso8859-10.b
+ iso8859-15.b
iso8859-2.b
iso8859-3.b
iso8859-4.b
@@ -1118,6 +1120,7 @@ appl
usbmct.b
usbmouse.b
utils.m
+ vac.b
venti.b
virgil.b
volume.b
@@ -1480,6 +1483,7 @@ dis
broke.dis
bytes.dis
cal.dis
+ calc.dis
cat.dis
cd.dis
cddb.dis
@@ -1552,7 +1556,7 @@ dis
itest.dis
itreplay.dis
kill.dis
- lc.dis
+ lc
lego
+
lib
@@ -1709,6 +1713,7 @@ dis
usbmass.dis
usbmct.dis
usbmouse.dis
+ vac.dis
venti.dis
virgil.dis
volume.dis
@@ -1737,7 +1742,6 @@ dis
mash.dis
math
+
- mathcalc.dis
mc.dis
md5sum.dis
mdb.dis
@@ -1831,6 +1835,9 @@ dis
usbd.dis
uudecode.dis
uuencode.dis
+ vacfs.dis
+ vacget.dis
+ vacput.dis
wav2iaf.dis
wc.dis
webgrab.dis
@@ -1963,6 +1970,7 @@ doc
limbotk
tk.ms
tk.pdf
+ manhow.pdf
mk.ms
mk.pdf
perform
@@ -2005,7 +2013,6 @@ fonts
psrename
icons
+
-install
keydb
countersigned
keys 600 inferno inferno /keydb/keys.dist
@@ -2069,6 +2076,7 @@ lib
+
units
usbdb
+ video.specs
wmcharon
wmsetup
wmsetup.grid
@@ -2261,7 +2269,7 @@ module
libc.m
libc0.m
linalg.m
- lists.mappl
+ lists.m
loader.m
lock.m
man.m
@@ -2333,6 +2341,7 @@ module
uris.m
url.m
usb.m
+ vac.m
venti.m
volume.m
wait.m
@@ -2376,6 +2385,8 @@ services
inferno.gif
start.html
vnlogo.gif
+src
+sys
#mux
# basic
# email
diff --git a/lib/proto/src b/lib/proto/src
index 4779b1af..7b8c5241 100644
--- a/lib/proto/src
+++ b/lib/proto/src
@@ -1,3 +1,4 @@
+INSTALL
asm
NOTICE
asm.h
@@ -769,6 +770,19 @@ libtk
utils.c
windw.c
xdata.c
+man
+ lib
+ checkman.awk
+ colophon
+ lookman
+ +
+ man
+ notes
+ permind
+ preface
+ secindex
+ title
+ trademarks
tools
NOTICE
db
diff --git a/man/1/INDEX b/man/1/INDEX
index 3d907507..159a36d9 100644
--- a/man/1/INDEX
+++ b/man/1/INDEX
@@ -283,6 +283,8 @@ uniq uniq
units units
uudecode uuencode
uuencode uuencode
+vacget vacget
+vacput vacget
wc wc
webgrab webgrab
wish wish
diff --git a/man/1/vacget b/man/1/vacget
new file mode 100644
index 00000000..8a5b04f9
--- /dev/null
+++ b/man/1/vacget
@@ -0,0 +1,75 @@
+.TH VACGET 1
+.SH NAME
+vacget, vacput \- venti archive utilities
+.SH SYNOPSIS
+.B vacget
+[
+.B -vdpt
+] [
+.B -a
+.I addr
+]
+.I vac:score
+.br
+.B vacput
+[
+.B -vd
+] [
+.B -a
+.I addr
+] [
+.B -b
+.I blocksize
+] [
+.B -n
+.I name
+]
+.I path ...
+.SH DESCRIPTION
+.I Vacget
+retrieves a venti archive from a venti server to the current working directory.
+.PP
+.I Vacput
+writes a venti archive to a venti server. The
+.I paths
+are walked recursively and all files and directories written to the archive. Temporary files, i.e. those with the
+.B DMTMP
+bit set, are skipped. Writing only changed files relative to a previously written archive is not implemented.
+.TP
+.B -d
+Print debug messages.
+.TP
+.B -p
+Try to preserve file permissions and owner/group. Only for vacget.
+.TP
+.B -v
+Be verbose. Prints files as they are being retrieved or written.
+.TP
+.B -t
+List files, do not write them. Only for vacget.
+.TP
+.BI -a " address"
+Dial
+.I address
+instead of the default venti server.
+.TP
+.BI -b " blocksize"
+Use blocks with
+.I blocksize
+bytes instead of the default 8192 byte blocks. Only for vacput.
+.TP
+.BI -n " name"
+Use
+.I name
+as the name in the root block. Only for vacput.
+.SH SOURCE
+.B /appl/cmd/vacget.b
+.br
+.B /appl/cmd/vacput.b
+.SH SEE ALSO
+.IR vcache (1),
+.IR venti (2),
+.IR vacfs (4),
+.IR ventisrv (8)
+.SH BUGS
+These tools need more testing.
diff --git a/man/4/INDEX b/man/4/INDEX
index ce9e711e..69dcfb7c 100644
--- a/man/4/INDEX
+++ b/man/4/INDEX
@@ -29,3 +29,4 @@ regquery registry
spree spree
tarfs tarfs
trfs trfs
+vacfs vacfs
diff --git a/man/4/vacfs b/man/4/vacfs
new file mode 100644
index 00000000..1cfeaff2
--- /dev/null
+++ b/man/4/vacfs
@@ -0,0 +1,47 @@
+.TH VACFS 4
+.SH NAME
+vacfs \- mount venti archive
+.SH SYNOPSIS
+.B vacfs
+[
+.B -Ddp
+] [
+.B -a
+.I addr
+]
+.I [vac:score]
+.SH DESCRIPTION
+.I Vacfs
+makes the contents of a venti archive available over styx. Standard input is used for reading and writing styx messages.
+If
+.I score
+is not specified, vacfs
+serves multiple venti archives. In this case the root directory lists no files, but walking to a directory
+.I score
+opens the venti archive with that score. Note that the vacfs does not support writing.
+.TP
+.B -D
+Print styx trace messages.
+.TP
+.B -d
+Print debug messages.
+.TP
+.B -p
+Disable permission checking.
+.TP
+.BI -a " address"
+Dial
+.I address
+instead of the default venti server.
+.SH SOURCE
+.B /appl/cmd/vacfs.b
+.SH SEE ALSO
+.IR vacget (1),
+.IR vacput (1),
+.IR vcache (1),
+.IR venti (2),
+.IR ventisrv (8)
+.SH BUGS
+Vacfs needs more testing.
+.br
+When the venti connection is broken, directories appear empty.
diff --git a/man/lib/checkman.awk b/man/lib/checkman.awk
new file mode 100644
index 00000000..ea17e8d6
--- /dev/null
+++ b/man/lib/checkman.awk
@@ -0,0 +1,187 @@
+# Usage: awk -f checkman.awk man?/*.?
+#
+# Checks:
+# - .TH is first line, and has proper name section number
+# - sections are in order NAME, SYNOPSIS, DESCRIPTION, EXAMPLES,
+# FILES, SOURCE, SEE ALSO, DIAGNOSTICS, BUGS
+# - there's a manual page for each cross-referenced page
+
+BEGIN {
+
+# .SH sections should come in the following order
+
+ Weight["NAME"] = 1
+ Weight["SYNOPSIS"] = 2
+ Weight["DESCRIPTION"] = 4
+ Weight["EXAMPLE"] = 8
+ Weight["EXAMPLES"] = 16
+ Weight["FILES"] = 32
+ Weight["SOURCE"] = 64
+ Weight["SEE ALSO"] = 128
+ Weight["DIAGNOSTICS"] = 256
+ Weight["SYSTEM CALLS"] = 512
+ Weight["BUGS"] = 1024
+}
+
+FNR==1 {
+ n = length(FILENAME)
+ seclen = 0
+ if (substr(FILENAME, 2, 1) == "/")
+ seclen = 1
+ else if (substr(FILENAME, 3, 1) == "/")
+ seclen = 2
+ if(seclen == 0)
+ print "FILENAME", FILENAME, "not of form [0-9][0-9]?/*"
+ else if(!(substr(FILENAME, seclen+2, n-seclen-1) ~ /^[A-Z]+$/)){
+ section = substr(FILENAME, 1, seclen)
+ name = substr(FILENAME, seclen+2, n-seclen-1)
+ if($1 != ".TH" || NF != 3)
+ print "First line of", FILENAME, "not a proper .TH"
+ else if($2 != toupper(name) || substr($3, 1, seclen) != section){
+ if($2!="INTRO" || name!="0intro")
+ print ".TH of", FILENAME, "doesn't match filename"
+ }else
+ Pages[section "/" $2] = 1
+ }
+ Sh = 0
+ }
+
+$1 == ".SH" {
+ if(inex)
+ print "Unterminated .EX in", FILENAME, ":", $0
+ inex = 0;
+ if (substr($2, 1, 1) == "\"") {
+ if (NF == 2) {
+ print "Unneeded quote in", FILENAME, ":", $0
+ $2 = substr($2, 2, length($2)-2)
+ } else if (NF == 3) {
+ $2 = substr($2, 2) substr($3, 1, length($3)-1)
+ NF = 2
+ }
+ }
+ w = Weight[$2]
+ if (w) {
+ if (w < Sh)
+ print "Heading", $2, "out of order in", FILENAME
+ Sh += w
+ }
+}
+
+$1 == ".EX" {
+ if(inex)
+ print "Nested .EX in", FILENAME, ":", $0
+ inex = 1
+}
+
+$1 == ".EE" {
+ if(!inex)
+ print "Bad .EE in", FILENAME, ":", $0
+ inex = 0;
+}
+
+$0 ~ /^\..*\([0-9]\)/ {
+ if ($1 == ".IR" && $3 ~ /\([0-9]\)/) {
+ name = $2
+ section = $3
+ }else if ($1 == ".RI" && $2 == "(" && $4 ~ /\([0-9]\)/) {
+ name = $3
+ section = $4
+ }else if ($1 == ".IR" && $3 ~ /9.\([0-9]\)/) {
+ name = $2
+ section = "9"
+ }else if ($1 == ".RI" && $2 == "(" && $4 ~ /9.\([0-9]\)/) {
+ name = $3
+ section = "9"
+ } else {
+ print "Possible bad cross-reference format in", FILENAME
+ print $0
+ next
+ }
+ gsub(/[^0-9]/, "", section)
+ Refs[section "/" toupper(name)]++
+}
+
+END {
+ print "Checking Cross-Referenced Pages"
+ for (i in Refs) {
+ if (!(i in Pages))
+ print "Need", tolower(i)
+ }
+ print ""
+ print "Checking commands"
+ getindex("/usr/inferno/doc/man/1")
+ getindex("/usr/inferno/doc/man/8")
+ Skipdirs["lib"] = 1
+ getbinlist("/usr/inferno/dis")
+ for (i in List) {
+ if (!(i in Index))
+ print "Need", i, "(in " List[i] ")"
+ }
+ clearindex()
+ clearlist()
+ print ""
+ print "Checking libraries"
+ getindex("/usr/inferno/doc/man/2")
+ getbinlist("/usr/inferno/dis/lib")
+ for (i in List) {
+ if (!(i in Index))
+ print "Need", i, "(in " List[i] ")"
+ }
+}
+
+func getindex(dir, fname)
+{
+ fname = dir "/INDEX"
+ while ((getline < fname) > 0)
+ Index[$1] = dir
+ close(fname)
+}
+
+func getindex9(dir, fname)
+{
+ fname = dir "/INDEX"
+ while ((getline < fname) > 0)
+ if($2 ~ "(getflags|picopen|getcmap)")
+ Index[$1] = dir
+ close(fname)
+}
+
+func getbinlist(dir, cmd, subdirs, nsd)
+{
+ cmd = "ls -p -l " dir
+ nsd = 0
+ while (cmd | getline) {
+ if ($1 ~ /^d/) {
+ if (!($10 in Skipdirs))
+ subdirs[++nsd] = $10
+ } else {
+ sub(".dis", "", $10)
+ List[$10] = dir
+ }
+ }
+ for ( ; nsd > 0 ; nsd--)
+ getbinlist(dir "/" subdirs[nsd])
+ close(cmd)
+}
+
+func getnmlist(lib, cmd)
+{
+ cmd = "nm -g -h " lib
+ while (cmd | getline) {
+ if (($1 == "T" || $1 == "L") && $2 !~ "^_")
+ List[$2] = lib
+ }
+ close(cmd)
+}
+
+func clearindex( i)
+{
+ for (i in Index)
+ delete Index[i]
+}
+
+func clearlist( i)
+{
+ for (i in List)
+ delete List[i]
+}
diff --git a/man/lib/colophon b/man/lib/colophon
new file mode 100644
index 00000000..ac7180e8
--- /dev/null
+++ b/man/lib/colophon
@@ -0,0 +1,26 @@
+.sp |3i
+.vs 12
+.ll 5.1i
+.in |0.4i
+.fp 1 R LucidaSans
+.ft 1
+.fi
+This book was typeset by the authors using
+.sp
+.ft CW
+.ps -1
+.ce
+eqn | tbl | troff -mpm | lp -d stdout | cropmarks
+.ps +1
+.ft R
+.sp
+The input text was characters from the Unicode Standard encoded in UTF-8.
+.sp
+The fonts used were Lucida Sans,
+in a special version incorporating over 1700 characters from the Unicode Standard,
+along with Lucida Sans Italic,
+Lucida Sans DemiBold, and
+Lucida Typewriter,
+designed by Bigelow & Holmes, Atherton, California.
+The hinted Adobe Type 1 representation of the fonts was
+provided by Y&Y Inc., 45 Walden Street, Concord, MA, 01742, USA.
diff --git a/man/lib/lookman/junkwords b/man/lib/lookman/junkwords
new file mode 100644
index 00000000..a0e8c6d4
--- /dev/null
+++ b/man/lib/lookman/junkwords
@@ -0,0 +1,509 @@
+
+a
+about
+above
+according
+across
+act
+action
+after
+again
+against
+ago
+air
+all
+allowed
+almost
+along
+already
+also
+although
+always
+american
+among
+an
+and
+another
+any
+anything
+appear
+appears
+appropriate
+are
+area
+areas
+argument
+arguments
+around
+as
+asked
+associated
+at
+available
+away
+b
+back
+be
+became
+because
+become
+been
+before
+began
+begin
+beginning
+begins
+behind
+being
+below
+best
+better
+between
+big
+board
+body
+both
+boy
+brought
+business
+but
+by
+c
+call
+called
+calls
+came
+can
+cannot
+car
+case
+cause
+causes
+certain
+change
+changed
+changes
+changing
+children
+church
+city
+close
+college
+come
+command
+commands
+community
+company
+contain
+containing
+contents
+corresponding
+could
+country
+course
+court
+currently
+d
+day
+days
+death
+depending
+description
+development
+did
+didnt
+different
+do
+does
+doesnt
+done
+dont
+door
+down
+during
+e
+each
+early
+economic
+effect
+either
+element
+elements
+else
+empty
+encountered
+end
+enough
+even
+ever
+every
+example
+examples
+experience
+eyes
+f
+face
+fact
+family
+far
+federal
+feet
+felt
+few
+field
+find
+first
+five
+followed
+following
+follows
+for
+force
+form
+found
+four
+free
+from
+function
+functions
+g
+gave
+general
+get
+give
+given
+gives
+giving
+go
+god
+going
+good
+got
+government
+great
+group
+h
+had
+half
+hand
+hands
+has
+have
+having
+he
+head
+heard
+held
+help
+her
+here
+high
+him
+himself
+his
+history
+home
+house
+how
+however
+human
+i
+if
+ignored
+im
+immediately
+implemented
+important
+in
+including
+individual
+information
+initial
+instead
+interest
+interpreted
+into
+is
+it
+its
+itself
+j
+job
+john
+just
+k
+keep
+kind
+knew
+know
+known
+l
+large
+last
+later
+law
+least
+left
+less
+let
+life
+light
+like
+line
+little
+local
+long
+look
+looked
+love
+m
+made
+major
+make
+makes
+making
+man
+many
+marked
+matter
+may
+me
+meaning
+means
+members
+men
+might
+mind
+miss
+moment
+money
+more
+most
+mr
+mrs
+much
+multiple
+must
+my
+n
+name
+named
+national
+need
+never
+new
+next
+night
+no
+nonzero
+normal
+normally
+not
+nothing
+now
+number
+numbers
+o
+of
+off
+office
+often
+old
+on
+once
+one
+only
+open
+or
+order
+other
+others
+otherwise
+our
+out
+over
+own
+p
+part
+passed
+past
+people
+per
+perhaps
+period
+place
+placed
+point
+political
+position
+possible
+power
+preceded
+present
+president
+previous
+probably
+problem
+problems
+program
+public
+put
+q
+question
+quite
+r
+rather
+really
+reason
+represented
+respectively
+result
+right
+room
+s
+said
+same
+saw
+say
+school
+second
+see
+seemed
+seems
+seen
+sense
+separate
+separated
+sequence
+service
+set
+sets
+several
+shall
+she
+should
+show
+side
+simple
+since
+single
+small
+so
+social
+society
+some
+something
+south
+special
+specified
+start
+starting
+state
+states
+still
+street
+study
+subsequent
+such
+supplied
+sure
+synopsis
+system
+t
+take
+taken
+tell
+terminated
+terminating
+than
+that
+the
+their
+them
+themselves
+then
+there
+these
+they
+thing
+things
+think
+this
+those
+though
+thought
+three
+through
+thus
+to
+today
+together
+told
+too
+took
+top
+toward
+trailing
+treated
+true
+turn
+turned
+two
+u
+under
+united
+until
+up
+upon
+us
+use
+used
+useful
+uses
+usually
+v
+very
+w
+want
+war
+was
+water
+way
+we
+week
+well
+went
+were
+west
+what
+when
+where
+whether
+which
+while
+white
+who
+whole
+whose
+why
+will
+with
+within
+without
+word
+words
+work
+world
+would
+x
+y
+year
+years
+yet
+york
+you
+young
+your
+z
+zero
+zeros
diff --git a/man/lib/lookman/mkindex b/man/lib/lookman/mkindex
new file mode 100755
index 00000000..45c48d74
--- /dev/null
+++ b/man/lib/lookman/mkindex
@@ -0,0 +1,13 @@
+#!/bin/rc
+# creates the index used by lookman
+>index
+for(i in /usr/inferno/man/[0-9]*/[a-z0-9:]*){
+ p=`{echo $i | sed 's@/usr/inferno(/man/.*)$@\1@'}
+ deroff -w < $i |
+ tr 'A-Z' 'a-z' |
+ sort -u |
+ comm -23 - junkwords |
+ sed 's@$@ '$p'@' >>index # stick file name on end of line
+}
+sort -o index index
+mv index /usr/inferno/man/index
diff --git a/man/lib/notes b/man/lib/notes
new file mode 100644
index 00000000..5ea602d9
--- /dev/null
+++ b/man/lib/notes
@@ -0,0 +1,10 @@
+.fp 1 R LucidaSans
+.ft 1
+.ps 20
+.sp |0.5i
+.ce
+Notes
+.bp
+.sp |0.5i
+.ce
+Notes
diff --git a/man/lib/preface b/man/lib/preface
new file mode 100644
index 00000000..a49bc721
--- /dev/null
+++ b/man/lib/preface
@@ -0,0 +1,73 @@
+.FP lucidasans
+.nr PS -1
+.nr VS -1
+.TL
+Preface
+.SP 0.4i exactly
+.LP
+Inferno benefits from the results of many years of systems research
+at the Computing Science Research Center at Lucent Technologies, Bell Labs.
+The system is clearly a cultural descendent of the earliest Unix systems,
+and amongst Inferno's inventors, listed below, are several venerable programmers
+associated with the development of Unix.
+Inferno looks out on a very different world from Unix: complexity is no longer
+confined to large mainframes, but has sprawled
+across world wide networks, trapping programmers in its web.
+.LP
+Inferno tackles this as radically now as Unix did then.
+First, it adopts key ideas from the system Plan 9, also from Bell Labs:
+.IP \(bu
+Replace a plethora of protocols by a simple, unifying file service protocol (Styx),
+that can be served even by tiny devices, giving a uniform
+way to access objects throughout the network.
+.IP \(bu
+Let applications `compute a name space': all resources are represented
+as file systems, which an application assembles into an application-specific
+hierarchy or `name space', private or shared, that hides their source (local or remote)
+and nature (static or dynamic), for completely transparent access.
+.IP \(bu
+Using those primitives, implement windowing systems, networked graphics, remote debugging,
+device control, and much more, with remarkable ease
+and great simplicity.
+.LP
+Inferno carries Plan 9's ideas further.
+Plan 9 virtualised resources; Inferno virtualises the whole system.
+The operating system kernel can run both native and `hosted' on a range
+of platforms presenting identical interfaces on all, offering wider portability.
+The Limbo programming language offers proper concurrent programming,
+and straightforward yet dynamic modularity.
+The Dis virtual machine allows applications to cross architecture boundaries
+invisibly during execution.
+Inferno shows the `continued appliance of computer science'.
+.LP
+The original development team at Bell Labs was
+Sean Dorward, Rob Pike and Phil Winterbottom,
+with Eric Grosse, Jim McKie, Dave Presotto,
+Dennis Ritchie, Ken Thompson and Howard Trickey.
+Many others have contributed much since then, both within Lucent and without.
+.LP
+Inferno® is now a supported, commercial product of Vita Nuova.
+The Third Edition of the Programmer's manual marked that event.
+The Fourth Edition brings many changes in content, but also makes the full
+source available as Free Software under a new `dual licence' scheme.
+.LP
+.sp
+.in 4i
+.nf
+.ft I
+.ce 100
+Dave Atkin
+John Bates
+Danny Byrne
+John Firth
+Charles Forsyth
+Michael Jeffrey
+Chris Locke
+Roger Peppé
+Nigel Roles
+.sp
+Vita Nuova
+.br
+June 2003
+.ce 0
+.in -4i
diff --git a/man/lib/secindex b/man/lib/secindex
new file mode 100755
index 00000000..060e7a6a
--- /dev/null
+++ b/man/lib/secindex
@@ -0,0 +1,43 @@
+#!/bin/rc
+U='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+L='abcdefghijklmnopqrstuvwxyz'
+builtin cd $1
+for (i in [a-z0-9:]*) {
+ sed -n '
+ /SH *NAM/,/SH/{
+ /SH/d
+ s/, *$//
+ ty
+ :y
+ s/ *\\*-.*//
+ tx
+ s/ *\\\(mi.*//
+ tx
+ s/[,:] */\
+/g
+ s/\n\\n/\
+/g
+ y/'$U'/'$L'/
+ s/\n/ '$i'&/g
+ s/$/ '$i'/
+ p
+ }
+ /SH *DES/q
+ d
+ :x
+ s/ *\\*-.*//
+ s/ *\\\(mi.*//
+ /^$/d
+ s/[,:] */\
+/g
+ s/\n\n/\
+/g
+ y/'$U'/'$L'/
+ s/\n/ '$i'&/g
+ s/(.|\n)*$/& '$i'/
+ p
+ q
+' $i
+kw=`{echo $i | sed 's/0intro/intro/'}
+echo $kw $i
+} | sort -u
diff --git a/man/lib/title b/man/lib/title
new file mode 100644
index 00000000..f9b2f6ec
--- /dev/null
+++ b/man/lib/title
@@ -0,0 +1,77 @@
+.fp 1 R LucidaSans
+.fp 2 I LucidaSansI
+.ps36
+.sp |2i
+.ce
+Inferno\s36\u\s8\u™
+.sp |3.275i
+.ps24
+.ce
+Programmer's Manual
+.sp .3i
+.ce
+Volume 1
+.ps12
+.sp |4.8i
+.ft I
+.ce
+Fourth Edition
+.sp |7.6i
+.ce
+Vita Nuova Holdings Limited
+.ce
+3 Innovation Close
+.ce
+York Science Park
+.ce
+University Road
+.ce
+York YO10 5ZF
+.ce
+England
+.bp
+.ft R
+\" .ll 5.5i
+\" .in .5i
+.hy 0
+.vs 10p
+.ps 10
+.sp |1i
+.nf
+Published by Vita Nuova Holdings Limited,
+3 Innovation Close,
+York Science Park,
+University Road,
+York YO10 5ZF,
+England
+.sp 3
+.ps 8
+Copyright © 1995-1999 Lucent Technologies Inc. All Rights Reserved.
+Portions Copyright © 1999-2005 Vita Nuova Holdings Limited. All Rights Reserved.
+Portions of Section 9 are derived from works which themselves are
+Copyright © 1990 The Regents of the University of California, and
+Copyright © 1994-1996 Sun Microsystems, Inc.
+subject to the terms described in the copyright notice in Section 9.
+.ps 10
+.sp 3
+All rights reserved; no part of this publication may be reproduced,
+stored in a retrieval system or transmitted in any form or by any means,
+electronic, mechanical, photocopying, recording or otherwise without
+the prior written permission of the publishers.
+.sp 3
+First published 2000. This edition published 2005.
+.sp 3
+.ig
+ISBN for complete set of 2 volumes: 0 9538701 0 3
+.br
+ISBN for this volume: 0 9538701 1 1
+.sp 3
+.ft I
+Printed in Great Britain by
+William Clowes,
+Beccles,
+Suffolk NR34 9QE,
+England.
+.ft R
+.sp 3
+Cover Design: Jeff Parker
diff --git a/man/lib/trademarks b/man/lib/trademarks
new file mode 100644
index 00000000..efa19bb0
--- /dev/null
+++ b/man/lib/trademarks
@@ -0,0 +1,46 @@
+.sp |2i
+.vs 12
+.fp 1 R LucidaSans
+.ft 1
+.fi
+Trademarks referenced in this document:
+.sp 2
+.ps -1
+.vs -1
+.nf
+Inferno, Dis, Styx and Limbo are registered trademarks of Vita Nuova Holdings Limited
+ in the United States and other countries.
+68020 and 68040 are trademarks of Motorola.
+AMD is a trade mark of Advanced Micro Devices.
+ARM is a trade mark of Advanced Risc Machines, Limited.
+DSP3210 is a trade mark of AT&T.
+Aladdin Ghostscript is a trademark of Aladdin Enterprises.
+CGA is a trademark of International Business Machines Corporation.
+Challenge, Indigo\s-2\u2\d\s0, Indy, and POWER Series are trademarks of Silicon Graphics, Inc.
+Ethernet is a trademark of Xerox Corporation.
+IBM, PS/2, and VGA are registered trademarks of International Business Machines Corporation.
+IDEA is a trademark of Ascom-Tech AG.
+Intel, i386, 960, 8088, 80286, 80386, 80486, and Pentium are trademarks of Intel Corporation.
+Java is a trademark and Sun is a registered trademark of Sun Microsystems Inc.
+Lucida and Pellucida are registered trademarks of Bigelow & Holmes.
+MIPS and R3000 are registered trademarks of MIPS Technologies, Inc.
+Microsoft is a registered trademark of Microsoft Corporation.
+Windows, Windows NT and MS-DOS are registered trademarks of Microsoft Corporation
+ in the United States and other countries.
+NFS is a registered trademark of Sun Microsystems, Inc.
+PDP and VAX are registered trademarks of Digital Equipment Corp.
+Plan 9 is a trademark of Lucent Technologies Inc.
+PostScript is a registered trademark of Adobe Systems Incorporated.
+ProPhone is a registered trademark of Prolink Corporation.
+R2000, R6000, R4000, and R4400 are trademarks of MIPS Technologies, Inc.
+RC4 is a registered trademark of RSA Security Inc.
+SecureNet is a registered trademark of Digital Pathways, Inc.
+Silicon Graphics, IRIS Indigo, IRIS, and IRIX are registered trademarks of Silicon Graphics, Inc.
+Sound Blaster is a registered trademark of Creative Labs, Inc.
+SPARC is a registered trademark of SPARC International, Inc.
+StrongARM is a registered trademark of Advanced RISC Machines, Ltd.
+ThinkJet is a registered trademark of the Hewlett-Packard Company.
+Unicode is a registered trademark of Unicode, Inc.
+UNIX is a registered trademark of The Open Group.
+.ps
+.vs
diff --git a/man/mkfile b/man/mkfile
new file mode 100644
index 00000000..336c94ab
--- /dev/null
+++ b/man/mkfile
@@ -0,0 +1,119 @@
+<../mkconfig
+DOC=$ROOT
+<$DOC/man/fonts
+
+LIB=$DOC/man/lib
+
+default:V: check
+
+indices:V:
+ rfork n
+ for (i in [0-9] 10){
+ $LIB/secindex $i > $i/INDEX
+ }
+ mk lookindex
+
+
+permind:V:
+ rm -f $LIB/permind/toc
+ {
+ echo -n $FONTS
+ echo .am TH
+ echo .tm '\\$1' '\\$2' '\\n%'
+ echo ..
+ for (i in [0-9]){
+ builtin cd $i
+ for(j in [a-z0-9]*)
+ switch($i/$j){
+ case 1/tbl
+ tbl $j
+ case 1/eqn 6/auth
+ eqn $j
+ case 1/pic
+ pic $j
+ case 1/grap
+ grap $j | pic
+ case *
+ cat $j
+ }
+ builtin cd ..
+ }
+ # section 10 is in a special order
+ builtin cd 10
+ cat 0intro
+ cat `{grep -l '^\.TH.*\.1' *}
+ cat `{grep -l '^\.TH.*\.2' *}
+ cat `{grep -l '^\.TH.*\.6' *}
+ cat `{grep -l '^\.TH.*\.8' *}
+ builtin cd ..
+ } | troff -$MAN > /dev/null >[2] $LIB/permind/toc
+ builtin cd $LIB/permind
+ rm -f out
+ mk out > /dev/null >[2] /dev/null
+
+check:V: checksource
+ awk -f $LIB/checkman.awk [0-9]/* | sed '/\/(cda|av|midi|pub|weather|service\.9net|isdn)(\/|\))/d'
+
+checksource:QV:
+ sam -d >[2]/dev/null <<'!'
+ f input
+ < cat [0-9]/[0-9a-z]*
+ B output
+ b input
+ ,x/^\.SH SOURCE/ .,/^\.SH/ x g/^\.B/t "output
+ b output
+ ,x/^\.B.? / d
+ ,x/ .*/d
+ ,s/.+/if(! test -f $ROOT& \&\& ! test -d $ROOT&) echo no such SOURCE file '&'/g
+ ,>rc
+ !
+
+lookindex:V:
+ builtin cd $LIB/lookman
+ mkindex
+
+
+print.out:V: permind
+ {
+ {echo -n $FONTS; cat $LIB/title} | troff
+ {echo -n $FONTS; cat $LIB/trademarks} | troff -ms
+ {echo -n $FONTS; echo ' '} | troff
+ {echo -n $FONTS; cat $LIB/preface} | troff -ms
+ {echo -n $FONTS; echo ' '} | troff
+ {
+ for (i in [0-9]){
+ builtin cd $i
+ for(j in [a-z0-9]*)
+ switch($i/$j){
+ case 1/tbl
+ tbl $j
+ case 1/eqn 6/auth
+ eqn $j
+ case 1/pic
+ pic $j
+ case 1/grap
+ grap $j | pic
+ case *
+ cat $j
+ }
+ builtin cd ..
+ }
+ # section 10 is in a special order
+ builtin cd 10
+ cat 0intro
+ cat `{grep -l '^\.TH.*\.1' *}
+ cat `{grep -l '^\.TH.*\.2' *}
+ cat `{grep -l '^\.TH.*\.6' *}
+ cat `{grep -l '^\.TH.*\.8' *}
+ builtin cd ..
+ } | troff -$MAN
+ {echo -n $FONTS; echo ' '} | troff
+ {echo -n $FONTS; echo ' '} | troff
+ cat $LIB/permind/out
+ {echo -n $FONTS; echo ' '} | troff
+ {echo -n $FONTS; echo ' '} | troff
+ {echo -n $FONTS; cat $LIB/colophon} | troff
+ } > print.out
+
+clean:V:
+ rm -f man.out
diff --git a/module/vac.m b/module/vac.m
new file mode 100644
index 00000000..fd6be340
--- /dev/null
+++ b/module/vac.m
@@ -0,0 +1,188 @@
+Vac: module {
+ PATH: con "/dis/lib/vac.dis";
+ init: fn();
+
+ dflag: int;
+
+ # taken from venti.m, merge back later
+ # some of this needs to be removed from venti.m since it does not belong there
+
+ # mode bits
+ Modeperm: con 8r777;
+ Modesticky,
+ Modesetuid,
+ Modesetgid,
+ Modeappend,
+ Modeexcl,
+ Modesymlink,
+ Modedir,
+ Modehidden,
+ Modesystem,
+ Modearchive,
+ Modetemp,
+ Modesnapshot,
+ Modedev,
+ Modenamedpipe: con 1<<(9+iota);
+
+ Entrysize: con 40;
+ Rootsize: con 300;
+ Metablocksize: con 12;
+ Metaentrysize: con 4;
+
+ Dsize: con 8*1024;
+
+ Entryactive: con (1<<0); # entry is in use
+ Entrydir: con (1<<1); # a directory
+ Entrydepthshift: con 2; # shift for pointer depth
+ Entrydepthmask: con (16r7<<2); # mask for pointer depth
+ Entrylocal: con (1<<5); # used for local storage: should not be set for venti blocks
+
+ Root: adt {
+ version: int;
+ name: string;
+ rtype: string;
+ score: Venti->Score; # to a Dir block
+ blocksize: int; # maximum block size
+ prev: ref Venti->Score; # last root block
+
+ new: fn(name, rtype: string, score: Venti->Score, blocksize: int, prev: ref Venti->Score): ref Root;
+ unpack: fn(d: array of byte): ref Root;
+ pack: fn(r: self ref Root): array of byte;
+ };
+
+ Entry: adt {
+ gen: int; # generation number (XXX should be unsigned)
+ psize: int; # pointer block size
+ dsize: int; # data block size
+ depth: int; # unpacked from flags
+ flags: int;
+ size: big; # (XXX should be unsigned)
+ score: Venti->Score;
+
+ new: fn(psize, dsize, flags: int, size: big, score: Venti->Score): ref Entry;
+ pack: fn(e: self ref Entry): array of byte;
+ unpack: fn(d: array of byte): ref Entry;
+ };
+
+ Direntry: adt {
+ version: int;
+ elem: string;
+ entry, gen: int;
+ mentry, mgen: int;
+ qid: big;
+ uid, gid, mid: string;
+ mtime, mcount, ctime, atime, mode, emode: int;
+
+ new: fn(): ref Direntry;
+ mk: fn(d: Sys->Dir): ref Direntry;
+ mkdir: fn(de: self ref Direntry): ref Sys->Dir;
+ pack: fn(de: self ref Direntry): array of byte;
+ unpack: fn(d: array of byte): ref Direntry;
+ };
+
+ Metablock: adt {
+ size, free, maxindex, nindex: int;
+
+ new: fn(): ref Metablock;
+ pack: fn(mb: self ref Metablock, d: array of byte);
+ unpack: fn(d: array of byte): ref Metablock;
+ };
+
+ Metaentry: adt {
+ offset, size: int;
+
+ pack: fn(me: self ref Metaentry, d: array of byte);
+ unpack: fn(d: array of byte, i: int): ref Metaentry;
+ };
+
+ # single block
+ Page: adt {
+ d: array of byte;
+ o: int;
+
+ new: fn(dsize: int): ref Page;
+ add: fn(p: self ref Page, s: Venti->Score);
+ full: fn(p: self ref Page): int;
+ data: fn(p: self ref Page): array of byte;
+ };
+
+ # hash tree file
+ File: adt {
+ p: array of ref Page;
+ dtype, dsize: int;
+ size: big;
+ s: ref Venti->Session;
+
+ new: fn(s: ref Venti->Session, dtype, dsize: int): ref File;
+ write: fn(f: self ref File, d: array of byte): int;
+ finish: fn(f: self ref File): ref Entry;
+ };
+
+ # for writing venti directories
+ Sink: adt {
+ f: ref File;
+ d: array of byte;
+ nd, ne: int;
+
+ new: fn(s: ref Venti->Session, dsize: int): ref Sink;
+ add: fn(m: self ref Sink, e: ref Entry): int;
+ finish: fn(m: self ref Sink): ref Entry;
+ };
+
+ Mentry: adt {
+ elem: string;
+ me: ref Metaentry;
+
+ cmp: fn(a, b: ref Mentry): int;
+ };
+
+ # for writing directory entries (meta blocks, meta entries, direntries)
+ MSink: adt {
+ f: ref File;
+ de: array of byte;
+ nde: int;
+ l: list of ref Mentry;
+
+ new: fn(s: ref Venti->Session, dsize: int): ref MSink;
+ add: fn(m: self ref MSink, de: ref Direntry): int;
+ finish: fn(m: self ref MSink): ref Entry;
+ };
+
+ # for reading pages from a hash tree referenced by an entry
+ Source: adt {
+ session: ref Venti->Session;
+ e: ref Entry;
+
+ new: fn(s: ref Venti->Session, e: ref Entry): ref Source;
+ get: fn(s: self ref Source, i: big, d: array of byte): int;
+ };
+
+ # for reading from a hash tree while keeping offset
+ Vacfile: adt {
+ s: ref Source;
+ o: big;
+
+ mk: fn(s: ref Source): ref Vacfile;
+ new: fn(session: ref Venti->Session, e: ref Entry): ref Vacfile;
+ read: fn(v: self ref Vacfile, d: array of byte, n: int): int;
+ seek: fn(v: self ref Vacfile, offset: big): big;
+ pread: fn(v: self ref Vacfile, d: array of byte, n: int, offset: big): int;
+ };
+
+ # for listing contents of a vac directory and walking to path elements
+ Vacdir: adt {
+ vf: ref Vacfile;
+ ms: ref Source;
+ p: big;
+ i: int;
+
+ mk: fn(vf: ref Vacfile, ms: ref Source): ref Vacdir;
+ new: fn(session: ref Venti->Session, e, me: ref Entry): ref Vacdir;
+ walk: fn(v: self ref Vacdir, elem: string): ref Direntry;
+ open: fn(v: self ref Vacdir, de: ref Direntry): (ref Entry, ref Entry);
+ readdir: fn(v: self ref Vacdir): (int, ref Direntry);
+ rewind: fn(v: self ref Vacdir);
+ };
+
+ vdroot: fn(session: ref Venti->Session, score: Venti->Score): (ref Vacdir, ref Direntry, string);
+};