Skip to content

Commit 55d26ff

Browse files
committed
Rationalize handling of single and double quotes in bootstrap data.
Change things around so that proper quoting of values interpolated into the BKI data by initdb is the responsibility of initdb, not something we half-heartedly handle by putting double quotes into the raw BKI data. (Note: experimentation shows that it still doesn't work to put a double quote into the initial superuser username, but that's the fault of inadequate quoting while interpolating the name into SQL scripts; the BKI aspect of it works fine now.) Having done that, we can remove the special-case handling of values that look like "something" from genbki.pl, and instead teach it to escape double --- and single --- quotes properly. This removes the nowhere-documented need to treat those specially in the BKI source data; whatever you write will be passed through unchanged into the inserted data value, modulo Perl's rules about single-quoted strings. Add documentation explaining the (pre-existing) handling of backslashes in the BKI data. Per an earlier discussion with John Naylor. Discussion: https://postgr.es/m/CAJVSVGUNao=-Q2-vAN3PYcdF5tnL5JAHwGwzZGuYHtq+Mk_9ng@mail.gmail.com
1 parent 9ffcccd commit 55d26ff

File tree

6 files changed

+89
-38
lines changed

6 files changed

+89
-38
lines changed

doc/src/sgml/bki.sgml

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,11 @@
184184
<programlisting>
185185
[
186186

187-
# LC_COLLATE and LC_CTYPE will be replaced at initdb time with user choices
188-
# that might contain non-word characters, so we must double-quote them.
189-
187+
# A comment could appear here.
190188
{ oid =&gt; '1', oid_symbol =&gt; 'TemplateDbOid',
191189
descr =&gt; 'database\'s default template',
192190
datname =&gt; 'template1', datdba =&gt; 'PGUID', encoding =&gt; 'ENCODING',
193-
datcollate =&gt; '"LC_COLLATE"', datctype =&gt; '"LC_CTYPE"', datistemplate =&gt; 't',
191+
datcollate =&gt; 'LC_COLLATE', datctype =&gt; 'LC_CTYPE', datistemplate =&gt; 't',
194192
datallowconn =&gt; 't', datconnlimit =&gt; '-1', datlastsysoid =&gt; '0',
195193
datfrozenxid =&gt; '0', datminmxid =&gt; '1', dattablespace =&gt; '1663',
196194
datacl =&gt; '_null_' },
@@ -234,10 +232,16 @@
234232

235233
<listitem>
236234
<para>
237-
All values must be single-quoted. Escape single quotes used within
238-
a value with a backslash. (Backslashes meant as data need not be
239-
doubled, however; this follows Perl's rules for simple quoted
240-
literals.)
235+
All values must be single-quoted. Escape single quotes used within a
236+
value with a backslash. Backslashes meant as data can, but need not,
237+
be doubled; this follows Perl's rules for simple quoted literals.
238+
Note that backslashes appearing as data will be treated as escapes by
239+
the bootstrap scanner, according to the same rules as for escape string
240+
constants (see <xref linkend="sql-syntax-strings-escape"/>); for
241+
example <literal>\t</literal> converts to a tab character. If you
242+
actually want a backslash in the final value, you will need to write
243+
four of them: Perl strips two, leaving <literal>\\</literal> for the
244+
bootstrap scanner to see.
241245
</para>
242246
</listitem>
243247

@@ -247,15 +251,6 @@
247251
</para>
248252
</listitem>
249253

250-
<listitem>
251-
<para>
252-
If a value is a macro to be expanded
253-
by <application>initdb</application>, it should also contain double
254-
quotes as shown above, unless we know that no special characters can
255-
appear within the string that will be substituted.
256-
</para>
257-
</listitem>
258-
259254
<listitem>
260255
<para>
261256
Comments are preceded by <literal>#</literal>, and must be on their

src/backend/catalog/genbki.pl

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -660,12 +660,19 @@ sub print_bki_insert
660660
# since that represents a NUL char in C code.
661661
$bki_value = '' if $bki_value eq '\0';
662662

663+
# Handle single quotes by doubling them, and double quotes by
664+
# converting them to octal escapes, because that's what the
665+
# bootstrap scanner requires. We do not process backslashes
666+
# specially; this allows escape-string-style backslash escapes
667+
# to be used in catalog data.
668+
$bki_value =~ s/'/''/g;
669+
$bki_value =~ s/"/\\042/g;
670+
663671
# Quote value if needed. We need not quote values that satisfy
664672
# the "id" pattern in bootscanner.l, currently "[-A-Za-z0-9_]+".
665673
$bki_value = sprintf(qq'"%s"', $bki_value)
666-
if $bki_value !~ /^"[^"]+"$/
667-
and ( length($bki_value) == 0
668-
or $bki_value =~ /[^-A-Za-z0-9_]/);
674+
if length($bki_value) == 0
675+
or $bki_value =~ /[^-A-Za-z0-9_]/;
669676

670677
push @bki_values, $bki_value;
671678
}

src/bin/initdb/initdb.c

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ static void make_postgres(FILE *cmdfd);
265265
static void trapsig(int signum);
266266
static void check_ok(void);
267267
static char *escape_quotes(const char *src);
268+
static char *escape_quotes_bki(const char *src);
268269
static int locale_date_order(const char *locale);
269270
static void check_locale_name(int category, const char *locale,
270271
char **canonname);
@@ -324,6 +325,10 @@ do { \
324325
output_failed = true, output_errno = errno; \
325326
} while (0)
326327

328+
/*
329+
* Escape single quotes and backslashes, suitably for insertions into
330+
* configuration files or SQL E'' strings.
331+
*/
327332
static char *
328333
escape_quotes(const char *src)
329334
{
@@ -337,6 +342,52 @@ escape_quotes(const char *src)
337342
return result;
338343
}
339344

345+
/*
346+
* Escape a field value to be inserted into the BKI data.
347+
* Here, we first run the value through escape_quotes (which
348+
* will be inverted by the backend's scanstr() function) and
349+
* then overlay special processing of double quotes, which
350+
* bootscanner.l will only accept as data if converted to octal
351+
* representation ("\042"). We always wrap the value in double
352+
* quotes, even if that isn't strictly necessary.
353+
*/
354+
static char *
355+
escape_quotes_bki(const char *src)
356+
{
357+
char *result;
358+
char *data = escape_quotes(src);
359+
char *resultp;
360+
char *datap;
361+
int nquotes = 0;
362+
363+
/* count double quotes in data */
364+
datap = data;
365+
while ((datap = strchr(datap, '"')) != NULL)
366+
{
367+
nquotes++;
368+
datap++;
369+
}
370+
371+
result = (char *) pg_malloc(strlen(data) + 3 + nquotes * 3);
372+
resultp = result;
373+
*resultp++ = '"';
374+
for (datap = data; *datap; datap++)
375+
{
376+
if (*datap == '"')
377+
{
378+
strcpy(resultp, "\\042");
379+
resultp += 4;
380+
}
381+
else
382+
*resultp++ = *datap;
383+
}
384+
*resultp++ = '"';
385+
*resultp = '\0';
386+
387+
free(data);
388+
return result;
389+
}
390+
340391
/*
341392
* make a copy of the array of lines, with token replaced by replacement
342393
* the first time it occurs on each line.
@@ -1368,13 +1419,17 @@ bootstrap_template1(void)
13681419
bki_lines = replace_token(bki_lines, "FLOAT8PASSBYVAL",
13691420
FLOAT8PASSBYVAL ? "true" : "false");
13701421

1371-
bki_lines = replace_token(bki_lines, "POSTGRES", escape_quotes(username));
1422+
bki_lines = replace_token(bki_lines, "POSTGRES",
1423+
escape_quotes_bki(username));
13721424

1373-
bki_lines = replace_token(bki_lines, "ENCODING", encodingid_to_string(encodingid));
1425+
bki_lines = replace_token(bki_lines, "ENCODING",
1426+
encodingid_to_string(encodingid));
13741427

1375-
bki_lines = replace_token(bki_lines, "LC_COLLATE", escape_quotes(lc_collate));
1428+
bki_lines = replace_token(bki_lines, "LC_COLLATE",
1429+
escape_quotes_bki(lc_collate));
13761430

1377-
bki_lines = replace_token(bki_lines, "LC_CTYPE", escape_quotes(lc_ctype));
1431+
bki_lines = replace_token(bki_lines, "LC_CTYPE",
1432+
escape_quotes_bki(lc_ctype));
13781433

13791434
/*
13801435
* Pass correct LC_xxx environment to bootstrap.

src/include/catalog/pg_authid.dat

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,11 @@
1212

1313
[
1414

15-
# POSTGRES will be replaced at initdb time with a user choice that might
16-
# contain non-word characters, so we must double-quote it.
17-
1815
# The C code typically refers to these roles using the #define symbols,
1916
# so make sure every entry has an oid_symbol value.
2017

2118
{ oid => '10', oid_symbol => 'BOOTSTRAP_SUPERUSERID',
22-
rolname => '"POSTGRES"', rolsuper => 't', rolinherit => 't',
19+
rolname => 'POSTGRES', rolsuper => 't', rolinherit => 't',
2320
rolcreaterole => 't', rolcreatedb => 't', rolcanlogin => 't',
2421
rolreplication => 't', rolbypassrls => 't', rolconnlimit => '-1',
2522
rolpassword => '_null_', rolvaliduntil => '_null_' },

src/include/catalog/pg_database.dat

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,10 @@
1212

1313
[
1414

15-
# LC_COLLATE and LC_CTYPE will be replaced at initdb time with user choices
16-
# that might contain non-word characters, so we must double-quote them.
17-
1815
{ oid => '1', oid_symbol => 'TemplateDbOid',
1916
descr => 'default template for new databases',
2017
datname => 'template1', datdba => 'PGUID', encoding => 'ENCODING',
21-
datcollate => '"LC_COLLATE"', datctype => '"LC_CTYPE"', datistemplate => 't',
18+
datcollate => 'LC_COLLATE', datctype => 'LC_CTYPE', datistemplate => 't',
2219
datallowconn => 't', datconnlimit => '-1', datlastsysoid => '0',
2320
datfrozenxid => '0', datminmxid => '1', dattablespace => '1663',
2421
datacl => '_null_' },

src/include/catalog/pg_proc.dat

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2417,7 +2417,7 @@
24172417
{ oid => '1216', descr => 'get description for table column',
24182418
proname => 'col_description', prolang => '14', procost => '100',
24192419
provolatile => 's', prorettype => 'text', proargtypes => 'oid int4',
2420-
prosrc => 'select description from pg_catalog.pg_description where objoid = $1 and classoid = \'\'pg_catalog.pg_class\'\'::pg_catalog.regclass and objsubid = $2' },
2420+
prosrc => 'select description from pg_catalog.pg_description where objoid = $1 and classoid = \'pg_catalog.pg_class\'::pg_catalog.regclass and objsubid = $2' },
24212421
{ oid => '1993',
24222422
descr => 'get description for object id and shared catalog name',
24232423
proname => 'shobj_description', prolang => '14', procost => '100',
@@ -3483,11 +3483,11 @@
34833483
{ oid => '879', descr => 'left-pad string to length',
34843484
proname => 'lpad', prolang => '14', prorettype => 'text',
34853485
proargtypes => 'text int4',
3486-
prosrc => 'select pg_catalog.lpad($1, $2, \'\' \'\')' },
3486+
prosrc => 'select pg_catalog.lpad($1, $2, \' \')' },
34873487
{ oid => '880', descr => 'right-pad string to length',
34883488
proname => 'rpad', prolang => '14', prorettype => 'text',
34893489
proargtypes => 'text int4',
3490-
prosrc => 'select pg_catalog.rpad($1, $2, \'\' \'\')' },
3490+
prosrc => 'select pg_catalog.rpad($1, $2, \' \')' },
34913491
{ oid => '881', descr => 'trim spaces from left end of string',
34923492
proname => 'ltrim', prorettype => 'text', proargtypes => 'text',
34933493
prosrc => 'ltrim1' },
@@ -6930,7 +6930,7 @@
69306930
descr => 'disk space usage for the main fork of the specified table or index',
69316931
proname => 'pg_relation_size', prolang => '14', provolatile => 'v',
69326932
prorettype => 'int8', proargtypes => 'regclass',
6933-
prosrc => 'select pg_catalog.pg_relation_size($1, \'\'main\'\')' },
6933+
prosrc => 'select pg_catalog.pg_relation_size($1, \'main\')' },
69346934
{ oid => '2332',
69356935
descr => 'disk space usage for the specified fork of a table or index',
69366936
proname => 'pg_relation_size', provolatile => 'v', prorettype => 'int8',
@@ -8168,7 +8168,7 @@
81688168
{ oid => '2932', descr => 'evaluate XPath expression',
81698169
proname => 'xpath', prolang => '14', prorettype => '_xml',
81708170
proargtypes => 'text xml',
8171-
prosrc => 'select pg_catalog.xpath($1, $2, \'\'{}\'\'::pg_catalog.text[])' },
8171+
prosrc => 'select pg_catalog.xpath($1, $2, \'{}\'::pg_catalog.text[])' },
81728172

81738173
{ oid => '2614', descr => 'test XML value against XPath expression',
81748174
proname => 'xmlexists', prorettype => 'bool', proargtypes => 'text xml',
@@ -8181,7 +8181,7 @@
81818181
{ oid => '3050', descr => 'test XML value against XPath expression',
81828182
proname => 'xpath_exists', prolang => '14', prorettype => 'bool',
81838183
proargtypes => 'text xml',
8184-
prosrc => 'select pg_catalog.xpath_exists($1, $2, \'\'{}\'\'::pg_catalog.text[])' },
8184+
prosrc => 'select pg_catalog.xpath_exists($1, $2, \'{}\'::pg_catalog.text[])' },
81858185
{ oid => '3051', descr => 'determine if a string is well formed XML',
81868186
proname => 'xml_is_well_formed', provolatile => 's', prorettype => 'bool',
81878187
proargtypes => 'text', prosrc => 'xml_is_well_formed' },

0 commit comments

Comments
 (0)