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__); }
107static unsigned displayLevel = 2;   /* 0 : no display ; 1: errors ; 2 : + result + interaction + warnings ; 3 : + progression; 4 : + information */
108
109
110/**************************************
111*  Local Variables
112***************************************/
113static 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
139int LZ4IO_compressFilename_Legacy(char* input_filename, char* output_filename, int compressionlevel);   /* hidden function */
140
141
142/****************************
143*  Functions
144*****************************/
145static 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
162static 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
193static 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
243static int badusage(void)
244{
245    DISPLAYLEVEL(1, "Incorrect parameters\n");
246    if (displayLevel >= 1) usage();
247    exit(1);
248}
249
250
251static void waitEnter(void)
252{
253    DISPLAY("Press enter to continue...\n");
254    getchar();
255}
256
257
258int 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