git subrepo pull (merge) --force deps/libchdr
[pcsx_rearmed.git] / deps / libchdr / deps / zstd-1.5.5 / contrib / pzstd / Options.cpp
CommitLineData
648db22b 1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under both the BSD-style license (found in the
6 * LICENSE file in the root directory of this source tree) and the GPLv2 (found
7 * in the COPYING file in the root directory of this source tree).
8 */
9#include "Options.h"
10#include "util.h"
11#include "utils/ScopeGuard.h"
12
13#include <algorithm>
14#include <cassert>
15#include <cstdio>
16#include <cstring>
17#include <iterator>
18#include <thread>
19#include <vector>
20
21
22namespace pzstd {
23
24namespace {
25unsigned defaultNumThreads() {
26#ifdef PZSTD_NUM_THREADS
27 return PZSTD_NUM_THREADS;
28#else
29 return std::thread::hardware_concurrency();
30#endif
31}
32
33unsigned parseUnsigned(const char **arg) {
34 unsigned result = 0;
35 while (**arg >= '0' && **arg <= '9') {
36 result *= 10;
37 result += **arg - '0';
38 ++(*arg);
39 }
40 return result;
41}
42
43const char *getArgument(const char *options, const char **argv, int &i,
44 int argc) {
45 if (options[1] != 0) {
46 return options + 1;
47 }
48 ++i;
49 if (i == argc) {
50 std::fprintf(stderr, "Option -%c requires an argument, but none provided\n",
51 *options);
52 return nullptr;
53 }
54 return argv[i];
55}
56
57const std::string kZstdExtension = ".zst";
58constexpr char kStdIn[] = "-";
59constexpr char kStdOut[] = "-";
60constexpr unsigned kDefaultCompressionLevel = 3;
61constexpr unsigned kMaxNonUltraCompressionLevel = 19;
62
63#ifdef _WIN32
64const char nullOutput[] = "nul";
65#else
66const char nullOutput[] = "/dev/null";
67#endif
68
69void notSupported(const char *option) {
70 std::fprintf(stderr, "Operation not supported: %s\n", option);
71}
72
73void usage() {
74 std::fprintf(stderr, "Usage:\n");
75 std::fprintf(stderr, " pzstd [args] [FILE(s)]\n");
76 std::fprintf(stderr, "Parallel ZSTD options:\n");
77 std::fprintf(stderr, " -p, --processes # : number of threads to use for (de)compression (default:<numcpus>)\n");
78
79 std::fprintf(stderr, "ZSTD options:\n");
80 std::fprintf(stderr, " -# : # compression level (1-%d, default:%d)\n", kMaxNonUltraCompressionLevel, kDefaultCompressionLevel);
81 std::fprintf(stderr, " -d, --decompress : decompression\n");
82 std::fprintf(stderr, " -o file : result stored into `file` (only if 1 input file)\n");
83 std::fprintf(stderr, " -f, --force : overwrite output without prompting, (de)compress links\n");
84 std::fprintf(stderr, " --rm : remove source file(s) after successful (de)compression\n");
85 std::fprintf(stderr, " -k, --keep : preserve source file(s) (default)\n");
86 std::fprintf(stderr, " -h, --help : display help and exit\n");
87 std::fprintf(stderr, " -V, --version : display version number and exit\n");
88 std::fprintf(stderr, " -v, --verbose : verbose mode; specify multiple times to increase log level (default:2)\n");
89 std::fprintf(stderr, " -q, --quiet : suppress warnings; specify twice to suppress errors too\n");
90 std::fprintf(stderr, " -c, --stdout : write to standard output (even if it is the console)\n");
91#ifdef UTIL_HAS_CREATEFILELIST
92 std::fprintf(stderr, " -r : operate recursively on directories\n");
93#endif
94 std::fprintf(stderr, " --ultra : enable levels beyond %i, up to %i (requires more memory)\n", kMaxNonUltraCompressionLevel, ZSTD_maxCLevel());
95 std::fprintf(stderr, " -C, --check : integrity check (default)\n");
96 std::fprintf(stderr, " --no-check : no integrity check\n");
97 std::fprintf(stderr, " -t, --test : test compressed file integrity\n");
98 std::fprintf(stderr, " -- : all arguments after \"--\" are treated as files\n");
99}
100} // anonymous namespace
101
102Options::Options()
103 : numThreads(defaultNumThreads()), maxWindowLog(23),
104 compressionLevel(kDefaultCompressionLevel), decompress(false),
105 overwrite(false), keepSource(true), writeMode(WriteMode::Auto),
106 checksum(true), verbosity(2) {}
107
108Options::Status Options::parse(int argc, const char **argv) {
109 bool test = false;
110 bool recursive = false;
111 bool ultra = false;
112 bool forceStdout = false;
113 bool followLinks = false;
114 // Local copy of input files, which are pointers into argv.
115 std::vector<const char *> localInputFiles;
116 for (int i = 1; i < argc; ++i) {
117 const char *arg = argv[i];
118 // Protect against empty arguments
119 if (arg[0] == 0) {
120 continue;
121 }
122 // Everything after "--" is an input file
123 if (!std::strcmp(arg, "--")) {
124 ++i;
125 std::copy(argv + i, argv + argc, std::back_inserter(localInputFiles));
126 break;
127 }
128 // Long arguments that don't have a short option
129 {
130 bool isLongOption = true;
131 if (!std::strcmp(arg, "--rm")) {
132 keepSource = false;
133 } else if (!std::strcmp(arg, "--ultra")) {
134 ultra = true;
135 maxWindowLog = 0;
136 } else if (!std::strcmp(arg, "--no-check")) {
137 checksum = false;
138 } else if (!std::strcmp(arg, "--sparse")) {
139 writeMode = WriteMode::Sparse;
140 notSupported("Sparse mode");
141 return Status::Failure;
142 } else if (!std::strcmp(arg, "--no-sparse")) {
143 writeMode = WriteMode::Regular;
144 notSupported("Sparse mode");
145 return Status::Failure;
146 } else if (!std::strcmp(arg, "--dictID")) {
147 notSupported(arg);
148 return Status::Failure;
149 } else if (!std::strcmp(arg, "--no-dictID")) {
150 notSupported(arg);
151 return Status::Failure;
152 } else {
153 isLongOption = false;
154 }
155 if (isLongOption) {
156 continue;
157 }
158 }
159 // Arguments with a short option simply set their short option.
160 const char *options = nullptr;
161 if (!std::strcmp(arg, "--processes")) {
162 options = "p";
163 } else if (!std::strcmp(arg, "--version")) {
164 options = "V";
165 } else if (!std::strcmp(arg, "--help")) {
166 options = "h";
167 } else if (!std::strcmp(arg, "--decompress")) {
168 options = "d";
169 } else if (!std::strcmp(arg, "--force")) {
170 options = "f";
171 } else if (!std::strcmp(arg, "--stdout")) {
172 options = "c";
173 } else if (!std::strcmp(arg, "--keep")) {
174 options = "k";
175 } else if (!std::strcmp(arg, "--verbose")) {
176 options = "v";
177 } else if (!std::strcmp(arg, "--quiet")) {
178 options = "q";
179 } else if (!std::strcmp(arg, "--check")) {
180 options = "C";
181 } else if (!std::strcmp(arg, "--test")) {
182 options = "t";
183 } else if (arg[0] == '-' && arg[1] != 0) {
184 options = arg + 1;
185 } else {
186 localInputFiles.emplace_back(arg);
187 continue;
188 }
189 assert(options != nullptr);
190
191 bool finished = false;
192 while (!finished && *options != 0) {
193 // Parse the compression level
194 if (*options >= '0' && *options <= '9') {
195 compressionLevel = parseUnsigned(&options);
196 continue;
197 }
198
199 switch (*options) {
200 case 'h':
201 case 'H':
202 usage();
203 return Status::Message;
204 case 'V':
205 std::fprintf(stderr, "PZSTD version: %s.\n", ZSTD_VERSION_STRING);
206 return Status::Message;
207 case 'p': {
208 finished = true;
209 const char *optionArgument = getArgument(options, argv, i, argc);
210 if (optionArgument == nullptr) {
211 return Status::Failure;
212 }
213 if (*optionArgument < '0' || *optionArgument > '9') {
214 std::fprintf(stderr, "Option -p expects a number, but %s provided\n",
215 optionArgument);
216 return Status::Failure;
217 }
218 numThreads = parseUnsigned(&optionArgument);
219 if (*optionArgument != 0) {
220 std::fprintf(stderr,
221 "Option -p expects a number, but %u%s provided\n",
222 numThreads, optionArgument);
223 return Status::Failure;
224 }
225 break;
226 }
227 case 'o': {
228 finished = true;
229 const char *optionArgument = getArgument(options, argv, i, argc);
230 if (optionArgument == nullptr) {
231 return Status::Failure;
232 }
233 outputFile = optionArgument;
234 break;
235 }
236 case 'C':
237 checksum = true;
238 break;
239 case 'k':
240 keepSource = true;
241 break;
242 case 'd':
243 decompress = true;
244 break;
245 case 'f':
246 overwrite = true;
247 forceStdout = true;
248 followLinks = true;
249 break;
250 case 't':
251 test = true;
252 decompress = true;
253 break;
254#ifdef UTIL_HAS_CREATEFILELIST
255 case 'r':
256 recursive = true;
257 break;
258#endif
259 case 'c':
260 outputFile = kStdOut;
261 forceStdout = true;
262 break;
263 case 'v':
264 ++verbosity;
265 break;
266 case 'q':
267 --verbosity;
268 // Ignore them for now
269 break;
270 // Unsupported options from Zstd
271 case 'D':
272 case 's':
273 notSupported("Zstd dictionaries.");
274 return Status::Failure;
275 case 'b':
276 case 'e':
277 case 'i':
278 case 'B':
279 notSupported("Zstd benchmarking options.");
280 return Status::Failure;
281 default:
282 std::fprintf(stderr, "Invalid argument: %s\n", arg);
283 return Status::Failure;
284 }
285 if (!finished) {
286 ++options;
287 }
288 } // while (*options != 0);
289 } // for (int i = 1; i < argc; ++i);
290
291 // Set options for test mode
292 if (test) {
293 outputFile = nullOutput;
294 keepSource = true;
295 }
296
297 // Input file defaults to standard input if not provided.
298 if (localInputFiles.empty()) {
299 localInputFiles.emplace_back(kStdIn);
300 }
301
302 // Check validity of input files
303 if (localInputFiles.size() > 1) {
304 const auto it = std::find(localInputFiles.begin(), localInputFiles.end(),
305 std::string{kStdIn});
306 if (it != localInputFiles.end()) {
307 std::fprintf(
308 stderr,
309 "Cannot specify standard input when handling multiple files\n");
310 return Status::Failure;
311 }
312 }
313 if (localInputFiles.size() > 1 || recursive) {
314 if (!outputFile.empty() && outputFile != nullOutput) {
315 std::fprintf(
316 stderr,
317 "Cannot specify an output file when handling multiple inputs\n");
318 return Status::Failure;
319 }
320 }
321
322 g_utilDisplayLevel = verbosity;
323 // Remove local input files that are symbolic links
324 if (!followLinks) {
325 std::remove_if(localInputFiles.begin(), localInputFiles.end(),
326 [&](const char *path) {
327 bool isLink = UTIL_isLink(path);
328 if (isLink && verbosity >= 2) {
329 std::fprintf(
330 stderr,
331 "Warning : %s is symbolic link, ignoring\n",
332 path);
333 }
334 return isLink;
335 });
336 }
337
338 // Translate input files/directories into files to (de)compress
339 if (recursive) {
340 FileNamesTable* const files = UTIL_createExpandedFNT(localInputFiles.data(), localInputFiles.size(), followLinks);
341 if (files == nullptr) {
342 std::fprintf(stderr, "Error traversing directories\n");
343 return Status::Failure;
344 }
345 auto guard =
346 makeScopeGuard([&] { UTIL_freeFileNamesTable(files); });
347 if (files->tableSize == 0) {
348 std::fprintf(stderr, "No files found\n");
349 return Status::Failure;
350 }
351 inputFiles.resize(files->tableSize);
352 std::copy(files->fileNames, files->fileNames + files->tableSize, inputFiles.begin());
353 } else {
354 inputFiles.resize(localInputFiles.size());
355 std::copy(localInputFiles.begin(), localInputFiles.end(),
356 inputFiles.begin());
357 }
358 localInputFiles.clear();
359 assert(!inputFiles.empty());
360
361 // If reading from standard input, default to standard output
362 if (inputFiles[0] == kStdIn && outputFile.empty()) {
363 assert(inputFiles.size() == 1);
364 outputFile = "-";
365 }
366
367 if (inputFiles[0] == kStdIn && IS_CONSOLE(stdin)) {
368 assert(inputFiles.size() == 1);
369 std::fprintf(stderr, "Cannot read input from interactive console\n");
370 return Status::Failure;
371 }
372 if (outputFile == "-" && IS_CONSOLE(stdout) && !(forceStdout && decompress)) {
373 std::fprintf(stderr, "Will not write to console stdout unless -c or -f is "
374 "specified and decompressing\n");
375 return Status::Failure;
376 }
377
378 // Check compression level
379 {
380 unsigned maxCLevel =
381 ultra ? ZSTD_maxCLevel() : kMaxNonUltraCompressionLevel;
382 if (compressionLevel > maxCLevel || compressionLevel == 0) {
383 std::fprintf(stderr, "Invalid compression level %u.\n", compressionLevel);
384 return Status::Failure;
385 }
386 }
387
388 // Check that numThreads is set
389 if (numThreads == 0) {
390 std::fprintf(stderr, "Invalid arguments: # of threads not specified "
391 "and unable to determine hardware concurrency.\n");
392 return Status::Failure;
393 }
394
395 // Modify verbosity
396 // If we are piping input and output, turn off interaction
397 if (inputFiles[0] == kStdIn && outputFile == kStdOut && verbosity == 2) {
398 verbosity = 1;
399 }
400 // If we are in multi-file mode, turn off interaction
401 if (inputFiles.size() > 1 && verbosity == 2) {
402 verbosity = 1;
403 }
404
405 return Status::Success;
406}
407
408std::string Options::getOutputFile(const std::string &inputFile) const {
409 if (!outputFile.empty()) {
410 return outputFile;
411 }
412 // Attempt to add/remove zstd extension from the input file
413 if (decompress) {
414 int stemSize = inputFile.size() - kZstdExtension.size();
415 if (stemSize > 0 && inputFile.substr(stemSize) == kZstdExtension) {
416 return inputFile.substr(0, stemSize);
417 } else {
418 return "";
419 }
420 } else {
421 return inputFile + kZstdExtension;
422 }
423}
424}