1 /* $Id: mkimage.cc,v 1.15 2005/07/09 19:14:46 atterer Exp $ -*- C++ -*-
3 |_) /| Copyright (C) 2001-2003 | richard@
4 | \/¯| Richard Atterer | atterer.org
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.
10 Create image from template / merge new files into image.tmp
19 #include <sys/types.h>
21 #include <unistd-jigdo.h>
32 #include <serialize.hh>
34 #include <zstream-gz.hh>
35 #include <mktemplate.hh>
37 //______________________________________________________________________
39 DEBUG_UNIT("make-image")
43 typedef JigdoDesc::ProgressReporter ProgressReporter;
45 // memset() is not portable enough...
46 void memClear(byte* buf, size_t size) {
48 *buf++ = 0; *buf++ = 0; *buf++ = 0; *buf++ = 0;
49 *buf++ = 0; *buf++ = 0; *buf++ = 0; *buf++ = 0;
59 //______________________________________________________________________
61 JigdoDesc::~JigdoDesc() { }
62 //______________________________________________________________________
64 bool JigdoDesc::isTemplate(bistream& file) {
65 if (!file.seekg(0, ios::beg)) return false;
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;
75 //______________________________________________________________________
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()));
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?"));
89 file.seekg(-descLen, ios::end);
90 debug("JigdoDesc::seekFromEnd2: now at file offset %1",
91 static_cast<uint64>(file.tellg()));
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;
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?"));
112 //______________________________________________________________________
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 =>
118 auto_ptr<JigdoDesc> desc;
121 SerialIstreamIterator f(file);
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?"));
129 //____________________
131 uint64 off = 0; // Offset in image
132 uint64 read = 0; // Nr of bytes read
138 while (file && read < len) {
143 case JigdoDesc::IMAGE_INFO_MD5:
144 unserialize6(entryLen, f);
145 unserialize(entryMd5, f);
146 unserialize4(blockLength, f);
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;
155 case JigdoDesc::IMAGE_INFO_SHA256:
156 unserialize6(entryLen, f);
157 unserialize(entrySha256, f);
158 unserialize4(blockLength, f);
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;
167 case JigdoDesc::UNMATCHED_DATA:
168 unserialize6(entryLen, f);
170 debug("JigdoDesc::read: %1 UnmatchedData %2", off, entryLen);
171 desc.reset(new JigdoDesc::UnmatchedData(off, entryLen));
172 push_back(desc.release());
177 case JigdoDesc::MATCHED_FILE_MD5:
178 case JigdoDesc::WRITTEN_FILE_MD5:
179 unserialize6(entryLen, f);
180 unserialize(rsum, f);
181 unserialize(entryMd5, f);
183 debug("JigdoDesc::read: %1 %2File %3 %4",
184 off, (type == JigdoDesc::MATCHED_FILE_MD5 ? "Matched" : "Written"),
185 entryLen, entryMd5.toString());
186 if (type == JigdoDesc::MATCHED_FILE_MD5)
187 desc.reset(new JigdoDesc::MatchedFileMD5(off, entryLen, rsum,entryMd5));
189 desc.reset(new JigdoDesc::WrittenFileMD5(off, entryLen, rsum,entryMd5));
190 push_back(desc.release());
191 read += 1 + 6 + rsum.serialSizeOf() + entryMd5.serialSizeOf();
195 case JigdoDesc::MATCHED_FILE_SHA256:
196 case JigdoDesc::WRITTEN_FILE_SHA256:
197 unserialize6(entryLen, f);
198 unserialize(rsum, f);
199 unserialize(entrySha256, f);
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));
207 desc.reset(new JigdoDesc::WrittenFileSHA256(off, entryLen, rsum,entrySha256));
208 push_back(desc.release());
209 read += 1 + 6 + rsum.serialSizeOf() + entrySha256.serialSizeOf();
213 // Template entry types that were obsoleted with version 0.6.3:
215 case JigdoDesc::OBSOLETE_IMAGE_INFO:
216 unserialize6(entryLen, f);
217 unserialize(entryMd5, f);
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();
227 case JigdoDesc::OBSOLETE_MATCHED_FILE:
228 case JigdoDesc::OBSOLETE_WRITTEN_FILE:
229 unserialize6(entryLen, f);
230 unserialize(entryMd5, f);
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. */
238 if (type == JigdoDesc::OBSOLETE_MATCHED_FILE)
239 desc.reset(new JigdoDesc::MatchedFileMD5(off, entryLen, rsum,entryMd5));
241 desc.reset(new JigdoDesc::WrittenFileMD5(off, entryLen, rsum,entryMd5));
242 push_back(desc.release());
243 read += 1 + 6 + entryMd5.serialSizeOf();
248 debug("JigdoDesc::read: unknown type %1", type);
249 throw JigdoDescError(_("Invalid template data - corrupted file?"));
252 //____________________
255 string err = subst(_("Error reading template data (%1)"),
257 throw JigdoDescError(err);
261 throw JigdoDescError(_("Invalid template data - corrupted file?"));
263 // Sanity check for an imageInfoMD5/imageInfoSHA256 entry on the end of
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?"));
274 //______________________________________________________________________
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);
286 if (DEBUG) bufLen += 1;
288 // Pass 2: Write DESC part
290 if (DEBUG) buf[bufLen - 1] = 0xa5;
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* matchedmd5;
304 JigdoDesc::WrittenFileMD5* writtenmd5;
305 JigdoDesc::MatchedFileSHA256* matchedsha;
306 JigdoDesc::WrittenFileSHA256* writtensha;
308 if ((unm = dynamic_cast<JigdoDesc::UnmatchedData*>(*i)) != 0)
309 p = unm->serialize(buf);
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 ((writtenmd5 = dynamic_cast<JigdoDesc::WrittenFileMD5*>(*i)) != 0) {
317 p = writtenmd5->serialize(buf);
318 } else if ((matchedmd5 = dynamic_cast<JigdoDesc::MatchedFileMD5*>(*i)) != 0) {
319 p = matchedmd5->serialize(buf);
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);
329 else if ((matchedsha = dynamic_cast<JigdoDesc::MatchedFileSHA256*>(*i)) != 0) {
330 p = matchedsha->serialize(buf);
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);
340 p = serialize6(descLen, buf);
341 writeBytes(file, buf, 6);
345 sd->update(buf, p - buf);
346 if (DEBUG) { Assert(buf[bufLen - 1] == 0xa5); }
349 //______________________________________________________________________
352 const int J_SIZE_WIDTH = 12;
353 const int J_CSUM_WIDTH = 46;
354 const int J_RSYNC_WIDTH = 12;
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()
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()
373 ostream& JigdoDesc::UnmatchedData::put(ostream& s) const {
375 << setw(J_SIZE_WIDTH) << offset()
376 << " " << setw(J_SIZE_WIDTH) << size()
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()
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()
399 ostream& JigdoDesc::WrittenFileMD5::put(ostream& s) const {
401 << setw(J_SIZE_WIDTH) << offset()
402 << " " << setw(J_SIZE_WIDTH) << size()
403 << " " << setw(J_CSUM_WIDTH) << md5()
404 << " " << setw(J_RSYNC_WIDTH) << rsync()
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()
418 void JigdoDescVec::list(ostream& s) throw() {
419 for (const_iterator i = begin(), e = end(); i != e; ++i) s << (**i);
422 //______________________________________________________________________
426 /* Helper functions for makeImage below, declared inline if only
429 /// Type of operation when recreating image data
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
436 //______________________________
438 inline void reportBytesWritten(const uint64 n, uint64& off,
439 uint64& nextReport, const uint64 totalBytes,
440 ProgressReporter& reporter) {
442 if (off >= nextReport) { // Keep user entertained
443 reporter.writingImage(off, totalBytes, off, totalBytes);
444 nextReport += REPORT_INTERVAL;
447 //______________________________
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();
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
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);
472 writeBytes(*img, buf, n);
473 reportBytesWritten(n, off, nextReport, totalBytes, reporter);
477 Paranoid(rl < rsyncLen);
478 size_t rsyncToAdd = rsyncLen - rl;
479 if (rsyncToAdd > n) rsyncToAdd = n;
480 rs.addBack(buf, rsyncToAdd);
482 Paranoid(rl <= rsyncLen);
483 if (rl >= rsyncLen) break;
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);
491 writeBytes(*img, buf, n);
492 reportBytesWritten(n, off, nextReport, totalBytes, reporter);
494 if (checkChecksum) md.update(buf, n);
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);
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"),
517 if (err.empty()) return 0; // Success
520 return 2; // "May have to fix something before you can continue"
522 return 3; // Yaargh, disaster! Please delete the .tmp file for me
524 //______________________________
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
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();
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
543 // Read from file, write to image
544 // First couple of k: Calculate RsyncSum rs and SHA256Sum 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);
550 writeBytes(*img, buf, n);
551 reportBytesWritten(n, off, nextReport, totalBytes, reporter);
555 Paranoid(rl < rsyncLen);
556 size_t rsyncToAdd = rsyncLen - rl;
557 if (rsyncToAdd > n) rsyncToAdd = n;
558 rs.addBack(buf, rsyncToAdd);
560 Paranoid(rl <= rsyncLen);
561 if (rl >= rsyncLen) break;
564 // Rest of file: Only calculate SHA256Sum md
565 while (*img && f && !f.eof() && toWrite > 0) {
566 size_t n = (toWrite < readAmount ? toWrite : readAmount);
567 readBytes(f, buf, n);
569 writeBytes(*img, buf, n);
570 reportBytesWritten(n, off, nextReport, totalBytes, reporter);
572 if (checkChecksum) md.update(buf, n);
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);
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"),
595 if (err.empty()) return 0; // Success
598 return 2; // "May have to fix something before you can continue"
600 return 3; // Yaargh, disaster! Please delete the .tmp file for me
602 //______________________________
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.
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
615 @param name Filename corresponding to img
616 @param totalBytes length of image
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) {
626 bool isTemplate = JigdoDesc::isTemplate(*templ); // seek to 1st DATA part
629 uint64 off = 0; // Current offset in image
630 uint64 nextReport = 0; // At what value of off to call reporter
631 uint64 blockLength = 0;
632 uint64 imageSize = 0;
634 vector<byte> bufVec(readAmount);
635 byte* buf = &bufVec[0];
636 /* Use an additional 8k of zip buffer. This is good if the
637 unmatched image data is already compressed, which means that
638 when it is compressed again by jigdo, it will get slightly
640 auto_ptr<Zibstream> data(new Zibstream(*templ, (unsigned int)readAmount + 8*1024));
641 # if HAVE_WORKING_FSTREAM
642 if (img == 0) img = &cout; // EEEEEK!
644 if (img == 0) img = &bcout;
647 /* First, find basic information about the image */
648 JigdoDesc::ImageInfoMD5* imageInfoMD5 =
649 dynamic_cast<JigdoDesc::ImageInfoMD5*>(files.back());
650 if (imageInfoMD5 != 0) {
651 blockLength = imageInfoMD5->blockLength();
652 imageSize = imageInfoMD5->size();
654 JigdoDesc::ImageInfoSHA256* imageInfoSHA256 =
655 dynamic_cast<JigdoDesc::ImageInfoSHA256*>(files.back());
656 if (imageInfoSHA256) {
657 blockLength = imageInfoSHA256->blockLength();
658 imageSize = imageInfoSHA256->size();
661 if (blockLength == 0 || imageSize == 0) {
662 reporter.error(_("Unable to find a valid image info block"));
667 for (JigdoDescVec::iterator i = files.begin(), e = files.end();
669 //____________________
671 /* Write all data for this part to img stream. In case of
672 MatchedFile*, write the appropriate number of bytes (of junk
673 data) even if file not present. [Using switch(type()) not
674 nice, but using virtual methods looks even worse.] */
675 switch ((*i)->type()) {
676 case JigdoDesc::IMAGE_INFO_MD5:
677 case JigdoDesc::IMAGE_INFO_SHA256:
679 case JigdoDesc::UNMATCHED_DATA: {
680 // Copy data from Zibstream to image.
681 JigdoDesc::UnmatchedData& self =
682 dynamic_cast<JigdoDesc::UnmatchedData&>(**i);
683 uint64 toWrite = self.size();
684 debug("mkimage writeAll(): %1 of unmatched data", toWrite);
685 memClear(buf, readAmount);
686 while (*img && toWrite > 0) {
688 reporter.error(_("Premature end of template data"));
691 data->read(buf, (unsigned int)(toWrite < readAmount ? toWrite : readAmount));
692 size_t n = data->gcount();
693 writeBytes(*img, buf, n);
694 reportBytesWritten(n, off, nextReport, totalBytes, reporter);
699 case JigdoDesc::MATCHED_FILE_MD5: {
700 /* If file present in cache, copy its data to image, if
701 not, copy zeroes. if check==true, verify MD sum match.
702 If successful, turn MatchedFileMD5 into WrittenFileMD5. */
703 JigdoDesc::MatchedFileMD5* self =
704 dynamic_cast<JigdoDesc::MatchedFileMD5*>(*i);
705 uint64 toWrite = self->size();
707 if (!toCopy.empty()) mfile = toCopy.front();
708 debug("mkimage writeAll(): FilePart@%1, %2 of matched file `%3',"
709 " toCopy size %4", mfile, toWrite,
710 (mfile != 0 ? mfile->leafName() : ""), toCopy.size());
711 if (mfile == 0 || self->md5() != *(mfile->getMD5Sum(cache))) {
712 // Write right amount of zeroes
713 memClear(buf, readAmount);
714 while (*img && toWrite > 0) {
715 size_t n = (toWrite < readAmount ? toWrite : readAmount);
716 writeBytes(*img, buf, n);
717 reportBytesWritten(n, off, nextReport, totalBytes, reporter);
720 if (result == 0) result = 1; // Soft failure
722 /* Copy data from file to image, taking care not to
723 write beyond toWrite. */
724 int status = fileToImageMD5(img, *mfile, *self, checkChecksum,
725 blockLength, reporter, buf, readAmount, off,
726 nextReport, totalBytes);
728 if (result < status) result = status;
729 if (status == 0) { // Mark file as written to image
730 *i = new JigdoDesc::WrittenFileMD5(self->offset(), self->size(),
731 self->rsync(), self->md5());
733 } else if (*img && (status > 2 || task == SINGLE_PASS)) {
734 // If !*img, exit after error msg below
735 /* If status <= 2 and task == {CREATE_TMP,MERGE_TMP},
736 we can continue; there has been an error copying
737 this individual file, but the right *amount* of
738 data has been written to the .tmp output file, and
739 the user may retry the failed one later. */
745 case JigdoDesc::MATCHED_FILE_SHA256: {
746 /* If file present in cache, copy its data to image, if
747 not, copy zeroes. if check==true, verify SHA256 sum match.
748 If successful, turn MatchedFileSHA256 into WrittenFileSHA256. */
749 JigdoDesc::MatchedFileSHA256* self =
750 dynamic_cast<JigdoDesc::MatchedFileSHA256*>(*i);
751 uint64 toWrite = self->size();
753 if (!toCopy.empty()) mfile = toCopy.front();
754 debug("mkimage writeAll(): FilePart@%1, %2 of matched file `%3',"
755 " toCopy size %4", mfile, toWrite,
756 (mfile != 0 ? mfile->leafName() : ""), toCopy.size());
757 if (mfile == 0 || self->sha256() != *(mfile->getSHA256Sum(cache))) {
758 // Write right amount of zeroes
759 memClear(buf, readAmount);
760 while (*img && toWrite > 0) {
761 size_t n = (toWrite < readAmount ? toWrite : readAmount);
762 writeBytes(*img, buf, n);
763 reportBytesWritten(n, off, nextReport, totalBytes, reporter);
766 if (result == 0) result = 1; // Soft failure
768 /* Copy data from file to image, taking care not to
769 write beyond toWrite. */
770 int status = fileToImageSHA256(img, *mfile, *self, checkChecksum,
771 blockLength, reporter, buf, readAmount, off,
772 nextReport, totalBytes);
774 if (result < status) result = status;
775 if (status == 0) { // Mark file as written to image
776 *i = new JigdoDesc::WrittenFileSHA256(self->offset(), self->size(),
777 self->rsync(), self->sha256());
779 } else if (*img && (status > 2 || task == SINGLE_PASS)) {
780 // If !*img, exit after error msg below
781 /* If status <= 2 and task == {CREATE_TMP,MERGE_TMP},
782 we can continue; there has been an error copying
783 this individual file, but the right *amount* of
784 data has been written to the .tmp output file, and
785 the user may retry the failed one later. */
791 case JigdoDesc::WRITTEN_FILE_MD5:
792 case JigdoDesc::WRITTEN_FILE_SHA256:
793 // These are never present in memory, cannot occur:
794 case JigdoDesc::OBSOLETE_IMAGE_INFO:
795 case JigdoDesc::OBSOLETE_MATCHED_FILE:
796 case JigdoDesc::OBSOLETE_WRITTEN_FILE:
797 debug("mkimage writeAll(): invalid type %1", (*i)->type());
799 _("Error - template data's DESC section invalid"));
800 Assert(false); // A WrittenFile* cannot occur here
804 //____________________
806 // Error while writing to image?
808 string err = subst(_("Error while writing to `%1' (%2)"),
809 name, strerror(errno));
813 //____________________
815 } // end iterating over 'files'
818 // Error while unpacking template data
819 reporter.error(e.message); return 3;
822 // If we created a new tmp file, append DESC info
823 if (task == CREATE_TMP && result > 0) {
827 // Must have "used up" all the parts that we found earlier
828 Assert(toCopy.empty());
829 return result; // 0 or 1
831 //______________________________
833 /* A temporary file already exists. Write the files listed in toCopy
834 to this temporary file. If image is now completed, truncate it to
835 its final length (removing the DESC section at the end),
836 otherwise update the DESC section (turn some need-file/
837 MatchedFile* entries into have-file/WrittenFile* entries). If 0 is
838 returned, caller should rename file to remove .tmp extension. */
839 inline int writeMerge(JigdoDescVec& files, queue<FilePart*>& toCopy,
840 const int missing, const size_t readAmount, bfstream* img,
841 const string& imageTmpFile, bool checkChecksum, ProgressReporter& reporter,
842 JigdoCache* cache, const uint64 totalBytes) {
843 vector<byte> bufVec(readAmount);
844 byte* buf = &bufVec[0];
845 int result = (missing == 0 ? 0 : 1);
846 uint64 bytesWritten = 0; // For 'x% done' calls to reporter
847 uint64 nextReport = 0; // At what value of bytesWritten to call reporter
848 uint64 blockLength = 0;
849 uint64 imageSize = 0;
851 /* First, find basic information about the image */
852 JigdoDesc::ImageInfoMD5* imageInfoMD5 =
853 dynamic_cast<JigdoDesc::ImageInfoMD5*>(files.back());
855 blockLength = imageInfoMD5->blockLength();
856 imageSize = imageInfoMD5->size();
858 JigdoDesc::ImageInfoSHA256* imageInfoSHA256 =
859 dynamic_cast<JigdoDesc::ImageInfoSHA256*>(files.back());
860 if (imageInfoSHA256) {
861 blockLength = imageInfoSHA256->blockLength();
862 imageSize = imageInfoSHA256->size();
865 if (blockLength == 0 || imageSize == 0) {
866 reporter.error(_("Unable to find a valid image info block"));
870 if (toCopy.empty() && missing > 0) return 1;
871 for (JigdoDescVec::iterator i = files.begin(), e = files.end();
874 JigdoDesc::MatchedFileMD5* matchMD5 =
875 dynamic_cast<JigdoDesc::MatchedFileMD5*>(*i);
876 JigdoDesc::MatchedFileSHA256* matchSHA256 =
877 dynamic_cast<JigdoDesc::MatchedFileSHA256*>(*i);
879 // Compare to 'case JigdoDesc::MATCHED_FILE_MD5:' clause in writeAll()
882 if (!toCopy.empty()) mfile = toCopy.front();
883 debug("mkimage writeMerge(): FilePart@%1, %2 of matched file `%3', "
884 "toCopy size %4", mfile, matchMD5->size(),
885 (mfile != 0 ? mfile->leafName() : ""), toCopy.size());
886 if (mfile == 0 || matchMD5->md5() != *(mfile->getMD5Sum(cache)))
889 /* Copy data from file to image, taking care not to write
890 beyond matchMD5->size(). */
891 img->seekp(matchMD5->offset(), ios::beg);
893 reporter.error(_("Error - could not access temporary file"));
897 int status = fileToImageMD5(img, *mfile, *matchMD5, checkChecksum,
898 blockLength, reporter, buf, readAmount, bytesWritten,
899 nextReport, totalBytes);
903 if (status == 0) { // Mark file as written to image
904 *i = new JigdoDesc::WrittenFileMD5(matchMD5->offset(),
909 } else if (status > 2) {
912 } else if (matchSHA256 != 0) {
914 if (!toCopy.empty()) mfile = toCopy.front();
915 debug("mkimage writeMerge(): FilePart@%1, %2 of matched file `%3', "
916 "toCopy size %4", mfile, matchSHA256->size(),
917 (mfile != 0 ? mfile->leafName() : ""), toCopy.size());
918 if (mfile == 0 || matchSHA256->sha256() != *(mfile->getSHA256Sum(cache)))
921 /* Copy data from file to image, taking care not to write
922 beyond matchSHA256->size(). */
923 img->seekp(matchSHA256->offset(), ios::beg);
925 reporter.error(_("Error - could not access temporary file"));
929 int status = fileToImageSHA256(img, *mfile, *matchSHA256, checkChecksum,
930 blockLength, reporter, buf, readAmount, bytesWritten,
931 nextReport, totalBytes);
935 if (status == 0) { // Mark file as written to image
936 *i = new JigdoDesc::WrittenFileSHA256(matchSHA256->offset(),
938 matchSHA256->rsync(),
939 matchSHA256->sha256());
941 } else if (status > 2) {
945 /* no match for either matchMD5 or matchSHA256 */
947 } // end iterating over 'files'
949 if (missing == 0 && result == 0) {
950 img->close(); // Necessary on Windows before truncating is possible
951 // Truncate to final image size
952 const char* tmpName = imageTmpFile.c_str();
953 if (compat_truncate(tmpName, imageSize) != 0) {
954 string err = subst(_("Could not truncate `%1' (%2)"),
955 imageTmpFile, strerror(errno));
961 // Update DESC section at end of temporary file
962 img->seekp(imageSize);
963 // No need to truncate here because DESC section never changes size
969 //______________________________
971 int info_NeedMoreFiles(ProgressReporter& reporter, const string& tmpName) {
972 string info = subst(_(
973 "Copied input files to temporary file `%1' - "
974 "repeat command and supply more files to continue"), tmpName);
976 return 1; // Soft failure
979 int error_CouldntRename(ProgressReporter& reporter, const char* name,
980 const char* finalName) {
981 string err = subst(_(
982 "Could not move finished image from `%1' to `%2' (%3)"),
983 name, finalName, strerror(errno));
988 } // end local namespace
989 //______________________________________________________________________
993 /// Read template data from templ (name in templFile) into files
994 void readTemplate(JigdoDescVec& files, const string& templFile,
996 if (JigdoDesc::isTemplate(*templ) == false) { // Check for template hdr
997 string err = subst(_("`%1' is not a template file"), templFile);
998 throw JigdoDescError(err);
1000 /* Read info at end of template data. NB: Exceptions are not
1001 caught here, but e.g. in ::makeImage() (cf. jigdo-file.cc) */
1002 JigdoDesc::seekFromEnd(*templ);
1005 //________________________________________
1007 /** Read data from end of temporary file imageTmp, output it to
1008 filesTmp. Next, compare it to template data in "files". If tmp
1009 file is OK for re-using return NULL - this means that the DESC
1010 entries match *exactly* - the only difference allowed is
1011 MatchedFile* turning into WrittenFile*. Otherwise, return a
1012 pointer to an error message describing the reason why the
1013 tmpfile data does not match the template data. */
1014 const char* readTmpFile(bistream& imageTmp, JigdoDescVec& filesTmp,
1015 const JigdoDescVec& files) {
1017 JigdoDesc::seekFromEnd(imageTmp);
1018 imageTmp >> filesTmp;
1019 } catch (JigdoDescError e) {
1020 return _("it was not created by jigdo-file, or is corrupted.");
1022 if (*files.back() != *filesTmp.back())
1023 return _("it corresponds to a different image/template.");
1024 if (files.size() != filesTmp.size())
1025 return _("since its creation, the template was regenerated.");
1026 for (size_t i = 0; i < files.size() - 1; ++i) {
1027 //cerr << "cmp " << i << '/' << (files.size() - 1) << endl;
1028 if (*files[i] != *filesTmp[i])
1029 return _("since its creation, the template was regenerated.");
1035 //________________________________________
1037 /* If imageTmpFile.empty(), must either write whole image or nothing
1038 at all. image and temporary file are created as needed, ditto for
1039 renaming of temporary to image. The cache must not throw errors. */
1040 int JigdoDesc::makeImage(JigdoCache* cache, const string& imageFile,
1041 const string& imageTmpFile, const string& templFile,
1042 bistream* templ, const bool optForce, ProgressReporter& reporter,
1043 const size_t readAmount, const bool optMkImageCheck) {
1045 Task task = CREATE_TMP;
1047 if (imageFile == "-" || imageTmpFile.empty()) task = SINGLE_PASS;
1048 //____________________
1050 // Read info from template
1052 readTemplate(files, templFile, templ);
1053 //____________________
1055 // Do we need to add new stuff to an existing tmp file?
1056 bfstream* img = 0; // Non-null => tmp file exists
1057 auto_ptr<bfstream> imgDel(img);
1058 struct stat fileInfo;
1059 if (task != SINGLE_PASS && stat(imageTmpFile.c_str(), &fileInfo) == 0) {
1060 /* A tmp file already exists. We'll only reuse it if the DESC
1061 entries match exactly. Otherwise, if --force enabled, overwrite
1063 const char* wontReuse = 0; // non-NULL means: will not reuse tmp file
1064 JigdoDescVec filesTmp;
1065 imgDel.reset(new bfstream(imageTmpFile.c_str(),
1066 ios::binary|ios::in|ios::out));
1069 wontReuse = strerror(errno);
1071 wontReuse = readTmpFile(*img, filesTmp, files);
1073 if (wontReuse != 0) {
1074 // Print out message
1075 string msg = subst(_("Will not reuse existing temporary file `%1' - "
1076 "%2"), imageTmpFile, wontReuse);
1077 // Return error if not allowed to overwrite tmp file
1079 reporter.error(msg);
1080 throw Error(_("Delete/rename the file or use --force"));
1082 // Open a new tmp file later (imgDel will close() this one for us)
1085 Paranoid(task == CREATE_TMP);
1087 // Reuse temporary file
1089 files.swap(filesTmp);
1090 Assert(!filesTmp.empty() && img != 0);
1092 } // endif (tmp file exists)
1094 Paranoid((task == MERGE_TMP) == (img != 0));
1095 //____________________
1097 /* Variables now in use:
1099 Mode of operation (CREATE_TMP/MERGE_TMP/SINGLE_PASS)
1101 Contents of image, maybe with some WrittenFile*s if MERGEing
1102 istream* templ: Template data (stream pointer at end of file)
1103 fstream* img: Temporary file if MERGE_TMP, else null
1106 /* Create queue of files that need to be copied to the image. Later
1107 on, we will be pop()ing to get to the actual filenames in order.
1108 Referenced FileParts are owned by the JigdoCache - never delete
1110 queue<FilePart*> toCopy;
1111 int missing = 0; // Nr of files that were not found
1112 JigdoCache::iterator ci, ce = cache->end();
1113 uint64 totalBytes = 0; // Total amount of data to be written, for "x% done"
1115 for (vector<JigdoDesc*>::iterator i = files.begin(), e = files.end();
1117 // Need this extra test because we do *not* want the WrittenFile*s
1118 switch ((*i)->type()) {
1120 case MATCHED_FILE_MD5:
1122 MatchedFileMD5* m = dynamic_cast<MatchedFileMD5*>(*i);
1124 //totalBytes += m->size();
1126 // Search for file with matching MD5 sum
1127 ci = cache->begin();
1130 // The call to getMD5Sum() may cause the whole file to be read!
1131 const MD5Sum* md = ci->getMD5Sum(cache);
1132 if (md != 0 && *md == m->md5()) {
1133 toCopy.push(&*ci); // Found matching file
1134 totalBytes += m->size();
1135 debug("%1 found, pushed %2", m->md5().toString(), &*ci);
1146 case MATCHED_FILE_SHA256:
1148 MatchedFileSHA256* m = dynamic_cast<MatchedFileSHA256*>(*i);
1150 //totalBytes += m->size();
1152 // Search for file with matching SHA256 sum
1153 ci = cache->begin();
1156 // The call to getSHA256Sum() may cause the whole file to be read!
1157 const SHA256Sum* md = ci->getSHA256Sum(cache);
1158 if (md != 0 && *md == m->sha256()) {
1159 toCopy.push(&*ci); // Found matching file
1160 totalBytes += m->size();
1161 debug("%1 found, pushed %2", m->sha256().toString(), &*ci);
1178 //____________________
1180 debug("JigdoDesc::mkImage: %1 missing, %2 found for copying to image, "
1181 "%3 entries in template", missing, toCopy.size(), files.size());
1183 // Files appearing >1 times are counted >1 times for the message
1184 string missingInfo = subst(
1185 _("Found %1 of the %2 files required by the template"),
1186 toCopy.size(), toCopy.size() + missing);
1187 reporter.info(missingInfo);
1188 //____________________
1190 /* There used to be the following here:
1191 | If possible (i.e. all files present, tmp file not yet created),
1192 | avoid creating any tmp file at all.
1193 | if (task == CREATE_TMP && missing == 0) task = SINGLE_PASS;
1194 We do not do this because even though it says "missing==0" *now*,
1195 there could be a read error from one of the files when we
1196 actually access it, in which case we should be able to ignore the
1197 error for the moment, and leave behind a partially complete .tmp
1200 /* Do nothing at all if a) no tmp file created yet, and b) *none* of
1201 the supplied files matched one of the missing parts, and c) the
1202 template actually contains at least one MatchedFile* (i.e. *do*
1203 write if template consists entirely of UnmatchedData). */
1204 # ifndef MKIMAGE_ALWAYS_CREATE_TMPFILE
1205 if (task == CREATE_TMP && toCopy.size() == 0 && missing != 0) {
1206 const char* m = _("Will not create image or temporary file - try again "
1207 "with different input files");
1209 return 1; // Return value: "Soft failure - may retry with more files"
1212 // Give error if unable to create image in one pass
1213 if (task == SINGLE_PASS && missing > 0) {
1214 reporter.error(_("Cannot create image because of missing files"));
1215 return 3; // Permanent failure
1218 //____________________
1220 if (task == MERGE_TMP) { // If MERGEing, img was already set up above
1221 int result = writeMerge(files, toCopy, missing, readAmount, img,
1222 imageTmpFile, optMkImageCheck, reporter, cache,
1224 if (missing != 0 && result < 3)
1225 info_NeedMoreFiles(reporter, imageTmpFile);
1227 if (compat_rename(imageTmpFile.c_str(), imageFile.c_str()) != 0)
1228 return error_CouldntRename(reporter, imageTmpFile.c_str(),
1230 string info = subst(_("Successfully created `%1'"), imageFile);
1231 reporter.info(info);
1236 // task == CREATE_TMP || task == SINGLE_PASS
1238 // Assign a stream to img which we're going to write image data to
1239 // If necessary, create a new temporary/output file
1241 const char* finalName = 0;
1242 if (task == CREATE_TMP) { // CREATE new tmp file
1243 name = imageTmpFile.c_str();
1244 finalName = imageFile.c_str();
1245 imgDel.reset(new bfstream(name, ios::binary|ios::out|ios::trunc));
1247 } else if (imageFile != "-") { // SINGLE_PASS; create output file
1248 name = imageFile.c_str();
1249 imgDel.reset(new bfstream(name, ios::binary|ios::out|ios::trunc));
1251 } else { // SINGLE_PASS, outputting to stdout
1254 img = 0; // Cannot do "img = &cout", so img==0 is special case: stdout
1256 if (img != 0 && !*img) {
1257 string err = subst(_("Could not open `%1' for output: %2"),
1258 name, strerror(errno));
1259 reporter.error(err);
1260 return 3; // Permanent failure
1263 /* Above, totalBytes was calculated for the case of a MERGE_TMP. If
1264 we're not merging, we need to write everything. */
1265 Assert(files.back()->type() == IMAGE_INFO_MD5 ||
1266 files.back()->type() == IMAGE_INFO_SHA256);
1267 uint64 imageSize = files.back()->size();
1268 totalBytes = imageSize;
1269 # if 0 /* # if WINDOWS */
1270 /* The C++ STL of the MinGW 1.1 gcc port for Windows doesn't support
1271 files >2GB. Fail early and with a clear error message... */
1272 if (imageSize >= (1U<<31))
1273 throw Error(_("Sorry, at the moment the Windows port of jigdo cannot "
1274 "create files bigger than 2 GB. Use the Linux version."));
1277 int result = writeAll(task, files, toCopy, templ, readAmount, img, name,
1278 optMkImageCheck, reporter, cache, totalBytes);
1279 if (result >= 3) return result;
1281 if (task == CREATE_TMP && result == 1) {
1282 info_NeedMoreFiles(reporter, imageTmpFile);
1283 } else if (result == 0) {
1285 img->close(); // Necessary on Windows before renaming is possible
1286 if (finalName != 0 && compat_rename(name, finalName) != 0)
1287 return error_CouldntRename(reporter, name, finalName);
1288 string info = subst(_("Successfully created `%1'"), imageFile);
1289 reporter.info(info);
1293 //______________________________________________________________________
1295 int JigdoDesc::listMissingMD5(set<MD5>& result, const string& imageTmpFile,
1296 const string& templFile, bistream* templ, ProgressReporter& reporter) {
1299 // Read info from template
1300 JigdoDescVec contents;
1301 readTemplate(contents, templFile, templ);
1303 // Read info from temporary file, if any
1304 if (!imageTmpFile.empty()) {
1305 bifstream imageTmp(imageTmpFile.c_str(), ios::binary);
1307 JigdoDescVec contentsTmp;
1308 const char* wontReuse = readTmpFile(imageTmp, contentsTmp, contents);
1309 if (wontReuse != 0) {
1310 string msg = subst(_("Ignoring existing temporary file `%1' - %2"),
1311 imageTmpFile, wontReuse);
1314 // tmp file present & valid - use *it* below to output missing parts
1315 swap(contents, contentsTmp);
1320 // Output MD5 sums of MatchedFileMD5 (but not WrittenFileMD5) entries
1321 for (size_t i = 0; i < contents.size() - 1; ++i) {
1322 MatchedFileMD5* mf = dynamic_cast<MatchedFileMD5*>(contents[i]);
1323 if (mf != 0 && mf->type() == MATCHED_FILE_MD5)
1324 result.insert(mf->md5());
1328 //______________________________________________________________________
1330 int JigdoDesc::listMissingSHA256(set<SHA256>& result, const string& imageTmpFile,
1331 const string& templFile, bistream* templ, ProgressReporter& reporter) {
1334 // Read info from template
1335 JigdoDescVec contents;
1336 readTemplate(contents, templFile, templ);
1338 // Read info from temporary file, if any
1339 if (!imageTmpFile.empty()) {
1340 bifstream imageTmp(imageTmpFile.c_str(), ios::binary);
1342 JigdoDescVec contentsTmp;
1343 const char* wontReuse = readTmpFile(imageTmp, contentsTmp, contents);
1344 if (wontReuse != 0) {
1345 string msg = subst(_("Ignoring existing temporary file `%1' - %2"),
1346 imageTmpFile, wontReuse);
1349 // tmp file present & valid - use *it* below to output missing parts
1350 swap(contents, contentsTmp);
1355 // Output SHA256 sums of MatchedFileSHA256 (but not WrittenFileSHA256) entries
1356 for (size_t i = 0; i < contents.size() - 1; ++i) {
1357 MatchedFileSHA256* mf = dynamic_cast<MatchedFileSHA256*>(contents[i]);
1358 if (mf != 0 && mf->type() == MATCHED_FILE_SHA256)
1359 result.insert(mf->sha256());
1363 //______________________________________________________________________
1365 void JigdoDesc::ProgressReporter::error(const string& message) {
1366 cerr << message << endl;
1368 void JigdoDesc::ProgressReporter::info(const string& message) {
1369 cerr << message << endl;
1371 void JigdoDesc::ProgressReporter::writingImage(uint64, uint64, uint64,
1374 JigdoDesc::ProgressReporter JigdoDesc::noReport;