From e569750c4ce6c6f13f601d2badf54d8df1b83fa5 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sat, 28 Nov 2020 16:59:30 -0600
Subject: [PATCH 4/4] Allow option file to include options files

FIXME: dopt is global and doesn't need to be passed around
---
 src/bin/pg_dump/pg_dump.c | 183 ++++++++++++++++++--------------------
 1 file changed, 86 insertions(+), 97 deletions(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 43e3a022d5..be6ba5d428 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -140,7 +140,6 @@ static char *use_role = NULL;
 static long rowsPerInsert;
 static int numWorkers = 1;
 static int compressLevel = -1;
-static char *optsfilename = NULL;
 
 /*
  * The default number of rows per INSERT when
@@ -308,13 +307,86 @@ static char *get_synchronized_snapshot(Archive *fout);
 static void setupDumpWorker(Archive *AHX);
 static TableInfo *getRootTableInfo(TableInfo *tbinfo);
 static void read_options_from_file(char *filename,
-								   DumpOptions *dopt,
 								   const char *optstring,
 								   const struct option *longopts,
 								   const char *progname);
 
 #define OPTIONS_FILE_OPT_NUMBER			12
 
+static DumpOptions dopt;
+static struct option long_options[] = {
+	{"data-only", no_argument, NULL, 'a'},
+	{"blobs", no_argument, NULL, 'b'},
+	{"no-blobs", no_argument, NULL, 'B'},
+	{"clean", no_argument, NULL, 'c'},
+	{"create", no_argument, NULL, 'C'},
+	{"dbname", required_argument, NULL, 'd'},
+	{"file", required_argument, NULL, 'f'},
+	{"format", required_argument, NULL, 'F'},
+	{"host", required_argument, NULL, 'h'},
+	{"jobs", 1, NULL, 'j'},
+	{"no-reconnect", no_argument, NULL, 'R'},
+	{"no-owner", no_argument, NULL, 'O'},
+	{"port", required_argument, NULL, 'p'},
+	{"schema", required_argument, NULL, 'n'},
+	{"exclude-schema", required_argument, NULL, 'N'},
+	{"schema-only", no_argument, NULL, 's'},
+	{"superuser", required_argument, NULL, 'S'},
+	{"table", required_argument, NULL, 't'},
+	{"exclude-table", required_argument, NULL, 'T'},
+	{"no-password", no_argument, NULL, 'w'},
+	{"password", no_argument, NULL, 'W'},
+	{"username", required_argument, NULL, 'U'},
+	{"verbose", no_argument, NULL, 'v'},
+	{"no-privileges", no_argument, NULL, 'x'},
+	{"no-acl", no_argument, NULL, 'x'},
+	{"compress", required_argument, NULL, 'Z'},
+	{"encoding", required_argument, NULL, 'E'},
+	{"help", no_argument, NULL, '?'},
+	{"version", no_argument, NULL, 'V'},
+
+	/*
+	 * the following options don't have an equivalent short option letter
+	 */
+	{"attribute-inserts", no_argument, &dopt.column_inserts, 1},
+	{"binary-upgrade", no_argument, &dopt.binary_upgrade, 1},
+	{"column-inserts", no_argument, &dopt.column_inserts, 1},
+	{"disable-dollar-quoting", no_argument, &dopt.disable_dollar_quoting, 1},
+	{"disable-triggers", no_argument, &dopt.disable_triggers, 1},
+	{"enable-row-security", no_argument, &dopt.enable_row_security, 1},
+	{"exclude-table-data", required_argument, NULL, 4},
+	{"extra-float-digits", required_argument, NULL, 8},
+	{"if-exists", no_argument, &dopt.if_exists, 1},
+	{"inserts", no_argument, NULL, 9},
+	{"lock-wait-timeout", required_argument, NULL, 2},
+	{"no-tablespaces", no_argument, &dopt.outputNoTablespaces, 1},
+	{"quote-all-identifiers", no_argument, &quote_all_identifiers, 1},
+	{"load-via-partition-root", no_argument, &dopt.load_via_partition_root, 1},
+	{"role", required_argument, NULL, 3},
+	{"section", required_argument, NULL, 5},
+	{"serializable-deferrable", no_argument, &dopt.serializable_deferrable, 1},
+	{"snapshot", required_argument, NULL, 6},
+	{"strict-names", no_argument, &strict_names, 1},
+	{"use-set-session-authorization", no_argument, &dopt.use_setsessauth, 1},
+	{"no-comments", no_argument, &dopt.no_comments, 1},
+	{"no-publications", no_argument, &dopt.no_publications, 1},
+	{"no-security-labels", no_argument, &dopt.no_security_labels, 1},
+	{"no-synchronized-snapshots", no_argument, &dopt.no_synchronized_snapshots, 1},
+	{"no-unlogged-table-data", no_argument, &dopt.no_unlogged_table_data, 1},
+	{"no-subscriptions", no_argument, &dopt.no_subscriptions, 1},
+	{"no-sync", no_argument, NULL, 7},
+	{"on-conflict-do-nothing", no_argument, &dopt.do_nothing, 1},
+	{"options-file", required_argument, NULL, OPTIONS_FILE_OPT_NUMBER},
+	{"rows-per-insert", required_argument, NULL, 10},
+	{"include-foreign-data", required_argument, NULL, 11},
+	{"index-collation-versions-unknown", no_argument, &dopt.coll_unknown, 1},
+	{"include-foreign-data-file", required_argument, NULL, 17},
+
+	{NULL, 0, NULL, 0}
+};
+
+const char *short_options = "abBcCd:E:f:F:h:j:n:N:Op:RsS:t:T:U:vwWxZ:";
+
 /*
  * It assigns the values of options to related DumpOption fields or to
  * some global values. It is called twice. First, for processing
@@ -328,6 +400,7 @@ process_option(int opt,
 			   const char *progname)
 {
 	char	   *endptr;
+	static int file_depth = 0;
 
 	switch (opt)
 	{
@@ -512,7 +585,16 @@ process_option(int opt,
 			break;
 
 		case OPTIONS_FILE_OPT_NUMBER:			/* filter implementation */
-			optsfilename = pg_strdup(optargstr);
+			if (file_depth++ > 99)
+			{
+				pg_log_error("Don't include so many files including so many files");
+				exit_nicely(1);
+			}
+			read_options_from_file(optargstr,
+					short_options,
+					long_options,
+					progname);
+			file_depth--;
 			break;
 
 		default:
@@ -539,80 +621,6 @@ main(int argc, char **argv)
 	int			plainText = 0;
 	ArchiveFormat archiveFormat = archUnknown;
 	ArchiveMode archiveMode;
-	static DumpOptions dopt;
-
-	static struct option long_options[] = {
-		{"data-only", no_argument, NULL, 'a'},
-		{"blobs", no_argument, NULL, 'b'},
-		{"no-blobs", no_argument, NULL, 'B'},
-		{"clean", no_argument, NULL, 'c'},
-		{"create", no_argument, NULL, 'C'},
-		{"dbname", required_argument, NULL, 'd'},
-		{"file", required_argument, NULL, 'f'},
-		{"format", required_argument, NULL, 'F'},
-		{"host", required_argument, NULL, 'h'},
-		{"jobs", 1, NULL, 'j'},
-		{"no-reconnect", no_argument, NULL, 'R'},
-		{"no-owner", no_argument, NULL, 'O'},
-		{"port", required_argument, NULL, 'p'},
-		{"schema", required_argument, NULL, 'n'},
-		{"exclude-schema", required_argument, NULL, 'N'},
-		{"schema-only", no_argument, NULL, 's'},
-		{"superuser", required_argument, NULL, 'S'},
-		{"table", required_argument, NULL, 't'},
-		{"exclude-table", required_argument, NULL, 'T'},
-		{"no-password", no_argument, NULL, 'w'},
-		{"password", no_argument, NULL, 'W'},
-		{"username", required_argument, NULL, 'U'},
-		{"verbose", no_argument, NULL, 'v'},
-		{"no-privileges", no_argument, NULL, 'x'},
-		{"no-acl", no_argument, NULL, 'x'},
-		{"compress", required_argument, NULL, 'Z'},
-		{"encoding", required_argument, NULL, 'E'},
-		{"help", no_argument, NULL, '?'},
-		{"version", no_argument, NULL, 'V'},
-
-		/*
-		 * the following options don't have an equivalent short option letter
-		 */
-		{"attribute-inserts", no_argument, &dopt.column_inserts, 1},
-		{"binary-upgrade", no_argument, &dopt.binary_upgrade, 1},
-		{"column-inserts", no_argument, &dopt.column_inserts, 1},
-		{"disable-dollar-quoting", no_argument, &dopt.disable_dollar_quoting, 1},
-		{"disable-triggers", no_argument, &dopt.disable_triggers, 1},
-		{"enable-row-security", no_argument, &dopt.enable_row_security, 1},
-		{"exclude-table-data", required_argument, NULL, 4},
-		{"extra-float-digits", required_argument, NULL, 8},
-		{"if-exists", no_argument, &dopt.if_exists, 1},
-		{"inserts", no_argument, NULL, 9},
-		{"lock-wait-timeout", required_argument, NULL, 2},
-		{"no-tablespaces", no_argument, &dopt.outputNoTablespaces, 1},
-		{"quote-all-identifiers", no_argument, &quote_all_identifiers, 1},
-		{"load-via-partition-root", no_argument, &dopt.load_via_partition_root, 1},
-		{"role", required_argument, NULL, 3},
-		{"section", required_argument, NULL, 5},
-		{"serializable-deferrable", no_argument, &dopt.serializable_deferrable, 1},
-		{"snapshot", required_argument, NULL, 6},
-		{"strict-names", no_argument, &strict_names, 1},
-		{"use-set-session-authorization", no_argument, &dopt.use_setsessauth, 1},
-		{"no-comments", no_argument, &dopt.no_comments, 1},
-		{"no-publications", no_argument, &dopt.no_publications, 1},
-		{"no-security-labels", no_argument, &dopt.no_security_labels, 1},
-		{"no-synchronized-snapshots", no_argument, &dopt.no_synchronized_snapshots, 1},
-		{"no-unlogged-table-data", no_argument, &dopt.no_unlogged_table_data, 1},
-		{"no-subscriptions", no_argument, &dopt.no_subscriptions, 1},
-		{"no-sync", no_argument, NULL, 7},
-		{"on-conflict-do-nothing", no_argument, &dopt.do_nothing, 1},
-		{"options-file", required_argument, NULL, OPTIONS_FILE_OPT_NUMBER},
-		{"rows-per-insert", required_argument, NULL, 10},
-		{"include-foreign-data", required_argument, NULL, 11},
-		{"index-collation-versions-unknown", no_argument, &dopt.coll_unknown, 1},
-		{"include-foreign-data-file", required_argument, NULL, 17},
-
-		{NULL, 0, NULL, 0}
-	};
-
-	const char *short_options = "abBcCd:E:f:F:h:j:n:N:Op:RsS:t:T:U:vwWxZ:";
 
 	pg_logging_init(argv[0]);
 	pg_logging_set_level(PG_LOG_WARNING);
@@ -649,13 +657,6 @@ main(int argc, char **argv)
 			exit_nicely(1);
 	}
 
-	if (optsfilename)
-		read_options_from_file(optsfilename,
-							   &dopt,
-							   short_options,
-							   long_options,
-							   progname);
-
 	/*
 	 * Non-option argument specifies database name as long as it wasn't
 	 * already specified with -d / --dbname
@@ -18875,7 +18876,6 @@ is_valid_shortopt(const char *optstring,
  */
 static void
 read_options_from_file(char *filename,
-					   DumpOptions *dopt,
 					   const char *optstring,
 					   const struct option *longopts,
 					   const char *progname)
@@ -18947,17 +18947,6 @@ read_options_from_file(char *filename,
 				while (isspace(*str))
 					str++;
 
-				/*
-				 * Don't allow --options-file inside options file.
-				 * It is simple protection against cycle.
-				 */
-				if (opt == OPTIONS_FILE_OPT_NUMBER)
-					exit_invalid_optfile_format(fp,
-												"option '%.*s' cannot be used in options file at line %d",
-												optname, optnamelen,
-												line.data,
-												lineno);
-
 				if (has_arg)
 				{
 					/* skip optional = */
@@ -19039,7 +19028,7 @@ read_options_from_file(char *filename,
 		}
 
 		if (opt != 0 &&
-			!process_option(opt, optargument.data, dopt, progname))
+			!process_option(opt, optargument.data, &dopt, progname))
 		{
 			fclose(fp);
 			exit_nicely(-1);
-- 
2.17.0

