Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * Load data from a backup manifest into memory.
4 : : *
5 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
6 : : * Portions Copyright (c) 1994, Regents of the University of California
7 : : *
8 : : * src/bin/pg_combinebackup/load_manifest.c
9 : : *
10 : : *-------------------------------------------------------------------------
11 : : */
12 : :
13 : : #include "postgres_fe.h"
14 : :
15 : : #include <sys/stat.h>
16 : : #include <unistd.h>
17 : :
18 : : #include "common/hashfn_unstable.h"
19 : : #include "common/logging.h"
20 : : #include "common/parse_manifest.h"
21 : : #include "load_manifest.h"
22 : :
23 : : /*
24 : : * For efficiency, we'd like our hash table containing information about the
25 : : * manifest to start out with approximately the correct number of entries.
26 : : * There's no way to know the exact number of entries without reading the whole
27 : : * file, but we can get an estimate by dividing the file size by the estimated
28 : : * number of bytes per line.
29 : : *
30 : : * This could be off by about a factor of two in either direction, because the
31 : : * checksum algorithm has a big impact on the line lengths; e.g. a SHA512
32 : : * checksum is 128 hex bytes, whereas a CRC-32C value is only 8, and there
33 : : * might be no checksum at all.
34 : : */
35 : : #define ESTIMATED_BYTES_PER_MANIFEST_LINE 100
36 : :
37 : : /*
38 : : * size of json chunk to be read in
39 : : *
40 : : */
41 : : #define READ_CHUNK_SIZE (128 * 1024)
42 : :
43 : : /*
44 : : * Define a hash table which we can use to store information about the files
45 : : * mentioned in the backup manifest.
46 : : */
47 : : #define SH_PREFIX manifest_files
48 : : #define SH_ELEMENT_TYPE manifest_file
49 : : #define SH_KEY_TYPE const char *
50 : : #define SH_KEY pathname
51 : : #define SH_HASH_KEY(tb, key) hash_string(key)
52 : : #define SH_EQUAL(tb, a, b) (strcmp(a, b) == 0)
53 : : #define SH_SCOPE extern
54 : : #define SH_RAW_ALLOCATOR pg_malloc0
55 : : #define SH_DEFINE
56 : : #include "lib/simplehash.h"
57 : :
58 : : static void combinebackup_version_cb(JsonManifestParseContext *context,
59 : : int manifest_version);
60 : : static void combinebackup_system_identifier_cb(JsonManifestParseContext *context,
61 : : uint64 manifest_system_identifier);
62 : : static void combinebackup_per_file_cb(JsonManifestParseContext *context,
63 : : const char *pathname, uint64 size,
64 : : pg_checksum_type checksum_type,
65 : : int checksum_length,
66 : : uint8 *checksum_payload);
67 : : static void combinebackup_per_wal_range_cb(JsonManifestParseContext *context,
68 : : TimeLineID tli,
69 : : XLogRecPtr start_lsn,
70 : : XLogRecPtr end_lsn);
71 : : pg_noreturn static void report_manifest_error(JsonManifestParseContext *context,
72 : : const char *fmt, ...)
73 : : pg_attribute_printf(2, 3);
74 : :
75 : : /*
76 : : * Load backup_manifest files from an array of backups and produces an array
77 : : * of manifest_data objects.
78 : : *
79 : : * NB: Since load_backup_manifest() can return NULL, the resulting array could
80 : : * contain NULL entries.
81 : : */
82 : : manifest_data **
948 rhaas@postgresql.org 83 :CBC 16 : load_backup_manifests(int n_backups, char **backup_directories)
84 : : {
85 : : manifest_data **result;
86 : : int i;
87 : :
148 michael@paquier.xyz 88 : 16 : result = pg_malloc_array(manifest_data *, n_backups);
948 rhaas@postgresql.org 89 [ + + ]: 48 : for (i = 0; i < n_backups; ++i)
90 : 32 : result[i] = load_backup_manifest(backup_directories[i]);
91 : :
92 : 16 : return result;
93 : : }
94 : :
95 : : /*
96 : : * Parse the backup_manifest file in the named backup directory. Construct a
97 : : * hash table with information about all the files it mentions, and a linked
98 : : * list of all the WAL ranges it mentions.
99 : : *
100 : : * If the backup_manifest file simply doesn't exist, logs a warning and returns
101 : : * NULL. Any other error, or any error parsing the contents of the file, is
102 : : * fatal.
103 : : */
104 : : manifest_data *
105 : 32 : load_backup_manifest(char *backup_directory)
106 : : {
107 : : char pathname[MAXPGPATH];
108 : : int fd;
109 : : struct stat statbuf;
110 : : off_t estimate;
111 : : uint32 initial_size;
112 : : manifest_files_hash *ht;
113 : : char *buffer;
114 : : JsonManifestParseContext context;
115 : : manifest_data *result;
116 : : size_t total_size;
10 peter@eisentraut.org 117 :GNC 32 : const size_t chunk_size = READ_CHUNK_SIZE;
118 : :
119 : : /* Open the manifest file. */
948 rhaas@postgresql.org 120 :CBC 32 : snprintf(pathname, MAXPGPATH, "%s/backup_manifest", backup_directory);
121 [ - + ]: 32 : if ((fd = open(pathname, O_RDONLY | PG_BINARY, 0)) < 0)
122 : : {
948 rhaas@postgresql.org 123 [ # # ]:UBC 0 : if (errno == ENOENT)
124 : : {
764 peter@eisentraut.org 125 : 0 : pg_log_warning("file \"%s\" does not exist", pathname);
948 rhaas@postgresql.org 126 : 0 : return NULL;
127 : : }
128 : 0 : pg_fatal("could not open file \"%s\": %m", pathname);
129 : : }
130 : :
131 : : /* Figure out how big the manifest is. */
948 rhaas@postgresql.org 132 [ - + ]:CBC 32 : if (fstat(fd, &statbuf) != 0)
948 rhaas@postgresql.org 133 :UBC 0 : pg_fatal("could not stat file \"%s\": %m", pathname);
134 : :
135 : : /* Guess how large to make the hash table based on the manifest size. */
948 rhaas@postgresql.org 136 :CBC 32 : estimate = statbuf.st_size / ESTIMATED_BYTES_PER_MANIFEST_LINE;
137 : 32 : initial_size = Min(PG_UINT32_MAX, Max(estimate, 256));
138 : :
139 : : /* Create the hash table. */
140 : 32 : ht = manifest_files_create(initial_size, NULL);
141 : :
148 michael@paquier.xyz 142 : 32 : result = pg_malloc0_object(manifest_data);
948 rhaas@postgresql.org 143 : 32 : result->files = ht;
144 : 32 : context.private_data = result;
864 145 : 32 : context.version_cb = combinebackup_version_cb;
146 : 32 : context.system_identifier_cb = combinebackup_system_identifier_cb;
948 147 : 32 : context.per_file_cb = combinebackup_per_file_cb;
148 : 32 : context.per_wal_range_cb = combinebackup_per_wal_range_cb;
149 : 32 : context.error_cb = report_manifest_error;
150 : :
10 peter@eisentraut.org 151 :GNC 32 : total_size = statbuf.st_size;
152 : :
153 : : /*
154 : : * Parse the file, in chunks if necessary.
155 : : */
156 [ - + ]: 32 : if (total_size <= chunk_size)
157 : : {
158 : : ssize_t rc;
159 : :
10 peter@eisentraut.org 160 :UNC 0 : buffer = pg_malloc(total_size);
161 : 0 : rc = read(fd, buffer, total_size);
162 [ # # ]: 0 : if (rc != total_size)
163 : : {
866 andrew@dunslane.net 164 [ # # ]:UBC 0 : if (rc < 0)
165 : 0 : pg_fatal("could not read file \"%s\": %m", pathname);
166 : : else
10 peter@eisentraut.org 167 :UNC 0 : pg_fatal("could not read file \"%s\": read %zd of %zu",
168 : : pathname, rc, total_size);
169 : : }
170 : :
171 : : /* Close the manifest file. */
866 andrew@dunslane.net 172 :UBC 0 : close(fd);
173 : :
174 : : /* Parse the manifest. */
10 peter@eisentraut.org 175 :UNC 0 : json_parse_manifest(&context, buffer, total_size);
176 : : }
177 : : else
178 : : {
10 peter@eisentraut.org 179 :GNC 32 : size_t bytes_left = total_size;
180 : : JsonManifestParseIncrementalState *inc_state;
181 : :
866 andrew@dunslane.net 182 :CBC 32 : inc_state = json_parse_manifest_incremental_init(&context);
183 : :
184 : 32 : buffer = pg_malloc(chunk_size + 1);
185 : :
186 [ + + ]: 96 : while (bytes_left > 0)
187 : : {
188 : : ssize_t rc;
10 peter@eisentraut.org 189 :GNC 64 : size_t bytes_to_read = chunk_size;
190 : :
191 : : /*
192 : : * Make sure that the last chunk is sufficiently large. (i.e. at
193 : : * least half the chunk size) so that it will contain fully the
194 : : * piece at the end with the checksum.
195 : : */
866 andrew@dunslane.net 196 [ + + ]:CBC 64 : if (bytes_left < chunk_size)
197 : 32 : bytes_to_read = bytes_left;
198 [ + - ]: 32 : else if (bytes_left < 2 * chunk_size)
199 : 32 : bytes_to_read = bytes_left / 2;
200 : 64 : rc = read(fd, buffer, bytes_to_read);
201 [ - + ]: 64 : if (rc != bytes_to_read)
202 : : {
866 andrew@dunslane.net 203 [ # # ]:UBC 0 : if (rc < 0)
204 : 0 : pg_fatal("could not read file \"%s\": %m", pathname);
205 : : else
10 peter@eisentraut.org 206 :UNC 0 : pg_fatal("could not read file \"%s\": read %zu of %zu",
207 : : pathname,
208 : : total_size + rc - bytes_left,
209 : : total_size);
210 : : }
866 andrew@dunslane.net 211 :CBC 64 : bytes_left -= rc;
606 alvherre@alvh.no-ip. 212 : 64 : json_parse_manifest_incremental_chunk(inc_state, buffer, rc, bytes_left == 0);
213 : : }
214 : :
215 : : /* Release the incremental state memory */
837 andrew@dunslane.net 216 : 32 : json_parse_manifest_incremental_shutdown(inc_state);
217 : :
866 218 : 32 : close(fd);
219 : : }
220 : :
221 : : /* All done. */
24 peter@eisentraut.org 222 :GNC 32 : pg_free(buffer);
948 rhaas@postgresql.org 223 :CBC 32 : return result;
224 : : }
225 : :
226 : : /*
227 : : * Report an error while parsing the manifest.
228 : : *
229 : : * We consider all such errors to be fatal errors. The manifest parser
230 : : * expects this function not to return.
231 : : */
232 : : static void
73 tgl@sss.pgh.pa.us 233 :UBC 0 : report_manifest_error(JsonManifestParseContext *context, const char *fmt, ...)
234 : : {
235 : : va_list ap;
236 : :
948 rhaas@postgresql.org 237 : 0 : va_start(ap, fmt);
238 : 0 : pg_log_generic_v(PG_LOG_ERROR, PG_LOG_PRIMARY, gettext(fmt), ap);
239 : 0 : va_end(ap);
240 : :
241 : 0 : exit(1);
242 : : }
243 : :
244 : : /*
245 : : * This callback to validate the manifest version number for incremental backup.
246 : : */
247 : : static void
864 rhaas@postgresql.org 248 :CBC 32 : combinebackup_version_cb(JsonManifestParseContext *context,
249 : : int manifest_version)
250 : : {
251 : : /* Incremental backups supported on manifest version 2 or later */
252 [ - + ]: 32 : if (manifest_version == 1)
864 rhaas@postgresql.org 253 :UBC 0 : pg_fatal("backup manifest version 1 does not support incremental backup");
864 rhaas@postgresql.org 254 :CBC 32 : }
255 : :
256 : : /*
257 : : * Record system identifier extracted from the backup manifest.
258 : : */
259 : : static void
260 : 32 : combinebackup_system_identifier_cb(JsonManifestParseContext *context,
261 : : uint64 manifest_system_identifier)
262 : : {
263 : 32 : manifest_data *manifest = context->private_data;
264 : :
265 : : /* Validation will be at the later stage */
266 : 32 : manifest->system_identifier = manifest_system_identifier;
267 : 32 : }
268 : :
269 : : /*
270 : : * Record details extracted from the backup manifest for one file.
271 : : */
272 : : static void
948 273 : 34352 : combinebackup_per_file_cb(JsonManifestParseContext *context,
274 : : const char *pathname, uint64 size,
275 : : pg_checksum_type checksum_type,
276 : : int checksum_length, uint8 *checksum_payload)
277 : : {
278 : 34352 : manifest_data *manifest = context->private_data;
279 : : manifest_file *m;
280 : : bool found;
281 : :
282 : : /* Make a new entry in the hash table for this file. */
283 : 34352 : m = manifest_files_insert(manifest->files, pathname, &found);
284 [ - + ]: 34352 : if (found)
948 rhaas@postgresql.org 285 :UBC 0 : pg_fatal("duplicate path name in backup manifest: \"%s\"", pathname);
286 : :
287 : : /* Initialize the entry. */
948 rhaas@postgresql.org 288 :CBC 34352 : m->size = size;
289 : 34352 : m->checksum_type = checksum_type;
290 : 34352 : m->checksum_length = checksum_length;
291 : 34352 : m->checksum_payload = checksum_payload;
292 : 34352 : }
293 : :
294 : : /*
295 : : * Record details extracted from the backup manifest for one WAL range.
296 : : */
297 : : static void
298 : 32 : combinebackup_per_wal_range_cb(JsonManifestParseContext *context,
299 : : TimeLineID tli,
300 : : XLogRecPtr start_lsn, XLogRecPtr end_lsn)
301 : : {
302 : 32 : manifest_data *manifest = context->private_data;
303 : : manifest_wal_range *range;
304 : :
305 : : /* Allocate and initialize a struct describing this WAL range. */
228 michael@paquier.xyz 306 : 32 : range = palloc_object(manifest_wal_range);
948 rhaas@postgresql.org 307 : 32 : range->tli = tli;
308 : 32 : range->start_lsn = start_lsn;
309 : 32 : range->end_lsn = end_lsn;
310 : 32 : range->prev = manifest->last_wal_range;
311 : 32 : range->next = NULL;
312 : :
313 : : /* Add it to the end of the list. */
314 [ + - ]: 32 : if (manifest->first_wal_range == NULL)
315 : 32 : manifest->first_wal_range = range;
316 : : else
948 rhaas@postgresql.org 317 :UBC 0 : manifest->last_wal_range->next = range;
948 rhaas@postgresql.org 318 :CBC 32 : manifest->last_wal_range = range;
319 : 32 : }
|