forsyth20121205-174063a80cc13 years agoHistory
699 lines · 15.4 KB · Brainfuck
RawDownload
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.