/src/postgres/src/common/parse_manifest.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * parse_manifest.c |
4 | | * Parse a backup manifest in JSON format. |
5 | | * |
6 | | * Portions Copyright (c) 1996-2026, 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 * |
129 | | json_parse_manifest_incremental_init(JsonManifestParseContext *context) |
130 | 0 | { |
131 | 0 | JsonManifestParseIncrementalState *incstate; |
132 | 0 | JsonManifestParseState *parse; |
133 | 0 | pg_cryptohash_ctx *manifest_ctx; |
134 | |
|
135 | 0 | incstate = palloc_object(JsonManifestParseIncrementalState); |
136 | 0 | parse = palloc_object(JsonManifestParseState); |
137 | |
|
138 | 0 | parse->context = context; |
139 | 0 | parse->state = JM_EXPECT_TOPLEVEL_START; |
140 | 0 | parse->saw_version_field = false; |
141 | |
|
142 | 0 | makeJsonLexContextIncremental(&(incstate->lex), PG_UTF8, true); |
143 | |
|
144 | 0 | incstate->sem.semstate = parse; |
145 | 0 | incstate->sem.object_start = json_manifest_object_start; |
146 | 0 | incstate->sem.object_end = json_manifest_object_end; |
147 | 0 | incstate->sem.array_start = json_manifest_array_start; |
148 | 0 | incstate->sem.array_end = json_manifest_array_end; |
149 | 0 | incstate->sem.object_field_start = json_manifest_object_field_start; |
150 | 0 | incstate->sem.object_field_end = NULL; |
151 | 0 | incstate->sem.array_element_start = NULL; |
152 | 0 | incstate->sem.array_element_end = NULL; |
153 | 0 | incstate->sem.scalar = json_manifest_scalar; |
154 | |
|
155 | 0 | manifest_ctx = pg_cryptohash_create(PG_SHA256); |
156 | 0 | if (manifest_ctx == NULL) |
157 | 0 | context->error_cb(context, "out of memory"); |
158 | 0 | if (pg_cryptohash_init(manifest_ctx) < 0) |
159 | 0 | context->error_cb(context, "could not initialize checksum of manifest"); |
160 | 0 | incstate->manifest_ctx = manifest_ctx; |
161 | |
|
162 | 0 | return incstate; |
163 | 0 | } |
164 | | |
165 | | /* |
166 | | * Free an incremental state object and its contents. |
167 | | */ |
168 | | void |
169 | | json_parse_manifest_incremental_shutdown(JsonManifestParseIncrementalState *incstate) |
170 | 0 | { |
171 | 0 | pfree(incstate->sem.semstate); |
172 | 0 | freeJsonLexContext(&(incstate->lex)); |
173 | | /* incstate->manifest_ctx has already been freed */ |
174 | 0 | pfree(incstate); |
175 | 0 | } |
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 |
185 | | json_parse_manifest_incremental_chunk(JsonManifestParseIncrementalState *incstate, |
186 | | const char *chunk, size_t size, bool is_last) |
187 | 0 | { |
188 | 0 | JsonParseErrorType res, |
189 | 0 | expected; |
190 | 0 | JsonManifestParseState *parse = incstate->sem.semstate; |
191 | 0 | JsonManifestParseContext *context = parse->context; |
192 | |
|
193 | 0 | res = pg_parse_json_incremental(&(incstate->lex), &(incstate->sem), |
194 | 0 | chunk, size, is_last); |
195 | |
|
196 | 0 | expected = is_last ? JSON_SUCCESS : JSON_INCOMPLETE; |
197 | |
|
198 | 0 | if (res != expected) |
199 | 0 | json_manifest_parse_failure(context, |
200 | 0 | json_errdetail(res, &(incstate->lex))); |
201 | |
|
202 | 0 | if (is_last && parse->state != JM_EXPECT_EOF) |
203 | 0 | json_manifest_parse_failure(context, "manifest ended unexpectedly"); |
204 | |
|
205 | 0 | if (!is_last) |
206 | 0 | { |
207 | 0 | if (pg_cryptohash_update(incstate->manifest_ctx, |
208 | 0 | (const uint8 *) chunk, size) < 0) |
209 | 0 | context->error_cb(context, "could not update checksum of manifest"); |
210 | 0 | } |
211 | 0 | else |
212 | 0 | { |
213 | 0 | verify_manifest_checksum(parse, chunk, size, incstate->manifest_ctx); |
214 | 0 | } |
215 | 0 | } |
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 |
227 | | json_parse_manifest(JsonManifestParseContext *context, const char *buffer, |
228 | | size_t size) |
229 | 0 | { |
230 | 0 | JsonLexContext *lex; |
231 | 0 | JsonParseErrorType json_error; |
232 | 0 | JsonSemAction sem; |
233 | 0 | JsonManifestParseState parse; |
234 | | |
235 | | /* Set up our private parsing context. */ |
236 | 0 | parse.context = context; |
237 | 0 | parse.state = JM_EXPECT_TOPLEVEL_START; |
238 | 0 | parse.saw_version_field = false; |
239 | | |
240 | | /* Create a JSON lexing context. */ |
241 | 0 | lex = makeJsonLexContextCstringLen(NULL, buffer, size, PG_UTF8, true); |
242 | | |
243 | | /* Set up semantic actions. */ |
244 | 0 | sem.semstate = &parse; |
245 | 0 | sem.object_start = json_manifest_object_start; |
246 | 0 | sem.object_end = json_manifest_object_end; |
247 | 0 | sem.array_start = json_manifest_array_start; |
248 | 0 | sem.array_end = json_manifest_array_end; |
249 | 0 | sem.object_field_start = json_manifest_object_field_start; |
250 | 0 | sem.object_field_end = NULL; |
251 | 0 | sem.array_element_start = NULL; |
252 | 0 | sem.array_element_end = NULL; |
253 | 0 | sem.scalar = json_manifest_scalar; |
254 | | |
255 | | /* Run the actual JSON parser. */ |
256 | 0 | json_error = pg_parse_json(lex, &sem); |
257 | 0 | if (json_error != JSON_SUCCESS) |
258 | 0 | json_manifest_parse_failure(context, json_errdetail(json_error, lex)); |
259 | 0 | if (parse.state != JM_EXPECT_EOF) |
260 | 0 | json_manifest_parse_failure(context, "manifest ended unexpectedly"); |
261 | | |
262 | | /* Verify the manifest checksum. */ |
263 | 0 | verify_manifest_checksum(&parse, buffer, size, NULL); |
264 | |
|
265 | 0 | freeJsonLexContext(lex); |
266 | 0 | } |
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 | | json_manifest_object_start(void *state) |
277 | 0 | { |
278 | 0 | JsonManifestParseState *parse = state; |
279 | |
|
280 | 0 | switch (parse->state) |
281 | 0 | { |
282 | 0 | case JM_EXPECT_TOPLEVEL_START: |
283 | 0 | parse->state = JM_EXPECT_TOPLEVEL_FIELD; |
284 | 0 | break; |
285 | 0 | case JM_EXPECT_FILES_NEXT: |
286 | 0 | parse->state = JM_EXPECT_THIS_FILE_FIELD; |
287 | 0 | parse->pathname = NULL; |
288 | 0 | parse->encoded_pathname = NULL; |
289 | 0 | parse->size = NULL; |
290 | 0 | parse->algorithm = NULL; |
291 | 0 | parse->checksum = NULL; |
292 | 0 | break; |
293 | 0 | case JM_EXPECT_WAL_RANGES_NEXT: |
294 | 0 | parse->state = JM_EXPECT_THIS_WAL_RANGE_FIELD; |
295 | 0 | parse->timeline = NULL; |
296 | 0 | parse->start_lsn = NULL; |
297 | 0 | parse->end_lsn = NULL; |
298 | 0 | break; |
299 | 0 | default: |
300 | 0 | json_manifest_parse_failure(parse->context, |
301 | 0 | "unexpected object start"); |
302 | 0 | break; |
303 | 0 | } |
304 | | |
305 | 0 | return JSON_SUCCESS; |
306 | 0 | } |
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 |
317 | | json_manifest_object_end(void *state) |
318 | 0 | { |
319 | 0 | JsonManifestParseState *parse = state; |
320 | |
|
321 | 0 | switch (parse->state) |
322 | 0 | { |
323 | 0 | case JM_EXPECT_TOPLEVEL_END: |
324 | 0 | parse->state = JM_EXPECT_EOF; |
325 | 0 | break; |
326 | 0 | case JM_EXPECT_THIS_FILE_FIELD: |
327 | 0 | json_manifest_finalize_file(parse); |
328 | 0 | parse->state = JM_EXPECT_FILES_NEXT; |
329 | 0 | break; |
330 | 0 | case JM_EXPECT_THIS_WAL_RANGE_FIELD: |
331 | 0 | json_manifest_finalize_wal_range(parse); |
332 | 0 | parse->state = JM_EXPECT_WAL_RANGES_NEXT; |
333 | 0 | break; |
334 | 0 | default: |
335 | 0 | json_manifest_parse_failure(parse->context, |
336 | 0 | "unexpected object end"); |
337 | 0 | break; |
338 | 0 | } |
339 | | |
340 | 0 | return JSON_SUCCESS; |
341 | 0 | } |
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 |
351 | | json_manifest_array_start(void *state) |
352 | 0 | { |
353 | 0 | JsonManifestParseState *parse = state; |
354 | |
|
355 | 0 | switch (parse->state) |
356 | 0 | { |
357 | 0 | case JM_EXPECT_FILES_START: |
358 | 0 | parse->state = JM_EXPECT_FILES_NEXT; |
359 | 0 | break; |
360 | 0 | case JM_EXPECT_WAL_RANGES_START: |
361 | 0 | parse->state = JM_EXPECT_WAL_RANGES_NEXT; |
362 | 0 | break; |
363 | 0 | default: |
364 | 0 | json_manifest_parse_failure(parse->context, |
365 | 0 | "unexpected array start"); |
366 | 0 | break; |
367 | 0 | } |
368 | | |
369 | 0 | return JSON_SUCCESS; |
370 | 0 | } |
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 |
378 | | json_manifest_array_end(void *state) |
379 | 0 | { |
380 | 0 | JsonManifestParseState *parse = state; |
381 | |
|
382 | 0 | switch (parse->state) |
383 | 0 | { |
384 | 0 | case JM_EXPECT_FILES_NEXT: |
385 | 0 | case JM_EXPECT_WAL_RANGES_NEXT: |
386 | 0 | parse->state = JM_EXPECT_TOPLEVEL_FIELD; |
387 | 0 | break; |
388 | 0 | default: |
389 | 0 | json_manifest_parse_failure(parse->context, |
390 | 0 | "unexpected array end"); |
391 | 0 | break; |
392 | 0 | } |
393 | | |
394 | 0 | return JSON_SUCCESS; |
395 | 0 | } |
396 | | |
397 | | /* |
398 | | * Invoked at the start of each object field in the JSON document. |
399 | | */ |
400 | | static JsonParseErrorType |
401 | | json_manifest_object_field_start(void *state, char *fname, bool isnull) |
402 | 0 | { |
403 | 0 | JsonManifestParseState *parse = state; |
404 | |
|
405 | 0 | switch (parse->state) |
406 | 0 | { |
407 | 0 | case JM_EXPECT_TOPLEVEL_FIELD: |
408 | | |
409 | | /* |
410 | | * Inside toplevel object. The version indicator should always be |
411 | | * the first field. |
412 | | */ |
413 | 0 | if (!parse->saw_version_field) |
414 | 0 | { |
415 | 0 | if (strcmp(fname, "PostgreSQL-Backup-Manifest-Version") != 0) |
416 | 0 | json_manifest_parse_failure(parse->context, |
417 | 0 | "expected version indicator"); |
418 | 0 | parse->state = JM_EXPECT_VERSION_VALUE; |
419 | 0 | parse->saw_version_field = true; |
420 | 0 | break; |
421 | 0 | } |
422 | | |
423 | | /* Is this the system identifier? */ |
424 | 0 | if (strcmp(fname, "System-Identifier") == 0) |
425 | 0 | { |
426 | 0 | parse->state = JM_EXPECT_SYSTEM_IDENTIFIER_VALUE; |
427 | 0 | break; |
428 | 0 | } |
429 | | |
430 | | /* Is this the list of files? */ |
431 | 0 | if (strcmp(fname, "Files") == 0) |
432 | 0 | { |
433 | 0 | parse->state = JM_EXPECT_FILES_START; |
434 | 0 | break; |
435 | 0 | } |
436 | | |
437 | | /* Is this the list of WAL ranges? */ |
438 | 0 | if (strcmp(fname, "WAL-Ranges") == 0) |
439 | 0 | { |
440 | 0 | parse->state = JM_EXPECT_WAL_RANGES_START; |
441 | 0 | break; |
442 | 0 | } |
443 | | |
444 | | /* Is this the manifest checksum? */ |
445 | 0 | if (strcmp(fname, "Manifest-Checksum") == 0) |
446 | 0 | { |
447 | 0 | parse->state = JM_EXPECT_MANIFEST_CHECKSUM_VALUE; |
448 | 0 | break; |
449 | 0 | } |
450 | | |
451 | | /* It's not a field we recognize. */ |
452 | 0 | json_manifest_parse_failure(parse->context, |
453 | 0 | "unrecognized top-level field"); |
454 | 0 | break; |
455 | | |
456 | 0 | case JM_EXPECT_THIS_FILE_FIELD: |
457 | | /* Inside object for one file; which key have we got? */ |
458 | 0 | if (strcmp(fname, "Path") == 0) |
459 | 0 | parse->file_field = JMFF_PATH; |
460 | 0 | else if (strcmp(fname, "Encoded-Path") == 0) |
461 | 0 | parse->file_field = JMFF_ENCODED_PATH; |
462 | 0 | else if (strcmp(fname, "Size") == 0) |
463 | 0 | parse->file_field = JMFF_SIZE; |
464 | 0 | else if (strcmp(fname, "Last-Modified") == 0) |
465 | 0 | parse->file_field = JMFF_LAST_MODIFIED; |
466 | 0 | else if (strcmp(fname, "Checksum-Algorithm") == 0) |
467 | 0 | parse->file_field = JMFF_CHECKSUM_ALGORITHM; |
468 | 0 | else if (strcmp(fname, "Checksum") == 0) |
469 | 0 | parse->file_field = JMFF_CHECKSUM; |
470 | 0 | else |
471 | 0 | json_manifest_parse_failure(parse->context, |
472 | 0 | "unexpected file field"); |
473 | 0 | parse->state = JM_EXPECT_THIS_FILE_VALUE; |
474 | 0 | break; |
475 | | |
476 | 0 | case JM_EXPECT_THIS_WAL_RANGE_FIELD: |
477 | | /* Inside object for one file; which key have we got? */ |
478 | 0 | if (strcmp(fname, "Timeline") == 0) |
479 | 0 | parse->wal_range_field = JMWRF_TIMELINE; |
480 | 0 | else if (strcmp(fname, "Start-LSN") == 0) |
481 | 0 | parse->wal_range_field = JMWRF_START_LSN; |
482 | 0 | else if (strcmp(fname, "End-LSN") == 0) |
483 | 0 | parse->wal_range_field = JMWRF_END_LSN; |
484 | 0 | else |
485 | 0 | json_manifest_parse_failure(parse->context, |
486 | 0 | "unexpected WAL range field"); |
487 | 0 | parse->state = JM_EXPECT_THIS_WAL_RANGE_VALUE; |
488 | 0 | break; |
489 | | |
490 | 0 | default: |
491 | 0 | json_manifest_parse_failure(parse->context, |
492 | 0 | "unexpected object field"); |
493 | 0 | break; |
494 | 0 | } |
495 | | |
496 | 0 | pfree(fname); |
497 | |
|
498 | 0 | return JSON_SUCCESS; |
499 | 0 | } |
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 |
517 | | json_manifest_scalar(void *state, char *token, JsonTokenType tokentype) |
518 | 0 | { |
519 | 0 | JsonManifestParseState *parse = state; |
520 | |
|
521 | 0 | switch (parse->state) |
522 | 0 | { |
523 | 0 | case JM_EXPECT_VERSION_VALUE: |
524 | 0 | parse->manifest_version = token; |
525 | 0 | json_manifest_finalize_version(parse); |
526 | 0 | parse->state = JM_EXPECT_TOPLEVEL_FIELD; |
527 | 0 | break; |
528 | | |
529 | 0 | case JM_EXPECT_SYSTEM_IDENTIFIER_VALUE: |
530 | 0 | parse->manifest_system_identifier = token; |
531 | 0 | json_manifest_finalize_system_identifier(parse); |
532 | 0 | parse->state = JM_EXPECT_TOPLEVEL_FIELD; |
533 | 0 | break; |
534 | | |
535 | 0 | case JM_EXPECT_THIS_FILE_VALUE: |
536 | 0 | switch (parse->file_field) |
537 | 0 | { |
538 | 0 | case JMFF_PATH: |
539 | 0 | parse->pathname = token; |
540 | 0 | break; |
541 | 0 | case JMFF_ENCODED_PATH: |
542 | 0 | parse->encoded_pathname = token; |
543 | 0 | break; |
544 | 0 | case JMFF_SIZE: |
545 | 0 | parse->size = token; |
546 | 0 | break; |
547 | 0 | case JMFF_LAST_MODIFIED: |
548 | 0 | pfree(token); /* unused */ |
549 | 0 | break; |
550 | 0 | case JMFF_CHECKSUM_ALGORITHM: |
551 | 0 | parse->algorithm = token; |
552 | 0 | break; |
553 | 0 | case JMFF_CHECKSUM: |
554 | 0 | parse->checksum = token; |
555 | 0 | break; |
556 | 0 | } |
557 | 0 | parse->state = JM_EXPECT_THIS_FILE_FIELD; |
558 | 0 | break; |
559 | | |
560 | 0 | case JM_EXPECT_THIS_WAL_RANGE_VALUE: |
561 | 0 | switch (parse->wal_range_field) |
562 | 0 | { |
563 | 0 | case JMWRF_TIMELINE: |
564 | 0 | parse->timeline = token; |
565 | 0 | break; |
566 | 0 | case JMWRF_START_LSN: |
567 | 0 | parse->start_lsn = token; |
568 | 0 | break; |
569 | 0 | case JMWRF_END_LSN: |
570 | 0 | parse->end_lsn = token; |
571 | 0 | break; |
572 | 0 | } |
573 | 0 | parse->state = JM_EXPECT_THIS_WAL_RANGE_FIELD; |
574 | 0 | break; |
575 | | |
576 | 0 | case JM_EXPECT_MANIFEST_CHECKSUM_VALUE: |
577 | 0 | parse->state = JM_EXPECT_TOPLEVEL_END; |
578 | 0 | parse->manifest_checksum = token; |
579 | 0 | break; |
580 | | |
581 | 0 | default: |
582 | 0 | json_manifest_parse_failure(parse->context, "unexpected scalar"); |
583 | 0 | break; |
584 | 0 | } |
585 | | |
586 | 0 | return JSON_SUCCESS; |
587 | 0 | } |
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 |
596 | | json_manifest_finalize_version(JsonManifestParseState *parse) |
597 | 0 | { |
598 | 0 | JsonManifestParseContext *context = parse->context; |
599 | 0 | int version; |
600 | 0 | char *ep; |
601 | |
|
602 | 0 | Assert(parse->saw_version_field); |
603 | | |
604 | | /* Parse version. */ |
605 | 0 | version = strtoi64(parse->manifest_version, &ep, 10); |
606 | 0 | if (*ep) |
607 | 0 | json_manifest_parse_failure(parse->context, |
608 | 0 | "manifest version not an integer"); |
609 | |
|
610 | 0 | if (version != 1 && version != 2) |
611 | 0 | json_manifest_parse_failure(parse->context, |
612 | 0 | "unexpected manifest version"); |
613 | | |
614 | | /* Invoke the callback for version */ |
615 | 0 | context->version_cb(context, version); |
616 | 0 | } |
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 | | json_manifest_finalize_system_identifier(JsonManifestParseState *parse) |
625 | 0 | { |
626 | 0 | JsonManifestParseContext *context = parse->context; |
627 | 0 | uint64 system_identifier; |
628 | 0 | char *ep; |
629 | |
|
630 | 0 | Assert(parse->manifest_system_identifier != NULL); |
631 | | |
632 | | /* Parse system identifier. */ |
633 | 0 | system_identifier = strtou64(parse->manifest_system_identifier, &ep, 10); |
634 | 0 | if (ep == parse->manifest_system_identifier || *ep) |
635 | 0 | json_manifest_parse_failure(parse->context, |
636 | 0 | "system identifier in manifest not an integer"); |
637 | | |
638 | | /* Invoke the callback for system identifier */ |
639 | 0 | context->system_identifier_cb(context, system_identifier); |
640 | 0 | } |
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 |
649 | | json_manifest_finalize_file(JsonManifestParseState *parse) |
650 | 0 | { |
651 | 0 | JsonManifestParseContext *context = parse->context; |
652 | 0 | uint64 size; |
653 | 0 | char *ep; |
654 | 0 | int checksum_string_length; |
655 | 0 | pg_checksum_type checksum_type; |
656 | 0 | int checksum_length; |
657 | 0 | uint8 *checksum_payload; |
658 | | |
659 | | /* Pathname and size are required. */ |
660 | 0 | if (parse->pathname == NULL && parse->encoded_pathname == NULL) |
661 | 0 | json_manifest_parse_failure(parse->context, "missing path name"); |
662 | 0 | if (parse->pathname != NULL && parse->encoded_pathname != NULL) |
663 | 0 | json_manifest_parse_failure(parse->context, |
664 | 0 | "both path name and encoded path name"); |
665 | 0 | if (parse->size == NULL) |
666 | 0 | json_manifest_parse_failure(parse->context, "missing size"); |
667 | 0 | if (parse->algorithm == NULL && parse->checksum != NULL) |
668 | 0 | json_manifest_parse_failure(parse->context, |
669 | 0 | "checksum without algorithm"); |
670 | | |
671 | | /* Decode encoded pathname, if that's what we have. */ |
672 | 0 | if (parse->encoded_pathname != NULL) |
673 | 0 | { |
674 | 0 | int encoded_length = strlen(parse->encoded_pathname); |
675 | 0 | int raw_length = encoded_length / 2; |
676 | |
|
677 | 0 | parse->pathname = palloc(raw_length + 1); |
678 | 0 | if (encoded_length % 2 != 0 || |
679 | 0 | !hexdecode_string((uint8 *) parse->pathname, |
680 | 0 | parse->encoded_pathname, |
681 | 0 | raw_length)) |
682 | 0 | json_manifest_parse_failure(parse->context, |
683 | 0 | "could not decode file name"); |
684 | 0 | parse->pathname[raw_length] = '\0'; |
685 | 0 | pfree(parse->encoded_pathname); |
686 | 0 | parse->encoded_pathname = NULL; |
687 | 0 | } |
688 | | |
689 | | /* Parse size. */ |
690 | 0 | size = strtou64(parse->size, &ep, 10); |
691 | 0 | if (ep == parse->size || *ep) |
692 | 0 | json_manifest_parse_failure(parse->context, |
693 | 0 | "file size is not an integer"); |
694 | | |
695 | | /* Parse the checksum algorithm, if it's present. */ |
696 | 0 | if (parse->algorithm == NULL) |
697 | 0 | checksum_type = CHECKSUM_TYPE_NONE; |
698 | 0 | else if (!pg_checksum_parse_type(parse->algorithm, &checksum_type)) |
699 | 0 | context->error_cb(context, "unrecognized checksum algorithm: \"%s\"", |
700 | 0 | parse->algorithm); |
701 | | |
702 | | /* Parse the checksum payload, if it's present. */ |
703 | 0 | checksum_string_length = parse->checksum == NULL ? 0 |
704 | 0 | : strlen(parse->checksum); |
705 | 0 | if (checksum_string_length == 0) |
706 | 0 | { |
707 | 0 | checksum_length = 0; |
708 | 0 | checksum_payload = NULL; |
709 | 0 | } |
710 | 0 | else |
711 | 0 | { |
712 | 0 | checksum_length = checksum_string_length / 2; |
713 | 0 | checksum_payload = palloc(checksum_length); |
714 | 0 | if (checksum_string_length % 2 != 0 || |
715 | 0 | !hexdecode_string(checksum_payload, parse->checksum, |
716 | 0 | checksum_length)) |
717 | 0 | context->error_cb(context, |
718 | 0 | "invalid checksum for file \"%s\": \"%s\"", |
719 | 0 | parse->pathname, parse->checksum); |
720 | 0 | } |
721 | | |
722 | | /* Invoke the callback with the details we've gathered. */ |
723 | 0 | context->per_file_cb(context, parse->pathname, size, |
724 | 0 | checksum_type, checksum_length, checksum_payload); |
725 | | |
726 | | /* Free memory we no longer need. */ |
727 | 0 | if (parse->size != NULL) |
728 | 0 | { |
729 | 0 | pfree(parse->size); |
730 | 0 | parse->size = NULL; |
731 | 0 | } |
732 | 0 | if (parse->algorithm != NULL) |
733 | 0 | { |
734 | 0 | pfree(parse->algorithm); |
735 | 0 | parse->algorithm = NULL; |
736 | 0 | } |
737 | 0 | if (parse->checksum != NULL) |
738 | 0 | { |
739 | 0 | pfree(parse->checksum); |
740 | 0 | parse->checksum = NULL; |
741 | 0 | } |
742 | 0 | } |
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 | | json_manifest_finalize_wal_range(JsonManifestParseState *parse) |
752 | 0 | { |
753 | 0 | JsonManifestParseContext *context = parse->context; |
754 | 0 | TimeLineID tli; |
755 | 0 | XLogRecPtr start_lsn, |
756 | 0 | end_lsn; |
757 | 0 | char *ep; |
758 | | |
759 | | /* Make sure all fields are present. */ |
760 | 0 | if (parse->timeline == NULL) |
761 | 0 | json_manifest_parse_failure(parse->context, "missing timeline"); |
762 | 0 | if (parse->start_lsn == NULL) |
763 | 0 | json_manifest_parse_failure(parse->context, "missing start LSN"); |
764 | 0 | if (parse->end_lsn == NULL) |
765 | 0 | json_manifest_parse_failure(parse->context, "missing end LSN"); |
766 | | |
767 | | /* Parse timeline. */ |
768 | 0 | tli = strtoul(parse->timeline, &ep, 10); |
769 | 0 | if (ep == parse->timeline || *ep) |
770 | 0 | json_manifest_parse_failure(parse->context, |
771 | 0 | "timeline is not an integer"); |
772 | 0 | if (!parse_xlogrecptr(&start_lsn, parse->start_lsn)) |
773 | 0 | json_manifest_parse_failure(parse->context, |
774 | 0 | "could not parse start LSN"); |
775 | 0 | if (!parse_xlogrecptr(&end_lsn, parse->end_lsn)) |
776 | 0 | json_manifest_parse_failure(parse->context, |
777 | 0 | "could not parse end LSN"); |
778 | | |
779 | | /* Invoke the callback with the details we've gathered. */ |
780 | 0 | context->per_wal_range_cb(context, tli, start_lsn, end_lsn); |
781 | | |
782 | | /* Free memory we no longer need. */ |
783 | 0 | if (parse->timeline != NULL) |
784 | 0 | { |
785 | 0 | pfree(parse->timeline); |
786 | 0 | parse->timeline = NULL; |
787 | 0 | } |
788 | 0 | if (parse->start_lsn != NULL) |
789 | 0 | { |
790 | 0 | pfree(parse->start_lsn); |
791 | 0 | parse->start_lsn = NULL; |
792 | 0 | } |
793 | 0 | if (parse->end_lsn != NULL) |
794 | 0 | { |
795 | 0 | pfree(parse->end_lsn); |
796 | 0 | parse->end_lsn = NULL; |
797 | 0 | } |
798 | 0 | } |
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 |
812 | | verify_manifest_checksum(JsonManifestParseState *parse, const char *buffer, |
813 | | size_t size, pg_cryptohash_ctx *incr_ctx) |
814 | 0 | { |
815 | 0 | JsonManifestParseContext *context = parse->context; |
816 | 0 | size_t i; |
817 | 0 | size_t number_of_newlines = 0; |
818 | 0 | size_t ultimate_newline = 0; |
819 | 0 | size_t penultimate_newline = 0; |
820 | 0 | pg_cryptohash_ctx *manifest_ctx; |
821 | 0 | uint8 manifest_checksum_actual[PG_SHA256_DIGEST_LENGTH]; |
822 | 0 | uint8 manifest_checksum_expected[PG_SHA256_DIGEST_LENGTH]; |
823 | | |
824 | | /* Find the last two newlines in the file. */ |
825 | 0 | for (i = 0; i < size; ++i) |
826 | 0 | { |
827 | 0 | if (buffer[i] == '\n') |
828 | 0 | { |
829 | 0 | ++number_of_newlines; |
830 | 0 | penultimate_newline = ultimate_newline; |
831 | 0 | ultimate_newline = i; |
832 | 0 | } |
833 | 0 | } |
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 | 0 | if (number_of_newlines < 2) |
841 | 0 | json_manifest_parse_failure(parse->context, |
842 | 0 | "expected at least 2 lines"); |
843 | 0 | if (ultimate_newline != size - 1) |
844 | 0 | json_manifest_parse_failure(parse->context, |
845 | 0 | "last line not newline-terminated"); |
846 | | |
847 | | /* Checksum the rest. */ |
848 | 0 | if (incr_ctx == NULL) |
849 | 0 | { |
850 | 0 | manifest_ctx = pg_cryptohash_create(PG_SHA256); |
851 | 0 | if (manifest_ctx == NULL) |
852 | 0 | context->error_cb(context, "out of memory"); |
853 | 0 | if (pg_cryptohash_init(manifest_ctx) < 0) |
854 | 0 | context->error_cb(context, "could not initialize checksum of manifest"); |
855 | 0 | } |
856 | 0 | else |
857 | 0 | { |
858 | 0 | manifest_ctx = incr_ctx; |
859 | 0 | } |
860 | 0 | if (pg_cryptohash_update(manifest_ctx, (const uint8 *) buffer, penultimate_newline + 1) < 0) |
861 | 0 | context->error_cb(context, "could not update checksum of manifest"); |
862 | 0 | if (pg_cryptohash_final(manifest_ctx, manifest_checksum_actual, |
863 | 0 | sizeof(manifest_checksum_actual)) < 0) |
864 | 0 | context->error_cb(context, "could not finalize checksum of manifest"); |
865 | | |
866 | | /* Now verify it. */ |
867 | 0 | if (parse->manifest_checksum == NULL) |
868 | 0 | context->error_cb(parse->context, "manifest has no checksum"); |
869 | 0 | if (strlen(parse->manifest_checksum) != PG_SHA256_DIGEST_LENGTH * 2 || |
870 | 0 | !hexdecode_string(manifest_checksum_expected, parse->manifest_checksum, |
871 | 0 | PG_SHA256_DIGEST_LENGTH)) |
872 | 0 | context->error_cb(context, "invalid manifest checksum: \"%s\"", |
873 | 0 | parse->manifest_checksum); |
874 | 0 | if (memcmp(manifest_checksum_actual, manifest_checksum_expected, |
875 | 0 | PG_SHA256_DIGEST_LENGTH) != 0) |
876 | 0 | context->error_cb(context, "manifest checksum mismatch"); |
877 | 0 | pg_cryptohash_free(manifest_ctx); |
878 | 0 | } |
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 | | json_manifest_parse_failure(JsonManifestParseContext *context, char *msg) |
890 | 0 | { |
891 | 0 | context->error_cb(context, "could not parse backup manifest: %s", msg); |
892 | 0 | pg_unreachable(); |
893 | 0 | } |
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 | | hexdecode_char(char c) |
902 | 0 | { |
903 | 0 | if (c >= '0' && c <= '9') |
904 | 0 | return c - '0'; |
905 | 0 | if (c >= 'a' && c <= 'f') |
906 | 0 | return c - 'a' + 10; |
907 | 0 | if (c >= 'A' && c <= 'F') |
908 | 0 | return c - 'A' + 10; |
909 | | |
910 | 0 | return -1; |
911 | 0 | } |
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 | | hexdecode_string(uint8 *result, char *input, int nbytes) |
920 | 0 | { |
921 | 0 | int i; |
922 | |
|
923 | 0 | for (i = 0; i < nbytes; ++i) |
924 | 0 | { |
925 | 0 | int n1 = hexdecode_char(input[i * 2]); |
926 | 0 | int n2 = hexdecode_char(input[i * 2 + 1]); |
927 | |
|
928 | 0 | if (n1 < 0 || n2 < 0) |
929 | 0 | return false; |
930 | 0 | result[i] = n1 * 16 + n2; |
931 | 0 | } |
932 | | |
933 | 0 | return true; |
934 | 0 | } |
935 | | |
936 | | /* |
937 | | * Parse an XLogRecPtr expressed using the usual string format. |
938 | | */ |
939 | | static bool |
940 | | parse_xlogrecptr(XLogRecPtr *result, char *input) |
941 | 0 | { |
942 | 0 | uint32 hi; |
943 | 0 | uint32 lo; |
944 | |
|
945 | 0 | if (sscanf(input, "%X/%08X", &hi, &lo) != 2) |
946 | 0 | return false; |
947 | 0 | *result = ((uint64) hi) << 32 | lo; |
948 | | return true; |
949 | 0 | } |