Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * parse_manifest.c
4 : * Parse a backup manifest in JSON format.
5 : *
6 : * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
7 : * Portions Copyright (c) 1994, Regents of the University of California
8 : *
9 : * src/bin/pg_verifybackup/parse_manifest.c
10 : *
11 : *-------------------------------------------------------------------------
12 : */
13 :
14 : #include "postgres_fe.h"
15 :
16 : #include "parse_manifest.h"
17 : #include "common/jsonapi.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_FILES_START,
29 : JM_EXPECT_FILES_NEXT,
30 : JM_EXPECT_THIS_FILE_FIELD,
31 : JM_EXPECT_THIS_FILE_VALUE,
32 : JM_EXPECT_WAL_RANGES_START,
33 : JM_EXPECT_WAL_RANGES_NEXT,
34 : JM_EXPECT_THIS_WAL_RANGE_FIELD,
35 : JM_EXPECT_THIS_WAL_RANGE_VALUE,
36 : JM_EXPECT_MANIFEST_CHECKSUM_VALUE,
37 : JM_EXPECT_EOF,
38 : } JsonManifestSemanticState;
39 :
40 : /*
41 : * Possible fields for one file as described by the manifest.
42 : */
43 : typedef enum
44 : {
45 : JMFF_PATH,
46 : JMFF_ENCODED_PATH,
47 : JMFF_SIZE,
48 : JMFF_LAST_MODIFIED,
49 : JMFF_CHECKSUM_ALGORITHM,
50 : JMFF_CHECKSUM,
51 : } JsonManifestFileField;
52 :
53 : /*
54 : * Possible fields for one file as described by the manifest.
55 : */
56 : typedef enum
57 : {
58 : JMWRF_TIMELINE,
59 : JMWRF_START_LSN,
60 : JMWRF_END_LSN,
61 : } JsonManifestWALRangeField;
62 :
63 : /*
64 : * Internal state used while decoding the JSON-format backup manifest.
65 : */
66 : typedef struct
67 : {
68 : JsonManifestParseContext *context;
69 : JsonManifestSemanticState state;
70 :
71 : /* These fields are used for parsing objects in the list of files. */
72 : JsonManifestFileField file_field;
73 : char *pathname;
74 : char *encoded_pathname;
75 : char *size;
76 : char *algorithm;
77 : pg_checksum_type checksum_algorithm;
78 : char *checksum;
79 :
80 : /* These fields are used for parsing objects in the list of WAL ranges. */
81 : JsonManifestWALRangeField wal_range_field;
82 : char *timeline;
83 : char *start_lsn;
84 : char *end_lsn;
85 :
86 : /* Miscellaneous other stuff. */
87 : bool saw_version_field;
88 : char *manifest_checksum;
89 : } JsonManifestParseState;
90 :
91 : static JsonParseErrorType json_manifest_object_start(void *state);
92 : static JsonParseErrorType json_manifest_object_end(void *state);
93 : static JsonParseErrorType json_manifest_array_start(void *state);
94 : static JsonParseErrorType json_manifest_array_end(void *state);
95 : static JsonParseErrorType json_manifest_object_field_start(void *state, char *fname,
96 : bool isnull);
97 : static JsonParseErrorType json_manifest_scalar(void *state, char *token,
98 : JsonTokenType tokentype);
99 : static void json_manifest_finalize_file(JsonManifestParseState *parse);
100 : static void json_manifest_finalize_wal_range(JsonManifestParseState *parse);
101 : static void verify_manifest_checksum(JsonManifestParseState *parse,
102 : char *buffer, size_t size);
103 : static void json_manifest_parse_failure(JsonManifestParseContext *context,
104 : char *msg);
105 :
106 : static int hexdecode_char(char c);
107 : static bool hexdecode_string(uint8 *result, char *input, int nbytes);
108 : static bool parse_xlogrecptr(XLogRecPtr *result, char *input);
109 :
110 : /*
111 : * Main entrypoint to parse a JSON-format backup manifest.
112 : *
113 : * Caller should set up the parsing context and then invoke this function.
114 : * For each file whose information is extracted from the manifest,
115 : * context->per_file_cb is invoked. In case of trouble, context->error_cb is
116 : * invoked and is expected not to return.
117 : */
118 : void
119 162 : json_parse_manifest(JsonManifestParseContext *context, char *buffer,
120 : size_t size)
121 : {
122 : JsonLexContext *lex;
123 : JsonParseErrorType json_error;
124 : JsonSemAction sem;
125 : JsonManifestParseState parse;
126 :
127 : /* Set up our private parsing context. */
128 162 : parse.context = context;
129 162 : parse.state = JM_EXPECT_TOPLEVEL_START;
130 162 : parse.saw_version_field = false;
131 :
132 : /* Create a JSON lexing context. */
133 162 : lex = makeJsonLexContextCstringLen(NULL, buffer, size, PG_UTF8, true);
134 :
135 : /* Set up semantic actions. */
136 162 : sem.semstate = &parse;
137 162 : sem.object_start = json_manifest_object_start;
138 162 : sem.object_end = json_manifest_object_end;
139 162 : sem.array_start = json_manifest_array_start;
140 162 : sem.array_end = json_manifest_array_end;
141 162 : sem.object_field_start = json_manifest_object_field_start;
142 162 : sem.object_field_end = NULL;
143 162 : sem.array_element_start = NULL;
144 162 : sem.array_element_end = NULL;
145 162 : sem.scalar = json_manifest_scalar;
146 :
147 : /* Run the actual JSON parser. */
148 162 : json_error = pg_parse_json(lex, &sem);
149 112 : if (json_error != JSON_SUCCESS)
150 2 : json_manifest_parse_failure(context, "parsing failed");
151 110 : if (parse.state != JM_EXPECT_EOF)
152 0 : json_manifest_parse_failure(context, "manifest ended unexpectedly");
153 :
154 : /* Verify the manifest checksum. */
155 110 : verify_manifest_checksum(&parse, buffer, size);
156 :
157 102 : freeJsonLexContext(lex);
158 102 : }
159 :
160 : /*
161 : * Invoked at the start of each object in the JSON document.
162 : *
163 : * The document as a whole is expected to be an object; each file and each
164 : * WAL range is also expected to be an object. If we're anywhere else in the
165 : * document, it's an error.
166 : */
167 : static JsonParseErrorType
168 100886 : json_manifest_object_start(void *state)
169 : {
170 100886 : JsonManifestParseState *parse = state;
171 :
172 100886 : switch (parse->state)
173 : {
174 160 : case JM_EXPECT_TOPLEVEL_START:
175 160 : parse->state = JM_EXPECT_TOPLEVEL_FIELD;
176 160 : break;
177 100604 : case JM_EXPECT_FILES_NEXT:
178 100604 : parse->state = JM_EXPECT_THIS_FILE_FIELD;
179 100604 : parse->pathname = NULL;
180 100604 : parse->encoded_pathname = NULL;
181 100604 : parse->size = NULL;
182 100604 : parse->algorithm = NULL;
183 100604 : parse->checksum = NULL;
184 100604 : break;
185 120 : case JM_EXPECT_WAL_RANGES_NEXT:
186 120 : parse->state = JM_EXPECT_THIS_WAL_RANGE_FIELD;
187 120 : parse->timeline = NULL;
188 120 : parse->start_lsn = NULL;
189 120 : parse->end_lsn = NULL;
190 120 : break;
191 2 : default:
192 2 : json_manifest_parse_failure(parse->context,
193 : "unexpected object start");
194 0 : break;
195 : }
196 :
197 100884 : return JSON_SUCCESS;
198 : }
199 :
200 : /*
201 : * Invoked at the end of each object in the JSON document.
202 : *
203 : * The possible cases here are the same as for json_manifest_object_start.
204 : * There's nothing special to do at the end of the document, but when we
205 : * reach the end of an object representing a particular file or WAL range,
206 : * we must call json_manifest_finalize_file() to save the associated details.
207 : */
208 : static JsonParseErrorType
209 100834 : json_manifest_object_end(void *state)
210 : {
211 100834 : JsonManifestParseState *parse = state;
212 :
213 100834 : switch (parse->state)
214 : {
215 110 : case JM_EXPECT_TOPLEVEL_END:
216 110 : parse->state = JM_EXPECT_EOF;
217 110 : break;
218 100602 : case JM_EXPECT_THIS_FILE_FIELD:
219 100602 : json_manifest_finalize_file(parse);
220 100584 : parse->state = JM_EXPECT_FILES_NEXT;
221 100584 : break;
222 118 : case JM_EXPECT_THIS_WAL_RANGE_FIELD:
223 118 : json_manifest_finalize_wal_range(parse);
224 106 : parse->state = JM_EXPECT_WAL_RANGES_NEXT;
225 106 : break;
226 4 : default:
227 4 : json_manifest_parse_failure(parse->context,
228 : "unexpected object end");
229 0 : break;
230 : }
231 :
232 100800 : return JSON_SUCCESS;
233 : }
234 :
235 : /*
236 : * Invoked at the start of each array in the JSON document.
237 : *
238 : * Within the toplevel object, the value associated with the "Files" key
239 : * should be an array. Similarly for the "WAL-Ranges" key. No other arrays
240 : * are expected.
241 : */
242 : static JsonParseErrorType
243 252 : json_manifest_array_start(void *state)
244 : {
245 252 : JsonManifestParseState *parse = state;
246 :
247 252 : switch (parse->state)
248 : {
249 130 : case JM_EXPECT_FILES_START:
250 130 : parse->state = JM_EXPECT_FILES_NEXT;
251 130 : break;
252 120 : case JM_EXPECT_WAL_RANGES_START:
253 120 : parse->state = JM_EXPECT_WAL_RANGES_NEXT;
254 120 : break;
255 2 : default:
256 2 : json_manifest_parse_failure(parse->context,
257 : "unexpected array start");
258 0 : break;
259 : }
260 :
261 250 : return JSON_SUCCESS;
262 : }
263 :
264 : /*
265 : * Invoked at the end of each array in the JSON document.
266 : *
267 : * The cases here are analogous to those in json_manifest_array_start.
268 : */
269 : static JsonParseErrorType
270 216 : json_manifest_array_end(void *state)
271 : {
272 216 : JsonManifestParseState *parse = state;
273 :
274 216 : switch (parse->state)
275 : {
276 216 : case JM_EXPECT_FILES_NEXT:
277 : case JM_EXPECT_WAL_RANGES_NEXT:
278 216 : parse->state = JM_EXPECT_TOPLEVEL_FIELD;
279 216 : break;
280 0 : default:
281 0 : json_manifest_parse_failure(parse->context,
282 : "unexpected array end");
283 0 : break;
284 : }
285 :
286 216 : return JSON_SUCCESS;
287 : }
288 :
289 : /*
290 : * Invoked at the start of each object field in the JSON document.
291 : */
292 : static JsonParseErrorType
293 499958 : json_manifest_object_field_start(void *state, char *fname, bool isnull)
294 : {
295 499958 : JsonManifestParseState *parse = state;
296 :
297 499958 : switch (parse->state)
298 : {
299 522 : case JM_EXPECT_TOPLEVEL_FIELD:
300 :
301 : /*
302 : * Inside toplevel object. The version indicator should always be
303 : * the first field.
304 : */
305 522 : if (!parse->saw_version_field)
306 : {
307 156 : if (strcmp(fname, "PostgreSQL-Backup-Manifest-Version") != 0)
308 2 : json_manifest_parse_failure(parse->context,
309 : "expected version indicator");
310 154 : parse->state = JM_EXPECT_VERSION_VALUE;
311 154 : parse->saw_version_field = true;
312 154 : break;
313 : }
314 :
315 : /* Is this the list of files? */
316 366 : if (strcmp(fname, "Files") == 0)
317 : {
318 134 : parse->state = JM_EXPECT_FILES_START;
319 134 : break;
320 : }
321 :
322 : /* Is this the list of WAL ranges? */
323 232 : if (strcmp(fname, "WAL-Ranges") == 0)
324 : {
325 120 : parse->state = JM_EXPECT_WAL_RANGES_START;
326 120 : break;
327 : }
328 :
329 : /* Is this the manifest checksum? */
330 112 : if (strcmp(fname, "Manifest-Checksum") == 0)
331 : {
332 110 : parse->state = JM_EXPECT_MANIFEST_CHECKSUM_VALUE;
333 110 : break;
334 : }
335 :
336 : /* It's not a field we recognize. */
337 2 : json_manifest_parse_failure(parse->context,
338 : "unrecognized top-level field");
339 0 : break;
340 :
341 499092 : case JM_EXPECT_THIS_FILE_FIELD:
342 : /* Inside object for one file; which key have we got? */
343 499092 : if (strcmp(fname, "Path") == 0)
344 98666 : parse->file_field = JMFF_PATH;
345 400426 : else if (strcmp(fname, "Encoded-Path") == 0)
346 1936 : parse->file_field = JMFF_ENCODED_PATH;
347 398490 : else if (strcmp(fname, "Size") == 0)
348 100596 : parse->file_field = JMFF_SIZE;
349 297894 : else if (strcmp(fname, "Last-Modified") == 0)
350 100582 : parse->file_field = JMFF_LAST_MODIFIED;
351 197312 : else if (strcmp(fname, "Checksum-Algorithm") == 0)
352 98654 : parse->file_field = JMFF_CHECKSUM_ALGORITHM;
353 98658 : else if (strcmp(fname, "Checksum") == 0)
354 98656 : parse->file_field = JMFF_CHECKSUM;
355 : else
356 2 : json_manifest_parse_failure(parse->context,
357 : "unexpected file field");
358 499090 : parse->state = JM_EXPECT_THIS_FILE_VALUE;
359 499090 : break;
360 :
361 344 : case JM_EXPECT_THIS_WAL_RANGE_FIELD:
362 : /* Inside object for one file; which key have we got? */
363 344 : if (strcmp(fname, "Timeline") == 0)
364 116 : parse->wal_range_field = JMWRF_TIMELINE;
365 228 : else if (strcmp(fname, "Start-LSN") == 0)
366 114 : parse->wal_range_field = JMWRF_START_LSN;
367 114 : else if (strcmp(fname, "End-LSN") == 0)
368 112 : parse->wal_range_field = JMWRF_END_LSN;
369 : else
370 2 : json_manifest_parse_failure(parse->context,
371 : "unexpected WAL range field");
372 342 : parse->state = JM_EXPECT_THIS_WAL_RANGE_VALUE;
373 342 : break;
374 :
375 0 : default:
376 0 : json_manifest_parse_failure(parse->context,
377 : "unexpected object field");
378 0 : break;
379 : }
380 :
381 499950 : return JSON_SUCCESS;
382 : }
383 :
384 : /*
385 : * Invoked at the start of each scalar in the JSON document.
386 : *
387 : * Object field names don't reach this code; those are handled by
388 : * json_manifest_object_field_start. When we're inside of the object for
389 : * a particular file or WAL range, that function will have noticed the name
390 : * of the field, and we'll get the corresponding value here. When we're in
391 : * the toplevel object, the parse state itself tells us which field this is.
392 : *
393 : * In all cases except for PostgreSQL-Backup-Manifest-Version, which we
394 : * can just check on the spot, the goal here is just to save the value in
395 : * the parse state for later use. We don't actually do anything until we
396 : * reach either the end of the object representing this file, or the end
397 : * of the manifest, as the case may be.
398 : */
399 : static JsonParseErrorType
400 499698 : json_manifest_scalar(void *state, char *token, JsonTokenType tokentype)
401 : {
402 499698 : JsonManifestParseState *parse = state;
403 :
404 499698 : switch (parse->state)
405 : {
406 154 : case JM_EXPECT_VERSION_VALUE:
407 154 : if (strcmp(token, "1") != 0)
408 2 : json_manifest_parse_failure(parse->context,
409 : "unexpected manifest version");
410 152 : parse->state = JM_EXPECT_TOPLEVEL_FIELD;
411 152 : break;
412 :
413 499090 : case JM_EXPECT_THIS_FILE_VALUE:
414 499090 : switch (parse->file_field)
415 : {
416 98666 : case JMFF_PATH:
417 98666 : parse->pathname = token;
418 98666 : break;
419 1936 : case JMFF_ENCODED_PATH:
420 1936 : parse->encoded_pathname = token;
421 1936 : break;
422 100596 : case JMFF_SIZE:
423 100596 : parse->size = token;
424 100596 : break;
425 100582 : case JMFF_LAST_MODIFIED:
426 100582 : pfree(token); /* unused */
427 100582 : break;
428 98654 : case JMFF_CHECKSUM_ALGORITHM:
429 98654 : parse->algorithm = token;
430 98654 : break;
431 98656 : case JMFF_CHECKSUM:
432 98656 : parse->checksum = token;
433 98656 : break;
434 : }
435 499090 : parse->state = JM_EXPECT_THIS_FILE_FIELD;
436 499090 : break;
437 :
438 342 : case JM_EXPECT_THIS_WAL_RANGE_VALUE:
439 342 : switch (parse->wal_range_field)
440 : {
441 116 : case JMWRF_TIMELINE:
442 116 : parse->timeline = token;
443 116 : break;
444 114 : case JMWRF_START_LSN:
445 114 : parse->start_lsn = token;
446 114 : break;
447 112 : case JMWRF_END_LSN:
448 112 : parse->end_lsn = token;
449 112 : break;
450 : }
451 342 : parse->state = JM_EXPECT_THIS_WAL_RANGE_FIELD;
452 342 : break;
453 :
454 110 : case JM_EXPECT_MANIFEST_CHECKSUM_VALUE:
455 110 : parse->state = JM_EXPECT_TOPLEVEL_END;
456 110 : parse->manifest_checksum = token;
457 110 : break;
458 :
459 2 : default:
460 2 : json_manifest_parse_failure(parse->context, "unexpected scalar");
461 0 : break;
462 : }
463 :
464 499694 : return JSON_SUCCESS;
465 : }
466 :
467 : /*
468 : * Do additional parsing and sanity-checking of the details gathered for one
469 : * file, and invoke the per-file callback so that the caller gets those
470 : * details. This happens for each file when the corresponding JSON object is
471 : * completely parsed.
472 : */
473 : static void
474 100602 : json_manifest_finalize_file(JsonManifestParseState *parse)
475 : {
476 100602 : JsonManifestParseContext *context = parse->context;
477 : size_t size;
478 : char *ep;
479 : int checksum_string_length;
480 : pg_checksum_type checksum_type;
481 : int checksum_length;
482 : uint8 *checksum_payload;
483 :
484 : /* Pathname and size are required. */
485 100602 : if (parse->pathname == NULL && parse->encoded_pathname == NULL)
486 2 : json_manifest_parse_failure(parse->context, "missing path name");
487 100600 : if (parse->pathname != NULL && parse->encoded_pathname != NULL)
488 2 : json_manifest_parse_failure(parse->context,
489 : "both path name and encoded path name");
490 100598 : if (parse->size == NULL)
491 2 : json_manifest_parse_failure(parse->context, "missing size");
492 100596 : if (parse->algorithm == NULL && parse->checksum != NULL)
493 2 : json_manifest_parse_failure(parse->context,
494 : "checksum without algorithm");
495 :
496 : /* Decode encoded pathname, if that's what we have. */
497 100594 : if (parse->encoded_pathname != NULL)
498 : {
499 1934 : int encoded_length = strlen(parse->encoded_pathname);
500 1934 : int raw_length = encoded_length / 2;
501 :
502 1934 : parse->pathname = palloc(raw_length + 1);
503 1934 : if (encoded_length % 2 != 0 ||
504 1932 : !hexdecode_string((uint8 *) parse->pathname,
505 : parse->encoded_pathname,
506 : raw_length))
507 2 : json_manifest_parse_failure(parse->context,
508 : "could not decode file name");
509 1932 : parse->pathname[raw_length] = '\0';
510 1932 : pfree(parse->encoded_pathname);
511 1932 : parse->encoded_pathname = NULL;
512 : }
513 :
514 : /* Parse size. */
515 100592 : size = strtoul(parse->size, &ep, 10);
516 100592 : if (*ep)
517 2 : json_manifest_parse_failure(parse->context,
518 : "file size is not an integer");
519 :
520 : /* Parse the checksum algorithm, if it's present. */
521 100590 : if (parse->algorithm == NULL)
522 1936 : checksum_type = CHECKSUM_TYPE_NONE;
523 98654 : else if (!pg_checksum_parse_type(parse->algorithm, &checksum_type))
524 2 : context->error_cb(context, "unrecognized checksum algorithm: \"%s\"",
525 : parse->algorithm);
526 :
527 : /* Parse the checksum payload, if it's present. */
528 100588 : checksum_string_length = parse->checksum == NULL ? 0
529 98652 : : strlen(parse->checksum);
530 100588 : if (checksum_string_length == 0)
531 : {
532 1936 : checksum_length = 0;
533 1936 : checksum_payload = NULL;
534 : }
535 : else
536 : {
537 98652 : checksum_length = checksum_string_length / 2;
538 98652 : checksum_payload = palloc(checksum_length);
539 98652 : if (checksum_string_length % 2 != 0 ||
540 98650 : !hexdecode_string(checksum_payload, parse->checksum,
541 : checksum_length))
542 2 : context->error_cb(context,
543 : "invalid checksum for file \"%s\": \"%s\"",
544 : parse->pathname, parse->checksum);
545 : }
546 :
547 : /* Invoke the callback with the details we've gathered. */
548 100586 : context->per_file_cb(context, parse->pathname, size,
549 : checksum_type, checksum_length, checksum_payload);
550 :
551 : /* Free memory we no longer need. */
552 100584 : if (parse->size != NULL)
553 : {
554 100584 : pfree(parse->size);
555 100584 : parse->size = NULL;
556 : }
557 100584 : if (parse->algorithm != NULL)
558 : {
559 98650 : pfree(parse->algorithm);
560 98650 : parse->algorithm = NULL;
561 : }
562 100584 : if (parse->checksum != NULL)
563 : {
564 98650 : pfree(parse->checksum);
565 98650 : parse->checksum = NULL;
566 : }
567 100584 : }
568 :
569 : /*
570 : * Do additional parsing and sanity-checking of the details gathered for one
571 : * WAL range, and invoke the per-WAL-range callback so that the caller gets
572 : * those details. This happens for each WAL range when the corresponding JSON
573 : * object is completely parsed.
574 : */
575 : static void
576 118 : json_manifest_finalize_wal_range(JsonManifestParseState *parse)
577 : {
578 118 : JsonManifestParseContext *context = parse->context;
579 : TimeLineID tli;
580 : XLogRecPtr start_lsn,
581 : end_lsn;
582 : char *ep;
583 :
584 : /* Make sure all fields are present. */
585 118 : if (parse->timeline == NULL)
586 2 : json_manifest_parse_failure(parse->context, "missing timeline");
587 116 : if (parse->start_lsn == NULL)
588 2 : json_manifest_parse_failure(parse->context, "missing start LSN");
589 114 : if (parse->end_lsn == NULL)
590 2 : json_manifest_parse_failure(parse->context, "missing end LSN");
591 :
592 : /* Parse timeline. */
593 112 : tli = strtoul(parse->timeline, &ep, 10);
594 112 : if (*ep)
595 2 : json_manifest_parse_failure(parse->context,
596 : "timeline is not an integer");
597 110 : if (!parse_xlogrecptr(&start_lsn, parse->start_lsn))
598 2 : json_manifest_parse_failure(parse->context,
599 : "could not parse start LSN");
600 108 : if (!parse_xlogrecptr(&end_lsn, parse->end_lsn))
601 2 : json_manifest_parse_failure(parse->context,
602 : "could not parse end LSN");
603 :
604 : /* Invoke the callback with the details we've gathered. */
605 106 : context->per_wal_range_cb(context, tli, start_lsn, end_lsn);
606 :
607 : /* Free memory we no longer need. */
608 106 : if (parse->timeline != NULL)
609 : {
610 106 : pfree(parse->timeline);
611 106 : parse->timeline = NULL;
612 : }
613 106 : if (parse->start_lsn != NULL)
614 : {
615 106 : pfree(parse->start_lsn);
616 106 : parse->start_lsn = NULL;
617 : }
618 106 : if (parse->end_lsn != NULL)
619 : {
620 106 : pfree(parse->end_lsn);
621 106 : parse->end_lsn = NULL;
622 : }
623 106 : }
624 :
625 : /*
626 : * Verify that the manifest checksum is correct.
627 : *
628 : * The last line of the manifest file is excluded from the manifest checksum,
629 : * because the last line is expected to contain the checksum that covers
630 : * the rest of the file.
631 : */
632 : static void
633 110 : verify_manifest_checksum(JsonManifestParseState *parse, char *buffer,
634 : size_t size)
635 : {
636 110 : JsonManifestParseContext *context = parse->context;
637 : size_t i;
638 110 : size_t number_of_newlines = 0;
639 110 : size_t ultimate_newline = 0;
640 110 : size_t penultimate_newline = 0;
641 : pg_cryptohash_ctx *manifest_ctx;
642 : uint8 manifest_checksum_actual[PG_SHA256_DIGEST_LENGTH];
643 : uint8 manifest_checksum_expected[PG_SHA256_DIGEST_LENGTH];
644 :
645 : /* Find the last two newlines in the file. */
646 14834132 : for (i = 0; i < size; ++i)
647 : {
648 14834022 : if (buffer[i] == '\n')
649 : {
650 101322 : ++number_of_newlines;
651 101322 : penultimate_newline = ultimate_newline;
652 101322 : ultimate_newline = i;
653 : }
654 : }
655 :
656 : /*
657 : * Make sure that the last newline is right at the end, and that there are
658 : * at least two lines total. We need this to be true in order for the
659 : * following code, which computes the manifest checksum, to work properly.
660 : */
661 110 : if (number_of_newlines < 2)
662 2 : json_manifest_parse_failure(parse->context,
663 : "expected at least 2 lines");
664 108 : if (ultimate_newline != size - 1)
665 2 : json_manifest_parse_failure(parse->context,
666 : "last line not newline-terminated");
667 :
668 : /* Checksum the rest. */
669 106 : manifest_ctx = pg_cryptohash_create(PG_SHA256);
670 106 : if (manifest_ctx == NULL)
671 0 : context->error_cb(context, "out of memory");
672 106 : if (pg_cryptohash_init(manifest_ctx) < 0)
673 0 : context->error_cb(context, "could not initialize checksum of manifest");
674 106 : if (pg_cryptohash_update(manifest_ctx, (uint8 *) buffer, penultimate_newline + 1) < 0)
675 0 : context->error_cb(context, "could not update checksum of manifest");
676 106 : if (pg_cryptohash_final(manifest_ctx, manifest_checksum_actual,
677 : sizeof(manifest_checksum_actual)) < 0)
678 0 : context->error_cb(context, "could not finalize checksum of manifest");
679 :
680 : /* Now verify it. */
681 106 : if (parse->manifest_checksum == NULL)
682 0 : context->error_cb(parse->context, "manifest has no checksum");
683 106 : if (strlen(parse->manifest_checksum) != PG_SHA256_DIGEST_LENGTH * 2 ||
684 106 : !hexdecode_string(manifest_checksum_expected, parse->manifest_checksum,
685 : PG_SHA256_DIGEST_LENGTH))
686 2 : context->error_cb(context, "invalid manifest checksum: \"%s\"",
687 : parse->manifest_checksum);
688 104 : if (memcmp(manifest_checksum_actual, manifest_checksum_expected,
689 : PG_SHA256_DIGEST_LENGTH) != 0)
690 2 : context->error_cb(context, "manifest checksum mismatch");
691 102 : pg_cryptohash_free(manifest_ctx);
692 102 : }
693 :
694 : /*
695 : * Report a parse error.
696 : *
697 : * This is intended to be used for fairly low-level failures that probably
698 : * shouldn't occur unless somebody has deliberately constructed a bad manifest,
699 : * or unless the server is generating bad manifests due to some bug. msg should
700 : * be a short string giving some hint as to what the problem is.
701 : */
702 : static void
703 50 : json_manifest_parse_failure(JsonManifestParseContext *context, char *msg)
704 : {
705 50 : context->error_cb(context, "could not parse backup manifest: %s", msg);
706 : }
707 :
708 : /*
709 : * Convert a character which represents a hexadecimal digit to an integer.
710 : *
711 : * Returns -1 if the character is not a hexadecimal digit.
712 : */
713 : static int
714 1445072 : hexdecode_char(char c)
715 : {
716 1445072 : if (c >= '0' && c <= '9')
717 972108 : return c - '0';
718 472964 : if (c >= 'a' && c <= 'f')
719 472948 : return c - 'a' + 10;
720 16 : if (c >= 'A' && c <= 'F')
721 12 : return c - 'A' + 10;
722 :
723 4 : return -1;
724 : }
725 :
726 : /*
727 : * Decode a hex string into a byte string, 2 hex chars per byte.
728 : *
729 : * Returns false if invalid characters are encountered; otherwise true.
730 : */
731 : static bool
732 100688 : hexdecode_string(uint8 *result, char *input, int nbytes)
733 : {
734 : int i;
735 :
736 823222 : for (i = 0; i < nbytes; ++i)
737 : {
738 722536 : int n1 = hexdecode_char(input[i * 2]);
739 722536 : int n2 = hexdecode_char(input[i * 2 + 1]);
740 :
741 722536 : if (n1 < 0 || n2 < 0)
742 2 : return false;
743 722534 : result[i] = n1 * 16 + n2;
744 : }
745 :
746 100686 : return true;
747 : }
748 :
749 : /*
750 : * Parse an XLogRecPtr expressed using the usual string format.
751 : */
752 : static bool
753 218 : parse_xlogrecptr(XLogRecPtr *result, char *input)
754 : {
755 : uint32 hi;
756 : uint32 lo;
757 :
758 218 : if (sscanf(input, "%X/%X", &hi, &lo) != 2)
759 4 : return false;
760 214 : *result = ((uint64) hi) << 32 | lo;
761 214 : return true;
762 : }
|