From 97bb113b5bf5427449b748c3ee25b647a2c5fef5 Mon Sep 17 00:00:00 2001
From: David Steele <david@pgmasters.net>
Date: Sun, 19 Nov 2023 16:54:36 +0000
Subject: Add label and start/stop time to backup manifest.

Add label passed by the user to pg_basebackup and backup start/stop time to
the backup_manifest file. Currently these fields are purely for informational
purposes.
---
 doc/src/sgml/backup-manifest.sgml        | 51 +++++++++++++++++++++
 src/backend/backup/backup_manifest.c     | 56 +++++++++++++++++++++++-
 src/backend/backup/basebackup.c          |  9 +++-
 src/bin/pg_verifybackup/parse_manifest.c | 40 +++++++++++++++++
 src/include/backup/backup_manifest.h     |  5 ++-
 5 files changed, 157 insertions(+), 4 deletions(-)

diff --git a/doc/src/sgml/backup-manifest.sgml b/doc/src/sgml/backup-manifest.sgml
index 771be1310a..a80b79e587 100644
--- a/doc/src/sgml/backup-manifest.sgml
+++ b/doc/src/sgml/backup-manifest.sgml
@@ -42,6 +42,16 @@
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>Backup-Label</literal></term>
+    <listitem>
+     <para>
+      Backup label specified by the user. This will be set to a default value
+      if no backup label was specified.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>Files</literal></term>
     <listitem>
@@ -67,6 +77,17 @@
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>Time-Range</literal></term>
+    <listitem>
+     <para>
+      The associated value is always an object describing the start/stop time
+      of the backup. The structure of this object is further described in
+      <xref linkend="backup-manifest-time-range" />.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>Manifest-Checksum</literal></term>
     <listitem>
@@ -213,4 +234,34 @@
    for the same timeline.
   </para>
  </sect1>
+
+ <sect1 id="backup-manifest-time-range">
+  <title>Backup Manifest Time Range Object</title>
+
+  <para>
+   The object which describes the time range always has three keys:
+  </para>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>Start-Time</literal></term>
+    <listitem>
+     <para>
+      The start time for the backup.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>End-Time</literal></term>
+    <listitem>
+     <para>
+      The end time for the backup. This is when the backup was stopped in
+      <productname>PostgreSQL</productname> and represents the earliest time
+      that can be used for time-based Point-In-Time Recovery.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </sect1>
 </chapter>
diff --git a/src/backend/backup/backup_manifest.c b/src/backend/backup/backup_manifest.c
index aeed362a9a..9540f05964 100644
--- a/src/backend/backup/backup_manifest.c
+++ b/src/backend/backup/backup_manifest.c
@@ -56,7 +56,8 @@ IsManifestEnabled(backup_manifest_info *manifest)
 void
 InitializeBackupManifest(backup_manifest_info *manifest,
 						 backup_manifest_option want_manifest,
-						 pg_checksum_type manifest_checksum_type)
+						 pg_checksum_type manifest_checksum_type,
+						 const char *label)
 {
 	memset(manifest, 0, sizeof(backup_manifest_info));
 	manifest->checksum_type = manifest_checksum_type;
@@ -78,9 +79,21 @@ InitializeBackupManifest(backup_manifest_info *manifest,
 	manifest->still_checksumming = true;
 
 	if (want_manifest != MANIFEST_OPTION_NO)
+	{
+		StringInfoData buf;
+
 		AppendToManifest(manifest,
 						 "{ \"PostgreSQL-Backup-Manifest-Version\": 1,\n"
-						 "\"Files\": [");
+						 "\"Backup-Label\": ");
+
+		/* JSON encode label and add it to manifest */
+		initStringInfo(&buf);
+		escape_json(&buf, label);
+		AppendStringToManifest(manifest, buf.data);
+		pfree(buf.data);
+
+		AppendToManifest(manifest, ",\n\"Files\": [");
+	}
 }
 
 /*
@@ -308,6 +321,45 @@ AddWALInfoToBackupManifest(backup_manifest_info *manifest, XLogRecPtr startptr,
 	AppendStringToManifest(manifest, "\n],\n");
 }
 
+/*
+ * Add backup start/end time information to the manifest.
+ */
+void
+AddTimeInfoToBackupManifest(backup_manifest_info *manifest, pg_time_t starttime,
+							pg_time_t endtime)
+{
+	StringInfoData buf;
+
+	if (!IsManifestEnabled(manifest))
+		return;
+
+	/* Start the time range. */
+	AppendStringToManifest(manifest, "\"Time-Range\": { ");
+
+	/*
+	 * Convert start/end time to strings and append them 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.
+	 */
+	initStringInfo(&buf);
+
+	appendStringInfoString(&buf, "\"Start-Time\": \"");
+	enlargeStringInfo(&buf, 128);
+	buf.len += pg_strftime(&buf.data[buf.len], 128, "%Y-%m-%d %H:%M:%S %Z",
+						   pg_gmtime(&starttime));
+	appendStringInfoString(&buf, "\", \"End-Time\": \"");
+	enlargeStringInfo(&buf, 128);
+	buf.len += pg_strftime(&buf.data[buf.len], 128, "%Y-%m-%d %H:%M:%S %Z",
+						   pg_gmtime(&endtime));
+	appendStringInfoString(&buf, "\" },\n");
+
+	/* Add to the manifest. */
+	AppendStringToManifest(manifest, buf.data);
+
+	/* Avoid leaking memory. */
+	pfree(buf.data);
+}
+
 /*
  * Finalize the backup manifest, and send it to the client.
  */
diff --git a/src/backend/backup/basebackup.c b/src/backend/backup/basebackup.c
index 35dd79babc..c7b3ba3e6e 100644
--- a/src/backend/backup/basebackup.c
+++ b/src/backend/backup/basebackup.c
@@ -225,6 +225,8 @@ perform_base_backup(basebackup_options *opt, bbsink *sink)
 	bbsink_state state;
 	XLogRecPtr	endptr;
 	TimeLineID	endtli;
+	pg_time_t	starttime;
+	pg_time_t	stoptime;
 	backup_manifest_info manifest;
 	BackupState *backup_state;
 	StringInfo	tablespace_map;
@@ -243,7 +245,7 @@ perform_base_backup(basebackup_options *opt, bbsink *sink)
 	backup_started_in_recovery = RecoveryInProgress();
 
 	InitializeBackupManifest(&manifest, opt->manifest,
-							 opt->manifest_checksum_type);
+							 opt->manifest_checksum_type, opt->label);
 
 	total_checksum_failures = 0;
 
@@ -380,6 +382,10 @@ perform_base_backup(basebackup_options *opt, bbsink *sink)
 		endptr = backup_state->stoppoint;
 		endtli = backup_state->stoptli;
 
+		/* Record start/stop time for manifest */
+		starttime = backup_state->starttime;
+		stoptime = backup_state->stoptime;
+
 		/* Deallocate backup-related variables. */
 		pfree(tablespace_map->data);
 		pfree(tablespace_map);
@@ -629,6 +635,7 @@ perform_base_backup(basebackup_options *opt, bbsink *sink)
 
 	AddWALInfoToBackupManifest(&manifest, state.startptr, state.starttli,
 							   endptr, endtli);
+	AddTimeInfoToBackupManifest(&manifest, starttime, stoptime);
 
 	SendBackupManifest(&manifest, sink);
 
diff --git a/src/bin/pg_verifybackup/parse_manifest.c b/src/bin/pg_verifybackup/parse_manifest.c
index bf0227c668..408af88e58 100644
--- a/src/bin/pg_verifybackup/parse_manifest.c
+++ b/src/bin/pg_verifybackup/parse_manifest.c
@@ -25,6 +25,7 @@ typedef enum
 	JM_EXPECT_TOPLEVEL_END,
 	JM_EXPECT_TOPLEVEL_FIELD,
 	JM_EXPECT_VERSION_VALUE,
+	JM_EXPECT_BACKUP_LABEL_VALUE,
 	JM_EXPECT_FILES_START,
 	JM_EXPECT_FILES_NEXT,
 	JM_EXPECT_THIS_FILE_FIELD,
@@ -33,6 +34,9 @@ typedef enum
 	JM_EXPECT_WAL_RANGES_NEXT,
 	JM_EXPECT_THIS_WAL_RANGE_FIELD,
 	JM_EXPECT_THIS_WAL_RANGE_VALUE,
+	JM_EXPECT_TIME_RANGE_START,
+	JM_EXPECT_THIS_TIME_RANGE_FIELD,
+	JM_EXPECT_THIS_TIME_RANGE_VALUE,
 	JM_EXPECT_MANIFEST_CHECKSUM_VALUE,
 	JM_EXPECT_EOF,
 } JsonManifestSemanticState;
@@ -188,6 +192,9 @@ json_manifest_object_start(void *state)
 			parse->start_lsn = NULL;
 			parse->end_lsn = NULL;
 			break;
+		case JM_EXPECT_TIME_RANGE_START:
+			parse->state = JM_EXPECT_THIS_TIME_RANGE_FIELD;
+			break;
 		default:
 			json_manifest_parse_failure(parse->context,
 										"unexpected object start");
@@ -223,6 +230,9 @@ json_manifest_object_end(void *state)
 			json_manifest_finalize_wal_range(parse);
 			parse->state = JM_EXPECT_WAL_RANGES_NEXT;
 			break;
+		case JM_EXPECT_THIS_TIME_RANGE_FIELD:
+			parse->state = JM_EXPECT_TOPLEVEL_FIELD;
+			break;
 		default:
 			json_manifest_parse_failure(parse->context,
 										"unexpected object end");
@@ -312,6 +322,13 @@ json_manifest_object_field_start(void *state, char *fname, bool isnull)
 				break;
 			}
 
+			/* Is this the backup label? */
+			if (strcmp(fname, "Backup-Label") == 0)
+			{
+				parse->state = JM_EXPECT_BACKUP_LABEL_VALUE;
+				break;
+			}
+
 			/* Is this the list of files? */
 			if (strcmp(fname, "Files") == 0)
 			{
@@ -326,6 +343,13 @@ json_manifest_object_field_start(void *state, char *fname, bool isnull)
 				break;
 			}
 
+			/* Is this the time range? */
+			if (strcmp(fname, "Time-Range") == 0)
+			{
+				parse->state = JM_EXPECT_TIME_RANGE_START;
+				break;
+			}
+
 			/* Is this the manifest checksum? */
 			if (strcmp(fname, "Manifest-Checksum") == 0)
 			{
@@ -372,6 +396,14 @@ json_manifest_object_field_start(void *state, char *fname, bool isnull)
 			parse->state = JM_EXPECT_THIS_WAL_RANGE_VALUE;
 			break;
 
+		case JM_EXPECT_THIS_TIME_RANGE_FIELD:
+			if (strcmp(fname, "Start-Time") != 0 &&
+				strcmp(fname, "End-Time") != 0)
+				json_manifest_parse_failure(parse->context,
+											"unexpected time range field");
+			parse->state = JM_EXPECT_THIS_TIME_RANGE_VALUE;
+			break;
+
 		default:
 			json_manifest_parse_failure(parse->context,
 										"unexpected object field");
@@ -410,6 +442,10 @@ json_manifest_scalar(void *state, char *token, JsonTokenType tokentype)
 			parse->state = JM_EXPECT_TOPLEVEL_FIELD;
 			break;
 
+		case JM_EXPECT_BACKUP_LABEL_VALUE:
+			parse->state = JM_EXPECT_TOPLEVEL_FIELD;
+			break;
+
 		case JM_EXPECT_THIS_FILE_VALUE:
 			switch (parse->file_field)
 			{
@@ -451,6 +487,10 @@ json_manifest_scalar(void *state, char *token, JsonTokenType tokentype)
 			parse->state = JM_EXPECT_THIS_WAL_RANGE_FIELD;
 			break;
 
+		case JM_EXPECT_THIS_TIME_RANGE_VALUE:
+			parse->state = JM_EXPECT_THIS_TIME_RANGE_FIELD;
+			break;
+
 		case JM_EXPECT_MANIFEST_CHECKSUM_VALUE:
 			parse->state = JM_EXPECT_TOPLEVEL_END;
 			parse->manifest_checksum = token;
diff --git a/src/include/backup/backup_manifest.h b/src/include/backup/backup_manifest.h
index bd7067ae42..4ab1291bba 100644
--- a/src/include/backup/backup_manifest.h
+++ b/src/include/backup/backup_manifest.h
@@ -37,7 +37,8 @@ typedef struct backup_manifest_info
 
 extern void InitializeBackupManifest(backup_manifest_info *manifest,
 									 backup_manifest_option want_manifest,
-									 pg_checksum_type manifest_checksum_type);
+									 pg_checksum_type manifest_checksum_type,
+									 const char *label);
 extern void AddFileToBackupManifest(backup_manifest_info *manifest,
 									Oid spcoid,
 									const char *pathname, size_t size,
@@ -47,6 +48,8 @@ extern void AddWALInfoToBackupManifest(backup_manifest_info *manifest,
 									   XLogRecPtr startptr,
 									   TimeLineID starttli, XLogRecPtr endptr,
 									   TimeLineID endtli);
+extern void AddTimeInfoToBackupManifest(backup_manifest_info *manifest,
+										pg_time_t starttime, pg_time_t endtime);
 
 extern void SendBackupManifest(backup_manifest_info *manifest, bbsink *sink);
 extern void FreeBackupManifest(backup_manifest_info *manifest);
-- 
2.34.1

