diff options
Diffstat (limited to 'appl/cmd/install/mergelog.b')
| -rw-r--r-- | appl/cmd/install/mergelog.b | 239 |
1 files changed, 239 insertions, 0 deletions
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); + } +} |
