blob: 837ce68129e60e5acaa06a81e31624aafd270215 [file] [log] [blame]
Yann Collet4856a002015-01-24 01:58:16 +01001/*
2 zstdcli - Command Line Interface (cli) for zstd
3 Copyright (C) Yann Collet 2014-2015
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 - zstd source repository : https://github.com/Cyan4973/zstd
23 - ztsd public forum : https://groups.google.com/forum/#!forum/lz4c
24*/
25/*
26 Note : this is user program.
27 It is not part of zstd compression library.
28 The license of this compression CLI program is GPLv2.
29 The license of zstd library is BSD.
30*/
31
32
33/**************************************
34* Compiler Options
35**************************************/
36#define _CRT_SECURE_NO_WARNINGS /* Visual : removes warning from strcpy */
37#define _POSIX_SOURCE 1 /* triggers fileno() within <stdio.h> on unix */
38
39
40/**************************************
41* Includes
42**************************************/
43#include <stdio.h> /* fprintf, getchar */
44#include <stdlib.h> /* exit, calloc, free */
45#include <string.h> /* strcmp, strlen */
46#include "bench.h" /* BMK_benchFiles, BMK_SetNbIterations */
47#include "fileio.h"
48
49
50/**************************************
51* OS-specific Includes
52**************************************/
53#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(_WIN32) || defined(__CYGWIN__)
54# include <fcntl.h> // _O_BINARY
55# include <io.h> // _setmode, _isatty
56# ifdef __MINGW32__
Yann Collete1e6f7d2015-01-25 15:50:24 +010057 /* int _fileno(FILE *stream); // seems no longer useful // MINGW somehow forgets to include this windows declaration into <stdio.h> */
Yann Collet4856a002015-01-24 01:58:16 +010058# endif
59# define SET_BINARY_MODE(file) _setmode(_fileno(file), _O_BINARY)
60# define IS_CONSOLE(stdStream) _isatty(_fileno(stdStream))
61#else
62# include <unistd.h> // isatty
63# define SET_BINARY_MODE(file)
64# define IS_CONSOLE(stdStream) isatty(fileno(stdStream))
65#endif
66
67
Yann Collete1e6f7d2015-01-25 15:50:24 +010068/**************************************
69* Constants
70**************************************/
Yann Collet4856a002015-01-24 01:58:16 +010071#define COMPRESSOR_NAME "zstd command line interface"
72#ifndef ZSTD_VERSION
73# define ZSTD_VERSION "v0.0.1"
74#endif
75#define AUTHOR "Yann Collet"
76#define WELCOME_MESSAGE "*** %s %i-bits %s, by %s (%s) ***\n", COMPRESSOR_NAME, (int)(sizeof(void*)*8), ZSTD_VERSION, AUTHOR, __DATE__
77#define ZSTD_EXTENSION ".zst"
78#define ZSTD_CAT "zstdcat"
Johan Förberg273d0492015-03-24 20:15:56 +010079#define ZSTD_UNZSTD "unzstd"
Yann Collet4856a002015-01-24 01:58:16 +010080
81#define KB *(1 <<10)
82#define MB *(1 <<20)
83#define GB *(1U<<30)
84
85
86/**************************************
87* Display Macros
88**************************************/
Yann Colletf44b2b02015-08-25 23:32:45 +010089#define DISPLAY(...) fprintf(displayOut, __VA_ARGS__)
Yann Collet4856a002015-01-24 01:58:16 +010090#define DISPLAYLEVEL(l, ...) if (displayLevel>=l) { DISPLAY(__VA_ARGS__); }
Yann Colletf44b2b02015-08-25 23:32:45 +010091static FILE* displayOut;
Yann Collet4856a002015-01-24 01:58:16 +010092static unsigned displayLevel = 2; // 0 : no display // 1: errors // 2 : + result + interaction + warnings ; // 3 : + progression; // 4 : + information
93
94
95/**************************************
96* Exceptions
97**************************************/
98#define DEBUG 0
99#define DEBUGOUTPUT(...) if (DEBUG) DISPLAY(__VA_ARGS__);
100#define EXM_THROW(error, ...) \
101{ \
102 DEBUGOUTPUT("Error defined at %s, line %i : \n", __FILE__, __LINE__); \
103 DISPLAYLEVEL(1, "Error %i : ", error); \
104 DISPLAYLEVEL(1, __VA_ARGS__); \
105 DISPLAYLEVEL(1, "\n"); \
106 exit(error); \
107}
108
109
110/**************************************
111* Command Line
112**************************************/
113static int usage(const char* programName)
114{
115 DISPLAY( "Usage :\n");
116 DISPLAY( " %s [arg] [input] [output]\n", programName);
117 DISPLAY( "\n");
118 DISPLAY( "input : a filename\n");
119 DISPLAY( " with no FILE, or when FILE is - , read standard input\n");
120 DISPLAY( "Arguments :\n");
Yann Collet2acb5d32015-10-29 16:49:43 +0100121 DISPLAY( " -1 : Fast compression (default) \n");
122 DISPLAY( " -9 : High compression \n");
Yann Collet4856a002015-01-24 01:58:16 +0100123 DISPLAY( " -d : decompression (default for %s extension)\n", ZSTD_EXTENSION);
124 //DISPLAY( " -z : force compression\n");
125 DISPLAY( " -f : overwrite output without prompting \n");
126 DISPLAY( " -h/-H : display help/long help and exit\n");
127 return 0;
128}
129
130static int usage_advanced(const char* programName)
131{
132 DISPLAY(WELCOME_MESSAGE);
133 usage(programName);
134 DISPLAY( "\n");
135 DISPLAY( "Advanced arguments :\n");
136 DISPLAY( " -V : display Version number and exit\n");
137 DISPLAY( " -v : verbose mode\n");
138 DISPLAY( " -q : suppress warnings; specify twice to suppress errors too\n");
139 DISPLAY( " -c : force write to standard output, even if it is the console\n");
140 //DISPLAY( " -t : test compressed file integrity\n");
141 DISPLAY( "Benchmark arguments :\n");
Yann Colletc776c462015-10-29 19:10:54 +0100142 DISPLAY( " -b# : benchmark file(s), using # compression level (default : 1) \n");
Yann Collet1c00dc32015-10-21 08:22:25 +0100143 DISPLAY( " -B# : cut file into independent blocks of size # (default : no block)\n");
Yann Colletf44b2b02015-08-25 23:32:45 +0100144 DISPLAY( " -i# : iteration loops [1-9](default : 3)\n");
Yann Colletc776c462015-10-29 19:10:54 +0100145 DISPLAY( " -r# : test all compression levels from 1 to # (default : disabled)\n");
Yann Collet4856a002015-01-24 01:58:16 +0100146 return 0;
147}
148
149static int badusage(const char* programName)
150{
151 DISPLAYLEVEL(1, "Incorrect parameters\n");
152 if (displayLevel >= 1) usage(programName);
153 return 1;
154}
155
156
157static void waitEnter(void)
158{
Yann Colletb5e06dc2015-07-04 23:20:56 -0800159 int unused;
Yann Collet4856a002015-01-24 01:58:16 +0100160 DISPLAY("Press enter to continue...\n");
Yann Colletb5e06dc2015-07-04 23:20:56 -0800161 unused = getchar();
162 (void)unused;
Yann Collet4856a002015-01-24 01:58:16 +0100163}
164
165
166int main(int argc, char** argv)
167{
168 int i,
169 bench=0,
170 decode=0,
171 forceStdout=0,
Yann Colletc776c462015-10-29 19:10:54 +0100172 main_pause=0,
173 rangeBench = 1;
Yann Collet4856a002015-01-24 01:58:16 +0100174 unsigned fileNameStart = 0;
175 unsigned nbFiles = 0;
Yann Colletf3eca252015-10-22 15:31:46 +0100176 unsigned cLevel = 0;
Yann Collet4856a002015-01-24 01:58:16 +0100177 const char* programName = argv[0];
178 const char* inFileName = NULL;
179 const char* outFileName = NULL;
180 char* dynNameSpace = NULL;
181 char extension[] = ZSTD_EXTENSION;
182
Yann Colletf44b2b02015-08-25 23:32:45 +0100183 displayOut = stderr;
Yann Collet1c00dc32015-10-21 08:22:25 +0100184 /* Pick out basename component. Don't rely on stdlib because of conflicting behavior. */
Yann Collet17867ce2015-07-07 00:14:27 -0800185 for (i = (int)strlen(programName); i > 0; i--)
Johan Förberg273d0492015-03-24 20:15:56 +0100186 {
Yann Colletf44b2b02015-08-25 23:32:45 +0100187 if (programName[i] == '/') { i++; break; }
Johan Förberg273d0492015-03-24 20:15:56 +0100188 }
189 programName += i;
190
Yann Colletf44b2b02015-08-25 23:32:45 +0100191 /* zstdcat preset behavior */
Yann Collet4856a002015-01-24 01:58:16 +0100192 if (!strcmp(programName, ZSTD_CAT)) { decode=1; forceStdout=1; displayLevel=1; outFileName=stdoutmark; }
193
Yann Colletf44b2b02015-08-25 23:32:45 +0100194 /* unzstd preset behavior */
Johan Förberg273d0492015-03-24 20:15:56 +0100195 if (!strcmp(programName, ZSTD_UNZSTD))
196 decode=1;
197
Yann Colletf44b2b02015-08-25 23:32:45 +0100198 /* command switches */
Yann Collet4856a002015-01-24 01:58:16 +0100199 for(i=1; i<argc; i++)
200 {
201 char* argument = argv[i];
202
Yann Colletf44b2b02015-08-25 23:32:45 +0100203 if(!argument) continue; /* Protection if argument empty */
204
205 /* long commands (--long-word) */
206 if (!strcmp(argument, "--version")) { displayOut=stdout; DISPLAY(WELCOME_MESSAGE); return 0; }
207 if (!strcmp(argument, "--help")) { displayOut=stdout; return usage_advanced(programName); }
Yann Collet50b6f942015-08-26 10:32:17 +0100208 if (!strcmp(argument, "--verbose")) { displayLevel=4; continue; }
Yann Collet4856a002015-01-24 01:58:16 +0100209
210 /* Decode commands (note : aggregated commands are allowed) */
211 if (argument[0]=='-')
212 {
213 /* '-' means stdin/stdout */
214 if (argument[1]==0)
215 {
216 if (!inFileName) inFileName=stdinmark;
217 else outFileName=stdoutmark;
Yann Collet50b6f942015-08-26 10:32:17 +0100218 continue;
Yann Collet4856a002015-01-24 01:58:16 +0100219 }
220
221 argument++;
222
223 while (argument[0]!=0)
224 {
Yann Colletf3eca252015-10-22 15:31:46 +0100225 /* compression Level */
226 if ((*argument>='0') && (*argument<='9'))
227 {
228 cLevel = 0;
229 while ((*argument >= '0') && (*argument <= '9'))
230 {
231 cLevel *= 10;
232 cLevel += *argument - '0';
233 argument++;
234 }
235 continue;
236 }
237
Yann Collet4856a002015-01-24 01:58:16 +0100238 switch(argument[0])
239 {
240 /* Display help */
Yann Colletf44b2b02015-08-25 23:32:45 +0100241 case 'V': displayOut=stdout; DISPLAY(WELCOME_MESSAGE); return 0; /* Version Only */
Yann Collet4856a002015-01-24 01:58:16 +0100242 case 'H':
Yann Colletf44b2b02015-08-25 23:32:45 +0100243 case 'h': displayOut=stdout; return usage_advanced(programName);
Yann Collet4856a002015-01-24 01:58:16 +0100244
Yann Collet1c00dc32015-10-21 08:22:25 +0100245 /* Compression (default) */
Yann Collet4856a002015-01-24 01:58:16 +0100246 //case 'z': forceCompress = 1; break;
247
Yann Collet1c00dc32015-10-21 08:22:25 +0100248 /* Decoding */
Yann Collet4856a002015-01-24 01:58:16 +0100249 case 'd': decode=1; argument++; break;
250
Yann Collet1c00dc32015-10-21 08:22:25 +0100251 /* Force stdout, even if stdout==console */
Yann Collet4856a002015-01-24 01:58:16 +0100252 case 'c': forceStdout=1; outFileName=stdoutmark; displayLevel=1; argument++; break;
253
254 // Test
255 //case 't': decode=1; LZ4IO_setOverwrite(1); output_filename=nulmark; break;
256
257 /* Overwrite */
258 case 'f': FIO_overwriteMode(); argument++; break;
259
260 /* Verbose mode */
261 case 'v': displayLevel=4; argument++; break;
262
263 /* Quiet mode */
264 case 'q': displayLevel--; argument++; break;
265
266 /* keep source file (default anyway, so useless; only for xz/lzma compatibility) */
267 case 'k': argument++; break;
268
269 /* Benchmark */
270 case 'b': bench=1; argument++; break;
271
272 /* Modify Nb Iterations (benchmark only) */
273 case 'i':
274 {
275 int iters= 0;
276 argument++;
277 while ((*argument >='0') && (*argument <='9'))
278 iters *= 10, iters += *argument++ - '0';
279 BMK_SetNbIterations(iters);
280 }
281 break;
282
Yann Collet1c00dc32015-10-21 08:22:25 +0100283 /* cut input into blocks (benchmark only) */
284 case 'B':
285 {
286 size_t bSize = 0;
287 argument++;
288 while ((*argument >='0') && (*argument <='9'))
289 bSize *= 10, bSize += *argument++ - '0';
290 if (*argument=='K') bSize<<=10, argument++; /* allows using KB notation */
291 if (*argument=='M') bSize<<=20, argument++;
292 if (*argument=='B') argument++;
293 BMK_SetBlockSize(bSize);
294 }
295 break;
Yann Colletc776c462015-10-29 19:10:54 +0100296
297 /* range bench (benchmark only) */
298 case 'r':
299 rangeBench = -1;
300 argument++;
301 break;
302
303 /* Pause at the end (hidden option) */
Yann Collet4856a002015-01-24 01:58:16 +0100304 case 'p': main_pause=1; argument++; break;
305
306 /* unknown command */
307 default : return badusage(programName);
308 }
309 }
310 continue;
311 }
312
313 /* first provided filename is input */
314 if (!inFileName) { inFileName = argument; fileNameStart = i; nbFiles = argc-i; continue; }
315
316 /* second provided filename is output */
317 if (!outFileName)
318 {
319 outFileName = argument;
320 if (!strcmp (outFileName, nullString)) outFileName = nulmark;
321 continue;
322 }
323 }
324
325 /* Welcome message (if verbose) */
326 DISPLAYLEVEL(3, WELCOME_MESSAGE);
327
328 /* No input filename ==> use stdin */
329 if(!inFileName) { inFileName=stdinmark; }
330
331 /* Check if input defined as console; trigger an error in this case */
332 if (!strcmp(inFileName, stdinmark) && IS_CONSOLE(stdin) ) return badusage(programName);
333
334 /* Check if benchmark is selected */
Yann Colletc776c462015-10-29 19:10:54 +0100335 if (bench) { BMK_benchFiles(argv+fileNameStart, nbFiles, cLevel*rangeBench); goto _end; }
Yann Collet4856a002015-01-24 01:58:16 +0100336
337 /* No output filename ==> try to select one automatically (when possible) */
338 while (!outFileName)
339 {
340 if (!IS_CONSOLE(stdout)) { outFileName=stdoutmark; break; } /* Default to stdout whenever possible (i.e. not a console) */
341 if (!decode) /* compression to file */
342 {
343 size_t l = strlen(inFileName);
344 dynNameSpace = (char*)calloc(1,l+5);
Yann Collet94f998b2015-07-04 23:10:40 -0800345 if (dynNameSpace==NULL) { DISPLAY("not enough memory\n"); exit(1); }
Yann Collet4856a002015-01-24 01:58:16 +0100346 strcpy(dynNameSpace, inFileName);
347 strcpy(dynNameSpace+l, ZSTD_EXTENSION);
348 outFileName = dynNameSpace;
349 DISPLAYLEVEL(2, "Compressed filename will be : %s \n", outFileName);
350 break;
351 }
352 /* decompression to file (automatic name will work only if input filename has correct format extension) */
353 {
354 size_t outl;
355 size_t inl = strlen(inFileName);
356 dynNameSpace = (char*)calloc(1,inl+1);
Yann Collet94f998b2015-07-04 23:10:40 -0800357 if (dynNameSpace==NULL) { DISPLAY("not enough memory\n"); exit(1); }
Yann Collet4856a002015-01-24 01:58:16 +0100358 outFileName = dynNameSpace;
359 strcpy(dynNameSpace, inFileName);
360 outl = inl;
361 if (inl>4)
362 while ((outl >= inl-4) && (inFileName[outl] == extension[outl-inl+4])) dynNameSpace[outl--]=0;
363 if (outl != inl-5) { DISPLAYLEVEL(1, "Cannot determine an output filename\n"); return badusage(programName); }
364 DISPLAYLEVEL(2, "Decoding file %s \n", outFileName);
365 }
366 }
367
368 /* Check if output is defined as console; trigger an error in this case */
369 if (!strcmp(outFileName,stdoutmark) && IS_CONSOLE(stdout) && !forceStdout) return badusage(programName);
370
371 /* No warning message in pure pipe mode (stdin + stdout) */
372 if (!strcmp(inFileName, stdinmark) && !strcmp(outFileName,stdoutmark) && (displayLevel==2)) displayLevel=1;
373
374 /* IO Stream/File */
375 FIO_setNotificationLevel(displayLevel);
376 if (decode)
377 FIO_decompressFilename(outFileName, inFileName);
378 else
Yann Colletf3eca252015-10-22 15:31:46 +0100379 FIO_compressFilename(outFileName, inFileName, cLevel);
Yann Collet4856a002015-01-24 01:58:16 +0100380
381_end:
382 if (main_pause) waitEnter();
383 free(dynNameSpace);
384 return 0;
385}