blob: d71850149e1b1a0487f613678affbbadc8fe6575 [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");
142 DISPLAY( " -b : benchmark file(s)\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 Collet4856a002015-01-24 01:58:16 +0100145 return 0;
146}
147
148static int badusage(const char* programName)
149{
150 DISPLAYLEVEL(1, "Incorrect parameters\n");
151 if (displayLevel >= 1) usage(programName);
152 return 1;
153}
154
155
156static void waitEnter(void)
157{
Yann Colletb5e06dc2015-07-04 23:20:56 -0800158 int unused;
Yann Collet4856a002015-01-24 01:58:16 +0100159 DISPLAY("Press enter to continue...\n");
Yann Colletb5e06dc2015-07-04 23:20:56 -0800160 unused = getchar();
161 (void)unused;
Yann Collet4856a002015-01-24 01:58:16 +0100162}
163
164
165int main(int argc, char** argv)
166{
167 int i,
168 bench=0,
169 decode=0,
170 forceStdout=0,
171 main_pause=0;
172 unsigned fileNameStart = 0;
173 unsigned nbFiles = 0;
Yann Colletf3eca252015-10-22 15:31:46 +0100174 unsigned cLevel = 0;
Yann Collet4856a002015-01-24 01:58:16 +0100175 const char* programName = argv[0];
176 const char* inFileName = NULL;
177 const char* outFileName = NULL;
178 char* dynNameSpace = NULL;
179 char extension[] = ZSTD_EXTENSION;
180
Yann Colletf44b2b02015-08-25 23:32:45 +0100181 displayOut = stderr;
Yann Collet1c00dc32015-10-21 08:22:25 +0100182 /* Pick out basename component. Don't rely on stdlib because of conflicting behavior. */
Yann Collet17867ce2015-07-07 00:14:27 -0800183 for (i = (int)strlen(programName); i > 0; i--)
Johan Förberg273d0492015-03-24 20:15:56 +0100184 {
Yann Colletf44b2b02015-08-25 23:32:45 +0100185 if (programName[i] == '/') { i++; break; }
Johan Förberg273d0492015-03-24 20:15:56 +0100186 }
187 programName += i;
188
Yann Colletf44b2b02015-08-25 23:32:45 +0100189 /* zstdcat preset behavior */
Yann Collet4856a002015-01-24 01:58:16 +0100190 if (!strcmp(programName, ZSTD_CAT)) { decode=1; forceStdout=1; displayLevel=1; outFileName=stdoutmark; }
191
Yann Colletf44b2b02015-08-25 23:32:45 +0100192 /* unzstd preset behavior */
Johan Förberg273d0492015-03-24 20:15:56 +0100193 if (!strcmp(programName, ZSTD_UNZSTD))
194 decode=1;
195
Yann Colletf44b2b02015-08-25 23:32:45 +0100196 /* command switches */
Yann Collet4856a002015-01-24 01:58:16 +0100197 for(i=1; i<argc; i++)
198 {
199 char* argument = argv[i];
200
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
267 /* Benchmark */
268 case 'b': bench=1; argument++; break;
269
270 /* Modify Nb Iterations (benchmark only) */
271 case 'i':
272 {
273 int iters= 0;
274 argument++;
275 while ((*argument >='0') && (*argument <='9'))
276 iters *= 10, iters += *argument++ - '0';
277 BMK_SetNbIterations(iters);
278 }
279 break;
280
Yann Collet1c00dc32015-10-21 08:22:25 +0100281 /* cut input into blocks (benchmark only) */
282 case 'B':
283 {
284 size_t bSize = 0;
285 argument++;
286 while ((*argument >='0') && (*argument <='9'))
287 bSize *= 10, bSize += *argument++ - '0';
288 if (*argument=='K') bSize<<=10, argument++; /* allows using KB notation */
289 if (*argument=='M') bSize<<=20, argument++;
290 if (*argument=='B') argument++;
291 BMK_SetBlockSize(bSize);
292 }
293 break;
294 /* Pause at the end (hidden option) */
Yann Collet4856a002015-01-24 01:58:16 +0100295 case 'p': main_pause=1; argument++; break;
296
297 /* unknown command */
298 default : return badusage(programName);
299 }
300 }
301 continue;
302 }
303
304 /* first provided filename is input */
305 if (!inFileName) { inFileName = argument; fileNameStart = i; nbFiles = argc-i; continue; }
306
307 /* second provided filename is output */
308 if (!outFileName)
309 {
310 outFileName = argument;
311 if (!strcmp (outFileName, nullString)) outFileName = nulmark;
312 continue;
313 }
314 }
315
316 /* Welcome message (if verbose) */
317 DISPLAYLEVEL(3, WELCOME_MESSAGE);
318
319 /* No input filename ==> use stdin */
320 if(!inFileName) { inFileName=stdinmark; }
321
322 /* Check if input defined as console; trigger an error in this case */
323 if (!strcmp(inFileName, stdinmark) && IS_CONSOLE(stdin) ) return badusage(programName);
324
325 /* Check if benchmark is selected */
Yann Colletf3eca252015-10-22 15:31:46 +0100326 if (bench) { BMK_benchFiles(argv+fileNameStart, nbFiles, cLevel); goto _end; }
Yann Collet4856a002015-01-24 01:58:16 +0100327
328 /* No output filename ==> try to select one automatically (when possible) */
329 while (!outFileName)
330 {
331 if (!IS_CONSOLE(stdout)) { outFileName=stdoutmark; break; } /* Default to stdout whenever possible (i.e. not a console) */
332 if (!decode) /* compression to file */
333 {
334 size_t l = strlen(inFileName);
335 dynNameSpace = (char*)calloc(1,l+5);
Yann Collet94f998b2015-07-04 23:10:40 -0800336 if (dynNameSpace==NULL) { DISPLAY("not enough memory\n"); exit(1); }
Yann Collet4856a002015-01-24 01:58:16 +0100337 strcpy(dynNameSpace, inFileName);
338 strcpy(dynNameSpace+l, ZSTD_EXTENSION);
339 outFileName = dynNameSpace;
340 DISPLAYLEVEL(2, "Compressed filename will be : %s \n", outFileName);
341 break;
342 }
343 /* decompression to file (automatic name will work only if input filename has correct format extension) */
344 {
345 size_t outl;
346 size_t inl = strlen(inFileName);
347 dynNameSpace = (char*)calloc(1,inl+1);
Yann Collet94f998b2015-07-04 23:10:40 -0800348 if (dynNameSpace==NULL) { DISPLAY("not enough memory\n"); exit(1); }
Yann Collet4856a002015-01-24 01:58:16 +0100349 outFileName = dynNameSpace;
350 strcpy(dynNameSpace, inFileName);
351 outl = inl;
352 if (inl>4)
353 while ((outl >= inl-4) && (inFileName[outl] == extension[outl-inl+4])) dynNameSpace[outl--]=0;
354 if (outl != inl-5) { DISPLAYLEVEL(1, "Cannot determine an output filename\n"); return badusage(programName); }
355 DISPLAYLEVEL(2, "Decoding file %s \n", outFileName);
356 }
357 }
358
359 /* Check if output is defined as console; trigger an error in this case */
360 if (!strcmp(outFileName,stdoutmark) && IS_CONSOLE(stdout) && !forceStdout) return badusage(programName);
361
362 /* No warning message in pure pipe mode (stdin + stdout) */
363 if (!strcmp(inFileName, stdinmark) && !strcmp(outFileName,stdoutmark) && (displayLevel==2)) displayLevel=1;
364
365 /* IO Stream/File */
366 FIO_setNotificationLevel(displayLevel);
367 if (decode)
368 FIO_decompressFilename(outFileName, inFileName);
369 else
Yann Colletf3eca252015-10-22 15:31:46 +0100370 FIO_compressFilename(outFileName, inFileName, cLevel);
Yann Collet4856a002015-01-24 01:58:16 +0100371
372_end:
373 if (main_pause) waitEnter();
374 free(dynNameSpace);
375 return 0;
376}