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