Path: blob/main/sys/contrib/zstd/programs/fileio.c
48254 views
/*1* Copyright (c) Yann Collet, Facebook, Inc.2* All rights reserved.3*4* This source code is licensed under both the BSD-style license (found in the5* LICENSE file in the root directory of this source tree) and the GPLv2 (found6* in the COPYING file in the root directory of this source tree).7* You may select, at your option, one of the above-listed licenses.8*/91011/* *************************************12* Compiler Options13***************************************/14#ifdef _MSC_VER /* Visual */15# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */16# pragma warning(disable : 4204) /* non-constant aggregate initializer */17#endif18#if defined(__MINGW32__) && !defined(_POSIX_SOURCE)19# define _POSIX_SOURCE 1 /* disable %llu warnings with MinGW on Windows */20#endif2122/*-*************************************23* Includes24***************************************/25#include "platform.h" /* Large Files support, SET_BINARY_MODE */26#include "util.h" /* UTIL_getFileSize, UTIL_isRegularFile, UTIL_isSameFile */27#include <stdio.h> /* fprintf, open, fdopen, fread, _fileno, stdin, stdout */28#include <stdlib.h> /* malloc, free */29#include <string.h> /* strcmp, strlen */30#include <fcntl.h> /* O_WRONLY */31#include <assert.h>32#include <errno.h> /* errno */33#include <limits.h> /* INT_MAX */34#include <signal.h>35#include "timefn.h" /* UTIL_getTime, UTIL_clockSpanMicro */3637#if defined (_MSC_VER)38# include <sys/stat.h>39# include <io.h>40#endif4142#include "../lib/common/mem.h" /* U32, U64 */43#include "fileio.h"4445#define ZSTD_STATIC_LINKING_ONLY /* ZSTD_magicNumber, ZSTD_frameHeaderSize_max */46#include "../lib/zstd.h"47#include "../lib/zstd_errors.h" /* ZSTD_error_frameParameter_windowTooLarge */4849#if defined(ZSTD_GZCOMPRESS) || defined(ZSTD_GZDECOMPRESS)50# include <zlib.h>51# if !defined(z_const)52# define z_const53# endif54#endif5556#if defined(ZSTD_LZMACOMPRESS) || defined(ZSTD_LZMADECOMPRESS)57# include <lzma.h>58#endif5960#define LZ4_MAGICNUMBER 0x184D220461#if defined(ZSTD_LZ4COMPRESS) || defined(ZSTD_LZ4DECOMPRESS)62# define LZ4F_ENABLE_OBSOLETE_ENUMS63# include <lz4frame.h>64# include <lz4.h>65#endif666768/*-*************************************69* Constants70***************************************/71#define ADAPT_WINDOWLOG_DEFAULT 23 /* 8 MB */72#define DICTSIZE_MAX (32 MB) /* protection against large input (attack scenario) */7374#define FNSPACE 307576/* Default file permissions 0666 (modulated by umask) */77#if !defined(_WIN32)78/* These macros aren't defined on windows. */79#define DEFAULT_FILE_PERMISSIONS (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)80#else81#define DEFAULT_FILE_PERMISSIONS (0666)82#endif8384/*-*************************************85* Macros86***************************************/87#define KB *(1 <<10)88#define MB *(1 <<20)89#define GB *(1U<<30)90#undef MAX91#define MAX(a,b) ((a)>(b) ? (a) : (b))9293struct FIO_display_prefs_s {94int displayLevel; /* 0 : no display; 1: errors; 2: + result + interaction + warnings; 3: + progression; 4: + information */95FIO_progressSetting_e progressSetting;96};9798static FIO_display_prefs_t g_display_prefs = {2, FIO_ps_auto};99100#define DISPLAY(...) fprintf(stderr, __VA_ARGS__)101#define DISPLAYOUT(...) fprintf(stdout, __VA_ARGS__)102#define DISPLAYLEVEL(l, ...) { if (g_display_prefs.displayLevel>=l) { DISPLAY(__VA_ARGS__); } }103104static const U64 g_refreshRate = SEC_TO_MICRO / 6;105static UTIL_time_t g_displayClock = UTIL_TIME_INITIALIZER;106107#define READY_FOR_UPDATE() ((g_display_prefs.progressSetting != FIO_ps_never) && UTIL_clockSpanMicro(g_displayClock) > g_refreshRate)108#define DELAY_NEXT_UPDATE() { g_displayClock = UTIL_getTime(); }109#define DISPLAYUPDATE(l, ...) { \110if (g_display_prefs.displayLevel>=l && (g_display_prefs.progressSetting != FIO_ps_never)) { \111if (READY_FOR_UPDATE() || (g_display_prefs.displayLevel>=4)) { \112DELAY_NEXT_UPDATE(); \113DISPLAY(__VA_ARGS__); \114if (g_display_prefs.displayLevel>=4) fflush(stderr); \115} } }116117#undef MIN /* in case it would be already defined */118#define MIN(a,b) ((a) < (b) ? (a) : (b))119120121#define EXM_THROW(error, ...) \122{ \123DISPLAYLEVEL(1, "zstd: "); \124DISPLAYLEVEL(5, "Error defined at %s, line %i : \n", __FILE__, __LINE__); \125DISPLAYLEVEL(1, "error %i : ", error); \126DISPLAYLEVEL(1, __VA_ARGS__); \127DISPLAYLEVEL(1, " \n"); \128exit(error); \129}130131#define CHECK_V(v, f) \132v = f; \133if (ZSTD_isError(v)) { \134DISPLAYLEVEL(5, "%s \n", #f); \135EXM_THROW(11, "%s", ZSTD_getErrorName(v)); \136}137#define CHECK(f) { size_t err; CHECK_V(err, f); }138139140/*-************************************141* Signal (Ctrl-C trapping)142**************************************/143static const char* g_artefact = NULL;144static void INThandler(int sig)145{146assert(sig==SIGINT); (void)sig;147#if !defined(_MSC_VER)148signal(sig, SIG_IGN); /* this invocation generates a buggy warning in Visual Studio */149#endif150if (g_artefact) {151assert(UTIL_isRegularFile(g_artefact));152remove(g_artefact);153}154DISPLAY("\n");155exit(2);156}157static void addHandler(char const* dstFileName)158{159if (UTIL_isRegularFile(dstFileName)) {160g_artefact = dstFileName;161signal(SIGINT, INThandler);162} else {163g_artefact = NULL;164}165}166/* Idempotent */167static void clearHandler(void)168{169if (g_artefact) signal(SIGINT, SIG_DFL);170g_artefact = NULL;171}172173174/*-*********************************************************175* Termination signal trapping (Print debug stack trace)176***********************************************************/177#if defined(__has_feature) && !defined(BACKTRACE_ENABLE) /* Clang compiler */178# if (__has_feature(address_sanitizer))179# define BACKTRACE_ENABLE 0180# endif /* __has_feature(address_sanitizer) */181#elif defined(__SANITIZE_ADDRESS__) && !defined(BACKTRACE_ENABLE) /* GCC compiler */182# define BACKTRACE_ENABLE 0183#endif184185#if !defined(BACKTRACE_ENABLE)186/* automatic detector : backtrace enabled by default on linux+glibc and osx */187# if (defined(__linux__) && (defined(__GLIBC__) && !defined(__UCLIBC__))) \188|| (defined(__APPLE__) && defined(__MACH__))189# define BACKTRACE_ENABLE 1190# else191# define BACKTRACE_ENABLE 0192# endif193#endif194195/* note : after this point, BACKTRACE_ENABLE is necessarily defined */196197198#if BACKTRACE_ENABLE199200#include <execinfo.h> /* backtrace, backtrace_symbols */201202#define MAX_STACK_FRAMES 50203204static void ABRThandler(int sig) {205const char* name;206void* addrlist[MAX_STACK_FRAMES];207char** symbollist;208int addrlen, i;209210switch (sig) {211case SIGABRT: name = "SIGABRT"; break;212case SIGFPE: name = "SIGFPE"; break;213case SIGILL: name = "SIGILL"; break;214case SIGINT: name = "SIGINT"; break;215case SIGSEGV: name = "SIGSEGV"; break;216default: name = "UNKNOWN";217}218219DISPLAY("Caught %s signal, printing stack:\n", name);220/* Retrieve current stack addresses. */221addrlen = backtrace(addrlist, MAX_STACK_FRAMES);222if (addrlen == 0) {223DISPLAY("\n");224return;225}226/* Create readable strings to each frame. */227symbollist = backtrace_symbols(addrlist, addrlen);228/* Print the stack trace, excluding calls handling the signal. */229for (i = ZSTD_START_SYMBOLLIST_FRAME; i < addrlen; i++) {230DISPLAY("%s\n", symbollist[i]);231}232free(symbollist);233/* Reset and raise the signal so default handler runs. */234signal(sig, SIG_DFL);235raise(sig);236}237#endif238239void FIO_addAbortHandler()240{241#if BACKTRACE_ENABLE242signal(SIGABRT, ABRThandler);243signal(SIGFPE, ABRThandler);244signal(SIGILL, ABRThandler);245signal(SIGSEGV, ABRThandler);246signal(SIGBUS, ABRThandler);247#endif248}249250251/*-************************************************************252* Avoid fseek()'s 2GiB barrier with MSVC, macOS, *BSD, MinGW253***************************************************************/254#if defined(_MSC_VER) && _MSC_VER >= 1400255# define LONG_SEEK _fseeki64256# define LONG_TELL _ftelli64257#elif !defined(__64BIT__) && (PLATFORM_POSIX_VERSION >= 200112L) /* No point defining Large file for 64 bit */258# define LONG_SEEK fseeko259# define LONG_TELL ftello260#elif defined(__MINGW32__) && !defined(__STRICT_ANSI__) && !defined(__NO_MINGW_LFS) && defined(__MSVCRT__)261# define LONG_SEEK fseeko64262# define LONG_TELL ftello64263#elif defined(_WIN32) && !defined(__DJGPP__)264# include <windows.h>265static int LONG_SEEK(FILE* file, __int64 offset, int origin) {266LARGE_INTEGER off;267DWORD method;268off.QuadPart = offset;269if (origin == SEEK_END)270method = FILE_END;271else if (origin == SEEK_CUR)272method = FILE_CURRENT;273else274method = FILE_BEGIN;275276if (SetFilePointerEx((HANDLE) _get_osfhandle(_fileno(file)), off, NULL, method))277return 0;278else279return -1;280}281static __int64 LONG_TELL(FILE* file) {282LARGE_INTEGER off, newOff;283off.QuadPart = 0;284newOff.QuadPart = 0;285SetFilePointerEx((HANDLE) _get_osfhandle(_fileno(file)), off, &newOff, FILE_CURRENT);286return newOff.QuadPart;287}288#else289# define LONG_SEEK fseek290# define LONG_TELL ftell291#endif292293294/*-*************************************295* Parameters: FIO_prefs_t296***************************************/297298/* typedef'd to FIO_prefs_t within fileio.h */299struct FIO_prefs_s {300301/* Algorithm preferences */302FIO_compressionType_t compressionType;303U32 sparseFileSupport; /* 0: no sparse allowed; 1: auto (file yes, stdout no); 2: force sparse */304int dictIDFlag;305int checksumFlag;306int blockSize;307int overlapLog;308U32 adaptiveMode;309U32 useRowMatchFinder;310int rsyncable;311int minAdaptLevel;312int maxAdaptLevel;313int ldmFlag;314int ldmHashLog;315int ldmMinMatch;316int ldmBucketSizeLog;317int ldmHashRateLog;318size_t streamSrcSize;319size_t targetCBlockSize;320int srcSizeHint;321int testMode;322ZSTD_paramSwitch_e literalCompressionMode;323324/* IO preferences */325U32 removeSrcFile;326U32 overwrite;327328/* Computation resources preferences */329unsigned memLimit;330int nbWorkers;331332int excludeCompressedFiles;333int patchFromMode;334int contentSize;335int allowBlockDevices;336};337338/*-*************************************339* Parameters: FIO_ctx_t340***************************************/341342/* typedef'd to FIO_ctx_t within fileio.h */343struct FIO_ctx_s {344345/* file i/o info */346int nbFilesTotal;347int hasStdinInput;348int hasStdoutOutput;349350/* file i/o state */351int currFileIdx;352int nbFilesProcessed;353size_t totalBytesInput;354size_t totalBytesOutput;355};356357358/*-*************************************359* Parameters: Initialization360***************************************/361362#define FIO_OVERLAP_LOG_NOTSET 9999363#define FIO_LDM_PARAM_NOTSET 9999364365366FIO_prefs_t* FIO_createPreferences(void)367{368FIO_prefs_t* const ret = (FIO_prefs_t*)malloc(sizeof(FIO_prefs_t));369if (!ret) EXM_THROW(21, "Allocation error : not enough memory");370371ret->compressionType = FIO_zstdCompression;372ret->overwrite = 0;373ret->sparseFileSupport = ZSTD_SPARSE_DEFAULT;374ret->dictIDFlag = 1;375ret->checksumFlag = 1;376ret->removeSrcFile = 0;377ret->memLimit = 0;378ret->nbWorkers = 1;379ret->blockSize = 0;380ret->overlapLog = FIO_OVERLAP_LOG_NOTSET;381ret->adaptiveMode = 0;382ret->rsyncable = 0;383ret->minAdaptLevel = -50; /* initializing this value requires a constant, so ZSTD_minCLevel() doesn't work */384ret->maxAdaptLevel = 22; /* initializing this value requires a constant, so ZSTD_maxCLevel() doesn't work */385ret->ldmFlag = 0;386ret->ldmHashLog = 0;387ret->ldmMinMatch = 0;388ret->ldmBucketSizeLog = FIO_LDM_PARAM_NOTSET;389ret->ldmHashRateLog = FIO_LDM_PARAM_NOTSET;390ret->streamSrcSize = 0;391ret->targetCBlockSize = 0;392ret->srcSizeHint = 0;393ret->testMode = 0;394ret->literalCompressionMode = ZSTD_ps_auto;395ret->excludeCompressedFiles = 0;396ret->allowBlockDevices = 0;397return ret;398}399400FIO_ctx_t* FIO_createContext(void)401{402FIO_ctx_t* const ret = (FIO_ctx_t*)malloc(sizeof(FIO_ctx_t));403if (!ret) EXM_THROW(21, "Allocation error : not enough memory");404405ret->currFileIdx = 0;406ret->hasStdinInput = 0;407ret->hasStdoutOutput = 0;408ret->nbFilesTotal = 1;409ret->nbFilesProcessed = 0;410ret->totalBytesInput = 0;411ret->totalBytesOutput = 0;412return ret;413}414415void FIO_freePreferences(FIO_prefs_t* const prefs)416{417free(prefs);418}419420void FIO_freeContext(FIO_ctx_t* const fCtx)421{422free(fCtx);423}424425426/*-*************************************427* Parameters: Display Options428***************************************/429430void FIO_setNotificationLevel(int level) { g_display_prefs.displayLevel=level; }431432void FIO_setProgressSetting(FIO_progressSetting_e setting) { g_display_prefs.progressSetting = setting; }433434435/*-*************************************436* Parameters: Setters437***************************************/438439/* FIO_prefs_t functions */440441void FIO_setCompressionType(FIO_prefs_t* const prefs, FIO_compressionType_t compressionType) { prefs->compressionType = compressionType; }442443void FIO_overwriteMode(FIO_prefs_t* const prefs) { prefs->overwrite = 1; }444445void FIO_setSparseWrite(FIO_prefs_t* const prefs, unsigned sparse) { prefs->sparseFileSupport = sparse; }446447void FIO_setDictIDFlag(FIO_prefs_t* const prefs, int dictIDFlag) { prefs->dictIDFlag = dictIDFlag; }448449void FIO_setChecksumFlag(FIO_prefs_t* const prefs, int checksumFlag) { prefs->checksumFlag = checksumFlag; }450451void FIO_setRemoveSrcFile(FIO_prefs_t* const prefs, unsigned flag) { prefs->removeSrcFile = (flag>0); }452453void FIO_setMemLimit(FIO_prefs_t* const prefs, unsigned memLimit) { prefs->memLimit = memLimit; }454455void FIO_setNbWorkers(FIO_prefs_t* const prefs, int nbWorkers) {456#ifndef ZSTD_MULTITHREAD457if (nbWorkers > 0) DISPLAYLEVEL(2, "Note : multi-threading is disabled \n");458#endif459prefs->nbWorkers = nbWorkers;460}461462void FIO_setExcludeCompressedFile(FIO_prefs_t* const prefs, int excludeCompressedFiles) { prefs->excludeCompressedFiles = excludeCompressedFiles; }463464void FIO_setAllowBlockDevices(FIO_prefs_t* const prefs, int allowBlockDevices) { prefs->allowBlockDevices = allowBlockDevices; }465466void FIO_setBlockSize(FIO_prefs_t* const prefs, int blockSize) {467if (blockSize && prefs->nbWorkers==0)468DISPLAYLEVEL(2, "Setting block size is useless in single-thread mode \n");469prefs->blockSize = blockSize;470}471472void FIO_setOverlapLog(FIO_prefs_t* const prefs, int overlapLog){473if (overlapLog && prefs->nbWorkers==0)474DISPLAYLEVEL(2, "Setting overlapLog is useless in single-thread mode \n");475prefs->overlapLog = overlapLog;476}477478void FIO_setAdaptiveMode(FIO_prefs_t* const prefs, unsigned adapt) {479if ((adapt>0) && (prefs->nbWorkers==0))480EXM_THROW(1, "Adaptive mode is not compatible with single thread mode \n");481prefs->adaptiveMode = adapt;482}483484void FIO_setUseRowMatchFinder(FIO_prefs_t* const prefs, int useRowMatchFinder) {485prefs->useRowMatchFinder = useRowMatchFinder;486}487488void FIO_setRsyncable(FIO_prefs_t* const prefs, int rsyncable) {489if ((rsyncable>0) && (prefs->nbWorkers==0))490EXM_THROW(1, "Rsyncable mode is not compatible with single thread mode \n");491prefs->rsyncable = rsyncable;492}493494void FIO_setStreamSrcSize(FIO_prefs_t* const prefs, size_t streamSrcSize) {495prefs->streamSrcSize = streamSrcSize;496}497498void FIO_setTargetCBlockSize(FIO_prefs_t* const prefs, size_t targetCBlockSize) {499prefs->targetCBlockSize = targetCBlockSize;500}501502void FIO_setSrcSizeHint(FIO_prefs_t* const prefs, size_t srcSizeHint) {503prefs->srcSizeHint = (int)MIN((size_t)INT_MAX, srcSizeHint);504}505506void FIO_setTestMode(FIO_prefs_t* const prefs, int testMode) {507prefs->testMode = (testMode!=0);508}509510void FIO_setLiteralCompressionMode(511FIO_prefs_t* const prefs,512ZSTD_paramSwitch_e mode) {513prefs->literalCompressionMode = mode;514}515516void FIO_setAdaptMin(FIO_prefs_t* const prefs, int minCLevel)517{518#ifndef ZSTD_NOCOMPRESS519assert(minCLevel >= ZSTD_minCLevel());520#endif521prefs->minAdaptLevel = minCLevel;522}523524void FIO_setAdaptMax(FIO_prefs_t* const prefs, int maxCLevel)525{526prefs->maxAdaptLevel = maxCLevel;527}528529void FIO_setLdmFlag(FIO_prefs_t* const prefs, unsigned ldmFlag) {530prefs->ldmFlag = (ldmFlag>0);531}532533void FIO_setLdmHashLog(FIO_prefs_t* const prefs, int ldmHashLog) {534prefs->ldmHashLog = ldmHashLog;535}536537void FIO_setLdmMinMatch(FIO_prefs_t* const prefs, int ldmMinMatch) {538prefs->ldmMinMatch = ldmMinMatch;539}540541void FIO_setLdmBucketSizeLog(FIO_prefs_t* const prefs, int ldmBucketSizeLog) {542prefs->ldmBucketSizeLog = ldmBucketSizeLog;543}544545546void FIO_setLdmHashRateLog(FIO_prefs_t* const prefs, int ldmHashRateLog) {547prefs->ldmHashRateLog = ldmHashRateLog;548}549550void FIO_setPatchFromMode(FIO_prefs_t* const prefs, int value)551{552prefs->patchFromMode = value != 0;553}554555void FIO_setContentSize(FIO_prefs_t* const prefs, int value)556{557prefs->contentSize = value != 0;558}559560/* FIO_ctx_t functions */561562void FIO_setHasStdoutOutput(FIO_ctx_t* const fCtx, int value) {563fCtx->hasStdoutOutput = value;564}565566void FIO_setNbFilesTotal(FIO_ctx_t* const fCtx, int value)567{568fCtx->nbFilesTotal = value;569}570571void FIO_determineHasStdinInput(FIO_ctx_t* const fCtx, const FileNamesTable* const filenames) {572size_t i = 0;573for ( ; i < filenames->tableSize; ++i) {574if (!strcmp(stdinmark, filenames->fileNames[i])) {575fCtx->hasStdinInput = 1;576return;577}578}579}580581/*-*************************************582* Functions583***************************************/584/** FIO_removeFile() :585* @result : Unlink `fileName`, even if it's read-only */586static int FIO_removeFile(const char* path)587{588stat_t statbuf;589if (!UTIL_stat(path, &statbuf)) {590DISPLAYLEVEL(2, "zstd: Failed to stat %s while trying to remove it\n", path);591return 0;592}593if (!UTIL_isRegularFileStat(&statbuf)) {594DISPLAYLEVEL(2, "zstd: Refusing to remove non-regular file %s\n", path);595return 0;596}597#if defined(_WIN32) || defined(WIN32)598/* windows doesn't allow remove read-only files,599* so try to make it writable first */600if (!(statbuf.st_mode & _S_IWRITE)) {601UTIL_chmod(path, &statbuf, _S_IWRITE);602}603#endif604return remove(path);605}606607/** FIO_openSrcFile() :608* condition : `srcFileName` must be non-NULL. `prefs` may be NULL.609* @result : FILE* to `srcFileName`, or NULL if it fails */610static FILE* FIO_openSrcFile(const FIO_prefs_t* const prefs, const char* srcFileName)611{612stat_t statbuf;613int allowBlockDevices = prefs != NULL ? prefs->allowBlockDevices : 0;614assert(srcFileName != NULL);615if (!strcmp (srcFileName, stdinmark)) {616DISPLAYLEVEL(4,"Using stdin for input \n");617SET_BINARY_MODE(stdin);618return stdin;619}620621if (!UTIL_stat(srcFileName, &statbuf)) {622DISPLAYLEVEL(1, "zstd: can't stat %s : %s -- ignored \n",623srcFileName, strerror(errno));624return NULL;625}626627if (!UTIL_isRegularFileStat(&statbuf)628&& !UTIL_isFIFOStat(&statbuf)629&& !(allowBlockDevices && UTIL_isBlockDevStat(&statbuf))630) {631DISPLAYLEVEL(1, "zstd: %s is not a regular file -- ignored \n",632srcFileName);633return NULL;634}635636{ FILE* const f = fopen(srcFileName, "rb");637if (f == NULL)638DISPLAYLEVEL(1, "zstd: %s: %s \n", srcFileName, strerror(errno));639return f;640}641}642643/** FIO_openDstFile() :644* condition : `dstFileName` must be non-NULL.645* @result : FILE* to `dstFileName`, or NULL if it fails */646static FILE*647FIO_openDstFile(FIO_ctx_t* fCtx, FIO_prefs_t* const prefs,648const char* srcFileName, const char* dstFileName,649const int mode)650{651if (prefs->testMode) return NULL; /* do not open file in test mode */652653assert(dstFileName != NULL);654if (!strcmp (dstFileName, stdoutmark)) {655DISPLAYLEVEL(4,"Using stdout for output \n");656SET_BINARY_MODE(stdout);657if (prefs->sparseFileSupport == 1) {658prefs->sparseFileSupport = 0;659DISPLAYLEVEL(4, "Sparse File Support is automatically disabled on stdout ; try --sparse \n");660}661return stdout;662}663664/* ensure dst is not the same as src */665if (srcFileName != NULL && UTIL_isSameFile(srcFileName, dstFileName)) {666DISPLAYLEVEL(1, "zstd: Refusing to open an output file which will overwrite the input file \n");667return NULL;668}669670if (prefs->sparseFileSupport == 1) {671prefs->sparseFileSupport = ZSTD_SPARSE_DEFAULT;672}673674if (UTIL_isRegularFile(dstFileName)) {675/* Check if destination file already exists */676#if !defined(_WIN32)677/* this test does not work on Windows :678* `NUL` and `nul` are detected as regular files */679if (!strcmp(dstFileName, nulmark)) {680EXM_THROW(40, "%s is unexpectedly categorized as a regular file",681dstFileName);682}683#endif684if (!prefs->overwrite) {685if (g_display_prefs.displayLevel <= 1) {686/* No interaction possible */687DISPLAY("zstd: %s already exists; not overwritten \n",688dstFileName);689return NULL;690}691DISPLAY("zstd: %s already exists; ", dstFileName);692if (UTIL_requireUserConfirmation("overwrite (y/n) ? ", "Not overwritten \n", "yY", fCtx->hasStdinInput))693return NULL;694}695/* need to unlink */696FIO_removeFile(dstFileName);697}698699{700#if defined(_WIN32)701/* Windows requires opening the file as a "binary" file to avoid702* mangling. This macro doesn't exist on unix. */703const int openflags = O_WRONLY|O_CREAT|O_TRUNC|O_BINARY;704const int fd = _open(dstFileName, openflags, mode);705FILE* f = NULL;706if (fd != -1) {707f = _fdopen(fd, "wb");708}709#else710const int openflags = O_WRONLY|O_CREAT|O_TRUNC;711const int fd = open(dstFileName, openflags, mode);712FILE* f = NULL;713if (fd != -1) {714f = fdopen(fd, "wb");715}716#endif717if (f == NULL) {718DISPLAYLEVEL(1, "zstd: %s: %s\n", dstFileName, strerror(errno));719}720return f;721}722}723724/*! FIO_createDictBuffer() :725* creates a buffer, pointed by `*bufferPtr`,726* loads `filename` content into it, up to DICTSIZE_MAX bytes.727* @return : loaded size728* if fileName==NULL, returns 0 and a NULL pointer729*/730static size_t FIO_createDictBuffer(void** bufferPtr, const char* fileName, FIO_prefs_t* const prefs)731{732FILE* fileHandle;733U64 fileSize;734stat_t statbuf;735736assert(bufferPtr != NULL);737*bufferPtr = NULL;738if (fileName == NULL) return 0;739740DISPLAYLEVEL(4,"Loading %s as dictionary \n", fileName);741742if (!UTIL_stat(fileName, &statbuf)) {743EXM_THROW(31, "Stat failed on dictionary file %s: %s", fileName, strerror(errno));744}745746if (!UTIL_isRegularFileStat(&statbuf)) {747EXM_THROW(32, "Dictionary %s must be a regular file.", fileName);748}749750fileHandle = fopen(fileName, "rb");751752if (fileHandle == NULL) {753EXM_THROW(33, "Couldn't open dictionary %s: %s", fileName, strerror(errno));754}755756fileSize = UTIL_getFileSizeStat(&statbuf);757{758size_t const dictSizeMax = prefs->patchFromMode ? prefs->memLimit : DICTSIZE_MAX;759if (fileSize > dictSizeMax) {760EXM_THROW(34, "Dictionary file %s is too large (> %u bytes)",761fileName, (unsigned)dictSizeMax); /* avoid extreme cases */762}763}764*bufferPtr = malloc((size_t)fileSize);765if (*bufferPtr==NULL) EXM_THROW(34, "%s", strerror(errno));766{ size_t const readSize = fread(*bufferPtr, 1, (size_t)fileSize, fileHandle);767if (readSize != fileSize) {768EXM_THROW(35, "Error reading dictionary file %s : %s",769fileName, strerror(errno));770}771}772fclose(fileHandle);773return (size_t)fileSize;774}775776777778/* FIO_checkFilenameCollisions() :779* Checks for and warns if there are any files that would have the same output path780*/781int FIO_checkFilenameCollisions(const char** filenameTable, unsigned nbFiles) {782const char **filenameTableSorted, *prevElem, *filename;783unsigned u;784785filenameTableSorted = (const char**) malloc(sizeof(char*) * nbFiles);786if (!filenameTableSorted) {787DISPLAY("Unable to malloc new str array, not checking for name collisions\n");788return 1;789}790791for (u = 0; u < nbFiles; ++u) {792filename = strrchr(filenameTable[u], PATH_SEP);793if (filename == NULL) {794filenameTableSorted[u] = filenameTable[u];795} else {796filenameTableSorted[u] = filename+1;797}798}799800qsort((void*)filenameTableSorted, nbFiles, sizeof(char*), UTIL_compareStr);801prevElem = filenameTableSorted[0];802for (u = 1; u < nbFiles; ++u) {803if (strcmp(prevElem, filenameTableSorted[u]) == 0) {804DISPLAY("WARNING: Two files have same filename: %s\n", prevElem);805}806prevElem = filenameTableSorted[u];807}808809free((void*)filenameTableSorted);810return 0;811}812813static const char*814extractFilename(const char* path, char separator)815{816const char* search = strrchr(path, separator);817if (search == NULL) return path;818return search+1;819}820821/* FIO_createFilename_fromOutDir() :822* Takes a source file name and specified output directory, and823* allocates memory for and returns a pointer to final path.824* This function never returns an error (it may abort() in case of pb)825*/826static char*827FIO_createFilename_fromOutDir(const char* path, const char* outDirName, const size_t suffixLen)828{829const char* filenameStart;830char separator;831char* result;832833#if defined(_MSC_VER) || defined(__MINGW32__) || defined (__MSVCRT__) /* windows support */834separator = '\\';835#else836separator = '/';837#endif838839filenameStart = extractFilename(path, separator);840#if defined(_MSC_VER) || defined(__MINGW32__) || defined (__MSVCRT__) /* windows support */841filenameStart = extractFilename(filenameStart, '/'); /* sometimes, '/' separator is also used on Windows (mingw+msys2) */842#endif843844result = (char*) calloc(1, strlen(outDirName) + 1 + strlen(filenameStart) + suffixLen + 1);845if (!result) {846EXM_THROW(30, "zstd: FIO_createFilename_fromOutDir: %s", strerror(errno));847}848849memcpy(result, outDirName, strlen(outDirName));850if (outDirName[strlen(outDirName)-1] == separator) {851memcpy(result + strlen(outDirName), filenameStart, strlen(filenameStart));852} else {853memcpy(result + strlen(outDirName), &separator, 1);854memcpy(result + strlen(outDirName) + 1, filenameStart, strlen(filenameStart));855}856857return result;858}859860/* FIO_highbit64() :861* gives position of highest bit.862* note : only works for v > 0 !863*/864static unsigned FIO_highbit64(unsigned long long v)865{866unsigned count = 0;867assert(v != 0);868v >>= 1;869while (v) { v >>= 1; count++; }870return count;871}872873static void FIO_adjustMemLimitForPatchFromMode(FIO_prefs_t* const prefs,874unsigned long long const dictSize,875unsigned long long const maxSrcFileSize)876{877unsigned long long maxSize = MAX(prefs->memLimit, MAX(dictSize, maxSrcFileSize));878unsigned const maxWindowSize = (1U << ZSTD_WINDOWLOG_MAX);879if (maxSize == UTIL_FILESIZE_UNKNOWN)880EXM_THROW(42, "Using --patch-from with stdin requires --stream-size");881assert(maxSize != UTIL_FILESIZE_UNKNOWN);882if (maxSize > maxWindowSize)883EXM_THROW(42, "Can't handle files larger than %u GB\n", maxWindowSize/(1 GB));884FIO_setMemLimit(prefs, (unsigned)maxSize);885}886887/* FIO_removeMultiFilesWarning() :888* Returns 1 if the console should abort, 0 if console should proceed.889* This function handles logic when processing multiple files with -o, displaying the appropriate warnings/prompts.890*891* If -f is specified, or there is just 1 file, zstd will always proceed as usual.892* If --rm is specified, there will be a prompt asking for user confirmation.893* If -f is specified with --rm, zstd will proceed as usual894* If -q is specified with --rm, zstd will abort pre-emptively895* If neither flag is specified, zstd will prompt the user for confirmation to proceed.896* If --rm is not specified, then zstd will print a warning to the user (which can be silenced with -q).897* However, if the output is stdout, we will always abort rather than displaying the warning prompt.898*/899static int FIO_removeMultiFilesWarning(FIO_ctx_t* const fCtx, const FIO_prefs_t* const prefs, const char* outFileName, int displayLevelCutoff)900{901int error = 0;902if (fCtx->nbFilesTotal > 1 && !prefs->overwrite) {903if (g_display_prefs.displayLevel <= displayLevelCutoff) {904if (prefs->removeSrcFile) {905DISPLAYLEVEL(1, "zstd: Aborting... not deleting files and processing into dst: %s\n", outFileName);906error = 1;907}908} else {909if (!strcmp(outFileName, stdoutmark)) {910DISPLAYLEVEL(2, "zstd: WARNING: all input files will be processed and concatenated into stdout. \n");911} else {912DISPLAYLEVEL(2, "zstd: WARNING: all input files will be processed and concatenated into a single output file: %s \n", outFileName);913}914DISPLAYLEVEL(2, "The concatenated output CANNOT regenerate the original directory tree. \n")915if (prefs->removeSrcFile) {916if (fCtx->hasStdoutOutput) {917DISPLAYLEVEL(1, "Aborting. Use -f if you really want to delete the files and output to stdout\n");918error = 1;919} else {920error = g_display_prefs.displayLevel > displayLevelCutoff && UTIL_requireUserConfirmation("This is a destructive operation. Proceed? (y/n): ", "Aborting...", "yY", fCtx->hasStdinInput);921}922}923}924}925return error;926}927928#ifndef ZSTD_NOCOMPRESS929930/* **********************************************************************931* Compression932************************************************************************/933typedef struct {934FILE* srcFile;935FILE* dstFile;936void* srcBuffer;937size_t srcBufferSize;938void* dstBuffer;939size_t dstBufferSize;940void* dictBuffer;941size_t dictBufferSize;942const char* dictFileName;943ZSTD_CStream* cctx;944} cRess_t;945946/** ZSTD_cycleLog() :947* condition for correct operation : hashLog > 1 */948static U32 ZSTD_cycleLog(U32 hashLog, ZSTD_strategy strat)949{950U32 const btScale = ((U32)strat >= (U32)ZSTD_btlazy2);951assert(hashLog > 1);952return hashLog - btScale;953}954955static void FIO_adjustParamsForPatchFromMode(FIO_prefs_t* const prefs,956ZSTD_compressionParameters* comprParams,957unsigned long long const dictSize,958unsigned long long const maxSrcFileSize,959int cLevel)960{961unsigned const fileWindowLog = FIO_highbit64(maxSrcFileSize) + 1;962ZSTD_compressionParameters const cParams = ZSTD_getCParams(cLevel, (size_t)maxSrcFileSize, (size_t)dictSize);963FIO_adjustMemLimitForPatchFromMode(prefs, dictSize, maxSrcFileSize);964if (fileWindowLog > ZSTD_WINDOWLOG_MAX)965DISPLAYLEVEL(1, "Max window log exceeded by file (compression ratio will suffer)\n");966comprParams->windowLog = MAX(ZSTD_WINDOWLOG_MIN, MIN(ZSTD_WINDOWLOG_MAX, fileWindowLog));967if (fileWindowLog > ZSTD_cycleLog(cParams.chainLog, cParams.strategy)) {968if (!prefs->ldmFlag)969DISPLAYLEVEL(1, "long mode automatically triggered\n");970FIO_setLdmFlag(prefs, 1);971}972if (cParams.strategy >= ZSTD_btopt) {973DISPLAYLEVEL(1, "[Optimal parser notes] Consider the following to improve patch size at the cost of speed:\n");974DISPLAYLEVEL(1, "- Use --single-thread mode in the zstd cli\n");975DISPLAYLEVEL(1, "- Set a larger targetLength (eg. --zstd=targetLength=4096)\n");976DISPLAYLEVEL(1, "- Set a larger chainLog (eg. --zstd=chainLog=%u)\n", ZSTD_CHAINLOG_MAX);977DISPLAYLEVEL(1, "Also consider playing around with searchLog and hashLog\n");978}979}980981static cRess_t FIO_createCResources(FIO_prefs_t* const prefs,982const char* dictFileName, unsigned long long const maxSrcFileSize,983int cLevel, ZSTD_compressionParameters comprParams) {984cRess_t ress;985memset(&ress, 0, sizeof(ress));986987DISPLAYLEVEL(6, "FIO_createCResources \n");988ress.cctx = ZSTD_createCCtx();989if (ress.cctx == NULL)990EXM_THROW(30, "allocation error (%s): can't create ZSTD_CCtx",991strerror(errno));992ress.srcBufferSize = ZSTD_CStreamInSize();993ress.srcBuffer = malloc(ress.srcBufferSize);994ress.dstBufferSize = ZSTD_CStreamOutSize();995996/* need to update memLimit before calling createDictBuffer997* because of memLimit check inside it */998if (prefs->patchFromMode) {999unsigned long long const ssSize = (unsigned long long)prefs->streamSrcSize;1000FIO_adjustParamsForPatchFromMode(prefs, &comprParams, UTIL_getFileSize(dictFileName), ssSize > 0 ? ssSize : maxSrcFileSize, cLevel);1001}1002ress.dstBuffer = malloc(ress.dstBufferSize);1003ress.dictBufferSize = FIO_createDictBuffer(&ress.dictBuffer, dictFileName, prefs); /* works with dictFileName==NULL */1004if (!ress.srcBuffer || !ress.dstBuffer)1005EXM_THROW(31, "allocation error : not enough memory");10061007/* Advanced parameters, including dictionary */1008if (dictFileName && (ress.dictBuffer==NULL))1009EXM_THROW(32, "allocation error : can't create dictBuffer");1010ress.dictFileName = dictFileName;10111012if (prefs->adaptiveMode && !prefs->ldmFlag && !comprParams.windowLog)1013comprParams.windowLog = ADAPT_WINDOWLOG_DEFAULT;10141015CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_contentSizeFlag, prefs->contentSize) ); /* always enable content size when available (note: supposed to be default) */1016CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_dictIDFlag, prefs->dictIDFlag) );1017CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_checksumFlag, prefs->checksumFlag) );1018/* compression level */1019CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_compressionLevel, cLevel) );1020/* max compressed block size */1021CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_targetCBlockSize, (int)prefs->targetCBlockSize) );1022/* source size hint */1023CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_srcSizeHint, (int)prefs->srcSizeHint) );1024/* long distance matching */1025CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_enableLongDistanceMatching, prefs->ldmFlag) );1026CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_ldmHashLog, prefs->ldmHashLog) );1027CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_ldmMinMatch, prefs->ldmMinMatch) );1028if (prefs->ldmBucketSizeLog != FIO_LDM_PARAM_NOTSET) {1029CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_ldmBucketSizeLog, prefs->ldmBucketSizeLog) );1030}1031if (prefs->ldmHashRateLog != FIO_LDM_PARAM_NOTSET) {1032CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_ldmHashRateLog, prefs->ldmHashRateLog) );1033}1034CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_useRowMatchFinder, prefs->useRowMatchFinder));1035/* compression parameters */1036CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_windowLog, (int)comprParams.windowLog) );1037CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_chainLog, (int)comprParams.chainLog) );1038CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_hashLog, (int)comprParams.hashLog) );1039CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_searchLog, (int)comprParams.searchLog) );1040CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_minMatch, (int)comprParams.minMatch) );1041CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_targetLength, (int)comprParams.targetLength) );1042CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_strategy, (int)comprParams.strategy) );1043CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_literalCompressionMode, (int)prefs->literalCompressionMode) );1044CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_enableDedicatedDictSearch, 1) );1045/* multi-threading */1046#ifdef ZSTD_MULTITHREAD1047DISPLAYLEVEL(5,"set nb workers = %u \n", prefs->nbWorkers);1048CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_nbWorkers, prefs->nbWorkers) );1049CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_jobSize, prefs->blockSize) );1050if (prefs->overlapLog != FIO_OVERLAP_LOG_NOTSET) {1051DISPLAYLEVEL(3,"set overlapLog = %u \n", prefs->overlapLog);1052CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_overlapLog, prefs->overlapLog) );1053}1054CHECK( ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_rsyncable, prefs->rsyncable) );1055#endif1056/* dictionary */1057if (prefs->patchFromMode) {1058CHECK( ZSTD_CCtx_refPrefix(ress.cctx, ress.dictBuffer, ress.dictBufferSize) );1059} else {1060CHECK( ZSTD_CCtx_loadDictionary(ress.cctx, ress.dictBuffer, ress.dictBufferSize) );1061}10621063return ress;1064}10651066static void FIO_freeCResources(const cRess_t* const ress)1067{1068free(ress->srcBuffer);1069free(ress->dstBuffer);1070free(ress->dictBuffer);1071ZSTD_freeCStream(ress->cctx); /* never fails */1072}107310741075#ifdef ZSTD_GZCOMPRESS1076static unsigned long long1077FIO_compressGzFrame(const cRess_t* ress, /* buffers & handlers are used, but not changed */1078const char* srcFileName, U64 const srcFileSize,1079int compressionLevel, U64* readsize)1080{1081unsigned long long inFileSize = 0, outFileSize = 0;1082z_stream strm;10831084if (compressionLevel > Z_BEST_COMPRESSION)1085compressionLevel = Z_BEST_COMPRESSION;10861087strm.zalloc = Z_NULL;1088strm.zfree = Z_NULL;1089strm.opaque = Z_NULL;10901091{ int const ret = deflateInit2(&strm, compressionLevel, Z_DEFLATED,109215 /* maxWindowLogSize */ + 16 /* gzip only */,10938, Z_DEFAULT_STRATEGY); /* see http://www.zlib.net/manual.html */1094if (ret != Z_OK) {1095EXM_THROW(71, "zstd: %s: deflateInit2 error %d \n", srcFileName, ret);1096} }10971098strm.next_in = 0;1099strm.avail_in = 0;1100strm.next_out = (Bytef*)ress->dstBuffer;1101strm.avail_out = (uInt)ress->dstBufferSize;11021103while (1) {1104int ret;1105if (strm.avail_in == 0) {1106size_t const inSize = fread(ress->srcBuffer, 1, ress->srcBufferSize, ress->srcFile);1107if (inSize == 0) break;1108inFileSize += inSize;1109strm.next_in = (z_const unsigned char*)ress->srcBuffer;1110strm.avail_in = (uInt)inSize;1111}1112ret = deflate(&strm, Z_NO_FLUSH);1113if (ret != Z_OK)1114EXM_THROW(72, "zstd: %s: deflate error %d \n", srcFileName, ret);1115{ size_t const cSize = ress->dstBufferSize - strm.avail_out;1116if (cSize) {1117if (fwrite(ress->dstBuffer, 1, cSize, ress->dstFile) != cSize)1118EXM_THROW(73, "Write error : cannot write to output file : %s ", strerror(errno));1119outFileSize += cSize;1120strm.next_out = (Bytef*)ress->dstBuffer;1121strm.avail_out = (uInt)ress->dstBufferSize;1122} }1123if (srcFileSize == UTIL_FILESIZE_UNKNOWN) {1124DISPLAYUPDATE(2, "\rRead : %u MB ==> %.2f%% ",1125(unsigned)(inFileSize>>20),1126(double)outFileSize/inFileSize*100)1127} else {1128DISPLAYUPDATE(2, "\rRead : %u / %u MB ==> %.2f%% ",1129(unsigned)(inFileSize>>20), (unsigned)(srcFileSize>>20),1130(double)outFileSize/inFileSize*100);1131} }11321133while (1) {1134int const ret = deflate(&strm, Z_FINISH);1135{ size_t const cSize = ress->dstBufferSize - strm.avail_out;1136if (cSize) {1137if (fwrite(ress->dstBuffer, 1, cSize, ress->dstFile) != cSize)1138EXM_THROW(75, "Write error : %s ", strerror(errno));1139outFileSize += cSize;1140strm.next_out = (Bytef*)ress->dstBuffer;1141strm.avail_out = (uInt)ress->dstBufferSize;1142} }1143if (ret == Z_STREAM_END) break;1144if (ret != Z_BUF_ERROR)1145EXM_THROW(77, "zstd: %s: deflate error %d \n", srcFileName, ret);1146}11471148{ int const ret = deflateEnd(&strm);1149if (ret != Z_OK) {1150EXM_THROW(79, "zstd: %s: deflateEnd error %d \n", srcFileName, ret);1151} }1152*readsize = inFileSize;1153return outFileSize;1154}1155#endif115611571158#ifdef ZSTD_LZMACOMPRESS1159static unsigned long long1160FIO_compressLzmaFrame(cRess_t* ress,1161const char* srcFileName, U64 const srcFileSize,1162int compressionLevel, U64* readsize, int plain_lzma)1163{1164unsigned long long inFileSize = 0, outFileSize = 0;1165lzma_stream strm = LZMA_STREAM_INIT;1166lzma_action action = LZMA_RUN;1167lzma_ret ret;11681169if (compressionLevel < 0) compressionLevel = 0;1170if (compressionLevel > 9) compressionLevel = 9;11711172if (plain_lzma) {1173lzma_options_lzma opt_lzma;1174if (lzma_lzma_preset(&opt_lzma, compressionLevel))1175EXM_THROW(81, "zstd: %s: lzma_lzma_preset error", srcFileName);1176ret = lzma_alone_encoder(&strm, &opt_lzma); /* LZMA */1177if (ret != LZMA_OK)1178EXM_THROW(82, "zstd: %s: lzma_alone_encoder error %d", srcFileName, ret);1179} else {1180ret = lzma_easy_encoder(&strm, compressionLevel, LZMA_CHECK_CRC64); /* XZ */1181if (ret != LZMA_OK)1182EXM_THROW(83, "zstd: %s: lzma_easy_encoder error %d", srcFileName, ret);1183}11841185strm.next_in = 0;1186strm.avail_in = 0;1187strm.next_out = (BYTE*)ress->dstBuffer;1188strm.avail_out = ress->dstBufferSize;11891190while (1) {1191if (strm.avail_in == 0) {1192size_t const inSize = fread(ress->srcBuffer, 1, ress->srcBufferSize, ress->srcFile);1193if (inSize == 0) action = LZMA_FINISH;1194inFileSize += inSize;1195strm.next_in = (BYTE const*)ress->srcBuffer;1196strm.avail_in = inSize;1197}11981199ret = lzma_code(&strm, action);12001201if (ret != LZMA_OK && ret != LZMA_STREAM_END)1202EXM_THROW(84, "zstd: %s: lzma_code encoding error %d", srcFileName, ret);1203{ size_t const compBytes = ress->dstBufferSize - strm.avail_out;1204if (compBytes) {1205if (fwrite(ress->dstBuffer, 1, compBytes, ress->dstFile) != compBytes)1206EXM_THROW(85, "Write error : %s", strerror(errno));1207outFileSize += compBytes;1208strm.next_out = (BYTE*)ress->dstBuffer;1209strm.avail_out = ress->dstBufferSize;1210} }1211if (srcFileSize == UTIL_FILESIZE_UNKNOWN)1212DISPLAYUPDATE(2, "\rRead : %u MB ==> %.2f%%",1213(unsigned)(inFileSize>>20),1214(double)outFileSize/inFileSize*100)1215else1216DISPLAYUPDATE(2, "\rRead : %u / %u MB ==> %.2f%%",1217(unsigned)(inFileSize>>20), (unsigned)(srcFileSize>>20),1218(double)outFileSize/inFileSize*100);1219if (ret == LZMA_STREAM_END) break;1220}12211222lzma_end(&strm);1223*readsize = inFileSize;12241225return outFileSize;1226}1227#endif12281229#ifdef ZSTD_LZ4COMPRESS12301231#if LZ4_VERSION_NUMBER <= 106001232#define LZ4F_blockLinked blockLinked1233#define LZ4F_max64KB max64KB1234#endif12351236static int FIO_LZ4_GetBlockSize_FromBlockId (int id) { return (1 << (8 + (2 * id))); }12371238static unsigned long long1239FIO_compressLz4Frame(cRess_t* ress,1240const char* srcFileName, U64 const srcFileSize,1241int compressionLevel, int checksumFlag,1242U64* readsize)1243{1244const size_t blockSize = FIO_LZ4_GetBlockSize_FromBlockId(LZ4F_max64KB);1245unsigned long long inFileSize = 0, outFileSize = 0;12461247LZ4F_preferences_t prefs;1248LZ4F_compressionContext_t ctx;12491250LZ4F_errorCode_t const errorCode = LZ4F_createCompressionContext(&ctx, LZ4F_VERSION);1251if (LZ4F_isError(errorCode))1252EXM_THROW(31, "zstd: failed to create lz4 compression context");12531254memset(&prefs, 0, sizeof(prefs));12551256assert(blockSize <= ress->srcBufferSize);12571258prefs.autoFlush = 1;1259prefs.compressionLevel = compressionLevel;1260prefs.frameInfo.blockMode = LZ4F_blockLinked;1261prefs.frameInfo.blockSizeID = LZ4F_max64KB;1262prefs.frameInfo.contentChecksumFlag = (contentChecksum_t)checksumFlag;1263#if LZ4_VERSION_NUMBER >= 106001264prefs.frameInfo.contentSize = (srcFileSize==UTIL_FILESIZE_UNKNOWN) ? 0 : srcFileSize;1265#endif1266assert(LZ4F_compressBound(blockSize, &prefs) <= ress->dstBufferSize);12671268{1269size_t readSize;1270size_t headerSize = LZ4F_compressBegin(ctx, ress->dstBuffer, ress->dstBufferSize, &prefs);1271if (LZ4F_isError(headerSize))1272EXM_THROW(33, "File header generation failed : %s",1273LZ4F_getErrorName(headerSize));1274if (fwrite(ress->dstBuffer, 1, headerSize, ress->dstFile) != headerSize)1275EXM_THROW(34, "Write error : %s (cannot write header)", strerror(errno));1276outFileSize += headerSize;12771278/* Read first block */1279readSize = fread(ress->srcBuffer, (size_t)1, (size_t)blockSize, ress->srcFile);1280inFileSize += readSize;12811282/* Main Loop */1283while (readSize>0) {1284size_t const outSize = LZ4F_compressUpdate(ctx,1285ress->dstBuffer, ress->dstBufferSize,1286ress->srcBuffer, readSize, NULL);1287if (LZ4F_isError(outSize))1288EXM_THROW(35, "zstd: %s: lz4 compression failed : %s",1289srcFileName, LZ4F_getErrorName(outSize));1290outFileSize += outSize;1291if (srcFileSize == UTIL_FILESIZE_UNKNOWN) {1292DISPLAYUPDATE(2, "\rRead : %u MB ==> %.2f%%",1293(unsigned)(inFileSize>>20),1294(double)outFileSize/inFileSize*100)1295} else {1296DISPLAYUPDATE(2, "\rRead : %u / %u MB ==> %.2f%%",1297(unsigned)(inFileSize>>20), (unsigned)(srcFileSize>>20),1298(double)outFileSize/inFileSize*100);1299}13001301/* Write Block */1302{ size_t const sizeCheck = fwrite(ress->dstBuffer, 1, outSize, ress->dstFile);1303if (sizeCheck != outSize)1304EXM_THROW(36, "Write error : %s", strerror(errno));1305}13061307/* Read next block */1308readSize = fread(ress->srcBuffer, (size_t)1, (size_t)blockSize, ress->srcFile);1309inFileSize += readSize;1310}1311if (ferror(ress->srcFile)) EXM_THROW(37, "Error reading %s ", srcFileName);13121313/* End of Stream mark */1314headerSize = LZ4F_compressEnd(ctx, ress->dstBuffer, ress->dstBufferSize, NULL);1315if (LZ4F_isError(headerSize))1316EXM_THROW(38, "zstd: %s: lz4 end of file generation failed : %s",1317srcFileName, LZ4F_getErrorName(headerSize));13181319{ size_t const sizeCheck = fwrite(ress->dstBuffer, 1, headerSize, ress->dstFile);1320if (sizeCheck != headerSize)1321EXM_THROW(39, "Write error : %s (cannot write end of stream)",1322strerror(errno));1323}1324outFileSize += headerSize;1325}13261327*readsize = inFileSize;1328LZ4F_freeCompressionContext(ctx);13291330return outFileSize;1331}1332#endif133313341335static unsigned long long1336FIO_compressZstdFrame(FIO_ctx_t* const fCtx,1337FIO_prefs_t* const prefs,1338const cRess_t* ressPtr,1339const char* srcFileName, U64 fileSize,1340int compressionLevel, U64* readsize)1341{1342cRess_t const ress = *ressPtr;1343FILE* const srcFile = ress.srcFile;1344FILE* const dstFile = ress.dstFile;1345U64 compressedfilesize = 0;1346ZSTD_EndDirective directive = ZSTD_e_continue;1347U64 pledgedSrcSize = ZSTD_CONTENTSIZE_UNKNOWN;13481349/* stats */1350ZSTD_frameProgression previous_zfp_update = { 0, 0, 0, 0, 0, 0 };1351ZSTD_frameProgression previous_zfp_correction = { 0, 0, 0, 0, 0, 0 };1352typedef enum { noChange, slower, faster } speedChange_e;1353speedChange_e speedChange = noChange;1354unsigned flushWaiting = 0;1355unsigned inputPresented = 0;1356unsigned inputBlocked = 0;1357unsigned lastJobID = 0;1358UTIL_HumanReadableSize_t const file_hrs = UTIL_makeHumanReadableSize(fileSize);13591360DISPLAYLEVEL(6, "compression using zstd format \n");13611362/* init */1363if (fileSize != UTIL_FILESIZE_UNKNOWN) {1364pledgedSrcSize = fileSize;1365CHECK(ZSTD_CCtx_setPledgedSrcSize(ress.cctx, fileSize));1366} else if (prefs->streamSrcSize > 0) {1367/* unknown source size; use the declared stream size */1368pledgedSrcSize = prefs->streamSrcSize;1369CHECK( ZSTD_CCtx_setPledgedSrcSize(ress.cctx, prefs->streamSrcSize) );1370}13711372{1373int windowLog;1374UTIL_HumanReadableSize_t windowSize;1375CHECK(ZSTD_CCtx_getParameter(ress.cctx, ZSTD_c_windowLog, &windowLog));1376if (windowLog == 0) {1377const ZSTD_compressionParameters cParams = ZSTD_getCParams(compressionLevel, fileSize, 0);1378windowLog = cParams.windowLog;1379}1380windowSize = UTIL_makeHumanReadableSize(MAX(1ULL, MIN(1ULL << windowLog, pledgedSrcSize)));1381DISPLAYLEVEL(4, "Decompression will require %.*f%s of memory\n", windowSize.precision, windowSize.value, windowSize.suffix);1382}1383(void)srcFileName;13841385/* Main compression loop */1386do {1387size_t stillToFlush;1388/* Fill input Buffer */1389size_t const inSize = fread(ress.srcBuffer, (size_t)1, ress.srcBufferSize, srcFile);1390ZSTD_inBuffer inBuff = { ress.srcBuffer, inSize, 0 };1391DISPLAYLEVEL(6, "fread %u bytes from source \n", (unsigned)inSize);1392*readsize += inSize;13931394if ((inSize == 0) || (*readsize == fileSize))1395directive = ZSTD_e_end;13961397stillToFlush = 1;1398while ((inBuff.pos != inBuff.size) /* input buffer must be entirely ingested */1399|| (directive == ZSTD_e_end && stillToFlush != 0) ) {14001401size_t const oldIPos = inBuff.pos;1402ZSTD_outBuffer outBuff = { ress.dstBuffer, ress.dstBufferSize, 0 };1403size_t const toFlushNow = ZSTD_toFlushNow(ress.cctx);1404CHECK_V(stillToFlush, ZSTD_compressStream2(ress.cctx, &outBuff, &inBuff, directive));14051406/* count stats */1407inputPresented++;1408if (oldIPos == inBuff.pos) inputBlocked++; /* input buffer is full and can't take any more : input speed is faster than consumption rate */1409if (!toFlushNow) flushWaiting = 1;14101411/* Write compressed stream */1412DISPLAYLEVEL(6, "ZSTD_compress_generic(end:%u) => input pos(%u)<=(%u)size ; output generated %u bytes \n",1413(unsigned)directive, (unsigned)inBuff.pos, (unsigned)inBuff.size, (unsigned)outBuff.pos);1414if (outBuff.pos) {1415size_t const sizeCheck = fwrite(ress.dstBuffer, 1, outBuff.pos, dstFile);1416if (sizeCheck != outBuff.pos)1417EXM_THROW(25, "Write error : %s (cannot write compressed block)",1418strerror(errno));1419compressedfilesize += outBuff.pos;1420}14211422/* display notification; and adapt compression level */1423if (READY_FOR_UPDATE()) {1424ZSTD_frameProgression const zfp = ZSTD_getFrameProgression(ress.cctx);1425double const cShare = (double)zfp.produced / (double)(zfp.consumed + !zfp.consumed/*avoid div0*/) * 100;1426UTIL_HumanReadableSize_t const buffered_hrs = UTIL_makeHumanReadableSize(zfp.ingested - zfp.consumed);1427UTIL_HumanReadableSize_t const consumed_hrs = UTIL_makeHumanReadableSize(zfp.consumed);1428UTIL_HumanReadableSize_t const produced_hrs = UTIL_makeHumanReadableSize(zfp.produced);14291430/* display progress notifications */1431if (g_display_prefs.displayLevel >= 3) {1432DISPLAYUPDATE(3, "\r(L%i) Buffered :%6.*f%4s - Consumed :%6.*f%4s - Compressed :%6.*f%4s => %.2f%% ",1433compressionLevel,1434buffered_hrs.precision, buffered_hrs.value, buffered_hrs.suffix,1435consumed_hrs.precision, consumed_hrs.value, consumed_hrs.suffix,1436produced_hrs.precision, produced_hrs.value, produced_hrs.suffix,1437cShare );1438} else if (g_display_prefs.displayLevel >= 2 || g_display_prefs.progressSetting == FIO_ps_always) {1439/* Require level 2 or forcibly displayed progress counter for summarized updates */1440DISPLAYLEVEL(1, "\r%79s\r", ""); /* Clear out the current displayed line */1441if (fCtx->nbFilesTotal > 1) {1442size_t srcFileNameSize = strlen(srcFileName);1443/* Ensure that the string we print is roughly the same size each time */1444if (srcFileNameSize > 18) {1445const char* truncatedSrcFileName = srcFileName + srcFileNameSize - 15;1446DISPLAYLEVEL(1, "Compress: %u/%u files. Current: ...%s ",1447fCtx->currFileIdx+1, fCtx->nbFilesTotal, truncatedSrcFileName);1448} else {1449DISPLAYLEVEL(1, "Compress: %u/%u files. Current: %*s ",1450fCtx->currFileIdx+1, fCtx->nbFilesTotal, (int)(18-srcFileNameSize), srcFileName);1451}1452}1453DISPLAYLEVEL(1, "Read:%6.*f%4s ", consumed_hrs.precision, consumed_hrs.value, consumed_hrs.suffix);1454if (fileSize != UTIL_FILESIZE_UNKNOWN)1455DISPLAYLEVEL(2, "/%6.*f%4s", file_hrs.precision, file_hrs.value, file_hrs.suffix);1456DISPLAYLEVEL(1, " ==> %2.f%%", cShare);1457DELAY_NEXT_UPDATE();1458}14591460/* adaptive mode : statistics measurement and speed correction */1461if (prefs->adaptiveMode) {14621463/* check output speed */1464if (zfp.currentJobID > 1) { /* only possible if nbWorkers >= 1 */14651466unsigned long long newlyProduced = zfp.produced - previous_zfp_update.produced;1467unsigned long long newlyFlushed = zfp.flushed - previous_zfp_update.flushed;1468assert(zfp.produced >= previous_zfp_update.produced);1469assert(prefs->nbWorkers >= 1);14701471/* test if compression is blocked1472* either because output is slow and all buffers are full1473* or because input is slow and no job can start while waiting for at least one buffer to be filled.1474* note : exclude starting part, since currentJobID > 1 */1475if ( (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)*/1476&& (zfp.nbActiveWorkers == 0) /* confirmed : no compression ongoing */1477) {1478DISPLAYLEVEL(6, "all buffers full : compression stopped => slow down \n")1479speedChange = slower;1480}14811482previous_zfp_update = zfp;14831484if ( (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) */1485&& (flushWaiting == 0) /* flush speed was never slowed by lack of production, so it's operating at max capacity */1486) {1487DISPLAYLEVEL(6, "compression faster than flush (%llu > %llu), and flushed was never slowed down by lack of production => slow down \n", newlyProduced, newlyFlushed);1488speedChange = slower;1489}1490flushWaiting = 0;1491}14921493/* course correct only if there is at least one new job completed */1494if (zfp.currentJobID > lastJobID) {1495DISPLAYLEVEL(6, "compression level adaptation check \n")14961497/* check input speed */1498if (zfp.currentJobID > (unsigned)(prefs->nbWorkers+1)) { /* warm up period, to fill all workers */1499if (inputBlocked <= 0) {1500DISPLAYLEVEL(6, "input is never blocked => input is slower than ingestion \n");1501speedChange = slower;1502} else if (speedChange == noChange) {1503unsigned long long newlyIngested = zfp.ingested - previous_zfp_correction.ingested;1504unsigned long long newlyConsumed = zfp.consumed - previous_zfp_correction.consumed;1505unsigned long long newlyProduced = zfp.produced - previous_zfp_correction.produced;1506unsigned long long newlyFlushed = zfp.flushed - previous_zfp_correction.flushed;1507previous_zfp_correction = zfp;1508assert(inputPresented > 0);1509DISPLAYLEVEL(6, "input blocked %u/%u(%.2f) - ingested:%u vs %u:consumed - flushed:%u vs %u:produced \n",1510inputBlocked, inputPresented, (double)inputBlocked/inputPresented*100,1511(unsigned)newlyIngested, (unsigned)newlyConsumed,1512(unsigned)newlyFlushed, (unsigned)newlyProduced);1513if ( (inputBlocked > inputPresented / 8) /* input is waiting often, because input buffers is full : compression or output too slow */1514&& (newlyFlushed * 33 / 32 > newlyProduced) /* flush everything that is produced */1515&& (newlyIngested * 33 / 32 > newlyConsumed) /* input speed as fast or faster than compression speed */1516) {1517DISPLAYLEVEL(6, "recommend faster as in(%llu) >= (%llu)comp(%llu) <= out(%llu) \n",1518newlyIngested, newlyConsumed, newlyProduced, newlyFlushed);1519speedChange = faster;1520}1521}1522inputBlocked = 0;1523inputPresented = 0;1524}15251526if (speedChange == slower) {1527DISPLAYLEVEL(6, "slower speed , higher compression \n")1528compressionLevel ++;1529if (compressionLevel > ZSTD_maxCLevel()) compressionLevel = ZSTD_maxCLevel();1530if (compressionLevel > prefs->maxAdaptLevel) compressionLevel = prefs->maxAdaptLevel;1531compressionLevel += (compressionLevel == 0); /* skip 0 */1532ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_compressionLevel, compressionLevel);1533}1534if (speedChange == faster) {1535DISPLAYLEVEL(6, "faster speed , lighter compression \n")1536compressionLevel --;1537if (compressionLevel < prefs->minAdaptLevel) compressionLevel = prefs->minAdaptLevel;1538compressionLevel -= (compressionLevel == 0); /* skip 0 */1539ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_compressionLevel, compressionLevel);1540}1541speedChange = noChange;15421543lastJobID = zfp.currentJobID;1544} /* if (zfp.currentJobID > lastJobID) */1545} /* if (g_adaptiveMode) */1546} /* if (READY_FOR_UPDATE()) */1547} /* while ((inBuff.pos != inBuff.size) */1548} while (directive != ZSTD_e_end);15491550if (ferror(srcFile)) {1551EXM_THROW(26, "Read error : I/O error");1552}1553if (fileSize != UTIL_FILESIZE_UNKNOWN && *readsize != fileSize) {1554EXM_THROW(27, "Read error : Incomplete read : %llu / %llu B",1555(unsigned long long)*readsize, (unsigned long long)fileSize);1556}15571558return compressedfilesize;1559}15601561/*! FIO_compressFilename_internal() :1562* same as FIO_compressFilename_extRess(), with `ress.desFile` already opened.1563* @return : 0 : compression completed correctly,1564* 1 : missing or pb opening srcFileName1565*/1566static int1567FIO_compressFilename_internal(FIO_ctx_t* const fCtx,1568FIO_prefs_t* const prefs,1569cRess_t ress,1570const char* dstFileName, const char* srcFileName,1571int compressionLevel)1572{1573UTIL_time_t const timeStart = UTIL_getTime();1574clock_t const cpuStart = clock();1575U64 readsize = 0;1576U64 compressedfilesize = 0;1577U64 const fileSize = UTIL_getFileSize(srcFileName);1578DISPLAYLEVEL(5, "%s: %llu bytes \n", srcFileName, (unsigned long long)fileSize);15791580/* compression format selection */1581switch (prefs->compressionType) {1582default:1583case FIO_zstdCompression:1584compressedfilesize = FIO_compressZstdFrame(fCtx, prefs, &ress, srcFileName, fileSize, compressionLevel, &readsize);1585break;15861587case FIO_gzipCompression:1588#ifdef ZSTD_GZCOMPRESS1589compressedfilesize = FIO_compressGzFrame(&ress, srcFileName, fileSize, compressionLevel, &readsize);1590#else1591(void)compressionLevel;1592EXM_THROW(20, "zstd: %s: file cannot be compressed as gzip (zstd compiled without ZSTD_GZCOMPRESS) -- ignored \n",1593srcFileName);1594#endif1595break;15961597case FIO_xzCompression:1598case FIO_lzmaCompression:1599#ifdef ZSTD_LZMACOMPRESS1600compressedfilesize = FIO_compressLzmaFrame(&ress, srcFileName, fileSize, compressionLevel, &readsize, prefs->compressionType==FIO_lzmaCompression);1601#else1602(void)compressionLevel;1603EXM_THROW(20, "zstd: %s: file cannot be compressed as xz/lzma (zstd compiled without ZSTD_LZMACOMPRESS) -- ignored \n",1604srcFileName);1605#endif1606break;16071608case FIO_lz4Compression:1609#ifdef ZSTD_LZ4COMPRESS1610compressedfilesize = FIO_compressLz4Frame(&ress, srcFileName, fileSize, compressionLevel, prefs->checksumFlag, &readsize);1611#else1612(void)compressionLevel;1613EXM_THROW(20, "zstd: %s: file cannot be compressed as lz4 (zstd compiled without ZSTD_LZ4COMPRESS) -- ignored \n",1614srcFileName);1615#endif1616break;1617}16181619/* Status */1620fCtx->totalBytesInput += (size_t)readsize;1621fCtx->totalBytesOutput += (size_t)compressedfilesize;1622DISPLAYLEVEL(2, "\r%79s\r", "");1623if (g_display_prefs.displayLevel >= 2 &&1624!fCtx->hasStdoutOutput &&1625(g_display_prefs.displayLevel >= 3 || fCtx->nbFilesTotal <= 1)) {1626UTIL_HumanReadableSize_t hr_isize = UTIL_makeHumanReadableSize((U64) readsize);1627UTIL_HumanReadableSize_t hr_osize = UTIL_makeHumanReadableSize((U64) compressedfilesize);1628if (readsize == 0) {1629DISPLAYLEVEL(2,"%-20s : (%6.*f%4s => %6.*f%4s, %s) \n",1630srcFileName,1631hr_isize.precision, hr_isize.value, hr_isize.suffix,1632hr_osize.precision, hr_osize.value, hr_osize.suffix,1633dstFileName);1634} else {1635DISPLAYLEVEL(2,"%-20s :%6.2f%% (%6.*f%4s => %6.*f%4s, %s) \n",1636srcFileName,1637(double)compressedfilesize / (double)readsize * 100,1638hr_isize.precision, hr_isize.value, hr_isize.suffix,1639hr_osize.precision, hr_osize.value, hr_osize.suffix,1640dstFileName);1641}1642}16431644/* Elapsed Time and CPU Load */1645{ clock_t const cpuEnd = clock();1646double const cpuLoad_s = (double)(cpuEnd - cpuStart) / CLOCKS_PER_SEC;1647U64 const timeLength_ns = UTIL_clockSpanNano(timeStart);1648double const timeLength_s = (double)timeLength_ns / 1000000000;1649double const cpuLoad_pct = (cpuLoad_s / timeLength_s) * 100;1650DISPLAYLEVEL(4, "%-20s : Completed in %.2f sec (cpu load : %.0f%%)\n",1651srcFileName, timeLength_s, cpuLoad_pct);1652}1653return 0;1654}165516561657/*! FIO_compressFilename_dstFile() :1658* open dstFileName, or pass-through if ress.dstFile != NULL,1659* then start compression with FIO_compressFilename_internal().1660* Manages source removal (--rm) and file permissions transfer.1661* note : ress.srcFile must be != NULL,1662* so reach this function through FIO_compressFilename_srcFile().1663* @return : 0 : compression completed correctly,1664* 1 : pb1665*/1666static int FIO_compressFilename_dstFile(FIO_ctx_t* const fCtx,1667FIO_prefs_t* const prefs,1668cRess_t ress,1669const char* dstFileName,1670const char* srcFileName,1671int compressionLevel)1672{1673int closeDstFile = 0;1674int result;1675stat_t statbuf;1676int transferMTime = 0;1677assert(ress.srcFile != NULL);1678if (ress.dstFile == NULL) {1679int dstFilePermissions = DEFAULT_FILE_PERMISSIONS;1680if ( strcmp (srcFileName, stdinmark)1681&& strcmp (dstFileName, stdoutmark)1682&& UTIL_stat(srcFileName, &statbuf)1683&& UTIL_isRegularFileStat(&statbuf) ) {1684dstFilePermissions = statbuf.st_mode;1685transferMTime = 1;1686}16871688closeDstFile = 1;1689DISPLAYLEVEL(6, "FIO_compressFilename_dstFile: opening dst: %s \n", dstFileName);1690ress.dstFile = FIO_openDstFile(fCtx, prefs, srcFileName, dstFileName, dstFilePermissions);1691if (ress.dstFile==NULL) return 1; /* could not open dstFileName */1692/* Must only be added after FIO_openDstFile() succeeds.1693* Otherwise we may delete the destination file if it already exists,1694* and the user presses Ctrl-C when asked if they wish to overwrite.1695*/1696addHandler(dstFileName);1697}16981699result = FIO_compressFilename_internal(fCtx, prefs, ress, dstFileName, srcFileName, compressionLevel);17001701if (closeDstFile) {1702FILE* const dstFile = ress.dstFile;1703ress.dstFile = NULL;17041705clearHandler();17061707DISPLAYLEVEL(6, "FIO_compressFilename_dstFile: closing dst: %s \n", dstFileName);1708if (fclose(dstFile)) { /* error closing dstFile */1709DISPLAYLEVEL(1, "zstd: %s: %s \n", dstFileName, strerror(errno));1710result=1;1711}1712if (transferMTime) {1713UTIL_utime(dstFileName, &statbuf);1714}1715if ( (result != 0) /* operation failure */1716&& strcmp(dstFileName, stdoutmark) /* special case : don't remove() stdout */1717) {1718FIO_removeFile(dstFileName); /* remove compression artefact; note don't do anything special if remove() fails */1719}1720}17211722return result;1723}17241725/* List used to compare file extensions (used with --exclude-compressed flag)1726* Different from the suffixList and should only apply to ZSTD compress operationResult1727*/1728static const char *compressedFileExtensions[] = {1729ZSTD_EXTENSION,1730TZSTD_EXTENSION,1731GZ_EXTENSION,1732TGZ_EXTENSION,1733LZMA_EXTENSION,1734XZ_EXTENSION,1735TXZ_EXTENSION,1736LZ4_EXTENSION,1737TLZ4_EXTENSION,1738NULL1739};17401741/*! FIO_compressFilename_srcFile() :1742* @return : 0 : compression completed correctly,1743* 1 : missing or pb opening srcFileName1744*/1745static int1746FIO_compressFilename_srcFile(FIO_ctx_t* const fCtx,1747FIO_prefs_t* const prefs,1748cRess_t ress,1749const char* dstFileName,1750const char* srcFileName,1751int compressionLevel)1752{1753int result;1754DISPLAYLEVEL(6, "FIO_compressFilename_srcFile: %s \n", srcFileName);17551756/* ensure src is not a directory */1757if (UTIL_isDirectory(srcFileName)) {1758DISPLAYLEVEL(1, "zstd: %s is a directory -- ignored \n", srcFileName);1759return 1;1760}17611762/* ensure src is not the same as dict (if present) */1763if (ress.dictFileName != NULL && UTIL_isSameFile(srcFileName, ress.dictFileName)) {1764DISPLAYLEVEL(1, "zstd: cannot use %s as an input file and dictionary \n", srcFileName);1765return 1;1766}17671768/* Check if "srcFile" is compressed. Only done if --exclude-compressed flag is used1769* YES => ZSTD will skip compression of the file and will return 0.1770* NO => ZSTD will resume with compress operation.1771*/1772if (prefs->excludeCompressedFiles == 1 && UTIL_isCompressedFile(srcFileName, compressedFileExtensions)) {1773DISPLAYLEVEL(4, "File is already compressed : %s \n", srcFileName);1774return 0;1775}17761777ress.srcFile = FIO_openSrcFile(prefs, srcFileName);1778if (ress.srcFile == NULL) return 1; /* srcFile could not be opened */17791780result = FIO_compressFilename_dstFile(fCtx, prefs, ress, dstFileName, srcFileName, compressionLevel);17811782fclose(ress.srcFile);1783ress.srcFile = NULL;1784if ( prefs->removeSrcFile /* --rm */1785&& result == 0 /* success */1786&& strcmp(srcFileName, stdinmark) /* exception : don't erase stdin */1787) {1788/* We must clear the handler, since after this point calling it would1789* delete both the source and destination files.1790*/1791clearHandler();1792if (FIO_removeFile(srcFileName))1793EXM_THROW(1, "zstd: %s: %s", srcFileName, strerror(errno));1794}1795return result;1796}17971798static const char* checked_index(const char* options[], size_t length, size_t index) {1799assert(index < length);1800// Necessary to avoid warnings since -O3 will omit the above `assert`1801(void) length;1802return options[index];1803}18041805#define INDEX(options, index) checked_index((options), sizeof(options) / sizeof(char*), (index))18061807void FIO_displayCompressionParameters(const FIO_prefs_t* prefs) {1808static const char* formatOptions[5] = {ZSTD_EXTENSION, GZ_EXTENSION, XZ_EXTENSION,1809LZMA_EXTENSION, LZ4_EXTENSION};1810static const char* sparseOptions[3] = {" --no-sparse", "", " --sparse"};1811static const char* checkSumOptions[3] = {" --no-check", "", " --check"};1812static const char* rowMatchFinderOptions[3] = {"", " --no-row-match-finder", " --row-match-finder"};1813static const char* compressLiteralsOptions[3] = {"", " --compress-literals", " --no-compress-literals"};18141815assert(g_display_prefs.displayLevel >= 4);18161817DISPLAY("--format=%s", formatOptions[prefs->compressionType]);1818DISPLAY("%s", INDEX(sparseOptions, prefs->sparseFileSupport));1819DISPLAY("%s", prefs->dictIDFlag ? "" : " --no-dictID");1820DISPLAY("%s", INDEX(checkSumOptions, prefs->checksumFlag));1821DISPLAY(" --block-size=%d", prefs->blockSize);1822if (prefs->adaptiveMode)1823DISPLAY(" --adapt=min=%d,max=%d", prefs->minAdaptLevel, prefs->maxAdaptLevel);1824DISPLAY("%s", INDEX(rowMatchFinderOptions, prefs->useRowMatchFinder));1825DISPLAY("%s", prefs->rsyncable ? " --rsyncable" : "");1826if (prefs->streamSrcSize)1827DISPLAY(" --stream-size=%u", (unsigned) prefs->streamSrcSize);1828if (prefs->srcSizeHint)1829DISPLAY(" --size-hint=%d", prefs->srcSizeHint);1830if (prefs->targetCBlockSize)1831DISPLAY(" --target-compressed-block-size=%u", (unsigned) prefs->targetCBlockSize);1832DISPLAY("%s", INDEX(compressLiteralsOptions, prefs->literalCompressionMode));1833DISPLAY(" --memory=%u", prefs->memLimit ? prefs->memLimit : 128 MB);1834DISPLAY(" --threads=%d", prefs->nbWorkers);1835DISPLAY("%s", prefs->excludeCompressedFiles ? " --exclude-compressed" : "");1836DISPLAY(" --%scontent-size", prefs->contentSize ? "" : "no-");1837DISPLAY("\n");1838}18391840#undef INDEX18411842int FIO_compressFilename(FIO_ctx_t* const fCtx, FIO_prefs_t* const prefs, const char* dstFileName,1843const char* srcFileName, const char* dictFileName,1844int compressionLevel, ZSTD_compressionParameters comprParams)1845{1846cRess_t const ress = FIO_createCResources(prefs, dictFileName, UTIL_getFileSize(srcFileName), compressionLevel, comprParams);1847int const result = FIO_compressFilename_srcFile(fCtx, prefs, ress, dstFileName, srcFileName, compressionLevel);18481849#define DISPLAY_LEVEL_DEFAULT 218501851FIO_freeCResources(&ress);1852return result;1853}18541855/* FIO_determineCompressedName() :1856* create a destination filename for compressed srcFileName.1857* @return a pointer to it.1858* This function never returns an error (it may abort() in case of pb)1859*/1860static const char*1861FIO_determineCompressedName(const char* srcFileName, const char* outDirName, const char* suffix)1862{1863static size_t dfnbCapacity = 0;1864static char* dstFileNameBuffer = NULL; /* using static allocation : this function cannot be multi-threaded */1865char* outDirFilename = NULL;1866size_t sfnSize = strlen(srcFileName);1867size_t const srcSuffixLen = strlen(suffix);1868if (outDirName) {1869outDirFilename = FIO_createFilename_fromOutDir(srcFileName, outDirName, srcSuffixLen);1870sfnSize = strlen(outDirFilename);1871assert(outDirFilename != NULL);1872}18731874if (dfnbCapacity <= sfnSize+srcSuffixLen+1) {1875/* resize buffer for dstName */1876free(dstFileNameBuffer);1877dfnbCapacity = sfnSize + srcSuffixLen + 30;1878dstFileNameBuffer = (char*)malloc(dfnbCapacity);1879if (!dstFileNameBuffer) {1880EXM_THROW(30, "zstd: %s", strerror(errno));1881}1882}1883assert(dstFileNameBuffer != NULL);18841885if (outDirFilename) {1886memcpy(dstFileNameBuffer, outDirFilename, sfnSize);1887free(outDirFilename);1888} else {1889memcpy(dstFileNameBuffer, srcFileName, sfnSize);1890}1891memcpy(dstFileNameBuffer+sfnSize, suffix, srcSuffixLen+1 /* Include terminating null */);1892return dstFileNameBuffer;1893}18941895static unsigned long long FIO_getLargestFileSize(const char** inFileNames, unsigned nbFiles)1896{1897size_t i;1898unsigned long long fileSize, maxFileSize = 0;1899for (i = 0; i < nbFiles; i++) {1900fileSize = UTIL_getFileSize(inFileNames[i]);1901maxFileSize = fileSize > maxFileSize ? fileSize : maxFileSize;1902}1903return maxFileSize;1904}19051906/* FIO_compressMultipleFilenames() :1907* compress nbFiles files1908* into either one destination (outFileName),1909* or into one file each (outFileName == NULL, but suffix != NULL),1910* or into a destination folder (specified with -O)1911*/1912int FIO_compressMultipleFilenames(FIO_ctx_t* const fCtx,1913FIO_prefs_t* const prefs,1914const char** inFileNamesTable,1915const char* outMirroredRootDirName,1916const char* outDirName,1917const char* outFileName, const char* suffix,1918const char* dictFileName, int compressionLevel,1919ZSTD_compressionParameters comprParams)1920{1921int status;1922int error = 0;1923cRess_t ress = FIO_createCResources(prefs, dictFileName,1924FIO_getLargestFileSize(inFileNamesTable, (unsigned)fCtx->nbFilesTotal),1925compressionLevel, comprParams);19261927/* init */1928assert(outFileName != NULL || suffix != NULL);1929if (outFileName != NULL) { /* output into a single destination (stdout typically) */1930if (FIO_removeMultiFilesWarning(fCtx, prefs, outFileName, 1 /* displayLevelCutoff */)) {1931FIO_freeCResources(&ress);1932return 1;1933}1934ress.dstFile = FIO_openDstFile(fCtx, prefs, NULL, outFileName, DEFAULT_FILE_PERMISSIONS);1935if (ress.dstFile == NULL) { /* could not open outFileName */1936error = 1;1937} else {1938for (; fCtx->currFileIdx < fCtx->nbFilesTotal; ++fCtx->currFileIdx) {1939status = FIO_compressFilename_srcFile(fCtx, prefs, ress, outFileName, inFileNamesTable[fCtx->currFileIdx], compressionLevel);1940if (!status) fCtx->nbFilesProcessed++;1941error |= status;1942}1943if (fclose(ress.dstFile))1944EXM_THROW(29, "Write error (%s) : cannot properly close %s",1945strerror(errno), outFileName);1946ress.dstFile = NULL;1947}1948} else {1949if (outMirroredRootDirName)1950UTIL_mirrorSourceFilesDirectories(inFileNamesTable, (unsigned)fCtx->nbFilesTotal, outMirroredRootDirName);19511952for (; fCtx->currFileIdx < fCtx->nbFilesTotal; ++fCtx->currFileIdx) {1953const char* const srcFileName = inFileNamesTable[fCtx->currFileIdx];1954const char* dstFileName = NULL;1955if (outMirroredRootDirName) {1956char* validMirroredDirName = UTIL_createMirroredDestDirName(srcFileName, outMirroredRootDirName);1957if (validMirroredDirName) {1958dstFileName = FIO_determineCompressedName(srcFileName, validMirroredDirName, suffix);1959free(validMirroredDirName);1960} else {1961DISPLAYLEVEL(2, "zstd: --output-dir-mirror cannot compress '%s' into '%s' \n", srcFileName, outMirroredRootDirName);1962error=1;1963continue;1964}1965} else {1966dstFileName = FIO_determineCompressedName(srcFileName, outDirName, suffix); /* cannot fail */1967}1968status = FIO_compressFilename_srcFile(fCtx, prefs, ress, dstFileName, srcFileName, compressionLevel);1969if (!status) fCtx->nbFilesProcessed++;1970error |= status;1971}19721973if (outDirName)1974FIO_checkFilenameCollisions(inFileNamesTable , (unsigned)fCtx->nbFilesTotal);1975}19761977if (fCtx->nbFilesProcessed >= 1 && fCtx->nbFilesTotal > 1 && fCtx->totalBytesInput != 0) {1978UTIL_HumanReadableSize_t hr_isize = UTIL_makeHumanReadableSize((U64) fCtx->totalBytesInput);1979UTIL_HumanReadableSize_t hr_osize = UTIL_makeHumanReadableSize((U64) fCtx->totalBytesOutput);19801981DISPLAYLEVEL(2, "\r%79s\r", "");1982DISPLAYLEVEL(2, "%3d files compressed :%.2f%% (%6.*f%4s => %6.*f%4s)\n",1983fCtx->nbFilesProcessed,1984(double)fCtx->totalBytesOutput/((double)fCtx->totalBytesInput)*100,1985hr_isize.precision, hr_isize.value, hr_isize.suffix,1986hr_osize.precision, hr_osize.value, hr_osize.suffix);1987}19881989FIO_freeCResources(&ress);1990return error;1991}19921993#endif /* #ifndef ZSTD_NOCOMPRESS */1994199519961997#ifndef ZSTD_NODECOMPRESS19981999/* **************************************************************************2000* Decompression2001***************************************************************************/2002typedef struct {2003void* srcBuffer;2004size_t srcBufferSize;2005size_t srcBufferLoaded;2006void* dstBuffer;2007size_t dstBufferSize;2008ZSTD_DStream* dctx;2009FILE* dstFile;2010} dRess_t;20112012static dRess_t FIO_createDResources(FIO_prefs_t* const prefs, const char* dictFileName)2013{2014dRess_t ress;2015memset(&ress, 0, sizeof(ress));20162017if (prefs->patchFromMode)2018FIO_adjustMemLimitForPatchFromMode(prefs, UTIL_getFileSize(dictFileName), 0 /* just use the dict size */);20192020/* Allocation */2021ress.dctx = ZSTD_createDStream();2022if (ress.dctx==NULL)2023EXM_THROW(60, "Error: %s : can't create ZSTD_DStream", strerror(errno));2024CHECK( ZSTD_DCtx_setMaxWindowSize(ress.dctx, prefs->memLimit) );2025CHECK( ZSTD_DCtx_setParameter(ress.dctx, ZSTD_d_forceIgnoreChecksum, !prefs->checksumFlag));20262027ress.srcBufferSize = ZSTD_DStreamInSize();2028ress.srcBuffer = malloc(ress.srcBufferSize);2029ress.dstBufferSize = ZSTD_DStreamOutSize();2030ress.dstBuffer = malloc(ress.dstBufferSize);2031if (!ress.srcBuffer || !ress.dstBuffer)2032EXM_THROW(61, "Allocation error : not enough memory");20332034/* dictionary */2035{ void* dictBuffer;2036size_t const dictBufferSize = FIO_createDictBuffer(&dictBuffer, dictFileName, prefs);2037CHECK( ZSTD_initDStream_usingDict(ress.dctx, dictBuffer, dictBufferSize) );2038free(dictBuffer);2039}20402041return ress;2042}20432044static void FIO_freeDResources(dRess_t ress)2045{2046CHECK( ZSTD_freeDStream(ress.dctx) );2047free(ress.srcBuffer);2048free(ress.dstBuffer);2049}205020512052/** FIO_fwriteSparse() :2053* @return : storedSkips,2054* argument for next call to FIO_fwriteSparse() or FIO_fwriteSparseEnd() */2055static unsigned2056FIO_fwriteSparse(FILE* file,2057const void* buffer, size_t bufferSize,2058const FIO_prefs_t* const prefs,2059unsigned storedSkips)2060{2061const size_t* const bufferT = (const size_t*)buffer; /* Buffer is supposed malloc'ed, hence aligned on size_t */2062size_t bufferSizeT = bufferSize / sizeof(size_t);2063const size_t* const bufferTEnd = bufferT + bufferSizeT;2064const size_t* ptrT = bufferT;2065static const size_t segmentSizeT = (32 KB) / sizeof(size_t); /* check every 32 KB */20662067if (prefs->testMode) return 0; /* do not output anything in test mode */20682069if (!prefs->sparseFileSupport) { /* normal write */2070size_t const sizeCheck = fwrite(buffer, 1, bufferSize, file);2071if (sizeCheck != bufferSize)2072EXM_THROW(70, "Write error : cannot write decoded block : %s",2073strerror(errno));2074return 0;2075}20762077/* avoid int overflow */2078if (storedSkips > 1 GB) {2079if (LONG_SEEK(file, 1 GB, SEEK_CUR) != 0)2080EXM_THROW(91, "1 GB skip error (sparse file support)");2081storedSkips -= 1 GB;2082}20832084while (ptrT < bufferTEnd) {2085size_t nb0T;20862087/* adjust last segment if < 32 KB */2088size_t seg0SizeT = segmentSizeT;2089if (seg0SizeT > bufferSizeT) seg0SizeT = bufferSizeT;2090bufferSizeT -= seg0SizeT;20912092/* count leading zeroes */2093for (nb0T=0; (nb0T < seg0SizeT) && (ptrT[nb0T] == 0); nb0T++) ;2094storedSkips += (unsigned)(nb0T * sizeof(size_t));20952096if (nb0T != seg0SizeT) { /* not all 0s */2097size_t const nbNon0ST = seg0SizeT - nb0T;2098/* skip leading zeros */2099if (LONG_SEEK(file, storedSkips, SEEK_CUR) != 0)2100EXM_THROW(92, "Sparse skip error ; try --no-sparse");2101storedSkips = 0;2102/* write the rest */2103if (fwrite(ptrT + nb0T, sizeof(size_t), nbNon0ST, file) != nbNon0ST)2104EXM_THROW(93, "Write error : cannot write decoded block : %s",2105strerror(errno));2106}2107ptrT += seg0SizeT;2108}21092110{ static size_t const maskT = sizeof(size_t)-1;2111if (bufferSize & maskT) {2112/* size not multiple of sizeof(size_t) : implies end of block */2113const char* const restStart = (const char*)bufferTEnd;2114const char* restPtr = restStart;2115const char* const restEnd = (const char*)buffer + bufferSize;2116assert(restEnd > restStart && restEnd < restStart + sizeof(size_t));2117for ( ; (restPtr < restEnd) && (*restPtr == 0); restPtr++) ;2118storedSkips += (unsigned) (restPtr - restStart);2119if (restPtr != restEnd) {2120/* not all remaining bytes are 0 */2121size_t const restSize = (size_t)(restEnd - restPtr);2122if (LONG_SEEK(file, storedSkips, SEEK_CUR) != 0)2123EXM_THROW(92, "Sparse skip error ; try --no-sparse");2124if (fwrite(restPtr, 1, restSize, file) != restSize)2125EXM_THROW(95, "Write error : cannot write end of decoded block : %s",2126strerror(errno));2127storedSkips = 0;2128} } }21292130return storedSkips;2131}21322133static void2134FIO_fwriteSparseEnd(const FIO_prefs_t* const prefs, FILE* file, unsigned storedSkips)2135{2136if (prefs->testMode) assert(storedSkips == 0);2137if (storedSkips>0) {2138assert(prefs->sparseFileSupport > 0); /* storedSkips>0 implies sparse support is enabled */2139(void)prefs; /* assert can be disabled, in which case prefs becomes unused */2140if (LONG_SEEK(file, storedSkips-1, SEEK_CUR) != 0)2141EXM_THROW(69, "Final skip error (sparse file support)");2142/* last zero must be explicitly written,2143* so that skipped ones get implicitly translated as zero by FS */2144{ const char lastZeroByte[1] = { 0 };2145if (fwrite(lastZeroByte, 1, 1, file) != 1)2146EXM_THROW(69, "Write error : cannot write last zero : %s", strerror(errno));2147} }2148}214921502151/** FIO_passThrough() : just copy input into output, for compatibility with gzip -df mode2152@return : 0 (no error) */2153static int FIO_passThrough(const FIO_prefs_t* const prefs,2154FILE* foutput, FILE* finput,2155void* buffer, size_t bufferSize,2156size_t alreadyLoaded)2157{2158size_t const blockSize = MIN(64 KB, bufferSize);2159size_t readFromInput;2160unsigned storedSkips = 0;21612162/* assumption : ress->srcBufferLoaded bytes already loaded and stored within buffer */2163{ size_t const sizeCheck = fwrite(buffer, 1, alreadyLoaded, foutput);2164if (sizeCheck != alreadyLoaded) {2165DISPLAYLEVEL(1, "Pass-through write error : %s\n", strerror(errno));2166return 1;2167} }21682169do {2170readFromInput = fread(buffer, 1, blockSize, finput);2171storedSkips = FIO_fwriteSparse(foutput, buffer, readFromInput, prefs, storedSkips);2172} while (readFromInput == blockSize);2173if (ferror(finput)) {2174DISPLAYLEVEL(1, "Pass-through read error : %s\n", strerror(errno));2175return 1;2176}2177assert(feof(finput));21782179FIO_fwriteSparseEnd(prefs, foutput, storedSkips);2180return 0;2181}21822183/* FIO_zstdErrorHelp() :2184* detailed error message when requested window size is too large */2185static void2186FIO_zstdErrorHelp(const FIO_prefs_t* const prefs,2187const dRess_t* ress,2188size_t err, const char* srcFileName)2189{2190ZSTD_frameHeader header;21912192/* Help message only for one specific error */2193if (ZSTD_getErrorCode(err) != ZSTD_error_frameParameter_windowTooLarge)2194return;21952196/* Try to decode the frame header */2197err = ZSTD_getFrameHeader(&header, ress->srcBuffer, ress->srcBufferLoaded);2198if (err == 0) {2199unsigned long long const windowSize = header.windowSize;2200unsigned const windowLog = FIO_highbit64(windowSize) + ((windowSize & (windowSize - 1)) != 0);2201assert(prefs->memLimit > 0);2202DISPLAYLEVEL(1, "%s : Window size larger than maximum : %llu > %u \n",2203srcFileName, windowSize, prefs->memLimit);2204if (windowLog <= ZSTD_WINDOWLOG_MAX) {2205unsigned const windowMB = (unsigned)((windowSize >> 20) + ((windowSize & ((1 MB) - 1)) != 0));2206assert(windowSize < (U64)(1ULL << 52)); /* ensure now overflow for windowMB */2207DISPLAYLEVEL(1, "%s : Use --long=%u or --memory=%uMB \n",2208srcFileName, windowLog, windowMB);2209return;2210} }2211DISPLAYLEVEL(1, "%s : Window log larger than ZSTD_WINDOWLOG_MAX=%u; not supported \n",2212srcFileName, ZSTD_WINDOWLOG_MAX);2213}22142215/** FIO_decompressFrame() :2216* @return : size of decoded zstd frame, or an error code2217*/2218#define FIO_ERROR_FRAME_DECODING ((unsigned long long)(-2))2219static unsigned long long2220FIO_decompressZstdFrame(FIO_ctx_t* const fCtx, dRess_t* ress, FILE* finput,2221const FIO_prefs_t* const prefs,2222const char* srcFileName,2223U64 alreadyDecoded) /* for multi-frames streams */2224{2225U64 frameSize = 0;2226U32 storedSkips = 0;22272228/* display last 20 characters only */2229{ size_t const srcFileLength = strlen(srcFileName);2230if (srcFileLength>20) srcFileName += srcFileLength-20;2231}22322233ZSTD_DCtx_reset(ress->dctx, ZSTD_reset_session_only);22342235/* Header loading : ensures ZSTD_getFrameHeader() will succeed */2236{ size_t const toDecode = ZSTD_FRAMEHEADERSIZE_MAX;2237if (ress->srcBufferLoaded < toDecode) {2238size_t const toRead = toDecode - ress->srcBufferLoaded;2239void* const startPosition = (char*)ress->srcBuffer + ress->srcBufferLoaded;2240ress->srcBufferLoaded += fread(startPosition, 1, toRead, finput);2241} }22422243/* Main decompression Loop */2244while (1) {2245ZSTD_inBuffer inBuff = { ress->srcBuffer, ress->srcBufferLoaded, 0 };2246ZSTD_outBuffer outBuff= { ress->dstBuffer, ress->dstBufferSize, 0 };2247size_t const readSizeHint = ZSTD_decompressStream(ress->dctx, &outBuff, &inBuff);2248const int displayLevel = (g_display_prefs.progressSetting == FIO_ps_always) ? 1 : 2;2249UTIL_HumanReadableSize_t const hrs = UTIL_makeHumanReadableSize(alreadyDecoded+frameSize);2250if (ZSTD_isError(readSizeHint)) {2251DISPLAYLEVEL(1, "%s : Decoding error (36) : %s \n",2252srcFileName, ZSTD_getErrorName(readSizeHint));2253FIO_zstdErrorHelp(prefs, ress, readSizeHint, srcFileName);2254return FIO_ERROR_FRAME_DECODING;2255}22562257/* Write block */2258storedSkips = FIO_fwriteSparse(ress->dstFile, ress->dstBuffer, outBuff.pos, prefs, storedSkips);2259frameSize += outBuff.pos;2260if (fCtx->nbFilesTotal > 1) {2261size_t srcFileNameSize = strlen(srcFileName);2262if (srcFileNameSize > 18) {2263const char* truncatedSrcFileName = srcFileName + srcFileNameSize - 15;2264DISPLAYUPDATE(displayLevel, "\rDecompress: %2u/%2u files. Current: ...%s : %.*f%s... ",2265fCtx->currFileIdx+1, fCtx->nbFilesTotal, truncatedSrcFileName, hrs.precision, hrs.value, hrs.suffix);2266} else {2267DISPLAYUPDATE(displayLevel, "\rDecompress: %2u/%2u files. Current: %s : %.*f%s... ",2268fCtx->currFileIdx+1, fCtx->nbFilesTotal, srcFileName, hrs.precision, hrs.value, hrs.suffix);2269}2270} else {2271DISPLAYUPDATE(displayLevel, "\r%-20.20s : %.*f%s... ",2272srcFileName, hrs.precision, hrs.value, hrs.suffix);2273}22742275if (inBuff.pos > 0) {2276memmove(ress->srcBuffer, (char*)ress->srcBuffer + inBuff.pos, inBuff.size - inBuff.pos);2277ress->srcBufferLoaded -= inBuff.pos;2278}22792280if (readSizeHint == 0) break; /* end of frame */22812282/* Fill input buffer */2283{ size_t const toDecode = MIN(readSizeHint, ress->srcBufferSize); /* support large skippable frames */2284if (ress->srcBufferLoaded < toDecode) {2285size_t const toRead = toDecode - ress->srcBufferLoaded; /* > 0 */2286void* const startPosition = (char*)ress->srcBuffer + ress->srcBufferLoaded;2287size_t const readSize = fread(startPosition, 1, toRead, finput);2288if (readSize==0) {2289DISPLAYLEVEL(1, "%s : Read error (39) : premature end \n",2290srcFileName);2291return FIO_ERROR_FRAME_DECODING;2292}2293ress->srcBufferLoaded += readSize;2294} } }22952296FIO_fwriteSparseEnd(prefs, ress->dstFile, storedSkips);22972298return frameSize;2299}230023012302#ifdef ZSTD_GZDECOMPRESS2303static unsigned long long2304FIO_decompressGzFrame(dRess_t* ress, FILE* srcFile,2305const FIO_prefs_t* const prefs,2306const char* srcFileName)2307{2308unsigned long long outFileSize = 0;2309z_stream strm;2310int flush = Z_NO_FLUSH;2311int decodingError = 0;2312unsigned storedSkips = 0;23132314strm.zalloc = Z_NULL;2315strm.zfree = Z_NULL;2316strm.opaque = Z_NULL;2317strm.next_in = 0;2318strm.avail_in = 0;2319/* see http://www.zlib.net/manual.html */2320if (inflateInit2(&strm, 15 /* maxWindowLogSize */ + 16 /* gzip only */) != Z_OK)2321return FIO_ERROR_FRAME_DECODING;23222323strm.next_out = (Bytef*)ress->dstBuffer;2324strm.avail_out = (uInt)ress->dstBufferSize;2325strm.avail_in = (uInt)ress->srcBufferLoaded;2326strm.next_in = (z_const unsigned char*)ress->srcBuffer;23272328for ( ; ; ) {2329int ret;2330if (strm.avail_in == 0) {2331ress->srcBufferLoaded = fread(ress->srcBuffer, 1, ress->srcBufferSize, srcFile);2332if (ress->srcBufferLoaded == 0) flush = Z_FINISH;2333strm.next_in = (z_const unsigned char*)ress->srcBuffer;2334strm.avail_in = (uInt)ress->srcBufferLoaded;2335}2336ret = inflate(&strm, flush);2337if (ret == Z_BUF_ERROR) {2338DISPLAYLEVEL(1, "zstd: %s: premature gz end \n", srcFileName);2339decodingError = 1; break;2340}2341if (ret != Z_OK && ret != Z_STREAM_END) {2342DISPLAYLEVEL(1, "zstd: %s: inflate error %d \n", srcFileName, ret);2343decodingError = 1; break;2344}2345{ size_t const decompBytes = ress->dstBufferSize - strm.avail_out;2346if (decompBytes) {2347storedSkips = FIO_fwriteSparse(ress->dstFile, ress->dstBuffer, decompBytes, prefs, storedSkips);2348outFileSize += decompBytes;2349strm.next_out = (Bytef*)ress->dstBuffer;2350strm.avail_out = (uInt)ress->dstBufferSize;2351}2352}2353if (ret == Z_STREAM_END) break;2354}23552356if (strm.avail_in > 0)2357memmove(ress->srcBuffer, strm.next_in, strm.avail_in);2358ress->srcBufferLoaded = strm.avail_in;2359if ( (inflateEnd(&strm) != Z_OK) /* release resources ; error detected */2360&& (decodingError==0) ) {2361DISPLAYLEVEL(1, "zstd: %s: inflateEnd error \n", srcFileName);2362decodingError = 1;2363}2364FIO_fwriteSparseEnd(prefs, ress->dstFile, storedSkips);2365return decodingError ? FIO_ERROR_FRAME_DECODING : outFileSize;2366}2367#endif236823692370#ifdef ZSTD_LZMADECOMPRESS2371static unsigned long long2372FIO_decompressLzmaFrame(dRess_t* ress, FILE* srcFile,2373const FIO_prefs_t* const prefs,2374const char* srcFileName, int plain_lzma)2375{2376unsigned long long outFileSize = 0;2377lzma_stream strm = LZMA_STREAM_INIT;2378lzma_action action = LZMA_RUN;2379lzma_ret initRet;2380int decodingError = 0;2381unsigned storedSkips = 0;23822383strm.next_in = 0;2384strm.avail_in = 0;2385if (plain_lzma) {2386initRet = lzma_alone_decoder(&strm, UINT64_MAX); /* LZMA */2387} else {2388initRet = lzma_stream_decoder(&strm, UINT64_MAX, 0); /* XZ */2389}23902391if (initRet != LZMA_OK) {2392DISPLAYLEVEL(1, "zstd: %s: %s error %d \n",2393plain_lzma ? "lzma_alone_decoder" : "lzma_stream_decoder",2394srcFileName, initRet);2395return FIO_ERROR_FRAME_DECODING;2396}23972398strm.next_out = (BYTE*)ress->dstBuffer;2399strm.avail_out = ress->dstBufferSize;2400strm.next_in = (BYTE const*)ress->srcBuffer;2401strm.avail_in = ress->srcBufferLoaded;24022403for ( ; ; ) {2404lzma_ret ret;2405if (strm.avail_in == 0) {2406ress->srcBufferLoaded = fread(ress->srcBuffer, 1, ress->srcBufferSize, srcFile);2407if (ress->srcBufferLoaded == 0) action = LZMA_FINISH;2408strm.next_in = (BYTE const*)ress->srcBuffer;2409strm.avail_in = ress->srcBufferLoaded;2410}2411ret = lzma_code(&strm, action);24122413if (ret == LZMA_BUF_ERROR) {2414DISPLAYLEVEL(1, "zstd: %s: premature lzma end \n", srcFileName);2415decodingError = 1; break;2416}2417if (ret != LZMA_OK && ret != LZMA_STREAM_END) {2418DISPLAYLEVEL(1, "zstd: %s: lzma_code decoding error %d \n",2419srcFileName, ret);2420decodingError = 1; break;2421}2422{ size_t const decompBytes = ress->dstBufferSize - strm.avail_out;2423if (decompBytes) {2424storedSkips = FIO_fwriteSparse(ress->dstFile, ress->dstBuffer, decompBytes, prefs, storedSkips);2425outFileSize += decompBytes;2426strm.next_out = (BYTE*)ress->dstBuffer;2427strm.avail_out = ress->dstBufferSize;2428} }2429if (ret == LZMA_STREAM_END) break;2430}24312432if (strm.avail_in > 0)2433memmove(ress->srcBuffer, strm.next_in, strm.avail_in);2434ress->srcBufferLoaded = strm.avail_in;2435lzma_end(&strm);2436FIO_fwriteSparseEnd(prefs, ress->dstFile, storedSkips);2437return decodingError ? FIO_ERROR_FRAME_DECODING : outFileSize;2438}2439#endif24402441#ifdef ZSTD_LZ4DECOMPRESS2442static unsigned long long2443FIO_decompressLz4Frame(dRess_t* ress, FILE* srcFile,2444const FIO_prefs_t* const prefs,2445const char* srcFileName)2446{2447unsigned long long filesize = 0;2448LZ4F_errorCode_t nextToLoad;2449LZ4F_decompressionContext_t dCtx;2450LZ4F_errorCode_t const errorCode = LZ4F_createDecompressionContext(&dCtx, LZ4F_VERSION);2451int decodingError = 0;2452unsigned storedSkips = 0;24532454if (LZ4F_isError(errorCode)) {2455DISPLAYLEVEL(1, "zstd: failed to create lz4 decompression context \n");2456return FIO_ERROR_FRAME_DECODING;2457}24582459/* Init feed with magic number (already consumed from FILE* sFile) */2460{ size_t inSize = 4;2461size_t outSize= 0;2462MEM_writeLE32(ress->srcBuffer, LZ4_MAGICNUMBER);2463nextToLoad = LZ4F_decompress(dCtx, ress->dstBuffer, &outSize, ress->srcBuffer, &inSize, NULL);2464if (LZ4F_isError(nextToLoad)) {2465DISPLAYLEVEL(1, "zstd: %s: lz4 header error : %s \n",2466srcFileName, LZ4F_getErrorName(nextToLoad));2467LZ4F_freeDecompressionContext(dCtx);2468return FIO_ERROR_FRAME_DECODING;2469} }24702471/* Main Loop */2472for (;nextToLoad;) {2473size_t readSize;2474size_t pos = 0;2475size_t decodedBytes = ress->dstBufferSize;24762477/* Read input */2478if (nextToLoad > ress->srcBufferSize) nextToLoad = ress->srcBufferSize;2479readSize = fread(ress->srcBuffer, 1, nextToLoad, srcFile);2480if (!readSize) break; /* reached end of file or stream */24812482while ((pos < readSize) || (decodedBytes == ress->dstBufferSize)) { /* still to read, or still to flush */2483/* Decode Input (at least partially) */2484size_t remaining = readSize - pos;2485decodedBytes = ress->dstBufferSize;2486nextToLoad = LZ4F_decompress(dCtx, ress->dstBuffer, &decodedBytes, (char*)(ress->srcBuffer)+pos, &remaining, NULL);2487if (LZ4F_isError(nextToLoad)) {2488DISPLAYLEVEL(1, "zstd: %s: lz4 decompression error : %s \n",2489srcFileName, LZ4F_getErrorName(nextToLoad));2490decodingError = 1; nextToLoad = 0; break;2491}2492pos += remaining;24932494/* Write Block */2495if (decodedBytes) {2496UTIL_HumanReadableSize_t hrs;2497storedSkips = FIO_fwriteSparse(ress->dstFile, ress->dstBuffer, decodedBytes, prefs, storedSkips);2498filesize += decodedBytes;2499hrs = UTIL_makeHumanReadableSize(filesize);2500DISPLAYUPDATE(2, "\rDecompressed : %.*f%s ", hrs.precision, hrs.value, hrs.suffix);2501}25022503if (!nextToLoad) break;2504}2505}2506/* can be out because readSize == 0, which could be an fread() error */2507if (ferror(srcFile)) {2508DISPLAYLEVEL(1, "zstd: %s: read error \n", srcFileName);2509decodingError=1;2510}25112512if (nextToLoad!=0) {2513DISPLAYLEVEL(1, "zstd: %s: unfinished lz4 stream \n", srcFileName);2514decodingError=1;2515}25162517LZ4F_freeDecompressionContext(dCtx);2518ress->srcBufferLoaded = 0; /* LZ4F will reach exact frame boundary */2519FIO_fwriteSparseEnd(prefs, ress->dstFile, storedSkips);25202521return decodingError ? FIO_ERROR_FRAME_DECODING : filesize;2522}2523#endif2524252525262527/** FIO_decompressFrames() :2528* Find and decode frames inside srcFile2529* srcFile presumed opened and valid2530* @return : 0 : OK2531* 1 : error2532*/2533static int FIO_decompressFrames(FIO_ctx_t* const fCtx,2534dRess_t ress, FILE* srcFile,2535const FIO_prefs_t* const prefs,2536const char* dstFileName, const char* srcFileName)2537{2538unsigned readSomething = 0;2539unsigned long long filesize = 0;2540assert(srcFile != NULL);25412542/* for each frame */2543for ( ; ; ) {2544/* check magic number -> version */2545size_t const toRead = 4;2546const BYTE* const buf = (const BYTE*)ress.srcBuffer;2547if (ress.srcBufferLoaded < toRead) /* load up to 4 bytes for header */2548ress.srcBufferLoaded += fread((char*)ress.srcBuffer + ress.srcBufferLoaded,2549(size_t)1, toRead - ress.srcBufferLoaded, srcFile);2550if (ress.srcBufferLoaded==0) {2551if (readSomething==0) { /* srcFile is empty (which is invalid) */2552DISPLAYLEVEL(1, "zstd: %s: unexpected end of file \n", srcFileName);2553return 1;2554} /* else, just reached frame boundary */2555break; /* no more input */2556}2557readSomething = 1; /* there is at least 1 byte in srcFile */2558if (ress.srcBufferLoaded < toRead) {2559DISPLAYLEVEL(1, "zstd: %s: unknown header \n", srcFileName);2560return 1;2561}2562if (ZSTD_isFrame(buf, ress.srcBufferLoaded)) {2563unsigned long long const frameSize = FIO_decompressZstdFrame(fCtx, &ress, srcFile, prefs, srcFileName, filesize);2564if (frameSize == FIO_ERROR_FRAME_DECODING) return 1;2565filesize += frameSize;2566} else if (buf[0] == 31 && buf[1] == 139) { /* gz magic number */2567#ifdef ZSTD_GZDECOMPRESS2568unsigned long long const frameSize = FIO_decompressGzFrame(&ress, srcFile, prefs, srcFileName);2569if (frameSize == FIO_ERROR_FRAME_DECODING) return 1;2570filesize += frameSize;2571#else2572DISPLAYLEVEL(1, "zstd: %s: gzip file cannot be uncompressed (zstd compiled without HAVE_ZLIB) -- ignored \n", srcFileName);2573return 1;2574#endif2575} else if ((buf[0] == 0xFD && buf[1] == 0x37) /* xz magic number */2576|| (buf[0] == 0x5D && buf[1] == 0x00)) { /* lzma header (no magic number) */2577#ifdef ZSTD_LZMADECOMPRESS2578unsigned long long const frameSize = FIO_decompressLzmaFrame(&ress, srcFile, prefs, srcFileName, buf[0] != 0xFD);2579if (frameSize == FIO_ERROR_FRAME_DECODING) return 1;2580filesize += frameSize;2581#else2582DISPLAYLEVEL(1, "zstd: %s: xz/lzma file cannot be uncompressed (zstd compiled without HAVE_LZMA) -- ignored \n", srcFileName);2583return 1;2584#endif2585} else if (MEM_readLE32(buf) == LZ4_MAGICNUMBER) {2586#ifdef ZSTD_LZ4DECOMPRESS2587unsigned long long const frameSize = FIO_decompressLz4Frame(&ress, srcFile, prefs, srcFileName);2588if (frameSize == FIO_ERROR_FRAME_DECODING) return 1;2589filesize += frameSize;2590#else2591DISPLAYLEVEL(1, "zstd: %s: lz4 file cannot be uncompressed (zstd compiled without HAVE_LZ4) -- ignored \n", srcFileName);2592return 1;2593#endif2594} else if ((prefs->overwrite) && !strcmp (dstFileName, stdoutmark)) { /* pass-through mode */2595return FIO_passThrough(prefs,2596ress.dstFile, srcFile,2597ress.srcBuffer, ress.srcBufferSize,2598ress.srcBufferLoaded);2599} else {2600DISPLAYLEVEL(1, "zstd: %s: unsupported format \n", srcFileName);2601return 1;2602} } /* for each frame */26032604/* Final Status */2605fCtx->totalBytesOutput += (size_t)filesize;2606DISPLAYLEVEL(2, "\r%79s\r", "");2607/* No status message in pipe mode (stdin - stdout) or multi-files mode */2608if ((g_display_prefs.displayLevel >= 2 && fCtx->nbFilesTotal <= 1) ||2609g_display_prefs.displayLevel >= 3 ||2610g_display_prefs.progressSetting == FIO_ps_always) {2611DISPLAYLEVEL(1, "\r%-20s: %llu bytes \n", srcFileName, filesize);2612}26132614return 0;2615}26162617/** FIO_decompressDstFile() :2618open `dstFileName`,2619or path-through if ress.dstFile is already != 0,2620then start decompression process (FIO_decompressFrames()).2621@return : 0 : OK26221 : operation aborted2623*/2624static int FIO_decompressDstFile(FIO_ctx_t* const fCtx,2625FIO_prefs_t* const prefs,2626dRess_t ress, FILE* srcFile,2627const char* dstFileName, const char* srcFileName)2628{2629int result;2630stat_t statbuf;2631int releaseDstFile = 0;2632int transferMTime = 0;26332634if ((ress.dstFile == NULL) && (prefs->testMode==0)) {2635int dstFilePermissions = DEFAULT_FILE_PERMISSIONS;2636if ( strcmp(srcFileName, stdinmark) /* special case : don't transfer permissions from stdin */2637&& strcmp(dstFileName, stdoutmark)2638&& UTIL_stat(srcFileName, &statbuf)2639&& UTIL_isRegularFileStat(&statbuf) ) {2640dstFilePermissions = statbuf.st_mode;2641transferMTime = 1;2642}26432644releaseDstFile = 1;26452646ress.dstFile = FIO_openDstFile(fCtx, prefs, srcFileName, dstFileName, dstFilePermissions);2647if (ress.dstFile==NULL) return 1;26482649/* Must only be added after FIO_openDstFile() succeeds.2650* Otherwise we may delete the destination file if it already exists,2651* and the user presses Ctrl-C when asked if they wish to overwrite.2652*/2653addHandler(dstFileName);2654}26552656result = FIO_decompressFrames(fCtx, ress, srcFile, prefs, dstFileName, srcFileName);26572658if (releaseDstFile) {2659FILE* const dstFile = ress.dstFile;2660clearHandler();2661ress.dstFile = NULL;2662if (fclose(dstFile)) {2663DISPLAYLEVEL(1, "zstd: %s: %s \n", dstFileName, strerror(errno));2664result = 1;2665}26662667if (transferMTime) {2668UTIL_utime(dstFileName, &statbuf);2669}26702671if ( (result != 0) /* operation failure */2672&& strcmp(dstFileName, stdoutmark) /* special case : don't remove() stdout */2673) {2674FIO_removeFile(dstFileName); /* remove decompression artefact; note: don't do anything special if remove() fails */2675}2676}26772678return result;2679}268026812682/** FIO_decompressSrcFile() :2683Open `srcFileName`, transfer control to decompressDstFile()2684@return : 0 : OK26851 : error2686*/2687static int FIO_decompressSrcFile(FIO_ctx_t* const fCtx, FIO_prefs_t* const prefs, dRess_t ress, const char* dstFileName, const char* srcFileName)2688{2689FILE* srcFile;2690int result;26912692if (UTIL_isDirectory(srcFileName)) {2693DISPLAYLEVEL(1, "zstd: %s is a directory -- ignored \n", srcFileName);2694return 1;2695}26962697srcFile = FIO_openSrcFile(prefs, srcFileName);2698if (srcFile==NULL) return 1;2699ress.srcBufferLoaded = 0;27002701result = FIO_decompressDstFile(fCtx, prefs, ress, srcFile, dstFileName, srcFileName);27022703/* Close file */2704if (fclose(srcFile)) {2705DISPLAYLEVEL(1, "zstd: %s: %s \n", srcFileName, strerror(errno)); /* error should not happen */2706return 1;2707}2708if ( prefs->removeSrcFile /* --rm */2709&& (result==0) /* decompression successful */2710&& strcmp(srcFileName, stdinmark) ) /* not stdin */ {2711/* We must clear the handler, since after this point calling it would2712* delete both the source and destination files.2713*/2714clearHandler();2715if (FIO_removeFile(srcFileName)) {2716/* failed to remove src file */2717DISPLAYLEVEL(1, "zstd: %s: %s \n", srcFileName, strerror(errno));2718return 1;2719} }2720return result;2721}2722272327242725int FIO_decompressFilename(FIO_ctx_t* const fCtx, FIO_prefs_t* const prefs,2726const char* dstFileName, const char* srcFileName,2727const char* dictFileName)2728{2729dRess_t const ress = FIO_createDResources(prefs, dictFileName);27302731int const decodingError = FIO_decompressSrcFile(fCtx, prefs, ress, dstFileName, srcFileName);27322733FIO_freeDResources(ress);2734return decodingError;2735}27362737static const char *suffixList[] = {2738ZSTD_EXTENSION,2739TZSTD_EXTENSION,2740#ifndef ZSTD_NODECOMPRESS2741ZSTD_ALT_EXTENSION,2742#endif2743#ifdef ZSTD_GZDECOMPRESS2744GZ_EXTENSION,2745TGZ_EXTENSION,2746#endif2747#ifdef ZSTD_LZMADECOMPRESS2748LZMA_EXTENSION,2749XZ_EXTENSION,2750TXZ_EXTENSION,2751#endif2752#ifdef ZSTD_LZ4DECOMPRESS2753LZ4_EXTENSION,2754TLZ4_EXTENSION,2755#endif2756NULL2757};27582759static const char *suffixListStr =2760ZSTD_EXTENSION "/" TZSTD_EXTENSION2761#ifdef ZSTD_GZDECOMPRESS2762"/" GZ_EXTENSION "/" TGZ_EXTENSION2763#endif2764#ifdef ZSTD_LZMADECOMPRESS2765"/" LZMA_EXTENSION "/" XZ_EXTENSION "/" TXZ_EXTENSION2766#endif2767#ifdef ZSTD_LZ4DECOMPRESS2768"/" LZ4_EXTENSION "/" TLZ4_EXTENSION2769#endif2770;27712772/* FIO_determineDstName() :2773* create a destination filename from a srcFileName.2774* @return a pointer to it.2775* @return == NULL if there is an error */2776static const char*2777FIO_determineDstName(const char* srcFileName, const char* outDirName)2778{2779static size_t dfnbCapacity = 0;2780static char* dstFileNameBuffer = NULL; /* using static allocation : this function cannot be multi-threaded */2781size_t dstFileNameEndPos;2782char* outDirFilename = NULL;2783const char* dstSuffix = "";2784size_t dstSuffixLen = 0;27852786size_t sfnSize = strlen(srcFileName);27872788size_t srcSuffixLen;2789const char* const srcSuffix = strrchr(srcFileName, '.');2790if (srcSuffix == NULL) {2791DISPLAYLEVEL(1,2792"zstd: %s: unknown suffix (%s expected). "2793"Can't derive the output file name. "2794"Specify it with -o dstFileName. Ignoring.\n",2795srcFileName, suffixListStr);2796return NULL;2797}2798srcSuffixLen = strlen(srcSuffix);27992800{2801const char** matchedSuffixPtr;2802for (matchedSuffixPtr = suffixList; *matchedSuffixPtr != NULL; matchedSuffixPtr++) {2803if (!strcmp(*matchedSuffixPtr, srcSuffix)) {2804break;2805}2806}28072808/* check suffix is authorized */2809if (sfnSize <= srcSuffixLen || *matchedSuffixPtr == NULL) {2810DISPLAYLEVEL(1,2811"zstd: %s: unknown suffix (%s expected). "2812"Can't derive the output file name. "2813"Specify it with -o dstFileName. Ignoring.\n",2814srcFileName, suffixListStr);2815return NULL;2816}28172818if ((*matchedSuffixPtr)[1] == 't') {2819dstSuffix = ".tar";2820dstSuffixLen = strlen(dstSuffix);2821}2822}28232824if (outDirName) {2825outDirFilename = FIO_createFilename_fromOutDir(srcFileName, outDirName, 0);2826sfnSize = strlen(outDirFilename);2827assert(outDirFilename != NULL);2828}28292830if (dfnbCapacity+srcSuffixLen <= sfnSize+1+dstSuffixLen) {2831/* allocate enough space to write dstFilename into it */2832free(dstFileNameBuffer);2833dfnbCapacity = sfnSize + 20;2834dstFileNameBuffer = (char*)malloc(dfnbCapacity);2835if (dstFileNameBuffer==NULL)2836EXM_THROW(74, "%s : not enough memory for dstFileName",2837strerror(errno));2838}28392840/* return dst name == src name truncated from suffix */2841assert(dstFileNameBuffer != NULL);2842dstFileNameEndPos = sfnSize - srcSuffixLen;2843if (outDirFilename) {2844memcpy(dstFileNameBuffer, outDirFilename, dstFileNameEndPos);2845free(outDirFilename);2846} else {2847memcpy(dstFileNameBuffer, srcFileName, dstFileNameEndPos);2848}28492850/* The short tar extensions tzst, tgz, txz and tlz4 files should have "tar"2851* extension on decompression. Also writes terminating null. */2852strcpy(dstFileNameBuffer + dstFileNameEndPos, dstSuffix);2853return dstFileNameBuffer;28542855/* note : dstFileNameBuffer memory is not going to be free */2856}28572858int2859FIO_decompressMultipleFilenames(FIO_ctx_t* const fCtx,2860FIO_prefs_t* const prefs,2861const char** srcNamesTable,2862const char* outMirroredRootDirName,2863const char* outDirName, const char* outFileName,2864const char* dictFileName)2865{2866int status;2867int error = 0;2868dRess_t ress = FIO_createDResources(prefs, dictFileName);28692870if (outFileName) {2871if (FIO_removeMultiFilesWarning(fCtx, prefs, outFileName, 1 /* displayLevelCutoff */)) {2872FIO_freeDResources(ress);2873return 1;2874}2875if (!prefs->testMode) {2876ress.dstFile = FIO_openDstFile(fCtx, prefs, NULL, outFileName, DEFAULT_FILE_PERMISSIONS);2877if (ress.dstFile == 0) EXM_THROW(19, "cannot open %s", outFileName);2878}2879for (; fCtx->currFileIdx < fCtx->nbFilesTotal; fCtx->currFileIdx++) {2880status = FIO_decompressSrcFile(fCtx, prefs, ress, outFileName, srcNamesTable[fCtx->currFileIdx]);2881if (!status) fCtx->nbFilesProcessed++;2882error |= status;2883}2884if ((!prefs->testMode) && (fclose(ress.dstFile)))2885EXM_THROW(72, "Write error : %s : cannot properly close output file",2886strerror(errno));2887} else {2888if (outMirroredRootDirName)2889UTIL_mirrorSourceFilesDirectories(srcNamesTable, (unsigned)fCtx->nbFilesTotal, outMirroredRootDirName);28902891for (; fCtx->currFileIdx < fCtx->nbFilesTotal; fCtx->currFileIdx++) { /* create dstFileName */2892const char* const srcFileName = srcNamesTable[fCtx->currFileIdx];2893const char* dstFileName = NULL;2894if (outMirroredRootDirName) {2895char* validMirroredDirName = UTIL_createMirroredDestDirName(srcFileName, outMirroredRootDirName);2896if (validMirroredDirName) {2897dstFileName = FIO_determineDstName(srcFileName, validMirroredDirName);2898free(validMirroredDirName);2899} else {2900DISPLAYLEVEL(2, "zstd: --output-dir-mirror cannot decompress '%s' into '%s'\n", srcFileName, outMirroredRootDirName);2901}2902} else {2903dstFileName = FIO_determineDstName(srcFileName, outDirName);2904}2905if (dstFileName == NULL) { error=1; continue; }2906status = FIO_decompressSrcFile(fCtx, prefs, ress, dstFileName, srcFileName);2907if (!status) fCtx->nbFilesProcessed++;2908error |= status;2909}2910if (outDirName)2911FIO_checkFilenameCollisions(srcNamesTable , (unsigned)fCtx->nbFilesTotal);2912}29132914if (fCtx->nbFilesProcessed >= 1 && fCtx->nbFilesTotal > 1 && fCtx->totalBytesOutput != 0)2915DISPLAYLEVEL(2, "%d files decompressed : %6zu bytes total \n", fCtx->nbFilesProcessed, fCtx->totalBytesOutput);29162917FIO_freeDResources(ress);2918return error;2919}29202921/* **************************************************************************2922* .zst file info (--list command)2923***************************************************************************/29242925typedef struct {2926U64 decompressedSize;2927U64 compressedSize;2928U64 windowSize;2929int numActualFrames;2930int numSkippableFrames;2931int decompUnavailable;2932int usesCheck;2933U32 nbFiles;2934} fileInfo_t;29352936typedef enum {2937info_success=0,2938info_frame_error=1,2939info_not_zstd=2,2940info_file_error=3,2941info_truncated_input=4,2942} InfoError;29432944#define ERROR_IF(c,n,...) { \2945if (c) { \2946DISPLAYLEVEL(1, __VA_ARGS__); \2947DISPLAYLEVEL(1, " \n"); \2948return n; \2949} \2950}29512952static InfoError2953FIO_analyzeFrames(fileInfo_t* info, FILE* const srcFile)2954{2955/* begin analyzing frame */2956for ( ; ; ) {2957BYTE headerBuffer[ZSTD_FRAMEHEADERSIZE_MAX];2958size_t const numBytesRead = fread(headerBuffer, 1, sizeof(headerBuffer), srcFile);2959if (numBytesRead < ZSTD_FRAMEHEADERSIZE_MIN(ZSTD_f_zstd1)) {2960if ( feof(srcFile)2961&& (numBytesRead == 0)2962&& (info->compressedSize > 0)2963&& (info->compressedSize != UTIL_FILESIZE_UNKNOWN) ) {2964unsigned long long file_position = (unsigned long long) LONG_TELL(srcFile);2965unsigned long long file_size = (unsigned long long) info->compressedSize;2966ERROR_IF(file_position != file_size, info_truncated_input,2967"Error: seeked to position %llu, which is beyond file size of %llu\n",2968file_position,2969file_size);2970break; /* correct end of file => success */2971}2972ERROR_IF(feof(srcFile), info_not_zstd, "Error: reached end of file with incomplete frame");2973ERROR_IF(1, info_frame_error, "Error: did not reach end of file but ran out of frames");2974}2975{ U32 const magicNumber = MEM_readLE32(headerBuffer);2976/* Zstandard frame */2977if (magicNumber == ZSTD_MAGICNUMBER) {2978ZSTD_frameHeader header;2979U64 const frameContentSize = ZSTD_getFrameContentSize(headerBuffer, numBytesRead);2980if ( frameContentSize == ZSTD_CONTENTSIZE_ERROR2981|| frameContentSize == ZSTD_CONTENTSIZE_UNKNOWN ) {2982info->decompUnavailable = 1;2983} else {2984info->decompressedSize += frameContentSize;2985}2986ERROR_IF(ZSTD_getFrameHeader(&header, headerBuffer, numBytesRead) != 0,2987info_frame_error, "Error: could not decode frame header");2988info->windowSize = header.windowSize;2989/* move to the end of the frame header */2990{ size_t const headerSize = ZSTD_frameHeaderSize(headerBuffer, numBytesRead);2991ERROR_IF(ZSTD_isError(headerSize), info_frame_error, "Error: could not determine frame header size");2992ERROR_IF(fseek(srcFile, ((long)headerSize)-((long)numBytesRead), SEEK_CUR) != 0,2993info_frame_error, "Error: could not move to end of frame header");2994}29952996/* skip all blocks in the frame */2997{ int lastBlock = 0;2998do {2999BYTE blockHeaderBuffer[3];3000ERROR_IF(fread(blockHeaderBuffer, 1, 3, srcFile) != 3,3001info_frame_error, "Error while reading block header");3002{ U32 const blockHeader = MEM_readLE24(blockHeaderBuffer);3003U32 const blockTypeID = (blockHeader >> 1) & 3;3004U32 const isRLE = (blockTypeID == 1);3005U32 const isWrongBlock = (blockTypeID == 3);3006long const blockSize = isRLE ? 1 : (long)(blockHeader >> 3);3007ERROR_IF(isWrongBlock, info_frame_error, "Error: unsupported block type");3008lastBlock = blockHeader & 1;3009ERROR_IF(fseek(srcFile, blockSize, SEEK_CUR) != 0,3010info_frame_error, "Error: could not skip to end of block");3011}3012} while (lastBlock != 1);3013}30143015/* check if checksum is used */3016{ BYTE const frameHeaderDescriptor = headerBuffer[4];3017int const contentChecksumFlag = (frameHeaderDescriptor & (1 << 2)) >> 2;3018if (contentChecksumFlag) {3019info->usesCheck = 1;3020ERROR_IF(fseek(srcFile, 4, SEEK_CUR) != 0,3021info_frame_error, "Error: could not skip past checksum");3022} }3023info->numActualFrames++;3024}3025/* Skippable frame */3026else if ((magicNumber & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) {3027U32 const frameSize = MEM_readLE32(headerBuffer + 4);3028long const seek = (long)(8 + frameSize - numBytesRead);3029ERROR_IF(LONG_SEEK(srcFile, seek, SEEK_CUR) != 0,3030info_frame_error, "Error: could not find end of skippable frame");3031info->numSkippableFrames++;3032}3033/* unknown content */3034else {3035return info_not_zstd;3036}3037} /* magic number analysis */3038} /* end analyzing frames */3039return info_success;3040}304130423043static InfoError3044getFileInfo_fileConfirmed(fileInfo_t* info, const char* inFileName)3045{3046InfoError status;3047FILE* const srcFile = FIO_openSrcFile(NULL, inFileName);3048ERROR_IF(srcFile == NULL, info_file_error, "Error: could not open source file %s", inFileName);30493050info->compressedSize = UTIL_getFileSize(inFileName);3051status = FIO_analyzeFrames(info, srcFile);30523053fclose(srcFile);3054info->nbFiles = 1;3055return status;3056}305730583059/** getFileInfo() :3060* Reads information from file, stores in *info3061* @return : InfoError status3062*/3063static InfoError3064getFileInfo(fileInfo_t* info, const char* srcFileName)3065{3066ERROR_IF(!UTIL_isRegularFile(srcFileName),3067info_file_error, "Error : %s is not a file", srcFileName);3068return getFileInfo_fileConfirmed(info, srcFileName);3069}307030713072static void3073displayInfo(const char* inFileName, const fileInfo_t* info, int displayLevel)3074{3075UTIL_HumanReadableSize_t const window_hrs = UTIL_makeHumanReadableSize(info->windowSize);3076UTIL_HumanReadableSize_t const compressed_hrs = UTIL_makeHumanReadableSize(info->compressedSize);3077UTIL_HumanReadableSize_t const decompressed_hrs = UTIL_makeHumanReadableSize(info->decompressedSize);3078double const ratio = (info->compressedSize == 0) ? 0 : ((double)info->decompressedSize)/(double)info->compressedSize;3079const char* const checkString = (info->usesCheck ? "XXH64" : "None");3080if (displayLevel <= 2) {3081if (!info->decompUnavailable) {3082DISPLAYOUT("%6d %5d %6.*f%4s %8.*f%4s %5.3f %5s %s\n",3083info->numSkippableFrames + info->numActualFrames,3084info->numSkippableFrames,3085compressed_hrs.precision, compressed_hrs.value, compressed_hrs.suffix,3086decompressed_hrs.precision, decompressed_hrs.value, decompressed_hrs.suffix,3087ratio, checkString, inFileName);3088} else {3089DISPLAYOUT("%6d %5d %6.*f%4s %5s %s\n",3090info->numSkippableFrames + info->numActualFrames,3091info->numSkippableFrames,3092compressed_hrs.precision, compressed_hrs.value, compressed_hrs.suffix,3093checkString, inFileName);3094}3095} else {3096DISPLAYOUT("%s \n", inFileName);3097DISPLAYOUT("# Zstandard Frames: %d\n", info->numActualFrames);3098if (info->numSkippableFrames)3099DISPLAYOUT("# Skippable Frames: %d\n", info->numSkippableFrames);3100DISPLAYOUT("Window Size: %.*f%s (%llu B)\n",3101window_hrs.precision, window_hrs.value, window_hrs.suffix,3102(unsigned long long)info->windowSize);3103DISPLAYOUT("Compressed Size: %.*f%s (%llu B)\n",3104compressed_hrs.precision, compressed_hrs.value, compressed_hrs.suffix,3105(unsigned long long)info->compressedSize);3106if (!info->decompUnavailable) {3107DISPLAYOUT("Decompressed Size: %.*f%s (%llu B)\n",3108decompressed_hrs.precision, decompressed_hrs.value, decompressed_hrs.suffix,3109(unsigned long long)info->decompressedSize);3110DISPLAYOUT("Ratio: %.4f\n", ratio);3111}3112DISPLAYOUT("Check: %s\n", checkString);3113DISPLAYOUT("\n");3114}3115}31163117static fileInfo_t FIO_addFInfo(fileInfo_t fi1, fileInfo_t fi2)3118{3119fileInfo_t total;3120memset(&total, 0, sizeof(total));3121total.numActualFrames = fi1.numActualFrames + fi2.numActualFrames;3122total.numSkippableFrames = fi1.numSkippableFrames + fi2.numSkippableFrames;3123total.compressedSize = fi1.compressedSize + fi2.compressedSize;3124total.decompressedSize = fi1.decompressedSize + fi2.decompressedSize;3125total.decompUnavailable = fi1.decompUnavailable | fi2.decompUnavailable;3126total.usesCheck = fi1.usesCheck & fi2.usesCheck;3127total.nbFiles = fi1.nbFiles + fi2.nbFiles;3128return total;3129}31303131static int3132FIO_listFile(fileInfo_t* total, const char* inFileName, int displayLevel)3133{3134fileInfo_t info;3135memset(&info, 0, sizeof(info));3136{ InfoError const error = getFileInfo(&info, inFileName);3137switch (error) {3138case info_frame_error:3139/* display error, but provide output */3140DISPLAYLEVEL(1, "Error while parsing \"%s\" \n", inFileName);3141break;3142case info_not_zstd:3143DISPLAYOUT("File \"%s\" not compressed by zstd \n", inFileName);3144if (displayLevel > 2) DISPLAYOUT("\n");3145return 1;3146case info_file_error:3147/* error occurred while opening the file */3148if (displayLevel > 2) DISPLAYOUT("\n");3149return 1;3150case info_truncated_input:3151DISPLAYOUT("File \"%s\" is truncated \n", inFileName);3152if (displayLevel > 2) DISPLAYOUT("\n");3153return 1;3154case info_success:3155default:3156break;3157}31583159displayInfo(inFileName, &info, displayLevel);3160*total = FIO_addFInfo(*total, info);3161assert(error == info_success || error == info_frame_error);3162return (int)error;3163}3164}31653166int FIO_listMultipleFiles(unsigned numFiles, const char** filenameTable, int displayLevel)3167{3168/* ensure no specified input is stdin (needs fseek() capability) */3169{ unsigned u;3170for (u=0; u<numFiles;u++) {3171ERROR_IF(!strcmp (filenameTable[u], stdinmark),31721, "zstd: --list does not support reading from standard input");3173} }31743175if (numFiles == 0) {3176if (!IS_CONSOLE(stdin)) {3177DISPLAYLEVEL(1, "zstd: --list does not support reading from standard input \n");3178}3179DISPLAYLEVEL(1, "No files given \n");3180return 1;3181}31823183if (displayLevel <= 2) {3184DISPLAYOUT("Frames Skips Compressed Uncompressed Ratio Check Filename\n");3185}3186{ int error = 0;3187fileInfo_t total;3188memset(&total, 0, sizeof(total));3189total.usesCheck = 1;3190/* --list each file, and check for any error */3191{ unsigned u;3192for (u=0; u<numFiles;u++) {3193error |= FIO_listFile(&total, filenameTable[u], displayLevel);3194} }3195if (numFiles > 1 && displayLevel <= 2) { /* display total */3196UTIL_HumanReadableSize_t const compressed_hrs = UTIL_makeHumanReadableSize(total.compressedSize);3197UTIL_HumanReadableSize_t const decompressed_hrs = UTIL_makeHumanReadableSize(total.decompressedSize);3198double const ratio = (total.compressedSize == 0) ? 0 : ((double)total.decompressedSize)/(double)total.compressedSize;3199const char* const checkString = (total.usesCheck ? "XXH64" : "");3200DISPLAYOUT("----------------------------------------------------------------- \n");3201if (total.decompUnavailable) {3202DISPLAYOUT("%6d %5d %6.*f%4s %5s %u files\n",3203total.numSkippableFrames + total.numActualFrames,3204total.numSkippableFrames,3205compressed_hrs.precision, compressed_hrs.value, compressed_hrs.suffix,3206checkString, (unsigned)total.nbFiles);3207} else {3208DISPLAYOUT("%6d %5d %6.*f%4s %8.*f%4s %5.3f %5s %u files\n",3209total.numSkippableFrames + total.numActualFrames,3210total.numSkippableFrames,3211compressed_hrs.precision, compressed_hrs.value, compressed_hrs.suffix,3212decompressed_hrs.precision, decompressed_hrs.value, decompressed_hrs.suffix,3213ratio, checkString, (unsigned)total.nbFiles);3214} }3215return error;3216}3217}321832193220#endif /* #ifndef ZSTD_NODECOMPRESS */322132223223