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:
178 case JigdoDesc::WRITTEN_FILE:
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 ? "Matched" : "Written"),
185 entryLen, entryMd5.toString());
186 if (type == JigdoDesc::MATCHED_FILE)
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* matched;
304 JigdoDesc::WrittenFileMD5* written;
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 ((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);
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 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);
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 MD5Sum 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
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
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!
642 if (img == 0) img = &bcout;
645 JigdoDesc::ImageInfoMD5& imageInfoMD5 =
646 dynamic_cast<JigdoDesc::ImageInfoMD5&>(*files.back());
649 for (JigdoDescVec::iterator i = files.begin(), e = files.end();
651 //____________________
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:
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) {
670 reporter.error(_("Premature end of template data"));
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);
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();
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);
702 if (result == 0) result = 1; // Soft failure
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);
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());
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. */
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();
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);
748 if (result == 0) result = 1; // Soft failure
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);
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());
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. */
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());
781 _("Error - template data's DESC section invalid"));
782 Assert(false); // A WrittenFileMD5 cannot occur here
786 //____________________
788 // Error while writing to image?
790 string err = subst(_("Error while writing to `%1' (%2)"),
791 name, strerror(errno));
795 //____________________
797 } // end iterating over 'files'
800 // Error while unpacking template data
801 reporter.error(e.message); return 3;
804 // If we created a new tmp file, append DESC info
805 if (task == CREATE_TMP && result > 0) {
809 // Must have "used up" all the parts that we found earlier
810 Assert(toCopy.empty());
811 return result; // 0 or 1
813 //______________________________
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
831 // FIXME! Needs SHA256
832 JigdoDesc::ImageInfoMD5& imageInfoMD5 =
833 dynamic_cast<JigdoDesc::ImageInfoMD5&>(*files.back());
835 if (toCopy.empty() && missing > 0) return 1;
836 for (JigdoDescVec::iterator i = files.begin(), e = files.end();
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;
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)))
850 /* Copy data from file to image, taking care not to write beyond
852 img->seekp(self->offset(), ios::beg);
854 reporter.error(_("Error - could not access temporary file"));
858 int status = fileToImageMD5(img, *mfile, *self, checkChecksum,
859 imageInfoMD5.blockLength(), reporter, buf, readAmount, bytesWritten,
860 nextReport, totalBytes);
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());
867 } else if (status > 2) {
870 } // end iterating over 'files'
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));
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
893 //______________________________
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);
900 return 1; // Soft failure
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));
912 } // end local namespace
913 //______________________________________________________________________
917 /// Read template data from templ (name in templFile) into files
918 void readTemplate(JigdoDescVec& files, const string& templFile,
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);
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);
929 //________________________________________
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) {
941 JigdoDesc::seekFromEnd(imageTmp);
942 imageTmp >> filesTmp;
943 } catch (JigdoDescError e) {
944 return _("it was not created by jigdo-file, or is corrupted.");
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.");
959 //________________________________________
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) {
969 Task task = CREATE_TMP;
971 if (imageFile == "-" || imageTmpFile.empty()) task = SINGLE_PASS;
972 //____________________
974 // Read info from template
976 readTemplate(files, templFile, templ);
977 //____________________
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
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));
993 wontReuse = strerror(errno);
995 wontReuse = readTmpFile(*img, filesTmp, files);
997 if (wontReuse != 0) {
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
1003 reporter.error(msg);
1004 throw Error(_("Delete/rename the file or use --force"));
1006 // Open a new tmp file later (imgDel will close() this one for us)
1009 Paranoid(task == CREATE_TMP);
1011 // Reuse temporary file
1013 files.swap(filesTmp);
1014 Assert(!filesTmp.empty() && img != 0);
1016 } // endif (tmp file exists)
1018 Paranoid((task == MERGE_TMP) == (img != 0));
1019 //____________________
1021 /* Variables now in use:
1023 Mode of operation (CREATE_TMP/MERGE_TMP/SINGLE_PASS)
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
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
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"
1039 for (vector<JigdoDesc*>::iterator i = files.begin(), e = files.end();
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);
1045 //totalBytes += m->size();
1047 // Search for file with matching MD5 sum
1048 ci = cache->begin();
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);
1062 if (!found) ++missing;
1065 //____________________
1067 debug("JigdoDesc::mkImage: %1 missing, %2 found for copying to image, "
1068 "%3 entries in template", missing, toCopy.size(), files.size());
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 //____________________
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
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");
1096 return 1; // Return value: "Soft failure - may retry with more files"
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
1105 //____________________
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,
1111 if (missing != 0 && result < 3)
1112 info_NeedMoreFiles(reporter, imageTmpFile);
1114 if (compat_rename(imageTmpFile.c_str(), imageFile.c_str()) != 0)
1115 return error_CouldntRename(reporter, imageTmpFile.c_str(),
1117 string info = subst(_("Successfully created `%1'"), imageFile);
1118 reporter.info(info);
1123 // task == CREATE_TMP || task == SINGLE_PASS
1125 // Assign a stream to img which we're going to write image data to
1126 // If necessary, create a new temporary/output file
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));
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));
1138 } else { // SINGLE_PASS, outputting to stdout
1141 img = 0; // Cannot do "img = &cout", so img==0 is special case: stdout
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
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."));
1163 int result = writeAll(task, files, toCopy, templ, readAmount, img, name,
1164 optMkImageCheck, reporter, cache, totalBytes);
1165 if (result >= 3) return result;
1167 if (task == CREATE_TMP && result == 1) {
1168 info_NeedMoreFiles(reporter, imageTmpFile);
1169 } else if (result == 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);
1179 //______________________________________________________________________
1181 int JigdoDesc::listMissing(set<MD5>& result, const string& imageTmpFile,
1182 const string& templFile, bistream* templ, ProgressReporter& reporter) {
1185 // FIXME! Needs SHA256
1187 // Read info from template
1188 JigdoDescVec contents;
1189 readTemplate(contents, templFile, templ);
1191 // Read info from temporary file, if any
1192 if (!imageTmpFile.empty()) {
1193 bifstream imageTmp(imageTmpFile.c_str(), ios::binary);
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);
1202 // tmp file present & valid - use *it* below to output missing parts
1203 swap(contents, contentsTmp);
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());
1216 //______________________________________________________________________
1218 void JigdoDesc::ProgressReporter::error(const string& message) {
1219 cerr << message << endl;
1221 void JigdoDesc::ProgressReporter::info(const string& message) {
1222 cerr << message << endl;
1224 void JigdoDesc::ProgressReporter::writingImage(uint64, uint64, uint64,
1227 JigdoDesc::ProgressReporter JigdoDesc::noReport;