Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * compress_lz4.c
4 : : * Routines for archivers to write a LZ4 compressed data stream.
5 : : *
6 : : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : * IDENTIFICATION
10 : : * src/bin/pg_dump/compress_lz4.c
11 : : *
12 : : *-------------------------------------------------------------------------
13 : : */
14 : : #include "postgres_fe.h"
15 : : #include <unistd.h>
16 : :
17 : : #include "compress_lz4.h"
18 : : #include "pg_backup_utils.h"
19 : :
20 : : #ifdef USE_LZ4
21 : : #include <lz4frame.h>
22 : :
23 : : /*
24 : : * LZ4F_HEADER_SIZE_MAX first appeared in v1.7.5 of the library.
25 : : * Redefine it for installations with a lesser version.
26 : : */
27 : : #ifndef LZ4F_HEADER_SIZE_MAX
28 : : #define LZ4F_HEADER_SIZE_MAX 32
29 : : #endif
30 : :
31 : : /*---------------------------------
32 : : * Common to both compression APIs
33 : : *---------------------------------
34 : : */
35 : :
36 : : /*
37 : : * (de)compression state used by both the Compressor and Stream APIs.
38 : : */
39 : : typedef struct LZ4State
40 : : {
41 : : /*
42 : : * Used by the Stream API to keep track of the file stream.
43 : : */
44 : : FILE *fp;
45 : :
46 : : LZ4F_preferences_t prefs;
47 : :
48 : : LZ4F_compressionContext_t ctx;
49 : : LZ4F_decompressionContext_t dtx;
50 : :
51 : : /*
52 : : * Used by the Stream API's lazy initialization.
53 : : */
54 : : bool inited;
55 : :
56 : : /*
57 : : * Used by the Stream API to distinguish between compression and
58 : : * decompression operations.
59 : : */
60 : : bool compressing;
61 : :
62 : : /*
63 : : * I/O buffer area.
64 : : */
65 : : char *buffer; /* buffer for compressed data */
66 : : size_t buflen; /* allocated size of buffer */
67 : : size_t bufdata; /* amount of valid data currently in buffer */
68 : : /* These fields are used only while decompressing: */
69 : : size_t bufnext; /* next buffer position to decompress */
70 : : char *outbuf; /* buffer for decompressed data */
71 : : size_t outbuflen; /* allocated size of outbuf */
72 : : size_t outbufdata; /* amount of valid data currently in outbuf */
73 : : size_t outbufnext; /* next outbuf position to return */
74 : :
75 : : /*
76 : : * Used by both APIs to keep track of error codes.
77 : : */
78 : : size_t errcode;
79 : : } LZ4State;
80 : :
81 : : /*
82 : : * LZ4State_compression_init
83 : : * Initialize the required LZ4State members for compression.
84 : : *
85 : : * Write the LZ4 frame header in a buffer keeping track of its length. Users of
86 : : * this function can choose when and how to write the header to a file stream.
87 : : *
88 : : * Returns true on success. In case of a failure returns false, and stores the
89 : : * error code in state->errcode.
90 : : */
91 : : static bool
940 tomas.vondra@postgre 92 :CBC 7 : LZ4State_compression_init(LZ4State *state)
93 : : {
94 : : size_t status;
95 : :
96 : : /*
97 : : * Compute size needed for buffer, assuming we will present at most
98 : : * DEFAULT_IO_BUFFER_SIZE input bytes at a time.
99 : : */
100 : 7 : state->buflen = LZ4F_compressBound(DEFAULT_IO_BUFFER_SIZE, &state->prefs);
101 : :
102 : : /*
103 : : * Add some slop to ensure we're not forced to flush every time.
104 : : *
105 : : * The present slop factor of 50% is chosen so that the typical output
106 : : * block size is about 128K when DEFAULT_IO_BUFFER_SIZE = 128K. We might
107 : : * need a different slop factor to maintain that equivalence if
108 : : * DEFAULT_IO_BUFFER_SIZE is changed dramatically.
109 : : */
11 tgl@sss.pgh.pa.us 110 :GNC 7 : state->buflen += state->buflen / 2;
111 : :
112 : : /*
113 : : * LZ4F_compressBegin requires a buffer that is greater or equal to
114 : : * LZ4F_HEADER_SIZE_MAX. Verify that the requirement is met.
115 : : */
940 tomas.vondra@postgre 116 [ - + ]:CBC 7 : if (state->buflen < LZ4F_HEADER_SIZE_MAX)
940 tomas.vondra@postgre 117 :UBC 0 : state->buflen = LZ4F_HEADER_SIZE_MAX;
118 : :
940 tomas.vondra@postgre 119 :CBC 7 : status = LZ4F_createCompressionContext(&state->ctx, LZ4F_VERSION);
120 [ - + ]: 7 : if (LZ4F_isError(status))
121 : : {
940 tomas.vondra@postgre 122 :UBC 0 : state->errcode = status;
123 : 0 : return false;
124 : : }
125 : :
940 tomas.vondra@postgre 126 :CBC 7 : state->buffer = pg_malloc(state->buflen);
127 : :
128 : : /*
129 : : * Insert LZ4 header into buffer.
130 : : */
131 : 7 : status = LZ4F_compressBegin(state->ctx,
132 : 7 : state->buffer, state->buflen,
133 : 7 : &state->prefs);
134 [ - + ]: 7 : if (LZ4F_isError(status))
135 : : {
940 tomas.vondra@postgre 136 :UBC 0 : state->errcode = status;
137 : 0 : return false;
138 : : }
139 : :
14 tgl@sss.pgh.pa.us 140 :GNC 7 : state->bufdata = status;
141 : :
940 tomas.vondra@postgre 142 :CBC 7 : return true;
143 : : }
144 : :
145 : : /*----------------------
146 : : * Compressor API
147 : : *----------------------
148 : : */
149 : :
150 : : /* Private routines that support LZ4 compressed data I/O */
151 : :
152 : : static void
977 153 : 3 : ReadDataFromArchiveLZ4(ArchiveHandle *AH, CompressorState *cs)
154 : : {
155 : : size_t r;
156 : : size_t readbuflen;
157 : : char *outbuf;
158 : : char *readbuf;
940 159 : 3 : LZ4F_decompressionContext_t ctx = NULL;
160 : : LZ4F_decompressOptions_t dec_opt;
161 : : LZ4F_errorCode_t status;
162 : :
163 : 3 : memset(&dec_opt, 0, sizeof(dec_opt));
164 : 3 : status = LZ4F_createDecompressionContext(&ctx, LZ4F_VERSION);
165 [ - + ]: 3 : if (LZ4F_isError(status))
940 tomas.vondra@postgre 166 :UBC 0 : pg_fatal("could not create LZ4 decompression context: %s",
167 : : LZ4F_getErrorName(status));
168 : :
14 tgl@sss.pgh.pa.us 169 :GNC 3 : outbuf = pg_malloc(DEFAULT_IO_BUFFER_SIZE);
170 : 3 : readbuf = pg_malloc(DEFAULT_IO_BUFFER_SIZE);
940 tomas.vondra@postgre 171 :CBC 3 : readbuflen = DEFAULT_IO_BUFFER_SIZE;
172 [ + + ]: 7 : while ((r = cs->readF(AH, &readbuf, &readbuflen)) > 0)
173 : : {
174 : : char *readp;
175 : : char *readend;
176 : :
177 : : /* Process one chunk */
178 : 4 : readp = readbuf;
179 : 4 : readend = readbuf + r;
180 [ + + ]: 9 : while (readp < readend)
181 : : {
182 : 5 : size_t out_size = DEFAULT_IO_BUFFER_SIZE;
183 : 5 : size_t read_size = readend - readp;
184 : :
185 : 5 : status = LZ4F_decompress(ctx, outbuf, &out_size,
186 : : readp, &read_size, &dec_opt);
187 [ - + ]: 5 : if (LZ4F_isError(status))
940 tomas.vondra@postgre 188 :UBC 0 : pg_fatal("could not decompress: %s",
189 : : LZ4F_getErrorName(status));
190 : :
940 tomas.vondra@postgre 191 :CBC 5 : ahwrite(outbuf, 1, out_size, AH);
192 : 5 : readp += read_size;
193 : : }
194 : : }
195 : :
196 : 3 : pg_free(outbuf);
197 : 3 : pg_free(readbuf);
198 : :
199 : 3 : status = LZ4F_freeDecompressionContext(ctx);
200 [ - + ]: 3 : if (LZ4F_isError(status))
940 tomas.vondra@postgre 201 :UBC 0 : pg_fatal("could not free LZ4 decompression context: %s",
202 : : LZ4F_getErrorName(status));
977 tomas.vondra@postgre 203 :CBC 3 : }
204 : :
205 : : static void
206 : 4 : WriteDataToArchiveLZ4(ArchiveHandle *AH, CompressorState *cs,
207 : : const void *data, size_t dLen)
208 : : {
940 209 : 4 : LZ4State *state = (LZ4State *) cs->private_data;
210 : 4 : size_t remaining = dLen;
211 : :
212 [ + + ]: 10 : while (remaining > 0)
213 : : {
214 : : size_t chunk;
215 : : size_t required;
216 : : size_t status;
217 : :
218 : : /* We don't try to present more than DEFAULT_IO_BUFFER_SIZE bytes */
14 tgl@sss.pgh.pa.us 219 :GNC 6 : chunk = Min(remaining, (size_t) DEFAULT_IO_BUFFER_SIZE);
220 : :
221 : : /* If not enough space, must flush buffer */
222 : 6 : required = LZ4F_compressBound(chunk, &state->prefs);
223 [ + + ]: 6 : if (required > state->buflen - state->bufdata)
224 : : {
225 : 1 : cs->writeF(AH, state->buffer, state->bufdata);
226 : 1 : state->bufdata = 0;
227 : : }
228 : :
940 tomas.vondra@postgre 229 :CBC 6 : status = LZ4F_compressUpdate(state->ctx,
14 tgl@sss.pgh.pa.us 230 :GNC 6 : state->buffer + state->bufdata,
231 : 6 : state->buflen - state->bufdata,
232 : : data, chunk, NULL);
233 : :
940 tomas.vondra@postgre 234 [ - + ]:CBC 6 : if (LZ4F_isError(status))
892 peter@eisentraut.org 235 :UBC 0 : pg_fatal("could not compress data: %s",
236 : : LZ4F_getErrorName(status));
237 : :
14 tgl@sss.pgh.pa.us 238 :GNC 6 : state->bufdata += status;
239 : :
240 : 6 : data = ((const char *) data) + chunk;
241 : 6 : remaining -= chunk;
242 : : }
977 tomas.vondra@postgre 243 :CBC 4 : }
244 : :
245 : : static void
246 : 6 : EndCompressorLZ4(ArchiveHandle *AH, CompressorState *cs)
247 : : {
940 248 : 6 : LZ4State *state = (LZ4State *) cs->private_data;
249 : : size_t required;
250 : : size_t status;
251 : :
252 : : /* Nothing needs to be done */
253 [ + + ]: 6 : if (!state)
254 : 3 : return;
255 : :
256 : : /* We might need to flush the buffer to make room for LZ4F_compressEnd */
14 tgl@sss.pgh.pa.us 257 :GNC 3 : required = LZ4F_compressBound(0, &state->prefs);
258 [ - + ]: 3 : if (required > state->buflen - state->bufdata)
259 : : {
14 tgl@sss.pgh.pa.us 260 :UNC 0 : cs->writeF(AH, state->buffer, state->bufdata);
261 : 0 : state->bufdata = 0;
262 : : }
263 : :
940 tomas.vondra@postgre 264 :CBC 3 : status = LZ4F_compressEnd(state->ctx,
14 tgl@sss.pgh.pa.us 265 :GNC 3 : state->buffer + state->bufdata,
266 : 3 : state->buflen - state->bufdata,
267 : : NULL);
940 tomas.vondra@postgre 268 [ - + ]:CBC 3 : if (LZ4F_isError(status))
892 peter@eisentraut.org 269 :UBC 0 : pg_fatal("could not end compression: %s",
270 : : LZ4F_getErrorName(status));
14 tgl@sss.pgh.pa.us 271 :GNC 3 : state->bufdata += status;
272 : :
273 : : /* Write the final bufferload */
274 : 3 : cs->writeF(AH, state->buffer, state->bufdata);
275 : :
940 tomas.vondra@postgre 276 :CBC 3 : status = LZ4F_freeCompressionContext(state->ctx);
277 [ - + ]: 3 : if (LZ4F_isError(status))
892 peter@eisentraut.org 278 :UBC 0 : pg_fatal("could not end compression: %s",
279 : : LZ4F_getErrorName(status));
280 : :
940 tomas.vondra@postgre 281 :CBC 3 : pg_free(state->buffer);
282 : 3 : pg_free(state);
283 : :
284 : 3 : cs->private_data = NULL;
285 : : }
286 : :
287 : : /*
288 : : * Public routines that support LZ4 compressed data I/O
289 : : */
290 : : void
977 291 : 6 : InitCompressorLZ4(CompressorState *cs, const pg_compress_specification compression_spec)
292 : : {
293 : : LZ4State *state;
294 : :
295 : 6 : cs->readData = ReadDataFromArchiveLZ4;
296 : 6 : cs->writeData = WriteDataToArchiveLZ4;
297 : 6 : cs->end = EndCompressorLZ4;
298 : :
299 : 6 : cs->compression_spec = compression_spec;
300 : :
301 : : /*
302 : : * Read operations have access to the whole input. No state needs to be
303 : : * carried between calls.
304 : : */
940 305 [ + + ]: 6 : if (cs->readF)
306 : 3 : return;
307 : :
308 : 3 : state = pg_malloc0(sizeof(*state));
309 [ + - ]: 3 : if (cs->compression_spec.level >= 0)
310 : 3 : state->prefs.compressionLevel = cs->compression_spec.level;
311 : :
312 [ - + ]: 3 : if (!LZ4State_compression_init(state))
940 tomas.vondra@postgre 313 :UBC 0 : pg_fatal("could not initialize LZ4 compression: %s",
314 : : LZ4F_getErrorName(state->errcode));
315 : :
940 tomas.vondra@postgre 316 :CBC 3 : cs->private_data = state;
317 : : }
318 : :
319 : : /*----------------------
320 : : * Compress Stream API
321 : : *----------------------
322 : : */
323 : :
324 : :
325 : : /*
326 : : * LZ4 equivalent to feof() or gzeof(). Return true iff there is no
327 : : * more buffered data and the end of the input file has been reached.
328 : : */
329 : : static bool
330 : 1 : LZ4Stream_eof(CompressFileHandle *CFH)
331 : : {
332 : 1 : LZ4State *state = (LZ4State *) CFH->private_data;
333 : :
14 tgl@sss.pgh.pa.us 334 :GNC 2 : return state->outbufnext >= state->outbufdata &&
335 [ + - + - : 2 : state->bufnext >= state->bufdata &&
+ - ]
336 : 1 : feof(state->fp);
337 : : }
338 : :
339 : : static const char *
940 tomas.vondra@postgre 340 :UBC 0 : LZ4Stream_get_error(CompressFileHandle *CFH)
341 : : {
342 : 0 : LZ4State *state = (LZ4State *) CFH->private_data;
343 : : const char *errmsg;
344 : :
345 [ # # ]: 0 : if (LZ4F_isError(state->errcode))
346 : 0 : errmsg = LZ4F_getErrorName(state->errcode);
347 : : else
977 348 : 0 : errmsg = strerror(errno);
349 : :
350 : 0 : return errmsg;
351 : : }
352 : :
353 : : /*
354 : : * Initialize an already alloc'ed LZ4State struct for subsequent calls.
355 : : *
356 : : * Creates the necessary contexts for either compression or decompression. When
357 : : * compressing data (indicated by compressing=true), it additionally writes the
358 : : * LZ4 header in the output buffer.
359 : : *
360 : : * It's expected that a not-yet-initialized LZ4State will be zero-filled.
361 : : *
362 : : * Returns true on success. In case of a failure returns false, and stores the
363 : : * error code in state->errcode.
364 : : */
365 : : static bool
14 tgl@sss.pgh.pa.us 366 :GNC 1834 : LZ4Stream_init(LZ4State *state, bool compressing)
367 : : {
368 : : size_t status;
369 : :
940 tomas.vondra@postgre 370 [ + + ]:CBC 1834 : if (state->inited)
949 371 : 1825 : return true;
372 : :
940 373 : 9 : state->compressing = compressing;
374 : :
375 [ + + ]: 9 : if (state->compressing)
376 : : {
377 [ - + ]: 4 : if (!LZ4State_compression_init(state))
949 tomas.vondra@postgre 378 :UBC 0 : return false;
379 : : }
380 : : else
381 : : {
940 tomas.vondra@postgre 382 :CBC 5 : status = LZ4F_createDecompressionContext(&state->dtx, LZ4F_VERSION);
977 383 [ - + ]: 5 : if (LZ4F_isError(status))
384 : : {
940 tomas.vondra@postgre 385 :UBC 0 : state->errcode = status;
949 386 : 0 : return false;
387 : : }
388 : :
14 tgl@sss.pgh.pa.us 389 :GNC 5 : state->buflen = DEFAULT_IO_BUFFER_SIZE;
940 tomas.vondra@postgre 390 :CBC 5 : state->buffer = pg_malloc(state->buflen);
14 tgl@sss.pgh.pa.us 391 :GNC 5 : state->outbuflen = DEFAULT_IO_BUFFER_SIZE;
392 : 5 : state->outbuf = pg_malloc(state->outbuflen);
393 : : }
394 : :
59 dgustafsson@postgres 395 :CBC 9 : state->inited = true;
949 tomas.vondra@postgre 396 : 9 : return true;
397 : : }
398 : :
399 : : /*
400 : : * The workhorse for reading decompressed content out of an LZ4 compressed
401 : : * stream.
402 : : *
403 : : * It will read up to 'ptrsize' decompressed content, or up to the new line
404 : : * char if one is found first when the eol_flag is set.
405 : : *
406 : : * Returns the number of bytes of decompressed data copied into the ptr
407 : : * buffer, or -1 in case of error.
408 : : */
409 : : static int
940 410 : 1741 : LZ4Stream_read_internal(LZ4State *state, void *ptr, int ptrsize, bool eol_flag)
411 : : {
949 412 : 1741 : int dsize = 0;
14 tgl@sss.pgh.pa.us 413 :GNC 1741 : int remaining = ptrsize;
414 : :
415 : : /* Lazy init */
416 [ - + ]: 1741 : if (!LZ4Stream_init(state, false /* decompressing */ ))
417 : : {
59 dgustafsson@postgres 418 :UBC 0 : pg_log_error("unable to initialize LZ4 library: %s",
419 : : LZ4F_getErrorName(state->errcode));
977 tomas.vondra@postgre 420 : 0 : return -1;
421 : : }
422 : :
423 : : /* Loop until postcondition is satisfied */
14 tgl@sss.pgh.pa.us 424 [ + + ]:GNC 1750 : while (remaining > 0)
425 : : {
426 : : /*
427 : : * If we already have some decompressed data, return that.
428 : : */
429 [ + + ]: 1734 : if (state->outbufnext < state->outbufdata)
430 : : {
431 : 1721 : char *outptr = state->outbuf + state->outbufnext;
432 : 1721 : size_t readlen = state->outbufdata - state->outbufnext;
433 : 1721 : bool eol_found = false;
434 : :
435 [ + + ]: 1721 : if (readlen > remaining)
436 : 1714 : readlen = remaining;
437 : : /* If eol_flag is set, don't read beyond a newline */
438 [ + + ]: 1721 : if (eol_flag)
439 : : {
440 : 1 : char *eolptr = memchr(outptr, '\n', readlen);
441 : :
442 [ + - ]: 1 : if (eolptr)
443 : : {
444 : 1 : readlen = eolptr - outptr + 1;
445 : 1 : eol_found = true;
446 : : }
447 : : }
448 : 1721 : memcpy(ptr, outptr, readlen);
449 : 1721 : ptr = ((char *) ptr) + readlen;
450 : 1721 : state->outbufnext += readlen;
451 : 1721 : dsize += readlen;
452 : 1721 : remaining -= readlen;
453 [ + + + + ]: 1721 : if (eol_found || remaining == 0)
454 : : break;
455 : : /* We must have emptied outbuf */
456 [ - + ]: 3 : Assert(state->outbufnext >= state->outbufdata);
457 : : }
458 : :
459 : : /*
460 : : * If we don't have any pending compressed data, load more into
461 : : * state->buffer.
462 : : */
463 [ + + ]: 16 : if (state->bufnext >= state->bufdata)
464 : : {
465 : : size_t rsize;
466 : :
467 : 14 : rsize = fread(state->buffer, 1, state->buflen, state->fp);
468 [ + + - + ]: 14 : if (rsize < state->buflen && !feof(state->fp))
469 : : {
14 tgl@sss.pgh.pa.us 470 :UNC 0 : pg_log_error("could not read from input file: %m");
471 : 0 : return -1;
472 : : }
14 tgl@sss.pgh.pa.us 473 [ + + ]:GNC 14 : if (rsize == 0)
474 : 7 : break; /* must be EOF */
475 : 7 : state->bufdata = rsize;
476 : 7 : state->bufnext = 0;
477 : : }
478 : :
479 : : /*
480 : : * Decompress some data into state->outbuf.
481 : : */
482 : : {
483 : : size_t status;
484 : 9 : size_t outlen = state->outbuflen;
485 : 9 : size_t inlen = state->bufdata - state->bufnext;
486 : :
487 : 9 : status = LZ4F_decompress(state->dtx,
488 : 9 : state->outbuf, &outlen,
489 : 9 : state->buffer + state->bufnext,
490 : : &inlen,
491 : : NULL);
977 tomas.vondra@postgre 492 [ - + ]:CBC 9 : if (LZ4F_isError(status))
493 : : {
940 tomas.vondra@postgre 494 :UBC 0 : state->errcode = status;
59 dgustafsson@postgres 495 : 0 : pg_log_error("could not read from input file: %s",
496 : : LZ4F_getErrorName(state->errcode));
977 tomas.vondra@postgre 497 : 0 : return -1;
498 : : }
14 tgl@sss.pgh.pa.us 499 :GNC 9 : state->bufnext += inlen;
500 : 9 : state->outbufdata = outlen;
501 : 9 : state->outbufnext = 0;
502 : : }
503 : : }
504 : :
949 tomas.vondra@postgre 505 :CBC 1741 : return dsize;
506 : : }
507 : :
508 : : /*
509 : : * Compress size bytes from ptr and write them to the stream.
510 : : */
511 : : static void
940 512 : 93 : LZ4Stream_write(const void *ptr, size_t size, CompressFileHandle *CFH)
513 : : {
514 : 93 : LZ4State *state = (LZ4State *) CFH->private_data;
14 tgl@sss.pgh.pa.us 515 :GNC 93 : size_t remaining = size;
516 : :
517 : : /* Lazy init */
518 [ - + ]: 93 : if (!LZ4Stream_init(state, true))
59 dgustafsson@postgres 519 :UBC 0 : pg_fatal("unable to initialize LZ4 library: %s",
520 : : LZ4F_getErrorName(state->errcode));
521 : :
977 tomas.vondra@postgre 522 [ + + ]:CBC 190 : while (remaining > 0)
523 : : {
524 : : size_t chunk;
525 : : size_t required;
526 : : size_t status;
527 : :
528 : : /* We don't try to present more than DEFAULT_IO_BUFFER_SIZE bytes */
14 tgl@sss.pgh.pa.us 529 :GNC 97 : chunk = Min(remaining, (size_t) DEFAULT_IO_BUFFER_SIZE);
530 : :
531 : : /* If not enough space, must flush buffer */
532 : 97 : required = LZ4F_compressBound(chunk, &state->prefs);
533 [ + + ]: 97 : if (required > state->buflen - state->bufdata)
534 : : {
535 : 2 : errno = 0;
536 [ - + ]: 2 : if (fwrite(state->buffer, 1, state->bufdata, state->fp) != state->bufdata)
537 : : {
14 tgl@sss.pgh.pa.us 538 [ # # ]:UNC 0 : errno = (errno) ? errno : ENOSPC;
539 : 0 : pg_fatal("error during writing: %m");
540 : : }
14 tgl@sss.pgh.pa.us 541 :GNC 2 : state->bufdata = 0;
542 : : }
543 : :
544 : 97 : status = LZ4F_compressUpdate(state->ctx,
545 : 97 : state->buffer + state->bufdata,
546 : 97 : state->buflen - state->bufdata,
547 : : ptr, chunk, NULL);
977 tomas.vondra@postgre 548 [ - + ]:CBC 97 : if (LZ4F_isError(status))
59 dgustafsson@postgres 549 :UBC 0 : pg_fatal("error during writing: %s", LZ4F_getErrorName(status));
14 tgl@sss.pgh.pa.us 550 :GNC 97 : state->bufdata += status;
551 : :
894 tomas.vondra@postgre 552 :CBC 97 : ptr = ((const char *) ptr) + chunk;
14 tgl@sss.pgh.pa.us 553 :GNC 97 : remaining -= chunk;
554 : : }
977 tomas.vondra@postgre 555 :CBC 93 : }
556 : :
557 : : /*
558 : : * fread() equivalent implementation for LZ4 compressed files.
559 : : */
560 : : static size_t
59 dgustafsson@postgres 561 : 167 : LZ4Stream_read(void *ptr, size_t size, CompressFileHandle *CFH)
562 : : {
940 tomas.vondra@postgre 563 : 167 : LZ4State *state = (LZ4State *) CFH->private_data;
564 : : int ret;
565 : :
566 [ - + ]: 167 : if ((ret = LZ4Stream_read_internal(state, ptr, size, false)) < 0)
940 tomas.vondra@postgre 567 :UBC 0 : pg_fatal("could not read from input file: %s", LZ4Stream_get_error(CFH));
568 : :
59 dgustafsson@postgres 569 :CBC 167 : return (size_t) ret;
570 : : }
571 : :
572 : : /*
573 : : * fgetc() equivalent implementation for LZ4 compressed files.
574 : : */
575 : : static int
940 tomas.vondra@postgre 576 :GBC 1572 : LZ4Stream_getc(CompressFileHandle *CFH)
577 : : {
578 : 1572 : LZ4State *state = (LZ4State *) CFH->private_data;
579 : : unsigned char c;
580 : :
581 [ - + ]: 1572 : if (LZ4Stream_read_internal(state, &c, 1, false) <= 0)
582 : : {
940 tomas.vondra@postgre 583 [ # # ]:UBC 0 : if (!LZ4Stream_eof(CFH))
584 : 0 : pg_fatal("could not read from input file: %s", LZ4Stream_get_error(CFH));
585 : : else
977 586 : 0 : pg_fatal("could not read from input file: end of file");
587 : : }
588 : :
977 tomas.vondra@postgre 589 :GBC 1572 : return c;
590 : : }
591 : :
592 : : /*
593 : : * fgets() equivalent implementation for LZ4 compressed files.
594 : : */
595 : : static char *
940 tomas.vondra@postgre 596 :CBC 2 : LZ4Stream_gets(char *ptr, int size, CompressFileHandle *CFH)
597 : : {
598 : 2 : LZ4State *state = (LZ4State *) CFH->private_data;
599 : : int ret;
600 : :
894 601 : 2 : ret = LZ4Stream_read_internal(state, ptr, size - 1, true);
602 : :
603 : : /*
604 : : * LZ4Stream_read_internal returning 0 or -1 means that it was either an
605 : : * EOF or an error, but gets_func is defined to return NULL in either case
606 : : * so we can treat both the same here.
607 : : */
59 dgustafsson@postgres 608 [ + + ]: 2 : if (ret <= 0)
977 tomas.vondra@postgre 609 : 1 : return NULL;
610 : :
611 : : /*
612 : : * Our caller expects the return string to be NULL terminated and we know
613 : : * that ret is greater than zero.
614 : : */
894 615 : 1 : ptr[ret - 1] = '\0';
616 : :
977 617 : 1 : return ptr;
618 : : }
619 : :
620 : : /*
621 : : * Finalize (de)compression of a stream. When compressing it will write any
622 : : * remaining content and/or generated footer from the LZ4 API.
623 : : */
624 : : static bool
940 625 : 9 : LZ4Stream_close(CompressFileHandle *CFH)
626 : : {
627 : : FILE *fp;
628 : 9 : LZ4State *state = (LZ4State *) CFH->private_data;
629 : : size_t required;
630 : : size_t status;
631 : : int ret;
632 : :
633 : 9 : fp = state->fp;
634 [ + - ]: 9 : if (state->inited)
635 : : {
636 [ + + ]: 9 : if (state->compressing)
637 : : {
638 : : /* We might need to flush the buffer to make room */
14 tgl@sss.pgh.pa.us 639 :GNC 4 : required = LZ4F_compressBound(0, &state->prefs);
640 [ - + ]: 4 : if (required > state->buflen - state->bufdata)
641 : : {
14 tgl@sss.pgh.pa.us 642 :UNC 0 : errno = 0;
643 [ # # ]: 0 : if (fwrite(state->buffer, 1, state->bufdata, state->fp) != state->bufdata)
644 : : {
645 [ # # ]: 0 : errno = (errno) ? errno : ENOSPC;
646 : 0 : pg_log_error("could not write to output file: %m");
647 : : }
648 : 0 : state->bufdata = 0;
649 : : }
650 : :
14 tgl@sss.pgh.pa.us 651 :GNC 4 : status = LZ4F_compressEnd(state->ctx,
652 : 4 : state->buffer + state->bufdata,
653 : 4 : state->buflen - state->bufdata,
654 : : NULL);
977 tomas.vondra@postgre 655 [ - + ]:CBC 4 : if (LZ4F_isError(status))
656 : : {
59 dgustafsson@postgres 657 :UBC 0 : pg_log_error("could not end compression: %s",
658 : : LZ4F_getErrorName(status));
659 : : }
660 : : else
14 tgl@sss.pgh.pa.us 661 :GNC 4 : state->bufdata += status;
662 : :
663 : 4 : errno = 0;
664 [ - + ]: 4 : if (fwrite(state->buffer, 1, state->bufdata, state->fp) != state->bufdata)
665 : : {
14 tgl@sss.pgh.pa.us 666 [ # # ]:UNC 0 : errno = (errno) ? errno : ENOSPC;
667 : 0 : pg_log_error("could not write to output file: %m");
668 : : }
669 : :
940 tomas.vondra@postgre 670 :CBC 4 : status = LZ4F_freeCompressionContext(state->ctx);
977 671 [ - + ]: 4 : if (LZ4F_isError(status))
59 dgustafsson@postgres 672 :UBC 0 : pg_log_error("could not end compression: %s",
673 : : LZ4F_getErrorName(status));
674 : : }
675 : : else
676 : : {
940 tomas.vondra@postgre 677 :CBC 5 : status = LZ4F_freeDecompressionContext(state->dtx);
977 678 [ - + ]: 5 : if (LZ4F_isError(status))
59 dgustafsson@postgres 679 :UBC 0 : pg_log_error("could not end decompression: %s",
680 : : LZ4F_getErrorName(status));
14 tgl@sss.pgh.pa.us 681 :GNC 5 : pg_free(state->outbuf);
682 : : }
683 : :
940 tomas.vondra@postgre 684 :CBC 9 : pg_free(state->buffer);
685 : : }
686 : :
687 : 9 : pg_free(state);
59 dgustafsson@postgres 688 : 9 : CFH->private_data = NULL;
689 : :
690 : 9 : errno = 0;
691 : 9 : ret = fclose(fp);
692 [ - + ]: 9 : if (ret != 0)
693 : : {
59 dgustafsson@postgres 694 :UBC 0 : pg_log_error("could not close file: %m");
695 : 0 : return false;
696 : : }
697 : :
59 dgustafsson@postgres 698 :CBC 9 : return true;
699 : : }
700 : :
701 : : static bool
940 tomas.vondra@postgre 702 : 9 : LZ4Stream_open(const char *path, int fd, const char *mode,
703 : : CompressFileHandle *CFH)
704 : : {
705 : 9 : LZ4State *state = (LZ4State *) CFH->private_data;
706 : :
977 707 [ - + ]: 9 : if (fd >= 0)
59 dgustafsson@postgres 708 :UBC 0 : state->fp = fdopen(dup(fd), mode);
709 : : else
59 dgustafsson@postgres 710 :CBC 9 : state->fp = fopen(path, mode);
711 [ - + ]: 9 : if (state->fp == NULL)
712 : : {
940 tomas.vondra@postgre 713 :UBC 0 : state->errcode = errno;
949 714 : 0 : return false;
715 : : }
716 : :
949 tomas.vondra@postgre 717 :CBC 9 : return true;
718 : : }
719 : :
720 : : static bool
940 721 : 3 : LZ4Stream_open_write(const char *path, const char *mode, CompressFileHandle *CFH)
722 : : {
723 : : char *fname;
724 : : int save_errno;
725 : : bool ret;
726 : :
977 727 : 3 : fname = psprintf("%s.lz4", path);
728 : 3 : ret = CFH->open_func(fname, -1, mode, CFH);
729 : :
949 730 : 3 : save_errno = errno;
977 731 : 3 : pg_free(fname);
949 732 : 3 : errno = save_errno;
733 : :
977 734 : 3 : return ret;
735 : : }
736 : :
737 : : /*
738 : : * Public routines
739 : : */
740 : : void
741 : 9 : InitCompressFileHandleLZ4(CompressFileHandle *CFH,
742 : : const pg_compress_specification compression_spec)
743 : : {
744 : : LZ4State *state;
745 : :
940 746 : 9 : CFH->open_func = LZ4Stream_open;
747 : 9 : CFH->open_write_func = LZ4Stream_open_write;
748 : 9 : CFH->read_func = LZ4Stream_read;
749 : 9 : CFH->write_func = LZ4Stream_write;
750 : 9 : CFH->gets_func = LZ4Stream_gets;
751 : 9 : CFH->getc_func = LZ4Stream_getc;
752 : 9 : CFH->eof_func = LZ4Stream_eof;
753 : 9 : CFH->close_func = LZ4Stream_close;
754 : 9 : CFH->get_error_func = LZ4Stream_get_error;
755 : :
977 756 : 9 : CFH->compression_spec = compression_spec;
940 757 : 9 : state = pg_malloc0(sizeof(*state));
977 758 [ + - ]: 9 : if (CFH->compression_spec.level >= 0)
940 759 : 9 : state->prefs.compressionLevel = CFH->compression_spec.level;
760 : :
761 : 9 : CFH->private_data = state;
977 762 : 9 : }
763 : : #else /* USE_LZ4 */
764 : : void
765 : : InitCompressorLZ4(CompressorState *cs,
766 : : const pg_compress_specification compression_spec)
767 : : {
768 : : pg_fatal("this build does not support compression with %s", "LZ4");
769 : : }
770 : :
771 : : void
772 : : InitCompressFileHandleLZ4(CompressFileHandle *CFH,
773 : : const pg_compress_specification compression_spec)
774 : : {
775 : : pg_fatal("this build does not support compression with %s", "LZ4");
776 : : }
777 : : #endif /* USE_LZ4 */
|