PostgreSQL Source Code git master
write_manifest.c
Go to the documentation of this file.
1/*-------------------------------------------------------------------------
2 *
3 * Write a new backup manifest.
4 *
5 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
6 * Portions Copyright (c) 1994, Regents of the University of California
7 *
8 * src/bin/pg_combinebackup/write_manifest.c
9 *
10 *-------------------------------------------------------------------------
11 */
12
13#include "postgres_fe.h"
14
15#include <fcntl.h>
16#include <time.h>
17#include <unistd.h>
18
20#include "common/file_perm.h"
21#include "common/logging.h"
22#include "lib/stringinfo.h"
23#include "load_manifest.h"
24#include "mb/pg_wchar.h"
25#include "write_manifest.h"
26
28{
30 int fd;
35};
36
37static void escape_json(StringInfo buf, const char *str);
38static void flush_manifest(manifest_writer *mwriter);
39static size_t hex_encode(const uint8 *src, size_t len, char *dst);
40
41/*
42 * Create a new backup manifest writer.
43 *
44 * The backup manifest will be written into a file named backup_manifest
45 * in the specified directory.
46 */
48create_manifest_writer(char *directory, uint64 system_identifier)
49{
50 manifest_writer *mwriter = pg_malloc(sizeof(manifest_writer));
51
52 snprintf(mwriter->pathname, MAXPGPATH, "%s/backup_manifest", directory);
53 mwriter->fd = -1;
54 initStringInfo(&mwriter->buf);
55 mwriter->first_file = true;
56 mwriter->still_checksumming = true;
58
59 appendStringInfo(&mwriter->buf,
60 "{ \"PostgreSQL-Backup-Manifest-Version\": 2,\n"
61 "\"System-Identifier\": " UINT64_FORMAT ",\n"
62 "\"Files\": [",
63 system_identifier);
64
65 return mwriter;
66}
67
68/*
69 * Add an entry for a file to a backup manifest.
70 *
71 * This is very similar to the backend's AddFileToBackupManifest, but
72 * various adjustments are required due to frontend/backend differences
73 * and other details.
74 */
75void
76add_file_to_manifest(manifest_writer *mwriter, const char *manifest_path,
77 uint64 size, time_t mtime,
78 pg_checksum_type checksum_type,
79 int checksum_length,
80 uint8 *checksum_payload)
81{
82 int pathlen = strlen(manifest_path);
83
84 if (mwriter->first_file)
85 {
86 appendStringInfoChar(&mwriter->buf, '\n');
87 mwriter->first_file = false;
88 }
89 else
90 appendStringInfoString(&mwriter->buf, ",\n");
91
92 if (pg_encoding_verifymbstr(PG_UTF8, manifest_path, pathlen) == pathlen)
93 {
94 appendStringInfoString(&mwriter->buf, "{ \"Path\": ");
95 escape_json(&mwriter->buf, manifest_path);
96 appendStringInfoString(&mwriter->buf, ", ");
97 }
98 else
99 {
100 appendStringInfoString(&mwriter->buf, "{ \"Encoded-Path\": \"");
101 enlargeStringInfo(&mwriter->buf, 2 * pathlen);
102 mwriter->buf.len += hex_encode((const uint8 *) manifest_path, pathlen,
103 &mwriter->buf.data[mwriter->buf.len]);
104 appendStringInfoString(&mwriter->buf, "\", ");
105 }
106
107 appendStringInfo(&mwriter->buf, "\"Size\": %" PRIu64 ", ", size);
108
109 appendStringInfoString(&mwriter->buf, "\"Last-Modified\": \"");
110 enlargeStringInfo(&mwriter->buf, 128);
111 mwriter->buf.len += strftime(&mwriter->buf.data[mwriter->buf.len], 128,
112 "%Y-%m-%d %H:%M:%S %Z",
113 gmtime(&mtime));
114 appendStringInfoChar(&mwriter->buf, '"');
115
116 if (mwriter->buf.len > 128 * 1024)
117 flush_manifest(mwriter);
118
119 if (checksum_length > 0)
120 {
121 appendStringInfo(&mwriter->buf,
122 ", \"Checksum-Algorithm\": \"%s\", \"Checksum\": \"",
123 pg_checksum_type_name(checksum_type));
124
125 enlargeStringInfo(&mwriter->buf, 2 * checksum_length);
126 mwriter->buf.len += hex_encode(checksum_payload, checksum_length,
127 &mwriter->buf.data[mwriter->buf.len]);
128
129 appendStringInfoChar(&mwriter->buf, '"');
130 }
131
132 appendStringInfoString(&mwriter->buf, " }");
133
134 if (mwriter->buf.len > 128 * 1024)
135 flush_manifest(mwriter);
136}
137
138/*
139 * Finalize the backup_manifest.
140 */
141void
143 manifest_wal_range *first_wal_range)
144{
145 uint8 checksumbuf[PG_SHA256_DIGEST_LENGTH];
146 int len;
147 manifest_wal_range *wal_range;
148
149 /* Terminate the list of files. */
150 appendStringInfoString(&mwriter->buf, "\n],\n");
151
152 /* Start a list of LSN ranges. */
153 appendStringInfoString(&mwriter->buf, "\"WAL-Ranges\": [\n");
154
155 for (wal_range = first_wal_range; wal_range != NULL;
156 wal_range = wal_range->next)
157 appendStringInfo(&mwriter->buf,
158 "%s{ \"Timeline\": %u, \"Start-LSN\": \"%X/%X\", \"End-LSN\": \"%X/%X\" }",
159 wal_range == first_wal_range ? "" : ",\n",
160 wal_range->tli,
161 LSN_FORMAT_ARGS(wal_range->start_lsn),
162 LSN_FORMAT_ARGS(wal_range->end_lsn));
163
164 /* Terminate the list of WAL ranges. */
165 appendStringInfoString(&mwriter->buf, "\n],\n");
166
167 /* Flush accumulated data and update checksum calculation. */
168 flush_manifest(mwriter);
169
170 /* Checksum only includes data up to this point. */
171 mwriter->still_checksumming = false;
172
173 /* Compute and insert manifest checksum. */
174 appendStringInfoString(&mwriter->buf, "\"Manifest-Checksum\": \"");
176 len = pg_checksum_final(&mwriter->manifest_ctx, checksumbuf);
178 mwriter->buf.len +=
179 hex_encode(checksumbuf, len, &mwriter->buf.data[mwriter->buf.len]);
180 appendStringInfoString(&mwriter->buf, "\"}\n");
181
182 /* Flush the last manifest checksum itself. */
183 flush_manifest(mwriter);
184
185 /* Close the file. */
186 if (close(mwriter->fd) != 0)
187 pg_fatal("could not close file \"%s\": %m", mwriter->pathname);
188 mwriter->fd = -1;
189}
190
191/*
192 * Produce a JSON string literal, properly escaping characters in the text.
193 */
194static void
196{
197 const char *p;
198
200 for (p = str; *p; p++)
201 {
202 switch (*p)
203 {
204 case '\b':
206 break;
207 case '\f':
209 break;
210 case '\n':
212 break;
213 case '\r':
215 break;
216 case '\t':
218 break;
219 case '"':
221 break;
222 case '\\':
224 break;
225 default:
226 if ((unsigned char) *p < ' ')
227 appendStringInfo(buf, "\\u%04x", (int) *p);
228 else
230 break;
231 }
232 }
234}
235
236/*
237 * Flush whatever portion of the backup manifest we have generated and
238 * buffered in memory out to a file on disk.
239 *
240 * The first call to this function will create the file. After that, we
241 * keep it open and just append more data.
242 */
243static void
245{
246 if (mwriter->fd == -1 &&
247 (mwriter->fd = open(mwriter->pathname,
248 O_WRONLY | O_CREAT | O_EXCL | PG_BINARY,
250 pg_fatal("could not open file \"%s\": %m", mwriter->pathname);
251
252 if (mwriter->buf.len > 0)
253 {
254 ssize_t wb;
255
256 wb = write(mwriter->fd, mwriter->buf.data, mwriter->buf.len);
257 if (wb != mwriter->buf.len)
258 {
259 if (wb < 0)
260 pg_fatal("could not write file \"%s\": %m", mwriter->pathname);
261 else
262 pg_fatal("could not write file \"%s\": wrote %d of %d",
263 mwriter->pathname, (int) wb, mwriter->buf.len);
264 }
265
266 if (mwriter->still_checksumming &&
268 (uint8 *) mwriter->buf.data,
269 mwriter->buf.len) < 0)
270 pg_fatal("could not update checksum of file \"%s\"",
271 mwriter->pathname);
272 resetStringInfo(&mwriter->buf);
273 }
274}
275
276/*
277 * Encode bytes using two hexadecimal digits for each one.
278 */
279static size_t
280hex_encode(const uint8 *src, size_t len, char *dst)
281{
282 const uint8 *end = src + len;
283
284 while (src < end)
285 {
286 unsigned n1 = (*src >> 4) & 0xF;
287 unsigned n2 = *src & 0xF;
288
289 *dst++ = n1 < 10 ? '0' + n1 : 'a' + n1 - 10;
290 *dst++ = n2 < 10 ? '0' + n2 : 'a' + n2 - 10;
291 ++src;
292 }
293
294 return len * 2;
295}
uint8_t uint8
Definition: c.h:500
#define PG_BINARY
Definition: c.h:1244
#define UINT64_FORMAT
Definition: c.h:521
uint64_t uint64
Definition: c.h:503
int pg_checksum_final(pg_checksum_context *context, uint8 *output)
char * pg_checksum_type_name(pg_checksum_type type)
int pg_checksum_update(pg_checksum_context *context, const uint8 *input, size_t len)
int pg_checksum_init(pg_checksum_context *context, pg_checksum_type type)
pg_checksum_type
@ CHECKSUM_TYPE_SHA256
void * pg_malloc(size_t size)
Definition: fe_memutils.c:47
int pg_file_create_mode
Definition: file_perm.c:19
Assert(PointerIsAligned(start, uint64))
const char * str
#define close(a)
Definition: win32.h:12
#define write(a, b, c)
Definition: win32.h:14
#define pg_fatal(...)
#define MAXPGPATH
const void size_t len
static char * buf
Definition: pg_test_fsync.c:72
@ PG_UTF8
Definition: pg_wchar.h:232
#define snprintf
Definition: port.h:239
#define PG_SHA256_DIGEST_LENGTH
Definition: sha2.h:23
#define PG_SHA256_DIGEST_STRING_LENGTH
Definition: sha2.h:24
void resetStringInfo(StringInfo str)
Definition: stringinfo.c:126
void appendStringInfo(StringInfo str, const char *fmt,...)
Definition: stringinfo.c:145
void enlargeStringInfo(StringInfo str, int needed)
Definition: stringinfo.c:337
void appendStringInfoString(StringInfo str, const char *s)
Definition: stringinfo.c:230
void appendStringInfoChar(StringInfo str, char ch)
Definition: stringinfo.c:242
void initStringInfo(StringInfo str)
Definition: stringinfo.c:97
#define appendStringInfoCharMacro(str, ch)
Definition: stringinfo.h:231
XLogRecPtr end_lsn
Definition: load_manifest.h:48
struct manifest_wal_range * next
Definition: load_manifest.h:49
XLogRecPtr start_lsn
Definition: load_manifest.h:47
char pathname[MAXPGPATH]
StringInfoData buf
pg_checksum_context manifest_ctx
int pg_encoding_verifymbstr(int encoding, const char *mbstr, int len)
Definition: wchar.c:2202
static void flush_manifest(manifest_writer *mwriter)
manifest_writer * create_manifest_writer(char *directory, uint64 system_identifier)
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)
static size_t hex_encode(const uint8 *src, size_t len, char *dst)
void finalize_manifest(manifest_writer *mwriter, manifest_wal_range *first_wal_range)
static void escape_json(StringInfo buf, const char *str)
#define LSN_FORMAT_ARGS(lsn)
Definition: xlogdefs.h:43
static const char * directory
Definition: zic.c:634