/*-------------------------------------------------------------------------
*
* Write a new backup manifest .
*
* Portions Copyright ( c ) 1996 - 2025 , PostgreSQL Global Development Group
* Portions Copyright ( c ) 1994 , Regents of the University of California
*
* src / bin / pg_combinebackup / write_manifest . c
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
#include "postgres_fe.h"
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#include "common/checksum_helper.h"
#include "common/file_perm.h"
#include "common/logging.h"
#include "lib/stringinfo.h"
#include "load_manifest.h"
#include "mb/pg_wchar.h"
#include "write_manifest.h"
struct manifest_writer
{
char pathname[MAXPGPATH];
int fd;
StringInfoData buf;
bool first_file;
bool still_checksumming;
pg_checksum_context manifest_ctx;
};
static void escape_json(StringInfo buf, const char *str);
static void flush_manifest(manifest_writer *mwriter);
static size_t hex_encode(const uint8 *src, size_t len, char *dst);
/*
* Create a new backup manifest writer .
*
* The backup manifest will be written into a file named backup_manifest
* in the specified directory .
*/
manifest_writer *
create_manifest_writer(char *directory, uint64 system_identifier)
{
manifest_writer *mwriter = pg_malloc(sizeof (manifest_writer));
snprintf(mwriter->pathname, MAXPGPATH, "%s/backup_manifest" , directory);
mwriter->fd = -1 ;
initStringInfo(&mwriter->buf);
mwriter->first_file = true ;
mwriter->still_checksumming = true ;
pg_checksum_init(&mwriter->manifest_ctx, CHECKSUM_TYPE_SHA256);
appendStringInfo(&mwriter->buf,
"{ \" PostgreSQL-Backup-Manifest-Version\": 2,\n"
"\" System-Identifier\": " UINT64_FORMAT ",\n"
"\" Files\": [" ,
system_identifier);
return mwriter;
}
/*
* Add an entry for a file to a backup manifest .
*
* This is very similar to the backend ' s AddFileToBackupManifest , but
* various adjustments are required due to frontend / backend differences
* and other details .
*/
void
add_file_to_manifest(manifest_writer *mwriter, const char *manifest_path,
uint64 size, time_t mtime,
pg_checksum_type checksum_type,
int checksum_length,
uint8 *checksum_payload)
{
int pathlen = strlen(manifest_path);
if (mwriter->first_file)
{
appendStringInfoChar(&mwriter->buf, '\n' );
mwriter->first_file = false ;
}
else
appendStringInfoString(&mwriter->buf, ",\n" );
if (pg_encoding_verifymbstr(PG_UTF8, manifest_path, pathlen) == pathlen)
{
appendStringInfoString(&mwriter->buf, "{ \" Path\": " );
escape_json(&mwriter->buf, manifest_path);
appendStringInfoString(&mwriter->buf, ", " );
}
else
{
appendStringInfoString(&mwriter->buf, "{ \" Encoded-Path\": \" ");
enlargeStringInfo(&mwriter->buf, 2 * pathlen);
mwriter->buf.len += hex_encode((const uint8 *) manifest_path, pathlen,
&mwriter->buf.data[mwriter->buf.len]);
appendStringInfoString(&mwriter->buf, "\" , ");
}
appendStringInfo(&mwriter->buf, "\" Size\": %" PRIu64 ", " , size);
appendStringInfoString(&mwriter->buf, "\" Last-Modified\": \" ");
enlargeStringInfo(&mwriter->buf, 128 );
mwriter->buf.len += strftime(&mwriter->buf.data[mwriter->buf.len], 128 ,
"%Y-%m-%d %H:%M:%S %Z" ,
gmtime(&mtime));
appendStringInfoChar(&mwriter->buf, '"' );
if (mwriter->buf.len > 128 * 1024 )
flush_manifest(mwriter);
if (checksum_length > 0 )
{
appendStringInfo(&mwriter->buf,
", \" Checksum-Algorithm\": \" %s\", \" Checksum\": \" ",
pg_checksum_type_name(checksum_type));
enlargeStringInfo(&mwriter->buf, 2 * checksum_length);
mwriter->buf.len += hex_encode(checksum_payload, checksum_length,
&mwriter->buf.data[mwriter->buf.len]);
appendStringInfoChar(&mwriter->buf, '"' );
}
appendStringInfoString(&mwriter->buf, " }" );
if (mwriter->buf.len > 128 * 1024 )
flush_manifest(mwriter);
}
/*
* Finalize the backup_manifest .
*/
void
finalize_manifest(manifest_writer *mwriter,
manifest_wal_range *first_wal_range)
{
uint8 checksumbuf[PG_SHA256_DIGEST_LENGTH];
int len;
manifest_wal_range *wal_range;
/* Terminate the list of files. */
appendStringInfoString(&mwriter->buf, "\n],\n" );
/* Start a list of LSN ranges. */
appendStringInfoString(&mwriter->buf, "\" WAL-Ranges\": [\n" );
for (wal_range = first_wal_range; wal_range != NULL;
wal_range = wal_range->next)
appendStringInfo(&mwriter->buf,
"%s{ \" Timeline\": %u, \" Start-LSN\": \" %X/%X\", \" End-LSN\": \" %X/%X\" }" ,
wal_range == first_wal_range ? "" : ",\n" ,
wal_range->tli,
LSN_FORMAT_ARGS(wal_range->start_lsn),
LSN_FORMAT_ARGS(wal_range->end_lsn));
/* Terminate the list of WAL ranges. */
appendStringInfoString(&mwriter->buf, "\n],\n" );
/* Flush accumulated data and update checksum calculation. */
flush_manifest(mwriter);
/* Checksum only includes data up to this point. */
mwriter->still_checksumming = false ;
/* Compute and insert manifest checksum. */
appendStringInfoString(&mwriter->buf, "\" Manifest-Checksum\": \" ");
enlargeStringInfo(&mwriter->buf, 2 * PG_SHA256_DIGEST_STRING_LENGTH);
len = pg_checksum_final(&mwriter->manifest_ctx, checksumbuf);
Assert(len == PG_SHA256_DIGEST_LENGTH);
mwriter->buf.len +=
hex_encode(checksumbuf, len, &mwriter->buf.data[mwriter->buf.len]);
appendStringInfoString(&mwriter->buf, "\" }\n");
/* Flush the last manifest checksum itself. */
flush_manifest(mwriter);
/* Close the file. */
if (close(mwriter->fd) != 0 )
pg_fatal("could not close file \" %s\": %m" , mwriter->pathname);
mwriter->fd = -1 ;
}
/*
* Produce a JSON string literal , properly escaping characters in the text .
*/
static void
escape_json(StringInfo buf, const char *str)
{
const char *p;
appendStringInfoCharMacro(buf, '"' );
for (p = str; *p; p++)
{
switch (*p)
{
case '\b' :
appendStringInfoString(buf, "\\b" );
break ;
case '\f' :
appendStringInfoString(buf, "\\f" );
break ;
case '\n' :
appendStringInfoString(buf, "\\n" );
break ;
case '\r' :
appendStringInfoString(buf, "\\r" );
break ;
case '\t' :
appendStringInfoString(buf, "\\t" );
break ;
case '"' :
appendStringInfoString(buf, "\\\" ");
break ;
case '\\' :
appendStringInfoString(buf, "\\\\" );
break ;
default :
if ((unsigned char ) *p < ' ' )
appendStringInfo(buf, "\\u%04x" , (int ) *p);
else
appendStringInfoCharMacro(buf, *p);
break ;
}
}
appendStringInfoCharMacro(buf, '"' );
}
/*
* Flush whatever portion of the backup manifest we have generated and
* buffered in memory out to a file on disk .
*
* The first call to this function will create the file . After that , we
* keep it open and just append more data .
*/
static void
flush_manifest(manifest_writer *mwriter)
{
if (mwriter->fd == -1 &&
(mwriter->fd = open(mwriter->pathname,
O_WRONLY | O_CREAT | O_EXCL | PG_BINARY,
pg_file_create_mode)) < 0 )
pg_fatal("could not open file \" %s\": %m" , mwriter->pathname);
if (mwriter->buf.len > 0 )
{
ssize_t wb;
wb = write(mwriter->fd, mwriter->buf.data, mwriter->buf.len);
if (wb != mwriter->buf.len)
{
if (wb < 0 )
pg_fatal("could not write file \" %s\": %m" , mwriter->pathname);
else
pg_fatal("could not write file \" %s\": wrote %d of %d" ,
mwriter->pathname, (int ) wb, mwriter->buf.len);
}
if (mwriter->still_checksumming &&
pg_checksum_update(&mwriter->manifest_ctx,
(uint8 *) mwriter->buf.data,
mwriter->buf.len) < 0 )
pg_fatal("could not update checksum of file \" %s\"" ,
mwriter->pathname);
resetStringInfo(&mwriter->buf);
}
}
/*
* Encode bytes using two hexadecimal digits for each one .
*/
static size_t
hex_encode(const uint8 *src, size_t len, char *dst)
{
const uint8 *end = src + len;
while (src < end)
{
unsigned n1 = (*src >> 4 ) & 0 xF;
unsigned n2 = *src & 0 xF;
*dst++ = n1 < 10 ? '0' + n1 : 'a' + n1 - 10 ;
*dst++ = n2 < 10 ? '0' + n2 : 'a' + n2 - 10 ;
++src;
}
return len * 2 ;
}
Messung V0.5 in Prozent C=91 H=95 G=92
¤ Dauer der Verarbeitung: 0.1 Sekunden
(vorverarbeitet am 2026-08-08)
¤
*© Formatika GbR, Deutschland