Warning fix - unused variable
[jigdo.git] / src / jigdo-file-cmd.cc
1 /* $Id: jigdo-file-cmd.cc,v 1.16 2005/07/10 11:12:18 atterer Exp $ -*- C++ -*-
2   __   _
3   |_) /|  Copyright (C) 2001-2002  |  richard@
4   | \/¯|  Richard Atterer          |  atterer.org
5   ¯ '` ¯
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License, version 2. See
8   the file COPYING for details.
9
10   Implementation of the different jigdo-file commands
11
12 */
13
14 #include <config.h>
15
16 #include <fstream>
17 #include <memory>
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <unistd-jigdo.h>
21 #include <errno.h>
22
23 #include <compat.hh>
24 #include <debug.hh>
25 #include <jigdo-file-cmd.hh>
26 #include <mimestream.hh>
27 #include <recursedir.hh>
28 #include <string.hh>
29 //______________________________________________________________________
30
31 namespace {
32
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) {
38   if (name == "-") {
39     dest = &bcin;
40     return 0;
41   }
42   bifstream* fdest = new bifstream(name.c_str(), ios::binary);
43   dest = fdest;
44   if (!*dest /*|| !fdest->is_open()*/) {
45     cerr << subst(_("%1: Could not open `%2' for input: %3"),
46                   binName(), name, strerror(errno)) << endl;
47     throw Cleanup(3);
48   }
49   return dest;
50 }
51 #endif
52
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) {
57   if (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);
62     return 0;
63   }
64   ifstream* fdest = new ifstream(name.c_str(), ios::binary);
65   dest = fdest;
66   if (!*dest || !fdest->is_open()) {
67     cerr << subst(_("%1: Could not open `%2' for input: %3"),
68                   binName(), name, strerror(errno)) << endl;
69     throw Cleanup(3);
70   }
71   return dest;
72 }
73
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;
79   struct stat fileInfo;
80   int err = stat(name.c_str(), &fileInfo);
81   if (err == -1 && errno == ENOENT) return 0;
82
83   if (errorMessage) {
84     cerr << subst(_("%1: Output file `%2' already exists - delete it or use "
85                     "--force"), binName(), name) << endl;
86   }
87   return 1;
88 }
89
90 #if !HAVE_WORKING_FSTREAM /* ie istream and bistream are not the same */
91 bostream* openForOutput(bostream*& dest, const string& name) throw(Cleanup) {
92   if (name == "-") {
93     dest = &bcout;
94     return 0;
95   }
96   dest = new bofstream(name.c_str(), ios::binary|ios::trunc);
97   if (!*dest) {
98     cerr << subst(_("%1: Could not open `%2' for output: %3"),
99                   binName(), name, strerror(errno)) << endl;
100     throw Cleanup(3);
101   }
102   return dest;
103 }
104 #endif
105
106 ostream* openForOutput(ostream*& dest, const string& name) {
107   if (name == "-") {
108     dest = reinterpret_cast<ostream*>(&cout); // EEEEK!
109     return 0;
110   } else {
111     dest = new ofstream(name.c_str(), ios::binary|ios::trunc);
112     if (!*dest) {
113       cerr << subst(_("%1: Could not open `%2' for output: %3"),
114                     binName(), name, strerror(errno)) << endl;
115       throw Cleanup(3);
116     }
117     return dest;
118   }
119 }
120
121 } // local namespace
122 //______________________________________________________________________
123
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) {
128   int result = 0;
129   string path, label, uri;
130
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();
134        i != e; ++i) {
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)
139            << '\n';
140       result = 1;
141     }
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);
147   }
148   optUris.clear();
149
150   // Go through list of --label arguments and add them to JigdoCache
151   for (vector<string>::iterator i = optLabels.begin(), e = optLabels.end();
152        i != e; ++i) {
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,
157                     *i) << '\n';
158       result = 1;
159     }
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()) {
164       uri = "file:";
165       uri += path;
166       compat_swapFileUriChars(uri);
167       if (uri[uri.length() - 1] != '/') uri += '/';
168       ConfigFile::quote(uri);
169     } else {
170       uri = m->second;
171     }
172     cache.addLabel(path, label, uri);
173   }
174   optLabels.clear();
175   return result;
176 }
177
178 /* As above, but add URIs to the beginning of the [Servers] section
179    of a ConfigFile. rescan() is only necessary when changing section
180    lines. */
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]"));
187     config.rescan();
188   } else {
189     ++ci;
190   }
191
192   for (vector<string>::iterator i = optUris.begin(), e = optUris.end();
193        i != e; ++i) {
194     // Just add it, no matter what it contains...
195     config.insert(ci, *i);
196   }
197   optUris.clear();
198   return;
199 }
200 //______________________________________________________________________
201
202 int JigdoFileCmd::makeTemplate() {
203   if (imageFile.empty() || jigdoFile.empty() || templFile.empty()) {
204     cerr << subst(_("%1"
205       " make-template: Not all of --image, --jigdo, --template specified.\n"
206       "(Attempt to deduce missing names failed.)\n"), binaryName);
207     exit_tryHelp();
208   }
209
210   if (fileNames.empty()) {
211     optReporter->info(_("Warning - no files specified. The template will "
212                         "contain the complete image contents!"));
213   }
214
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);
218
219   // Open files
220   bistream* image;
221   auto_ptr<bistream> imageDel(openForInput(image, imageFile));
222
223   auto_ptr<ConfigFile> cfDel(new ConfigFile());
224   ConfigFile* cf = cfDel.get();
225   if (!jigdoMergeFile.empty()) { // Load file to add to jigdo output
226     istream* jigdoMerge;
227     auto_ptr<istream> jigdoMergeDel(openForInput(jigdoMerge,
228                                                  jigdoMergeFile));
229     *jigdoMerge >> *cf;
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);
234       return 3;
235     }
236   }
237   JigdoConfig jc(jigdoFile, cfDel.release(), *optReporter);
238
239   bostream* templ;
240   auto_ptr<bostream> templDel(openForOutput(templ, templFile));
241   //____________________
242
243   JigdoCache cache(cacheFile, optCacheExpiry, readAmount, *optReporter);
244   cache.setParams(blockLength, csumBlockLength);
245   cache.setCheckFiles(optCheckFiles);
246   if (addLabels(cache)) return 3;
247   while (true) {
248     try { cache.readFilenames(fileNames); } // Recurse through directories
249     catch (RecurseError e) { optReporter->error(e.message); continue; }
250     break;
251   }
252   // Create and run MkTemplate operation
253   auto_ptr<MkTemplate>
254     op(new MkTemplate(&cache, image, &jc, templ, *optReporter,
255                       optZipQuality, readAmount, optAddImage, optAddServers,
256                       optBzip2, optChecksumChoice));
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;
266
267   // Write out jigdo file
268   ostream* jigdoF;
269   auto_ptr<ostream> jigdoDel(openForOutput(jigdoF, jigdoFile));
270   *jigdoF << jc.configFile();
271   if (jigdoF->bad()) {
272     string err = subst(_("%1 make-template: Could not write `%2' (%3)"),
273                        binaryName, jigdoFile, strerror(errno));
274     optReporter->error(err);
275     return 3;
276   }
277
278   return 0;
279 }
280 //______________________________________________________________________
281
282 int JigdoFileCmd::makeImage() {
283   if (imageFile.empty() || templFile.empty()) {
284     cerr << subst(_(
285       "%1 make-image: Not both --image and --template specified.\n"
286       "(Attempt to deduce missing names failed.)\n"), binaryName);
287     exit_tryHelp();
288   }
289
290   if (imageFile != "-" && willOutputTo(imageFile, optForce) > 0) return 3;
291   JigdoCache cache(cacheFile, optCacheExpiry, readAmount, *optReporter);
292   cache.setParams(blockLength, csumBlockLength);
293   while (true) {
294     try { cache.readFilenames(fileNames); } // Recurse through directories
295     catch (RecurseError e) { optReporter->error(e.message); continue; }
296     break;
297   }
298
299   string imageTmpFile;
300   if (imageFile != "-") {
301     imageTmpFile = imageFile;
302     imageTmpFile += EXTSEPS"tmp";
303   }
304
305   bistream* templ;
306   auto_ptr<bistream> templDel(openForInput(templ, templFile));
307
308   try {
309     return JigdoDesc::makeImage(&cache, imageFile, imageTmpFile, templFile,
310       templ, optForce, *optReporter, readAmount, optMkImageCheck);
311   } catch (Error e) {
312     string err = binaryName; err += " make-image: "; err += e.message;
313     optReporter->error(err);
314     return 3;
315   }
316 }
317 //______________________________________________________________________
318
319 int JigdoFileCmd::listTemplate() {
320   if (templFile.empty()) {
321     cerr << subst(_("%1 list-template: --template not specified.\n"),
322                   binaryName);
323     exit_tryHelp();
324   }
325   if (templFile == "-") {
326     cerr << subst(_("%1 list-template: Sorry, cannot read from standard "
327                     "input.\n"), binaryName);
328     exit_tryHelp();
329   }
330
331   if (JigdoFileCmd::optHex) Base64String::hex = true;
332
333   // Open file
334   bistream* templ;
335   auto_ptr<bistream> templDel(openForInput(templ, templFile));
336
337   if (JigdoDesc::isTemplate(*templ) == false)
338     optReporter->info(
339         _("Warning: This does not seem to be a template file"));
340
341   JigdoDescVec contents;
342   try {
343     JigdoDesc::seekFromEnd(*templ);
344     *templ >> contents;
345     contents.list(cout);
346     if (!*templ) {
347       string err = subst(_("%1 list-template: %2"), binaryName,
348                          strerror(errno));
349       optReporter->error(err);
350       return 3;
351     }
352   } catch (JigdoDescError e) {
353     string err = subst(_("%1: %2"), binaryName, e.message);
354     optReporter->error(err);
355     return 3;
356   }
357   return 0;
358 }
359 //______________________________________________________________________
360
361 int JigdoFileCmd::verifyImage() {
362   if (imageFile.empty() || templFile.empty()) {
363     cerr << subst(_(
364       "%1 verify: Not both --image and --template specified.\n"
365       "(Attempt to deduce missing names failed.)\n"), binaryName);
366     exit_tryHelp();
367   }
368
369   bistream* image;
370   auto_ptr<bistream> imageDel(openForInput(image, imageFile));
371
372   JigdoDescVec contents;
373   JigdoDesc::ImageInfoMD5* info_md5 = 0;
374   JigdoDesc::ImageInfoSHA256* info_sha256 = 0;
375   try {
376     bistream* templ;
377     auto_ptr<bistream> templDel(openForInput(templ, templFile));
378
379     if (JigdoDesc::isTemplate(*templ) == false)
380       optReporter->info(
381           _("Warning: This does not seem to be a template file"));
382
383     JigdoDesc::seekFromEnd(*templ);
384     *templ >> contents;
385     if (!*templ) {
386       string err = subst(_("%1 verify: %2"), binaryName, strerror(errno));
387       optReporter->error(err);
388       return 3;
389     }
390
391     // Attempt verification via SHA256 first if that's possible
392     for (JigdoDescVec::iterator i = contents.begin(); i != contents.end(); ++i) {
393       info_sha256 = dynamic_cast<JigdoDesc::ImageInfoSHA256*>(*i);
394       if (info_sha256) {
395         SHA256Sum md; // SHA256Sum of image
396         md.updateFromStream(*image, info_sha256->size(), readAmount, *optReporter);
397         md.finish();
398         if (*image) {
399           image->get();
400           if (image->eof() && md == info_sha256->sha256()) {
401             optReporter->info(_("OK: SHA256 Checksums match, image is good!"));
402             return 0;
403           }
404         }
405       }
406     }
407
408     for (JigdoDescVec::iterator i = contents.begin(); i != contents.end(); ++i) {
409       info_md5 = dynamic_cast<JigdoDesc::ImageInfoMD5*>(*i);
410       if (info_md5) {
411         MD5Sum md; // MD5Sum of image
412         md.updateFromStream(*image, info_md5->size(), readAmount, *optReporter);
413         md.finish();
414         if (*image) {
415           image->get();
416           if (image->eof() && md == info_md5->md5()) {
417             optReporter->info(_("OK: MD5 Checksums match, image is good!"));
418             optReporter->info(_("WARNING: MD5 is not considered a secure hash!"));
419             optReporter->info(_("WARNING: It is recommended to verify your image in other ways too!"));
420             return 0;
421           }
422         }
423       }
424     }
425     if (info_sha256 == 0 && info_md5 == 0) {
426       string err = subst(_("%1 verify: Invalid template data - "
427                            "corrupted file?"), binaryName);
428       optReporter->error(err);
429       return 3;
430     }
431   } catch (JigdoDescError e) {
432     string err = subst(_("%1: %2"), binaryName, e.message);
433     optReporter->error(err);
434     return 3;
435   }
436
437   // If we've checked and no match, we fall through to here
438   optReporter->error(_(
439       "ERROR: Checksums do not match, image might be corrupted!"));
440   return 2;
441 }
442 //______________________________________________________________________
443
444 /* Look up a query (e.g. "MyServer:foo/path/bar") in the JigdoConfig
445    mapping. Returns true if something was found, and prints out all
446    resulting URIs. */
447 bool JigdoFileCmd::printMissing_lookup(JigdoConfig& jc, const string& query,
448                                        bool printAll) {
449   JigdoConfig::Lookup l(jc, query);
450   string uri;
451   if (!l.next(uri)) return false;
452   do {
453     // Omit "file:" when printing
454     if (uri[0] == 'f' && uri[1] == 'i' && uri[2] == 'l'
455         && uri[3] == 'e' && uri[4] == ':') {
456       string nativeFilename(uri, 5);
457       compat_swapFileUriChars(nativeFilename);
458       cout << nativeFilename << endl;
459     } else {
460       cout << uri << '\n';
461     }
462   } while (printAll && l.next(uri));
463   return true;
464 }
465 //______________________________
466
467 int JigdoFileCmd::printMissing(Command command) {
468   if (imageFile.empty() || jigdoFile.empty() || templFile.empty()) {
469     cerr << subst(_(
470       "%1 print-missing: Not all of --image, --jigdo, --template specified.\n"
471       "(Attempt to deduce missing names failed.)\n"), binaryName);
472     exit_tryHelp();
473   }
474
475   bistream* templ;
476   auto_ptr<bistream> templDel(openForInput(templ, templFile));
477
478   string imageTmpFile;
479   if (imageFile != "-") {
480     imageTmpFile = imageFile;
481     imageTmpFile += EXTSEPS"tmp";
482
483     // If image file exists, assume that it is complete; print nothing
484     struct stat fileInfo;
485     int err = stat(imageFile.c_str(), &fileInfo);
486     if (err == 0) return 0;
487   }
488
489   // Read .jigdo file
490   istream* jigdo;
491   auto_ptr<istream> jigdoDel(openForInput(jigdo, jigdoFile));
492   auto_ptr<ConfigFile> cfDel(new ConfigFile());
493   ConfigFile* cf = cfDel.get();
494   *jigdo >> *cf;
495   JigdoConfig jc(jigdoFile, cfDel.release(), *optReporter);
496   // Add any mappings specified on command line
497   if (!optUris.empty()) {
498     addUris(jc.configFile());
499     jc.rescan();
500   }
501
502   set<MD5> MD5sums;
503   set<SHA256> SHA256sums;
504   try {
505     JigdoDesc::listMissingMD5(MD5sums, imageTmpFile, templFile, templ,
506                               *optReporter);
507     JigdoDesc::listMissingSHA256(SHA256sums, imageTmpFile, templFile, templ,
508                                  *optReporter);
509   } catch (Error e) {
510     string err = subst(_("%1 print-missing: %2"), binaryName, e.message);
511     optReporter->error(err);
512     return 3;
513   }
514   //____________________
515
516   string partsSection = "Parts";
517   switch (command) {
518
519   case PRINT_MISSING: {
520     // To list just the first URI
521     for (set<MD5>::iterator i = MD5sums.begin(), e = MD5sums.end(); i != e; ++i) {
522       Base64String m;
523       m.write(i->sum, 16).flush();
524       string& s(m.result());
525
526       vector<string> words;
527       size_t off;
528       bool found = false;
529       for (ConfigFile::Find f(cf, partsSection, s, &off);
530            !f.finished(); off = f.next()) {
531         // f.section() points to "[section]" line, or end() if 0th section
532         // f.label()   points to "label=..." line, or end() if f.finished()
533         // off is offset of part after "label=", or 0
534         words.clear();
535         ConfigFile::split(words, *f.label(), off);
536         // Ignore everything but the first word
537         if (printMissing_lookup(jc, words[0], false)) { found = true; break;}
538       }
539       if (!found) {
540         /* No mapping found in [Parts] (this shouldn't happen) - create
541            fake "MD5sum:<md5sum>" label line */
542         s.insert(0, "MD5Sum:");
543         printMissing_lookup(jc, s, false);
544       }
545     }
546
547     // To list just the first URI
548     for (set<SHA256>::iterator i = SHA256sums.begin(), e = SHA256sums.end(); i != e; ++i) {
549       Base64String m;
550       m.write(i->sum, 32).flush();
551       string& s(m.result());
552
553       vector<string> words;
554       size_t off;
555       bool found = false;
556       for (ConfigFile::Find f(cf, partsSection, s, &off);
557            !f.finished(); off = f.next()) {
558         // f.section() points to "[section]" line, or end() if 0th section
559         // f.label()   points to "label=..." line, or end() if f.finished()
560         // off is offset of part after "label=", or 0
561         words.clear();
562         ConfigFile::split(words, *f.label(), off);
563         // Ignore everything but the first word
564         if (printMissing_lookup(jc, words[0], false)) { found = true; break;}
565       }
566       if (!found) {
567         /* No mapping found in [Parts] (this shouldn't happen) - create
568            fake "SHA256sum:<sha256sum>" label line */
569         s.insert(0, "SHA256Sum:");
570         printMissing_lookup(jc, s, false);
571       }
572     }
573     break;
574   }
575
576   case PRINT_MISSING_ALL: {
577     // To list all URIs for each missing file, separated by empty lines:
578     for (set<MD5>::iterator i = MD5sums.begin(), e = MD5sums.end(); i != e; ++i){
579       Base64String m;
580       m.write(i->sum, 16).flush();
581       string& s(m.result());
582
583       vector<string> words;
584       size_t off;
585       for (ConfigFile::Find f(cf, partsSection, s, &off);
586            !f.finished(); off = f.next()) {
587         // f.section() points to "[section]" line, or end() if 0th section
588         // f.label()   points to "label=..." line, or end() if f.finished()
589         // off is offset of part after "label=", or 0
590         words.clear();
591         ConfigFile::split(words, *f.label(), off);
592         // Ignore everything but the first word
593         printMissing_lookup(jc, words[0], true);
594       }
595       // Last resort: "MD5sum:<md5sum>" label line
596       s.insert(0, "MD5Sum:");
597       printMissing_lookup(jc, s, true);
598       cout << endl;
599     }
600     for (set<SHA256>::iterator i = SHA256sums.begin(), e = SHA256sums.end(); i != e; ++i){
601       Base64String m;
602       m.write(i->sum, 32).flush();
603       string& s(m.result());
604
605       vector<string> words;
606       size_t off;
607       for (ConfigFile::Find f(cf, partsSection, s, &off);
608            !f.finished(); off = f.next()) {
609         // f.section() points to "[section]" line, or end() if 0th section
610         // f.label()   points to "label=..." line, or end() if f.finished()
611         // off is offset of part after "label=", or 0
612         words.clear();
613         ConfigFile::split(words, *f.label(), off);
614         // Ignore everything but the first word
615         printMissing_lookup(jc, words[0], true);
616       }
617       // Last resort: "SHA256sum:<sha256sum>" label line
618       s.insert(0, "SHA256Sum:");
619       printMissing_lookup(jc, s, true);
620       cout << endl;
621     }
622     break;
623   }
624
625   default:
626     Paranoid(false);
627
628   } // end switch()
629
630   return 0;
631 }
632 //______________________________________________________________________
633
634 // Enter all file arguments into the cache
635 int JigdoFileCmd::scanFiles() {
636   if (cacheFile.empty()) {
637     cerr << subst(_("%1 scan: Please specify a --cache file.\n"),
638                   binaryName);
639     exit_tryHelp();
640   }
641
642   JigdoCache cache(cacheFile, optCacheExpiry, readAmount, *optReporter);
643   cache.setParams(blockLength, csumBlockLength);
644   if (addLabels(cache)) return 3;
645   while (true) {
646     try { cache.readFilenames(fileNames); } // Recurse through directories
647     catch (RecurseError e) { optReporter->error(e.message); continue; }
648     break;
649   }
650   JigdoCache::iterator ci = cache.begin(), ce = cache.end();
651   if (optScanWholeFile) {
652     // Cause entire file to be read
653     while (ci != ce) {
654       ci->getMD5Sum(&cache);
655       ci->getSHA256Sum(&cache);
656       ++ci;
657     }
658   } else {
659     // Only cause first checksum block to be read; not scanning the whole file
660     while (ci != ce) {
661       ci->getMD5Sums(&cache, 0);
662       ci->getSHA256Sums(&cache, 0);
663       ++ci;
664     }
665   }
666   return 0;
667   // Cache data is written out when the JigdoCache is destroyed
668 }
669 //______________________________________________________________________
670
671 /* Print MD5 checksums of arguments like md5sum(1), but using our
672    Base64-like encoding for the checksum, not hexadecimal like
673    md5sum(1). Additionally, try to make use of the cache, and only
674    print out the part of any filename following any "//". This is
675    actually very similar to scanFiles() above. */
676 int JigdoFileCmd::md5sumFiles() {
677   JigdoCache cache(cacheFile, optCacheExpiry, readAmount, *optReporter);
678   cache.setParams(blockLength, csumBlockLength);
679   cache.setCheckFiles(optCheckFiles);
680   while (true) {
681     try { cache.readFilenames(fileNames); } // Recurse through directories
682     catch (RecurseError e) { optReporter->error(e.message); continue; }
683     break;
684   }
685
686   if (JigdoFileCmd::optHex) Base64String::hex = true;
687
688   JigdoCache::iterator ci = cache.begin(), ce = cache.end();
689   while (ci != ce) {
690     Base64String m;
691     // Causes whole file to be read
692     const MD5Sum* md = ci->getMD5Sum(&cache);
693     if (md != 0) {
694       m.write(md->digest(), 16).flush();
695       string& s(m.result());
696       s += "  ";
697       if (ci->getPath() == "/") s += '/';
698       s += ci->leafName();
699       // Output checksum line
700       optReporter->coutInfo(s);
701     }
702     ++ci;
703   }
704   return 0;
705   // Cache data is written out when the JigdoCache is destroyed
706 }
707 //______________________________________________________________________
708
709 /* Print SHA256 checksums of arguments like sha256sum(1), but using our
710    Base64-like encoding for the checksum, not hexadecimal like
711    sha256sum(1). Additionally, try to make use of the cache, and only
712    print out the part of any filename following any "//". This is
713    actually very similar to scanFiles() above. */
714 int JigdoFileCmd::sha256sumFiles() {
715   JigdoCache cache(cacheFile, optCacheExpiry, readAmount, *optReporter);
716   cache.setParams(blockLength, csumBlockLength);
717   cache.setCheckFiles(optCheckFiles);
718   while (true) {
719     try { cache.readFilenames(fileNames); } // Recurse through directories
720     catch (RecurseError e) { optReporter->error(e.message); continue; }
721     break;
722   }
723
724   if (JigdoFileCmd::optHex) Base64String::hex = true;
725
726   JigdoCache::iterator ci = cache.begin(), ce = cache.end();
727   while (ci != ce) {
728     Base64String m;
729     // Causes whole file to be read
730     const SHA256Sum* md = ci->getSHA256Sum(&cache);
731     if (md != 0) {
732       m.write(md->digest(), 32).flush();
733       string& s(m.result());
734       s += "  ";
735       if (ci->getPath() == "/") s += '/';
736       s += ci->leafName();
737       // Output checksum line
738       optReporter->coutInfo(s);
739     }
740     ++ci;
741   }
742   return 0;
743   // Cache data is written out when the JigdoCache is destroyed
744 }