Merge remote-tracking branch 'mvo/debian/sid' into debian/experimental-no-abi-break
[ntk/apt.git] / methods / rred.cc
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
8 #include <config.h>
9
10 #include <apt-pkg/fileutl.h>
11 #include <apt-pkg/mmap.h>
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>
16 #include <apt-pkg/configuration.h>
17
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>
27 #include <sys/stat.h>
28 #include <utime.h>
29
30 #include <apti18n.h>
31
32 #define BLOCK_SIZE (512*1024)
33
34 class 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 }
106 };
107
108 struct 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 }
146 }
147 }
148 };
149
150 class 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;
165 #else
166 return true;
167 #endif
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)
230 {
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;
249 }
250 }
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);
278
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());
289 }
290
291 size_t check_next_offset(size_t max)
292 {
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());
303 }
304 return max;
305 }
306
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 }
334
335 void left(void) {
336 assert(pos_is_okay());
337 where--;
338 pos -= where->offset + where->add_cnt;
339 assert(pos_is_okay());
340 }
341
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
350 class Patch {
351 FileChanges filechanges;
352 MemBlock add_text;
353
354 static bool retry_fwrite(char *b, size_t l, FILE *f, Hashes *hash)
355 {
356 size_t r = 1;
357 while (r > 0 && l > 0)
358 {
359 r = fwrite(b, 1, l, f);
360 if (hash)
361 hash->Add((unsigned char*)b, r);
362 l -= r;
363 b += r;
364 }
365 return l == 0;
366 }
367
368 static void dump_rest(FILE *o, FILE *i, Hashes *hash)
369 {
370 char buffer[BLOCK_SIZE];
371 size_t l;
372 while (0 < (l = fread(buffer, 1, sizeof(buffer), i))) {
373 if (!retry_fwrite(buffer, l, o, hash))
374 break;
375 }
376 }
377
378 static void dump_lines(FILE *o, FILE *i, size_t n, Hashes *hash)
379 {
380 char buffer[BLOCK_SIZE];
381 size_t l;
382 while (n > 0) {
383 if (fgets(buffer, sizeof(buffer), i) == 0)
384 buffer[0] = '\0';
385 l = strlen(buffer);
386 if (l == 0 || buffer[l-1] == '\n')
387 n--;
388 retry_fwrite(buffer, l, o, hash);
389 }
390 }
391
392 static void skip_lines(FILE *i, int n)
393 {
394 char buffer[BLOCK_SIZE];
395 size_t l;
396 while (n > 0) {
397 if (fgets(buffer, sizeof(buffer), i) == 0)
398 buffer[0] = '\0';
399 l = strlen(buffer);
400 if (l == 0 || buffer[l-1] == '\n')
401 n--;
402 }
403 }
404
405 static void dump_mem(FILE *o, char *p, size_t s, Hashes *hash) {
406 retry_fwrite(p, s, o, hash);
407 }
408
409 public:
410
411 void read_diff(FileFd &f)
412 {
413 char buffer[BLOCK_SIZE];
414 bool cmdwanted = true;
415
416 Change ch(0);
417 while(f.ReadLine(buffer, sizeof(buffer)))
418 {
419 if (cmdwanted) {
420 char *m, *c;
421 size_t s, e;
422 s = strtol(buffer, &m, 10);
423 if (m == buffer) {
424 s = e = ch.offset + ch.add_cnt;
425 c = buffer;
426 } else if (*m == ',') {
427 m++;
428 e = strtol(m, &c, 10);
429 } else {
430 e = s;
431 c = m;
432 }
433 switch(*c) {
434 case 'a':
435 cmdwanted = false;
436 ch.add = NULL;
437 ch.add_cnt = 0;
438 ch.add_len = 0;
439 ch.offset = s;
440 ch.del_cnt = 0;
441 break;
442 case 'c':
443 cmdwanted = false;
444 ch.add = NULL;
445 ch.add_cnt = 0;
446 ch.add_len = 0;
447 ch.offset = s - 1;
448 ch.del_cnt = e - s + 1;
449 break;
450 case 'd':
451 ch.offset = s - 1;
452 ch.del_cnt = e - s + 1;
453 ch.add = NULL;
454 ch.add_cnt = 0;
455 ch.add_len = 0;
456 filechanges.add_change(ch);
457 break;
458 }
459 } else { /* !cmdwanted */
460 if (buffer[0] == '.' && buffer[1] == '\n') {
461 cmdwanted = true;
462 filechanges.add_change(ch);
463 } else {
464 char *last = NULL;
465 char *add;
466 size_t l;
467 if (ch.add)
468 last = ch.add + ch.add_len;
469 l = strlen(buffer);
470 add = add_text.add_easy(buffer, l, last);
471 if (!add) {
472 ch.add_len += l;
473 ch.add_cnt++;
474 } else {
475 if (ch.add) {
476 filechanges.add_change(ch);
477 ch.del_cnt = 0;
478 }
479 ch.offset += ch.add_cnt;
480 ch.add = add;
481 ch.add_len = l;
482 ch.add_cnt = 1;
483 }
484 }
485 }
486 }
487 }
488
489 void write_diff(FILE *f)
490 {
491 size_t line = 0;
492 std::list<struct Change>::reverse_iterator ch;
493 for (ch = filechanges.rbegin(); ch != filechanges.rend(); ch++) {
494 line += ch->offset + ch->del_cnt;
495 }
496
497 for (ch = filechanges.rbegin(); ch != filechanges.rend(); ch++) {
498 std::list<struct Change>::reverse_iterator mg_i, mg_e = ch;
499 while (ch->del_cnt == 0 && ch->offset == 0)
500 ch++;
501 line -= ch->del_cnt;
502 if (ch->add_cnt > 0) {
503 if (ch->del_cnt == 0) {
504 fprintf(f, "%lua\n", line);
505 } else if (ch->del_cnt == 1) {
506 fprintf(f, "%luc\n", line+1);
507 } else {
508 fprintf(f, "%lu,%luc\n", line+1, line+ch->del_cnt);
509 }
510
511 mg_i = ch;
512 do {
513 dump_mem(f, mg_i->add, mg_i->add_len, NULL);
514 } while (mg_i-- != mg_e);
515
516 fprintf(f, ".\n");
517 } else if (ch->del_cnt == 1) {
518 fprintf(f, "%lud\n", line+1);
519 } else if (ch->del_cnt > 1) {
520 fprintf(f, "%lu,%lud\n", line+1, line+ch->del_cnt);
521 }
522 line -= ch->offset;
523 }
524 }
525
526 void apply_against_file(FILE *out, FILE *in, Hashes *hash = NULL)
527 {
528 std::list<struct Change>::iterator ch;
529 for (ch = filechanges.begin(); ch != filechanges.end(); ch++) {
530 dump_lines(out, in, ch->offset, hash);
531 skip_lines(in, ch->del_cnt);
532 dump_mem(out, ch->add, ch->add_len, hash);
533 }
534 dump_rest(out, in, hash);
535 }
536 };
537
538 class RredMethod : public pkgAcqMethod {
539 private:
540 bool Debug;
541
542 protected:
543 virtual bool Fetch(FetchItem *Itm) {
544 Debug = _config->FindB("Debug::pkgAcquire::RRed", false);
545 URI Get = Itm->Uri;
546 std::string Path = Get.Host + Get.Path; // rred:/path - no host
547
548 FetchResult Res;
549 Res.Filename = Itm->DestFile;
550 if (Itm->Uri.empty())
551 {
552 Path = Itm->DestFile;
553 Itm->DestFile.append(".result");
554 } else
555 URIStart(Res);
556
557 std::vector<std::string> patchpaths;
558 Patch patch;
559
560 if (FileExists(Path + ".ed") == true)
561 patchpaths.push_back(Path + ".ed");
562 else
563 {
564 _error->PushToStack();
565 std::vector<std::string> patches = GetListOfFilesInDir(flNotFile(Path), "gz", true, false);
566 _error->RevertToStack();
567
568 std::string const baseName = Path + ".ed.";
569 for (std::vector<std::string>::const_iterator p = patches.begin();
570 p != patches.end(); ++p)
571 if (p->compare(0, baseName.length(), baseName) == 0)
572 patchpaths.push_back(*p);
573 }
574
575 std::string patch_name;
576 for (std::vector<std::string>::iterator I = patchpaths.begin();
577 I != patchpaths.end();
578 I++)
579 {
580 patch_name = *I;
581 if (Debug == true)
582 std::clog << "Patching " << Path << " with " << patch_name
583 << std::endl;
584
585 FileFd p;
586 // all patches are compressed, even if the name doesn't reflect it
587 if (p.Open(patch_name, FileFd::ReadOnly, FileFd::Gzip) == false) {
588 std::cerr << "Could not open patch file " << patch_name << std::endl;
589 _error->DumpErrors(std::cerr);
590 abort();
591 }
592 patch.read_diff(p);
593 p.Close();
594 }
595
596 if (Debug == true)
597 std::clog << "Applying patches against " << Path
598 << " and writing results to " << Itm->DestFile
599 << std::endl;
600
601 FILE *inp = fopen(Path.c_str(), "r");
602 FILE *out = fopen(Itm->DestFile.c_str(), "w");
603
604 Hashes hash;
605
606 patch.apply_against_file(out, inp, &hash);
607
608 fclose(out);
609 fclose(inp);
610
611 if (Debug == true) {
612 std::clog << "rred: finished file patching of " << Path << "." << std::endl;
613 }
614
615 struct stat bufbase, bufpatch;
616 if (stat(Path.c_str(), &bufbase) != 0 ||
617 stat(patch_name.c_str(), &bufpatch) != 0)
618 return _error->Errno("stat", _("Failed to stat"));
619
620 struct utimbuf timebuf;
621 timebuf.actime = bufbase.st_atime;
622 timebuf.modtime = bufpatch.st_mtime;
623 if (utime(Itm->DestFile.c_str(), &timebuf) != 0)
624 return _error->Errno("utime", _("Failed to set modification time"));
625
626 if (stat(Itm->DestFile.c_str(), &bufbase) != 0)
627 return _error->Errno("stat", _("Failed to stat"));
628
629 Res.LastModified = bufbase.st_mtime;
630 Res.Size = bufbase.st_size;
631 Res.TakeHashes(hash);
632 URIDone(Res);
633
634 return true;
635 }
636
637 public:
638 RredMethod() : pkgAcqMethod("2.0",SingleInstance | SendConfig) {}
639 };
640
641 int main(int argc, char **argv)
642 {
643 int i;
644 bool just_diff = true;
645 Patch patch;
646
647 if (argc <= 1) {
648 RredMethod Mth;
649 return Mth.Run();
650 }
651
652 if (argc > 1 && strcmp(argv[1], "-f") == 0) {
653 just_diff = false;
654 i = 2;
655 } else {
656 i = 1;
657 }
658
659 for (; i < argc; i++) {
660 FileFd p;
661 if (p.Open(argv[i], FileFd::ReadOnly) == false) {
662 _error->DumpErrors(std::cerr);
663 exit(1);
664 }
665 patch.read_diff(p);
666 }
667
668 if (just_diff) {
669 patch.write_diff(stdout);
670 } else {
671 FILE *out, *inp;
672 out = stdout;
673 inp = stdin;
674
675 patch.apply_against_file(out, inp);
676 }
677 return 0;
678 }