2 * Copyright (c) 2016-present, Facebook, Inc.
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree. An additional grant
7 * of patent rights can be found in the PATENTS file in the same directory.
10 #include "utils/ScopeGuard.h"
21 #if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(_WIN32) || \
23 #include <io.h> /* _isatty */
24 #define IS_CONSOLE(stdStream) _isatty(_fileno(stdStream))
25 #elif defined(_POSIX_C_SOURCE) || defined(_XOPEN_SOURCE) || defined(_POSIX_SOURCE) || (defined(__APPLE__) && defined(__MACH__)) || \
26 defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) /* https://sourceforge.net/p/predef/wiki/OperatingSystems/ */
27 #include <unistd.h> /* isatty */
28 #define IS_CONSOLE(stdStream) isatty(fileno(stdStream))
30 #define IS_CONSOLE(stdStream) 0
36 unsigned defaultNumThreads() {
37 #ifdef PZSTD_NUM_THREADS
38 return PZSTD_NUM_THREADS
;
40 return std::thread::hardware_concurrency();
44 unsigned parseUnsigned(const char **arg
) {
46 while (**arg
>= '0' && **arg
<= '9') {
48 result
+= **arg
- '0';
54 const char *getArgument(const char *options
, const char **argv
, int &i
,
56 if (options
[1] != 0) {
61 std::fprintf(stderr
, "Option -%c requires an argument, but none provided\n",
68 const std::string kZstdExtension
= ".zst";
69 constexpr char kStdIn
[] = "-";
70 constexpr char kStdOut
[] = "-";
71 constexpr unsigned kDefaultCompressionLevel
= 3;
72 constexpr unsigned kMaxNonUltraCompressionLevel
= 19;
75 const char nullOutput
[] = "nul";
77 const char nullOutput
[] = "/dev/null";
80 void notSupported(const char *option
) {
81 std::fprintf(stderr
, "Operation not supported: %s\n", option
);
85 std::fprintf(stderr
, "Usage:\n");
86 std::fprintf(stderr
, " pzstd [args] [FILE(s)]\n");
87 std::fprintf(stderr
, "Parallel ZSTD options:\n");
88 std::fprintf(stderr
, " -p, --processes # : number of threads to use for (de)compression (default:%d)\n", defaultNumThreads());
90 std::fprintf(stderr
, "ZSTD options:\n");
91 std::fprintf(stderr
, " -# : # compression level (1-%d, default:%d)\n", kMaxNonUltraCompressionLevel
, kDefaultCompressionLevel
);
92 std::fprintf(stderr
, " -d, --decompress : decompression\n");
93 std::fprintf(stderr
, " -o file : result stored into `file` (only if 1 input file)\n");
94 std::fprintf(stderr
, " -f, --force : overwrite output without prompting\n");
95 std::fprintf(stderr
, " --rm : remove source file(s) after successful (de)compression\n");
96 std::fprintf(stderr
, " -k, --keep : preserve source file(s) (default)\n");
97 std::fprintf(stderr
, " -h, --help : display help and exit\n");
98 std::fprintf(stderr
, " -V, --version : display version number and exit\n");
99 std::fprintf(stderr
, " -v, --verbose : verbose mode; specify multiple times to increase log level (default:2)\n");
100 std::fprintf(stderr
, " -q, --quiet : suppress warnings; specify twice to suppress errors too\n");
101 std::fprintf(stderr
, " -c, --stdout : force write to standard output, even if it is the console\n");
102 #ifdef UTIL_HAS_CREATEFILELIST
103 std::fprintf(stderr
, " -r : operate recursively on directories\n");
105 std::fprintf(stderr
, " --ultra : enable levels beyond %i, up to %i (requires more memory)\n", kMaxNonUltraCompressionLevel
, ZSTD_maxCLevel());
106 std::fprintf(stderr
, " -C, --check : integrity check (default)\n");
107 std::fprintf(stderr
, " --no-check : no integrity check\n");
108 std::fprintf(stderr
, " -t, --test : test compressed file integrity\n");
109 std::fprintf(stderr
, " -- : all arguments after \"--\" are treated as files\n");
111 } // anonymous namespace
114 : numThreads(defaultNumThreads()), maxWindowLog(23),
115 compressionLevel(kDefaultCompressionLevel
), decompress(false),
116 overwrite(false), keepSource(true), writeMode(WriteMode::Auto
),
117 checksum(true), verbosity(2) {}
119 Options::Status
Options::parse(int argc
, const char **argv
) {
121 bool recursive
= false;
123 bool forceStdout
= false;
124 // Local copy of input files, which are pointers into argv.
125 std::vector
<const char *> localInputFiles
;
126 for (int i
= 1; i
< argc
; ++i
) {
127 const char *arg
= argv
[i
];
128 // Protect against empty arguments
132 // Everything after "--" is an input file
133 if (!std::strcmp(arg
, "--")) {
135 std::copy(argv
+ i
, argv
+ argc
, std::back_inserter(localInputFiles
));
138 // Long arguments that don't have a short option
140 bool isLongOption
= true;
141 if (!std::strcmp(arg
, "--rm")) {
143 } else if (!std::strcmp(arg
, "--ultra")) {
146 } else if (!std::strcmp(arg
, "--no-check")) {
148 } else if (!std::strcmp(arg
, "--sparse")) {
149 writeMode
= WriteMode::Sparse
;
150 notSupported("Sparse mode");
151 return Status::Failure
;
152 } else if (!std::strcmp(arg
, "--no-sparse")) {
153 writeMode
= WriteMode::Regular
;
154 notSupported("Sparse mode");
155 return Status::Failure
;
156 } else if (!std::strcmp(arg
, "--dictID")) {
158 return Status::Failure
;
159 } else if (!std::strcmp(arg
, "--no-dictID")) {
161 return Status::Failure
;
163 isLongOption
= false;
169 // Arguments with a short option simply set their short option.
170 const char *options
= nullptr;
171 if (!std::strcmp(arg
, "--processes")) {
173 } else if (!std::strcmp(arg
, "--version")) {
175 } else if (!std::strcmp(arg
, "--help")) {
177 } else if (!std::strcmp(arg
, "--decompress")) {
179 } else if (!std::strcmp(arg
, "--force")) {
181 } else if (!std::strcmp(arg
, "--stdout")) {
183 } else if (!std::strcmp(arg
, "--keep")) {
185 } else if (!std::strcmp(arg
, "--verbose")) {
187 } else if (!std::strcmp(arg
, "--quiet")) {
189 } else if (!std::strcmp(arg
, "--check")) {
191 } else if (!std::strcmp(arg
, "--test")) {
193 } else if (arg
[0] == '-' && arg
[1] != 0) {
196 localInputFiles
.emplace_back(arg
);
199 assert(options
!= nullptr);
201 bool finished
= false;
202 while (!finished
&& *options
!= 0) {
203 // Parse the compression level
204 if (*options
>= '0' && *options
<= '9') {
205 compressionLevel
= parseUnsigned(&options
);
213 return Status::Message
;
215 std::fprintf(stderr
, "PZSTD version: %s.\n", ZSTD_VERSION_STRING
);
216 return Status::Message
;
219 const char *optionArgument
= getArgument(options
, argv
, i
, argc
);
220 if (optionArgument
== nullptr) {
221 return Status::Failure
;
223 if (*optionArgument
< '0' || *optionArgument
> '9') {
224 std::fprintf(stderr
, "Option -p expects a number, but %s provided\n",
226 return Status::Failure
;
228 numThreads
= parseUnsigned(&optionArgument
);
229 if (*optionArgument
!= 0) {
231 "Option -p expects a number, but %u%s provided\n",
232 numThreads
, optionArgument
);
233 return Status::Failure
;
239 const char *optionArgument
= getArgument(options
, argv
, i
, argc
);
240 if (optionArgument
== nullptr) {
241 return Status::Failure
;
243 outputFile
= optionArgument
;
263 #ifdef UTIL_HAS_CREATEFILELIST
269 outputFile
= kStdOut
;
277 // Ignore them for now
279 // Unsupported options from Zstd
282 notSupported("Zstd dictionaries.");
283 return Status::Failure
;
288 notSupported("Zstd benchmarking options.");
289 return Status::Failure
;
291 std::fprintf(stderr
, "Invalid argument: %s\n", arg
);
292 return Status::Failure
;
297 } // while (*options != 0);
298 } // for (int i = 1; i < argc; ++i);
300 // Set options for test mode
302 outputFile
= nullOutput
;
306 // Input file defaults to standard input if not provided.
307 if (localInputFiles
.empty()) {
308 localInputFiles
.emplace_back(kStdIn
);
311 // Check validity of input files
312 if (localInputFiles
.size() > 1) {
313 const auto it
= std::find(localInputFiles
.begin(), localInputFiles
.end(),
314 std::string
{kStdIn
});
315 if (it
!= localInputFiles
.end()) {
318 "Cannot specify standard input when handling multiple files\n");
319 return Status::Failure
;
322 if (localInputFiles
.size() > 1 || recursive
) {
323 if (!outputFile
.empty() && outputFile
!= nullOutput
) {
326 "Cannot specify an output file when handling multiple inputs\n");
327 return Status::Failure
;
331 // Translate input files/directories into files to (de)compress
333 char *scratchBuffer
= nullptr;
334 unsigned numFiles
= 0;
336 UTIL_createFileList(localInputFiles
.data(), localInputFiles
.size(),
337 &scratchBuffer
, &numFiles
);
338 if (files
== nullptr) {
339 std::fprintf(stderr
, "Error traversing directories\n");
340 return Status::Failure
;
343 makeScopeGuard([&] { UTIL_freeFileList(files
, scratchBuffer
); });
345 std::fprintf(stderr
, "No files found\n");
346 return Status::Failure
;
348 inputFiles
.resize(numFiles
);
349 std::copy(files
, files
+ numFiles
, inputFiles
.begin());
351 inputFiles
.resize(localInputFiles
.size());
352 std::copy(localInputFiles
.begin(), localInputFiles
.end(),
355 localInputFiles
.clear();
356 assert(!inputFiles
.empty());
358 // If reading from standard input, default to standard output
359 if (inputFiles
[0] == kStdIn
&& outputFile
.empty()) {
360 assert(inputFiles
.size() == 1);
364 if (inputFiles
[0] == kStdIn
&& IS_CONSOLE(stdin
)) {
365 assert(inputFiles
.size() == 1);
366 std::fprintf(stderr
, "Cannot read input from interactive console\n");
367 return Status::Failure
;
369 if (outputFile
== "-" && IS_CONSOLE(stdout
) && !(forceStdout
&& decompress
)) {
370 std::fprintf(stderr
, "Will not write to console stdout unless -c or -f is "
371 "specified and decompressing\n");
372 return Status::Failure
;
375 // Check compression level
378 ultra
? ZSTD_maxCLevel() : kMaxNonUltraCompressionLevel
;
379 if (compressionLevel
> maxCLevel
|| compressionLevel
== 0) {
380 std::fprintf(stderr
, "Invalid compression level %u.\n", compressionLevel
);
381 return Status::Failure
;
385 // Check that numThreads is set
386 if (numThreads
== 0) {
387 std::fprintf(stderr
, "Invalid arguments: # of threads not specified "
388 "and unable to determine hardware concurrency.\n");
389 return Status::Failure
;
393 // If we are piping input and output, turn off interaction
394 if (inputFiles
[0] == kStdIn
&& outputFile
== kStdOut
&& verbosity
== 2) {
397 // If we are in multi-file mode, turn off interaction
398 if (inputFiles
.size() > 1 && verbosity
== 2) {
402 return Status::Success
;
405 std::string
Options::getOutputFile(const std::string
&inputFile
) const {
406 if (!outputFile
.empty()) {
409 // Attempt to add/remove zstd extension from the input file
411 int stemSize
= inputFile
.size() - kZstdExtension
.size();
412 if (stemSize
> 0 && inputFile
.substr(stemSize
) == kZstdExtension
) {
413 return inputFile
.substr(0, stemSize
);
418 return inputFile
+ kZstdExtension
;