1 //  This file is part of par2cmdline (a PAR 2.0 compatible file verification and
2 //  repair tool). See http://parchive.sourceforge.net for details of PAR 2.0.
3 //
4 //  Copyright (c) 2003 Peter Brian Clements
5 //  Copyright (c) 2019 Michael D. Nahas
6 //
7 //  par2cmdline is free software; you can redistribute it and/or modify
8 //  it under the terms of the GNU General Public License as published by
9 //  the Free Software Foundation; either version 2 of the License, or
10 //  (at your option) any later version.
11 //
12 //  par2cmdline is distributed in the hope that it will be useful,
13 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
14 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 //  GNU General Public License for more details.
16 //
17 //  You should have received a copy of the GNU General Public License
18 //  along with this program; if not, write to the Free Software
19 //  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20 
21 // This is included here, so that cout and cerr are not used elsewhere.
22 #include<iostream>
23 #include<algorithm>
24 #include "commandline.h"
25 using namespace std;
26 
27 #ifdef _MSC_VER
28 #ifdef _DEBUG
29 #undef THIS_FILE
30 static char THIS_FILE[]=__FILE__;
31 #define new DEBUG_NEW
32 #endif
33 #endif
34 
35 // OpenMP
36 #ifdef _OPENMP
37 # include <omp.h>
38 #endif
39 
CommandLine(void)40 CommandLine::CommandLine(void)
41 : filesize_cache()
42 , version(verUnknown)
43 , noiselevel(nlUnknown)
44 , memorylimit(0)
45 , basepath()
46 #ifdef _OPENMP
47 , nthreads(0) // 0 means use default number
48 , filethreads( _FILE_THREADS ) // default from header file
49 #endif
50 , parfilename()
51 , rawfilenames()
52 , extrafiles()
53 , operation(opNone)
54 , purgefiles(false)
55 , skipdata(false)
56 , skipleaway(0)
57 , blockcount(0)
58 , blocksize(0)
59 , firstblock(0)
60 , recoveryfilescheme(scUnknown)
61 , recoveryfilecount(0)
62 , recoveryblockcount(0)
63 , recoveryblockcountset(false)
64 , redundancy(0)
65 , redundancysize(0)
66 , redundancyset(false)
67 , recursive(false)
68 {
69 }
70 
showversion(void)71 void CommandLine::showversion(void)
72 {
73   string version = PACKAGE " version " VERSION;
74   cout << version << endl;
75 }
76 
banner(void)77 void CommandLine::banner(void)
78 {
79   cout << "Copyright (C) 2003-2015 Peter Brian Clements." << endl
80     << "Copyright (C) 2011-2012 Marcel Partap." << endl
81     << "Copyright (C) 2012-2017 Ike Devolder." << endl
82     << "Copyright (C) 2014-2017 Jussi Kansanen." << endl
83     << "Copyright (C) 2019 Michael Nahas." << endl
84     << endl
85     << "par2cmdline comes with ABSOLUTELY NO WARRANTY." << endl
86     << endl
87     << "This is free software, and you are welcome to redistribute it and/or modify" << endl
88     << "it under the terms of the GNU General Public License as published by the" << endl
89     << "Free Software Foundation; either version 2 of the License, or (at your" << endl
90     << "option) any later version. See COPYING for details." << endl
91     << endl;
92 }
93 
usage(void)94 void CommandLine::usage(void)
95 {
96   cout <<
97     "Usage:\n"
98     "  par2 -h  : show this help\n"
99     "  par2 -V  : show version\n"
100     "  par2 -VV : show version and copyright\n"
101     "\n"
102     "  par2 c(reate) [options] <PAR2 file> [files] : Create PAR2 files\n"
103     "  par2 v(erify) [options] <PAR2 file> [files] : Verify files using PAR2 file\n"
104     "  par2 r(epair) [options] <PAR2 file> [files] : Repair files using PAR2 files\n"
105     "\n"
106     "You may also leave out the \"c\", \"v\", and \"r\" commands by using \"par2create\",\n"
107     "\"par2verify\", or \"par2repair\" instead.\n"
108     "\n"
109     "Options: (all uses)\n"
110     "  -B<path> : Set the basepath to use as reference for the datafiles\n"
111     "  -v [-v]  : Be more verbose\n"
112     "  -q [-q]  : Be more quiet (-q -q gives silence)\n"
113     "  -m<n>    : Memory (in MB) to use\n";
114 #ifdef _OPENMP
115   cout <<
116     "  -t<n>    : Number of threads used for main processing (" << omp_get_max_threads() << " detected)\n"
117     "  -T<n>    : Number of files hashed in parallel\n"
118     "             (" << _FILE_THREADS << " are the default)\n";
119 #endif
120   cout <<
121     "  --       : Treat all following arguments as filenames\n"
122     "Options: (verify or repair)\n"
123     "  -p       : Purge backup files and par files on successful recovery or\n"
124     "             when no recovery is needed\n"
125     "  -N       : Data skipping (find badly mispositioned data blocks)\n"
126     "  -S<n>    : Skip leaway (distance +/- from expected block position)\n"
127     "Options: (create)\n"
128     "  -a<file> : Set the main PAR2 archive name\n"
129     "  -b<n>    : Set the Block-Count\n"
130     "  -s<n>    : Set the Block-Size (don't use both -b and -s)\n"
131     "  -r<n>    : Level of redundancy (%%)\n"
132     "  -r<c><n> : Redundancy target size, <c>=g(iga),m(ega),k(ilo) bytes\n"
133     "  -c<n>    : Recovery Block-Count (don't use both -r and -c)\n"
134     "  -f<n>    : First Recovery-Block-Number\n"
135     "  -u       : Uniform recovery file sizes\n"
136     "  -l       : Limit size of recovery files (don't use both -u and -l)\n"
137     "  -n<n>    : Number of recovery files (don't use both -n and -l)\n"
138     "  -R       : Recurse into subdirectories\n"
139     "\n";
140   cout <<
141     "Example:\n"
142     "   par2 repair *.par2\n"
143     "\n";
144 }
145 
Parse(int argc,const char * const * argv)146 bool CommandLine::Parse(int argc, const char * const *argv)
147 {
148   if (!ReadArgs(argc, argv))
149     return false;
150 
151   if (operation != opNone) {  // user didn't do "par --help", etc.
152     if (!CheckValuesAndSetDefaults())
153       return false;
154   }
155 
156   if (operation == opCreate) {
157     if (!ComputeBlockSize())
158       return false;
159 
160     u64 sourceblockcount = 0;
161     u64 largestfilesize = 0;
162     for (vector<string>::const_iterator i=extrafiles.begin(); i!=extrafiles.end(); i++)
163     {
164       u64 filesize = filesize_cache.get(*i);
165       sourceblockcount += (filesize + blocksize-1) / blocksize;
166       if (filesize > largestfilesize)
167       {
168 	largestfilesize = filesize;
169       }
170     }
171 
172     if (!ComputeRecoveryBlockCount(&recoveryblockcount,
173 				   sourceblockcount,
174 				   blocksize,
175 				   firstblock,
176 				   recoveryfilescheme,
177 				   recoveryfilecount,
178 				   recoveryblockcountset,
179 				   redundancy,
180 				   redundancysize,
181 				   largestfilesize))
182     {
183       return false;
184     }
185   }
186 
187   return true;
188 }
189 
ReadArgs(int argc,const char * const * argv)190 bool CommandLine::ReadArgs(int argc, const char * const *argv)
191 {
192   if (argc<1)
193   {
194     return false;
195   }
196 
197   // Split the program name into path and filename
198   string path, name;
199   DiskFile::SplitFilename(argv[0], path, name);
200   argc--;
201   argv++;
202 
203   if (argc>0)
204   {
205     if (argv[0][0] == '-')
206     {
207       if (argv[0] == string("-h") || argv[0] == string("--help"))
208       {
209 	usage();
210 	return true;
211       }
212       else if (argv[0] == string("-V") || argv[0] == string("--version"))
213       {
214 	showversion();
215 	return true;
216       }
217       else if (argv[0] == string("-VV"))
218       {
219 	showversion();
220 	cout << endl;
221 	banner();
222 	return true;
223       }
224     }
225   }
226 
227   // Strip ".exe" from the end
228   if (name.size() > 4 && 0 == stricmp(".exe", name.substr(name.length()-4).c_str()))
229   {
230     name = name.substr(0, name.length()-4);
231   }
232 
233   // Check the resulting program name
234   if (0 == stricmp("par2create", name.c_str()))
235   {
236     operation = opCreate;
237   }
238   else if (0 == stricmp("par2verify", name.c_str()))
239   {
240     operation = opVerify;
241   }
242   else if (0 == stricmp("par2repair", name.c_str()))
243   {
244     operation = opRepair;
245   }
246 
247   // Have we determined what operation we want?
248   if (operation == opNone)
249   {
250     if (argc<2)
251     {
252       cerr << "Not enough command line arguments." << endl;
253       return false;
254     }
255 
256     switch (tolower(argv[0][0]))
257     {
258     case 'c':
259       if (argv[0][1] == 0 || 0 == stricmp(argv[0], "create"))
260         operation = opCreate;
261       break;
262     case 'v':
263       if (argv[0][1] == 0 || 0 == stricmp(argv[0], "verify"))
264         operation = opVerify;
265       break;
266     case 'r':
267       if (argv[0][1] == 0 || 0 == stricmp(argv[0], "repair"))
268         operation = opRepair;
269       break;
270     }
271 
272     if (operation == opNone)
273     {
274       cerr << "Invalid operation specified: " << argv[0] << endl;
275       return false;
276     }
277     argc--;
278     argv++;
279   }
280 
281   bool options = true;
282   basepath = "";
283 
284   while (argc>0)
285   {
286     if (argv[0][0])
287     {
288       if (options && argv[0][0] != '-')
289         options = false;
290 
291       if (options)
292       {
293         switch (argv[0][1])
294         {
295         case 'a':
296           {
297             if (operation == opCreate)
298             {
299               string str = argv[0];
300               bool setparfile = false;
301               if (str == "-a")
302               {
303                 setparfile = SetParFilename(argv[1]);
304                 argc--;
305                 argv++;
306               }
307               else
308               {
309                 setparfile = SetParFilename(str.substr(2));
310               }
311 
312               if (! setparfile)
313               {
314                 cerr << "failed to set the main par file" << endl;
315                 return false;
316               }
317             }
318           }
319           break;
320         case 'b':  // Set the block count
321           {
322             if (operation != opCreate)
323             {
324               cerr << "Cannot specify block count unless creating." << endl;
325               return false;
326             }
327             if (blockcount > 0)
328             {
329               cerr << "Cannot specify block count twice." << endl;
330               return false;
331             }
332             else if (blocksize > 0)
333             {
334               cerr << "Cannot specify both block count and block size." << endl;
335               return false;
336             }
337 
338             const char *p = &argv[0][2];
339             while (blockcount <= 3276 && *p && isdigit(*p))
340             {
341               blockcount = blockcount * 10 + (*p - '0');
342               p++;
343             }
344             if (0 == blockcount || blockcount > 32768 || *p)
345             {
346               cerr << "Invalid block count option: " << argv[0] << endl;
347               return false;
348             }
349           }
350           break;
351 
352         case 's':  // Set the block size
353           {
354             if (operation != opCreate)
355             {
356               cerr << "Cannot specify block size unless creating." << endl;
357               return false;
358             }
359             if (blocksize > 0)
360             {
361               cerr << "Cannot specify block size twice." << endl;
362               return false;
363             }
364             else if (blockcount > 0)
365             {
366               cerr << "Cannot specify both block count and block size." << endl;
367               return false;
368             }
369 
370             const char *p = &argv[0][2];
371             while (blocksize <= 429496729 && *p && isdigit(*p))
372             {
373               blocksize = blocksize * 10 + (*p - '0');
374               p++;
375             }
376             if (*p || blocksize == 0)
377             {
378               cerr << "Invalid block size option: " << argv[0] << endl;
379               return false;
380             }
381             if (blocksize & 3)
382             {
383               cerr << "Block size must be a multiple of 4." << endl;
384               return false;
385             }
386           }
387           break;
388 
389 #ifdef _OPENMP
390         case 't':  // Set amount of threads
391           {
392             nthreads = 0;
393             const char *p = &argv[0][2];
394 
395             while (*p && isdigit(*p))
396             {
397               nthreads = nthreads * 10 + (*p - '0');
398               p++;
399             }
400 
401             if (!nthreads)
402             {
403               cerr << "Invalid thread option: " << argv[0] << endl;
404               return false;
405             }
406           }
407           break;
408 
409         case 'T':  // Set amount of file threads
410           {
411             filethreads = 0;
412             const char *p = &argv[0][2];
413 
414             while (*p && isdigit(*p))
415             {
416               filethreads = filethreads * 10 + (*p - '0');
417               p++;
418             }
419 
420             if (!filethreads)
421             {
422               cerr << "Invalid file-thread option: " << argv[0] << endl;
423               return false;
424             }
425           }
426           break;
427 #endif
428 
429         case 'r':  // Set the amount of redundancy required
430           {
431             if (operation != opCreate)
432             {
433               cerr << "Cannot specify redundancy unless creating." << endl;
434               return false;
435             }
436             if (redundancyset)
437             {
438               cerr << "Cannot specify redundancy twice." << endl;
439               return false;
440             }
441             else if (recoveryblockcountset)
442             {
443               cerr << "Cannot specify both redundancy and recovery block count." << endl;
444               return false;
445             }
446 
447             if (argv[0][2] == 'k'
448                 || argv[0][2] == 'm'
449                 || argv[0][2] == 'g'
450             )
451             {
452               const char *p = &argv[0][3];
453               while (*p && isdigit(*p))
454               {
455                 redundancysize = redundancysize * 10 + (*p - '0');
456                 p++;
457               }
458               switch (argv[0][2])
459               {
460                 case 'g':
461                   redundancysize = redundancysize * 1024;
462                 case 'm':
463                   redundancysize = redundancysize * 1024;
464                 case 'k':
465                   redundancysize = redundancysize * 1024;
466                   break;
467               }
468             }
469             else
470             {
471               const char *p = &argv[0][2];
472               while (redundancy <= 10 && *p && isdigit(*p))
473               {
474                 redundancy = redundancy * 10 + (*p - '0');
475                 p++;
476               }
477               if (redundancy > 100 || *p)
478               {
479                 cerr << "Invalid redundancy option: " << argv[0] << endl;
480                 return false;
481               }
482               if (redundancy == 0 && recoveryfilecount > 0)
483               {
484                 cerr << "Cannot set redundancy to 0 and file count > 0" << endl;
485                 return false;
486               }
487             }
488             redundancyset = true;
489           }
490           break;
491 
492         case 'c': // Set the number of recovery blocks to create
493           {
494             if (operation != opCreate)
495             {
496               cerr << "Cannot specify recovery block count unless creating." << endl;
497               return false;
498             }
499             if (recoveryblockcountset)
500             {
501               cerr << "Cannot specify recovery block count twice." << endl;
502               return false;
503             }
504             else if (redundancyset)
505             {
506               cerr << "Cannot specify both recovery block count and redundancy." << endl;
507               return false;
508             }
509 
510             const char *p = &argv[0][2];
511             while (recoveryblockcount <= 32768 && *p && isdigit(*p))
512             {
513               recoveryblockcount = recoveryblockcount * 10 + (*p - '0');
514               p++;
515             }
516             if (recoveryblockcount > 32768 || *p)
517             {
518               cerr << "Invalid recoveryblockcount option: " << argv[0] << endl;
519               return false;
520             }
521             if (recoveryblockcount == 0 && recoveryfilecount > 0)
522             {
523               cerr << "Cannot set recoveryblockcount to 0 and file count > 0" << endl;
524               return false;
525             }
526             recoveryblockcountset = true;
527           }
528           break;
529 
530         case 'f':  // Specify the First block recovery number
531           {
532             if (operation != opCreate)
533             {
534               cerr << "Cannot specify first block number unless creating." << endl;
535               return false;
536             }
537             if (firstblock > 0)
538             {
539               cerr << "Cannot specify first block twice." << endl;
540               return false;
541             }
542 
543             const char *p = &argv[0][2];
544             while (firstblock <= 3276 && *p && isdigit(*p))
545             {
546               firstblock = firstblock * 10 + (*p - '0');
547               p++;
548             }
549             if (firstblock > 32768 || *p)
550             {
551               cerr << "Invalid first block option: " << argv[0] << endl;
552               return false;
553             }
554           }
555           break;
556 
557         case 'u':  // Specify uniformly sized recovery files
558           {
559             if (operation != opCreate)
560             {
561               cerr << "Cannot specify uniform files unless creating." << endl;
562               return false;
563             }
564             if (argv[0][2])
565             {
566               cerr << "Invalid option: " << argv[0] << endl;
567               return false;
568             }
569             if (recoveryfilescheme != scUnknown)
570             {
571               cerr << "Cannot specify two recovery file size schemes." << endl;
572               return false;
573             }
574 
575             recoveryfilescheme = scUniform;
576           }
577           break;
578 
579         case 'l':  // Limit the size of the recovery files
580           {
581             if (operation != opCreate)
582             {
583               cerr << "Cannot specify limit files unless creating." << endl;
584               return false;
585             }
586             if (argv[0][2])
587             {
588               cerr << "Invalid option: " << argv[0] << endl;
589               return false;
590             }
591             if (recoveryfilescheme != scUnknown)
592             {
593               cerr << "Cannot specify two recovery file size schemes." << endl;
594               return false;
595             }
596             if (recoveryfilecount > 0)
597             {
598               cerr << "Cannot specify limited size and number of files at the same time." << endl;
599               return false;
600             }
601 
602             recoveryfilescheme = scLimited;
603           }
604           break;
605 
606         case 'n':  // Specify the number of recovery files
607           {
608             if (operation != opCreate)
609             {
610               cerr << "Cannot specify recovery file count unless creating." << endl;
611               return false;
612             }
613             if (recoveryfilecount > 0)
614             {
615               cerr << "Cannot specify recovery file count twice." << endl;
616               return false;
617             }
618             // (Removed "Cannot set file count when redundancy is set to 0.")
619             if (recoveryblockcountset && recoveryblockcount == 0)
620             {
621               cerr << "Cannot set file count when recovery block count is set to 0." << endl;
622               return false;
623             }
624             if (recoveryfilescheme == scLimited)
625             {
626               cerr << "Cannot specify limited size and number of files at the same time." << endl;
627               return false;
628             }
629 
630             const char *p = &argv[0][2];
631             while (*p && isdigit(*p))
632             {
633               recoveryfilecount = recoveryfilecount * 10 + (*p - '0');
634               p++;
635             }
636             if (recoveryfilecount == 0 || *p)
637             {
638               cerr << "Invalid recovery file count option: " << argv[0] << endl;
639               return false;
640             }
641           }
642           break;
643 
644         case 'm':  // Specify how much memory to use for output buffers
645           {
646             if (memorylimit > 0)
647             {
648               cerr << "Cannot specify memory limit twice." << endl;
649               return false;
650             }
651 
652             const char *p = &argv[0][2];
653             while (*p && isdigit(*p))
654             {
655               memorylimit = memorylimit * 10 + (*p - '0');
656               p++;
657             }
658             if (memorylimit == 0 || *p)
659             {
660               cerr << "Invalid memory limit option: " << argv[0] << endl;
661               return false;
662             }
663           }
664           break;
665 
666         case 'v':
667           {
668             switch (noiselevel)
669             {
670             case nlUnknown:
671               {
672                 if (argv[0][2] == 'v')
673                   noiselevel = nlDebug;
674                 else
675                   noiselevel = nlNoisy;
676               }
677               break;
678             case nlNoisy:
679             case nlDebug:
680               noiselevel = nlDebug;
681               break;
682             default:
683               cerr << "Cannot use both -v and -q." << endl;
684               return false;
685               break;
686             }
687           }
688           break;
689 
690         case 'q':
691           {
692             switch (noiselevel)
693             {
694             case nlUnknown:
695               {
696                 if (argv[0][2] == 'q')
697                   noiselevel = nlSilent;
698                 else
699                   noiselevel = nlQuiet;
700               }
701               break;
702             case nlQuiet:
703             case nlSilent:
704               noiselevel = nlSilent;
705               break;
706             default:
707               cerr << "Cannot use both -v and -q." << endl;
708               return false;
709               break;
710             }
711           }
712           break;
713 
714         case 'p':
715           {
716             if (operation != opRepair && operation != opVerify)
717             {
718               cerr << "Cannot specify purge unless repairing or verifying." << endl;
719               return false;
720             }
721             purgefiles = true;
722           }
723           break;
724 
725         case 'h':
726           {
727             usage();
728             return false;
729           }
730 	  // "break;" not needed.
731 
732         case 'R':
733           {
734             if (operation == opCreate)
735             {
736               recursive = true;
737             }
738             else
739             {
740               cerr << "Cannot specific Recursive unless creating." << endl;
741               return false;
742             }
743           }
744           break;
745 
746         case 'N':
747           {
748             if (operation == opCreate)
749             {
750               cerr << "Cannot specify Data Skipping unless reparing or verifying." << endl;
751               return false;
752             }
753             skipdata = true;
754           }
755           break;
756 
757         case 'S':  // Set the skip leaway
758           {
759             if (operation == opCreate)
760             {
761               cerr << "Cannot specify skip leaway when creating." << endl;
762               return false;
763             }
764             if (!skipdata)
765             {
766               cerr << "Cannot specify skip leaway and no skipping." << endl;
767               return false;
768             }
769 
770             const char *p = &argv[0][2];
771             while (skipleaway <= 429496729 && *p && isdigit(*p))
772             {
773               skipleaway = skipleaway * 10 + (*p - '0');
774               p++;
775             }
776             if (*p || skipleaway == 0)
777             {
778               cerr << "Invalid skipleaway option: " << argv[0] << endl;
779               return false;
780             }
781           }
782           break;
783 
784         case 'B': // Set the basepath manually
785           {
786             string str = argv[0];
787             if (str == "-B")
788             {
789               basepath = DiskFile::GetCanonicalPathname(argv[1]);
790               argc--;
791               argv++;
792             }
793             else
794             {
795               basepath = DiskFile::GetCanonicalPathname(str.substr(2));
796             }
797           }
798           break;
799 
800         case '-':
801           {
802 	    if (argv[0] != string("--")) {
803               cerr << "Unknown option: " << argv[0] << endl;
804 	      cerr << "  (Options must appear after create, repair or verify.)" << endl;
805 	      cerr << "  (Run \"" << path << name << " --help\" for supported options.)" << endl;
806               return false;
807             }
808 
809             argc--;
810             argv++;
811             options = false;
812             continue;
813           }
814           break;
815         default:
816           {
817             cerr << "Invalid option specified: " << argv[0] << endl;
818             return false;
819           }
820         }
821       }
822       else if (parfilename.length() == 0)
823       {
824         string filename = argv[0];
825         bool setparfile = SetParFilename(filename);
826         if (! setparfile)
827         {
828           cerr << "failed to set the main par file" << endl;
829           return false;
830         }
831       }
832       else
833       {
834 
835         string path;
836         string name;
837         DiskFile::SplitFilename(argv[0], path, name);
838 	std::unique_ptr< list<string> > filenames(
839 						DiskFile::FindFiles(path, name, recursive)
840 						);
841 
842         list<string>::iterator fn = filenames->begin();
843         while (fn != filenames->end())
844         {
845           // Convert filename from command line into a full path + filename
846           string filename = DiskFile::GetCanonicalPathname(*fn);
847           rawfilenames.push_back(filename);
848           ++fn;
849         }
850 
851         // delete filenames;   Taken care of by unique_ptr<>
852       }
853     }
854 
855     argc--;
856     argv++;
857   }
858 
859   return true;
860 }
861 
862 
863 // This webpage has code to get physical memory size on many OSes
864 // http://nadeausoftware.com/articles/2012/09/c_c_tip_how_get_physical_memory_size_system
865 
866 #ifdef _WIN32
GetTotalPhysicalMemory()867 u64 CommandLine::GetTotalPhysicalMemory()
868 {
869   u64 TotalPhysicalMemory = 0;
870 
871   HMODULE hLib = ::LoadLibraryA("kernel32.dll");
872   if (NULL != hLib)
873   {
874     BOOL (WINAPI *pfn)(LPMEMORYSTATUSEX) = (BOOL (WINAPI*)(LPMEMORYSTATUSEX))::GetProcAddress(hLib, "GlobalMemoryStatusEx");
875 
876     if (NULL != pfn)
877     {
878       MEMORYSTATUSEX mse;
879       mse.dwLength = sizeof(mse);
880       if (pfn(&mse))
881       {
882 	TotalPhysicalMemory = mse.ullTotalPhys;
883       }
884     }
885 
886     ::FreeLibrary(hLib);
887   }
888 
889   if (TotalPhysicalMemory == 0)
890   {
891     MEMORYSTATUS ms;
892     ::ZeroMemory(&ms, sizeof(ms));
893     ::GlobalMemoryStatus(&ms);
894 
895     TotalPhysicalMemory = ms.dwTotalPhys;
896   }
897 
898   return TotalPhysicalMemory;
899 }
900 #elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE)
901 // POSIX compliant OSes, including OSX/MacOS and Cygwin.  Also works for Linux.
GetTotalPhysicalMemory()902 u64 CommandLine::GetTotalPhysicalMemory()
903 {
904   long pages = sysconf(_SC_PHYS_PAGES);
905   long page_size = sysconf(_SC_PAGESIZE);
906   return pages*page_size;
907 }
908 #else
909 // default version == unable to request memory size
GetTotalPhysicalMemory()910 u64 CommandLine::GetTotalPhysicalMemory()
911 {
912   return 0;
913 }
914 #endif
915 
916 
CheckValuesAndSetDefaults()917 bool CommandLine::CheckValuesAndSetDefaults() {
918   if (parfilename.length() == 0)
919   {
920     cerr << "You must specify a Recovery file." << endl;
921     return false;
922   }
923 
924 
925   // Default noise level
926   if (noiselevel == nlUnknown)
927   {
928     noiselevel = nlNormal;
929   }
930 
931   // Default memorylimit of 128MB
932   if (memorylimit == 0)
933   {
934     u64 TotalPhysicalMemory = GetTotalPhysicalMemory();
935 
936     if (TotalPhysicalMemory == 0)
937     {
938       // Default/error case:
939       // NOTE: In 2019, Ubuntu's minimum requirements are 256MiB.
940       TotalPhysicalMemory = 256 * 1048576;
941     }
942 
943     // Half of total physical memory
944     memorylimit = (size_t)(TotalPhysicalMemory / 1048576 / 2);
945   }
946   // convert to megabytes
947   memorylimit *= 1048576;
948 
949   if (noiselevel >= nlDebug)
950   {
951     cout << "[DEBUG] memorylimit: " << memorylimit << " bytes" << endl;
952   }
953 
954 
955   // Default basepath  (uses parfilename)
956   if ("" == basepath)
957   {
958     if (noiselevel >= nlDebug)
959     {
960       cout << "[DEBUG] parfilename: " << parfilename << endl;
961     }
962 
963     string dummy;
964     string path;
965     DiskFile::SplitFilename(parfilename, path, dummy);
966     basepath = DiskFile::GetCanonicalPathname(path);
967 
968     // fallback
969     if ("" == basepath)
970     {
971       basepath = DiskFile::GetCanonicalPathname("./");
972     }
973   }
974 
975   string lastchar = basepath.substr(basepath.length() -1);
976   if ("/" != lastchar && "\\" != lastchar)
977   {
978 #ifdef _WIN32
979     basepath = basepath + "\\";
980 #else
981     basepath = basepath + "/";
982 #endif
983   }
984 
985   if (noiselevel >= nlDebug)
986   {
987     cout << "[DEBUG] basepath: " << basepath << endl;
988   }
989 
990 
991   // parfilename is checked earlier, because it is used by basepath.
992 
993 
994   // check extrafiles
995   list<string>::iterator rawfilenames_fn;
996   for (rawfilenames_fn = rawfilenames.begin(); rawfilenames_fn != rawfilenames.end(); ++rawfilenames_fn)
997   {
998     string filename = *rawfilenames_fn;
999 
1000     // Originally, all specified files were supposed to exist, or the program
1001     // would stop with an error message. This was not practical, for example in
1002     // a directory with files appearing and disappearing (an active download directory).
1003     // So the new rule is: when a specified file doesn't exist, it is silently skipped.
1004     if (!DiskFile::FileExists(filename))
1005     {
1006       cout << "Ignoring non-existent source file: " << filename << endl;
1007     }
1008     // skip files outside basepath
1009     else if (filename.find(basepath) == string::npos)
1010     {
1011       cout << "Ignoring out of basepath source file: " << filename << endl;
1012     }
1013     else
1014     {
1015       u64 filesize = filesize_cache.get(filename);
1016 
1017       // Ignore all 0 byte files
1018       if (filesize == 0)
1019       {
1020         cout << "Skipping 0 byte file: " << filename << endl;
1021       }
1022       else if (extrafiles.end() != find(extrafiles.begin(), extrafiles.end(), filename))
1023       {
1024         cout << "Skipping duplicate filename: " << filename << endl;
1025       }
1026       else
1027       {
1028         extrafiles.push_back(filename);
1029       }
1030     } //end file exists
1031   }
1032 
1033 
1034   // operation should alway be set, but let's be thorough.
1035   if (operation == opNone) {
1036     cerr << "ERROR: No operation was specified (create, repair, or verify)" << endl;
1037     return false;
1038   }
1039 
1040 
1041   if (operation != opCreate) {
1042     // skipdata is bool and either value is valid.
1043 
1044     // Default skip leaway
1045     if (skipdata && skipleaway == 0)
1046     {
1047       // Expect to find blocks within +/- 64 bytes of the expected
1048       // position relative to the last block that was found.
1049       skipleaway = 64;
1050     }
1051   }
1052 
1053   // If we a creating, check the other parameters
1054   if (operation == opCreate)
1055   {
1056     // If we are creating, the source files must be given.
1057     if (extrafiles.size() == 0)
1058     {
1059       // Does the par filename include the ".par2" on the end?
1060       if (parfilename.length() > 5 && 0 == stricmp(parfilename.substr(parfilename.length()-5, 5).c_str(), ".par2"))
1061       {
1062         // Yes it does.
1063         cerr << "You must specify a list of files when creating." << endl;
1064         return false;
1065       }
1066       else
1067       {
1068         // No it does not.
1069 
1070         // In that case check to see if the file exists, and if it does
1071         // assume that you wish to create par2 files for it.
1072 
1073         u64 filesize = 0;
1074         if (DiskFile::FileExists(parfilename) &&
1075             (filesize = DiskFile::GetFileSize(parfilename)) > 0)
1076         {
1077           extrafiles.push_back(parfilename);
1078         }
1079         else
1080         {
1081           // The file does not exist or it is empty.
1082 
1083           cerr << "You must specify a list of files when creating." << endl;
1084           return false;
1085         }
1086       }
1087     }
1088 
1089     // Strip the ".par2" from the end of the filename of the main PAR2 file.
1090     if (parfilename.length() > 5 && 0 == stricmp(parfilename.substr(parfilename.length()-5, 5).c_str(), ".par2"))
1091     {
1092       parfilename = parfilename.substr(0, parfilename.length()-5);
1093     }
1094 
1095     // If neither block count not block size is specified
1096     if (blockcount == 0 && blocksize == 0)
1097     {
1098       // Use a block count of 2000
1099       blockcount = 2000;
1100     }
1101 
1102     // If no recovery file size scheme is specified then use Variable
1103     if (recoveryfilescheme == scUnknown)
1104     {
1105       recoveryfilescheme = scVariable;
1106     }
1107 
1108     // Assume a redundancy of 5% if neither redundancy or recoveryblockcount were set.
1109     if (!redundancyset && !recoveryblockcountset)
1110     {
1111       redundancy = 5;
1112       redundancyset = true;
1113     }
1114   }
1115 
1116 
1117   return true;
1118 }
1119 
1120 
ComputeBlockSize()1121 bool CommandLine::ComputeBlockSize() {
1122 
1123   if (blocksize == 0) {
1124     // compute value from blockcount
1125 
1126     if (blockcount < extrafiles.size())
1127     {
1128       // The block count cannot be less than the number of files.
1129 
1130       cerr << "Block count (" << blockcount <<
1131               ") cannot be smaller than the number of files(" << extrafiles.size() << "). " << endl;
1132       return false;
1133     }
1134     else if (blockcount == extrafiles.size())
1135     {
1136       // If the block count is the same as the number of files, then the block
1137       // size is the size of the largest file (rounded up to a multiple of 4).
1138 
1139       u64 largestfilesize = 0;
1140       for (vector<string>::const_iterator i=extrafiles.begin(); i!=extrafiles.end(); i++)
1141       {
1142 	u64 filesize = filesize_cache.get(*i);
1143 	if (filesize > largestfilesize)
1144 	{
1145 	  largestfilesize = filesize;
1146 	}
1147       }
1148       blocksize = (largestfilesize + 3) & ~3;
1149     }
1150     else
1151     {
1152       u64 totalsize = 0;
1153       for (vector<string>::const_iterator i=extrafiles.begin(); i!=extrafiles.end(); i++)
1154       {
1155         totalsize += (filesize_cache.get(*i) + 3) / 4;
1156       }
1157 
1158       if (blockcount > totalsize)
1159       {
1160         blocksize = 4;
1161       }
1162       else
1163       {
1164         // Absolute lower bound and upper bound on the source block size that will
1165         // result in the requested source block count.
1166         u64 lowerBound = totalsize / blockcount;
1167         u64 upperBound = (totalsize + blockcount - extrafiles.size() - 1) / (blockcount - extrafiles.size());
1168 
1169         u64 count = 0;
1170         u64 size;
1171 
1172         do
1173         {
1174           size = (lowerBound + upperBound)/2;
1175 
1176           count = 0;
1177           for (vector<string>::const_iterator i=extrafiles.begin(); i!=extrafiles.end(); i++)
1178           {
1179             count += ((filesize_cache.get(*i)+3)/4 + size-1) / size;
1180           }
1181           if (count > blockcount)
1182           {
1183             lowerBound = size+1;
1184             if (lowerBound >= upperBound)
1185             {
1186               size = lowerBound;
1187               count = 0;
1188               for (vector<string>::const_iterator i=extrafiles.begin(); i!=extrafiles.end(); i++)
1189               {
1190                 count += ((filesize_cache.get(*i)+3)/4 + size-1) / size;
1191               }
1192             }
1193           }
1194           else
1195           {
1196             upperBound = size;
1197           }
1198         }
1199         while (lowerBound < upperBound);
1200 
1201         if (count > 32768)
1202         {
1203           cerr << "Error calculating block size. cannot be higher than 32768." << endl;
1204           return false;
1205         }
1206         else if (count == 0)
1207         {
1208           cerr << "Error calculating block size. cannot be 0." << endl;
1209           return false;
1210         }
1211 
1212         blocksize = size*4;
1213       }
1214     }
1215   }
1216 
1217   return true;
1218 }
1219 
1220 
1221 // Determine how many recovery blocks to create based on the source block
1222 // count and the requested level of redundancy.
ComputeRecoveryBlockCount(u32 * recoveryblockcount,u32 sourceblockcount,u64 blocksize,u32 firstblock,Scheme recoveryfilescheme,u32 recoveryfilecount,bool recoveryblockcountset,u32 redundancy,u64 redundancysize,u64 largestfilesize)1223 bool CommandLine::ComputeRecoveryBlockCount(u32 *recoveryblockcount,
1224 					    u32 sourceblockcount,
1225 					    u64 blocksize,
1226 					    u32 firstblock,
1227 					    Scheme recoveryfilescheme,
1228 					    u32 recoveryfilecount,
1229 					    bool recoveryblockcountset,
1230 					    u32 redundancy,
1231 					    u64 redundancysize,
1232 					    u64 largestfilesize)
1233 {
1234   if (recoveryblockcountset) {
1235     // no need to assign value.
1236     // pass through, so that value can be checked below.
1237   }
1238   else if (redundancy > 0)
1239   {
1240     // count is the number of input blocks
1241 
1242     // Determine recoveryblockcount
1243     *recoveryblockcount = (sourceblockcount * redundancy + 50) / 100;
1244   }
1245   else if (redundancysize > 0)
1246   {
1247     const u64 overhead_per_recovery_file = sourceblockcount * (u64) 21;
1248     const u64 recovery_packet_size = blocksize + (u64) 70;
1249     if (recoveryfilecount == 0)
1250     {
1251       u32 estimatedFileCount = 15;
1252       u64 overhead = estimatedFileCount * overhead_per_recovery_file;
1253       u64 estimatedrecoveryblockcount;
1254       if (overhead > redundancysize)
1255       {
1256         estimatedrecoveryblockcount = 1;  // at least 1
1257       }
1258       else
1259       {
1260 	estimatedrecoveryblockcount = (u32)((redundancysize - overhead) / recovery_packet_size);
1261       }
1262 
1263       // recoveryfilecount assigned below.
1264       bool success = ComputeRecoveryFileCount(cout,
1265 					      cerr,
1266 					      &recoveryfilecount,
1267 					      recoveryfilescheme,
1268 					      estimatedrecoveryblockcount,
1269 					      largestfilesize,
1270 					      blocksize);
1271       if (!success) {
1272 	return false;
1273       }
1274     }
1275 
1276     const u64 overhead = recoveryfilecount * overhead_per_recovery_file;
1277     if (overhead > redundancysize)
1278     {
1279       *recoveryblockcount = 1;  // at least 1
1280     }
1281     else
1282     {
1283       *recoveryblockcount = (u32)((redundancysize - overhead) / recovery_packet_size);
1284     }
1285   }
1286   else
1287   {
1288     cerr << "Redundancy and Redundancysize not set." << endl;
1289     return false;
1290   }
1291 
1292   // Force valid values if necessary
1293   if (*recoveryblockcount == 0 && redundancy > 0)
1294     *recoveryblockcount = 1;
1295 
1296   if (*recoveryblockcount > 65536)
1297   {
1298     cerr << "Too many recovery blocks requested." << endl;
1299     return false;
1300   }
1301 
1302   // Check that the last recovery block number would not be too large
1303   if (firstblock + *recoveryblockcount >= 65536)
1304   {
1305     cerr << "First recovery block number is too high." << endl;
1306     return false;
1307   }
1308 
1309   cout << endl;
1310   return true;
1311 }
1312 
1313 
1314 
1315 
1316 
SetParFilename(string filename)1317 bool CommandLine::SetParFilename(string filename)
1318 {
1319   bool result = false;
1320   string::size_type where;
1321 
1322   if ((where = filename.find_first_of('*')) != string::npos ||
1323       (where = filename.find_first_of('?')) != string::npos)
1324   {
1325     cerr << "par2 file must not have a wildcard in it." << endl;
1326     return result;
1327   }
1328 
1329   // If we are verifying or repairing, the PAR2 file must
1330   // already exist
1331   if (operation != opCreate)
1332   {
1333     // Find the last '.' in the filename
1334     string::size_type where = filename.find_last_of('.');
1335     if (where != string::npos)
1336     {
1337       // Get what follows the last '.'
1338       string tail = filename.substr(where+1);
1339 
1340       if (0 == stricmp(tail.c_str(), "par2"))
1341       {
1342         parfilename = filename;
1343         version = verPar2;
1344       }
1345       else if (0 == stricmp(tail.c_str(), "par") ||
1346                (tail.size() == 3 &&
1347                tolower(tail[0]) == 'p' &&
1348                isdigit(tail[1]) &&
1349                isdigit(tail[2])))
1350       {
1351         parfilename = filename;
1352         version = verPar1;
1353       }
1354 
1355       if (DiskFile::FileExists(filename)) {
1356         result = true;
1357       }
1358     }
1359 
1360     // If we haven't figured out which version of PAR file we
1361     // are using from the file extension, then presumable the
1362     // files filename was actually the name of a data file.
1363     if (version == verUnknown)
1364     {
1365       // Check for the existence of a PAR2 of PAR file.
1366       if (DiskFile::FileExists(filename + ".par2"))
1367       {
1368         version = verPar2;
1369         parfilename = filename + ".par2";
1370         result = true;
1371       }
1372       else if (DiskFile::FileExists(filename + ".PAR2"))
1373       {
1374         version = verPar2;
1375         parfilename = filename + ".PAR2";
1376         result = true;
1377       }
1378       else if (DiskFile::FileExists(filename + ".par"))
1379       {
1380         version = verPar1;
1381         parfilename = filename + ".par";
1382         result = true;
1383       }
1384       else if (DiskFile::FileExists(filename + ".PAR"))
1385       {
1386         version = verPar1;
1387         parfilename = filename + ".PAR";
1388         result = true;
1389       }
1390     }
1391   }
1392   else
1393   {
1394     parfilename = DiskFile::GetCanonicalPathname(filename);
1395     version = verPar2;
1396     result = true;
1397   }
1398 
1399   return result;
1400 }
1401