blob: f3780a49fafee5011aa7fe9d8e6fe7364e72e877 [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");
121 DISPLAY( " -d : decompression (default for %s extension)\n", ZSTD_EXTENSION);
122 //DISPLAY( " -z : force compression\n");
123 DISPLAY( " -f : overwrite output without prompting \n");
124 DISPLAY( " -h/-H : display help/long help and exit\n");
125 return 0;
126}
127
128static int usage_advanced(const char* programName)
129{
130 DISPLAY(WELCOME_MESSAGE);
131 usage(programName);
132 DISPLAY( "\n");
133 DISPLAY( "Advanced arguments :\n");
134 DISPLAY( " -V : display Version number and exit\n");
135 DISPLAY( " -v : verbose mode\n");
136 DISPLAY( " -q : suppress warnings; specify twice to suppress errors too\n");
137 DISPLAY( " -c : force write to standard output, even if it is the console\n");
138 //DISPLAY( " -t : test compressed file integrity\n");
139 DISPLAY( "Benchmark arguments :\n");
140 DISPLAY( " -b : benchmark file(s)\n");
Yann Colletf44b2b02015-08-25 23:32:45 +0100141 DISPLAY( " -i# : iteration loops [1-9](default : 3)\n");
Yann Collet4856a002015-01-24 01:58:16 +0100142 return 0;
143}
144
145static int badusage(const char* programName)
146{
147 DISPLAYLEVEL(1, "Incorrect parameters\n");
148 if (displayLevel >= 1) usage(programName);
149 return 1;
150}
151
152
153static void waitEnter(void)
154{
Yann Colletb5e06dc2015-07-04 23:20:56 -0800155 int unused;
Yann Collet4856a002015-01-24 01:58:16 +0100156 DISPLAY("Press enter to continue...\n");
Yann Colletb5e06dc2015-07-04 23:20:56 -0800157 unused = getchar();
158 (void)unused;
Yann Collet4856a002015-01-24 01:58:16 +0100159}
160
161
162int main(int argc, char** argv)
163{
164 int i,
165 bench=0,
166 decode=0,
167 forceStdout=0,
168 main_pause=0;
169 unsigned fileNameStart = 0;
170 unsigned nbFiles = 0;
171 const char* programName = argv[0];
172 const char* inFileName = NULL;
173 const char* outFileName = NULL;
174 char* dynNameSpace = NULL;
175 char extension[] = ZSTD_EXTENSION;
176
Yann Colletf44b2b02015-08-25 23:32:45 +0100177 displayOut = stderr;
Johan Förberg273d0492015-03-24 20:15:56 +0100178 /* Pick out basename component. Don't rely on stdlib because of conflicting behaviour. */
Yann Collet17867ce2015-07-07 00:14:27 -0800179 for (i = (int)strlen(programName); i > 0; i--)
Johan Förberg273d0492015-03-24 20:15:56 +0100180 {
Yann Colletf44b2b02015-08-25 23:32:45 +0100181 if (programName[i] == '/') { i++; break; }
Johan Förberg273d0492015-03-24 20:15:56 +0100182 }
183 programName += i;
184
Yann Colletf44b2b02015-08-25 23:32:45 +0100185 /* zstdcat preset behavior */
Yann Collet4856a002015-01-24 01:58:16 +0100186 if (!strcmp(programName, ZSTD_CAT)) { decode=1; forceStdout=1; displayLevel=1; outFileName=stdoutmark; }
187
Yann Colletf44b2b02015-08-25 23:32:45 +0100188 /* unzstd preset behavior */
Johan Förberg273d0492015-03-24 20:15:56 +0100189 if (!strcmp(programName, ZSTD_UNZSTD))
190 decode=1;
191
Yann Colletf44b2b02015-08-25 23:32:45 +0100192 /* command switches */
Yann Collet4856a002015-01-24 01:58:16 +0100193 for(i=1; i<argc; i++)
194 {
195 char* argument = argv[i];
196
Yann Colletf44b2b02015-08-25 23:32:45 +0100197 if(!argument) continue; /* Protection if argument empty */
198
199 /* long commands (--long-word) */
200 if (!strcmp(argument, "--version")) { displayOut=stdout; DISPLAY(WELCOME_MESSAGE); return 0; }
201 if (!strcmp(argument, "--help")) { displayOut=stdout; return usage_advanced(programName); }
Yann Collet4856a002015-01-24 01:58:16 +0100202
203 /* Decode commands (note : aggregated commands are allowed) */
204 if (argument[0]=='-')
205 {
206 /* '-' means stdin/stdout */
207 if (argument[1]==0)
208 {
209 if (!inFileName) inFileName=stdinmark;
210 else outFileName=stdoutmark;
211 }
212
213 argument++;
214
215 while (argument[0]!=0)
216 {
217 switch(argument[0])
218 {
219 /* Display help */
Yann Colletf44b2b02015-08-25 23:32:45 +0100220 case 'V': displayOut=stdout; DISPLAY(WELCOME_MESSAGE); return 0; /* Version Only */
Yann Collet4856a002015-01-24 01:58:16 +0100221 case 'H':
Yann Colletf44b2b02015-08-25 23:32:45 +0100222 case 'h': displayOut=stdout; return usage_advanced(programName);
Yann Collet4856a002015-01-24 01:58:16 +0100223
224 // Compression (default)
225 //case 'z': forceCompress = 1; break;
226
227 // Decoding
228 case 'd': decode=1; argument++; break;
229
230 // Force stdout, even if stdout==console
231 case 'c': forceStdout=1; outFileName=stdoutmark; displayLevel=1; argument++; break;
232
233 // Test
234 //case 't': decode=1; LZ4IO_setOverwrite(1); output_filename=nulmark; break;
235
236 /* Overwrite */
237 case 'f': FIO_overwriteMode(); argument++; break;
238
239 /* Verbose mode */
240 case 'v': displayLevel=4; argument++; break;
241
242 /* Quiet mode */
243 case 'q': displayLevel--; argument++; break;
244
245 /* keep source file (default anyway, so useless; only for xz/lzma compatibility) */
246 case 'k': argument++; break;
247
248 /* Benchmark */
249 case 'b': bench=1; argument++; break;
250
251 /* Modify Nb Iterations (benchmark only) */
252 case 'i':
253 {
254 int iters= 0;
255 argument++;
256 while ((*argument >='0') && (*argument <='9'))
257 iters *= 10, iters += *argument++ - '0';
258 BMK_SetNbIterations(iters);
259 }
260 break;
261
262 /* Pause at the end (hidden option) */
263 case 'p': main_pause=1; argument++; break;
264
265 /* unknown command */
266 default : return badusage(programName);
267 }
268 }
269 continue;
270 }
271
272 /* first provided filename is input */
273 if (!inFileName) { inFileName = argument; fileNameStart = i; nbFiles = argc-i; continue; }
274
275 /* second provided filename is output */
276 if (!outFileName)
277 {
278 outFileName = argument;
279 if (!strcmp (outFileName, nullString)) outFileName = nulmark;
280 continue;
281 }
282 }
283
284 /* Welcome message (if verbose) */
285 DISPLAYLEVEL(3, WELCOME_MESSAGE);
286
287 /* No input filename ==> use stdin */
288 if(!inFileName) { inFileName=stdinmark; }
289
290 /* Check if input defined as console; trigger an error in this case */
291 if (!strcmp(inFileName, stdinmark) && IS_CONSOLE(stdin) ) return badusage(programName);
292
293 /* Check if benchmark is selected */
Yann Collet213089c2015-06-18 07:43:16 -0800294 if (bench) { BMK_benchFiles(argv+fileNameStart, nbFiles, 0); goto _end; }
Yann Collet4856a002015-01-24 01:58:16 +0100295
296 /* No output filename ==> try to select one automatically (when possible) */
297 while (!outFileName)
298 {
299 if (!IS_CONSOLE(stdout)) { outFileName=stdoutmark; break; } /* Default to stdout whenever possible (i.e. not a console) */
300 if (!decode) /* compression to file */
301 {
302 size_t l = strlen(inFileName);
303 dynNameSpace = (char*)calloc(1,l+5);
Yann Collet94f998b2015-07-04 23:10:40 -0800304 if (dynNameSpace==NULL) { DISPLAY("not enough memory\n"); exit(1); }
Yann Collet4856a002015-01-24 01:58:16 +0100305 strcpy(dynNameSpace, inFileName);
306 strcpy(dynNameSpace+l, ZSTD_EXTENSION);
307 outFileName = dynNameSpace;
308 DISPLAYLEVEL(2, "Compressed filename will be : %s \n", outFileName);
309 break;
310 }
311 /* decompression to file (automatic name will work only if input filename has correct format extension) */
312 {
313 size_t outl;
314 size_t inl = strlen(inFileName);
315 dynNameSpace = (char*)calloc(1,inl+1);
Yann Collet94f998b2015-07-04 23:10:40 -0800316 if (dynNameSpace==NULL) { DISPLAY("not enough memory\n"); exit(1); }
Yann Collet4856a002015-01-24 01:58:16 +0100317 outFileName = dynNameSpace;
318 strcpy(dynNameSpace, inFileName);
319 outl = inl;
320 if (inl>4)
321 while ((outl >= inl-4) && (inFileName[outl] == extension[outl-inl+4])) dynNameSpace[outl--]=0;
322 if (outl != inl-5) { DISPLAYLEVEL(1, "Cannot determine an output filename\n"); return badusage(programName); }
323 DISPLAYLEVEL(2, "Decoding file %s \n", outFileName);
324 }
325 }
326
327 /* Check if output is defined as console; trigger an error in this case */
328 if (!strcmp(outFileName,stdoutmark) && IS_CONSOLE(stdout) && !forceStdout) return badusage(programName);
329
330 /* No warning message in pure pipe mode (stdin + stdout) */
331 if (!strcmp(inFileName, stdinmark) && !strcmp(outFileName,stdoutmark) && (displayLevel==2)) displayLevel=1;
332
333 /* IO Stream/File */
334 FIO_setNotificationLevel(displayLevel);
335 if (decode)
336 FIO_decompressFilename(outFileName, inFileName);
337 else
338 FIO_compressFilename(outFileName, inFileName);
339
340_end:
341 if (main_pause) waitEnter();
342 free(dynNameSpace);
343 return 0;
344}