Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * backup_manifest.c
4 : : * code for generating and sending a backup manifest
5 : : *
6 : : * Portions Copyright (c) 2010-2024, PostgreSQL Global Development Group
7 : : *
8 : : * IDENTIFICATION
9 : : * src/backend/backup/backup_manifest.c
10 : : *
11 : : *-------------------------------------------------------------------------
12 : : */
13 : : #include "postgres.h"
14 : :
15 : : #include "access/timeline.h"
16 : : #include "access/xlog.h"
17 : : #include "backup/backup_manifest.h"
18 : : #include "backup/basebackup_sink.h"
19 : : #include "mb/pg_wchar.h"
20 : : #include "utils/builtins.h"
21 : : #include "utils/json.h"
22 : :
23 : : static void AppendStringToManifest(backup_manifest_info *manifest, const char *s);
24 : :
25 : : /*
26 : : * Does the user want a backup manifest?
27 : : *
28 : : * It's simplest to always have a manifest_info object, so that we don't need
29 : : * checks for NULL pointers in too many places. However, if the user doesn't
30 : : * want a manifest, we set manifest->buffile to NULL.
31 : : */
32 : : static inline bool
1452 rhaas@postgresql.org 33 :CBC 144601 : IsManifestEnabled(backup_manifest_info *manifest)
34 : : {
1455 35 : 144601 : return (manifest->buffile != NULL);
36 : : }
37 : :
38 : : /*
39 : : * Convenience macro for appending data to the backup manifest.
40 : : */
41 : : #define AppendToManifest(manifest, ...) \
42 : : { \
43 : : char *_manifest_s = psprintf(__VA_ARGS__); \
44 : : AppendStringToManifest(manifest, _manifest_s); \
45 : : pfree(_manifest_s); \
46 : : }
47 : :
48 : : /*
49 : : * Initialize state so that we can construct a backup manifest.
50 : : *
51 : : * NB: Although the checksum type for the data files is configurable, the
52 : : * checksum for the manifest itself always uses SHA-256. See comments in
53 : : * SendBackupManifest.
54 : : */
55 : : void
1452 56 : 149 : InitializeBackupManifest(backup_manifest_info *manifest,
57 : : backup_manifest_option want_manifest,
58 : : pg_checksum_type manifest_checksum_type)
59 : : {
1249 michael@paquier.xyz 60 : 149 : memset(manifest, 0, sizeof(backup_manifest_info));
61 : 149 : manifest->checksum_type = manifest_checksum_type;
62 : :
1455 rhaas@postgresql.org 63 [ + + ]: 149 : if (want_manifest == MANIFEST_OPTION_NO)
64 : 1 : manifest->buffile = NULL;
65 : : else
66 : : {
67 : 148 : manifest->buffile = BufFileCreateTemp(false);
1229 michael@paquier.xyz 68 : 148 : manifest->manifest_ctx = pg_cryptohash_create(PG_SHA256);
69 [ - + ]: 148 : if (pg_cryptohash_init(manifest->manifest_ctx) < 0)
824 michael@paquier.xyz 70 [ # # ]:UBC 0 : elog(ERROR, "failed to initialize checksum of backup manifest: %s",
71 : : pg_cryptohash_error(manifest->manifest_ctx));
72 : : }
73 : :
1455 rhaas@postgresql.org 74 :CBC 149 : manifest->manifest_size = UINT64CONST(0);
75 : 149 : manifest->force_encode = (want_manifest == MANIFEST_OPTION_FORCE_ENCODE);
76 : 149 : manifest->first_file = true;
77 : 149 : manifest->still_checksumming = true;
78 : :
79 [ + + ]: 149 : if (want_manifest != MANIFEST_OPTION_NO)
80 : 148 : AppendToManifest(manifest,
81 : : "{ \"PostgreSQL-Backup-Manifest-Version\": 2,\n"
82 : : "\"System-Identifier\": " UINT64_FORMAT ",\n"
83 : : "\"Files\": [",
84 : : GetSystemIdentifier());
85 : 149 : }
86 : :
87 : : /*
88 : : * Free resources assigned to a backup manifest constructed.
89 : : */
90 : : void
1229 michael@paquier.xyz 91 : 140 : FreeBackupManifest(backup_manifest_info *manifest)
92 : : {
93 : 140 : pg_cryptohash_free(manifest->manifest_ctx);
94 : 140 : manifest->manifest_ctx = NULL;
95 : 140 : }
96 : :
97 : : /*
98 : : * Add an entry to the backup manifest for a file.
99 : : */
100 : : void
174 rhaas@postgresql.org 101 :GNC 144315 : AddFileToBackupManifest(backup_manifest_info *manifest, Oid spcoid,
102 : : const char *pathname, size_t size, pg_time_t mtime,
103 : : pg_checksum_context *checksum_ctx)
104 : : {
105 : : char pathbuf[MAXPGPATH];
106 : : int pathlen;
107 : : StringInfoData buf;
108 : :
1455 rhaas@postgresql.org 109 [ + + ]:CBC 144315 : if (!IsManifestEnabled(manifest))
110 : 967 : return;
111 : :
112 : : /*
113 : : * If this file is part of a tablespace, the pathname passed to this
114 : : * function will be relative to the tar file that contains it. We want the
115 : : * pathname relative to the data directory (ignoring the intermediate
116 : : * symlink traversal).
117 : : */
174 rhaas@postgresql.org 118 [ + + ]:GNC 143348 : if (OidIsValid(spcoid))
119 : : {
120 : 325 : snprintf(pathbuf, sizeof(pathbuf), "pg_tblspc/%u/%s", spcoid,
121 : : pathname);
1455 rhaas@postgresql.org 122 :CBC 325 : pathname = pathbuf;
123 : : }
124 : :
125 : : /*
126 : : * Each file's entry needs to be separated from any entry that follows by
127 : : * a comma, but there's no comma before the first one or after the last
128 : : * one. To make that work, adding a file to the manifest starts by
129 : : * terminating the most recently added line, with a comma if appropriate,
130 : : * but does not terminate the line inserted for this file.
131 : : */
132 : 143348 : initStringInfo(&buf);
133 [ + + ]: 143348 : if (manifest->first_file)
134 : : {
1277 drowley@postgresql.o 135 : 148 : appendStringInfoChar(&buf, '\n');
1455 rhaas@postgresql.org 136 : 148 : manifest->first_file = false;
137 : : }
138 : : else
139 : 143200 : appendStringInfoString(&buf, ",\n");
140 : :
141 : : /*
142 : : * Write the relative pathname to this file out to the manifest. The
143 : : * manifest is always stored in UTF-8, so we have to encode paths that are
144 : : * not valid in that encoding.
145 : : */
146 : 143348 : pathlen = strlen(pathname);
147 [ + + + - ]: 285730 : if (!manifest->force_encode &&
148 : 142382 : pg_verify_mbstr(PG_UTF8, pathname, pathlen, true))
149 : : {
150 : 142382 : appendStringInfoString(&buf, "{ \"Path\": ");
151 : 142382 : escape_json(&buf, pathname);
152 : 142382 : appendStringInfoString(&buf, ", ");
153 : : }
154 : : else
155 : : {
156 : 966 : appendStringInfoString(&buf, "{ \"Encoded-Path\": \"");
969 michael@paquier.xyz 157 : 966 : enlargeStringInfo(&buf, 2 * pathlen);
158 : 1932 : buf.len += hex_encode(pathname, pathlen,
159 : 966 : &buf.data[buf.len]);
1455 rhaas@postgresql.org 160 : 966 : appendStringInfoString(&buf, "\", ");
161 : : }
162 : :
163 : 143348 : appendStringInfo(&buf, "\"Size\": %zu, ", size);
164 : :
165 : : /*
166 : : * Convert last modification time to a string and append it to the
167 : : * manifest. Since it's not clear what time zone to use and since time
168 : : * zone definitions can change, possibly causing confusion, use GMT
169 : : * always.
170 : : */
171 : 143348 : appendStringInfoString(&buf, "\"Last-Modified\": \"");
172 : 143348 : enlargeStringInfo(&buf, 128);
173 : 143348 : buf.len += pg_strftime(&buf.data[buf.len], 128, "%Y-%m-%d %H:%M:%S %Z",
174 : 143348 : pg_gmtime(&mtime));
1277 drowley@postgresql.o 175 : 143348 : appendStringInfoChar(&buf, '"');
176 : :
177 : : /* Add checksum information. */
1455 rhaas@postgresql.org 178 [ + + ]: 143348 : if (checksum_ctx->type != CHECKSUM_TYPE_NONE)
179 : : {
180 : : uint8 checksumbuf[PG_CHECKSUM_MAX_LENGTH];
181 : : int checksumlen;
182 : :
183 : 142382 : checksumlen = pg_checksum_final(checksum_ctx, checksumbuf);
1229 michael@paquier.xyz 184 [ - + ]: 142382 : if (checksumlen < 0)
1229 michael@paquier.xyz 185 [ # # ]:UBC 0 : elog(ERROR, "could not finalize checksum of file \"%s\"",
186 : : pathname);
187 : :
1455 rhaas@postgresql.org 188 :CBC 142382 : appendStringInfo(&buf,
189 : : ", \"Checksum-Algorithm\": \"%s\", \"Checksum\": \"",
190 : : pg_checksum_type_name(checksum_ctx->type));
969 michael@paquier.xyz 191 : 142382 : enlargeStringInfo(&buf, 2 * checksumlen);
192 : 284764 : buf.len += hex_encode((char *) checksumbuf, checksumlen,
193 : 142382 : &buf.data[buf.len]);
1277 drowley@postgresql.o 194 : 142382 : appendStringInfoChar(&buf, '"');
195 : : }
196 : :
197 : : /* Close out the object. */
1455 rhaas@postgresql.org 198 : 143348 : appendStringInfoString(&buf, " }");
199 : :
200 : : /* OK, add it to the manifest. */
201 : 143348 : AppendStringToManifest(manifest, buf.data);
202 : :
203 : : /* Avoid leaking memory. */
204 : 143348 : pfree(buf.data);
205 : : }
206 : :
207 : : /*
208 : : * Add information about the WAL that will need to be replayed when restoring
209 : : * this backup to the manifest.
210 : : */
211 : : void
1452 212 : 143 : AddWALInfoToBackupManifest(backup_manifest_info *manifest, XLogRecPtr startptr,
213 : : TimeLineID starttli, XLogRecPtr endptr,
214 : : TimeLineID endtli)
215 : : {
216 : : List *timelines;
217 : : ListCell *lc;
1455 218 : 143 : bool first_wal_range = true;
219 : 143 : bool found_start_timeline = false;
220 : :
221 [ + + ]: 143 : if (!IsManifestEnabled(manifest))
222 : 1 : return;
223 : :
224 : : /* Terminate the list of files. */
225 : 142 : AppendStringToManifest(manifest, "\n],\n");
226 : :
227 : : /* Read the timeline history for the ending timeline. */
228 : 142 : timelines = readTimeLineHistory(endtli);
229 : :
230 : : /* Start a list of LSN ranges. */
231 : 142 : AppendStringToManifest(manifest, "\"WAL-Ranges\": [\n");
232 : :
233 [ + - + - : 142 : foreach(lc, timelines)
+ - ]
234 : : {
235 : 142 : TimeLineHistoryEntry *entry = lfirst(lc);
236 : : XLogRecPtr tl_beginptr;
237 : :
238 : : /*
239 : : * We only care about timelines that were active during the backup.
240 : : * Skip any that ended before the backup started. (Note that if
241 : : * entry->end is InvalidXLogRecPtr, it means that the timeline has not
242 : : * yet ended.)
243 : : */
244 [ - + - - ]: 142 : if (!XLogRecPtrIsInvalid(entry->end) && entry->end < startptr)
1455 rhaas@postgresql.org 245 :UBC 0 : continue;
246 : :
247 : : /*
248 : : * Because the timeline history file lists newer timelines before
249 : : * older ones, the first timeline we encounter that is new enough to
250 : : * matter ought to match the ending timeline of the backup.
251 : : */
1455 rhaas@postgresql.org 252 [ + - - + ]:CBC 142 : if (first_wal_range && endtli != entry->tli)
1455 rhaas@postgresql.org 253 [ # # ]:UBC 0 : ereport(ERROR,
254 : : errmsg("expected end timeline %u but found timeline %u",
255 : : starttli, entry->tli));
256 : :
257 : : /*
258 : : * If this timeline entry matches with the timeline on which the
259 : : * backup started, WAL needs to be checked from the start LSN of the
260 : : * backup. If this entry refers to a newer timeline, WAL needs to be
261 : : * checked since the beginning of this timeline, so use the LSN where
262 : : * the timeline began.
263 : : */
965 michael@paquier.xyz 264 [ + - ]:CBC 142 : if (starttli == entry->tli)
265 : 142 : tl_beginptr = startptr;
266 : : else
267 : : {
965 michael@paquier.xyz 268 :UBC 0 : tl_beginptr = entry->begin;
269 : :
270 : : /*
271 : : * If we reach a TLI that has no valid beginning LSN, there can't
272 : : * be any more timelines in the history after this point, so we'd
273 : : * better have arrived at the expected starting TLI. If not,
274 : : * something's gone horribly wrong.
275 : : */
276 [ # # ]: 0 : if (XLogRecPtrIsInvalid(entry->begin))
1455 rhaas@postgresql.org 277 [ # # ]: 0 : ereport(ERROR,
278 : : errmsg("expected start timeline %u but found timeline %u",
279 : : starttli, entry->tli));
280 : : }
281 : :
1455 rhaas@postgresql.org 282 [ + - ]:CBC 142 : AppendToManifest(manifest,
283 : : "%s{ \"Timeline\": %u, \"Start-LSN\": \"%X/%X\", \"End-LSN\": \"%X/%X\" }",
284 : : first_wal_range ? "" : ",\n",
285 : : entry->tli,
286 : : LSN_FORMAT_ARGS(tl_beginptr),
287 : : LSN_FORMAT_ARGS(endptr));
288 : :
289 [ + - ]: 142 : if (starttli == entry->tli)
290 : : {
291 : 142 : found_start_timeline = true;
292 : 142 : break;
293 : : }
294 : :
1455 rhaas@postgresql.org 295 :UBC 0 : endptr = entry->begin;
296 : 0 : first_wal_range = false;
297 : : }
298 : :
299 : : /*
300 : : * The last entry in the timeline history for the ending timeline should
301 : : * be the ending timeline itself. Verify that this is what we observed.
302 : : */
1455 rhaas@postgresql.org 303 [ - + ]:CBC 142 : if (!found_start_timeline)
1455 rhaas@postgresql.org 304 [ # # ]:UBC 0 : ereport(ERROR,
305 : : errmsg("start timeline %u not found in history of timeline %u",
306 : : starttli, endtli));
307 : :
308 : : /* Terminate the list of WAL ranges. */
1455 rhaas@postgresql.org 309 :CBC 142 : AppendStringToManifest(manifest, "\n],\n");
310 : : }
311 : :
312 : : /*
313 : : * Finalize the backup manifest, and send it to the client.
314 : : */
315 : : void
703 tgl@sss.pgh.pa.us 316 : 143 : SendBackupManifest(backup_manifest_info *manifest, bbsink *sink)
317 : : {
318 : : uint8 checksumbuf[PG_SHA256_DIGEST_LENGTH];
319 : : char checksumstringbuf[PG_SHA256_DIGEST_STRING_LENGTH];
1455 rhaas@postgresql.org 320 : 143 : size_t manifest_bytes_done = 0;
321 : :
322 [ + + ]: 143 : if (!IsManifestEnabled(manifest))
323 : 1 : return;
324 : :
325 : : /*
326 : : * Append manifest checksum, so that the problems with the manifest itself
327 : : * can be detected.
328 : : *
329 : : * We always use SHA-256 for this, regardless of what algorithm is chosen
330 : : * for checksumming the files. If we ever want to make the checksum
331 : : * algorithm used for the manifest file variable, the client will need a
332 : : * way to figure out which algorithm to use as close to the beginning of
333 : : * the manifest file as possible, to avoid having to read the whole thing
334 : : * twice.
335 : : */
336 : 142 : manifest->still_checksumming = false;
1154 michael@paquier.xyz 337 [ - + ]: 142 : if (pg_cryptohash_final(manifest->manifest_ctx, checksumbuf,
338 : : sizeof(checksumbuf)) < 0)
824 michael@paquier.xyz 339 [ # # ]:UBC 0 : elog(ERROR, "failed to finalize checksum of backup manifest: %s",
340 : : pg_cryptohash_error(manifest->manifest_ctx));
1455 rhaas@postgresql.org 341 :CBC 142 : AppendStringToManifest(manifest, "\"Manifest-Checksum\": \"");
342 : :
969 michael@paquier.xyz 343 : 142 : hex_encode((char *) checksumbuf, sizeof checksumbuf, checksumstringbuf);
344 : 142 : checksumstringbuf[PG_SHA256_DIGEST_STRING_LENGTH - 1] = '\0';
345 : :
1455 rhaas@postgresql.org 346 : 142 : AppendStringToManifest(manifest, checksumstringbuf);
347 : 142 : AppendStringToManifest(manifest, "\"}\n");
348 : :
349 : : /*
350 : : * We've written all the data to the manifest file. Rewind the file so
351 : : * that we can read it all back.
352 : : */
382 peter@eisentraut.org 353 [ - + ]: 142 : if (BufFileSeek(manifest->buffile, 0, 0, SEEK_SET))
1455 rhaas@postgresql.org 354 [ # # ]:UBC 0 : ereport(ERROR,
355 : : (errcode_for_file_access(),
356 : : errmsg("could not rewind temporary file")));
357 : :
358 : :
359 : : /*
360 : : * Send the backup manifest.
361 : : */
891 rhaas@postgresql.org 362 :CBC 142 : bbsink_begin_manifest(sink);
1455 363 [ + + ]: 873 : while (manifest_bytes_done < manifest->manifest_size)
364 : : {
365 : : size_t bytes_to_read;
366 : :
891 367 : 731 : bytes_to_read = Min(sink->bbs_buffer_length,
368 : : manifest->manifest_size - manifest_bytes_done);
454 peter@eisentraut.org 369 : 731 : BufFileReadExact(manifest->buffile, sink->bbs_buffer, bytes_to_read);
891 rhaas@postgresql.org 370 : 731 : bbsink_manifest_contents(sink, bytes_to_read);
1455 371 : 731 : manifest_bytes_done += bytes_to_read;
372 : : }
891 373 : 142 : bbsink_end_manifest(sink);
374 : :
375 : : /* Release resources */
1455 376 : 142 : BufFileClose(manifest->buffile);
377 : : }
378 : :
379 : : /*
380 : : * Append a cstring to the manifest.
381 : : */
382 : : static void
471 peter@eisentraut.org 383 : 144490 : AppendStringToManifest(backup_manifest_info *manifest, const char *s)
384 : : {
1452 rhaas@postgresql.org 385 : 144490 : int len = strlen(s);
386 : :
387 [ - + ]: 144490 : Assert(manifest != NULL);
388 [ + + ]: 144490 : if (manifest->still_checksumming)
389 : : {
1229 michael@paquier.xyz 390 [ - + ]: 144064 : if (pg_cryptohash_update(manifest->manifest_ctx, (uint8 *) s, len) < 0)
824 michael@paquier.xyz 391 [ # # ]:UBC 0 : elog(ERROR, "failed to update checksum of backup manifest: %s",
392 : : pg_cryptohash_error(manifest->manifest_ctx));
393 : : }
1398 tmunro@postgresql.or 394 :CBC 144490 : BufFileWrite(manifest->buffile, s, len);
1452 rhaas@postgresql.org 395 : 144490 : manifest->manifest_size += len;
396 : 144490 : }
|