integrate Anthonys rred with POC for client-side merge
[ntk/apt.git] / methods / rred.cc
CommitLineData
dbd5418b
AT
1// Copyright (c) 2014 Anthony Towns
2//
3// This program is free software; you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation; either version 2 of the License, or
6// (at your option) any later version.
7
ea542140
DK
8#include <config.h>
9
2e178d1c 10#include <apt-pkg/fileutl.h>
bb1293d9 11#include <apt-pkg/mmap.h>
2e178d1c
MV
12#include <apt-pkg/error.h>
13#include <apt-pkg/acquire-method.h>
14#include <apt-pkg/strutl.h>
15#include <apt-pkg/hashes.h>
472ff00e 16#include <apt-pkg/configuration.h>
2e178d1c 17
dbd5418b
AT
18#include <string>
19#include <list>
20#include <vector>
21#include <iterator>
22
23#include <assert.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
2e178d1c 27#include <sys/stat.h>
2e178d1c 28#include <utime.h>
dbd5418b 29
2e178d1c 30#include <apti18n.h>
dbd5418b
AT
31
32#define BLOCK_SIZE (512*1024)
33
34class MemBlock {
35 char *start;
36 size_t size;
37 char *free;
38 struct MemBlock *next;
39
40 MemBlock(size_t size)
41 {
42 free = start = new char[size];
43 size = size;
44 next = NULL;
45 }
46
47 size_t avail(void) { return size - (free - start); }
48
49 public:
50
51 MemBlock(void) {
52 free = start = new char[BLOCK_SIZE];
53 size = BLOCK_SIZE;
54 next = NULL;
55 }
56
57 ~MemBlock() {
58 delete [] start;
59 delete next;
60 }
61
62 void clear(void) {
63 free = start;
64 if (next)
65 next->clear();
66 }
67
68 char *add_easy(char *src, size_t len, char *last)
69 {
70 if (last) {
71 for (MemBlock *k = this; k; k = k->next) {
72 if (k->free == last) {
73 if (len <= k->avail()) {
74 char *n = k->add(src, len);
75 assert(last == n);
76 if (last == n)
77 return NULL;
78 return n;
79 } else {
80 break;
81 }
82 } else if (last >= start && last < free) {
83 break;
84 }
85 }
86 }
87 return add(src, len);
88 }
89
90 char *add(char *src, size_t len) {
91 if (len > avail()) {
92 if (!next) {
93 if (len > BLOCK_SIZE) {
94 next = new MemBlock(len);
95 } else {
96 next = new MemBlock;
97 }
98 }
99 return next->add(src, len);
100 }
101 char *dst = free;
102 free += len;
103 memcpy(dst, src, len);
104 return dst;
105 }
2e178d1c 106};
dbd5418b
AT
107
108struct Change {
109 /* Ordering:
110 *
111 * 1. write out <offset> lines unchanged
112 * 2. skip <del_cnt> lines from source
113 * 3. write out <add_cnt> lines (<add>/<add_len>)
114 */
115 size_t offset;
116 size_t del_cnt;
117 size_t add_cnt; /* lines */
118 size_t add_len; /* bytes */
119 char *add;
120
121 Change(int off)
122 {
123 offset = off;
124 del_cnt = add_cnt = add_len = 0;
125 add = NULL;
126 }
127
128 /* actually, don't write <lines> lines from <add> */
129 void skip_lines(size_t lines)
130 {
131 while (lines > 0) {
132 char *s = (char*) memchr(add, '\n', add_len);
133 assert(s != NULL);
134 s++;
135 add_len -= (s - add);
136 add_cnt--;
137 lines--;
138 if (add_len == 0) {
139 add = NULL;
140 assert(add_cnt == 0);
141 assert(lines == 0);
142 } else {
143 add = s;
144 assert(add_cnt > 0);
145 }
d84cd865
MV
146 }
147 }
bb1293d9 148};
dbd5418b
AT
149
150class FileChanges {
151 std::list<struct Change> changes;
152 std::list<struct Change>::iterator where;
153 size_t pos; // line number is as far left of iterator as possible
154
155 bool pos_is_okay(void)
156 {
157#ifdef POSDEBUG
158 size_t cpos = 0;
159 std::list<struct Change>::iterator x;
160 for (x = changes.begin(); x != where; x++) {
161 assert(x != changes.end());
162 cpos += x->offset + x->add_cnt;
163 }
164 return cpos == pos;
bb1293d9 165#else
dbd5418b 166 return true;
bb1293d9 167#endif
dbd5418b
AT
168 }
169
170 public:
171 FileChanges() {
172 where = changes.end();
173 pos = 0;
174 }
175
176 std::list<struct Change>::iterator begin(void) { return changes.begin(); }
177 std::list<struct Change>::iterator end(void) { return changes.end(); }
178
179 std::list<struct Change>::reverse_iterator rbegin(void) { return changes.rbegin(); }
180 std::list<struct Change>::reverse_iterator rend(void) { return changes.rend(); }
181
182 void add_change(Change c) {
183 assert(pos_is_okay());
184 go_to_change_for(c.offset);
185 assert(pos + where->offset == c.offset);
186 if (c.del_cnt > 0)
187 delete_lines(c.del_cnt);
188 assert(pos + where->offset == c.offset);
189 if (c.add_len > 0) {
190 assert(pos_is_okay());
191 if (where->add_len > 0)
192 new_change();
193 assert(where->add_len == 0 && where->add_cnt == 0);
194
195 where->add_len = c.add_len;
196 where->add_cnt = c.add_cnt;
197 where->add = c.add;
198 }
199 assert(pos_is_okay());
200 merge();
201 assert(pos_is_okay());
202 }
203
204 private:
205 void merge(void)
206 {
207 while (where->offset == 0 && where != changes.begin()) {
208 left();
209 }
210 std::list<struct Change>::iterator next = where;
211 next++;
212
213 while (next != changes.end() && next->offset == 0) {
214 where->del_cnt += next->del_cnt;
215 next->del_cnt = 0;
216 if (next->add == NULL) {
217 next = changes.erase(next);
218 } else if (where->add == NULL) {
219 where->add = next->add;
220 where->add_len = next->add_len;
221 where->add_cnt = next->add_cnt;
222 next = changes.erase(next);
223 } else {
224 next++;
225 }
226 }
227 }
228
229 void go_to_change_for(size_t line)
47d2bc78 230 {
dbd5418b
AT
231 while(where != changes.end()) {
232 if (line < pos) {
233 left();
234 continue;
235 }
236 if (pos + where->offset + where->add_cnt <= line) {
237 right();
238 continue;
239 }
240 // line is somewhere in this slot
241 if (line < pos + where->offset) {
242 break;
243 } else if (line == pos + where->offset) {
244 return;
245 } else {
246 split(line - pos);
247 right();
248 return;
47d2bc78 249 }
bb1293d9 250 }
dbd5418b
AT
251 /* it goes before this patch */
252 insert(line-pos);
253 }
254
255 void new_change(void) { insert(where->offset); }
256
257 void insert(size_t offset)
258 {
259 assert(pos_is_okay());
260 assert(where == changes.end() || offset <= where->offset);
261 if (where != changes.end())
262 where->offset -= offset;
263 changes.insert(where, Change(offset));
264 where--;
265 assert(pos_is_okay());
266 }
267
268 void split(size_t offset)
269 {
270 assert(pos_is_okay());
271
272 assert(where->offset < offset);
273 assert(offset < where->offset + where->add_cnt);
274
275 size_t keep_lines = offset - where->offset;
276
277 Change before(*where);
47d2bc78 278
dbd5418b
AT
279 where->del_cnt = 0;
280 where->offset = 0;
281 where->skip_lines(keep_lines);
282
283 before.add_cnt = keep_lines;
284 before.add_len -= where->add_len;
285
286 changes.insert(where, before);
287 where--;
288 assert(pos_is_okay());
3de9ff77 289 }
dbd5418b
AT
290
291 size_t check_next_offset(size_t max)
47d2bc78 292 {
dbd5418b
AT
293 assert(pos_is_okay());
294 if (max > 0)
295 {
296 where++;
297 if (where != changes.end()) {
298 if (where->offset < max)
299 max = where->offset;
300 }
301 where--;
302 assert(pos_is_okay());
47d2bc78 303 }
dbd5418b
AT
304 return max;
305 }
3de9ff77 306
dbd5418b
AT
307 void delete_lines(size_t cnt)
308 {
309 std::list<struct Change>::iterator x = where;
310 assert(pos_is_okay());
311 while (cnt > 0)
312 {
313 size_t del;
314 del = x->add_cnt;
315 if (del > cnt)
316 del = cnt;
317 x->skip_lines(del);
318 cnt -= del;
319
320 x++;
321 if (x == changes.end()) {
322 del = cnt;
323 } else {
324 del = x->offset;
325 if (del > cnt)
326 del = cnt;
327 x->offset -= del;
328 }
329 where->del_cnt += del;
330 cnt -= del;
331 }
332 assert(pos_is_okay());
333 }
47d2bc78 334
dbd5418b
AT
335 void left(void) {
336 assert(pos_is_okay());
337 where--;
338 pos -= where->offset + where->add_cnt;
339 assert(pos_is_okay());
340 }
47d2bc78 341
dbd5418b
AT
342 void right(void) {
343 assert(pos_is_okay());
344 pos += where->offset + where->add_cnt;
345 where++;
346 assert(pos_is_okay());
347 }
348};
349
350class Patch {
351 FileChanges filechanges;
352 MemBlock add_text;
353
354 static void dump_rest(FILE *o, FILE *i, Hashes *hash)
355 {
356 char buffer[BLOCK_SIZE];
357 size_t l;
358 while (0 < (l = fread(buffer, 1, sizeof(buffer), i))) {
359 fwrite(buffer, 1, l, o);
360 if (hash)
361 hash->Add((unsigned char*)buffer, l);
362 }
363 }
364
365 static void dump_lines(FILE *o, FILE *i, size_t n, Hashes *hash)
366 {
367 char buffer[BLOCK_SIZE];
368 size_t l;
369 while (n > 0) {
370 if (fgets(buffer, sizeof(buffer), i) == 0)
371 buffer[0] = '\0';
372 l = strlen(buffer);
373 if (l == 0 || buffer[l-1] == '\n')
374 n--;
375 fwrite(buffer, 1, l, o);
376
377 if (hash)
378 hash->Add((unsigned char*)buffer, l);
379 }
380 }
381
382 static void skip_lines(FILE *i, int n)
383 {
384 char buffer[BLOCK_SIZE];
385 size_t l;
386 while (n > 0) {
387 if (fgets(buffer, sizeof(buffer), i) == 0)
388 buffer[0] = '\0';
389 l = strlen(buffer);
390 if (l == 0 || buffer[l-1] == '\n')
391 n--;
392 }
393 }
394
395 static bool dump_mem(FILE *o, char *p, size_t s, Hashes *hash) {
396 size_t r;
397 while (s > 0) {
398 r = fwrite(p, 1, s, o);
399 if (hash)
400 hash->Add((unsigned char*)p, s);
401 s -= r;
402 p += r;
403 if (r == 0) return false;
404 }
405 return true;
406 }
407
408 public:
409
50bd6fd3 410 void read_diff(FileFd &f)
dbd5418b
AT
411 {
412 char buffer[BLOCK_SIZE];
413 bool cmdwanted = true;
414
415 Change ch(0);
50bd6fd3 416 while(f.ReadLine(buffer, sizeof(buffer)))
dbd5418b
AT
417 {
418 if (cmdwanted) {
419 char *m, *c;
420 size_t s, e;
421 s = strtol(buffer, &m, 10);
422 if (m == buffer) {
423 s = e = ch.offset + ch.add_cnt;
424 c = buffer;
425 } else if (*m == ',') {
426 m++;
427 e = strtol(m, &c, 10);
428 } else {
429 e = s;
430 c = m;
431 }
432 switch(*c) {
433 case 'a':
434 cmdwanted = false;
435 ch.add = NULL;
436 ch.add_cnt = 0;
437 ch.add_len = 0;
438 ch.offset = s;
439 ch.del_cnt = 0;
440 break;
441 case 'c':
442 cmdwanted = false;
443 ch.add = NULL;
444 ch.add_cnt = 0;
445 ch.add_len = 0;
446 ch.offset = s - 1;
447 ch.del_cnt = e - s + 1;
448 break;
449 case 'd':
450 ch.offset = s - 1;
451 ch.del_cnt = e - s + 1;
452 ch.add = NULL;
453 ch.add_cnt = 0;
454 ch.add_len = 0;
455 filechanges.add_change(ch);
456 break;
457 }
458 } else { /* !cmdwaanted */
459 if (buffer[0] == '.' && buffer[1] == '\n') {
460 cmdwanted = true;
461 filechanges.add_change(ch);
462 } else {
463 char *last = NULL;
464 char *add;
465 size_t l;
466 if (ch.add)
467 last = ch.add + ch.add_len;
468 l = strlen(buffer);
469 add = add_text.add_easy(buffer, l, last);
470 if (!add) {
471 ch.add_len += l;
472 ch.add_cnt++;
473 } else {
474 if (ch.add) {
475 filechanges.add_change(ch);
476 ch.del_cnt = 0;
477 }
478 ch.offset += ch.add_cnt;
479 ch.add = add;
480 ch.add_len = l;
481 ch.add_cnt = 1;
482 }
483 }
484 }
485 }
486 }
487
488 void write_diff(FILE *f)
489 {
490 size_t line = 0;
491 std::list<struct Change>::reverse_iterator ch;
492 for (ch = filechanges.rbegin(); ch != filechanges.rend(); ch++) {
493 line += ch->offset + ch->del_cnt;
494 }
495
496 for (ch = filechanges.rbegin(); ch != filechanges.rend(); ch++) {
497 std::list<struct Change>::reverse_iterator mg_i, mg_e = ch;
498 while (ch->del_cnt == 0 && ch->offset == 0)
499 ch++;
500 line -= ch->del_cnt;
501 if (ch->add_cnt > 0) {
502 if (ch->del_cnt == 0) {
503 fprintf(f, "%lua\n", line);
504 } else if (ch->del_cnt == 1) {
505 fprintf(f, "%luc\n", line+1);
506 } else {
507 fprintf(f, "%lu,%luc\n", line+1, line+ch->del_cnt);
508 }
509
510 mg_i = ch;
511 do {
512 dump_mem(f, mg_i->add, mg_i->add_len, NULL);
513 } while (mg_i-- != mg_e);
514
515 fprintf(f, ".\n");
516 } else if (ch->del_cnt == 1) {
517 fprintf(f, "%lud\n", line+1);
518 } else if (ch->del_cnt > 1) {
519 fprintf(f, "%lu,%lud\n", line+1, line+ch->del_cnt);
520 }
521 line -= ch->offset;
522 }
523 }
47d2bc78 524
dbd5418b
AT
525 void apply_against_file(FILE *out, FILE *in, Hashes *hash = NULL)
526 {
527 std::list<struct Change>::iterator ch;
528 for (ch = filechanges.begin(); ch != filechanges.end(); ch++) {
529 dump_lines(out, in, ch->offset, hash);
530 skip_lines(in, ch->del_cnt);
531 dump_mem(out, ch->add, ch->add_len, hash);
532 }
533 dump_rest(out, in, hash);
47d2bc78 534 }
dbd5418b
AT
535};
536
dbd5418b
AT
537class RredMethod : public pkgAcqMethod {
538 private:
539 bool Debug;
dbd5418b
AT
540
541 protected:
dbd5418b
AT
542 virtual bool Fetch(FetchItem *Itm) {
543 Debug = _config->FindB("Debug::pkgAcquire::RRed", false);
544 URI Get = Itm->Uri;
545 std::string Path = Get.Host + Get.Path; // rred:/path - no host
546
547 FetchResult Res;
548 Res.Filename = Itm->DestFile;
50bd6fd3
DK
549 if (Itm->Uri.empty())
550 {
dbd5418b
AT
551 Path = Itm->DestFile;
552 Itm->DestFile.append(".result");
553 } else
554 URIStart(Res);
555
50bd6fd3 556 std::vector<std::string> patchpaths;
dbd5418b
AT
557 Patch patch;
558
50bd6fd3 559 if (FileExists(Path + ".ed") == true)
dbd5418b 560 patchpaths.push_back(Path + ".ed");
50bd6fd3
DK
561 else
562 {
563 _error->PushToStack();
564 std::vector<std::string> patches = GetListOfFilesInDir(flNotFile(Path), "gz", true, false);
565 _error->RevertToStack();
566
567 std::string const baseName = Path + ".ed.";
568 for (std::vector<std::string>::const_iterator p = patches.begin();
569 p != patches.end(); ++p)
570 if (p->compare(0, baseName.length(), baseName) == 0)
571 patchpaths.push_back(*p);
dbd5418b
AT
572 }
573
574 std::string patch_name;
575 for (std::vector<std::string>::iterator I = patchpaths.begin();
576 I != patchpaths.end();
577 I++)
578 {
579 patch_name = *I;
580 if (Debug == true)
581 std::clog << "Patching " << Path << " with " << patch_name
582 << std::endl;
583
50bd6fd3
DK
584 FileFd p;
585 // all patches are compressed, even if the name doesn't reflect it
586 if (p.Open(patch_name, FileFd::ReadOnly, FileFd::Gzip) == false) {
587 std::cerr << "Could not open patch file " << patch_name << std::endl;
588 _error->DumpErrors(std::cerr);
dbd5418b
AT
589 abort();
590 }
591 patch.read_diff(p);
50bd6fd3 592 p.Close();
dbd5418b
AT
593 }
594
595 if (Debug == true)
596 std::clog << "Applying patches against " << Path
597 << " and writing results to " << Itm->DestFile
598 << std::endl;
599
600 FILE *inp = fopen(Path.c_str(), "r");
601 FILE *out = fopen(Itm->DestFile.c_str(), "w");
602
603 Hashes hash;
604
605 patch.apply_against_file(out, inp, &hash);
606
607 fclose(out);
608 fclose(inp);
609
610 if (Debug == true) {
611 std::clog << "rred: finished file patching of " << Path << "." << std::endl;
612 }
613
614 struct stat bufbase, bufpatch;
615 if (stat(Path.c_str(), &bufbase) != 0 ||
616 stat(patch_name.c_str(), &bufpatch) != 0)
617 return _error->Errno("stat", _("Failed to stat"));
618
619 struct utimbuf timebuf;
620 timebuf.actime = bufbase.st_atime;
621 timebuf.modtime = bufpatch.st_mtime;
622 if (utime(Itm->DestFile.c_str(), &timebuf) != 0)
623 return _error->Errno("utime", _("Failed to set modification time"));
624
625 if (stat(Itm->DestFile.c_str(), &bufbase) != 0)
626 return _error->Errno("stat", _("Failed to stat"));
627
628 Res.LastModified = bufbase.st_mtime;
629 Res.Size = bufbase.st_size;
630 Res.TakeHashes(hash);
631 URIDone(Res);
632
633 return true;
634 }
635
636 public:
637 RredMethod() : pkgAcqMethod("2.0",SingleInstance | SendConfig) {}
bb1293d9 638};
dbd5418b
AT
639
640int main(int argc, char **argv)
641{
642 int i;
643 bool just_diff = true;
644 Patch patch;
645
646 if (argc <= 1) {
647 RredMethod Mth;
648 return Mth.Run();
649 }
650
651 if (argc > 1 && strcmp(argv[1], "-f") == 0) {
652 just_diff = false;
653 i = 2;
654 } else {
655 i = 1;
656 }
657
658 for (; i < argc; i++) {
50bd6fd3
DK
659 FileFd p;
660 if (p.Open(argv[i], FileFd::ReadOnly) == false) {
661 _error->DumpErrors(std::cerr);
dbd5418b
AT
662 exit(1);
663 }
664 patch.read_diff(p);
665 }
666
667 if (just_diff) {
668 patch.write_diff(stdout);
669 } else {
670 FILE *out, *inp;
671 out = stdout;
672 inp = stdin;
673
674 patch.apply_against_file(out, inp);
675 }
676 return 0;
2e178d1c 677}