1implement Updatelog; 2 3include "sys.m"; 4 sys: Sys; 5 6include "draw.m"; 7 8include "bufio.m"; 9 bufio: Bufio; 10 Iobuf: import bufio; 11 12include "daytime.m"; 13 daytime: Daytime; 14 15include "string.m"; 16 str: String; 17 18include "keyring.m"; 19 kr: Keyring; 20 21include "logs.m"; 22 logs: Logs; 23 Db, Entry, Byname, Byseq: import logs; 24 S, mkpath: import logs; 25 Log: type Entry; 26 27include "fsproto.m"; 28 fsproto: FSproto; 29 Direntry: import fsproto; 30 31include "arg.m"; 32 33Updatelog: module 34{ 35 init: fn(nil: ref Draw->Context, nil: list of string); 36}; 37 38now: int; 39gen := 0; 40changesonly := 0; 41uid: string; 42gid: string; 43debug := 0; 44state: ref Db; 45rootdir := "."; 46scanonly: list of string; 47exclude: list of string; 48sums := 0; 49stderr: ref Sys->FD; 50Seen: con 1<<31; 51bout: ref Iobuf; 52 53init(nil: ref Draw->Context, args: list of string) 54{ 55 sys = load Sys Sys->PATH; 56 bufio = load Bufio Bufio->PATH; 57 ensure(bufio, Bufio->PATH); 58 fsproto = load FSproto FSproto->PATH; 59 ensure(fsproto, FSproto->PATH); 60 daytime = load Daytime Daytime->PATH; 61 ensure(daytime, Daytime->PATH); 62 str = load String String->PATH; 63 ensure(str, String->PATH); 64 logs = load Logs Logs->PATH; 65 ensure(logs, Logs->PATH); 66 kr = load Keyring Keyring->PATH; 67 ensure(kr, Keyring->PATH); 68 69 arg := load Arg Arg->PATH; 70 if(arg == nil) 71 error(sys->sprint("can't load %s: %r", Arg->PATH)); 72 73 protofile := "/lib/proto/all"; 74 arg->init(args); 75 arg->setusage("updatelog [-p proto] [-r root] [-t now gen] [-c] [-x path] x.log [path ...]"); 76 while((o := arg->opt()) != 0) 77 case o { 78 'D' => 79 debug = 1; 80 'p' => 81 protofile = arg->earg(); 82 'r' => 83 rootdir = arg->earg(); 84 'c' => 85 changesonly = 1; 86 'u' => 87 uid = arg->earg(); 88 'g' => 89 gid = arg->earg(); 90 's' => 91 sums = 1; 92 't' => 93 now = int arg->earg(); 94 gen = int arg->earg(); 95 'x' => 96 s := arg->earg(); 97 exclude = trimpath(s) :: exclude; 98 * => 99 arg->usage(); 100 } 101 args = arg->argv(); 102 if(args == nil) 103 arg->usage(); 104 arg = nil; 105 106 stderr = sys->fildes(2); 107 bout = bufio->fopen(sys->fildes(1), Bufio->OWRITE); 108 109 fsproto->init(); 110 logs->init(bufio); 111 112 logfile := hd args; 113 while((args = tl args) != nil) 114 scanonly = trimpath(hd args) :: scanonly; 115 checkroot(rootdir, "replica root"); 116 117 state = Db.new("server state"); 118 119 # 120 # replay log to rebuild server state 121 # 122 logfd := sys->open(logfile, Sys->OREAD); 123 if(logfd == nil) 124 error(sys->sprint("can't open %s: %r", logfile)); 125 f := bufio->fopen(logfd, Sys->OREAD); 126 if(f == nil) 127 error(sys->sprint("can't open %s: %r", logfile)); 128 while((log := readlog(f)) != nil) 129 replaylog(state, log); 130 131 # 132 # walk the set of names produced by the proto file, comparing against the server state 133 # 134 now = daytime->now(); 135 doproto(rootdir, protofile); 136 137 if(changesonly){ 138 bout.flush(); 139 exit; 140 } 141 142 # 143 # names in the original state that we didn't see in the walk must have been removed: 144 # print 'd' log entries for them, in reverse lexicographic order (children before parents) 145 # 146 state.sort(Logs->Byname); 147 for(i := state.nstate; --i >= 0;){ 148 e := state.state[i]; 149 if((e.x & Seen) == 0 && considered(e.path)){ 150 change('d', e, e.seq, e.d, e.path, e.serverpath, e.contents); # TO DO: content 151 if(debug) 152 sys->fprint(sys->fildes(2), "remove %q\n", e.path); 153 } 154 } 155 bout.flush(); 156} 157 158ensure[T](m: T, path: string) 159{ 160 if(m == nil) 161 error(sys->sprint("can't load %s: %r", path)); 162} 163 164checkroot(dir: string, what: string) 165{ 166 (ok, d) := sys->stat(dir); 167 if(ok < 0) 168 error(sys->sprint("can't stat %s %q: %r", what, dir)); 169 if((d.mode & Sys->DMDIR) == 0) 170 error(sys->sprint("%s %q: not a directory", what, dir)); 171} 172 173considered(s: string): int 174{ 175 if(scanonly != nil && !islisted(s, scanonly)) 176 return 0; 177 return exclude == nil || !islisted(s, exclude); 178} 179 180readlog(in: ref Iobuf): ref Log 181{ 182 (e, err) := Entry.read(in); 183 if(err != nil) 184 error(err); 185 return e; 186} 187 188# 189# replay a log to reach the state wrt files previously taken from the server 190# 191replaylog(db: ref Db, log: ref Log) 192{ 193 e := db.look(log.path); 194 indb := e != nil && !e.removed(); 195 case log.action { 196 'a' => # add new file 197 if(indb){ 198 note(sys->sprint("%q duplicate create", log.path)); 199 return; 200 } 201 'c' => # contents 202 if(!indb){ 203 note(sys->sprint("%q contents but no entry", log.path)); 204 return; 205 } 206 'd' => # delete 207 if(!indb){ 208 note(sys->sprint("%q deleted but no entry", log.path)); 209 return; 210 } 211 if(e.d.mtime > log.d.mtime){ 212 note(sys->sprint("%q deleted but it's newer", log.path)); 213 return; 214 } 215 'm' => # metadata 216 if(!indb){ 217 note(sys->sprint("%q metadata but no entry", log.path)); 218 return; 219 } 220 * => 221 error(sys->sprint("bad log entry: %bd %bd", log.seq>>32, log.seq & big 16rFFFFFFFF)); 222 } 223 update(db, e, log); 224} 225 226# 227# update file state e to reflect the effect of the log, 228# creating a new entry if necessary 229# 230update(db: ref Db, e: ref Entry, log: ref Entry) 231{ 232 if(e == nil) 233 e = db.entry(log.seq, log.path, log.d); 234 e.update(log); 235} 236 237doproto(tree: string, protofile: string) 238{ 239 entries := chan of Direntry; 240 warnings := chan of (string, string); 241 err := fsproto->readprotofile(protofile, tree, entries, warnings); 242 if(err != nil) 243 error(sys->sprint("can't read %s: %s", protofile, err)); 244 for(;;)alt{ 245 (old, new, d) := <-entries => 246 if(d == nil) 247 return; 248 if(debug) 249 sys->fprint(stderr, "old=%q new=%q length=%bd\n", old, new, d.length); 250 while(new != nil && new[0] == '/') 251 new = new[1:]; 252 if(!considered(new)) 253 continue; 254 if(sums && (d.mode & Sys->DMDIR) == 0) 255 digests := md5sum(old) :: nil; 256 if(uid != nil) 257 d.uid = uid; 258 if(gid != nil) 259 d.gid = gid; 260 old = relative(old, rootdir); 261 db := state.look(new); 262 if(db == nil){ 263 if(!changesonly){ 264 db = state.entry(nextseq(), new, *d); 265 change('a', db, db.seq, db.d, db.path, old, digests); 266 } 267 }else{ 268 if(!samestat(db.d, *d)) 269 change('c', db, nextseq(), *d, new, old, digests); 270 if(!samemeta(db.d, *d)) 271 change('m', db, nextseq(), *d, new, old, nil); # need digest? 272 } 273 if(db != nil) 274 db.x |= Seen; 275 (old, msg) := <-warnings => 276 #if(contains(msg, "entry not found") || contains(msg, "not exist")) 277 # break; 278 sys->fprint(sys->fildes(2), "updatelog: warning[old=%s]: %s\n", old, msg); 279 } 280} 281 282change(action: int, e: ref Entry, seq: big, d: Sys->Dir, path: string, serverpath: string, digests: list of string) 283{ 284 log := ref Entry; 285 log.seq = seq; 286 log.action = action; 287 log.d = d; 288 log.path = path; 289 log.serverpath = serverpath; 290 log.contents = digests; 291 e.update(log); 292 bout.puts(log.logtext()+"\n"); 293} 294 295samestat(a: Sys->Dir, b: Sys->Dir): int 296{ 297 # doesn't check permission/ownership, does check QTDIR/QTFILE 298 if(a.mode & Sys->DMDIR) 299 return (b.mode & Sys->DMDIR) != 0; 300 return a.length == b.length && a.mtime == b.mtime && a.qid.qtype == b.qid.qtype; # TO DO: a.name==b.name? 301} 302 303samemeta(a: Sys->Dir, b: Sys->Dir): int 304{ 305 return a.mode == b.mode && (uid == nil || a.uid == b.uid) && (gid == nil || a.gid == b.gid) && samestat(a, b); 306} 307 308nextseq(): big 309{ 310 return (big now << 32) | big gen++; 311} 312 313error(s: string) 314{ 315 sys->fprint(sys->fildes(2), "updatelog: %s\n", s); 316 raise "fail:error"; 317} 318 319note(s: string) 320{ 321 sys->fprint(sys->fildes(2), "updatelog: note: %s\n", s); 322} 323 324contains(s: string, sub: string): int 325{ 326 return str->splitstrl(s, sub).t1 != nil; 327} 328 329isprefix(a, b: string): int 330{ 331 la := len a; 332 lb := len b; 333 if(la > lb) 334 return 0; 335 if(la == lb) 336 return a == b; 337 return a == b[0:la] && b[la] == '/'; 338} 339 340trimpath(s: string): string 341{ 342 while(len s > 1 && s[len s-1] == '/') 343 s = s[0:len s-1]; 344 while(s != nil && s[0] == '/') 345 s = s[1:]; 346 return s; 347} 348 349relative(name: string, root: string): string 350{ 351 if(root == nil || name == nil) 352 return name; 353 if(isprefix(root, name)){ 354 name = name[len root:]; 355 while(name != nil && name[0] == '/') 356 name = name[1:]; 357 } 358 return name; 359} 360 361islisted(s: string, l: list of string): int 362{ 363 for(; l != nil; l = tl l) 364 if(isprefix(hd l, s)) 365 return 1; 366 return 0; 367} 368 369md5sum(file: string): string 370{ 371 fd := sys->open(file, Sys->OREAD); 372 if(fd == nil) 373 error(sys->sprint("can't open %s: %r", file)); 374 ds: ref Keyring->DigestState; 375 buf := array[Sys->ATOMICIO] of byte; 376 while((n := sys->read(fd, buf, len buf)) > 0) 377 ds = kr->md5(buf, n, nil, ds); 378 if(n < 0) 379 error(sys->sprint("error reading %s: %r", file)); 380 digest := array[Keyring->MD5dlen] of byte; 381 kr->md5(nil, 0, digest, ds); 382 s: string; 383 for(i := 0; i < len digest; i++) 384 s += sys->sprint("%.2ux", int digest[i]); 385 return s; 386} 387