From fa77fb314ddac3d047ec34809479f506a172c56c Mon Sep 17 00:00:00 2001
From: David Christensen <david@endpoint.com>
Date: Sun, 28 Mar 2010 23:54:09 -0500
Subject: [PATCH] Add -C option to initdb to allow invocation-time GUC appending to postgresql.conf

This is a simple mechanism to allow you to provide explicit overrides
to any GUC at initdb time.  As a basic example, consider the case
where you are programmatically generating multiple db clusters in
order to test various configurations:

  $ for cluster in 1 2 3 4 5 6;
  >   do initdb -D data$cluster -C "port = 1234$cluster" -C 'max_connections = 10' -C shared_buffers=1M;
  > done

A possible future improvement would be to provide some basic
formatting corrections to allow specificications such as -C 'port
1234', -C port=1234, and -C 'port = 1234' to all be ultimately output
as 'port = 1234' in the final output.  This would be consistent with
postmaster's parsing.

The -C flag was chosen to be a mnemonic for "config".
---
 src/bin/initdb/initdb.c |  109 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 108 insertions(+), 1 deletions(-)

diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index f40ad87..4855f99 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -111,6 +111,7 @@ static char infoversion[100];
 static bool caught_signal = false;
 static bool output_failed = false;
 static int	output_errno = 0;
+static char **append_config_buffer;
 
 /* defaults */
 static int	n_connections = 10;
@@ -271,6 +272,27 @@ xstrdup(const char *s)
 	return result;
 }
 
+/* like xstrdup, but appends a newline to the duplicated string*/
+static char *
+xstrdupln(const char *s)
+{
+	char	   *result;
+    int        len = strlen(s);
+
+	result = (char*)malloc(len+2);
+	if (!result)
+	{
+		fprintf(stderr, _("%s: out of memory\n"), progname);
+		exit(1);
+	}
+    memcpy(result,s,len);
+
+    result[len]   = '\n';
+    result[len+1] = 0;
+
+	return result;
+}
+
 /*
  * make a copy of the array of lines, with token replaced by replacement
  * the first time it occurs on each line.
@@ -417,6 +439,79 @@ readfile(const char *path)
 }
 
 /*
+ * return a char** created by appending the source** with the dest**
+ *
+ * adds newlines to the end of any line which are missing one. 
+ */
+static char **
+append_lines(char **src, char **lines)
+{
+    char **buf;
+    int i;
+    int src_lines = 0;
+    int app_lines = 0;
+
+    for (i=0; src[i]; i++)
+        src_lines++;
+    for (i=0; lines[i]; i++)
+        app_lines++;
+
+    buf = (char**) pg_malloc((src_lines + app_lines + 1) * (sizeof(char *)));
+
+    memcpy(buf,src,(sizeof(char *))*src_lines);
+
+    for (i=0; i<app_lines; i++)
+        buf[src_lines+i] = xstrdup(lines[i]);
+    buf[src_lines + app_lines] = 0; // fill in the last slot
+
+    return buf;
+}
+
+/*
+ * append a single line to the char** buffer.  adds a trailing newline if one does not exist.
+ */
+static char **
+append_line(char **src, char *line)
+{
+    char **buf;
+    int i;
+    int src_lines = 0;
+
+    if (!line)
+        return src;
+
+    for (i=0; src[i]; i++)
+        src_lines++;
+
+    /* we assume that anything existing in the buffer already has been
+     * xstrdup'd, and so only xstrdup new lines
+     */
+    buf = (char**) pg_malloc((src_lines + 2) * (sizeof(char *)));
+    memcpy(buf,src,(sizeof(char *))*src_lines);
+
+    buf[src_lines] = xstrdupln(line);
+    buf[src_lines+1] = 0;
+
+    return buf;
+}
+
+/*
+ * append a configuration line to the config buffer.  If not already set, add a standard header as the initial contents.
+ */
+static void
+append_config_line(char *line)
+{
+    if (!append_config_buffer)
+    {
+        append_config_buffer = (char**)pg_malloc(sizeof(char*)*2);
+        append_config_buffer[0] = xstrdupln("\n## initdb -C customizations start here");
+        append_config_buffer[1] = 0;
+    }
+
+    append_config_buffer = append_line(append_config_buffer,line);
+}
+
+/*
  * write an array of lines to a file
  *
  * This is only used to write text files.  Use fopen "w" not PG_BINARY_W
@@ -1163,9 +1258,15 @@ setup_config(void)
 						 "#default_text_search_config = 'pg_catalog.simple'",
 							  repltok);
 
+    if (append_config_buffer)
+    {
+        conflines = append_lines(conflines, append_config_buffer);
+    }
+
 	snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
 
 	writefile(path, conflines);
+
 	chmod(path, 0600);
 
 	free(conflines);
@@ -2396,6 +2497,8 @@ usage(const char *progname)
 	printf(_("  -X, --xlogdir=XLOGDIR     location for the transaction log directory\n"));
 	printf(_("\nLess commonly used options:\n"));
 	printf(_("  -d, --debug               generate lots of debugging output\n"));
+	printf(_("  -C, --append-config=LINE  append the provided line to the postgresql.conf file\n"
+             "                            (can be used multiple times)\n"));
 	printf(_("  -L DIRECTORY              where to find the input files\n"));
 	printf(_("  -n, --noclean             do not clean up after errors\n"));
 	printf(_("  -s, --show                show internal settings\n"));
@@ -2436,6 +2539,7 @@ main(int argc, char *argv[])
 		{"show", no_argument, NULL, 's'},
 		{"noclean", no_argument, NULL, 'n'},
 		{"xlogdir", required_argument, NULL, 'X'},
+		{"append-config", required_argument, NULL, 'C'},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -2488,7 +2592,7 @@ main(int argc, char *argv[])
 
 	/* process command-line options */
 
-	while ((c = getopt_long(argc, argv, "dD:E:L:nU:WA:sT:X:", long_options, &option_index)) != -1)
+	while ((c = getopt_long(argc, argv, "C:dD:E:L:nU:WA:sT:X:", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -2554,6 +2658,9 @@ main(int argc, char *argv[])
 			case 'X':
 				xlog_dir = xstrdup(optarg);
 				break;
+            case 'C':
+                append_config_line(optarg);
+                break;
 			default:
 				/* getopt_long already emitted a complaint */
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
-- 
1.6.4.rc3

