1*3117ece4Schristos /* 2*3117ece4Schristos * Copyright (c) Meta Platforms, Inc. and affiliates. 3*3117ece4Schristos * All rights reserved. 4*3117ece4Schristos * 5*3117ece4Schristos * This source code is licensed under both the BSD-style license (found in the 6*3117ece4Schristos * LICENSE file in the root directory of this source tree) and the GPLv2 (found 7*3117ece4Schristos * in the COPYING file in the root directory of this source tree). 8*3117ece4Schristos * You may select, at your option, one of the above-listed licenses. 9*3117ece4Schristos */ 10*3117ece4Schristos 11*3117ece4Schristos 12*3117ece4Schristos #include <stdio.h> // printf 13*3117ece4Schristos #include <stdlib.h> // free 14*3117ece4Schristos #include <string.h> // memset, strcat, strlen 15*3117ece4Schristos #include <zstd.h> // presumes zstd library is installed 16*3117ece4Schristos #include "common.h" // Helper functions, CHECK(), and CHECK_ZSTD() 17*3117ece4Schristos 18*3117ece4Schristos static void compressFile_orDie(const char* fname, const char* outName, int cLevel, 19*3117ece4Schristos int nbThreads) 20*3117ece4Schristos { 21*3117ece4Schristos fprintf (stderr, "Starting compression of %s with level %d, using %d threads\n", 22*3117ece4Schristos fname, cLevel, nbThreads); 23*3117ece4Schristos 24*3117ece4Schristos /* Open the input and output files. */ 25*3117ece4Schristos FILE* const fin = fopen_orDie(fname, "rb"); 26*3117ece4Schristos FILE* const fout = fopen_orDie(outName, "wb"); 27*3117ece4Schristos /* Create the input and output buffers. 28*3117ece4Schristos * They may be any size, but we recommend using these functions to size them. 29*3117ece4Schristos * Performance will only suffer significantly for very tiny buffers. 30*3117ece4Schristos */ 31*3117ece4Schristos size_t const buffInSize = ZSTD_CStreamInSize(); 32*3117ece4Schristos void* const buffIn = malloc_orDie(buffInSize); 33*3117ece4Schristos size_t const buffOutSize = ZSTD_CStreamOutSize(); 34*3117ece4Schristos void* const buffOut = malloc_orDie(buffOutSize); 35*3117ece4Schristos 36*3117ece4Schristos /* Create the context. */ 37*3117ece4Schristos ZSTD_CCtx* const cctx = ZSTD_createCCtx(); 38*3117ece4Schristos CHECK(cctx != NULL, "ZSTD_createCCtx() failed!"); 39*3117ece4Schristos 40*3117ece4Schristos /* Set any parameters you want. 41*3117ece4Schristos * Here we set the compression level, and enable the checksum. 42*3117ece4Schristos */ 43*3117ece4Schristos CHECK_ZSTD( ZSTD_CCtx_setParameter(cctx, ZSTD_c_compressionLevel, cLevel) ); 44*3117ece4Schristos CHECK_ZSTD( ZSTD_CCtx_setParameter(cctx, ZSTD_c_checksumFlag, 1) ); 45*3117ece4Schristos if (nbThreads > 1) { 46*3117ece4Schristos size_t const r = ZSTD_CCtx_setParameter(cctx, ZSTD_c_nbWorkers, nbThreads); 47*3117ece4Schristos if (ZSTD_isError(r)) { 48*3117ece4Schristos fprintf (stderr, "Note: the linked libzstd library doesn't support multithreading. " 49*3117ece4Schristos "Reverting to single-thread mode. \n"); 50*3117ece4Schristos } 51*3117ece4Schristos } 52*3117ece4Schristos 53*3117ece4Schristos /* This loop read from the input file, compresses that entire chunk, 54*3117ece4Schristos * and writes all output produced to the output file. 55*3117ece4Schristos */ 56*3117ece4Schristos size_t const toRead = buffInSize; 57*3117ece4Schristos for (;;) { 58*3117ece4Schristos size_t read = fread_orDie(buffIn, toRead, fin); 59*3117ece4Schristos /* Select the flush mode. 60*3117ece4Schristos * If the read may not be finished (read == toRead) we use 61*3117ece4Schristos * ZSTD_e_continue. If this is the last chunk, we use ZSTD_e_end. 62*3117ece4Schristos * Zstd optimizes the case where the first flush mode is ZSTD_e_end, 63*3117ece4Schristos * since it knows it is compressing the entire source in one pass. 64*3117ece4Schristos */ 65*3117ece4Schristos int const lastChunk = (read < toRead); 66*3117ece4Schristos ZSTD_EndDirective const mode = lastChunk ? ZSTD_e_end : ZSTD_e_continue; 67*3117ece4Schristos /* Set the input buffer to what we just read. 68*3117ece4Schristos * We compress until the input buffer is empty, each time flushing the 69*3117ece4Schristos * output. 70*3117ece4Schristos */ 71*3117ece4Schristos ZSTD_inBuffer input = { buffIn, read, 0 }; 72*3117ece4Schristos int finished; 73*3117ece4Schristos do { 74*3117ece4Schristos /* Compress into the output buffer and write all of the output to 75*3117ece4Schristos * the file so we can reuse the buffer next iteration. 76*3117ece4Schristos */ 77*3117ece4Schristos ZSTD_outBuffer output = { buffOut, buffOutSize, 0 }; 78*3117ece4Schristos size_t const remaining = ZSTD_compressStream2(cctx, &output , &input, mode); 79*3117ece4Schristos CHECK_ZSTD(remaining); 80*3117ece4Schristos fwrite_orDie(buffOut, output.pos, fout); 81*3117ece4Schristos /* If we're on the last chunk we're finished when zstd returns 0, 82*3117ece4Schristos * which means its consumed all the input AND finished the frame. 83*3117ece4Schristos * Otherwise, we're finished when we've consumed all the input. 84*3117ece4Schristos */ 85*3117ece4Schristos finished = lastChunk ? (remaining == 0) : (input.pos == input.size); 86*3117ece4Schristos } while (!finished); 87*3117ece4Schristos CHECK(input.pos == input.size, 88*3117ece4Schristos "Impossible: zstd only returns 0 when the input is completely consumed!"); 89*3117ece4Schristos 90*3117ece4Schristos if (lastChunk) { 91*3117ece4Schristos break; 92*3117ece4Schristos } 93*3117ece4Schristos } 94*3117ece4Schristos 95*3117ece4Schristos ZSTD_freeCCtx(cctx); 96*3117ece4Schristos fclose_orDie(fout); 97*3117ece4Schristos fclose_orDie(fin); 98*3117ece4Schristos free(buffIn); 99*3117ece4Schristos free(buffOut); 100*3117ece4Schristos } 101*3117ece4Schristos 102*3117ece4Schristos 103*3117ece4Schristos static char* createOutFilename_orDie(const char* filename) 104*3117ece4Schristos { 105*3117ece4Schristos size_t const inL = strlen(filename); 106*3117ece4Schristos size_t const outL = inL + 5; 107*3117ece4Schristos void* const outSpace = malloc_orDie(outL); 108*3117ece4Schristos memset(outSpace, 0, outL); 109*3117ece4Schristos strcat(outSpace, filename); 110*3117ece4Schristos strcat(outSpace, ".zst"); 111*3117ece4Schristos return (char*)outSpace; 112*3117ece4Schristos } 113*3117ece4Schristos 114*3117ece4Schristos int main(int argc, const char** argv) 115*3117ece4Schristos { 116*3117ece4Schristos const char* const exeName = argv[0]; 117*3117ece4Schristos 118*3117ece4Schristos if (argc < 2) { 119*3117ece4Schristos printf("wrong arguments\n"); 120*3117ece4Schristos printf("usage:\n"); 121*3117ece4Schristos printf("%s FILE [LEVEL] [THREADS]\n", exeName); 122*3117ece4Schristos return 1; 123*3117ece4Schristos } 124*3117ece4Schristos 125*3117ece4Schristos int cLevel = 1; 126*3117ece4Schristos int nbThreads = 1; 127*3117ece4Schristos 128*3117ece4Schristos if (argc >= 3) { 129*3117ece4Schristos cLevel = atoi (argv[2]); 130*3117ece4Schristos CHECK(cLevel != 0, "can't parse LEVEL!"); 131*3117ece4Schristos } 132*3117ece4Schristos 133*3117ece4Schristos if (argc >= 4) { 134*3117ece4Schristos nbThreads = atoi (argv[3]); 135*3117ece4Schristos CHECK(nbThreads != 0, "can't parse THREADS!"); 136*3117ece4Schristos } 137*3117ece4Schristos 138*3117ece4Schristos const char* const inFilename = argv[1]; 139*3117ece4Schristos 140*3117ece4Schristos char* const outFilename = createOutFilename_orDie(inFilename); 141*3117ece4Schristos compressFile_orDie(inFilename, outFilename, cLevel, nbThreads); 142*3117ece4Schristos 143*3117ece4Schristos free(outFilename); /* not strictly required, since program execution stops there, 144*3117ece4Schristos * but some static analyzer may complain otherwise */ 145*3117ece4Schristos return 0; 146*3117ece4Schristos } 147