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