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