]> git.ipfire.org Git - thirdparty/zstd.git/commitdiff
added : -m : decompress multiple files
authorYann Collet <yann.collet.73@gmail.com>
Thu, 17 Dec 2015 19:30:14 +0000 (20:30 +0100)
committerYann Collet <yann.collet.73@gmail.com>
Thu, 17 Dec 2015 19:30:14 +0000 (20:30 +0100)
Makefile
NEWS
lib/zstd.h
programs/Makefile
programs/fileio.c
programs/fileio.h
programs/playTests.sh
programs/zstd.1
programs/zstdcli.c

index abec33f8671d8ca6f3cc22e4a53f33056accacc3..a741034ad8959ea2c3b1bf54d531f2d7bf3f72fa 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -32,7 +32,7 @@
 # ################################################################
 
 # Version number
-export VERSION := 0.4.4
+export VERSION := 0.4.5
 
 PRGDIR  = programs
 ZSTDDIR = lib
diff --git a/NEWS b/NEWS
index 079483a3c85f50e56ec5ea6c55a87f2370c83f76..ee9a4585c905680313e439c7ff4f44daed6fec33 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,6 @@
+v0.4.5
+new : -m/--multiple : compress/decompress multiple files
+
 v0.4.4
 Fixed : high compression modes for Windows 32 bits
 new : external dictionary API extended to buffered mode and accessible through command line
index b0c841f3a9a1fa6c4fab45d570ee389f7a5c88c3..d6eb0b517bbf5c424c52b7870455b5c0ecc5a81f 100644 (file)
@@ -62,7 +62,7 @@ extern "C" {
 ***************************************/
 #define ZSTD_VERSION_MAJOR    0    /* for breaking interface changes  */
 #define ZSTD_VERSION_MINOR    4    /* for new (non-breaking) interface capabilities */
-#define ZSTD_VERSION_RELEASE  3    /* for tweaks, bug-fixes, or development */
+#define ZSTD_VERSION_RELEASE  5    /* for tweaks, bug-fixes, or development */
 #define ZSTD_VERSION_NUMBER  (ZSTD_VERSION_MAJOR *100*100 + ZSTD_VERSION_MINOR *100 + ZSTD_VERSION_RELEASE)
 ZSTDLIB_API unsigned ZSTD_versionNumber (void);
 
index 822f2d24bbec306ef895898dfac04645f814857d..57fa87c11f55aecdab632e56e73d86f6c2ab2ee4 100644 (file)
@@ -30,7 +30,7 @@
 # fullbench32: Same as fullbench, but forced to compile in 32-bits mode
 # ##########################################################################
 
-VERSION?= 0.4.4
+VERSION?= 0.4.5
 
 DESTDIR?=
 PREFIX ?= /usr/local
index 7ec5f6c5bccdf9d75a41bdf20038de25a13e1b7b..3867301a99577a46803e100b222427b3d020a8b7 100644 (file)
@@ -175,52 +175,6 @@ static unsigned FIO_GetMilliSpan(clock_t nPrevious)
 }
 
 
-static void FIO_getFileHandles(FILE** pfinput, FILE** pfoutput, const char* input_filename, const char* output_filename)
-{
-    if (!strcmp (input_filename, stdinmark))
-    {
-        DISPLAYLEVEL(4,"Using stdin for input\n");
-        *pfinput = stdin;
-        SET_BINARY_MODE(stdin);
-    }
-    else
-    {
-        *pfinput = fopen(input_filename, "rb");
-    }
-
-    if (!strcmp (output_filename, stdoutmark))
-    {
-        DISPLAYLEVEL(4,"Using stdout for output\n");
-        *pfoutput = stdout;
-        SET_BINARY_MODE(stdout);
-    }
-    else
-    {
-        /* Check if destination file already exists */
-        *pfoutput=0;
-        if (strcmp(output_filename,nulmark)) *pfoutput = fopen( output_filename, "rb" );
-        if (*pfoutput!=0)
-        {
-            fclose(*pfoutput);
-            if (!g_overwrite)
-            {
-                char ch='N';
-                if (g_displayLevel <= 1)   /* No interaction possible */
-                    EXM_THROW(11, "Operation aborted : %s already exists", output_filename);
-                DISPLAYLEVEL(2, "Warning : %s already exists\n", output_filename);
-                DISPLAYLEVEL(2, "Overwrite ? (y/N) : ");
-                ch = (char)getchar();
-                if ((ch!='Y') && (ch!='y')) EXM_THROW(11, "Operation aborted : %s already exists", output_filename);
-            }
-        }
-        *pfoutput = fopen( output_filename, "wb" );
-    }
-
-    if ( *pfinput==0 ) EXM_THROW(12, "Pb opening src : %s", input_filename);
-    if ( *pfoutput==0) EXM_THROW(13, "Pb opening dst : %s", output_filename);
-}
-
-
 static U64 FIO_getFileSize(const char* infilename)
 {
     int r;
@@ -273,12 +227,20 @@ static int FIO_getFiles(FILE** fileOutPtr, FILE** fileInPtr,
                 /* prompt for overwrite authorization */
                 int ch = 'N';
                 fclose(*fileOutPtr);
-                DISPLAYLEVEL(2, "Warning : %s already exists\n", dstFileName);
+                DISPLAY("Warning : %s already exists \n", dstFileName);
                 if ((g_displayLevel <= 1) || (*fileInPtr == stdin))
-                    EXM_THROW(11, "Operation aborted : %s already exists", dstFileName);   /* No interaction possible */
-                DISPLAYLEVEL(2, "Overwrite ? (y/N) : ");
+                {
+                    /* No interaction possible */
+                    DISPLAY("Operation aborted : %s already exists \n", dstFileName);
+                    return 1;
+                }
+                DISPLAY("Overwrite ? (y/N) : ");
                 while((ch = getchar()) != '\n' && ch != EOF);   /* flush integrated */
-                if ((ch!='Y') && (ch!='y')) EXM_THROW(12, "No. Operation aborted : %s already exists", dstFileName);
+                if ((ch!='Y') && (ch!='y'))
+                {
+                    DISPLAY("No. Operation aborted : %s already exists \n", dstFileName);
+                    return 1;
+                }
             }
         }
         *fileOutPtr = fopen( dstFileName, "wb" );
@@ -289,6 +251,42 @@ static int FIO_getFiles(FILE** fileOutPtr, FILE** fileInPtr,
     return 0;
 }
 
+/*!FIO_loadFile
+*  creates a buffer, pointed by *bufferPtr,
+*  loads "filename" content into it
+*  up to MAX_DICT_SIZE bytes
+*/
+static size_t FIO_loadFile(void** bufferPtr, const char* fileName)
+{
+    FILE* fileHandle;
+    size_t readSize;
+    U64 fileSize;
+
+    *bufferPtr = NULL;
+    if (fileName == NULL)
+        return 0;
+
+    DISPLAYLEVEL(4,"Loading %s as dictionary \n", fileName);
+    fileHandle = fopen(fileName, "rb");
+    if (fileHandle==0) EXM_THROW(31, "Error opening file %s", fileName);
+    fileSize = FIO_getFileSize(fileName);
+    if (fileSize > MAX_DICT_SIZE)
+    {
+        int seekResult;
+        if (fileSize > 1 GB) EXM_THROW(32, "Dictionary file %s is too large", fileName);   /* avoid extreme cases */
+        DISPLAYLEVEL(2,"Dictionary %s is too large : using last %u bytes only \n", fileName, MAX_DICT_SIZE);
+        seekResult = fseek(fileHandle, (long int)(fileSize-MAX_DICT_SIZE), SEEK_SET);   /* use end of file */
+        if (seekResult != 0) EXM_THROW(33, "Error seeking into file %s", fileName);
+        fileSize = MAX_DICT_SIZE;
+    }
+    *bufferPtr = (BYTE*)malloc((size_t)fileSize);
+    if (*bufferPtr==NULL) EXM_THROW(34, "Allocation error : not enough memory for dictBuffer");
+    readSize = fread(*bufferPtr, 1, (size_t)fileSize, fileHandle);
+    if (readSize!=fileSize) EXM_THROW(35, "Error reading dictionary file %s", fileName);
+    fclose(fileHandle);
+    return (size_t)fileSize;
+}
+
 
 /* **********************************************************************
 *  Compression
@@ -318,33 +316,7 @@ static cRess_t FIO_createCResources(const char* dictFileName)
     if (!ress.srcBuffer || !ress.dstBuffer) EXM_THROW(31, "Allocation error : not enough memory");
 
     /* dictionary */
-    ress.dictBuffer = NULL;
-    ress.dictBufferSize = 0;
-    if (dictFileName)
-    {
-        FILE* dictHandle;
-        size_t readSize;
-        U64 dictSize;
-        DISPLAYLEVEL(4,"Using %s as dictionary \n", dictFileName);
-        dictHandle = fopen(dictFileName, "rb");
-        if (dictHandle==0) EXM_THROW(31, "Error opening dictionary file %s", dictFileName);
-        dictSize = FIO_getFileSize(dictFileName);
-        if (dictSize > MAX_DICT_SIZE)
-        {
-            int seekResult;
-            if (dictSize > 1 GB) EXM_THROW(32, "Dictionary file %s is too large", dictFileName);   /* avoid extreme cases */
-            DISPLAYLEVEL(2,"Dictionary %s is too large : using last %u bytes only \n", dictFileName, MAX_DICT_SIZE);
-            seekResult = fseek(dictHandle, (long int)(dictSize-MAX_DICT_SIZE), SEEK_SET);   /* use end of file */
-            if (seekResult != 0) EXM_THROW(33, "Error seeking into dictionary file %s", dictFileName);
-            dictSize = MAX_DICT_SIZE;
-        }
-        ress.dictBuffer = (BYTE*)malloc((size_t)dictSize);
-        if (ress.dictBuffer==NULL) EXM_THROW(34, "Allocation error : not enough memory for dictBuffer");
-        readSize = fread(ress.dictBuffer, 1, (size_t)dictSize, dictHandle);
-        if (readSize!=dictSize) EXM_THROW(35, "Error reading dictionary file %s", dictFileName);
-        fclose(dictHandle);
-        ress.dictBufferSize = (size_t)dictSize;
-    }
+    ress.dictBufferSize = FIO_loadFile(&(ress.dictBuffer), dictFileName);
 
     return ress;
 }
@@ -504,48 +476,80 @@ int FIO_compressMultipleFilenames(const char** inFileNamesTable, unsigned nbFile
 }
 
 
-
-
 /* **************************************************************************
 *  Decompression
 ****************************************************************************/
+typedef struct {
+    void*  srcBuffer;
+    size_t srcBufferSize;
+    void*  dstBuffer;
+    size_t dstBufferSize;
+    void*  dictBuffer;
+    size_t dictBufferSize;
+    ZBUFF_DCtx* dctx;
+} dRess_t;
+
+static dRess_t FIO_createDResources(const char* dictFileName)
+{
+    dRess_t ress;
+
+    /* init */
+    ress.dctx = ZBUFF_createDCtx();
+    if (ress.dctx==NULL) EXM_THROW(60, "Can't create ZBUFF decompression context");
 
+    /* Allocate Memory */
+    ress.srcBufferSize = ZBUFF_recommendedDInSize();
+    ress.srcBuffer = malloc(ress.srcBufferSize);
+    ress.dstBufferSize = ZBUFF_recommendedDOutSize();
+    ress.dstBuffer = malloc(ress.dstBufferSize);
+    if (!ress.srcBuffer || !ress.dstBuffer) EXM_THROW(61, "Allocation error : not enough memory");
 
-unsigned long long FIO_decompressFrame(FILE* foutput, FILE* finput,
-                                       BYTE* inBuff, size_t inBuffSize, size_t alreadyLoaded,
-                                       BYTE* outBuff, size_t outBuffSize,
-                                       BYTE* dictBuff, size_t dictSize,
-                                       ZBUFF_DCtx* dctx)
+    /* dictionary */
+    ress.dictBufferSize = FIO_loadFile(&(ress.dictBuffer), dictFileName);
+
+    return ress;
+}
+
+static void FIO_freeDResources(dRess_t ress)
+{
+    size_t errorCode = ZBUFF_freeDCtx(ress.dctx);
+    if (ZBUFF_isError(errorCode)) EXM_THROW(69, "Error : can't free ZBUFF context resource : %s", ZBUFF_getErrorName(errorCode));
+    free(ress.srcBuffer);
+    free(ress.dstBuffer);
+    free(ress.dictBuffer);
+}
+
+
+unsigned long long FIO_decompressFrame(dRess_t ress,
+                                       FILE* foutput, FILE* finput, size_t alreadyLoaded)
 {
     U64    frameSize = 0;
     size_t readSize=alreadyLoaded;
 
     /* Main decompression Loop */
-    ZBUFF_decompressInit(dctx);
-    ZBUFF_decompressWithDictionary(dctx, dictBuff, dictSize);
+    ZBUFF_decompressInit(ress.dctx);
+    ZBUFF_decompressWithDictionary(ress.dctx, ress.dictBuffer, ress.dictBufferSize);
     while (1)
     {
         /* Decode */
         size_t sizeCheck;
-        size_t inSize=readSize, decodedSize=outBuffSize;
-        size_t inStart=0;
-        size_t toRead = ZBUFF_decompressContinue(dctx, outBuff, &decodedSize, inBuff+inStart, &inSize);
+        size_t inSize=readSize, decodedSize=ress.dstBufferSize;
+        size_t toRead = ZBUFF_decompressContinue(ress.dctx, ress.dstBuffer, &decodedSize, ress.srcBuffer, &inSize);
         if (ZBUFF_isError(toRead)) EXM_THROW(36, "Decoding error : %s", ZBUFF_getErrorName(toRead));
         readSize -= inSize;
-        inStart += inSize;
 
         /* Write block */
-        sizeCheck = fwrite(outBuff, 1, decodedSize, foutput);
+        sizeCheck = fwrite(ress.dstBuffer, 1, decodedSize, foutput);
         if (sizeCheck != decodedSize) EXM_THROW(37, "Write error : unable to write data block to destination file");
         frameSize += decodedSize;
         DISPLAYUPDATE(2, "\rDecoded : %u MB...     ", (U32)(frameSize>>20) );
 
         if (toRead == 0) break;
-        if (readSize) continue;   /* still some data left within inBuff */
+        if (readSize) EXM_THROW(38, "Decoding error : should consume entire input");
 
         /* Fill input buffer */
-        if (toRead > inBuffSize) EXM_THROW(34, "too large block");
-        readSize = fread(inBuff, 1, toRead, finput);
+        if (toRead > ress.srcBufferSize) EXM_THROW(34, "too large block");
+        readSize = fread(ress.srcBuffer, 1, toRead, finput);
         if (readSize != toRead) EXM_THROW(35, "Read error");
     }
 
@@ -553,88 +557,96 @@ unsigned long long FIO_decompressFrame(FILE* foutput, FILE* finput,
 }
 
 
-unsigned long long FIO_decompressFilename(const char* output_filename, const char* input_filename, const char* dictFileName)
+static int FIO_decompressFile_extRess(dRess_t ress,
+                                      const char* dstFileName, const char* srcFileName)
 {
-    FILE* finput, *foutput;
-    BYTE* inBuff=NULL;
-    size_t inBuffSize = ZBUFF_recommendedDInSize();
-    BYTE* outBuff=NULL;
-    size_t outBuffSize = ZBUFF_recommendedDOutSize();
-    BYTE* dictBuff=NULL;
-    size_t dictSize = 0;
-    U64   filesize = 0;
-    size_t toRead;
-
-    /* dictionary */
-    if (dictFileName)
-    {
-        FILE* dictHandle;
-        size_t readSize;
-        DISPLAYLEVEL(4,"Using %s as dictionary \n", dictFileName);
-        dictHandle = fopen(dictFileName, "rb");
-        if (dictHandle==0) EXM_THROW(21, "Error opening dictionary file %s", dictFileName);
-        dictSize = (size_t)FIO_getFileSize(dictFileName);
-        if (dictSize > MAX_DICT_SIZE)
-        {
-            int seekResult;
-            if (dictSize > 1 GB) EXM_THROW(21, "Dictionary file %s is too large", dictFileName);   /* avoid extreme cases */
-            DISPLAYLEVEL(2,"Dictionary %s is too large : using last %u bytes only \n", dictFileName, MAX_DICT_SIZE);
-            seekResult = fseek(dictHandle, (long int)(dictSize-MAX_DICT_SIZE), SEEK_SET);   /* use end of file */
-            if (seekResult != 0) EXM_THROW(21, "Error seeking into dictionary file %s", dictFileName);
-            dictSize = MAX_DICT_SIZE;
-        }
-        dictBuff = (BYTE*)malloc(dictSize);
-        if (dictBuff==NULL) EXM_THROW(20, "Allocation error : not enough memory for dictBuff");
-        readSize = fread(dictBuff, 1, (size_t)dictSize, dictHandle);
-        if (readSize!=dictSize) EXM_THROW(21, "Error reading dictionary file %s", dictFileName);
-        fclose(dictHandle);
-    }
+    unsigned long long filesize = 0;
+    FILE* srcFile;
+    FILE* dstFile;
 
     /* Init */
-    ZBUFF_DCtx* dctx = ZBUFF_createDCtx();
-    FIO_getFileHandles(&finput, &foutput, input_filename, output_filename);
-
-    /* Allocate Memory (if needed) */
-    inBuff  = (BYTE*)malloc(inBuffSize);
-    outBuff  = (BYTE*)malloc(outBuffSize);
-    if (!inBuff || !outBuff) EXM_THROW(33, "Allocation error : not enough memory");
+    if (FIO_getFiles(&dstFile, &srcFile, dstFileName, srcFileName)) return 1;
 
     /* for each frame */
     for ( ; ; )
     {
         size_t sizeCheck;
         /* check magic number -> version */
-        toRead = 4;
-        sizeCheck = fread(inBuff, (size_t)1, toRead, finput);
+        size_t toRead = 4;
+        sizeCheck = fread(ress.srcBuffer, (size_t)1, toRead, srcFile);
         if (sizeCheck==0) break;   /* no more input */
         if (sizeCheck != toRead) EXM_THROW(31, "Read error : cannot read header");
 #if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT==1)
-        if (ZSTD_isLegacy(MEM_readLE32(inBuff)))
+        if (ZSTD_isLegacy(MEM_readLE32(ress.srcBuffer)))
         {
-            filesize += FIO_decompressLegacyFrame(foutput, finput, MEM_readLE32(inBuff));
+            filesize += FIO_decompressLegacyFrame(dstFile, srcFile, MEM_readLE32(ress.srcBuffer));
             continue;
         }
 #endif   /* ZSTD_LEGACY_SUPPORT */
 
-        filesize += FIO_decompressFrame(foutput, finput,
-                                        inBuff, inBuffSize, toRead,
-                                        outBuff, outBuffSize,
-                                        dictBuff, dictSize,
-                                        dctx);
+        filesize += FIO_decompressFrame(ress, dstFile, srcFile, toRead);
     }
 
+    /* Final Status */
     DISPLAYLEVEL(2, "\r%79s\r", "");
-    DISPLAYLEVEL(2, "Decoded %llu bytes   \n", (long long unsigned)filesize);
+    DISPLAYLEVEL(2, "Successfully decoded %llu bytes \n", filesize);
 
-    /* clean */
-    free(inBuff);
-    free(outBuff);
-    free(dictBuff);
-    ZBUFF_freeDCtx(dctx);
-    fclose(finput);
-    if (fclose(foutput)) EXM_THROW(38, "Write error : cannot properly close %s", output_filename);
-
-    return filesize;
+    /* Close */
+    fclose(srcFile);
+    if (fclose(dstFile)) EXM_THROW(38, "Write error : cannot properly close %s", dstFileName);
+
+    return 0;
 }
 
 
+int FIO_decompressFilename(const char* dstFileName, const char* srcFileName,
+                           const char* dictFileName)
+{
+    int missingFiles = 0;
+    dRess_t ress = FIO_createDResources(dictFileName);
+
+    missingFiles += FIO_decompressFile_extRess(ress, dstFileName, srcFileName);
+
+    FIO_freeDResources(ress);
+    return missingFiles;
+}
+
+
+#define MAXSUFFIXSIZE 8
+int FIO_decompressMultipleFilenames(const char** srcNamesTable, unsigned nbFiles,
+                                    const char* suffix,
+                                    const char* dictFileName)
+{
+    unsigned u;
+    int skippedFiles = 0;
+    int missingFiles = 0;
+    char* dstFileName = (char*)malloc(FNSPACE);
+    size_t dfnSize = FNSPACE;
+    const size_t suffixSize = strlen(suffix);
+    dRess_t ress;
+
+       if (dstFileName==NULL) EXM_THROW(70, "not enough memory for dstFileName");
+    ress = FIO_createDResources(dictFileName);
+
+    for (u=0; u<nbFiles; u++)
+    {
+        const char* srcFileName = srcNamesTable[u];
+        size_t sfnSize = strlen(srcFileName);
+        const char* suffixPtr = srcFileName + sfnSize - suffixSize;
+        if (dfnSize <= sfnSize-suffixSize+1) { free(dstFileName); dfnSize = sfnSize + 20; dstFileName = (char*)malloc(dfnSize); if (dstFileName==NULL) EXM_THROW(71, "not enough memory for dstFileName"); }
+        if (sfnSize <= suffixSize  ||  strcmp(suffixPtr, suffix) != 0)
+        {
+            DISPLAYLEVEL(1, "File extension doesn't match expected extension (%4s); will not process file: %s\n", suffix, srcFileName);
+            skippedFiles++;
+            continue;
+        }
+        memcpy(dstFileName, srcFileName, sfnSize - suffixSize);
+        dstFileName[sfnSize-suffixSize] = '\0';
+
+        missingFiles += FIO_decompressFile_extRess(ress, dstFileName, srcFileName);
+    }
+
+    FIO_freeDResources(ress);
+    free(dstFileName);
+    return missingFiles + skippedFiles;
+}
index a9187ec99bb9c8744bcefffe554c80e8e5c3a0cf..0e25d842f84f41aa946994e12e05abc604045d62 100644 (file)
@@ -53,25 +53,30 @@ void FIO_setNotificationLevel(unsigned level);
 *  Single File functions
 ***************************************/
 int FIO_compressFilename (const char* outfilename, const char* infilename, const char* dictFileName, int compressionLevel);
-unsigned long long FIO_decompressFilename (const char* outfilename, const char* infilename, const char* dictFileName);
+int FIO_decompressFilename (const char* outfilename, const char* infilename, const char* dictFileName);
 /**
 FIO_compressFilename :
     @result : 0 == ok;  1 == pb with src file.
 
 FIO_decompressFilename :
-    @result : size of regenerated file
+    @result : 0 == ok;  1 == pb with src file.
 */
 
 
 /* *************************************
 *  Multiple File functions
 ***************************************/
-int FIO_compressMultipleFilenames(const char** inFileNamesTable, unsigned nbFiles,
+int FIO_compressMultipleFilenames(const char** srcNamesTable, unsigned nbFiles,
                                   const char* suffix,
                                   const char* dictFileName, int compressionLevel);
+int FIO_decompressMultipleFilenames(const char** srcNamesTable, unsigned nbFiles,
+                                    const char* suffix,
+                                    const char* dictFileName);
 /**
 FIO_compressMultipleFilenames :
     @result : nb of missing files
+FIO_decompressMultipleFilenames :
+    @result : nb of missing or skipped files
 */
 
 
index 25c7fb23acbc485071c6dfe84a3da1ab6b3eae4f..5829ad2a2ed953c889ec755be85b975c5f57d944 100755 (executable)
@@ -49,6 +49,19 @@ echo "*** dictionary tests *** "
 ./datagen -g1M | $ZSTD -D tmpDict | $ZSTD -D tmpDict -dv | md5sum > tmp2
 diff -q tmp1 tmp2
 
+echo "*** multiple files tests *** "
+
+./datagen -s1        > tmp1 2> /dev/null
+./datagen -s2 -g100K > tmp2 2> /dev/null
+./datagen -s3 -g1M   > tmp3 2> /dev/null
+./zstd -f -m tmp*
+ls -ls tmp*
+rm tmp1 tmp2 tmp3
+./zstd -df -m *.zst
+ls -ls tmp*
+./zstd -f -m tmp1 notHere tmp2 && die "missing file not detected!"
+rm tmp*
+
 echo "**** zstd round-trip tests **** "
 
 roundTripTest
index fdc8cc462f846d54833cf4ab5c92c1eed4baf3bf..8d69c4ddfd9630e666a433f02e5afb96391d8238 100644 (file)
@@ -31,12 +31,21 @@ is equivalent to
 It is based on the \fBLZ77\fR family, with FSE & huff0 entropy stage.
 zstd offers compression speed > 200 MB/s per core.
 It also features a fast decoder, with speed > 500 MB/s per core.
+
+\fBzstd\fR command line is generally similar to gzip, but features the following differences :
+ - Original files are preserved
+ - By default, \fBzstd file1 file2\fR means : compress file1 \fBinto\fR file2.
+     Use \fB-m\fR command if you want : compress file1 into file1.zstd and file2 into file2.zst
+ - By default, when compressing files, \fBzstd\fR displays advancement notification and result summary.
+     Use \fB-q\fR to turn them off
+
+
 \fBzstd\fR supports the following options :
 
 .SH OPTIONS
 .TP
-.B \-1
fast compression (default)
+.B \-#
# compression level [1-19](default:1)
 .TP
 .B \-d
  decompression
@@ -44,6 +53,14 @@ It also features a fast decoder, with speed > 500 MB/s per core.
 .B \-f
  overwrite output without prompting
 .TP
+.BR \-m ", " --multiple
+ multiple files mode
+ In this mode, multiple files on the command line means compression or decompression of each named file
+ Notifications are also turned off by default
+.TP
+.B \-D
+ Use next file as dictionary content for compress / decompression
+.TP
 .BR \-h/\-H ", " --help
  display help/long help and exit
 .TP
@@ -53,20 +70,17 @@ It also features a fast decoder, with speed > 500 MB/s per core.
 .BR \-v ", " --verbose
  verbose mode
 .TP
-.B \-q
- suppress warnings; specify twice to suppress errors too
+.BR \-q ", " --quiet
+ suppress warnings and notifications; specify twice to suppress errors too
 .TP
-.B \-c
+.B \-c 
  force write to standard output, even if it is the console
 .TP
-.B \-t
- test compressed file integrity
-.TP
 .B \-z
  force compression
 .TP
-.B \-b
- benchmark file(s)
+.B \-b#
+ benchmark file(s) using compression level #
 .TP
 .B \-i#
  iteration loops [1-9](default : 3), benchmark mode only
index 37d48b8e883be7e640046accffc2297795e9e89a..9610e0fd4f9a87aa61fce4e41dfa0a94ffeacc53 100644 (file)
@@ -47,6 +47,7 @@
 #ifndef ZSTD_NOBENCH
 #  include "bench.h"  /* BMK_benchFiles, BMK_SetNbIterations */
 #endif
+#include "zstd.h"     /* ZSTD version numbers */
 
 
 /**************************************
@@ -72,7 +73,9 @@
 **************************************/
 #define COMPRESSOR_NAME "zstd command line interface"
 #ifndef ZSTD_VERSION
-#  define ZSTD_VERSION "v0.4.4"
+#  define QUOTE(str) #str
+#  define EXPAND_AND_QUOTE(str) QUOTE(str)
+#  define ZSTD_VERSION "v" EXPAND_AND_QUOTE(ZSTD_VERSION_MAJOR) "." EXPAND_AND_QUOTE(ZSTD_VERSION_MINOR) "." EXPAND_AND_QUOTE(ZSTD_VERSION_RELEASE)
 #endif
 #define AUTHOR "Yann Collet"
 #define WELCOME_MESSAGE "*** %s %i-bits %s, by %s (%s) ***\n", COMPRESSOR_NAME, (int)(sizeof(void*)*8), ZSTD_VERSION, AUTHOR, __DATE__
@@ -140,7 +143,6 @@ static int usage_advanced(const char* programName)
     DISPLAY( " -m     : multiple input filenames mode");
     DISPLAY( " -c     : force write to standard output, even if it is the console\n");
     DISPLAY( " -D file: use file content as Dictionary \n");
-    //DISPLAY( " -t     : test compressed file integrity\n");
 #ifndef ZSTD_NOBENCH
     DISPLAY( "Benchmark arguments :\n");
     DISPLAY( " -b#    : benchmark file(s), using # compression level (default : 1) \n");
@@ -210,7 +212,9 @@ int main(int argCount, const char** argv)
         /* long commands (--long-word) */
         if (!strcmp(argument, "--version")) { displayOut=stdout; DISPLAY(WELCOME_MESSAGE); return 0; }
         if (!strcmp(argument, "--help")) { displayOut=stdout; return usage_advanced(programName); }
+        if (!strcmp(argument, "--multiple")) { multiple=1; continue; }
         if (!strcmp(argument, "--verbose")) { displayLevel=4; continue; }
+        if (!strcmp(argument, "--quiet")) { displayLevel--; continue; }
 
         /* Decode commands (note : aggregated commands are allowed) */
         if (argument[0]=='-')
@@ -400,16 +404,27 @@ int main(int argCount, const char** argv)
     if (!strcmp(inFileName, stdinmark) && !strcmp(outFileName,stdoutmark) && (displayLevel==2)) displayLevel=1;
     if (multiple && (displayLevel==2)) displayLevel=1;
 
+    if ((!multiple) && (nbFiles>2))
+    {
+        DISPLAY("Too many files on the command line (%u > 2). Do you mean -m ? \n", nbFiles);
+        return nbFiles;
+    }
+
     /* IO Stream/File */
     FIO_setNotificationLevel(displayLevel);
     if (decode)
+    {
+      if (multiple)
+        operationResult = FIO_decompressMultipleFilenames(argv+fileNameStart, nbFiles, ZSTD_EXTENSION, dictFileName);
+      else
         FIO_decompressFilename(outFileName, inFileName, dictFileName);
+    }
     else
     {
         if (multiple)
           operationResult = FIO_compressMultipleFilenames(argv+fileNameStart, nbFiles, ZSTD_EXTENSION, dictFileName, cLevel);
         else
-          FIO_compressFilename(outFileName, inFileName, dictFileName, cLevel);
+          operationResult = FIO_compressFilename(outFileName, inFileName, dictFileName, cLevel);
     }
 
 _end: