WIP sha256 support
[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));
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::verifyImageMD5() {
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::ImageInfo* info;
374   try {
375     bistream* templ;
376     auto_ptr<bistream> templDel(openForInput(templ, templFile));
377
378     if (JigdoDesc::isTemplate(*templ) == false)
379       optReporter->info(
380           _("Warning: This does not seem to be a template file"));
381
382     JigdoDesc::seekFromEnd(*templ);
383     *templ >> contents;
384     if (!*templ) {
385       string err = subst(_("%1 verify: %2"), binaryName, strerror(errno));
386       optReporter->error(err);
387       return 3;
388     }
389     info = dynamic_cast<JigdoDesc::ImageInfo*>(contents.back());
390     if (info == 0) {
391       string err = subst(_("%1 verify: Invalid template data - "
392                            "corrupted file?"), binaryName);
393       optReporter->error(err);
394       return 3;
395     }
396   } catch (JigdoDescError e) {
397     string err = subst(_("%1: %2"), binaryName, e.message);
398     optReporter->error(err);
399     return 3;
400   }
401
402   MD5Sum md; // MD5Sum of image
403   md.updateFromStream(*image, info->size(), readAmount, *optReporter);
404   md.finish();
405   if (*image) {
406     image->get();
407     if (image->eof() && md == info->md5()) {
408       optReporter->info(_("OK: Checksums match, image is good!"));
409       return 0;
410     }
411   }
412   optReporter->error(_(
413       "ERROR: Checksums do not match, image might be corrupted!"));
414   return 2;
415 }
416 //______________________________________________________________________
417
418 int JigdoFileCmd::verifyImageSHA256() {
419   if (imageFile.empty() || templFile.empty()) {
420     cerr << subst(_(
421       "%1 verify: Not both --image and --template specified.\n"
422       "(Attempt to deduce missing names failed.)\n"), binaryName);
423     exit_tryHelp();
424   }
425
426   bistream* image;
427   auto_ptr<bistream> imageDel(openForInput(image, imageFile));
428
429   JigdoDescVec contents;
430   JigdoDesc::ImageInfoSHA256* info;
431   try {
432     bistream* templ;
433     auto_ptr<bistream> templDel(openForInput(templ, templFile));
434
435     if (JigdoDesc::isTemplate(*templ) == false)
436       optReporter->info(
437           _("Warning: This does not seem to be a template file"));
438
439     JigdoDesc::seekFromEnd(*templ);
440     *templ >> contents;
441     if (!*templ) {
442       string err = subst(_("%1 verify: %2"), binaryName, strerror(errno));
443       optReporter->error(err);
444       return 3;
445     }
446     info = dynamic_cast<JigdoDesc::ImageInfoSHA256*>(contents.back());
447     if (info == 0) {
448       string err = subst(_("%1 verify: Invalid template data - "
449                            "corrupted file?"), binaryName);
450       optReporter->error(err);
451       return 3;
452     }
453   } catch (JigdoDescError e) {
454     string err = subst(_("%1: %2"), binaryName, e.message);
455     optReporter->error(err);
456     return 3;
457   }
458
459   SHA256Sum md; // SHA256Sum of image
460   md.updateFromStream(*image, info->size(), readAmount, *optReporter);
461   md.finish();
462   if (*image) {
463     image->get();
464     if (image->eof() && md == info->sha256()) {
465       optReporter->info(_("OK: Checksums match, image is good!"));
466       return 0;
467     }
468   }
469   optReporter->error(_(
470       "ERROR: Checksums do not match, image might be corrupted!"));
471   return 2;
472 }
473 //______________________________________________________________________
474
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
477    resulting URIs. */
478 bool JigdoFileCmd::printMissing_lookup(JigdoConfig& jc, const string& query,
479                                        bool printAll) {
480   JigdoConfig::Lookup l(jc, query);
481   string uri;
482   if (!l.next(uri)) return false;
483   do {
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;
490     } else {
491       cout << uri << '\n';
492     }
493   } while (printAll && l.next(uri));
494   return true;
495 }
496 //______________________________
497
498 int JigdoFileCmd::printMissing(Command command) {
499   if (imageFile.empty() || jigdoFile.empty() || templFile.empty()) {
500     cerr << subst(_(
501       "%1 make-template: Not all of --image, --jigdo, --template specified.\n"
502       "(Attempt to deduce missing names failed.)\n"), binaryName);
503     exit_tryHelp();
504   }
505
506   bistream* templ;
507   auto_ptr<bistream> templDel(openForInput(templ, templFile));
508
509   string imageTmpFile;
510   if (imageFile != "-") {
511     imageTmpFile = imageFile;
512     imageTmpFile += EXTSEPS"tmp";
513
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;
518   }
519
520   // Read .jigdo file
521   istream* jigdo;
522   auto_ptr<istream> jigdoDel(openForInput(jigdo, jigdoFile));
523   auto_ptr<ConfigFile> cfDel(new ConfigFile());
524   ConfigFile* cf = cfDel.get();
525   *jigdo >> *cf;
526   JigdoConfig jc(jigdoFile, cfDel.release(), *optReporter);
527   // Add any mappings specified on command line
528   if (!optUris.empty()) {
529     addUris(jc.configFile());
530     jc.rescan();
531   }
532
533   set<MD5> sums;
534   try {
535     JigdoDesc::listMissing(sums, imageTmpFile, templFile, templ,
536                            *optReporter);
537   } catch (Error e) {
538     string err = subst(_("%1 print-missing: %2"), binaryName, e.message);
539     optReporter->error(err);
540     return 3;
541   }
542   //____________________
543
544   string partsSection = "Parts";
545   switch (command) {
546
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) {
550       Base64String m;
551       m.write(i->sum, 16).flush();
552       string& s(m.result());
553
554       vector<string> words;
555       size_t off;
556       bool found = false;
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
562         words.clear();
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;}
566       }
567       if (!found) {
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);
572       }
573     }
574     break;
575   }
576
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){
580       Base64String m;
581       m.write(i->sum, 16).flush();
582       string& s(m.result());
583
584       vector<string> words;
585       size_t off;
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
591         words.clear();
592         ConfigFile::split(words, *f.label(), off);
593         // Ignore everything but the first word
594         printMissing_lookup(jc, words[0], true);
595       }
596       // Last resort: "MD5sum:<md5sum>" label line
597       s.insert(0, "MD5Sum:");
598       printMissing_lookup(jc, s, true);
599       cout << endl;
600     }
601     break;
602   }
603
604   default:
605     Paranoid(false);
606
607   } // end switch()
608
609   return 0;
610 }
611 //______________________________________________________________________
612
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"),
617                   binaryName);
618     exit_tryHelp();
619   }
620
621   JigdoCache cache(cacheFile, optCacheExpiry, readAmount, *optReporter);
622   cache.setParams(blockLength, csumBlockLength);
623   if (addLabels(cache)) return 3;
624   while (true) {
625     try { cache.readFilenames(fileNames); } // Recurse through directories
626     catch (RecurseError e) { optReporter->error(e.message); continue; }
627     break;
628   }
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; }
633   } else {
634     // Only cause first md5 block to be read; not scanning the whole file
635     while (ci != ce) { ci->getMD5Sums(&cache, 0); ++ci; }
636   }
637   return 0;
638   // Cache data is written out when the JigdoCache is destroyed
639 }
640 //______________________________________________________________________
641
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);
651   while (true) {
652     try { cache.readFilenames(fileNames); } // Recurse through directories
653     catch (RecurseError e) { optReporter->error(e.message); continue; }
654     break;
655   }
656
657   if (JigdoFileCmd::optHex) Base64String::hex = true;
658
659   JigdoCache::iterator ci = cache.begin(), ce = cache.end();
660   while (ci != ce) {
661     Base64String m;
662     // Causes whole file to be read
663     const MD5Sum* md = ci->getMD5Sum(&cache);
664     if (md != 0) {
665       m.write(md->digest(), 16).flush();
666       string& s(m.result());
667       s += "  ";
668       if (ci->getPath() == "/") s += '/';
669       s += ci->leafName();
670       // Output checksum line
671       optReporter->coutInfo(s);
672     }
673     ++ci;
674   }
675   return 0;
676   // Cache data is written out when the JigdoCache is destroyed
677 }
678 //______________________________________________________________________
679
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);
689   while (true) {
690     try { cache.readFilenames(fileNames); } // Recurse through directories
691     catch (RecurseError e) { optReporter->error(e.message); continue; }
692     break;
693   }
694
695   if (JigdoFileCmd::optHex) Base64String::hex = true;
696
697   JigdoCache::iterator ci = cache.begin(), ce = cache.end();
698   while (ci != ce) {
699     Base64String m;
700     // Causes whole file to be read
701     const SHA256Sum* md = ci->getSHA256Sum(&cache);
702     if (md != 0) {
703       m.write(md->digest(), 32).flush();
704       string& s(m.result());
705       s += "  ";
706       if (ci->getPath() == "/") s += '/';
707       s += ci->leafName();
708       // Output checksum line
709       optReporter->coutInfo(s);
710     }
711     ++ci;
712   }
713   return 0;
714   // Cache data is written out when the JigdoCache is destroyed
715 }