| /*------------------------------------------------------------------------- |
| * |
| * bbstreamer_lz4.c |
| * |
| * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group |
| * |
| * IDENTIFICATION |
| * src/bin/pg_basebackup/bbstreamer_lz4.c |
| *------------------------------------------------------------------------- |
| */ |
| |
| #include "postgres_fe.h" |
| |
| #include <unistd.h> |
| |
| #ifdef USE_LZ4 |
| #include <lz4frame.h> |
| #endif |
| |
| #include "bbstreamer.h" |
| #include "common/logging.h" |
| #include "common/file_perm.h" |
| #include "common/string.h" |
| |
| #ifdef USE_LZ4 |
| typedef struct bbstreamer_lz4_frame |
| { |
| bbstreamer base; |
| |
| LZ4F_compressionContext_t cctx; |
| LZ4F_decompressionContext_t dctx; |
| LZ4F_preferences_t prefs; |
| |
| size_t bytes_written; |
| bool header_written; |
| } bbstreamer_lz4_frame; |
| |
| static void bbstreamer_lz4_compressor_content(bbstreamer *streamer, |
| bbstreamer_member *member, |
| const char *data, int len, |
| bbstreamer_archive_context context); |
| static void bbstreamer_lz4_compressor_finalize(bbstreamer *streamer); |
| static void bbstreamer_lz4_compressor_free(bbstreamer *streamer); |
| |
| const bbstreamer_ops bbstreamer_lz4_compressor_ops = { |
| .content = bbstreamer_lz4_compressor_content, |
| .finalize = bbstreamer_lz4_compressor_finalize, |
| .free = bbstreamer_lz4_compressor_free |
| }; |
| |
| static void bbstreamer_lz4_decompressor_content(bbstreamer *streamer, |
| bbstreamer_member *member, |
| const char *data, int len, |
| bbstreamer_archive_context context); |
| static void bbstreamer_lz4_decompressor_finalize(bbstreamer *streamer); |
| static void bbstreamer_lz4_decompressor_free(bbstreamer *streamer); |
| |
| const bbstreamer_ops bbstreamer_lz4_decompressor_ops = { |
| .content = bbstreamer_lz4_decompressor_content, |
| .finalize = bbstreamer_lz4_decompressor_finalize, |
| .free = bbstreamer_lz4_decompressor_free |
| }; |
| #endif |
| |
| /* |
| * Create a new base backup streamer that performs lz4 compression of tar |
| * blocks. |
| */ |
| bbstreamer * |
| bbstreamer_lz4_compressor_new(bbstreamer *next, pg_compress_specification *compress) |
| { |
| #ifdef USE_LZ4 |
| bbstreamer_lz4_frame *streamer; |
| LZ4F_errorCode_t ctxError; |
| LZ4F_preferences_t *prefs; |
| |
| Assert(next != NULL); |
| |
| streamer = palloc0(sizeof(bbstreamer_lz4_frame)); |
| *((const bbstreamer_ops **) &streamer->base.bbs_ops) = |
| &bbstreamer_lz4_compressor_ops; |
| |
| streamer->base.bbs_next = next; |
| initStringInfo(&streamer->base.bbs_buffer); |
| streamer->header_written = false; |
| |
| /* Initialize stream compression preferences */ |
| prefs = &streamer->prefs; |
| memset(prefs, 0, sizeof(LZ4F_preferences_t)); |
| prefs->frameInfo.blockSizeID = LZ4F_max256KB; |
| prefs->compressionLevel = compress->level; |
| |
| ctxError = LZ4F_createCompressionContext(&streamer->cctx, LZ4F_VERSION); |
| if (LZ4F_isError(ctxError)) |
| pg_log_error("could not create lz4 compression context: %s", |
| LZ4F_getErrorName(ctxError)); |
| |
| return &streamer->base; |
| #else |
| pg_fatal("this build does not support compression with %s", "LZ4"); |
| return NULL; /* keep compiler quiet */ |
| #endif |
| } |
| |
| #ifdef USE_LZ4 |
| /* |
| * Compress the input data to output buffer. |
| * |
| * Find out the compression bound based on input data length for each |
| * invocation to make sure that output buffer has enough capacity to |
| * accommodate the compressed data. In case if the output buffer |
| * capacity falls short of compression bound then forward the content |
| * of output buffer to next streamer and empty the buffer. |
| */ |
| static void |
| bbstreamer_lz4_compressor_content(bbstreamer *streamer, |
| bbstreamer_member *member, |
| const char *data, int len, |
| bbstreamer_archive_context context) |
| { |
| bbstreamer_lz4_frame *mystreamer; |
| uint8 *next_in, |
| *next_out; |
| size_t out_bound, |
| compressed_size, |
| avail_out; |
| |
| mystreamer = (bbstreamer_lz4_frame *) streamer; |
| next_in = (uint8 *) data; |
| |
| /* Write header before processing the first input chunk. */ |
| if (!mystreamer->header_written) |
| { |
| compressed_size = LZ4F_compressBegin(mystreamer->cctx, |
| (uint8 *) mystreamer->base.bbs_buffer.data, |
| mystreamer->base.bbs_buffer.maxlen, |
| &mystreamer->prefs); |
| |
| if (LZ4F_isError(compressed_size)) |
| pg_log_error("could not write lz4 header: %s", |
| LZ4F_getErrorName(compressed_size)); |
| |
| mystreamer->bytes_written += compressed_size; |
| mystreamer->header_written = true; |
| } |
| |
| /* |
| * Update the offset and capacity of output buffer based on number of |
| * bytes written to output buffer. |
| */ |
| next_out = (uint8 *) mystreamer->base.bbs_buffer.data + mystreamer->bytes_written; |
| avail_out = mystreamer->base.bbs_buffer.maxlen - mystreamer->bytes_written; |
| |
| /* |
| * Find out the compression bound and make sure that output buffer has the |
| * required capacity for the success of LZ4F_compressUpdate. If needed |
| * forward the content to next streamer and empty the buffer. |
| */ |
| out_bound = LZ4F_compressBound(len, &mystreamer->prefs); |
| if (avail_out < out_bound) |
| { |
| bbstreamer_content(mystreamer->base.bbs_next, member, |
| mystreamer->base.bbs_buffer.data, |
| mystreamer->bytes_written, |
| context); |
| |
| /* Enlarge buffer if it falls short of out bound. */ |
| if (mystreamer->base.bbs_buffer.maxlen < out_bound) |
| enlargeStringInfo(&mystreamer->base.bbs_buffer, out_bound); |
| |
| avail_out = mystreamer->base.bbs_buffer.maxlen; |
| mystreamer->bytes_written = 0; |
| next_out = (uint8 *) mystreamer->base.bbs_buffer.data; |
| } |
| |
| /* |
| * This call compresses the data starting at next_in and generates the |
| * output starting at next_out. It expects the caller to provide the size |
| * of input buffer and capacity of output buffer by providing parameters |
| * len and avail_out. |
| * |
| * It returns the number of bytes compressed to output buffer. |
| */ |
| compressed_size = LZ4F_compressUpdate(mystreamer->cctx, |
| next_out, avail_out, |
| next_in, len, NULL); |
| |
| if (LZ4F_isError(compressed_size)) |
| pg_log_error("could not compress data: %s", |
| LZ4F_getErrorName(compressed_size)); |
| |
| mystreamer->bytes_written += compressed_size; |
| } |
| |
| /* |
| * End-of-stream processing. |
| */ |
| static void |
| bbstreamer_lz4_compressor_finalize(bbstreamer *streamer) |
| { |
| bbstreamer_lz4_frame *mystreamer; |
| uint8 *next_out; |
| size_t footer_bound, |
| compressed_size, |
| avail_out; |
| |
| mystreamer = (bbstreamer_lz4_frame *) streamer; |
| |
| /* Find out the footer bound and update the output buffer. */ |
| footer_bound = LZ4F_compressBound(0, &mystreamer->prefs); |
| if ((mystreamer->base.bbs_buffer.maxlen - mystreamer->bytes_written) < |
| footer_bound) |
| { |
| bbstreamer_content(mystreamer->base.bbs_next, NULL, |
| mystreamer->base.bbs_buffer.data, |
| mystreamer->bytes_written, |
| BBSTREAMER_UNKNOWN); |
| |
| /* Enlarge buffer if it falls short of footer bound. */ |
| if (mystreamer->base.bbs_buffer.maxlen < footer_bound) |
| enlargeStringInfo(&mystreamer->base.bbs_buffer, footer_bound); |
| |
| avail_out = mystreamer->base.bbs_buffer.maxlen; |
| mystreamer->bytes_written = 0; |
| next_out = (uint8 *) mystreamer->base.bbs_buffer.data; |
| } |
| else |
| { |
| next_out = (uint8 *) mystreamer->base.bbs_buffer.data + mystreamer->bytes_written; |
| avail_out = mystreamer->base.bbs_buffer.maxlen - mystreamer->bytes_written; |
| } |
| |
| /* |
| * Finalize the frame and flush whatever data remaining in compression |
| * context. |
| */ |
| compressed_size = LZ4F_compressEnd(mystreamer->cctx, |
| next_out, avail_out, NULL); |
| |
| if (LZ4F_isError(compressed_size)) |
| pg_log_error("could not end lz4 compression: %s", |
| LZ4F_getErrorName(compressed_size)); |
| |
| mystreamer->bytes_written += compressed_size; |
| |
| bbstreamer_content(mystreamer->base.bbs_next, NULL, |
| mystreamer->base.bbs_buffer.data, |
| mystreamer->bytes_written, |
| BBSTREAMER_UNKNOWN); |
| |
| bbstreamer_finalize(mystreamer->base.bbs_next); |
| } |
| |
| /* |
| * Free memory. |
| */ |
| static void |
| bbstreamer_lz4_compressor_free(bbstreamer *streamer) |
| { |
| bbstreamer_lz4_frame *mystreamer; |
| |
| mystreamer = (bbstreamer_lz4_frame *) streamer; |
| bbstreamer_free(streamer->bbs_next); |
| LZ4F_freeCompressionContext(mystreamer->cctx); |
| pfree(streamer->bbs_buffer.data); |
| pfree(streamer); |
| } |
| #endif |
| |
| /* |
| * Create a new base backup streamer that performs decompression of lz4 |
| * compressed blocks. |
| */ |
| bbstreamer * |
| bbstreamer_lz4_decompressor_new(bbstreamer *next) |
| { |
| #ifdef USE_LZ4 |
| bbstreamer_lz4_frame *streamer; |
| LZ4F_errorCode_t ctxError; |
| |
| Assert(next != NULL); |
| |
| streamer = palloc0(sizeof(bbstreamer_lz4_frame)); |
| *((const bbstreamer_ops **) &streamer->base.bbs_ops) = |
| &bbstreamer_lz4_decompressor_ops; |
| |
| streamer->base.bbs_next = next; |
| initStringInfo(&streamer->base.bbs_buffer); |
| |
| /* Initialize internal stream state for decompression */ |
| ctxError = LZ4F_createDecompressionContext(&streamer->dctx, LZ4F_VERSION); |
| if (LZ4F_isError(ctxError)) |
| pg_fatal("could not initialize compression library: %s", |
| LZ4F_getErrorName(ctxError)); |
| |
| return &streamer->base; |
| #else |
| pg_fatal("this build does not support compression with %s", "LZ4"); |
| return NULL; /* keep compiler quiet */ |
| #endif |
| } |
| |
| #ifdef USE_LZ4 |
| /* |
| * Decompress the input data to output buffer until we run out of input |
| * data. Each time the output buffer is full, pass on the decompressed data |
| * to the next streamer. |
| */ |
| static void |
| bbstreamer_lz4_decompressor_content(bbstreamer *streamer, |
| bbstreamer_member *member, |
| const char *data, int len, |
| bbstreamer_archive_context context) |
| { |
| bbstreamer_lz4_frame *mystreamer; |
| uint8 *next_in, |
| *next_out; |
| size_t avail_in, |
| avail_out; |
| |
| mystreamer = (bbstreamer_lz4_frame *) streamer; |
| next_in = (uint8 *) data; |
| next_out = (uint8 *) mystreamer->base.bbs_buffer.data; |
| avail_in = len; |
| avail_out = mystreamer->base.bbs_buffer.maxlen; |
| |
| while (avail_in > 0) |
| { |
| size_t ret, |
| read_size, |
| out_size; |
| |
| read_size = avail_in; |
| out_size = avail_out; |
| |
| /* |
| * This call decompresses the data starting at next_in and generates |
| * the output data starting at next_out. It expects the caller to |
| * provide size of the input buffer and total capacity of the output |
| * buffer by providing the read_size and out_size parameters |
| * respectively. |
| * |
| * Per the documentation of LZ4, parameters read_size and out_size |
| * behaves as dual parameters. On return, the number of bytes consumed |
| * from the input buffer will be written back to read_size and the |
| * number of bytes decompressed to output buffer will be written back |
| * to out_size respectively. |
| */ |
| ret = LZ4F_decompress(mystreamer->dctx, |
| next_out, &out_size, |
| next_in, &read_size, NULL); |
| |
| if (LZ4F_isError(ret)) |
| pg_log_error("could not decompress data: %s", |
| LZ4F_getErrorName(ret)); |
| |
| /* Update input buffer based on number of bytes consumed */ |
| avail_in -= read_size; |
| next_in += read_size; |
| |
| mystreamer->bytes_written += out_size; |
| |
| /* |
| * If output buffer is full then forward the content to next streamer |
| * and update the output buffer. |
| */ |
| if (mystreamer->bytes_written >= mystreamer->base.bbs_buffer.maxlen) |
| { |
| bbstreamer_content(mystreamer->base.bbs_next, member, |
| mystreamer->base.bbs_buffer.data, |
| mystreamer->base.bbs_buffer.maxlen, |
| context); |
| |
| avail_out = mystreamer->base.bbs_buffer.maxlen; |
| mystreamer->bytes_written = 0; |
| next_out = (uint8 *) mystreamer->base.bbs_buffer.data; |
| } |
| else |
| { |
| avail_out = mystreamer->base.bbs_buffer.maxlen - mystreamer->bytes_written; |
| next_out += mystreamer->bytes_written; |
| } |
| } |
| } |
| |
| /* |
| * End-of-stream processing. |
| */ |
| static void |
| bbstreamer_lz4_decompressor_finalize(bbstreamer *streamer) |
| { |
| bbstreamer_lz4_frame *mystreamer; |
| |
| mystreamer = (bbstreamer_lz4_frame *) streamer; |
| |
| /* |
| * End of the stream, if there is some pending data in output buffers then |
| * we must forward it to next streamer. |
| */ |
| bbstreamer_content(mystreamer->base.bbs_next, NULL, |
| mystreamer->base.bbs_buffer.data, |
| mystreamer->base.bbs_buffer.maxlen, |
| BBSTREAMER_UNKNOWN); |
| |
| bbstreamer_finalize(mystreamer->base.bbs_next); |
| } |
| |
| /* |
| * Free memory. |
| */ |
| static void |
| bbstreamer_lz4_decompressor_free(bbstreamer *streamer) |
| { |
| bbstreamer_lz4_frame *mystreamer; |
| |
| mystreamer = (bbstreamer_lz4_frame *) streamer; |
| bbstreamer_free(streamer->bbs_next); |
| LZ4F_freeDecompressionContext(mystreamer->dctx); |
| pfree(streamer->bbs_buffer.data); |
| pfree(streamer); |
| } |
| #endif |