1 /*
2   LZ4cli - LZ4 Command Line Interface
3   Copyright (C) Yann Collet 2011-2014
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 : http://code.google.com/p/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 *  Tuning parameters
35 ***************************************/
36 /* ENABLE_LZ4C_LEGACY_OPTIONS :
37    Control the availability of -c0, -c1 and -hc legacy arguments
38    Default : Legacy options are disabled */
39 /* #define ENABLE_LZ4C_LEGACY_OPTIONS */
40 
41 
42 /**************************************
43 *  Compiler Options
44 ***************************************/
45 /* Disable some Visual warning messages */
46 #ifdef _MSC_VER
47 #  define _CRT_SECURE_NO_WARNINGS
48 #  define _CRT_SECURE_NO_DEPRECATE     /* VS2005 */
49 #  pragma warning(disable : 4127)      /* disable: C4127: conditional expression is constant */
50 #endif
51 
52 #define _POSIX_SOURCE 1        /* for fileno() within <stdio.h> on unix */
53 
54 
55 /****************************
56 *  Includes
57 *****************************/
58 #include <stdio.h>    /* fprintf, getchar */
59 #include <stdlib.h>   /* exit, calloc, free */
60 #include <string.h>   /* strcmp, strlen */
61 #include "bench.h"    /* BMK_benchFile, BMK_SetNbIterations, BMK_SetBlocksize, BMK_SetPause */
62 #include "lz4io.h"
63 
64 
65 /****************************
66 *  OS-specific Includes
67 *****************************/
68 #if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(_WIN32) || defined(__CYGWIN__)
69 #  include <fcntl.h>    /* _O_BINARY */
70 #  include <io.h>       /* _setmode, _isatty */
71 #  ifdef __MINGW32__
72    int _fileno(FILE *stream);   /* MINGW somehow forgets to include this prototype into <stdio.h> */
73 #  endif
74 #  define SET_BINARY_MODE(file) _setmode(_fileno(file), _O_BINARY)
75 #  define IS_CONSOLE(stdStream) _isatty(_fileno(stdStream))
76 #else
77 #  include <unistd.h>   /* isatty */
78 #  define SET_BINARY_MODE(file)
79 #  define IS_CONSOLE(stdStream) isatty(fileno(stdStream))
80 #endif
81 
82 
83 /*****************************
84 *  Constants
85 ******************************/
86 #define COMPRESSOR_NAME "LZ4 command line interface"
87 #ifndef LZ4_VERSION
88 #  define LZ4_VERSION "r126"
89 #endif
90 #define AUTHOR "Yann Collet"
91 #define WELCOME_MESSAGE "*** %s %i-bits %s, by %s (%s) ***\n", COMPRESSOR_NAME, (int)(sizeof(void*)*8), LZ4_VERSION, AUTHOR, __DATE__
92 #define LZ4_EXTENSION ".lz4"
93 #define LZ4_CAT "lz4cat"
94 
95 #define KB *(1U<<10)
96 #define MB *(1U<<20)
97 #define GB *(1U<<30)
98 
99 #define LZ4_BLOCKSIZEID_DEFAULT 7
100 
101 
102 /**************************************
103 *  Macros
104 ***************************************/
105 #define DISPLAY(...)           fprintf(stderr, __VA_ARGS__)
106 #define DISPLAYLEVEL(l, ...)   if (displayLevel>=l) { DISPLAY(__VA_ARGS__); }
107 static unsigned displayLevel = 2;   /* 0 : no display ; 1: errors ; 2 : + result + interaction + warnings ; 3 : + progression; 4 : + information */
108 
109 
110 /**************************************
111 *  Local Variables
112 ***************************************/
113 static char* programName;
114 
115 
116 /**************************************
117 *  Exceptions
118 ***************************************/
119 #define DEBUG 0
120 #define DEBUGOUTPUT(...) if (DEBUG) DISPLAY(__VA_ARGS__);
121 #define EXM_THROW(error, ...)                                             \
122 {                                                                         \
123     DEBUGOUTPUT("Error defined at %s, line %i : \n", __FILE__, __LINE__); \
124     DISPLAYLEVEL(1, "Error %i : ", error);                                \
125     DISPLAYLEVEL(1, __VA_ARGS__);                                         \
126     DISPLAYLEVEL(1, "\n");                                                \
127     exit(error);                                                          \
128 }
129 
130 
131 /**************************************
132 *  Version modifiers
133 ***************************************/
134 #define EXTENDED_ARGUMENTS
135 #define EXTENDED_HELP
136 #define EXTENDED_FORMAT
137 #define DEFAULT_COMPRESSOR   LZ4IO_compressFilename
138 #define DEFAULT_DECOMPRESSOR LZ4IO_decompressFilename
139 int LZ4IO_compressFilename_Legacy(char* input_filename, char* output_filename, int compressionlevel);   /* hidden function */
140 
141 
142 /****************************
143 *  Functions
144 *****************************/
usage(void)145 static int usage(void)
146 {
147     DISPLAY( "Usage :\n");
148     DISPLAY( "      %s [arg] [input] [output]\n", programName);
149     DISPLAY( "\n");
150     DISPLAY( "input   : a filename\n");
151     DISPLAY( "          with no FILE, or when FILE is - or %s, read standard input\n", stdinmark);
152     DISPLAY( "Arguments :\n");
153     DISPLAY( " -1     : Fast compression (default) \n");
154     DISPLAY( " -9     : High compression \n");
155     DISPLAY( " -d     : decompression (default for %s extension)\n", LZ4_EXTENSION);
156     DISPLAY( " -z     : force compression\n");
157     DISPLAY( " -f     : overwrite output without prompting \n");
158     DISPLAY( " -h/-H  : display help/long help and exit\n");
159     return 0;
160 }
161 
usage_advanced(void)162 static int usage_advanced(void)
163 {
164     DISPLAY(WELCOME_MESSAGE);
165     usage();
166     DISPLAY( "\n");
167     DISPLAY( "Advanced arguments :\n");
168     DISPLAY( " -V     : display Version number and exit\n");
169     DISPLAY( " -v     : verbose mode\n");
170     DISPLAY( " -q     : suppress warnings; specify twice to suppress errors too\n");
171     DISPLAY( " -c     : force write to standard output, even if it is the console\n");
172     DISPLAY( " -t     : test compressed file integrity\n");
173     DISPLAY( " -l     : compress using Legacy format (Linux kernel compression)\n");
174     DISPLAY( " -B#    : Block size [4-7](default : 7)\n");
175     DISPLAY( " -BD    : Block dependency (improve compression ratio)\n");
176     /* DISPLAY( " -BX    : enable block checksum (default:disabled)\n");   *//* Option currently inactive */
177     DISPLAY( " -Sx    : disable stream checksum (default:enabled)\n");
178     DISPLAY( "Benchmark arguments :\n");
179     DISPLAY( " -b     : benchmark file(s)\n");
180     DISPLAY( " -i#    : iteration loops [1-9](default : 3), benchmark mode only\n");
181 #if defined(ENABLE_LZ4C_LEGACY_OPTIONS)
182     DISPLAY( "Legacy arguments :\n");
183     DISPLAY( " -c0    : fast compression\n");
184     DISPLAY( " -c1    : high compression\n");
185     DISPLAY( " -hc    : high compression\n");
186     DISPLAY( " -y     : overwrite output without prompting \n");
187     DISPLAY( " -s     : suppress warnings \n");
188 #endif /* ENABLE_LZ4C_LEGACY_OPTIONS */
189     EXTENDED_HELP;
190     return 0;
191 }
192 
usage_longhelp(void)193 static int usage_longhelp(void)
194 {
195     DISPLAY( "\n");
196     DISPLAY( "Which values can get [output] ? \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( "               + if compression selected, output to filename%s \n", LZ4_EXTENSION);
204     DISPLAY( "               + if decompression selected, 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( "There are technically 2 accessible compression levels.\n");
209     DISPLAY( "-0 ... -2 => Fast compression\n");
210     DISPLAY( "-3 ... -9 => High compression\n");
211     DISPLAY( "\n");
212     DISPLAY( "stdin, stdout and the console : \n");
213     DISPLAY( "To protect the console from binary flooding (bad argument mistake)\n");
214     DISPLAY( "%s will refuse to read from console, or write to console \n", programName);
215     DISPLAY( "except if '-c' command is specified, to force output to console \n");
216     DISPLAY( "\n");
217     DISPLAY( "Simple example :\n");
218     DISPLAY( "1 : compress 'filename' fast, using default output name 'filename.lz4'\n");
219     DISPLAY( "          %s filename\n", programName);
220     DISPLAY( "\n");
221     DISPLAY( "Arguments can be appended together, or provided independently. For example :\n");
222     DISPLAY( "2 : compress 'filename' in high compression mode, overwrite output if exists\n");
223     DISPLAY( "          %s -f9 filename \n", programName);
224     DISPLAY( "    is equivalent to :\n");
225     DISPLAY( "          %s -f -9 filename \n", programName);
226     DISPLAY( "\n");
227     DISPLAY( "%s can be used in 'pure pipe mode', for example :\n", programName);
228     DISPLAY( "3 : compress data stream from 'generator', send result to 'consumer'\n");
229     DISPLAY( "          generator | %s | consumer \n", programName);
230 #if defined(ENABLE_LZ4C_LEGACY_OPTIONS)
231     DISPLAY( "\n");
232     DISPLAY( "Warning :\n");
233     DISPLAY( "Legacy arguments take precedence. Therefore : \n");
234     DISPLAY( "          %s -hc filename\n", programName);
235     DISPLAY( "means 'compress filename in high compression mode'\n");
236     DISPLAY( "It is not equivalent to :\n");
237     DISPLAY( "          %s -h -c filename\n", programName);
238     DISPLAY( "which would display help text and exit\n");
239 #endif /* ENABLE_LZ4C_LEGACY_OPTIONS */
240     return 0;
241 }
242 
badusage(void)243 static int badusage(void)
244 {
245     DISPLAYLEVEL(1, "Incorrect parameters\n");
246     if (displayLevel >= 1) usage();
247     exit(1);
248 }
249 
250 
waitEnter(void)251 static void waitEnter(void)
252 {
253     DISPLAY("Press enter to continue...\n");
254     getchar();
255 }
256 
257 
main(int argc,char ** argv)258 int main(int argc, char** argv)
259 {
260     int i,
261         cLevel=0,
262         decode=0,
263         bench=0,
264         filenamesStart=2,
265         legacy_format=0,
266         forceStdout=0,
267         forceCompress=0,
268         main_pause=0;
269     char* input_filename=0;
270     char* output_filename=0;
271     char* dynNameSpace=0;
272     char nullOutput[] = NULL_OUTPUT;
273     char extension[] = LZ4_EXTENSION;
274     int blockSize;
275 
276     /* Init */
277     programName = argv[0];
278     LZ4IO_setOverwrite(0);
279     blockSize = LZ4IO_setBlockSizeID(LZ4_BLOCKSIZEID_DEFAULT);
280 
281     /* lz4cat behavior */
282     if (!strcmp(programName, LZ4_CAT)) { decode=1; forceStdout=1; output_filename=stdoutmark; displayLevel=1; }
283 
284     /* command switches */
285     for(i=1; i<argc; i++)
286     {
287         char* argument = argv[i];
288 
289         if(!argument) continue;   /* Protection if argument empty */
290 
291         /* Decode command (note : aggregated commands are allowed) */
292         if (argument[0]=='-')
293         {
294             /* '-' means stdin/stdout */
295             if (argument[1]==0)
296             {
297                 if (!input_filename) input_filename=stdinmark;
298                 else output_filename=stdoutmark;
299             }
300 
301             while (argument[1]!=0)
302             {
303                 argument ++;
304 
305 #if defined(ENABLE_LZ4C_LEGACY_OPTIONS)
306                 /* Legacy arguments (-c0, -c1, -hc, -y, -s) */
307                 if ((argument[0]=='c') && (argument[1]=='0')) { cLevel=0; argument++; continue; }  /* -c0 (fast compression) */
308                 if ((argument[0]=='c') && (argument[1]=='1')) { cLevel=9; argument++; continue; }  /* -c1 (high compression) */
309                 if ((argument[0]=='h') && (argument[1]=='c')) { cLevel=9; argument++; continue; }  /* -hc (high compression) */
310                 if (*argument=='y') { LZ4IO_setOverwrite(1); continue; }                           /* -y (answer 'yes' to overwrite permission) */
311                 if (*argument=='s') { displayLevel=1; continue; }                                  /* -s (silent mode) */
312 #endif /* ENABLE_LZ4C_LEGACY_OPTIONS */
313 
314                 if ((*argument>='0') && (*argument<='9'))
315                 {
316                     cLevel = 0;
317                     while ((*argument >= '0') && (*argument <= '9'))
318                     {
319                         cLevel *= 10;
320                         cLevel += *argument - '0';
321                         argument++;
322                     }
323                     argument--;
324                     continue;
325                 }
326 
327                 switch(argument[0])
328                 {
329                     /* Display help */
330                 case 'V': DISPLAY(WELCOME_MESSAGE); return 0;   /* Version */
331                 case 'h': usage_advanced(); return 0;
332                 case 'H': usage_advanced(); usage_longhelp(); return 0;
333 
334                     /* Compression (default) */
335                 case 'z': forceCompress = 1; break;
336 
337                     /* Use Legacy format (ex : Linux kernel compression) */
338                 case 'l': legacy_format = 1; blockSize = 8 MB; break;
339 
340                     /* Decoding */
341                 case 'd': decode=1; break;
342 
343                     /* Force stdout, even if stdout==console */
344                 case 'c': forceStdout=1; output_filename=stdoutmark; displayLevel=1; break;
345 
346                     /* Test integrity */
347                 case 't': decode=1; LZ4IO_setOverwrite(1); output_filename=nulmark; break;
348 
349                     /* Overwrite */
350                 case 'f': LZ4IO_setOverwrite(1); break;
351 
352                     /* Verbose mode */
353                 case 'v': displayLevel=4; break;
354 
355                     /* Quiet mode */
356                 case 'q': displayLevel--; break;
357 
358                     /* keep source file (default anyway, so useless) (for xz/lzma compatibility) */
359                 case 'k': break;
360 
361                     /* Modify Block Properties */
362                 case 'B':
363                     while (argument[1]!=0)
364                     {
365                         int exitBlockProperties=0;
366                         switch(argument[1])
367                         {
368                         case '4':
369                         case '5':
370                         case '6':
371                         case '7':
372                         {
373                             int B = argument[1] - '0';
374                             blockSize = LZ4IO_setBlockSizeID(B);
375                             BMK_SetBlocksize(blockSize);
376                             argument++;
377                             break;
378                         }
379                         case 'D': LZ4IO_setBlockMode(LZ4IO_blockLinked); argument++; break;
380                         case 'X': LZ4IO_setBlockChecksumMode(1); argument ++; break;   /* currently disables */
381                         default : exitBlockProperties=1;
382                         }
383                         if (exitBlockProperties) break;
384                     }
385                     break;
386 
387                     /* Modify Stream properties */
388                 case 'S': if (argument[1]=='x') { LZ4IO_setStreamChecksumMode(0); argument++; break; } else { badusage(); }
389 
390                     /* Benchmark */
391                 case 'b': bench=1; break;
392 
393                     /* Modify Nb Iterations (benchmark only) */
394                 case 'i':
395                     if ((argument[1] >='1') && (argument[1] <='9'))
396                     {
397                         int iters = argument[1] - '0';
398                         BMK_SetNbIterations(iters);
399                         argument++;
400                     }
401                     break;
402 
403                     /* Pause at the end (hidden option) */
404                 case 'p': main_pause=1; BMK_SetPause(); break;
405 
406                     /* Specific commands for customized versions */
407                 EXTENDED_ARGUMENTS;
408 
409                     /* Unrecognised command */
410                 default : badusage();
411                 }
412             }
413             continue;
414         }
415 
416         /* first provided filename is input */
417         if (!input_filename) { input_filename=argument; filenamesStart=i; continue; }
418 
419         /* second provided filename is output */
420         if (!output_filename)
421         {
422             output_filename=argument;
423             if (!strcmp (output_filename, nullOutput)) output_filename = nulmark;
424             continue;
425         }
426     }
427 
428     DISPLAYLEVEL(3, WELCOME_MESSAGE);
429     if (!decode) DISPLAYLEVEL(4, "Blocks size : %i KB\n", blockSize>>10);
430 
431     /* No input filename ==> use stdin */
432     if(!input_filename) { input_filename=stdinmark; }
433 
434     /* Check if input or output are defined as console; trigger an error in this case */
435     if (!strcmp(input_filename, stdinmark) && IS_CONSOLE(stdin) ) badusage();
436 
437     /* Check if benchmark is selected */
438     if (bench) return BMK_benchFile(argv+filenamesStart, argc-filenamesStart, cLevel);
439 
440     /* No output filename ==> try to select one automatically (when possible) */
441     while (!output_filename)
442     {
443         if (!IS_CONSOLE(stdout)) { output_filename=stdoutmark; break; }   /* Default to stdout whenever possible (i.e. not a console) */
444         if ((!decode) && !(forceCompress))   /* auto-determine compression or decompression, based on file extension */
445         {
446             size_t l = strlen(input_filename);
447             if (!strcmp(input_filename+(l-4), LZ4_EXTENSION)) decode=1;
448         }
449         if (!decode)   /* compression to file */
450         {
451             size_t l = strlen(input_filename);
452             dynNameSpace = (char*)calloc(1,l+5);
453             output_filename = dynNameSpace;
454             strcpy(output_filename, input_filename);
455             strcpy(output_filename+l, LZ4_EXTENSION);
456             DISPLAYLEVEL(2, "Compressed filename will be : %s \n", output_filename);
457             break;
458         }
459         /* decompression to file (automatic name will work only if input filename has correct format extension) */
460         {
461             size_t outl;
462             size_t inl = strlen(input_filename);
463             dynNameSpace = (char*)calloc(1,inl+1);
464             output_filename = dynNameSpace;
465             strcpy(output_filename, input_filename);
466             outl = inl;
467             if (inl>4)
468                 while ((outl >= inl-4) && (input_filename[outl] ==  extension[outl-inl+4])) output_filename[outl--]=0;
469             if (outl != inl-5) { DISPLAYLEVEL(1, "Cannot determine an output filename\n"); badusage(); }
470             DISPLAYLEVEL(2, "Decoding file %s \n", output_filename);
471         }
472     }
473 
474     /* Check if output is defined as console; trigger an error in this case */
475     if (!strcmp(output_filename,stdoutmark) && IS_CONSOLE(stdout) && !forceStdout) badusage();
476 
477     /* No warning message in pure pipe mode (stdin + stdout) */
478     if (!strcmp(input_filename, stdinmark) && !strcmp(output_filename,stdoutmark) && (displayLevel==2)) displayLevel=1;
479 
480 
481     /* IO Stream/File */
482     LZ4IO_setNotificationLevel(displayLevel);
483     if (decode) DEFAULT_DECOMPRESSOR(input_filename, output_filename);
484     else
485     {
486         /* compression is default action */
487         if (legacy_format)
488         {
489             DISPLAYLEVEL(3, "! Generating compressed LZ4 using Legacy format (deprecated) ! \n");
490             LZ4IO_compressFilename_Legacy(input_filename, output_filename, cLevel);
491         }
492         else
493         {
494             DEFAULT_COMPRESSOR(input_filename, output_filename, cLevel);
495         }
496     }
497 
498     if (main_pause) waitEnter();
499     free(dynNameSpace);
500     return 0;
501 }
502