/*-------------------------------------------------------------------------
*
* parse_manifest . c
* Parse a backup manifest in JSON format .
*
* Portions Copyright ( c ) 1996 - 2025 , PostgreSQL Global Development Group
* Portions Copyright ( c ) 1994 , Regents of the University of California
*
* src / common / parse_manifest . c
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
#include "postgres_fe.h"
#include "common/jsonapi.h"
#include "common/parse_manifest.h"
/*
* Semantic states for JSON manifest parsing .
*/
typedef enum
{
JM_EXPECT_TOPLEVEL_START,
JM_EXPECT_TOPLEVEL_END,
JM_EXPECT_TOPLEVEL_FIELD,
JM_EXPECT_VERSION_VALUE,
JM_EXPECT_SYSTEM_IDENTIFIER_VALUE,
JM_EXPECT_FILES_START,
JM_EXPECT_FILES_NEXT,
JM_EXPECT_THIS_FILE_FIELD,
JM_EXPECT_THIS_FILE_VALUE,
JM_EXPECT_WAL_RANGES_START,
JM_EXPECT_WAL_RANGES_NEXT,
JM_EXPECT_THIS_WAL_RANGE_FIELD,
JM_EXPECT_THIS_WAL_RANGE_VALUE,
JM_EXPECT_MANIFEST_CHECKSUM_VALUE,
JM_EXPECT_EOF,
} JsonManifestSemanticState;
/*
* Possible fields for one file as described by the manifest .
*/
typedef enum
{
JMFF_PATH,
JMFF_ENCODED_PATH,
JMFF_SIZE,
JMFF_LAST_MODIFIED,
JMFF_CHECKSUM_ALGORITHM,
JMFF_CHECKSUM,
} JsonManifestFileField;
/*
* Possible fields for one file as described by the manifest .
*/
typedef enum
{
JMWRF_TIMELINE,
JMWRF_START_LSN,
JMWRF_END_LSN,
} JsonManifestWALRangeField;
/*
* Internal state used while decoding the JSON - format backup manifest .
*/
typedef struct
{
JsonManifestParseContext *context;
JsonManifestSemanticState state;
/* These fields are used for parsing objects in the list of files. */
JsonManifestFileField file_field;
char *pathname;
char *encoded_pathname;
char *size;
char *algorithm;
pg_checksum_type checksum_algorithm;
char *checksum;
/* These fields are used for parsing objects in the list of WAL ranges. */
JsonManifestWALRangeField wal_range_field;
char *timeline;
char *start_lsn;
char *end_lsn;
/* Miscellaneous other stuff. */
bool saw_version_field;
char *manifest_version;
char *manifest_system_identifier;
char *manifest_checksum;
} JsonManifestParseState;
/* typedef appears in parse_manifest.h */
struct JsonManifestParseIncrementalState
{
JsonLexContext lex;
JsonSemAction sem;
pg_cryptohash_ctx *manifest_ctx;
};
static JsonParseErrorType json_manifest_object_start(void *state);
static JsonParseErrorType json_manifest_object_end(void *state);
static JsonParseErrorType json_manifest_array_start(void *state);
static JsonParseErrorType json_manifest_array_end(void *state);
static JsonParseErrorType json_manifest_object_field_start(void *state, char *fname,
bool isnull);
static JsonParseErrorType json_manifest_scalar(void *state, char *token,
JsonTokenType tokentype);
static void json_manifest_finalize_version(JsonManifestParseState *parse);
static void json_manifest_finalize_system_identifier(JsonManifestParseState *parse);
static void json_manifest_finalize_file(JsonManifestParseState *parse);
static void json_manifest_finalize_wal_range(JsonManifestParseState *parse);
static void verify_manifest_checksum(JsonManifestParseState *parse,
const char *buffer, size_t size,
pg_cryptohash_ctx *incr_ctx);
pg_noreturn static void json_manifest_parse_failure(JsonManifestParseContext *context,
char *msg);
static int hexdecode_char(char c);
static bool hexdecode_string(uint8 *result, char *input, int nbytes);
static bool parse_xlogrecptr(XLogRecPtr *result, char *input);
/*
* Set up for incremental parsing of the manifest .
*/
JsonManifestParseIncrementalState *
json_parse_manifest_incremental_init(JsonManifestParseContext *context)
{
JsonManifestParseIncrementalState *incstate;
JsonManifestParseState *parse;
pg_cryptohash_ctx *manifest_ctx;
incstate = palloc(sizeof (JsonManifestParseIncrementalState));
parse = palloc(sizeof (JsonManifestParseState));
parse->context = context;
parse->state = JM_EXPECT_TOPLEVEL_START;
parse->saw_version_field = false ;
makeJsonLexContextIncremental(&(incstate->lex), PG_UTF8, true );
incstate->sem.semstate = parse;
incstate->sem.object_start = json_manifest_object_start;
incstate->sem.object_end = json_manifest_object_end;
incstate->sem.array_start = json_manifest_array_start;
incstate->sem.array_end = json_manifest_array_end;
incstate->sem.object_field_start = json_manifest_object_field_start;
incstate->sem.object_field_end = NULL;
incstate->sem.array_element_start = NULL;
incstate->sem.array_element_end = NULL;
incstate->sem.scalar = json_manifest_scalar;
manifest_ctx = pg_cryptohash_create(PG_SHA256);
if (manifest_ctx == NULL)
context->error_cb(context, "out of memory" );
if (pg_cryptohash_init(manifest_ctx) < 0 )
context->error_cb(context, "could not initialize checksum of manifest" );
incstate->manifest_ctx = manifest_ctx;
return incstate;
}
/*
* Free an incremental state object and its contents .
*/
void
json_parse_manifest_incremental_shutdown(JsonManifestParseIncrementalState *incstate)
{
pfree(incstate->sem.semstate);
freeJsonLexContext(&(incstate->lex));
/* incstate->manifest_ctx has already been freed */
pfree(incstate);
}
/*
* parse the manifest in pieces .
*
* The caller must ensure that the final piece contains the final lines
* with the complete checksum .
*/
void
json_parse_manifest_incremental_chunk(JsonManifestParseIncrementalState *incstate,
const char *chunk, size_t size, bool is_last)
{
JsonParseErrorType res,
expected;
JsonManifestParseState *parse = incstate->sem.semstate;
JsonManifestParseContext *context = parse->context;
res = pg_parse_json_incremental(&(incstate->lex), &(incstate->sem),
chunk, size, is_last);
expected = is_last ? JSON_SUCCESS : JSON_INCOMPLETE;
if (res != expected)
json_manifest_parse_failure(context,
json_errdetail(res, &(incstate->lex)));
if (is_last && parse->state != JM_EXPECT_EOF)
json_manifest_parse_failure(context, "manifest ended unexpectedly" );
if (!is_last)
{
if (pg_cryptohash_update(incstate->manifest_ctx,
(const uint8 *) chunk, size) < 0 )
context->error_cb(context, "could not update checksum of manifest" );
}
else
{
verify_manifest_checksum(parse, chunk, size, incstate->manifest_ctx);
}
}
/*
* Main entrypoint to parse a JSON - format backup manifest .
*
* Caller should set up the parsing context and then invoke this function .
* For each file whose information is extracted from the manifest ,
* context - > per_file_cb is invoked . In case of trouble , context - > error_cb is
* invoked and is expected not to return .
*/
void
json_parse_manifest(JsonManifestParseContext *context, const char *buffer,
size_t size)
{
JsonLexContext *lex;
JsonParseErrorType json_error;
JsonSemAction sem;
JsonManifestParseState parse;
/* Set up our private parsing context. */
parse.context = context;
parse.state = JM_EXPECT_TOPLEVEL_START;
parse.saw_version_field = false ;
/* Create a JSON lexing context. */
lex = makeJsonLexContextCstringLen(NULL, buffer, size, PG_UTF8, true );
/* Set up semantic actions. */
sem.semstate = &parse;
sem.object_start = json_manifest_object_start;
sem.object_end = json_manifest_object_end;
sem.array_start = json_manifest_array_start;
sem.array_end = json_manifest_array_end;
sem.object_field_start = json_manifest_object_field_start;
sem.object_field_end = NULL;
sem.array_element_start = NULL;
sem.array_element_end = NULL;
sem.scalar = json_manifest_scalar;
/* Run the actual JSON parser. */
json_error = pg_parse_json(lex, &sem);
if (json_error != JSON_SUCCESS)
json_manifest_parse_failure(context, json_errdetail(json_error, lex));
if (parse.state != JM_EXPECT_EOF)
json_manifest_parse_failure(context, "manifest ended unexpectedly" );
/* Verify the manifest checksum. */
verify_manifest_checksum(&parse, buffer, size, NULL);
freeJsonLexContext(lex);
}
/*
* Invoked at the start of each object in the JSON document .
*
* The document as a whole is expected to be an object ; each file and each
* WAL range is also expected to be an object . If we ' re anywhere else in the
* document , it ' s an error .
*/
static JsonParseErrorType
json_manifest_object_start(void *state)
{
JsonManifestParseState *parse = state;
switch (parse->state)
{
case JM_EXPECT_TOPLEVEL_START:
parse->state = JM_EXPECT_TOPLEVEL_FIELD;
break ;
case JM_EXPECT_FILES_NEXT:
parse->state = JM_EXPECT_THIS_FILE_FIELD;
parse->pathname = NULL;
parse->encoded_pathname = NULL;
parse->size = NULL;
parse->algorithm = NULL;
parse->checksum = NULL;
break ;
case JM_EXPECT_WAL_RANGES_NEXT:
parse->state = JM_EXPECT_THIS_WAL_RANGE_FIELD;
parse->timeline = NULL;
parse->start_lsn = NULL;
parse->end_lsn = NULL;
break ;
default :
json_manifest_parse_failure(parse->context,
"unexpected object start" );
break ;
}
return JSON_SUCCESS;
}
/*
* Invoked at the end of each object in the JSON document .
*
* The possible cases here are the same as for json_manifest_object_start .
* There ' s nothing special to do at the end of the document , but when we
* reach the end of an object representing a particular file or WAL range ,
* we must call json_manifest_finalize_file ( ) to save the associated details .
*/
static JsonParseErrorType
json_manifest_object_end(void *state)
{
JsonManifestParseState *parse = state;
switch (parse->state)
{
case JM_EXPECT_TOPLEVEL_END:
parse->state = JM_EXPECT_EOF;
break ;
case JM_EXPECT_THIS_FILE_FIELD:
json_manifest_finalize_file(parse);
parse->state = JM_EXPECT_FILES_NEXT;
break ;
case JM_EXPECT_THIS_WAL_RANGE_FIELD:
json_manifest_finalize_wal_range(parse);
parse->state = JM_EXPECT_WAL_RANGES_NEXT;
break ;
default :
json_manifest_parse_failure(parse->context,
"unexpected object end" );
break ;
}
return JSON_SUCCESS;
}
/*
* Invoked at the start of each array in the JSON document .
*
* Within the toplevel object , the value associated with the " Files " key
* should be an array . Similarly for the " WAL - Ranges " key . No other arrays
* are expected .
*/
static JsonParseErrorType
json_manifest_array_start(void *state)
{
JsonManifestParseState *parse = state;
switch (parse->state)
{
case JM_EXPECT_FILES_START:
parse->state = JM_EXPECT_FILES_NEXT;
break ;
case JM_EXPECT_WAL_RANGES_START:
parse->state = JM_EXPECT_WAL_RANGES_NEXT;
break ;
default :
json_manifest_parse_failure(parse->context,
"unexpected array start" );
break ;
}
return JSON_SUCCESS;
}
/*
* Invoked at the end of each array in the JSON document .
*
* The cases here are analogous to those in json_manifest_array_start .
*/
static JsonParseErrorType
json_manifest_array_end(void *state)
{
JsonManifestParseState *parse = state;
switch (parse->state)
{
case JM_EXPECT_FILES_NEXT:
case JM_EXPECT_WAL_RANGES_NEXT:
parse->state = JM_EXPECT_TOPLEVEL_FIELD;
break ;
default :
json_manifest_parse_failure(parse->context,
"unexpected array end" );
break ;
}
return JSON_SUCCESS;
}
/*
* Invoked at the start of each object field in the JSON document .
*/
static JsonParseErrorType
json_manifest_object_field_start(void *state, char *fname, bool isnull)
{
JsonManifestParseState *parse = state;
switch (parse->state)
{
case JM_EXPECT_TOPLEVEL_FIELD:
/*
* Inside toplevel object . The version indicator should always be
* the first field .
*/
if (!parse->saw_version_field)
{
if (strcmp(fname, "PostgreSQL-Backup-Manifest-Version" ) != 0 )
json_manifest_parse_failure(parse->context,
"expected version indicator" );
parse->state = JM_EXPECT_VERSION_VALUE;
parse->saw_version_field = true ;
break ;
}
/* Is this the system identifier? */
if (strcmp(fname, "System-Identifier" ) == 0 )
{
parse->state = JM_EXPECT_SYSTEM_IDENTIFIER_VALUE;
break ;
}
/* Is this the list of files? */
if (strcmp(fname, "Files" ) == 0 )
{
parse->state = JM_EXPECT_FILES_START;
break ;
}
/* Is this the list of WAL ranges? */
if (strcmp(fname, "WAL-Ranges" ) == 0 )
{
parse->state = JM_EXPECT_WAL_RANGES_START;
break ;
}
/* Is this the manifest checksum? */
if (strcmp(fname, "Manifest-Checksum" ) == 0 )
{
parse->state = JM_EXPECT_MANIFEST_CHECKSUM_VALUE;
break ;
}
/* It's not a field we recognize. */
json_manifest_parse_failure(parse->context,
"unrecognized top-level field" );
break ;
case JM_EXPECT_THIS_FILE_FIELD:
/* Inside object for one file; which key have we got? */
if (strcmp(fname, "Path" ) == 0 )
parse->file_field = JMFF_PATH;
else if (strcmp(fname, "Encoded-Path" ) == 0 )
parse->file_field = JMFF_ENCODED_PATH;
else if (strcmp(fname, "Size" ) == 0 )
parse->file_field = JMFF_SIZE;
else if (strcmp(fname, "Last-Modified" ) == 0 )
parse->file_field = JMFF_LAST_MODIFIED;
else if (strcmp(fname, "Checksum-Algorithm" ) == 0 )
parse->file_field = JMFF_CHECKSUM_ALGORITHM;
else if (strcmp(fname, "Checksum" ) == 0 )
parse->file_field = JMFF_CHECKSUM;
else
json_manifest_parse_failure(parse->context,
"unexpected file field" );
parse->state = JM_EXPECT_THIS_FILE_VALUE;
break ;
case JM_EXPECT_THIS_WAL_RANGE_FIELD:
/* Inside object for one file; which key have we got? */
if (strcmp(fname, "Timeline" ) == 0 )
parse->wal_range_field = JMWRF_TIMELINE;
else if (strcmp(fname, "Start-LSN" ) == 0 )
parse->wal_range_field = JMWRF_START_LSN;
else if (strcmp(fname, "End-LSN" ) == 0 )
parse->wal_range_field = JMWRF_END_LSN;
else
json_manifest_parse_failure(parse->context,
"unexpected WAL range field" );
parse->state = JM_EXPECT_THIS_WAL_RANGE_VALUE;
break ;
default :
json_manifest_parse_failure(parse->context,
"unexpected object field" );
break ;
}
pfree(fname);
return JSON_SUCCESS;
}
/*
* Invoked at the start of each scalar in the JSON document .
*
* Object field names don ' t reach this code ; those are handled by
* json_manifest_object_field_start . When we ' re inside of the object for
* a particular file or WAL range , that function will have noticed the name
* of the field , and we ' ll get the corresponding value here . When we ' re in
* the toplevel object , the parse state itself tells us which field this is .
*
* In all cases except for PostgreSQL - Backup - Manifest - Version , which we
* can just check on the spot , the goal here is just to save the value in
* the parse state for later use . We don ' t actually do anything until we
* reach either the end of the object representing this file , or the end
* of the manifest , as the case may be .
*/
static JsonParseErrorType
json_manifest_scalar(void *state, char *token, JsonTokenType tokentype)
{
JsonManifestParseState *parse = state;
switch (parse->state)
{
case JM_EXPECT_VERSION_VALUE:
parse->manifest_version = token;
json_manifest_finalize_version(parse);
parse->state = JM_EXPECT_TOPLEVEL_FIELD;
break ;
case JM_EXPECT_SYSTEM_IDENTIFIER_VALUE:
parse->manifest_system_identifier = token;
json_manifest_finalize_system_identifier(parse);
parse->state = JM_EXPECT_TOPLEVEL_FIELD;
break ;
case JM_EXPECT_THIS_FILE_VALUE:
switch (parse->file_field)
{
case JMFF_PATH:
parse->pathname = token;
break ;
case JMFF_ENCODED_PATH:
parse->encoded_pathname = token;
break ;
case JMFF_SIZE:
parse->size = token;
break ;
case JMFF_LAST_MODIFIED:
pfree(token); /* unused */
break ;
case JMFF_CHECKSUM_ALGORITHM:
parse->algorithm = token;
break ;
case JMFF_CHECKSUM:
parse->checksum = token;
break ;
}
parse->state = JM_EXPECT_THIS_FILE_FIELD;
break ;
case JM_EXPECT_THIS_WAL_RANGE_VALUE:
switch (parse->wal_range_field)
{
case JMWRF_TIMELINE:
parse->timeline = token;
break ;
case JMWRF_START_LSN:
parse->start_lsn = token;
break ;
case JMWRF_END_LSN:
parse->end_lsn = token;
break ;
}
parse->state = JM_EXPECT_THIS_WAL_RANGE_FIELD;
break ;
case JM_EXPECT_MANIFEST_CHECKSUM_VALUE:
parse->state = JM_EXPECT_TOPLEVEL_END;
parse->manifest_checksum = token;
break ;
default :
json_manifest_parse_failure(parse->context, "unexpected scalar" );
break ;
}
return JSON_SUCCESS;
}
/*
* Do additional parsing and sanity - checking of the manifest version , and invoke
* the callback so that the caller can gets that detail and take actions
* accordingly . This happens for each manifest when the corresponding JSON
* object is completely parsed .
*/
static void
json_manifest_finalize_version(JsonManifestParseState *parse)
{
JsonManifestParseContext *context = parse->context;
int version;
char *ep;
Assert(parse->saw_version_field);
/* Parse version. */
version = strtoi64(parse->manifest_version, &ep, 10 );
if (*ep)
json_manifest_parse_failure(parse->context,
"manifest version not an integer" );
if (version != 1 && version != 2 )
json_manifest_parse_failure(parse->context,
"unexpected manifest version" );
/* Invoke the callback for version */
context->version_cb(context, version);
}
/*
* Do additional parsing and sanity - checking of the system identifier , and
* invoke the callback so that the caller can gets that detail and take actions
* accordingly .
*/
static void
json_manifest_finalize_system_identifier(JsonManifestParseState *parse)
{
JsonManifestParseContext *context = parse->context;
uint64 system_identifier;
char *ep;
Assert(parse->manifest_system_identifier != NULL);
/* Parse system identifier. */
system_identifier = strtou64(parse->manifest_system_identifier, &ep, 10 );
if (*ep)
json_manifest_parse_failure(parse->context,
"system identifier in manifest not an integer" );
/* Invoke the callback for system identifier */
context->system_identifier_cb(context, system_identifier);
}
/*
* Do additional parsing and sanity - checking of the details gathered for one
* file , and invoke the per - file callback so that the caller gets those
* details . This happens for each file when the corresponding JSON object is
* completely parsed .
*/
static void
json_manifest_finalize_file(JsonManifestParseState *parse)
{
JsonManifestParseContext *context = parse->context;
uint64 size;
char *ep;
int checksum_string_length;
pg_checksum_type checksum_type;
int checksum_length;
uint8 *checksum_payload;
/* Pathname and size are required. */
if (parse->pathname == NULL && parse->encoded_pathname == NULL)
json_manifest_parse_failure(parse->context, "missing path name" );
if (parse->pathname != NULL && parse->encoded_pathname != NULL)
json_manifest_parse_failure(parse->context,
"both path name and encoded path name" );
if (parse->size == NULL)
json_manifest_parse_failure(parse->context, "missing size" );
if (parse->algorithm == NULL && parse->checksum != NULL)
json_manifest_parse_failure(parse->context,
"checksum without algorithm" );
/* Decode encoded pathname, if that's what we have. */
if (parse->encoded_pathname != NULL)
{
int encoded_length = strlen(parse->encoded_pathname);
int raw_length = encoded_length / 2 ;
parse->pathname = palloc(raw_length + 1 );
if (encoded_length % 2 != 0 ||
!hexdecode_string((uint8 *) parse->pathname,
parse->encoded_pathname,
raw_length))
json_manifest_parse_failure(parse->context,
"could not decode file name" );
parse->pathname[raw_length] = '\0' ;
pfree(parse->encoded_pathname);
parse->encoded_pathname = NULL;
}
/* Parse size. */
size = strtou64(parse->size, &ep, 10 );
if (*ep)
json_manifest_parse_failure(parse->context,
"file size is not an integer" );
/* Parse the checksum algorithm, if it's present. */
if (parse->algorithm == NULL)
checksum_type = CHECKSUM_TYPE_NONE;
else if (!pg_checksum_parse_type(parse->algorithm, &checksum_type))
context->error_cb(context, "unrecognized checksum algorithm: \" %s\"" ,
parse->algorithm);
/* Parse the checksum payload, if it's present. */
checksum_string_length = parse->checksum == NULL ? 0
: strlen(parse->checksum);
if (checksum_string_length == 0 )
{
checksum_length = 0 ;
checksum_payload = NULL;
}
else
{
checksum_length = checksum_string_length / 2 ;
checksum_payload = palloc(checksum_length);
if (checksum_string_length % 2 != 0 ||
!hexdecode_string(checksum_payload, parse->checksum,
checksum_length))
context->error_cb(context,
"invalid checksum for file \" %s\": \" %s\"" ,
parse->pathname, parse->checksum);
}
/* Invoke the callback with the details we've gathered. */
context->per_file_cb(context, parse->pathname, size,
checksum_type, checksum_length, checksum_payload);
/* Free memory we no longer need. */
if (parse->size != NULL)
{
pfree(parse->size);
parse->size = NULL;
}
if (parse->algorithm != NULL)
{
pfree(parse->algorithm);
parse->algorithm = NULL;
}
if (parse->checksum != NULL)
{
pfree(parse->checksum);
parse->checksum = NULL;
}
}
/*
* Do additional parsing and sanity - checking of the details gathered for one
* WAL range , and invoke the per - WAL - range callback so that the caller gets
* those details . This happens for each WAL range when the corresponding JSON
* object is completely parsed .
*/
static void
json_manifest_finalize_wal_range(JsonManifestParseState *parse)
{
JsonManifestParseContext *context = parse->context;
TimeLineID tli;
XLogRecPtr start_lsn,
end_lsn;
char *ep;
/* Make sure all fields are present. */
if (parse->timeline == NULL)
json_manifest_parse_failure(parse->context, "missing timeline" );
if (parse->start_lsn == NULL)
json_manifest_parse_failure(parse->context, "missing start LSN" );
if (parse->end_lsn == NULL)
json_manifest_parse_failure(parse->context, "missing end LSN" );
/* Parse timeline. */
tli = strtoul(parse->timeline, &ep, 10 );
if (*ep)
json_manifest_parse_failure(parse->context,
"timeline is not an integer" );
if (!parse_xlogrecptr(&start_lsn, parse->start_lsn))
json_manifest_parse_failure(parse->context,
"could not parse start LSN" );
if (!parse_xlogrecptr(&end_lsn, parse->end_lsn))
json_manifest_parse_failure(parse->context,
"could not parse end LSN" );
/* Invoke the callback with the details we've gathered. */
context->per_wal_range_cb(context, tli, start_lsn, end_lsn);
/* Free memory we no longer need. */
if (parse->timeline != NULL)
{
pfree(parse->timeline);
parse->timeline = NULL;
}
if (parse->start_lsn != NULL)
{
pfree(parse->start_lsn);
parse->start_lsn = NULL;
}
if (parse->end_lsn != NULL)
{
pfree(parse->end_lsn);
parse->end_lsn = NULL;
}
}
/*
* Verify that the manifest checksum is correct .
*
* The last line of the manifest file is excluded from the manifest checksum ,
* because the last line is expected to contain the checksum that covers
* the rest of the file .
*
* For an incremental parse , this will just be called on the last chunk of the
* manifest , and the cryptohash context passed in . For a non - incremental
* parse incr_ctx will be NULL .
*/
static void
verify_manifest_checksum(JsonManifestParseState *parse, const char *buffer,
size_t size, pg_cryptohash_ctx *incr_ctx)
{
JsonManifestParseContext *context = parse->context;
size_t i;
size_t number_of_newlines = 0 ;
size_t ultimate_newline = 0 ;
size_t penultimate_newline = 0 ;
pg_cryptohash_ctx *manifest_ctx;
uint8 manifest_checksum_actual[PG_SHA256_DIGEST_LENGTH];
uint8 manifest_checksum_expected[PG_SHA256_DIGEST_LENGTH];
/* Find the last two newlines in the file. */
for (i = 0 ; i < size; ++i)
{
if (buffer[i] == '\n' )
{
++number_of_newlines;
penultimate_newline = ultimate_newline;
ultimate_newline = i;
}
}
/*
* Make sure that the last newline is right at the end , and that there are
* at least two lines total . We need this to be true in order for the
* following code , which computes the manifest checksum , to work properly .
*/
if (number_of_newlines < 2 )
json_manifest_parse_failure(parse->context,
"expected at least 2 lines" );
if (ultimate_newline != size - 1 )
json_manifest_parse_failure(parse->context,
"last line not newline-terminated" );
/* Checksum the rest. */
if (incr_ctx == NULL)
{
manifest_ctx = pg_cryptohash_create(PG_SHA256);
if (manifest_ctx == NULL)
context->error_cb(context, "out of memory" );
if (pg_cryptohash_init(manifest_ctx) < 0 )
context->error_cb(context, "could not initialize checksum of manifest" );
}
else
{
manifest_ctx = incr_ctx;
}
if (pg_cryptohash_update(manifest_ctx, (const uint8 *) buffer, penultimate_newline + 1 ) < 0 )
context->error_cb(context, "could not update checksum of manifest" );
if (pg_cryptohash_final(manifest_ctx, manifest_checksum_actual,
sizeof (manifest_checksum_actual)) < 0 )
context->error_cb(context, "could not finalize checksum of manifest" );
/* Now verify it. */
if (parse->manifest_checksum == NULL)
context->error_cb(parse->context, "manifest has no checksum" );
if (strlen(parse->manifest_checksum) != PG_SHA256_DIGEST_LENGTH * 2 ||
!hexdecode_string(manifest_checksum_expected, parse->manifest_checksum,
PG_SHA256_DIGEST_LENGTH))
context->error_cb(context, "invalid manifest checksum: \" %s\"" ,
parse->manifest_checksum);
if (memcmp(manifest_checksum_actual, manifest_checksum_expected,
PG_SHA256_DIGEST_LENGTH) != 0 )
context->error_cb(context, "manifest checksum mismatch" );
pg_cryptohash_free(manifest_ctx);
}
/*
* Report a parse error .
*
* This is intended to be used for fairly low - level failures that probably
* shouldn ' t occur unless somebody has deliberately constructed a bad manifest ,
* or unless the server is generating bad manifests due to some bug . msg should
* be a short string giving some hint as to what the problem is .
*/
static void
json_manifest_parse_failure(JsonManifestParseContext *context, char *msg)
{
context->error_cb(context, "could not parse backup manifest: %s" , msg);
pg_unreachable();
}
/*
* Convert a character which represents a hexadecimal digit to an integer .
*
* Returns - 1 if the character is not a hexadecimal digit .
*/
static int
hexdecode_char(char c)
{
if (c >= '0' && c <= '9' )
return c - '0' ;
if (c >= 'a' && c <= 'f' )
return c - 'a' + 10 ;
if (c >= 'A' && c <= 'F' )
return c - 'A' + 10 ;
return -1 ;
}
/*
* Decode a hex string into a byte string , 2 hex chars per byte .
*
* Returns false if invalid characters are encountered ; otherwise true .
*/
static bool
hexdecode_string(uint8 *result, char *input, int nbytes)
{
int i;
for (i = 0 ; i < nbytes; ++i)
{
int n1 = hexdecode_char(input[i * 2 ]);
int n2 = hexdecode_char(input[i * 2 + 1 ]);
if (n1 < 0 || n2 < 0 )
return false ;
result[i] = n1 * 16 + n2;
}
return true ;
}
/*
* Parse an XLogRecPtr expressed using the usual string format .
*/
static bool
parse_xlogrecptr(XLogRecPtr *result, char *input)
{
uint32 hi;
uint32 lo;
if (sscanf(input, "%X/%X" , &hi, &lo) != 2 )
return false ;
*result = ((uint64) hi) << 32 | lo;
return true ;
}
Messung V0.5 in Prozent C=94 H=94 G=93
¤ Dauer der Verarbeitung: 0.20 Sekunden
(vorverarbeitet am 2026-08-08)
¤
*© Formatika GbR, Deutschland