1implement Mergelog; 2 3# 4# combine old and new log sections into one with the most recent data 5# 6 7include "sys.m"; 8 sys: Sys; 9 10include "draw.m"; 11 12include "bufio.m"; 13 bufio: Bufio; 14 Iobuf: import bufio; 15 16include "string.m"; 17 str: String; 18 19include "keyring.m"; 20 kr: Keyring; 21 22include "daytime.m"; 23 daytime: Daytime; 24 25include "logs.m"; 26 logs: Logs; 27 Db, Entry, Byname, Byseq: import logs; 28 S: import logs; 29 30include "arg.m"; 31 32Mergelog: module 33{ 34 init: fn(nil: ref Draw->Context, nil: list of string); 35}; 36 37Apply, Applydb, Install, Asis, Skip: con iota; 38 39client: ref Db; # client current state from client log 40updates: ref Db; # state delta from new section of server log 41 42nerror := 0; 43nconflict := 0; 44debug := 0; 45verbose := 0; 46resolve := 0; 47setuid := 0; 48setgid := 0; 49nflag := 0; 50timefile: string; 51clientroot: string; 52srvroot: string; 53logfd: ref Sys->FD; 54now := 0; 55gen := 0; 56noerr := 0; 57 58init(nil: ref Draw->Context, args: list of string) 59{ 60 sys = load Sys Sys->PATH; 61 62 bufio = load Bufio Bufio->PATH; 63 ensure(bufio, Bufio->PATH); 64 str = load String String->PATH; 65 ensure(str, String->PATH); 66 kr = load Keyring Keyring->PATH; 67 ensure(kr, Keyring->PATH); 68 daytime = load Daytime Daytime->PATH; 69 ensure(daytime, Daytime->PATH); 70 logs = load Logs Logs->PATH; 71 ensure(logs, Logs->PATH); 72 logs->init(bufio); 73 74 arg := load Arg Arg->PATH; 75 ensure(arg, Arg->PATH); 76 arg->init(args); 77 arg->setusage("mergelog [-vd] oldlog [path ... ] <newlog"); 78 dump := 0; 79 while((o := arg->opt()) != 0) 80 case o { 81 'd' => dump = 1; debug = 1; 82 'v' => verbose = 1; 83 * => arg->usage(); 84 } 85 args = arg->argv(); 86 if(len args < 3) 87 arg->usage(); 88 arg = nil; 89 90 now = daytime->now(); 91 client = Db.new("existing log"); 92 updates = Db.new("update log"); 93 clientlog := hd args; args = tl args; 94 if(args != nil) 95 error("restriction by path not yet done"); 96 97 # replay the client log to build last installation state of files taken from server 98 logfd = sys->open(clientlog, Sys->OREAD); 99 if(logfd == nil) 100 error(sys->sprint("can't open %s: %r", clientlog)); 101 f := bufio->fopen(logfd, Sys->OREAD); 102 if(f == nil) 103 error(sys->sprint("can't open %s: %r", clientlog)); 104 while((log := readlog(f)) != nil) 105 replaylog(client, log); 106 f = nil; 107 108 # read new log entries and use the new section to build a sequence of update actions 109 f = bufio->fopen(sys->fildes(0), Sys->OREAD); 110 while((log = readlog(f)) != nil) 111 replaylog(client, log); 112 client.sort(Byseq); 113 dumpdb(client); 114 if(nerror) 115 raise sys->sprint("fail:%d errors", nerror); 116} 117 118readlog(in: ref Iobuf): ref Entry 119{ 120 (e, err) := Entry.read(in); 121 if(err != nil) 122 error(err); 123 return e; 124} 125 126# 127# replay a log to reach the state wrt files previously taken from the server 128# 129replaylog(db: ref Db, log: ref Entry) 130{ 131 e := db.look(log.path); 132 indb := e != nil && !e.removed(); 133 case log.action { 134 'a' => # add new file 135 if(indb){ 136 note(sys->sprint("%q duplicate create", log.path)); 137 return; 138 } 139 'c' => # contents 140 if(!indb){ 141 note(sys->sprint("%q contents but no entry", log.path)); 142 return; 143 } 144 'd' => # delete 145 if(!indb){ 146 note(sys->sprint("%q deleted but no entry", log.path)); 147 return; 148 } 149 if(e.d.mtime > log.d.mtime){ 150 note(sys->sprint("%q deleted but it's newer", log.path)); 151 return; 152 } 153 'm' => # metadata 154 if(!indb){ 155 note(sys->sprint("%q metadata but no entry", log.path)); 156 return; 157 } 158 * => 159 error(sys->sprint("bad log entry: %bd %bd", log.seq>>32, log.seq & big 16rFFFFFFFF)); 160 } 161 update(db, e, log); 162} 163 164# 165# update file state e to reflect the effect of the log, 166# creating a new entry if necessary 167# 168update(db: ref Db, e: ref Entry, log: ref Entry) 169{ 170 if(e == nil) 171 e = db.entry(log.seq, log.path, log.d); 172 e.update(log); 173} 174 175rev[T](l: list of T): list of T 176{ 177 rl: list of T; 178 for(; l != nil; l = tl l) 179 rl = hd l :: rl; 180 return rl; 181} 182 183ensure[T](m: T, path: string) 184{ 185 if(m == nil) 186 error(sys->sprint("can't load %s: %r", path)); 187} 188 189error(s: string) 190{ 191 sys->fprint(sys->fildes(2), "applylog: %s\n", s); 192 raise "fail:error"; 193} 194 195note(s: string) 196{ 197 sys->fprint(sys->fildes(2), "applylog: note: %s\n", s); 198} 199 200warn(s: string) 201{ 202 sys->fprint(sys->fildes(2), "applylog: warning: %s\n", s); 203 nerror++; 204} 205 206samestat(a: Sys->Dir, b: Sys->Dir): int 207{ 208 # doesn't check permission/ownership, does check QTDIR/QTFILE 209 if(a.mode & Sys->DMDIR) 210 return (b.mode & Sys->DMDIR) != 0; 211 return a.length == b.length && a.mtime == b.mtime && a.qid.qtype == b.qid.qtype; # TO DO: a.name==b.name? 212} 213 214samemeta(a: Sys->Dir, b: Sys->Dir): int 215{ 216 return a.mode == b.mode && (!setuid || a.uid == b.uid) && (!setgid || a.gid == b.gid) && samestat(a, b); 217} 218 219bigof(s: string, base: int): big 220{ 221 (b, r) := str->tobig(s, base); 222 if(r != nil) 223 error("cruft in integer field in log entry: "+s); 224 return b; 225} 226 227intof(s: string, base: int): int 228{ 229 return int bigof(s, base); 230} 231 232dumpdb(db: ref Db) 233{ 234 for(i := 0; i < db.nstate; i++){ 235 s := db.state[i].text(); 236 if(s != nil) 237 sys->print("%s\n", s); 238 } 239} 240