Import Debian changes 0.7.3-3
[jigdo.git] / src / jigdo-file.cc
1 /* $Id: jigdo-file.cc,v 1.20 2006/05/14 18:23:31 atterer Exp $ -*- C++ -*-
2   __   _
3   |_) /|  Copyright (C) 2000-2002  |  richard@
4   | \/¯|  Richard Atterer          |  atterer.net
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   Command line utility to create .jigdo and .template files
11
12 */
13
14 #include <config.h>
15
16 #include <fstream>
17 #include <glibc-getopt.h>
18 #include <errno.h>
19 #if ENABLE_NLS
20 #  include <locale.h>
21 #endif
22 #include <stdlib.h>
23 #include <string.h>
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <unistd-jigdo.h>
27 #include <zlib.h>
28
29 #include <compat.hh>
30 #include <configfile.hh>
31 #include <debug.hh>
32 #include <jigdo-file-cmd.hh>
33 #include <jigdoconfig.hh>
34 #include <log.hh>
35 #include <mkimage.hh>
36 #include <mktemplate.hh>
37 #include <recursedir.hh>
38 #include <scan.hh>
39 #include <string.hh>
40 //______________________________________________________________________
41
42 RecurseDir JigdoFileCmd::fileNames;
43 string JigdoFileCmd::imageFile;
44 string JigdoFileCmd::jigdoFile;
45 string JigdoFileCmd::templFile;
46 string JigdoFileCmd::jigdoMergeFile;
47 string JigdoFileCmd::cacheFile;
48 size_t JigdoFileCmd::optCacheExpiry = 60*60*24*30; // default: 30 days
49 vector<string> JigdoFileCmd::optLabels;
50 vector<string> JigdoFileCmd::optUris;
51 size_t JigdoFileCmd::blockLength    =   1*1024U;
52 size_t JigdoFileCmd::md5BlockLength = 128*1024U - 55;
53 size_t JigdoFileCmd::readAmount     = 128*1024U;
54 int JigdoFileCmd::optZipQuality = Z_BEST_COMPRESSION;
55 bool JigdoFileCmd::optBzip2 = false;
56 bool JigdoFileCmd::optForce = false;
57 bool JigdoFileCmd::optMkImageCheck = true;
58 bool JigdoFileCmd::optCheckFiles = true;
59 bool JigdoFileCmd::optScanWholeFile = false;
60 bool JigdoFileCmd::optGreedyMatching = true;
61 bool JigdoFileCmd::optAddImage = true;
62 bool JigdoFileCmd::optAddServers = true;
63 bool JigdoFileCmd::optHex = false;
64 string JigdoFileCmd::optDebug;
65 AnyReporter* JigdoFileCmd::optReporter = 0;
66 string JigdoFileCmd::optMatchExec;
67 #if WINDOWS
68   const char* const JigdoFileCmd::binaryName = "jigdo-file";
69 #else
70   string JigdoFileCmd::binaryName;
71 #endif
72 //______________________________________________________________________
73
74 namespace {
75
76 // Absolute minimum for --min-length (i.e. blockLength), in bytes
77 const size_t MINIMUM_BLOCKLENGTH = 256;
78
79 char optHelp = '\0';
80 bool optVersion = false;
81
82 // Return value of main(), for "delayed error exit"
83 int returnValue = 0;
84
85 // Size of image file (zero if stdin), for percentage progress reports
86 static uint64 imageSize;
87 //______________________________________________________________________
88
89 /// Progress report class that writes informational messages to cerr
90 class MyProgressReporter : public AnyReporter {
91 public:
92   MyProgressReporter(bool prog) : printProgress(prog) { }
93
94   // Length of "100% 9999k/9999k " part before "scanning ..." etc message
95   static const unsigned PROGRESS_WIDTH = 23;
96
97   virtual void error(const string& message) { print(message); }
98   virtual void info(const string& message) { print(message); }
99   virtual void coutInfo(const string& message);
100   virtual void scanningFile(const FilePart* file, uint64 offInFile) {
101     if (!printProgress) return;
102     string m;
103     append(m, 100 * offInFile / file->size(), 3); // 3
104     m += '%'; // 1 char
105     append(m, offInFile / 1024, 8); // >= 8 chars
106     m += "k/"; // 2 chars
107     append(m, file->size() / 1024); // want >= 8 chars
108     m += 'k'; // 1 char
109     if (m.size() < 3+1+8+2+8+1)
110       m += "          " + 10 - (3+1+8+2+8+1 - m.size());
111     Paranoid(m.length() == PROGRESS_WIDTH);
112     m += _("scanning");
113     m += " `";
114     m += file->leafName();
115     m += '\'';
116     print(m, false);
117   }
118   virtual void scanningImage(uint64 offset) {
119     if (!printProgress) return;
120     string m;
121     if (imageSize != 0) {
122       append(m, 100 * (totalAll + offset) / (totalAll + imageSize), 3); // 3
123       m += '%'; // 1 char
124     } else {
125       m += "    ";
126     }
127     append(m, offset / 1024, 8); // >= 8 chars
128     m += 'k'; // 1 char
129     if (imageSize != 0) {
130       m += '/'; // 1 char
131       append(m, imageSize / 1024); // want >= 8 chars
132       m += 'k'; // 1 char
133     }
134     if (m.size() < 3+1+8+1+1+8+1)
135       m += "          " + 10 - (3+1+8+1+1+8+1 - m.size());
136     Paranoid(m.length() == PROGRESS_WIDTH);
137     m += _("scanning image");
138     print(m, false);
139   }
140   virtual void readingMD5(uint64 offInStream, uint64 size) {
141     if (!printProgress) return;
142     string m;
143     append(m, 100 * offInStream / size, 3); // 3
144     m += '%'; // 1 char
145     append(m, offInStream / 1024, 8); // >= 8 chars
146     m += "k/"; // 2 chars
147     append(m, size / 1024); // want >= 8 chars
148     m += 'k'; // 1 char
149     if (m.size() < 3+1+8+2+8+1)
150       m += "          " + 10 - (3+1+8+2+8+1 - m.size());
151     Paranoid(m.length() == PROGRESS_WIDTH);
152     m += _("verifying image");
153     print(m, false);
154   }
155   virtual void writingImage(uint64 written, uint64 totalToWrite,
156                             uint64 imgOff, uint64 imgSize) {
157     if (!printProgress) return;
158     string m;
159     append(m, 100 * written / totalToWrite, 3); // 3
160     m += '%'; // 1 char
161     append(m, imgOff / 1024, 8); // >= 8 chars
162     m += "k/"; // 2 chars
163     append(m, imgSize / 1024); // want >= 8 chars
164     m += 'k'; // 1 char
165     if (m.size() < 3+1+8+2+8+1)
166       m += "          " + 10 - (3+1+8+2+8+1 - m.size());
167     Paranoid(m.length() == PROGRESS_WIDTH);
168     m += _("writing image");
169     print(m, false);
170   }
171   virtual void abortingScan() {
172     string m(_("Error scanning image - abort"));
173     print(m);
174   }
175   virtual void matchFound(const FilePart* file, uint64 offInImage) {
176     string m = subst(_("Match of `%1' at offset %2"),
177                      file->leafName(), offInImage);
178     print(m);
179   }
180   virtual void finished(uint64 imageSize) {
181     if (!printProgress) return;
182     string m = subst(_("Finished - image size is %1 bytes."), imageSize);
183     print(m);
184   }
185 private:
186   static void print(string s, bool addNewline = true);
187   bool printProgress;
188   static string prevLine;
189   static uint64 totalAll;
190 };
191 //________________________________________
192
193 string MyProgressReporter::prevLine;
194 uint64 MyProgressReporter::totalAll = 0;
195
196 /* Print the string to stderr. Repeatedly overwrite the same line with
197    different progress reports if requested with addNewline==false. If
198    the tail of the message is the same as a previous message printed
199    on the same line, do not print the tail. */
200 void MyProgressReporter::print(string s, bool addNewline) {
201   size_t screenWidth = static_cast<size_t>(ttyWidth()) - 1;
202   // For progress messages, truncate if too long
203   if (!addNewline && screenWidth != 0 && s.length() > screenWidth) {
204     string::size_type tick = s.find("`", PROGRESS_WIDTH);
205     if (tick != string::npos)
206       s.replace(tick + 1, s.length() - screenWidth + 3, "...", 3);
207     if (s.length() > screenWidth) {
208       s.resize(screenWidth);
209       s[screenWidth - 1] = '$';
210     }
211   }
212
213   if (s.size() != prevLine.size()) {
214     // Print new message, maybe pad with spaces to overwrite rest of old one
215     cerr << s;
216     if (s.size() < prevLine.size()) {
217       size_t nrSpaces = prevLine.size() - s.size();
218       while (nrSpaces >= 10) { cerr << "          "; nrSpaces -= 10; }
219       if (nrSpaces > 0) cerr << ("         " + 9 - nrSpaces);
220     }
221   } else {
222     /* Same length as previous - to reduce cursor flicker, only redraw
223        as much as necessary */
224     int i = s.size() - 1;
225     while (i >= 0 && s[i] == prevLine[i]) --i;
226     for (int j = 0; j <= i; ++j) cerr << s[j];
227   }
228
229   // Should the message just printed be overwritten on next call?
230   if (addNewline) {
231     // No, leave it visible
232     cerr << endl;
233     prevLine = "";
234   } else {
235     // Yes, overwrite with next message
236     cerr << '\r' << flush;
237     prevLine = s;
238   }
239 }
240 //________________________________________
241
242 /* If stdout is redirected to a file, the file should just contain the
243    progress reports, none of the padding space chars. */
244 void MyProgressReporter::coutInfo(const string& message) {
245   cout << message << flush; // Need to flush because we mix cout and cerr
246   if (message.size() < prevLine.size()) {
247     size_t nrSpaces = prevLine.size() - message.size();
248     while (nrSpaces >= 10) { cerr << "          "; nrSpaces -= 10; }
249     if (nrSpaces > 0) cerr << ("         " + 9 - nrSpaces);
250   }
251   cout << endl;
252   cerr << '\r' << flush;
253   /* Ensure good-looking progress reports even if cout goes to a file
254      and cerr to the terminal. */
255   if (message.size() > prevLine.size())
256     prevLine.resize(message.size(), '\0');
257   else
258     prevLine[prevLine.size() - 1] = '\0';
259 }
260 //________________________________________
261
262 /// Progress report class that writes informational messages to cerr
263 class MyGrepProgressReporter : public AnyReporter {
264 public:
265   // Default error()/info() will print to cerr
266   virtual void matchFound(const FilePart* file, uint64 offInImage) {
267     cout << offInImage << ' ' << file->getPath() << file->leafName() << endl;
268   }
269 };
270 //________________________________________
271
272 /// Progress report class that writes informational messages to cerr
273 class MyQuietProgressReporter : public AnyReporter {
274   virtual void info(const string&) { }
275 };
276 //________________________________________
277
278 MyProgressReporter reporterDefault(true);
279 MyProgressReporter reporterNoprogress(false);
280 MyGrepProgressReporter reporterGrep;
281 MyQuietProgressReporter reporterQuiet;
282 //______________________________________________________________________
283
284 inline void printUsage(bool detailed, size_t blockLength,
285                        size_t md5BlockLength, size_t readAmount) {
286   if (detailed) {
287     cout << subst(_(
288     "\n"
289     "Copyright (C) 2001-%1 Richard Atterer <http://atterer.net>\n"
290     "This program is free software; you can redistribute it and/or modify\n"
291     "it under the terms of the GNU General Public License, version 2. See\n"
292     "the file COPYING or <http://www.gnu.org/copyleft/gpl.html> for details.\n"
293     "\n"), CURRENT_YEAR);
294   }
295   cout << subst(_(
296     "\n"
297     "Usage: %1 COMMAND [OPTIONS] [FILES...]\n"
298     "Commands:\n"
299     "  make-template mt Create template and jigdo from image and files\n"
300     "  make-image mi    Recreate image from template and files (can merge\n"
301     "                   files in >1 steps, uses `IMG%2tmp' for --image=IMG)\n"
302     "  print-missing pm After make-image, print files still missing for\n"
303     "                   the image to be completely recreated\n"),
304     binName(), EXTSEPS);
305   if (detailed) cout << _(
306     "  print-missing-all pma\n"
307     "                   Print all URIs for each missing file\n"
308     "  scan sc          Update cache with information about supplied files\n");
309   cout << _(
310     "  verify ver       Check whether image matches checksum from template\n"
311     "  md5sum md5       Print MD5 checksums similar to md5sum(1)\n");
312   if (detailed) {
313     cout << _(
314     "  list-template ls Print low-level listing of contents of template\n"
315     "                   data or tmp file\n");
316   }
317   cout << subst(_(
318     "\n"
319     "Important options:\n"
320     "  -i  --image=FILE Output/input filename for image file\n"
321     "  -j  --jigdo=FILE Input/output filename for jigdo file\n"
322     "  -t  --template=FILE\n"
323     "                   Input/output filename for template file\n"
324     "  -T  --files-from=FILE\n"
325     "                   Read further filenames from FILE (`-' for stdin)\n"
326     "  -r  --report=default|noprogress|quiet|grep\n"
327     "                   Control format of status reports to stderr (or\n"
328     "                   stdout in case of `grep')\n"
329     "  -f  --force      Silently delete existent output files\n"
330     "      --label Label=%1%2path\n"
331     "                   [make-template] Replace name of input file\n"
332     "                   `%1%2path%3a%2file%4txt' (note the `%3') with\n"
333     "                   `Label:a/file%4txt' in output jigdo\n"
334     "      --uri Label=http://www.site.com\n"
335     "                   [make-template] Add mapping from Label to given\n"
336     "                   URI instead of default `file:' URI\n"
337     "                   [print-missing] Override mapping in input jigdo\n"
338     "  -0 to -9         Set amount of compression in output template\n"
339     "      --bzip2      Use bzip2 compression instead of default --gzip\n"
340     "      --cache=FILE Store/reload information about any files scanned\n"),
341     (WINDOWS ? "C:" : ""), DIRSEPS, SPLITSEP, EXTSEPS);
342   if (detailed) {
343     cout << _(
344       "      --no-cache   Do not cache information about scanned files\n"
345       "      --cache-expiry=SECONDS[h|d|w|m|y]\n"
346       "                   Remove cache entries if last access was longer\n"
347       "                   ago than given amount of time [default 30 days]\n"
348       "  -h  --help       Output short help\n"
349       "  -H  --help-all   Output this help\n");
350   } else {
351     cout << _("  -h  --help       Output this help\n"
352               "  -H  --help-all   Output more detailed help\n");
353   }
354   cout << _("  -v  --version    Output version info") << endl;
355   if (detailed) {
356     cout << subst(_(
357     "\n"
358     "Further options: (can append 'k', 'M', 'G' to any BYTES argument)\n"
359     "  --merge=FILE     [make-template] Add FILE contents to output jigdo\n"
360     "  --no-force       Do not delete existent output files [default]\n"
361     "  --min-length=BYTES [default %1]\n"
362     "                   [make-template] Minimum length of files to search\n"
363     "                   for in image data\n"
364     "  --md5-block-size=BYTES [default %2]\n"
365     "                   Uninteresting internal parameter -\n"
366     "                   jigdo-file enforces: min-length < md5-block-size\n"
367     "  --readbuffer=BYTES [default %3k]\n"
368     "                   Amount of data to read at a time\n"
369     "  --check-files [default]\n"
370     "                   [make-template,md5sum] Check if files exist and\n"
371     "                   get or verify checksums, date and size\n"
372     "                   [make-image] Verify checksum of files written to\n"
373     "                   image\n"
374     "  --no-check-files [make-template,md5sum] when used with --cache,\n"
375     "                   [make-image] Do not verify checksums of files\n"
376     "  --scan-whole-file [scan] Scan whole file instead of only first block\n"
377     "  --no-scan-whole-file [scan] Scan only first block [default]\n"
378     "  --greedy-matching [make-template] Prefer immediate matches of small\n"
379     "                   files now over possible (but uncertain) matches of \n"
380     "                   larger files later [default]\n"
381     "  --no-greedy-matching\n"
382     "                   [make-template] Skip a smaller match and prefer a\n"
383     "                   pending larger one, with the risk of missing both\n"
384     "  --image-section [default]\n"
385     "  --no-image-section\n"
386     "  --servers-section [default]\n"
387     "  --no-servers-section\n"
388     "                   [make-template] When creating the jigdo file, do\n"
389     "                   or do not add the sections `[Image]' or `[Servers]'\n"
390     "  --debug[=all|=UNIT1,UNIT2...|=help]\n"
391     "                   Print debugging information for all units, or for\n"
392     "                   specified units, or print list of units.\n"
393     "                   Can use `~', e.g. `all,~libwww'\n"
394     "  --no-debug       No debugging info [default]\n"
395     "  --match-exec=CMD [make-template] Execute command when files match\n"
396     "                   CMD is passed to a shell, with environment set up:\n"
397     "                   LABEL, LABELPATH, MATCHPATH, LEAF, MD5SUM, FILE\n"
398     "                   e.g. 'mkdir -p \"${LABEL:-.}/$MATCHPATH\" && ln -f \"$FILE\" \"${LABEL:-.}/$MATCHPATH$LEAF\"'\n"
399     "  --no-hex [default]\n"
400     "  --hex            [md5sum, list-template] Output checksums in\n"
401     "                   hexadecimal, not Base64\n"
402     "  --gzip           [default] Use gzip compression, not --bzip2\n"),
403     blockLength, md5BlockLength, readAmount / 1024) << endl;
404   }
405   return;
406 }
407 //______________________________________________________________________
408
409 /* Like atoi(), but tolerate exactly one suffix 'k', 'K', 'm', 'M',
410    'g' or 'G' for kilo, mega, giga */
411 size_t scanMemSize(const char* str) {
412   const char* s = str;
413   size_t x = 0;
414   while (*s >= '0' && *s <= '9') {
415     x = 10 * x + *s - '0';
416     ++s;
417   }
418   switch (*s) { // Fallthrough mania!
419   case 'g': case 'G':
420     x = x * 1024;
421   case 'm': case 'M':
422     x = x * 1024;
423   case 'k': case 'K':
424     x = x * 1024;
425     if (*++s == '\0') return x;
426   default:
427     cerr << subst(_("%1: Invalid size specifier `%2'"), binName(), str)
428          << endl;
429     throw Cleanup(3);
430   case '\0': return x;
431   }
432 }
433
434 /* Like atoi(), but tolerate exactly one suffix 'h', 'd', 'w', 'm',
435    'y' for hours, days, weeks, months, years. Returns seconds. Special
436    value "off" results in 0 to be returned. */
437 size_t scanTimespan(const char* str) {
438   if (strcmp(str, "off") == 0) return 0;
439   const char* s = str;
440   size_t x = 0;
441   while (*s >= '0' && *s <= '9') {
442     x = 10 * x + *s - '0';
443     ++s;
444   }
445   switch (*s) {
446   case 'h': case 'H': x = x * 60 * 60; ++s; break;
447   case 'd': case 'D': x = x * 60 * 60 * 24; ++s; break;
448   case 'w': case 'W': x = x * 60 * 60 * 24 * 7; ++s; break;
449   case 'm': case 'M': x = x * 60 * 60 * 24 * 30; ++s; break;
450   case 'y': case 'Y': x = x * 60 * 60 * 24 * 365; ++s; break;
451   }
452   if (*s == '\0') return x;
453   cerr << subst(_("%1: Invalid time specifier `%2'"), binName(), str)
454        << endl;
455   throw Cleanup(3);
456 }
457 //______________________________________________________________________
458
459 /* Try creating a filename in dest by stripping any file extension
460    from source and appending ext.
461    Only deduceName() should call deduceName2(). */
462 void deduceName2(string& dest, const char* ext, const string& src) {
463   Paranoid(dest.empty());
464   string::size_type lastDot = src.rfind(EXTSEP);
465   if (lastDot != string::npos) {
466     if (src.find(DIRSEP, lastDot + 1) != string::npos)
467       lastDot = string::npos;
468   }
469   dest.assign(src, 0U, lastDot);
470   dest += ext;
471   if (dest == src) dest = "";
472 }
473
474 inline void deduceName(string& dest, const char* ext, const string& src) {
475   if (!dest.empty()) return;
476   deduceName2(dest, ext, src);
477 }
478 //______________________________________________________________________
479
480 void outOfMemory() {
481   cerr << subst(_("%1: Out of memory - aborted."), binName()) << endl;
482   exit(3);
483 }
484 //______________________________________________________________________
485
486 } // local namespace
487
488 enum {
489   LONGOPT_BUFSIZE = 0x100, LONGOPT_NOFORCE, LONGOPT_MINSIZE,
490   LONGOPT_MD5SIZE, LONGOPT_MKIMAGECHECK, LONGOPT_NOMKIMAGECHECK,
491   LONGOPT_LABEL, LONGOPT_URI, LONGOPT_ADDSERVERS, LONGOPT_NOADDSERVERS,
492   LONGOPT_ADDIMAGE, LONGOPT_NOADDIMAGE, LONGOPT_NOCACHE, LONGOPT_CACHEEXPIRY,
493   LONGOPT_MERGE, LONGOPT_HEX, LONGOPT_NOHEX, LONGOPT_DEBUG, LONGOPT_NODEBUG,
494   LONGOPT_MATCHEXEC, LONGOPT_BZIP2, LONGOPT_GZIP, LONGOPT_SCANWHOLEFILE,
495   LONGOPT_NOSCANWHOLEFILE, LONGOPT_GREEDYMATCHING, LONGOPT_NOGREEDYMATCHING
496 };
497
498 // Deal with command line switches
499 JigdoFileCmd::Command JigdoFileCmd::cmdOptions(int argc, char* argv[]) {
500 # if !WINDOWS
501   binaryName = argv[0];
502 # endif
503   bool error = false;
504   optReporter = &reporterDefault;
505
506   while (true) {
507     static const struct option longopts[] = {
508       { "bzip2",              no_argument,       0, LONGOPT_BZIP2 },
509       { "cache",              required_argument, 0, 'c' },
510       { "cache-expiry",       required_argument, 0, LONGOPT_CACHEEXPIRY },
511       { "check-files",        no_argument,       0, LONGOPT_MKIMAGECHECK },
512       { "debug",              optional_argument, 0, LONGOPT_DEBUG },
513       { "files-from",         required_argument, 0, 'T' }, // "-T" like tar's
514       { "force",              no_argument,       0, 'f' },
515       { "greedy-matching",    no_argument,       0, LONGOPT_GREEDYMATCHING },
516       { "gzip",               no_argument,       0, LONGOPT_GZIP },
517       { "help",               no_argument,       0, 'h' },
518       { "help-all",           no_argument,       0, 'H' },
519       { "hex",                no_argument,       0, LONGOPT_HEX },
520       { "image",              required_argument, 0, 'i' },
521       { "image-section",      no_argument,       0, LONGOPT_ADDIMAGE },
522       { "jigdo",              required_argument, 0, 'j' },
523       { "label",              required_argument, 0, LONGOPT_LABEL },
524       { "match-exec",         required_argument, 0, LONGOPT_MATCHEXEC },
525       { "md5-block-size",     required_argument, 0, LONGOPT_MD5SIZE },
526       { "merge",              required_argument, 0, LONGOPT_MERGE },
527       { "min-length",         required_argument, 0, LONGOPT_MINSIZE },
528       { "no-cache",           no_argument,       0, LONGOPT_NOCACHE },
529       { "no-check-files",     no_argument,       0, LONGOPT_NOMKIMAGECHECK },
530       { "no-debug",           no_argument,       0, LONGOPT_NODEBUG },
531       { "no-force",           no_argument,       0, LONGOPT_NOFORCE },
532       { "no-greedy-matching", no_argument,       0, LONGOPT_NOGREEDYMATCHING },
533       { "no-hex",             no_argument,       0, LONGOPT_NOHEX },
534       { "no-image-section",   no_argument,       0, LONGOPT_NOADDIMAGE },
535       { "no-scan-whole-file", no_argument,       0, LONGOPT_NOSCANWHOLEFILE },
536       { "no-servers-section", no_argument,       0, LONGOPT_NOADDSERVERS },
537       { "readbuffer",         required_argument, 0, LONGOPT_BUFSIZE },
538       { "report",             required_argument, 0, 'r' },
539       { "scan-whole-file",    no_argument,       0, LONGOPT_SCANWHOLEFILE },
540       { "servers-section",    no_argument,       0, LONGOPT_ADDSERVERS },
541       { "template",           required_argument, 0, 't' },
542       { "uri",                required_argument, 0, LONGOPT_URI },
543       { "version",            no_argument,       0, 'v' },
544       { 0, 0, 0, 0 }
545     };
546
547     int c = getopt_long(argc, argv, "0123456789hHvT:i:j:t:c:fr:",
548                         longopts, 0);
549     if (c == -1) break;
550
551     switch (c) {
552     case '0': case '1': case '2': case '3': case '4':
553     case '5': case '6': case '7': case '8': case '9':
554       optZipQuality = c - '0'; break;
555     case LONGOPT_BZIP2: optBzip2 = true; break;
556     case LONGOPT_GZIP:  optBzip2 = false; break;
557     case 'h': case 'H': optHelp = c; break;
558     case 'v': optVersion = true; break;
559     case 'T': fileNames.addFilesFrom(
560                 strcmp(optarg, "-") == 0 ? "" : optarg); break;
561     case 'i': imageFile = optarg; break;
562     case 'j': jigdoFile = optarg; break;
563     case 't': templFile = optarg; break;
564     case LONGOPT_MERGE: jigdoMergeFile = optarg; break;
565     case 'c': cacheFile = optarg; break;
566     case LONGOPT_NOCACHE: cacheFile.erase(); break;
567     case LONGOPT_CACHEEXPIRY: optCacheExpiry = scanTimespan(optarg); break;
568     case 'f': optForce = true; break;
569     case LONGOPT_NOFORCE: optForce = false; break;
570     case LONGOPT_MINSIZE:    blockLength = scanMemSize(optarg); break;
571     case LONGOPT_MD5SIZE: md5BlockLength = scanMemSize(optarg); break;
572     case LONGOPT_BUFSIZE:     readAmount = scanMemSize(optarg); break;
573     case 'r':
574       if (strcmp(optarg, "default") == 0) {
575         optReporter = &reporterDefault;
576       } else if (strcmp(optarg, "noprogress") == 0) {
577         optReporter = &reporterNoprogress;
578       } else if (strcmp(optarg, "quiet") == 0) {
579         optReporter = &reporterQuiet;
580       } else if (strcmp(optarg, "grep") == 0) {
581         optReporter = &reporterGrep;
582       } else {
583         cerr << subst(_("%1: Invalid argument to --report (allowed: "
584                         "default noprogress quiet grep)"), binName())
585              << '\n';
586         error = true;
587       }
588       break;
589     case LONGOPT_MKIMAGECHECK: optMkImageCheck = true;
590                                optCheckFiles = true; break;
591     case LONGOPT_NOMKIMAGECHECK: optMkImageCheck = false;
592                                  optCheckFiles = false; break;
593     case LONGOPT_GREEDYMATCHING: optGreedyMatching = true; break;
594     case LONGOPT_NOGREEDYMATCHING: optGreedyMatching = false; break;
595     case LONGOPT_SCANWHOLEFILE: optScanWholeFile = true; break;
596     case LONGOPT_NOSCANWHOLEFILE: optScanWholeFile = false; break;
597     case LONGOPT_ADDSERVERS: optAddServers = true; break;
598     case LONGOPT_NOADDSERVERS: optAddServers = false; break;
599     case LONGOPT_ADDIMAGE: optAddImage = true; break;
600     case LONGOPT_NOADDIMAGE: optAddImage = false; break;
601     case LONGOPT_LABEL: optLabels.push_back(string(optarg)); break;
602     case LONGOPT_URI: optUris.push_back(string(optarg)); break;
603     case LONGOPT_HEX: optHex = true; break;
604     case LONGOPT_NOHEX: optHex = false; break;
605     case LONGOPT_DEBUG:
606       if (optarg) optDebug = optarg; else optDebug = "all";
607       break;
608     case LONGOPT_NODEBUG: optDebug.erase(); break;
609     case LONGOPT_MATCHEXEC: optMatchExec = optarg; break;
610     case '?': error = true;
611     case ':': break;
612     default:
613       msg("getopt returned %1", static_cast<int>(c));
614       break;
615     }
616   }
617
618   if (error) exit_tryHelp();
619
620   if (optHelp != '\0' || optVersion) {
621     if (optVersion) cout << "jigdo-file version " JIGDO_VERSION << endl;
622     if (optHelp != '\0') printUsage(optHelp == 'H', blockLength,
623                                     md5BlockLength, readAmount);
624     throw Cleanup(0);
625   }
626
627 # if WINDOWS
628   Logger::scanOptions(optDebug, binName());
629 # else
630   Logger::scanOptions(optDebug, binName().c_str());
631 # endif
632   //______________________________
633
634   // Silently correct invalid blockLength/md5BlockLength args
635   if (blockLength < MINIMUM_BLOCKLENGTH) blockLength = MINIMUM_BLOCKLENGTH;
636   if (blockLength >= md5BlockLength) md5BlockLength = blockLength + 1;
637   // Round to next k*64+55 for efficient MD5 calculation
638   md5BlockLength = ((md5BlockLength + 63 - 55) & ~63U) + 55;
639
640   Paranoid(blockLength >= MINIMUM_BLOCKLENGTH
641            && blockLength < md5BlockLength);
642   //______________________________
643
644   // Complain if name of command isn't there
645   if (optind >= argc) {
646     cerr << subst(_("%1: Please specify a command"), binName()) << '\n';
647     exit_tryHelp();
648   }
649
650   // Find Command code corresponding to command string on command line :)
651   Command result;
652   {
653     const char* command = argv[optind++];
654     struct CodesEntry { char* name; Command code; };
655     const CodesEntry codes[] = {
656       { "make-template",     MAKE_TEMPLATE },
657       { "mt",                MAKE_TEMPLATE },
658       { "make-image",        MAKE_IMAGE },
659       { "mi",                MAKE_IMAGE },
660       { "print-missing",     PRINT_MISSING },
661       { "pm",                PRINT_MISSING },
662       { "print-missing-all", PRINT_MISSING_ALL },
663       { "pma",               PRINT_MISSING_ALL },
664       { "verify",            VERIFY },
665       { "ver",               VERIFY },
666       { "scan",              SCAN },
667       { "sc",                SCAN },
668       { "list-template",     LIST_TEMPLATE },
669       { "ls",                LIST_TEMPLATE },
670       { "md5sum",            MD5SUM },
671       { "md5",               MD5SUM }
672     };
673
674     const CodesEntry *c = codes;
675     const CodesEntry *end = codes+sizeof(codes)/sizeof(CodesEntry);
676     while (true) {
677       if (strcmp(command, c->name) == 0) {
678         result = c->code;
679         break;
680       }
681       ++c;
682       if (c == end) {
683         string allCommands;
684         for (const CodesEntry *c = codes,
685               *end = codes+sizeof(codes)/sizeof(CodesEntry); c != end; ++c) {
686           allCommands += ' ';
687           allCommands += c->name;
688         }
689         cerr << subst(_("%1: Invalid command `%2'\n(Must be one of:%3)"),
690                       binName(), command, allCommands) << '\n';
691         exit_tryHelp();
692       }
693     }
694   }
695   //____________________
696
697   while (optind < argc) fileNames.addFile(argv[optind++]);
698
699 # if 0
700   /* If no --files-from given and no files on command line, assume we
701      are to read any list of filenames from stdin. */
702   if (fileNames.empty()) fileNames.addFilesFrom("");
703 # endif
704   //____________________
705
706   // If --image, --jigdo or --template not given, create name from other args
707   if (!imageFile.empty() && imageFile != "-") {
708     deduceName(jigdoFile, EXTSEPS"jigdo", imageFile);
709     deduceName(templFile, EXTSEPS"template", imageFile);
710   } else if (!jigdoFile.empty() && jigdoFile != "-") {
711     deduceName(imageFile, "", jigdoFile);
712     deduceName(templFile, EXTSEPS"template", jigdoFile);
713   } else if (!templFile.empty() && templFile != "-") {
714     deduceName(imageFile, "", templFile);
715     deduceName(jigdoFile, EXTSEPS"jigdo", templFile);
716   }
717
718   if (imageFile != "-") {
719     struct stat fileInfo;
720     if (stat(imageFile.c_str(), &fileInfo) == 0)
721       imageSize = fileInfo.st_size;
722   }
723   //____________________
724
725   if (msg) {
726     msg("Image file: %1", imageFile);
727     msg("Jigdo:      %1", jigdoFile);
728     msg("Template:   %1", templFile);
729   }
730
731   return result;
732 }
733 //______________________________________________________________________
734
735 void exit_tryHelp() {
736   cerr << subst(_("%1: Try `%1 -h' or `man jigdo-file' for more "
737                   "information"), binName()) << endl;
738   throw Cleanup(3);
739 }
740 //______________________________________________________________________
741
742 int main(int argc, char* argv[]) {
743
744 # if ENABLE_NLS
745   setlocale (LC_ALL, "");
746   bindtextdomain(PACKAGE, PACKAGE_LOCALE_DIR);
747   textdomain(PACKAGE);
748 # endif
749 # if DEBUG
750   Logger::setEnabled("general");
751 # else
752   Debug::abortAfterFailedAssertion = false;
753 # endif
754
755   try {
756     set_new_handler(outOfMemory);
757     JigdoFileCmd::Command command = JigdoFileCmd::cmdOptions(argc, argv);
758     switch (command) {
759     case JigdoFileCmd::MAKE_TEMPLATE:
760       returnValue = JigdoFileCmd::makeTemplate(); break;
761     case JigdoFileCmd::MAKE_IMAGE:
762       returnValue = JigdoFileCmd::makeImage();    break;
763     case JigdoFileCmd::PRINT_MISSING:
764     case JigdoFileCmd::PRINT_MISSING_ALL:
765       returnValue = JigdoFileCmd::printMissing(command); break;
766     case JigdoFileCmd::SCAN:
767       returnValue = JigdoFileCmd::scanFiles();    break;
768     case JigdoFileCmd::VERIFY:
769       returnValue = JigdoFileCmd::verifyImage();  break;
770     case JigdoFileCmd::LIST_TEMPLATE:
771       returnValue = JigdoFileCmd::listTemplate(); break;
772     case JigdoFileCmd::MD5SUM:
773       JigdoFileCmd::optCheckFiles = true; // Quick fix, possibly not 100% correct
774       returnValue = JigdoFileCmd::md5sumFiles();  break;
775     }
776   }
777   catch (bad_alloc) { outOfMemory(); }
778   catch (Cleanup c) {
779     msg("[Cleanup %1]", c.returnValue);
780     return c.returnValue;
781   }
782   catch (Error e) {
783     string err = binName(); err += ": "; err += e.message;
784     JigdoFileCmd::optReporter->error(err);
785     return 3;
786   }
787   catch (...) { // Uncaught exception - this should not happen(tm)
788     string err = binName(); err += ": Unknown error";
789     JigdoFileCmd::optReporter->error(err);
790     return 3;
791   }
792   msg("[exit(%1)]", returnValue);
793   return returnValue;
794 }