mirror of https://github.com/postgres/postgres
Expand pg_dump's compression streaming and file APIs to support the lz4 algorithm. The newly added compress_lz4.{c,h} files cover all the functionality of the aforementioned APIs. Minor changes were necessary in various pg_backup_* files, where code for the 'lz4' file suffix has been added, as well as pg_dump's compression option parsing. Author: Georgios Kokolatos Reviewed-by: Michael Paquier, Rachel Heaton, Justin Pryzby, Shi Yu, Tomas Vondra Discussion: https://postgr.es/m/faUNEOpts9vunEaLnmxmG-DldLSg_ql137OC3JYDmgrOMHm1RvvWY2IdBkv_CRxm5spCCb_OmKNk2T03TMm0fBEWveFF9wA1WizPuAgB7Ss%3D%40protonmail.compull/131/head
parent
e0b3074e89
commit
0da243fed0
@ -0,0 +1,626 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* compress_lz4.c |
||||
* Routines for archivers to write a LZ4 compressed data stream. |
||||
* |
||||
* Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group |
||||
* Portions Copyright (c) 1994, Regents of the University of California |
||||
* |
||||
* IDENTIFICATION |
||||
* src/bin/pg_dump/compress_lz4.c |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#include "postgres_fe.h" |
||||
#include "pg_backup_utils.h" |
||||
|
||||
#include "compress_lz4.h" |
||||
|
||||
#ifdef USE_LZ4 |
||||
#include <lz4.h> |
||||
#include <lz4frame.h> |
||||
|
||||
#define LZ4_OUT_SIZE (4 * 1024) |
||||
#define LZ4_IN_SIZE (16 * 1024) |
||||
|
||||
/*
|
||||
* LZ4F_HEADER_SIZE_MAX first appeared in v1.7.5 of the library. |
||||
* Redefine it for installations with a lesser version. |
||||
*/ |
||||
#ifndef LZ4F_HEADER_SIZE_MAX |
||||
#define LZ4F_HEADER_SIZE_MAX 32 |
||||
#endif |
||||
|
||||
/*----------------------
|
||||
* Compressor API |
||||
*---------------------- |
||||
*/ |
||||
|
||||
typedef struct LZ4CompressorState |
||||
{ |
||||
char *outbuf; |
||||
size_t outsize; |
||||
} LZ4CompressorState; |
||||
|
||||
/* Private routines that support LZ4 compressed data I/O */ |
||||
static void ReadDataFromArchiveLZ4(ArchiveHandle *AH, CompressorState *cs); |
||||
static void WriteDataToArchiveLZ4(ArchiveHandle *AH, CompressorState *cs, |
||||
const void *data, size_t dLen); |
||||
static void EndCompressorLZ4(ArchiveHandle *AH, CompressorState *cs); |
||||
|
||||
static void |
||||
ReadDataFromArchiveLZ4(ArchiveHandle *AH, CompressorState *cs) |
||||
{ |
||||
LZ4_streamDecode_t lz4StreamDecode; |
||||
char *buf; |
||||
char *decbuf; |
||||
size_t buflen; |
||||
size_t cnt; |
||||
|
||||
buflen = LZ4_IN_SIZE; |
||||
buf = pg_malloc(buflen); |
||||
decbuf = pg_malloc(buflen); |
||||
|
||||
LZ4_setStreamDecode(&lz4StreamDecode, NULL, 0); |
||||
|
||||
while ((cnt = cs->readF(AH, &buf, &buflen))) |
||||
{ |
||||
int decBytes = LZ4_decompress_safe_continue(&lz4StreamDecode, |
||||
buf, decbuf, |
||||
cnt, buflen); |
||||
|
||||
ahwrite(decbuf, 1, decBytes, AH); |
||||
} |
||||
|
||||
pg_free(buf); |
||||
pg_free(decbuf); |
||||
} |
||||
|
||||
static void |
||||
WriteDataToArchiveLZ4(ArchiveHandle *AH, CompressorState *cs, |
||||
const void *data, size_t dLen) |
||||
{ |
||||
LZ4CompressorState *LZ4cs = (LZ4CompressorState *) cs->private_data; |
||||
size_t compressed; |
||||
size_t requiredsize = LZ4_compressBound(dLen); |
||||
|
||||
if (requiredsize > LZ4cs->outsize) |
||||
{ |
||||
LZ4cs->outbuf = pg_realloc(LZ4cs->outbuf, requiredsize); |
||||
LZ4cs->outsize = requiredsize; |
||||
} |
||||
|
||||
compressed = LZ4_compress_default(data, LZ4cs->outbuf, |
||||
dLen, LZ4cs->outsize); |
||||
|
||||
if (compressed <= 0) |
||||
pg_fatal("failed to LZ4 compress data"); |
||||
|
||||
cs->writeF(AH, LZ4cs->outbuf, compressed); |
||||
} |
||||
|
||||
static void |
||||
EndCompressorLZ4(ArchiveHandle *AH, CompressorState *cs) |
||||
{ |
||||
LZ4CompressorState *LZ4cs; |
||||
|
||||
LZ4cs = (LZ4CompressorState *) cs->private_data; |
||||
if (LZ4cs) |
||||
{ |
||||
pg_free(LZ4cs->outbuf); |
||||
pg_free(LZ4cs); |
||||
cs->private_data = NULL; |
||||
} |
||||
} |
||||
|
||||
|
||||
/*
|
||||
* Public routines that support LZ4 compressed data I/O |
||||
*/ |
||||
void |
||||
InitCompressorLZ4(CompressorState *cs, const pg_compress_specification compression_spec) |
||||
{ |
||||
cs->readData = ReadDataFromArchiveLZ4; |
||||
cs->writeData = WriteDataToArchiveLZ4; |
||||
cs->end = EndCompressorLZ4; |
||||
|
||||
cs->compression_spec = compression_spec; |
||||
|
||||
/* Will be lazy init'd */ |
||||
cs->private_data = pg_malloc0(sizeof(LZ4CompressorState)); |
||||
} |
||||
|
||||
/*----------------------
|
||||
* Compress File API |
||||
*---------------------- |
||||
*/ |
||||
|
||||
/*
|
||||
* State needed for LZ4 (de)compression using the CompressFileHandle API. |
||||
*/ |
||||
typedef struct LZ4File |
||||
{ |
||||
FILE *fp; |
||||
|
||||
LZ4F_preferences_t prefs; |
||||
|
||||
LZ4F_compressionContext_t ctx; |
||||
LZ4F_decompressionContext_t dtx; |
||||
|
||||
bool inited; |
||||
bool compressing; |
||||
|
||||
size_t buflen; |
||||
char *buffer; |
||||
|
||||
size_t overflowalloclen; |
||||
size_t overflowlen; |
||||
char *overflowbuf; |
||||
|
||||
size_t errcode; |
||||
} LZ4File; |
||||
|
||||
/*
|
||||
* LZ4 equivalent to feof() or gzeof(). The end of file is reached if there |
||||
* is no decompressed output in the overflow buffer and the end of the file |
||||
* is reached. |
||||
*/ |
||||
static int |
||||
LZ4File_eof(CompressFileHandle *CFH) |
||||
{ |
||||
LZ4File *fs = (LZ4File *) CFH->private_data; |
||||
|
||||
return fs->overflowlen == 0 && feof(fs->fp); |
||||
} |
||||
|
||||
static const char * |
||||
LZ4File_get_error(CompressFileHandle *CFH) |
||||
{ |
||||
LZ4File *fs = (LZ4File *) CFH->private_data; |
||||
const char *errmsg; |
||||
|
||||
if (LZ4F_isError(fs->errcode)) |
||||
errmsg = LZ4F_getErrorName(fs->errcode); |
||||
else |
||||
errmsg = strerror(errno); |
||||
|
||||
return errmsg; |
||||
} |
||||
|
||||
/*
|
||||
* Prepare an already alloc'ed LZ4File struct for subsequent calls. |
||||
* |
||||
* It creates the necessary contexts for the operations. When compressing, |
||||
* it additionally writes the LZ4 header in the output stream. |
||||
*/ |
||||
static int |
||||
LZ4File_init(LZ4File *fs, int size, bool compressing) |
||||
{ |
||||
size_t status; |
||||
|
||||
if (fs->inited) |
||||
return 0; |
||||
|
||||
fs->compressing = compressing; |
||||
fs->inited = true; |
||||
|
||||
if (fs->compressing) |
||||
{ |
||||
fs->buflen = LZ4F_compressBound(LZ4_IN_SIZE, &fs->prefs); |
||||
if (fs->buflen < LZ4F_HEADER_SIZE_MAX) |
||||
fs->buflen = LZ4F_HEADER_SIZE_MAX; |
||||
|
||||
status = LZ4F_createCompressionContext(&fs->ctx, LZ4F_VERSION); |
||||
if (LZ4F_isError(status)) |
||||
{ |
||||
fs->errcode = status; |
||||
return 1; |
||||
} |
||||
|
||||
fs->buffer = pg_malloc(fs->buflen); |
||||
status = LZ4F_compressBegin(fs->ctx, fs->buffer, fs->buflen, |
||||
&fs->prefs); |
||||
|
||||
if (LZ4F_isError(status)) |
||||
{ |
||||
fs->errcode = status; |
||||
return 1; |
||||
} |
||||
|
||||
if (fwrite(fs->buffer, 1, status, fs->fp) != status) |
||||
{ |
||||
errno = (errno) ? errno : ENOSPC; |
||||
return 1; |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
status = LZ4F_createDecompressionContext(&fs->dtx, LZ4F_VERSION); |
||||
if (LZ4F_isError(status)) |
||||
{ |
||||
fs->errcode = status; |
||||
return 1; |
||||
} |
||||
|
||||
fs->buflen = size > LZ4_OUT_SIZE ? size : LZ4_OUT_SIZE; |
||||
fs->buffer = pg_malloc(fs->buflen); |
||||
|
||||
fs->overflowalloclen = fs->buflen; |
||||
fs->overflowbuf = pg_malloc(fs->overflowalloclen); |
||||
fs->overflowlen = 0; |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
/*
|
||||
* Read already decompressed content from the overflow buffer into 'ptr' up to |
||||
* 'size' bytes, if available. If the eol_flag is set, then stop at the first |
||||
* occurrence of the new line char prior to 'size' bytes. |
||||
* |
||||
* Any unread content in the overflow buffer is moved to the beginning. |
||||
*/ |
||||
static int |
||||
LZ4File_read_overflow(LZ4File *fs, void *ptr, int size, bool eol_flag) |
||||
{ |
||||
char *p; |
||||
int readlen = 0; |
||||
|
||||
if (fs->overflowlen == 0) |
||||
return 0; |
||||
|
||||
if (fs->overflowlen >= size) |
||||
readlen = size; |
||||
else |
||||
readlen = fs->overflowlen; |
||||
|
||||
if (eol_flag && (p = memchr(fs->overflowbuf, '\n', readlen))) |
||||
/* Include the line terminating char */ |
||||
readlen = p - fs->overflowbuf + 1; |
||||
|
||||
memcpy(ptr, fs->overflowbuf, readlen); |
||||
fs->overflowlen -= readlen; |
||||
|
||||
if (fs->overflowlen > 0) |
||||
memmove(fs->overflowbuf, fs->overflowbuf + readlen, fs->overflowlen); |
||||
|
||||
return readlen; |
||||
} |
||||
|
||||
/*
|
||||
* The workhorse for reading decompressed content out of an LZ4 compressed |
||||
* stream. |
||||
* |
||||
* It will read up to 'ptrsize' decompressed content, or up to the new line |
||||
* char if found first when the eol_flag is set. It is possible that the |
||||
* decompressed output generated by reading any compressed input via the |
||||
* LZ4F API, exceeds 'ptrsize'. Any exceeding decompressed content is stored |
||||
* at an overflow buffer within LZ4File. Of course, when the function is |
||||
* called, it will first try to consume any decompressed content already |
||||
* present in the overflow buffer, before decompressing new content. |
||||
*/ |
||||
static int |
||||
LZ4File_read_internal(LZ4File *fs, void *ptr, int ptrsize, bool eol_flag) |
||||
{ |
||||
size_t dsize = 0; |
||||
size_t rsize; |
||||
size_t size = ptrsize; |
||||
bool eol_found = false; |
||||
|
||||
void *readbuf; |
||||
|
||||
/* Lazy init */ |
||||
if (LZ4File_init(fs, size, false /* decompressing */ )) |
||||
return -1; |
||||
|
||||
/* Verify that there is enough space in the outbuf */ |
||||
if (size > fs->buflen) |
||||
{ |
||||
fs->buflen = size; |
||||
fs->buffer = pg_realloc(fs->buffer, size); |
||||
} |
||||
|
||||
/* use already decompressed content if available */ |
||||
dsize = LZ4File_read_overflow(fs, ptr, size, eol_flag); |
||||
if (dsize == size || (eol_flag && memchr(ptr, '\n', dsize))) |
||||
return dsize; |
||||
|
||||
readbuf = pg_malloc(size); |
||||
|
||||
do |
||||
{ |
||||
char *rp; |
||||
char *rend; |
||||
|
||||
rsize = fread(readbuf, 1, size, fs->fp); |
||||
if (rsize < size && !feof(fs->fp)) |
||||
return -1; |
||||
|
||||
rp = (char *) readbuf; |
||||
rend = (char *) readbuf + rsize; |
||||
|
||||
while (rp < rend) |
||||
{ |
||||
size_t status; |
||||
size_t outlen = fs->buflen; |
||||
size_t read_remain = rend - rp; |
||||
|
||||
memset(fs->buffer, 0, outlen); |
||||
status = LZ4F_decompress(fs->dtx, fs->buffer, &outlen, |
||||
rp, &read_remain, NULL); |
||||
if (LZ4F_isError(status)) |
||||
{ |
||||
fs->errcode = status; |
||||
return -1; |
||||
} |
||||
|
||||
rp += read_remain; |
||||
|
||||
/*
|
||||
* fill in what space is available in ptr if the eol flag is set, |
||||
* either skip if one already found or fill up to EOL if present |
||||
* in the outbuf |
||||
*/ |
||||
if (outlen > 0 && dsize < size && eol_found == false) |
||||
{ |
||||
char *p; |
||||
size_t lib = (!eol_flag) ? size - dsize : size - 1 - dsize; |
||||
size_t len = outlen < lib ? outlen : lib; |
||||
|
||||
if (eol_flag && |
||||
(p = memchr(fs->buffer, '\n', outlen)) && |
||||
(size_t) (p - fs->buffer + 1) <= len) |
||||
{ |
||||
len = p - fs->buffer + 1; |
||||
eol_found = true; |
||||
} |
||||
|
||||
memcpy((char *) ptr + dsize, fs->buffer, len); |
||||
dsize += len; |
||||
|
||||
/* move what did not fit, if any, at the beginning of the buf */ |
||||
if (len < outlen) |
||||
memmove(fs->buffer, fs->buffer + len, outlen - len); |
||||
outlen -= len; |
||||
} |
||||
|
||||
/* if there is available output, save it */ |
||||
if (outlen > 0) |
||||
{ |
||||
while (fs->overflowlen + outlen > fs->overflowalloclen) |
||||
{ |
||||
fs->overflowalloclen *= 2; |
||||
fs->overflowbuf = pg_realloc(fs->overflowbuf, |
||||
fs->overflowalloclen); |
||||
} |
||||
|
||||
memcpy(fs->overflowbuf + fs->overflowlen, fs->buffer, outlen); |
||||
fs->overflowlen += outlen; |
||||
} |
||||
} |
||||
} while (rsize == size && dsize < size && eol_found == 0); |
||||
|
||||
pg_free(readbuf); |
||||
|
||||
return (int) dsize; |
||||
} |
||||
|
||||
/*
|
||||
* Compress size bytes from ptr and write them to the stream. |
||||
*/ |
||||
static size_t |
||||
LZ4File_write(const void *ptr, size_t size, CompressFileHandle *CFH) |
||||
{ |
||||
LZ4File *fs = (LZ4File *) CFH->private_data; |
||||
size_t status; |
||||
int remaining = size; |
||||
|
||||
/* Lazy init */ |
||||
if (LZ4File_init(fs, size, true)) |
||||
return -1; |
||||
|
||||
while (remaining > 0) |
||||
{ |
||||
int chunk = remaining < LZ4_IN_SIZE ? remaining : LZ4_IN_SIZE; |
||||
|
||||
remaining -= chunk; |
||||
|
||||
status = LZ4F_compressUpdate(fs->ctx, fs->buffer, fs->buflen, |
||||
ptr, chunk, NULL); |
||||
if (LZ4F_isError(status)) |
||||
{ |
||||
fs->errcode = status; |
||||
return -1; |
||||
} |
||||
|
||||
if (fwrite(fs->buffer, 1, status, fs->fp) != status) |
||||
{ |
||||
errno = (errno) ? errno : ENOSPC; |
||||
return 1; |
||||
} |
||||
} |
||||
|
||||
return size; |
||||
} |
||||
|
||||
/*
|
||||
* fread() equivalent implementation for LZ4 compressed files. |
||||
*/ |
||||
static size_t |
||||
LZ4File_read(void *ptr, size_t size, CompressFileHandle *CFH) |
||||
{ |
||||
LZ4File *fs = (LZ4File *) CFH->private_data; |
||||
int ret; |
||||
|
||||
ret = LZ4File_read_internal(fs, ptr, size, false); |
||||
if (ret != size && !LZ4File_eof(CFH)) |
||||
pg_fatal("could not read from input file: %s", LZ4File_get_error(CFH)); |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
/*
|
||||
* fgetc() equivalent implementation for LZ4 compressed files. |
||||
*/ |
||||
static int |
||||
LZ4File_getc(CompressFileHandle *CFH) |
||||
{ |
||||
LZ4File *fs = (LZ4File *) CFH->private_data; |
||||
unsigned char c; |
||||
|
||||
if (LZ4File_read_internal(fs, &c, 1, false) != 1) |
||||
{ |
||||
if (!LZ4File_eof(CFH)) |
||||
pg_fatal("could not read from input file: %s", LZ4File_get_error(CFH)); |
||||
else |
||||
pg_fatal("could not read from input file: end of file"); |
||||
} |
||||
|
||||
return c; |
||||
} |
||||
|
||||
/*
|
||||
* fgets() equivalent implementation for LZ4 compressed files. |
||||
*/ |
||||
static char * |
||||
LZ4File_gets(char *ptr, int size, CompressFileHandle *CFH) |
||||
{ |
||||
LZ4File *fs = (LZ4File *) CFH->private_data; |
||||
size_t dsize; |
||||
|
||||
dsize = LZ4File_read_internal(fs, ptr, size, true); |
||||
if (dsize < 0) |
||||
pg_fatal("could not read from input file: %s", LZ4File_get_error(CFH)); |
||||
|
||||
/* Done reading */ |
||||
if (dsize == 0) |
||||
return NULL; |
||||
|
||||
return ptr; |
||||
} |
||||
|
||||
/*
|
||||
* Finalize (de)compression of a stream. When compressing it will write any |
||||
* remaining content and/or generated footer from the LZ4 API. |
||||
*/ |
||||
static int |
||||
LZ4File_close(CompressFileHandle *CFH) |
||||
{ |
||||
FILE *fp; |
||||
LZ4File *fs = (LZ4File *) CFH->private_data; |
||||
size_t status; |
||||
int ret; |
||||
|
||||
fp = fs->fp; |
||||
if (fs->inited) |
||||
{ |
||||
if (fs->compressing) |
||||
{ |
||||
status = LZ4F_compressEnd(fs->ctx, fs->buffer, fs->buflen, NULL); |
||||
if (LZ4F_isError(status)) |
||||
pg_fatal("failed to end compression: %s", |
||||
LZ4F_getErrorName(status)); |
||||
else if ((ret = fwrite(fs->buffer, 1, status, fs->fp)) != status) |
||||
{ |
||||
errno = (errno) ? errno : ENOSPC; |
||||
WRITE_ERROR_EXIT; |
||||
} |
||||
|
||||
status = LZ4F_freeCompressionContext(fs->ctx); |
||||
if (LZ4F_isError(status)) |
||||
pg_fatal("failed to end compression: %s", |
||||
LZ4F_getErrorName(status)); |
||||
} |
||||
else |
||||
{ |
||||
status = LZ4F_freeDecompressionContext(fs->dtx); |
||||
if (LZ4F_isError(status)) |
||||
pg_fatal("failed to end decompression: %s", |
||||
LZ4F_getErrorName(status)); |
||||
pg_free(fs->overflowbuf); |
||||
} |
||||
|
||||
pg_free(fs->buffer); |
||||
} |
||||
|
||||
pg_free(fs); |
||||
|
||||
return fclose(fp); |
||||
} |
||||
|
||||
static int |
||||
LZ4File_open(const char *path, int fd, const char *mode, |
||||
CompressFileHandle *CFH) |
||||
{ |
||||
FILE *fp; |
||||
LZ4File *lz4fp = (LZ4File *) CFH->private_data; |
||||
|
||||
if (fd >= 0) |
||||
fp = fdopen(fd, mode); |
||||
else |
||||
fp = fopen(path, mode); |
||||
if (fp == NULL) |
||||
{ |
||||
lz4fp->errcode = errno; |
||||
return 1; |
||||
} |
||||
|
||||
lz4fp->fp = fp; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int |
||||
LZ4File_open_write(const char *path, const char *mode, CompressFileHandle *CFH) |
||||
{ |
||||
char *fname; |
||||
int ret; |
||||
|
||||
fname = psprintf("%s.lz4", path); |
||||
ret = CFH->open_func(fname, -1, mode, CFH); |
||||
pg_free(fname); |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
/*
|
||||
* Public routines |
||||
*/ |
||||
void |
||||
InitCompressFileHandleLZ4(CompressFileHandle *CFH, |
||||
const pg_compress_specification compression_spec) |
||||
{ |
||||
LZ4File *lz4fp; |
||||
|
||||
CFH->open_func = LZ4File_open; |
||||
CFH->open_write_func = LZ4File_open_write; |
||||
CFH->read_func = LZ4File_read; |
||||
CFH->write_func = LZ4File_write; |
||||
CFH->gets_func = LZ4File_gets; |
||||
CFH->getc_func = LZ4File_getc; |
||||
CFH->eof_func = LZ4File_eof; |
||||
CFH->close_func = LZ4File_close; |
||||
CFH->get_error_func = LZ4File_get_error; |
||||
|
||||
CFH->compression_spec = compression_spec; |
||||
lz4fp = pg_malloc0(sizeof(*lz4fp)); |
||||
if (CFH->compression_spec.level >= 0) |
||||
lz4fp->prefs.compressionLevel = CFH->compression_spec.level; |
||||
|
||||
CFH->private_data = lz4fp; |
||||
} |
||||
#else /* USE_LZ4 */ |
||||
void |
||||
InitCompressorLZ4(CompressorState *cs, |
||||
const pg_compress_specification compression_spec) |
||||
{ |
||||
pg_fatal("this build does not support compression with %s", "LZ4"); |
||||
} |
||||
|
||||
void |
||||
InitCompressFileHandleLZ4(CompressFileHandle *CFH, |
||||
const pg_compress_specification compression_spec) |
||||
{ |
||||
pg_fatal("this build does not support compression with %s", "LZ4"); |
||||
} |
||||
#endif /* USE_LZ4 */ |
@ -0,0 +1,24 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* compress_lz4.h |
||||
* LZ4 interface to compress_io.c routines |
||||
* |
||||
* Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group |
||||
* Portions Copyright (c) 1994, Regents of the University of California |
||||
* |
||||
* IDENTIFICATION |
||||
* src/bin/pg_dump/compress_lz4.h |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#ifndef _COMPRESS_LZ4_H_ |
||||
#define _COMPRESS_LZ4_H_ |
||||
|
||||
#include "compress_io.h" |
||||
|
||||
extern void InitCompressorLZ4(CompressorState *cs, |
||||
const pg_compress_specification compression_spec); |
||||
extern void InitCompressFileHandleLZ4(CompressFileHandle *CFH, |
||||
const pg_compress_specification compression_spec); |
||||
|
||||
#endif /* _COMPRESS_LZ4_H_ */ |
Loading…
Reference in new issue