Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * parse_manifest.c
4 : : * Parse a backup manifest in JSON format.
5 : : *
6 : : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : * src/common/parse_manifest.c
10 : : *
11 : : *-------------------------------------------------------------------------
12 : : */
13 : :
14 : : #include "postgres_fe.h"
15 : :
16 : : #include "common/jsonapi.h"
17 : : #include "common/parse_manifest.h"
18 : :
19 : : /*
20 : : * Semantic states for JSON manifest parsing.
21 : : */
22 : : typedef enum
23 : : {
24 : : JM_EXPECT_TOPLEVEL_START,
25 : : JM_EXPECT_TOPLEVEL_END,
26 : : JM_EXPECT_TOPLEVEL_FIELD,
27 : : JM_EXPECT_VERSION_VALUE,
28 : : JM_EXPECT_SYSTEM_IDENTIFIER_VALUE,
29 : : JM_EXPECT_FILES_START,
30 : : JM_EXPECT_FILES_NEXT,
31 : : JM_EXPECT_THIS_FILE_FIELD,
32 : : JM_EXPECT_THIS_FILE_VALUE,
33 : : JM_EXPECT_WAL_RANGES_START,
34 : : JM_EXPECT_WAL_RANGES_NEXT,
35 : : JM_EXPECT_THIS_WAL_RANGE_FIELD,
36 : : JM_EXPECT_THIS_WAL_RANGE_VALUE,
37 : : JM_EXPECT_MANIFEST_CHECKSUM_VALUE,
38 : : JM_EXPECT_EOF,
39 : : } JsonManifestSemanticState;
40 : :
41 : : /*
42 : : * Possible fields for one file as described by the manifest.
43 : : */
44 : : typedef enum
45 : : {
46 : : JMFF_PATH,
47 : : JMFF_ENCODED_PATH,
48 : : JMFF_SIZE,
49 : : JMFF_LAST_MODIFIED,
50 : : JMFF_CHECKSUM_ALGORITHM,
51 : : JMFF_CHECKSUM,
52 : : } JsonManifestFileField;
53 : :
54 : : /*
55 : : * Possible fields for one file as described by the manifest.
56 : : */
57 : : typedef enum
58 : : {
59 : : JMWRF_TIMELINE,
60 : : JMWRF_START_LSN,
61 : : JMWRF_END_LSN,
62 : : } JsonManifestWALRangeField;
63 : :
64 : : /*
65 : : * Internal state used while decoding the JSON-format backup manifest.
66 : : */
67 : : typedef struct
68 : : {
69 : : JsonManifestParseContext *context;
70 : : JsonManifestSemanticState state;
71 : :
72 : : /* These fields are used for parsing objects in the list of files. */
73 : : JsonManifestFileField file_field;
74 : : char *pathname;
75 : : char *encoded_pathname;
76 : : char *size;
77 : : char *algorithm;
78 : : pg_checksum_type checksum_algorithm;
79 : : char *checksum;
80 : :
81 : : /* These fields are used for parsing objects in the list of WAL ranges. */
82 : : JsonManifestWALRangeField wal_range_field;
83 : : char *timeline;
84 : : char *start_lsn;
85 : : char *end_lsn;
86 : :
87 : : /* Miscellaneous other stuff. */
88 : : bool saw_version_field;
89 : : char *manifest_version;
90 : : char *manifest_system_identifier;
91 : : char *manifest_checksum;
92 : : } JsonManifestParseState;
93 : :
94 : : /* typedef appears in parse_manifest.h */
95 : : struct JsonManifestParseIncrementalState
96 : : {
97 : : JsonLexContext lex;
98 : : JsonSemAction sem;
99 : : pg_cryptohash_ctx *manifest_ctx;
100 : : };
101 : :
102 : : static JsonParseErrorType json_manifest_object_start(void *state);
103 : : static JsonParseErrorType json_manifest_object_end(void *state);
104 : : static JsonParseErrorType json_manifest_array_start(void *state);
105 : : static JsonParseErrorType json_manifest_array_end(void *state);
106 : : static JsonParseErrorType json_manifest_object_field_start(void *state, char *fname,
107 : : bool isnull);
108 : : static JsonParseErrorType json_manifest_scalar(void *state, char *token,
109 : : JsonTokenType tokentype);
110 : : static void json_manifest_finalize_version(JsonManifestParseState *parse);
111 : : static void json_manifest_finalize_system_identifier(JsonManifestParseState *parse);
112 : : static void json_manifest_finalize_file(JsonManifestParseState *parse);
113 : : static void json_manifest_finalize_wal_range(JsonManifestParseState *parse);
114 : : static void verify_manifest_checksum(JsonManifestParseState *parse,
115 : : char *buffer, size_t size,
116 : : pg_cryptohash_ctx *incr_ctx);
117 : : static void json_manifest_parse_failure(JsonManifestParseContext *context,
118 : : char *msg);
119 : :
120 : : static int hexdecode_char(char c);
121 : : static bool hexdecode_string(uint8 *result, char *input, int nbytes);
122 : : static bool parse_xlogrecptr(XLogRecPtr *result, char *input);
123 : :
124 : : /*
125 : : * Set up for incremental parsing of the manifest.
126 : : */
127 : :
128 : : JsonManifestParseIncrementalState *
35 andrew@dunslane.net 129 :GNC 97 : json_parse_manifest_incremental_init(JsonManifestParseContext *context)
130 : : {
131 : : JsonManifestParseIncrementalState *incstate;
132 : : JsonManifestParseState *parse;
133 : : pg_cryptohash_ctx *manifest_ctx;
134 : :
135 : 97 : incstate = palloc(sizeof(JsonManifestParseIncrementalState));
136 : 97 : parse = palloc(sizeof(JsonManifestParseState));
137 : :
138 : 97 : parse->context = context;
139 : 97 : parse->state = JM_EXPECT_TOPLEVEL_START;
140 : 97 : parse->saw_version_field = false;
141 : :
142 : 97 : makeJsonLexContextIncremental(&(incstate->lex), PG_UTF8, true);
143 : :
144 : 97 : incstate->sem.semstate = parse;
145 : 97 : incstate->sem.object_start = json_manifest_object_start;
146 : 97 : incstate->sem.object_end = json_manifest_object_end;
147 : 97 : incstate->sem.array_start = json_manifest_array_start;
148 : 97 : incstate->sem.array_end = json_manifest_array_end;
149 : 97 : incstate->sem.object_field_start = json_manifest_object_field_start;
150 : 97 : incstate->sem.object_field_end = NULL;
151 : 97 : incstate->sem.array_element_start = NULL;
152 : 97 : incstate->sem.array_element_end = NULL;
153 : 97 : incstate->sem.scalar = json_manifest_scalar;
154 : :
155 : 97 : manifest_ctx = pg_cryptohash_create(PG_SHA256);
156 [ - + ]: 97 : if (manifest_ctx == NULL)
35 andrew@dunslane.net 157 :UNC 0 : context->error_cb(context, "out of memory");
35 andrew@dunslane.net 158 [ - + ]:GNC 97 : if (pg_cryptohash_init(manifest_ctx) < 0)
35 andrew@dunslane.net 159 :UNC 0 : context->error_cb(context, "could not initialize checksum of manifest");
35 andrew@dunslane.net 160 :GNC 97 : incstate->manifest_ctx = manifest_ctx;
161 : :
162 : 97 : return incstate;
163 : : }
164 : :
165 : : /*
166 : : * Free an incremental state object and its contents.
167 : : */
168 : : void
5 169 : 95 : json_parse_manifest_incremental_shutdown(JsonManifestParseIncrementalState *incstate)
170 : : {
171 : 95 : pfree(incstate->sem.semstate);
172 : 95 : freeJsonLexContext(&(incstate->lex));
173 : : /* incstate->manifest_ctx has already been freed */
174 : 95 : pfree(incstate);
175 : 95 : }
176 : :
177 : : /*
178 : : * parse the manifest in pieces.
179 : : *
180 : : * The caller must ensure that the final piece contains the final lines
181 : : * with the complete checksum.
182 : : */
183 : :
184 : : void
35 185 : 193 : json_parse_manifest_incremental_chunk(
186 : : JsonManifestParseIncrementalState *incstate, char *chunk, int size,
187 : : bool is_last)
188 : : {
189 : : JsonParseErrorType res,
190 : : expected;
191 : 193 : JsonManifestParseState *parse = incstate->sem.semstate;
192 : 193 : JsonManifestParseContext *context = parse->context;
193 : :
194 : 193 : res = pg_parse_json_incremental(&(incstate->lex), &(incstate->sem),
195 : : chunk, size, is_last);
196 : :
197 : 192 : expected = is_last ? JSON_SUCCESS : JSON_INCOMPLETE;
198 : :
199 [ - + ]: 192 : if (res != expected)
35 andrew@dunslane.net 200 :UNC 0 : json_manifest_parse_failure(context,
201 : : json_errdetail(res, &(incstate->lex)));
202 : :
35 andrew@dunslane.net 203 [ + + - + ]:GNC 192 : if (is_last && parse->state != JM_EXPECT_EOF)
35 andrew@dunslane.net 204 :UNC 0 : json_manifest_parse_failure(context, "manifest ended unexpectedly");
205 : :
35 andrew@dunslane.net 206 [ + + ]:GNC 192 : if (!is_last)
207 : : {
208 [ - + ]: 96 : if (pg_cryptohash_update(incstate->manifest_ctx,
209 : : (uint8 *) chunk, size) < 0)
35 andrew@dunslane.net 210 :UNC 0 : context->error_cb(context, "could not update checksum of manifest");
211 : : }
212 : : else
213 : : {
35 andrew@dunslane.net 214 :GNC 96 : verify_manifest_checksum(parse, chunk, size, incstate->manifest_ctx);
215 : : }
216 : 191 : }
217 : :
218 : :
219 : : /*
220 : : * Main entrypoint to parse a JSON-format backup manifest.
221 : : *
222 : : * Caller should set up the parsing context and then invoke this function.
223 : : * For each file whose information is extracted from the manifest,
224 : : * context->per_file_cb is invoked. In case of trouble, context->error_cb is
225 : : * invoked and is expected not to return.
226 : : */
227 : : void
1472 rhaas@postgresql.org 228 :CBC 32 : json_parse_manifest(JsonManifestParseContext *context, char *buffer,
229 : : size_t size)
230 : : {
231 : : JsonLexContext *lex;
232 : : JsonParseErrorType json_error;
233 : : JsonSemAction sem;
234 : : JsonManifestParseState parse;
235 : :
236 : : /* Set up our private parsing context. */
237 : 32 : parse.context = context;
238 : 32 : parse.state = JM_EXPECT_TOPLEVEL_START;
239 : 32 : parse.saw_version_field = false;
240 : :
241 : : /* Create a JSON lexing context. */
192 alvherre@alvh.no-ip. 242 :GNC 32 : lex = makeJsonLexContextCstringLen(NULL, buffer, size, PG_UTF8, true);
243 : :
244 : : /* Set up semantic actions. */
1472 rhaas@postgresql.org 245 :CBC 32 : sem.semstate = &parse;
246 : 32 : sem.object_start = json_manifest_object_start;
247 : 32 : sem.object_end = json_manifest_object_end;
248 : 32 : sem.array_start = json_manifest_array_start;
249 : 32 : sem.array_end = json_manifest_array_end;
250 : 32 : sem.object_field_start = json_manifest_object_field_start;
251 : 32 : sem.object_field_end = NULL;
252 : 32 : sem.array_element_start = NULL;
253 : 32 : sem.array_element_end = NULL;
254 : 32 : sem.scalar = json_manifest_scalar;
255 : :
256 : : /* Run the actual JSON parser. */
257 : 32 : json_error = pg_parse_json(lex, &sem);
258 [ + + ]: 6 : if (json_error != JSON_SUCCESS)
28 dgustafsson@postgres 259 :GNC 1 : json_manifest_parse_failure(context, json_errdetail(json_error, lex));
1472 rhaas@postgresql.org 260 [ - + ]:CBC 5 : if (parse.state != JM_EXPECT_EOF)
1472 rhaas@postgresql.org 261 :UBC 0 : json_manifest_parse_failure(context, "manifest ended unexpectedly");
262 : :
263 : : /* Verify the manifest checksum. */
35 andrew@dunslane.net 264 :GNC 5 : verify_manifest_checksum(&parse, buffer, size, NULL);
265 : :
192 alvherre@alvh.no-ip. 266 : 2 : freeJsonLexContext(lex);
1472 rhaas@postgresql.org 267 :CBC 2 : }
268 : :
269 : : /*
270 : : * Invoked at the start of each object in the JSON document.
271 : : *
272 : : * The document as a whole is expected to be an object; each file and each
273 : : * WAL range is also expected to be an object. If we're anywhere else in the
274 : : * document, it's an error.
275 : : */
276 : : static JsonParseErrorType
277 : 96873 : json_manifest_object_start(void *state)
278 : : {
279 : 96873 : JsonManifestParseState *parse = state;
280 : :
281 [ + + + + ]: 96873 : switch (parse->state)
282 : : {
283 : 128 : case JM_EXPECT_TOPLEVEL_START:
284 : 128 : parse->state = JM_EXPECT_TOPLEVEL_FIELD;
285 : 128 : break;
286 : 96638 : case JM_EXPECT_FILES_NEXT:
287 : 96638 : parse->state = JM_EXPECT_THIS_FILE_FIELD;
288 : 96638 : parse->pathname = NULL;
289 : 96638 : parse->encoded_pathname = NULL;
290 : 96638 : parse->size = NULL;
291 : 96638 : parse->algorithm = NULL;
292 : 96638 : parse->checksum = NULL;
293 : 96638 : break;
294 : 106 : case JM_EXPECT_WAL_RANGES_NEXT:
295 : 106 : parse->state = JM_EXPECT_THIS_WAL_RANGE_FIELD;
296 : 106 : parse->timeline = NULL;
297 : 106 : parse->start_lsn = NULL;
298 : 106 : parse->end_lsn = NULL;
299 : 106 : break;
300 : 1 : default:
301 : 1 : json_manifest_parse_failure(parse->context,
302 : : "unexpected object start");
1472 rhaas@postgresql.org 303 :UBC 0 : break;
304 : : }
305 : :
490 tgl@sss.pgh.pa.us 306 :CBC 96872 : return JSON_SUCCESS;
307 : : }
308 : :
309 : : /*
310 : : * Invoked at the end of each object in the JSON document.
311 : : *
312 : : * The possible cases here are the same as for json_manifest_object_start.
313 : : * There's nothing special to do at the end of the document, but when we
314 : : * reach the end of an object representing a particular file or WAL range,
315 : : * we must call json_manifest_finalize_file() to save the associated details.
316 : : */
317 : : static JsonParseErrorType
1472 rhaas@postgresql.org 318 : 96845 : json_manifest_object_end(void *state)
319 : : {
320 : 96845 : JsonManifestParseState *parse = state;
321 : :
322 [ + + + + ]: 96845 : switch (parse->state)
323 : : {
324 : 101 : case JM_EXPECT_TOPLEVEL_END:
325 : 101 : parse->state = JM_EXPECT_EOF;
326 : 101 : break;
327 : 96637 : case JM_EXPECT_THIS_FILE_FIELD:
328 : 96637 : json_manifest_finalize_file(parse);
329 : 96628 : parse->state = JM_EXPECT_FILES_NEXT;
330 : 96628 : break;
331 : 105 : case JM_EXPECT_THIS_WAL_RANGE_FIELD:
332 : 105 : json_manifest_finalize_wal_range(parse);
333 : 99 : parse->state = JM_EXPECT_WAL_RANGES_NEXT;
334 : 99 : break;
335 : 2 : default:
336 : 2 : json_manifest_parse_failure(parse->context,
337 : : "unexpected object end");
1472 rhaas@postgresql.org 338 :UBC 0 : break;
339 : : }
340 : :
490 tgl@sss.pgh.pa.us 341 :CBC 96828 : return JSON_SUCCESS;
342 : : }
343 : :
344 : : /*
345 : : * Invoked at the start of each array in the JSON document.
346 : : *
347 : : * Within the toplevel object, the value associated with the "Files" key
348 : : * should be an array. Similarly for the "WAL-Ranges" key. No other arrays
349 : : * are expected.
350 : : */
351 : : static JsonParseErrorType
1472 rhaas@postgresql.org 352 : 218 : json_manifest_array_start(void *state)
353 : : {
354 : 218 : JsonManifestParseState *parse = state;
355 : :
356 [ + + + ]: 218 : switch (parse->state)
357 : : {
358 : 111 : case JM_EXPECT_FILES_START:
359 : 111 : parse->state = JM_EXPECT_FILES_NEXT;
360 : 111 : break;
361 : 106 : case JM_EXPECT_WAL_RANGES_START:
362 : 106 : parse->state = JM_EXPECT_WAL_RANGES_NEXT;
363 : 106 : break;
364 : 1 : default:
365 : 1 : json_manifest_parse_failure(parse->context,
366 : : "unexpected array start");
1472 rhaas@postgresql.org 367 :UBC 0 : break;
368 : : }
369 : :
490 tgl@sss.pgh.pa.us 370 :CBC 217 : return JSON_SUCCESS;
371 : : }
372 : :
373 : : /*
374 : : * Invoked at the end of each array in the JSON document.
375 : : *
376 : : * The cases here are analogous to those in json_manifest_array_start.
377 : : */
378 : : static JsonParseErrorType
1472 rhaas@postgresql.org 379 : 200 : json_manifest_array_end(void *state)
380 : : {
381 : 200 : JsonManifestParseState *parse = state;
382 : :
383 [ + - ]: 200 : switch (parse->state)
384 : : {
385 : 200 : case JM_EXPECT_FILES_NEXT:
386 : : case JM_EXPECT_WAL_RANGES_NEXT:
387 : 200 : parse->state = JM_EXPECT_TOPLEVEL_FIELD;
388 : 200 : break;
1472 rhaas@postgresql.org 389 :UBC 0 : default:
390 : 0 : json_manifest_parse_failure(parse->context,
391 : : "unexpected array end");
392 : 0 : break;
393 : : }
394 : :
490 tgl@sss.pgh.pa.us 395 :CBC 200 : return JSON_SUCCESS;
396 : : }
397 : :
398 : : /*
399 : : * Invoked at the start of each object field in the JSON document.
400 : : */
401 : : static JsonParseErrorType
1472 rhaas@postgresql.org 402 : 480144 : json_manifest_object_field_start(void *state, char *fname, bool isnull)
403 : : {
404 : 480144 : JsonManifestParseState *parse = state;
405 : :
406 [ + + + - ]: 480144 : switch (parse->state)
407 : : {
408 : 546 : case JM_EXPECT_TOPLEVEL_FIELD:
409 : :
410 : : /*
411 : : * Inside toplevel object. The version indicator should always be
412 : : * the first field.
413 : : */
414 [ + + ]: 546 : if (!parse->saw_version_field)
415 : : {
416 [ + + ]: 126 : if (strcmp(fname, "PostgreSQL-Backup-Manifest-Version") != 0)
417 : 1 : json_manifest_parse_failure(parse->context,
418 : : "expected version indicator");
419 : 125 : parse->state = JM_EXPECT_VERSION_VALUE;
420 : 125 : parse->saw_version_field = true;
421 : 125 : break;
422 : : }
423 : :
424 : : /* Is this the system identifier? */
32 rhaas@postgresql.org 425 [ + + ]:GNC 420 : if (strcmp(fname, "System-Identifier") == 0)
426 : : {
427 : 99 : parse->state = JM_EXPECT_SYSTEM_IDENTIFIER_VALUE;
428 : 99 : break;
429 : : }
430 : :
431 : : /* Is this the list of files? */
1472 rhaas@postgresql.org 432 [ + + ]:CBC 321 : if (strcmp(fname, "Files") == 0)
433 : : {
434 : 113 : parse->state = JM_EXPECT_FILES_START;
435 : 113 : break;
436 : : }
437 : :
438 : : /* Is this the list of WAL ranges? */
439 [ + + ]: 208 : if (strcmp(fname, "WAL-Ranges") == 0)
440 : : {
441 : 106 : parse->state = JM_EXPECT_WAL_RANGES_START;
442 : 106 : break;
443 : : }
444 : :
445 : : /* Is this the manifest checksum? */
446 [ + + ]: 102 : if (strcmp(fname, "Manifest-Checksum") == 0)
447 : : {
448 : 101 : parse->state = JM_EXPECT_MANIFEST_CHECKSUM_VALUE;
449 : 101 : break;
450 : : }
451 : :
452 : : /* It's not a field we recognize. */
453 : 1 : json_manifest_parse_failure(parse->context,
454 : : "unrecognized top-level field");
1472 rhaas@postgresql.org 455 :UBC 0 : break;
456 : :
1472 rhaas@postgresql.org 457 :CBC 479288 : case JM_EXPECT_THIS_FILE_FIELD:
458 : : /* Inside object for one file; which key have we got? */
459 [ + + ]: 479288 : if (strcmp(fname, "Path") == 0)
460 : 95669 : parse->file_field = JMFF_PATH;
461 [ + + ]: 383619 : else if (strcmp(fname, "Encoded-Path") == 0)
462 : 968 : parse->file_field = JMFF_ENCODED_PATH;
463 [ + + ]: 382651 : else if (strcmp(fname, "Size") == 0)
464 : 96634 : parse->file_field = JMFF_SIZE;
465 [ + + ]: 286017 : else if (strcmp(fname, "Last-Modified") == 0)
466 : 96627 : parse->file_field = JMFF_LAST_MODIFIED;
467 [ + + ]: 189390 : else if (strcmp(fname, "Checksum-Algorithm") == 0)
468 : 94694 : parse->file_field = JMFF_CHECKSUM_ALGORITHM;
469 [ + + ]: 94696 : else if (strcmp(fname, "Checksum") == 0)
470 : 94695 : parse->file_field = JMFF_CHECKSUM;
471 : : else
472 : 1 : json_manifest_parse_failure(parse->context,
473 : : "unexpected file field");
474 : 479287 : parse->state = JM_EXPECT_THIS_FILE_VALUE;
475 : 479287 : break;
476 : :
477 : 310 : case JM_EXPECT_THIS_WAL_RANGE_FIELD:
478 : : /* Inside object for one file; which key have we got? */
479 [ + + ]: 310 : if (strcmp(fname, "Timeline") == 0)
480 : 104 : parse->wal_range_field = JMWRF_TIMELINE;
481 [ + + ]: 206 : else if (strcmp(fname, "Start-LSN") == 0)
482 : 103 : parse->wal_range_field = JMWRF_START_LSN;
483 [ + + ]: 103 : else if (strcmp(fname, "End-LSN") == 0)
484 : 102 : parse->wal_range_field = JMWRF_END_LSN;
485 : : else
486 : 1 : json_manifest_parse_failure(parse->context,
487 : : "unexpected WAL range field");
488 : 309 : parse->state = JM_EXPECT_THIS_WAL_RANGE_VALUE;
489 : 309 : break;
490 : :
1472 rhaas@postgresql.org 491 :UBC 0 : default:
492 : 0 : json_manifest_parse_failure(parse->context,
493 : : "unexpected object field");
494 : 0 : break;
495 : : }
496 : :
35 andrew@dunslane.net 497 :GNC 480140 : pfree(fname);
498 : :
490 tgl@sss.pgh.pa.us 499 :CBC 480140 : return JSON_SUCCESS;
500 : : }
501 : :
502 : : /*
503 : : * Invoked at the start of each scalar in the JSON document.
504 : : *
505 : : * Object field names don't reach this code; those are handled by
506 : : * json_manifest_object_field_start. When we're inside of the object for
507 : : * a particular file or WAL range, that function will have noticed the name
508 : : * of the field, and we'll get the corresponding value here. When we're in
509 : : * the toplevel object, the parse state itself tells us which field this is.
510 : : *
511 : : * In all cases except for PostgreSQL-Backup-Manifest-Version, which we
512 : : * can just check on the spot, the goal here is just to save the value in
513 : : * the parse state for later use. We don't actually do anything until we
514 : : * reach either the end of the object representing this file, or the end
515 : : * of the manifest, as the case may be.
516 : : */
517 : : static JsonParseErrorType
1472 rhaas@postgresql.org 518 : 479922 : json_manifest_scalar(void *state, char *token, JsonTokenType tokentype)
519 : : {
520 : 479922 : JsonManifestParseState *parse = state;
521 : :
522 [ + + + + : 479922 : switch (parse->state)
+ + ]
523 : : {
524 : 125 : case JM_EXPECT_VERSION_VALUE:
32 rhaas@postgresql.org 525 :GNC 125 : parse->manifest_version = token;
526 : 125 : json_manifest_finalize_version(parse);
527 : 123 : parse->state = JM_EXPECT_TOPLEVEL_FIELD;
528 : 123 : break;
529 : :
530 : 99 : case JM_EXPECT_SYSTEM_IDENTIFIER_VALUE:
531 : 99 : parse->manifest_system_identifier = token;
532 : 99 : json_manifest_finalize_system_identifier(parse);
1472 rhaas@postgresql.org 533 :CBC 98 : parse->state = JM_EXPECT_TOPLEVEL_FIELD;
534 : 98 : break;
535 : :
536 : 479287 : case JM_EXPECT_THIS_FILE_VALUE:
537 [ + + + + : 479287 : switch (parse->file_field)
+ + - ]
538 : : {
539 : 95669 : case JMFF_PATH:
540 : 95669 : parse->pathname = token;
541 : 95669 : break;
542 : 968 : case JMFF_ENCODED_PATH:
543 : 968 : parse->encoded_pathname = token;
544 : 968 : break;
545 : 96634 : case JMFF_SIZE:
546 : 96634 : parse->size = token;
547 : 96634 : break;
548 : 96627 : case JMFF_LAST_MODIFIED:
549 : 96627 : pfree(token); /* unused */
550 : 96627 : break;
551 : 94694 : case JMFF_CHECKSUM_ALGORITHM:
552 : 94694 : parse->algorithm = token;
553 : 94694 : break;
554 : 94695 : case JMFF_CHECKSUM:
555 : 94695 : parse->checksum = token;
556 : 94695 : break;
557 : : }
558 : 479287 : parse->state = JM_EXPECT_THIS_FILE_FIELD;
559 : 479287 : break;
560 : :
561 : 309 : case JM_EXPECT_THIS_WAL_RANGE_VALUE:
562 [ + + + - ]: 309 : switch (parse->wal_range_field)
563 : : {
564 : 104 : case JMWRF_TIMELINE:
565 : 104 : parse->timeline = token;
566 : 104 : break;
567 : 103 : case JMWRF_START_LSN:
568 : 103 : parse->start_lsn = token;
569 : 103 : break;
570 : 102 : case JMWRF_END_LSN:
571 : 102 : parse->end_lsn = token;
572 : 102 : break;
573 : : }
574 : 309 : parse->state = JM_EXPECT_THIS_WAL_RANGE_FIELD;
575 : 309 : break;
576 : :
577 : 101 : case JM_EXPECT_MANIFEST_CHECKSUM_VALUE:
578 : 101 : parse->state = JM_EXPECT_TOPLEVEL_END;
579 : 101 : parse->manifest_checksum = token;
580 : 101 : break;
581 : :
582 : 1 : default:
583 : 1 : json_manifest_parse_failure(parse->context, "unexpected scalar");
1472 rhaas@postgresql.org 584 :UBC 0 : break;
585 : : }
586 : :
490 tgl@sss.pgh.pa.us 587 :CBC 479918 : return JSON_SUCCESS;
588 : : }
589 : :
590 : : /*
591 : : * Do additional parsing and sanity-checking of the manifest version, and invoke
592 : : * the callback so that the caller can gets that detail and take actions
593 : : * accordingly. This happens for each manifest when the corresponding JSON
594 : : * object is completely parsed.
595 : : */
596 : : static void
32 rhaas@postgresql.org 597 :GNC 125 : json_manifest_finalize_version(JsonManifestParseState *parse)
598 : : {
599 : 125 : JsonManifestParseContext *context = parse->context;
600 : : int version;
601 : : char *ep;
602 : :
603 [ - + ]: 125 : Assert(parse->saw_version_field);
604 : :
605 : : /* Parse version. */
606 : 125 : version = strtoi64(parse->manifest_version, &ep, 10);
607 [ + + ]: 125 : if (*ep)
608 : 1 : json_manifest_parse_failure(parse->context,
609 : : "manifest version not an integer");
610 : :
611 [ + + + + ]: 124 : if (version != 1 && version != 2)
612 : 1 : json_manifest_parse_failure(parse->context,
613 : : "unexpected manifest version");
614 : :
615 : : /* Invoke the callback for version */
616 : 123 : context->version_cb(context, version);
617 : 123 : }
618 : :
619 : : /*
620 : : * Do additional parsing and sanity-checking of the system identifier, and
621 : : * invoke the callback so that the caller can gets that detail and take actions
622 : : * accordingly.
623 : : */
624 : : static void
625 : 99 : json_manifest_finalize_system_identifier(JsonManifestParseState *parse)
626 : : {
627 : 99 : JsonManifestParseContext *context = parse->context;
628 : : uint64 system_identifier;
629 : : char *ep;
630 : :
631 [ - + ]: 99 : Assert(parse->manifest_system_identifier != NULL);
632 : :
633 : : /* Parse system identifier. */
634 : 99 : system_identifier = strtou64(parse->manifest_system_identifier, &ep, 10);
635 [ - + ]: 99 : if (*ep)
32 rhaas@postgresql.org 636 :UNC 0 : json_manifest_parse_failure(parse->context,
637 : : "manifest system identifier not an integer");
638 : :
639 : : /* Invoke the callback for system identifier */
32 rhaas@postgresql.org 640 :GNC 99 : context->system_identifier_cb(context, system_identifier);
641 : 98 : }
642 : :
643 : : /*
644 : : * Do additional parsing and sanity-checking of the details gathered for one
645 : : * file, and invoke the per-file callback so that the caller gets those
646 : : * details. This happens for each file when the corresponding JSON object is
647 : : * completely parsed.
648 : : */
649 : : static void
1472 rhaas@postgresql.org 650 :CBC 96637 : json_manifest_finalize_file(JsonManifestParseState *parse)
651 : : {
652 : 96637 : JsonManifestParseContext *context = parse->context;
653 : : size_t size;
654 : : char *ep;
655 : : int checksum_string_length;
656 : : pg_checksum_type checksum_type;
657 : : int checksum_length;
658 : : uint8 *checksum_payload;
659 : :
660 : : /* Pathname and size are required. */
661 [ + + + + ]: 96637 : if (parse->pathname == NULL && parse->encoded_pathname == NULL)
1308 peter@eisentraut.org 662 : 1 : json_manifest_parse_failure(parse->context, "missing path name");
1472 rhaas@postgresql.org 663 [ + + + + ]: 96636 : if (parse->pathname != NULL && parse->encoded_pathname != NULL)
664 : 1 : json_manifest_parse_failure(parse->context,
665 : : "both path name and encoded path name");
666 [ + + ]: 96635 : if (parse->size == NULL)
667 : 1 : json_manifest_parse_failure(parse->context, "missing size");
668 [ + + + + ]: 96634 : if (parse->algorithm == NULL && parse->checksum != NULL)
669 : 1 : json_manifest_parse_failure(parse->context,
670 : : "checksum without algorithm");
671 : :
672 : : /* Decode encoded pathname, if that's what we have. */
673 [ + + ]: 96633 : if (parse->encoded_pathname != NULL)
674 : : {
675 : 967 : int encoded_length = strlen(parse->encoded_pathname);
676 : 967 : int raw_length = encoded_length / 2;
677 : :
678 : 967 : parse->pathname = palloc(raw_length + 1);
679 [ + + ]: 967 : if (encoded_length % 2 != 0 ||
680 [ - + ]: 966 : !hexdecode_string((uint8 *) parse->pathname,
681 : : parse->encoded_pathname,
682 : : raw_length))
683 : 1 : json_manifest_parse_failure(parse->context,
684 : : "could not decode file name");
685 : 966 : parse->pathname[raw_length] = '\0';
686 : 966 : pfree(parse->encoded_pathname);
687 : 966 : parse->encoded_pathname = NULL;
688 : : }
689 : :
690 : : /* Parse size. */
691 : 96632 : size = strtoul(parse->size, &ep, 10);
692 [ + + ]: 96632 : if (*ep)
693 : 1 : json_manifest_parse_failure(parse->context,
694 : : "file size is not an integer");
695 : :
696 : : /* Parse the checksum algorithm, if it's present. */
697 [ + + ]: 96631 : if (parse->algorithm == NULL)
698 : 1937 : checksum_type = CHECKSUM_TYPE_NONE;
699 [ + + ]: 94694 : else if (!pg_checksum_parse_type(parse->algorithm, &checksum_type))
700 : 1 : context->error_cb(context, "unrecognized checksum algorithm: \"%s\"",
701 : : parse->algorithm);
702 : :
703 : : /* Parse the checksum payload, if it's present. */
704 [ + + ]: 96630 : checksum_string_length = parse->checksum == NULL ? 0
705 : 94693 : : strlen(parse->checksum);
706 [ + + ]: 96630 : if (checksum_string_length == 0)
707 : : {
708 : 1937 : checksum_length = 0;
709 : 1937 : checksum_payload = NULL;
710 : : }
711 : : else
712 : : {
713 : 94693 : checksum_length = checksum_string_length / 2;
714 : 94693 : checksum_payload = palloc(checksum_length);
715 [ + + ]: 94693 : if (checksum_string_length % 2 != 0 ||
716 [ - + ]: 94692 : !hexdecode_string(checksum_payload, parse->checksum,
717 : : checksum_length))
718 : 1 : context->error_cb(context,
719 : : "invalid checksum for file \"%s\": \"%s\"",
720 : : parse->pathname, parse->checksum);
721 : : }
722 : :
723 : : /* Invoke the callback with the details we've gathered. */
131 rhaas@postgresql.org 724 :GNC 96629 : context->per_file_cb(context, parse->pathname, size,
725 : : checksum_type, checksum_length, checksum_payload);
726 : :
727 : : /* Free memory we no longer need. */
1472 rhaas@postgresql.org 728 [ + - ]:CBC 96628 : if (parse->size != NULL)
729 : : {
730 : 96628 : pfree(parse->size);
731 : 96628 : parse->size = NULL;
732 : : }
733 [ + + ]: 96628 : if (parse->algorithm != NULL)
734 : : {
735 : 94692 : pfree(parse->algorithm);
736 : 94692 : parse->algorithm = NULL;
737 : : }
738 [ + + ]: 96628 : if (parse->checksum != NULL)
739 : : {
740 : 94692 : pfree(parse->checksum);
741 : 94692 : parse->checksum = NULL;
742 : : }
743 : 96628 : }
744 : :
745 : : /*
746 : : * Do additional parsing and sanity-checking of the details gathered for one
747 : : * WAL range, and invoke the per-WAL-range callback so that the caller gets
748 : : * those details. This happens for each WAL range when the corresponding JSON
749 : : * object is completely parsed.
750 : : */
751 : : static void
752 : 105 : json_manifest_finalize_wal_range(JsonManifestParseState *parse)
753 : : {
754 : 105 : JsonManifestParseContext *context = parse->context;
755 : : TimeLineID tli;
756 : : XLogRecPtr start_lsn,
757 : : end_lsn;
758 : : char *ep;
759 : :
760 : : /* Make sure all fields are present. */
761 [ + + ]: 105 : if (parse->timeline == NULL)
762 : 1 : json_manifest_parse_failure(parse->context, "missing timeline");
763 [ + + ]: 104 : if (parse->start_lsn == NULL)
764 : 1 : json_manifest_parse_failure(parse->context, "missing start LSN");
765 [ + + ]: 103 : if (parse->end_lsn == NULL)
766 : 1 : json_manifest_parse_failure(parse->context, "missing end LSN");
767 : :
768 : : /* Parse timeline. */
769 : 102 : tli = strtoul(parse->timeline, &ep, 10);
770 [ + + ]: 102 : if (*ep)
771 : 1 : json_manifest_parse_failure(parse->context,
772 : : "timeline is not an integer");
773 [ + + ]: 101 : if (!parse_xlogrecptr(&start_lsn, parse->start_lsn))
774 : 1 : json_manifest_parse_failure(parse->context,
775 : : "could not parse start LSN");
776 [ + + ]: 100 : if (!parse_xlogrecptr(&end_lsn, parse->end_lsn))
777 : 1 : json_manifest_parse_failure(parse->context,
778 : : "could not parse end LSN");
779 : :
780 : : /* Invoke the callback with the details we've gathered. */
131 rhaas@postgresql.org 781 :GNC 99 : context->per_wal_range_cb(context, tli, start_lsn, end_lsn);
782 : :
783 : : /* Free memory we no longer need. */
1472 rhaas@postgresql.org 784 [ + - ]:CBC 99 : if (parse->timeline != NULL)
785 : : {
786 : 99 : pfree(parse->timeline);
787 : 99 : parse->timeline = NULL;
788 : : }
789 [ + - ]: 99 : if (parse->start_lsn != NULL)
790 : : {
791 : 99 : pfree(parse->start_lsn);
792 : 99 : parse->start_lsn = NULL;
793 : : }
794 [ + - ]: 99 : if (parse->end_lsn != NULL)
795 : : {
796 : 99 : pfree(parse->end_lsn);
797 : 99 : parse->end_lsn = NULL;
798 : : }
799 : 99 : }
800 : :
801 : : /*
802 : : * Verify that the manifest checksum is correct.
803 : : *
804 : : * The last line of the manifest file is excluded from the manifest checksum,
805 : : * because the last line is expected to contain the checksum that covers
806 : : * the rest of the file.
807 : : *
808 : : * For an incremental parse, this will just be called on the last chunk of the
809 : : * manifest, and the cryptohash context passed in. For a non-incremental
810 : : * parse incr_ctx will be NULL.
811 : : */
812 : : static void
813 : 101 : verify_manifest_checksum(JsonManifestParseState *parse, char *buffer,
814 : : size_t size, pg_cryptohash_ctx *incr_ctx)
815 : : {
816 : 101 : JsonManifestParseContext *context = parse->context;
817 : : size_t i;
818 : 101 : size_t number_of_newlines = 0;
819 : 101 : size_t ultimate_newline = 0;
820 : 101 : size_t penultimate_newline = 0;
821 : : pg_cryptohash_ctx *manifest_ctx;
822 : : uint8 manifest_checksum_actual[PG_SHA256_DIGEST_LENGTH];
823 : : uint8 manifest_checksum_expected[PG_SHA256_DIGEST_LENGTH];
824 : :
825 : : /* Find the last two newlines in the file. */
826 [ + + ]: 6757491 : for (i = 0; i < size; ++i)
827 : : {
828 [ + + ]: 6757390 : if (buffer[i] == '\n')
829 : : {
830 : 47040 : ++number_of_newlines;
831 : 47040 : penultimate_newline = ultimate_newline;
832 : 47040 : ultimate_newline = i;
833 : : }
834 : : }
835 : :
836 : : /*
837 : : * Make sure that the last newline is right at the end, and that there are
838 : : * at least two lines total. We need this to be true in order for the
839 : : * following code, which computes the manifest checksum, to work properly.
840 : : */
841 [ + + ]: 101 : if (number_of_newlines < 2)
842 : 1 : json_manifest_parse_failure(parse->context,
843 : : "expected at least 2 lines");
844 [ + + ]: 100 : if (ultimate_newline != size - 1)
845 : 1 : json_manifest_parse_failure(parse->context,
846 : : "last line not newline-terminated");
847 : :
848 : : /* Checksum the rest. */
35 andrew@dunslane.net 849 [ + + ]:GNC 99 : if (incr_ctx == NULL)
850 : : {
851 : 3 : manifest_ctx = pg_cryptohash_create(PG_SHA256);
852 [ - + ]: 3 : if (manifest_ctx == NULL)
35 andrew@dunslane.net 853 :UNC 0 : context->error_cb(context, "out of memory");
35 andrew@dunslane.net 854 [ - + ]:GNC 3 : if (pg_cryptohash_init(manifest_ctx) < 0)
35 andrew@dunslane.net 855 :UNC 0 : context->error_cb(context, "could not initialize checksum of manifest");
856 : : }
857 : : else
858 : : {
35 andrew@dunslane.net 859 :GNC 96 : manifest_ctx = incr_ctx;
860 : : }
1229 michael@paquier.xyz 861 [ - + ]:CBC 99 : if (pg_cryptohash_update(manifest_ctx, (uint8 *) buffer, penultimate_newline + 1) < 0)
1229 michael@paquier.xyz 862 :UBC 0 : context->error_cb(context, "could not update checksum of manifest");
1154 michael@paquier.xyz 863 [ - + ]:CBC 99 : if (pg_cryptohash_final(manifest_ctx, manifest_checksum_actual,
864 : : sizeof(manifest_checksum_actual)) < 0)
1229 michael@paquier.xyz 865 :UBC 0 : context->error_cb(context, "could not finalize checksum of manifest");
866 : :
867 : : /* Now verify it. */
1472 rhaas@postgresql.org 868 [ - + ]:CBC 99 : if (parse->manifest_checksum == NULL)
1472 rhaas@postgresql.org 869 :UBC 0 : context->error_cb(parse->context, "manifest has no checksum");
1472 rhaas@postgresql.org 870 [ + - ]:CBC 99 : if (strlen(parse->manifest_checksum) != PG_SHA256_DIGEST_LENGTH * 2 ||
871 [ + + ]: 99 : !hexdecode_string(manifest_checksum_expected, parse->manifest_checksum,
872 : : PG_SHA256_DIGEST_LENGTH))
873 : 1 : context->error_cb(context, "invalid manifest checksum: \"%s\"",
874 : : parse->manifest_checksum);
875 [ + + ]: 98 : if (memcmp(manifest_checksum_actual, manifest_checksum_expected,
876 : : PG_SHA256_DIGEST_LENGTH) != 0)
877 : 1 : context->error_cb(context, "manifest checksum mismatch");
1229 michael@paquier.xyz 878 : 97 : pg_cryptohash_free(manifest_ctx);
1472 rhaas@postgresql.org 879 : 97 : }
880 : :
881 : : /*
882 : : * Report a parse error.
883 : : *
884 : : * This is intended to be used for fairly low-level failures that probably
885 : : * shouldn't occur unless somebody has deliberately constructed a bad manifest,
886 : : * or unless the server is generating bad manifests due to some bug. msg should
887 : : * be a short string giving some hint as to what the problem is.
888 : : */
889 : : static void
890 : 26 : json_manifest_parse_failure(JsonManifestParseContext *context, char *msg)
891 : : {
892 : 26 : context->error_cb(context, "could not parse backup manifest: %s", msg);
893 : : }
894 : :
895 : : /*
896 : : * Convert a character which represents a hexadecimal digit to an integer.
897 : : *
898 : : * Returns -1 if the character is not a hexadecimal digit.
899 : : */
900 : : static int
901 : 1134784 : hexdecode_char(char c)
902 : : {
903 [ + - + + ]: 1134784 : if (c >= '0' && c <= '9')
904 : 740601 : return c - '0';
905 [ + + + - ]: 394183 : if (c >= 'a' && c <= 'f')
906 : 394175 : return c - 'a' + 10;
907 [ + - + + ]: 8 : if (c >= 'A' && c <= 'F')
908 : 6 : return c - 'A' + 10;
909 : :
910 : 2 : return -1;
911 : : }
912 : :
913 : : /*
914 : : * Decode a hex string into a byte string, 2 hex chars per byte.
915 : : *
916 : : * Returns false if invalid characters are encountered; otherwise true.
917 : : */
918 : : static bool
919 : 95757 : hexdecode_string(uint8 *result, char *input, int nbytes)
920 : : {
921 : : int i;
922 : :
923 [ + + ]: 663148 : for (i = 0; i < nbytes; ++i)
924 : : {
925 : 567392 : int n1 = hexdecode_char(input[i * 2]);
926 : 567392 : int n2 = hexdecode_char(input[i * 2 + 1]);
927 : :
928 [ + + - + ]: 567392 : if (n1 < 0 || n2 < 0)
929 : 1 : return false;
930 : 567391 : result[i] = n1 * 16 + n2;
931 : : }
932 : :
933 : 95756 : return true;
934 : : }
935 : :
936 : : /*
937 : : * Parse an XLogRecPtr expressed using the usual string format.
938 : : */
939 : : static bool
940 : 201 : parse_xlogrecptr(XLogRecPtr *result, char *input)
941 : : {
942 : : uint32 hi;
943 : : uint32 lo;
944 : :
945 [ + + ]: 201 : if (sscanf(input, "%X/%X", &hi, &lo) != 2)
946 : 2 : return false;
947 : 199 : *result = ((uint64) hi) << 32 | lo;
948 : 199 : return true;
949 : : }
|