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