From bc5c881757ff4dc56c79f81ee27dbdff028f3b46 Mon Sep 17 00:00:00 2001
From: David Christensen <david.christensen@crunchydata.com>
Date: Fri, 5 Jan 2024 15:51:10 -0500
Subject: [PATCH v4 14/22] feature: Add reserved_page_size to initdb/bootstrap

Include a basic initdb test to exercise a few of the constraints.
---
 src/backend/bootstrap/bootstrap.c    | 15 ++++++++--
 src/bin/initdb/initdb.c              | 37 +++++++++++++++++++++--
 src/bin/initdb/meson.build           |  1 +
 src/bin/initdb/t/002_reservedsize.pl | 45 ++++++++++++++++++++++++++++
 4 files changed, 93 insertions(+), 5 deletions(-)
 create mode 100644 src/bin/initdb/t/002_reservedsize.pl

diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 20d9e786d8..5258b160db 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -27,6 +27,7 @@
 #include "catalog/index.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
+#include "common/blocksize.h"
 #include "common/link-canary.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -217,10 +218,18 @@ BootstrapModeMain(int argc, char *argv[], bool check_only)
 	argv++;
 	argc--;
 
-	while ((flag = getopt(argc, argv, "B:c:d:D:Fkr:X:-:")) != -1)
+	while ((flag = getopt(argc, argv, "b:B:c:d:D:Fkr:X:-:")) != -1)
 	{
 		switch (flag)
 		{
+			case 'b':
+				bootstrap_reserved_page_size = strtol(optarg, NULL, 0);
+				if (!IsValidReservedPageSize(bootstrap_reserved_page_size))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid reserved page size: %s; must be multiple of 8 between 0 and 256",
+									optarg)));
+				break;
 			case 'B':
 				SetConfigOption("shared_buffers", optarg, PGC_POSTMASTER, PGC_S_ARGV);
 				break;
@@ -296,6 +305,8 @@ BootstrapModeMain(int argc, char *argv[], bool check_only)
 	if (!SelectConfigFiles(userDoption, progname))
 		proc_exit(1);
 
+	BlockSizeInit(BLCKSZ, bootstrap_reserved_page_size);
+
 	/*
 	 * Validate we have been given a reasonable-looking DataDir and change
 	 * into it
@@ -308,8 +319,6 @@ BootstrapModeMain(int argc, char *argv[], bool check_only)
 	SetProcessingMode(BootstrapProcessing);
 	IgnoreSystemIndexes = true;
 
-	BlockSizeInit(BLCKSZ, bootstrap_reserved_page_size);
-
 	InitializeMaxBackends();
 
 	CreateSharedMemoryAndSemaphores();
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index de58002a5d..229367446c 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -69,6 +69,7 @@
 #include "catalog/pg_class_d.h" /* pgrminclude ignore */
 #include "catalog/pg_collation_d.h"
 #include "catalog/pg_database_d.h"	/* pgrminclude ignore */
+#include "common/blocksize.h"
 #include "common/file_perm.h"
 #include "common/file_utils.h"
 #include "common/logging.h"
@@ -166,6 +167,8 @@ static bool data_checksums = false;
 static char *xlog_dir = NULL;
 static int	wal_segment_size_mb = (DEFAULT_XLOG_SEG_SIZE) / (1024 * 1024);
 static DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
+static char *str_reserved_page_size = NULL;
+static int reserved_page_size = 0;
 
 
 /* internal vars */
@@ -1154,11 +1157,12 @@ test_specific_config_settings(int test_conns, int test_buffs)
 
 	/* Set up the test postmaster invocation */
 	printfPQExpBuffer(&cmd,
-					  "\"%s\" --check %s %s "
+					  "\"%s\" --check %s %s -b %d "
 					  "-c max_connections=%d "
 					  "-c shared_buffers=%d "
 					  "-c dynamic_shared_memory_type=%s",
 					  backend_exec, boot_options, extra_options,
+					  reserved_page_size,
 					  test_conns, test_buffs,
 					  dynamic_shared_memory_type);
 
@@ -1536,6 +1540,9 @@ bootstrap_template1(void)
 
 	printfPQExpBuffer(&cmd, "\"%s\" --boot %s %s", backend_exec, boot_options, extra_options);
 	appendPQExpBuffer(&cmd, " -X %d", wal_segment_size_mb * (1024 * 1024));
+
+	if (reserved_page_size)
+		appendPQExpBuffer(&cmd, " -b %d", reserved_page_size);
 	if (data_checksums)
 		appendPQExpBuffer(&cmd, " -k");
 	if (debug)
@@ -2435,6 +2442,7 @@ usage(const char *progname)
 	printf(_("  -A, --auth=METHOD         default authentication method for local connections\n"));
 	printf(_("      --auth-host=METHOD    default authentication method for local TCP/IP connections\n"));
 	printf(_("      --auth-local=METHOD   default authentication method for local-socket connections\n"));
+	printf(_("  -b, --reserved-size=SIZE  reserved space in disk pages for page features\n"));
 	printf(_(" [-D, --pgdata=]DATADIR     location for this database cluster\n"));
 	printf(_("  -E, --encoding=ENCODING   set default encoding for new databases\n"));
 	printf(_("  -g, --allow-group-access  allow group read/execute on data directory\n"));
@@ -3100,6 +3108,7 @@ main(int argc, char *argv[])
 		{"sync-only", no_argument, NULL, 'S'},
 		{"waldir", required_argument, NULL, 'X'},
 		{"wal-segsize", required_argument, NULL, 12},
+		{"reserved-size", required_argument, NULL, 'b'},
 		{"data-checksums", no_argument, NULL, 'k'},
 		{"allow-group-access", no_argument, NULL, 'g'},
 		{"discard-caches", no_argument, NULL, 14},
@@ -3148,7 +3157,7 @@ main(int argc, char *argv[])
 
 	/* process command-line options */
 
-	while ((c = getopt_long(argc, argv, "A:c:dD:E:gkL:nNsST:U:WX:",
+	while ((c = getopt_long(argc, argv, "A:b:c:dD:E:gkL:nNsST:U:WX:",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -3172,6 +3181,9 @@ main(int argc, char *argv[])
 			case 11:
 				authmethodhost = pg_strdup(optarg);
 				break;
+			case 'b':
+				str_reserved_page_size = pg_strdup(optarg);
+				break;
 			case 'c':
 				{
 					char	   *buf = pg_strdup(optarg);
@@ -3357,6 +3369,27 @@ main(int argc, char *argv[])
 	if (!IsValidWalSegSize(wal_segment_size_mb * 1024 * 1024))
 		pg_fatal("argument of %s must be a power of two between 1 and 1024", "--wal-segsize");
 
+	if (str_reserved_page_size == NULL)
+		reserved_page_size = 0;
+	else
+	{
+		char	   *endptr;
+
+		/* check that the argument is a number */
+		reserved_page_size = strtol(str_reserved_page_size, &endptr, 10);
+
+		/* verify that the  segment size is valid */
+		if (endptr == str_reserved_page_size || *endptr != '\0')
+			pg_fatal("argument of --reserved-size must be a number");
+		/* check for valid block_size; last is bitwise power of two check */
+		if (!IsValidReservedPageSize(reserved_page_size))
+			pg_fatal("argument of --reserved-size must be a multiple of 8 between 0 and 256");
+	}
+
+	BlockSizeInit(BLCKSZ, reserved_page_size);
+	if (reserved_page_size)
+		printf(_("Reserving %u bytes on disk pages for additional features.\n"), reserved_page_size);
+
 	get_restricted_token();
 
 	setup_pgdata();
diff --git a/src/bin/initdb/meson.build b/src/bin/initdb/meson.build
index 7dc5ed6e77..914104f266 100644
--- a/src/bin/initdb/meson.build
+++ b/src/bin/initdb/meson.build
@@ -34,6 +34,7 @@ tests += {
     'env': {'with_icu': icu.found() ? 'yes' : 'no'},
     'tests': [
       't/001_initdb.pl',
+      't/002_reservedsize.pl',
     ],
   },
 }
diff --git a/src/bin/initdb/t/002_reservedsize.pl b/src/bin/initdb/t/002_reservedsize.pl
new file mode 100644
index 0000000000..7cb53aa07f
--- /dev/null
+++ b/src/bin/initdb/t/002_reservedsize.pl
@@ -0,0 +1,45 @@
+
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+
+use strict;
+use warnings;
+use Fcntl ':mode';
+use File::stat qw{lstat};
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# validate expected handling of --reserved-size
+
+# default is 0 reserved size
+my $node1 = PostgreSQL::Test::Cluster->new('node1');
+$node1->init();
+$node1->start;
+
+is($node1->safe_psql('postgres',q{SELECT current_setting('reserved_page_size')}),
+   0, "reserved_size defaults to 0");
+
+$node1->stop;
+
+# reserve 8 bytes
+my $node2 = PostgreSQL::Test::Cluster->new('node2');
+$node2->init(extra => ['--reserved-size=8'] );
+$node2->start;
+
+is($node2->safe_psql('postgres',q{SELECT current_setting('reserved_page_size')}),
+   8, "reserved_page_size passes through correctly");
+
+$node2->stop;
+
+# reserve non-multiple of 8 bytes : initdb error
+command_fails_like([ 'initdb', '--reserved-size=18' ],
+	qr/\Qinitdb: error: argument of --reserved-size must be a multiple of 8 between 0 and 256\E/,
+	'--reserved-size requires multiple of 8');
+
+# reserve too much space : initdb error
+command_fails_like([ 'initdb', '--reserved-size=1024' ],
+	qr/\Qinitdb: error: argument of --reserved-size must be a multiple of 8 between 0 and 256\E/,
+	'--reserved-size must be less than 256');
+
+done_testing();
-- 
2.40.1

