diff options
| author | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
|---|---|---|
| committer | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
| commit | 37da2899f40661e3e9631e497da8dc59b971cbd0 (patch) | |
| tree | cbc6d4680e347d906f5fa7fca73214418741df72 /appl/cmd/install | |
| parent | 54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff) | |
20060303a
Diffstat (limited to 'appl/cmd/install')
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; + } + } +} |
