xref: /inferno-os/appl/cmd/install/mergelog.b (revision 37da2899f40661e3e9631e497da8dc59b971cbd0)
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