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