From 3e51b278db6a4e2f50f3f6b9edc9667af1f934fd Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 22 Mar 2023 13:48:44 -0400 Subject: [PATCH] Add "-c name=value" switch to initdb. This option, or its long form --set, sets the GUC "name" to "value". The setting applies in the bootstrap and standalone servers run by initdb, and is also written into the generated postgresql.conf. This can save an extra editing step when creating a new cluster, but the real use-case is for coping with situations where the bootstrap server fails to start due to environmental issues; for example, if it's necessary to force huge_pages to off. Discussion: https://postgr.es/m/2844176.1674681919@sss.pgh.pa.us --- doc/src/sgml/ref/initdb.sgml | 17 ++ src/bin/initdb/initdb.c | 474 +++++++++++++++++++++++++-------- src/bin/initdb/t/001_initdb.pl | 11 +- 3 files changed, 385 insertions(+), 117 deletions(-) diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml index daebe75ee4..87945b4b62 100644 --- a/doc/src/sgml/ref/initdb.sgml +++ b/doc/src/sgml/ref/initdb.sgml @@ -464,6 +464,23 @@ PostgreSQL documentation Other, less commonly used, options are also available: + + + + + + Forcibly set the server parameter name + to value during initdb, + and also install that setting in the + generated postgresql.conf file, + so that it will apply during future server runs. + This option can be given more than once to set several parameters. + It is primarily useful when the environment is such that the server + will not start at all using the default parameters. + + + + diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c index 68d430ed63..2fc6b3e960 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -85,6 +85,13 @@ /* Ideally this would be in a .h file, but it hardly seems worth the trouble */ extern const char *select_default_timezone(const char *share_path); +/* simple list of strings */ +typedef struct _stringlist +{ + char *str; + struct _stringlist *next; +} _stringlist; + static const char *const auth_methods_host[] = { "trust", "reject", "scram-sha-256", "md5", "password", "ident", "radius", #ifdef ENABLE_GSS @@ -150,6 +157,8 @@ static char *pwfilename = NULL; static char *superuser_password = NULL; static const char *authmethodhost = NULL; static const char *authmethodlocal = NULL; +static _stringlist *extra_guc_names = NULL; +static _stringlist *extra_guc_values = NULL; static bool debug = false; static bool noclean = false; static bool noinstructions = false; @@ -250,7 +259,10 @@ static char backend_exec[MAXPGPATH]; static char **replace_token(char **lines, const char *token, const char *replacement); - +static char **replace_guc_value(char **lines, + const char *guc_name, const char *guc_value, + bool mark_as_comment); +static bool guc_value_requires_quotes(const char *guc_value); static char **readfile(const char *path); static void writefile(char *path, char **lines); static FILE *popen_check(const char *command, const char *mode); @@ -261,6 +273,7 @@ static void check_input(char *path); static void write_version_file(const char *extrapath); static void set_null_conf(void); static void test_config_settings(void); +static bool test_specific_config_settings(int test_conns, int test_buffs); static void setup_config(void); static void bootstrap_template1(void); static void setup_auth(FILE *cmdfd); @@ -368,9 +381,34 @@ escape_quotes_bki(const char *src) } /* - * make a copy of the array of lines, with token replaced by replacement + * Add an item at the end of a stringlist. + */ +static void +add_stringlist_item(_stringlist **listhead, const char *str) +{ + _stringlist *newentry = pg_malloc(sizeof(_stringlist)); + _stringlist *oldentry; + + newentry->str = pg_strdup(str); + newentry->next = NULL; + if (*listhead == NULL) + *listhead = newentry; + else + { + for (oldentry = *listhead; oldentry->next; oldentry = oldentry->next) + /* skip */ ; + oldentry->next = newentry; + } +} + +/* + * Make a copy of the array of lines, with token replaced by replacement * the first time it occurs on each line. * + * The original data structure is not changed, but we share any unchanged + * strings with it. (This definition lends itself to memory leaks, but + * we don't care too much about leaks in this program.) + * * This does most of what sed was used for in the shell script, but * doesn't need any regexp stuff. */ @@ -424,6 +462,168 @@ replace_token(char **lines, const char *token, const char *replacement) return result; } +/* + * Make a copy of the array of lines, replacing the possibly-commented-out + * assignment of parameter guc_name with a live assignment of guc_value. + * The value will be suitably quoted. + * + * If mark_as_comment is true, the replacement line is prefixed with '#'. + * This is used for fixing up cases where the effective default might not + * match what is in postgresql.conf.sample. + * + * We assume there's at most one matching assignment. If we find no match, + * append a new line with the desired assignment. + * + * The original data structure is not changed, but we share any unchanged + * strings with it. (This definition lends itself to memory leaks, but + * we don't care too much about leaks in this program.) + */ +static char ** +replace_guc_value(char **lines, const char *guc_name, const char *guc_value, + bool mark_as_comment) +{ + char **result; + int namelen = strlen(guc_name); + PQExpBuffer newline = createPQExpBuffer(); + int numlines = 0; + int i; + + /* prepare the replacement line, except for possible comment and newline */ + if (mark_as_comment) + appendPQExpBufferChar(newline, '#'); + appendPQExpBuffer(newline, "%s = ", guc_name); + if (guc_value_requires_quotes(guc_value)) + appendPQExpBuffer(newline, "'%s'", escape_quotes(guc_value)); + else + appendPQExpBufferStr(newline, guc_value); + + /* create the new pointer array */ + for (i = 0; lines[i]; i++) + numlines++; + + /* leave room for one extra string in case we need to append */ + result = (char **) pg_malloc((numlines + 2) * sizeof(char *)); + + /* initialize result with all the same strings */ + memcpy(result, lines, (numlines + 1) * sizeof(char *)); + + for (i = 0; i < numlines; i++) + { + const char *where; + + /* + * Look for a line assigning to guc_name. Typically it will be + * preceded by '#', but that might not be the case if a -c switch + * overrides a previous assignment. We allow leading whitespace too, + * although normally there wouldn't be any. + */ + where = result[i]; + while (*where == '#' || isspace((unsigned char) *where)) + where++; + if (strncmp(where, guc_name, namelen) != 0) + continue; + where += namelen; + while (isspace((unsigned char) *where)) + where++; + if (*where != '=') + continue; + + /* found it -- append the original comment if any */ + where = strrchr(where, '#'); + if (where) + { + /* + * We try to preserve original indentation, which is tedious. + * oldindent and newindent are measured in de-tab-ified columns. + */ + const char *ptr; + int oldindent = 0; + int newindent; + + for (ptr = result[i]; ptr < where; ptr++) + { + if (*ptr == '\t') + oldindent += 8 - (oldindent % 8); + else + oldindent++; + } + /* ignore the possibility of tabs in guc_value */ + newindent = newline->len; + /* append appropriate tabs and spaces, forcing at least one */ + oldindent = Max(oldindent, newindent + 1); + while (newindent < oldindent) + { + int newindent_if_tab = newindent + 8 - (newindent % 8); + + if (newindent_if_tab <= oldindent) + { + appendPQExpBufferChar(newline, '\t'); + newindent = newindent_if_tab; + } + else + { + appendPQExpBufferChar(newline, ' '); + newindent++; + } + } + /* and finally append the old comment */ + appendPQExpBufferStr(newline, where); + /* we'll have appended the original newline; don't add another */ + } + else + appendPQExpBufferChar(newline, '\n'); + + result[i] = newline->data; + + break; /* assume there's only one match */ + } + + if (i >= numlines) + { + /* + * No match, so append a new entry. (We rely on the bootstrap server + * to complain if it's not a valid GUC name.) + */ + appendPQExpBufferChar(newline, '\n'); + result[numlines++] = newline->data; + result[numlines] = NULL; /* keep the array null-terminated */ + } + + return result; +} + +/* + * Decide if we should quote a replacement GUC value. We aren't too tense + * here, but we'd like to avoid quoting simple identifiers and numbers + * with units, which are common cases. + */ +static bool +guc_value_requires_quotes(const char *guc_value) +{ + /* Don't use macros here, they might accept too much */ +#define LETTERS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +#define DIGITS "0123456789" + + if (*guc_value == '\0') + return true; /* empty string must be quoted */ + if (strchr(LETTERS, *guc_value)) + { + if (strspn(guc_value, LETTERS DIGITS) == strlen(guc_value)) + return false; /* it's an identifier */ + return true; /* nope */ + } + if (strchr(DIGITS, *guc_value)) + { + /* skip over digits */ + guc_value += strspn(guc_value, DIGITS); + /* there can be zero or more unit letters after the digits */ + if (strspn(guc_value, LETTERS) == strlen(guc_value)) + return false; /* it's a number, possibly with units */ + return true; /* nope */ + } + return true; /* all else must be quoted */ +} + /* * get the lines from a text file */ @@ -881,11 +1081,9 @@ test_config_settings(void) 400, 300, 200, 100, 50 }; - char cmd[MAXPGPATH]; const int connslen = sizeof(trial_conns) / sizeof(int); const int bufslen = sizeof(trial_bufs) / sizeof(int); int i, - status, test_conns, test_buffs, ok_buffers = 0; @@ -911,19 +1109,7 @@ test_config_settings(void) test_conns = trial_conns[i]; test_buffs = MIN_BUFS_FOR_CONNS(test_conns); - snprintf(cmd, sizeof(cmd), - "\"%s\" --check %s %s " - "-c max_connections=%d " - "-c shared_buffers=%d " - "-c dynamic_shared_memory_type=%s " - "< \"%s\" > \"%s\" 2>&1", - backend_exec, boot_options, extra_options, - test_conns, test_buffs, - dynamic_shared_memory_type, - DEVNULL, DEVNULL); - fflush(NULL); - status = system(cmd); - if (status == 0) + if (test_specific_config_settings(test_conns, test_buffs)) { ok_buffers = test_buffs; break; @@ -948,19 +1134,7 @@ test_config_settings(void) break; } - snprintf(cmd, sizeof(cmd), - "\"%s\" --check %s %s " - "-c max_connections=%d " - "-c shared_buffers=%d " - "-c dynamic_shared_memory_type=%s " - "< \"%s\" > \"%s\" 2>&1", - backend_exec, boot_options, extra_options, - n_connections, test_buffs, - dynamic_shared_memory_type, - DEVNULL, DEVNULL); - fflush(NULL); - status = system(cmd); - if (status == 0) + if (test_specific_config_settings(n_connections, test_buffs)) break; } n_buffers = test_buffs; @@ -976,6 +1150,48 @@ test_config_settings(void) printf("%s\n", default_timezone ? default_timezone : "GMT"); } +/* + * Test a specific combination of configuration settings. + */ +static bool +test_specific_config_settings(int test_conns, int test_buffs) +{ + PQExpBuffer cmd = createPQExpBuffer(); + _stringlist *gnames, + *gvalues; + int status; + + /* Set up the test postmaster invocation */ + printfPQExpBuffer(cmd, + "\"%s\" --check %s %s " + "-c max_connections=%d " + "-c shared_buffers=%d " + "-c dynamic_shared_memory_type=%s", + backend_exec, boot_options, extra_options, + test_conns, test_buffs, + dynamic_shared_memory_type); + + /* Add any user-given setting overrides */ + for (gnames = extra_guc_names, gvalues = extra_guc_values; + gnames != NULL; /* assume lists have the same length */ + gnames = gnames->next, gvalues = gvalues->next) + { + appendPQExpBuffer(cmd, " -c %s=", gnames->str); + appendShellString(cmd, gvalues->str); + } + + appendPQExpBuffer(cmd, + " < \"%s\" > \"%s\" 2>&1", + DEVNULL, DEVNULL); + + fflush(NULL); + status = system(cmd->data); + + destroyPQExpBuffer(cmd); + + return (status == 0); +} + /* * Calculate the default wal_size with a "pretty" unit. */ @@ -1003,6 +1219,8 @@ setup_config(void) char repltok[MAXPGPATH]; char path[MAXPGPATH]; char *autoconflines[3]; + _stringlist *gnames, + *gvalues; fputs(_("creating configuration files ... "), stdout); fflush(stdout); @@ -1011,120 +1229,116 @@ setup_config(void) conflines = readfile(conf_file); - snprintf(repltok, sizeof(repltok), "max_connections = %d", n_connections); - conflines = replace_token(conflines, "#max_connections = 100", repltok); + snprintf(repltok, sizeof(repltok), "%d", n_connections); + conflines = replace_guc_value(conflines, "max_connections", + repltok, false); if ((n_buffers * (BLCKSZ / 1024)) % 1024 == 0) - snprintf(repltok, sizeof(repltok), "shared_buffers = %dMB", + snprintf(repltok, sizeof(repltok), "%dMB", (n_buffers * (BLCKSZ / 1024)) / 1024); else - snprintf(repltok, sizeof(repltok), "shared_buffers = %dkB", + snprintf(repltok, sizeof(repltok), "%dkB", n_buffers * (BLCKSZ / 1024)); - conflines = replace_token(conflines, "#shared_buffers = 128MB", repltok); + conflines = replace_guc_value(conflines, "shared_buffers", + repltok, false); - snprintf(repltok, sizeof(repltok), "#unix_socket_directories = '%s'", - DEFAULT_PGSOCKET_DIR); - conflines = replace_token(conflines, "#unix_socket_directories = '/tmp'", - repltok); + /* + * Hack: don't replace the LC_XXX GUCs when their value is 'C', because + * replace_guc_value will decide not to quote that, which looks strange. + */ + if (strcmp(lc_messages, "C") != 0) + conflines = replace_guc_value(conflines, "lc_messages", + lc_messages, false); -#if DEF_PGPORT != 5432 - snprintf(repltok, sizeof(repltok), "#port = %d", DEF_PGPORT); - conflines = replace_token(conflines, "#port = 5432", repltok); -#endif + if (strcmp(lc_monetary, "C") != 0) + conflines = replace_guc_value(conflines, "lc_monetary", + lc_monetary, false); - /* set default max_wal_size and min_wal_size */ - snprintf(repltok, sizeof(repltok), "min_wal_size = %s", - pretty_wal_size(DEFAULT_MIN_WAL_SEGS)); - conflines = replace_token(conflines, "#min_wal_size = 80MB", repltok); + if (strcmp(lc_numeric, "C") != 0) + conflines = replace_guc_value(conflines, "lc_numeric", + lc_numeric, false); - snprintf(repltok, sizeof(repltok), "max_wal_size = %s", - pretty_wal_size(DEFAULT_MAX_WAL_SEGS)); - conflines = replace_token(conflines, "#max_wal_size = 1GB", repltok); - - snprintf(repltok, sizeof(repltok), "lc_messages = '%s'", - escape_quotes(lc_messages)); - conflines = replace_token(conflines, "#lc_messages = 'C'", repltok); - - snprintf(repltok, sizeof(repltok), "lc_monetary = '%s'", - escape_quotes(lc_monetary)); - conflines = replace_token(conflines, "#lc_monetary = 'C'", repltok); - - snprintf(repltok, sizeof(repltok), "lc_numeric = '%s'", - escape_quotes(lc_numeric)); - conflines = replace_token(conflines, "#lc_numeric = 'C'", repltok); - - snprintf(repltok, sizeof(repltok), "lc_time = '%s'", - escape_quotes(lc_time)); - conflines = replace_token(conflines, "#lc_time = 'C'", repltok); + if (strcmp(lc_time, "C") != 0) + conflines = replace_guc_value(conflines, "lc_time", + lc_time, false); switch (locale_date_order(lc_time)) { case DATEORDER_YMD: - strcpy(repltok, "datestyle = 'iso, ymd'"); + strcpy(repltok, "iso, ymd"); break; case DATEORDER_DMY: - strcpy(repltok, "datestyle = 'iso, dmy'"); + strcpy(repltok, "iso, dmy"); break; case DATEORDER_MDY: default: - strcpy(repltok, "datestyle = 'iso, mdy'"); + strcpy(repltok, "iso, mdy"); break; } - conflines = replace_token(conflines, "#datestyle = 'iso, mdy'", repltok); + conflines = replace_guc_value(conflines, "datestyle", + repltok, false); - snprintf(repltok, sizeof(repltok), - "default_text_search_config = 'pg_catalog.%s'", - escape_quotes(default_text_search_config)); - conflines = replace_token(conflines, - "#default_text_search_config = 'pg_catalog.simple'", - repltok); + snprintf(repltok, sizeof(repltok), "pg_catalog.%s", + default_text_search_config); + conflines = replace_guc_value(conflines, "default_text_search_config", + repltok, false); if (default_timezone) { - snprintf(repltok, sizeof(repltok), "timezone = '%s'", - escape_quotes(default_timezone)); - conflines = replace_token(conflines, "#timezone = 'GMT'", repltok); - snprintf(repltok, sizeof(repltok), "log_timezone = '%s'", - escape_quotes(default_timezone)); - conflines = replace_token(conflines, "#log_timezone = 'GMT'", repltok); + conflines = replace_guc_value(conflines, "timezone", + default_timezone, false); + conflines = replace_guc_value(conflines, "log_timezone", + default_timezone, false); } - snprintf(repltok, sizeof(repltok), "dynamic_shared_memory_type = %s", - dynamic_shared_memory_type); - conflines = replace_token(conflines, "#dynamic_shared_memory_type = posix", - repltok); + conflines = replace_guc_value(conflines, "dynamic_shared_memory_type", + dynamic_shared_memory_type, false); + + /* + * Fix up various entries to match the true compile-time defaults. Since + * these are indeed defaults, keep the postgresql.conf lines commented. + */ + conflines = replace_guc_value(conflines, "unix_socket_directories", + DEFAULT_PGSOCKET_DIR, true); + + conflines = replace_guc_value(conflines, "port", + DEF_PGPORT_STR, true); + + conflines = replace_guc_value(conflines, "min_wal_size", + pretty_wal_size(DEFAULT_MIN_WAL_SEGS), true); + + conflines = replace_guc_value(conflines, "max_wal_size", + pretty_wal_size(DEFAULT_MAX_WAL_SEGS), true); #if DEFAULT_BACKEND_FLUSH_AFTER > 0 - snprintf(repltok, sizeof(repltok), "#backend_flush_after = %dkB", + snprintf(repltok, sizeof(repltok), "%dkB", DEFAULT_BACKEND_FLUSH_AFTER * (BLCKSZ / 1024)); - conflines = replace_token(conflines, "#backend_flush_after = 0", - repltok); + conflines = replace_guc_value(conflines, "backend_flush_after", + repltok, true); #endif #if DEFAULT_BGWRITER_FLUSH_AFTER > 0 - snprintf(repltok, sizeof(repltok), "#bgwriter_flush_after = %dkB", + snprintf(repltok, sizeof(repltok), "%dkB", DEFAULT_BGWRITER_FLUSH_AFTER * (BLCKSZ / 1024)); - conflines = replace_token(conflines, "#bgwriter_flush_after = 0", - repltok); + conflines = replace_guc_value(conflines, "bgwriter_flush_after", + repltok, true); #endif #if DEFAULT_CHECKPOINT_FLUSH_AFTER > 0 - snprintf(repltok, sizeof(repltok), "#checkpoint_flush_after = %dkB", + snprintf(repltok, sizeof(repltok), "%dkB", DEFAULT_CHECKPOINT_FLUSH_AFTER * (BLCKSZ / 1024)); - conflines = replace_token(conflines, "#checkpoint_flush_after = 0", - repltok); + conflines = replace_guc_value(conflines, "checkpoint_flush_after", + repltok, true); #endif #ifndef USE_PREFETCH - conflines = replace_token(conflines, - "#effective_io_concurrency = 1", - "#effective_io_concurrency = 0"); + conflines = replace_guc_value(conflines, "effective_io_concurrency", + "0", true); #endif #ifdef WIN32 - conflines = replace_token(conflines, - "#update_process_title = on", - "#update_process_title = off"); + conflines = replace_guc_value(conflines, "update_process_title", + "off", true); #endif /* @@ -1136,9 +1350,8 @@ setup_config(void) (strcmp(authmethodhost, "md5") == 0 && strcmp(authmethodlocal, "scram-sha-256") != 0)) { - conflines = replace_token(conflines, - "#password_encryption = scram-sha-256", - "password_encryption = md5"); + conflines = replace_guc_value(conflines, "password_encryption", + "md5", false); } /* @@ -1149,23 +1362,33 @@ setup_config(void) */ if (pg_dir_create_mode == PG_DIR_MODE_GROUP) { - conflines = replace_token(conflines, - "#log_file_mode = 0600", - "log_file_mode = 0640"); + conflines = replace_guc_value(conflines, "log_file_mode", + "0640", false); } + /* + * Now replace anything that's overridden via -c switches. + */ + for (gnames = extra_guc_names, gvalues = extra_guc_values; + gnames != NULL; /* assume lists have the same length */ + gnames = gnames->next, gvalues = gvalues->next) + { + conflines = replace_guc_value(conflines, gnames->str, + gvalues->str, false); + } + + /* ... and write out the finished postgresql.conf file */ snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data); writefile(path, conflines); if (chmod(path, pg_file_create_mode) != 0) pg_fatal("could not change permissions of \"%s\": %m", path); - /* - * create the automatic configuration file to store the configuration - * parameters set by ALTER SYSTEM command. The parameters present in this - * file will override the value of parameters that exists before parse of - * this file. - */ + free(conflines); + + + /* postgresql.auto.conf */ + autoconflines[0] = pg_strdup("# Do not edit this file manually!\n"); autoconflines[1] = pg_strdup("# It will be overwritten by the ALTER SYSTEM command.\n"); autoconflines[2] = NULL; @@ -1176,8 +1399,6 @@ setup_config(void) if (chmod(path, pg_file_create_mode) != 0) pg_fatal("could not change permissions of \"%s\": %m", path); - free(conflines); - /* pg_hba.conf */ @@ -2183,6 +2404,7 @@ usage(const char *progname) printf(_(" -X, --waldir=WALDIR location for the write-ahead log directory\n")); printf(_(" --wal-segsize=SIZE size of WAL segments, in megabytes\n")); printf(_("\nLess commonly used options:\n")); + printf(_(" -c, --set NAME=VALUE override default setting for server parameter\n")); printf(_(" -d, --debug generate lots of debugging output\n")); printf(_(" --discard-caches set debug_discard_caches=1\n")); printf(_(" -L DIRECTORY where to find the input files\n")); @@ -2819,6 +3041,7 @@ main(int argc, char *argv[]) {"nosync", no_argument, NULL, 'N'}, /* for backwards compatibility */ {"no-sync", no_argument, NULL, 'N'}, {"no-instructions", no_argument, NULL, 13}, + {"set", required_argument, NULL, 'c'}, {"sync-only", no_argument, NULL, 'S'}, {"waldir", required_argument, NULL, 'X'}, {"wal-segsize", required_argument, NULL, 12}, @@ -2869,7 +3092,8 @@ main(int argc, char *argv[]) /* process command-line options */ - while ((c = getopt_long(argc, argv, "A:dD:E:gkL:nNsST:U:WX:", long_options, &option_index)) != -1) + while ((c = getopt_long(argc, argv, "A:c:dD:E:gkL:nNsST:U:WX:", + long_options, &option_index)) != -1) { switch (c) { @@ -2892,6 +3116,24 @@ main(int argc, char *argv[]) case 11: authmethodhost = pg_strdup(optarg); break; + case 'c': + { + char *buf = pg_strdup(optarg); + char *equals = strchr(buf, '='); + + if (!equals) + { + pg_log_error("-c %s requires a value", buf); + pg_log_error_hint("Try \"%s --help\" for more information.", + progname); + exit(1); + } + *equals++ = '\0'; /* terminate variable name */ + add_stringlist_item(&extra_guc_names, buf); + add_stringlist_item(&extra_guc_values, equals); + pfree(buf); + } + break; case 'D': pg_data = pg_strdup(optarg); break; diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl index 7ed346bd08..b97420f7e8 100644 --- a/src/bin/initdb/t/001_initdb.pl +++ b/src/bin/initdb/t/001_initdb.pl @@ -48,7 +48,13 @@ mkdir $datadir; local (%ENV) = %ENV; delete $ENV{TZ}; - command_ok([ 'initdb', '-N', '-T', 'german', '-X', $xlogdir, $datadir ], + # while we are here, also exercise -T and -c options + command_ok( + [ + 'initdb', '-N', '-T', 'german', '-c', + 'default_text_search_config=german', + '-X', $xlogdir, $datadir + ], 'successful creation'); # Permissions on PGDATA should be default @@ -142,4 +148,7 @@ command_fails( ], 'fails for invalid option combination'); +command_fails([ 'initdb', '--no-sync', '--set', 'foo=bar', "$tempdir/dataX" ], + 'fails for invalid --set option'); + done_testing();