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 logupdates: 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)Lines 1–500 of 699. Load more
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)