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