WIP sha256 support
[jigdo.git] / src / mkimage.cc
1 /* $Id: mkimage.cc,v 1.15 2005/07/09 19:14:46 atterer Exp $ -*- C++ -*-
2   __   _
3   |_) /|  Copyright (C) 2001-2003  |  richard@
4   | \/¯|  Richard Atterer          |  atterer.org
5   ¯ '` ¯
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License, version 2. See
8   the file COPYING for details.
9
10   Create image from template / merge new files into image.tmp
11
12 */
13
14 #include <config.h>
15
16 #include <errno.h>
17 #include <stdio.h>
18 #include <string.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <unistd-jigdo.h>
22
23 #include <iomanip>
24 #include <iostream>
25 #include <fstream>
26 #include <memory>
27
28 #include <compat.hh>
29 #include <log.hh>
30 #include <mkimage.hh>
31 #include <scan.hh>
32 #include <serialize.hh>
33 #include <string.hh>
34 #include <zstream-gz.hh>
35
36 //______________________________________________________________________
37
38 DEBUG_UNIT("make-image")
39
40 namespace {
41
42 typedef JigdoDesc::ProgressReporter ProgressReporter;
43
44 // memset() is not portable enough...
45 void memClear(byte* buf, size_t size) {
46   while (size > 8) {
47     *buf++ = 0; *buf++ = 0; *buf++ = 0; *buf++ = 0;
48     *buf++ = 0; *buf++ = 0; *buf++ = 0; *buf++ = 0;
49     size -= 8;
50   }
51   while (size > 0) {
52     *buf++ = 0;
53     --size;
54   }
55 }
56
57 } // local namespace
58 //______________________________________________________________________
59
60 JigdoDesc::~JigdoDesc() { }
61 //______________________________________________________________________
62
63 bool JigdoDesc::isTemplate(bistream& file) {
64   if (!file.seekg(0, ios::beg)) return false;
65   string l;
66   getline(file, l); // "JigsawDownload template 1.0 jigdo-file/0.0.1"
67   string templHdr = TEMPLATE_HDR;
68   if (compat_compare(l, 0, templHdr.length(), templHdr) != 0) return false;
69   getline(file, l); // Ignore comment line
70   getline(file, l); // Empty line, except for CR
71   if (l != "\r") return false;
72   return true;
73 }
74 //______________________________________________________________________
75
76 void JigdoDesc::seekFromEnd(bistream& file) {
77   file.seekg(-6, ios::end);
78   debug("JigdoDesc::seekFromEnd0: now at file offset %1",
79         static_cast<uint64>(file.tellg()));
80   uint64 descLen;
81   SerialIstreamIterator f(file);
82   unserialize6(descLen, f);
83   if (static_cast<uint64>(file.tellg()) < descLen) { // Is this cast correct?
84     debug("JigdoDesc::seekFromEnd1 descLen=%1", descLen);
85     throw JigdoDescError(_("Invalid template data - corrupted file?"));
86   }
87
88   file.seekg(-descLen, ios::end);
89   debug("JigdoDesc::seekFromEnd2: now at file offset %1",
90         static_cast<uint64>(file.tellg()));
91
92   size_t toRead = 4;
93   byte buf[4];
94   buf[3] = '\0';
95   byte* b = buf;
96   do {
97     readBytes(file, b, toRead);
98     size_t n = file.gcount();
99     debug("JigdoDesc::seekFromEnd3: read %1, now at file offset %2",
100           n, static_cast<uint64>(file.tellg()));
101     //cerr<<"read "<<n<<' '<<file.tellg()<<endl;
102     b += n;
103     toRead -= n;
104   } while (file.good() && toRead > 0);
105   if (buf[0] != 'D' || buf[1] != 'E' || buf[2] != 'S' || buf[3] != 'C') {
106     debug("JigdoDesc::seekFromEnd4 %1 %2 %3 %4",
107           int(buf[0]), int(buf[1]), int(buf[2]), int(buf[3]));
108     throw JigdoDescError(_("Invalid template data - corrupted file?"));
109   }
110 }
111 //______________________________________________________________________
112
113 bistream& JigdoDescVec::get(bistream& file) {
114   /* Need auto_ptr: If we did a direct push_back(new JigdoDesc), the
115      "new" might succeed, but the push_back() fail with bad_alloc =>
116      mem leak */
117   auto_ptr<JigdoDesc> desc;
118   clear();
119
120   SerialIstreamIterator f(file);
121   uint64 len;
122   unserialize6(len, f); // descLen - 16, i.e. length of entries
123   if (len < 45 || len > 256*1024*1024) {
124     debug("JigdoDescVec::get: DESC section too small/large");
125     throw JigdoDescError(_("Invalid template data - corrupted file?"));
126   }
127   len -= 16;
128   //____________________
129
130   uint64 off = 0; // Offset in image
131   uint64 read = 0; // Nr of bytes read
132   MD5 entryMd5;
133   SHA256 entrySha256;
134   uint64 entryLen;
135   RsyncSum64 rsum;
136   size_t blockLength;
137   while (file && read < len) {
138     byte type = *f;
139     ++f;
140     switch (type) {
141
142     case JigdoDesc::IMAGE_INFO:
143       unserialize6(entryLen, f);
144       unserialize(entryMd5, f);
145       unserialize4(blockLength, f);
146       if (!file) break;
147       debug("JigdoDesc::read: ImageInfo %1 %2",
148             entryLen, entryMd5.toString());
149       desc.reset(new JigdoDesc::ImageInfo(entryLen, entryMd5, blockLength));
150       push_back(desc.release());
151       read += 1 + 6 + entryMd5.serialSizeOf() + 4;
152       break;
153
154     case JigdoDesc::IMAGE_INFO_SHA256:
155       unserialize6(entryLen, f);
156       unserialize(entrySha256, f);
157       unserialize4(blockLength, f);
158       if (!file) break;
159       debug("JigdoDesc::read: ImageInfo %1 %2",
160             entryLen, entrySha256.toString());
161       desc.reset(new JigdoDesc::ImageInfoSHA256(entryLen, entrySha256, blockLength));
162       push_back(desc.release());
163       read += 1 + 6 + entrySha256.serialSizeOf() + 4;
164       break;
165
166     case JigdoDesc::UNMATCHED_DATA:
167       unserialize6(entryLen, f);
168       if (!file) break;
169       debug("JigdoDesc::read: %1 UnmatchedData %2", off, entryLen);
170       desc.reset(new JigdoDesc::UnmatchedData(off, entryLen));
171       push_back(desc.release());
172       read += 1 + 6;
173       off += entryLen;
174       break;
175
176     case JigdoDesc::MATCHED_FILE:
177     case JigdoDesc::WRITTEN_FILE:
178       unserialize6(entryLen, f);
179       unserialize(rsum, f);
180       unserialize(entryMd5, f);
181       if (!file) break;
182       debug("JigdoDesc::read: %1 %2File %3 %4",
183             off, (type == JigdoDesc::MATCHED_FILE ? "Matched" : "Written"),
184             entryLen, entryMd5.toString());
185       if (type == JigdoDesc::MATCHED_FILE)
186         desc.reset(new JigdoDesc::MatchedFile(off, entryLen, rsum,entryMd5));
187       else
188         desc.reset(new JigdoDesc::WrittenFile(off, entryLen, rsum,entryMd5));
189       push_back(desc.release());
190       read += 1 + 6 + rsum.serialSizeOf() + entryMd5.serialSizeOf();
191       off += entryLen;
192       break;
193
194     case JigdoDesc::MATCHED_FILE_SHA256:
195     case JigdoDesc::WRITTEN_FILE_SHA256:
196       unserialize6(entryLen, f);
197       unserialize(rsum, f);
198       unserialize(entrySha256, f);
199       if (!file) break;
200       debug("JigdoDesc::read: %1 %2File %3 %4",
201             off, (type == JigdoDesc::MATCHED_FILE_SHA256 ? "Matched" : "Written"),
202             entryLen, entrySha256.toString());
203       if (type == JigdoDesc::MATCHED_FILE_SHA256)
204         desc.reset(new JigdoDesc::MatchedFileSHA256(off, entryLen, rsum,entrySha256));
205       else
206         desc.reset(new JigdoDesc::WrittenFileSHA256(off, entryLen, rsum,entrySha256));
207       push_back(desc.release());
208       read += 1 + 6 + rsum.serialSizeOf() + entrySha256.serialSizeOf();
209       off += entryLen;
210       break;
211
212       // Template entry types that were obsoleted with version 0.6.3:
213
214     case JigdoDesc::OBSOLETE_IMAGE_INFO:
215       unserialize6(entryLen, f);
216       unserialize(entryMd5, f);
217       if (!file) break;
218       debug("JigdoDesc::read: old ImageInfo %1 %2",
219             entryLen, entryMd5.toString());
220       // Special case: passing blockLength==0, which is otherwise impossible
221       desc.reset(new JigdoDesc::ImageInfo(entryLen, entryMd5, 0));
222       push_back(desc.release());
223       read += 1 + 6 + entryMd5.serialSizeOf();
224       break;
225
226     case JigdoDesc::OBSOLETE_MATCHED_FILE:
227     case JigdoDesc::OBSOLETE_WRITTEN_FILE:
228       unserialize6(entryLen, f);
229       unserialize(entryMd5, f);
230       if (!file) break;
231       debug("JigdoDesc::read: %1 old %2File %3 %4", off,
232             (type == JigdoDesc::OBSOLETE_MATCHED_FILE ? "Matched" :
233              "Written"), entryLen, entryMd5.toString());
234       /* Value of rsum is "don't care" because the ImageInfo's
235          blockLength will be zero. */
236       rsum.reset();
237       if (type == JigdoDesc::OBSOLETE_MATCHED_FILE)
238         desc.reset(new JigdoDesc::MatchedFile(off, entryLen, rsum,entryMd5));
239       else
240         desc.reset(new JigdoDesc::WrittenFile(off, entryLen, rsum,entryMd5));
241       push_back(desc.release());
242       read += 1 + 6 + entryMd5.serialSizeOf();
243       off += entryLen;
244       break;
245
246     default:
247       debug("JigdoDesc::read: unknown type %1", type);
248       throw JigdoDescError(_("Invalid template data - corrupted file?"));
249     }
250   }
251   //____________________
252
253   if (read < len) {
254     string err = subst(_("Error reading template data (%1)"),
255                        strerror(errno));
256     throw JigdoDescError(err);
257   }
258
259   if (empty())
260     throw JigdoDescError(_("Invalid template data - corrupted file?"));
261   JigdoDesc::ImageInfo* ii = dynamic_cast<JigdoDesc::ImageInfo*>(back());
262   if (ii == 0 || ii->size() != off) {
263     if (ii != 0) debug("JigdoDesc::read4: %1 != %2", ii->size(), off);
264     throw JigdoDescError(_("Invalid template data - corrupted file?"));
265   }
266   return file;
267 }
268 //______________________________________________________________________
269
270 bostream& JigdoDescVec::put(bostream& file, MD5Sum* md, SHA256Sum* sd) const {
271   // Pass 1: Accumulate sizes of entries, calculate descLen
272   // 4 for DESC, 6 each for length of part at start & end
273   uint64 descLen = 4 + 6*2; // Length of DESC part
274   unsigned bufLen = 4 + 6;
275   for (const_iterator i = begin(), e = end(); i != e; ++i) {
276     unsigned s = (unsigned)(*i)->serialSizeOf();
277     bufLen = max(bufLen, s);
278     descLen += s;
279   }
280   if (DEBUG) bufLen += 1;
281
282   // Pass 2: Write DESC part
283   byte buf[bufLen];
284   if (DEBUG) buf[bufLen - 1] = 0xa5;
285   byte* p;
286   p = serialize4(0x43534544, buf); // "DESC" in little-endian order
287   p = serialize6(descLen, p);
288   writeBytes(file, buf, 4 + 6);
289   if (md != 0)
290           md->update(buf, 4 + 6);
291   if (sd != 0)
292           sd->update(buf, 4 + 6);
293   for (const_iterator i = begin(), e = end(); i != e; ++i) {
294     JigdoDesc::ImageInfo* info;
295     JigdoDesc::UnmatchedData* unm;
296     JigdoDesc::MatchedFile* matched;
297     JigdoDesc::WrittenFile* written;
298     JigdoDesc::MatchedFileSHA256* matchedsha;
299     JigdoDesc::WrittenFileSHA256* writtensha;
300     /* NB we must first try to cast to WrittenFile, then to
301        MatchedFile, because WrittenFile derives from MatchedFile. */
302     if ((info = dynamic_cast<JigdoDesc::ImageInfo*>(*i)) != 0)
303       p = info->serialize(buf);
304     else if ((unm = dynamic_cast<JigdoDesc::UnmatchedData*>(*i)) != 0)
305       p = unm->serialize(buf);
306     else if ((written = dynamic_cast<JigdoDesc::WrittenFile*>(*i)) != 0)
307       p = written->serialize(buf);
308     else if ((matched = dynamic_cast<JigdoDesc::MatchedFile*>(*i)) != 0)
309       p = matched->serialize(buf);
310     else if ((writtensha = dynamic_cast<JigdoDesc::WrittenFileSHA256*>(*i)) != 0)
311       p = writtensha->serialize(buf);
312     else if ((matchedsha = dynamic_cast<JigdoDesc::MatchedFileSHA256*>(*i)) != 0)
313       p = matchedsha->serialize(buf);
314     else { Assert(false); continue; }
315     writeBytes(file, buf, p - buf);
316     if (md != 0)
317       md->update(buf, p - buf);
318     if (sd != 0)
319       sd->update(buf, p - buf);
320   }
321   p = serialize6(descLen, buf);
322   writeBytes(file, buf, 6);
323   if (md != 0)
324     md->update(buf, 6);
325   if (sd != 0)
326     sd->update(buf, p - buf);
327   if (DEBUG) { Assert(buf[bufLen - 1] == 0xa5); }
328     return file;
329 }
330 //______________________________________________________________________
331
332 namespace {
333   const int J_SIZE_WIDTH = 12;
334   const int J_CSUM_WIDTH = 46;
335   const int J_RSYNC_WIDTH = 12;
336 }
337
338 ostream& JigdoDesc::ImageInfo::put(ostream& s) const {
339   s << "image-info-md5    "
340     << setw(J_SIZE_WIDTH) << size()
341     << " " << setw(J_SIZE_WIDTH) << blockLength()
342     << " " << setw(J_CSUM_WIDTH) << md5()
343     << "\n";
344   return s;
345 }
346 ostream& JigdoDesc::ImageInfoSHA256::put(ostream& s) const {
347   s << "image-info-sha256 "
348     << setw(J_SIZE_WIDTH) << size()
349     << " " << setw(J_SIZE_WIDTH) << blockLength()
350     << " " << setw(J_CSUM_WIDTH) << sha256()
351     << "\n";
352   return s;
353 }
354 ostream& JigdoDesc::UnmatchedData::put(ostream& s) const {
355   s << "in-template       "
356     << setw(J_SIZE_WIDTH) << offset()
357     << " " << setw(J_SIZE_WIDTH) << size()
358     << "\n";
359   return s;
360 }
361 ostream& JigdoDesc::MatchedFile::put(ostream& s) const {
362   s << "need-file-md5     "
363     << setw(J_SIZE_WIDTH) << offset()
364     << " " << setw(J_SIZE_WIDTH) << size()
365     << " " << setw(J_CSUM_WIDTH) << md5()
366     << " " << setw(J_RSYNC_WIDTH) << rsync()
367     << "\n";
368   return s;
369 }
370 ostream& JigdoDesc::MatchedFileSHA256::put(ostream& s) const {
371   s << "need-file-sha256  "
372     << setw(J_SIZE_WIDTH) << offset()
373     << " " << setw(J_SIZE_WIDTH) << size()
374     << " " << setw(J_CSUM_WIDTH) << sha256()
375     << " " << setw(J_RSYNC_WIDTH) << rsync()
376     << "\n";
377   return s;
378 }
379
380 ostream& JigdoDesc::WrittenFile::put(ostream& s) const {
381   s << "have-file        "
382     << setw(J_SIZE_WIDTH) << offset()
383     << " " << setw(J_SIZE_WIDTH) << size()
384     << " " << setw(J_CSUM_WIDTH) << md5()
385     << " " << setw(J_RSYNC_WIDTH) << rsync()
386     << "\n";
387   return s;
388 }
389 ostream& JigdoDesc::WrittenFileSHA256::put(ostream& s) const {
390   s << "have-file-sha256 "
391     << setw(J_SIZE_WIDTH) << offset()
392     << " " << setw(J_SIZE_WIDTH) << size()
393     << " " << setw(J_CSUM_WIDTH) << sha256()
394     << " " << setw(J_RSYNC_WIDTH) << rsync()
395     << "\n";
396   return s;
397 }
398
399 void JigdoDescVec::list(ostream& s) throw() {
400   for (const_iterator i = begin(), e = end(); i != e; ++i) s << (**i);
401   s << flush;
402 }
403 //______________________________________________________________________
404
405 namespace {
406
407   /* Helper functions for makeImage below, declared inline if only
408      used once */
409
410   /// Type of operation when recreating image data
411   enum Task {
412     CREATE_TMP, // Create a new .tmp file and copy some files into it,
413                 // maybe rename at end
414     MERGE_TMP, // .tmp exists, copy over more files, maybe rename at end
415     SINGLE_PASS // single-pass, all or nothing; writing to stdout
416   };
417   //______________________________
418
419   inline void reportBytesWritten(const uint64 n, uint64& off,
420       uint64& nextReport, const uint64 totalBytes,
421       ProgressReporter& reporter) {
422     off += n;
423     if (off >= nextReport) { // Keep user entertained
424       reporter.writingImage(off, totalBytes, off, totalBytes);
425       nextReport += REPORT_INTERVAL;
426     }
427   }
428   //______________________________
429
430   /* Read up to file.size() of bytes from file, write it to image
431      stream. Check MD5/rsync sum if requested. Take care not to write
432      more than specified amount to image, even if file is longer. */
433   int fileToImageMD5(bostream* img, FilePart& file,
434       const JigdoDesc::MatchedFile& matched, bool checkChecksum, size_t rsyncLen,
435       ProgressReporter& reporter, byte* buf, size_t readAmount, uint64& off,
436       uint64& nextReport, const uint64 totalBytes) {
437     uint64 toWrite = file.size();
438     MD5Sum md;
439     RsyncSum64 rs;
440     size_t rl = 0; // Length covered by rs so far
441     string fileName(file.getPath());
442     fileName += file.leafName();
443     bifstream f(fileName.c_str(), ios::binary);
444     string err; // !err.empty() => error occurred
445
446     // Read from file, write to image
447     // First couple of k: Calculate RsyncSum rs and MD5Sum md
448     if (checkChecksum && rsyncLen > 0) {
449       while (*img && f && !f.eof() && toWrite > 0) {
450         size_t n = (toWrite < readAmount ? toWrite : readAmount);
451         readBytes(f, buf, n);
452         n = f.gcount();
453         writeBytes(*img, buf, n);
454         reportBytesWritten(n, off, nextReport, totalBytes, reporter);
455         toWrite -= n;
456         md.update(buf, n);
457         // Update RsyncSum
458         Paranoid(rl < rsyncLen);
459         size_t rsyncToAdd = rsyncLen - rl;
460         if (rsyncToAdd > n) rsyncToAdd = n;
461         rs.addBack(buf, rsyncToAdd);
462         rl += rsyncToAdd;
463         Paranoid(rl <= rsyncLen);
464         if (rl >= rsyncLen) break;
465       }
466     }
467     // Rest of file: Only calculate MD5Sum md
468     while (*img && f && !f.eof() && toWrite > 0) {
469       size_t n = (toWrite < readAmount ? toWrite : readAmount);
470       readBytes(f, buf, n);
471       n = f.gcount();
472       writeBytes(*img, buf, n);
473       reportBytesWritten(n, off, nextReport, totalBytes, reporter);
474       toWrite -= n;
475       if (checkChecksum) md.update(buf, n);
476     }
477
478     if (toWrite > 0 && (!f || f.eof())) {
479       const char* errDetail = "";
480       if (errno != 0) errDetail = strerror(errno);
481       else if (f.eof()) errDetail = _("file is too short");
482       err = subst(_("Error reading from `%1' (%2)"), fileName, errDetail);
483       // Even if there was an error - always try to write right amount
484       memClear(buf, readAmount);
485       while (*img && toWrite > 0) {
486         size_t n = (toWrite < readAmount ? toWrite : readAmount);
487         writeBytes(*img, buf, n);
488         reportBytesWritten(n, off, nextReport, totalBytes, reporter);
489         toWrite -= n;
490       }
491     } else if (checkChecksum
492                && (md.finish() != matched.md5()
493                    || (rsyncLen > 0 && rs != matched.rsync()))) {
494       err = subst(_("Error: `%1' does not match checksum in template data"),
495                   fileName);
496     }
497
498     if (err.empty()) return 0; // Success
499     reporter.error(err);
500     if (toWrite == 0)
501       return 2; // "May have to fix something before you can continue"
502     else
503       return 3; // Yaargh, disaster! Please delete the .tmp file for me
504   }
505   //______________________________
506
507   /* Read up to file.size() of bytes from file, write it to image
508      stream. Check MD5/rsync sum if requested. Take care not to write
509      more than specified amount to image, even if file is longer. */
510   int fileToImageSHA256(bostream* img, FilePart& file,
511       const JigdoDesc::MatchedFileSHA256& matched, bool checkChecksum, size_t rsyncLen,
512       ProgressReporter& reporter, byte* buf, size_t readAmount, uint64& off,
513       uint64& nextReport, const uint64 totalBytes) {
514     uint64 toWrite = file.size();
515     SHA256Sum md;
516     RsyncSum64 rs;
517     size_t rl = 0; // Length covered by rs so far
518     string fileName(file.getPath());
519     fileName += file.leafName();
520     bifstream f(fileName.c_str(), ios::binary);
521     string err; // !err.empty() => error occurred
522
523     // Read from file, write to image
524     // First couple of k: Calculate RsyncSum rs and MD5Sum md
525     if (checkChecksum && rsyncLen > 0) {
526       while (*img && f && !f.eof() && toWrite > 0) {
527         size_t n = (toWrite < readAmount ? toWrite : readAmount);
528         readBytes(f, buf, n);
529         n = f.gcount();
530         writeBytes(*img, buf, n);
531         reportBytesWritten(n, off, nextReport, totalBytes, reporter);
532         toWrite -= n;
533         md.update(buf, n);
534         // Update RsyncSum
535         Paranoid(rl < rsyncLen);
536         size_t rsyncToAdd = rsyncLen - rl;
537         if (rsyncToAdd > n) rsyncToAdd = n;
538         rs.addBack(buf, rsyncToAdd);
539         rl += rsyncToAdd;
540         Paranoid(rl <= rsyncLen);
541         if (rl >= rsyncLen) break;
542       }
543     }
544     // Rest of file: Only calculate MD5Sum md
545     while (*img && f && !f.eof() && toWrite > 0) {
546       size_t n = (toWrite < readAmount ? toWrite : readAmount);
547       readBytes(f, buf, n);
548       n = f.gcount();
549       writeBytes(*img, buf, n);
550       reportBytesWritten(n, off, nextReport, totalBytes, reporter);
551       toWrite -= n;
552       if (checkChecksum) md.update(buf, n);
553     }
554
555     if (toWrite > 0 && (!f || f.eof())) {
556       const char* errDetail = "";
557       if (errno != 0) errDetail = strerror(errno);
558       else if (f.eof()) errDetail = _("file is too short");
559       err = subst(_("Error reading from `%1' (%2)"), fileName, errDetail);
560       // Even if there was an error - always try to write right amount
561       memClear(buf, readAmount);
562       while (*img && toWrite > 0) {
563         size_t n = (toWrite < readAmount ? toWrite : readAmount);
564         writeBytes(*img, buf, n);
565         reportBytesWritten(n, off, nextReport, totalBytes, reporter);
566         toWrite -= n;
567       }
568     } else if (checkChecksum
569                && (md.finish() != matched.sha256()
570                    || (rsyncLen > 0 && rs != matched.rsync()))) {
571       err = subst(_("Error: `%1' does not match checksum in template data"),
572                   fileName);
573     }
574
575     if (err.empty()) return 0; // Success
576     reporter.error(err);
577     if (toWrite == 0)
578       return 2; // "May have to fix something before you can continue"
579     else
580       return 3; // Yaargh, disaster! Please delete the .tmp file for me
581   }
582   //______________________________
583
584   /* Write all bytes of the image data, i.e. both UnmatchedData and
585      MatchedFiles. If any UnmatchedFiles are present in 'files', write
586      zeroes instead of the file content and also append a DESC section
587      after the actual data.
588
589      Why does this write zeroes, and not simply seek() forward the
590      appropriate amount of bytes? - Because when seek() is used, a
591      sparse file might be generated. This could result in "No room on
592      device" later on - but we'd rather like that error as early as
593      possible.
594
595      @param name Filename corresponding to img
596      @param totalBytes length of image
597
598      if img==0, write to cout. If 0 is returned and not writing to
599      cout, caller should rename file to remove .tmp extension. */
600   inline int writeAll(const Task& task, JigdoDescVec& files,
601       queue<FilePart*>& toCopy, bistream* templ, const size_t readAmount,
602       bostream* img, const char* name, bool checkChecksum,
603       ProgressReporter& reporter, JigdoCache* cache,
604       const uint64 totalBytes) {
605
606     bool isTemplate = JigdoDesc::isTemplate(*templ); // seek to 1st DATA part
607     Assert(isTemplate);
608     int result = 0;
609     uint64 off = 0; // Current offset in image
610     uint64 nextReport = 0; // At what value of off to call reporter
611
612     vector<byte> bufVec(readAmount);
613     byte* buf = &bufVec[0];
614     /* Use an additional 8k of zip buffer. This is good if the
615        unmatched image data is already compressed, which means that
616        when it is compressed again by jigdo, it will get slightly
617        larger. */
618     auto_ptr<Zibstream> data(new Zibstream(*templ, (unsigned int)readAmount + 8*1024));
619 #   if HAVE_WORKING_FSTREAM
620     if (img == 0) img = &cout; // EEEEEK!
621 #   else
622     if (img == 0) img = &bcout;
623 #   endif
624
625     JigdoDesc::ImageInfo& imageInfo =
626         dynamic_cast<JigdoDesc::ImageInfo&>(*files.back());
627
628     try {
629       for (JigdoDescVec::iterator i = files.begin(), e = files.end();
630            i != e; ++i) {
631         //____________________
632
633         /* Write all data for this part to img stream. In case of
634            MatchedFile, write the appropriate number of bytes (of junk
635            data) even if file not present. [Using switch(type()) not
636            nice, but using virtual methods looks even worse.] */
637         switch ((*i)->type()) {
638           case JigdoDesc::IMAGE_INFO:
639           case JigdoDesc::IMAGE_INFO_SHA256:
640             break;
641           case JigdoDesc::UNMATCHED_DATA: {
642             // Copy data from Zibstream to image.
643             JigdoDesc::UnmatchedData& self =
644                 dynamic_cast<JigdoDesc::UnmatchedData&>(**i);
645             uint64 toWrite = self.size();
646             debug("mkimage writeAll(): %1 of unmatched data", toWrite);
647             memClear(buf, readAmount);
648             while (*img && toWrite > 0) {
649               if (!*data) {
650                 reporter.error(_("Premature end of template data"));
651                 return 3;
652               }
653               data->read(buf, (unsigned int)(toWrite < readAmount ? toWrite : readAmount));
654               size_t n = data->gcount();
655               writeBytes(*img, buf, n);
656               reportBytesWritten(n, off, nextReport, totalBytes, reporter);
657               toWrite -= n;
658             }
659             break;
660           }
661           case JigdoDesc::MATCHED_FILE: {
662             /* If file present in cache, copy its data to image, if
663                not, copy zeroes. if check==true, verify MD sum match.
664                If successful, turn MatchedFile into WrittenFile. */
665             JigdoDesc::MatchedFile* self =
666                 dynamic_cast<JigdoDesc::MatchedFile*>(*i);
667             uint64 toWrite = self->size();
668             FilePart* mfile = 0;
669             if (!toCopy.empty()) mfile = toCopy.front();
670             debug("mkimage writeAll(): FilePart@%1, %2 of matched file `%3',"
671                   " toCopy size %4", mfile, toWrite,
672                   (mfile != 0 ? mfile->leafName() : ""), toCopy.size());
673             if (mfile == 0 || self->md5() != *(mfile->getMD5Sum(cache))) {
674               // Write right amount of zeroes
675               memClear(buf, readAmount);
676               while (*img && toWrite > 0) {
677                 size_t n = (toWrite < readAmount ? toWrite : readAmount);
678                 writeBytes(*img, buf, n);
679                 reportBytesWritten(n, off, nextReport, totalBytes, reporter);
680                 toWrite -= n;
681               }
682               if (result == 0) result = 1; // Soft failure
683             } else {
684               /* Copy data from file to image, taking care not to
685                  write beyond toWrite. */
686               int status = fileToImageMD5(img, *mfile, *self, checkChecksum,
687                   imageInfo.blockLength(), reporter, buf, readAmount, off,
688                   nextReport, totalBytes);
689               toCopy.pop();
690               if (result < status) result = status;
691               if (status == 0) { // Mark file as written to image
692                 *i = new JigdoDesc::WrittenFile(self->offset(), self->size(),
693                                                 self->rsync(), self->md5());
694                 delete self;
695               } else if (*img && (status > 2 || task == SINGLE_PASS)) {
696                 // If !*img, exit after error msg below
697                 /* If status <= 2 and task == {CREATE_TMP,MERGE_TMP},
698                    we can continue; there has been an error copying
699                    this individual file, but the right *amount* of
700                    data has been written to the .tmp output file, and
701                    the user may retry the failed one later. */
702                 return result;
703               }
704             }
705             break;
706           }
707           case JigdoDesc::MATCHED_FILE_SHA256: {
708             /* If file present in cache, copy its data to image, if
709                not, copy zeroes. if check==true, verify SHA256 sum match.
710                If successful, turn MatchedFileSHA256 into WrittenFileSHA256. */
711             JigdoDesc::MatchedFileSHA256* self =
712                 dynamic_cast<JigdoDesc::MatchedFileSHA256*>(*i);
713             uint64 toWrite = self->size();
714             FilePart* mfile = 0;
715             if (!toCopy.empty()) mfile = toCopy.front();
716             debug("mkimage writeAll(): FilePart@%1, %2 of matched file `%3',"
717                   " toCopy size %4", mfile, toWrite,
718                   (mfile != 0 ? mfile->leafName() : ""), toCopy.size());
719             if (mfile == 0 || self->sha256() != *(mfile->getSHA256Sum(cache))) {
720               // Write right amount of zeroes
721               memClear(buf, readAmount);
722               while (*img && toWrite > 0) {
723                 size_t n = (toWrite < readAmount ? toWrite : readAmount);
724                 writeBytes(*img, buf, n);
725                 reportBytesWritten(n, off, nextReport, totalBytes, reporter);
726                 toWrite -= n;
727               }
728               if (result == 0) result = 1; // Soft failure
729             } else {
730               /* Copy data from file to image, taking care not to
731                  write beyond toWrite. */
732               int status = fileToImageSHA256(img, *mfile, *self, checkChecksum,
733                   imageInfo.blockLength(), reporter, buf, readAmount, off,
734                   nextReport, totalBytes);
735               toCopy.pop();
736               if (result < status) result = status;
737               if (status == 0) { // Mark file as written to image
738                 *i = new JigdoDesc::WrittenFileSHA256(self->offset(), self->size(),
739                                                       self->rsync(), self->sha256());
740                 delete self;
741               } else if (*img && (status > 2 || task == SINGLE_PASS)) {
742                 // If !*img, exit after error msg below
743                 /* If status <= 2 and task == {CREATE_TMP,MERGE_TMP},
744                    we can continue; there has been an error copying
745                    this individual file, but the right *amount* of
746                    data has been written to the .tmp output file, and
747                    the user may retry the failed one later. */
748                 return result;
749               }
750             }
751             break;
752           }
753           case JigdoDesc::WRITTEN_FILE:
754           case JigdoDesc::WRITTEN_FILE_SHA256:
755           // These are never present in memory, cannot occur:
756           case JigdoDesc::OBSOLETE_IMAGE_INFO:
757           case JigdoDesc::OBSOLETE_MATCHED_FILE:
758           case JigdoDesc::OBSOLETE_WRITTEN_FILE:
759             debug("mkimage writeAll(): invalid type %1", (*i)->type());
760             reporter.error(
761                 _("Error - template data's DESC section invalid"));
762             Assert(false); // A WrittenFile cannot occur here
763             return 3;
764             break;
765         }
766         //____________________
767
768         // Error while writing to image?
769         if (!*img) {
770           string err = subst(_("Error while writing to `%1' (%2)"),
771                              name, strerror(errno));
772           reporter.error(err);
773           return 3;
774         }
775         //____________________
776
777       } // end iterating over 'files'
778
779     } catch (Zerror e) {
780       // Error while unpacking template data
781       reporter.error(e.message); return 3;
782     }
783
784     // If we created a new tmp file, append DESC info
785     if (task == CREATE_TMP && result > 0) {
786       *img << files;
787       if (!*img) return 3;
788     }
789     // Must have "used up" all the parts that we found earlier
790     Assert(toCopy.empty());
791     return result; // 0 or 1
792   }
793   //______________________________
794
795   /* A temporary file already exists. Write the files listed in toCopy
796      to this temporary file. If image is now completed, truncate it to
797      its final length (removing the DESC section at the end),
798      otherwise update the DESC section (turn some need-file/
799      MatchedFile entries into have-file/WrittenFile entries). If 0 is
800      returned, caller should rename file to remove .tmp extension. */
801   inline int writeMerge(JigdoDescVec& files, queue<FilePart*>& toCopy,
802       const int missing, const size_t readAmount, bfstream* img,
803       const string& imageTmpFile, bool checkChecksum, ProgressReporter& reporter,
804       JigdoCache* cache, const uint64 totalBytes) {
805     vector<byte> bufVec(readAmount);
806     byte* buf = &bufVec[0];
807     int result = (missing == 0 ? 0 : 1);
808     uint64 bytesWritten = 0; // For 'x% done' calls to reporter
809     uint64 nextReport = 0; // At what value of bytesWritten to call reporter
810
811     JigdoDesc::ImageInfo& imageInfo =
812         dynamic_cast<JigdoDesc::ImageInfo&>(*files.back());
813
814     if (toCopy.empty() && missing > 0) return 1;
815     for (JigdoDescVec::iterator i = files.begin(), e = files.end();
816          i != e; ++i) {
817       // Compare to 'case JigdoDesc::MATCHED_FILE:' clause in writeAll()
818       JigdoDesc::MatchedFile* self =
819           dynamic_cast<JigdoDesc::MatchedFile*>(*i);
820       if (self == 0) continue;
821       FilePart* mfile = 0;
822       if (!toCopy.empty()) mfile = toCopy.front();
823       debug("mkimage writeMerge(): FilePart@%1, %2 of matched file `%3', "
824             "toCopy size %4", mfile, self->size(),
825             (mfile != 0 ? mfile->leafName() : ""), toCopy.size());
826       if (mfile == 0 || self->md5() != *(mfile->getMD5Sum(cache)))
827         continue;
828
829       /* Copy data from file to image, taking care not to write beyond
830          self->size(). */
831       img->seekp(self->offset(), ios::beg);
832       if (!*img) {
833         reporter.error(_("Error - could not access temporary file"));
834         result = 2;
835         break;
836       }
837       int status = fileToImageMD5(img, *mfile, *self, checkChecksum,
838           imageInfo.blockLength(), reporter, buf, readAmount, bytesWritten,
839           nextReport, totalBytes);
840       toCopy.pop();
841       if (result < status) result = status;
842       if (status == 0) { // Mark file as written to image
843         *i = new JigdoDesc::WrittenFile(self->offset(), self->size(),
844                                         self->rsync(), self->md5());
845         delete self;
846       } else if (status > 2) {
847         break;
848       }
849     } // end iterating over 'files'
850
851     uint64 imageSize = imageInfo.size();
852     if (missing == 0 && result == 0) {
853       img->close(); // Necessary on Windows before truncating is possible
854       // Truncate to final image size
855       const char* tmpName = imageTmpFile.c_str();
856       if (compat_truncate(tmpName, imageSize) != 0) {
857         string err = subst(_("Could not truncate `%1' (%2)"),
858                            imageTmpFile, strerror(errno));
859         reporter.error(err);
860         return 3;
861       }
862       return 0;
863     } else {
864       // Update DESC section at end of temporary file
865       img->seekp(imageSize);
866       // No need to truncate here because DESC section never changes size
867       *img << files;
868       if (!*img) return 3;
869       return result;
870     }
871   }
872   //______________________________
873
874   int info_NeedMoreFiles(ProgressReporter& reporter, const string& tmpName) {
875     string info = subst(_(
876           "Copied input files to temporary file `%1' - "
877           "repeat command and supply more files to continue"), tmpName);
878     reporter.info(info);
879     return 1; // Soft failure
880   }
881
882   int error_CouldntRename(ProgressReporter& reporter, const char* name,
883                           const char* finalName) {
884     string err = subst(_(
885         "Could not move finished image from `%1' to `%2' (%3)"),
886         name, finalName, strerror(errno));
887     reporter.error(err);
888     return 3;
889   }
890
891 } // end local namespace
892 //______________________________________________________________________
893
894 namespace {
895
896   /// Read template data from templ (name in templFile) into files
897   void readTemplate(JigdoDescVec& files, const string& templFile,
898                     bistream* templ) {
899     if (JigdoDesc::isTemplate(*templ) == false) { // Check for template hdr
900       string err = subst(_("`%1' is not a template file"), templFile);
901       throw JigdoDescError(err);
902     }
903     /* Read info at end of template data. NB: Exceptions are not
904        caught here, but e.g. in ::makeImage() (cf. jigdo-file.cc) */
905     JigdoDesc::seekFromEnd(*templ);
906     *templ >> files;
907   }
908   //________________________________________
909
910   /** Read data from end of temporary file imageTmp, output it to
911       filesTmp. Next, compare it to template data in "files". If tmp
912       file is OK for re-using return NULL - this means that the DESC
913       entries match *exactly* - the only difference allowed is
914       MatchedFile turning into WrittenFile. Otherwise, return a
915       pointer to an error message describing the reason why the
916       tmpfile data does not match the template data. */
917   const char* readTmpFile(bistream& imageTmp, JigdoDescVec& filesTmp,
918                           const JigdoDescVec& files) {
919     try {
920       JigdoDesc::seekFromEnd(imageTmp);
921       imageTmp >> filesTmp;
922     } catch (JigdoDescError e) {
923       return _("it was not created by jigdo-file, or is corrupted.");
924     }
925     if (*files.back() != *filesTmp.back())
926       return _("it corresponds to a different image/template.");
927     if (files.size() != filesTmp.size())
928       return _("since its creation, the template was regenerated.");
929     for (size_t i = 0; i < files.size() - 1; ++i) {
930       //cerr << "cmp " << i << '/' << (files.size() - 1) << endl;
931       if (*files[i] != *filesTmp[i])
932         return _("since its creation, the template was regenerated.");
933     }
934     return 0;
935   }
936
937 }
938 //________________________________________
939
940 /* If imageTmpFile.empty(), must either write whole image or nothing
941    at all. image and temporary file are created as needed, ditto for
942    renaming of temporary to image. The cache must not throw errors. */
943 int JigdoDesc::makeImage(JigdoCache* cache, const string& imageFile,
944     const string& imageTmpFile, const string& templFile,
945     bistream* templ, const bool optForce, ProgressReporter& reporter,
946     const size_t readAmount, const bool optMkImageCheck) {
947
948   Task task = CREATE_TMP;
949
950   if (imageFile == "-" || imageTmpFile.empty()) task = SINGLE_PASS;
951   //____________________
952
953   // Read info from template
954   JigdoDescVec files;
955   readTemplate(files, templFile, templ);
956   //____________________
957
958   // Do we need to add new stuff to an existing tmp file?
959   bfstream* img = 0; // Non-null => tmp file exists
960   auto_ptr<bfstream> imgDel(img);
961   struct stat fileInfo;
962   if (task != SINGLE_PASS && stat(imageTmpFile.c_str(), &fileInfo) == 0) {
963     /* A tmp file already exists. We'll only reuse it if the DESC
964        entries match exactly. Otherwise, if --force enabled, overwrite
965        it, else error. */
966     const char* wontReuse = 0; // non-NULL means: will not reuse tmp file
967     JigdoDescVec filesTmp;
968     imgDel.reset(new bfstream(imageTmpFile.c_str(),
969                               ios::binary|ios::in|ios::out));
970     img = imgDel.get();
971     if (!*img)
972       wontReuse = strerror(errno);
973     else
974       wontReuse = readTmpFile(*img, filesTmp, files);
975
976     if (wontReuse != 0) {
977       // Print out message
978       string msg = subst(_("Will not reuse existing temporary file `%1' - "
979                            "%2"), imageTmpFile, wontReuse);
980       // Return error if not allowed to overwrite tmp file
981       if (!optForce) {
982         reporter.error(msg);
983         throw Error(_("Delete/rename the file or use --force"));
984       }
985       // Open a new tmp file later (imgDel will close() this one for us)
986       reporter.info(msg);
987       img = 0;
988       Paranoid(task == CREATE_TMP);
989     } else {
990       // Reuse temporary file
991       task = MERGE_TMP;
992       files.swap(filesTmp);
993       Assert(!filesTmp.empty() && img != 0);
994     }
995   } // endif (tmp file exists)
996
997   Paranoid((task == MERGE_TMP) == (img != 0));
998   //____________________
999
1000   /* Variables now in use:
1001      enum task:
1002              Mode of operation (CREATE_TMP/MERGE_TMP/SINGLE_PASS)
1003      JigdoDescVec files:
1004              Contents of image, maybe with some WrittenFiles if MERGEing
1005      istream* templ: Template data (stream pointer at end of file)
1006      fstream* img: Temporary file if MERGE_TMP, else null
1007   */
1008
1009   /* Create queue of files that need to be copied to the image. Later
1010      on, we will be pop()ing to get to the actual filenames in order.
1011      Referenced FileParts are owned by the JigdoCache - never delete
1012      them. */
1013   queue<FilePart*> toCopy;
1014   int missing = 0; // Nr of files that were not found
1015   JigdoCache::iterator ci, ce = cache->end();
1016   uint64 totalBytes = 0; // Total amount of data to be written, for "x% done"
1017
1018   for (vector<JigdoDesc*>::iterator i = files.begin(), e = files.end();
1019        i != e; ++i) {
1020     // Need this extra test because we do *not* want the WrittenFiles
1021     if ((*i)->type() != MATCHED_FILE) continue;
1022     MatchedFile* m = dynamic_cast<MatchedFile*>(*i);
1023     Paranoid(m != 0);
1024     //totalBytes += m->size();
1025
1026     // Search for file with matching MD5 sum
1027     ci = cache->begin();
1028     bool found = false;
1029     while (ci != ce) {
1030       // The call to getMD5Sum() may cause the whole file to be read!
1031       const MD5Sum* md = ci->getMD5Sum(cache);
1032       if (md != 0 && *md == m->md5()) {
1033         toCopy.push(&*ci); // Found matching file
1034         totalBytes += m->size();
1035         debug("%1 found, pushed %2", m->md5().toString(), &*ci);
1036         found = true;
1037         break;
1038       }
1039       ++ci;
1040     }
1041     if (!found) ++missing;
1042
1043   }
1044   //____________________
1045
1046   debug("JigdoDesc::mkImage: %1 missing, %2 found for copying to image, "
1047         "%3 entries in template", missing, toCopy.size(), files.size());
1048
1049   // Files appearing >1 times are counted >1 times for the message
1050   string missingInfo = subst(
1051       _("Found %1 of the %2 files required by the template"),
1052       toCopy.size(), toCopy.size() + missing);
1053   reporter.info(missingInfo);
1054   //____________________
1055
1056   /* There used to be the following here:
1057      | If possible (i.e. all files present, tmp file not yet created),
1058      | avoid creating any tmp file at all.
1059      | if (task == CREATE_TMP && missing == 0) task = SINGLE_PASS;
1060      We do not do this because even though it says "missing==0" *now*,
1061      there could be a read error from one of the files when we
1062      actually access it, in which case we should be able to ignore the
1063      error for the moment, and leave behind a partially complete .tmp
1064      file. */
1065
1066   /* Do nothing at all if a) no tmp file created yet, and b) *none* of
1067      the supplied files matched one of the missing parts, and c) the
1068      template actually contains at least one MatchedFile (i.e. *do*
1069      write if template consists entirely of UnmatchedData). */
1070 # ifndef MKIMAGE_ALWAYS_CREATE_TMPFILE
1071   if (task == CREATE_TMP && toCopy.size() == 0 && missing != 0) {
1072     const char* m = _("Will not create image or temporary file - try again "
1073                       "with different input files");
1074     reporter.info(m);
1075     return 1; // Return value: "Soft failure - may retry with more files"
1076   }
1077
1078   // Give error if unable to create image in one pass
1079   if (task == SINGLE_PASS && missing > 0) {
1080     reporter.error(_("Cannot create image because of missing files"));
1081     return 3; // Permanent failure
1082   }
1083 # endif
1084   //____________________
1085
1086   if (task == MERGE_TMP) { // If MERGEing, img was already set up above
1087     int result = writeMerge(files, toCopy, missing, readAmount, img,
1088                             imageTmpFile, optMkImageCheck, reporter, cache,
1089                             totalBytes);
1090     if (missing != 0 && result < 3)
1091       info_NeedMoreFiles(reporter, imageTmpFile);
1092     if (result == 0) {
1093       if (compat_rename(imageTmpFile.c_str(), imageFile.c_str()) != 0)
1094         return error_CouldntRename(reporter, imageTmpFile.c_str(),
1095                                    imageFile.c_str());
1096       string info = subst(_("Successfully created `%1'"), imageFile);
1097       reporter.info(info);
1098     }
1099     return result;
1100   }
1101
1102   // task == CREATE_TMP || task == SINGLE_PASS
1103
1104   // Assign a stream to img which we're going to write image data to
1105   // If necessary, create a new temporary/output file
1106   const char* name;
1107   const char* finalName = 0;
1108   if (task == CREATE_TMP) { // CREATE new tmp file
1109     name = imageTmpFile.c_str();
1110     finalName = imageFile.c_str();
1111     imgDel.reset(new bfstream(name, ios::binary|ios::out|ios::trunc));
1112     img = imgDel.get();
1113   } else if (imageFile != "-") { // SINGLE_PASS; create output file
1114     name = imageFile.c_str();
1115     imgDel.reset(new bfstream(name, ios::binary|ios::out|ios::trunc));
1116     img = imgDel.get();
1117   } else { // SINGLE_PASS, outputting to stdout
1118     name = "-";
1119     imgDel.reset(0);
1120     img = 0; // Cannot do "img = &cout", so img==0 is special case: stdout
1121   }
1122   if (img != 0 && !*img) {
1123     string err = subst(_("Could not open `%1' for output: %2"),
1124                        name, strerror(errno));
1125     reporter.error(err);
1126     return 3; // Permanent failure
1127   }
1128
1129   /* Above, totalBytes was calculated for the case of a MERGE_TMP. If
1130      we're not merging, we need to write everything. */
1131   Assert(files.back()->type() == IMAGE_INFO);
1132   uint64 imageSize = files.back()->size();
1133   totalBytes = imageSize;
1134 # if 0 /* # if WINDOWS */
1135   /* The C++ STL of the MinGW 1.1 gcc port for Windows doesn't support
1136      files >2GB. Fail early and with a clear error message... */
1137   if (imageSize >= (1U<<31))
1138     throw Error(_("Sorry, at the moment the Windows port of jigdo cannot "
1139                   "create files bigger than 2 GB. Use the Linux version."));
1140 # endif
1141
1142   int result = writeAll(task, files, toCopy, templ, readAmount, img, name,
1143                         optMkImageCheck, reporter, cache, totalBytes);
1144   if (result >= 3) return result;
1145
1146   if (task == CREATE_TMP && result == 1) {
1147     info_NeedMoreFiles(reporter, imageTmpFile);
1148   } else if (result == 0) {
1149     if (img != 0)
1150       img->close(); // Necessary on Windows before renaming is possible
1151     if (finalName != 0 && compat_rename(name, finalName) != 0)
1152       return error_CouldntRename(reporter, name, finalName);
1153     string info = subst(_("Successfully created `%1'"), imageFile);
1154     reporter.info(info);
1155   }
1156   return result;
1157 }
1158 //______________________________________________________________________
1159
1160 int JigdoDesc::listMissing(set<MD5>& result, const string& imageTmpFile,
1161     const string& templFile, bistream* templ, ProgressReporter& reporter) {
1162   result.clear();
1163
1164   // Read info from template
1165   JigdoDescVec contents;
1166   readTemplate(contents, templFile, templ);
1167
1168   // Read info from temporary file, if any
1169   if (!imageTmpFile.empty()) {
1170     bifstream imageTmp(imageTmpFile.c_str(), ios::binary);
1171     if (imageTmp) {
1172       JigdoDescVec contentsTmp;
1173       const char* wontReuse = readTmpFile(imageTmp, contentsTmp, contents);
1174       if (wontReuse != 0) {
1175         string msg = subst(_("Ignoring existing temporary file `%1' - %2"),
1176                            imageTmpFile, wontReuse);
1177         reporter.info(msg);
1178       } else {
1179         // tmp file present & valid - use *it* below to output missing parts
1180         swap(contents, contentsTmp);
1181       }
1182     }
1183   }
1184
1185   // Output MD5 sums of MatchedFile (but not WrittenFile) entries
1186   for (size_t i = 0; i < contents.size() - 1; ++i) {
1187     MatchedFile* mf = dynamic_cast<MatchedFile*>(contents[i]);
1188     if (mf != 0 && mf->type() == MATCHED_FILE)
1189       result.insert(mf->md5());
1190   }
1191   return 0;
1192 }
1193 //______________________________________________________________________
1194
1195 void JigdoDesc::ProgressReporter::error(const string& message) {
1196   cerr << message << endl;
1197 }
1198 void JigdoDesc::ProgressReporter::info(const string& message) {
1199   cerr << message << endl;
1200 }
1201 void JigdoDesc::ProgressReporter::writingImage(uint64, uint64, uint64,
1202                                               uint64) { }
1203
1204 JigdoDesc::ProgressReporter JigdoDesc::noReport;