1 /* $Id: jigdo-file-cmd.cc,v 1.16 2005/07/10 11:12:18 atterer Exp $ -*- C++ -*-
3 |_) /| Copyright (C) 2001-2002 | 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 Implementation of the different jigdo-file commands
18 #include <sys/types.h>
20 #include <unistd-jigdo.h>
25 #include <jigdo-file-cmd.hh>
26 #include <mimestream.hh>
27 #include <recursedir.hh>
29 //______________________________________________________________________
33 #if !HAVE_WORKING_FSTREAM /* ie istream and bistream are not the same */
34 /* Open named file or stdin if name is "-". Store pointer to stream obj in
35 dest and return it (except when it points to an object which should not be
36 deleted by the caller; in this case return null). */
37 bistream* openForInput(bistream*& dest, const string& name) throw(Cleanup) {
42 bifstream* fdest = new bifstream(name.c_str(), ios::binary);
44 if (!*dest /*|| !fdest->is_open()*/) {
45 cerr << subst(_("%1: Could not open `%2' for input: %3"),
46 binName(), name, strerror(errno)) << endl;
53 /* Open named file or stdin if name is "-". Store pointer to stream obj in
54 dest and return it (except when it points to an object which should not be
55 deleted by the caller; in this case return null). */
56 istream* openForInput(istream*& dest, const string& name) {
58 /* EEEEK! There's no standard way to switch mode of cin to binary. (There
59 might be an implementation-dependent way? close()ing and re-open()ing
60 cin may have strange effects.) */
61 dest = reinterpret_cast<istream*>(&cin);
64 ifstream* fdest = new ifstream(name.c_str(), ios::binary);
66 if (!*dest || !fdest->is_open()) {
67 cerr << subst(_("%1: Could not open `%2' for input: %3"),
68 binName(), name, strerror(errno)) << endl;
74 /* Ensure that an output file is not already present. Should use this
75 for all files before openForOutput() */
76 int willOutputTo(const string& name, bool optForce,
77 bool errorMessage = true) {
78 if (optForce) return 0;
80 int err = stat(name.c_str(), &fileInfo);
81 if (err == -1 && errno == ENOENT) return 0;
84 cerr << subst(_("%1: Output file `%2' already exists - delete it or use "
85 "--force"), binName(), name) << endl;
90 #if !HAVE_WORKING_FSTREAM /* ie istream and bistream are not the same */
91 bostream* openForOutput(bostream*& dest, const string& name) throw(Cleanup) {
96 dest = new bofstream(name.c_str(), ios::binary|ios::trunc);
98 cerr << subst(_("%1: Could not open `%2' for output: %3"),
99 binName(), name, strerror(errno)) << endl;
106 ostream* openForOutput(ostream*& dest, const string& name) {
108 dest = reinterpret_cast<ostream*>(&cout); // EEEEK!
111 dest = new ofstream(name.c_str(), ios::binary|ios::trunc);
113 cerr << subst(_("%1: Could not open `%2' for output: %3"),
114 binName(), name, strerror(errno)) << endl;
122 //______________________________________________________________________
124 /* Read contents of optLabels/optUris and call addLabel() for the
125 supplied cache object to set up the label mapping.
126 optLabels/optUris is cleared after use. */
127 int JigdoFileCmd::addLabels(JigdoCache& cache) {
129 string path, label, uri;
131 // Create a map from label name to URI
132 map<string, string> uriMap;
133 for (vector<string>::iterator i = optUris.begin(), e = optUris.end();
135 pair<string, string> entry;
136 string::size_type firstEquals = i->find('=');
137 if (firstEquals == string::npos) {
138 cerr << subst(_("%1: Invalid argument to --uri: `%2'"), binaryName, *i)
142 entry.first.assign(*i, 0U, firstEquals);
143 entry.second.assign(*i, firstEquals + 1, string::npos);
144 // Add mapping to uriMap
145 msg("URI mapping: `%1' => `%2'", entry.first, entry.second);
146 uriMap.insert(entry);
150 // Go through list of --label arguments and add them to JigdoCache
151 for (vector<string>::iterator i = optLabels.begin(), e = optLabels.end();
153 // Label name is everything before first '=' in argument to --label
154 string::size_type firstEquals = i->find('=');
155 if (firstEquals == string::npos) {
156 cerr << subst(_("%1: Invalid argument to --label: `%2'"), binaryName,
160 label.assign(*i, 0U, firstEquals);
161 path.assign(*i, firstEquals + 1, string::npos);
162 map<string, string>::iterator m = uriMap.find(label); // Lookup
163 if (m == uriMap.end()) {
166 compat_swapFileUriChars(uri);
167 if (uri[uri.length() - 1] != '/') uri += '/';
168 ConfigFile::quote(uri);
172 cache.addLabel(path, label, uri);
178 /* As above, but add URIs to the beginning of the [Servers] section
179 of a ConfigFile. rescan() is only necessary when changing section
181 void JigdoFileCmd::addUris(ConfigFile& config) {
182 // Let ci point to the line before which the label mapping will be inserted
183 ConfigFile::iterator ci = config.firstSection("Servers");
184 if (ci == config.end()) {
185 // No [Servers] section yet; append it at end
186 config.insert(ci, string("[Servers]"));
192 for (vector<string>::iterator i = optUris.begin(), e = optUris.end();
194 // Just add it, no matter what it contains...
195 config.insert(ci, *i);
200 //______________________________________________________________________
202 int JigdoFileCmd::makeTemplate() {
203 if (imageFile.empty() || jigdoFile.empty() || templFile.empty()) {
205 " make-template: Not all of --image, --jigdo, --template specified.\n"
206 "(Attempt to deduce missing names failed.)\n"), binaryName);
210 if (fileNames.empty()) {
211 optReporter->info(_("Warning - no files specified. The template will "
212 "contain the complete image contents!"));
215 // Give >1 error messages if >1 output files not present, hence no "||"
216 if (willOutputTo(jigdoFile, optForce)
217 + willOutputTo(templFile, optForce) > 0) throw Cleanup(3);
221 auto_ptr<bistream> imageDel(openForInput(image, imageFile));
223 auto_ptr<ConfigFile> cfDel(new ConfigFile());
224 ConfigFile* cf = cfDel.get();
225 if (!jigdoMergeFile.empty()) { // Load file to add to jigdo output
227 auto_ptr<istream> jigdoMergeDel(openForInput(jigdoMerge,
230 if (jigdoMerge->bad()) {
231 string err = subst(_("%1 make-template: Could not read `%2' (%3)"),
232 binaryName, jigdoMergeFile, strerror(errno));
233 optReporter->error(err);
237 JigdoConfig jc(jigdoFile, cfDel.release(), *optReporter);
240 auto_ptr<bostream> templDel(openForOutput(templ, templFile));
241 //____________________
243 JigdoCache cache(cacheFile, optCacheExpiry, readAmount, *optReporter);
244 cache.setParams(blockLength, csumBlockLength);
245 cache.setCheckFiles(optCheckFiles);
246 if (addLabels(cache)) return 3;
248 try { cache.readFilenames(fileNames); } // Recurse through directories
249 catch (RecurseError e) { optReporter->error(e.message); continue; }
252 // Create and run MkTemplate operation
254 op(new MkTemplate(&cache, image, &jc, templ, *optReporter,
255 optZipQuality, readAmount, optAddImage, optAddServers,
257 op->setMatchExec(optMatchExec);
258 op->setGreedyMatching(optGreedyMatching);
259 size_t lastDirSep = imageFile.rfind(DIRSEP);
260 if (lastDirSep == string::npos) lastDirSep = 0; else ++lastDirSep;
261 string imageFileLeaf(imageFile, lastDirSep);
262 lastDirSep = templFile.rfind(DIRSEP);
263 if (lastDirSep == string::npos) lastDirSep = 0; else ++lastDirSep;
264 string templFileLeaf(templFile, lastDirSep);
265 if (op->run(imageFileLeaf, templFileLeaf)) return 3;
267 // Write out jigdo file
269 auto_ptr<ostream> jigdoDel(openForOutput(jigdoF, jigdoFile));
270 *jigdoF << jc.configFile();
272 string err = subst(_("%1 make-template: Could not write `%2' (%3)"),
273 binaryName, jigdoFile, strerror(errno));
274 optReporter->error(err);
280 //______________________________________________________________________
282 int JigdoFileCmd::makeImage() {
283 if (imageFile.empty() || templFile.empty()) {
285 "%1 make-image: Not both --image and --template specified.\n"
286 "(Attempt to deduce missing names failed.)\n"), binaryName);
290 if (imageFile != "-" && willOutputTo(imageFile, optForce) > 0) return 3;
291 JigdoCache cache(cacheFile, optCacheExpiry, readAmount, *optReporter);
292 cache.setParams(blockLength, csumBlockLength);
294 try { cache.readFilenames(fileNames); } // Recurse through directories
295 catch (RecurseError e) { optReporter->error(e.message); continue; }
300 if (imageFile != "-") {
301 imageTmpFile = imageFile;
302 imageTmpFile += EXTSEPS"tmp";
306 auto_ptr<bistream> templDel(openForInput(templ, templFile));
309 return JigdoDesc::makeImage(&cache, imageFile, imageTmpFile, templFile,
310 templ, optForce, *optReporter, readAmount, optMkImageCheck);
312 string err = binaryName; err += " make-image: "; err += e.message;
313 optReporter->error(err);
317 //______________________________________________________________________
319 int JigdoFileCmd::listTemplate() {
320 if (templFile.empty()) {
321 cerr << subst(_("%1 list-template: --template not specified.\n"),
325 if (templFile == "-") {
326 cerr << subst(_("%1 list-template: Sorry, cannot read from standard "
327 "input.\n"), binaryName);
331 if (JigdoFileCmd::optHex) Base64String::hex = true;
335 auto_ptr<bistream> templDel(openForInput(templ, templFile));
337 if (JigdoDesc::isTemplate(*templ) == false)
339 _("Warning: This does not seem to be a template file"));
341 JigdoDescVec contents;
343 JigdoDesc::seekFromEnd(*templ);
347 string err = subst(_("%1 list-template: %2"), binaryName,
349 optReporter->error(err);
352 } catch (JigdoDescError e) {
353 string err = subst(_("%1: %2"), binaryName, e.message);
354 optReporter->error(err);
359 //______________________________________________________________________
361 int JigdoFileCmd::verifyImageMD5() {
362 if (imageFile.empty() || templFile.empty()) {
364 "%1 verify: Not both --image and --template specified.\n"
365 "(Attempt to deduce missing names failed.)\n"), binaryName);
370 auto_ptr<bistream> imageDel(openForInput(image, imageFile));
372 JigdoDescVec contents;
373 JigdoDesc::ImageInfo* info;
376 auto_ptr<bistream> templDel(openForInput(templ, templFile));
378 if (JigdoDesc::isTemplate(*templ) == false)
380 _("Warning: This does not seem to be a template file"));
382 JigdoDesc::seekFromEnd(*templ);
385 string err = subst(_("%1 verify: %2"), binaryName, strerror(errno));
386 optReporter->error(err);
389 info = dynamic_cast<JigdoDesc::ImageInfo*>(contents.back());
391 string err = subst(_("%1 verify: Invalid template data - "
392 "corrupted file?"), binaryName);
393 optReporter->error(err);
396 } catch (JigdoDescError e) {
397 string err = subst(_("%1: %2"), binaryName, e.message);
398 optReporter->error(err);
402 MD5Sum md; // MD5Sum of image
403 md.updateFromStream(*image, info->size(), readAmount, *optReporter);
407 if (image->eof() && md == info->md5()) {
408 optReporter->info(_("OK: Checksums match, image is good!"));
412 optReporter->error(_(
413 "ERROR: Checksums do not match, image might be corrupted!"));
416 //______________________________________________________________________
418 int JigdoFileCmd::verifyImageSHA256() {
419 if (imageFile.empty() || templFile.empty()) {
421 "%1 verify: Not both --image and --template specified.\n"
422 "(Attempt to deduce missing names failed.)\n"), binaryName);
427 auto_ptr<bistream> imageDel(openForInput(image, imageFile));
429 JigdoDescVec contents;
430 JigdoDesc::ImageInfoSHA256* info;
433 auto_ptr<bistream> templDel(openForInput(templ, templFile));
435 if (JigdoDesc::isTemplate(*templ) == false)
437 _("Warning: This does not seem to be a template file"));
439 JigdoDesc::seekFromEnd(*templ);
442 string err = subst(_("%1 verify: %2"), binaryName, strerror(errno));
443 optReporter->error(err);
446 info = dynamic_cast<JigdoDesc::ImageInfoSHA256*>(contents.back());
448 string err = subst(_("%1 verify: Invalid template data - "
449 "corrupted file?"), binaryName);
450 optReporter->error(err);
453 } catch (JigdoDescError e) {
454 string err = subst(_("%1: %2"), binaryName, e.message);
455 optReporter->error(err);
459 SHA256Sum md; // SHA256Sum of image
460 md.updateFromStream(*image, info->size(), readAmount, *optReporter);
464 if (image->eof() && md == info->sha256()) {
465 optReporter->info(_("OK: Checksums match, image is good!"));
469 optReporter->error(_(
470 "ERROR: Checksums do not match, image might be corrupted!"));
473 //______________________________________________________________________
475 /* Look up a query (e.g. "MyServer:foo/path/bar") in the JigdoConfig
476 mapping. Returns true if something was found, and prints out all
478 bool JigdoFileCmd::printMissing_lookup(JigdoConfig& jc, const string& query,
480 JigdoConfig::Lookup l(jc, query);
482 if (!l.next(uri)) return false;
484 // Omit "file:" when printing
485 if (uri[0] == 'f' && uri[1] == 'i' && uri[2] == 'l'
486 && uri[3] == 'e' && uri[4] == ':') {
487 string nativeFilename(uri, 5);
488 compat_swapFileUriChars(nativeFilename);
489 cout << nativeFilename << endl;
493 } while (printAll && l.next(uri));
496 //______________________________
498 int JigdoFileCmd::printMissing(Command command) {
499 if (imageFile.empty() || jigdoFile.empty() || templFile.empty()) {
501 "%1 make-template: Not all of --image, --jigdo, --template specified.\n"
502 "(Attempt to deduce missing names failed.)\n"), binaryName);
507 auto_ptr<bistream> templDel(openForInput(templ, templFile));
510 if (imageFile != "-") {
511 imageTmpFile = imageFile;
512 imageTmpFile += EXTSEPS"tmp";
514 // If image file exists, assume that it is complete; print nothing
515 struct stat fileInfo;
516 int err = stat(imageFile.c_str(), &fileInfo);
517 if (err == 0) return 0;
522 auto_ptr<istream> jigdoDel(openForInput(jigdo, jigdoFile));
523 auto_ptr<ConfigFile> cfDel(new ConfigFile());
524 ConfigFile* cf = cfDel.get();
526 JigdoConfig jc(jigdoFile, cfDel.release(), *optReporter);
527 // Add any mappings specified on command line
528 if (!optUris.empty()) {
529 addUris(jc.configFile());
535 JigdoDesc::listMissing(sums, imageTmpFile, templFile, templ,
538 string err = subst(_("%1 print-missing: %2"), binaryName, e.message);
539 optReporter->error(err);
542 //____________________
544 string partsSection = "Parts";
547 case PRINT_MISSING: {
548 // To list just the first URI
549 for (set<MD5>::iterator i = sums.begin(), e = sums.end(); i != e; ++i) {
551 m.write(i->sum, 16).flush();
552 string& s(m.result());
554 vector<string> words;
557 for (ConfigFile::Find f(cf, partsSection, s, &off);
558 !f.finished(); off = f.next()) {
559 // f.section() points to "[section]" line, or end() if 0th section
560 // f.label() points to "label=..." line, or end() if f.finished()
561 // off is offset of part after "label=", or 0
563 ConfigFile::split(words, *f.label(), off);
564 // Ignore everything but the first word
565 if (printMissing_lookup(jc, words[0], false)) { found = true; break;}
568 /* No mapping found in [Parts] (this shouldn't happen) - create
569 fake "MD5sum:<md5sum>" label line */
570 s.insert(0, "MD5Sum:");
571 printMissing_lookup(jc, s, false);
577 case PRINT_MISSING_ALL: {
578 // To list all URIs for each missing file, separated by empty lines:
579 for (set<MD5>::iterator i = sums.begin(), e = sums.end(); i != e; ++i){
581 m.write(i->sum, 16).flush();
582 string& s(m.result());
584 vector<string> words;
586 for (ConfigFile::Find f(cf, partsSection, s, &off);
587 !f.finished(); off = f.next()) {
588 // f.section() points to "[section]" line, or end() if 0th section
589 // f.label() points to "label=..." line, or end() if f.finished()
590 // off is offset of part after "label=", or 0
592 ConfigFile::split(words, *f.label(), off);
593 // Ignore everything but the first word
594 printMissing_lookup(jc, words[0], true);
596 // Last resort: "MD5sum:<md5sum>" label line
597 s.insert(0, "MD5Sum:");
598 printMissing_lookup(jc, s, true);
611 //______________________________________________________________________
613 // Enter all file arguments into the cache
614 int JigdoFileCmd::scanFiles() {
615 if (cacheFile.empty()) {
616 cerr << subst(_("%1 scan: Please specify a --cache file.\n"),
621 JigdoCache cache(cacheFile, optCacheExpiry, readAmount, *optReporter);
622 cache.setParams(blockLength, csumBlockLength);
623 if (addLabels(cache)) return 3;
625 try { cache.readFilenames(fileNames); } // Recurse through directories
626 catch (RecurseError e) { optReporter->error(e.message); continue; }
629 JigdoCache::iterator ci = cache.begin(), ce = cache.end();
630 if (optScanWholeFile) {
631 // Cause entire file to be read
632 while (ci != ce) { ci->getMD5Sum(&cache); ++ci; }
634 // Only cause first md5 block to be read; not scanning the whole file
635 while (ci != ce) { ci->getMD5Sums(&cache, 0); ++ci; }
638 // Cache data is written out when the JigdoCache is destroyed
640 //______________________________________________________________________
642 /* Print MD5 checksums of arguments like md5sum(1), but using our
643 Base64-like encoding for the checksum, not hexadecimal like
644 md5sum(1). Additionally, try to make use of the cache, and only
645 print out the part of any filename following any "//". This is
646 actually very similar to scanFiles() above. */
647 int JigdoFileCmd::md5sumFiles() {
648 JigdoCache cache(cacheFile, optCacheExpiry, readAmount, *optReporter);
649 cache.setParams(blockLength, csumBlockLength);
650 cache.setCheckFiles(optCheckFiles);
652 try { cache.readFilenames(fileNames); } // Recurse through directories
653 catch (RecurseError e) { optReporter->error(e.message); continue; }
657 if (JigdoFileCmd::optHex) Base64String::hex = true;
659 JigdoCache::iterator ci = cache.begin(), ce = cache.end();
662 // Causes whole file to be read
663 const MD5Sum* md = ci->getMD5Sum(&cache);
665 m.write(md->digest(), 16).flush();
666 string& s(m.result());
668 if (ci->getPath() == "/") s += '/';
670 // Output checksum line
671 optReporter->coutInfo(s);
676 // Cache data is written out when the JigdoCache is destroyed
678 //______________________________________________________________________
680 /* Print SHA256 checksums of arguments like sha256sum(1), but using our
681 Base64-like encoding for the checksum, not hexadecimal like
682 sha256sum(1). Additionally, try to make use of the cache, and only
683 print out the part of any filename following any "//". This is
684 actually very similar to scanFiles() above. */
685 int JigdoFileCmd::sha256sumFiles() {
686 JigdoCache cache(cacheFile, optCacheExpiry, readAmount, *optReporter);
687 cache.setParams(blockLength, csumBlockLength);
688 cache.setCheckFiles(optCheckFiles);
690 try { cache.readFilenames(fileNames); } // Recurse through directories
691 catch (RecurseError e) { optReporter->error(e.message); continue; }
695 if (JigdoFileCmd::optHex) Base64String::hex = true;
697 JigdoCache::iterator ci = cache.begin(), ce = cache.end();
700 // Causes whole file to be read
701 const SHA256Sum* md = ci->getSHA256Sum(&cache);
703 m.write(md->digest(), 32).flush();
704 string& s(m.result());
706 if (ci->getPath() == "/") s += '/';
708 // Output checksum line
709 optReporter->coutInfo(s);
714 // Cache data is written out when the JigdoCache is destroyed