blob: 8c80b15737c6a10af80995d0ffe03a331eb17329 [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
Yann Collet6a2f0322015-12-02 15:05:22 +010073# define ZSTD_VERSION "v0.4.2"
Yann Collet4856a002015-01-24 01:58:16 +010074#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");
Yann Collet28e7cef2015-12-03 12:11:30 +0100141#ifndef ZSTD_NOBENCH
Yann Collet4856a002015-01-24 01:58:16 +0100142 DISPLAY( "Benchmark arguments :\n");
Yann Colletc776c462015-10-29 19:10:54 +0100143 DISPLAY( " -b# : benchmark file(s), using # compression level (default : 1) \n");
Yann Collet1c00dc32015-10-21 08:22:25 +0100144 DISPLAY( " -B# : cut file into independent blocks of size # (default : no block)\n");
Yann Colletf44b2b02015-08-25 23:32:45 +0100145 DISPLAY( " -i# : iteration loops [1-9](default : 3)\n");
Yann Colletc776c462015-10-29 19:10:54 +0100146 DISPLAY( " -r# : test all compression levels from 1 to # (default : disabled)\n");
Yann Collet28e7cef2015-12-03 12:11:30 +0100147#endif
Yann Collet4856a002015-01-24 01:58:16 +0100148 return 0;
149}
150
151static int badusage(const char* programName)
152{
153 DISPLAYLEVEL(1, "Incorrect parameters\n");
154 if (displayLevel >= 1) usage(programName);
155 return 1;
156}
157
158
159static void waitEnter(void)
160{
Yann Colletb5e06dc2015-07-04 23:20:56 -0800161 int unused;
Yann Collet4856a002015-01-24 01:58:16 +0100162 DISPLAY("Press enter to continue...\n");
Yann Colletb5e06dc2015-07-04 23:20:56 -0800163 unused = getchar();
164 (void)unused;
Yann Collet4856a002015-01-24 01:58:16 +0100165}
166
167
Yann Collet7f6e91f2015-11-11 14:39:50 +0100168int main(int argCount, const char** argv)
Yann Collet4856a002015-01-24 01:58:16 +0100169{
170 int i,
171 bench=0,
172 decode=0,
173 forceStdout=0,
Yann Collet28e7cef2015-12-03 12:11:30 +0100174 main_pause=0;
Yann Collet44fe9912015-10-29 22:02:40 +0100175 unsigned cLevel = 1;
Yann Collet4856a002015-01-24 01:58:16 +0100176 const char* programName = argv[0];
177 const char* inFileName = NULL;
178 const char* outFileName = NULL;
179 char* dynNameSpace = NULL;
Yann Collet50c5cdb2015-11-04 20:35:33 +0100180 const char extension[] = ZSTD_EXTENSION;
Yann Collet28e7cef2015-12-03 12:11:30 +0100181 unsigned fileNameStart = 0;
182 unsigned nbFiles = 0;
183 int rangeBench = 1;
Yann Collet4856a002015-01-24 01:58:16 +0100184
Yann Collet28e7cef2015-12-03 12:11:30 +0100185 /* init */
186 (void)rangeBench; (void)nbFiles; (void)fileNameStart; /* not used when ZSTD_NOBENCH set */
Yann Colletf44b2b02015-08-25 23:32:45 +0100187 displayOut = stderr;
Yann Collet1c00dc32015-10-21 08:22:25 +0100188 /* Pick out basename component. Don't rely on stdlib because of conflicting behavior. */
Yann Colletd062f132015-12-01 01:31:17 +0100189 for (i = (int)strlen(programName); i > 0; i--) { if (programName[i] == '/') { i++; break; } }
Johan Förberg273d0492015-03-24 20:15:56 +0100190 programName += i;
191
Yann Colletd062f132015-12-01 01:31:17 +0100192 /* preset behaviors */
193 if (!strcmp(programName, ZSTD_UNZSTD)) decode=1;
Yann Collet4856a002015-01-24 01:58:16 +0100194 if (!strcmp(programName, ZSTD_CAT)) { decode=1; forceStdout=1; displayLevel=1; outFileName=stdoutmark; }
195
Yann Colletf44b2b02015-08-25 23:32:45 +0100196 /* command switches */
Yann Collet7f6e91f2015-11-11 14:39:50 +0100197 for(i=1; i<argCount; i++)
Yann Collet4856a002015-01-24 01:58:16 +0100198 {
Yann Collet7f6e91f2015-11-11 14:39:50 +0100199 const char* argument = argv[i];
Yann Collet4856a002015-01-24 01:58:16 +0100200
Yann Colletf44b2b02015-08-25 23:32:45 +0100201 if(!argument) continue; /* Protection if argument empty */
202
203 /* long commands (--long-word) */
204 if (!strcmp(argument, "--version")) { displayOut=stdout; DISPLAY(WELCOME_MESSAGE); return 0; }
205 if (!strcmp(argument, "--help")) { displayOut=stdout; return usage_advanced(programName); }
Yann Collet50b6f942015-08-26 10:32:17 +0100206 if (!strcmp(argument, "--verbose")) { displayLevel=4; continue; }
Yann Collet4856a002015-01-24 01:58:16 +0100207
208 /* Decode commands (note : aggregated commands are allowed) */
209 if (argument[0]=='-')
210 {
211 /* '-' means stdin/stdout */
212 if (argument[1]==0)
213 {
214 if (!inFileName) inFileName=stdinmark;
215 else outFileName=stdoutmark;
Yann Collet50b6f942015-08-26 10:32:17 +0100216 continue;
Yann Collet4856a002015-01-24 01:58:16 +0100217 }
218
219 argument++;
220
221 while (argument[0]!=0)
222 {
Yann Colletf3eca252015-10-22 15:31:46 +0100223 /* compression Level */
224 if ((*argument>='0') && (*argument<='9'))
225 {
226 cLevel = 0;
227 while ((*argument >= '0') && (*argument <= '9'))
228 {
229 cLevel *= 10;
230 cLevel += *argument - '0';
231 argument++;
232 }
233 continue;
234 }
235
Yann Collet4856a002015-01-24 01:58:16 +0100236 switch(argument[0])
237 {
238 /* Display help */
Yann Colletf44b2b02015-08-25 23:32:45 +0100239 case 'V': displayOut=stdout; DISPLAY(WELCOME_MESSAGE); return 0; /* Version Only */
Yann Collet4856a002015-01-24 01:58:16 +0100240 case 'H':
Yann Colletf44b2b02015-08-25 23:32:45 +0100241 case 'h': displayOut=stdout; return usage_advanced(programName);
Yann Collet4856a002015-01-24 01:58:16 +0100242
Yann Collet1c00dc32015-10-21 08:22:25 +0100243 /* Compression (default) */
Yann Collet4856a002015-01-24 01:58:16 +0100244 //case 'z': forceCompress = 1; break;
245
Yann Collet1c00dc32015-10-21 08:22:25 +0100246 /* Decoding */
Yann Collet4856a002015-01-24 01:58:16 +0100247 case 'd': decode=1; argument++; break;
248
Yann Collet1c00dc32015-10-21 08:22:25 +0100249 /* Force stdout, even if stdout==console */
Yann Collet4856a002015-01-24 01:58:16 +0100250 case 'c': forceStdout=1; outFileName=stdoutmark; displayLevel=1; argument++; break;
251
252 // Test
253 //case 't': decode=1; LZ4IO_setOverwrite(1); output_filename=nulmark; break;
254
255 /* Overwrite */
256 case 'f': FIO_overwriteMode(); argument++; break;
257
258 /* Verbose mode */
259 case 'v': displayLevel=4; argument++; break;
260
261 /* Quiet mode */
262 case 'q': displayLevel--; argument++; break;
263
264 /* keep source file (default anyway, so useless; only for xz/lzma compatibility) */
265 case 'k': argument++; break;
266
Yann Collet28e7cef2015-12-03 12:11:30 +0100267#ifndef ZSTD_NOBENCH
Yann Collet4856a002015-01-24 01:58:16 +0100268 /* Benchmark */
269 case 'b': bench=1; argument++; break;
270
271 /* Modify Nb Iterations (benchmark only) */
272 case 'i':
273 {
274 int iters= 0;
275 argument++;
276 while ((*argument >='0') && (*argument <='9'))
277 iters *= 10, iters += *argument++ - '0';
278 BMK_SetNbIterations(iters);
279 }
280 break;
281
Yann Collet1c00dc32015-10-21 08:22:25 +0100282 /* cut input into blocks (benchmark only) */
283 case 'B':
284 {
285 size_t bSize = 0;
286 argument++;
287 while ((*argument >='0') && (*argument <='9'))
288 bSize *= 10, bSize += *argument++ - '0';
289 if (*argument=='K') bSize<<=10, argument++; /* allows using KB notation */
290 if (*argument=='M') bSize<<=20, argument++;
291 if (*argument=='B') argument++;
292 BMK_SetBlockSize(bSize);
293 }
294 break;
Yann Colletc776c462015-10-29 19:10:54 +0100295
296 /* range bench (benchmark only) */
297 case 'r':
298 rangeBench = -1;
299 argument++;
300 break;
Yann Collet28e7cef2015-12-03 12:11:30 +0100301#endif /* ZSTD_NOBENCH */
Yann Colletc776c462015-10-29 19:10:54 +0100302
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 */
Yann Collet7f6e91f2015-11-11 14:39:50 +0100314 if (!inFileName) { inFileName = argument; fileNameStart = i; nbFiles = argCount-i; continue; }
Yann Collet4856a002015-01-24 01:58:16 +0100315
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
Yann Colletd062f132015-12-01 01:31:17 +0100328 /* Check if benchmark is selected */
Yann Collet28e7cef2015-12-03 12:11:30 +0100329 if (bench)
330 {
331#ifndef ZSTD_NOBENCH
332 BMK_benchFiles(argv+fileNameStart, nbFiles, cLevel*rangeBench);
333#endif
334 goto _end;
335 }
Yann Colletd062f132015-12-01 01:31:17 +0100336
Yann Collet4856a002015-01-24 01:58:16 +0100337 /* No input filename ==> use stdin */
338 if(!inFileName) { inFileName=stdinmark; }
339
340 /* Check if input defined as console; trigger an error in this case */
341 if (!strcmp(inFileName, stdinmark) && IS_CONSOLE(stdin) ) return badusage(programName);
342
Yann Collet4856a002015-01-24 01:58:16 +0100343 /* No output filename ==> try to select one automatically (when possible) */
344 while (!outFileName)
345 {
346 if (!IS_CONSOLE(stdout)) { outFileName=stdoutmark; break; } /* Default to stdout whenever possible (i.e. not a console) */
347 if (!decode) /* compression to file */
348 {
349 size_t l = strlen(inFileName);
350 dynNameSpace = (char*)calloc(1,l+5);
Yann Collet94f998b2015-07-04 23:10:40 -0800351 if (dynNameSpace==NULL) { DISPLAY("not enough memory\n"); exit(1); }
Yann Collet4856a002015-01-24 01:58:16 +0100352 strcpy(dynNameSpace, inFileName);
353 strcpy(dynNameSpace+l, ZSTD_EXTENSION);
354 outFileName = dynNameSpace;
355 DISPLAYLEVEL(2, "Compressed filename will be : %s \n", outFileName);
356 break;
357 }
358 /* decompression to file (automatic name will work only if input filename has correct format extension) */
359 {
Yann Collet50c5cdb2015-11-04 20:35:33 +0100360 size_t filenameSize = strlen(inFileName);
361 if (strcmp(inFileName + (filenameSize-4), extension))
362 {
363 DISPLAYLEVEL(1, "unknown suffix - cannot determine destination filename\n");
364 return badusage(programName);
365 }
366 dynNameSpace = (char*)calloc(1,filenameSize+1);
Yann Collet94f998b2015-07-04 23:10:40 -0800367 if (dynNameSpace==NULL) { DISPLAY("not enough memory\n"); exit(1); }
Yann Collet4856a002015-01-24 01:58:16 +0100368 outFileName = dynNameSpace;
369 strcpy(dynNameSpace, inFileName);
Yann Collet50c5cdb2015-11-04 20:35:33 +0100370 dynNameSpace[filenameSize-4]=0;
Yann Collet4856a002015-01-24 01:58:16 +0100371 DISPLAYLEVEL(2, "Decoding file %s \n", outFileName);
372 }
373 }
374
375 /* Check if output is defined as console; trigger an error in this case */
376 if (!strcmp(outFileName,stdoutmark) && IS_CONSOLE(stdout) && !forceStdout) return badusage(programName);
377
378 /* No warning message in pure pipe mode (stdin + stdout) */
379 if (!strcmp(inFileName, stdinmark) && !strcmp(outFileName,stdoutmark) && (displayLevel==2)) displayLevel=1;
380
381 /* IO Stream/File */
382 FIO_setNotificationLevel(displayLevel);
383 if (decode)
384 FIO_decompressFilename(outFileName, inFileName);
385 else
Yann Colletf3eca252015-10-22 15:31:46 +0100386 FIO_compressFilename(outFileName, inFileName, cLevel);
Yann Collet4856a002015-01-24 01:58:16 +0100387
388_end:
389 if (main_pause) waitEnter();
390 free(dynNameSpace);
391 return 0;
392}