1 /*
2 LZ4cli - LZ4 Command Line Interface
3 Copyright (C) Yann Collet 2011-2023
4
5 GPL v2 License
6
7 This program 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 This program 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 along
18 with this program; if not, write to the Free Software Foundation, Inc.,
19 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21 You can contact the author at :
22 - LZ4 source repository : https://github.com/lz4/lz4
23 - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c
24 */
25 /*
26 Note : this is stand-alone program.
27 It is not part of LZ4 compression library, it is a user program of the LZ4 library.
28 The license of LZ4 library is BSD.
29 The license of xxHash library is BSD.
30 The license of this compression CLI program is GPLv2.
31 */
32
33
34 /*-************************************
35 * Compiler options
36 **************************************/
37 #ifdef _MSC_VER /* Visual Studio */
38 # pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */
39 #endif
40
41
42 /****************************
43 * Includes
44 *****************************/
45 #include "platform.h" /* Compiler options, IS_CONSOLE */
46 #include "util.h" /* UTIL_HAS_CREATEFILELIST, UTIL_createFileList */
47 #include <stdio.h> /* fprintf, getchar */
48 #include <stdlib.h> /* exit, calloc, free */
49 #include <string.h> /* strcmp, strlen */
50 #include "lz4conf.h" /* compile-time constants */
51 #include "bench.h" /* BMK_benchFile, BMK_SetNbIterations, BMK_SetBlocksize, BMK_SetPause */
52 #include "lz4io.h" /* LZ4IO_compressFilename, LZ4IO_decompressFilename, LZ4IO_compressMultipleFilenames */
53 #include "lz4hc.h" /* LZ4HC_CLEVEL_MAX */
54 #include "lz4.h" /* LZ4_VERSION_STRING */
55
56
57 /*****************************
58 * Constants
59 ******************************/
60 #if LZ4IO_MULTITHREAD
61 # define IO_MT "multithread"
62 #else
63 # define IO_MT "single-thread"
64 #endif
65 #define COMPRESSOR_NAME "lz4"
66 #define AUTHOR "Yann Collet"
67 #define WELCOME_MESSAGE "*** %s v%s %i-bit %s, by %s ***\n", COMPRESSOR_NAME, LZ4_versionString(), (int)(sizeof(void*)*8), IO_MT, AUTHOR
68 #define LZ4_EXTENSION ".lz4"
69 #define LZ4CAT "lz4cat"
70 #define UNLZ4 "unlz4"
71 #define LZ4_LEGACY "lz4c"
72 static int g_lz4c_legacy_commands = 0;
73
74 #define KB *(1U<<10)
75 #define MB *(1U<<20)
76 #define GB *(1U<<30)
77
78
79 /*-************************************
80 * Macros
81 ***************************************/
82 #define DISPLAYOUT(...) fprintf(stdout, __VA_ARGS__)
83 #define DISPLAY(...) fprintf(stderr, __VA_ARGS__)
84 #define DISPLAYLEVEL(l, ...) do { if (displayLevel>=l) DISPLAY(__VA_ARGS__); } while (0)
85 static unsigned displayLevel = 2; /* 0 : no display ; 1: errors only ; 2 : downgradable normal ; 3 : non-downgradable normal; 4 : + information */
86
87
88 /*-************************************
89 * Errors and Messages
90 ***************************************/
91 #ifndef DEBUG
92 # define DEBUG 0
93 #endif
94 #define DEBUGOUTPUT(...) do { if (DEBUG) DISPLAY(__VA_ARGS__); } while (0)
95 #define END_PROCESS(error, ...) \
96 do { \
97 DEBUGOUTPUT("Error in %s, line %i : \n", __FILE__, __LINE__); \
98 DISPLAYLEVEL(1, "Error %i : ", error); \
99 DISPLAYLEVEL(1, __VA_ARGS__); \
100 DISPLAYLEVEL(1, "\n"); \
101 exit(error); \
102 } while (0)
103
errorOut(const char * msg)104 static void errorOut(const char* msg)
105 {
106 DISPLAYLEVEL(1, "%s \n", msg); exit(1);
107 }
108
109
110 /*-************************************
111 * Version modifiers
112 ***************************************/
113 #define DEFAULT_COMPRESSOR LZ4IO_compressFilename
114 #define DEFAULT_DECOMPRESSOR LZ4IO_decompressFilename
115 int LZ4IO_compressFilename_Legacy(const char* input_filename, const char* output_filename, int compressionlevel, const LZ4IO_prefs_t* prefs); /* hidden function */
116 int LZ4IO_compressMultipleFilenames_Legacy(
117 const char** inFileNamesTable, int ifntSize,
118 const char* suffix,
119 int compressionLevel, const LZ4IO_prefs_t* prefs);
120
121 /*-***************************
122 * Functions
123 *****************************/
usage(const char * exeName)124 static int usage(const char* exeName)
125 {
126 DISPLAY( "Usage : \n");
127 DISPLAY( " %s [arg] [input] [output] \n", exeName);
128 DISPLAY( "\n");
129 DISPLAY( "input : a filename \n");
130 DISPLAY( " with no FILE, or when FILE is - or %s, read standard input\n", stdinmark);
131 DISPLAY( "Arguments : \n");
132 DISPLAY( " -1 : fast compression (default) \n");
133 DISPLAY( " -%2d : slowest compression level \n", LZ4HC_CLEVEL_MAX);
134 DISPLAY( " -T# : use # threads for compression (default:%i==auto) \n", LZ4_NBWORKERS_DEFAULT);
135 DISPLAY( " -d : decompression (default for %s extension)\n", LZ4_EXTENSION);
136 DISPLAY( " -f : overwrite output without prompting \n");
137 DISPLAY( " -k : preserve source files(s) (default) \n");
138 DISPLAY( "--rm : remove source file(s) after successful de/compression \n");
139 DISPLAY( " -h/-H : display help/long help and exit \n");
140 return 0;
141 }
142
usage_advanced(const char * exeName)143 static int usage_advanced(const char* exeName)
144 {
145 DISPLAY(WELCOME_MESSAGE);
146 usage(exeName);
147 DISPLAY( "\n");
148 DISPLAY( "Advanced arguments :\n");
149 DISPLAY( " -V : display Version number and exit \n");
150 DISPLAY( " -v : verbose mode \n");
151 DISPLAY( " -q : suppress warnings; specify twice to suppress errors too\n");
152 DISPLAY( " -c : force write to standard output, even if it is the console\n");
153 DISPLAY( " -t : test compressed file integrity\n");
154 DISPLAY( " -m : multiple input files (implies automatic output filenames)\n");
155 #ifdef UTIL_HAS_CREATEFILELIST
156 DISPLAY( " -r : operate recursively on directories (sets also -m) \n");
157 #endif
158 DISPLAY( " -l : compress using Legacy format (Linux kernel compression)\n");
159 DISPLAY( " -z : force compression \n");
160 DISPLAY( " -D FILE: use FILE as dictionary (compression & decompression)\n");
161 DISPLAY( " -B# : cut file into blocks of size # bytes [32+] \n");
162 DISPLAY( " or predefined block size [4-7] (default: %i) \n", LZ4_BLOCKSIZEID_DEFAULT);
163 DISPLAY( " -BI : Block Independence (default) \n");
164 DISPLAY( " -BD : Block dependency (improves compression ratio) \n");
165 DISPLAY( " -BX : enable block checksum (default:disabled) \n");
166 DISPLAY( "--no-frame-crc : disable stream checksum (default:enabled) \n");
167 DISPLAY( "--content-size : compressed frame includes original size (default:not present)\n");
168 DISPLAY( "--list FILE : lists information about .lz4 files (useful for files compressed with --content-size flag)\n");
169 DISPLAY( "--[no-]sparse : sparse mode (default:enabled on file, disabled on stdout)\n");
170 DISPLAY( "--favor-decSpeed: compressed files decompress faster, but are less compressed \n");
171 DISPLAY( "--fast[=#]: switch to ultra fast compression level (default: %i)\n", 1);
172 DISPLAY( "--best : same as -%d\n", LZ4HC_CLEVEL_MAX);
173 DISPLAY( "Benchmark arguments : \n");
174 DISPLAY( " -b# : benchmark file(s), using # compression level (default : 1) \n");
175 DISPLAY( " -e# : test all compression levels from -bX to # (default : 1)\n");
176 DISPLAY( " -i# : minimum evaluation time in seconds (default : 3s) \n");
177 if (g_lz4c_legacy_commands) {
178 DISPLAY( "Legacy arguments : \n");
179 DISPLAY( " -c0 : fast compression \n");
180 DISPLAY( " -c1 : high compression \n");
181 DISPLAY( " -c2,-hc: very high compression \n");
182 DISPLAY( " -y : overwrite output without prompting \n");
183 }
184 return 0;
185 }
186
usage_longhelp(const char * exeName)187 static int usage_longhelp(const char* exeName)
188 {
189 usage_advanced(exeName);
190 DISPLAY( "\n");
191 DISPLAY( "****************************\n");
192 DISPLAY( "***** Advanced comment *****\n");
193 DISPLAY( "****************************\n");
194 DISPLAY( "\n");
195 DISPLAY( "Which values can [output] have ? \n");
196 DISPLAY( "---------------------------------\n");
197 DISPLAY( "[output] : a filename \n");
198 DISPLAY( " '%s', or '-' for standard output (pipe mode)\n", stdoutmark);
199 DISPLAY( " '%s' to discard output (test mode) \n", NULL_OUTPUT);
200 DISPLAY( "[output] can be left empty. In this case, it receives the following value :\n");
201 DISPLAY( " - if stdout is not the console, then [output] = stdout \n");
202 DISPLAY( " - if stdout is console : \n");
203 DISPLAY( " + for compression, output to filename%s \n", LZ4_EXTENSION);
204 DISPLAY( " + for decompression, output to filename without '%s'\n", LZ4_EXTENSION);
205 DISPLAY( " > if input filename has no '%s' extension : error \n", LZ4_EXTENSION);
206 DISPLAY( "\n");
207 DISPLAY( "Compression levels : \n");
208 DISPLAY( "---------------------\n");
209 DISPLAY( "-0 ... -2 => Fast compression, all identical\n");
210 DISPLAY( "-3 ... -%d => High compression; higher number == more compression but slower\n", LZ4HC_CLEVEL_MAX);
211 DISPLAY( "\n");
212 DISPLAY( "stdin, stdout and the console : \n");
213 DISPLAY( "--------------------------------\n");
214 DISPLAY( "To protect the console from binary flooding (bad argument mistake)\n");
215 DISPLAY( "%s will refuse to read from console, or write to console \n", exeName);
216 DISPLAY( "except if '-c' command is specified, to force output to console \n");
217 DISPLAY( "\n");
218 DISPLAY( "Simple example :\n");
219 DISPLAY( "----------------\n");
220 DISPLAY( "1 : compress 'filename' fast, using default output name 'filename.lz4'\n");
221 DISPLAY( " %s filename\n", exeName);
222 DISPLAY( "\n");
223 DISPLAY( "Short arguments can be aggregated. For example :\n");
224 DISPLAY( "----------------------------------\n");
225 DISPLAY( "2 : compress 'filename' in high compression mode, overwrite output if exists\n");
226 DISPLAY( " %s -9 -f filename \n", exeName);
227 DISPLAY( " is equivalent to :\n");
228 DISPLAY( " %s -9f filename \n", exeName);
229 DISPLAY( "\n");
230 DISPLAY( "%s can be used in 'pure pipe mode'. For example :\n", exeName);
231 DISPLAY( "-------------------------------------\n");
232 DISPLAY( "3 : compress data stream from 'generator', send result to 'consumer'\n");
233 DISPLAY( " generator | %s | consumer \n", exeName);
234 if (g_lz4c_legacy_commands) {
235 DISPLAY( "\n");
236 DISPLAY( "***** Warning ***** \n");
237 DISPLAY( "Legacy arguments take precedence. Therefore : \n");
238 DISPLAY( "--------------------------------- \n");
239 DISPLAY( " %s -hc filename \n", exeName);
240 DISPLAY( "means 'compress filename in high compression mode' \n");
241 DISPLAY( "It is not equivalent to : \n");
242 DISPLAY( " %s -h -c filename \n", exeName);
243 DISPLAY( "which displays help text and exits \n");
244 }
245 return 0;
246 }
247
badusage(const char * exeName)248 static int badusage(const char* exeName)
249 {
250 DISPLAYLEVEL(1, "Incorrect parameters\n");
251 if (displayLevel >= 1) usage(exeName);
252 exit(1);
253 }
254
255
waitEnter(void)256 static void waitEnter(void)
257 {
258 DISPLAY("Press enter to continue...\n");
259 (void)getchar();
260 }
261
lastNameFromPath(const char * path)262 static const char* lastNameFromPath(const char* path)
263 {
264 const char* name = path;
265 if (strrchr(name, '/')) name = strrchr(name, '/') + 1;
266 if (strrchr(name, '\\')) name = strrchr(name, '\\') + 1; /* windows */
267 return name;
268 }
269
270 /*! exeNameMatch() :
271 @return : a non-zero value if exeName matches test, excluding the extension
272 */
exeNameMatch(const char * exeName,const char * test)273 static int exeNameMatch(const char* exeName, const char* test)
274 {
275 return !strncmp(exeName, test, strlen(test)) &&
276 (exeName[strlen(test)] == '\0' || exeName[strlen(test)] == '.');
277 }
278
279 /*! readU32FromChar() :
280 * @return : unsigned integer value read from input in `char` format
281 * allows and interprets K, KB, KiB, M, MB and MiB suffix.
282 * Will also modify `*stringPtr`, advancing it to position where it stopped reading.
283 * Note : function result can overflow if digit string > MAX_UINT */
readU32FromChar(const char ** stringPtr)284 static unsigned readU32FromChar(const char** stringPtr)
285 {
286 unsigned result = 0;
287 while ((**stringPtr >='0') && (**stringPtr <='9')) {
288 result *= 10;
289 result += (unsigned)(**stringPtr - '0');
290 (*stringPtr)++ ;
291 }
292 if ((**stringPtr=='K') || (**stringPtr=='M')) {
293 result <<= 10;
294 if (**stringPtr=='M') result <<= 10;
295 (*stringPtr)++ ;
296 if (**stringPtr=='i') (*stringPtr)++;
297 if (**stringPtr=='B') (*stringPtr)++;
298 }
299 return result;
300 }
301
302 #define CLEAN_RETURN(i) { operationResult = (i); goto _cleanup; }
303
304 #define NEXT_FIELD(ptr) { \
305 if (*argument == '=') { \
306 ptr = ++argument; \
307 argument += strlen(ptr); \
308 } else { \
309 argNb++; \
310 if (argNb >= argCount) { \
311 DISPLAYLEVEL(1, "error: missing command argument \n"); \
312 CLEAN_RETURN(1); \
313 } \
314 ptr = argv[argNb]; \
315 assert(ptr != NULL); \
316 if (ptr[0]=='-') { \
317 DISPLAYLEVEL(1, "error: command cannot be separated from its argument by another command \n"); \
318 CLEAN_RETURN(1); \
319 } } }
320
321 #define NEXT_UINT32(val32) { \
322 const char* __nb; \
323 NEXT_FIELD(__nb); \
324 val32 = readU32FromChar(&__nb); \
325 if(*__nb != 0) { \
326 errorOut("error: only numeric values with optional suffixes K, KB, KiB, M, MB, MiB are allowed"); \
327 } \
328 }
329
330 /** longCommandWArg() :
331 * check if *stringPtr is the same as longCommand.
332 * If yes, @return 1 and advances *stringPtr to the position which immediately follows longCommand.
333 * @return 0 and doesn't modify *stringPtr otherwise.
334 */
longCommandWArg(const char ** stringPtr,const char * longCommand)335 static int longCommandWArg(const char** stringPtr, const char* longCommand)
336 {
337 size_t const comSize = strlen(longCommand);
338 int const result = !strncmp(*stringPtr, longCommand, comSize);
339 if (result) *stringPtr += comSize;
340 return result;
341 }
342
343 typedef enum { om_auto, om_compress, om_decompress, om_test, om_bench, om_list } operationMode_e;
344
345 /** determineOpMode() :
346 * auto-determine operation mode, based on input filename extension
347 * @return `om_decompress` if input filename has .lz4 extension and `om_compress` otherwise.
348 */
determineOpMode(const char * inputFilename)349 static operationMode_e determineOpMode(const char* inputFilename)
350 {
351 size_t const inSize = strlen(inputFilename);
352 size_t const extSize = strlen(LZ4_EXTENSION);
353 size_t const extStart= (inSize > extSize) ? inSize-extSize : 0;
354 if (!strcmp(inputFilename+extStart, LZ4_EXTENSION)) return om_decompress;
355 else return om_compress;
356 }
357
358 #define ENV_NBTHREADS "LZ4_NBWORKERS"
359
init_nbWorkers(void)360 static unsigned init_nbWorkers(void)
361 {
362 const char* const env = getenv(ENV_NBTHREADS);
363 if (env != NULL) {
364 const char* ptr = env;
365 if ((*ptr>='0') && (*ptr<='9')) {
366 return readU32FromChar(&ptr);
367 }
368 DISPLAYLEVEL(2, "Ignore environment variable setting %s=%s: not a valid unsigned value \n", ENV_NBTHREADS, env);
369 }
370 return LZ4_NBWORKERS_DEFAULT;
371 }
372
373 #define ENV_CLEVEL "LZ4_CLEVEL"
374
init_cLevel(void)375 static int init_cLevel(void)
376 {
377 const char* const env = getenv(ENV_CLEVEL);
378 if (env != NULL) {
379 const char* ptr = env;
380 if ((*ptr>='0') && (*ptr<='9')) {
381 return (int)readU32FromChar(&ptr);
382 }
383 DISPLAYLEVEL(2, "Ignore environment variable setting %s=%s: not a valid unsigned value \n", ENV_CLEVEL, env);
384 }
385 return LZ4_CLEVEL_DEFAULT;
386 }
387
main(int argCount,const char ** argv)388 int main(int argCount, const char** argv)
389 {
390 int argNb,
391 cLevel=init_cLevel(),
392 cLevelLast=-10000,
393 legacy_format=0,
394 forceStdout=0,
395 forceOverwrite=0,
396 main_pause=0,
397 multiple_inputs=0,
398 all_arguments_are_files=0,
399 operationResult=0;
400 unsigned nbWorkers = init_nbWorkers();
401 operationMode_e mode = om_auto;
402 const char* input_filename = NULL;
403 const char* output_filename= NULL;
404 const char* dictionary_filename = NULL;
405 char* dynNameSpace = NULL;
406 const char** inFileNames = (const char**)calloc((size_t)argCount, sizeof(char*));
407 unsigned ifnIdx=0;
408 LZ4IO_prefs_t* const prefs = LZ4IO_defaultPreferences();
409 const char nullOutput[] = NULL_OUTPUT;
410 const char extension[] = LZ4_EXTENSION;
411 size_t blockSize = LZ4IO_setBlockSizeID(prefs, LZ4_BLOCKSIZEID_DEFAULT);
412 const char* const exeName = lastNameFromPath(argv[0]);
413 char* fileNamesBuf = NULL;
414 #ifdef UTIL_HAS_CREATEFILELIST
415 unsigned fileNamesNb, recursive=0;
416 #endif
417
418 /* Init */
419 if (inFileNames==NULL) {
420 DISPLAY("Allocation error : not enough memory \n");
421 operationResult = 1;
422 goto _cleanup;
423 }
424 inFileNames[0] = stdinmark;
425 LZ4IO_setOverwrite(prefs, 0);
426
427 /* predefined behaviors, based on binary/link name */
428 if (exeNameMatch(exeName, LZ4CAT)) {
429 mode = om_decompress;
430 LZ4IO_setOverwrite(prefs, 1);
431 LZ4IO_setPassThrough(prefs, 1);
432 LZ4IO_setRemoveSrcFile(prefs, 0);
433 forceStdout=1;
434 output_filename=stdoutmark;
435 displayLevel=1;
436 multiple_inputs=1;
437 }
438 if (exeNameMatch(exeName, UNLZ4)) { mode = om_decompress; }
439 if (exeNameMatch(exeName, LZ4_LEGACY)) { g_lz4c_legacy_commands=1; }
440
441 /* command switches */
442 for(argNb=1; argNb<argCount; argNb++) {
443 const char* argument = argv[argNb];
444
445 if(!argument) continue; /* Protection if argument empty */
446
447 /* Short commands (note : aggregated short commands are allowed) */
448 if (!all_arguments_are_files && argument[0]=='-') {
449 /* '-' means stdin/stdout */
450 if (argument[1]==0) {
451 if (!input_filename) input_filename=stdinmark;
452 else output_filename=stdoutmark;
453 continue;
454 }
455
456 /* long commands (--long-word) */
457 if (argument[1]=='-') {
458 if (!strcmp(argument, "--")) { all_arguments_are_files = 1; continue; }
459 if (!strcmp(argument, "--compress")) { mode = om_compress; continue; }
460 if ( (!strcmp(argument, "--decompress"))
461 || (!strcmp(argument, "--uncompress"))) {
462 if (mode != om_bench) mode = om_decompress;
463 BMK_setDecodeOnlyMode(1);
464 continue;
465 }
466 if (!strcmp(argument, "--multiple")) { multiple_inputs = 1; continue; }
467 if (!strcmp(argument, "--test")) { mode = om_test; continue; }
468 if (!strcmp(argument, "--force")) { LZ4IO_setOverwrite(prefs, 1); continue; }
469 if (!strcmp(argument, "--no-force")) { LZ4IO_setOverwrite(prefs, 0); continue; }
470 if ((!strcmp(argument, "--stdout"))
471 || (!strcmp(argument, "--to-stdout"))) { forceStdout=1; output_filename=stdoutmark; continue; }
472 if (!strcmp(argument, "--frame-crc")) { LZ4IO_setStreamChecksumMode(prefs, 1); BMK_skipChecksums(0); continue; }
473 if (!strcmp(argument, "--no-frame-crc")) { LZ4IO_setStreamChecksumMode(prefs, 0); BMK_skipChecksums(1); continue; }
474 if (!strcmp(argument, "--no-crc")) { LZ4IO_setStreamChecksumMode(prefs, 0); LZ4IO_setBlockChecksumMode(prefs, 0); BMK_skipChecksums(1); continue; }
475 if (!strcmp(argument, "--content-size")) { LZ4IO_setContentSize(prefs, 1); continue; }
476 if (!strcmp(argument, "--no-content-size")) { LZ4IO_setContentSize(prefs, 0); continue; }
477 if (!strcmp(argument, "--list")) { mode = om_list; multiple_inputs = 1; continue; }
478 if (!strcmp(argument, "--sparse")) { LZ4IO_setSparseFile(prefs, 2); continue; }
479 if (!strcmp(argument, "--no-sparse")) { LZ4IO_setSparseFile(prefs, 0); continue; }
480 if (!strcmp(argument, "--favor-decSpeed")) { LZ4IO_favorDecSpeed(prefs, 1); continue; }
481 if (!strcmp(argument, "--verbose")) { displayLevel++; continue; }
482 if (!strcmp(argument, "--quiet")) { if (displayLevel) displayLevel--; continue; }
483 if (!strcmp(argument, "--version")) { DISPLAYOUT(WELCOME_MESSAGE); goto _cleanup; }
484 if (!strcmp(argument, "--help")) { usage_advanced(exeName); goto _cleanup; }
485 if (!strcmp(argument, "--keep")) { LZ4IO_setRemoveSrcFile(prefs, 0); continue; } /* keep source file (default) */
486 if (!strcmp(argument, "--rm")) { LZ4IO_setRemoveSrcFile(prefs, 1); continue; }
487
488 if (longCommandWArg(&argument, "--threads")) {
489 NEXT_UINT32(nbWorkers);
490 continue;
491 }
492 if (longCommandWArg(&argument, "--fast")) {
493 /* Parse optional acceleration factor */
494 if (*argument == '=') {
495 U32 fastLevel;
496 ++argument;
497 fastLevel = readU32FromChar(&argument);
498 if (fastLevel) {
499 cLevel = -(int)fastLevel;
500 } else {
501 badusage(exeName);
502 }
503 } else if (*argument != 0) {
504 /* Invalid character following --fast */
505 badusage(exeName);
506 } else {
507 cLevel = -1; /* default for --fast */
508 }
509 continue;
510 }
511
512 /* For gzip(1) compatibility */
513 if (!strcmp(argument, "--best")) { cLevel=LZ4HC_CLEVEL_MAX; continue; }
514 }
515
516 while (argument[1]!=0) {
517 argument ++;
518
519 if (g_lz4c_legacy_commands) {
520 /* Legacy commands (-c0, -c1, -hc, -y) */
521 if (!strcmp(argument, "c0")) { cLevel=0; argument++; continue; } /* -c0 (fast compression) */
522 if (!strcmp(argument, "c1")) { cLevel=9; argument++; continue; } /* -c1 (high compression) */
523 if (!strcmp(argument, "c2")) { cLevel=12; argument++; continue; } /* -c2 (very high compression) */
524 if (!strcmp(argument, "hc")) { cLevel=12; argument++; continue; } /* -hc (very high compression) */
525 if (!strcmp(argument, "y")) { LZ4IO_setOverwrite(prefs, 1); continue; } /* -y (answer 'yes' to overwrite permission) */
526 }
527
528 if ((*argument>='0') && (*argument<='9')) {
529 cLevel = (int)readU32FromChar(&argument);
530 argument--;
531 continue;
532 }
533
534
535 switch(argument[0])
536 {
537 /* Display help */
538 case 'V': DISPLAYOUT(WELCOME_MESSAGE); goto _cleanup; /* Version */
539 case 'h': usage_advanced(exeName); goto _cleanup;
540 case 'H': usage_longhelp(exeName); goto _cleanup;
541
542 case 'e':
543 argument++;
544 cLevelLast = (int)readU32FromChar(&argument);
545 argument--;
546 break;
547
548 /* Compression (default) */
549 case 'z': mode = om_compress; break;
550
551 /* Modify Nb Worker threads (compression only) */
552 case 'T':
553 { argument++;
554 nbWorkers = readU32FromChar(&argument);
555 argument--;
556 }
557 break;
558
559 case 'D':
560 if (argument[1] == '\0') {
561 /* path is next arg */
562 if (argNb + 1 == argCount) {
563 /* there is no next arg */
564 badusage(exeName);
565 }
566 dictionary_filename = argv[++argNb];
567 } else {
568 /* path follows immediately */
569 dictionary_filename = argument + 1;
570 }
571 /* skip to end of argument so that we jump to parsing next argument */
572 argument += strlen(argument) - 1;
573 break;
574
575 /* Use Legacy format (ex : Linux kernel compression) */
576 case 'l': legacy_format = 1; blockSize = 8 MB; break;
577
578 /* Decoding */
579 case 'd':
580 if (mode != om_bench) mode = om_decompress;
581 BMK_setDecodeOnlyMode(1);
582 break;
583
584 /* Force stdout, even if stdout==console */
585 case 'c':
586 forceStdout=1;
587 output_filename=stdoutmark;
588 LZ4IO_setPassThrough(prefs, 1);
589 break;
590
591 /* Test integrity */
592 case 't': mode = om_test; break;
593
594 /* Overwrite */
595 case 'f': forceOverwrite=1; LZ4IO_setOverwrite(prefs, 1); break;
596
597 /* Verbose mode */
598 case 'v': displayLevel++; break;
599
600 /* Quiet mode */
601 case 'q': if (displayLevel) displayLevel--; break;
602
603 /* keep source file (default anyway, so useless) (for xz/lzma compatibility) */
604 case 'k': LZ4IO_setRemoveSrcFile(prefs, 0); break;
605
606 /* Modify Block Properties */
607 case 'B':
608 while (argument[1]!=0) {
609 int exitBlockProperties=0;
610 switch(argument[1])
611 {
612 case 'D': LZ4IO_setBlockMode(prefs, LZ4IO_blockLinked); argument++; break;
613 case 'I': LZ4IO_setBlockMode(prefs, LZ4IO_blockIndependent); argument++; break;
614 case 'X': LZ4IO_setBlockChecksumMode(prefs, 1); argument ++; break; /* disabled by default */
615 default :
616 if (argument[1] < '0' || argument[1] > '9') {
617 exitBlockProperties=1;
618 break;
619 } else {
620 unsigned B;
621 argument++;
622 B = readU32FromChar(&argument);
623 argument--;
624 if (B < 4) badusage(exeName);
625 if (B <= 7) {
626 blockSize = LZ4IO_setBlockSizeID(prefs, B);
627 BMK_setBlockSize(blockSize);
628 DISPLAYLEVEL(2, "using blocks of size %u KB \n", (U32)(blockSize>>10));
629 } else {
630 if (B < 32) badusage(exeName);
631 blockSize = LZ4IO_setBlockSize(prefs, B);
632 BMK_setBlockSize(blockSize);
633 if (blockSize >= 1024) {
634 DISPLAYLEVEL(2, "using blocks of size %u KB \n", (U32)(blockSize>>10));
635 } else {
636 DISPLAYLEVEL(2, "using blocks of size %u bytes \n", (U32)(blockSize));
637 }
638 }
639 break;
640 }
641 }
642 if (exitBlockProperties) break;
643 }
644 break;
645
646 /* Benchmark */
647 case 'b': mode = om_bench; multiple_inputs=1;
648 break;
649
650 /* hidden command : benchmark files, but do not fuse result */
651 case 'S': BMK_setBenchSeparately(1);
652 break;
653
654 #ifdef UTIL_HAS_CREATEFILELIST
655 /* recursive */
656 case 'r': recursive=1;
657 #endif
658 /* fall-through */
659 /* Treat non-option args as input files. See https://code.google.com/p/lz4/issues/detail?id=151 */
660 case 'm': multiple_inputs=1;
661 break;
662
663 /* Modify Nb Seconds (benchmark only) */
664 case 'i':
665 { unsigned iters;
666 argument++;
667 iters = readU32FromChar(&argument);
668 argument--;
669 BMK_setNotificationLevel(displayLevel);
670 BMK_setNbSeconds(iters); /* notification if displayLevel >= 3 */
671 }
672 break;
673
674 /* Pause at the end (hidden option) */
675 case 'p': main_pause=1; break;
676
677 /* Unrecognised command */
678 default : badusage(exeName);
679 }
680 }
681 continue;
682 }
683
684 /* Store in *inFileNames[] if -m is used. */
685 if (multiple_inputs) { inFileNames[ifnIdx++] = argument; continue; }
686
687 /* original cli logic : lz4 input output */
688 /* First non-option arg is input_filename. */
689 if (!input_filename) { input_filename = argument; continue; }
690
691 /* Second non-option arg is output_filename */
692 if (!output_filename) {
693 output_filename = argument;
694 if (!strcmp (output_filename, nullOutput)) output_filename = nulmark;
695 continue;
696 }
697
698 /* 3rd+ non-option arg should not exist */
699 DISPLAYLEVEL(1, "%s : %s won't be used ! Do you want multiple input files (-m) ? \n",
700 forceOverwrite ? "Warning" : "Error",
701 argument);
702 if (!forceOverwrite) exit(1);
703 }
704
705 DISPLAYLEVEL(3, WELCOME_MESSAGE);
706 #ifdef _POSIX_C_SOURCE
707 DISPLAYLEVEL(4, "_POSIX_C_SOURCE defined: %ldL\n", (long) _POSIX_C_SOURCE);
708 #endif
709 #ifdef _POSIX_VERSION
710 DISPLAYLEVEL(4, "_POSIX_VERSION defined: %ldL\n", (long) _POSIX_VERSION);
711 #endif
712 #ifdef PLATFORM_POSIX_VERSION
713 DISPLAYLEVEL(4, "PLATFORM_POSIX_VERSION defined: %ldL\n", (long) PLATFORM_POSIX_VERSION);
714 #endif
715 #ifdef _FILE_OFFSET_BITS
716 DISPLAYLEVEL(5, "_FILE_OFFSET_BITS defined: %ldL\n", (long) _FILE_OFFSET_BITS);
717 #endif
718 #if !LZ4IO_MULTITHREAD
719 if (nbWorkers > 1) {
720 DISPLAYLEVEL(2, "warning: this executable doesn't support multithreading \n");
721 }
722 #endif
723 if ((mode == om_compress) || (mode == om_bench)) {
724 DISPLAYLEVEL(4, "Blocks size : %u KB\n", (U32)(blockSize>>10));
725 }
726
727 if (multiple_inputs) {
728 input_filename = inFileNames[0];
729 #ifdef UTIL_HAS_CREATEFILELIST
730 if (recursive) { /* at this stage, filenameTable is a list of paths, which can contain both files and directories */
731 const char** extendedFileList = UTIL_createFileList(inFileNames, ifnIdx, &fileNamesBuf, &fileNamesNb);
732 if (extendedFileList) {
733 unsigned u;
734 for (u=0; u<fileNamesNb; u++) DISPLAYLEVEL(4, "%u %s\n", u, extendedFileList[u]);
735 free((void*)inFileNames);
736 inFileNames = extendedFileList;
737 ifnIdx = fileNamesNb;
738 } }
739 #endif
740 }
741
742 if (dictionary_filename) {
743 if (!strcmp(dictionary_filename, stdinmark) && IS_CONSOLE(stdin)) {
744 DISPLAYLEVEL(1, "refusing to read from a console\n");
745 exit(1);
746 }
747 LZ4IO_setDictionaryFilename(prefs, dictionary_filename);
748 }
749
750 /* benchmark and test modes */
751 if (mode == om_bench) {
752 BMK_setNotificationLevel(displayLevel);
753 operationResult = BMK_benchFiles(inFileNames, ifnIdx, cLevel, cLevelLast, dictionary_filename);
754 goto _cleanup;
755 }
756
757 if (mode == om_test) {
758 LZ4IO_setTestMode(prefs, 1);
759 output_filename = nulmark;
760 mode = om_decompress; /* defer to decompress */
761 }
762
763 /* No input provided => use stdin */
764 if (!input_filename) input_filename = stdinmark;
765
766 /* Refuse to use the console as input */
767 if (!strcmp(input_filename, stdinmark) && IS_CONSOLE(stdin) ) {
768 DISPLAYLEVEL(1, "refusing to read from a console\n");
769 exit(1);
770 }
771
772 if (!strcmp(input_filename, stdinmark)) {
773 /* if input==stdin and no output defined, stdout becomes default output */
774 if (!output_filename) output_filename = stdoutmark;
775 }
776
777 /* No output filename ==> try to select one automatically (when possible) */
778 if ((!output_filename) && (multiple_inputs==0)) {
779 if (mode == om_auto) { /* auto-determine compression or decompression, based on file extension */
780 mode = determineOpMode(input_filename);
781 }
782 if (mode == om_compress) { /* compression to file */
783 size_t const l = strlen(input_filename);
784 dynNameSpace = (char*)calloc(1,l+5);
785 if (dynNameSpace==NULL) { perror(exeName); exit(1); }
786 strcpy(dynNameSpace, input_filename);
787 strcat(dynNameSpace, LZ4_EXTENSION);
788 output_filename = dynNameSpace;
789 DISPLAYLEVEL(2, "Compressed filename will be : %s \n", output_filename);
790 }
791 if (mode == om_decompress) {
792 /* decompress to file (automatic output name only works if input filename has correct format extension) */
793 size_t outl;
794 size_t const inl = strlen(input_filename);
795 dynNameSpace = (char*)calloc(1,inl+1);
796 if (dynNameSpace==NULL) { perror(exeName); exit(1); }
797 strcpy(dynNameSpace, input_filename);
798 outl = inl;
799 if (inl>4)
800 while ((outl >= inl-4) && (input_filename[outl] == extension[outl-inl+4])) dynNameSpace[outl--]=0;
801 if (outl != inl-5) { DISPLAYLEVEL(1, "Cannot determine an output filename \n"); badusage(exeName); }
802 output_filename = dynNameSpace;
803 DISPLAYLEVEL(2, "Decoding file %s \n", output_filename);
804 }
805 }
806
807 if (mode == om_list) {
808 if (!multiple_inputs) inFileNames[ifnIdx++] = input_filename;
809 } else {
810 if (!multiple_inputs) assert(output_filename != NULL);
811 }
812 /* when multiple_inputs==1, output_filename may simply be useless,
813 * however, output_filename must be !NULL for next strcmp() tests */
814 if (!output_filename) output_filename = "*\\dummy^!//";
815
816 /* Check if output is defined as console; trigger an error in this case */
817 if ( !strcmp(output_filename,stdoutmark)
818 && mode != om_list
819 && IS_CONSOLE(stdout)
820 && !forceStdout) {
821 DISPLAYLEVEL(1, "refusing to write to console without -c \n");
822 exit(1);
823 }
824 /* Downgrade notification level in stdout and multiple file mode */
825 if (!strcmp(output_filename,stdoutmark) && (displayLevel==2)) displayLevel=1;
826 if ((multiple_inputs) && (displayLevel==2)) displayLevel=1;
827
828 /* Auto-determine compression or decompression, based on file extension */
829 if (mode == om_auto) {
830 mode = determineOpMode(input_filename);
831 }
832
833 /* IO Stream/File */
834 LZ4IO_setNotificationLevel((int)displayLevel);
835 if (ifnIdx == 0) multiple_inputs = 0;
836 if (mode == om_decompress) {
837 if (multiple_inputs) {
838 const char* dec_extension = LZ4_EXTENSION;
839 if (!strcmp(output_filename, stdoutmark)) dec_extension = stdoutmark;
840 if (!strcmp(output_filename, nulmark)) dec_extension = nulmark;
841 assert(ifnIdx < INT_MAX);
842 operationResult = LZ4IO_decompressMultipleFilenames(inFileNames, (int)ifnIdx, dec_extension, prefs);
843 } else {
844 operationResult = DEFAULT_DECOMPRESSOR(input_filename, output_filename, prefs);
845 }
846 } else if (mode == om_list){
847 operationResult = LZ4IO_displayCompressedFilesInfo(inFileNames, ifnIdx);
848 } else { /* compression is default action */
849 #if LZ4IO_MULTITHREAD
850 if (nbWorkers != 1) {
851 if (nbWorkers==0)
852 nbWorkers = (unsigned)LZ4IO_defaultNbWorkers();
853 if (nbWorkers > LZ4_NBWORKERS_MAX) {
854 DISPLAYLEVEL(3, "Requested %u threads too large => automatically reduced to %u \n",
855 nbWorkers, LZ4_NBWORKERS_MAX);
856 nbWorkers = LZ4_NBWORKERS_MAX;
857 } else {
858 DISPLAYLEVEL(3, "Using %u threads for compression \n", nbWorkers);
859 }
860 }
861 LZ4IO_setNbWorkers(prefs, (int)nbWorkers);
862 #endif
863 if (legacy_format) {
864 DISPLAYLEVEL(3, "! Generating LZ4 Legacy format (deprecated) ! \n");
865 if(multiple_inputs){
866 const char* const leg_extension = !strcmp(output_filename,stdoutmark) ? stdoutmark : LZ4_EXTENSION;
867 operationResult = LZ4IO_compressMultipleFilenames_Legacy(inFileNames, (int)ifnIdx, leg_extension, cLevel, prefs);
868 } else {
869 operationResult = LZ4IO_compressFilename_Legacy(input_filename, output_filename, cLevel, prefs);
870 }
871 } else {
872 if (multiple_inputs) {
873 const char* const comp_extension = !strcmp(output_filename,stdoutmark) ? stdoutmark : LZ4_EXTENSION;
874 assert(ifnIdx <= INT_MAX);
875 operationResult = LZ4IO_compressMultipleFilenames(inFileNames, (int)ifnIdx, comp_extension, cLevel, prefs);
876 } else {
877 operationResult = DEFAULT_COMPRESSOR(input_filename, output_filename, cLevel, prefs);
878 } } }
879
880 _cleanup:
881 if (main_pause) waitEnter();
882 free(dynNameSpace);
883 free(fileNamesBuf);
884 LZ4IO_freePreferences(prefs);
885 free((void*)inFileNames);
886 return operationResult;
887 }
888