From 44f8747a75197b3842c41f77503fa8389ba8db3e Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Mon, 17 Jun 2024 21:20:13 -0500
Subject: [PATCH 1/1] allow --single, etc. in any order

---
 src/backend/bootstrap/bootstrap.c |  12 ++--
 src/backend/main/main.c           | 101 ++++++++++++++++++++----------
 src/backend/tcop/postgres.c       |  15 ++---
 3 files changed, 77 insertions(+), 51 deletions(-)

diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 986f6f1d9c..53011b4300 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -210,13 +210,6 @@ BootstrapModeMain(int argc, char *argv[], bool check_only)
 	/* Set defaults, to be overridden by explicit options below */
 	InitializeGUCOptions();
 
-	/* an initial --boot or --check should be present */
-	Assert(argc > 1
-		   && (strcmp(argv[1], "--boot") == 0
-			   || strcmp(argv[1], "--check") == 0));
-	argv++;
-	argc--;
-
 	while ((flag = getopt(argc, argv, "B:c:d:D:Fkr:X:-:")) != -1)
 	{
 		switch (flag)
@@ -230,6 +223,11 @@ BootstrapModeMain(int argc, char *argv[], bool check_only)
 					char	   *name,
 							   *value;
 
+					/* --boot and --check were already processed in main() */
+					if (strcmp(optarg, "boot") == 0 ||
+						strcmp(optarg, "check") == 0)
+						break;
+
 					ParseLongOption(optarg, &name, &value);
 					if (!value)
 					{
diff --git a/src/backend/main/main.c b/src/backend/main/main.c
index bfd0c5ed65..b893a9f298 100644
--- a/src/backend/main/main.c
+++ b/src/backend/main/main.c
@@ -57,7 +57,17 @@ static void check_root(const char *progname);
 int
 main(int argc, char *argv[])
 {
-	bool		do_check_root = true;
+	bool		check = false;
+	bool		boot = false;
+#ifdef EXEC_BACKEND
+	bool		forkchild = false;
+#endif
+	bool		describe_config = false;
+	bool		single = false;
+	bool		show_help = false;
+	bool		version = false;
+	bool		check_guc = false;
+	int			modes_set = 0;
 
 	reached_main = true;
 
@@ -136,61 +146,86 @@ main(int argc, char *argv[])
 	 */
 	unsetenv("LC_ALL");
 
+	for (int i = 1; i < argc; i++)
+	{
+		modes_set++;
+
+		if (strcmp(argv[i], "--check") == 0)
+			check = true;
+		else if (strcmp(argv[i], "--boot") == 0)
+			boot = true;
+#ifdef EXEC_BACKEND
+		else if (strncmp(argv[i], "--forkchild", 11) == 0)
+			forkchild = true;
+#endif
+		else if (strcmp(argv[i], "--describe-config") == 0)
+			describe_config = true;
+		else if (strcmp(argv[i], "--single") == 0)
+			single = true;
+		else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-?") == 0)
+			show_help = true;
+		else if (strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-V") == 0)
+			version = true;
+		else if (strcmp(argv[i], "-C") == 0)
+			check_guc = true;
+		else
+			modes_set--;
+	}
+
+	if (modes_set > 1)
+	{
+		/* hack to avoid assertion failure in proc_exit() */
+		MyProcPid = getpid();
+
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("multiple server modes set"),
+				 errdetail("Only one of %s may be set.",
+#ifdef EXEC_BACKEND
+						   "--check, --boot, --forkchild, --describe-config, --single, --help/-?, --version/-V, -C")));
+#else
+						   "--check, --boot, --describe-config, --single, --help/-?, --version/-V, -C")));
+#endif
+	}
+
 	/*
 	 * Catch standard options before doing much else, in particular before we
 	 * insist on not being root.
 	 */
-	if (argc > 1)
+	if (show_help)
 	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			help(progname);
-			exit(0);
-		}
-		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
-		{
-			fputs(PG_BACKEND_VERSIONSTR, stdout);
-			exit(0);
-		}
+		help(progname);
+		exit(0);
+	}
 
-		/*
-		 * In addition to the above, we allow "--describe-config" and "-C var"
-		 * to be called by root.  This is reasonably safe since these are
-		 * read-only activities.  The -C case is important because pg_ctl may
-		 * try to invoke it while still holding administrator privileges on
-		 * Windows.  Note that while -C can normally be in any argv position,
-		 * if you want to bypass the root check you must put it first.  This
-		 * reduces the risk that we might misinterpret some other mode's -C
-		 * switch as being the postmaster/postgres one.
-		 */
-		if (strcmp(argv[1], "--describe-config") == 0)
-			do_check_root = false;
-		else if (argc > 2 && strcmp(argv[1], "-C") == 0)
-			do_check_root = false;
+	if (version)
+	{
+		fputs(PG_BACKEND_VERSIONSTR, stdout);
+		exit(0);
 	}
 
 	/*
 	 * Make sure we are not running as root, unless it's safe for the selected
 	 * option.
 	 */
-	if (do_check_root)
+	if (!describe_config && !check_guc)
 		check_root(progname);
 
 	/*
-	 * Dispatch to one of various subprograms depending on first argument.
+	 * Dispatch to one of various subprograms.
 	 */
 
-	if (argc > 1 && strcmp(argv[1], "--check") == 0)
+	if (check)
 		BootstrapModeMain(argc, argv, true);
-	else if (argc > 1 && strcmp(argv[1], "--boot") == 0)
+	else if (boot)
 		BootstrapModeMain(argc, argv, false);
 #ifdef EXEC_BACKEND
-	else if (argc > 1 && strncmp(argv[1], "--fork", 6) == 0)
+	else if (forkchild)
 		SubPostmasterMain(argc, argv);
 #endif
-	else if (argc > 1 && strcmp(argv[1], "--describe-config") == 0)
+	else if (describe_config)
 		GucInfoMain();
-	else if (argc > 1 && strcmp(argv[1], "--single") == 0)
+	else if (single)
 		PostgresSingleUserMain(argc, argv,
 							   strdup(get_user_name_or_exit(progname)));
 	else
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 45a3794b8e..74f96ce479 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3796,20 +3796,9 @@ process_postgres_switches(int argc, char *argv[], GucContext ctx,
 	int			flag;
 
 	if (secure)
-	{
 		gucsource = PGC_S_ARGV; /* switches came from command line */
-
-		/* Ignore the initial --single argument, if present */
-		if (argc > 1 && strcmp(argv[1], "--single") == 0)
-		{
-			argv++;
-			argc--;
-		}
-	}
 	else
-	{
 		gucsource = PGC_S_CLIENT;	/* switches came from client */
-	}
 
 #ifdef HAVE_INT_OPTERR
 
@@ -3850,6 +3839,10 @@ process_postgres_switches(int argc, char *argv[], GucContext ctx,
 					char	   *name,
 							   *value;
 
+					/* --single was already processed in main() */
+					if (strcmp(optarg, "single") == 0)
+						break;
+
 					ParseLongOption(optarg, &name, &value);
 					if (!value)
 					{
-- 
2.39.3 (Apple Git-146)

