summaryrefslogtreecommitdiff
path: root/appl/cmd/install
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
commit37da2899f40661e3e9631e497da8dc59b971cbd0 (patch)
treecbc6d4680e347d906f5fa7fca73214418741df72 /appl/cmd/install
parent54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff)
20060303a
Diffstat (limited to 'appl/cmd/install')
-rw-r--r--appl/cmd/install/NOTICE6
-rw-r--r--appl/cmd/install/applylog.b699
-rw-r--r--appl/cmd/install/arch.b288
-rw-r--r--appl/cmd/install/arch.m36
-rw-r--r--appl/cmd/install/archfs.b579
-rw-r--r--appl/cmd/install/archfs.m7
-rw-r--r--appl/cmd/install/ckproto.b267
-rw-r--r--appl/cmd/install/create.b445
-rw-r--r--appl/cmd/install/eproto.b357
-rw-r--r--appl/cmd/install/info.b73
-rw-r--r--appl/cmd/install/inst.b500
-rw-r--r--appl/cmd/install/install.b430
-rw-r--r--appl/cmd/install/log.b76
-rw-r--r--appl/cmd/install/logs.b287
-rw-r--r--appl/cmd/install/logs.m44
-rw-r--r--appl/cmd/install/mergelog.b239
-rw-r--r--appl/cmd/install/mkfile43
-rw-r--r--appl/cmd/install/mkproto.b99
-rw-r--r--appl/cmd/install/proto.b320
-rw-r--r--appl/cmd/install/proto.m6
-rw-r--r--appl/cmd/install/proto2list.b209
-rw-r--r--appl/cmd/install/protocaller.m8
-rw-r--r--appl/cmd/install/updatelog.b386
-rw-r--r--appl/cmd/install/wdiff.b148
-rw-r--r--appl/cmd/install/wfind.b204
-rw-r--r--appl/cmd/install/wrap.b684
-rw-r--r--appl/cmd/install/wrap.m41
-rw-r--r--appl/cmd/install/wrap2list.b305
28 files changed, 6786 insertions, 0 deletions
diff --git a/appl/cmd/install/NOTICE b/appl/cmd/install/NOTICE
new file mode 100644
index 00000000..1c591576
--- /dev/null
+++ b/appl/cmd/install/NOTICE
@@ -0,0 +1,6 @@
+Most of the code in this directory is a limbo version of Russ Cox's wrap, the
+software package manager that was written for Plan9 distributions. His original
+C code may have been modularized and partly rewritten to use limbo features,
+but the credit and thanks must go to Russ for developing the original system.
+
+
diff --git a/appl/cmd/install/applylog.b b/appl/cmd/install/applylog.b
new file mode 100644
index 00000000..6a1b2e63
--- /dev/null
+++ b/appl/cmd/install/applylog.b
@@ -0,0 +1,699 @@
+implement Applylog;
+
+#
+# apply a plan 9-style replica log
+# this version applies everything and doesn't use the database
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "string.m";
+ str: String;
+
+include "keyring.m";
+ kr: Keyring;
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "logs.m";
+ logs: Logs;
+ Db, Entry, Byname, Byseq: import logs;
+ S: import logs;
+
+include "arg.m";
+
+Applylog: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+Apply, Applydb, Install, Asis, Skip: con iota;
+
+client: ref Db; # client current state from client log
+updates: ref Db; # state delta from new section of server log
+
+nerror := 0;
+nconflict := 0;
+debug := 0;
+verbose := 0;
+resolve := 0;
+setuid := 0;
+setgid := 0;
+nflag := 0;
+timefile: string;
+clientroot: string;
+srvroot: string;
+logfd: ref Sys->FD;
+now := 0;
+gen := 0;
+noerr := 0;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ bufio = load Bufio Bufio->PATH;
+ ensure(bufio, Bufio->PATH);
+ str = load String String->PATH;
+ ensure(str, String->PATH);
+ kr = load Keyring Keyring->PATH;
+ ensure(kr, Keyring->PATH);
+ daytime = load Daytime Daytime->PATH;
+ ensure(daytime, Daytime->PATH);
+ logs = load Logs Logs->PATH;
+ ensure(logs, Logs->PATH);
+ logs->init(bufio);
+
+ arg := load Arg Arg->PATH;
+ ensure(arg, Arg->PATH);
+ arg->init(args);
+ arg->setusage("applylog [-vuged] [-sc] [-T timefile] clientlog clientroot serverroot [path ... ] <serverlog");
+ dump := 0;
+ while((o := arg->opt()) != 0)
+ case o {
+ 'T' => timefile = arg->earg();
+ 'd' => dump = 1; debug = 1;
+ 'e' => noerr = 1;
+ 'g' => setgid = 1;
+ 'n' => nflag = 1; verbose = 1;
+ 's' or 'c' => resolve = o;
+ 'u' => setuid = 1;
+ 'v' => verbose = 1;
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(len args < 3)
+ arg->usage();
+ arg = nil;
+
+ now = daytime->now();
+ client = Db.new("client log");
+ updates = Db.new("update log");
+ clientlog := hd args; args = tl args;
+ clientroot = hd args; args = tl args;
+ srvroot = hd args; args = tl args;
+ if(args != nil)
+ error("restriction by path not yet done");
+
+ checkroot(clientroot, "client root");
+ checkroot(srvroot, "server root");
+
+ # replay the client log to build last installation state of files taken from server
+ if(nflag)
+ logfd = sys->open(clientlog, Sys->OREAD);
+ else
+ logfd = sys->open(clientlog, Sys->ORDWR);
+ if(logfd == nil)
+ error(sys->sprint("can't open %s: %r", clientlog));
+ f := bufio->fopen(logfd, Sys->OREAD);
+ if(f == nil)
+ error(sys->sprint("can't open %s: %r", clientlog));
+ while((log := readlog(f)) != nil)
+ replaylog(client, log);
+ f = nil;
+ sys->seek(logfd, big 0, 2);
+ if(dump)
+ dumpstate();
+ if(debug){
+ sys->print(" CLIENT STATE\n");
+ client.sort(Byname);
+ dumpdb(client, 0);
+ }
+
+ # read server's log and use the new section to build a sequence of update actions
+ minseq := big 0;
+ if(timefile != nil)
+ minseq = readseq(timefile);
+ f = bufio->fopen(sys->fildes(0), Sys->OREAD);
+ while((log = readlog(f)) != nil)
+ if(log.seq > minseq)
+ update(updates, updates.look(log.path), log);
+ updates.sort(Byseq);
+ if(debug){
+ sys->print(" SEQUENCED UPDATES\n");
+ dumpdb(updates, 1);
+ }
+
+ # apply those actions
+ maxseq := minseq;
+ skip := 0;
+ for(i := 0; i < updates.nstate; i++){
+ e := updates.state[i];
+ ce := client.look(e.path);
+ if(ce != nil && ce.seq >= e.seq){ # replay
+ if(debug)
+ sys->print("replay %c %q\n", e.action, e.path);
+ if(!nflag && !skip)
+ maxseq = e.seq;
+ continue;
+ }
+ if(verbose)
+ sys->print("%s\n", e.sumtext());
+ case chooseaction(e) {
+ Install =>
+ if(debug)
+ sys->print("resolve %q to install\n", e.path);
+ c := e;
+ c.action = 'a'; # force (re)creation/installation
+ if(!enact(c)){
+ skip = 1;
+ continue; # don't update db
+ }
+ Apply =>
+ if(!enact(e)){
+ skip = 1;
+ continue; # don't update db
+ }
+ Applydb =>
+ if(debug)
+ sys->print("resolve %q to update db\n", e.path);
+ # carry on to update the log
+ Asis =>
+ if(debug)
+ sys->print("resolve %q to client\n", e.path);
+ #continue; # ?
+ Skip =>
+ if(debug)
+ sys->print("conflict %q\n", e.path);
+ skip = 1;
+ continue;
+ * =>
+ error("internal error: unexpected result from chooseaction");
+ }
+ # action complete: add to client log
+ if(ce == nil)
+ ce = client.entry(e.seq, e.path, e.d);
+ ce.update(e);
+ if(!nflag){
+ if(!skip)
+ maxseq = e.seq;
+ if(logfd != nil){
+ # append action, now accepted, to client's own log
+ if(sys->fprint(logfd, "%s\n", e.logtext()) < 0)
+ error(sys->sprint("error writing to %q: %r", clientlog));
+ }
+ }
+ }
+ sys->fprint(sys->fildes(2), "maxseq: %bud %bud\n", maxseq>>32, maxseq & 16rFFFFFFFF);
+ if(!nflag && !skip && timefile != nil)
+ writeseq(timefile, maxseq);
+ if(nconflict)
+ raise sys->sprint("fail:%d conflicts", nconflict);
+ if(nerror)
+ raise sys->sprint("fail:%d errors", nerror);
+}
+
+checkroot(dir: string, what: string)
+{
+ (ok, d) := sys->stat(dir);
+ if(ok < 0)
+ error(sys->sprint("can't stat %s %q: %r", what, dir));
+ if((d.mode & Sys->DMDIR) == 0)
+ error(sys->sprint("%s %q: not a directory", what, dir));
+}
+
+readlog(in: ref Iobuf): ref Entry
+{
+ (e, err) := Entry.read(in);
+ if(err != nil)
+ error(err);
+ return e;
+}
+
+readseq(file: string): big
+{
+ fd := sys->open(file, Sys->OREAD);
+ if(fd == nil)
+ error(sys->sprint("can't open %q: %r", file));
+ buf := array[128] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n <= 0)
+ error(sys->sprint("can't read valid seq from %q", file));
+ (nf, flds) := sys->tokenize(string buf[0:n], " \t\n");
+ if(nf != 2)
+ error(sys->sprint("illegal sequence number in %q", file));
+ n0 := bigof(hd flds, 10);
+ n1 := bigof(hd tl flds, 10);
+ return (n0 << 32) | n1;
+}
+
+writeseq(file: string, n: big)
+{
+ fd := sys->create(file, Sys->OWRITE, 8r666);
+ if(fd == nil)
+ error(sys->sprint("can't create %q: %r", file));
+ if(sys->fprint(fd, "%11bud %11bud", n>>32, n&16rFFFFFFFF) < 0)
+ error(sys->sprint("error writing seq to %q: %r", file));
+}
+
+#
+# replay a log to reach the state wrt files previously taken from the server
+#
+replaylog(db: ref Db, log: ref Entry)
+{
+ e := db.look(log.path);
+ indb := e != nil && !e.removed();
+ case log.action {
+ 'a' => # add new file
+ if(indb){
+ note(sys->sprint("%q duplicate create", log.path));
+ return;
+ }
+ 'c' => # contents
+ if(!indb){
+ note(sys->sprint("%q contents but no entry", log.path));
+ return;
+ }
+ 'd' => # delete
+ if(!indb){
+ note(sys->sprint("%q deleted but no entry", log.path));
+ return;
+ }
+ if(e.d.mtime > log.d.mtime){
+ note(sys->sprint("%q deleted but it's newer", log.path));
+ return;
+ }
+ 'm' => # metadata
+ if(!indb){
+ note(sys->sprint("%q metadata but no entry", log.path));
+ return;
+ }
+ * =>
+ error(sys->sprint("bad log entry: %bd %bd", log.seq>>32, log.seq & big 16rFFFFFFFF));
+ }
+ update(db, e, log);
+}
+
+#
+# update file state e to reflect the effect of the log,
+# creating a new entry if necessary
+#
+update(db: ref Db, e: ref Entry, log: ref Entry)
+{
+ if(e == nil)
+ e = db.entry(log.seq, log.path, log.d);
+ e.update(log);
+}
+
+chooseaction(e: ref Entry): int
+{
+ cf := logs->mkpath(clientroot, e.path);
+ sf := logs->mkpath(srvroot, e.serverpath);
+ (ishere, cd) := sys->stat(logs->mkpath(clientroot, e.path));
+ ishere = ishere >= 0; # in local file system
+ db := client.look(e.path);
+ indb := db != nil && !db.removed(); # previously arrived from server
+
+ unchanged := indb && ishere && (samestat(db.d, cd) || samecontents(sf, cf)) || !indb && !ishere;
+ if(unchanged && (e.action != 'm' || samemeta(db.d, cd)))
+ return Apply;
+ if(!ishere && e.action == 'd'){
+ if(indb)
+ return Applydb;
+ return Asis;
+ }
+ case resolve {
+ 'c' =>
+ return Asis;
+ 's' =>
+ if(!ishere || e.action == 'm' && !unchanged)
+ return Install;
+ return Apply;
+ * =>
+ # describe source of conflict
+ if(indb){
+ if(ishere){
+ if(e.action == 'm' && unchanged && !samemeta(db.d, cd))
+ conflict(e.path, "locally modified metadata", action(e.action));
+ else
+ conflict(e.path, "locally modified", action(e.action));
+ }else
+ conflict(e.path, "locally removed", action(e.action));
+ }else{
+ if(db != nil)
+ conflict(e.path, "locally retained or recreated", action(e.action)); # server installed it but later removed it
+ else
+ conflict(e.path, "locally created", action(e.action));
+ }
+ return Skip;
+ }
+}
+
+enact(e: ref Entry): int
+{
+ if(nflag)
+ return 0;
+ srcfile := logs->mkpath(srvroot, e.serverpath);
+ dstfile := logs->mkpath(clientroot, e.path);
+ case e.action {
+ 'a' => # create and copy in
+ if(debug)
+ sys->print("create %q\n", dstfile);
+ if(e.d.mode & Sys->DMDIR)
+ err := mkdir(dstfile, e);
+ else
+ err = copyin(srcfile, dstfile, 1, e);
+ if(err != nil){
+ if(noerr)
+ error(err);
+ warn(err);
+ return 0;
+ }
+ 'c' => # contents
+ err := copyin(srcfile, dstfile, 0, e);
+ if(err != nil){
+ if(noerr)
+ error(err);
+ warn(err);
+ return 0;
+ }
+ 'd' => # delete
+ if(debug)
+ sys->print("remove %q\n", dstfile);
+ if(remove(dstfile) < 0){
+ warn(sys->sprint("can't remove %q: %r", dstfile));
+ return 0;
+ }
+ 'm' => # metadata
+ if(debug)
+ sys->print("wstat %q\n", dstfile);
+ d := sys->nulldir;
+ d.mode = e.d.mode;
+ if(sys->wstat(dstfile, d) < 0)
+ warn(sys->sprint("%q: can't change mode to %uo", dstfile, d.mode));
+ if(setgid){
+ d = sys->nulldir;
+ d.gid = e.d.gid;
+ if(sys->wstat(dstfile, d) < 0)
+ warn(sys->sprint("%q: can't change gid to %q", dstfile, d.gid));
+ }
+ if(setuid){
+ d = sys->nulldir;
+ d.uid = e.d.uid;
+ if(sys->wstat(dstfile, d) < 0)
+ warn(sys->sprint("%q: can't change uid to %q", dstfile, d.uid));
+ }
+ * =>
+ error(sys->sprint("unexpected log operation: %c %q", e.action, e.path));
+ return 0;
+ }
+ return 1;
+}
+
+rev[T](l: list of T): list of T
+{
+ rl: list of T;
+ for(; l != nil; l = tl l)
+ rl = hd l :: rl;
+ return rl;
+}
+
+ensure[T](m: T, path: string)
+{
+ if(m == nil)
+ error(sys->sprint("can't load %s: %r", path));
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "applylog: %s\n", s);
+ raise "fail:error";
+}
+
+note(s: string)
+{
+ sys->fprint(sys->fildes(2), "applylog: note: %s\n", s);
+}
+
+warn(s: string)
+{
+ sys->fprint(sys->fildes(2), "applylog: warning: %s\n", s);
+ nerror++;
+}
+
+conflict(name: string, why: string, wont: string)
+{
+ sys->fprint(sys->fildes(2), "%q: %s; will not %s\n", name, why, wont);
+ nconflict++;
+}
+
+action(a: int): string
+{
+ case a {
+ 'a' => return "create";
+ 'c' => return "update";
+ 'd' => return "delete";
+ 'm' => return "update metadata";
+ * => return sys->sprint("unknown action %c", a);
+ }
+}
+
+samecontents(path1, path2: string): int
+{
+ f1 := sys->open(path1, Sys->OREAD);
+ if(f1 == nil)
+ return 0;
+ f2 := sys->open(path2, Sys->OREAD);
+ if(f2 == nil)
+ return 0;
+ b1 := array[Sys->ATOMICIO] of byte;
+ b2 := array[Sys->ATOMICIO] of byte;
+ n := 256; # start with something small; dis files and big executables should fail more quickly
+ n1, n2: int;
+ do{
+ n1 = sys->read(f1, b1, n);
+ n2 = sys->read(f2, b2, n);
+ if(n1 != n2)
+ return 0;
+ for(i := 0; i < n1; i++)
+ if(b1[i] != b2[i])
+ return 0;
+ n += len b1 - n;
+ }while(n1 > 0);
+ return 1;
+}
+
+samestat(a: Sys->Dir, b: Sys->Dir): int
+{
+ # doesn't check permission/ownership, does check QTDIR/QTFILE
+ if(a.mode & Sys->DMDIR)
+ return (b.mode & Sys->DMDIR) != 0;
+ return a.length == b.length && a.mtime == b.mtime && a.qid.qtype == b.qid.qtype; # TO DO: a.name==b.name?
+}
+
+samemeta(a: Sys->Dir, b: Sys->Dir): int
+{
+ return a.mode == b.mode && (!setuid || a.uid == b.uid) && (!setgid || a.gid == b.gid) && samestat(a, b);
+}
+
+bigof(s: string, base: int): big
+{
+ (b, r) := str->tobig(s, base);
+ if(r != nil)
+ error("cruft in integer field in log entry: "+s);
+ return b;
+}
+
+intof(s: string, base: int): int
+{
+ return int bigof(s, base);
+}
+
+mkdir(dstpath: string, e: ref Entry): string
+{
+ fd := create(dstpath, Sys->OREAD, e.d.mode);
+ if(fd == nil)
+ return sys->sprint("can't mkdir %q: %r", dstpath);
+ fchmod(fd, e.d.mode);
+ if(setgid)
+ fchgrp(fd, e.d.gid);
+ if(setuid)
+ fchown(fd, e.d.uid);
+# e.d.mtime = now;
+ return nil;
+}
+
+fchmod(fd: ref Sys->FD, mode: int)
+{
+ d := sys->nulldir;
+ d.mode = mode;
+ if(sys->fwstat(fd, d) < 0)
+ warn(sys->sprint("%q: can't set mode %o: %r", sys->fd2path(fd), mode));
+}
+
+fchgrp(fd: ref Sys->FD, gid: string)
+{
+ d := sys->nulldir;
+ d.gid = gid;
+ if(sys->fwstat(fd, d) < 0)
+ warn(sys->sprint("%q: can't set group id %s: %r", sys->fd2path(fd), gid));
+}
+
+fchown(fd: ref Sys->FD, uid: string)
+{
+ d := sys->nulldir;
+ d.uid = uid;
+ if(sys->fwstat(fd, d) < 0)
+ warn(sys->sprint("%q: can't set user id %s: %r", sys->fd2path(fd), uid));
+}
+
+copyin(srcpath: string, dstpath: string, dowstat: int, e: ref Entry): string
+{
+ if(debug)
+ sys->print("copyin %q -> %q\n", srcpath, dstpath);
+ f := sys->open(srcpath, Sys->OREAD);
+ if(f == nil)
+ return sys->sprint("can't open %q: %r", srcpath);
+ t: ref Sys->FD;
+ (ok, nil) := sys->stat(dstpath);
+ if(ok < 0){
+ t = create(dstpath, Sys->OWRITE, e.d.mode | 8r222);
+ if(t == nil)
+ return sys->sprint("can't create %q: %r", dstpath);
+ # TO DO: force access to parent directory
+ dowstat = 1;
+ }else{
+ t = sys->open(dstpath, Sys->OWRITE|Sys->OTRUNC);
+ if(t == nil){
+ err := sys->sprint("%r");
+ if(!contains(err, "permission"))
+ return sys->sprint("can't overwrite %q: %s", dstpath, err);
+ }
+ }
+ (nw, err) := copy(f, t);
+ if(err != nil)
+ return err;
+ if(nw != e.d.length)
+ warn(sys->sprint("%q: log said %bud bytes, copied %bud bytes", dstpath, e.d.length, nw));
+ f = nil;
+ if(dowstat){
+ fchmod(t, e.d.mode);
+ if(setgid)
+ fchgrp(t, e.d.gid);
+ if(setuid)
+ fchown(t, e.d.uid);
+ }
+ nd := sys->nulldir;
+ nd.mtime = e.d.mtime;
+ if(sys->fwstat(t, nd) < 0)
+ warn(sys->sprint("%q: can't set mtime: %r", dstpath));
+ return nil;
+}
+
+copy(f: ref Sys->FD, t: ref Sys->FD): (big, string)
+{
+ buf := array[Sys->ATOMICIO] of byte;
+ nw := big 0;
+ while((n := sys->read(f, buf, len buf)) > 0){
+ if(sys->write(t, buf, n) != n)
+ return (nw, sys->sprint("error writing %q: %r", sys->fd2path(t)));
+ nw += big n;
+ }
+ if(n < 0)
+ return (nw, sys->sprint("error reading %q: %r", sys->fd2path(f)));
+ return (nw, nil);
+}
+
+contents(e: ref Entry): string
+{
+ s := "";
+ for(cl := e.contents; cl != nil; cl = tl cl)
+ s += " " + hd cl;
+ return s;
+}
+
+dumpstate()
+{
+ for(i := 0; i < client.nstate; i++)
+ sys->print("%d\t%s\n", i, client.state[i].text());
+}
+
+dumpdb(db: ref Db, tag: int)
+{
+ for(i := 0; i < db.nstate; i++){
+ if(!tag)
+ s := db.state[i].dbtext();
+ else
+ s = db.state[i].text();
+ if(s != nil)
+ sys->print("%s\n", s);
+ }
+}
+
+#
+# perhaps these should be in a utility module
+#
+parent(name: string): string
+{
+ slash := -1;
+ for(i := 0; i < len name; i++)
+ if(name[i] == '/')
+ slash = i;
+ if(slash > 0)
+ return name[0:slash];
+ return "/";
+}
+
+writableparent(name: string): (int, string)
+{
+ p := parent(name);
+ (ok, d) := sys->stat(p);
+ if(ok < 0)
+ return (-1, nil);
+ nd := sys->nulldir;
+ nd.mode |= 8r222;
+ sys->wstat(p, nd);
+ return (d.mode, p);
+}
+
+create(name: string, rw: int, mode: int): ref Sys->FD
+{
+ fd := sys->create(name, rw, mode);
+ if(fd == nil){
+ err := sys->sprint("%r");
+ if(!contains(err, "permission")){
+ sys->werrstr(err);
+ return nil;
+ }
+ (pm, p) := writableparent(name);
+ if(pm >= 0){
+ fd = sys->create(name, rw, mode);
+ d := sys->nulldir;
+ d.mode = pm;
+ sys->wstat(p, d);
+ }
+ sys->werrstr(err);
+ }
+ return fd;
+}
+
+remove(name: string): int
+{
+ if(sys->remove(name) >= 0)
+ return 0;
+ err := sys->sprint("%r");
+ if(contains(err, "entry not found") || contains(err, "not exist"))
+ return 0;
+ (pm, p) := writableparent(name);
+ rc := sys->remove(name);
+ d := sys->nulldir;
+ if(pm >= 0){
+ d.mode = pm;
+ sys->wstat(p, d);
+ }
+ sys->werrstr(err);
+ return rc;
+}
+
+contains(s: string, sub: string): int
+{
+ return str->splitstrl(s, sub).t1 != nil;
+}
diff --git a/appl/cmd/install/arch.b b/appl/cmd/install/arch.b
new file mode 100644
index 00000000..3f4d660d
--- /dev/null
+++ b/appl/cmd/install/arch.b
@@ -0,0 +1,288 @@
+implement Arch;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "daytime.m";
+ daytime : Daytime;
+include "string.m";
+ str : String;
+include "bufio.m";
+ bufio : Bufio;
+ Iobuf : import bufio;
+include "sh.m";
+include "arch.m";
+
+addp := 1;
+
+buf := array[Sys->ATOMICIO] of byte;
+
+init(bio: Bufio)
+{
+ sys = load Sys Sys->PATH;
+ if(bio == nil)
+ bufio = load Bufio Bufio->PATH;
+ else
+ bufio = bio;
+ daytime = load Daytime Daytime->PATH;
+ str = load String String->PATH;
+}
+
+addperms(p: int)
+{
+ addp = p;
+}
+
+openarch(file : string) : ref Archive
+{
+ return openarch0(file, 1);
+}
+
+openarchfs(file : string) : ref Archive
+{
+ return openarch0(file, 0);
+}
+
+openarch0(file : string, newpgrp : int) : ref Archive
+{
+ pid := 0;
+ canseek := 1;
+ b := bufio->open(file, Bufio->OREAD);
+ if (b == nil)
+ return nil;
+ if (b.getb() == 16r1f && ((c := b.getb()) == 16r8b || c == 16r9d)) {
+ # spawn gunzip
+ canseek = 0;
+ (b, pid) = gunzipstream(file, newpgrp);
+ if (b == nil)
+ return nil;
+ }
+ else
+ b.seek(big 0, Bufio->SEEKSTART);
+ ar := ref Archive;
+ ar.b = b;
+ ar.nexthdr = 0;
+ ar.canseek = canseek;
+ ar.pid = pid;
+ ar.hdr = ref Ahdr;
+ ar.hdr.d = ref Sys->Dir;
+ return ar;
+}
+
+EOARCH : con "end of archive\n";
+PREMEOARCH : con "premature end of archive";
+NFLDS : con 6;
+
+openarchgz(file : string) : (string, ref Sys->FD)
+{
+ ar := openarch(file);
+ if (ar == nil || ar.canseek)
+ return (nil, nil);
+ (newfile, fd) := opentemp("wrap.gz");
+ if (fd == nil)
+ return (nil, nil);
+ bout := bufio->fopen(fd, Bufio->OWRITE);
+ if (bout == nil)
+ return (nil, nil);
+ while ((a := gethdr(ar)) != nil) {
+ if (len a.name >= 5 && a.name[0:5] == "/wrap") {
+ puthdr(bout, a.name, a.d);
+ getfile(ar, bout, int a.d.length);
+ }
+ else
+ break;
+ }
+ closearch(ar);
+ bout.puts(EOARCH);
+ bout.flush();
+ sys->seek(fd, big 0, Sys->SEEKSTART);
+ return (newfile, fd);
+}
+
+gunzipstream(file : string, newpgrp : int) : (ref Iobuf, int)
+{
+ p := array[2] of ref Sys->FD;
+ if (sys->pipe(p) < 0)
+ return (nil, 0);
+ fd := sys->open(file, Sys->OREAD);
+ if (fd == nil)
+ return (nil, 0);
+ b := bufio->fopen(p[0], Bufio->OREAD);
+ if (b == nil)
+ return (nil, 0);
+ c := chan of int;
+ spawn gunzip(fd, p[1], c, newpgrp);
+ pid := <- c;
+ p[0] = p[1] = nil;
+ if (pid < 0)
+ return (nil, 0);
+ return (b, pid);
+}
+
+GUNZIP : con "/dis/gunzip.dis";
+
+gunzip(stdin : ref Sys->FD, stdout : ref Sys->FD, c : chan of int, newpgrp : int)
+{
+ if (newpgrp)
+ pid := sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);
+ else
+ pid = sys->pctl(Sys->FORKFD, nil);
+ sys->dup(stdin.fd, 0);
+ sys->dup(stdout.fd, 1);
+ sys->dup(1, 2);
+ stdin = stdout = nil;
+ cmd := load Command GUNZIP;
+ if (cmd == nil) {
+ c <-= -1;
+ return;
+ }
+ c <-= pid;
+ cmd->init(nil, GUNZIP :: nil);
+}
+
+closearch(ar : ref Archive)
+{
+ if (ar.pid != 0) {
+ fd := sys->open("#p/" + string ar.pid + "/ctl", sys->OWRITE);
+ if (fd != nil)
+ sys->fprint(fd, "killgrp");
+ }
+ ar.b.close();
+ ar.b = nil;
+}
+
+gethdr(ar : ref Archive) : ref Ahdr
+{
+ a := ar.hdr;
+ b := ar.b;
+ m := int b.offset();
+ n := ar.nexthdr;
+ if (m != n) {
+ if (ar.canseek)
+ b.seek(big n, Bufio->SEEKSTART);
+ else {
+ if (m > n)
+ fatal(sys->sprint("bad offset in gethdr: m=%d n=%d", m, n));
+ if(drain(ar, n-m) < 0)
+ return nil;
+ }
+ }
+ if ((s := b.gets('\n')) == nil) {
+ ar.err = PREMEOARCH;
+ return nil;
+ }
+# fd := sys->open("./debug", Sys->OWRITE);
+# sys->seek(fd, 0, Sys->SEEKEND);
+# sys->fprint(fd, "gethdr: %d %d %d %d %s\n", ar.canseek, m, n, b.offset(), s);
+# fd = nil;
+ if (s == EOARCH)
+ return nil;
+ (nf, fs) := sys->tokenize(s, " \t\n");
+ if(nf != NFLDS) {
+ ar.err = "too few fields in file header";
+ return nil;
+ }
+ a.name = hd fs; fs = tl fs;
+ (a.d.mode, nil) = str->toint(hd fs, 8); fs = tl fs;
+ a.d.uid = hd fs; fs = tl fs;
+ a.d.gid = hd fs; fs = tl fs;
+ (a.d.mtime, nil) = str->toint(hd fs, 10); fs = tl fs;
+ (tmp, nil) := str->toint(hd fs, 10); fs = tl fs;
+ a.d.length = big tmp;
+ ar.nexthdr = int (b.offset()+a.d.length);
+ return a;
+}
+
+getfile(ar : ref Archive, bout : ref Bufio->Iobuf, n : int) : string
+{
+ err: string;
+ bin := ar.b;
+ while (n > 0) {
+ m := len buf;
+ if (n < m)
+ m = n;
+ p := bin.read(buf, m);
+ if (p != m)
+ return PREMEOARCH;
+ p = bout.write(buf, m);
+ if (p != m)
+ err = sys->sprint("cannot write: %r");
+ n -= m;
+ }
+ return err;
+}
+
+puthdr(b : ref Iobuf, name : string, d : ref Sys->Dir)
+{
+ mode := d.mode;
+ if(addp){
+ mode |= 8r664;
+ if(mode & Sys->DMDIR || mode & 8r111)
+ mode |= 8r111;
+ }
+ b.puts(sys->sprint("%s %uo %s %s %ud %d\n", name, mode, d.uid, d.gid, d.mtime, int d.length));
+}
+
+putstring(b : ref Iobuf, s : string)
+{
+ b.puts(s);
+}
+
+putfile(b : ref Iobuf, f : string, n : int) : string
+{
+ fd := sys->open(f, Sys->OREAD);
+ if (fd == nil)
+ return sys->sprint("cannot open %s: %r", f);
+ i := 0;
+ for (;;) {
+ m := sys->read(fd, buf, len buf);
+ if (m < 0)
+ return sys->sprint("cannot read %s: %r", f);
+ if (m == 0)
+ break;
+ if (b.write(buf, m) != m)
+ return sys->sprint("%s: cannot write: %r", f);
+ i += m;
+ }
+ if (i != n) {
+ b.seek(big (n-i), Sys->SEEKRELA);
+ return sys->sprint("%s: %d bytes written: should be %d", f, i, n);
+ }
+ return nil;
+}
+
+putend(b : ref Iobuf)
+{
+ b.puts(EOARCH);
+ b.flush();
+}
+
+drain(ar : ref Archive, n : int) : int
+{
+ while (n > 0) {
+ m := n;
+ if (m > len buf)
+ m = len buf;
+ p := ar.b.read(buf, m);
+ if (p != m){
+ ar.err = "unexpectedly short read";
+ return -1;
+ }
+ n -= m;
+ }
+ return 0;
+}
+
+opentemp(prefix: string): (string, ref Sys->FD)
+{
+ name := sys->sprint("/tmp/%s.%ud.%d", prefix, daytime->now(), sys->pctl(0, nil));
+ # would use ORCLOSE here but it messes up under Nt
+ fd := sys->create(name, Sys->ORDWR, 8r600);
+ return (name, fd);
+}
+
+fatal(s : string)
+{
+ sys->fprint(sys->fildes(2), "%s\n", s);
+ raise "fail:error";
+}
diff --git a/appl/cmd/install/arch.m b/appl/cmd/install/arch.m
new file mode 100644
index 00000000..03837445
--- /dev/null
+++ b/appl/cmd/install/arch.m
@@ -0,0 +1,36 @@
+Arch : module
+{
+ PATH : con "/dis/install/arch.dis";
+
+ Ahdr : adt {
+ name : string;
+ modestr : string;
+ d : ref Sys->Dir;
+ };
+
+ Archive : adt {
+ b : ref Bufio->Iobuf;
+ nexthdr : int;
+ canseek : int;
+ pid : int;
+ hdr : ref Ahdr;
+ err : string;
+ };
+
+ init: fn(bio: Bufio);
+
+ openarch: fn(name : string) : ref Archive;
+ openarchfs: fn(name : string) : ref Archive;
+ openarchgz: fn(name : string) : (string, ref Sys->FD);
+ gethdr: fn(ar : ref Archive) : ref Ahdr;
+ getfile: fn(ar : ref Archive, bout : ref Bufio->Iobuf, n : int) : string;
+ drain: fn(ar : ref Archive, n : int) : int;
+ closearch: fn(ar : ref Archive);
+
+ puthdr: fn(b : ref Bufio->Iobuf, name : string, d : ref Sys->Dir);
+ putstring: fn(b : ref Bufio->Iobuf, s : string);
+ putfile: fn(b : ref Bufio->Iobuf, f : string, n : int) : string;
+ putend: fn(b : ref Bufio->Iobuf);
+
+ addperms: fn(p: int);
+};
diff --git a/appl/cmd/install/archfs.b b/appl/cmd/install/archfs.b
new file mode 100644
index 00000000..3705aee9
--- /dev/null
+++ b/appl/cmd/install/archfs.b
@@ -0,0 +1,579 @@
+implement Archfs;
+
+include "sys.m";
+ sys : Sys;
+include "draw.m";
+include "bufio.m";
+ bufio : Bufio;
+include "arg.m";
+ arg : Arg;
+include "string.m";
+ str : String;
+include "daytime.m";
+ daytime : Daytime;
+include "styx.m";
+ styx: Styx;
+include "archfs.m";
+include "arch.m";
+ arch : Arch;
+
+# add write some day
+
+Iobuf : import bufio;
+Tmsg, Rmsg: import styx;
+
+Einuse : con "fid already in use";
+Ebadfid : con "bad fid";
+Eopen : con "fid already opened";
+Enotfound : con "file does not exist";
+Enotdir : con "not a directory";
+Eperm : con "permission denied";
+Ebadarg : con "bad argument";
+Eexists : con "file already exists";
+
+UID : con "inferno";
+GID : con "inferno";
+
+DEBUG: con 0;
+
+Dir : adt {
+ dir : Sys->Dir;
+ offset : int;
+ parent : cyclic ref Dir;
+ child : cyclic ref Dir;
+ sibling : cyclic ref Dir;
+};
+
+Fid : adt {
+ fid : int;
+ open: int;
+ dir : ref Dir;
+ next : cyclic ref Fid;
+};
+
+HTSZ : con 32;
+fidtab := array[HTSZ] of ref Fid;
+
+root : ref Dir;
+qid : int;
+mtpt := "/mnt";
+bio : ref Iobuf;
+buf : array of byte;
+skip := 0;
+
+# Archfs : module
+# {
+# init : fn(ctxt : ref Draw->Context, args : list of string);
+# };
+
+init(nil : ref Draw->Context, args : list of string)
+{
+ init0(nil, args, nil);
+}
+
+initc(args : list of string, c : chan of int)
+{
+ init0(nil, args, c);
+}
+
+chanint : chan of int;
+
+init0(nil : ref Draw->Context, args : list of string, chi : chan of int)
+{
+ chanint = chi;
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ arg = load Arg Arg->PATH;
+ str = load String String->PATH;
+ daytime = load Daytime Daytime->PATH;
+ styx = load Styx Styx->PATH;
+ arch = load Arch Arch->PATH;
+ if (bufio == nil || arg == nil || styx == nil || arch == nil)
+ fatal("failed to load modules", 1);
+ styx->init();
+ arch->init(bufio);
+ arg->init(args);
+ while ((c := arg->opt()) != 0) {
+ case c {
+ 'm' =>
+ mtpt = arg->arg();
+ if (mtpt == nil)
+ fatal("mount point missing", 1);
+ 's' =>
+ skip = 1;
+ }
+ }
+ args = arg->argv();
+ if (args == nil)
+ fatal("missing archive file", 1);
+ buf = array[Sys->ATOMICIO] of byte;
+ # root = newdir("/", UID, GID, 8r755|Sys->DMDIR, daytime->now());
+ root = newdir(basename(mtpt), UID, GID, 8r755|Sys->DMDIR, daytime->now());
+ root.parent = root;
+ readarch(hd args, tl args);
+ p := array[2] of ref Sys->FD;
+ if(sys->pipe(p) < 0)
+ fatal("can't create pipe", 1);
+ ch := chan of ref Tmsg;
+ sync := chan of int;
+ spawn reader(p[1], ch, sync);
+ <- sync;
+ pidch := chan of int;
+ spawn serve(p[1], ch, pidch);
+ pid := <- pidch;
+ if(sys->mount(p[0], nil, mtpt, Sys->MREPL, nil) < 0)
+ fatal(sys->sprint("cannot mount archive on %s: %r", mtpt), 1);
+ p[0] = p[1] = nil;
+ if (chi != nil) {
+ chi <-= pid;
+ chanint = nil;
+ }
+}
+
+reply(fd: ref Sys->FD, m: ref Rmsg): int
+{
+ if(DEBUG)
+ sys->fprint(sys->fildes(2), "R: %s\n", m.text());
+ s := m.pack();
+ if(s == nil)
+ return -1;
+ return sys->write(fd, s, len s);
+}
+
+error(fd: ref Sys->FD, m: ref Tmsg, e : string)
+{
+ reply(fd, ref Rmsg.Error(m.tag, e));
+}
+
+reader(fd: ref Sys->FD, ch: chan of ref Tmsg, sync: chan of int)
+{
+ sys->pctl(Sys->NEWFD|Sys->NEWNS, fd.fd :: nil);
+ sync <-= 1;
+ while((m := Tmsg.read(fd, Styx->MAXRPC)) != nil && tagof m != tagof Tmsg.Readerror)
+ ch <-= m;
+ ch <-= m;
+}
+
+serve(fd: ref Sys->FD, ch : chan of ref Tmsg, pidch : chan of int)
+{
+ e : string;
+ f : ref Fid;
+
+ pidch <-= sys->pctl(0, nil);
+ for (;;) {
+ m0 := <- ch;
+ if (m0 == nil)
+ return;
+ if(DEBUG)
+ sys->fprint(sys->fildes(2), "T: %s\n", m0.text());
+ pick m := m0 {
+ Readerror =>
+ fatal("read error on styx server", 1);
+ Version =>
+ (s, v) := styx->compatible(m, Styx->MAXRPC, Styx->VERSION);
+ reply(fd, ref Rmsg.Version(m.tag, s, v));
+ Auth =>
+ error(fd, m, "no authentication required");
+ Flush =>
+ reply(fd, ref Rmsg.Flush(m.tag));
+ Walk =>
+ (f, e) = mapfid(m.fid);
+ if (e != nil) {
+ error(fd, m, e);
+ continue;
+ }
+ if (f.open) {
+ error(fd, m, Eopen);
+ continue;
+ }
+ err := 0;
+ dir := f.dir;
+ nq := 0;
+ nn := len m.names;
+ qids := array[nn] of Sys->Qid;
+ if(nn > 0){
+ for(k := 0; k < nn; k++){
+ if ((dir.dir.mode & Sys->DMDIR) == 0) {
+ if(k == 0){
+ error(fd, m, Enotdir);
+ err = 1;
+ }
+ break;
+ }
+ dir = lookup(dir, m.names[k]);
+ if (dir == nil) {
+ if(k == 0){
+ error(fd, m, Enotfound);
+ err = 1;
+ }
+ break;
+ }
+ qids[nq++] = dir.dir.qid;
+ }
+ }
+ if(err)
+ continue;
+ if(nq < nn)
+ qids = qids[0: nq];
+ if(nq == nn){
+ if(m.newfid != m.fid){
+ f = newfid(m.newfid);
+ if (f == nil) {
+ error(fd, m, Einuse);
+ continue;
+ }
+ }
+ f.dir = dir;
+ }
+ reply(fd, ref Rmsg.Walk(m.tag, qids));
+ Open =>
+ (f, e) = mapfid(m.fid);
+ if (e != nil) {
+ error(fd, m, e);
+ continue;
+ }
+ if (m.mode & (Sys->OWRITE|Sys->ORDWR|Sys->OTRUNC|Sys->ORCLOSE)) {
+ error(fd, m, Eperm);
+ continue;
+ }
+ f.open = 1;
+ reply(fd, ref Rmsg.Open(m.tag, f.dir.dir.qid, Styx->MAXFDATA));
+ Create =>
+ error(fd, m, Eperm);
+ Read =>
+ (f, e) = mapfid(m.fid);
+ if (e != nil) {
+ error(fd, m, e);
+ continue;
+ }
+ data := readdir(f.dir, int m.offset, m.count);
+ reply(fd, ref Rmsg.Read(m.tag, data));
+ Write =>
+ error(fd, m, Eperm);
+ Clunk =>
+ (f, e) = mapfid(m.fid);
+ if (e != nil) {
+ error(fd, m, e);
+ continue;
+ }
+ freefid(f);
+ reply(fd, ref Rmsg.Clunk(m.tag));
+ Stat =>
+ (f, e) = mapfid(m.fid);
+ if (e != nil) {
+ error(fd, m, e);
+ continue;
+ }
+ reply(fd, ref Rmsg.Stat(m.tag, f.dir.dir));
+ Remove =>
+ error(fd, m, Eperm);
+ Wstat =>
+ error(fd, m, Eperm);
+ Attach =>
+ f = newfid(m.fid);
+ if (f == nil) {
+ error(fd, m, Einuse);
+ continue;
+ }
+ f.dir = root;
+ reply(fd, ref Rmsg.Attach(m.tag, f.dir.dir.qid));
+ * =>
+ fatal("unknown styx message", 1);
+ }
+ }
+}
+
+newfid(fid : int) : ref Fid
+{
+ (f, nil) := mapfid(fid);
+ if(f != nil)
+ return nil;
+ f = ref Fid;
+ f.fid = fid;
+ f.open = 0;
+ hv := hashval(fid);
+ f.next = fidtab[hv];
+ fidtab[hv] = f;
+ return f;
+}
+
+freefid(f: ref Fid)
+{
+ hv := hashval(f.fid);
+ lf : ref Fid;
+ for(ff := fidtab[hv]; ff != nil; ff = ff.next){
+ if(f == ff){
+ if(lf == nil)
+ fidtab[hv] = ff.next;
+ else
+ lf.next = ff.next;
+ return;
+ }
+ lf = ff;
+ }
+ fatal("cannot find fid", 1);
+}
+
+mapfid(fid : int) : (ref Fid, string)
+{
+ hv := hashval(fid);
+ for (f := fidtab[hv]; f != nil; f = f.next)
+ if (int f.fid == fid)
+ break;
+ if (f == nil)
+ return (nil, Ebadfid);
+ if (f.dir == nil)
+ return (nil, Enotfound);
+ return (f, nil);
+}
+
+hashval(n : int) : int
+{
+ return (n & ~Sys->DMDIR)%HTSZ;
+}
+
+readarch(f : string, args : list of string)
+{
+ ar := arch->openarchfs(f);
+ if(ar == nil || ar.b == nil)
+ fatal(sys->sprint("cannot open %s(%r)\n", f), 1);
+ bio = ar.b;
+ while ((a := arch->gethdr(ar)) != nil) {
+ if (args != nil) {
+ if (!selected(a.name, args)) {
+ if (skip)
+ return;
+ arch->drain(ar, int a.d.length);
+ continue;
+ }
+ mkdirs("/", a.name);
+ }
+ d := mkdir(a.name, a.d.mode, a.d.mtime, a.d.uid, a.d.gid, 0);
+ if((a.d.mode & Sys->DMDIR) == 0) {
+ d.dir.length = a.d.length;
+ d.offset = int bio.offset();
+ }
+ arch->drain(ar, int a.d.length);
+ }
+ if (ar.err != nil)
+ fatal(ar.err, 0);
+}
+
+selected(s: string, args: list of string): int
+{
+ for(; args != nil; args = tl args)
+ if(fileprefix(hd args, s))
+ return 1;
+ return 0;
+}
+
+fileprefix(prefix, s: string): int
+{
+ n := len prefix;
+ m := len s;
+ if(n > m || !str->prefix(prefix, s))
+ return 0;
+ if(m > n && s[n] != '/')
+ return 0;
+ return 1;
+}
+
+basename(f : string) : string
+{
+ for (i := len f; i > 0; )
+ if (f[--i] == '/')
+ return f[i+1:];
+ return f;
+}
+
+split(p : string) : (string, string)
+{
+ if (p == nil)
+ fatal("nil string in split", 1);
+ if (p[0] != '/')
+ fatal("p0 not / in split", 1);
+ while (p[0] == '/')
+ p = p[1:];
+ i := 0;
+ while (i < len p && p[i] != '/')
+ i++;
+ if (i == len p)
+ return (p, nil);
+ else
+ return (p[0:i], p[i:]);
+}
+
+mkdirs(basedir, name: string)
+{
+ (nil, names) := sys->tokenize(name, "/");
+ while(names != nil) {
+ # sys->print("mkdir %s\n", basedir);
+ mkdir(basedir, 8r775|Sys->DMDIR, daytime->now(), UID, GID, 1);
+ if(tl names == nil)
+ break;
+ basedir = basedir + "/" + hd names;
+ names = tl names;
+ }
+}
+
+readdir(d : ref Dir, offset : int, n : int) : array of byte
+{
+ if (d.dir.mode & Sys->DMDIR)
+ return readd(d, offset, n);
+ else
+ return readf(d, offset, n);
+}
+
+readd(d : ref Dir, o : int, n : int) : array of byte
+{
+ k := 0;
+ m := 0;
+ b := array[n] of byte;
+ for (s := d.child; s != nil; s = s.sibling) {
+ l := styx->packdirsize(s.dir);
+ if(k < o){
+ k += l;
+ continue;
+ }
+ if(m+l > n)
+ break;
+ b[m: ] = styx->packdir(s.dir);
+ m += l;
+ }
+ return b[0: m];
+}
+
+readf(d : ref Dir, offset : int, n : int) : array of byte
+{
+ leng := int d.dir.length;
+ if (offset+n > leng)
+ n = leng-offset;
+ if (n <= 0 || offset < 0)
+ return nil;
+ bio.seek(big (d.offset+offset), Bufio->SEEKSTART);
+ a := array[n] of byte;
+ p := 0;
+ m := 0;
+ for ( ; n != 0; n -= m) {
+ l := len buf;
+ if (n < l)
+ l = n;
+ m = bio.read(buf, l);
+ if (m <= 0 || m != l)
+ fatal("premature eof", 1);
+ a[p:] = buf[0:m];
+ p += m;
+ }
+ return a;
+}
+
+mkdir(f : string, mode : int, mtime : int, uid : string, gid : string, existsok : int) : ref Dir
+{
+ if (f == "/")
+ return nil;
+ d := newdir(basename(f), uid, gid, mode, mtime);
+ addfile(d, f, existsok);
+ return d;
+}
+
+addfile(d : ref Dir, path : string, existsok : int)
+{
+ elem : string;
+
+ opath := path;
+ p := prev := root;
+ basedir := "";
+# sys->print("addfile %s : %s\n", d.dir.name, path);
+ while (path != nil) {
+ (elem, path) = split(path);
+ basedir += "/" + elem;
+ op := p;
+ p = lookup(p, elem);
+ if (path == nil) {
+ if (p != nil) {
+ if (!existsok && (p.dir.mode&Sys->DMDIR) == 0)
+ sys->fprint(sys->fildes(2), "addfile: %s already there", opath);
+ # fatal(sys->sprint("addfile: %s already there", opath), 1);
+ return;
+ }
+ if (prev.child == nil)
+ prev.child = d;
+ else {
+ for (s := prev.child; s.sibling != nil; s = s.sibling)
+ ;
+ s.sibling = d;
+ }
+ d.parent = prev;
+ }
+ else {
+ if (p == nil) {
+ mkdir(basedir, 8r775|Sys->DMDIR, daytime->now(), UID, GID, 1);
+ p = lookup(op, elem);
+ if (p == nil)
+ fatal("bad file system", 1);
+ }
+ }
+ prev = p;
+ }
+}
+
+lookup(p : ref Dir, f : string) : ref Dir
+{
+ if ((p.dir.mode&Sys->DMDIR) == 0)
+ fatal("not a directory in lookup", 1);
+ if (f == ".")
+ return p;
+ if (f == "..")
+ return p.parent;
+ for (d := p.child; d != nil; d = d.sibling)
+ if (d.dir.name == f)
+ return d;
+ return nil;
+}
+
+newdir(name, uid, gid : string, mode, mtime : int) : ref Dir
+{
+ dir : Sys->Dir;
+
+ dir.name = name;
+ dir.uid = uid;
+ dir.gid = gid;
+ dir.qid.path = big (qid++);
+ if(mode&Sys->DMDIR)
+ dir.qid.qtype = Sys->QTDIR;
+ else
+ dir.qid.qtype = Sys->QTFILE;
+ dir.qid.vers = 0;
+ dir.mode = mode;
+ dir.atime = dir.mtime = mtime;
+ dir.length = big 0;
+ dir.dtype = 'X';
+ dir.dev = 0;
+
+ d := ref Dir;
+ d.dir = dir;
+ d.offset = 0;
+ return d;
+}
+
+# pr(d : ref Dir)
+# {
+# dir := d.dir;
+# sys->print("%s %s %s %x %x %x %d %d %d %d %d %d\n",
+# dir.name, dir.uid, dir.gid, dir.qid.path, dir.qid.vers, dir.mode, dir.atime, dir.mtime, dir.length, dir.dtype, dir.dev, d.offset);
+# }
+
+fatal(e : string, pr: int)
+{
+ if(pr){
+ sys->fprint(sys->fildes(2), "fatal: %s\n", e);
+ if (chanint != nil)
+ chanint <-= -1;
+ }
+ else{
+ # probably not an archive file
+ if (chanint != nil)
+ chanint <-= -2;
+ }
+ exit;
+}
diff --git a/appl/cmd/install/archfs.m b/appl/cmd/install/archfs.m
new file mode 100644
index 00000000..57c32542
--- /dev/null
+++ b/appl/cmd/install/archfs.m
@@ -0,0 +1,7 @@
+Archfs : module
+{
+ PATH : con "/dis/install/archfs.dis";
+
+ init : fn(ctxt : ref Draw->Context, args : list of string);
+ initc : fn(args : list of string, c : chan of int);
+};
diff --git a/appl/cmd/install/ckproto.b b/appl/cmd/install/ckproto.b
new file mode 100644
index 00000000..1e214f96
--- /dev/null
+++ b/appl/cmd/install/ckproto.b
@@ -0,0 +1,267 @@
+implement Ckproto;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "arg.m";
+ arg: Arg;
+include "readdir.m";
+ readdir : Readdir;
+include "proto.m";
+ proto : Proto;
+include "protocaller.m";
+ protocaller : Protocaller;
+
+WARN, ERROR, FATAL : import Protocaller;
+
+Ckproto: module{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+ protofile: fn(new : string, old : string, d : ref Sys->Dir);
+ protoerr: fn(lev : int, line : int, err : string);
+};
+
+Dir : adt {
+ name : string;
+ proto : string;
+ parent : cyclic ref Dir;
+ child : cyclic ref Dir;
+ sibling : cyclic ref Dir;
+};
+
+root := "/";
+droot : ref Dir;
+protof : string;
+stderr : ref Sys->FD;
+omitgen := 0; # forget generated files
+verbose : int;
+ckmode: int;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ arg = load Arg Arg->PATH;
+ readdir = load Readdir Readdir->PATH;
+ proto = load Proto Proto->PATH;
+ protocaller = load Protocaller "$self";
+
+ stderr = sys->fildes(2);
+ sys->pctl(Sys->NEWPGRP|Sys->FORKNS|Sys->FORKFD, nil);
+ arg->init(args);
+ while ((c := arg->opt()) != 0) {
+ case c {
+ 'r' =>
+ root = arg->arg();
+ if (root == nil)
+ fatal("missing argument to -r");
+ 'o' =>
+ omitgen = 1;
+ 'v' =>
+ verbose = 1;
+ 'm' =>
+ ckmode = 1;
+ * =>
+ fatal("usage: install/ckproto [-o] [-v] [-m] [-r root] protofile ....");
+ }
+ }
+ droot = ref Dir("/", nil, nil, nil, nil);
+ droot.parent = droot;
+ args = arg->argv();
+ while (args != nil) {
+ protof = hd args;
+ proto->rdproto(hd args, root, protocaller);
+ args = tl args;
+ }
+ if (verbose)
+ prtree(droot, -1);
+ ckdir(root, droot);
+}
+
+protofile(new : string, old : string, nil : ref Sys->Dir)
+{
+ if (verbose) {
+ if (old == new)
+ sys->print("%s\n", new);
+ else
+ sys->print("%s %s\n", new, old);
+ }
+ addfile(droot, old);
+ if (new != old)
+ addfile(droot, new);
+}
+
+protoerr(lev : int, line : int, err : string)
+{
+ s := "line " + string line + " : " + err;
+ case lev {
+ WARN => warn(s);
+ ERROR => error(s);
+ FATAL => fatal(s);
+ }
+}
+
+ckdir(d : string, dird : ref Dir)
+{
+ (dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT);
+ for (i := 0; i < n; i++) {
+ dire := lookup(dird, dir[i].name);
+ if(omitgen && generated(dir[i].name))
+ continue;
+ if (dire == nil){
+ sys->print("%s missing\n", mkpath(d, dir[i].name));
+ continue;
+ }
+ if(ckmode){
+ if(dir[i].mode & Sys->DMDIR){
+ if((dir[i].mode & 8r775) != 8r775)
+ sys->print("directory %s not 775 at least\n", mkpath(d, dir[i].name));
+ }
+ else{
+ if((dir[i].mode & 8r664) != 8r664)
+ sys->print("file %s not 664 at least\n", mkpath(d, dir[i].name));
+ }
+ }
+ if (dir[i].mode & Sys->DMDIR)
+ ckdir(mkpath(d, dir[i].name), dire);
+ }
+}
+
+addfile(root : ref Dir, path : string)
+{
+ elem : string;
+
+ # ckexists(path);
+
+ curd := root;
+ opath := path;
+ while (path != nil) {
+ (elem, path) = split(path);
+ d := lookup(curd, elem);
+ if (d == nil) {
+ d = ref Dir(elem, protof, curd, nil, nil);
+ if (curd.child == nil)
+ curd.child = d;
+ else {
+ prev, this : ref Dir;
+
+ for (this = curd.child; this != nil; this = this.sibling) {
+ if (elem < this.name) {
+ d.sibling = this;
+ if (prev == nil)
+ curd.child = d;
+ else
+ prev.sibling = d;
+ break;
+ }
+ prev = this;
+ }
+ if (this == nil)
+ prev.sibling = d;
+ }
+ }
+ else if (path == nil && d.proto == protof)
+ sys->print("%s repeated in proto %s\n", opath, protof);
+ curd = d;
+ }
+}
+
+lookup(p : ref Dir, f : string) : ref Dir
+{
+ if (f == ".")
+ return p;
+ if (f == "..")
+ return p.parent;
+ for (d := p.child; d != nil; d = d.sibling) {
+ if (d.name == f)
+ return d;
+ if (d.name > f)
+ return nil;
+ }
+ return nil;
+}
+
+prtree(root : ref Dir, indent : int)
+{
+ if (indent >= 0)
+ sys->print("%s%s\n", string array[indent] of { * => byte '\t' }, root.name);
+ for (s := root.child; s != nil; s = s.sibling)
+ prtree(s, indent+1);
+}
+
+mkpath(prefix, elem: string): string
+{
+ slash1 := slash2 := 0;
+ if (len prefix > 0)
+ slash1 = prefix[len prefix - 1] == '/';
+ if (len elem > 0)
+ slash2 = elem[0] == '/';
+ if (slash1 && slash2)
+ return prefix+elem[1:];
+ if (!slash1 && !slash2)
+ return prefix+"/"+elem;
+ return prefix+elem;
+}
+
+split(p : string) : (string, string)
+{
+ if (p == nil)
+ fatal("nil string in split");
+ if (p[0] != '/')
+ fatal("p0 notg / in split");
+ while (p[0] == '/')
+ p = p[1:];
+ i := 0;
+ while (i < len p && p[i] != '/')
+ i++;
+ if (i == len p)
+ return (p, nil);
+ else
+ return (p[0:i], p[i:]);
+}
+
+
+gens := array[] of {
+ "dis", "sbl", "out", "0", "1", "2", "5", "8", "k", "q", "v", "t"
+};
+
+generated(f : string) : int
+{
+ for (i := len f -1; i >= 0; i--)
+ if (f[i] == '.')
+ break;
+ if (i < 0)
+ return 0;
+ suff := f[i+1:];
+ for (i = 0; i < len gens; i++)
+ if (suff == gens[i])
+ return 1;
+ return 0;
+}
+
+warn(s: string)
+{
+ sys->print("%s: %s\n", protof, s);
+}
+
+error(s: string)
+{
+ sys->fprint(stderr, "%s: %s\n", protof, s);
+ exit;;
+}
+
+fatal(s: string)
+{
+ sys->fprint(stderr, "fatal: %s\n", s);
+ exit;
+}
+
+ckexists(path: string)
+{
+ s := mkpath(root, path);
+ (ok, nil) := sys->stat(s);
+ if(ok < 0)
+ sys->print("%s does not exist\n", s);
+}
diff --git a/appl/cmd/install/create.b b/appl/cmd/install/create.b
new file mode 100644
index 00000000..848fdc6b
--- /dev/null
+++ b/appl/cmd/install/create.b
@@ -0,0 +1,445 @@
+implement Create;
+
+include "sys.m";
+ sys: Sys;
+ Dir, sprint, fprint: import sys;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "string.m";
+ str: String;
+include "arg.m";
+ arg: Arg;
+include "daytime.m";
+include "keyring.m";
+ keyring : Keyring;
+include "sh.m";
+include "wrap.m";
+ wrap : Wrap;
+include "arch.m";
+ arch : Arch;
+include "proto.m";
+ proto : Proto;
+include "protocaller.m";
+ protocaller : Protocaller;
+
+WARN, ERROR, FATAL : import Protocaller;
+
+Create: module{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+ protofile: fn(new : string, old : string, d : ref Sys->Dir);
+ protoerr: fn(lev : int, line : int, err : string);
+};
+
+bout: ref Iobuf; # stdout when writing archive
+protof: string;
+notesf: string;
+oldroot: string;
+buf: array of byte;
+buflen := 1024-8;
+verb: int;
+xflag: int;
+stderr: ref Sys->FD;
+uid, gid : string;
+desc : string;
+pass : int;
+update : int;
+md5s : ref Keyring->DigestState;
+w : ref Wrap->Wrapped;
+root := "/";
+prefix, notprefix: list of string;
+onlist: list of (string, string); # NEW
+remfile: string;
+
+n2o(n: string): string
+{
+ for(onl := onlist; onl != nil; onl = tl onl)
+ if((hd onl).t1 == n)
+ return (hd onl).t0;
+ return n;
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ str = load String String->PATH;
+ arg = load Arg Arg->PATH;
+ wrap = load Wrap Wrap->PATH;
+ wrap->init(bufio);
+ arch = load Arch Arch->PATH;
+ arch->init(bufio);
+ daytime := load Daytime Daytime->PATH;
+ now := daytime->now();
+ # {
+ # for(i := 0; i < 21; i++){
+ # n := now+(i-9)*100000000;
+ # sys->print("%d -> %s\n", n, wrap->now2string(n));
+ # if(wrap->string2now(wrap->now2string(n)) != n)
+ # sys->print("%d wrong\n", n);
+ # }
+ # }
+ daytime = nil;
+ proto = load Proto Proto->PATH;
+ protocaller = load Protocaller "$self";
+
+ sys->pctl(Sys->NEWPGRP|Sys->FORKNS|Sys->FORKFD, nil);
+ stderr = sys->fildes(2);
+ if(arg == nil)
+ error(sys->sprint("can't load %s: %r", Arg->PATH));
+ name := "";
+ desc = "inferno";
+ tostdout := 0;
+ not := 0;
+ arg->init(args);
+ while((c := arg->opt()) != 0)
+ case c {
+ 'n' =>
+ not = 1;
+ 'o' =>
+ tostdout = 1;
+ 'p' =>
+ protof = reqarg("proto file (-p)");
+ 'r' =>
+ root = reqarg("root directory (-r)");
+ 's' =>
+ oldroot = reqarg("source directory (-d)");
+ 'u' =>
+ update = 1;
+ 'v' =>
+ verb = 1;
+ 'x' =>
+ xflag = 1;
+ 'N' =>
+ uid = reqarg("user name (-U)");
+ 'G' =>
+ gid = reqarg("group name (-G)");
+ 'd' or 'D' =>
+ desc = reqarg("product description (-D)");
+ 't' =>
+ rt := reqarg("package time (-t)");
+ now = int rt;
+ 'i' =>
+ notesf = reqarg("file (-i)");
+ 'R' =>
+ remfile = reqarg("remove file (-R)");
+ 'P' =>
+ arch->addperms(0);
+ * =>
+ usage();
+ }
+
+ args = arg->argv();
+ if(args == nil)
+ usage();
+ if (tostdout || xflag) {
+ bout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
+ if(bout == nil)
+ error(sys->sprint("can't open standard output for archive: %r"));
+ }
+ else {
+ # ar := sys->sprint("%ud", now);
+ ar := wrap->now2string(now, 0);
+ bout = bufio->create(ar, Sys->OWRITE, 8r664);
+ if(bout == nil)
+ error(sys->sprint("can't create %s for archive: %r", ar));
+ sys->print("archiving package %s to %s\n", hd args, ar);
+ }
+ buf = array [buflen] of byte;
+ name = hd args;
+ if(update){
+ if(not)
+ notprefix = tl args;
+ else
+ prefix = tl args;
+ }
+ else if (tl args != nil)
+ fatal("only one name allowed");
+ if (!xflag)
+ digest := wrapinit(name, now);
+ fprint(stderr, "processing %s\n", protof);
+ proto->rdproto(protof, oldroot, protocaller);
+ if (!xflag)
+ wrapend(digest);
+ if (!xflag)
+ fprint(stderr, "file system made\n");
+ arch->putend(bout);
+ exits();
+}
+
+protofile(new : string, old : string, d : ref Sys->Dir)
+{
+ if(xflag && bout != nil){
+ bout.puts(sys->sprint("%s\t%d\t%bd\n", new, d.mtime, d.length));
+ return;
+ }
+ d.uid = uid;
+ d.gid = gid;
+ if (!(d.mode & Sys->DMDIR)) {
+ # if(verb)
+ # fprint(stderr, "%s\n", new);
+ f := sys->open(old, Sys->OREAD);
+ if(f == nil){
+ warn(sys->sprint("can't open %s: %r", old));
+ return;
+ }
+ }
+ mkarch(new, old, d);
+}
+
+protoerr(lev : int, line : int, err : string)
+{
+ s := "line " + string line + " : " + err;
+ case lev {
+ WARN => warn(s);
+ ERROR => error(s);
+ FATAL => fatal(s);
+ }
+}
+
+quit()
+{
+ if(bout != nil)
+ bout.flush();
+ exits();
+}
+
+reqarg(what: string): string
+{
+ if((o := arg->arg()) == nil){
+ sys->fprint(stderr, "missing %s\n", what);
+ exits();
+ }
+ return o;
+}
+
+puthdr(f : string, d: ref Dir)
+{
+ if (d.mode & Sys->DMDIR)
+ d.length = big 0;
+ arch->puthdr(bout, f, d);
+}
+
+error(s: string)
+{
+ fprint(stderr, "%s: %s\n", protof, s);
+ quit();
+}
+
+fatal(s: string)
+{
+ fprint(stderr, "fatal: %s\n", s);
+ exits();
+}
+
+warn(s: string)
+{
+ fprint(stderr, "%s: %s\n", protof, s);
+}
+
+usage()
+{
+ fprint(stderr, "usage: install/create [-ovx] [-N uid] [-G gid] [-r root] [-d desc] [-s src-fs] [-p proto] name\n");
+ fprint(stderr, "or install/create -u [-ovx] [-N uid] [-G gid] [-r root] [-d desc] [-s src-fs] [-p proto] old-package [prefix ...]\n");
+ exits();
+}
+
+wrapinit(name : string, t : int) : array of byte
+{
+ rmfile : string;
+ rmfd: ref Sys->FD;
+
+ if (uid == nil)
+ uid = "inferno";
+ if (gid == nil)
+ gid = "inferno";
+ if (update) {
+ w = wrap->openwraphdr(name, root, nil, 0);
+ if (w == nil)
+ fatal("no such package found");
+ # ignore any updates - NEW commented out
+ # while (w.nu > 0 && w.u[w.nu-1].typ == wrap->UPD)
+ # w.nu--;
+
+ # w.nu = 1; NEW commented out
+ if (protof == nil)
+ protof = w.u[0].dir + "/proto";
+ name = w.name;
+ }
+ else {
+ if (protof == nil)
+ fatal("proto file missing");
+ }
+ (md5file, md5fd) := opentemp("wrap.md5", t);
+ if (md5fd == nil)
+ fatal(sys->sprint("cannot create %s", md5file));
+ keyring = load Keyring Keyring->PATH;
+ md5s = keyring->md5(nil, 0, nil, nil);
+ md5b := bufio->fopen(md5fd, Bufio->OWRITE);
+ if (md5b == nil)
+ fatal(sys->sprint("cannot open %s", md5file));
+ fprint(stderr, "wrap pass %s\n", protof);
+ obout := bout;
+ bout = md5b;
+ pass = 0;
+ proto->rdproto(protof, oldroot, protocaller);
+ bout.flush();
+ bout = md5b = nil;
+ digest := array[keyring->MD5dlen] of { * => byte 0 };
+ keyring->md5(nil, 0, digest, md5s);
+ md5s = nil;
+ (md5sort, md5sfd) := opentemp("wrap.md5s", t);
+ if (md5sfd == nil)
+ fatal(sys->sprint("cannot create %s", md5sort));
+ endc := chan of int;
+ md5fd = nil; # close md5file
+ spawn fsort(md5sfd, md5file, endc);
+ md5sfd = nil;
+ res := <- endc;
+ if (res < 0)
+ fatal("sort failed");
+ if (update) {
+ (rmfile, rmfd) = opentemp("wrap.rm", t);
+ if (rmfd == nil)
+ fatal(sys->sprint("cannot create %s", rmfile));
+ rmed: list of string;
+ for(i := w.nu-1; i >= 0; i--){ # NEW does loop
+ w.u[i].bmd5.seek(big 0, Bufio->SEEKSTART);
+ while ((p := w.u[i].bmd5.gets('\n')) != nil) {
+ if(prefix != nil && !wrap->match(p, prefix))
+ continue;
+ if(notprefix != nil && !wrap->notmatch(p, notprefix))
+ continue;
+ (q, nil) := str->splitl(p, " ");
+ q = pathcat(root, q);
+ (ok, nil) := sys->stat(q);
+ if(ok < 0)
+ (ok, nil) = sys->stat(n2o(q));
+ if (len q >= 7 && q[len q - 7:] == "emu.new") # quick hack for now
+ continue;
+ if (ok < 0){
+ for(r := rmed; r != nil; r = tl r) # NEW to avoid duplication
+ if(hd r == q)
+ break;
+ if(r == nil){
+ # sys->fprint(rmfd, "%s\n", q);
+ rmed = q :: rmed;
+ }
+ }
+ }
+ }
+ for(r := rmed; r != nil; r = tl r)
+ sys->fprint(rmfd, "%s\n", hd r);
+ if(remfile != nil){
+ rfd := sys->open(remfile, Sys->OREAD);
+ rbuf := array[128] of byte;
+ for(;;){
+ n := sys->read(rfd, rbuf, 128);
+ if(n <= 0)
+ break;
+ sys->write(rmfd, rbuf, n);
+ }
+ }
+ rmfd = nil;
+ rmed = nil;
+ }
+ bout = obout;
+ if (update)
+ wrap->putwrap(bout, name, t, desc, w.tfull, prefix == nil && notprefix == nil, uid, gid);
+ else
+ wrap->putwrap(bout, name, t, desc, 0, 1, uid, gid);
+ wrap->putwrapfile(bout, name, t, "proto", protof, uid, gid);
+ wrap->putwrapfile(bout, name, t, "md5sum", md5sort, uid, gid);
+ if (update)
+ wrap->putwrapfile(bout, name, t, "remove", rmfile, uid, gid);
+ if(notesf != nil)
+ wrap->putwrapfile(bout, name, t, "notes", notesf, uid, gid);
+ md5s = keyring->md5(nil, 0, nil, nil);
+ pass = 1;
+ return digest;
+}
+
+wrapend(digest : array of byte)
+{
+ digest0 := array[keyring->MD5dlen] of { * => byte 0 };
+ keyring->md5(nil, 0, digest0, md5s);
+ md5s = nil;
+ if (wrap->memcmp(digest, digest0, keyring->MD5dlen) != 0)
+ warn(sys->sprint("files changed underfoot %s %s", wrap->md5conv(digest), wrap->md5conv(digest0)));
+}
+
+mkarch(new : string, old : string, d : ref Dir)
+{
+ if(pass == 0 && old != new)
+ onlist = (old, new) :: onlist;
+ if(prefix != nil && !wrap->match(new, prefix))
+ return;
+ if(notprefix != nil && !wrap->notmatch(new, notprefix))
+ return;
+ digest := array[keyring->MD5dlen] of { * => byte 0 };
+ wrap->md5file(old, digest);
+ (ok, nil) := wrap->getfileinfo(w, new, digest, nil, nil);
+ if (ok >= 0)
+ return;
+ n := array of byte new;
+ keyring->md5(n, len n, nil, md5s);
+ if (pass == 0) {
+ bout.puts(sys->sprint("%s %s\n", new, wrap->md5conv(digest)));
+ return;
+ }
+ if(verb)
+ fprint(stderr, "%s\n", new);
+ puthdr(new, d);
+ if(!(d.mode & Sys->DMDIR)) {
+ err := arch->putfile(bout, old, int d.length);
+ if (err != nil)
+ warn(err);
+ }
+}
+
+fsort(fd : ref Sys->FD, file : string, c : chan of int)
+{
+ sys->pctl(Sys->FORKFD, nil);
+ sys->dup(fd.fd, 1);
+ cmd := "/dis/sort.dis";
+ m := load Command cmd;
+ if(m == nil) {
+ c <-= -1;
+ return;
+ }
+ m->init(nil, cmd :: file :: nil);
+ c <-= 0;
+}
+
+tmpfiles: list of string;
+
+opentemp(prefix: string, t: int): (string, ref Sys->FD)
+{
+ name := sys->sprint("/tmp/%s.%ud.%d", prefix, t, sys->pctl(0, nil));
+ fd := sys->create(name, Sys->ORDWR, 8r666);
+ # fd := sys->create(name, Sys->ORDWR | Sys->ORCLOSE, 8r666); not on Nt
+ tmpfiles = name :: tmpfiles;
+ return (name, fd);
+}
+
+exits()
+{
+ wrap->end();
+ for( ; tmpfiles != nil; tmpfiles = tl tmpfiles)
+ sys->remove(hd tmpfiles);
+ exit;
+}
+
+pathcat(s : string, t : string) : string
+{
+ if (s == nil) return t;
+ if (t == nil) return s;
+ slashs := s[len s - 1] == '/';
+ slasht := t[0] == '/';
+ if (slashs && slasht)
+ return s + t[1:];
+ if (!slashs && !slasht)
+ return s + "/" + t;
+ return s + t;
+}
diff --git a/appl/cmd/install/eproto.b b/appl/cmd/install/eproto.b
new file mode 100644
index 00000000..b87a4390
--- /dev/null
+++ b/appl/cmd/install/eproto.b
@@ -0,0 +1,357 @@
+implement Fsmodule;
+include "sys.m";
+ sys: Sys;
+include "readdir.m";
+ readdir: Readdir;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "string.m";
+ str: String;
+include "draw.m";
+include "sh.m";
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, report, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+File: adt {
+ name: string;
+ mode: int;
+ owner: string;
+ group: string;
+ old: string;
+ flags: int;
+ sub: cyclic array of ref File;
+};
+
+Proto: adt {
+ indent: int;
+ lastline: string;
+ iob: ref Iobuf;
+};
+
+Star, Plus: con 1<<iota;
+
+types(): string
+{
+ return "ts-rs";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: eproto: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ readdir = load Readdir Readdir->PATH;
+ if(readdir == nil)
+ badmod(Readdir->PATH);
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ badmod(Bufio->PATH);
+ str = load String String->PATH;
+ if(str == nil)
+ badmod(String->PATH);
+}
+
+run(nil: ref Draw->Context, report: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ protofile := (hd args).s().i;
+ rootpath: string;
+ if(opts != nil)
+ rootpath = (hd (hd opts).args).s().i;
+ if(rootpath == nil)
+ rootpath = "/";
+
+ proto := ref Proto(0, nil, nil);
+ if((proto.iob = bufio->open(protofile, Sys->OREAD)) == nil){
+ sys->fprint(sys->fildes(2), "fs: eproto: cannot open %q: %r\n", protofile);
+ return nil;
+ }
+ root := ref File(rootpath, ~0, nil, nil, nil, 0, nil);
+ (root.flags, root.sub) = readproto(proto, -1);
+ c := Entrychan(chan of int, chan of Entry);
+ spawn protowalk(c, root, report.start("proto"));
+ return ref Value.T(c);
+}
+
+protowalk(c: Entrychan, root: ref File, errorc: chan of string)
+{
+ if(<-c.sync == 0){
+ quit(errorc);
+ exit;
+ }
+ protowalk1(c, root.flags, root.name, file2dir(root, nil), root.sub, -1, errorc);
+ c.c <-= (nil, nil, 0);
+ quit(errorc);
+}
+
+protowalk1(c: Entrychan, flags: int, path: string, d: ref Sys->Dir,
+ sub: array of ref File, depth: int, errorc: chan of string): int
+{
+ if(depth >= 0)
+ c.c <-= (d, path, depth);
+ depth++;
+ (a, n) := readdir->init(path, Readdir->NAME|Readdir->COMPACT);
+ j := 0;
+ prevsub: string;
+ for(i := 0; i < n; i++){
+ for(; j < len sub; j++){
+ s := sub[j].name;
+ if(s == prevsub){
+ report(errorc, sys->sprint("duplicate entry %s", pathconcat(path, s)));
+ continue; # eliminate duplicates in proto
+ }
+ if(s >= a[i].name || sub[j].old != nil)
+ break;
+ report(errorc, sys->sprint("%s not found", pathconcat(path, s)));
+ }
+ foundsub := j < len sub && (sub[j].name == a[i].name || sub[j].old != nil);
+ if(foundsub || flags&Plus ||
+ (flags&Star && (a[i].mode & Sys->DMDIR)==0)){
+ f: ref File;
+ if(foundsub){
+ f = sub[j++];
+ prevsub = f.name;
+ }
+ p: string;
+ d: ref Sys->Dir;
+ if(foundsub && f.old != nil){
+ p = f.old;
+ (ok, xd) := sys->stat(p);
+ if(ok == -1){
+ report(errorc, sys->sprint("cannot stat %q: %r", p));
+ continue;
+ }
+ d = ref xd;
+ }else{
+ p = pathconcat(path, a[i].name);
+ d = a[i];
+ }
+
+ d = file2dir(f, d);
+ r: int;
+ if((d.mode & Sys->DMDIR) == 0)
+ r = walkfile(c, p, d, depth, errorc);
+ else if(flags & Plus)
+ r = protowalk1(c, Plus, p, d, nil, depth, errorc);
+ else
+ r = protowalk1(c, f.flags, p, d, f.sub, depth, errorc);
+ if(r == Skip)
+ return Next;
+ }
+ }
+ return Next;
+}
+
+pathconcat(p, name: string): string
+{
+ if(p != nil && p[len p - 1] != '/')
+ p[len p] = '/';
+ return p+name;
+}
+
+# from(ish) walk.b
+walkfile(c: Entrychan, path: string, d: ref Sys->Dir, depth: int, errorc: chan of string): int
+{
+ fd := sys->open(path, Sys->OREAD);
+ if(fd == nil)
+ report(errorc, sys->sprint("cannot open %q: %r", path));
+ else
+ c.c <-= (d, path, depth);
+ return Next;
+}
+
+readproto(proto: ref Proto, indent: int): (int, array of ref File)
+{
+ a := array[10] of ref File;
+ n := 0;
+ flags := 0;
+ while((f := readline(proto, indent)) != nil){
+ if(f.name == "*")
+ flags |= Star;
+ else if(f.name == "+")
+ flags |= Plus;
+ else{
+ (f.flags, f.sub) = readproto(proto, proto.indent);
+ if(n == len a)
+ a = (array[n * 2] of ref File)[0:] = a;
+ a[n++] = f;
+ }
+ }
+ if(n < len a)
+ a = (array[n] of ref File)[0:] = a[0:n];
+ mergesort(a, array[n] of ref File);
+ return (flags, a);
+}
+
+readline(proto: ref Proto, indent: int): ref File
+{
+ s: string;
+ if(proto.lastline != nil){
+ s = proto.lastline;
+ proto.lastline = nil;
+ }else if(proto.indent == -1)
+ return nil;
+ else if((s = proto.iob.gets('\n')) == nil){
+ proto.indent = -1;
+ return nil;
+ }
+ spc := 0;
+ for(i := 0; i < len s; i++){
+ c := s[i];
+ if(c == ' ')
+ spc++;
+ else if(c == '\t')
+ spc += 8;
+ else
+ break;
+ }
+ if(i == len s || s[i] == '#' || s[i] == '\n')
+ return readline(proto, indent); # XXX sort out tail recursion!
+ if(spc <= indent){
+ proto.lastline = s;
+ return nil;
+ }
+ proto.indent = spc;
+ (nil, toks) := sys->tokenize(s, " \t\n");
+ f := ref File(nil, ~0, nil, nil, nil, 0, nil);
+ (f.name, toks) = (getname(hd toks, 0), tl toks);
+ if(toks == nil)
+ return f;
+ (f.mode, toks) = (getmode(hd toks), tl toks);
+ if(toks == nil)
+ return f;
+ (f.owner, toks) = (getname(hd toks, 1), tl toks);
+ if(toks == nil)
+ return f;
+ (f.group, toks) = (getname(hd toks, 1), tl toks);
+ if(toks == nil)
+ return f;
+ (f.old, toks) = (hd toks, tl toks);
+ return f;
+}
+
+mergesort(a, b: array of ref File)
+{
+ r := len a;
+ if (r > 1) {
+ m := (r-1)/2 + 1;
+ mergesort(a[0:m], b[0:m]);
+ mergesort(a[m:], b[m:]);
+ b[0:] = a;
+ for ((i, j, k) := (0, m, 0); i < m && j < r; k++) {
+ if(b[i].name > b[j].name)
+ a[k] = b[j++];
+ else
+ a[k] = b[i++];
+ }
+ if (i < m)
+ a[k:] = b[i:m];
+ else if (j < r)
+ a[k:] = b[j:r];
+ }
+}
+
+getname(s: string, allowminus: int): string
+{
+ if(s == nil)
+ return nil;
+ if(allowminus && s == "-")
+ return nil;
+ if(s[0] == '$'){
+ s = getenv(s[1:]);
+ if(s == nil)
+ ; # TO DO: w.warn(sys->sprint("can't read environment variable %s", s));
+ return s;
+ }
+ return s;
+}
+
+getenv(s: string): string
+{
+ if(s == "user")
+ return readfile("/dev/user"); # more accurate?
+ return readfile("/env/"+s);
+}
+
+readfile(f: string): string
+{
+ fd := sys->open(f, Sys->OREAD);
+ if(fd != nil){
+ a := array[256] of byte;
+ n := sys->read(fd, a, len a);
+ if(n > 0)
+ return string a[0:n];
+ }
+ return nil;
+}
+
+getmode(s: string): int
+{
+ s = getname(s, 1);
+ if(s == nil)
+ return ~0;
+ m := 0;
+ i := 0;
+ if(s[i] == 'd'){
+ m |= Sys->DMDIR;
+ i++;
+ }
+ if(i < len s && s[i] == 'a'){
+ m |= Sys->DMAPPEND;
+ i++;
+ }
+ if(i < len s && s[i] == 'l'){
+ m |= Sys->DMEXCL;
+ i++;
+ }
+ (xmode, t) := str->toint(s, 8);
+ if(t != nil){
+ # report(aux.errorc, "bad mode specification %q", s);
+ return ~0;
+ }
+ return xmode | m;
+}
+
+file2dir(f: ref File, old: ref Sys->Dir): ref Sys->Dir
+{
+ d := ref Sys->nulldir;
+ if(old != nil){
+ if(old.dtype != 'M'){
+ d.uid = "sys";
+ d.gid = "sys";
+ xmode := (old.mode >> 6) & 7;
+ d.mode = old.mode | xmode | (xmode << 3);
+ }else{
+ d.uid = old.uid;
+ d.gid = old.gid;
+ d.mode = old.mode;
+ }
+ d.length = old.length;
+ d.mtime = old.mtime;
+ d.atime = old.atime;
+ d.muid = old.muid;
+ d.name = old.name;
+ }
+ if(f != nil){
+ d.name = f.name;
+ if(f.owner != nil)
+ d.uid = f.owner;
+ if(f.group != nil)
+ d.gid = f.group;
+ if(f.mode != ~0)
+ d.mode = f.mode;
+ }
+ return d;
+}
diff --git a/appl/cmd/install/info.b b/appl/cmd/install/info.b
new file mode 100644
index 00000000..1c95128f
--- /dev/null
+++ b/appl/cmd/install/info.b
@@ -0,0 +1,73 @@
+implement Info;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "daytime.m";
+ daytime: Daytime;
+include "arg.m";
+ arg: Arg;
+include "wrap.m";
+ wrap : Wrap;
+
+Info: module{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+root : string;
+
+TYPLEN : con 4;
+typestr := array[TYPLEN] of { "???", "package", "update", "full update" };
+
+fatal(err : string)
+{
+ sys->fprint(sys->fildes(2), "%s\n", err);
+ raise "fail:error";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ daytime = load Daytime Daytime->PATH;
+ arg = load Arg Arg->PATH;
+ wrap = load Wrap Wrap->PATH;
+ wrap->init(bufio);
+
+ arg->init(args);
+ while ((c := arg->opt()) != 0) {
+ case c {
+ 'r' =>
+ root = arg->arg();
+ if (root == nil)
+ fatal("missing root name");
+ * =>
+ fatal(sys->sprint("bad argument -%c", c));
+ }
+ }
+ args = arg->argv();
+ if (args == nil || tl args != nil)
+ fatal("usage: install/info [-r root] package");
+ w := wrap->openwraphdr(hd args, root, nil, 0);
+ if (w == nil)
+ fatal("no such package found");
+ tm := daytime->text(daytime->local(w.tfull));
+ sys->print("%s (complete as of %s)\n", w.name, tm[0:28]);
+ for (i := w.nu; --i >= 0;) {
+ typ := w.u[i].typ;
+ if (typ < 0 || typ >= TYPLEN)
+ sys->print("%s", typestr[0]);
+ else
+ sys->print("%s", typestr[typ]);
+ sys->print(" %s", wrap->now2string(w.u[i].time, 0));
+ if (typ & wrap->UPD)
+ sys->print(" updating %s", wrap->now2string(w.u[i].utime, 0));
+ if (w.u[i].desc != nil)
+ sys->print(": %s", w.u[i].desc);
+ sys->print("\n");
+ }
+ wrap->end();
+}
diff --git a/appl/cmd/install/inst.b b/appl/cmd/install/inst.b
new file mode 100644
index 00000000..dfec4785
--- /dev/null
+++ b/appl/cmd/install/inst.b
@@ -0,0 +1,500 @@
+implement Inst;
+
+include "sys.m";
+ sys: Sys;
+ Dir, sprint, fprint: import sys;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "string.m";
+ str: String;
+include "arg.m";
+ arg: Arg;
+include "keyring.m";
+ keyring : Keyring;
+include "arch.m";
+ arch : Arch;
+include "wrap.m";
+ wrap : Wrap;
+
+Inst: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+LEN: con Sys->ATOMICIO;
+
+tflag := 0;
+uflag := 0;
+hflag := 0;
+vflag := 0;
+fflag := 1;
+stderr: ref Sys->FD;
+bout: ref Iobuf;
+argv0 := "inst";
+oldw, w : ref Wrap->Wrapped;
+root := "/";
+force := 0;
+stoponerr := 1;
+
+# membogus(argv: list of string)
+# {
+#
+# }
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ error(sys->sprint("cannot load %s: %r\n", Bufio->PATH));
+
+ str = load String String->PATH;
+ if(str == nil)
+ error(sys->sprint("cannot load %s: %r\n", String->PATH));
+
+ arg = load Arg Arg->PATH;
+ if(arg == nil)
+ error(sys->sprint("cannot load %s: %r\n", Arg->PATH));
+ keyring = load Keyring Keyring->PATH;
+ if(keyring == nil)
+ error(sys->sprint("cannot load %s: %r\n", Keyring->PATH));
+ arch = load Arch Arch->PATH;
+ if(arch == nil)
+ error(sys->sprint("cannot load %s: %r\n", Arch->PATH));
+ arch->init(bufio);
+ wrap = load Wrap Wrap->PATH;
+ if(wrap == nil)
+ error(sys->sprint("cannot load %s: %r\n", Wrap->PATH));
+ wrap->init(bufio);
+ arg->init(args);
+ while((c := arg->opt()) != 0)
+ case c {
+ 'f' =>
+ fflag = 0;
+ 'h' =>
+ hflag = 1;
+ bout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
+ if(bout == nil)
+ error(sys->sprint("can't access standard output: %r"));
+ 't' =>
+ tflag = 1;
+ 'u' =>
+ uflag = 1;
+ 'v' =>
+ vflag = 1;
+ 'r' =>
+ root = arg->arg();
+ if (root == nil)
+ fatal("root missing");
+ 'F' =>
+ force = 1;
+ 'c' =>
+ stoponerr = 0;
+ * =>
+ usage();
+ }
+ args = arg->argv();
+ if (args == nil)
+ usage();
+ ar := arch->openarch(hd args);
+ if(ar == nil || ar.b == nil)
+ error(sys->sprint("can't access %s: %r", hd args));
+ w = wrap->openwraphdr(hd args, root, nil, 0);
+ if (w == nil)
+ fatal("no such package found");
+ if(w.nu != 1)
+ fatal("strange package: more than one piece");
+ if (force == 0)
+ oldw = wrap->openwrap(w.name, root, 0);
+ if (force == 0 && w.u[0].utime && (oldw == nil || oldw.tfull < w.u[0].utime)){
+ tfull: int;
+ if(oldw == nil)
+ tfull = -1;
+ else
+ tfull = oldw.tfull;
+ fatal(sys->sprint("need %s version of %s already installed (pkg %d)", wrap->now2string(w.u[0].utime, 0), w.name, tfull));
+ }
+ args = tl args;
+ digest := array[Keyring->MD5dlen] of byte;
+ digest0 := array[Keyring->MD5dlen] of byte;
+ digest1 := array[Keyring->MD5dlen] of byte;
+
+ while ((a := arch->gethdr(ar)) != nil) {
+ why := "";
+ docopy := 0;
+ if(force)
+ docopy = 1;
+ else if(a.d.mode & Sys->DMDIR)
+ docopy = 1;
+ else if(wrap->md5file(root+a.name, digest) < 0)
+ docopy = 1;
+ else{
+ wrap->md5filea(root+a.name, digest1);
+ (ok, t) := wrap->getfileinfo(oldw, a.name, digest, nil, digest1);
+ if (ok >= 0) {
+ if(t > w.u[0].time){
+ docopy = 0;
+ why = "version from newer package exists";
+ }
+ else
+ docopy = 1;
+ }
+ else {
+ (ok, t) = wrap->getfileinfo(oldw, a.name, nil, nil, nil);
+ if(ok >= 0){
+ docopy = 0;
+ why = "locally modified";
+ }
+ else{
+ docopy = 0;
+ why = "locally created";
+ }
+ }
+ }
+ if(!docopy){
+ wrap->md5sum(ar.b, digest0, int a.d.length);
+ if(wrap->memcmp(digest, digest0, Keyring->MD5dlen))
+ skipfile(a.name, why);
+ continue;
+ }
+ if(args != nil){
+ if(!selected(a.name, args)){
+ arch->drain(ar, int a.d.length);
+ continue;
+ }
+ if (!hflag)
+ mkdirs(root, a.name);
+ }
+ name := pathcat(root, a.name);
+ if(hflag){
+ bout.puts(sys->sprint("%s %uo %s %s %ud %d\n",
+ name, a.d.mode, a.d.uid, a.d.gid, a.d.mtime, int a.d.length));
+ arch->drain(ar, int a.d.length);
+ continue;
+ }
+ if(a.d.mode & Sys->DMDIR)
+ mkdir(name, a.d);
+ else
+ extract(ar, name, a.d);
+ }
+ arch->closearch(ar);
+ if(ar.err == nil){
+ # fprint(stderr, "done\n");
+ quit(nil);
+ }
+ else {
+ fprint(stderr, "%s\n", ar.err);
+ quit("eof");
+ }
+}
+
+skipfile(f : string, why : string)
+{
+ sys->fprint(stderr, "skipping %s: %s\n", f, why);
+}
+
+skiprmfile(f: string, why: string)
+{
+ sys->fprint(stderr, "not removing %s: %s\n", f, why);
+}
+
+doremove(s : string)
+{
+ p := pathcat(root, s);
+ digest := array[Keyring->MD5dlen] of { * => byte 0 };
+ digest1 := array[Keyring->MD5dlen] of { * => byte 0 };
+ if(wrap->md5file(p, digest) < 0)
+ ;
+ else{
+ wrap->md5filea(p, digest1);
+ (ok, nil) := wrap->getfileinfo(oldw, s, digest, nil, digest1);
+ if(force == 0 && ok < 0)
+ skiprmfile(p, "locally modified");
+ else{
+ if (vflag)
+ sys->print("rm %s\n", p);
+ remove(p);
+ }
+ }
+}
+
+quit(s: string)
+{
+ if (s == nil) {
+ p := w.u[0].dir + "/remove";
+ if ((b := bufio->open(p, Bufio->OREAD)) != nil) {
+ while ((t := b.gets('\n')) != nil) {
+ lt := len t;
+ if (t[lt-1] == '\n')
+ t = t[0:lt-1];
+ doremove(t);
+ }
+ }
+ }
+ if(bout != nil)
+ bout.flush();
+ if(wrap != nil)
+ wrap->end();
+ if(s != nil)
+ raise "fail: "+s;
+ else
+ fprint(stderr, "done\n");
+ exit;
+}
+
+fileprefix(prefix, s: string): int
+{
+ n := len prefix;
+ m := len s;
+ if(n > m || !str->prefix(prefix, s))
+ return 0;
+ if(m > n && s[n] != '/')
+ return 0;
+ return 1;
+}
+
+selected(s: string, args: list of string): int
+{
+ for(; args != nil; args = tl args)
+ if(fileprefix(hd args, s))
+ return 1;
+ return 0;
+}
+
+mkdirs(basedir, name: string)
+{
+ (nil, names) := sys->tokenize(name, "/");
+ while(names != nil) {
+ create(basedir, Sys->OREAD, 8r775|Sys->DMDIR);
+ if(tl names == nil)
+ break;
+ basedir = basedir + "/" + hd names;
+ names = tl names;
+ }
+}
+
+mkdir(name: string, dir : ref Sys->Dir)
+{
+ d: Dir;
+ i: int;
+
+ if(vflag) {
+ MTPT : con "/n/remote";
+ s := name;
+ if (len name >= len MTPT && name[0:len MTPT] == MTPT)
+ s = name[len MTPT:];
+ sys->print("installing directory %s\n", s);
+ }
+ fd := create(name, Sys->OREAD, dir.mode);
+ if(fd == nil) {
+ err := sys->sprint("%r");
+ (i, d) = sys->stat(name);
+ if(i < 0 || !(d.mode & Sys->DMDIR)){
+ werr(sys->sprint("can't make directory %s: %s", name, err));
+ return;
+ }
+ }
+ else {
+ (i, d) = sys->fstat(fd);
+ if(i < 0)
+ warn(sys->sprint("can't stat %s: %r", name));
+ fd = nil;
+ }
+ d = sys->nulldir;
+ (nil, p) := str->splitr(name, "/");
+ if(p == nil)
+ p = name;
+ d.name = p;
+ d.mode = dir.mode;
+ if(tflag || uflag)
+ d.mtime = dir.mtime;
+ if(uflag){
+ d.uid = dir.uid;
+ d.gid = dir.gid;
+ }
+ fd = nil;
+ if(sys->wstat(name, d) < 0){
+ e := sys->sprint("%r");
+ if(wstat(name, d) < 0)
+ warn(sys->sprint("can't set modes for %s: %s", name, e));
+ }
+ if(uflag){
+ (i, d) = sys->stat(name);
+ if(i < 0)
+ warn(sys->sprint("can't reread modes for %s: %r", name));
+ if(dir.uid != d.uid)
+ warn(sys->sprint("%s: uid mismatch %s %s", name, dir.uid, d.uid));
+ if(dir.gid != d.gid)
+ warn(sys->sprint("%s: gid mismatch %s %s", name, dir.gid, d.gid));
+ }
+}
+
+extract(ar : ref Arch->Archive, name: string, dir : ref Sys->Dir)
+{
+ sfd := create(name, Sys->OWRITE, dir.mode);
+ if(sfd == nil) {
+ if(!fflag || remove(name) == -1 ||
+ (sfd = create(name, Sys->OWRITE, dir.mode)) == nil) {
+ werr(sys->sprint("can't make file %s: %r", name));
+ arch->drain(ar, int dir.length);
+ return;
+ }
+ }
+ b := bufio->fopen(sfd, Bufio->OWRITE);
+ if (b == nil) {
+ warn(sys->sprint("can't open file %s for bufio : %r", name));
+ arch->drain(ar, int dir.length);
+ return;
+ }
+ err := arch->getfile(ar, b, int dir.length);
+ if (err != nil) {
+ if (len err >= 9 && err[0:9] == "premature")
+ fatal(err);
+ else
+ warn(err);
+ }
+ (i, d) := sys->fstat(b.fd);
+ if(i < 0)
+ warn(sys->sprint("can't stat %s: %r", name));
+ d = sys->nulldir;
+ (nil, p) := str->splitr(name, "/");
+ if(p == nil)
+ p = name;
+ d.name = p;
+ d.mode = dir.mode;
+ if(tflag || uflag)
+ d.mtime = dir.mtime;
+ if(uflag){
+ d.uid = dir.uid;
+ d.gid = dir.gid;
+ }
+ if(b.flush() == Bufio->ERROR)
+ werr(sys->sprint("error writing %s: %r", name));
+ b.close();
+ sfd = nil;
+ if(sys->wstat(name, d) < 0){
+ e := sys->sprint("%r");
+ if(wstat(name, d) < 0)
+ warn(sys->sprint("can't set modes for %s: %s", name, e));
+ }
+ if(uflag){
+ (i, d) = sys->stat(name);
+ if(i < 0)
+ warn(sys->sprint("can't reread modes for %s: %r", name));
+ if(d.uid != dir.uid)
+ warn(sys->sprint("%s: uid mismatch %s %s", name, dir.uid, d.uid));
+ if(d.gid != dir.gid)
+ warn(sys->sprint("%s: gid mismatch %s %s", name, dir.gid, d.gid));
+ }
+}
+
+error(s: string)
+{
+ fprint(stderr, "%s: %s\n", argv0, s);
+ quit("error");
+}
+
+werr(s: string)
+{
+ fprint(stderr, "%s: %s\n", argv0, s);
+ if(stoponerr)
+ quit("werr");
+}
+
+warn(s: string)
+{
+ fprint(stderr, "%s: %s\n", argv0, s);
+}
+
+usage()
+{
+ fprint(stderr, "Usage: inst [-h] [-u] [-v] [-f] [-c] [-F] [-r dest-root] [file ...]\n");
+ raise "fail: usage";
+}
+
+fatal(s : string)
+{
+ sys->fprint(stderr, "inst: %s\n", s);
+ if(wrap != nil)
+ wrap->end();
+ exit;
+}
+
+parent(name : string) : string
+{
+ slash := -1;
+ for (i := 0; i < len name; i++)
+ if (name[i] == '/')
+ slash = i;
+ if (slash > 0)
+ return name[0:slash];
+ return "/";
+}
+
+create(name : string, rw : int, mode : int) : ref Sys->FD
+{
+ fd := sys->create(name, rw, mode);
+ if (fd == nil) {
+ p := parent(name);
+ (ok, d) := sys->stat(p);
+ if (ok < 0)
+ return nil;
+ omode := d.mode;
+ d = sys->nulldir;
+ d.mode = omode | 8r222; # ensure parent is writable
+ sys->wstat(p, d);
+ fd = sys->create(name, rw, mode);
+ d.mode = omode;
+ sys->wstat(p, d);
+ }
+ return fd;
+}
+
+remove(name : string) : int
+{
+ if (sys->remove(name) < 0) {
+ (ok, d) := sys->stat(name);
+ if (ok < 0)
+ return -1;
+ omode := d.mode;
+ d.mode |= 8r222;
+ sys->wstat(name, d);
+ if (sys->remove(name) >= 0)
+ return 0;
+ d.mode = omode;
+ sys->wstat(name, d);
+ return -1;
+ }
+ return 0;
+}
+
+wstat(name : string, d : Dir) : int
+{
+ (ok, dir) := sys->stat(name);
+ if (ok < 0)
+ return -1;
+ omode := dir.mode;
+ dir.mode |= 8r222;
+ sys->wstat(name, dir);
+ if (sys->wstat(name, d) >= 0)
+ return 0;
+ dir.mode = omode;
+ sys->wstat(name, dir);
+ return -1;
+}
+
+pathcat(s : string, t : string) : string
+{
+ if (s == nil) return t;
+ if (t == nil) return s;
+ slashs := s[len s - 1] == '/';
+ slasht := t[0] == '/';
+ if (slashs && slasht)
+ return s + t[1:];
+ if (!slashs && !slasht)
+ return s + "/" + t;
+ return s + t;
+}
diff --git a/appl/cmd/install/install.b b/appl/cmd/install/install.b
new file mode 100644
index 00000000..858f3a27
--- /dev/null
+++ b/appl/cmd/install/install.b
@@ -0,0 +1,430 @@
+implement Install;
+
+#
+# Determine which packages need installing and calls install/inst
+# to actually install each one
+#
+
+# usage: install/install -d -F -g -s -u -i installdir -p platform -r root -P package
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "string.m";
+ str: String;
+include "arg.m";
+ arg: Arg;
+include "readdir.m";
+ readdir : Readdir;
+include "sh.m";
+
+Install: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+# required dirs, usually in the standard inferno root.
+# The network download doesn't include them because of
+# problems with versions of tar that won't create empty dirs
+# so we'll make sure they exist.
+
+reqdirs := array [] of {
+ "/mnt",
+ "/mnt/wrap",
+ "/n",
+ "/n/remote",
+ "/tmp",
+};
+
+YES, NO, QUIT, ERR : con iota;
+INST : con "install/inst"; # actual install program
+MTPT : con "/n/remote"; # mount point for user's inferno root
+
+debug := 0;
+force := 0;
+exitemu := 0;
+uflag := 0;
+stderr : ref Sys->FD;
+installdir := "/install";
+platform := "Plan9";
+lcplatform : string;
+root := "/usr/inferno";
+local: int;
+global: int = 1;
+waitfd : ref Sys->FD;
+
+Product : adt {
+ name : string;
+ pkgs : ref Package;
+ nxt : ref Product;
+};
+
+Package : adt {
+ name : string;
+ nxt : ref Package;
+};
+
+instprods : ref Product; # products/packages already installed
+
+# platform independent packages
+xpkgs := array[] of { "inferno", "utils", "src", "ipaq", "minitel", "sds" };
+ypkgs: list of string;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+
+ # Hack for network download...
+ # make sure the dirs we need exist
+ for (dirix := 0; dirix < len reqdirs; dirix++) {
+ dir := reqdirs[dirix];
+ (exists, nil) := sys->stat(dir);
+ if (exists == -1) {
+ fd := sys->create(dir, Sys->OREAD, Sys->DMDIR + 8r7775);
+ if (fd == nil)
+ fatal(sys->sprint("cannot create directory %s: %r\n", dir));
+ fd = nil;
+ }
+ }
+
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ fatal(sys->sprint("cannot load %s: %r\n", Bufio->PATH));
+ readdir = load Readdir Readdir->PATH;
+ if(readdir == nil)
+ fatal(sys->sprint("cannot load %s: %r\n", Readdir->PATH));
+ str = load String String->PATH;
+ if(str == nil)
+ fatal(sys->sprint("cannot load %s: %r\n", String->PATH));
+ arg = load Arg Arg->PATH;
+ if(arg == nil)
+ fatal(sys->sprint("cannot load %s: %r\n", Arg->PATH));
+ arg->init(args);
+ while((c := arg->opt()) != 0) {
+ case c {
+ 'd' =>
+ debug = 1;
+ 'F' =>
+ force = 1;
+ 's' =>
+ exitemu = 1;
+ 'i' =>
+ installdir = arg->arg();
+ if (installdir == nil)
+ fatal("install directory missing");
+ 'p' =>
+ platform = arg->arg();
+ if (platform == nil)
+ fatal("platform missing");
+ 'P' =>
+ pkg := arg->arg();
+ if (pkg == nil)
+ fatal("package missing");
+ ypkgs = pkg :: ypkgs;
+ 'r' =>
+ root = arg->arg();
+ if (root == nil)
+ fatal("inferno root missing");
+ 'u' =>
+ uflag = 1;
+ 'g' =>
+ global = 0;
+ '*' =>
+ usage();
+ }
+ }
+ if (arg->argv() != nil)
+ usage();
+ lcplatform = str->tolower(platform);
+ (ok, dir) := sys->stat(installdir);
+ if (ok < 0)
+ fatal(sys->sprint("cannot open install directory %s", installdir));
+ nt := lcplatform == "nt";
+ if (nt) {
+ # root os of the form ?:/.........
+ if (len root < 3 || root[1] != ':' || root[2] != '/')
+ fatal(sys->sprint("root %s not of the form ?:/.......", root));
+ spec := root[0:2];
+ root = root[2:];
+ if (sys->bind("#U"+spec, MTPT, Sys->MREPL|Sys->MCREATE) < 0)
+ fatal(sys->sprint("cannot bind to drive %s", spec));
+ }
+ else {
+ if (root[0] != '/')
+ fatal(sys->sprint("root %s must be an absolute path name", root));
+ if (sys->bind("#U*", MTPT, Sys->MREPL|Sys->MCREATE) < 0)
+ fatal("cannot bind to system root");
+ }
+ (ok, dir) = sys->stat(MTPT+root);
+ if (ok >= 0) {
+ if ((dir.mode & Sys->DMDIR) == 0)
+ fatal(sys->sprint("inferno root %s is not a directory", root));
+ }
+ else if (sys->create(MTPT+root, Sys->OREAD, 8r775 | Sys->DMDIR) == nil)
+ fatal(sys->sprint("cannot create inferno root %s: %r", root));
+ # need a writable tmp directory /tmp in case installing from CD
+ (ok, dir) = sys->stat(MTPT+root+"/tmp");
+ if (ok >= 0) {
+ if ((dir.mode & Sys->DMDIR) == 0)
+ fatal(sys->sprint("inferno root tmp %s is not a directory", root+"/tmp"));
+ }
+ else if (sys->create(MTPT+root+"/tmp", Sys->OREAD, 8r775 | Sys->DMDIR) == nil)
+ fatal(sys->sprint("cannot create inferno root tmp %s: %r", root+"/tmp"));
+ if (sys->bind(MTPT+root, MTPT, Sys->MREPL | Sys->MCREATE) < 0)
+ fatal("cannot bind inferno root");
+ if (sys->bind(MTPT+"/tmp", "/tmp", Sys->MREPL | Sys->MCREATE) < 0)
+ fatal("cannot bind inferno root tmp");
+ root = MTPT;
+
+ if (nt || 1)
+ local = 1;
+ else {
+ sys->print("You can either install software specific to %s only or\n", platform);
+ sys->print(" install software for all platforms that we support.\n");
+ sys->print("If you are unsure what to do, answer yes to the question following.\n");
+ sys->print(" You can install the remainder of the software at a later date if desired.\n");
+ sys->print("\n");
+ b := bufio->fopen(sys->fildes(0), Bufio->OREAD);
+ if (b == nil)
+ fatal("cannot open stdin");
+ for (;;) {
+ sys->print("Install software specific to %s only ? (yes/no/quit) ", platform);
+ resp := getresponse(b);
+ ans := answer(resp);
+ if (ans == QUIT)
+ exit;
+ else if (ans == ERR)
+ sys->print("bad response %s\n\n", resp);
+ else {
+ local = ans == YES;
+ break;
+ }
+ }
+ }
+ instprods = dowraps(root+"/wrap");
+ doprods(installdir);
+ if (!nt)
+ sys->print("installation complete\n");
+ if (exitemu)
+ shutdown();
+}
+
+getresponse(b : ref Iobuf) : string
+{
+ s := b.gets('\n');
+ while (s != nil && (s[0] == ' ' || s[0] == '\t'))
+ s = s[1:];
+ while (s != nil && ((c := s[len s - 1]) == ' ' || c == '\t' || c == '\n'))
+ s = s[0: len s - 1];
+ return s;
+}
+
+answer(s : string) : int
+{
+ s = str->tolower(s);
+ if (s == "y" || s == "yes")
+ return YES;
+ if (s == "n" || s == "no")
+ return NO;
+ if (s == "q" || s == "quit")
+ return QUIT;
+ return ERR;
+}
+
+usage()
+{
+ fatal("Usage: install [-d] [-F] [-s] [-u] [-i installdir ] [-p platform ] [-r root]");
+}
+
+fatal(s : string)
+{
+ sys->fprint(stderr, "install: %s\n", s);
+ exit;
+}
+
+dowraps(d : string) : ref Product
+{
+ p : ref Product;
+
+ # make an inventory of what is already apparently installed
+ (dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT);
+ for (i := 0; i < n; i++) {
+ if (dir[i].mode & Sys->DMDIR) {
+ p = ref Product(str->tolower(dir[i].name), nil, p);
+ p.pkgs = dowrap(d + "/" + dir[i].name);
+ }
+ }
+ return p;
+}
+
+dowrap(d : string) : ref Package
+{
+ p : ref Package;
+
+ (dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT);
+ for (i := 0; i < n; i++)
+ p = ref Package(dir[i].name, p);
+ return p;
+}
+
+doprods(d : string)
+{
+ (dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT);
+ for (i := 0; i < n; i++) {
+ if (dir[i].mode & Sys->DMDIR)
+ doprod(str->tolower(dir[i].name), d + "/" + dir[i].name);
+ }
+}
+
+doprod(pr : string, d : string)
+{
+ # base package, updates and update packages have the name
+ # <timestamp> or <timestamp.gz>
+ if (!wanted(pr))
+ return;
+ (dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT);
+ for (i := 0; i < n; i++) {
+ pk := dir[i].name;
+ l := len pk;
+ if (l >= 4 && pk[l-3:l] == ".gz")
+ pk = pk[0:l-3];
+ else if (l >= 5 && (pk[l-4:] == ".tgz" || pk[l-4:] == ".9gz"))
+ pk = pk[0:l-4];
+ dopkg(pk, pr, d+"/"+dir[i].name);
+
+ }
+}
+
+dopkg(pk : string, pr : string, d : string)
+{
+ if (!installed(pk, pr))
+ install(d);
+}
+
+installed(pkg : string, prd : string) : int
+{
+ for (pr := instprods; pr != nil; pr = pr.nxt) {
+ if (pr.name == prd) {
+ for (pk := pr.pkgs; pk != nil; pk = pk.nxt) {
+ if (pk.name == pkg)
+ return 1;
+ }
+ return 0;
+ }
+ }
+ return 0;
+}
+
+lookup(pr : string) : int
+{
+ for (i := 0; i < len xpkgs; i++) {
+ if (xpkgs[i] == pr)
+ return i;
+ }
+ return -1;
+}
+
+plookup(pr: string): int
+{
+ for(ps := ypkgs; ps != nil; ps = tl ps)
+ if(pr == hd ps)
+ return 1;
+ return 0;
+}
+
+wanted(pr : string) : int
+{
+ if (!local || global)
+ return 1;
+ if(ypkgs != nil) # overrides everything else
+ return plookup(pr);
+ found := lookup(pr);
+ if (found >= 0)
+ return 1;
+ return pr == lcplatform || prefix(lcplatform, pr);
+}
+
+install(d : string)
+{
+ if (waitfd == nil)
+ waitfd = openwait(sys->pctl(0, nil));
+ sys->fprint(stderr, "installing package %s\n", d);
+ if (debug)
+ return;
+ c := chan of int;
+ args := "-t" :: "-v" :: "-r" :: root :: d :: nil;
+ if (uflag)
+ args = "-u" :: args;
+ if (force)
+ args = "-F" :: args;
+ spawn exec(INST, INST :: args, c);
+ execpid := <- c;
+ wait(waitfd, execpid);
+}
+
+exec(cmd : string, argl : list of string, ci : chan of int)
+{
+ ci <-= sys->pctl(Sys->FORKNS|Sys->NEWFD|Sys->NEWPGRP, 0 :: 1 :: 2 :: stderr.fd :: nil);
+ file := cmd;
+ if(len file<4 || file[len file-4:] !=".dis")
+ file += ".dis";
+ c := load Command file;
+ if(c == nil) {
+ err := sys->sprint("%r");
+ if(file[0] !='/' && file[0:2] !="./") {
+ c = load Command "/dis/"+file;
+ if(c == nil)
+ err = sys->sprint("%r");
+ }
+ if(c == nil)
+ fatal(sys->sprint("%s: %s\n", cmd, err));
+ }
+ c->init(nil, argl);
+}
+
+openwait(pid : int) : ref Sys->FD
+{
+ w := sys->sprint("#p/%d/wait", pid);
+ fd := sys->open(w, Sys->OREAD);
+ if (fd == nil)
+ fatal("fd == nil in wait");
+ return fd;
+}
+
+wait(wfd : ref Sys->FD, wpid : int)
+{
+ n : int;
+
+ buf := array[Sys->WAITLEN] of byte;
+ status := "";
+ for(;;) {
+ if ((n = sys->read(wfd, buf, len buf)) < 0)
+ fatal("bad read in wait");
+ status = string buf[0:n];
+ break;
+ }
+ if (int status != wpid)
+ fatal("bad status in wait");
+ if(status[len status - 1] != ':')
+ fatal(sys->sprint("%s\n", status));
+}
+
+shutdown()
+{
+ fd := sys->open("/dev/sysctl", sys->OWRITE);
+ if(fd == nil)
+ fatal("cannot shutdown emu");
+ if (sys->write(fd, array of byte "halt", 4) < 0)
+ fatal(sys->sprint("shutdown: write failed: %r\n"));
+}
+
+prefix(s, t : string) : int
+{
+ if (len s <= len t)
+ return t[0:len s] == s;
+ return 0;
+}
diff --git a/appl/cmd/install/log.b b/appl/cmd/install/log.b
new file mode 100644
index 00000000..d624f446
--- /dev/null
+++ b/appl/cmd/install/log.b
@@ -0,0 +1,76 @@
+implement Fsmodule;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "sh.m";
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s, quit: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Next, Down, Skip, Quit: import Fslib;
+
+types(): string
+{
+ return "vt-us-gs";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: log: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ daytime = load Daytime Daytime->PATH;
+ if(daytime == nil)
+ badmod(Daytime->PATH);
+}
+
+run(nil: ref Draw->Context, report: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ uid, gid: string;
+ for(; opts != nil; opts = tl opts){
+ o := hd (hd opts).args;
+ case (hd opts).opt {
+ 'u' => uid = o.s().i;
+ 'g' => gid = o.s().i;
+ }
+ }
+ sync := chan of int;
+ spawn logproc(sync, (hd args).t().i, report.start("log"), uid, gid);
+ return ref Value.V(sync);
+}
+
+logproc(sync: chan of int, c: Entrychan, errorc: chan of string, uid: string, gid: string)
+{
+ if(<-sync == 0){
+ c.sync <-= 0;
+ quit(errorc);
+ exit;
+ }
+ c.sync <-= 1;
+
+ now := daytime->now();
+ for(seq := 0; ((d, p, nil) := <-c.c).t0 != nil; seq++){
+ if(uid != nil)
+ d.uid = uid;
+ if(gid != nil)
+ d.gid = gid;
+ sys->print("%ud %ud %c %q - - %uo %q %q %ud %bd%s\n", now, seq, 'a', p, d.mode, d.uid, d.gid, d.mtime, d.length, "");
+ }
+ quit(errorc);
+}
diff --git a/appl/cmd/install/logs.b b/appl/cmd/install/logs.b
new file mode 100644
index 00000000..20135622
--- /dev/null
+++ b/appl/cmd/install/logs.b
@@ -0,0 +1,287 @@
+implement Logs;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "string.m";
+ str: String;
+
+include "logs.m";
+
+Hashsize: con 1024;
+Incr: con 500;
+
+init(bio: Bufio): string
+{
+ sys = load Sys Sys->PATH;
+ bufio = bio;
+ str = load String String->PATH;
+ if(str == nil)
+ return sys->sprint("can't load %s: %r", String->PATH);
+ return nil;
+}
+
+Entry.read(in: ref Iobuf): (ref Entry, string)
+{
+ if((s := in.gets('\n')) == nil)
+ return (nil, nil);
+ if(s[len s-1] == '\n')
+ s = s[0:len s-1];
+
+ e := ref Entry;
+ e.x = -1;
+
+ l := str->unquoted(s);
+ fields := array[11] of string;
+ for(i := 0; l != nil; l = tl l)
+ fields[i++] = S(hd l);
+
+ # time gen verb path serverpath mode uid gid mtime length
+ # 1064889121 4 a sys/src/cmd/ip/httpd/webls.denied - 664 sys sys 1064887847 3
+ # time[0] gen[1] op[2] path[3] (serverpath|"-")[4] mode[5] uid[6] gid[7] mtime[8] length[9]
+
+ if(i < 10 || len fields[2] != 1)
+ return (nil, sys->sprint("bad log entry: %q", s));
+ e.action = fields[2][0];
+ case e.action {
+ 'a' or 'c' or 'd' or 'm' =>
+ ;
+ * =>
+ return (nil, sys->sprint("bad log entry: %q", s));
+ }
+
+ time := bigof(fields[0], 10);
+ sgen := bigof(fields[1], 10);
+ e.seq = (time << 32) | sgen; # for easier comparison
+
+ # time/gen check
+ # name check
+
+ if(fields[4] == "-") # undocumented
+ fields[4] = fields[3];
+ e.path = fields[3];
+ e.serverpath = fields[4];
+ e.d = sys->nulldir;
+ {
+ e.d.mode = intof(fields[5], 8);
+ e.d.qid.qtype = e.d.mode>>24;
+ e.d.uid = fields[6];
+ if(e.d.uid == "-")
+ e.d.uid = "";
+ e.d.gid = fields[7];
+ if(e.d.gid == "-")
+ e.d.gid = "";
+ e.d.mtime = intof(fields[8], 10);
+ e.d.length = bigof(fields[9], 10);
+ }exception ex {
+ "log format:*" =>
+ return (nil, sys->sprint("%s in log entry %q", ex, s));
+ }
+ e.contents = fields[10] :: nil; # optional
+ return (e, nil);
+}
+
+rev[T](l: list of T): list of T
+{
+ rl: list of T;
+ for(; l != nil; l = tl l)
+ rl = hd l :: rl;
+ return rl;
+}
+
+bigof(s: string, base: int): big
+{
+ (b, r) := str->tobig(s, base);
+ if(r != nil)
+ raise "invalid integer field";
+ return b;
+}
+
+intof(s: string, base: int): int
+{
+ return int bigof(s, base);
+}
+
+mkpath(root: string, name: string): string
+{
+ if(len root > 0 && root[len root-1] != '/' && (len name == 0 || name[0] != '/'))
+ return root+"/"+name;
+ return root+name;
+}
+
+contents(e: ref Entry): string
+{
+ if(e.contents == nil)
+ return "";
+ s := "";
+ for(cl := e.contents; cl != nil; cl = tl cl)
+ s += " " + hd cl;
+ return s[1:];
+}
+
+Entry.text(e: self ref Entry): string
+{
+ a := e.action;
+ if(a == 0)
+ a = '?';
+ return sys->sprint("%bd %bd %q [%d] %c m=%uo l=%bd t=%ud c=%q", e.seq>>32, e.seq & 16rFFFFFFFF, e.path, e.x, a, e.d.mode, e.d.length, e.d.mtime, contents(e));
+}
+
+Entry.sumtext(e: self ref Entry): string
+{
+ case e.action {
+ 'a' or 'm' =>
+ return sys->sprint("%c %q %uo %q %q %ud", e.action, e.path, e.d.mode, e.d.uid, e.d.gid, e.d.mtime);
+ 'd' or 'c' =>
+ return sys->sprint("%c %q", e.action, e.path);
+ * =>
+ return sys->sprint("? %q", e.path);
+ }
+}
+
+Entry.dbtext(e: self ref Entry): string
+{
+ # path dpath|"-" mode uid gid mtime length
+ return sys->sprint("%bd %bd %q - %uo %q %q %ud %bd%s", e.seq>>32, e.seq & 16rFFFFFFFF, e.path, e.d.mode, e.d.uid, e.d.gid, e.d.mtime, e.d.length, contents(e));
+}
+
+Entry.logtext(e: self ref Entry): string
+{
+ # gen n act path spath|"-" dpath|"-" mode uid gid mtime length
+ a := e.action;
+ if(a == 0)
+ a = '?';
+ sf := e.serverpath;
+ if(sf == nil || sf == e.path)
+ sf = "-";
+ return sys->sprint("%bd %bd %c %q %q %uo %q %q %ud %bd%s", e.seq>>32, e.seq & 16rFFFFFFFF, a, e.path, sf, e.d.mode, e.d.uid, e.d.gid, e.d.mtime, e.d.length, contents(e));
+}
+
+Entry.remove(e: self ref Entry)
+{
+ e.action = 'd';
+}
+
+Entry.removed(e: self ref Entry): int
+{
+ return e.action == 'd';
+}
+
+Entry.update(e: self ref Entry, n: ref Entry)
+{
+ if(n == nil)
+ return;
+ if(n.action == 'd')
+ e.contents = nil;
+ else
+ e.d = n.d;
+ if(n.action != 'm' || e.action == 'd')
+ e.action = n.action;
+ e.serverpath = S(n.serverpath);
+ for(nl := rev(n.contents); nl != nil; nl = tl nl)
+ e.contents = hd nl :: e.contents;
+ if(n.seq > e.seq)
+ e.seq = n.seq;
+}
+
+Db.new(name: string): ref Db
+{
+ db := ref Db;
+ db.name = name;
+ db.stateht = array[Hashsize] of list of ref Entry;
+ db.nstate = 0;
+ db.state = array[50] of ref Entry;
+ return db;
+}
+
+Db.look(db: self ref Db, name: string): ref Entry
+{
+ (b, nil) := hash(name, len db.stateht);
+ for(l := db.stateht[b]; l != nil; l = tl l)
+ if((hd l).path == name)
+ return hd l;
+ return nil;
+}
+
+Db.entry(db: self ref Db, seq: big, name: string, d: Sys->Dir): ref Entry
+{
+ e := ref Entry;
+ e.action = 'a';
+ e.seq = seq;
+ e.path = name;
+ e.d = d;
+ e.x = db.nstate++;
+ if(e.x >= len db.state){
+ a := array[len db.state + Incr] of ref Entry;
+ a[0:] = db.state;
+ db.state = a;
+ }
+ db.state[e.x] = e;
+ (b, nil) := hash(name, len db.stateht);
+ db.stateht[b] = e :: db.stateht[b];
+ return e;
+}
+
+Db.sort(db: self ref Db, key: int)
+{
+ sortentries(db.state[0:db.nstate], key);
+}
+
+sortentries(a: array of ref Entry, key: int): (array of ref Entry, int)
+{
+ mergesort(a, array[len a] of ref Entry, key);
+ return (a, len a);
+}
+
+mergesort(a, b: array of ref Entry, key: int)
+{
+ r := len a;
+ if(r > 1) {
+ m := (r-1)/2 + 1;
+ mergesort(a[0:m], b[0:m], key);
+ mergesort(a[m:], b[m:], key);
+ b[0:] = a;
+ for((i, j, k) := (0, m, 0); i < m && j < r; k++) {
+ if(key==Byname && b[i].path > b[j].path || key==Byseq && b[i].seq > b[j].seq)
+ a[k] = b[j++];
+ else
+ a[k] = b[i++];
+ }
+ if(i < m)
+ a[k:] = b[i:m];
+ else if(j < r)
+ a[k:] = b[j:r];
+ }
+}
+
+strings: array of list of string;
+
+S(s: string): string
+{
+ if(strings == nil)
+ strings = array[257] of list of string;
+ h := hash(s, len strings).t0;
+ for(sl := strings[h]; sl != nil; sl = tl sl)
+ if(hd sl == s)
+ return hd sl;
+ strings[h] = s :: strings[h];
+ return s;
+}
+
+hash(s: string, n: int): (int, int)
+{
+ # hashpjw
+ h := 0;
+ for(i:=0; i<len s; i++){
+ h = (h<<4) + s[i];
+ if((g := h & int 16rF0000000) != 0)
+ h ^= ((g>>24) & 16rFF) | g;
+ }
+ return ((h&~(1<<31))%n, h);
+}
diff --git a/appl/cmd/install/logs.m b/appl/cmd/install/logs.m
new file mode 100644
index 00000000..bed3c68d
--- /dev/null
+++ b/appl/cmd/install/logs.m
@@ -0,0 +1,44 @@
+Logs: module
+{
+ PATH: con "/dis/install/logs.dis";
+
+ Entry: adt
+ {
+ seq: big; # time<<32 | gen
+ action: int;
+ path: string;
+ serverpath: string;
+ x: int;
+ d: Sys->Dir;
+ contents: list of string; # MD5 hash of content, most recent first
+
+ read: fn(in: ref Bufio->Iobuf): (ref Entry, string);
+ remove: fn(e: self ref Entry);
+ removed: fn(e: self ref Entry): int;
+ update: fn(e: self ref Entry, n: ref Entry);
+ text: fn(e: self ref Entry): string;
+ dbtext: fn(e: self ref Entry): string;
+ sumtext: fn(e: self ref Entry): string;
+ logtext: fn(e: self ref Entry): string;
+ };
+
+ Db: adt
+ {
+ name: string;
+ state: array of ref Entry;
+ nstate: int;
+ stateht: array of list of ref Entry;
+
+ new: fn(name: string): ref Db;
+ entry: fn(db: self ref Db, seq: big, name: string, d: Sys->Dir): ref Entry;
+ look: fn(db: self ref Db, name: string): ref Entry;
+ sort: fn(db: self ref Db, byname: int);
+ };
+
+ Byseq, Byname: con iota;
+
+ init: fn(bio: Bufio): string;
+
+ S: fn(s: string): string;
+ mkpath: fn(root: string, name: string): string;
+};
diff --git a/appl/cmd/install/mergelog.b b/appl/cmd/install/mergelog.b
new file mode 100644
index 00000000..8998d8a9
--- /dev/null
+++ b/appl/cmd/install/mergelog.b
@@ -0,0 +1,239 @@
+implement Mergelog;
+
+#
+# combine old and new log sections into one with the most recent data
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "string.m";
+ str: String;
+
+include "keyring.m";
+ kr: Keyring;
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "logs.m";
+ logs: Logs;
+ Db, Entry, Byname, Byseq: import logs;
+ S: import logs;
+
+include "arg.m";
+
+Mergelog: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+Apply, Applydb, Install, Asis, Skip: con iota;
+
+client: ref Db; # client current state from client log
+updates: ref Db; # state delta from new section of server log
+
+nerror := 0;
+nconflict := 0;
+debug := 0;
+verbose := 0;
+resolve := 0;
+setuid := 0;
+setgid := 0;
+nflag := 0;
+timefile: string;
+clientroot: string;
+srvroot: string;
+logfd: ref Sys->FD;
+now := 0;
+gen := 0;
+noerr := 0;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+
+ bufio = load Bufio Bufio->PATH;
+ ensure(bufio, Bufio->PATH);
+ str = load String String->PATH;
+ ensure(str, String->PATH);
+ kr = load Keyring Keyring->PATH;
+ ensure(kr, Keyring->PATH);
+ daytime = load Daytime Daytime->PATH;
+ ensure(daytime, Daytime->PATH);
+ logs = load Logs Logs->PATH;
+ ensure(logs, Logs->PATH);
+ logs->init(bufio);
+
+ arg := load Arg Arg->PATH;
+ ensure(arg, Arg->PATH);
+ arg->init(args);
+ arg->setusage("mergelog [-vd] oldlog [path ... ] <newlog");
+ dump := 0;
+ while((o := arg->opt()) != 0)
+ case o {
+ 'd' => dump = 1; debug = 1;
+ 'v' => verbose = 1;
+ * => arg->usage();
+ }
+ args = arg->argv();
+ if(len args < 3)
+ arg->usage();
+ arg = nil;
+
+ now = daytime->now();
+ client = Db.new("existing log");
+ updates = Db.new("update log");
+ clientlog := hd args; args = tl args;
+ if(args != nil)
+ error("restriction by path not yet done");
+
+ # replay the client log to build last installation state of files taken from server
+ logfd = sys->open(clientlog, Sys->OREAD);
+ if(logfd == nil)
+ error(sys->sprint("can't open %s: %r", clientlog));
+ f := bufio->fopen(logfd, Sys->OREAD);
+ if(f == nil)
+ error(sys->sprint("can't open %s: %r", clientlog));
+ while((log := readlog(f)) != nil)
+ replaylog(client, log);
+ f = nil;
+
+ # read new log entries and use the new section to build a sequence of update actions
+ f = bufio->fopen(sys->fildes(0), Sys->OREAD);
+ while((log = readlog(f)) != nil)
+ replaylog(client, log);
+ client.sort(Byseq);
+ dumpdb(client);
+ if(nerror)
+ raise sys->sprint("fail:%d errors", nerror);
+}
+
+readlog(in: ref Iobuf): ref Entry
+{
+ (e, err) := Entry.read(in);
+ if(err != nil)
+ error(err);
+ return e;
+}
+
+#
+# replay a log to reach the state wrt files previously taken from the server
+#
+replaylog(db: ref Db, log: ref Entry)
+{
+ e := db.look(log.path);
+ indb := e != nil && !e.removed();
+ case log.action {
+ 'a' => # add new file
+ if(indb){
+ note(sys->sprint("%q duplicate create", log.path));
+ return;
+ }
+ 'c' => # contents
+ if(!indb){
+ note(sys->sprint("%q contents but no entry", log.path));
+ return;
+ }
+ 'd' => # delete
+ if(!indb){
+ note(sys->sprint("%q deleted but no entry", log.path));
+ return;
+ }
+ if(e.d.mtime > log.d.mtime){
+ note(sys->sprint("%q deleted but it's newer", log.path));
+ return;
+ }
+ 'm' => # metadata
+ if(!indb){
+ note(sys->sprint("%q metadata but no entry", log.path));
+ return;
+ }
+ * =>
+ error(sys->sprint("bad log entry: %bd %bd", log.seq>>32, log.seq & big 16rFFFFFFFF));
+ }
+ update(db, e, log);
+}
+
+#
+# update file state e to reflect the effect of the log,
+# creating a new entry if necessary
+#
+update(db: ref Db, e: ref Entry, log: ref Entry)
+{
+ if(e == nil)
+ e = db.entry(log.seq, log.path, log.d);
+ e.update(log);
+}
+
+rev[T](l: list of T): list of T
+{
+ rl: list of T;
+ for(; l != nil; l = tl l)
+ rl = hd l :: rl;
+ return rl;
+}
+
+ensure[T](m: T, path: string)
+{
+ if(m == nil)
+ error(sys->sprint("can't load %s: %r", path));
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "applylog: %s\n", s);
+ raise "fail:error";
+}
+
+note(s: string)
+{
+ sys->fprint(sys->fildes(2), "applylog: note: %s\n", s);
+}
+
+warn(s: string)
+{
+ sys->fprint(sys->fildes(2), "applylog: warning: %s\n", s);
+ nerror++;
+}
+
+samestat(a: Sys->Dir, b: Sys->Dir): int
+{
+ # doesn't check permission/ownership, does check QTDIR/QTFILE
+ if(a.mode & Sys->DMDIR)
+ return (b.mode & Sys->DMDIR) != 0;
+ return a.length == b.length && a.mtime == b.mtime && a.qid.qtype == b.qid.qtype; # TO DO: a.name==b.name?
+}
+
+samemeta(a: Sys->Dir, b: Sys->Dir): int
+{
+ return a.mode == b.mode && (!setuid || a.uid == b.uid) && (!setgid || a.gid == b.gid) && samestat(a, b);
+}
+
+bigof(s: string, base: int): big
+{
+ (b, r) := str->tobig(s, base);
+ if(r != nil)
+ error("cruft in integer field in log entry: "+s);
+ return b;
+}
+
+intof(s: string, base: int): int
+{
+ return int bigof(s, base);
+}
+
+dumpdb(db: ref Db)
+{
+ for(i := 0; i < db.nstate; i++){
+ s := db.state[i].text();
+ if(s != nil)
+ sys->print("%s\n", s);
+ }
+}
diff --git a/appl/cmd/install/mkfile b/appl/cmd/install/mkfile
new file mode 100644
index 00000000..5da4b55c
--- /dev/null
+++ b/appl/cmd/install/mkfile
@@ -0,0 +1,43 @@
+<../../../mkconfig
+
+TARG=\
+ create.dis\
+ info.dis\
+ wdiff.dis\
+ inst.dis\
+ wrap.dis\
+ archfs.dis\
+ install.dis\
+ arch.dis\
+ proto.dis\
+ ckproto.dis\
+ proto2list.dis\
+ wrap2list.dis\
+ wfind.dis\
+ mkproto.dis\
+ applylog.dis\
+ logs.dis\
+ log.dis\
+ mergelog.dis\
+ updatelog.dis\
+ eproto.dis\
+
+MODULES=\
+ wrap.m\
+ arch.m\
+ archfs.m\
+ logs.m\
+ proto.m\
+ protocaller.m\
+
+SYSMODULES=\
+ arg.m\
+ bufio.m\
+ sys.m\
+ draw.m\
+ bufio.m\
+ string.m\
+
+DISBIN=$ROOT/dis/install
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/cmd/install/mkproto.b b/appl/cmd/install/mkproto.b
new file mode 100644
index 00000000..cee3fd21
--- /dev/null
+++ b/appl/cmd/install/mkproto.b
@@ -0,0 +1,99 @@
+#
+# Copyright © 2000 Vita Nuova (Holdings) Limited. All rights reserved.
+#
+
+implement Mkproto;
+
+# make a proto description of the directory or file
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "readdir.m";
+ readdir: Readdir;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+Mkproto: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "Usage: mkproto [ file|directory ... ]\n");
+ raise "fail:usage";
+}
+
+not: list of string;
+bout: ref Iobuf;
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ readdir = load Readdir Readdir->PATH;
+ bufio = load Bufio Bufio->PATH;
+
+ bout = bufio->fopen(sys->fildes(1), Bufio->OWRITE);
+ argv = tl argv;
+ while (argv != nil && hd argv != nil && (hd argv)[0] == '-') {
+ not = (hd argv)[1:] :: not;
+ argv = tl argv;
+ }
+ if (argv == nil)
+ visit(".", nil, -1);
+ else if (tl argv == nil)
+ visit(hd argv, nil, -1);
+ else {
+ for ( ; argv != nil; argv = tl argv)
+ visit(hd argv, hd argv, 0);
+ }
+ bout.flush();
+}
+
+warn(s: string)
+{
+ sys->fprint(sys->fildes(2), "mkproto: %s\n", s);
+}
+
+visit(fulln: string, reln: string, depth: int)
+{
+ if (depth == 0) {
+ for (n := not; n != nil; n = tl n) {
+ if (hd n == reln) {
+ # sys->fprint(stderr, "skipping %s\n", reln);
+ return;
+ }
+ }
+ # sys->fprint(stderr, "doing %s\n", reln);
+ }
+ (ok, d) := sys->stat(fulln);
+ if(ok < 0){
+ warn(sys->sprint("cannot stat %s: %r", fulln));
+ return;
+ }
+ if (depth >= 0)
+ visitf(fulln, reln, d, depth);
+ if (d.mode & Sys->DMDIR)
+ visitd(fulln, reln, d, depth);
+}
+
+visitd(fulln: string, nil: string, nil: Sys->Dir, depth: int)
+{
+ (dir, n) := readdir->init(fulln, Readdir->NAME|Readdir->COMPACT);
+ for (i := 0; i < n; i++) {
+ path := "/"+dir[i].name;
+ visit(fulln+path, dir[i].name, depth+1);
+ }
+}
+
+visitf(nil: string, reln: string, nil: Sys->Dir, depth: int)
+{
+ for (i := 0; i < depth; i++)
+ bout.putc('\t');
+ bout.puts(sys->sprint("%q\n", reln));
+}
diff --git a/appl/cmd/install/proto.b b/appl/cmd/install/proto.b
new file mode 100644
index 00000000..c25ee220
--- /dev/null
+++ b/appl/cmd/install/proto.b
@@ -0,0 +1,320 @@
+implement Proto;
+
+include "sys.m";
+ sys: Sys;
+ Dir : import Sys;
+include "draw.m";
+include "bufio.m";
+ bufio : Bufio;
+ Iobuf : import bufio;
+include "string.m";
+ str: String;
+include "readdir.m";
+ readdir : Readdir;
+include "proto.m";
+include "protocaller.m";
+
+NAMELEN: con 8192;
+
+WARN, ERROR, FATAL : import Protocaller;
+
+File: adt {
+ new: string;
+ elem: string;
+ old: string;
+ uid: string;
+ gid: string;
+ mode: int;
+};
+
+indent: int;
+lineno := 0;
+newfile: string;
+oldfile: string;
+oldroot : string;
+b: ref Iobuf;
+cmod : Protocaller;
+
+rdproto(proto : string, root : string, pcmod : Protocaller) : int
+{
+ if (sys == nil) {
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ str = load String String->PATH;
+ readdir = load Readdir Readdir->PATH;
+ }
+ cmod = pcmod;
+ oldroot = root;
+ b = bufio->open(proto, Sys->OREAD);
+ if(b == nil){
+ cmod->protoerr(FATAL, lineno, sys->sprint("can't open %s: %r: skipping\n", proto));
+ b.close();
+ return -1;
+ }
+ lineno = 0;
+ indent = 0;
+ file := ref File;
+ file.mode = 0;
+ mkfs(file, -1);
+ b.close();
+ return 0;
+}
+
+mkfs(me: ref File, level: int)
+{
+ (child, fp) := getfile(me);
+ if(child == nil)
+ return;
+ if(child.elem == "+" || child.elem == "*" || child.elem == "%"){
+ rec := child.elem[0] == '+';
+ filesonly := child.elem[0] == '%';
+ child.new = me.new;
+ setnames(child);
+ mktree(child, rec, filesonly);
+ (child, fp) = getfile(me);
+ }
+ while(child != nil && indent > level){
+ if(mkfile(child))
+ mkfs(child, indent);
+ (child, fp) = getfile(me);
+ }
+ if(child != nil){
+ b.seek(big fp, 0);
+ lineno--;
+ }
+}
+
+mktree(me: ref File, rec: int, filesonly: int)
+{
+ fd := sys->open(oldfile, Sys->OREAD);
+ if(fd == nil){
+ cmod->protoerr(WARN, lineno, sys->sprint("can't open %s: %r", oldfile));
+ return;
+ }
+ child := ref *me;
+ (d, n) := readdir->init(oldfile, Readdir->NAME|Readdir->COMPACT);
+ for (i := 0; i < n; i++) {
+ if (filesonly && (d[i].mode & Sys->DMDIR))
+ continue;
+ child.new = mkpath(me.new, d[i].name);
+ if(me.old != nil)
+ child.old = mkpath(me.old, d[i].name);
+ child.elem = d[i].name;
+ setnames(child);
+ if(copyfile(child, d[i]) && rec)
+ mktree(child, rec, filesonly);
+ }
+}
+
+mkfile(f: ref File): int
+{
+ (i, dir) := sys->stat(oldfile);
+ if(i < 0){
+ cmod->protoerr(WARN, lineno, sys->sprint("can't stat file %s: %r", oldfile));
+ skipdir();
+ return 0;
+ }
+ return copyfile(f, ref dir);
+}
+
+copyfile(f: ref File, d: ref Dir): int
+{
+ d.name = f.elem;
+ if(f.mode != ~0){
+ if((d.mode&Sys->DMDIR) != (f.mode&Sys->DMDIR))
+ cmod->protoerr(WARN, lineno, sys->sprint("inconsistent mode for %s", f.new));
+ else
+ d.mode = f.mode;
+ }
+ cmod->protofile(newfile, oldfile, d);
+ return (d.mode & Sys->DMDIR) != 0;
+}
+
+setnames(f: ref File)
+{
+ newfile = f.new;
+ if(f.old != nil){
+ if(f.old[0] == '/')
+ oldfile = mkpath(oldroot, f.old);
+ else
+ oldfile = f.old;
+ }else
+ oldfile = mkpath(oldroot, f.new);
+}
+
+#
+# skip all files in the proto that
+# could be in the current dir
+#
+skipdir()
+{
+ if(indent < 0)
+ return;
+ level := indent;
+ for(;;){
+ indent = 0;
+ fp := b.offset();
+ p := b.gets('\n');
+ if (p != nil && p[len p - 1] != '\n')
+ p += "\n";
+ lineno++;
+ if(p == nil){
+ indent = -1;
+ return;
+ }
+ for(j := 0; (c := p[j++]) != '\n';)
+ if(c == ' ')
+ indent++;
+ else if(c == '\t')
+ indent += 8;
+ else
+ break;
+ if(indent <= level){
+ b.seek(fp, 0);
+ lineno--;
+ return;
+ }
+ }
+}
+
+getfile(old: ref File): (ref File, int)
+{
+ f: ref File;
+ p, elem: string;
+ c: int;
+
+ if(indent < 0)
+ return (nil, 0);
+ fp := int b.offset();
+ do {
+ indent = 0;
+ p = b.gets('\n');
+ if (p != nil && p[len p - 1] != '\n')
+ p += "\n";
+ lineno++;
+ if(p == nil){
+ indent = -1;
+ return (nil, 0);
+ }
+ for(; (c = p[0]) != '\n'; p = p[1:])
+ if(c == ' ')
+ indent++;
+ else if(c == '\t')
+ indent += 8;
+ else
+ break;
+ } while(c == '\n' || c == '#');
+ f = ref File;
+ (elem, p) = getname(p, NAMELEN);
+ f.new = mkpath(old.new, elem);
+ (nil, f.elem) = str->splitr(f.new, "/");
+ if(f.elem == nil)
+ cmod->protoerr(ERROR, lineno, sys->sprint("can't find file name component of %s", f.new));
+ (f.mode, p) = getmode(p);
+ (f.uid, p) = getname(p, NAMELEN);
+ if(f.uid == nil)
+ f.uid = "-";
+ (f.gid, p) = getname(p, NAMELEN);
+ if(f.gid == nil)
+ f.gid = "-";
+ f.old = getpath(p);
+ if(f.old == "-")
+ f.old = nil;
+ if(f.old == nil && old.old != nil)
+ f.old = mkpath(old.old, elem);
+ setnames(f);
+ return (f, fp);
+}
+
+getpath(p: string): string
+{
+ for(; (c := p[0]) == ' ' || c == '\t'; p = p[1:])
+ ;
+ for(n := 0; (c = p[n]) != '\n' && c != ' ' && c != '\t'; n++)
+ ;
+ return p[0:n];
+}
+
+getname(p: string, lim: int): (string, string)
+{
+ for(; (c := p[0]) == ' ' || c == '\t'; p = p[1:])
+ ;
+ i := 0;
+ s := "";
+ for(; (c = p[0]) != '\n' && c != ' ' && c != '\t'; p = p[1:])
+ s[i++] = c;
+ if(len s >= lim){
+ cmod->protoerr(WARN, lineno, sys->sprint("name %s too long; truncated", s));
+ s = s[0:lim-1];
+ }
+ if(len s > 0 && s[0] == '$'){
+ s = getenv(s[1:]);
+ if(s == nil)
+ cmod->protoerr(ERROR, lineno, sys->sprint("can't read environment variable %s", s));
+ if(len s >= NAMELEN)
+ s = s[0:NAMELEN-1];
+ }
+ return (s, p);
+}
+
+getenv(s: string): string
+{
+ if(s == "user")
+ return getuser();
+ return nil;
+}
+
+getuser(): string
+{
+ fd := sys->open("/dev/user", Sys->OREAD);
+ if(fd != nil){
+ u := array [100] of byte;
+ n := sys->read(fd, u, len u);
+ if(n > 0)
+ return string u[0:n];
+ }
+ return nil;
+}
+
+getmode(p: string): (int, string)
+{
+ s: string;
+
+ (s, p) = getname(p, 7);
+ if(s == nil || s == "-")
+ return (~0, p);
+ m := 0;
+ if(s[0] == 'd'){
+ m |= Sys->DMDIR;
+ s = s[1:];
+ }
+ if(s[0] == 'a'){
+ #m |= CHAPPEND;
+ s = s[1:];
+ }
+ if(s[0] == 'l'){
+ #m |= CHEXCL;
+ s = s[1:];
+ }
+ for(i:=0; i<len s || i < 3; i++)
+ if(i >= len s || !(s[i]>='0' && s[i]<='7')){
+ cmod->protoerr(WARN, lineno, sys->sprint("bad mode specification %s", s));
+ return (~0, p);
+ }
+ (v, nil) := str->toint(s, 8);
+ return (m|v, p);
+}
+
+mkpath(prefix, elem: string): string
+{
+ slash1 := slash2 := 0;
+ if (len prefix > 0)
+ slash1 = prefix[len prefix - 1] == '/';
+ if (len elem > 0)
+ slash2 = elem[0] == '/';
+ if (slash1 && slash2)
+ return prefix+elem[1:];
+ if (!slash1 && !slash2)
+ return prefix+"/"+elem;
+ return prefix+elem;
+}
diff --git a/appl/cmd/install/proto.m b/appl/cmd/install/proto.m
new file mode 100644
index 00000000..07d3507f
--- /dev/null
+++ b/appl/cmd/install/proto.m
@@ -0,0 +1,6 @@
+Proto : module
+{
+ PATH : con "/dis/install/proto.dis";
+
+ rdproto: fn(proto : string, root : string, pcmod : Protocaller) : int;
+}; \ No newline at end of file
diff --git a/appl/cmd/install/proto2list.b b/appl/cmd/install/proto2list.b
new file mode 100644
index 00000000..b5997c15
--- /dev/null
+++ b/appl/cmd/install/proto2list.b
@@ -0,0 +1,209 @@
+#
+# Copyright © 2001 Vita Nuova (Holdings) Limited. All rights reserved.
+#
+
+implement Proto2list;
+
+# make a version list suitable for SDS from a series of proto files
+
+include "sys.m";
+ sys : Sys;
+include "draw.m";
+include "bufio.m";
+ bufio : Bufio;
+ Iobuf : import bufio;
+include "crc.m";
+ crcm : Crc;
+include "proto.m";
+ proto : Proto;
+include "protocaller.m";
+ protocaller : Protocaller;
+
+WARN, ERROR, FATAL : import Protocaller;
+
+Proto2list: module
+{
+ init : fn(ctxt: ref Draw->Context, argv: list of string);
+ protofile: fn(new : string, old : string, d : ref Sys->Dir);
+ protoerr: fn(lev : int, line : int, err : string);
+};
+
+stderr: ref Sys->FD;
+protof: string;
+
+Element: type (string, string);
+
+List: adt{
+ as: array of Element;
+ n: int;
+ init: fn(l: self ref List);
+ add: fn(l: self ref List, e: Element);
+ end: fn(l: self ref List): array of Element;
+};
+
+flist: ref List;
+
+List.init(l: self ref List)
+{
+ l.as = array[1024] of Element;
+ l.n = 0;
+}
+
+List.add(l: self ref List, e: Element)
+{
+ if(l.n == len l.as)
+ l.as = (array[2*l.n] of Element)[0:] = l.as;
+ l.as[l.n++] = e;
+}
+
+List.end(l: self ref List): array of Element
+{
+ return l.as[0: l.n];
+}
+
+usage()
+{
+ sys->fprint(stderr, "Usage: proto2list protofile ...\n");
+ exit;
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ crcm = load Crc Crc->PATH;
+ proto = load Proto Proto->PATH;
+ protocaller = load Protocaller "$self";
+ stderr = sys->fildes(2);
+ root := "/";
+ flist = ref List;
+ flist.init();
+ for(argv = tl argv; argv != nil; argv = tl argv){
+ protof = hd argv;
+ proto->rdproto(hd argv, root, protocaller);
+ }
+ fs := flist.end();
+ sort(fs);
+ fs = uniq(fs);
+ out(fs);
+}
+
+protofile(new : string, old : string, nil : ref Sys->Dir)
+{
+ if(new == old)
+ new = "-";
+ flist.add((old, new));
+}
+
+out(fs: array of Element)
+{
+ nf := len fs;
+ for(i := 0; i < nf; i++){
+ (f, g) := fs[i];
+ (ok, d) := sys->stat(f);
+ if (ok < 0) {
+ sys->fprint(stderr, "cannot open %s\n", f);
+ continue;
+ }
+ if (d.mode & Sys->DMDIR)
+ d.length = big 0;
+ sys->print("%s %s %d %d %d %d %d\n", f, g, int d.length, d.mode, d.mtime, crc(f, d), 0);
+ }
+}
+
+protoerr(lev : int, line : int, err : string)
+{
+ s := "line " + string line + " : " + err;
+ case lev {
+ WARN => warn(s);
+ ERROR => error(s);
+ FATAL => fatal(s);
+ }
+}
+
+crc(f : string, d: Sys->Dir) : int
+{
+ crcs := crcm->init(0, int 16rffffffff);
+ if (d.mode & Sys->DMDIR)
+ return 0;
+ fd := sys->open(f, Sys->OREAD);
+ if (fd == nil) {
+ sys->fprint(stderr, "cannot open %s\n", f);
+ return 0;
+ }
+ crc := 0;
+ buf := array[Sys->ATOMICIO] of byte;
+ for (;;) {
+ nr := sys->read(fd, buf, len buf);
+ if (nr < 0) {
+ sys->fprint(stderr, "bad read on %s : %r\n", f);
+ return 0;
+ }
+ if (nr <= 0)
+ break;
+ crc = crcm->crc(crcs, buf, nr);
+ }
+ crcm->reset(crcs);
+ return crc;
+}
+
+sort(a: array of Element)
+{
+ mergesort(a, array[len a] of Element);
+}
+
+mergesort(a, b: array of Element)
+{
+ r := len a;
+ if (r > 1) {
+ m := (r-1)/2 + 1;
+ mergesort(a[0:m], b[0:m]);
+ mergesort(a[m:], b[m:]);
+ b[0:] = a;
+ for ((i, j, k) := (0, m, 0); i < m && j < r; k++) {
+ if (b[i].t0 > b[j].t0)
+ a[k] = b[j++];
+ else
+ a[k] = b[i++];
+ }
+ if (i < m)
+ a[k:] = b[i:m];
+ else if (j < r)
+ a[k:] = b[j:r];
+ }
+}
+
+
+uniq(a: array of Element): array of Element
+{
+ m := n := len a;
+ for(i := 0; i < n-1; ){
+ if(a[i].t0 == a[i+1].t0){
+ if(a[i].t1 != a[i+1].t1)
+ warn(sys->sprint("duplicate %s(%s %s)", a[i].t0, a[i].t1, a[i+1].t1));
+ a[i+1:] = a[i+2: n--];
+ }
+ else
+ i++;
+ }
+ if(n == m)
+ return a;
+ return a[0: n];
+}
+
+error(s: string)
+{
+ sys->fprint(stderr, "%s: %s\n", protof, s);
+ exit;
+}
+
+fatal(s: string)
+{
+ sys->fprint(stderr, "fatal: %s\n", s);
+ exit;
+}
+
+warn(s: string)
+{
+ sys->fprint(stderr, "%s: %s\n", protof, s);
+}
diff --git a/appl/cmd/install/protocaller.m b/appl/cmd/install/protocaller.m
new file mode 100644
index 00000000..1e269d1f
--- /dev/null
+++ b/appl/cmd/install/protocaller.m
@@ -0,0 +1,8 @@
+Protocaller : module{
+ init: fn(ctxt : ref Draw->Context, args : list of string);
+ protofile: fn(new : string, old : string, d : ref Sys->Dir);
+
+ WARN, ERROR, FATAL : con iota;
+
+ protoerr: fn(lev : int, line : int, err : string);
+}; \ No newline at end of file
diff --git a/appl/cmd/install/updatelog.b b/appl/cmd/install/updatelog.b
new file mode 100644
index 00000000..d9c6959e
--- /dev/null
+++ b/appl/cmd/install/updatelog.b
@@ -0,0 +1,386 @@
+implement Updatelog;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "string.m";
+ str: String;
+
+include "keyring.m";
+ kr: Keyring;
+
+include "logs.m";
+ logs: Logs;
+ Db, Entry, Byname, Byseq: import logs;
+ S, mkpath: import logs;
+ Log: type Entry;
+
+include "fsproto.m";
+ fsproto: FSproto;
+ Direntry: import fsproto;
+
+include "arg.m";
+
+Updatelog: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+now: int;
+gen := 0;
+changesonly := 0;
+uid: string;
+gid: string;
+debug := 0;
+state: ref Db;
+rootdir := ".";
+scanonly: list of string;
+exclude: list of string;
+sums := 0;
+stderr: ref Sys->FD;
+Seen: con 1<<31;
+bout: ref Iobuf;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ ensure(bufio, Bufio->PATH);
+ fsproto = load FSproto FSproto->PATH;
+ ensure(fsproto, FSproto->PATH);
+ daytime = load Daytime Daytime->PATH;
+ ensure(daytime, Daytime->PATH);
+ str = load String String->PATH;
+ ensure(str, String->PATH);
+ logs = load Logs Logs->PATH;
+ ensure(logs, Logs->PATH);
+ kr = load Keyring Keyring->PATH;
+ ensure(kr, Keyring->PATH);
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ error(sys->sprint("can't load %s: %r", Arg->PATH));
+
+ protofile := "/lib/proto/all";
+ arg->init(args);
+ arg->setusage("updatelog [-p proto] [-r root] [-t now gen] [-c] [-x path] x.log [path ...]");
+ while((o := arg->opt()) != 0)
+ case o {
+ 'D' =>
+ debug = 1;
+ 'p' =>
+ protofile = arg->earg();
+ 'r' =>
+ rootdir = arg->earg();
+ 'c' =>
+ changesonly = 1;
+ 'u' =>
+ uid = arg->earg();
+ 'g' =>
+ gid = arg->earg();
+ 's' =>
+ sums = 1;
+ 't' =>
+ now = int arg->earg();
+ gen = int arg->earg();
+ 'x' =>
+ s := arg->earg();
+ exclude = trimpath(s) :: exclude;
+ * =>
+ arg->usage();
+ }
+ args = arg->argv();
+ if(args == nil)
+ arg->usage();
+ arg = nil;
+
+ stderr = sys->fildes(2);
+ bout = bufio->fopen(sys->fildes(1), Bufio->OWRITE);
+
+ fsproto->init();
+ logs->init(bufio);
+
+ logfile := hd args;
+ while((args = tl args) != nil)
+ scanonly = trimpath(hd args) :: scanonly;
+ checkroot(rootdir, "replica root");
+
+ state = Db.new("server state");
+
+ #
+ # replay log to rebuild server state
+ #
+ logfd := sys->open(logfile, Sys->OREAD);
+ if(logfd == nil)
+ error(sys->sprint("can't open %s: %r", logfile));
+ f := bufio->fopen(logfd, Sys->OREAD);
+ if(f == nil)
+ error(sys->sprint("can't open %s: %r", logfile));
+ while((log := readlog(f)) != nil)
+ replaylog(state, log);
+
+ #
+ # walk the set of names produced by the proto file, comparing against the server state
+ #
+ now = daytime->now();
+ doproto(rootdir, protofile);
+
+ if(changesonly){
+ bout.flush();
+ exit;
+ }
+
+ #
+ # names in the original state that we didn't see in the walk must have been removed:
+ # print 'd' log entries for them, in reverse lexicographic order (children before parents)
+ #
+ state.sort(Logs->Byname);
+ for(i := state.nstate; --i >= 0;){
+ e := state.state[i];
+ if((e.x & Seen) == 0 && considered(e.path)){
+ change('d', e, e.seq, e.d, e.path, e.serverpath, e.contents); # TO DO: content
+ if(debug)
+ sys->fprint(sys->fildes(2), "remove %q\n", e.path);
+ }
+ }
+ bout.flush();
+}
+
+ensure[T](m: T, path: string)
+{
+ if(m == nil)
+ error(sys->sprint("can't load %s: %r", path));
+}
+
+checkroot(dir: string, what: string)
+{
+ (ok, d) := sys->stat(dir);
+ if(ok < 0)
+ error(sys->sprint("can't stat %s %q: %r", what, dir));
+ if((d.mode & Sys->DMDIR) == 0)
+ error(sys->sprint("%s %q: not a directory", what, dir));
+}
+
+considered(s: string): int
+{
+ if(scanonly != nil && !islisted(s, scanonly))
+ return 0;
+ return exclude == nil || !islisted(s, exclude);
+}
+
+readlog(in: ref Iobuf): ref Log
+{
+ (e, err) := Entry.read(in);
+ if(err != nil)
+ error(err);
+ return e;
+}
+
+#
+# replay a log to reach the state wrt files previously taken from the server
+#
+replaylog(db: ref Db, log: ref Log)
+{
+ e := db.look(log.path);
+ indb := e != nil && !e.removed();
+ case log.action {
+ 'a' => # add new file
+ if(indb){
+ note(sys->sprint("%q duplicate create", log.path));
+ return;
+ }
+ 'c' => # contents
+ if(!indb){
+ note(sys->sprint("%q contents but no entry", log.path));
+ return;
+ }
+ 'd' => # delete
+ if(!indb){
+ note(sys->sprint("%q deleted but no entry", log.path));
+ return;
+ }
+ if(e.d.mtime > log.d.mtime){
+ note(sys->sprint("%q deleted but it's newer", log.path));
+ return;
+ }
+ 'm' => # metadata
+ if(!indb){
+ note(sys->sprint("%q metadata but no entry", log.path));
+ return;
+ }
+ * =>
+ error(sys->sprint("bad log entry: %bd %bd", log.seq>>32, log.seq & big 16rFFFFFFFF));
+ }
+ update(db, e, log);
+}
+
+#
+# update file state e to reflect the effect of the log,
+# creating a new entry if necessary
+#
+update(db: ref Db, e: ref Entry, log: ref Entry)
+{
+ if(e == nil)
+ e = db.entry(log.seq, log.path, log.d);
+ e.update(log);
+}
+
+doproto(tree: string, protofile: string)
+{
+ entries := chan of Direntry;
+ warnings := chan of (string, string);
+ err := fsproto->readprotofile(protofile, tree, entries, warnings);
+ if(err != nil)
+ error(sys->sprint("can't read %s: %s", protofile, err));
+ for(;;)alt{
+ (old, new, d) := <-entries =>
+ if(d == nil)
+ return;
+ if(debug)
+ sys->fprint(stderr, "old=%q new=%q length=%bd\n", old, new, d.length);
+ while(new != nil && new[0] == '/')
+ new = new[1:];
+ if(!considered(new))
+ continue;
+ if(sums && (d.mode & Sys->DMDIR) == 0)
+ digests := md5sum(old) :: nil;
+ if(uid != nil)
+ d.uid = uid;
+ if(gid != nil)
+ d.gid = gid;
+ old = relative(old, rootdir);
+ db := state.look(new);
+ if(db == nil){
+ if(!changesonly){
+ db = state.entry(nextseq(), new, *d);
+ change('a', db, db.seq, db.d, db.path, old, digests);
+ }
+ }else{
+ if(!samestat(db.d, *d))
+ change('c', db, nextseq(), *d, new, old, digests);
+ if(!samemeta(db.d, *d))
+ change('m', db, nextseq(), *d, new, old, nil); # need digest?
+ }
+ if(db != nil)
+ db.x |= Seen;
+ (old, msg) := <-warnings =>
+ #if(contains(msg, "entry not found") || contains(msg, "not exist"))
+ # break;
+ sys->fprint(sys->fildes(2), "updatelog: warning[old=%s]: %s\n", old, msg);
+ }
+}
+
+change(action: int, e: ref Entry, seq: big, d: Sys->Dir, path: string, serverpath: string, digests: list of string)
+{
+ log := ref Entry;
+ log.seq = seq;
+ log.action = action;
+ log.d = d;
+ log.path = path;
+ log.serverpath = serverpath;
+ log.contents = digests;
+ e.update(log);
+ bout.puts(log.logtext()+"\n");
+}
+
+samestat(a: Sys->Dir, b: Sys->Dir): int
+{
+ # doesn't check permission/ownership, does check QTDIR/QTFILE
+ if(a.mode & Sys->DMDIR)
+ return (b.mode & Sys->DMDIR) != 0;
+ return a.length == b.length && a.mtime == b.mtime && a.qid.qtype == b.qid.qtype; # TO DO: a.name==b.name?
+}
+
+samemeta(a: Sys->Dir, b: Sys->Dir): int
+{
+ return a.mode == b.mode && (uid == nil || a.uid == b.uid) && (gid == nil || a.gid == b.gid) && samestat(a, b);
+}
+
+nextseq(): big
+{
+ return (big now << 32) | big gen++;
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "updatelog: %s\n", s);
+ raise "fail:error";
+}
+
+note(s: string)
+{
+ sys->fprint(sys->fildes(2), "updatelog: note: %s\n", s);
+}
+
+contains(s: string, sub: string): int
+{
+ return str->splitstrl(s, sub).t1 != nil;
+}
+
+isprefix(a, b: string): int
+{
+ la := len a;
+ lb := len b;
+ if(la > lb)
+ return 0;
+ if(la == lb)
+ return a == b;
+ return a == b[0:la] && b[la] == '/';
+}
+
+trimpath(s: string): string
+{
+ while(len s > 1 && s[len s-1] == '/')
+ s = s[0:len s-1];
+ while(s != nil && s[0] == '/')
+ s = s[1:];
+ return s;
+}
+
+relative(name: string, root: string): string
+{
+ if(root == nil || name == nil)
+ return name;
+ if(isprefix(root, name)){
+ name = name[len root:];
+ while(name != nil && name[0] == '/')
+ name = name[1:];
+ }
+ return name;
+}
+
+islisted(s: string, l: list of string): int
+{
+ for(; l != nil; l = tl l)
+ if(isprefix(hd l, s))
+ return 1;
+ return 0;
+}
+
+md5sum(file: string): string
+{
+ fd := sys->open(file, Sys->OREAD);
+ if(fd == nil)
+ error(sys->sprint("can't open %s: %r", file));
+ ds: ref Keyring->DigestState;
+ buf := array[Sys->ATOMICIO] of byte;
+ while((n := sys->read(fd, buf, len buf)) > 0)
+ ds = kr->md5(buf, n, nil, ds);
+ if(n < 0)
+ error(sys->sprint("error reading %s: %r", file));
+ digest := array[Keyring->MD5dlen] of byte;
+ kr->md5(nil, 0, digest, ds);
+ s: string;
+ for(i := 0; i < len digest; i++)
+ s += sys->sprint("%.2ux", int digest[i]);
+ return s;
+}
diff --git a/appl/cmd/install/wdiff.b b/appl/cmd/install/wdiff.b
new file mode 100644
index 00000000..47088417
--- /dev/null
+++ b/appl/cmd/install/wdiff.b
@@ -0,0 +1,148 @@
+implement Wdiff;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "arg.m";
+ arg: Arg;
+include "wrap.m";
+ wrap : Wrap;
+include "sh.m";
+include "keyring.m";
+ keyring : Keyring;
+
+
+Wdiff: module{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+root := "/";
+bflag : int;
+listing : int;
+package: int;
+
+diff(w : ref Wrap->Wrapped, name : string, c : chan of int)
+{
+ sys->pctl(Sys->FORKFD, nil);
+ wrapped := w.root+"/"+name;
+ local := root+"/"+name;
+ (ok, dir) := sys->stat(local);
+ if (ok < 0) {
+ sys->print("cannot stat %s\n", local);
+ c <-= -1;
+ return;
+ }
+ (ok, dir) = sys->stat(wrapped);
+ if (ok < 0) {
+ sys->print("cannot stat %s\n", wrapped);
+ c <-= -1;
+ return;
+ }
+ cmd := "/dis/diff.dis";
+ m := load Command cmd;
+ if(m == nil) {
+ c <-= -1;
+ return;
+ }
+ if (bflag)
+ m->init(nil, cmd :: "-b" :: wrapped :: local :: nil);
+ else
+ m->init(nil, cmd :: wrapped :: local :: nil);
+ c <-= 0;
+}
+
+fatal(err : string)
+{
+ sys->fprint(sys->fildes(2), "%s\n", err);
+ exit;
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ arg = load Arg Arg->PATH;
+ keyring = load Keyring Keyring->PATH;
+ wrap = load Wrap Wrap->PATH;
+ wrap->init(bufio);
+
+ arg->init(args);
+ while ((c := arg->opt()) != 0) {
+ case c {
+ 'b' =>
+ bflag = 1;
+ 'l' =>
+ listing = 1;
+ 'p' =>
+ package = 1;
+ 'r' =>
+ root = arg->arg();
+ if (root == nil)
+ fatal("missing root name");
+ * =>
+ fatal(sys->sprint("bad argument -%c", c));
+ }
+ }
+ args = arg->argv();
+ if (args == nil || tl args != nil)
+ fatal("usage: install/wdiff [-blp] [-r root] package");
+ (ok, dir) := sys->stat(hd args);
+ if (ok < 0)
+ fatal(sys->sprint("no such file %s", hd args));
+ w := wrap->openwraphdr(hd args, root, nil, !listing);
+ if (w == nil)
+ fatal("no such package found");
+
+ if(package){
+ while(w.nu > 0 && w.u[w.nu-1].typ == wrap->UPD)
+ w.nu--;
+ }
+
+ digest := array[keyring->MD5dlen] of { * => byte 0 };
+ digest0 := array[keyring->MD5dlen] of { * => byte 0 };
+
+ # loop through each md5sum file of each package in increasing time order
+ for(i := 0; i < w.nu; i++){
+ b := bufio->open(w.u[i].dir+"/md5sum", Sys->OREAD);
+ if (b == nil)
+ fatal("md5sum file not found");
+ while ((p := b.gets('\n')) != nil) {
+ (n, lst) := sys->tokenize(p, " \t\n");
+ if (n != 2)
+ fatal("error in md5sum file");
+ p = hd lst;
+ q := root+"/"+p;
+ (ok, dir) = sys->stat(q);
+ if (ok >= 0 && (dir.mode & Sys->DMDIR))
+ continue;
+ t: int;
+ (ok, t) = wrap->getfileinfo(w, p, nil, digest0, nil);
+ if(ok < 0){
+ sys->print("cannot happen\n");
+ continue;
+ }
+ if(t != w.u[i].time) # covered by later update
+ continue;
+ if (wrap->md5file(q, digest) < 0) {
+ sys->print("%s removed\n", p);
+ continue;
+ }
+ str := wrap->md5conv(digest);
+ str0 := wrap->md5conv(digest0);
+ # if (str == hd tl lst)
+ if(str == str0)
+ continue;
+ if (listing)
+ sys->print("%s modified\n", p);
+ else {
+ endc := chan of int;
+ spawn diff(w, p, endc);
+ <- endc;
+ }
+ }
+ }
+ wrap->end();
+}
diff --git a/appl/cmd/install/wfind.b b/appl/cmd/install/wfind.b
new file mode 100644
index 00000000..579fd946
--- /dev/null
+++ b/appl/cmd/install/wfind.b
@@ -0,0 +1,204 @@
+implement Wfind;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "arg.m";
+ arg: Arg;
+include "wrap.m";
+ wrap : Wrap;
+include "sh.m";
+include "keyring.m";
+ keyring : Keyring;
+include "readdir.m";
+ readdir : Readdir;
+
+Wfind: module{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+fatal(err : string)
+{
+ sys->fprint(sys->fildes(2), "%s\n", err);
+ exit;
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ arg = load Arg Arg->PATH;
+ keyring = load Keyring Keyring->PATH;
+ readdir = load Readdir Readdir->PATH;
+ wrap = load Wrap Wrap->PATH;
+ wrap->init(bufio);
+
+ pkgs: list of string;
+ indir := "/install";
+ arg->init(args);
+ while ((c := arg->opt()) != 0) {
+ case c {
+ 'p' =>
+ pkg := arg->arg();
+ if (pkg == nil)
+ fatal("missing package name");
+ pkgs = pkg :: pkgs;
+ * =>
+ fatal(sys->sprint("bad argument -%c", c));
+ }
+ }
+ args = arg->argv();
+ if (args == nil)
+ fatal("usage: install/wfind [-p package ... ] file ...");
+ # (ok, dir) := sys->stat(indir);
+ # if (ok < 0)
+ # fatal(sys->sprint("cannot open install directory %s", indir));
+ if(pkgs != nil){
+ npkgs: list of string;
+ for(pkg := pkgs; pkg != nil; pkg = tl pkg)
+ npkgs = hd pkg :: npkgs;
+ pkgs = npkgs;
+ for(pkg = pkgs; pkg != nil; pkg = tl pkg)
+ scanpkg(hd pkg, indir+"/"+hd pkg, args);
+ }
+ else
+ scanpkgs(indir, args);
+ prfiles();
+}
+
+scanpkgs(d : string, files: list of string)
+{
+ (dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT);
+ for (i := 0; i < n; i++) {
+ if (dir[i].mode & Sys->DMDIR)
+ scanpkg(dir[i].name, d + "/" + dir[i].name, files);
+ }
+}
+
+scanpkg(pkg : string, d : string, files: list of string)
+{
+ # base package, updates and update packages have the name
+ # <timestamp> or <timestamp.gz>
+ (dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT);
+ for (i := 0; i < n; i++) {
+ f := dir[i].name;
+ l := len f;
+ if (l >= 4 && f[l-3:l] == ".gz")
+ f = f[0:l-3];
+ scanfile(f, pkg, d+"/"+dir[i].name, files);
+ }
+ w := wrap->openwrap(pkg, "/", 0);
+ if(w == nil)
+ return;
+ for(i = 0; i < w.nu; i++)
+ scanw(w, i, files, WRAP, pkg);
+}
+
+scanfile(f: string, pkg: string, d: string, files: list of string)
+{
+ f = nil;
+ # sys->print("%s %s %s\n", f, pkg, d);
+ w := wrap->openwraphdr(d, "/", nil, 0);
+ if(w == nil)
+ return;
+ if(w.nu != 1)
+ fatal("strange package: more than one piece");
+ # sys->print(" %s %d %s %d %d %d\n", w.name, w.tfull, w.u[0].desc, w.u[0].time, w.u[0].utime, w.u[0].typ);
+ scanw(w, 0, files, INSTALL, pkg);
+}
+
+scanw(w: ref Wrap->Wrapped, i: int, files: list of string, where: int, pkg: string)
+{
+ w.u[i].bmd5.seek(big 0, Bufio->SEEKSTART);
+ while ((p := w.u[i].bmd5.gets('\n')) != nil){
+ # sys->print("%s", p);
+ (n, l) := sys->tokenize(p, " \n");
+ if(n != 2)
+ fatal(sys->sprint("bad md5 file in %s\n", wtype(where)+"/"+w.name+"/"+wrap->now2string(w.u[i].time, 0)));
+ file := hd l;
+ md5 := hd tl l;
+ for(fs := files; fs != nil; fs = tl fs){
+ if(strsuffix(file, hd fs)){
+ # sys->print("%s %s %s %d\n", pkg, file, md5, where);
+ addfile(file, w, i, md5, where, pkg);
+ }
+ }
+ }
+}
+
+Stat: adt{
+ name: string;
+ occs: list of (ref Wrap->Wrapped, int, string, int, string);
+ md5: string;
+};
+
+stats: list of ref Stat;
+
+addfile(file: string, w: ref Wrap->Wrapped, i: int, md5: string, where: int, pkg: string)
+{
+ for(sts := stats; sts != nil; sts = tl sts){
+ st := hd sts;
+ if(st.name == file){
+ st.occs = (w, i, md5, where, pkg) :: st.occs;
+ return;
+ }
+ }
+ digest := array[keyring->MD5dlen] of { * => byte 0 };
+ if (wrap->md5file(file, digest) < 0)
+ str := "non-existent"+blanks(32-12);
+ else
+ str = wrap->md5conv(digest);
+ st := ref Stat;
+ st.name = file;
+ st.occs = (w, i, md5, where, pkg) :: nil;
+ st.md5 = str;
+ stats = st :: stats;
+}
+
+prfiles()
+{
+ for(sts := stats; sts != nil; sts = tl sts){
+ st := hd sts;
+ sys->print("%s\n", st.name);
+ proccs(st.occs);
+ sys->print("\t%s %s\n", st.md5, st.name);
+ }
+}
+
+proccs(ocs: list of (ref Wrap->Wrapped, int, string, int, string))
+{
+ if(ocs != nil){
+ proccs(tl ocs);
+ (w, i, md5, where, pkg) := hd ocs;
+ sys->print("\t%s %s/%s(%s)\t%s\n", md5, w.name, wrap->now2string(w.u[i].time, 0), ptype(w.u[i].typ), wtype(where)+"/"+pkg);
+ }
+}
+
+ptype(p: int): string
+{
+ return (array[] of { "???", "package ", "update ", "full upd" })[p];
+}
+
+INSTALL: con 0;
+WRAP: con 1;
+
+wtype(w: int): string
+{
+ return (array[] of { "/install", "/wrap" })[w];
+}
+
+strsuffix(s: string, suf: string): int
+{
+ return (l1 := len s) >= (l2 := len suf) && s[l1-l2: l1] == suf;
+}
+
+blanks(n: int): string
+{
+ s := "";
+ for(i := 0; i < n; i++)
+ s += " ";
+ return s;
+}
diff --git a/appl/cmd/install/wrap.b b/appl/cmd/install/wrap.b
new file mode 100644
index 00000000..1b90c765
--- /dev/null
+++ b/appl/cmd/install/wrap.b
@@ -0,0 +1,684 @@
+implement Wrap;
+
+include "sys.m";
+ sys : Sys;
+include "draw.m";
+include "bufio.m";
+ bufio : Bufio;
+ Iobuf : import bufio;
+include "keyring.m";
+ keyring : Keyring;
+include "sh.m";
+include "arch.m";
+ arch : Arch;
+include "wrap.m";
+include "archfs.m";
+
+archpid := -1;
+gzfd: ref Sys->FD;
+gzfile: string;
+
+init(bio: Bufio)
+{
+ sys = load Sys Sys->PATH;
+ if(bio == nil)
+ bufio = load Bufio Bufio->PATH;
+ else
+ bufio = bio;
+ keyring = load Keyring Keyring->PATH;
+ arch = load Arch Arch->PATH;
+ arch->init(bufio);
+}
+
+end()
+{
+ if(gzfile != nil)
+ sys->remove(gzfile);
+ if (archpid > 0){
+ fd := sys->open("#p/" + string archpid + "/ctl", sys->OWRITE);
+ if (fd != nil)
+ sys->fprint(fd, "killgrp");
+ }
+}
+
+archfs(f : string, mtpt : string, all : int, c : chan of int)
+{
+ sys->pctl(Sys->NEWPGRP, nil);
+ cmd := "/dis/install/archfs.dis";
+ m := load Archfs Archfs->PATH;
+ if(m == nil) {
+ c <-= -1;
+ return;
+ }
+ ch := chan of int;
+ if (all)
+ spawn m->initc(cmd :: "-m" :: mtpt :: f :: nil, ch);
+ else
+ spawn m->initc(cmd :: "-s" :: "-m" :: mtpt :: f :: "/wrap" :: nil, ch);
+ pid := <- ch;
+ c <-= pid;
+}
+
+mountarch(f : string, mtpt : string, all : int) : int
+{
+ c := chan of int;
+ spawn archfs(f, mtpt, all, c);
+ pid := <- c;
+ if (pid < 0) {
+ if(pid == -1)
+ sys->fprint(sys->fildes(2), "fatal: cannot run archfs\n");
+ # else probably not an archive file
+ return -1;
+ }
+ archpid = pid;
+ return 0;
+}
+
+openmount(f : string, d : string) : ref Wrapped
+{
+ if (f == nil) {
+ p := d+"/wrap";
+ f = getfirstdir(p);
+ if (f == nil)
+ return nil;
+ }
+ w := ref Wrapped;
+ w.name = f;
+ w.root = d;
+ # p := d + "/wrap/" + f;
+ p := pathcat(d, pathcat("wrap", f));
+ (w.u, w.nu, w.tfull) = openupdate(p);
+ if (w.nu < 0) {
+ closewrap(w);
+ return nil;
+ }
+ return w;
+}
+
+closewrap(w : ref Wrapped)
+{
+ w = nil;
+}
+
+openwraphdr(f : string, d : string, argl : list of string, all : int) : ref Wrapped
+{
+ argl = nil;
+ (ok, dir) := sys->stat(f);
+ if (ok < 0 || dir.mode & Sys->DMDIR)
+ return openwrap(f, d, all);
+ (nf, fd) := arch->openarchgz(f);
+ if (nf != nil) {
+ gzfile = nf;
+ f = nf;
+ gzfd = fd;
+ }
+ return openwrap(f, "/mnt/wrap", all);
+}
+
+openwrap(f : string, d : string, all : int) : ref Wrapped
+{
+ if (d == nil)
+ d = "/";
+ if((w := openmount(f, d)) != nil)
+ return w; # don't mess about if /wrap/ structure exists
+ (ok, dir) := sys->stat(f);
+ if (ok < 0)
+ return nil;
+ # accept root/ or root/wrap/pkgname
+ if (dir.mode & Sys->DMDIR) {
+ d = f;
+ if ((i := strstr(f, "/wrap/")) >= 0) {
+ f = f[i+6:];
+ d = d[0:i+6];
+ }
+ else
+ f = nil;
+ return openmount(f, d);
+ }
+ (ok, dir) = sys->stat(f);
+ if (ok < 0 || dir.mode & Sys->DMDIR)
+ return openmount(f, d); # ?
+ if (mountarch(f, d, all) < 0)
+ return nil;
+ return openmount(nil, d);
+}
+
+getfirstdir(d : string) : string
+{
+ if ((fd := sys->open(d, Sys->OREAD)) == nil)
+ return nil;
+ for(;;){
+ (n, dir) := sys->dirread(fd);
+ if(n <= 0)
+ break;
+ for(i:=0; i<n; i++)
+ if(dir[i].mode & Sys->DMDIR)
+ return dir[i].name;
+ }
+ return nil;
+}
+
+NONE : con 0;
+
+sniffdir(base : string, elem : string) : (int, int)
+{
+ # t := int elem;
+ t := string2now(elem, 0);
+ if (t == 0)
+ return (NONE, 0);
+ # buf := sys->sprint("%ud", t);
+ # if (buf != elem)
+ # return (NONE, 0);
+ rv := NONE;
+ p := base + "/" + elem + "/package";
+ (ok, nil) := sys->stat(p);
+ if (ok >= 0)
+ rv |= FULL;
+ p = base + "/" + elem + "/update";
+ (ok, nil) = sys->stat(p);
+ if (ok >= 0)
+ rv |= UPD;
+ return (rv, t);
+}
+
+openupdate(d : string) : (array of Update, int, int)
+{
+ u : array of Update;
+
+ if ((fd := sys->open(d, Sys->OREAD)) == nil)
+ return (nil, -1, 0);
+ #
+ # We are looking to find the most recent full
+ # package; anything before that is irrelevant.
+ # Also figure out the most recent package update.
+ # Non-package updates before that are irrelevant.
+ # If there are no packages installed,
+ # grab all the updates we can find.
+ #
+ tbase := -1;
+ tfull := -1;
+ nu := 0;
+ for(;;){
+ (n, dir) := sys->dirread(fd);
+ if(n <= 0)
+ break;
+ for(i := 0; i < n; i++){
+ (k, t) := sniffdir(d, dir[i].name);
+ case (k) {
+ FULL =>
+ nu++;
+ if (t > tfull)
+ tfull = t;
+ if (t > tbase)
+ tbase = t;
+ FULL|UPD =>
+ nu++;
+ if (t > tfull)
+ tfull = t;
+ UPD =>
+ nu++;
+ }
+ }
+ }
+ if (nu == 0)
+ return (nil, -1, 0);
+ u = nil;
+ nu = 0;
+ if ((fd = sys->open(d, Sys->OREAD)) == nil)
+ return (nil, -1, 0);
+ for(;;){
+ (n, dir) := sys->dirread(fd);
+ if(n <= 0)
+ break;
+ for(i := 0; i < n; i++){
+ (k, t) := sniffdir(d, dir[i].name);
+ if (k == 0)
+ continue;
+ if (t < tbase)
+ continue;
+ if (t < tfull && k == UPD)
+ continue;
+ if (nu%8 == 0) {
+ newu := array[nu+8] of Update;
+ newu[0:] = u[0:nu];
+ u = newu;
+ }
+ u[nu].typ = k;
+ if (readupdate(u, nu, d, dir[i].name) != nil)
+ nu++;
+ }
+ }
+ if (nu == 0)
+ return (nil, -1, 0);
+ qsort(u, nu);
+ return (u, nu, tfull);
+}
+
+readupdate(u : array of Update, ui : int, base : string, elem : string) : array of Update
+{
+ # u[ui].dir = base + "/" + elem;
+ u[ui].dir = pathcat(base, elem);
+ p := u[ui].dir + "/desc";
+ u[ui].desc = readfile(p);
+ # u[ui].time = int elem;
+ u[ui].time = string2now(elem, 0);
+ p = u[ui].dir + "/md5sum";
+ u[ui].bmd5 = bufio->open(p, Bufio->OREAD);
+ p = u[ui].dir + "/update";
+ q := readfile(p);
+ if (q != nil)
+ u[ui].utime = int q;
+ else
+ u[ui].utime = 0;
+ if (u[ui].bmd5 == nil)
+ return nil;
+ return u;
+}
+
+readfile(s : string) : string
+{
+ (ok, d) := sys->stat(s);
+ if (ok < 0)
+ return nil;
+ buf := array[int d.length] of byte;
+ if ((fd := sys->open(s, Sys->OREAD)) == nil || sys->read(fd, buf, int d.length) != int d.length)
+ return nil;
+ s = string buf;
+ ls := len s;
+ if (s[ls-1] == '\n')
+ s = s[0:ls-1];
+ return s;
+}
+
+hex(c : int) : int
+{
+ if (c >= '0' && c <= '9')
+ return c-'0';
+ if (c >= 'a' && c <= 'f')
+ return c-'a'+10;
+ if (c >= 'A' && c <= 'F')
+ return c-'A'+10;
+ return -1;
+}
+
+getfileinfo(w : ref Wrapped, f : string, rdigest : array of byte, wdigest : array of byte, ardigest: array of byte) : (int, int)
+{
+ p : string;
+
+ if (w == nil)
+ return (-1, 0);
+ digest := array[keyring->MD5dlen] of { * => byte 0 };
+ for (i := w.nu-1; i >= 0; i--){
+ if ((p = bsearch(w.u[i].bmd5, f)) == nil)
+ continue;
+ if (p == nil)
+ continue;
+ k := 0;
+ while (k < len p && p[k] != ' ')
+ k++;
+ if (k == len p)
+ continue;
+ q := p[k+1:];
+ if (q == nil)
+ continue;
+ if (len q != 2*Keyring->MD5dlen+1)
+ continue;
+ for (j := 0; j < Keyring->MD5dlen; j++) {
+ a := hex(q[2*j]);
+ b := hex(q[2*j+1]);
+ if (a < 0 || b < 0)
+ break;
+ digest[j] = byte ((a<<4)|b);
+ }
+ if(j != Keyring->MD5dlen)
+ continue;
+ if(rdigest == nil || memcmp(rdigest, digest, keyring->MD5dlen) == 0 || (ardigest != nil && memcmp(ardigest, digest, keyring->MD5dlen) == 0))
+ break;
+ else
+ return (-1, 0); # NEW
+ }
+ if(i < 0)
+ return (-1, 0);
+ if(wdigest != nil)
+ wdigest[0:] = rdigest;
+ return (0, w.u[i].time);
+
+
+}
+
+bsearch(b : ref Bufio->Iobuf, p : string) : string
+{
+ if (b == nil)
+ return nil;
+ lo := 0;
+ b.seek(big 0, Bufio->SEEKEND);
+ hi := int b.offset();
+ l := len p;
+ while (lo < hi) {
+ m := (lo+hi)/2;
+ b.seek(big m, Bufio->SEEKSTART);
+ b.gets('\n');
+ if (int b.offset() == hi) {
+ bgetbackc(b);
+ m = int b.offset();
+ while (m-- > lo) {
+ if (bgetbackc(b) == '\n') {
+ b.getc();
+ break;
+ }
+ }
+ }
+ s := b.gets('\n');
+ if (len s >= l+1 && s[0:l] == p && (s[l] == ' ' || s[l] == '\n'))
+ return s;
+ if (s < p)
+ lo = int b.offset();
+ else
+ hi = int b.offset()-len s;
+ }
+ return nil;
+}
+
+bgetbackc(b : ref Bufio->Iobuf) : int
+{
+ m := int b.offset();
+ b.seek(big (m-1), Bufio->SEEKSTART);
+ c := b.getc();
+ b.ungetc();
+ return c;
+}
+
+strstr(s : string, p : string) : int
+{
+ lp := len p;
+ ls := len s;
+ for (i := 0; i < ls-lp; i++)
+ if (s[i:i+lp] == p)
+ return i;
+ return -1;
+}
+
+qsort(a : array of Update, n : int)
+{
+ i, j : int;
+ t : Update;
+
+ while(n > 1) {
+ i = n>>1;
+ t = a[0]; a[0] = a[i]; a[i] = t;
+ i = 0;
+ j = n;
+ for(;;) {
+ do
+ i++;
+ while(i < n && a[i].time < a[0].time);
+ do
+ j--;
+ while(j > 0 && a[j].time > a[0].time);
+ if(j < i)
+ break;
+ t = a[i]; a[i] = a[j]; a[j] = t;
+ }
+ t = a[0]; a[0] = a[j]; a[j] = t;
+ n = n-j-1;
+ if(j >= n) {
+ qsort(a, j);
+ a = a[j+1:];
+ } else {
+ qsort(a[j+1:], n);
+ n = j;
+ }
+ }
+}
+
+md5file(file : string, digest : array of byte) : int
+{
+ (ok, d) := sys->stat(file);
+ if (ok < 0)
+ return -1;
+ if (d.mode & Sys->DMDIR)
+ return 0;
+ bio := bufio->open(file, Bufio->OREAD);
+ if (bio == nil)
+ return -1;
+ # return md5sum(bio, digest, d.length);
+ buff := array[Sys->ATOMICIO] of byte;
+ ds := keyring->md5(nil, 0, nil, nil);
+ while ((n := bio.read(buff, len buff)) > 0)
+ keyring->md5(buff, n, nil, ds);
+ keyring->md5(nil, 0, digest, ds);
+ bio = nil;
+ return 0;
+}
+
+md5sum(b : ref Iobuf, digest : array of byte, leng : int) : int
+{
+ ds := keyring->md5(nil, 0, nil, nil);
+ buff := array[Sys->ATOMICIO] of byte;
+ while (leng > 0) {
+ if (leng > len buff)
+ n := len buff;
+ else
+ n = leng;
+ if ((n = b.read(buff, n)) <= 0)
+ return -1;
+ keyring->md5(buff, n, nil, ds);
+ leng -= n;
+ }
+ keyring->md5(nil, 0, digest, ds);
+ return 0;
+}
+
+md5conv(d : array of byte) : string
+{
+ s : string = nil;
+
+ for (i := 0; i < keyring->MD5dlen; i++)
+ s += sys->sprint("%.2ux", int d[i]);
+ return s;
+}
+
+zd : Sys->Dir;
+
+newd(time : int, uid : string, gid : string) : ref Sys->Dir
+{
+ d := ref Sys->Dir;
+ *d = zd;
+ d.uid = uid;
+ d.gid = gid;
+ d.mtime = time;
+ return d;
+}
+
+putwrapfile(b : ref Iobuf, name : string, time : int, elem : string, file : string, uid : string, gid : string)
+{
+ d := newd(time, uid, gid);
+ d.mode = 8r444;
+ (ok, dir) := sys->stat(file);
+ if (ok < 0)
+ sys->fprint(sys->fildes(2), "cannot stat %s: %r", file);
+ d.length = dir.length;
+ # s := "/wrap/"+name+"/"+sys->sprint("%ud", time)+"/"+elem;
+ s := "/wrap/"+name+"/"+now2string(time, 0)+"/"+elem;
+ arch->puthdr(b, s, d);
+ arch->putfile(b, file, int d.length);
+}
+
+putwrap(b : ref Iobuf, name : string, time : int, desc : string, utime : int, pkg : int, uid : string, gid : string)
+{
+ if (!(utime || pkg))
+ sys->fprint(sys->fildes(2), "bad precondition in putwrap()");
+ d := newd(time, uid, gid);
+ d.mode = Sys->DMDIR|8r775;
+ s := "/wrap";
+ arch->puthdr(b, s, d);
+ s += "/"+name;
+ arch->puthdr(b, s, d);
+ # s += "/"+sys->sprint("%ud", time);
+ s += "/"+now2string(time, 0);
+ arch->puthdr(b, s, d);
+ d.mode = 8r444;
+ s += "/";
+ dir := s;
+ if (utime) {
+ s = dir+"update";
+ d.length = big 23;
+ arch->puthdr(b, s, d);
+ arch->putstring(b, sys->sprint("%22ud\n", utime));
+ }
+ if (pkg) {
+ s = dir+"package";
+ d.length = big 0;
+ arch->puthdr(b, s, d);
+ }
+ if (desc != nil) {
+ s = dir+"desc";
+ d.length = big (len desc+1);
+ d.mode = 8r444;
+ arch->puthdr(b, s, d);
+ arch->putstring(b, desc+"\n");
+ }
+}
+
+memcmp(b1, b2 : array of byte, n : int) : int
+{
+ for (i := 0; i < n; i++)
+ if (b1[i] < b2[i])
+ return -1;
+ else if (b1[i] > b2[i])
+ return 1;
+ return 0;
+}
+
+strprefix(s: string, pre: string): int
+{
+ return len s >= (l := len pre) && s[0:l] == pre;
+}
+
+match(s: string, pre: list of string): int
+{
+ if(pre == nil || s == "/wrap" || strprefix(s, "/wrap/"))
+ return 1;
+ for( ; pre != nil; pre = tl pre)
+ if(strprefix(s, hd pre))
+ return 1;
+ return 0;
+}
+
+notmatch(s: string, pre: list of string): int
+{
+ if(pre == nil || s == "/wrap" || strprefix(s, "/wrap/"))
+ return 1;
+ for( ; pre != nil; pre = tl pre)
+ if(strprefix(s, hd pre))
+ return 0;
+ return 1;
+}
+
+pathcat(s : string, t : string) : string
+{
+ if (s == nil) return t;
+ if (t == nil) return s;
+ slashs := s[len s - 1] == '/';
+ slasht := t[0] == '/';
+ if (slashs && slasht)
+ return s + t[1:];
+ if (!slashs && !slasht)
+ return s + "/" + t;
+ return s + t;
+}
+
+md5filea(file : string, digest : array of byte) : int
+{
+ n, n0: int;
+
+ (ok, d) := sys->stat(file);
+ if (ok < 0)
+ return -1;
+ if (d.mode & Sys->DMDIR)
+ return 0;
+ bio := bufio->open(file, Bufio->OREAD);
+ if (bio == nil)
+ return -1;
+ buff := array[Sys->ATOMICIO] of byte;
+ m := len buff;
+ ds := keyring->md5(nil, 0, nil, nil);
+ r := 0;
+ while(1){
+ if(r){
+ if((n = bio.read(buff[1:], m-1)) <= 0)
+ break;
+ n++;
+ }
+ else{
+ if ((n = bio.read(buff, m)) <= 0)
+ break;
+ }
+ (n0, r) = remcr(buff, n);
+ if(r){
+ keyring->md5(buff, n0-1, nil, ds);
+ buff[0] = byte '\r';
+ }
+ else
+ keyring->md5(buff, n0, nil, ds);
+ }
+ if(r)
+ keyring->md5(buff, 1, nil, ds);
+ keyring->md5(nil, 0, digest, ds);
+ bio = nil;
+ return 0;
+}
+
+remcr(b: array of byte, n: int): (int, int)
+{
+ if(n == 0)
+ return (0, 0);
+ for(i := 0; i < n; ){
+ if(b[i] == byte '\r' && i+1 < n && b[i+1] == byte '\n')
+ b[i:] = b[i+1:n--];
+ else
+ i++;
+ }
+ return (n, b[n-1] == byte '\r');
+}
+
+TEN2EIGHT: con 100000000;
+
+now2string(n: int, flag: int): string
+{
+ if(flag == 0)
+ return sys->sprint("%ud", n);
+ if(n < 0)
+ return nil;
+ q := n/TEN2EIGHT;
+ s := "0" + string (n-TEN2EIGHT*q);
+ while(len s < 9)
+ s = "0" + s;
+ if(q <= 9)
+ s[0] = '0' + q - 0;
+ else if(q <= 21)
+ s[0] = 'A' + q - 10;
+ else
+ return nil;
+ return s;
+}
+
+string2now(s: string, flag: int): int
+{
+ if(flag == 0 && s[0] != 'A')
+ return int s;
+ if(len s != 9)
+ return 0;
+ r := int s[1: ];
+ c := s[0];
+ if(c >= '0' && c <= '9')
+ q := c - '0' + 0;
+ else if(c >= 'A' && c <= 'L')
+ q = c - 'A' + 10;
+ else
+ return 0;
+ n := TEN2EIGHT*q + r;
+ if(n < 0)
+ return 0;
+ return n;
+}
diff --git a/appl/cmd/install/wrap.m b/appl/cmd/install/wrap.m
new file mode 100644
index 00000000..c15624ca
--- /dev/null
+++ b/appl/cmd/install/wrap.m
@@ -0,0 +1,41 @@
+Wrap : module
+{
+ PATH : con "/dis/install/wrap.dis";
+
+ FULL, UPD : con iota+1;
+
+ Update : adt {
+ desc : string;
+ dir : string;
+ time : int;
+ utime : int;
+ bmd5 : ref Bufio->Iobuf;
+ typ : int;
+ };
+
+ Wrapped : adt {
+ name : string;
+ root : string;
+ tfull : int;
+ u : array of Update;
+ nu : int;
+ };
+
+ init: fn(bio: Bufio);
+ openwrap: fn(f : string, d : string, all : int) : ref Wrapped;
+ openwraphdr: fn(f : string, d : string, argl : list of string, all : int) : ref Wrapped;
+ getfileinfo: fn(w : ref Wrapped, f : string, rdigest : array of byte, wdigest: array of byte, ardigest: array of byte) : (int, int);
+ putwrapfile: fn(b : ref Bufio->Iobuf, name : string, time : int, elem : string, file : string, uid : string, gid : string);
+ putwrap: fn(b : ref Bufio->Iobuf, name : string, time : int, desc : string, utime : int, pkg : int, uid : string, gid : string);
+ md5file: fn(file : string, digest : array of byte) : int;
+ md5filea: fn(file : string, digest : array of byte) : int;
+ md5sum: fn(b : ref Bufio->Iobuf, digest : array of byte, leng : int) : int;
+ md5conv: fn(d : array of byte) : string;
+ # utilities
+ match: fn(s: string, pre: list of string): int;
+ notmatch: fn(s: string, pre: list of string): int;
+ memcmp: fn(b1, b2: array of byte, n: int): int;
+ end: fn();
+ now2string: fn(n: int, flag: int): string;
+ string2now: fn(s: string, flag: int): int;
+};
diff --git a/appl/cmd/install/wrap2list.b b/appl/cmd/install/wrap2list.b
new file mode 100644
index 00000000..d2656cf5
--- /dev/null
+++ b/appl/cmd/install/wrap2list.b
@@ -0,0 +1,305 @@
+#
+# Copyright © 2001 Vita Nuova (Holdings) Limited. All rights reserved.
+#
+
+implement Wrap2list;
+
+# make a version list suitable for SDS from /wrap
+
+include "sys.m";
+ sys : Sys;
+include "draw.m";
+include "bufio.m";
+ bufio : Bufio;
+ Iobuf : import bufio;
+include "crc.m";
+ crcm : Crc;
+include "wrap.m";
+ wrap: Wrap;
+
+Wrap2list: module
+{
+ init : fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+stderr: ref Sys->FD;
+
+HASHSZ: con 64;
+
+Element: type string;
+
+Hash: adt{
+ elems: array of Element;
+ nelems: int;
+};
+
+List: adt{
+ tabs: array of ref Hash;
+ init: fn(l: self ref List);
+ add: fn(l: self ref List, e: Element);
+ subtract: fn(l: self ref List, e: Element);
+ end: fn(l: self ref List): array of Element;
+};
+
+flist: ref List;
+
+hash(s: string): int
+{
+ h := 0;
+ n := len s;
+ for(i := 0; i < n; i++)
+ h += s[i];
+ if(h < 0)
+ h = -h;
+ return h%HASHSZ;
+}
+
+List.init(l: self ref List)
+{
+ ts := l.tabs = array[HASHSZ] of ref Hash;
+ for(i := 0; i < HASHSZ; i++){
+ t := ts[i] = ref Hash;
+ t.elems = array[HASHSZ] of Element;
+ t.nelems = 0;
+ }
+}
+
+List.add(l: self ref List, e: Element)
+{
+ h := hash(e);
+ t := l.tabs[h];
+ n := t.nelems;
+ es := t.elems;
+ for(i := 0; i < n; i++){
+ if(e == es[i])
+ return;
+ }
+ if(n == len es)
+ es = t.elems = (array[2*n] of Element)[0:] = es;
+ es[t.nelems++] = e;
+# sys->print("+ %s\n", e);
+}
+
+List.subtract(l: self ref List, e: Element)
+{
+ h := hash(e);
+ t := l.tabs[h];
+ n := t.nelems;
+ es := t.elems;
+ for(i := 0; i < n; i++){
+ if(e == es[i]){
+ es[i] = nil;
+ break;
+ }
+ }
+# sys->print("- %s\n", e);
+}
+
+List.end(l: self ref List): array of Element
+{
+ tot := 0;
+ ts := l.tabs;
+ for(i := 0; i < HASHSZ; i++)
+ tot += ts[i].nelems;
+ a := array[tot] of Element;
+ m := 0;
+ for(i = 0; i < HASHSZ; i++){
+ t := ts[i];
+ n := t.nelems;
+ es := t.elems;
+ a[m:] = es[0: n];
+ m += n;
+ }
+ return a;
+}
+
+usage()
+{
+ sys->fprint(stderr, "Usage: wrap2list [ file ... ]\n");
+ exit;
+}
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ crcm = load Crc Crc->PATH;
+ wrap = load Wrap Wrap->PATH;
+ wrap->init(bufio);
+ if(argv != nil)
+ argv = tl argv;
+ init := 0;
+ if(argv != nil && hd argv == "-i"){
+ init = 1;
+ argv = tl argv;
+ }
+ stderr = sys->fildes(2);
+ # root := "/";
+ flist = ref List;
+ flist.init();
+ fd := sys->open("/wrap", Sys->OREAD);
+ for(;;){
+ (nd, d) := sys->dirread(fd);
+ if(nd <= 0)
+ break;
+ for(i:=0; i<nd; i++){
+ if((d[0].mode & Sys->DMDIR) && (w := wrap->openwrap(d[i].name, "/", 1)) != nil){
+ # sys->fprint(stderr, "%s %s %d %d\n", w.name, w.root, w.tfull, w.nu);
+ for(j := 0; j < w.nu; j++){
+ addfiles(w.u[j].bmd5);
+ if((b := bufio->open(w.u[j].dir+"/remove", Bufio->OREAD)) != nil)
+ subtractfiles(b);
+ # sys->fprint(stderr, "%d: %s %s %d %d %d\n", i, w.u[j].desc, w.u[j].dir, w.u[j].time, w.u[j].utime, w.u[j].typ);
+ }
+ }
+ }
+ }
+ for( ; argv != nil; argv = tl argv){
+ if((b := bufio->open(hd argv, Bufio->OREAD)) != nil)
+ addfiles(b);
+ }
+ out(uniq(rmnil(sort(flist.end()))), init);
+}
+
+addfiles(b: ref Bufio->Iobuf)
+{
+ b.seek(big 0, Bufio->SEEKSTART);
+ while((s := b.gets('\n')) != nil){
+ (n, l) := sys->tokenize(s, " \n");
+ if(n > 0)
+ flist.add(hd l);
+ }
+}
+
+subtractfiles(b: ref Bufio->Iobuf)
+{
+ b.seek(big 0, Bufio->SEEKSTART);
+ while((s := b.gets('\n')) != nil){
+ (n, l) := sys->tokenize(s, " \n");
+ if(n > 0)
+ flist.subtract(hd l);
+ }
+}
+
+out(fs: array of Element, init: int)
+{
+ nf := len fs;
+ for(i := 0; i < nf; i++){
+ f := fs[i];
+ outl(f, nil, init);
+ l := len f;
+ if(l >= 7 && f[l-7:] == "emu.new"){
+ g := f;
+ f[l-3] = 'e';
+ f[l-2] = 'x';
+ f[l-1] = 'e';
+ outl(f, g, init); # try emu.exe
+ outl(f[0: l-4], g, init); # try emu
+# sys->fprint(sys->fildes(2), "%s %s\n", f, g);
+ }
+ }
+}
+
+outl(f: string, g: string, init: int)
+{
+ (ok, d) := sys->stat(f);
+ if(ok < 0){
+ # sys->fprint(stderr, "cannot open %s\n", f);
+ return;
+ }
+ if(g == nil)
+ g = "-";
+ if(d.mode & Sys->DMDIR)
+ d.length = big 0;
+ if(init)
+ mtime := 0;
+ else
+ mtime = d.mtime;
+ sys->print("%s %s %d %d %d %d %d\n", f, g, int d.length, d.mode, mtime, crc(f, d), 0);
+}
+
+crc(f: string, d: Sys->Dir): int
+{
+ crcs := crcm->init(0, int 16rffffffff);
+ if(d.mode & Sys->DMDIR)
+ return 0;
+ fd := sys->open(f, Sys->OREAD);
+ if(fd == nil){
+ sys->fprint(stderr, "cannot open %s\n", f);
+ return 0;
+ }
+ crc := 0;
+ buf := array[Sys->ATOMICIO] of byte;
+ for(;;){
+ nr := sys->read(fd, buf, len buf);
+ if(nr < 0){
+ sys->fprint(stderr, "bad read on %s : %r\n", f);
+ return 0;
+ }
+ if(nr <= 0)
+ break;
+ crc = crcm->crc(crcs, buf, nr);
+ }
+ crcm->reset(crcs);
+ return crc;
+}
+
+sort(a: array of Element): array of Element
+{
+ qsort(a, len a);
+ return a;
+}
+
+rmnil(a: array of Element): array of Element
+{
+ n := len a;
+ for(i := 0; i < n; i++)
+ if(a[i] != nil)
+ break;
+ return a[i: n];
+}
+
+uniq(a: array of Element): array of Element
+{
+ n := len a;
+ for(i := 0; i < n-1; ){
+ if(a[i] == a[i+1])
+ a[i+1:] = a[i+2: n--];
+ else
+ i++;
+ }
+ return a[0: n];
+}
+
+qsort(a: array of Element, n: int)
+{
+ i, j: int;
+ t: Element;
+
+ while(n > 1){
+ i = n>>1;
+ t = a[0]; a[0] = a[i]; a[i] = t;
+ i = 0;
+ j = n;
+ for(;;){
+ do
+ i++;
+ while(i < n && a[i] < a[0]);
+ do
+ j--;
+ while(j > 0 && a[j] > a[0]);
+ if(j < i)
+ break;
+ t = a[i]; a[i] = a[j]; a[j] = t;
+ }
+ t = a[0]; a[0] = a[j]; a[j] = t;
+ n = n-j-1;
+ if(j >= n){
+ qsort(a, j);
+ a = a[j+1:];
+ }else{
+ qsort(a[j+1:], n);
+ n = j;
+ }
+ }
+}