Fix lots of warnings about implicit type conversions
[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) throw(JigdoDescError) {
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     throw(JigdoDescError, bad_alloc) {
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   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::UNMATCHED_DATA:
155       unserialize6(entryLen, f);
156       if (!file) break;
157       debug("JigdoDesc::read: %1 UnmatchedData %2", off, entryLen);
158       desc.reset(new JigdoDesc::UnmatchedData(off, entryLen));
159       push_back(desc.release());
160       read += 1 + 6;
161       off += entryLen;
162       break;
163
164     case JigdoDesc::MATCHED_FILE:
165     case JigdoDesc::WRITTEN_FILE:
166       unserialize6(entryLen, f);
167       unserialize(rsum, f);
168       unserialize(entryMd5, f);
169       if (!file) break;
170       debug("JigdoDesc::read: %1 %2File %3 %4",
171             off, (type == JigdoDesc::MATCHED_FILE ? "Matched" : "Written"),
172             entryLen, entryMd5.toString());
173       if (type == JigdoDesc::MATCHED_FILE)
174         desc.reset(new JigdoDesc::MatchedFile(off, entryLen, rsum,entryMd5));
175       else
176         desc.reset(new JigdoDesc::WrittenFile(off, entryLen, rsum,entryMd5));
177       push_back(desc.release());
178       read += 1 + 6 + rsum.serialSizeOf() + entryMd5.serialSizeOf();
179       off += entryLen;
180       break;
181
182       // Template entry types that were obsoleted with version 0.6.3:
183
184     case JigdoDesc::OBSOLETE_IMAGE_INFO:
185       unserialize6(entryLen, f);
186       unserialize(entryMd5, f);
187       if (!file) break;
188       debug("JigdoDesc::read: old ImageInfo %1 %2",
189             entryLen, entryMd5.toString());
190       // Special case: passing blockLength==0, which is otherwise impossible
191       desc.reset(new JigdoDesc::ImageInfo(entryLen, entryMd5, 0));
192       push_back(desc.release());
193       read += 1 + 6 + entryMd5.serialSizeOf();
194       break;
195
196     case JigdoDesc::OBSOLETE_MATCHED_FILE:
197     case JigdoDesc::OBSOLETE_WRITTEN_FILE:
198       unserialize6(entryLen, f);
199       unserialize(entryMd5, f);
200       if (!file) break;
201       debug("JigdoDesc::read: %1 old %2File %3 %4", off,
202             (type == JigdoDesc::OBSOLETE_MATCHED_FILE ? "Matched" :
203              "Written"), entryLen, entryMd5.toString());
204       /* Value of rsum is "don't care" because the ImageInfo's
205          blockLength will be zero. */
206       rsum.reset();
207       if (type == JigdoDesc::OBSOLETE_MATCHED_FILE)
208         desc.reset(new JigdoDesc::MatchedFile(off, entryLen, rsum,entryMd5));
209       else
210         desc.reset(new JigdoDesc::WrittenFile(off, entryLen, rsum,entryMd5));
211       push_back(desc.release());
212       read += 1 + 6 + entryMd5.serialSizeOf();
213       off += entryLen;
214       break;
215
216     default:
217       debug("JigdoDesc::read: unknown type %1", type);
218       throw JigdoDescError(_("Invalid template data - corrupted file?"));
219     }
220   }
221   //____________________
222
223   if (read < len) {
224     string err = subst(_("Error reading template data (%1)"),
225                        strerror(errno));
226     throw JigdoDescError(err);
227   }
228
229   if (empty())
230     throw JigdoDescError(_("Invalid template data - corrupted file?"));
231   JigdoDesc::ImageInfo* ii = dynamic_cast<JigdoDesc::ImageInfo*>(back());
232   if (ii == 0 || ii->size() != off) {
233     if (ii != 0) debug("JigdoDesc::read4: %1 != %2", ii->size(), off);
234     throw JigdoDescError(_("Invalid template data - corrupted file?"));
235   }
236   return file;
237 }
238 //______________________________________________________________________
239
240 bostream& JigdoDescVec::put(bostream& file, MD5Sum* md) const {
241   // Pass 1: Accumulate sizes of entries, calculate descLen
242   // 4 for DESC, 6 each for length of part at start & end
243   uint64 descLen = 4 + 6*2; // Length of DESC part
244   unsigned bufLen = 4 + 6;
245   for (const_iterator i = begin(), e = end(); i != e; ++i) {
246     unsigned s = (unsigned)(*i)->serialSizeOf();
247     bufLen = max(bufLen, s);
248     descLen += s;
249   }
250   if (DEBUG) bufLen += 1;
251
252   // Pass 2: Write DESC part
253   byte buf[bufLen];
254   if (DEBUG) buf[bufLen - 1] = 0xa5;
255   byte* p;
256   p = serialize4(0x43534544, buf); // "DESC" in little-endian order
257   p = serialize6(descLen, p);
258   writeBytes(file, buf, 4 + 6);
259   if (md != 0) md->update(buf, 4 + 6);
260   for (const_iterator i = begin(), e = end(); i != e; ++i) {
261     JigdoDesc::ImageInfo* info;
262     JigdoDesc::UnmatchedData* unm;
263     JigdoDesc::MatchedFile* matched;
264     JigdoDesc::WrittenFile* written;
265     /* NB we must first try to cast to WrittenFile, then to
266        MatchedFile, because WrittenFile derives from MatchedFile. */
267     if ((info = dynamic_cast<JigdoDesc::ImageInfo*>(*i)) != 0)
268       p = info->serialize(buf);
269     else if ((unm = dynamic_cast<JigdoDesc::UnmatchedData*>(*i)) != 0)
270       p = unm->serialize(buf);
271     else if ((written = dynamic_cast<JigdoDesc::WrittenFile*>(*i)) != 0)
272       p = written->serialize(buf);
273     else if ((matched = dynamic_cast<JigdoDesc::MatchedFile*>(*i)) != 0)
274       p = matched->serialize(buf);
275     else { Assert(false); continue; }
276     writeBytes(file, buf, p - buf);
277     if (md != 0) md->update(buf, p - buf);
278   }
279   p = serialize6(descLen, buf);
280   writeBytes(file, buf, 6);
281   if (md != 0) md->update(buf, 6);
282   if (DEBUG) { Assert(buf[bufLen - 1] == 0xa5); }
283   return file;
284 }
285 //______________________________________________________________________
286
287 namespace {
288   const int J_SIZE_WIDTH = 12;
289 }
290
291 ostream& JigdoDesc::ImageInfo::put(ostream& s) const {
292   s << "image-info  " << setw(J_SIZE_WIDTH) << size() << "              "
293     << md5() << ' ' << blockLength() << '\n';
294   return s;
295 }
296 ostream& JigdoDesc::UnmatchedData::put(ostream& s) const {
297   s << "in-template " << setw(J_SIZE_WIDTH) << offset() << ' '
298     << setw(J_SIZE_WIDTH) << size() << '\n';
299   return s;
300 }
301 ostream& JigdoDesc::MatchedFile::put(ostream& s) const {
302   s << "need-file   " << setw(J_SIZE_WIDTH) << offset() << ' '
303     << setw(J_SIZE_WIDTH) << size() << ' ' << md5() << ' ' << rsync() << '\n';
304   return s;
305 }
306 ostream& JigdoDesc::WrittenFile::put(ostream& s) const {
307   s << "have-file   " << setw(J_SIZE_WIDTH) << offset() << ' '
308     << setw(J_SIZE_WIDTH) << size() << ' ' << md5() << ' ' << rsync() << '\n';
309   return s;
310 }
311
312 void JigdoDescVec::list(ostream& s) throw() {
313   for (const_iterator i = begin(), e = end(); i != e; ++i) s << (**i);
314   s << flush;
315 }
316 //______________________________________________________________________
317
318 namespace {
319
320   /* Helper functions for makeImage below, declared inline if only
321      used once */
322
323   /// Type of operation when recreating image data
324   enum Task {
325     CREATE_TMP, // Create a new .tmp file and copy some files into it,
326                 // maybe rename at end
327     MERGE_TMP, // .tmp exists, copy over more files, maybe rename at end
328     SINGLE_PASS // single-pass, all or nothing; writing to stdout
329   };
330   //______________________________
331
332   inline void reportBytesWritten(const uint64 n, uint64& off,
333       uint64& nextReport, const uint64 totalBytes,
334       ProgressReporter& reporter) {
335     off += n;
336     if (off >= nextReport) { // Keep user entertained
337       reporter.writingImage(off, totalBytes, off, totalBytes);
338       nextReport += REPORT_INTERVAL;
339     }
340   }
341   //______________________________
342
343   /* Read up to file.size() of bytes from file, write it to image
344      stream. Check MD5/rsync sum if requested. Take care not to write
345      more than specified amount to image, even if file is longer. */
346   int fileToImage(bostream* img, FilePart& file,
347       const JigdoDesc::MatchedFile& matched, bool checkMD5, size_t rsyncLen,
348       ProgressReporter& reporter, byte* buf, size_t readAmount, uint64& off,
349       uint64& nextReport, const uint64 totalBytes) {
350     uint64 toWrite = file.size();
351     MD5Sum md;
352     RsyncSum64 rs;
353     size_t rl = 0; // Length covered by rs so far
354     string fileName(file.getPath());
355     fileName += file.leafName();
356     bifstream f(fileName.c_str(), ios::binary);
357     string err; // !err.empty() => error occurred
358
359     // Read from file, write to image
360     // First couple of k: Calculate RsyncSum rs and MD5Sum md
361     if (checkMD5 && rsyncLen > 0) {
362       while (*img && f && !f.eof() && toWrite > 0) {
363         size_t n = (toWrite < readAmount ? toWrite : readAmount);
364         readBytes(f, buf, n);
365         n = f.gcount();
366         writeBytes(*img, buf, n);
367         reportBytesWritten(n, off, nextReport, totalBytes, reporter);
368         toWrite -= n;
369         md.update(buf, n);
370         // Update RsyncSum
371         Paranoid(rl < rsyncLen);
372         size_t rsyncToAdd = rsyncLen - rl;
373         if (rsyncToAdd > n) rsyncToAdd = n;
374         rs.addBack(buf, rsyncToAdd);
375         rl += rsyncToAdd;
376         Paranoid(rl <= rsyncLen);
377         if (rl >= rsyncLen) break;
378       }
379     }
380     // Rest of file: Only calculate MD5Sum md
381     while (*img && f && !f.eof() && toWrite > 0) {
382       size_t n = (toWrite < readAmount ? toWrite : readAmount);
383       readBytes(f, buf, n);
384       n = f.gcount();
385       writeBytes(*img, buf, n);
386       reportBytesWritten(n, off, nextReport, totalBytes, reporter);
387       toWrite -= n;
388       if (checkMD5) md.update(buf, n);
389     }
390
391     if (toWrite > 0 && (!f || f.eof())) {
392       const char* errDetail = "";
393       if (errno != 0) errDetail = strerror(errno);
394       else if (f.eof()) errDetail = _("file is too short");
395       err = subst(_("Error reading from `%1' (%2)"), fileName, errDetail);
396       // Even if there was an error - always try to write right amount
397       memClear(buf, readAmount);
398       while (*img && toWrite > 0) {
399         size_t n = (toWrite < readAmount ? toWrite : readAmount);
400         writeBytes(*img, buf, n);
401         reportBytesWritten(n, off, nextReport, totalBytes, reporter);
402         toWrite -= n;
403       }
404     } else if (checkMD5
405                && (md.finish() != matched.md5()
406                    || (rsyncLen > 0 && rs != matched.rsync()))) {
407       err = subst(_("Error: `%1' does not match checksum in template data"),
408                   fileName);
409     }
410
411     if (err.empty()) return 0; // Success
412     reporter.error(err);
413     if (toWrite == 0)
414       return 2; // "May have to fix something before you can continue"
415     else
416       return 3; // Yaargh, disaster! Please delete the .tmp file for me
417   }
418   //______________________________
419
420   /* Write all bytes of the image data, i.e. both UnmatchedData and
421      MatchedFiles. If any UnmatchedFiles are present in 'files', write
422      zeroes instead of the file content and also append a DESC section
423      after the actual data.
424
425      Why does this write zeroes, and not simply seek() forward the
426      appropriate amount of bytes? - Because when seek() is used, a
427      sparse file might be generated. This could result in "No room on
428      device" later on - but we'd rather like that error as early as
429      possible.
430
431      @param name Filename corresponding to img
432      @param totalBytes length of image
433
434      if img==0, write to cout. If 0 is returned and not writing to
435      cout, caller should rename file to remove .tmp extension. */
436   inline int writeAll(const Task& task, JigdoDescVec& files,
437       queue<FilePart*>& toCopy, bistream* templ, const size_t readAmount,
438       bostream* img, const char* name, bool checkMD5,
439       ProgressReporter& reporter, JigdoCache* cache,
440       const uint64 totalBytes) {
441
442     bool isTemplate = JigdoDesc::isTemplate(*templ); // seek to 1st DATA part
443     Assert(isTemplate);
444     int result = 0;
445     uint64 off = 0; // Current offset in image
446     uint64 nextReport = 0; // At what value of off to call reporter
447
448     vector<byte> bufVec(readAmount);
449     byte* buf = &bufVec[0];
450     /* Use an additional 8k of zip buffer. This is good if the
451        unmatched image data is already compressed, which means that
452        when it is compressed again by jigdo, it will get slightly
453        larger. */
454     auto_ptr<Zibstream> data(new Zibstream(*templ, (unsigned int)readAmount + 8*1024));
455 #   if HAVE_WORKING_FSTREAM
456     if (img == 0) img = &cout; // EEEEEK!
457 #   else
458     if (img == 0) img = &bcout;
459 #   endif
460
461     JigdoDesc::ImageInfo& imageInfo =
462         dynamic_cast<JigdoDesc::ImageInfo&>(*files.back());
463
464     try {
465       for (JigdoDescVec::iterator i = files.begin(), e = files.end();
466            i != e; ++i) {
467         //____________________
468
469         /* Write all data for this part to img stream. In case of
470            MatchedFile, write the appropriate number of bytes (of junk
471            data) even if file not present. [Using switch(type()) not
472            nice, but using virtual methods looks even worse.] */
473         switch ((*i)->type()) {
474           case JigdoDesc::IMAGE_INFO:
475             break;
476           case JigdoDesc::UNMATCHED_DATA: {
477             // Copy data from Zibstream to image.
478             JigdoDesc::UnmatchedData& self =
479                 dynamic_cast<JigdoDesc::UnmatchedData&>(**i);
480             uint64 toWrite = self.size();
481             debug("mkimage writeAll(): %1 of unmatched data", toWrite);
482             memClear(buf, readAmount);
483             while (*img && toWrite > 0) {
484               if (!*data) {
485                 reporter.error(_("Premature end of template data"));
486                 return 3;
487               }
488               data->read(buf, (unsigned int)(toWrite < readAmount ? toWrite : readAmount));
489               size_t n = data->gcount();
490               writeBytes(*img, buf, n);
491               reportBytesWritten(n, off, nextReport, totalBytes, reporter);
492               toWrite -= n;
493             }
494             break;
495           }
496           case JigdoDesc::MATCHED_FILE: {
497             /* If file present in cache, copy its data to image, if
498                not, copy zeroes. if check==true, verify MD sum match.
499                If successful, turn MatchedFile into WrittenFile. */
500             JigdoDesc::MatchedFile* self =
501                 dynamic_cast<JigdoDesc::MatchedFile*>(*i);
502             uint64 toWrite = self->size();
503             FilePart* mfile = 0;
504             if (!toCopy.empty()) mfile = toCopy.front();
505             debug("mkimage writeAll(): FilePart@%1, %2 of matched file `%3',"
506                   " toCopy size %4", mfile, toWrite,
507                   (mfile != 0 ? mfile->leafName() : ""), toCopy.size());
508             if (mfile == 0 || self->md5() != *(mfile->getMD5Sum(cache))) {
509               // Write right amount of zeroes
510               memClear(buf, readAmount);
511               while (*img && toWrite > 0) {
512                 size_t n = (toWrite < readAmount ? toWrite : readAmount);
513                 writeBytes(*img, buf, n);
514                 reportBytesWritten(n, off, nextReport, totalBytes, reporter);
515                 toWrite -= n;
516               }
517               if (result == 0) result = 1; // Soft failure
518             } else {
519               /* Copy data from file to image, taking care not to
520                  write beyond toWrite. */
521               int status = fileToImage(img, *mfile, *self, checkMD5,
522                   imageInfo.blockLength(), reporter, buf, readAmount, off,
523                   nextReport, totalBytes);
524               toCopy.pop();
525               if (result < status) result = status;
526               if (status == 0) { // Mark file as written to image
527                 *i = new JigdoDesc::WrittenFile(self->offset(), self->size(),
528                                                 self->rsync(), self->md5());
529                 delete self;
530               } else if (*img && (status > 2 || task == SINGLE_PASS)) {
531                 // If !*img, exit after error msg below
532                 /* If status <= 2 and task == {CREATE_TMP,MERGE_TMP},
533                    we can continue; there has been an error copying
534                    this individual file, but the right *amount* of
535                    data has been written to the .tmp output file, and
536                    the user may retry the failed one later. */
537                 return result;
538               }
539             }
540             break;
541           }
542           case JigdoDesc::WRITTEN_FILE:
543           // These are never present in memory, cannot occur:
544           case JigdoDesc::OBSOLETE_IMAGE_INFO:
545           case JigdoDesc::OBSOLETE_MATCHED_FILE:
546           case JigdoDesc::OBSOLETE_WRITTEN_FILE:
547             debug("mkimage writeAll(): invalid type %1", (*i)->type());
548             reporter.error(
549                 _("Error - template data's DESC section invalid"));
550             Assert(false); // A WrittenFile cannot occur here
551             return 3;
552             break;
553         }
554         //____________________
555
556         // Error while writing to image?
557         if (!*img) {
558           string err = subst(_("Error while writing to `%1' (%2)"),
559                              name, strerror(errno));
560           reporter.error(err);
561           return 3;
562         }
563         //____________________
564
565       } // end iterating over 'files'
566
567     } catch (Zerror e) {
568       // Error while unpacking template data
569       reporter.error(e.message); return 3;
570     }
571
572     // If we created a new tmp file, append DESC info
573     if (task == CREATE_TMP && result > 0) {
574       *img << files;
575       if (!*img) return 3;
576     }
577     // Must have "used up" all the parts that we found earlier
578     Assert(toCopy.empty());
579     return result; // 0 or 1
580   }
581   //______________________________
582
583   /* A temporary file already exists. Write the files listed in toCopy
584      to this temporary file. If image is now completed, truncate it to
585      its final length (removing the DESC section at the end),
586      otherwise update the DESC section (turn some need-file/
587      MatchedFile entries into have-file/WrittenFile entries). If 0 is
588      returned, caller should rename file to remove .tmp extension. */
589   inline int writeMerge(JigdoDescVec& files, queue<FilePart*>& toCopy,
590       const int missing, const size_t readAmount, bfstream* img,
591       const string& imageTmpFile, bool checkMD5, ProgressReporter& reporter,
592       JigdoCache* cache, const uint64 totalBytes) {
593     vector<byte> bufVec(readAmount);
594     byte* buf = &bufVec[0];
595     int result = (missing == 0 ? 0 : 1);
596     uint64 bytesWritten = 0; // For 'x% done' calls to reporter
597     uint64 nextReport = 0; // At what value of bytesWritten to call reporter
598
599     JigdoDesc::ImageInfo& imageInfo =
600         dynamic_cast<JigdoDesc::ImageInfo&>(*files.back());
601
602     if (toCopy.empty() && missing > 0) return 1;
603     for (JigdoDescVec::iterator i = files.begin(), e = files.end();
604          i != e; ++i) {
605       // Compare to 'case JigdoDesc::MATCHED_FILE:' clause in writeAll()
606       JigdoDesc::MatchedFile* self =
607           dynamic_cast<JigdoDesc::MatchedFile*>(*i);
608       if (self == 0) continue;
609       FilePart* mfile = 0;
610       if (!toCopy.empty()) mfile = toCopy.front();
611       debug("mkimage writeMerge(): FilePart@%1, %2 of matched file `%3', "
612             "toCopy size %4", mfile, self->size(),
613             (mfile != 0 ? mfile->leafName() : ""), toCopy.size());
614       if (mfile == 0 || self->md5() != *(mfile->getMD5Sum(cache)))
615         continue;
616
617       /* Copy data from file to image, taking care not to write beyond
618          self->size(). */
619       img->seekp(self->offset(), ios::beg);
620       if (!*img) {
621         reporter.error(_("Error - could not access temporary file"));
622         result = 2;
623         break;
624       }
625       int status = fileToImage(img, *mfile, *self, checkMD5,
626           imageInfo.blockLength(), reporter, buf, readAmount, bytesWritten,
627           nextReport, totalBytes);
628       toCopy.pop();
629       if (result < status) result = status;
630       if (status == 0) { // Mark file as written to image
631         *i = new JigdoDesc::WrittenFile(self->offset(), self->size(),
632                                         self->rsync(), self->md5());
633         delete self;
634       } else if (status > 2) {
635         break;
636       }
637     } // end iterating over 'files'
638
639     uint64 imageSize = imageInfo.size();
640     if (missing == 0 && result == 0) {
641       img->close(); // Necessary on Windows before truncating is possible
642       // Truncate to final image size
643       const char* tmpName = imageTmpFile.c_str();
644       if (compat_truncate(tmpName, imageSize) != 0) {
645         string err = subst(_("Could not truncate `%1' (%2)"),
646                            imageTmpFile, strerror(errno));
647         reporter.error(err);
648         return 3;
649       }
650       return 0;
651     } else {
652       // Update DESC section at end of temporary file
653       img->seekp(imageSize);
654       // No need to truncate here because DESC section never changes size
655       *img << files;
656       if (!*img) return 3;
657       return result;
658     }
659   }
660   //______________________________
661
662   int info_NeedMoreFiles(ProgressReporter& reporter, const string& tmpName) {
663     string info = subst(_(
664           "Copied input files to temporary file `%1' - "
665           "repeat command and supply more files to continue"), tmpName);
666     reporter.info(info);
667     return 1; // Soft failure
668   }
669
670   int error_CouldntRename(ProgressReporter& reporter, const char* name,
671                           const char* finalName) {
672     string err = subst(_(
673         "Could not move finished image from `%1' to `%2' (%3)"),
674         name, finalName, strerror(errno));
675     reporter.error(err);
676     return 3;
677   }
678
679 } // end local namespace
680 //______________________________________________________________________
681
682 namespace {
683
684   /// Read template data from templ (name in templFile) into files
685   void readTemplate(JigdoDescVec& files, const string& templFile,
686                     bistream* templ) {
687     if (JigdoDesc::isTemplate(*templ) == false) { // Check for template hdr
688       string err = subst(_("`%1' is not a template file"), templFile);
689       throw JigdoDescError(err);
690     }
691     /* Read info at end of template data. NB: Exceptions are not
692        caught here, but e.g. in ::makeImage() (cf. jigdo-file.cc) */
693     JigdoDesc::seekFromEnd(*templ);
694     *templ >> files;
695   }
696   //________________________________________
697
698   /** Read data from end of temporary file imageTmp, output it to
699       filesTmp. Next, compare it to template data in "files". If tmp
700       file is OK for re-using return NULL - this means that the DESC
701       entries match *exactly* - the only difference allowed is
702       MatchedFile turning into WrittenFile. Otherwise, return a
703       pointer to an error message describing the reason why the
704       tmpfile data does not match the template data. */
705   const char* readTmpFile(bistream& imageTmp, JigdoDescVec& filesTmp,
706                           const JigdoDescVec& files) {
707     try {
708       JigdoDesc::seekFromEnd(imageTmp);
709       imageTmp >> filesTmp;
710     } catch (JigdoDescError e) {
711       return _("it was not created by jigdo-file, or is corrupted.");
712     }
713     if (*files.back() != *filesTmp.back())
714       return _("it corresponds to a different image/template.");
715     if (files.size() != filesTmp.size())
716       return _("since its creation, the template was regenerated.");
717     for (size_t i = 0; i < files.size() - 1; ++i) {
718       //cerr << "cmp " << i << '/' << (files.size() - 1) << endl;
719       if (*files[i] != *filesTmp[i])
720         return _("since its creation, the template was regenerated.");
721     }
722     return 0;
723   }
724
725 }
726 //________________________________________
727
728 /* If imageTmpFile.empty(), must either write whole image or nothing
729    at all. image and temporary file are created as needed, ditto for
730    renaming of temporary to image. The cache must not throw errors. */
731 int JigdoDesc::makeImage(JigdoCache* cache, const string& imageFile,
732     const string& imageTmpFile, const string& templFile,
733     bistream* templ, const bool optForce, ProgressReporter& reporter,
734     const size_t readAmount, const bool optMkImageCheck) {
735
736   Task task = CREATE_TMP;
737
738   if (imageFile == "-" || imageTmpFile.empty()) task = SINGLE_PASS;
739   //____________________
740
741   // Read info from template
742   JigdoDescVec files;
743   readTemplate(files, templFile, templ);
744   //____________________
745
746   // Do we need to add new stuff to an existing tmp file?
747   bfstream* img = 0; // Non-null => tmp file exists
748   auto_ptr<bfstream> imgDel(img);
749   struct stat fileInfo;
750   if (task != SINGLE_PASS && stat(imageTmpFile.c_str(), &fileInfo) == 0) {
751     /* A tmp file already exists. We'll only reuse it if the DESC
752        entries match exactly. Otherwise, if --force enabled, overwrite
753        it, else error. */
754     const char* wontReuse = 0; // non-NULL means: will not reuse tmp file
755     JigdoDescVec filesTmp;
756     imgDel.reset(new bfstream(imageTmpFile.c_str(),
757                               ios::binary|ios::in|ios::out));
758     img = imgDel.get();
759     if (!*img)
760       wontReuse = strerror(errno);
761     else
762       wontReuse = readTmpFile(*img, filesTmp, files);
763
764     if (wontReuse != 0) {
765       // Print out message
766       string msg = subst(_("Will not reuse existing temporary file `%1' - "
767                            "%2"), imageTmpFile, wontReuse);
768       // Return error if not allowed to overwrite tmp file
769       if (!optForce) {
770         reporter.error(msg);
771         throw Error(_("Delete/rename the file or use --force"));
772       }
773       // Open a new tmp file later (imgDel will close() this one for us)
774       reporter.info(msg);
775       img = 0;
776       Paranoid(task == CREATE_TMP);
777     } else {
778       // Reuse temporary file
779       task = MERGE_TMP;
780       files.swap(filesTmp);
781       Assert(!filesTmp.empty() && img != 0);
782     }
783   } // endif (tmp file exists)
784
785   Paranoid((task == MERGE_TMP) == (img != 0));
786   //____________________
787
788   /* Variables now in use:
789      enum task:
790              Mode of operation (CREATE_TMP/MERGE_TMP/SINGLE_PASS)
791      JigdoDescVec files:
792              Contents of image, maybe with some WrittenFiles if MERGEing
793      istream* templ: Template data (stream pointer at end of file)
794      fstream* img: Temporary file if MERGE_TMP, else null
795   */
796
797   /* Create queue of files that need to be copied to the image. Later
798      on, we will be pop()ing to get to the actual filenames in order.
799      Referenced FileParts are owned by the JigdoCache - never delete
800      them. */
801   queue<FilePart*> toCopy;
802   int missing = 0; // Nr of files that were not found
803   JigdoCache::iterator ci, ce = cache->end();
804   uint64 totalBytes = 0; // Total amount of data to be written, for "x% done"
805
806   for (vector<JigdoDesc*>::iterator i = files.begin(), e = files.end();
807        i != e; ++i) {
808     // Need this extra test because we do *not* want the WrittenFiles
809     if ((*i)->type() != MATCHED_FILE) continue;
810     MatchedFile* m = dynamic_cast<MatchedFile*>(*i);
811     Paranoid(m != 0);
812     //totalBytes += m->size();
813
814     // Search for file with matching MD5 sum
815     ci = cache->begin();
816     bool found = false;
817     while (ci != ce) {
818       // The call to getMD5Sum() may cause the whole file to be read!
819       const MD5Sum* md = ci->getMD5Sum(cache);
820       if (md != 0 && *md == m->md5()) {
821         toCopy.push(&*ci); // Found matching file
822         totalBytes += m->size();
823         debug("%1 found, pushed %2", m->md5().toString(), &*ci);
824         found = true;
825         break;
826       }
827       ++ci;
828     }
829     if (!found) ++missing;
830
831   }
832   //____________________
833
834   debug("JigdoDesc::mkImage: %1 missing, %2 found for copying to image, "
835         "%3 entries in template", missing, toCopy.size(), files.size());
836
837   // Files appearing >1 times are counted >1 times for the message
838   string missingInfo = subst(
839       _("Found %1 of the %2 files required by the template"),
840       toCopy.size(), toCopy.size() + missing);
841   reporter.info(missingInfo);
842   //____________________
843
844   /* There used to be the following here:
845      | If possible (i.e. all files present, tmp file not yet created),
846      | avoid creating any tmp file at all.
847      | if (task == CREATE_TMP && missing == 0) task = SINGLE_PASS;
848      We do not do this because even though it says "missing==0" *now*,
849      there could be a read error from one of the files when we
850      actually access it, in which case we should be able to ignore the
851      error for the moment, and leave behind a partially complete .tmp
852      file. */
853
854   /* Do nothing at all if a) no tmp file created yet, and b) *none* of
855      the supplied files matched one of the missing parts, and c) the
856      template actually contains at least one MatchedFile (i.e. *do*
857      write if template consists entirely of UnmatchedData). */
858 # ifndef MKIMAGE_ALWAYS_CREATE_TMPFILE
859   if (task == CREATE_TMP && toCopy.size() == 0 && missing != 0) {
860     const char* m = _("Will not create image or temporary file - try again "
861                       "with different input files");
862     reporter.info(m);
863     return 1; // Return value: "Soft failure - may retry with more files"
864   }
865
866   // Give error if unable to create image in one pass
867   if (task == SINGLE_PASS && missing > 0) {
868     reporter.error(_("Cannot create image because of missing files"));
869     return 3; // Permanent failure
870   }
871 # endif
872   //____________________
873
874   if (task == MERGE_TMP) { // If MERGEing, img was already set up above
875     int result = writeMerge(files, toCopy, missing, readAmount, img,
876                             imageTmpFile, optMkImageCheck, reporter, cache,
877                             totalBytes);
878     if (missing != 0 && result < 3)
879       info_NeedMoreFiles(reporter, imageTmpFile);
880     if (result == 0) {
881       if (compat_rename(imageTmpFile.c_str(), imageFile.c_str()) != 0)
882         return error_CouldntRename(reporter, imageTmpFile.c_str(),
883                                    imageFile.c_str());
884       string info = subst(_("Successfully created `%1'"), imageFile);
885       reporter.info(info);
886     }
887     return result;
888   }
889
890   // task == CREATE_TMP || task == SINGLE_PASS
891
892   // Assign a stream to img which we're going to write image data to
893   // If necessary, create a new temporary/output file
894   const char* name;
895   const char* finalName = 0;
896   if (task == CREATE_TMP) { // CREATE new tmp file
897     name = imageTmpFile.c_str();
898     finalName = imageFile.c_str();
899     imgDel.reset(new bfstream(name, ios::binary|ios::out|ios::trunc));
900     img = imgDel.get();
901   } else if (imageFile != "-") { // SINGLE_PASS; create output file
902     name = imageFile.c_str();
903     imgDel.reset(new bfstream(name, ios::binary|ios::out|ios::trunc));
904     img = imgDel.get();
905   } else { // SINGLE_PASS, outputting to stdout
906     name = "-";
907     imgDel.reset(0);
908     img = 0; // Cannot do "img = &cout", so img==0 is special case: stdout
909   }
910   if (img != 0 && !*img) {
911     string err = subst(_("Could not open `%1' for output: %2"),
912                        name, strerror(errno));
913     reporter.error(err);
914     return 3; // Permanent failure
915   }
916
917   /* Above, totalBytes was calculated for the case of a MERGE_TMP. If
918      we're not merging, we need to write everything. */
919   Assert(files.back()->type() == IMAGE_INFO);
920   uint64 imageSize = files.back()->size();
921   totalBytes = imageSize;
922 # if 0 /* # if WINDOWS */
923   /* The C++ STL of the MinGW 1.1 gcc port for Windows doesn't support
924      files >2GB. Fail early and with a clear error message... */
925   if (imageSize >= (1U<<31))
926     throw Error(_("Sorry, at the moment the Windows port of jigdo cannot "
927                   "create files bigger than 2 GB. Use the Linux version."));
928 # endif
929
930   int result = writeAll(task, files, toCopy, templ, readAmount, img, name,
931                         optMkImageCheck, reporter, cache, totalBytes);
932   if (result >= 3) return result;
933
934   if (task == CREATE_TMP && result == 1) {
935     info_NeedMoreFiles(reporter, imageTmpFile);
936   } else if (result == 0) {
937     if (img != 0)
938       img->close(); // Necessary on Windows before renaming is possible
939     if (finalName != 0 && compat_rename(name, finalName) != 0)
940       return error_CouldntRename(reporter, name, finalName);
941     string info = subst(_("Successfully created `%1'"), imageFile);
942     reporter.info(info);
943   }
944   return result;
945 }
946 //______________________________________________________________________
947
948 int JigdoDesc::listMissing(set<MD5>& result, const string& imageTmpFile,
949     const string& templFile, bistream* templ, ProgressReporter& reporter) {
950   result.clear();
951
952   // Read info from template
953   JigdoDescVec contents;
954   readTemplate(contents, templFile, templ);
955
956   // Read info from temporary file, if any
957   if (!imageTmpFile.empty()) {
958     bifstream imageTmp(imageTmpFile.c_str(), ios::binary);
959     if (imageTmp) {
960       JigdoDescVec contentsTmp;
961       const char* wontReuse = readTmpFile(imageTmp, contentsTmp, contents);
962       if (wontReuse != 0) {
963         string msg = subst(_("Ignoring existing temporary file `%1' - %2"),
964                            imageTmpFile, wontReuse);
965         reporter.info(msg);
966       } else {
967         // tmp file present & valid - use *it* below to output missing parts
968         swap(contents, contentsTmp);
969       }
970     }
971   }
972
973   // Output MD5 sums of MatchedFile (but not WrittenFile) entries
974   for (size_t i = 0; i < contents.size() - 1; ++i) {
975     MatchedFile* mf = dynamic_cast<MatchedFile*>(contents[i]);
976     if (mf != 0 && mf->type() == MATCHED_FILE)
977       result.insert(mf->md5());
978   }
979   return 0;
980 }
981 //______________________________________________________________________
982
983 void JigdoDesc::ProgressReporter::error(const string& message) {
984   cerr << message << endl;
985 }
986 void JigdoDesc::ProgressReporter::info(const string& message) {
987   cerr << message << endl;
988 }
989 void JigdoDesc::ProgressReporter::writingImage(uint64, uint64, uint64,
990                                               uint64) { }
991
992 JigdoDesc::ProgressReporter JigdoDesc::noReport;