xref: /src/sys/contrib/zstd/programs/fileio.c (revision c0d9a07101a1e72769ee0619a583f63a078fb391)
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  * You may select, at your option, one of the above-listed licenses.
9  */
10 
11 
12 /* *************************************
13 *  Compiler Options
14 ***************************************/
15 #ifdef _MSC_VER   /* Visual */
16 #  pragma warning(disable : 4127)  /* disable: C4127: conditional expression is constant */
17 #  pragma warning(disable : 4204)  /* non-constant aggregate initializer */
18 #endif
19 #if defined(__MINGW32__) && !defined(_POSIX_SOURCE)
20 #  define _POSIX_SOURCE 1          /* disable %llu warnings with MinGW on Windows */
21 #endif
22 
23 /*-*************************************
24 *  Includes
25 ***************************************/
26 #include "platform.h"   /* Large Files support, SET_BINARY_MODE */
27 #include "util.h"       /* UTIL_getFileSize, UTIL_isRegularFile, UTIL_isSameFile */
28 #include <stdio.h>      /* fprintf, open, fdopen, fread, _fileno, stdin, stdout */
29 #include <stdlib.h>     /* malloc, free */
30 #include <string.h>     /* strcmp, strlen */
31 #include <time.h>       /* clock_t, to measure process time */
32 #include <fcntl.h>      /* O_WRONLY */
33 #include <assert.h>
34 #include <errno.h>      /* errno */
35 #include <limits.h>     /* INT_MAX */
36 #include <signal.h>
37 #include "timefn.h"     /* UTIL_getTime, UTIL_clockSpanMicro */
38 
39 #if defined (_MSC_VER)
40 #  include <sys/stat.h>
41 #  include <io.h>
42 #endif
43 
44 #include "fileio.h"
45 #include "fileio_asyncio.h"
46 #include "fileio_common.h"
47 
48 FIO_display_prefs_t g_display_prefs = {2, FIO_ps_auto};
49 UTIL_time_t g_displayClock = UTIL_TIME_INITIALIZER;
50 
51 #define ZSTD_STATIC_LINKING_ONLY   /* ZSTD_magicNumber, ZSTD_frameHeaderSize_max */
52 #include "../lib/zstd.h"
53 #include "../lib/zstd_errors.h"  /* ZSTD_error_frameParameter_windowTooLarge */
54 
55 #if defined(ZSTD_GZCOMPRESS) || defined(ZSTD_GZDECOMPRESS)
56 #  include <zlib.h>
57 #  if !defined(z_const)
58 #    define z_const
59 #  endif
60 #endif
61 
62 #if defined(ZSTD_LZMACOMPRESS) || defined(ZSTD_LZMADECOMPRESS)
63 #  include <lzma.h>
64 #endif
65 
66 #define LZ4_MAGICNUMBER 0x184D2204
67 #if defined(ZSTD_LZ4COMPRESS) || defined(ZSTD_LZ4DECOMPRESS)
68 #  define LZ4F_ENABLE_OBSOLETE_ENUMS
69 #  include <lz4frame.h>
70 #  include <lz4.h>
71 #endif
72 
FIO_zlibVersion(void)73 char const* FIO_zlibVersion(void)
74 {
75 #if defined(ZSTD_GZCOMPRESS) || defined(ZSTD_GZDECOMPRESS)
76     return zlibVersion();
77 #else
78     return "Unsupported";
79 #endif
80 }
81 
FIO_lz4Version(void)82 char const* FIO_lz4Version(void)
83 {
84 #if defined(ZSTD_LZ4COMPRESS) || defined(ZSTD_LZ4DECOMPRESS)
85     /* LZ4_versionString() added in v1.7.3 */
86 #   if LZ4_VERSION_NUMBER >= 10703
87         return LZ4_versionString();
88 #   else
89 #       define ZSTD_LZ4_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE
90 #       define ZSTD_LZ4_VERSION_STRING ZSTD_EXPAND_AND_QUOTE(ZSTD_LZ4_VERSION)
91         return ZSTD_LZ4_VERSION_STRING;
92 #   endif
93 #else
94     return "Unsupported";
95 #endif
96 }
97 
FIO_lzmaVersion(void)98 char const* FIO_lzmaVersion(void)
99 {
100 #if defined(ZSTD_LZMACOMPRESS) || defined(ZSTD_LZMADECOMPRESS)
101     return lzma_version_string();
102 #else
103     return "Unsupported";
104 #endif
105 }
106 
107 
108 /*-*************************************
109 *  Constants
110 ***************************************/
111 #define ADAPT_WINDOWLOG_DEFAULT 23   /* 8 MB */
112 #define DICTSIZE_MAX (32 MB)   /* protection against large input (attack scenario) */
113 
114 #define FNSPACE 30
115 
116 /* Default file permissions 0666 (modulated by umask) */
117 /* Temporary restricted file permissions are used when we're going to
118  * chmod/chown at the end of the operation. */
119 #if !defined(_WIN32)
120 /* These macros aren't defined on windows. */
121 #define DEFAULT_FILE_PERMISSIONS (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)
122 #define TEMPORARY_FILE_PERMISSIONS (S_IRUSR|S_IWUSR)
123 #else
124 #define DEFAULT_FILE_PERMISSIONS (0666)
125 #define TEMPORARY_FILE_PERMISSIONS (0600)
126 #endif
127 
128 /*-************************************
129 *  Signal (Ctrl-C trapping)
130 **************************************/
131 static const char* g_artefact = NULL;
INThandler(int sig)132 static void INThandler(int sig)
133 {
134     assert(sig==SIGINT); (void)sig;
135 #if !defined(_MSC_VER)
136     signal(sig, SIG_IGN);  /* this invocation generates a buggy warning in Visual Studio */
137 #endif
138     if (g_artefact) {
139         assert(UTIL_isRegularFile(g_artefact));
140         remove(g_artefact);
141     }
142     DISPLAY("\n");
143     exit(2);
144 }
addHandler(char const * dstFileName)145 static void addHandler(char const* dstFileName)
146 {
147     if (UTIL_isRegularFile(dstFileName)) {
148         g_artefact = dstFileName;
149         signal(SIGINT, INThandler);
150     } else {
151         g_artefact = NULL;
152     }
153 }
154 /* Idempotent */
clearHandler(void)155 static void clearHandler(void)
156 {
157     if (g_artefact) signal(SIGINT, SIG_DFL);
158     g_artefact = NULL;
159 }
160 
161 
162 /*-*********************************************************
163 *  Termination signal trapping (Print debug stack trace)
164 ***********************************************************/
165 #if defined(__has_feature) && !defined(BACKTRACE_ENABLE) /* Clang compiler */
166 #  if (__has_feature(address_sanitizer))
167 #    define BACKTRACE_ENABLE 0
168 #  endif /* __has_feature(address_sanitizer) */
169 #elif defined(__SANITIZE_ADDRESS__) && !defined(BACKTRACE_ENABLE) /* GCC compiler */
170 #  define BACKTRACE_ENABLE 0
171 #endif
172 
173 #if !defined(BACKTRACE_ENABLE)
174 /* automatic detector : backtrace enabled by default on linux+glibc and osx */
175 #  if (defined(__linux__) && (defined(__GLIBC__) && !defined(__UCLIBC__))) \
176      || (defined(__APPLE__) && defined(__MACH__))
177 #    define BACKTRACE_ENABLE 1
178 #  else
179 #    define BACKTRACE_ENABLE 0
180 #  endif
181 #endif
182 
183 /* note : after this point, BACKTRACE_ENABLE is necessarily defined */
184 
185 
186 #if BACKTRACE_ENABLE
187 
188 #include <execinfo.h>   /* backtrace, backtrace_symbols */
189 
190 #define MAX_STACK_FRAMES    50
191 
ABRThandler(int sig)192 static void ABRThandler(int sig) {
193     const char* name;
194     void* addrlist[MAX_STACK_FRAMES];
195     char** symbollist;
196     int addrlen, i;
197 
198     switch (sig) {
199         case SIGABRT: name = "SIGABRT"; break;
200         case SIGFPE: name = "SIGFPE"; break;
201         case SIGILL: name = "SIGILL"; break;
202         case SIGINT: name = "SIGINT"; break;
203         case SIGSEGV: name = "SIGSEGV"; break;
204         default: name = "UNKNOWN";
205     }
206 
207     DISPLAY("Caught %s signal, printing stack:\n", name);
208     /* Retrieve current stack addresses. */
209     addrlen = backtrace(addrlist, MAX_STACK_FRAMES);
210     if (addrlen == 0) {
211         DISPLAY("\n");
212         return;
213     }
214     /* Create readable strings to each frame. */
215     symbollist = backtrace_symbols(addrlist, addrlen);
216     /* Print the stack trace, excluding calls handling the signal. */
217     for (i = ZSTD_START_SYMBOLLIST_FRAME; i < addrlen; i++) {
218         DISPLAY("%s\n", symbollist[i]);
219     }
220     free(symbollist);
221     /* Reset and raise the signal so default handler runs. */
222     signal(sig, SIG_DFL);
223     raise(sig);
224 }
225 #endif
226 
FIO_addAbortHandler(void)227 void FIO_addAbortHandler(void)
228 {
229 #if BACKTRACE_ENABLE
230     signal(SIGABRT, ABRThandler);
231     signal(SIGFPE, ABRThandler);
232     signal(SIGILL, ABRThandler);
233     signal(SIGSEGV, ABRThandler);
234     signal(SIGBUS, ABRThandler);
235 #endif
236 }
237 
238 /*-*************************************
239 *  Parameters: FIO_ctx_t
240 ***************************************/
241 
242 /* typedef'd to FIO_ctx_t within fileio.h */
243 struct FIO_ctx_s {
244 
245     /* file i/o info */
246     int nbFilesTotal;
247     int hasStdinInput;
248     int hasStdoutOutput;
249 
250     /* file i/o state */
251     int currFileIdx;
252     int nbFilesProcessed;
253     size_t totalBytesInput;
254     size_t totalBytesOutput;
255 };
256 
FIO_shouldDisplayFileSummary(FIO_ctx_t const * fCtx)257 static int FIO_shouldDisplayFileSummary(FIO_ctx_t const* fCtx)
258 {
259     return fCtx->nbFilesTotal <= 1 || g_display_prefs.displayLevel >= 3;
260 }
261 
FIO_shouldDisplayMultipleFileSummary(FIO_ctx_t const * fCtx)262 static int FIO_shouldDisplayMultipleFileSummary(FIO_ctx_t const* fCtx)
263 {
264     int const shouldDisplay = (fCtx->nbFilesProcessed >= 1 && fCtx->nbFilesTotal > 1);
265     assert(shouldDisplay || FIO_shouldDisplayFileSummary(fCtx) || fCtx->nbFilesProcessed == 0);
266     return shouldDisplay;
267 }
268 
269 
270 /*-*************************************
271 *  Parameters: Initialization
272 ***************************************/
273 
274 #define FIO_OVERLAP_LOG_NOTSET 9999
275 #define FIO_LDM_PARAM_NOTSET 9999
276 
277 
FIO_createPreferences(void)278 FIO_prefs_t* FIO_createPreferences(void)
279 {
280     FIO_prefs_t* const ret = (FIO_prefs_t*)malloc(sizeof(FIO_prefs_t));
281     if (!ret) EXM_THROW(21, "Allocation error : not enough memory");
282 
283     ret->compressionType = FIO_zstdCompression;
284     ret->overwrite = 0;
285     ret->sparseFileSupport = ZSTD_SPARSE_DEFAULT;
286     ret->dictIDFlag = 1;
287     ret->checksumFlag = 1;
288     ret->removeSrcFile = 0;
289     ret->memLimit = 0;
290     ret->nbWorkers = 1;
291     ret->blockSize = 0;
292     ret->overlapLog = FIO_OVERLAP_LOG_NOTSET;
293     ret->adaptiveMode = 0;
294     ret->rsyncable = 0;
295     ret->minAdaptLevel = -50;   /* initializing this value requires a constant, so ZSTD_minCLevel() doesn't work */
296     ret->maxAdaptLevel = 22;   /* initializing this value requires a constant, so ZSTD_maxCLevel() doesn't work */
297     ret->ldmFlag = 0;
298     ret->ldmHashLog = 0;
299     ret->ldmMinMatch = 0;
300     ret->ldmBucketSizeLog = FIO_LDM_PARAM_NOTSET;
301     ret->ldmHashRateLog = FIO_LDM_PARAM_NOTSET;
302     ret->streamSrcSize = 0;
303     ret->targetCBlockSize = 0;
304     ret->srcSizeHint = 0;
305     ret->testMode = 0;
306     ret->literalCompressionMode = ZSTD_ps_auto;
307     ret->excludeCompressedFiles = 0;
308     ret->allowBlockDevices = 0;
309     ret->asyncIO = AIO_supported();
310     ret->passThrough = -1;
311     return ret;
312 }
313 
FIO_createContext(void)314 FIO_ctx_t* FIO_createContext(void)
315 {
316     FIO_ctx_t* const ret = (FIO_ctx_t*)malloc(sizeof(FIO_ctx_t));
317     if (!ret) EXM_THROW(21, "Allocation error : not enough memory");
318 
319     ret->currFileIdx = 0;
320     ret->hasStdinInput = 0;
321     ret->hasStdoutOutput = 0;
322     ret->nbFilesTotal = 1;
323     ret->nbFilesProcessed = 0;
324     ret->totalBytesInput = 0;
325     ret->totalBytesOutput = 0;
326     return ret;
327 }
328 
FIO_freePreferences(FIO_prefs_t * const prefs)329 void FIO_freePreferences(FIO_prefs_t* const prefs)
330 {
331     free(prefs);
332 }
333 
FIO_freeContext(FIO_ctx_t * const fCtx)334 void FIO_freeContext(FIO_ctx_t* const fCtx)
335 {
336     free(fCtx);
337 }
338 
339 
340 /*-*************************************
341 *  Parameters: Display Options
342 ***************************************/
343 
FIO_setNotificationLevel(int level)344 void FIO_setNotificationLevel(int level) { g_display_prefs.displayLevel=level; }
345 
FIO_setProgressSetting(FIO_progressSetting_e setting)346 void FIO_setProgressSetting(FIO_progressSetting_e setting) { g_display_prefs.progressSetting = setting; }
347 
348 
349 /*-*************************************
350 *  Parameters: Setters
351 ***************************************/
352 
353 /* FIO_prefs_t functions */
354 
FIO_setCompressionType(FIO_prefs_t * const prefs,FIO_compressionType_t compressionType)355 void FIO_setCompressionType(FIO_prefs_t* const prefs, FIO_compressionType_t compressionType) { prefs->compressionType = compressionType; }
356 
FIO_overwriteMode(FIO_prefs_t * const prefs)357 void FIO_overwriteMode(FIO_prefs_t* const prefs) { prefs->overwrite = 1; }
358 
FIO_setSparseWrite(FIO_prefs_t * const prefs,int sparse)359 void FIO_setSparseWrite(FIO_prefs_t* const prefs, int sparse) { prefs->sparseFileSupport = sparse; }
360 
FIO_setDictIDFlag(FIO_prefs_t * const prefs,int dictIDFlag)361 void FIO_setDictIDFlag(FIO_prefs_t* const prefs, int dictIDFlag) { prefs->dictIDFlag = dictIDFlag; }
362 
FIO_setChecksumFlag(FIO_prefs_t * const prefs,int checksumFlag)363 void FIO_setChecksumFlag(FIO_prefs_t* const prefs, int checksumFlag) { prefs->checksumFlag = checksumFlag; }
364 
FIO_setRemoveSrcFile(FIO_prefs_t * const prefs,int flag)365 void FIO_setRemoveSrcFile(FIO_prefs_t* const prefs, int flag) { prefs->removeSrcFile = (flag!=0); }
366 
FIO_setMemLimit(FIO_prefs_t * const prefs,unsigned memLimit)367 void FIO_setMemLimit(FIO_prefs_t* const prefs, unsigned memLimit) { prefs->memLimit = memLimit; }
368 
FIO_setNbWorkers(FIO_prefs_t * const prefs,int nbWorkers)369 void FIO_setNbWorkers(FIO_prefs_t* const prefs, int nbWorkers) {
370 #ifndef ZSTD_MULTITHREAD
371     if (nbWorkers > 0) DISPLAYLEVEL(2, "Note : multi-threading is disabled \n");
372 #endif
373     prefs->nbWorkers = nbWorkers;
374 }
375 
FIO_setExcludeCompressedFile(FIO_prefs_t * const prefs,int excludeCompressedFiles)376 void FIO_setExcludeCompressedFile(FIO_prefs_t* const prefs, int excludeCompressedFiles) { prefs->excludeCompressedFiles = excludeCompressedFiles; }
377 
FIO_setAllowBlockDevices(FIO_prefs_t * const prefs,int allowBlockDevices)378 void FIO_setAllowBlockDevices(FIO_prefs_t* const prefs, int allowBlockDevices) { prefs->allowBlockDevices = allowBlockDevices; }
379 
FIO_setBlockSize(FIO_prefs_t * const prefs,int blockSize)380 void FIO_setBlockSize(FIO_prefs_t* const prefs, int blockSize) {
381     if (blockSize && prefs->nbWorkers==0)
382         DISPLAYLEVEL(2, "Setting block size is useless in single-thread mode \n");
383     prefs->blockSize = blockSize;
384 }
385 
FIO_setOverlapLog(FIO_prefs_t * const prefs,int overlapLog)386 void FIO_setOverlapLog(FIO_prefs_t* const prefs, int overlapLog){
387     if (overlapLog && prefs->nbWorkers==0)
388         DISPLAYLEVEL(2, "Setting overlapLog is useless in single-thread mode \n");
389     prefs->overlapLog = overlapLog;
390 }
391 
FIO_setAdaptiveMode(FIO_prefs_t * const prefs,int adapt)392 void FIO_setAdaptiveMode(FIO_prefs_t* const prefs, int adapt) {
393     if ((adapt>0) && (prefs->nbWorkers==0))
394         EXM_THROW(1, "Adaptive mode is not compatible with single thread mode \n");
395     prefs->adaptiveMode = adapt;
396 }
397 
FIO_setUseRowMatchFinder(FIO_prefs_t * const prefs,int useRowMatchFinder)398 void FIO_setUseRowMatchFinder(FIO_prefs_t* const prefs, int useRowMatchFinder) {
399     prefs->useRowMatchFinder = useRowMatchFinder;
400 }
401 
FIO_setRsyncable(FIO_prefs_t * const prefs,int rsyncable)402 void FIO_setRsyncable(FIO_prefs_t* const prefs, int rsyncable) {
403     if ((rsyncable>0) && (prefs->nbWorkers==0))
404         EXM_THROW(1, "Rsyncable mode is not compatible with single thread mode \n");
405     prefs->rsyncable = rsyncable;
406 }
407 
FIO_setStreamSrcSize(FIO_prefs_t * const prefs,size_t streamSrcSize)408 void FIO_setStreamSrcSize(FIO_prefs_t* const prefs, size_t streamSrcSize) {
409     prefs->streamSrcSize = streamSrcSize;
410 }
411 
FIO_setTargetCBlockSize(FIO_prefs_t * const prefs,size_t targetCBlockSize)412 void FIO_setTargetCBlockSize(FIO_prefs_t* const prefs, size_t targetCBlockSize) {
413     prefs->targetCBlockSize = targetCBlockSize;
414 }
415 
FIO_setSrcSizeHint(FIO_prefs_t * const prefs,size_t srcSizeHint)416 void FIO_setSrcSizeHint(FIO_prefs_t* const prefs, size_t srcSizeHint) {
417     prefs->srcSizeHint = (int)MIN((size_t)INT_MAX, srcSizeHint);
418 }
419 
FIO_setTestMode(FIO_prefs_t * const prefs,int testMode)420 void FIO_setTestMode(FIO_prefs_t* const prefs, int testMode) {
421     prefs->testMode = (testMode!=0);
422 }
423 
FIO_setLiteralCompressionMode(FIO_prefs_t * const prefs,ZSTD_ParamSwitch_e mode)424 void FIO_setLiteralCompressionMode(
425         FIO_prefs_t* const prefs,
426         ZSTD_ParamSwitch_e mode) {
427     prefs->literalCompressionMode = mode;
428 }
429 
FIO_setAdaptMin(FIO_prefs_t * const prefs,int minCLevel)430 void FIO_setAdaptMin(FIO_prefs_t* const prefs, int minCLevel)
431 {
432 #ifndef ZSTD_NOCOMPRESS
433     assert(minCLevel >= ZSTD_minCLevel());
434 #endif
435     prefs->minAdaptLevel = minCLevel;
436 }
437 
FIO_setAdaptMax(FIO_prefs_t * const prefs,int maxCLevel)438 void FIO_setAdaptMax(FIO_prefs_t* const prefs, int maxCLevel)
439 {
440     prefs->maxAdaptLevel = maxCLevel;
441 }
442 
FIO_setLdmFlag(FIO_prefs_t * const prefs,unsigned ldmFlag)443 void FIO_setLdmFlag(FIO_prefs_t* const prefs, unsigned ldmFlag) {
444     prefs->ldmFlag = (ldmFlag>0);
445 }
446 
FIO_setLdmHashLog(FIO_prefs_t * const prefs,int ldmHashLog)447 void FIO_setLdmHashLog(FIO_prefs_t* const prefs, int ldmHashLog) {
448     prefs->ldmHashLog = ldmHashLog;
449 }
450 
FIO_setLdmMinMatch(FIO_prefs_t * const prefs,int ldmMinMatch)451 void FIO_setLdmMinMatch(FIO_prefs_t* const prefs, int ldmMinMatch) {
452     prefs->ldmMinMatch = ldmMinMatch;
453 }
454 
FIO_setLdmBucketSizeLog(FIO_prefs_t * const prefs,int ldmBucketSizeLog)455 void FIO_setLdmBucketSizeLog(FIO_prefs_t* const prefs, int ldmBucketSizeLog) {
456     prefs->ldmBucketSizeLog = ldmBucketSizeLog;
457 }
458 
459 
FIO_setLdmHashRateLog(FIO_prefs_t * const prefs,int ldmHashRateLog)460 void FIO_setLdmHashRateLog(FIO_prefs_t* const prefs, int ldmHashRateLog) {
461     prefs->ldmHashRateLog = ldmHashRateLog;
462 }
463 
FIO_setPatchFromMode(FIO_prefs_t * const prefs,int value)464 void FIO_setPatchFromMode(FIO_prefs_t* const prefs, int value)
465 {
466     prefs->patchFromMode = value != 0;
467 }
468 
FIO_setContentSize(FIO_prefs_t * const prefs,int value)469 void FIO_setContentSize(FIO_prefs_t* const prefs, int value)
470 {
471     prefs->contentSize = value != 0;
472 }
473 
FIO_setAsyncIOFlag(FIO_prefs_t * const prefs,int value)474 void FIO_setAsyncIOFlag(FIO_prefs_t* const prefs, int value) {
475 #ifdef ZSTD_MULTITHREAD
476     prefs->asyncIO = value;
477 #else
478     (void) prefs;
479     (void) value;
480     DISPLAYLEVEL(2, "Note : asyncio is disabled (lack of multithreading support) \n");
481 #endif
482 }
483 
FIO_setPassThroughFlag(FIO_prefs_t * const prefs,int value)484 void FIO_setPassThroughFlag(FIO_prefs_t* const prefs, int value) {
485     prefs->passThrough = (value != 0);
486 }
487 
FIO_setMMapDict(FIO_prefs_t * const prefs,ZSTD_ParamSwitch_e value)488 void FIO_setMMapDict(FIO_prefs_t* const prefs, ZSTD_ParamSwitch_e value)
489 {
490     prefs->mmapDict = value;
491 }
492 
493 /* FIO_ctx_t functions */
494 
FIO_setHasStdoutOutput(FIO_ctx_t * const fCtx,int value)495 void FIO_setHasStdoutOutput(FIO_ctx_t* const fCtx, int value) {
496     fCtx->hasStdoutOutput = value;
497 }
498 
FIO_setNbFilesTotal(FIO_ctx_t * const fCtx,int value)499 void FIO_setNbFilesTotal(FIO_ctx_t* const fCtx, int value)
500 {
501     fCtx->nbFilesTotal = value;
502 }
503 
FIO_determineHasStdinInput(FIO_ctx_t * const fCtx,const FileNamesTable * const filenames)504 void FIO_determineHasStdinInput(FIO_ctx_t* const fCtx, const FileNamesTable* const filenames) {
505     size_t i = 0;
506     for ( ; i < filenames->tableSize; ++i) {
507         if (!strcmp(stdinmark, filenames->fileNames[i])) {
508             fCtx->hasStdinInput = 1;
509             return;
510         }
511     }
512 }
513 
514 /*-*************************************
515 *  Functions
516 ***************************************/
517 /** FIO_removeFile() :
518  * @result : Unlink `fileName`, even if it's read-only */
FIO_removeFile(const char * path)519 static int FIO_removeFile(const char* path)
520 {
521     stat_t statbuf;
522     if (!UTIL_stat(path, &statbuf)) {
523         DISPLAYLEVEL(2, "zstd: Failed to stat %s while trying to remove it\n", path);
524         return 0;
525     }
526     if (!UTIL_isRegularFileStat(&statbuf)) {
527         DISPLAYLEVEL(2, "zstd: Refusing to remove non-regular file %s\n", path);
528         return 0;
529     }
530 #if defined(_WIN32)
531     /* windows doesn't allow remove read-only files,
532      * so try to make it writable first */
533     if (!(statbuf.st_mode & _S_IWRITE)) {
534         UTIL_chmod(path, &statbuf, _S_IWRITE);
535     }
536 #endif
537     return remove(path);
538 }
539 
540 /** FIO_openSrcFile() :
541  *  condition : `srcFileName` must be non-NULL. `prefs` may be NULL.
542  * @result : FILE* to `srcFileName`, or NULL if it fails */
FIO_openSrcFile(const FIO_prefs_t * const prefs,const char * srcFileName,stat_t * statbuf)543 static FILE* FIO_openSrcFile(const FIO_prefs_t* const prefs, const char* srcFileName, stat_t* statbuf)
544 {
545     int allowBlockDevices = prefs != NULL ? prefs->allowBlockDevices : 0;
546     assert(srcFileName != NULL);
547     assert(statbuf != NULL);
548     if (!strcmp (srcFileName, stdinmark)) {
549         DISPLAYLEVEL(4,"Using stdin for input \n");
550         SET_BINARY_MODE(stdin);
551         return stdin;
552     }
553 
554     if (!UTIL_stat(srcFileName, statbuf)) {
555         DISPLAYLEVEL(1, "zstd: can't stat %s : %s -- ignored \n",
556                         srcFileName, strerror(errno));
557         return NULL;
558     }
559 
560     if (!UTIL_isRegularFileStat(statbuf)
561      && !UTIL_isFIFOStat(statbuf)
562      && !(allowBlockDevices && UTIL_isBlockDevStat(statbuf))
563     ) {
564         DISPLAYLEVEL(1, "zstd: %s is not a regular file -- ignored \n",
565                         srcFileName);
566         return NULL;
567     }
568 
569     {   FILE* const f = fopen(srcFileName, "rb");
570         if (f == NULL)
571             DISPLAYLEVEL(1, "zstd: %s: %s \n", srcFileName, strerror(errno));
572         return f;
573     }
574 }
575 
576 /** FIO_openDstFile() :
577  *  condition : `dstFileName` must be non-NULL.
578  * @result : FILE* to `dstFileName`, or NULL if it fails */
579 static FILE*
FIO_openDstFile(FIO_ctx_t * fCtx,FIO_prefs_t * const prefs,const char * srcFileName,const char * dstFileName,const int mode)580 FIO_openDstFile(FIO_ctx_t* fCtx, FIO_prefs_t* const prefs,
581                 const char* srcFileName, const char* dstFileName,
582                 const int mode)
583 {
584     int isDstRegFile;
585 
586     if (prefs->testMode) return NULL;  /* do not open file in test mode */
587 
588     assert(dstFileName != NULL);
589     if (!strcmp (dstFileName, stdoutmark)) {
590         DISPLAYLEVEL(4,"Using stdout for output \n");
591         SET_BINARY_MODE(stdout);
592         if (prefs->sparseFileSupport == 1) {
593             prefs->sparseFileSupport = 0;
594             DISPLAYLEVEL(4, "Sparse File Support is automatically disabled on stdout ; try --sparse \n");
595         }
596         return stdout;
597     }
598 
599     /* ensure dst is not the same as src */
600     if (srcFileName != NULL && UTIL_isSameFile(srcFileName, dstFileName)) {
601         DISPLAYLEVEL(1, "zstd: Refusing to open an output file which will overwrite the input file \n");
602         return NULL;
603     }
604 
605     isDstRegFile = UTIL_isRegularFile(dstFileName);  /* invoke once */
606     if (prefs->sparseFileSupport == 1) {
607         prefs->sparseFileSupport = ZSTD_SPARSE_DEFAULT;
608         if (!isDstRegFile) {
609             prefs->sparseFileSupport = 0;
610             DISPLAYLEVEL(4, "Sparse File Support is disabled when output is not a file \n");
611         }
612     }
613 
614     if (isDstRegFile) {
615         /* Check if destination file already exists */
616 #if !defined(_WIN32)
617         /* this test does not work on Windows :
618          * `NUL` and `nul` are detected as regular files */
619         if (!strcmp(dstFileName, nulmark)) {
620             EXM_THROW(40, "%s is unexpectedly categorized as a regular file",
621                         dstFileName);
622         }
623 #endif
624         if (!prefs->overwrite) {
625             if (g_display_prefs.displayLevel <= 1) {
626                 /* No interaction possible */
627                 DISPLAYLEVEL(1, "zstd: %s already exists; not overwritten  \n",
628                         dstFileName);
629                 return NULL;
630             }
631             DISPLAY("zstd: %s already exists; ", dstFileName);
632             if (UTIL_requireUserConfirmation("overwrite (y/n) ? ", "Not overwritten  \n", "yY", fCtx->hasStdinInput))
633                 return NULL;
634         }
635         /* need to unlink */
636         FIO_removeFile(dstFileName);
637     }
638 
639     {
640 #if defined(_WIN32)
641         /* Windows requires opening the file as a "binary" file to avoid
642          * mangling. This macro doesn't exist on unix. */
643         const int openflags = O_WRONLY|O_CREAT|O_TRUNC|O_BINARY;
644         const int fd = _open(dstFileName, openflags, mode);
645         FILE* f = NULL;
646         if (fd != -1) {
647             f = _fdopen(fd, "wb");
648         }
649 #else
650         const int openflags = O_WRONLY|O_CREAT|O_TRUNC;
651         const int fd = open(dstFileName, openflags, mode);
652         FILE* f = NULL;
653         if (fd != -1) {
654             f = fdopen(fd, "wb");
655         }
656 #endif
657         if (f == NULL) {
658             DISPLAYLEVEL(1, "zstd: %s: %s\n", dstFileName, strerror(errno));
659         } else {
660             /* An increased buffer size can provide a significant performance
661              * boost on some platforms. Note that providing a NULL buf with a
662              * size that's not 0 is not defined in ANSI C, but is defined in an
663              * extension. There are three possibilities here:
664              * 1. Libc supports the extended version and everything is good.
665              * 2. Libc ignores the size when buf is NULL, in which case
666              *    everything will continue as if we didn't call `setvbuf()`.
667              * 3. We fail the call and execution continues but a warning
668              *    message might be shown.
669              * In all cases due execution continues. For now, I believe that
670              * this is a more cost-effective solution than managing the buffers
671              * allocations ourselves (will require an API change).
672              */
673             if (setvbuf(f, NULL, _IOFBF, 1 MB)) {
674                 DISPLAYLEVEL(2, "Warning: setvbuf failed for %s\n", dstFileName);
675             }
676         }
677         return f;
678     }
679 }
680 
681 
682 /* FIO_getDictFileStat() :
683  */
FIO_getDictFileStat(const char * fileName,stat_t * dictFileStat)684 static void FIO_getDictFileStat(const char* fileName, stat_t* dictFileStat) {
685     assert(dictFileStat != NULL);
686     if (fileName == NULL) return;
687 
688     if (!UTIL_stat(fileName, dictFileStat)) {
689         EXM_THROW(31, "Stat failed on dictionary file %s: %s", fileName, strerror(errno));
690     }
691 
692     if (!UTIL_isRegularFileStat(dictFileStat)) {
693         EXM_THROW(32, "Dictionary %s must be a regular file.", fileName);
694     }
695 }
696 
697 /*  FIO_setDictBufferMalloc() :
698  *  allocates a buffer, pointed by `dict->dictBuffer`,
699  *  loads `filename` content into it, up to DICTSIZE_MAX bytes.
700  * @return : loaded size
701  *  if fileName==NULL, returns 0 and a NULL pointer
702  */
FIO_setDictBufferMalloc(FIO_Dict_t * dict,const char * fileName,FIO_prefs_t * const prefs,stat_t * dictFileStat)703 static size_t FIO_setDictBufferMalloc(FIO_Dict_t* dict, const char* fileName, FIO_prefs_t* const prefs, stat_t* dictFileStat)
704 {
705     FILE* fileHandle;
706     U64 fileSize;
707     void** bufferPtr = &dict->dictBuffer;
708 
709     assert(bufferPtr != NULL);
710     assert(dictFileStat != NULL);
711     *bufferPtr = NULL;
712     if (fileName == NULL) return 0;
713 
714     DISPLAYLEVEL(4,"Loading %s as dictionary \n", fileName);
715 
716     fileHandle = fopen(fileName, "rb");
717 
718     if (fileHandle == NULL) {
719         EXM_THROW(33, "Couldn't open dictionary %s: %s", fileName, strerror(errno));
720     }
721 
722     fileSize = UTIL_getFileSizeStat(dictFileStat);
723     {
724         size_t const dictSizeMax = prefs->patchFromMode ? prefs->memLimit : DICTSIZE_MAX;
725         if (fileSize >  dictSizeMax) {
726             EXM_THROW(34, "Dictionary file %s is too large (> %u bytes)",
727                             fileName,  (unsigned)dictSizeMax);   /* avoid extreme cases */
728         }
729     }
730     *bufferPtr = malloc((size_t)fileSize);
731     if (*bufferPtr==NULL) EXM_THROW(34, "%s", strerror(errno));
732     {   size_t const readSize = fread(*bufferPtr, 1, (size_t)fileSize, fileHandle);
733         if (readSize != fileSize) {
734             EXM_THROW(35, "Error reading dictionary file %s : %s",
735                     fileName, strerror(errno));
736         }
737     }
738     fclose(fileHandle);
739     return (size_t)fileSize;
740 }
741 
742 #if (PLATFORM_POSIX_VERSION > 0)
743 #include <sys/mman.h>
FIO_munmap(FIO_Dict_t * dict)744 static void FIO_munmap(FIO_Dict_t* dict)
745 {
746     munmap(dict->dictBuffer, dict->dictBufferSize);
747     dict->dictBuffer = NULL;
748     dict->dictBufferSize = 0;
749 }
FIO_setDictBufferMMap(FIO_Dict_t * dict,const char * fileName,FIO_prefs_t * const prefs,stat_t * dictFileStat)750 static size_t FIO_setDictBufferMMap(FIO_Dict_t* dict, const char* fileName, FIO_prefs_t* const prefs, stat_t* dictFileStat)
751 {
752     int fileHandle;
753     U64 fileSize;
754     void** bufferPtr = &dict->dictBuffer;
755 
756     assert(bufferPtr != NULL);
757     assert(dictFileStat != NULL);
758     *bufferPtr = NULL;
759     if (fileName == NULL) return 0;
760 
761     DISPLAYLEVEL(4,"Loading %s as dictionary \n", fileName);
762 
763     fileHandle = open(fileName, O_RDONLY);
764 
765     if (fileHandle == -1) {
766         EXM_THROW(33, "Couldn't open dictionary %s: %s", fileName, strerror(errno));
767     }
768 
769     fileSize = UTIL_getFileSizeStat(dictFileStat);
770     {
771         size_t const dictSizeMax = prefs->patchFromMode ? prefs->memLimit : DICTSIZE_MAX;
772         if (fileSize >  dictSizeMax) {
773             EXM_THROW(34, "Dictionary file %s is too large (> %u bytes)",
774                             fileName,  (unsigned)dictSizeMax);   /* avoid extreme cases */
775         }
776     }
777 
778     *bufferPtr = mmap(NULL, (size_t)fileSize, PROT_READ, MAP_PRIVATE, fileHandle, 0);
779     if (*bufferPtr==NULL) EXM_THROW(34, "%s", strerror(errno));
780 
781     close(fileHandle);
782     return (size_t)fileSize;
783 }
784 #elif defined(_MSC_VER) || defined(_WIN32)
785 #include <windows.h>
FIO_munmap(FIO_Dict_t * dict)786 static void FIO_munmap(FIO_Dict_t* dict)
787 {
788     UnmapViewOfFile(dict->dictBuffer);
789     CloseHandle(dict->dictHandle);
790     dict->dictBuffer = NULL;
791     dict->dictBufferSize = 0;
792 }
FIO_setDictBufferMMap(FIO_Dict_t * dict,const char * fileName,FIO_prefs_t * const prefs,stat_t * dictFileStat)793 static size_t FIO_setDictBufferMMap(FIO_Dict_t* dict, const char* fileName, FIO_prefs_t* const prefs, stat_t* dictFileStat)
794 {
795     HANDLE fileHandle, mapping;
796     U64 fileSize;
797     void** bufferPtr = &dict->dictBuffer;
798 
799     assert(bufferPtr != NULL);
800     assert(dictFileStat != NULL);
801     *bufferPtr = NULL;
802     if (fileName == NULL) return 0;
803 
804     DISPLAYLEVEL(4,"Loading %s as dictionary \n", fileName);
805 
806     fileHandle = CreateFileA(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
807 
808     if (fileHandle == INVALID_HANDLE_VALUE) {
809         EXM_THROW(33, "Couldn't open dictionary %s: %s", fileName, strerror(errno));
810     }
811 
812     fileSize = UTIL_getFileSizeStat(dictFileStat);
813     {
814         size_t const dictSizeMax = prefs->patchFromMode ? prefs->memLimit : DICTSIZE_MAX;
815         if (fileSize >  dictSizeMax) {
816             EXM_THROW(34, "Dictionary file %s is too large (> %u bytes)",
817                             fileName,  (unsigned)dictSizeMax);   /* avoid extreme cases */
818         }
819     }
820 
821     mapping = CreateFileMapping(fileHandle, NULL, PAGE_READONLY, 0, 0, NULL);
822 	if (mapping == NULL) {
823         EXM_THROW(35, "Couldn't map dictionary %s: %s", fileName, strerror(errno));
824     }
825 
826 	*bufferPtr = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, (DWORD)fileSize); /* we can only cast to DWORD here because dictSize <= 2GB */
827 	if (*bufferPtr==NULL) EXM_THROW(36, "%s", strerror(errno));
828 
829     dict->dictHandle = fileHandle;
830     return (size_t)fileSize;
831 }
832 #else
FIO_setDictBufferMMap(FIO_Dict_t * dict,const char * fileName,FIO_prefs_t * const prefs,stat_t * dictFileStat)833 static size_t FIO_setDictBufferMMap(FIO_Dict_t* dict, const char* fileName, FIO_prefs_t* const prefs, stat_t* dictFileStat)
834 {
835    return FIO_setDictBufferMalloc(dict, fileName, prefs, dictFileStat);
836 }
FIO_munmap(FIO_Dict_t * dict)837 static void FIO_munmap(FIO_Dict_t* dict) {
838    free(dict->dictBuffer);
839    dict->dictBuffer = NULL;
840    dict->dictBufferSize = 0;
841 }
842 #endif
843 
FIO_freeDict(FIO_Dict_t * dict)844 static void FIO_freeDict(FIO_Dict_t* dict) {
845     if (dict->dictBufferType == FIO_mallocDict) {
846         free(dict->dictBuffer);
847         dict->dictBuffer = NULL;
848         dict->dictBufferSize = 0;
849     } else if (dict->dictBufferType == FIO_mmapDict)  {
850         FIO_munmap(dict);
851     } else {
852         assert(0); /* Should not reach this case */
853     }
854 }
855 
FIO_initDict(FIO_Dict_t * dict,const char * fileName,FIO_prefs_t * const prefs,stat_t * dictFileStat,FIO_dictBufferType_t dictBufferType)856 static void FIO_initDict(FIO_Dict_t* dict, const char* fileName, FIO_prefs_t* const prefs, stat_t* dictFileStat, FIO_dictBufferType_t dictBufferType) {
857     dict->dictBufferType = dictBufferType;
858     if (dict->dictBufferType == FIO_mallocDict) {
859         dict->dictBufferSize = FIO_setDictBufferMalloc(dict, fileName, prefs, dictFileStat);
860     } else if (dict->dictBufferType == FIO_mmapDict)  {
861         dict->dictBufferSize = FIO_setDictBufferMMap(dict, fileName, prefs, dictFileStat);
862     } else {
863         assert(0); /* Should not reach this case */
864     }
865 }
866 
867 
868 /* FIO_checkFilenameCollisions() :
869  * Checks for and warns if there are any files that would have the same output path
870  */
FIO_checkFilenameCollisions(const char ** filenameTable,unsigned nbFiles)871 int FIO_checkFilenameCollisions(const char** filenameTable, unsigned nbFiles) {
872     const char **filenameTableSorted, *prevElem, *filename;
873     unsigned u;
874 
875     filenameTableSorted = (const char**) malloc(sizeof(char*) * nbFiles);
876     if (!filenameTableSorted) {
877         DISPLAYLEVEL(1, "Allocation error during filename collision checking \n");
878         return 1;
879     }
880 
881     for (u = 0; u < nbFiles; ++u) {
882         filename = strrchr(filenameTable[u], PATH_SEP);
883         if (filename == NULL) {
884             filenameTableSorted[u] = filenameTable[u];
885         } else {
886             filenameTableSorted[u] = filename+1;
887         }
888     }
889 
890     qsort((void*)filenameTableSorted, nbFiles, sizeof(char*), UTIL_compareStr);
891     prevElem = filenameTableSorted[0];
892     for (u = 1; u < nbFiles; ++u) {
893         if (strcmp(prevElem, filenameTableSorted[u]) == 0) {
894             DISPLAYLEVEL(2, "WARNING: Two files have same filename: %s\n", prevElem);
895         }
896         prevElem = filenameTableSorted[u];
897     }
898 
899     free((void*)filenameTableSorted);
900     return 0;
901 }
902 
903 static const char*
extractFilename(const char * path,char separator)904 extractFilename(const char* path, char separator)
905 {
906     const char* search = strrchr(path, separator);
907     if (search == NULL) return path;
908     return search+1;
909 }
910 
911 /* FIO_createFilename_fromOutDir() :
912  * Takes a source file name and specified output directory, and
913  * allocates memory for and returns a pointer to final path.
914  * This function never returns an error (it may abort() in case of pb)
915  */
916 static char*
FIO_createFilename_fromOutDir(const char * path,const char * outDirName,const size_t suffixLen)917 FIO_createFilename_fromOutDir(const char* path, const char* outDirName, const size_t suffixLen)
918 {
919     const char* filenameStart;
920     char separator;
921     char* result;
922 
923 #if defined(_MSC_VER) || defined(__MINGW32__) || defined (__MSVCRT__) /* windows support */
924     separator = '\\';
925 #else
926     separator = '/';
927 #endif
928 
929     filenameStart = extractFilename(path, separator);
930 #if defined(_MSC_VER) || defined(__MINGW32__) || defined (__MSVCRT__) /* windows support */
931     filenameStart = extractFilename(filenameStart, '/');  /* sometimes, '/' separator is also used on Windows (mingw+msys2) */
932 #endif
933 
934     result = (char*) calloc(1, strlen(outDirName) + 1 + strlen(filenameStart) + suffixLen + 1);
935     if (!result) {
936         EXM_THROW(30, "zstd: FIO_createFilename_fromOutDir: %s", strerror(errno));
937     }
938 
939     memcpy(result, outDirName, strlen(outDirName));
940     if (outDirName[strlen(outDirName)-1] == separator) {
941         memcpy(result + strlen(outDirName), filenameStart, strlen(filenameStart));
942     } else {
943         memcpy(result + strlen(outDirName), &separator, 1);
944         memcpy(result + strlen(outDirName) + 1, filenameStart, strlen(filenameStart));
945     }
946 
947     return result;
948 }
949 
950 /* FIO_highbit64() :
951  * gives position of highest bit.
952  * note : only works for v > 0 !
953  */
FIO_highbit64(unsigned long long v)954 static unsigned FIO_highbit64(unsigned long long v)
955 {
956     unsigned count = 0;
957     assert(v != 0);
958     v >>= 1;
959     while (v) { v >>= 1; count++; }
960     return count;
961 }
962 
FIO_adjustMemLimitForPatchFromMode(FIO_prefs_t * const prefs,unsigned long long const dictSize,unsigned long long const maxSrcFileSize)963 static void FIO_adjustMemLimitForPatchFromMode(FIO_prefs_t* const prefs,
964                                     unsigned long long const dictSize,
965                                     unsigned long long const maxSrcFileSize)
966 {
967     unsigned long long maxSize = MAX(prefs->memLimit, MAX(dictSize, maxSrcFileSize));
968     unsigned const maxWindowSize = (1U << ZSTD_WINDOWLOG_MAX);
969     if (maxSize == UTIL_FILESIZE_UNKNOWN)
970         EXM_THROW(42, "Using --patch-from with stdin requires --stream-size");
971     assert(maxSize != UTIL_FILESIZE_UNKNOWN);
972     if (maxSize > maxWindowSize)
973         EXM_THROW(42, "Can't handle files larger than %u GB\n", maxWindowSize/(1 GB));
974     FIO_setMemLimit(prefs, (unsigned)maxSize);
975 }
976 
977 /* FIO_multiFilesConcatWarning() :
978  * This function handles logic when processing multiple files with -o or -c, displaying the appropriate warnings/prompts.
979  * Returns 1 if the console should abort, 0 if console should proceed.
980  *
981  * If output is stdout or test mode is active, check that `--rm` disabled.
982  *
983  * If there is just 1 file to process, zstd will proceed as usual.
984  * If each file get processed into its own separate destination file, proceed as usual.
985  *
986  * When multiple files are processed into a single output,
987  * display a warning message, then disable --rm if it's set.
988  *
989  * If -f is specified or if output is stdout, just proceed.
990  * If output is set with -o, prompt for confirmation.
991  */
FIO_multiFilesConcatWarning(const FIO_ctx_t * fCtx,FIO_prefs_t * prefs,const char * outFileName,int displayLevelCutoff)992 static int FIO_multiFilesConcatWarning(const FIO_ctx_t* fCtx, FIO_prefs_t* prefs, const char* outFileName, int displayLevelCutoff)
993 {
994     if (fCtx->hasStdoutOutput) {
995         if (prefs->removeSrcFile)
996             /* this should not happen ; hard fail, to protect user's data
997              * note: this should rather be an assert(), but we want to be certain that user's data will not be wiped out in case it nonetheless happen */
998             EXM_THROW(43, "It's not allowed to remove input files when processed output is piped to stdout. "
999                 "This scenario is not supposed to be possible. "
1000                 "This is a programming error. File an issue for it to be fixed.");
1001     }
1002     if (prefs->testMode) {
1003         if (prefs->removeSrcFile)
1004             /* this should not happen ; hard fail, to protect user's data
1005              * note: this should rather be an assert(), but we want to be certain that user's data will not be wiped out in case it nonetheless happen */
1006             EXM_THROW(43, "Test mode shall not remove input files! "
1007                  "This scenario is not supposed to be possible. "
1008                  "This is a programming error. File an issue for it to be fixed.");
1009         return 0;
1010     }
1011 
1012     if (fCtx->nbFilesTotal == 1) return 0;
1013     assert(fCtx->nbFilesTotal > 1);
1014 
1015     if (!outFileName) return 0;
1016 
1017     if (fCtx->hasStdoutOutput) {
1018         DISPLAYLEVEL(2, "zstd: WARNING: all input files will be processed and concatenated into stdout. \n");
1019     } else {
1020         DISPLAYLEVEL(2, "zstd: WARNING: all input files will be processed and concatenated into a single output file: %s \n", outFileName);
1021     }
1022     DISPLAYLEVEL(2, "The concatenated output CANNOT regenerate original file names nor directory structure. \n")
1023 
1024     /* multi-input into single output : --rm is not allowed */
1025     if (prefs->removeSrcFile) {
1026         DISPLAYLEVEL(2, "Since it's a destructive operation, input files will not be removed. \n");
1027         prefs->removeSrcFile = 0;
1028     }
1029 
1030     if (fCtx->hasStdoutOutput) return 0;
1031     if (prefs->overwrite) return 0;
1032 
1033     /* multiple files concatenated into single destination file using -o without -f */
1034     if (g_display_prefs.displayLevel <= displayLevelCutoff) {
1035         /* quiet mode => no prompt => fail automatically */
1036         DISPLAYLEVEL(1, "Concatenating multiple processed inputs into a single output loses file metadata. \n");
1037         DISPLAYLEVEL(1, "Aborting. \n");
1038         return 1;
1039     }
1040     /* normal mode => prompt */
1041     return UTIL_requireUserConfirmation("Proceed? (y/n): ", "Aborting...", "yY", fCtx->hasStdinInput);
1042 }
1043 
setInBuffer(const void * buf,size_t s,size_t pos)1044 static ZSTD_inBuffer setInBuffer(const void* buf, size_t s, size_t pos)
1045 {
1046     ZSTD_inBuffer i;
1047     i.src = buf;
1048     i.size = s;
1049     i.pos = pos;
1050     return i;
1051 }
1052 
setOutBuffer(void * buf,size_t s,size_t pos)1053 static ZSTD_outBuffer setOutBuffer(void* buf, size_t s, size_t pos)
1054 {
1055     ZSTD_outBuffer o;
1056     o.dst = buf;
1057     o.size = s;
1058     o.pos = pos;
1059     return o;
1060 }
1061 
1062 #ifndef ZSTD_NOCOMPRESS
1063 
1064 /* **********************************************************************
1065  *  Compression
1066  ************************************************************************/
1067 typedef struct {
1068     FIO_Dict_t dict;
1069     const char* dictFileName;
1070     stat_t dictFileStat;
1071     ZSTD_CStream* cctx;
1072     WritePoolCtx_t *writeCtx;
1073     ReadPoolCtx_t *readCtx;
1074 } cRess_t;
1075 
1076 /** ZSTD_cycleLog() :
1077  *  condition for correct operation : hashLog > 1 */
ZSTD_cycleLog(U32 hashLog,ZSTD_strategy strat)1078 static U32 ZSTD_cycleLog(U32 hashLog, ZSTD_strategy strat)
1079 {
1080     U32 const btScale = ((U32)strat >= (U32)ZSTD_btlazy2);
1081     assert(hashLog > 1);
1082     return hashLog - btScale;
1083 }
1084 
FIO_adjustParamsForPatchFromMode(FIO_prefs_t * const prefs,ZSTD_compressionParameters * comprParams,unsigned long long const dictSize,unsigned long long const maxSrcFileSize,int cLevel)1085 static void FIO_adjustParamsForPatchFromMode(FIO_prefs_t* const prefs,
1086                                     ZSTD_compressionParameters* comprParams,
1087                                     unsigned long long const dictSize,
1088                                     unsigned long long const maxSrcFileSize,
1089                                     int cLevel)
1090 {
1091     unsigned const fileWindowLog = FIO_highbit64(maxSrcFileSize) + 1;
1092     ZSTD_compressionParameters const cParams = ZSTD_getCParams(cLevel, (size_t)maxSrcFileSize, (size_t)dictSize);
1093     FIO_adjustMemLimitForPatchFromMode(prefs, dictSize, maxSrcFileSize);
1094     if (fileWindowLog > ZSTD_WINDOWLOG_MAX)
1095         DISPLAYLEVEL(1, "Max window log exceeded by file (compression ratio will suffer)\n");
1096     comprParams->windowLog = MAX(ZSTD_WINDOWLOG_MIN, MIN(ZSTD_WINDOWLOG_MAX, fileWindowLog));
1097     if (fileWindowLog > ZSTD_cycleLog(cParams.chainLog, cParams.strategy)) {
1098         if (!prefs->ldmFlag)
1099             DISPLAYLEVEL(2, "long mode automatically triggered\n");
1100         FIO_setLdmFlag(prefs, 1);
1101     }
1102     if (cParams.strategy >= ZSTD_btopt) {
1103         DISPLAYLEVEL(4, "[Optimal parser notes] Consider the following to improve patch size at the cost of speed:\n");
1104         DISPLAYLEVEL(4, "- Set a larger targetLength (e.g. --zstd=targetLength=4096)\n");
1105         DISPLAYLEVEL(4, "- Set a larger chainLog (e.g. --zstd=chainLog=%u)\n", ZSTD_CHAINLOG_MAX);
1106         DISPLAYLEVEL(4, "- Set a larger LDM hashLog (e.g. --zstd=ldmHashLog=%u)\n", ZSTD_LDM_HASHLOG_MAX);
1107         DISPLAYLEVEL(4, "- Set a smaller LDM rateLog (e.g. --zstd=ldmHashRateLog=%u)\n", ZSTD_LDM_HASHRATELOG_MIN);
1108         DISPLAYLEVEL(4, "Also consider playing around with searchLog and hashLog\n");
1109     }
1110 }
1111 
FIO_createCResources(FIO_prefs_t * const prefs,const char * dictFileName,unsigned long long const maxSrcFileSize,int cLevel,ZSTD_compressionParameters comprParams)1112 static cRess_t FIO_createCResources(FIO_prefs_t* const prefs,
1113                                     const char* dictFileName, unsigned long long const maxSrcFileSize,
1114                                     int cLevel, ZSTD_compressionParameters comprParams) {
1115     int useMMap = prefs->mmapDict == ZSTD_ps_enable;
1116     int forceNoUseMMap = prefs->mmapDict == ZSTD_ps_disable;
1117     FIO_dictBufferType_t dictBufferType;
1118     cRess_t ress;
1119     memset(&ress, 0, sizeof(ress));
1120 
1121     DISPLAYLEVEL(6, "FIO_createCResources \n");
1122     ress.cctx = ZSTD_createCCtx();
1123     if (ress.cctx == NULL)
1124         EXM_THROW(30, "allocation error (%s): can't create ZSTD_CCtx",
1125                     strerror(errno));
1126 
1127     FIO_getDictFileStat(dictFileName, &ress.dictFileStat);
1128 
1129     /* need to update memLimit before calling createDictBuffer
1130      * because of memLimit check inside it */
1131     if (prefs->patchFromMode) {
1132         U64 const dictSize = UTIL_getFileSizeStat(&ress.dictFileStat);
1133         unsigned long long const ssSize = (unsigned long long)prefs->streamSrcSize;
1134         useMMap |= dictSize > prefs->memLimit;
1135         FIO_adjustParamsForPatchFromMode(prefs, &comprParams, dictSize, ssSize > 0 ? ssSize : maxSrcFileSize, cLevel);
1136     }
1137 
1138     dictBufferType = (useMMap && !forceNoUseMMap) ? FIO_mmapDict : FIO_mallocDict;
1139     FIO_initDict(&ress.dict, dictFileName, prefs, &ress.dictFileStat, dictBufferType);   /* works with dictFileName==NULL */
1140 
1141     ress.writeCtx = AIO_WritePool_create(prefs, ZSTD_CStreamOutSize());
1142     ress.readCtx = AIO_ReadPool_create(prefs, ZSTD_CStreamInSize());
1143 
1144     /* Advanced parameters, including dictionary */
1145     if (dictFileName && (ress.dict.dictBuffer==NULL))
1146         EXM_THROW(32, "allocation error : can't create dictBuffer");
1147     ress.dictFileName = dictFileName;
1148 
1149     if (prefs->adaptiveMode && !prefs->ldmFlag && !comprParams.windowLog)
1150         comprParams.windowLog = ADAPT_WINDOWLOG_DEFAULT;
1151 
1152     CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_contentSizeFlag, prefs->contentSize) );  /* always enable content size when available (note: supposed to be default) */
1153     CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_dictIDFlag, prefs->dictIDFlag) );
1154     CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_checksumFlag, prefs->checksumFlag) );
1155     /* compression level */
1156     CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_compressionLevel, cLevel) );
1157     /* max compressed block size */
1158     CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_targetCBlockSize, (int)prefs->targetCBlockSize) );
1159     /* source size hint */
1160     CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_srcSizeHint, (int)prefs->srcSizeHint) );
1161     /* long distance matching */
1162     CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_enableLongDistanceMatching, prefs->ldmFlag) );
1163     CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_ldmHashLog, prefs->ldmHashLog) );
1164     CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_ldmMinMatch, prefs->ldmMinMatch) );
1165     if (prefs->ldmBucketSizeLog != FIO_LDM_PARAM_NOTSET) {
1166         CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_ldmBucketSizeLog, prefs->ldmBucketSizeLog) );
1167     }
1168     if (prefs->ldmHashRateLog != FIO_LDM_PARAM_NOTSET) {
1169         CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_ldmHashRateLog, prefs->ldmHashRateLog) );
1170     }
1171     CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_useRowMatchFinder, prefs->useRowMatchFinder));
1172     /* compression parameters */
1173     CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_windowLog, (int)comprParams.windowLog) );
1174     CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_chainLog, (int)comprParams.chainLog) );
1175     CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_hashLog, (int)comprParams.hashLog) );
1176     CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_searchLog, (int)comprParams.searchLog) );
1177     CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_minMatch, (int)comprParams.minMatch) );
1178     CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_targetLength, (int)comprParams.targetLength) );
1179     CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_strategy, (int)comprParams.strategy) );
1180     CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_literalCompressionMode, (int)prefs->literalCompressionMode) );
1181     CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_enableDedicatedDictSearch, 1) );
1182     /* multi-threading */
1183 #ifdef ZSTD_MULTITHREAD
1184     DISPLAYLEVEL(5,"set nb workers = %u \n", prefs->nbWorkers);
1185     CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_nbWorkers, prefs->nbWorkers) );
1186     CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_jobSize, prefs->blockSize) );
1187     if (prefs->overlapLog != FIO_OVERLAP_LOG_NOTSET) {
1188         DISPLAYLEVEL(3,"set overlapLog = %u \n", prefs->overlapLog);
1189         CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_overlapLog, prefs->overlapLog) );
1190     }
1191     CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_rsyncable, prefs->rsyncable) );
1192 #endif
1193     /* dictionary */
1194     if (prefs->patchFromMode) {
1195         CHECK( ZSTD_CCtx_refPrefix(ress.cctx, ress.dict.dictBuffer, ress.dict.dictBufferSize) );
1196     } else {
1197         CHECK( ZSTD_CCtx_loadDictionary_byReference(ress.cctx, ress.dict.dictBuffer, ress.dict.dictBufferSize) );
1198     }
1199 
1200     return ress;
1201 }
1202 
FIO_freeCResources(cRess_t * const ress)1203 static void FIO_freeCResources(cRess_t* const ress)
1204 {
1205     FIO_freeDict(&(ress->dict));
1206     AIO_WritePool_free(ress->writeCtx);
1207     AIO_ReadPool_free(ress->readCtx);
1208     ZSTD_freeCStream(ress->cctx);   /* never fails */
1209 }
1210 
1211 
1212 #ifdef ZSTD_GZCOMPRESS
1213 static unsigned long long
FIO_compressGzFrame(const cRess_t * ress,const char * srcFileName,U64 const srcFileSize,int compressionLevel,U64 * readsize)1214 FIO_compressGzFrame(const cRess_t* ress,  /* buffers & handlers are used, but not changed */
1215                     const char* srcFileName, U64 const srcFileSize,
1216                     int compressionLevel, U64* readsize)
1217 {
1218     unsigned long long inFileSize = 0, outFileSize = 0;
1219     z_stream strm;
1220     IOJob_t *writeJob = NULL;
1221 
1222     if (compressionLevel > Z_BEST_COMPRESSION)
1223         compressionLevel = Z_BEST_COMPRESSION;
1224 
1225     strm.zalloc = Z_NULL;
1226     strm.zfree = Z_NULL;
1227     strm.opaque = Z_NULL;
1228 
1229     {   int const ret = deflateInit2(&strm, compressionLevel, Z_DEFLATED,
1230                         15 /* maxWindowLogSize */ + 16 /* gzip only */,
1231                         8, Z_DEFAULT_STRATEGY); /* see https://www.zlib.net/manual.html */
1232         if (ret != Z_OK) {
1233             EXM_THROW(71, "zstd: %s: deflateInit2 error %d \n", srcFileName, ret);
1234     }   }
1235 
1236     writeJob = AIO_WritePool_acquireJob(ress->writeCtx);
1237     strm.next_in = 0;
1238     strm.avail_in = 0;
1239     strm.next_out = (Bytef*)writeJob->buffer;
1240     strm.avail_out = (uInt)writeJob->bufferSize;
1241 
1242     while (1) {
1243         int ret;
1244         if (strm.avail_in == 0) {
1245             AIO_ReadPool_fillBuffer(ress->readCtx, ZSTD_CStreamInSize());
1246             if (ress->readCtx->srcBufferLoaded == 0) break;
1247             inFileSize += ress->readCtx->srcBufferLoaded;
1248             strm.next_in = (z_const unsigned char*)ress->readCtx->srcBuffer;
1249             strm.avail_in = (uInt)ress->readCtx->srcBufferLoaded;
1250         }
1251 
1252         {
1253             size_t const availBefore = strm.avail_in;
1254             ret = deflate(&strm, Z_NO_FLUSH);
1255             AIO_ReadPool_consumeBytes(ress->readCtx, availBefore - strm.avail_in);
1256         }
1257 
1258         if (ret != Z_OK)
1259             EXM_THROW(72, "zstd: %s: deflate error %d \n", srcFileName, ret);
1260         {   size_t const cSize = writeJob->bufferSize - strm.avail_out;
1261             if (cSize) {
1262                 writeJob->usedBufferSize = cSize;
1263                 AIO_WritePool_enqueueAndReacquireWriteJob(&writeJob);
1264                 outFileSize += cSize;
1265                 strm.next_out = (Bytef*)writeJob->buffer;
1266                 strm.avail_out = (uInt)writeJob->bufferSize;
1267             }   }
1268         if (srcFileSize == UTIL_FILESIZE_UNKNOWN) {
1269             DISPLAYUPDATE_PROGRESS(
1270                     "\rRead : %u MB ==> %.2f%% ",
1271                     (unsigned)(inFileSize>>20),
1272                     (double)outFileSize/(double)inFileSize*100)
1273         } else {
1274             DISPLAYUPDATE_PROGRESS(
1275                     "\rRead : %u / %u MB ==> %.2f%% ",
1276                     (unsigned)(inFileSize>>20), (unsigned)(srcFileSize>>20),
1277                     (double)outFileSize/(double)inFileSize*100);
1278     }   }
1279 
1280     while (1) {
1281         int const ret = deflate(&strm, Z_FINISH);
1282         {   size_t const cSize = writeJob->bufferSize - strm.avail_out;
1283             if (cSize) {
1284                 writeJob->usedBufferSize = cSize;
1285                 AIO_WritePool_enqueueAndReacquireWriteJob(&writeJob);
1286                 outFileSize += cSize;
1287                 strm.next_out = (Bytef*)writeJob->buffer;
1288                 strm.avail_out = (uInt)writeJob->bufferSize;
1289             }   }
1290         if (ret == Z_STREAM_END) break;
1291         if (ret != Z_BUF_ERROR)
1292             EXM_THROW(77, "zstd: %s: deflate error %d \n", srcFileName, ret);
1293     }
1294 
1295     {   int const ret = deflateEnd(&strm);
1296         if (ret != Z_OK) {
1297             EXM_THROW(79, "zstd: %s: deflateEnd error %d \n", srcFileName, ret);
1298     }   }
1299     *readsize = inFileSize;
1300     AIO_WritePool_releaseIoJob(writeJob);
1301     AIO_WritePool_sparseWriteEnd(ress->writeCtx);
1302     return outFileSize;
1303 }
1304 #endif
1305 
1306 
1307 #ifdef ZSTD_LZMACOMPRESS
1308 static unsigned long long
FIO_compressLzmaFrame(cRess_t * ress,const char * srcFileName,U64 const srcFileSize,int compressionLevel,U64 * readsize,int plain_lzma)1309 FIO_compressLzmaFrame(cRess_t* ress,
1310                       const char* srcFileName, U64 const srcFileSize,
1311                       int compressionLevel, U64* readsize, int plain_lzma)
1312 {
1313     unsigned long long inFileSize = 0, outFileSize = 0;
1314     lzma_stream strm = LZMA_STREAM_INIT;
1315     lzma_action action = LZMA_RUN;
1316     lzma_ret ret;
1317     IOJob_t *writeJob = NULL;
1318 
1319     if (compressionLevel < 0) compressionLevel = 0;
1320     if (compressionLevel > 9) compressionLevel = 9;
1321 
1322     if (plain_lzma) {
1323         lzma_options_lzma opt_lzma;
1324         if (lzma_lzma_preset(&opt_lzma, compressionLevel))
1325             EXM_THROW(81, "zstd: %s: lzma_lzma_preset error", srcFileName);
1326         ret = lzma_alone_encoder(&strm, &opt_lzma); /* LZMA */
1327         if (ret != LZMA_OK)
1328             EXM_THROW(82, "zstd: %s: lzma_alone_encoder error %d", srcFileName, ret);
1329     } else {
1330         ret = lzma_easy_encoder(&strm, compressionLevel, LZMA_CHECK_CRC64); /* XZ */
1331         if (ret != LZMA_OK)
1332             EXM_THROW(83, "zstd: %s: lzma_easy_encoder error %d", srcFileName, ret);
1333     }
1334 
1335     writeJob =AIO_WritePool_acquireJob(ress->writeCtx);
1336     strm.next_out = (BYTE*)writeJob->buffer;
1337     strm.avail_out = writeJob->bufferSize;
1338     strm.next_in = 0;
1339     strm.avail_in = 0;
1340 
1341     while (1) {
1342         if (strm.avail_in == 0) {
1343             size_t const inSize = AIO_ReadPool_fillBuffer(ress->readCtx, ZSTD_CStreamInSize());
1344             if (ress->readCtx->srcBufferLoaded == 0) action = LZMA_FINISH;
1345             inFileSize += inSize;
1346             strm.next_in = (BYTE const*)ress->readCtx->srcBuffer;
1347             strm.avail_in = ress->readCtx->srcBufferLoaded;
1348         }
1349 
1350         {
1351             size_t const availBefore = strm.avail_in;
1352             ret = lzma_code(&strm, action);
1353             AIO_ReadPool_consumeBytes(ress->readCtx, availBefore - strm.avail_in);
1354         }
1355 
1356 
1357         if (ret != LZMA_OK && ret != LZMA_STREAM_END)
1358             EXM_THROW(84, "zstd: %s: lzma_code encoding error %d", srcFileName, ret);
1359         {   size_t const compBytes = writeJob->bufferSize - strm.avail_out;
1360             if (compBytes) {
1361                 writeJob->usedBufferSize = compBytes;
1362                 AIO_WritePool_enqueueAndReacquireWriteJob(&writeJob);
1363                 outFileSize += compBytes;
1364                 strm.next_out = (BYTE*)writeJob->buffer;
1365                 strm.avail_out = writeJob->bufferSize;
1366         }   }
1367         if (srcFileSize == UTIL_FILESIZE_UNKNOWN)
1368             DISPLAYUPDATE_PROGRESS("\rRead : %u MB ==> %.2f%%",
1369                             (unsigned)(inFileSize>>20),
1370                             (double)outFileSize/(double)inFileSize*100)
1371         else
1372             DISPLAYUPDATE_PROGRESS("\rRead : %u / %u MB ==> %.2f%%",
1373                             (unsigned)(inFileSize>>20), (unsigned)(srcFileSize>>20),
1374                             (double)outFileSize/(double)inFileSize*100);
1375         if (ret == LZMA_STREAM_END) break;
1376     }
1377 
1378     lzma_end(&strm);
1379     *readsize = inFileSize;
1380 
1381     AIO_WritePool_releaseIoJob(writeJob);
1382     AIO_WritePool_sparseWriteEnd(ress->writeCtx);
1383 
1384     return outFileSize;
1385 }
1386 #endif
1387 
1388 #ifdef ZSTD_LZ4COMPRESS
1389 
1390 #if LZ4_VERSION_NUMBER <= 10600
1391 #define LZ4F_blockLinked blockLinked
1392 #define LZ4F_max64KB max64KB
1393 #endif
1394 
FIO_LZ4_GetBlockSize_FromBlockId(int id)1395 static int FIO_LZ4_GetBlockSize_FromBlockId (int id) { return (1 << (8 + (2 * id))); }
1396 
1397 static unsigned long long
FIO_compressLz4Frame(cRess_t * ress,const char * srcFileName,U64 const srcFileSize,int compressionLevel,int checksumFlag,U64 * readsize)1398 FIO_compressLz4Frame(cRess_t* ress,
1399                      const char* srcFileName, U64 const srcFileSize,
1400                      int compressionLevel, int checksumFlag,
1401                      U64* readsize)
1402 {
1403     const size_t blockSize = FIO_LZ4_GetBlockSize_FromBlockId(LZ4F_max64KB);
1404     unsigned long long inFileSize = 0, outFileSize = 0;
1405 
1406     LZ4F_preferences_t prefs;
1407     LZ4F_compressionContext_t ctx;
1408 
1409     IOJob_t* writeJob = AIO_WritePool_acquireJob(ress->writeCtx);
1410 
1411     LZ4F_errorCode_t const errorCode = LZ4F_createCompressionContext(&ctx, LZ4F_VERSION);
1412     if (LZ4F_isError(errorCode))
1413         EXM_THROW(31, "zstd: failed to create lz4 compression context");
1414 
1415     memset(&prefs, 0, sizeof(prefs));
1416 
1417     assert(blockSize <= ress->readCtx->base.jobBufferSize);
1418 
1419     /* autoflush off to mitigate a bug in lz4<=1.9.3 for compression level 12 */
1420     prefs.autoFlush = 0;
1421     prefs.compressionLevel = compressionLevel;
1422     prefs.frameInfo.blockMode = LZ4F_blockLinked;
1423     prefs.frameInfo.blockSizeID = LZ4F_max64KB;
1424     prefs.frameInfo.contentChecksumFlag = (contentChecksum_t)checksumFlag;
1425 #if LZ4_VERSION_NUMBER >= 10600
1426     prefs.frameInfo.contentSize = (srcFileSize==UTIL_FILESIZE_UNKNOWN) ? 0 : srcFileSize;
1427 #endif
1428     assert(LZ4F_compressBound(blockSize, &prefs) <= writeJob->bufferSize);
1429 
1430     {
1431         size_t headerSize = LZ4F_compressBegin(ctx, writeJob->buffer, writeJob->bufferSize, &prefs);
1432         if (LZ4F_isError(headerSize))
1433             EXM_THROW(33, "File header generation failed : %s",
1434                             LZ4F_getErrorName(headerSize));
1435         writeJob->usedBufferSize = headerSize;
1436         AIO_WritePool_enqueueAndReacquireWriteJob(&writeJob);
1437         outFileSize += headerSize;
1438 
1439         /* Read first block */
1440         inFileSize += AIO_ReadPool_fillBuffer(ress->readCtx, blockSize);
1441 
1442         /* Main Loop */
1443         while (ress->readCtx->srcBufferLoaded) {
1444             size_t inSize = MIN(blockSize, ress->readCtx->srcBufferLoaded);
1445             size_t const outSize = LZ4F_compressUpdate(ctx, writeJob->buffer, writeJob->bufferSize,
1446                                                        ress->readCtx->srcBuffer, inSize, NULL);
1447             if (LZ4F_isError(outSize))
1448                 EXM_THROW(35, "zstd: %s: lz4 compression failed : %s",
1449                             srcFileName, LZ4F_getErrorName(outSize));
1450             outFileSize += outSize;
1451             if (srcFileSize == UTIL_FILESIZE_UNKNOWN) {
1452                 DISPLAYUPDATE_PROGRESS("\rRead : %u MB ==> %.2f%%",
1453                                 (unsigned)(inFileSize>>20),
1454                                 (double)outFileSize/(double)inFileSize*100)
1455             } else {
1456                 DISPLAYUPDATE_PROGRESS("\rRead : %u / %u MB ==> %.2f%%",
1457                                 (unsigned)(inFileSize>>20), (unsigned)(srcFileSize>>20),
1458                                 (double)outFileSize/(double)inFileSize*100);
1459             }
1460 
1461             /* Write Block */
1462             writeJob->usedBufferSize = outSize;
1463             AIO_WritePool_enqueueAndReacquireWriteJob(&writeJob);
1464 
1465             /* Read next block */
1466             AIO_ReadPool_consumeBytes(ress->readCtx, inSize);
1467             inFileSize += AIO_ReadPool_fillBuffer(ress->readCtx, blockSize);
1468         }
1469 
1470         /* End of Stream mark */
1471         headerSize = LZ4F_compressEnd(ctx, writeJob->buffer, writeJob->bufferSize, NULL);
1472         if (LZ4F_isError(headerSize))
1473             EXM_THROW(38, "zstd: %s: lz4 end of file generation failed : %s",
1474                         srcFileName, LZ4F_getErrorName(headerSize));
1475 
1476         writeJob->usedBufferSize = headerSize;
1477         AIO_WritePool_enqueueAndReacquireWriteJob(&writeJob);
1478         outFileSize += headerSize;
1479     }
1480 
1481     *readsize = inFileSize;
1482     LZ4F_freeCompressionContext(ctx);
1483     AIO_WritePool_releaseIoJob(writeJob);
1484     AIO_WritePool_sparseWriteEnd(ress->writeCtx);
1485 
1486     return outFileSize;
1487 }
1488 #endif
1489 
1490 static unsigned long long
FIO_compressZstdFrame(FIO_ctx_t * const fCtx,FIO_prefs_t * const prefs,const cRess_t * ressPtr,const char * srcFileName,U64 fileSize,int compressionLevel,U64 * readsize)1491 FIO_compressZstdFrame(FIO_ctx_t* const fCtx,
1492                       FIO_prefs_t* const prefs,
1493                       const cRess_t* ressPtr,
1494                       const char* srcFileName, U64 fileSize,
1495                       int compressionLevel, U64* readsize)
1496 {
1497     cRess_t const ress = *ressPtr;
1498     IOJob_t* writeJob = AIO_WritePool_acquireJob(ressPtr->writeCtx);
1499 
1500     U64 compressedfilesize = 0;
1501     ZSTD_EndDirective directive = ZSTD_e_continue;
1502     U64 pledgedSrcSize = ZSTD_CONTENTSIZE_UNKNOWN;
1503 
1504     /* stats */
1505     ZSTD_frameProgression previous_zfp_update = { 0, 0, 0, 0, 0, 0 };
1506     ZSTD_frameProgression previous_zfp_correction = { 0, 0, 0, 0, 0, 0 };
1507     typedef enum { noChange, slower, faster } speedChange_e;
1508     speedChange_e speedChange = noChange;
1509     unsigned flushWaiting = 0;
1510     unsigned inputPresented = 0;
1511     unsigned inputBlocked = 0;
1512     unsigned lastJobID = 0;
1513     UTIL_time_t lastAdaptTime = UTIL_getTime();
1514     U64 const adaptEveryMicro = REFRESH_RATE;
1515 
1516     UTIL_HumanReadableSize_t const file_hrs = UTIL_makeHumanReadableSize(fileSize);
1517 
1518     DISPLAYLEVEL(6, "compression using zstd format \n");
1519 
1520     /* init */
1521     if (fileSize != UTIL_FILESIZE_UNKNOWN) {
1522         pledgedSrcSize = fileSize;
1523         CHECK(ZSTD_CCtx_setPledgedSrcSize(ress.cctx, fileSize));
1524     } else if (prefs->streamSrcSize > 0) {
1525       /* unknown source size; use the declared stream size */
1526       pledgedSrcSize = prefs->streamSrcSize;
1527       CHECK( ZSTD_CCtx_setPledgedSrcSize(ress.cctx, prefs->streamSrcSize) );
1528     }
1529 
1530     {   int windowLog;
1531         UTIL_HumanReadableSize_t windowSize;
1532         CHECK(ZSTD_CCtx_getParameter(ress.cctx, ZSTD_c_windowLog, &windowLog));
1533         if (windowLog == 0) {
1534             if (prefs->ldmFlag) {
1535                 /* If long mode is set without a window size libzstd will set this size internally */
1536                 windowLog = ZSTD_WINDOWLOG_LIMIT_DEFAULT;
1537             } else {
1538                 const ZSTD_compressionParameters cParams = ZSTD_getCParams(compressionLevel, fileSize, 0);
1539                 windowLog = (int)cParams.windowLog;
1540             }
1541         }
1542         windowSize = UTIL_makeHumanReadableSize(MAX(1ULL, MIN(1ULL << windowLog, pledgedSrcSize)));
1543         DISPLAYLEVEL(4, "Decompression will require %.*f%s of memory\n", windowSize.precision, windowSize.value, windowSize.suffix);
1544     }
1545 
1546     /* Main compression loop */
1547     do {
1548         size_t stillToFlush;
1549         /* Fill input Buffer */
1550         size_t const inSize = AIO_ReadPool_fillBuffer(ress.readCtx, ZSTD_CStreamInSize());
1551         ZSTD_inBuffer inBuff = setInBuffer( ress.readCtx->srcBuffer, ress.readCtx->srcBufferLoaded, 0 );
1552         DISPLAYLEVEL(6, "fread %u bytes from source \n", (unsigned)inSize);
1553         *readsize += inSize;
1554 
1555         if ((ress.readCtx->srcBufferLoaded == 0) || (*readsize == fileSize))
1556             directive = ZSTD_e_end;
1557 
1558         stillToFlush = 1;
1559         while ((inBuff.pos != inBuff.size)   /* input buffer must be entirely ingested */
1560             || (directive == ZSTD_e_end && stillToFlush != 0) ) {
1561 
1562             size_t const oldIPos = inBuff.pos;
1563             ZSTD_outBuffer outBuff = setOutBuffer( writeJob->buffer, writeJob->bufferSize, 0 );
1564             size_t const toFlushNow = ZSTD_toFlushNow(ress.cctx);
1565             CHECK_V(stillToFlush, ZSTD_compressStream2(ress.cctx, &outBuff, &inBuff, directive));
1566             AIO_ReadPool_consumeBytes(ress.readCtx, inBuff.pos - oldIPos);
1567 
1568             /* count stats */
1569             inputPresented++;
1570             if (oldIPos == inBuff.pos) inputBlocked++;  /* input buffer is full and can't take any more : input speed is faster than consumption rate */
1571             if (!toFlushNow) flushWaiting = 1;
1572 
1573             /* Write compressed stream */
1574             DISPLAYLEVEL(6, "ZSTD_compress_generic(end:%u) => input pos(%u)<=(%u)size ; output generated %u bytes \n",
1575                          (unsigned)directive, (unsigned)inBuff.pos, (unsigned)inBuff.size, (unsigned)outBuff.pos);
1576             if (outBuff.pos) {
1577                 writeJob->usedBufferSize = outBuff.pos;
1578                 AIO_WritePool_enqueueAndReacquireWriteJob(&writeJob);
1579                 compressedfilesize += outBuff.pos;
1580             }
1581 
1582             /* adaptive mode : statistics measurement and speed correction */
1583             if (prefs->adaptiveMode && UTIL_clockSpanMicro(lastAdaptTime) > adaptEveryMicro) {
1584                 ZSTD_frameProgression const zfp = ZSTD_getFrameProgression(ress.cctx);
1585 
1586                 lastAdaptTime = UTIL_getTime();
1587 
1588                 /* check output speed */
1589                 if (zfp.currentJobID > 1) {  /* only possible if nbWorkers >= 1 */
1590 
1591                     unsigned long long newlyProduced = zfp.produced - previous_zfp_update.produced;
1592                     unsigned long long newlyFlushed = zfp.flushed - previous_zfp_update.flushed;
1593                     assert(zfp.produced >= previous_zfp_update.produced);
1594                     assert(prefs->nbWorkers >= 1);
1595 
1596                     /* test if compression is blocked
1597                         * either because output is slow and all buffers are full
1598                         * or because input is slow and no job can start while waiting for at least one buffer to be filled.
1599                         * note : exclude starting part, since currentJobID > 1 */
1600                     if ( (zfp.consumed == previous_zfp_update.consumed)   /* no data compressed : no data available, or no more buffer to compress to, OR compression is really slow (compression of a single block is slower than update rate)*/
1601                         && (zfp.nbActiveWorkers == 0)                       /* confirmed : no compression ongoing */
1602                         ) {
1603                         DISPLAYLEVEL(6, "all buffers full : compression stopped => slow down \n")
1604                         speedChange = slower;
1605                     }
1606 
1607                     previous_zfp_update = zfp;
1608 
1609                     if ( (newlyProduced > (newlyFlushed * 9 / 8))   /* compression produces more data than output can flush (though production can be spiky, due to work unit : (N==4)*block sizes) */
1610                         && (flushWaiting == 0)                        /* flush speed was never slowed by lack of production, so it's operating at max capacity */
1611                         ) {
1612                         DISPLAYLEVEL(6, "compression faster than flush (%llu > %llu), and flushed was never slowed down by lack of production => slow down \n", newlyProduced, newlyFlushed);
1613                         speedChange = slower;
1614                     }
1615                     flushWaiting = 0;
1616                 }
1617 
1618                 /* course correct only if there is at least one new job completed */
1619                 if (zfp.currentJobID > lastJobID) {
1620                     DISPLAYLEVEL(6, "compression level adaptation check \n")
1621 
1622                     /* check input speed */
1623                     if (zfp.currentJobID > (unsigned)(prefs->nbWorkers+1)) {   /* warm up period, to fill all workers */
1624                         if (inputBlocked <= 0) {
1625                             DISPLAYLEVEL(6, "input is never blocked => input is slower than ingestion \n");
1626                             speedChange = slower;
1627                         } else if (speedChange == noChange) {
1628                             unsigned long long newlyIngested = zfp.ingested - previous_zfp_correction.ingested;
1629                             unsigned long long newlyConsumed = zfp.consumed - previous_zfp_correction.consumed;
1630                             unsigned long long newlyProduced = zfp.produced - previous_zfp_correction.produced;
1631                             unsigned long long newlyFlushed  = zfp.flushed  - previous_zfp_correction.flushed;
1632                             previous_zfp_correction = zfp;
1633                             assert(inputPresented > 0);
1634                             DISPLAYLEVEL(6, "input blocked %u/%u(%.2f) - ingested:%u vs %u:consumed - flushed:%u vs %u:produced \n",
1635                                             inputBlocked, inputPresented, (double)inputBlocked/inputPresented*100,
1636                                             (unsigned)newlyIngested, (unsigned)newlyConsumed,
1637                                             (unsigned)newlyFlushed, (unsigned)newlyProduced);
1638                             if ( (inputBlocked > inputPresented / 8)     /* input is waiting often, because input buffers is full : compression or output too slow */
1639                                 && (newlyFlushed * 33 / 32 > newlyProduced)  /* flush everything that is produced */
1640                                 && (newlyIngested * 33 / 32 > newlyConsumed) /* input speed as fast or faster than compression speed */
1641                             ) {
1642                                 DISPLAYLEVEL(6, "recommend faster as in(%llu) >= (%llu)comp(%llu) <= out(%llu) \n",
1643                                                 newlyIngested, newlyConsumed, newlyProduced, newlyFlushed);
1644                                 speedChange = faster;
1645                             }
1646                         }
1647                         inputBlocked = 0;
1648                         inputPresented = 0;
1649                     }
1650 
1651                     if (speedChange == slower) {
1652                         DISPLAYLEVEL(6, "slower speed , higher compression \n")
1653                         compressionLevel ++;
1654                         if (compressionLevel > ZSTD_maxCLevel()) compressionLevel = ZSTD_maxCLevel();
1655                         if (compressionLevel > prefs->maxAdaptLevel) compressionLevel = prefs->maxAdaptLevel;
1656                         compressionLevel += (compressionLevel == 0);   /* skip 0 */
1657                         ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_compressionLevel, compressionLevel);
1658                     }
1659                     if (speedChange == faster) {
1660                         DISPLAYLEVEL(6, "faster speed , lighter compression \n")
1661                         compressionLevel --;
1662                         if (compressionLevel < prefs->minAdaptLevel) compressionLevel = prefs->minAdaptLevel;
1663                         compressionLevel -= (compressionLevel == 0);   /* skip 0 */
1664                         ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_compressionLevel, compressionLevel);
1665                     }
1666                     speedChange = noChange;
1667 
1668                     lastJobID = zfp.currentJobID;
1669                 }  /* if (zfp.currentJobID > lastJobID) */
1670             } /* if (prefs->adaptiveMode && UTIL_clockSpanMicro(lastAdaptTime) > adaptEveryMicro) */
1671 
1672             /* display notification */
1673             if (SHOULD_DISPLAY_PROGRESS() && READY_FOR_UPDATE()) {
1674                 ZSTD_frameProgression const zfp = ZSTD_getFrameProgression(ress.cctx);
1675                 double const cShare = (double)zfp.produced / (double)(zfp.consumed + !zfp.consumed/*avoid div0*/) * 100;
1676                 UTIL_HumanReadableSize_t const buffered_hrs = UTIL_makeHumanReadableSize(zfp.ingested - zfp.consumed);
1677                 UTIL_HumanReadableSize_t const consumed_hrs = UTIL_makeHumanReadableSize(zfp.consumed);
1678                 UTIL_HumanReadableSize_t const produced_hrs = UTIL_makeHumanReadableSize(zfp.produced);
1679 
1680                 DELAY_NEXT_UPDATE();
1681 
1682                 /* display progress notifications */
1683                 DISPLAY_PROGRESS("\r%79s\r", "");    /* Clear out the current displayed line */
1684                 if (g_display_prefs.displayLevel >= 3) {
1685                     /* Verbose progress update */
1686                     DISPLAY_PROGRESS(
1687                             "(L%i) Buffered:%5.*f%s - Consumed:%5.*f%s - Compressed:%5.*f%s => %.2f%% ",
1688                             compressionLevel,
1689                             buffered_hrs.precision, buffered_hrs.value, buffered_hrs.suffix,
1690                             consumed_hrs.precision, consumed_hrs.value, consumed_hrs.suffix,
1691                             produced_hrs.precision, produced_hrs.value, produced_hrs.suffix,
1692                             cShare );
1693                 } else {
1694                     /* Require level 2 or forcibly displayed progress counter for summarized updates */
1695                     if (fCtx->nbFilesTotal > 1) {
1696                         size_t srcFileNameSize = strlen(srcFileName);
1697                         /* Ensure that the string we print is roughly the same size each time */
1698                         if (srcFileNameSize > 18) {
1699                             const char* truncatedSrcFileName = srcFileName + srcFileNameSize - 15;
1700                             DISPLAY_PROGRESS("Compress: %u/%u files. Current: ...%s ",
1701                                         fCtx->currFileIdx+1, fCtx->nbFilesTotal, truncatedSrcFileName);
1702                         } else {
1703                             DISPLAY_PROGRESS("Compress: %u/%u files. Current: %*s ",
1704                                         fCtx->currFileIdx+1, fCtx->nbFilesTotal, (int)(18-srcFileNameSize), srcFileName);
1705                         }
1706                     }
1707                     DISPLAY_PROGRESS("Read:%6.*f%4s ", consumed_hrs.precision, consumed_hrs.value, consumed_hrs.suffix);
1708                     if (fileSize != UTIL_FILESIZE_UNKNOWN)
1709                         DISPLAY_PROGRESS("/%6.*f%4s", file_hrs.precision, file_hrs.value, file_hrs.suffix);
1710                     DISPLAY_PROGRESS(" ==> %2.f%%", cShare);
1711                 }
1712             }  /* if (SHOULD_DISPLAY_PROGRESS() && READY_FOR_UPDATE()) */
1713         }  /* while ((inBuff.pos != inBuff.size) */
1714     } while (directive != ZSTD_e_end);
1715 
1716     if (fileSize != UTIL_FILESIZE_UNKNOWN && *readsize != fileSize) {
1717         EXM_THROW(27, "Read error : Incomplete read : %llu / %llu B",
1718                 (unsigned long long)*readsize, (unsigned long long)fileSize);
1719     }
1720 
1721     AIO_WritePool_releaseIoJob(writeJob);
1722     AIO_WritePool_sparseWriteEnd(ressPtr->writeCtx);
1723 
1724     return compressedfilesize;
1725 }
1726 
1727 /*! FIO_compressFilename_internal() :
1728  *  same as FIO_compressFilename_extRess(), with `ress.desFile` already opened.
1729  *  @return : 0 : compression completed correctly,
1730  *            1 : missing or pb opening srcFileName
1731  */
1732 static int
FIO_compressFilename_internal(FIO_ctx_t * const fCtx,FIO_prefs_t * const prefs,cRess_t ress,const char * dstFileName,const char * srcFileName,int compressionLevel)1733 FIO_compressFilename_internal(FIO_ctx_t* const fCtx,
1734                               FIO_prefs_t* const prefs,
1735                               cRess_t ress,
1736                               const char* dstFileName, const char* srcFileName,
1737                               int compressionLevel)
1738 {
1739     UTIL_time_t const timeStart = UTIL_getTime();
1740     clock_t const cpuStart = clock();
1741     U64 readsize = 0;
1742     U64 compressedfilesize = 0;
1743     U64 const fileSize = UTIL_getFileSize(srcFileName);
1744     DISPLAYLEVEL(5, "%s: %llu bytes \n", srcFileName, (unsigned long long)fileSize);
1745 
1746     /* compression format selection */
1747     switch (prefs->compressionType) {
1748         default:
1749         case FIO_zstdCompression:
1750             compressedfilesize = FIO_compressZstdFrame(fCtx, prefs, &ress, srcFileName, fileSize, compressionLevel, &readsize);
1751             break;
1752 
1753         case FIO_gzipCompression:
1754 #ifdef ZSTD_GZCOMPRESS
1755             compressedfilesize = FIO_compressGzFrame(&ress, srcFileName, fileSize, compressionLevel, &readsize);
1756 #else
1757             (void)compressionLevel;
1758             EXM_THROW(20, "zstd: %s: file cannot be compressed as gzip (zstd compiled without ZSTD_GZCOMPRESS) -- ignored \n",
1759                             srcFileName);
1760 #endif
1761             break;
1762 
1763         case FIO_xzCompression:
1764         case FIO_lzmaCompression:
1765 #ifdef ZSTD_LZMACOMPRESS
1766             compressedfilesize = FIO_compressLzmaFrame(&ress, srcFileName, fileSize, compressionLevel, &readsize, prefs->compressionType==FIO_lzmaCompression);
1767 #else
1768             (void)compressionLevel;
1769             EXM_THROW(20, "zstd: %s: file cannot be compressed as xz/lzma (zstd compiled without ZSTD_LZMACOMPRESS) -- ignored \n",
1770                             srcFileName);
1771 #endif
1772             break;
1773 
1774         case FIO_lz4Compression:
1775 #ifdef ZSTD_LZ4COMPRESS
1776             compressedfilesize = FIO_compressLz4Frame(&ress, srcFileName, fileSize, compressionLevel, prefs->checksumFlag, &readsize);
1777 #else
1778             (void)compressionLevel;
1779             EXM_THROW(20, "zstd: %s: file cannot be compressed as lz4 (zstd compiled without ZSTD_LZ4COMPRESS) -- ignored \n",
1780                             srcFileName);
1781 #endif
1782             break;
1783     }
1784 
1785     /* Status */
1786     fCtx->totalBytesInput += (size_t)readsize;
1787     fCtx->totalBytesOutput += (size_t)compressedfilesize;
1788     DISPLAY_PROGRESS("\r%79s\r", "");
1789     if (FIO_shouldDisplayFileSummary(fCtx)) {
1790         UTIL_HumanReadableSize_t hr_isize = UTIL_makeHumanReadableSize((U64) readsize);
1791         UTIL_HumanReadableSize_t hr_osize = UTIL_makeHumanReadableSize((U64) compressedfilesize);
1792         if (readsize == 0) {
1793             DISPLAY_SUMMARY("%-20s :  (%6.*f%s => %6.*f%s, %s) \n",
1794                 srcFileName,
1795                 hr_isize.precision, hr_isize.value, hr_isize.suffix,
1796                 hr_osize.precision, hr_osize.value, hr_osize.suffix,
1797                 dstFileName);
1798         } else {
1799             DISPLAY_SUMMARY("%-20s :%6.2f%%   (%6.*f%s => %6.*f%s, %s) \n",
1800                 srcFileName,
1801                 (double)compressedfilesize / (double)readsize * 100,
1802                 hr_isize.precision, hr_isize.value, hr_isize.suffix,
1803                 hr_osize.precision, hr_osize.value, hr_osize.suffix,
1804                 dstFileName);
1805         }
1806     }
1807 
1808     /* Elapsed Time and CPU Load */
1809     {   clock_t const cpuEnd = clock();
1810         double const cpuLoad_s = (double)(cpuEnd - cpuStart) / CLOCKS_PER_SEC;
1811         U64 const timeLength_ns = UTIL_clockSpanNano(timeStart);
1812         double const timeLength_s = (double)timeLength_ns / 1000000000;
1813         double const cpuLoad_pct = (cpuLoad_s / timeLength_s) * 100;
1814         DISPLAYLEVEL(4, "%-20s : Completed in %.2f sec  (cpu load : %.0f%%)\n",
1815                         srcFileName, timeLength_s, cpuLoad_pct);
1816     }
1817     return 0;
1818 }
1819 
1820 
1821 /*! FIO_compressFilename_dstFile() :
1822  *  open dstFileName, or pass-through if ress.file != NULL,
1823  *  then start compression with FIO_compressFilename_internal().
1824  *  Manages source removal (--rm) and file permissions transfer.
1825  *  note : ress.srcFile must be != NULL,
1826  *  so reach this function through FIO_compressFilename_srcFile().
1827  *  @return : 0 : compression completed correctly,
1828  *            1 : pb
1829  */
FIO_compressFilename_dstFile(FIO_ctx_t * const fCtx,FIO_prefs_t * const prefs,cRess_t ress,const char * dstFileName,const char * srcFileName,const stat_t * srcFileStat,int compressionLevel)1830 static int FIO_compressFilename_dstFile(FIO_ctx_t* const fCtx,
1831                                         FIO_prefs_t* const prefs,
1832                                         cRess_t ress,
1833                                         const char* dstFileName,
1834                                         const char* srcFileName,
1835                                         const stat_t* srcFileStat,
1836                                         int compressionLevel)
1837 {
1838     int closeDstFile = 0;
1839     int result;
1840     int transferStat = 0;
1841     int dstFd = -1;
1842 
1843     assert(AIO_ReadPool_getFile(ress.readCtx) != NULL);
1844     if (AIO_WritePool_getFile(ress.writeCtx) == NULL) {
1845         int dstFileInitialPermissions = DEFAULT_FILE_PERMISSIONS;
1846         if ( strcmp (srcFileName, stdinmark)
1847           && strcmp (dstFileName, stdoutmark)
1848           && UTIL_isRegularFileStat(srcFileStat) ) {
1849             transferStat = 1;
1850             dstFileInitialPermissions = TEMPORARY_FILE_PERMISSIONS;
1851         }
1852 
1853         closeDstFile = 1;
1854         DISPLAYLEVEL(6, "FIO_compressFilename_dstFile: opening dst: %s \n", dstFileName);
1855         {   FILE *dstFile = FIO_openDstFile(fCtx, prefs, srcFileName, dstFileName, dstFileInitialPermissions);
1856             if (dstFile==NULL) return 1;  /* could not open dstFileName */
1857             dstFd = fileno(dstFile);
1858             AIO_WritePool_setFile(ress.writeCtx, dstFile);
1859         }
1860         /* Must only be added after FIO_openDstFile() succeeds.
1861          * Otherwise we may delete the destination file if it already exists,
1862          * and the user presses Ctrl-C when asked if they wish to overwrite.
1863          */
1864         addHandler(dstFileName);
1865     }
1866 
1867     result = FIO_compressFilename_internal(fCtx, prefs, ress, dstFileName, srcFileName, compressionLevel);
1868 
1869     if (closeDstFile) {
1870         clearHandler();
1871 
1872         if (transferStat) {
1873             UTIL_setFDStat(dstFd, dstFileName, srcFileStat);
1874         }
1875 
1876         DISPLAYLEVEL(6, "FIO_compressFilename_dstFile: closing dst: %s \n", dstFileName);
1877         if (AIO_WritePool_closeFile(ress.writeCtx)) { /* error closing file */
1878             DISPLAYLEVEL(1, "zstd: %s: %s \n", dstFileName, strerror(errno));
1879             result=1;
1880         }
1881 
1882         if (transferStat) {
1883             UTIL_utime(dstFileName, srcFileStat);
1884         }
1885 
1886         if ( (result != 0)  /* operation failure */
1887           && strcmp(dstFileName, stdoutmark)  /* special case : don't remove() stdout */
1888           ) {
1889             FIO_removeFile(dstFileName); /* remove compression artefact; note don't do anything special if remove() fails */
1890         }
1891     }
1892 
1893     return result;
1894 }
1895 
1896 /* List used to compare file extensions (used with --exclude-compressed flag)
1897 * Different from the suffixList and should only apply to ZSTD compress operationResult
1898 */
1899 static const char *compressedFileExtensions[] = {
1900     ZSTD_EXTENSION,
1901     TZSTD_EXTENSION,
1902     GZ_EXTENSION,
1903     TGZ_EXTENSION,
1904     LZMA_EXTENSION,
1905     XZ_EXTENSION,
1906     TXZ_EXTENSION,
1907     LZ4_EXTENSION,
1908     TLZ4_EXTENSION,
1909     ".7z",
1910     ".aa3",
1911     ".aac",
1912     ".aar",
1913     ".ace",
1914     ".alac",
1915     ".ape",
1916     ".apk",
1917     ".apng",
1918     ".arc",
1919     ".archive",
1920     ".arj",
1921     ".ark",
1922     ".asf",
1923     ".avi",
1924     ".avif",
1925     ".ba",
1926     ".br",
1927     ".bz2",
1928     ".cab",
1929     ".cdx",
1930     ".chm",
1931     ".cr2",
1932     ".divx",
1933     ".dmg",
1934     ".dng",
1935     ".docm",
1936     ".docx",
1937     ".dotm",
1938     ".dotx",
1939     ".dsft",
1940     ".ear",
1941     ".eftx",
1942     ".emz",
1943     ".eot",
1944     ".epub",
1945     ".f4v",
1946     ".flac",
1947     ".flv",
1948     ".gho",
1949     ".gif",
1950     ".gifv",
1951     ".gnp",
1952     ".iso",
1953     ".jar",
1954     ".jpeg",
1955     ".jpg",
1956     ".jxl",
1957     ".lz",
1958     ".lzh",
1959     ".m4a",
1960     ".m4v",
1961     ".mkv",
1962     ".mov",
1963     ".mp2",
1964     ".mp3",
1965     ".mp4",
1966     ".mpa",
1967     ".mpc",
1968     ".mpe",
1969     ".mpeg",
1970     ".mpg",
1971     ".mpl",
1972     ".mpv",
1973     ".msi",
1974     ".odp",
1975     ".ods",
1976     ".odt",
1977     ".ogg",
1978     ".ogv",
1979     ".otp",
1980     ".ots",
1981     ".ott",
1982     ".pea",
1983     ".png",
1984     ".pptx",
1985     ".qt",
1986     ".rar",
1987     ".s7z",
1988     ".sfx",
1989     ".sit",
1990     ".sitx",
1991     ".sqx",
1992     ".svgz",
1993     ".swf",
1994     ".tbz2",
1995     ".tib",
1996     ".tlz",
1997     ".vob",
1998     ".war",
1999     ".webm",
2000     ".webp",
2001     ".wma",
2002     ".wmv",
2003     ".woff",
2004     ".woff2",
2005     ".wvl",
2006     ".xlsx",
2007     ".xpi",
2008     ".xps",
2009     ".zip",
2010     ".zipx",
2011     ".zoo",
2012     ".zpaq",
2013     NULL
2014 };
2015 
2016 /*! FIO_compressFilename_srcFile() :
2017  *  @return : 0 : compression completed correctly,
2018  *            1 : missing or pb opening srcFileName
2019  */
2020 static int
FIO_compressFilename_srcFile(FIO_ctx_t * const fCtx,FIO_prefs_t * const prefs,cRess_t ress,const char * dstFileName,const char * srcFileName,int compressionLevel)2021 FIO_compressFilename_srcFile(FIO_ctx_t* const fCtx,
2022                              FIO_prefs_t* const prefs,
2023                              cRess_t ress,
2024                              const char* dstFileName,
2025                              const char* srcFileName,
2026                              int compressionLevel)
2027 {
2028     int result;
2029     FILE* srcFile;
2030     stat_t srcFileStat;
2031     U64 fileSize = UTIL_FILESIZE_UNKNOWN;
2032     DISPLAYLEVEL(6, "FIO_compressFilename_srcFile: %s \n", srcFileName);
2033 
2034     if (strcmp(srcFileName, stdinmark)) {
2035         if (UTIL_stat(srcFileName, &srcFileStat)) {
2036             /* failure to stat at all is handled during opening */
2037 
2038             /* ensure src is not a directory */
2039             if (UTIL_isDirectoryStat(&srcFileStat)) {
2040                 DISPLAYLEVEL(1, "zstd: %s is a directory -- ignored \n", srcFileName);
2041                 return 1;
2042             }
2043 
2044             /* ensure src is not the same as dict (if present) */
2045             if (ress.dictFileName != NULL && UTIL_isSameFileStat(srcFileName, ress.dictFileName, &srcFileStat, &ress.dictFileStat)) {
2046                 DISPLAYLEVEL(1, "zstd: cannot use %s as an input file and dictionary \n", srcFileName);
2047                 return 1;
2048             }
2049         }
2050     }
2051 
2052     /* Check if "srcFile" is compressed. Only done if --exclude-compressed flag is used
2053     * YES => ZSTD will skip compression of the file and will return 0.
2054     * NO => ZSTD will resume with compress operation.
2055     */
2056     if (prefs->excludeCompressedFiles == 1 && UTIL_isCompressedFile(srcFileName, compressedFileExtensions)) {
2057         DISPLAYLEVEL(4, "File is already compressed : %s \n", srcFileName);
2058         return 0;
2059     }
2060 
2061     srcFile = FIO_openSrcFile(prefs, srcFileName, &srcFileStat);
2062     if (srcFile == NULL) return 1;   /* srcFile could not be opened */
2063 
2064     /* Don't use AsyncIO for small files */
2065     if (strcmp(srcFileName, stdinmark)) /* Stdin doesn't have stats */
2066         fileSize = UTIL_getFileSizeStat(&srcFileStat);
2067     if(fileSize != UTIL_FILESIZE_UNKNOWN && fileSize < ZSTD_BLOCKSIZE_MAX * 3) {
2068         AIO_ReadPool_setAsync(ress.readCtx, 0);
2069         AIO_WritePool_setAsync(ress.writeCtx, 0);
2070     } else {
2071         AIO_ReadPool_setAsync(ress.readCtx, 1);
2072         AIO_WritePool_setAsync(ress.writeCtx, 1);
2073     }
2074 
2075     AIO_ReadPool_setFile(ress.readCtx, srcFile);
2076     result = FIO_compressFilename_dstFile(
2077             fCtx, prefs, ress,
2078             dstFileName, srcFileName,
2079             &srcFileStat, compressionLevel);
2080     AIO_ReadPool_closeFile(ress.readCtx);
2081 
2082     if ( prefs->removeSrcFile  /* --rm */
2083       && result == 0           /* success */
2084       && strcmp(srcFileName, stdinmark)  /* exception : don't erase stdin */
2085       ) {
2086         /* We must clear the handler, since after this point calling it would
2087          * delete both the source and destination files.
2088          */
2089         clearHandler();
2090         if (FIO_removeFile(srcFileName))
2091             EXM_THROW(1, "zstd: %s: %s", srcFileName, strerror(errno));
2092     }
2093     return result;
2094 }
2095 
2096 static const char*
checked_index(const char * options[],size_t length,size_t index)2097 checked_index(const char* options[], size_t length, size_t index) {
2098     assert(index < length);
2099     /* Necessary to avoid warnings since -O3 will omit the above `assert` */
2100     (void) length;
2101     return options[index];
2102 }
2103 
2104 #define INDEX(options, index) checked_index((options), sizeof(options)  / sizeof(char*), (size_t)(index))
2105 
FIO_displayCompressionParameters(const FIO_prefs_t * prefs)2106 void FIO_displayCompressionParameters(const FIO_prefs_t* prefs)
2107 {
2108     static const char* formatOptions[5] = {ZSTD_EXTENSION, GZ_EXTENSION, XZ_EXTENSION,
2109                                            LZMA_EXTENSION, LZ4_EXTENSION};
2110     static const char* sparseOptions[3] = {" --no-sparse", "", " --sparse"};
2111     static const char* checkSumOptions[3] = {" --no-check", "", " --check"};
2112     static const char* rowMatchFinderOptions[3] = {"", " --no-row-match-finder", " --row-match-finder"};
2113     static const char* compressLiteralsOptions[3] = {"", " --compress-literals", " --no-compress-literals"};
2114 
2115     assert(g_display_prefs.displayLevel >= 4);
2116 
2117     DISPLAY("--format=%s", formatOptions[prefs->compressionType]);
2118     DISPLAY("%s", INDEX(sparseOptions, prefs->sparseFileSupport));
2119     DISPLAY("%s", prefs->dictIDFlag ? "" : " --no-dictID");
2120     DISPLAY("%s", INDEX(checkSumOptions, prefs->checksumFlag));
2121     DISPLAY(" --block-size=%d", prefs->blockSize);
2122     if (prefs->adaptiveMode)
2123         DISPLAY(" --adapt=min=%d,max=%d", prefs->minAdaptLevel, prefs->maxAdaptLevel);
2124     DISPLAY("%s", INDEX(rowMatchFinderOptions, prefs->useRowMatchFinder));
2125     DISPLAY("%s", prefs->rsyncable ? " --rsyncable" : "");
2126     if (prefs->streamSrcSize)
2127         DISPLAY(" --stream-size=%u", (unsigned) prefs->streamSrcSize);
2128     if (prefs->srcSizeHint)
2129         DISPLAY(" --size-hint=%d", prefs->srcSizeHint);
2130     if (prefs->targetCBlockSize)
2131         DISPLAY(" --target-compressed-block-size=%u", (unsigned) prefs->targetCBlockSize);
2132     DISPLAY("%s", INDEX(compressLiteralsOptions, prefs->literalCompressionMode));
2133     DISPLAY(" --memory=%u", prefs->memLimit ? prefs->memLimit : 128 MB);
2134     DISPLAY(" --threads=%d", prefs->nbWorkers);
2135     DISPLAY("%s", prefs->excludeCompressedFiles ? " --exclude-compressed" : "");
2136     DISPLAY(" --%scontent-size", prefs->contentSize ? "" : "no-");
2137     DISPLAY("\n");
2138 }
2139 
2140 #undef INDEX
2141 
FIO_compressFilename(FIO_ctx_t * const fCtx,FIO_prefs_t * const prefs,const char * dstFileName,const char * srcFileName,const char * dictFileName,int compressionLevel,ZSTD_compressionParameters comprParams)2142 int FIO_compressFilename(FIO_ctx_t* const fCtx, FIO_prefs_t* const prefs, const char* dstFileName,
2143                          const char* srcFileName, const char* dictFileName,
2144                          int compressionLevel, ZSTD_compressionParameters comprParams)
2145 {
2146     cRess_t ress = FIO_createCResources(prefs, dictFileName, UTIL_getFileSize(srcFileName), compressionLevel, comprParams);
2147     int const result = FIO_compressFilename_srcFile(fCtx, prefs, ress, dstFileName, srcFileName, compressionLevel);
2148 
2149 #define DISPLAY_LEVEL_DEFAULT 2
2150 
2151     FIO_freeCResources(&ress);
2152     return result;
2153 }
2154 
2155 /* FIO_determineCompressedName() :
2156  * create a destination filename for compressed srcFileName.
2157  * @return a pointer to it.
2158  * This function never returns an error (it may abort() in case of pb)
2159  */
2160 static const char*
FIO_determineCompressedName(const char * srcFileName,const char * outDirName,const char * suffix)2161 FIO_determineCompressedName(const char* srcFileName, const char* outDirName, const char* suffix)
2162 {
2163     static size_t dfnbCapacity = 0;
2164     static char* dstFileNameBuffer = NULL;   /* using static allocation : this function cannot be multi-threaded */
2165     char* outDirFilename = NULL;
2166     size_t sfnSize = strlen(srcFileName);
2167     size_t const srcSuffixLen = strlen(suffix);
2168 
2169     if(!strcmp(srcFileName, stdinmark)) {
2170         return stdoutmark;
2171     }
2172 
2173     if (outDirName) {
2174         outDirFilename = FIO_createFilename_fromOutDir(srcFileName, outDirName, srcSuffixLen);
2175         sfnSize = strlen(outDirFilename);
2176         assert(outDirFilename != NULL);
2177     }
2178 
2179     if (dfnbCapacity <= sfnSize+srcSuffixLen+1) {
2180         /* resize buffer for dstName */
2181         free(dstFileNameBuffer);
2182         dfnbCapacity = sfnSize + srcSuffixLen + 30;
2183         dstFileNameBuffer = (char*)malloc(dfnbCapacity);
2184         if (!dstFileNameBuffer) {
2185             EXM_THROW(30, "zstd: %s", strerror(errno));
2186         }
2187     }
2188     assert(dstFileNameBuffer != NULL);
2189 
2190     if (outDirFilename) {
2191         memcpy(dstFileNameBuffer, outDirFilename, sfnSize);
2192         free(outDirFilename);
2193     } else {
2194         memcpy(dstFileNameBuffer, srcFileName, sfnSize);
2195     }
2196     memcpy(dstFileNameBuffer+sfnSize, suffix, srcSuffixLen+1 /* Include terminating null */);
2197     return dstFileNameBuffer;
2198 }
2199 
FIO_getLargestFileSize(const char ** inFileNames,unsigned nbFiles)2200 static unsigned long long FIO_getLargestFileSize(const char** inFileNames, unsigned nbFiles)
2201 {
2202     size_t i;
2203     unsigned long long fileSize, maxFileSize = 0;
2204     for (i = 0; i < nbFiles; i++) {
2205         fileSize = UTIL_getFileSize(inFileNames[i]);
2206         maxFileSize = fileSize > maxFileSize ? fileSize : maxFileSize;
2207     }
2208     return maxFileSize;
2209 }
2210 
2211 /* FIO_compressMultipleFilenames() :
2212  * compress nbFiles files
2213  * into either one destination (outFileName),
2214  * or into one file each (outFileName == NULL, but suffix != NULL),
2215  * or into a destination folder (specified with -O)
2216  */
FIO_compressMultipleFilenames(FIO_ctx_t * const fCtx,FIO_prefs_t * const prefs,const char ** inFileNamesTable,const char * outMirroredRootDirName,const char * outDirName,const char * outFileName,const char * suffix,const char * dictFileName,int compressionLevel,ZSTD_compressionParameters comprParams)2217 int FIO_compressMultipleFilenames(FIO_ctx_t* const fCtx,
2218                                   FIO_prefs_t* const prefs,
2219                                   const char** inFileNamesTable,
2220                                   const char* outMirroredRootDirName,
2221                                   const char* outDirName,
2222                                   const char* outFileName, const char* suffix,
2223                                   const char* dictFileName, int compressionLevel,
2224                                   ZSTD_compressionParameters comprParams)
2225 {
2226     int status;
2227     int error = 0;
2228     cRess_t ress = FIO_createCResources(prefs, dictFileName,
2229         FIO_getLargestFileSize(inFileNamesTable, (unsigned)fCtx->nbFilesTotal),
2230         compressionLevel, comprParams);
2231 
2232     /* init */
2233     assert(outFileName != NULL || suffix != NULL);
2234     if (outFileName != NULL) {   /* output into a single destination (stdout typically) */
2235         FILE *dstFile;
2236         if (FIO_multiFilesConcatWarning(fCtx, prefs, outFileName, 1 /* displayLevelCutoff */)) {
2237             FIO_freeCResources(&ress);
2238             return 1;
2239         }
2240         dstFile = FIO_openDstFile(fCtx, prefs, NULL, outFileName, DEFAULT_FILE_PERMISSIONS);
2241         if (dstFile == NULL) {  /* could not open outFileName */
2242             error = 1;
2243         } else {
2244             AIO_WritePool_setFile(ress.writeCtx, dstFile);
2245             for (; fCtx->currFileIdx < fCtx->nbFilesTotal; ++fCtx->currFileIdx) {
2246                 status = FIO_compressFilename_srcFile(fCtx, prefs, ress, outFileName, inFileNamesTable[fCtx->currFileIdx], compressionLevel);
2247                 if (!status) fCtx->nbFilesProcessed++;
2248                 error |= status;
2249             }
2250             if (AIO_WritePool_closeFile(ress.writeCtx))
2251                 EXM_THROW(29, "Write error (%s) : cannot properly close %s",
2252                             strerror(errno), outFileName);
2253         }
2254     } else {
2255         if (outMirroredRootDirName)
2256             UTIL_mirrorSourceFilesDirectories(inFileNamesTable, (unsigned)fCtx->nbFilesTotal, outMirroredRootDirName);
2257 
2258         for (; fCtx->currFileIdx < fCtx->nbFilesTotal; ++fCtx->currFileIdx) {
2259             const char* const srcFileName = inFileNamesTable[fCtx->currFileIdx];
2260             const char* dstFileName = NULL;
2261             if (outMirroredRootDirName) {
2262                 char* validMirroredDirName = UTIL_createMirroredDestDirName(srcFileName, outMirroredRootDirName);
2263                 if (validMirroredDirName) {
2264                     dstFileName = FIO_determineCompressedName(srcFileName, validMirroredDirName, suffix);
2265                     free(validMirroredDirName);
2266                 } else {
2267                     DISPLAYLEVEL(2, "zstd: --output-dir-mirror cannot compress '%s' into '%s' \n", srcFileName, outMirroredRootDirName);
2268                     error=1;
2269                     continue;
2270                 }
2271             } else {
2272                 dstFileName = FIO_determineCompressedName(srcFileName, outDirName, suffix);  /* cannot fail */
2273             }
2274             status = FIO_compressFilename_srcFile(fCtx, prefs, ress, dstFileName, srcFileName, compressionLevel);
2275             if (!status) fCtx->nbFilesProcessed++;
2276             error |= status;
2277         }
2278 
2279         if (outDirName)
2280             FIO_checkFilenameCollisions(inFileNamesTable , (unsigned)fCtx->nbFilesTotal);
2281     }
2282 
2283     if (FIO_shouldDisplayMultipleFileSummary(fCtx)) {
2284         UTIL_HumanReadableSize_t hr_isize = UTIL_makeHumanReadableSize((U64) fCtx->totalBytesInput);
2285         UTIL_HumanReadableSize_t hr_osize = UTIL_makeHumanReadableSize((U64) fCtx->totalBytesOutput);
2286 
2287         DISPLAY_PROGRESS("\r%79s\r", "");
2288         if (fCtx->totalBytesInput == 0) {
2289             DISPLAY_SUMMARY("%3d files compressed : (%6.*f%4s => %6.*f%4s)\n",
2290                             fCtx->nbFilesProcessed,
2291                             hr_isize.precision, hr_isize.value, hr_isize.suffix,
2292                             hr_osize.precision, hr_osize.value, hr_osize.suffix);
2293         } else {
2294             DISPLAY_SUMMARY("%3d files compressed : %.2f%% (%6.*f%4s => %6.*f%4s)\n",
2295                             fCtx->nbFilesProcessed,
2296                             (double)fCtx->totalBytesOutput/((double)fCtx->totalBytesInput)*100,
2297                             hr_isize.precision, hr_isize.value, hr_isize.suffix,
2298                             hr_osize.precision, hr_osize.value, hr_osize.suffix);
2299         }
2300     }
2301 
2302     FIO_freeCResources(&ress);
2303     return error;
2304 }
2305 
2306 #endif /* #ifndef ZSTD_NOCOMPRESS */
2307 
2308 
2309 
2310 #ifndef ZSTD_NODECOMPRESS
2311 
2312 /* **************************************************************************
2313  *  Decompression
2314  ***************************************************************************/
2315 typedef struct {
2316     FIO_Dict_t dict;
2317     ZSTD_DStream* dctx;
2318     WritePoolCtx_t *writeCtx;
2319     ReadPoolCtx_t *readCtx;
2320 } dRess_t;
2321 
FIO_createDResources(FIO_prefs_t * const prefs,const char * dictFileName)2322 static dRess_t FIO_createDResources(FIO_prefs_t* const prefs, const char* dictFileName)
2323 {
2324     int useMMap = prefs->mmapDict == ZSTD_ps_enable;
2325     int forceNoUseMMap = prefs->mmapDict == ZSTD_ps_disable;
2326     stat_t statbuf;
2327     dRess_t ress;
2328     memset(&statbuf, 0, sizeof(statbuf));
2329     memset(&ress, 0, sizeof(ress));
2330 
2331     FIO_getDictFileStat(dictFileName, &statbuf);
2332 
2333     if (prefs->patchFromMode){
2334         U64 const dictSize = UTIL_getFileSizeStat(&statbuf);
2335         useMMap |= dictSize > prefs->memLimit;
2336         FIO_adjustMemLimitForPatchFromMode(prefs, dictSize, 0 /* just use the dict size */);
2337     }
2338 
2339     /* Allocation */
2340     ress.dctx = ZSTD_createDStream();
2341     if (ress.dctx==NULL)
2342         EXM_THROW(60, "Error: %s : can't create ZSTD_DStream", strerror(errno));
2343     CHECK( ZSTD_DCtx_setMaxWindowSize(ress.dctx, prefs->memLimit) );
2344     CHECK( ZSTD_DCtx_setParameter(ress.dctx, ZSTD_d_forceIgnoreChecksum, !prefs->checksumFlag));
2345 
2346     /* dictionary */
2347     {
2348         FIO_dictBufferType_t dictBufferType = (useMMap && !forceNoUseMMap) ? FIO_mmapDict : FIO_mallocDict;
2349         FIO_initDict(&ress.dict, dictFileName, prefs, &statbuf, dictBufferType);
2350 
2351         CHECK(ZSTD_DCtx_reset(ress.dctx, ZSTD_reset_session_only) );
2352 
2353         if (prefs->patchFromMode){
2354             CHECK(ZSTD_DCtx_refPrefix(ress.dctx, ress.dict.dictBuffer, ress.dict.dictBufferSize));
2355         } else {
2356             CHECK(ZSTD_DCtx_loadDictionary_byReference(ress.dctx, ress.dict.dictBuffer, ress.dict.dictBufferSize));
2357         }
2358     }
2359 
2360     ress.writeCtx = AIO_WritePool_create(prefs, ZSTD_DStreamOutSize());
2361     ress.readCtx = AIO_ReadPool_create(prefs, ZSTD_DStreamInSize());
2362     return ress;
2363 }
2364 
FIO_freeDResources(dRess_t ress)2365 static void FIO_freeDResources(dRess_t ress)
2366 {
2367     FIO_freeDict(&(ress.dict));
2368     CHECK( ZSTD_freeDStream(ress.dctx) );
2369     AIO_WritePool_free(ress.writeCtx);
2370     AIO_ReadPool_free(ress.readCtx);
2371 }
2372 
2373 /* FIO_passThrough() : just copy input into output, for compatibility with gzip -df mode
2374  * @return : 0 (no error) */
FIO_passThrough(dRess_t * ress)2375 static int FIO_passThrough(dRess_t *ress)
2376 {
2377     size_t const blockSize = MIN(MIN(64 KB, ZSTD_DStreamInSize()), ZSTD_DStreamOutSize());
2378     IOJob_t *writeJob = AIO_WritePool_acquireJob(ress->writeCtx);
2379     AIO_ReadPool_fillBuffer(ress->readCtx, blockSize);
2380 
2381     while(ress->readCtx->srcBufferLoaded) {
2382         size_t writeSize;
2383         writeSize = MIN(blockSize, ress->readCtx->srcBufferLoaded);
2384         assert(writeSize <= writeJob->bufferSize);
2385         memcpy(writeJob->buffer, ress->readCtx->srcBuffer, writeSize);
2386         writeJob->usedBufferSize = writeSize;
2387         AIO_WritePool_enqueueAndReacquireWriteJob(&writeJob);
2388         AIO_ReadPool_consumeBytes(ress->readCtx, writeSize);
2389         AIO_ReadPool_fillBuffer(ress->readCtx, blockSize);
2390     }
2391     assert(ress->readCtx->reachedEof);
2392     AIO_WritePool_releaseIoJob(writeJob);
2393     AIO_WritePool_sparseWriteEnd(ress->writeCtx);
2394     return 0;
2395 }
2396 
2397 /* FIO_zstdErrorHelp() :
2398  * detailed error message when requested window size is too large */
2399 static void
FIO_zstdErrorHelp(const FIO_prefs_t * const prefs,const dRess_t * ress,size_t err,const char * srcFileName)2400 FIO_zstdErrorHelp(const FIO_prefs_t* const prefs,
2401                   const dRess_t* ress,
2402                   size_t err,
2403                   const char* srcFileName)
2404 {
2405     ZSTD_FrameHeader header;
2406 
2407     /* Help message only for one specific error */
2408     if (ZSTD_getErrorCode(err) != ZSTD_error_frameParameter_windowTooLarge)
2409         return;
2410 
2411     /* Try to decode the frame header */
2412     err = ZSTD_getFrameHeader(&header, ress->readCtx->srcBuffer, ress->readCtx->srcBufferLoaded);
2413     if (err == 0) {
2414         unsigned long long const windowSize = header.windowSize;
2415         unsigned const windowLog = FIO_highbit64(windowSize) + ((windowSize & (windowSize - 1)) != 0);
2416         assert(prefs->memLimit > 0);
2417         DISPLAYLEVEL(1, "%s : Window size larger than maximum : %llu > %u \n",
2418                         srcFileName, windowSize, prefs->memLimit);
2419         if (windowLog <= ZSTD_WINDOWLOG_MAX) {
2420             unsigned const windowMB = (unsigned)((windowSize >> 20) + ((windowSize & ((1 MB) - 1)) != 0));
2421             assert(windowSize < (U64)(1ULL << 52));   /* ensure now overflow for windowMB */
2422             DISPLAYLEVEL(1, "%s : Use --long=%u or --memory=%uMB \n",
2423                             srcFileName, windowLog, windowMB);
2424             return;
2425     }   }
2426     DISPLAYLEVEL(1, "%s : Window log larger than ZSTD_WINDOWLOG_MAX=%u; not supported \n",
2427                     srcFileName, ZSTD_WINDOWLOG_MAX);
2428 }
2429 
2430 /** FIO_decompressFrame() :
2431  *  @return : size of decoded zstd frame, or an error code
2432  */
2433 #define FIO_ERROR_FRAME_DECODING   ((unsigned long long)(-2))
2434 static unsigned long long
FIO_decompressZstdFrame(FIO_ctx_t * const fCtx,dRess_t * ress,const FIO_prefs_t * const prefs,const char * srcFileName,U64 alreadyDecoded)2435 FIO_decompressZstdFrame(FIO_ctx_t* const fCtx, dRess_t* ress,
2436                         const FIO_prefs_t* const prefs,
2437                         const char* srcFileName,
2438                         U64 alreadyDecoded)  /* for multi-frames streams */
2439 {
2440     U64 frameSize = 0;
2441     const char* srcFName20 = srcFileName;
2442     IOJob_t* writeJob = AIO_WritePool_acquireJob(ress->writeCtx);
2443     assert(writeJob);
2444 
2445     /* display last 20 characters only when not --verbose */
2446     {   size_t const srcFileLength = strlen(srcFileName);
2447         if ((srcFileLength>20) && (g_display_prefs.displayLevel<3))
2448             srcFName20 += srcFileLength-20;
2449     }
2450 
2451     ZSTD_DCtx_reset(ress->dctx, ZSTD_reset_session_only);
2452 
2453     /* Header loading : ensures ZSTD_getFrameHeader() will succeed */
2454     AIO_ReadPool_fillBuffer(ress->readCtx, ZSTD_FRAMEHEADERSIZE_MAX);
2455 
2456     /* Main decompression Loop */
2457     while (1) {
2458         ZSTD_inBuffer  inBuff = setInBuffer( ress->readCtx->srcBuffer, ress->readCtx->srcBufferLoaded, 0 );
2459         ZSTD_outBuffer outBuff= setOutBuffer( writeJob->buffer, writeJob->bufferSize, 0 );
2460         size_t const readSizeHint = ZSTD_decompressStream(ress->dctx, &outBuff, &inBuff);
2461         UTIL_HumanReadableSize_t const hrs = UTIL_makeHumanReadableSize(alreadyDecoded+frameSize);
2462         if (ZSTD_isError(readSizeHint)) {
2463             DISPLAYLEVEL(1, "%s : Decoding error (36) : %s \n",
2464                             srcFileName, ZSTD_getErrorName(readSizeHint));
2465             FIO_zstdErrorHelp(prefs, ress, readSizeHint, srcFileName);
2466             AIO_WritePool_releaseIoJob(writeJob);
2467             return FIO_ERROR_FRAME_DECODING;
2468         }
2469 
2470         /* Write block */
2471         writeJob->usedBufferSize = outBuff.pos;
2472         AIO_WritePool_enqueueAndReacquireWriteJob(&writeJob);
2473         frameSize += outBuff.pos;
2474         if (fCtx->nbFilesTotal > 1) {
2475             DISPLAYUPDATE_PROGRESS(
2476                         "\rDecompress: %2u/%2u files. Current: %s : %.*f%s...    ",
2477                         fCtx->currFileIdx+1, fCtx->nbFilesTotal, srcFName20, hrs.precision, hrs.value, hrs.suffix);
2478         } else {
2479             DISPLAYUPDATE_PROGRESS("\r%-20.20s : %.*f%s...     ",
2480                             srcFName20, hrs.precision, hrs.value, hrs.suffix);
2481         }
2482 
2483         AIO_ReadPool_consumeBytes(ress->readCtx, inBuff.pos);
2484 
2485         if (readSizeHint == 0) break;   /* end of frame */
2486 
2487         /* Fill input buffer */
2488         {   size_t const toDecode = MIN(readSizeHint, ZSTD_DStreamInSize());  /* support large skippable frames */
2489             if (ress->readCtx->srcBufferLoaded < toDecode) {
2490                 size_t const readSize = AIO_ReadPool_fillBuffer(ress->readCtx, toDecode);
2491                 if (readSize==0) {
2492                     DISPLAYLEVEL(1, "%s : Read error (39) : premature end \n",
2493                                  srcFileName);
2494                     AIO_WritePool_releaseIoJob(writeJob);
2495                     return FIO_ERROR_FRAME_DECODING;
2496                 }
2497             }   }   }
2498 
2499     AIO_WritePool_releaseIoJob(writeJob);
2500     AIO_WritePool_sparseWriteEnd(ress->writeCtx);
2501 
2502     return frameSize;
2503 }
2504 
2505 
2506 #ifdef ZSTD_GZDECOMPRESS
2507 static unsigned long long
FIO_decompressGzFrame(dRess_t * ress,const char * srcFileName)2508 FIO_decompressGzFrame(dRess_t* ress, const char* srcFileName)
2509 {
2510     unsigned long long outFileSize = 0;
2511     z_stream strm;
2512     int flush = Z_NO_FLUSH;
2513     int decodingError = 0;
2514     IOJob_t *writeJob = NULL;
2515 
2516     strm.zalloc = Z_NULL;
2517     strm.zfree = Z_NULL;
2518     strm.opaque = Z_NULL;
2519     strm.next_in = 0;
2520     strm.avail_in = 0;
2521     /* see https://www.zlib.net/manual.html */
2522     if (inflateInit2(&strm, 15 /* maxWindowLogSize */ + 16 /* gzip only */) != Z_OK)
2523         return FIO_ERROR_FRAME_DECODING;
2524 
2525     writeJob = AIO_WritePool_acquireJob(ress->writeCtx);
2526     strm.next_out = (Bytef*)writeJob->buffer;
2527     strm.avail_out = (uInt)writeJob->bufferSize;
2528     strm.avail_in = (uInt)ress->readCtx->srcBufferLoaded;
2529     strm.next_in = (z_const unsigned char*)ress->readCtx->srcBuffer;
2530 
2531     for ( ; ; ) {
2532         int ret;
2533         if (strm.avail_in == 0) {
2534             AIO_ReadPool_consumeAndRefill(ress->readCtx);
2535             if (ress->readCtx->srcBufferLoaded == 0) flush = Z_FINISH;
2536             strm.next_in = (z_const unsigned char*)ress->readCtx->srcBuffer;
2537             strm.avail_in = (uInt)ress->readCtx->srcBufferLoaded;
2538         }
2539         ret = inflate(&strm, flush);
2540         if (ret == Z_BUF_ERROR) {
2541             DISPLAYLEVEL(1, "zstd: %s: premature gz end \n", srcFileName);
2542             decodingError = 1; break;
2543         }
2544         if (ret != Z_OK && ret != Z_STREAM_END) {
2545             DISPLAYLEVEL(1, "zstd: %s: inflate error %d \n", srcFileName, ret);
2546             decodingError = 1; break;
2547         }
2548         {   size_t const decompBytes = writeJob->bufferSize - strm.avail_out;
2549             if (decompBytes) {
2550                 writeJob->usedBufferSize = decompBytes;
2551                 AIO_WritePool_enqueueAndReacquireWriteJob(&writeJob);
2552                 outFileSize += decompBytes;
2553                 strm.next_out = (Bytef*)writeJob->buffer;
2554                 strm.avail_out = (uInt)writeJob->bufferSize;
2555             }
2556         }
2557         if (ret == Z_STREAM_END) break;
2558     }
2559 
2560     AIO_ReadPool_consumeBytes(ress->readCtx, ress->readCtx->srcBufferLoaded - strm.avail_in);
2561 
2562     if ( (inflateEnd(&strm) != Z_OK)  /* release resources ; error detected */
2563       && (decodingError==0) ) {
2564         DISPLAYLEVEL(1, "zstd: %s: inflateEnd error \n", srcFileName);
2565         decodingError = 1;
2566     }
2567     AIO_WritePool_releaseIoJob(writeJob);
2568     AIO_WritePool_sparseWriteEnd(ress->writeCtx);
2569     return decodingError ? FIO_ERROR_FRAME_DECODING : outFileSize;
2570 }
2571 #endif
2572 
2573 #ifdef ZSTD_LZMADECOMPRESS
2574 static unsigned long long
FIO_decompressLzmaFrame(dRess_t * ress,const char * srcFileName,int plain_lzma)2575 FIO_decompressLzmaFrame(dRess_t* ress,
2576                         const char* srcFileName, int plain_lzma)
2577 {
2578     unsigned long long outFileSize = 0;
2579     lzma_stream strm = LZMA_STREAM_INIT;
2580     lzma_action action = LZMA_RUN;
2581     lzma_ret initRet;
2582     int decodingError = 0;
2583     IOJob_t *writeJob = NULL;
2584 
2585     strm.next_in = 0;
2586     strm.avail_in = 0;
2587     if (plain_lzma) {
2588         initRet = lzma_alone_decoder(&strm, UINT64_MAX); /* LZMA */
2589     } else {
2590         initRet = lzma_stream_decoder(&strm, UINT64_MAX, 0); /* XZ */
2591     }
2592 
2593     if (initRet != LZMA_OK) {
2594         DISPLAYLEVEL(1, "zstd: %s: %s error %d \n",
2595                         plain_lzma ? "lzma_alone_decoder" : "lzma_stream_decoder",
2596                         srcFileName, initRet);
2597         return FIO_ERROR_FRAME_DECODING;
2598     }
2599 
2600     writeJob = AIO_WritePool_acquireJob(ress->writeCtx);
2601     strm.next_out = (BYTE*)writeJob->buffer;
2602     strm.avail_out = writeJob->bufferSize;
2603     strm.next_in = (BYTE const*)ress->readCtx->srcBuffer;
2604     strm.avail_in = ress->readCtx->srcBufferLoaded;
2605 
2606     for ( ; ; ) {
2607         lzma_ret ret;
2608         if (strm.avail_in == 0) {
2609             AIO_ReadPool_consumeAndRefill(ress->readCtx);
2610             if (ress->readCtx->srcBufferLoaded == 0) action = LZMA_FINISH;
2611             strm.next_in = (BYTE const*)ress->readCtx->srcBuffer;
2612             strm.avail_in = ress->readCtx->srcBufferLoaded;
2613         }
2614         ret = lzma_code(&strm, action);
2615 
2616         if (ret == LZMA_BUF_ERROR) {
2617             DISPLAYLEVEL(1, "zstd: %s: premature lzma end \n", srcFileName);
2618             decodingError = 1; break;
2619         }
2620         if (ret != LZMA_OK && ret != LZMA_STREAM_END) {
2621             DISPLAYLEVEL(1, "zstd: %s: lzma_code decoding error %d \n",
2622                             srcFileName, ret);
2623             decodingError = 1; break;
2624         }
2625         {   size_t const decompBytes = writeJob->bufferSize - strm.avail_out;
2626             if (decompBytes) {
2627                 writeJob->usedBufferSize = decompBytes;
2628                 AIO_WritePool_enqueueAndReacquireWriteJob(&writeJob);
2629                 outFileSize += decompBytes;
2630                 strm.next_out = (BYTE*)writeJob->buffer;
2631                 strm.avail_out = writeJob->bufferSize;
2632         }   }
2633         if (ret == LZMA_STREAM_END) break;
2634     }
2635 
2636     AIO_ReadPool_consumeBytes(ress->readCtx, ress->readCtx->srcBufferLoaded - strm.avail_in);
2637     lzma_end(&strm);
2638     AIO_WritePool_releaseIoJob(writeJob);
2639     AIO_WritePool_sparseWriteEnd(ress->writeCtx);
2640     return decodingError ? FIO_ERROR_FRAME_DECODING : outFileSize;
2641 }
2642 #endif
2643 
2644 #ifdef ZSTD_LZ4DECOMPRESS
2645 static unsigned long long
FIO_decompressLz4Frame(dRess_t * ress,const char * srcFileName)2646 FIO_decompressLz4Frame(dRess_t* ress, const char* srcFileName)
2647 {
2648     unsigned long long filesize = 0;
2649     LZ4F_errorCode_t nextToLoad = 4;
2650     LZ4F_decompressionContext_t dCtx;
2651     LZ4F_errorCode_t const errorCode = LZ4F_createDecompressionContext(&dCtx, LZ4F_VERSION);
2652     int decodingError = 0;
2653     IOJob_t *writeJob = NULL;
2654 
2655     if (LZ4F_isError(errorCode)) {
2656         DISPLAYLEVEL(1, "zstd: failed to create lz4 decompression context \n");
2657         return FIO_ERROR_FRAME_DECODING;
2658     }
2659 
2660     writeJob = AIO_WritePool_acquireJob(ress->writeCtx);
2661 
2662     /* Main Loop */
2663     for (;nextToLoad;) {
2664         size_t pos = 0;
2665         size_t decodedBytes = writeJob->bufferSize;
2666         int fullBufferDecoded = 0;
2667 
2668         /* Read input */
2669         AIO_ReadPool_fillBuffer(ress->readCtx, nextToLoad);
2670         if(!ress->readCtx->srcBufferLoaded) break; /* reached end of file */
2671 
2672         while ((pos < ress->readCtx->srcBufferLoaded) || fullBufferDecoded) {  /* still to read, or still to flush */
2673             /* Decode Input (at least partially) */
2674             size_t remaining = ress->readCtx->srcBufferLoaded - pos;
2675             decodedBytes = writeJob->bufferSize;
2676             nextToLoad = LZ4F_decompress(dCtx, writeJob->buffer, &decodedBytes, (char*)(ress->readCtx->srcBuffer)+pos,
2677                                          &remaining, NULL);
2678             if (LZ4F_isError(nextToLoad)) {
2679                 DISPLAYLEVEL(1, "zstd: %s: lz4 decompression error : %s \n",
2680                                 srcFileName, LZ4F_getErrorName(nextToLoad));
2681                 decodingError = 1; nextToLoad = 0; break;
2682             }
2683             pos += remaining;
2684             assert(pos <= ress->readCtx->srcBufferLoaded);
2685             fullBufferDecoded = decodedBytes == writeJob->bufferSize;
2686 
2687             /* Write Block */
2688             if (decodedBytes) {
2689                 UTIL_HumanReadableSize_t hrs;
2690                 writeJob->usedBufferSize = decodedBytes;
2691                 AIO_WritePool_enqueueAndReacquireWriteJob(&writeJob);
2692                 filesize += decodedBytes;
2693                 hrs = UTIL_makeHumanReadableSize(filesize);
2694                 DISPLAYUPDATE_PROGRESS("\rDecompressed : %.*f%s  ", hrs.precision, hrs.value, hrs.suffix);
2695             }
2696 
2697             if (!nextToLoad) break;
2698         }
2699         AIO_ReadPool_consumeBytes(ress->readCtx, pos);
2700     }
2701     if (nextToLoad!=0) {
2702         DISPLAYLEVEL(1, "zstd: %s: unfinished lz4 stream \n", srcFileName);
2703         decodingError=1;
2704     }
2705 
2706     LZ4F_freeDecompressionContext(dCtx);
2707     AIO_WritePool_releaseIoJob(writeJob);
2708     AIO_WritePool_sparseWriteEnd(ress->writeCtx);
2709 
2710     return decodingError ? FIO_ERROR_FRAME_DECODING : filesize;
2711 }
2712 #endif
2713 
2714 
2715 
2716 /** FIO_decompressFrames() :
2717  *  Find and decode frames inside srcFile
2718  *  srcFile presumed opened and valid
2719  * @return : 0 : OK
2720  *           1 : error
2721  */
FIO_decompressFrames(FIO_ctx_t * const fCtx,dRess_t ress,const FIO_prefs_t * const prefs,const char * dstFileName,const char * srcFileName)2722 static int FIO_decompressFrames(FIO_ctx_t* const fCtx,
2723                                 dRess_t ress, const FIO_prefs_t* const prefs,
2724                                 const char* dstFileName, const char* srcFileName)
2725 {
2726     unsigned readSomething = 0;
2727     unsigned long long filesize = 0;
2728     int passThrough = prefs->passThrough;
2729 
2730     if (passThrough == -1) {
2731         /* If pass-through mode is not explicitly enabled or disabled,
2732          * default to the legacy behavior of enabling it if we are writing
2733          * to stdout with the overwrite flag enabled.
2734          */
2735         passThrough = prefs->overwrite && !strcmp(dstFileName, stdoutmark);
2736     }
2737     assert(passThrough == 0 || passThrough == 1);
2738 
2739     /* for each frame */
2740     for ( ; ; ) {
2741         /* check magic number -> version */
2742         size_t const toRead = 4;
2743         const BYTE* buf;
2744         AIO_ReadPool_fillBuffer(ress.readCtx, toRead);
2745         buf = (const BYTE*)ress.readCtx->srcBuffer;
2746         if (ress.readCtx->srcBufferLoaded==0) {
2747             if (readSomething==0) {  /* srcFile is empty (which is invalid) */
2748                 DISPLAYLEVEL(1, "zstd: %s: unexpected end of file \n", srcFileName);
2749                 return 1;
2750             }  /* else, just reached frame boundary */
2751             break;   /* no more input */
2752         }
2753         readSomething = 1;   /* there is at least 1 byte in srcFile */
2754         if (ress.readCtx->srcBufferLoaded < toRead) { /* not enough input to check magic number */
2755             if (passThrough) {
2756                 return FIO_passThrough(&ress);
2757             }
2758             DISPLAYLEVEL(1, "zstd: %s: unknown header \n", srcFileName);
2759             return 1;
2760         }
2761         if (ZSTD_isFrame(buf, ress.readCtx->srcBufferLoaded)) {
2762             unsigned long long const frameSize = FIO_decompressZstdFrame(fCtx, &ress, prefs, srcFileName, filesize);
2763             if (frameSize == FIO_ERROR_FRAME_DECODING) return 1;
2764             filesize += frameSize;
2765         } else if (buf[0] == 31 && buf[1] == 139) { /* gz magic number */
2766 #ifdef ZSTD_GZDECOMPRESS
2767             unsigned long long const frameSize = FIO_decompressGzFrame(&ress, srcFileName);
2768             if (frameSize == FIO_ERROR_FRAME_DECODING) return 1;
2769             filesize += frameSize;
2770 #else
2771             DISPLAYLEVEL(1, "zstd: %s: gzip file cannot be uncompressed (zstd compiled without HAVE_ZLIB) -- ignored \n", srcFileName);
2772             return 1;
2773 #endif
2774         } else if ((buf[0] == 0xFD && buf[1] == 0x37)  /* xz magic number */
2775                 || (buf[0] == 0x5D && buf[1] == 0x00)) { /* lzma header (no magic number) */
2776 #ifdef ZSTD_LZMADECOMPRESS
2777             unsigned long long const frameSize = FIO_decompressLzmaFrame(&ress, srcFileName, buf[0] != 0xFD);
2778             if (frameSize == FIO_ERROR_FRAME_DECODING) return 1;
2779             filesize += frameSize;
2780 #else
2781             DISPLAYLEVEL(1, "zstd: %s: xz/lzma file cannot be uncompressed (zstd compiled without HAVE_LZMA) -- ignored \n", srcFileName);
2782             return 1;
2783 #endif
2784         } else if (MEM_readLE32(buf) == LZ4_MAGICNUMBER) {
2785 #ifdef ZSTD_LZ4DECOMPRESS
2786             unsigned long long const frameSize = FIO_decompressLz4Frame(&ress, srcFileName);
2787             if (frameSize == FIO_ERROR_FRAME_DECODING) return 1;
2788             filesize += frameSize;
2789 #else
2790             DISPLAYLEVEL(1, "zstd: %s: lz4 file cannot be uncompressed (zstd compiled without HAVE_LZ4) -- ignored \n", srcFileName);
2791             return 1;
2792 #endif
2793         } else if (passThrough) {
2794             return FIO_passThrough(&ress);
2795         } else {
2796             DISPLAYLEVEL(1, "zstd: %s: unsupported format \n", srcFileName);
2797             return 1;
2798     }   }  /* for each frame */
2799 
2800     /* Final Status */
2801     fCtx->totalBytesOutput += (size_t)filesize;
2802     DISPLAY_PROGRESS("\r%79s\r", "");
2803     if (FIO_shouldDisplayFileSummary(fCtx))
2804         DISPLAY_SUMMARY("%-20s: %llu bytes \n", srcFileName, filesize);
2805 
2806     return 0;
2807 }
2808 
2809 /** FIO_decompressDstFile() :
2810     open `dstFileName`, or pass-through if writeCtx's file is already != 0,
2811     then start decompression process (FIO_decompressFrames()).
2812     @return : 0 : OK
2813               1 : operation aborted
2814 */
FIO_decompressDstFile(FIO_ctx_t * const fCtx,FIO_prefs_t * const prefs,dRess_t ress,const char * dstFileName,const char * srcFileName,const stat_t * srcFileStat)2815 static int FIO_decompressDstFile(FIO_ctx_t* const fCtx,
2816                                  FIO_prefs_t* const prefs,
2817                                  dRess_t ress,
2818                                  const char* dstFileName,
2819                                  const char* srcFileName,
2820                                  const stat_t* srcFileStat)
2821 {
2822     int result;
2823     int releaseDstFile = 0;
2824     int transferStat = 0;
2825     int dstFd = 0;
2826 
2827     if ((AIO_WritePool_getFile(ress.writeCtx) == NULL) && (prefs->testMode == 0)) {
2828         FILE *dstFile;
2829         int dstFilePermissions = DEFAULT_FILE_PERMISSIONS;
2830         if ( strcmp(srcFileName, stdinmark)   /* special case : don't transfer permissions from stdin */
2831           && strcmp(dstFileName, stdoutmark)
2832           && UTIL_isRegularFileStat(srcFileStat) ) {
2833             transferStat = 1;
2834             dstFilePermissions = TEMPORARY_FILE_PERMISSIONS;
2835         }
2836 
2837         releaseDstFile = 1;
2838 
2839         dstFile = FIO_openDstFile(fCtx, prefs, srcFileName, dstFileName, dstFilePermissions);
2840         if (dstFile==NULL) return 1;
2841         dstFd = fileno(dstFile);
2842         AIO_WritePool_setFile(ress.writeCtx, dstFile);
2843 
2844         /* Must only be added after FIO_openDstFile() succeeds.
2845          * Otherwise we may delete the destination file if it already exists,
2846          * and the user presses Ctrl-C when asked if they wish to overwrite.
2847          */
2848         addHandler(dstFileName);
2849     }
2850 
2851     result = FIO_decompressFrames(fCtx, ress, prefs, dstFileName, srcFileName);
2852 
2853     if (releaseDstFile) {
2854         clearHandler();
2855 
2856         if (transferStat) {
2857             UTIL_setFDStat(dstFd, dstFileName, srcFileStat);
2858         }
2859 
2860         if (AIO_WritePool_closeFile(ress.writeCtx)) {
2861             DISPLAYLEVEL(1, "zstd: %s: %s \n", dstFileName, strerror(errno));
2862             result = 1;
2863         }
2864 
2865         if (transferStat) {
2866             UTIL_utime(dstFileName, srcFileStat);
2867         }
2868 
2869         if ( (result != 0)  /* operation failure */
2870           && strcmp(dstFileName, stdoutmark)  /* special case : don't remove() stdout */
2871           ) {
2872             FIO_removeFile(dstFileName);  /* remove decompression artefact; note: don't do anything special if remove() fails */
2873         }
2874     }
2875 
2876     return result;
2877 }
2878 
2879 
2880 /** FIO_decompressSrcFile() :
2881     Open `srcFileName`, transfer control to decompressDstFile()
2882     @return : 0 : OK
2883               1 : error
2884 */
FIO_decompressSrcFile(FIO_ctx_t * const fCtx,FIO_prefs_t * const prefs,dRess_t ress,const char * dstFileName,const char * srcFileName)2885 static int FIO_decompressSrcFile(FIO_ctx_t* const fCtx, FIO_prefs_t* const prefs, dRess_t ress, const char* dstFileName, const char* srcFileName)
2886 {
2887     FILE* srcFile;
2888     stat_t srcFileStat;
2889     int result;
2890     U64 fileSize = UTIL_FILESIZE_UNKNOWN;
2891 
2892     if (UTIL_isDirectory(srcFileName)) {
2893         DISPLAYLEVEL(1, "zstd: %s is a directory -- ignored \n", srcFileName);
2894         return 1;
2895     }
2896 
2897     srcFile = FIO_openSrcFile(prefs, srcFileName, &srcFileStat);
2898     if (srcFile==NULL) return 1;
2899 
2900     /* Don't use AsyncIO for small files */
2901     if (strcmp(srcFileName, stdinmark)) /* Stdin doesn't have stats */
2902         fileSize = UTIL_getFileSizeStat(&srcFileStat);
2903     if(fileSize != UTIL_FILESIZE_UNKNOWN && fileSize < ZSTD_BLOCKSIZE_MAX * 3) {
2904         AIO_ReadPool_setAsync(ress.readCtx, 0);
2905         AIO_WritePool_setAsync(ress.writeCtx, 0);
2906     } else {
2907         AIO_ReadPool_setAsync(ress.readCtx, 1);
2908         AIO_WritePool_setAsync(ress.writeCtx, 1);
2909     }
2910 
2911     AIO_ReadPool_setFile(ress.readCtx, srcFile);
2912 
2913     result = FIO_decompressDstFile(fCtx, prefs, ress, dstFileName, srcFileName, &srcFileStat);
2914 
2915     AIO_ReadPool_setFile(ress.readCtx, NULL);
2916 
2917     /* Close file */
2918     if (fclose(srcFile)) {
2919         DISPLAYLEVEL(1, "zstd: %s: %s \n", srcFileName, strerror(errno));  /* error should not happen */
2920         return 1;
2921     }
2922     if ( prefs->removeSrcFile  /* --rm */
2923       && (result==0)      /* decompression successful */
2924       && strcmp(srcFileName, stdinmark) ) /* not stdin */ {
2925         /* We must clear the handler, since after this point calling it would
2926          * delete both the source and destination files.
2927          */
2928         clearHandler();
2929         if (FIO_removeFile(srcFileName)) {
2930             /* failed to remove src file */
2931             DISPLAYLEVEL(1, "zstd: %s: %s \n", srcFileName, strerror(errno));
2932             return 1;
2933     }   }
2934     return result;
2935 }
2936 
2937 
2938 
FIO_decompressFilename(FIO_ctx_t * const fCtx,FIO_prefs_t * const prefs,const char * dstFileName,const char * srcFileName,const char * dictFileName)2939 int FIO_decompressFilename(FIO_ctx_t* const fCtx, FIO_prefs_t* const prefs,
2940                            const char* dstFileName, const char* srcFileName,
2941                            const char* dictFileName)
2942 {
2943     dRess_t const ress = FIO_createDResources(prefs, dictFileName);
2944 
2945     int const decodingError = FIO_decompressSrcFile(fCtx, prefs, ress, dstFileName, srcFileName);
2946 
2947 
2948 
2949     FIO_freeDResources(ress);
2950     return decodingError;
2951 }
2952 
2953 static const char *suffixList[] = {
2954     ZSTD_EXTENSION,
2955     TZSTD_EXTENSION,
2956 #ifndef ZSTD_NODECOMPRESS
2957     ZSTD_ALT_EXTENSION,
2958 #endif
2959 #ifdef ZSTD_GZDECOMPRESS
2960     GZ_EXTENSION,
2961     TGZ_EXTENSION,
2962 #endif
2963 #ifdef ZSTD_LZMADECOMPRESS
2964     LZMA_EXTENSION,
2965     XZ_EXTENSION,
2966     TXZ_EXTENSION,
2967 #endif
2968 #ifdef ZSTD_LZ4DECOMPRESS
2969     LZ4_EXTENSION,
2970     TLZ4_EXTENSION,
2971 #endif
2972     NULL
2973 };
2974 
2975 static const char *suffixListStr =
2976     ZSTD_EXTENSION "/" TZSTD_EXTENSION
2977 #ifdef ZSTD_GZDECOMPRESS
2978     "/" GZ_EXTENSION "/" TGZ_EXTENSION
2979 #endif
2980 #ifdef ZSTD_LZMADECOMPRESS
2981     "/" LZMA_EXTENSION "/" XZ_EXTENSION "/" TXZ_EXTENSION
2982 #endif
2983 #ifdef ZSTD_LZ4DECOMPRESS
2984     "/" LZ4_EXTENSION "/" TLZ4_EXTENSION
2985 #endif
2986 ;
2987 
2988 /* FIO_determineDstName() :
2989  * create a destination filename from a srcFileName.
2990  * @return a pointer to it.
2991  * @return == NULL if there is an error */
2992 static const char*
FIO_determineDstName(const char * srcFileName,const char * outDirName)2993 FIO_determineDstName(const char* srcFileName, const char* outDirName)
2994 {
2995     static size_t dfnbCapacity = 0;
2996     static char* dstFileNameBuffer = NULL;   /* using static allocation : this function cannot be multi-threaded */
2997     size_t dstFileNameEndPos;
2998     char* outDirFilename = NULL;
2999     const char* dstSuffix = "";
3000     size_t dstSuffixLen = 0;
3001 
3002     size_t sfnSize = strlen(srcFileName);
3003 
3004     size_t srcSuffixLen;
3005     const char* const srcSuffix = strrchr(srcFileName, '.');
3006 
3007     if(!strcmp(srcFileName, stdinmark)) {
3008         return stdoutmark;
3009     }
3010 
3011     if (srcSuffix == NULL) {
3012         DISPLAYLEVEL(1,
3013             "zstd: %s: unknown suffix (%s expected). "
3014             "Can't derive the output file name. "
3015             "Specify it with -o dstFileName. Ignoring.\n",
3016             srcFileName, suffixListStr);
3017         return NULL;
3018     }
3019     srcSuffixLen = strlen(srcSuffix);
3020 
3021     {
3022         const char** matchedSuffixPtr;
3023         for (matchedSuffixPtr = suffixList; *matchedSuffixPtr != NULL; matchedSuffixPtr++) {
3024             if (!strcmp(*matchedSuffixPtr, srcSuffix)) {
3025                 break;
3026             }
3027         }
3028 
3029         /* check suffix is authorized */
3030         if (sfnSize <= srcSuffixLen || *matchedSuffixPtr == NULL) {
3031             DISPLAYLEVEL(1,
3032                 "zstd: %s: unknown suffix (%s expected). "
3033                 "Can't derive the output file name. "
3034                 "Specify it with -o dstFileName. Ignoring.\n",
3035                 srcFileName, suffixListStr);
3036             return NULL;
3037         }
3038 
3039         if ((*matchedSuffixPtr)[1] == 't') {
3040             dstSuffix = ".tar";
3041             dstSuffixLen = strlen(dstSuffix);
3042         }
3043     }
3044 
3045     if (outDirName) {
3046         outDirFilename = FIO_createFilename_fromOutDir(srcFileName, outDirName, 0);
3047         sfnSize = strlen(outDirFilename);
3048         assert(outDirFilename != NULL);
3049     }
3050 
3051     if (dfnbCapacity+srcSuffixLen <= sfnSize+1+dstSuffixLen) {
3052         /* allocate enough space to write dstFilename into it */
3053         free(dstFileNameBuffer);
3054         dfnbCapacity = sfnSize + 20;
3055         dstFileNameBuffer = (char*)malloc(dfnbCapacity);
3056         if (dstFileNameBuffer==NULL)
3057             EXM_THROW(74, "%s : not enough memory for dstFileName",
3058                       strerror(errno));
3059     }
3060 
3061     /* return dst name == src name truncated from suffix */
3062     assert(dstFileNameBuffer != NULL);
3063     dstFileNameEndPos = sfnSize - srcSuffixLen;
3064     if (outDirFilename) {
3065         memcpy(dstFileNameBuffer, outDirFilename, dstFileNameEndPos);
3066         free(outDirFilename);
3067     } else {
3068         memcpy(dstFileNameBuffer, srcFileName, dstFileNameEndPos);
3069     }
3070 
3071     /* The short tar extensions tzst, tgz, txz and tlz4 files should have "tar"
3072      * extension on decompression. Also writes terminating null. */
3073     strcpy(dstFileNameBuffer + dstFileNameEndPos, dstSuffix);
3074     return dstFileNameBuffer;
3075 
3076     /* note : dstFileNameBuffer memory is not going to be free */
3077 }
3078 
3079 int
FIO_decompressMultipleFilenames(FIO_ctx_t * const fCtx,FIO_prefs_t * const prefs,const char ** srcNamesTable,const char * outMirroredRootDirName,const char * outDirName,const char * outFileName,const char * dictFileName)3080 FIO_decompressMultipleFilenames(FIO_ctx_t* const fCtx,
3081                                 FIO_prefs_t* const prefs,
3082                                 const char** srcNamesTable,
3083                                 const char* outMirroredRootDirName,
3084                                 const char* outDirName, const char* outFileName,
3085                                 const char* dictFileName)
3086 {
3087     int status;
3088     int error = 0;
3089     dRess_t ress = FIO_createDResources(prefs, dictFileName);
3090 
3091     if (outFileName) {
3092         if (FIO_multiFilesConcatWarning(fCtx, prefs, outFileName, 1 /* displayLevelCutoff */)) {
3093             FIO_freeDResources(ress);
3094             return 1;
3095         }
3096         if (!prefs->testMode) {
3097             FILE* dstFile = FIO_openDstFile(fCtx, prefs, NULL, outFileName, DEFAULT_FILE_PERMISSIONS);
3098             if (dstFile == 0) EXM_THROW(19, "cannot open %s", outFileName);
3099             AIO_WritePool_setFile(ress.writeCtx, dstFile);
3100         }
3101         for (; fCtx->currFileIdx < fCtx->nbFilesTotal; fCtx->currFileIdx++) {
3102             status = FIO_decompressSrcFile(fCtx, prefs, ress, outFileName, srcNamesTable[fCtx->currFileIdx]);
3103             if (!status) fCtx->nbFilesProcessed++;
3104             error |= status;
3105         }
3106         if ((!prefs->testMode) && (AIO_WritePool_closeFile(ress.writeCtx)))
3107             EXM_THROW(72, "Write error : %s : cannot properly close output file",
3108                         strerror(errno));
3109     } else {
3110         if (outMirroredRootDirName)
3111             UTIL_mirrorSourceFilesDirectories(srcNamesTable, (unsigned)fCtx->nbFilesTotal, outMirroredRootDirName);
3112 
3113         for (; fCtx->currFileIdx < fCtx->nbFilesTotal; fCtx->currFileIdx++) {   /* create dstFileName */
3114             const char* const srcFileName = srcNamesTable[fCtx->currFileIdx];
3115             const char* dstFileName = NULL;
3116             if (outMirroredRootDirName) {
3117                 char* validMirroredDirName = UTIL_createMirroredDestDirName(srcFileName, outMirroredRootDirName);
3118                 if (validMirroredDirName) {
3119                     dstFileName = FIO_determineDstName(srcFileName, validMirroredDirName);
3120                     free(validMirroredDirName);
3121                 } else {
3122                     DISPLAYLEVEL(2, "zstd: --output-dir-mirror cannot decompress '%s' into '%s'\n", srcFileName, outMirroredRootDirName);
3123                 }
3124             } else {
3125                 dstFileName = FIO_determineDstName(srcFileName, outDirName);
3126             }
3127             if (dstFileName == NULL) { error=1; continue; }
3128             status = FIO_decompressSrcFile(fCtx, prefs, ress, dstFileName, srcFileName);
3129             if (!status) fCtx->nbFilesProcessed++;
3130             error |= status;
3131         }
3132         if (outDirName)
3133             FIO_checkFilenameCollisions(srcNamesTable , (unsigned)fCtx->nbFilesTotal);
3134     }
3135 
3136     if (FIO_shouldDisplayMultipleFileSummary(fCtx)) {
3137         DISPLAY_PROGRESS("\r%79s\r", "");
3138         DISPLAY_SUMMARY("%d files decompressed : %6llu bytes total \n",
3139             fCtx->nbFilesProcessed, (unsigned long long)fCtx->totalBytesOutput);
3140     }
3141 
3142     FIO_freeDResources(ress);
3143     return error;
3144 }
3145 
3146 /* **************************************************************************
3147  *  .zst file info (--list command)
3148  ***************************************************************************/
3149 
3150 typedef struct {
3151     U64 decompressedSize;
3152     U64 compressedSize;
3153     U64 windowSize;
3154     int numActualFrames;
3155     int numSkippableFrames;
3156     int decompUnavailable;
3157     int usesCheck;
3158     BYTE checksum[4];
3159     U32 nbFiles;
3160     unsigned dictID;
3161 } fileInfo_t;
3162 
3163 typedef enum {
3164   info_success=0,
3165   info_frame_error=1,
3166   info_not_zstd=2,
3167   info_file_error=3,
3168   info_truncated_input=4
3169 } InfoError;
3170 
3171 #define ERROR_IF(c,n,...) {             \
3172     if (c) {                           \
3173         DISPLAYLEVEL(1, __VA_ARGS__);  \
3174         DISPLAYLEVEL(1, " \n");        \
3175         return n;                      \
3176     }                                  \
3177 }
3178 
3179 static InfoError
FIO_analyzeFrames(fileInfo_t * info,FILE * const srcFile)3180 FIO_analyzeFrames(fileInfo_t* info, FILE* const srcFile)
3181 {
3182     /* begin analyzing frame */
3183     for ( ; ; ) {
3184         BYTE headerBuffer[ZSTD_FRAMEHEADERSIZE_MAX];
3185         size_t const numBytesRead = fread(headerBuffer, 1, sizeof(headerBuffer), srcFile);
3186         if (numBytesRead < ZSTD_FRAMEHEADERSIZE_MIN(ZSTD_f_zstd1)) {
3187             if ( feof(srcFile)
3188               && (numBytesRead == 0)
3189               && (info->compressedSize > 0)
3190               && (info->compressedSize != UTIL_FILESIZE_UNKNOWN) ) {
3191                 unsigned long long file_position = (unsigned long long) LONG_TELL(srcFile);
3192                 unsigned long long file_size = (unsigned long long) info->compressedSize;
3193                 ERROR_IF(file_position != file_size, info_truncated_input,
3194                   "Error: seeked to position %llu, which is beyond file size of %llu\n",
3195                   file_position,
3196                   file_size);
3197                 break;  /* correct end of file => success */
3198             }
3199             ERROR_IF(feof(srcFile), info_not_zstd, "Error: reached end of file with incomplete frame");
3200             ERROR_IF(1, info_frame_error, "Error: did not reach end of file but ran out of frames");
3201         }
3202         {   U32 const magicNumber = MEM_readLE32(headerBuffer);
3203             /* Zstandard frame */
3204             if (magicNumber == ZSTD_MAGICNUMBER) {
3205                 ZSTD_FrameHeader header;
3206                 U64 const frameContentSize = ZSTD_getFrameContentSize(headerBuffer, numBytesRead);
3207                 if ( frameContentSize == ZSTD_CONTENTSIZE_ERROR
3208                   || frameContentSize == ZSTD_CONTENTSIZE_UNKNOWN ) {
3209                     info->decompUnavailable = 1;
3210                 } else {
3211                     info->decompressedSize += frameContentSize;
3212                 }
3213                 ERROR_IF(ZSTD_getFrameHeader(&header, headerBuffer, numBytesRead) != 0,
3214                         info_frame_error, "Error: could not decode frame header");
3215                 if (info->dictID != 0 && info->dictID != header.dictID) {
3216                     DISPLAY("WARNING: File contains multiple frames with different dictionary IDs. Showing dictID 0 instead");
3217                     info->dictID = 0;
3218                 } else {
3219                     info->dictID = header.dictID;
3220                 }
3221                 info->windowSize = header.windowSize;
3222                 /* move to the end of the frame header */
3223                 {   size_t const headerSize = ZSTD_frameHeaderSize(headerBuffer, numBytesRead);
3224                     ERROR_IF(ZSTD_isError(headerSize), info_frame_error, "Error: could not determine frame header size");
3225                     ERROR_IF(fseek(srcFile, ((long)headerSize)-((long)numBytesRead), SEEK_CUR) != 0,
3226                             info_frame_error, "Error: could not move to end of frame header");
3227                 }
3228 
3229                 /* skip all blocks in the frame */
3230                 {   int lastBlock = 0;
3231                     do {
3232                         BYTE blockHeaderBuffer[3];
3233                         ERROR_IF(fread(blockHeaderBuffer, 1, 3, srcFile) != 3,
3234                                 info_frame_error, "Error while reading block header");
3235                         {   U32 const blockHeader = MEM_readLE24(blockHeaderBuffer);
3236                             U32 const blockTypeID = (blockHeader >> 1) & 3;
3237                             U32 const isRLE = (blockTypeID == 1);
3238                             U32 const isWrongBlock = (blockTypeID == 3);
3239                             long const blockSize = isRLE ? 1 : (long)(blockHeader >> 3);
3240                             ERROR_IF(isWrongBlock, info_frame_error, "Error: unsupported block type");
3241                             lastBlock = blockHeader & 1;
3242                             ERROR_IF(fseek(srcFile, blockSize, SEEK_CUR) != 0,
3243                                     info_frame_error, "Error: could not skip to end of block");
3244                         }
3245                     } while (lastBlock != 1);
3246                 }
3247 
3248                 /* check if checksum is used */
3249                 {   BYTE const frameHeaderDescriptor = headerBuffer[4];
3250                     int const contentChecksumFlag = (frameHeaderDescriptor & (1 << 2)) >> 2;
3251                     if (contentChecksumFlag) {
3252                         info->usesCheck = 1;
3253                         ERROR_IF(fread(info->checksum, 1, 4, srcFile) != 4,
3254                                 info_frame_error, "Error: could not read checksum");
3255                 }   }
3256                 info->numActualFrames++;
3257             }
3258             /* Skippable frame */
3259             else if ((magicNumber & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) {
3260                 U32 const frameSize = MEM_readLE32(headerBuffer + 4);
3261                 long const seek = (long)(8 + frameSize - numBytesRead);
3262                 ERROR_IF(LONG_SEEK(srcFile, seek, SEEK_CUR) != 0,
3263                         info_frame_error, "Error: could not find end of skippable frame");
3264                 info->numSkippableFrames++;
3265             }
3266             /* unknown content */
3267             else {
3268                 return info_not_zstd;
3269             }
3270         }  /* magic number analysis */
3271     }  /* end analyzing frames */
3272     return info_success;
3273 }
3274 
3275 
3276 static InfoError
getFileInfo_fileConfirmed(fileInfo_t * info,const char * inFileName)3277 getFileInfo_fileConfirmed(fileInfo_t* info, const char* inFileName)
3278 {
3279     InfoError status;
3280     stat_t srcFileStat;
3281     FILE* const srcFile = FIO_openSrcFile(NULL, inFileName, &srcFileStat);
3282     ERROR_IF(srcFile == NULL, info_file_error, "Error: could not open source file %s", inFileName);
3283 
3284     info->compressedSize = UTIL_getFileSizeStat(&srcFileStat);
3285     status = FIO_analyzeFrames(info, srcFile);
3286 
3287     fclose(srcFile);
3288     info->nbFiles = 1;
3289     return status;
3290 }
3291 
3292 
3293 /** getFileInfo() :
3294  *  Reads information from file, stores in *info
3295  * @return : InfoError status
3296  */
3297 static InfoError
getFileInfo(fileInfo_t * info,const char * srcFileName)3298 getFileInfo(fileInfo_t* info, const char* srcFileName)
3299 {
3300     ERROR_IF(!UTIL_isRegularFile(srcFileName),
3301             info_file_error, "Error : %s is not a file", srcFileName);
3302     return getFileInfo_fileConfirmed(info, srcFileName);
3303 }
3304 
3305 
3306 static void
displayInfo(const char * inFileName,const fileInfo_t * info,int displayLevel)3307 displayInfo(const char* inFileName, const fileInfo_t* info, int displayLevel)
3308 {
3309     UTIL_HumanReadableSize_t const window_hrs = UTIL_makeHumanReadableSize(info->windowSize);
3310     UTIL_HumanReadableSize_t const compressed_hrs = UTIL_makeHumanReadableSize(info->compressedSize);
3311     UTIL_HumanReadableSize_t const decompressed_hrs = UTIL_makeHumanReadableSize(info->decompressedSize);
3312     double const ratio = (info->compressedSize == 0) ? 0 : ((double)info->decompressedSize)/(double)info->compressedSize;
3313     const char* const checkString = (info->usesCheck ? "XXH64" : "None");
3314     if (displayLevel <= 2) {
3315         if (!info->decompUnavailable) {
3316             DISPLAYOUT("%6d  %5d  %6.*f%4s  %8.*f%4s  %5.3f  %5s  %s\n",
3317                     info->numSkippableFrames + info->numActualFrames,
3318                     info->numSkippableFrames,
3319                     compressed_hrs.precision, compressed_hrs.value, compressed_hrs.suffix,
3320                     decompressed_hrs.precision, decompressed_hrs.value, decompressed_hrs.suffix,
3321                     ratio, checkString, inFileName);
3322         } else {
3323             DISPLAYOUT("%6d  %5d  %6.*f%4s                       %5s  %s\n",
3324                     info->numSkippableFrames + info->numActualFrames,
3325                     info->numSkippableFrames,
3326                     compressed_hrs.precision, compressed_hrs.value, compressed_hrs.suffix,
3327                     checkString, inFileName);
3328         }
3329     } else {
3330         DISPLAYOUT("%s \n", inFileName);
3331         DISPLAYOUT("# Zstandard Frames: %d\n", info->numActualFrames);
3332         if (info->numSkippableFrames)
3333             DISPLAYOUT("# Skippable Frames: %d\n", info->numSkippableFrames);
3334         DISPLAYOUT("DictID: %u\n", info->dictID);
3335         DISPLAYOUT("Window Size: %.*f%s (%llu B)\n",
3336                    window_hrs.precision, window_hrs.value, window_hrs.suffix,
3337                    (unsigned long long)info->windowSize);
3338         DISPLAYOUT("Compressed Size: %.*f%s (%llu B)\n",
3339                     compressed_hrs.precision, compressed_hrs.value, compressed_hrs.suffix,
3340                     (unsigned long long)info->compressedSize);
3341         if (!info->decompUnavailable) {
3342             DISPLAYOUT("Decompressed Size: %.*f%s (%llu B)\n",
3343                     decompressed_hrs.precision, decompressed_hrs.value, decompressed_hrs.suffix,
3344                     (unsigned long long)info->decompressedSize);
3345             DISPLAYOUT("Ratio: %.4f\n", ratio);
3346         }
3347 
3348         if (info->usesCheck && info->numActualFrames == 1) {
3349             DISPLAYOUT("Check: %s %02x%02x%02x%02x\n", checkString,
3350                 info->checksum[3], info->checksum[2],
3351                 info->checksum[1], info->checksum[0]
3352             );
3353         } else {
3354             DISPLAYOUT("Check: %s\n", checkString);
3355         }
3356 
3357         DISPLAYOUT("\n");
3358     }
3359 }
3360 
FIO_addFInfo(fileInfo_t fi1,fileInfo_t fi2)3361 static fileInfo_t FIO_addFInfo(fileInfo_t fi1, fileInfo_t fi2)
3362 {
3363     fileInfo_t total;
3364     memset(&total, 0, sizeof(total));
3365     total.numActualFrames = fi1.numActualFrames + fi2.numActualFrames;
3366     total.numSkippableFrames = fi1.numSkippableFrames + fi2.numSkippableFrames;
3367     total.compressedSize = fi1.compressedSize + fi2.compressedSize;
3368     total.decompressedSize = fi1.decompressedSize + fi2.decompressedSize;
3369     total.decompUnavailable = fi1.decompUnavailable | fi2.decompUnavailable;
3370     total.usesCheck = fi1.usesCheck & fi2.usesCheck;
3371     total.nbFiles = fi1.nbFiles + fi2.nbFiles;
3372     return total;
3373 }
3374 
3375 static int
FIO_listFile(fileInfo_t * total,const char * inFileName,int displayLevel)3376 FIO_listFile(fileInfo_t* total, const char* inFileName, int displayLevel)
3377 {
3378     fileInfo_t info;
3379     memset(&info, 0, sizeof(info));
3380     {   InfoError const error = getFileInfo(&info, inFileName);
3381         switch (error) {
3382             case info_frame_error:
3383                 /* display error, but provide output */
3384                 DISPLAYLEVEL(1, "Error while parsing \"%s\" \n", inFileName);
3385                 break;
3386             case info_not_zstd:
3387                 DISPLAYOUT("File \"%s\" not compressed by zstd \n", inFileName);
3388                 if (displayLevel > 2) DISPLAYOUT("\n");
3389                 return 1;
3390             case info_file_error:
3391                 /* error occurred while opening the file */
3392                 if (displayLevel > 2) DISPLAYOUT("\n");
3393                 return 1;
3394             case info_truncated_input:
3395                 DISPLAYOUT("File \"%s\" is truncated \n", inFileName);
3396                 if (displayLevel > 2) DISPLAYOUT("\n");
3397                 return 1;
3398             case info_success:
3399             default:
3400                 break;
3401         }
3402 
3403         displayInfo(inFileName, &info, displayLevel);
3404         *total = FIO_addFInfo(*total, info);
3405         assert(error == info_success || error == info_frame_error);
3406         return (int)error;
3407     }
3408 }
3409 
FIO_listMultipleFiles(unsigned numFiles,const char ** filenameTable,int displayLevel)3410 int FIO_listMultipleFiles(unsigned numFiles, const char** filenameTable, int displayLevel)
3411 {
3412     /* ensure no specified input is stdin (needs fseek() capability) */
3413     {   unsigned u;
3414         for (u=0; u<numFiles;u++) {
3415             ERROR_IF(!strcmp (filenameTable[u], stdinmark),
3416                     1, "zstd: --list does not support reading from standard input");
3417     }   }
3418 
3419     if (numFiles == 0) {
3420         if (!UTIL_isConsole(stdin)) {
3421             DISPLAYLEVEL(1, "zstd: --list does not support reading from standard input \n");
3422         }
3423         DISPLAYLEVEL(1, "No files given \n");
3424         return 1;
3425     }
3426 
3427     if (displayLevel <= 2) {
3428         DISPLAYOUT("Frames  Skips  Compressed  Uncompressed  Ratio  Check  Filename\n");
3429     }
3430     {   int error = 0;
3431         fileInfo_t total;
3432         memset(&total, 0, sizeof(total));
3433         total.usesCheck = 1;
3434         /* --list each file, and check for any error */
3435         {   unsigned u;
3436             for (u=0; u<numFiles;u++) {
3437                 error |= FIO_listFile(&total, filenameTable[u], displayLevel);
3438         }   }
3439         if (numFiles > 1 && displayLevel <= 2) {   /* display total */
3440             UTIL_HumanReadableSize_t const compressed_hrs = UTIL_makeHumanReadableSize(total.compressedSize);
3441             UTIL_HumanReadableSize_t const decompressed_hrs = UTIL_makeHumanReadableSize(total.decompressedSize);
3442             double const ratio = (total.compressedSize == 0) ? 0 : ((double)total.decompressedSize)/(double)total.compressedSize;
3443             const char* const checkString = (total.usesCheck ? "XXH64" : "");
3444             DISPLAYOUT("----------------------------------------------------------------- \n");
3445             if (total.decompUnavailable) {
3446                 DISPLAYOUT("%6d  %5d  %6.*f%4s                       %5s  %u files\n",
3447                         total.numSkippableFrames + total.numActualFrames,
3448                         total.numSkippableFrames,
3449                         compressed_hrs.precision, compressed_hrs.value, compressed_hrs.suffix,
3450                         checkString, (unsigned)total.nbFiles);
3451             } else {
3452                 DISPLAYOUT("%6d  %5d  %6.*f%4s  %8.*f%4s  %5.3f  %5s  %u files\n",
3453                         total.numSkippableFrames + total.numActualFrames,
3454                         total.numSkippableFrames,
3455                         compressed_hrs.precision, compressed_hrs.value, compressed_hrs.suffix,
3456                         decompressed_hrs.precision, decompressed_hrs.value, decompressed_hrs.suffix,
3457                         ratio, checkString, (unsigned)total.nbFiles);
3458         }   }
3459         return error;
3460     }
3461 }
3462 
3463 
3464 #endif /* #ifndef ZSTD_NODECOMPRESS */
3465