Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * walmethods.c - implementations of different ways to write received wal
4 : : *
5 : : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
6 : : *
7 : : * IDENTIFICATION
8 : : * src/bin/pg_basebackup/walmethods.c
9 : : *-------------------------------------------------------------------------
10 : : */
11 : :
12 : : #include "postgres_fe.h"
13 : :
14 : : #include <sys/stat.h>
15 : : #include <time.h>
16 : : #include <unistd.h>
17 : :
18 : : #ifdef USE_LZ4
19 : : #include <lz4frame.h>
20 : : #endif
21 : : #ifdef HAVE_LIBZ
22 : : #include <zlib.h>
23 : : #endif
24 : :
25 : : #include "common/file_perm.h"
26 : : #include "common/file_utils.h"
27 : : #include "common/logging.h"
28 : : #include "pgtar.h"
29 : : #include "receivelog.h"
30 : : #include "streamutil.h"
31 : :
32 : : /* Size of zlib buffer for .tar.gz */
33 : : #define ZLIB_OUT_SIZE 4096
34 : :
35 : : /* Size of LZ4 input chunk for .lz4 */
36 : : #define LZ4_IN_SIZE 4096
37 : :
38 : : /*-------------------------------------------------------------------------
39 : : * WalDirectoryMethod - write wal to a directory looking like pg_wal
40 : : *-------------------------------------------------------------------------
41 : : */
42 : :
43 : : static Walfile *dir_open_for_write(WalWriteMethod *wwmethod,
44 : : const char *pathname,
45 : : const char *temp_suffix,
46 : : size_t pad_to_size);
47 : : static int dir_close(Walfile *f, WalCloseMethod method);
48 : : static bool dir_existsfile(WalWriteMethod *wwmethod, const char *pathname);
49 : : static ssize_t dir_get_file_size(WalWriteMethod *wwmethod,
50 : : const char *pathname);
51 : : static char *dir_get_file_name(WalWriteMethod *wwmethod,
52 : : const char *pathname, const char *temp_suffix);
53 : : static ssize_t dir_write(Walfile *f, const void *buf, size_t count);
54 : : static int dir_sync(Walfile *f);
55 : : static bool dir_finish(WalWriteMethod *wwmethod);
56 : : static void dir_free(WalWriteMethod *wwmethod);
57 : :
58 : : const WalWriteMethodOps WalDirectoryMethodOps = {
59 : : .open_for_write = dir_open_for_write,
60 : : .close = dir_close,
61 : : .existsfile = dir_existsfile,
62 : : .get_file_size = dir_get_file_size,
63 : : .get_file_name = dir_get_file_name,
64 : : .write = dir_write,
65 : : .sync = dir_sync,
66 : : .finish = dir_finish,
67 : : .free = dir_free
68 : : };
69 : :
70 : : /*
71 : : * Global static data for this method
72 : : */
73 : : typedef struct DirectoryMethodData
74 : : {
75 : : WalWriteMethod base;
76 : : char *basedir;
77 : : } DirectoryMethodData;
78 : :
79 : : /*
80 : : * Local file handle
81 : : */
82 : : typedef struct DirectoryMethodFile
83 : : {
84 : : Walfile base;
85 : : int fd;
86 : : char *fullpath;
87 : : char *temp_suffix;
88 : : #ifdef HAVE_LIBZ
89 : : gzFile gzfp;
90 : : #endif
91 : : #ifdef USE_LZ4
92 : : LZ4F_compressionContext_t ctx;
93 : : size_t lz4bufsize;
94 : : void *lz4buf;
95 : : #endif
96 : : } DirectoryMethodFile;
97 : :
98 : : #define clear_error(wwmethod) \
99 : : ((wwmethod)->lasterrstring = NULL, (wwmethod)->lasterrno = 0)
100 : :
101 : : static char *
573 rhaas@postgresql.org 102 :CBC 417 : dir_get_file_name(WalWriteMethod *wwmethod,
103 : : const char *pathname, const char *temp_suffix)
104 : : {
999 michael@paquier.xyz 105 : 417 : char *filename = pg_malloc0(MAXPGPATH * sizeof(char));
106 : :
107 [ + + ]: 834 : snprintf(filename, MAXPGPATH, "%s%s%s",
108 : : pathname,
573 rhaas@postgresql.org 109 [ + + ]: 417 : wwmethod->compression_algorithm == PG_COMPRESSION_GZIP ? ".gz" :
110 [ + + ]: 409 : wwmethod->compression_algorithm == PG_COMPRESSION_LZ4 ? ".lz4" : "",
111 : : temp_suffix ? temp_suffix : "");
112 : :
999 michael@paquier.xyz 113 : 417 : return filename;
114 : : }
115 : :
116 : : static Walfile *
573 rhaas@postgresql.org 117 : 155 : dir_open_for_write(WalWriteMethod *wwmethod, const char *pathname,
118 : : const char *temp_suffix, size_t pad_to_size)
119 : : {
120 : 155 : DirectoryMethodData *dir_data = (DirectoryMethodData *) wwmethod;
121 : : char tmppath[MAXPGPATH];
122 : : char *filename;
123 : : int fd;
124 : : DirectoryMethodFile *f;
125 : : #ifdef HAVE_LIBZ
2644 magnus@hagander.net 126 : 155 : gzFile gzfp = NULL;
127 : : #endif
128 : : #ifdef USE_LZ4
891 michael@paquier.xyz 129 : 155 : LZ4F_compressionContext_t ctx = NULL;
130 : 155 : size_t lz4bufsize = 0;
131 : 155 : void *lz4buf = NULL;
132 : : #endif
133 : :
573 rhaas@postgresql.org 134 : 155 : clear_error(wwmethod);
135 : :
136 : 155 : filename = dir_get_file_name(wwmethod, pathname, temp_suffix);
999 michael@paquier.xyz 137 : 155 : snprintf(tmppath, sizeof(tmppath), "%s/%s",
138 : : dir_data->basedir, filename);
993 139 : 155 : pg_free(filename);
140 : :
141 : : /*
142 : : * Open a file for non-compressed as well as compressed files. Tracking
143 : : * the file descriptor is important for dir_sync() method as gzflush()
144 : : * does not do any system calls to fsync() to make changes permanent on
145 : : * disk.
146 : : */
2199 sfrost@snowman.net 147 : 155 : fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, pg_file_create_mode);
2730 magnus@hagander.net 148 [ - + ]: 155 : if (fd < 0)
149 : : {
573 rhaas@postgresql.org 150 :UBC 0 : wwmethod->lasterrno = errno;
2730 magnus@hagander.net 151 : 0 : return NULL;
152 : : }
153 : :
154 : : #ifdef HAVE_LIBZ
573 rhaas@postgresql.org 155 [ + + ]:CBC 155 : if (wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
156 : : {
2644 magnus@hagander.net 157 : 2 : gzfp = gzdopen(fd, "wb");
158 [ - + ]: 2 : if (gzfp == NULL)
159 : : {
573 rhaas@postgresql.org 160 :UBC 0 : wwmethod->lasterrno = errno;
2644 magnus@hagander.net 161 : 0 : close(fd);
162 : 0 : return NULL;
163 : : }
164 : :
573 rhaas@postgresql.org 165 [ - + ]:CBC 2 : if (gzsetparams(gzfp, wwmethod->compression_level,
166 : : Z_DEFAULT_STRATEGY) != Z_OK)
167 : : {
573 rhaas@postgresql.org 168 :UBC 0 : wwmethod->lasterrno = errno;
2644 magnus@hagander.net 169 : 0 : gzclose(gzfp);
170 : 0 : return NULL;
171 : : }
172 : : }
173 : : #endif
174 : : #ifdef USE_LZ4
573 rhaas@postgresql.org 175 [ + + ]:CBC 155 : if (wwmethod->compression_algorithm == PG_COMPRESSION_LZ4)
176 : : {
177 : : size_t ctx_out;
178 : : size_t header_size;
179 : : LZ4F_preferences_t prefs;
180 : :
891 michael@paquier.xyz 181 : 2 : ctx_out = LZ4F_createCompressionContext(&ctx, LZ4F_VERSION);
182 [ - + ]: 2 : if (LZ4F_isError(ctx_out))
183 : : {
573 rhaas@postgresql.org 184 :UBC 0 : wwmethod->lasterrstring = LZ4F_getErrorName(ctx_out);
891 michael@paquier.xyz 185 : 0 : close(fd);
186 : 0 : return NULL;
187 : : }
188 : :
891 michael@paquier.xyz 189 :CBC 2 : lz4bufsize = LZ4F_compressBound(LZ4_IN_SIZE, NULL);
190 : 2 : lz4buf = pg_malloc0(lz4bufsize);
191 : :
192 : : /* assign the compression level, default is 0 */
727 193 : 2 : memset(&prefs, 0, sizeof(prefs));
573 rhaas@postgresql.org 194 : 2 : prefs.compressionLevel = wwmethod->compression_level;
195 : :
196 : : /* add the header */
727 michael@paquier.xyz 197 : 2 : header_size = LZ4F_compressBegin(ctx, lz4buf, lz4bufsize, &prefs);
891 198 [ - + ]: 2 : if (LZ4F_isError(header_size))
199 : : {
573 rhaas@postgresql.org 200 :UBC 0 : wwmethod->lasterrstring = LZ4F_getErrorName(header_size);
891 michael@paquier.xyz 201 : 0 : (void) LZ4F_freeCompressionContext(ctx);
202 : 0 : pg_free(lz4buf);
203 : 0 : close(fd);
204 : 0 : return NULL;
205 : : }
206 : :
891 michael@paquier.xyz 207 :CBC 2 : errno = 0;
208 [ - + ]: 2 : if (write(fd, lz4buf, header_size) != header_size)
209 : : {
210 : : /* If write didn't set errno, assume problem is no disk space */
573 rhaas@postgresql.org 211 [ # # ]:UBC 0 : wwmethod->lasterrno = errno ? errno : ENOSPC;
891 michael@paquier.xyz 212 : 0 : (void) LZ4F_freeCompressionContext(ctx);
213 : 0 : pg_free(lz4buf);
214 : 0 : close(fd);
215 : 0 : return NULL;
216 : : }
217 : : }
218 : : #endif
219 : :
220 : : /* Do pre-padding on non-compressed files */
573 rhaas@postgresql.org 221 [ + + + + ]:CBC 155 : if (pad_to_size && wwmethod->compression_algorithm == PG_COMPRESSION_NONE)
222 : : {
223 : : ssize_t rc;
224 : :
405 michael@paquier.xyz 225 : 119 : rc = pg_pwrite_zeros(fd, pad_to_size, 0);
226 : :
523 227 [ - + ]: 119 : if (rc < 0)
228 : : {
523 michael@paquier.xyz 229 :UBC 0 : wwmethod->lasterrno = errno;
230 : 0 : close(fd);
231 : 0 : return NULL;
232 : : }
233 : :
234 : : /*
235 : : * pg_pwrite() (called via pg_pwrite_zeros()) may have moved the file
236 : : * position, so reset it (see win32pwrite.c).
237 : : */
2730 magnus@hagander.net 238 [ - + ]:CBC 119 : if (lseek(fd, 0, SEEK_SET) != 0)
239 : : {
573 rhaas@postgresql.org 240 :UBC 0 : wwmethod->lasterrno = errno;
2730 magnus@hagander.net 241 : 0 : close(fd);
242 : 0 : return NULL;
243 : : }
244 : : }
245 : :
246 : : /*
247 : : * fsync WAL file and containing directory, to ensure the file is
248 : : * persistently created and zeroed (if padded). That's particularly
249 : : * important when using synchronous mode, where the file is modified and
250 : : * fsynced in-place, without a directory fsync.
251 : : */
573 rhaas@postgresql.org 252 [ + + ]:CBC 155 : if (wwmethod->sync)
253 : : {
1840 peter@eisentraut.org 254 [ + - - + ]: 14 : if (fsync_fname(tmppath, false) != 0 ||
255 : 7 : fsync_parent_path(tmppath) != 0)
256 : : {
573 rhaas@postgresql.org 257 :UBC 0 : wwmethod->lasterrno = errno;
258 : : #ifdef HAVE_LIBZ
259 [ # # ]: 0 : if (wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
2644 magnus@hagander.net 260 : 0 : gzclose(gzfp);
261 : : else
262 : : #endif
263 : : #ifdef USE_LZ4
573 rhaas@postgresql.org 264 [ # # ]: 0 : if (wwmethod->compression_algorithm == PG_COMPRESSION_LZ4)
265 : : {
891 michael@paquier.xyz 266 : 0 : (void) LZ4F_compressEnd(ctx, lz4buf, lz4bufsize, NULL);
267 : 0 : (void) LZ4F_freeCompressionContext(ctx);
268 : 0 : pg_free(lz4buf);
269 : 0 : close(fd);
270 : : }
271 : : else
272 : : #endif
2644 magnus@hagander.net 273 : 0 : close(fd);
2730 274 : 0 : return NULL;
275 : : }
276 : : }
277 : :
2730 magnus@hagander.net 278 :CBC 155 : f = pg_malloc0(sizeof(DirectoryMethodFile));
279 : : #ifdef HAVE_LIBZ
573 rhaas@postgresql.org 280 [ + + ]: 155 : if (wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
2644 magnus@hagander.net 281 : 2 : f->gzfp = gzfp;
282 : : #endif
283 : : #ifdef USE_LZ4
573 rhaas@postgresql.org 284 [ + + ]: 155 : if (wwmethod->compression_algorithm == PG_COMPRESSION_LZ4)
285 : : {
891 michael@paquier.xyz 286 : 2 : f->ctx = ctx;
287 : 2 : f->lz4buf = lz4buf;
288 : 2 : f->lz4bufsize = lz4bufsize;
289 : : }
290 : : #endif
291 : :
573 rhaas@postgresql.org 292 : 155 : f->base.wwmethod = wwmethod;
293 : 155 : f->base.currpos = 0;
294 : 155 : f->base.pathname = pg_strdup(pathname);
2730 magnus@hagander.net 295 : 155 : f->fd = fd;
296 : 155 : f->fullpath = pg_strdup(tmppath);
297 [ + + ]: 155 : if (temp_suffix)
298 : 14 : f->temp_suffix = pg_strdup(temp_suffix);
299 : :
573 rhaas@postgresql.org 300 : 155 : return &f->base;
301 : : }
302 : :
303 : : static ssize_t
304 : 5599 : dir_write(Walfile *f, const void *buf, size_t count)
305 : : {
306 : : ssize_t r;
2730 magnus@hagander.net 307 : 5599 : DirectoryMethodFile *df = (DirectoryMethodFile *) f;
308 : :
309 [ - + ]: 5599 : Assert(f != NULL);
573 rhaas@postgresql.org 310 : 5599 : clear_error(f->wwmethod);
311 : :
312 : : #ifdef HAVE_LIBZ
313 [ + + ]: 5599 : if (f->wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
314 : : {
879 tgl@sss.pgh.pa.us 315 : 9 : errno = 0;
2644 magnus@hagander.net 316 : 9 : r = (ssize_t) gzwrite(df->gzfp, buf, count);
879 tgl@sss.pgh.pa.us 317 [ - + ]: 9 : if (r != count)
318 : : {
319 : : /* If write didn't set errno, assume problem is no disk space */
573 rhaas@postgresql.org 320 [ # # ]:UBC 0 : f->wwmethod->lasterrno = errno ? errno : ENOSPC;
321 : : }
322 : : }
323 : : else
324 : : #endif
325 : : #ifdef USE_LZ4
573 rhaas@postgresql.org 326 [ + + ]:CBC 5590 : if (f->wwmethod->compression_algorithm == PG_COMPRESSION_LZ4)
327 : : {
328 : : size_t chunk;
329 : : size_t remaining;
891 michael@paquier.xyz 330 : 9 : const void *inbuf = buf;
331 : :
332 : 9 : remaining = count;
333 [ + + ]: 266 : while (remaining > 0)
334 : : {
335 : : size_t compressed;
336 : :
337 [ + + ]: 257 : if (remaining > LZ4_IN_SIZE)
338 : 248 : chunk = LZ4_IN_SIZE;
339 : : else
340 : 9 : chunk = remaining;
341 : :
342 : 257 : remaining -= chunk;
343 : 257 : compressed = LZ4F_compressUpdate(df->ctx,
344 : : df->lz4buf, df->lz4bufsize,
345 : : inbuf, chunk,
346 : : NULL);
347 : :
348 [ - + ]: 257 : if (LZ4F_isError(compressed))
349 : : {
573 rhaas@postgresql.org 350 :UBC 0 : f->wwmethod->lasterrstring = LZ4F_getErrorName(compressed);
891 michael@paquier.xyz 351 : 0 : return -1;
352 : : }
353 : :
879 tgl@sss.pgh.pa.us 354 :CBC 257 : errno = 0;
891 michael@paquier.xyz 355 [ - + ]: 257 : if (write(df->fd, df->lz4buf, compressed) != compressed)
356 : : {
357 : : /* If write didn't set errno, assume problem is no disk space */
573 rhaas@postgresql.org 358 [ # # ]:UBC 0 : f->wwmethod->lasterrno = errno ? errno : ENOSPC;
891 michael@paquier.xyz 359 : 0 : return -1;
360 : : }
361 : :
891 michael@paquier.xyz 362 :CBC 257 : inbuf = ((char *) inbuf) + chunk;
363 : : }
364 : :
365 : : /* Our caller keeps track of the uncompressed size. */
366 : 9 : r = (ssize_t) count;
367 : : }
368 : : else
369 : : #endif
370 : : {
879 tgl@sss.pgh.pa.us 371 : 5581 : errno = 0;
2644 magnus@hagander.net 372 : 5581 : r = write(df->fd, buf, count);
879 tgl@sss.pgh.pa.us 373 [ - + ]: 5581 : if (r != count)
374 : : {
375 : : /* If write didn't set errno, assume problem is no disk space */
573 rhaas@postgresql.org 376 [ # # ]:UBC 0 : f->wwmethod->lasterrno = errno ? errno : ENOSPC;
377 : : }
378 : : }
2730 magnus@hagander.net 379 [ + - ]:CBC 5599 : if (r > 0)
573 rhaas@postgresql.org 380 : 5599 : df->base.currpos += r;
2730 magnus@hagander.net 381 : 5599 : return r;
382 : : }
383 : :
384 : : static int
573 rhaas@postgresql.org 385 : 155 : dir_close(Walfile *f, WalCloseMethod method)
386 : : {
387 : : int r;
2730 magnus@hagander.net 388 : 155 : DirectoryMethodFile *df = (DirectoryMethodFile *) f;
573 rhaas@postgresql.org 389 : 155 : DirectoryMethodData *dir_data = (DirectoryMethodData *) f->wwmethod;
390 : : char tmppath[MAXPGPATH];
391 : : char tmppath2[MAXPGPATH];
392 : :
2730 magnus@hagander.net 393 [ - + ]: 155 : Assert(f != NULL);
573 rhaas@postgresql.org 394 : 155 : clear_error(f->wwmethod);
395 : :
396 : : #ifdef HAVE_LIBZ
397 [ + + ]: 155 : if (f->wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
398 : : {
879 tgl@sss.pgh.pa.us 399 : 2 : errno = 0; /* in case gzclose() doesn't set it */
2644 magnus@hagander.net 400 : 2 : r = gzclose(df->gzfp);
401 : : }
402 : : else
403 : : #endif
404 : : #ifdef USE_LZ4
573 rhaas@postgresql.org 405 [ + + ]: 153 : if (f->wwmethod->compression_algorithm == PG_COMPRESSION_LZ4)
406 : : {
407 : : size_t compressed;
408 : :
891 michael@paquier.xyz 409 : 2 : compressed = LZ4F_compressEnd(df->ctx,
410 : : df->lz4buf, df->lz4bufsize,
411 : : NULL);
412 : :
413 [ - + ]: 2 : if (LZ4F_isError(compressed))
414 : : {
573 rhaas@postgresql.org 415 :UBC 0 : f->wwmethod->lasterrstring = LZ4F_getErrorName(compressed);
891 michael@paquier.xyz 416 : 0 : return -1;
417 : : }
418 : :
879 tgl@sss.pgh.pa.us 419 :CBC 2 : errno = 0;
891 michael@paquier.xyz 420 [ - + ]: 2 : if (write(df->fd, df->lz4buf, compressed) != compressed)
421 : : {
422 : : /* If write didn't set errno, assume problem is no disk space */
573 rhaas@postgresql.org 423 [ # # ]:UBC 0 : f->wwmethod->lasterrno = errno ? errno : ENOSPC;
891 michael@paquier.xyz 424 : 0 : return -1;
425 : : }
426 : :
891 michael@paquier.xyz 427 :CBC 2 : r = close(df->fd);
428 : : }
429 : : else
430 : : #endif
2644 magnus@hagander.net 431 : 151 : r = close(df->fd);
432 : :
2730 433 [ + - ]: 155 : if (r == 0)
434 : : {
435 : : /* Build path to the current version of the file */
436 [ + + + + ]: 155 : if (method == CLOSE_NORMAL && df->temp_suffix)
437 : 8 : {
438 : : char *filename;
439 : : char *filename2;
440 : :
441 : : /*
442 : : * If we have a temp prefix, normal operation is to rename the
443 : : * file.
444 : : */
573 rhaas@postgresql.org 445 : 8 : filename = dir_get_file_name(f->wwmethod, df->base.pathname,
446 : 8 : df->temp_suffix);
999 michael@paquier.xyz 447 : 8 : snprintf(tmppath, sizeof(tmppath), "%s/%s",
448 : : dir_data->basedir, filename);
993 449 : 8 : pg_free(filename);
450 : :
451 : : /* permanent name, so no need for the prefix */
573 rhaas@postgresql.org 452 : 8 : filename2 = dir_get_file_name(f->wwmethod, df->base.pathname, NULL);
999 michael@paquier.xyz 453 : 8 : snprintf(tmppath2, sizeof(tmppath2), "%s/%s",
454 : : dir_data->basedir, filename2);
993 455 : 8 : pg_free(filename2);
573 rhaas@postgresql.org 456 [ + + ]: 8 : if (f->wwmethod->sync)
812 andres@anarazel.de 457 : 3 : r = durable_rename(tmppath, tmppath2);
458 : : else
459 : : {
460 [ - + ]: 5 : if (rename(tmppath, tmppath2) != 0)
461 : : {
812 andres@anarazel.de 462 :UBC 0 : pg_log_error("could not rename file \"%s\" to \"%s\": %m",
463 : : tmppath, tmppath2);
464 : 0 : r = -1;
465 : : }
466 : : }
467 : : }
2730 magnus@hagander.net 468 [ - + ]:CBC 147 : else if (method == CLOSE_UNLINK)
469 : : {
470 : : char *filename;
471 : :
472 : : /* Unlink the file once it's closed */
573 rhaas@postgresql.org 473 :UBC 0 : filename = dir_get_file_name(f->wwmethod, df->base.pathname,
474 : 0 : df->temp_suffix);
999 michael@paquier.xyz 475 : 0 : snprintf(tmppath, sizeof(tmppath), "%s/%s",
476 : : dir_data->basedir, filename);
993 477 : 0 : pg_free(filename);
2730 magnus@hagander.net 478 : 0 : r = unlink(tmppath);
479 : : }
480 : : else
481 : : {
482 : : /*
483 : : * Else either CLOSE_NORMAL and no temp suffix, or
484 : : * CLOSE_NO_RENAME. In this case, fsync the file and containing
485 : : * directory if sync mode is requested.
486 : : */
573 rhaas@postgresql.org 487 [ + + ]:CBC 147 : if (f->wwmethod->sync)
488 : : {
1840 peter@eisentraut.org 489 : 4 : r = fsync_fname(df->fullpath, false);
2730 magnus@hagander.net 490 [ + - ]: 4 : if (r == 0)
1840 peter@eisentraut.org 491 : 4 : r = fsync_parent_path(df->fullpath);
492 : : }
493 : : }
494 : : }
495 : :
879 tgl@sss.pgh.pa.us 496 [ - + ]: 155 : if (r != 0)
573 rhaas@postgresql.org 497 :UBC 0 : f->wwmethod->lasterrno = errno;
498 : :
499 : : #ifdef USE_LZ4
891 michael@paquier.xyz 500 :CBC 155 : pg_free(df->lz4buf);
501 : : /* supports free on NULL */
502 : 155 : LZ4F_freeCompressionContext(df->ctx);
503 : : #endif
504 : :
573 rhaas@postgresql.org 505 : 155 : pg_free(df->base.pathname);
2730 magnus@hagander.net 506 : 155 : pg_free(df->fullpath);
667 peter@eisentraut.org 507 : 155 : pg_free(df->temp_suffix);
2730 magnus@hagander.net 508 : 155 : pg_free(df);
509 : :
510 : 155 : return r;
511 : : }
512 : :
513 : : static int
573 rhaas@postgresql.org 514 :UBC 0 : dir_sync(Walfile *f)
515 : : {
516 : : int r;
517 : :
2730 magnus@hagander.net 518 [ # # ]: 0 : Assert(f != NULL);
573 rhaas@postgresql.org 519 : 0 : clear_error(f->wwmethod);
520 : :
521 [ # # ]: 0 : if (!f->wwmethod->sync)
2730 magnus@hagander.net 522 : 0 : return 0;
523 : :
524 : : #ifdef HAVE_LIBZ
573 rhaas@postgresql.org 525 [ # # ]: 0 : if (f->wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
526 : : {
2644 magnus@hagander.net 527 [ # # ]: 0 : if (gzflush(((DirectoryMethodFile *) f)->gzfp, Z_SYNC_FLUSH) != Z_OK)
528 : : {
573 rhaas@postgresql.org 529 : 0 : f->wwmethod->lasterrno = errno;
2644 magnus@hagander.net 530 : 0 : return -1;
531 : : }
532 : : }
533 : : #endif
534 : : #ifdef USE_LZ4
573 rhaas@postgresql.org 535 [ # # ]: 0 : if (f->wwmethod->compression_algorithm == PG_COMPRESSION_LZ4)
536 : : {
891 michael@paquier.xyz 537 : 0 : DirectoryMethodFile *df = (DirectoryMethodFile *) f;
538 : : size_t compressed;
539 : :
540 : : /* Flush any internal buffers */
541 : 0 : compressed = LZ4F_flush(df->ctx, df->lz4buf, df->lz4bufsize, NULL);
542 [ # # ]: 0 : if (LZ4F_isError(compressed))
543 : : {
573 rhaas@postgresql.org 544 : 0 : f->wwmethod->lasterrstring = LZ4F_getErrorName(compressed);
891 michael@paquier.xyz 545 : 0 : return -1;
546 : : }
547 : :
879 tgl@sss.pgh.pa.us 548 : 0 : errno = 0;
891 michael@paquier.xyz 549 [ # # ]: 0 : if (write(df->fd, df->lz4buf, compressed) != compressed)
550 : : {
551 : : /* If write didn't set errno, assume problem is no disk space */
573 rhaas@postgresql.org 552 [ # # ]: 0 : f->wwmethod->lasterrno = errno ? errno : ENOSPC;
891 michael@paquier.xyz 553 : 0 : return -1;
554 : : }
555 : : }
556 : : #endif
557 : :
879 tgl@sss.pgh.pa.us 558 : 0 : r = fsync(((DirectoryMethodFile *) f)->fd);
559 [ # # ]: 0 : if (r < 0)
573 rhaas@postgresql.org 560 : 0 : f->wwmethod->lasterrno = errno;
879 tgl@sss.pgh.pa.us 561 : 0 : return r;
562 : : }
563 : :
564 : : static ssize_t
573 rhaas@postgresql.org 565 : 0 : dir_get_file_size(WalWriteMethod *wwmethod, const char *pathname)
566 : : {
567 : 0 : DirectoryMethodData *dir_data = (DirectoryMethodData *) wwmethod;
568 : : struct stat statbuf;
569 : : char tmppath[MAXPGPATH];
570 : :
2730 magnus@hagander.net 571 : 0 : snprintf(tmppath, sizeof(tmppath), "%s/%s",
572 : : dir_data->basedir, pathname);
573 : :
574 [ # # ]: 0 : if (stat(tmppath, &statbuf) != 0)
575 : : {
573 rhaas@postgresql.org 576 : 0 : wwmethod->lasterrno = errno;
2730 magnus@hagander.net 577 : 0 : return -1;
578 : : }
579 : :
580 : 0 : return statbuf.st_size;
581 : : }
582 : :
583 : : static bool
573 rhaas@postgresql.org 584 :CBC 121 : dir_existsfile(WalWriteMethod *wwmethod, const char *pathname)
585 : : {
586 : 121 : DirectoryMethodData *dir_data = (DirectoryMethodData *) wwmethod;
587 : : char tmppath[MAXPGPATH];
588 : : int fd;
589 : :
590 : 121 : clear_error(wwmethod);
591 : :
2730 magnus@hagander.net 592 : 121 : snprintf(tmppath, sizeof(tmppath), "%s/%s",
593 : : dir_data->basedir, pathname);
594 : :
595 : 121 : fd = open(tmppath, O_RDONLY | PG_BINARY, 0);
596 [ + - ]: 121 : if (fd < 0)
597 : :
598 : : /*
599 : : * Skip setting dir_data->lasterrno here because we are only checking
600 : : * for existence.
601 : : */
602 : 121 : return false;
2730 magnus@hagander.net 603 :UBC 0 : close(fd);
604 : 0 : return true;
605 : : }
606 : :
607 : : static bool
573 rhaas@postgresql.org 608 :CBC 114 : dir_finish(WalWriteMethod *wwmethod)
609 : : {
610 : 114 : clear_error(wwmethod);
611 : :
612 [ + + ]: 114 : if (wwmethod->sync)
613 : : {
614 : 4 : DirectoryMethodData *dir_data = (DirectoryMethodData *) wwmethod;
615 : :
616 : : /*
617 : : * Files are fsynced when they are closed, but we need to fsync the
618 : : * directory entry here as well.
619 : : */
1840 peter@eisentraut.org 620 [ - + ]: 4 : if (fsync_fname(dir_data->basedir, true) != 0)
621 : : {
573 rhaas@postgresql.org 622 :UBC 0 : wwmethod->lasterrno = errno;
2730 magnus@hagander.net 623 : 0 : return false;
624 : : }
625 : : }
2730 magnus@hagander.net 626 :CBC 114 : return true;
627 : : }
628 : :
629 : : static void
573 rhaas@postgresql.org 630 : 114 : dir_free(WalWriteMethod *wwmethod)
631 : : {
632 : 114 : DirectoryMethodData *dir_data = (DirectoryMethodData *) wwmethod;
633 : :
634 : 114 : pg_free(dir_data->basedir);
635 : 114 : pg_free(wwmethod);
636 : 114 : }
637 : :
638 : :
639 : : WalWriteMethod *
892 michael@paquier.xyz 640 : 117 : CreateWalDirectoryMethod(const char *basedir,
641 : : pg_compress_algorithm compression_algorithm,
642 : : int compression_level, bool sync)
643 : : {
644 : : DirectoryMethodData *wwmethod;
645 : :
573 rhaas@postgresql.org 646 : 117 : wwmethod = pg_malloc0(sizeof(DirectoryMethodData));
647 : 117 : *((const WalWriteMethodOps **) &wwmethod->base.ops) =
648 : : &WalDirectoryMethodOps;
649 : 117 : wwmethod->base.compression_algorithm = compression_algorithm;
650 : 117 : wwmethod->base.compression_level = compression_level;
651 : 117 : wwmethod->base.sync = sync;
652 : 117 : clear_error(&wwmethod->base);
653 : 117 : wwmethod->basedir = pg_strdup(basedir);
654 : :
655 : 117 : return &wwmethod->base;
656 : : }
657 : :
658 : :
659 : : /*-------------------------------------------------------------------------
660 : : * WalTarMethod - write wal to a tar file containing pg_wal contents
661 : : *-------------------------------------------------------------------------
662 : : */
663 : :
664 : : static Walfile *tar_open_for_write(WalWriteMethod *wwmethod,
665 : : const char *pathname,
666 : : const char *temp_suffix,
667 : : size_t pad_to_size);
668 : : static int tar_close(Walfile *f, WalCloseMethod method);
669 : : static bool tar_existsfile(WalWriteMethod *wwmethod, const char *pathname);
670 : : static ssize_t tar_get_file_size(WalWriteMethod *wwmethod,
671 : : const char *pathname);
672 : : static char *tar_get_file_name(WalWriteMethod *wwmethod,
673 : : const char *pathname, const char *temp_suffix);
674 : : static ssize_t tar_write(Walfile *f, const void *buf, size_t count);
675 : : static int tar_sync(Walfile *f);
676 : : static bool tar_finish(WalWriteMethod *wwmethod);
677 : : static void tar_free(WalWriteMethod *wwmethod);
678 : :
679 : : const WalWriteMethodOps WalTarMethodOps = {
680 : : .open_for_write = tar_open_for_write,
681 : : .close = tar_close,
682 : : .existsfile = tar_existsfile,
683 : : .get_file_size = tar_get_file_size,
684 : : .get_file_name = tar_get_file_name,
685 : : .write = tar_write,
686 : : .sync = tar_sync,
687 : : .finish = tar_finish,
688 : : .free = tar_free
689 : : };
690 : :
691 : : typedef struct TarMethodFile
692 : : {
693 : : Walfile base;
694 : : off_t ofs_start; /* Where does the *header* for this file start */
695 : : char header[TAR_BLOCK_SIZE];
696 : : size_t pad_to_size;
697 : : } TarMethodFile;
698 : :
699 : : typedef struct TarMethodData
700 : : {
701 : : WalWriteMethod base;
702 : : char *tarfilename;
703 : : int fd;
704 : : TarMethodFile *currentfile;
705 : : #ifdef HAVE_LIBZ
706 : : z_streamp zp;
707 : : void *zlibOut;
708 : : #endif
709 : : } TarMethodData;
710 : :
711 : : #ifdef HAVE_LIBZ
712 : : static bool
251 peter@eisentraut.org 713 :GNC 5952 : tar_write_compressed_data(TarMethodData *tar_data, const void *buf, size_t count,
714 : : bool flush)
715 : : {
2730 magnus@hagander.net 716 :CBC 5952 : tar_data->zp->next_in = buf;
717 : 5952 : tar_data->zp->avail_in = count;
718 : :
719 [ + + + + ]: 11918 : while (tar_data->zp->avail_in || flush)
720 : : {
721 : : int r;
722 : :
723 [ + + ]: 5978 : r = deflate(tar_data->zp, flush ? Z_FINISH : Z_NO_FLUSH);
724 [ - + ]: 5978 : if (r == Z_STREAM_ERROR)
725 : : {
342 peter@eisentraut.org 726 :UBC 0 : tar_data->base.lasterrstring = _("could not compress data");
2730 magnus@hagander.net 727 : 0 : return false;
728 : : }
729 : :
2730 magnus@hagander.net 730 [ + + ]:CBC 5978 : if (tar_data->zp->avail_out < ZLIB_OUT_SIZE)
731 : : {
732 : 62 : size_t len = ZLIB_OUT_SIZE - tar_data->zp->avail_out;
733 : :
2079 michael@paquier.xyz 734 : 62 : errno = 0;
2730 magnus@hagander.net 735 [ - + ]: 62 : if (write(tar_data->fd, tar_data->zlibOut, len) != len)
736 : : {
737 : : /* If write didn't set errno, assume problem is no disk space */
573 rhaas@postgresql.org 738 [ # # ]:UBC 0 : tar_data->base.lasterrno = errno ? errno : ENOSPC;
2730 magnus@hagander.net 739 : 0 : return false;
740 : : }
741 : :
2730 magnus@hagander.net 742 :CBC 62 : tar_data->zp->next_out = tar_data->zlibOut;
743 : 62 : tar_data->zp->avail_out = ZLIB_OUT_SIZE;
744 : : }
745 : :
746 [ + + ]: 5978 : if (r == Z_STREAM_END)
747 : 12 : break;
748 : : }
749 : :
750 [ + + ]: 5952 : if (flush)
751 : : {
752 : : /* Reset the stream for writing */
753 [ - + ]: 12 : if (deflateReset(tar_data->zp) != Z_OK)
754 : : {
342 peter@eisentraut.org 755 :UBC 0 : tar_data->base.lasterrstring = _("could not reset compression stream");
2730 magnus@hagander.net 756 : 0 : return false;
757 : : }
758 : : }
759 : :
2730 magnus@hagander.net 760 :CBC 5952 : return true;
761 : : }
762 : : #endif
763 : :
764 : : static ssize_t
573 rhaas@postgresql.org 765 : 14294 : tar_write(Walfile *f, const void *buf, size_t count)
766 : : {
767 : 14294 : TarMethodData *tar_data = (TarMethodData *) f->wwmethod;
768 : : ssize_t r;
769 : :
2730 magnus@hagander.net 770 [ - + ]: 14294 : Assert(f != NULL);
573 rhaas@postgresql.org 771 : 14294 : clear_error(f->wwmethod);
772 : :
773 : : /* Tarfile will always be positioned at the end */
774 [ + + ]: 14294 : if (f->wwmethod->compression_algorithm == PG_COMPRESSION_NONE)
775 : : {
879 tgl@sss.pgh.pa.us 776 : 8357 : errno = 0;
2730 magnus@hagander.net 777 : 8357 : r = write(tar_data->fd, buf, count);
879 tgl@sss.pgh.pa.us 778 [ - + ]: 8357 : if (r != count)
779 : : {
780 : : /* If write didn't set errno, assume problem is no disk space */
573 rhaas@postgresql.org 781 [ # # ]:UBC 0 : f->wwmethod->lasterrno = errno ? errno : ENOSPC;
879 tgl@sss.pgh.pa.us 782 : 0 : return -1;
783 : : }
573 rhaas@postgresql.org 784 :CBC 8357 : f->currpos += r;
2730 magnus@hagander.net 785 : 8357 : return r;
786 : : }
787 : : #ifdef HAVE_LIBZ
573 rhaas@postgresql.org 788 [ + - ]: 5937 : else if (f->wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
789 : : {
251 peter@eisentraut.org 790 [ - + ]:GNC 5937 : if (!tar_write_compressed_data(tar_data, buf, count, false))
2730 magnus@hagander.net 791 :UBC 0 : return -1;
573 rhaas@postgresql.org 792 :CBC 5937 : f->currpos += count;
2730 magnus@hagander.net 793 : 5937 : return count;
794 : : }
795 : : #endif
796 : : else
797 : : {
798 : : /* Can't happen - compression enabled with no method set */
573 rhaas@postgresql.org 799 :UBC 0 : f->wwmethod->lasterrno = ENOSYS;
2730 magnus@hagander.net 800 : 0 : return -1;
801 : : }
802 : : }
803 : :
804 : : static bool
2524 bruce@momjian.us 805 :CBC 7 : tar_write_padding_data(TarMethodFile *f, size_t bytes)
806 : : {
807 : : PGAlignedXLogBlock zerobuf;
2730 magnus@hagander.net 808 : 7 : size_t bytesleft = bytes;
809 : :
2052 tgl@sss.pgh.pa.us 810 : 7 : memset(zerobuf.data, 0, XLOG_BLCKSZ);
2730 magnus@hagander.net 811 [ + + ]: 14119 : while (bytesleft)
812 : : {
2052 tgl@sss.pgh.pa.us 813 : 14112 : size_t bytestowrite = Min(bytesleft, XLOG_BLCKSZ);
573 rhaas@postgresql.org 814 : 14112 : ssize_t r = tar_write(&f->base, zerobuf.data, bytestowrite);
815 : :
2730 magnus@hagander.net 816 [ - + ]: 14112 : if (r < 0)
2730 magnus@hagander.net 817 :UBC 0 : return false;
2730 magnus@hagander.net 818 :CBC 14112 : bytesleft -= r;
819 : : }
820 : :
821 : 7 : return true;
822 : : }
823 : :
824 : : static char *
573 rhaas@postgresql.org 825 : 21 : tar_get_file_name(WalWriteMethod *wwmethod, const char *pathname,
826 : : const char *temp_suffix)
827 : : {
999 michael@paquier.xyz 828 : 21 : char *filename = pg_malloc0(MAXPGPATH * sizeof(char));
829 : :
830 [ - + ]: 21 : snprintf(filename, MAXPGPATH, "%s%s",
831 : : pathname, temp_suffix ? temp_suffix : "");
832 : :
833 : 21 : return filename;
834 : : }
835 : :
836 : : static Walfile *
573 rhaas@postgresql.org 837 : 7 : tar_open_for_write(WalWriteMethod *wwmethod, const char *pathname,
838 : : const char *temp_suffix, size_t pad_to_size)
839 : : {
840 : 7 : TarMethodData *tar_data = (TarMethodData *) wwmethod;
841 : : char *tmppath;
842 : :
843 : 7 : clear_error(wwmethod);
844 : :
2730 magnus@hagander.net 845 [ + - ]: 7 : if (tar_data->fd < 0)
846 : : {
847 : : /*
848 : : * We open the tar file only when we first try to write to it.
849 : : */
850 : 7 : tar_data->fd = open(tar_data->tarfilename,
851 : : O_WRONLY | O_CREAT | PG_BINARY,
852 : : pg_file_create_mode);
853 [ - + ]: 7 : if (tar_data->fd < 0)
854 : : {
573 rhaas@postgresql.org 855 :UBC 0 : wwmethod->lasterrno = errno;
2730 magnus@hagander.net 856 : 0 : return NULL;
857 : : }
858 : :
859 : : #ifdef HAVE_LIBZ
573 rhaas@postgresql.org 860 [ + + ]:CBC 7 : if (wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
861 : : {
2730 magnus@hagander.net 862 : 3 : tar_data->zp = (z_streamp) pg_malloc(sizeof(z_stream));
863 : 3 : tar_data->zp->zalloc = Z_NULL;
864 : 3 : tar_data->zp->zfree = Z_NULL;
865 : 3 : tar_data->zp->opaque = Z_NULL;
866 : 3 : tar_data->zp->next_out = tar_data->zlibOut;
867 : 3 : tar_data->zp->avail_out = ZLIB_OUT_SIZE;
868 : :
869 : : /*
870 : : * Initialize deflation library. Adding the magic value 16 to the
871 : : * default 15 for the windowBits parameter makes the output be
872 : : * gzip instead of zlib.
873 : : */
573 rhaas@postgresql.org 874 [ - + ]: 3 : if (deflateInit2(tar_data->zp, wwmethod->compression_level,
875 : : Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK)
876 : : {
2730 magnus@hagander.net 877 :UBC 0 : pg_free(tar_data->zp);
878 : 0 : tar_data->zp = NULL;
573 rhaas@postgresql.org 879 : 0 : wwmethod->lasterrstring =
342 peter@eisentraut.org 880 : 0 : _("could not initialize compression library");
2730 magnus@hagander.net 881 : 0 : return NULL;
882 : : }
883 : : }
884 : : #endif
885 : :
886 : : /* There's no tar header itself, the file starts with regular files */
887 : : }
888 : :
2730 magnus@hagander.net 889 [ - + ]:CBC 7 : if (tar_data->currentfile != NULL)
890 : : {
573 rhaas@postgresql.org 891 :UBC 0 : wwmethod->lasterrstring =
342 peter@eisentraut.org 892 : 0 : _("implementation error: tar files can't have more than one open file");
2730 magnus@hagander.net 893 : 0 : return NULL;
894 : : }
895 : :
2730 magnus@hagander.net 896 :CBC 7 : tar_data->currentfile = pg_malloc0(sizeof(TarMethodFile));
573 rhaas@postgresql.org 897 : 7 : tar_data->currentfile->base.wwmethod = wwmethod;
898 : :
899 : 7 : tmppath = tar_get_file_name(wwmethod, pathname, temp_suffix);
900 : :
901 : : /* Create a header with size set to 0 - we will fill out the size on close */
2730 magnus@hagander.net 902 [ - + ]: 7 : if (tarCreateHeader(tar_data->currentfile->header, tmppath, NULL, 0, S_IRUSR | S_IWUSR, 0, 0, time(NULL)) != TAR_OK)
903 : : {
2730 magnus@hagander.net 904 :UBC 0 : pg_free(tar_data->currentfile);
993 michael@paquier.xyz 905 : 0 : pg_free(tmppath);
2730 magnus@hagander.net 906 : 0 : tar_data->currentfile = NULL;
342 peter@eisentraut.org 907 : 0 : wwmethod->lasterrstring = _("could not create tar header");
2730 magnus@hagander.net 908 : 0 : return NULL;
909 : : }
910 : :
993 michael@paquier.xyz 911 :CBC 7 : pg_free(tmppath);
912 : :
913 : : #ifdef HAVE_LIBZ
573 rhaas@postgresql.org 914 [ + + ]: 7 : if (wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
915 : : {
916 : : /* Flush existing data */
917 [ - + ]: 3 : if (!tar_write_compressed_data(tar_data, NULL, 0, true))
2730 magnus@hagander.net 918 :UBC 0 : return NULL;
919 : :
920 : : /* Turn off compression for header */
578 michael@paquier.xyz 921 [ - + ]:CBC 3 : if (deflateParams(tar_data->zp, 0, Z_DEFAULT_STRATEGY) != Z_OK)
922 : : {
573 rhaas@postgresql.org 923 :UBC 0 : wwmethod->lasterrstring =
342 peter@eisentraut.org 924 : 0 : _("could not change compression parameters");
2730 magnus@hagander.net 925 : 0 : return NULL;
926 : : }
927 : : }
928 : : #endif
929 : :
2730 magnus@hagander.net 930 :CBC 7 : tar_data->currentfile->ofs_start = lseek(tar_data->fd, 0, SEEK_CUR);
931 [ - + ]: 7 : if (tar_data->currentfile->ofs_start == -1)
932 : : {
573 rhaas@postgresql.org 933 :UBC 0 : wwmethod->lasterrno = errno;
2730 magnus@hagander.net 934 : 0 : pg_free(tar_data->currentfile);
935 : 0 : tar_data->currentfile = NULL;
936 : 0 : return NULL;
937 : : }
573 rhaas@postgresql.org 938 :CBC 7 : tar_data->currentfile->base.currpos = 0;
939 : :
940 [ + + ]: 7 : if (wwmethod->compression_algorithm == PG_COMPRESSION_NONE)
941 : : {
2079 michael@paquier.xyz 942 : 4 : errno = 0;
1451 rhaas@postgresql.org 943 [ - + ]: 4 : if (write(tar_data->fd, tar_data->currentfile->header,
944 : : TAR_BLOCK_SIZE) != TAR_BLOCK_SIZE)
945 : : {
946 : : /* If write didn't set errno, assume problem is no disk space */
573 rhaas@postgresql.org 947 [ # # ]:UBC 0 : wwmethod->lasterrno = errno ? errno : ENOSPC;
2730 magnus@hagander.net 948 : 0 : pg_free(tar_data->currentfile);
949 : 0 : tar_data->currentfile = NULL;
950 : 0 : return NULL;
951 : : }
952 : : }
953 : : #ifdef HAVE_LIBZ
573 rhaas@postgresql.org 954 [ + - ]:CBC 3 : else if (wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
955 : : {
956 : : /* Write header through the zlib APIs but with no compression */
957 [ - + ]: 3 : if (!tar_write_compressed_data(tar_data, tar_data->currentfile->header,
958 : : TAR_BLOCK_SIZE, true))
2730 magnus@hagander.net 959 :UBC 0 : return NULL;
960 : :
961 : : /* Re-enable compression for the rest of the file */
573 rhaas@postgresql.org 962 [ - + ]:CBC 3 : if (deflateParams(tar_data->zp, wwmethod->compression_level,
963 : : Z_DEFAULT_STRATEGY) != Z_OK)
964 : : {
342 peter@eisentraut.org 965 :UBC 0 : wwmethod->lasterrstring = _("could not change compression parameters");
2730 magnus@hagander.net 966 : 0 : return NULL;
967 : : }
968 : : }
969 : : #endif
970 : : else
971 : : {
972 : : /* not reachable */
828 michael@paquier.xyz 973 : 0 : Assert(false);
974 : : }
975 : :
573 rhaas@postgresql.org 976 :CBC 7 : tar_data->currentfile->base.pathname = pg_strdup(pathname);
977 : :
978 : : /*
979 : : * Uncompressed files are padded on creation, but for compression we can't
980 : : * do that
981 : : */
2730 magnus@hagander.net 982 [ + - ]: 7 : if (pad_to_size)
983 : : {
984 : 7 : tar_data->currentfile->pad_to_size = pad_to_size;
573 rhaas@postgresql.org 985 [ + + ]: 7 : if (wwmethod->compression_algorithm == PG_COMPRESSION_NONE)
986 : : {
987 : : /* Uncompressed, so pad now */
879 tgl@sss.pgh.pa.us 988 [ - + ]: 4 : if (!tar_write_padding_data(tar_data->currentfile, pad_to_size))
879 tgl@sss.pgh.pa.us 989 :UBC 0 : return NULL;
990 : : /* Seek back to start */
1451 rhaas@postgresql.org 991 :CBC 8 : if (lseek(tar_data->fd,
992 : 4 : tar_data->currentfile->ofs_start + TAR_BLOCK_SIZE,
993 [ - + ]: 4 : SEEK_SET) != tar_data->currentfile->ofs_start + TAR_BLOCK_SIZE)
994 : : {
573 rhaas@postgresql.org 995 :UBC 0 : wwmethod->lasterrno = errno;
2730 magnus@hagander.net 996 : 0 : return NULL;
997 : : }
998 : :
573 rhaas@postgresql.org 999 :CBC 4 : tar_data->currentfile->base.currpos = 0;
1000 : : }
1001 : : }
1002 : :
1003 : 7 : return &tar_data->currentfile->base;
1004 : : }
1005 : :
1006 : : static ssize_t
573 rhaas@postgresql.org 1007 :UBC 0 : tar_get_file_size(WalWriteMethod *wwmethod, const char *pathname)
1008 : : {
1009 : 0 : clear_error(wwmethod);
1010 : :
1011 : : /* Currently not used, so not supported */
1012 : 0 : wwmethod->lasterrno = ENOSYS;
2730 magnus@hagander.net 1013 : 0 : return -1;
1014 : : }
1015 : :
1016 : : static int
573 rhaas@postgresql.org 1017 :CBC 7 : tar_sync(Walfile *f)
1018 : : {
1019 : 7 : TarMethodData *tar_data = (TarMethodData *) f->wwmethod;
1020 : : int r;
1021 : :
2730 magnus@hagander.net 1022 [ - + ]: 7 : Assert(f != NULL);
573 rhaas@postgresql.org 1023 : 7 : clear_error(f->wwmethod);
1024 : :
1025 [ + - ]: 7 : if (!f->wwmethod->sync)
2728 magnus@hagander.net 1026 : 7 : return 0;
1027 : :
1028 : : /*
1029 : : * Always sync the whole tarfile, because that's all we can do. This makes
1030 : : * no sense on compressed files, so just ignore those.
1031 : : */
573 rhaas@postgresql.org 1032 [ # # ]:UBC 0 : if (f->wwmethod->compression_algorithm != PG_COMPRESSION_NONE)
2730 magnus@hagander.net 1033 : 0 : return 0;
1034 : :
879 tgl@sss.pgh.pa.us 1035 : 0 : r = fsync(tar_data->fd);
1036 [ # # ]: 0 : if (r < 0)
573 rhaas@postgresql.org 1037 : 0 : f->wwmethod->lasterrno = errno;
879 tgl@sss.pgh.pa.us 1038 : 0 : return r;
1039 : : }
1040 : :
1041 : : static int
573 rhaas@postgresql.org 1042 :CBC 7 : tar_close(Walfile *f, WalCloseMethod method)
1043 : : {
1044 : : ssize_t filesize;
1045 : : int padding;
1046 : 7 : TarMethodData *tar_data = (TarMethodData *) f->wwmethod;
2730 magnus@hagander.net 1047 : 7 : TarMethodFile *tf = (TarMethodFile *) f;
1048 : :
1049 [ - + ]: 7 : Assert(f != NULL);
573 rhaas@postgresql.org 1050 : 7 : clear_error(f->wwmethod);
1051 : :
2730 magnus@hagander.net 1052 [ - + ]: 7 : if (method == CLOSE_UNLINK)
1053 : : {
573 rhaas@postgresql.org 1054 [ # # ]:UBC 0 : if (f->wwmethod->compression_algorithm != PG_COMPRESSION_NONE)
1055 : : {
342 peter@eisentraut.org 1056 : 0 : f->wwmethod->lasterrstring = _("unlink not supported with compression");
2730 magnus@hagander.net 1057 : 0 : return -1;
1058 : : }
1059 : :
1060 : : /*
1061 : : * Unlink the file that we just wrote to the tar. We do this by
1062 : : * truncating it to the start of the header. This is safe as we only
1063 : : * allow writing of the very last file.
1064 : : */
1065 [ # # ]: 0 : if (ftruncate(tar_data->fd, tf->ofs_start) != 0)
1066 : : {
573 rhaas@postgresql.org 1067 : 0 : f->wwmethod->lasterrno = errno;
2730 magnus@hagander.net 1068 : 0 : return -1;
1069 : : }
1070 : :
573 rhaas@postgresql.org 1071 : 0 : pg_free(tf->base.pathname);
2730 magnus@hagander.net 1072 : 0 : pg_free(tf);
1073 : 0 : tar_data->currentfile = NULL;
1074 : :
1075 : 0 : return 0;
1076 : : }
1077 : :
1078 : : /*
1079 : : * Pad the file itself with zeroes if necessary. Note that this is
1080 : : * different from the tar format padding -- this is the padding we asked
1081 : : * for when the file was opened.
1082 : : */
2730 magnus@hagander.net 1083 [ + - ]:CBC 7 : if (tf->pad_to_size)
1084 : : {
573 rhaas@postgresql.org 1085 [ + + ]: 7 : if (f->wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
1086 : : {
1087 : : /*
1088 : : * A compressed tarfile is padded on close since we cannot know
1089 : : * the size of the compressed output until the end.
1090 : : */
1091 : 3 : size_t sizeleft = tf->pad_to_size - tf->base.currpos;
1092 : :
2730 magnus@hagander.net 1093 [ + - ]: 3 : if (sizeleft)
1094 : : {
1095 [ - + ]: 3 : if (!tar_write_padding_data(tf, sizeleft))
2730 magnus@hagander.net 1096 :UBC 0 : return -1;
1097 : : }
1098 : : }
1099 : : else
1100 : : {
1101 : : /*
1102 : : * An uncompressed tarfile was padded on creation, so just adjust
1103 : : * the current position as if we seeked to the end.
1104 : : */
573 rhaas@postgresql.org 1105 :CBC 4 : tf->base.currpos = tf->pad_to_size;
1106 : : }
1107 : : }
1108 : :
1109 : : /*
1110 : : * Get the size of the file, and pad out to a multiple of the tar block
1111 : : * size.
1112 : : */
1113 : 7 : filesize = f->currpos;
1451 1114 : 7 : padding = tarPaddingBytesRequired(filesize);
2730 magnus@hagander.net 1115 [ - + ]: 7 : if (padding)
1116 : : {
638 peter@eisentraut.org 1117 :UBC 0 : char zerobuf[TAR_BLOCK_SIZE] = {0};
1118 : :
2730 magnus@hagander.net 1119 [ # # ]: 0 : if (tar_write(f, zerobuf, padding) != padding)
1120 : 0 : return -1;
1121 : : }
1122 : :
1123 : :
1124 : : #ifdef HAVE_LIBZ
573 rhaas@postgresql.org 1125 [ + + ]:CBC 7 : if (f->wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
1126 : : {
1127 : : /* Flush the current buffer */
1128 [ - + ]: 3 : if (!tar_write_compressed_data(tar_data, NULL, 0, true))
2730 magnus@hagander.net 1129 :UBC 0 : return -1;
1130 : : }
1131 : : #endif
1132 : :
1133 : : /*
1134 : : * Now go back and update the header with the correct filesize and
1135 : : * possibly also renaming the file. We overwrite the entire current header
1136 : : * when done, including the checksum.
1137 : : */
257 rhaas@postgresql.org 1138 :GNC 7 : print_tar_number(&(tf->header[TAR_OFFSET_SIZE]), 12, filesize);
1139 : :
2730 magnus@hagander.net 1140 [ + - ]:CBC 7 : if (method == CLOSE_NORMAL)
1141 : :
1142 : : /*
1143 : : * We overwrite it with what it was before if we have no tempname,
1144 : : * since we're going to write the buffer anyway.
1145 : : */
257 rhaas@postgresql.org 1146 :GNC 7 : strlcpy(&(tf->header[TAR_OFFSET_NAME]), tf->base.pathname, 100);
1147 : :
1148 : 7 : print_tar_number(&(tf->header[TAR_OFFSET_CHECKSUM]), 8,
1149 : 7 : tarChecksum(((TarMethodFile *) f)->header));
2730 magnus@hagander.net 1150 [ - + ]:CBC 7 : if (lseek(tar_data->fd, tf->ofs_start, SEEK_SET) != ((TarMethodFile *) f)->ofs_start)
1151 : : {
573 rhaas@postgresql.org 1152 :UBC 0 : f->wwmethod->lasterrno = errno;
2730 magnus@hagander.net 1153 : 0 : return -1;
1154 : : }
573 rhaas@postgresql.org 1155 [ + + ]:CBC 7 : if (f->wwmethod->compression_algorithm == PG_COMPRESSION_NONE)
1156 : : {
2079 michael@paquier.xyz 1157 : 4 : errno = 0;
1451 rhaas@postgresql.org 1158 [ - + ]: 4 : if (write(tar_data->fd, tf->header, TAR_BLOCK_SIZE) != TAR_BLOCK_SIZE)
1159 : : {
1160 : : /* If write didn't set errno, assume problem is no disk space */
573 rhaas@postgresql.org 1161 [ # # ]:UBC 0 : f->wwmethod->lasterrno = errno ? errno : ENOSPC;
2730 magnus@hagander.net 1162 : 0 : return -1;
1163 : : }
1164 : : }
1165 : : #ifdef HAVE_LIBZ
573 rhaas@postgresql.org 1166 [ + - ]:CBC 3 : else if (f->wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
1167 : : {
1168 : : /* Turn off compression */
578 michael@paquier.xyz 1169 [ - + ]: 3 : if (deflateParams(tar_data->zp, 0, Z_DEFAULT_STRATEGY) != Z_OK)
1170 : : {
342 peter@eisentraut.org 1171 :UBC 0 : f->wwmethod->lasterrstring = _("could not change compression parameters");
2730 magnus@hagander.net 1172 : 0 : return -1;
1173 : : }
1174 : :
1175 : : /* Overwrite the header, assuming the size will be the same */
573 rhaas@postgresql.org 1176 [ - + ]:CBC 3 : if (!tar_write_compressed_data(tar_data, tar_data->currentfile->header,
1177 : : TAR_BLOCK_SIZE, true))
2730 magnus@hagander.net 1178 :UBC 0 : return -1;
1179 : :
1180 : : /* Turn compression back on */
573 rhaas@postgresql.org 1181 [ - + ]:CBC 3 : if (deflateParams(tar_data->zp, f->wwmethod->compression_level,
1182 : : Z_DEFAULT_STRATEGY) != Z_OK)
1183 : : {
342 peter@eisentraut.org 1184 :UBC 0 : f->wwmethod->lasterrstring = _("could not change compression parameters");
2730 magnus@hagander.net 1185 : 0 : return -1;
1186 : : }
1187 : : }
1188 : : #endif
1189 : : else
1190 : : {
1191 : : /* not reachable */
828 michael@paquier.xyz 1192 : 0 : Assert(false);
1193 : : }
1194 : :
1195 : : /* Move file pointer back down to end, so we can write the next file */
2730 magnus@hagander.net 1196 [ - + ]:CBC 7 : if (lseek(tar_data->fd, 0, SEEK_END) < 0)
1197 : : {
573 rhaas@postgresql.org 1198 :UBC 0 : f->wwmethod->lasterrno = errno;
2730 magnus@hagander.net 1199 : 0 : return -1;
1200 : : }
1201 : :
1202 : : /* Always fsync on close, so the padding gets fsynced */
2119 michael@paquier.xyz 1203 [ - + ]:CBC 7 : if (tar_sync(f) < 0)
1204 : : {
1205 : : /* XXX this seems pretty bogus; why is only this case fatal? */
737 tgl@sss.pgh.pa.us 1206 :UBC 0 : pg_fatal("could not fsync file \"%s\": %s",
1207 : : tf->base.pathname, GetLastWalMethodError(f->wwmethod));
1208 : : }
1209 : :
1210 : : /* Clean up and done */
573 rhaas@postgresql.org 1211 :CBC 7 : pg_free(tf->base.pathname);
2730 magnus@hagander.net 1212 : 7 : pg_free(tf);
1213 : 7 : tar_data->currentfile = NULL;
1214 : :
1215 : 7 : return 0;
1216 : : }
1217 : :
1218 : : static bool
573 rhaas@postgresql.org 1219 : 4 : tar_existsfile(WalWriteMethod *wwmethod, const char *pathname)
1220 : : {
1221 : 4 : clear_error(wwmethod);
1222 : : /* We only deal with new tarfiles, so nothing externally created exists */
2730 magnus@hagander.net 1223 : 4 : return false;
1224 : : }
1225 : :
1226 : : static bool
573 rhaas@postgresql.org 1227 : 7 : tar_finish(WalWriteMethod *wwmethod)
1228 : : {
1229 : 7 : TarMethodData *tar_data = (TarMethodData *) wwmethod;
638 peter@eisentraut.org 1230 : 7 : char zerobuf[1024] = {0};
1231 : :
573 rhaas@postgresql.org 1232 : 7 : clear_error(wwmethod);
1233 : :
2730 magnus@hagander.net 1234 [ - + ]: 7 : if (tar_data->currentfile)
1235 : : {
573 rhaas@postgresql.org 1236 [ # # ]:UBC 0 : if (tar_close(&tar_data->currentfile->base, CLOSE_NORMAL) != 0)
2730 magnus@hagander.net 1237 : 0 : return false;
1238 : : }
1239 : :
1240 : : /* A tarfile always ends with two empty blocks */
573 rhaas@postgresql.org 1241 [ + + ]:CBC 7 : if (wwmethod->compression_algorithm == PG_COMPRESSION_NONE)
1242 : : {
2079 michael@paquier.xyz 1243 : 4 : errno = 0;
2730 magnus@hagander.net 1244 [ - + ]: 4 : if (write(tar_data->fd, zerobuf, sizeof(zerobuf)) != sizeof(zerobuf))
1245 : : {
1246 : : /* If write didn't set errno, assume problem is no disk space */
573 rhaas@postgresql.org 1247 [ # # ]:UBC 0 : wwmethod->lasterrno = errno ? errno : ENOSPC;
2730 magnus@hagander.net 1248 : 0 : return false;
1249 : : }
1250 : : }
1251 : : #ifdef HAVE_LIBZ
573 rhaas@postgresql.org 1252 [ + - ]:CBC 3 : else if (wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
1253 : : {
1254 [ - + ]: 3 : if (!tar_write_compressed_data(tar_data, zerobuf, sizeof(zerobuf),
1255 : : false))
2730 magnus@hagander.net 1256 :UBC 0 : return false;
1257 : :
1258 : : /* Also flush all data to make sure the gzip stream is finished */
2730 magnus@hagander.net 1259 :CBC 3 : tar_data->zp->next_in = NULL;
1260 : 3 : tar_data->zp->avail_in = 0;
1261 : : while (true)
2730 magnus@hagander.net 1262 :UBC 0 : {
1263 : : int r;
1264 : :
2730 magnus@hagander.net 1265 :CBC 3 : r = deflate(tar_data->zp, Z_FINISH);
1266 : :
1267 [ - + ]: 3 : if (r == Z_STREAM_ERROR)
1268 : : {
342 peter@eisentraut.org 1269 :UBC 0 : wwmethod->lasterrstring = _("could not compress data");
2730 magnus@hagander.net 1270 : 0 : return false;
1271 : : }
2730 magnus@hagander.net 1272 [ + - ]:CBC 3 : if (tar_data->zp->avail_out < ZLIB_OUT_SIZE)
1273 : : {
1274 : 3 : size_t len = ZLIB_OUT_SIZE - tar_data->zp->avail_out;
1275 : :
2079 michael@paquier.xyz 1276 : 3 : errno = 0;
2730 magnus@hagander.net 1277 [ - + ]: 3 : if (write(tar_data->fd, tar_data->zlibOut, len) != len)
1278 : : {
1279 : : /*
1280 : : * If write didn't set errno, assume problem is no disk
1281 : : * space.
1282 : : */
573 rhaas@postgresql.org 1283 [ # # ]:UBC 0 : wwmethod->lasterrno = errno ? errno : ENOSPC;
2730 magnus@hagander.net 1284 : 0 : return false;
1285 : : }
1286 : : }
2730 magnus@hagander.net 1287 [ + - ]:CBC 3 : if (r == Z_STREAM_END)
1288 : 3 : break;
1289 : : }
1290 : :
1291 [ - + ]: 3 : if (deflateEnd(tar_data->zp) != Z_OK)
1292 : : {
342 peter@eisentraut.org 1293 :UBC 0 : wwmethod->lasterrstring = _("could not close compression stream");
2730 magnus@hagander.net 1294 : 0 : return false;
1295 : : }
1296 : : }
1297 : : #endif
1298 : : else
1299 : : {
1300 : : /* not reachable */
828 michael@paquier.xyz 1301 : 0 : Assert(false);
1302 : : }
1303 : :
1304 : : /* sync the empty blocks as well, since they're after the last file */
573 rhaas@postgresql.org 1305 [ - + ]:CBC 7 : if (wwmethod->sync)
1306 : : {
2119 michael@paquier.xyz 1307 [ # # ]:UBC 0 : if (fsync(tar_data->fd) != 0)
1308 : : {
573 rhaas@postgresql.org 1309 : 0 : wwmethod->lasterrno = errno;
2119 michael@paquier.xyz 1310 : 0 : return false;
1311 : : }
1312 : : }
1313 : :
2730 magnus@hagander.net 1314 [ - + ]:CBC 7 : if (close(tar_data->fd) != 0)
1315 : : {
573 rhaas@postgresql.org 1316 :UBC 0 : wwmethod->lasterrno = errno;
2730 magnus@hagander.net 1317 : 0 : return false;
1318 : : }
1319 : :
2730 magnus@hagander.net 1320 :CBC 7 : tar_data->fd = -1;
1321 : :
573 rhaas@postgresql.org 1322 [ - + ]: 7 : if (wwmethod->sync)
1323 : : {
879 tgl@sss.pgh.pa.us 1324 [ # # # # ]:UBC 0 : if (fsync_fname(tar_data->tarfilename, false) != 0 ||
1325 : 0 : fsync_parent_path(tar_data->tarfilename) != 0)
1326 : : {
573 rhaas@postgresql.org 1327 : 0 : wwmethod->lasterrno = errno;
2730 magnus@hagander.net 1328 : 0 : return false;
1329 : : }
1330 : : }
1331 : :
2730 magnus@hagander.net 1332 :CBC 7 : return true;
1333 : : }
1334 : :
1335 : : static void
573 rhaas@postgresql.org 1336 : 7 : tar_free(WalWriteMethod *wwmethod)
1337 : : {
1338 : 7 : TarMethodData *tar_data = (TarMethodData *) wwmethod;
1339 : :
1340 : 7 : pg_free(tar_data->tarfilename);
1341 : : #ifdef HAVE_LIBZ
1342 [ + + ]: 7 : if (wwmethod->compression_algorithm == PG_COMPRESSION_GZIP)
1343 : 3 : pg_free(tar_data->zlibOut);
1344 : : #endif
1345 : 7 : pg_free(wwmethod);
1346 : 7 : }
1347 : :
1348 : : /*
1349 : : * The argument compression_algorithm is currently ignored. It is in place for
1350 : : * symmetry with CreateWalDirectoryMethod which uses it for distinguishing
1351 : : * between the different compression methods. CreateWalTarMethod and its family
1352 : : * of functions handle only zlib compression.
1353 : : */
1354 : : WalWriteMethod *
892 michael@paquier.xyz 1355 : 7 : CreateWalTarMethod(const char *tarbase,
1356 : : pg_compress_algorithm compression_algorithm,
1357 : : int compression_level, bool sync)
1358 : : {
1359 : : TarMethodData *wwmethod;
733 1360 : 7 : const char *suffix = (compression_algorithm == PG_COMPRESSION_GZIP) ?
331 tgl@sss.pgh.pa.us 1361 [ + + ]: 7 : ".tar.gz" : ".tar";
1362 : :
573 rhaas@postgresql.org 1363 : 7 : wwmethod = pg_malloc0(sizeof(TarMethodData));
1364 : 7 : *((const WalWriteMethodOps **) &wwmethod->base.ops) =
1365 : : &WalTarMethodOps;
1366 : 7 : wwmethod->base.compression_algorithm = compression_algorithm;
1367 : 7 : wwmethod->base.compression_level = compression_level;
1368 : 7 : wwmethod->base.sync = sync;
1369 : 7 : clear_error(&wwmethod->base);
1370 : :
1371 : 7 : wwmethod->tarfilename = pg_malloc0(strlen(tarbase) + strlen(suffix) + 1);
1372 : 7 : sprintf(wwmethod->tarfilename, "%s%s", tarbase, suffix);
1373 : 7 : wwmethod->fd = -1;
1374 : : #ifdef HAVE_LIBZ
733 michael@paquier.xyz 1375 [ + + ]: 7 : if (compression_algorithm == PG_COMPRESSION_GZIP)
573 rhaas@postgresql.org 1376 : 3 : wwmethod->zlibOut = (char *) pg_malloc(ZLIB_OUT_SIZE + 1);
1377 : : #endif
1378 : :
1379 : 7 : return &wwmethod->base;
1380 : : }
1381 : :
1382 : : const char *
573 rhaas@postgresql.org 1383 :UBC 0 : GetLastWalMethodError(WalWriteMethod *wwmethod)
1384 : : {
1385 [ # # ]: 0 : if (wwmethod->lasterrstring)
1386 : 0 : return wwmethod->lasterrstring;
1387 : 0 : return strerror(wwmethod->lasterrno);
1388 : : }
|