v9-0004-Modify-server-code-to-generate-backup-manifest-in.patch
application/octet-stream
Filename: v9-0004-Modify-server-code-to-generate-backup-manifest-in.patch
Type: application/octet-stream
Part: 1
Message:
Re: backup manifests
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v9-0004
Subject: Modify server code to generate backup manifest in JSON format.
| File | + | − |
|---|---|---|
| src/backend/replication/basebackup.c | 75 | 76 |
From cceeb92251b8cd3612740c545a33dae6afefbf41 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Thu, 27 Feb 2020 17:46:43 +0530
Subject: [PATCH v9 4/5] Modify server code to generate backup manifest in JSON
format.
This will eventually get merged into the previous patch to add
backup manifest functionality to the server, but I'm keeping
it separate for now because I don't have the validator working
with this format yet.
---
src/backend/replication/basebackup.c | 151 +++++++++++++--------------
1 file changed, 75 insertions(+), 76 deletions(-)
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index 27fa67f321..77f15f6233 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -41,6 +41,7 @@
#include "storage/ipc.h"
#include "storage/reinit.h"
#include "utils/builtins.h"
+#include "utils/json.h"
#include "utils/ps_status.h"
#include "utils/relcache.h"
#include "utils/resowner.h"
@@ -64,6 +65,7 @@ struct manifest_info
pg_checksum_type checksum_type;
pg_sha256_ctx manifest_ctx;
uint64 manifest_size;
+ bool first_file;
bool still_checksumming;
};
@@ -88,7 +90,6 @@ static void AddFileToManifest(manifest_info *manifest, const char *spcoid,
const char *pathname, size_t size, time_t mtime,
pg_checksum_context *checksum_ctx);
static void SendBackupManifest(manifest_info *manifest);
-static char *escape_field_for_manifest(const char *s);
static void perform_base_backup(basebackup_options *opt);
static void parse_basebackup_options(List *options, basebackup_options *opt);
static void SendXlogRecPtrResult(XLogRecPtr ptr, TimeLineID tli);
@@ -1004,9 +1005,12 @@ InitializeManifest(manifest_info *manifest, pg_checksum_type checksum_type)
manifest->checksum_type = checksum_type;
pg_sha256_init(&manifest->manifest_ctx);
manifest->manifest_size = UINT64CONST(0);
+ manifest->first_file = true;
manifest->still_checksumming = true;
- AppendToManifest(manifest, "PostgreSQL-Backup-Manifest-Version\t1\n");
+ AppendToManifest(manifest,
+ "{ \"PostgreSQL-Backup-Manifest-Version\": 1,\n"
+ "\"Files\": [");
}
/*
@@ -1037,8 +1041,8 @@ AddFileToManifest(manifest_info *manifest, const char *spcoid,
pg_checksum_context *checksum_ctx)
{
char pathbuf[MAXPGPATH];
- char *escaped_pathname;
- static char timebuf[128];
+ int pathlen;
+ StringInfoData buf;
/*
* If this file is part of a tablespace, the pathname passed to this
@@ -1053,40 +1057,81 @@ AddFileToManifest(manifest_info *manifest, const char *spcoid,
pathname = pathbuf;
}
- /* Escape pathname, if necessary. */
- escaped_pathname = escape_field_for_manifest(pathname);
+ /*
+ * Each file's entry need to be separated from any entry that follows
+ * by a comma, but there's no comma before the first one or after the
+ * last one. To make that work, adding a file to the manifest starts
+ * by terminating the most recently added line, with a comma if
+ * appropriate, but does not terminate the line inserted for this file.
+ */
+ initStringInfo(&buf);
+ if (manifest->first_file)
+ {
+ appendStringInfoString(&buf, "\n");
+ manifest->first_file = false;
+ }
+ else
+ appendStringInfoString(&buf, ",\n");
+
+ /*
+ * Write the relative pathname to this file out to the manifest. The
+ * manifest is always stored in UTF-8, so we have to encode paths that
+ * are not valid in that encoding.
+ */
+ pathlen = strlen(pathname);
+ if (pg_verify_mbstr(PG_UTF8, pathname, pathlen, true))
+ {
+ appendStringInfoString(&buf, "{ \"Path\": ");
+ escape_json(&buf, pathname);
+ appendStringInfoString(&buf, ", ");
+ }
+ else
+ {
+ appendStringInfoString(&buf, "{ \"Encoded-Path\": \"");
+ enlargeStringInfo(&buf, 2 * pathlen);
+ buf.len += hex_encode((char *) pathname, pathlen,
+ &buf.data[buf.len]);
+ appendStringInfoString(&buf, "\", ");
+ }
+
+ appendStringInfo(&buf, "\"Size\": %zu, ", size);
/*
- * Convert time to a string. Since it's not clear what time zone to use
- * and since time zone definitions can change, possibly causing confusion,
- * use GMT always.
+ * Convert last modification time to a string and append it to the
+ * manifest. Since it's not clear what time zone to use and since time
+ * zone definitions can change, possibly causing confusion, use GMT always.
*/
- pg_strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S %Z",
- pg_gmtime(&mtime));
+ appendStringInfoString(&buf, "\"Last-Modified\": \"");
+ enlargeStringInfo(&buf, 128);
+ buf.len += pg_strftime(&buf.data[buf.len], 128, "%Y-%m-%d %H:%M:%S %Z",
+ pg_gmtime(&mtime));
+ appendStringInfoString(&buf, "\"");
- /* Add to manifest. */
- AppendToManifest(manifest, "File\t%s\t%zu\t%s\t%s",
- escaped_pathname == NULL ? pathname : escaped_pathname,
- size, timebuf, pg_checksum_type_name(checksum_ctx->type));
+ /* Add checksum information. */
if (checksum_ctx->type != CHECKSUM_TYPE_NONE)
{
uint8 checksumbuf[PG_CHECKSUM_MAX_LENGTH];
- char checksumstringbuf[PG_CHECKSUM_MAX_LENGTH * 2 + 1];
int checksumlen;
- int checksumstringlen;
- AppendStringToManifest(manifest, ":");
checksumlen = pg_checksum_final(checksum_ctx, checksumbuf);
- checksumstringlen = hex_encode((char *) checksumbuf, checksumlen,
- checksumstringbuf);
- checksumstringbuf[checksumstringlen] = '\0';
- AppendStringToManifest(manifest, checksumstringbuf);
+
+ appendStringInfo(&buf,
+ ", \"Checksum-Algorithm\": \"%s\", \"Checksum\": \"",
+ pg_checksum_type_name(checksum_ctx->type));
+ enlargeStringInfo(&buf, 2 * checksumlen);
+ buf.len += hex_encode((char *) checksumbuf, checksumlen,
+ &buf.data[buf.len]);
+ appendStringInfoString(&buf, "\"");
}
- AppendStringToManifest(manifest, "\n");
+
+ /* Close out the object. */
+ appendStringInfoString(&buf, " }");
+
+ /* OK, add it to the manifest. */
+ AppendStringToManifest(manifest, buf.data);
/* Avoid leaking memory. */
- if (escaped_pathname != NULL)
- pfree(escaped_pathname);
+ pfree(buf.data);
}
/*
@@ -1100,6 +1145,9 @@ SendBackupManifest(manifest_info *manifest)
char checksumstringbuf[PG_SHA256_DIGEST_STRING_LENGTH];
size_t manifest_bytes_done = 0;
+ /* Terminate the list of files. */
+ AppendStringToManifest(manifest, "],\n");
+
/*
* Append manifest checksum, so that the problems with the manifest itself
* can be detected.
@@ -1113,11 +1161,11 @@ SendBackupManifest(manifest_info *manifest)
*/
manifest->still_checksumming = false;
pg_sha256_final(&manifest->manifest_ctx, checksumbuf);
- AppendStringToManifest(manifest, "Manifest-Checksum\t");
+ AppendStringToManifest(manifest, "\"Manifest-Checksum\": \"");
hex_encode((char *) checksumbuf, sizeof checksumbuf, checksumstringbuf);
checksumstringbuf[PG_SHA256_DIGEST_STRING_LENGTH - 1] = '\0';
AppendStringToManifest(manifest, checksumstringbuf);
- AppendStringToManifest(manifest, "\n");
+ AppendStringToManifest(manifest, "\"}\n");
/*
* We've written all the data to the manifest file. Rewind the file so
@@ -1165,55 +1213,6 @@ SendBackupManifest(manifest_info *manifest)
BufFileClose(manifest->buffile);
}
-/*
- * Escape a field for inclusion in a manifest.
- *
- * We use the following escaping rule: If a field contains \t, \r, or \n,
- * the field must be surrounded by double-quotes, and any internal double
- * quotes must be doubled. Otherwise, no escaping is required.
- *
- * The return value is a new palloc'd string with escaping added, or NULL
- * if no escaping is required.
- */
-static char *
-escape_field_for_manifest(const char *s)
-{
- bool escaping_required = false;
- int escaped_length = 2;
- const char *t;
- char *result;
- char *r;
-
- for (t = s; *t != '\0'; ++t)
- {
- if (*t == '\t' || *t == '\r' || *t == '\n')
- escaping_required = true;
- if (*t == '"')
- ++escaped_length;
- ++escaped_length;
- }
-
- if (!escaping_required)
- return NULL;
-
- result = palloc(escaped_length + 1);
- result[0] = '"';
- result[escaped_length - 1] = '"';
- result[escaped_length] = '\0';
- r = result + 1;
-
- for (t = s; *t != '\0'; ++t)
- {
- *(r++) = *t;
- if (*t == '"')
- *(r++) = *t;
- }
-
- Assert(r == &result[escaped_length - 1]);
-
- return result;
-}
-
/*
* Send a single resultset containing just a single
* XLogRecPtr record (in text format)
--
2.17.2 (Apple Git-113)