aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--contrib/amcheck/expected/check_btree.out8
-rw-r--r--contrib/amcheck/sql/check_btree.sql7
-rw-r--r--contrib/amcheck/verify_common.c24
-rw-r--r--contrib/amcheck/verify_common.h2
-rw-r--r--doc/src/sgml/libpq.sgml24
-rw-r--r--doc/src/sgml/ref/psql-ref.sgml9
-rw-r--r--src/backend/libpq/hba.c38
-rw-r--r--src/bin/psql/command.c7
-rw-r--r--src/interfaces/libpq/fe-connect.c54
-rw-r--r--src/interfaces/libpq/libpq-int.h2
-rw-r--r--src/interfaces/libpq/t/006_service.pl79
-rw-r--r--src/test/authentication/t/003_peer.pl18
12 files changed, 229 insertions, 43 deletions
diff --git a/contrib/amcheck/expected/check_btree.out b/contrib/amcheck/expected/check_btree.out
index c6f4b16c556..6558f2c5a4f 100644
--- a/contrib/amcheck/expected/check_btree.out
+++ b/contrib/amcheck/expected/check_btree.out
@@ -60,6 +60,14 @@ SELECT bt_index_parent_check('bttest_a_brin_idx');
ERROR: expected "btree" index as targets for verification
DETAIL: Relation "bttest_a_brin_idx" is a brin index.
ROLLBACK;
+-- verify partitioned indexes are rejected (error)
+BEGIN;
+CREATE TABLE bttest_partitioned (a int, b int) PARTITION BY list (a);
+CREATE INDEX bttest_btree_partitioned_idx ON bttest_partitioned USING btree (b);
+SELECT bt_index_parent_check('bttest_btree_partitioned_idx');
+ERROR: expected index as targets for verification
+DETAIL: This operation is not supported for partitioned indexes.
+ROLLBACK;
-- normal check outside of xact
SELECT bt_index_check('bttest_a_idx');
bt_index_check
diff --git a/contrib/amcheck/sql/check_btree.sql b/contrib/amcheck/sql/check_btree.sql
index 0793dbfeebd..171f7f691ec 100644
--- a/contrib/amcheck/sql/check_btree.sql
+++ b/contrib/amcheck/sql/check_btree.sql
@@ -52,6 +52,13 @@ CREATE INDEX bttest_a_brin_idx ON bttest_a USING brin(id);
SELECT bt_index_parent_check('bttest_a_brin_idx');
ROLLBACK;
+-- verify partitioned indexes are rejected (error)
+BEGIN;
+CREATE TABLE bttest_partitioned (a int, b int) PARTITION BY list (a);
+CREATE INDEX bttest_btree_partitioned_idx ON bttest_partitioned USING btree (b);
+SELECT bt_index_parent_check('bttest_btree_partitioned_idx');
+ROLLBACK;
+
-- normal check outside of xact
SELECT bt_index_check('bttest_a_idx');
-- more expansive tests
diff --git a/contrib/amcheck/verify_common.c b/contrib/amcheck/verify_common.c
index d095e62ce55..a31ce06ed99 100644
--- a/contrib/amcheck/verify_common.c
+++ b/contrib/amcheck/verify_common.c
@@ -18,11 +18,13 @@
#include "verify_common.h"
#include "catalog/index.h"
#include "catalog/pg_am.h"
+#include "commands/defrem.h"
#include "commands/tablecmds.h"
#include "utils/guc.h"
#include "utils/syscache.h"
static bool amcheck_index_mainfork_expected(Relation rel);
+static bool index_checkable(Relation rel, Oid am_id);
/*
@@ -155,23 +157,21 @@ amcheck_lock_relation_and_check(Oid indrelid,
* callable by non-superusers. If granted, it's useful to be able to check a
* whole cluster.
*/
-bool
+static bool
index_checkable(Relation rel, Oid am_id)
{
- if (rel->rd_rel->relkind != RELKIND_INDEX ||
- rel->rd_rel->relam != am_id)
- {
- HeapTuple amtup;
- HeapTuple amtuprel;
+ if (rel->rd_rel->relkind != RELKIND_INDEX)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("expected index as targets for verification"),
+ errdetail_relkind_not_supported(rel->rd_rel->relkind)));
- amtup = SearchSysCache1(AMOID, ObjectIdGetDatum(am_id));
- amtuprel = SearchSysCache1(AMOID, ObjectIdGetDatum(rel->rd_rel->relam));
+ if (rel->rd_rel->relam != am_id)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("expected \"%s\" index as targets for verification", NameStr(((Form_pg_am) GETSTRUCT(amtup))->amname)),
+ errmsg("expected \"%s\" index as targets for verification", get_am_name(am_id)),
errdetail("Relation \"%s\" is a %s index.",
- RelationGetRelationName(rel), NameStr(((Form_pg_am) GETSTRUCT(amtuprel))->amname))));
- }
+ RelationGetRelationName(rel), get_am_name(rel->rd_rel->relam))));
if (RELATION_IS_OTHER_TEMP(rel))
ereport(ERROR,
@@ -182,7 +182,7 @@ index_checkable(Relation rel, Oid am_id)
if (!rel->rd_index->indisvalid)
ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot check index \"%s\"",
RelationGetRelationName(rel)),
errdetail("Index is not valid.")));
diff --git a/contrib/amcheck/verify_common.h b/contrib/amcheck/verify_common.h
index 42ef9c20fe2..3f4c57f963d 100644
--- a/contrib/amcheck/verify_common.h
+++ b/contrib/amcheck/verify_common.h
@@ -26,5 +26,3 @@ extern void amcheck_lock_relation_and_check(Oid indrelid,
Oid am_id,
IndexDoCheckCallback check,
LOCKMODE lockmode, void *state);
-
-extern bool index_checkable(Relation rel, Oid am_id);
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index b2c2cf9eac8..5bf59a19855 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2320,6 +2320,19 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem>
</varlistentry>
+ <varlistentry id="libpq-connect-servicefile" xreflabel="servicefile">
+ <term><literal>servicefile</literal></term>
+ <listitem>
+ <para>
+ This option specifies the name of the per-user connection service file
+ (see <xref linkend="libpq-pgservice"/>).
+ Defaults to <filename>~/.pg_service.conf</filename>, or
+ <filename>%APPDATA%\postgresql\.pg_service.conf</filename> on
+ Microsoft Windows.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="libpq-connect-target-session-attrs" xreflabel="target_session_attrs">
<term><literal>target_session_attrs</literal></term>
<listitem>
@@ -9140,12 +9153,8 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
<indexterm>
<primary><envar>PGSERVICEFILE</envar></primary>
</indexterm>
- <envar>PGSERVICEFILE</envar> specifies the name of the per-user
- connection service file
- (see <xref linkend="libpq-pgservice"/>).
- Defaults to <filename>~/.pg_service.conf</filename>, or
- <filename>%APPDATA%\postgresql\.pg_service.conf</filename> on
- Microsoft Windows.
+ <envar>PGSERVICEFILE</envar> behaves the same as the
+ <xref linkend="libpq-connect-servicefile"/> connection parameter.
</para>
</listitem>
@@ -9576,7 +9585,8 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
On Microsoft Windows, it is named
<filename>%APPDATA%\postgresql\.pg_service.conf</filename> (where
<filename>%APPDATA%</filename> refers to the Application Data subdirectory
- in the user's profile). A different file name can be specified by
+ in the user's profile). A different file name can be specified using the
+ <literal>servicefile</literal> key word in a libpq connection string or by
setting the environment variable <envar>PGSERVICEFILE</envar>.
The system-wide file is named <filename>pg_service.conf</filename>.
By default it is sought in the <filename>etc</filename> directory
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 95f4cac2467..4f7b11175c6 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -4623,6 +4623,15 @@ bar
</listitem>
</varlistentry>
+ <varlistentry id="app-psql-variables-servicefile">
+ <term><varname>SERVICEFILE</varname></term>
+ <listitem>
+ <para>
+ The service file name, if applicable.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="app-psql-variables-shell-error">
<term><varname>SHELL_ERROR</varname></term>
<listitem>
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 332fad27835..fecee8224d0 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -2873,8 +2873,11 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name,
!token_has_regexp(identLine->pg_user) &&
(ofs = strstr(identLine->pg_user->string, "\\1")) != NULL)
{
+ const char *repl_str;
+ size_t repl_len;
+ char *old_pg_user;
char *expanded_pg_user;
- int offset;
+ size_t offset;
/* substitution of the first argument requested */
if (matches[1].rm_so < 0)
@@ -2886,18 +2889,33 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name,
*error_p = true;
return;
}
+ repl_str = system_user + matches[1].rm_so;
+ repl_len = matches[1].rm_eo - matches[1].rm_so;
/*
- * length: original length minus length of \1 plus length of match
- * plus null terminator
+ * It's allowed to have more than one \1 in the string, and we'll
+ * replace them all. But that's pretty unusual so we optimize on
+ * the assumption of only one occurrence, which motivates doing
+ * repeated replacements instead of making two passes over the
+ * string to determine the final length right away.
*/
- expanded_pg_user = palloc0(strlen(identLine->pg_user->string) - 2 + (matches[1].rm_eo - matches[1].rm_so) + 1);
- offset = ofs - identLine->pg_user->string;
- memcpy(expanded_pg_user, identLine->pg_user->string, offset);
- memcpy(expanded_pg_user + offset,
- system_user + matches[1].rm_so,
- matches[1].rm_eo - matches[1].rm_so);
- strcat(expanded_pg_user, ofs + 2);
+ old_pg_user = identLine->pg_user->string;
+ do
+ {
+ /*
+ * length: current length minus length of \1 plus length of
+ * replacement plus null terminator
+ */
+ expanded_pg_user = palloc(strlen(old_pg_user) - 2 + repl_len + 1);
+ /* ofs points into the old_pg_user string at this point */
+ offset = ofs - old_pg_user;
+ memcpy(expanded_pg_user, old_pg_user, offset);
+ memcpy(expanded_pg_user + offset, repl_str, repl_len);
+ strcpy(expanded_pg_user + offset + repl_len, ofs + 2);
+ if (old_pg_user != identLine->pg_user->string)
+ pfree(old_pg_user);
+ old_pg_user = expanded_pg_user;
+ } while ((ofs = strstr(old_pg_user + offset + repl_len, "\\1")) != NULL);
/*
* Mark the token as quoted, so it will only be compared literally
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 0a55901b14e..0e00d73487c 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -4481,6 +4481,7 @@ SyncVariables(void)
char vbuf[32];
const char *server_version;
char *service_name;
+ char *service_file;
/* get stuff from connection */
pset.encoding = PQclientEncoding(pset.db);
@@ -4500,6 +4501,11 @@ SyncVariables(void)
if (service_name)
pg_free(service_name);
+ service_file = get_conninfo_value("servicefile");
+ SetVariable(pset.vars, "SERVICEFILE", service_file);
+ if (service_file)
+ pg_free(service_file);
+
/* this bit should match connection_warnings(): */
/* Try to get full text form of version, might include "devel" etc */
server_version = PQparameterStatus(pset.db, "server_version");
@@ -4529,6 +4535,7 @@ UnsyncVariables(void)
{
SetVariable(pset.vars, "DBNAME", NULL);
SetVariable(pset.vars, "SERVICE", NULL);
+ SetVariable(pset.vars, "SERVICEFILE", NULL);
SetVariable(pset.vars, "USER", NULL);
SetVariable(pset.vars, "HOST", NULL);
SetVariable(pset.vars, "PORT", NULL);
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 09eb79812ac..2a2b10d5a29 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -201,6 +201,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Database-Service", "", 20,
offsetof(struct pg_conn, pgservice)},
+ {"servicefile", "PGSERVICEFILE", NULL, NULL,
+ "Database-Service-File", "", 64,
+ offsetof(struct pg_conn, pgservicefile)},
+
{"user", "PGUSER", NULL, NULL,
"Database-User", "", 20,
offsetof(struct pg_conn, pguser)},
@@ -5062,6 +5066,7 @@ freePGconn(PGconn *conn)
free(conn->dbName);
free(conn->replication);
free(conn->pgservice);
+ free(conn->pgservicefile);
free(conn->pguser);
if (conn->pgpass)
{
@@ -5914,6 +5919,7 @@ static int
parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage)
{
const char *service = conninfo_getval(options, "service");
+ const char *service_fname = conninfo_getval(options, "servicefile");
char serviceFile[MAXPGPATH];
char *env;
bool group_found = false;
@@ -5933,10 +5939,13 @@ parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage)
return 0;
/*
- * Try PGSERVICEFILE if specified, else try ~/.pg_service.conf (if that
- * exists).
+ * First, try the "servicefile" option in connection string. Then, try
+ * the PGSERVICEFILE environment variable. Finally, check
+ * ~/.pg_service.conf (if that exists).
*/
- if ((env = getenv("PGSERVICEFILE")) != NULL)
+ if (service_fname != NULL)
+ strlcpy(serviceFile, service_fname, sizeof(serviceFile));
+ else if ((env = getenv("PGSERVICEFILE")) != NULL)
strlcpy(serviceFile, env, sizeof(serviceFile));
else
{
@@ -6092,7 +6101,17 @@ parseServiceFile(const char *serviceFile,
if (strcmp(key, "service") == 0)
{
libpq_append_error(errorMessage,
- "nested service specifications not supported in service file \"%s\", line %d",
+ "nested \"service\" specifications not supported in service file \"%s\", line %d",
+ serviceFile,
+ linenr);
+ result = 3;
+ goto exit;
+ }
+
+ if (strcmp(key, "servicefile") == 0)
+ {
+ libpq_append_error(errorMessage,
+ "nested \"servicefile\" specifications not supported in service file \"%s\", line %d",
serviceFile,
linenr);
result = 3;
@@ -6135,6 +6154,33 @@ parseServiceFile(const char *serviceFile,
}
exit:
+
+ /*
+ * If a service has been successfully found, set the "servicefile" option
+ * if not already set. This matters when we use a default service file or
+ * PGSERVICEFILE, where we want to be able track the value.
+ */
+ if (*group_found && result == 0)
+ {
+ for (i = 0; options[i].keyword; i++)
+ {
+ if (strcmp(options[i].keyword, "servicefile") != 0)
+ continue;
+
+ /* If value is already set, nothing to do */
+ if (options[i].val != NULL)
+ break;
+
+ options[i].val = strdup(serviceFile);
+ if (options[i].val == NULL)
+ {
+ libpq_append_error(errorMessage, "out of memory");
+ result = 3;
+ }
+ break;
+ }
+ }
+
fclose(f);
return result;
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index a6cfd7f5c9d..70c28f2ffca 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -389,6 +389,8 @@ struct pg_conn
char *dbName; /* database name */
char *replication; /* connect as the replication standby? */
char *pgservice; /* Postgres service, if any */
+ char *pgservicefile; /* path to a service file containing
+ * service(s) */
char *pguser; /* Postgres username and password, if any */
char *pgpass;
char *pgpassfile; /* path to a file containing password(s) */
diff --git a/src/interfaces/libpq/t/006_service.pl b/src/interfaces/libpq/t/006_service.pl
index d896558a6cc..797e6232b8f 100644
--- a/src/interfaces/libpq/t/006_service.pl
+++ b/src/interfaces/libpq/t/006_service.pl
@@ -53,6 +53,13 @@ copy($srvfile_valid, $srvfile_nested)
or die "Could not copy $srvfile_valid to $srvfile_nested: $!";
append_to_file($srvfile_nested, 'service=invalid_srv' . $newline);
+# Service file with nested "servicefile" defined.
+my $srvfile_nested_2 = "$td/pg_service_nested_2.conf";
+copy($srvfile_valid, $srvfile_nested_2)
+ or die "Could not copy $srvfile_valid to $srvfile_nested_2: $!";
+append_to_file($srvfile_nested_2,
+ 'servicefile=' . $srvfile_default . $newline);
+
# Set the fallback directory lookup of the service file to the temporary
# directory of this test. PGSYSCONFDIR is used if the service file
# defined in PGSERVICEFILE cannot be found, or when a service file is
@@ -158,9 +165,77 @@ local $ENV{PGSERVICEFILE} = "$srvfile_empty";
$dummy_node->connect_fails(
'service=my_srv',
- 'connection with nested service file',
+ 'connection with "service" in nested service file',
+ expected_stderr =>
+ qr/nested "service" specifications not supported in service file/);
+
+ local $ENV{PGSERVICEFILE} = $srvfile_nested_2;
+
+ $dummy_node->connect_fails(
+ 'service=my_srv',
+ 'connection with "servicefile" in nested service file',
expected_stderr =>
- qr/nested service specifications not supported in service file/);
+ qr/nested "servicefile" specifications not supported in service file/
+ );
+}
+
+# Properly escape backslashes in the path, to ensure the generation of
+# correct connection strings.
+my $srvfile_win_cared = $srvfile_valid;
+$srvfile_win_cared =~ s/\\/\\\\/g;
+
+# Checks that the "servicefile" option works as expected
+{
+ $dummy_node->connect_ok(
+ q{service=my_srv servicefile='} . $srvfile_win_cared . q{'},
+ 'connection with valid servicefile in connection string',
+ sql => "SELECT 'connect3_1'",
+ expected_stdout => qr/connect3_1/);
+
+ # Encode slashes and backslash
+ my $encoded_srvfile = $srvfile_valid =~ s{([\\/])}{
+ $1 eq '/' ? '%2F' : '%5C'
+ }ger;
+
+ # Additionally encode a colon in servicefile path of Windows
+ $encoded_srvfile =~ s/:/%3A/g;
+
+ $dummy_node->connect_ok(
+ 'postgresql:///?service=my_srv&servicefile=' . $encoded_srvfile,
+ 'connection with valid servicefile in URI',
+ sql => "SELECT 'connect3_2'",
+ expected_stdout => qr/connect3_2/);
+
+ local $ENV{PGSERVICE} = 'my_srv';
+ $dummy_node->connect_ok(
+ q{servicefile='} . $srvfile_win_cared . q{'},
+ 'connection with PGSERVICE and servicefile in connection string',
+ sql => "SELECT 'connect3_3'",
+ expected_stdout => qr/connect3_3/);
+
+ $dummy_node->connect_ok(
+ 'postgresql://?servicefile=' . $encoded_srvfile,
+ 'connection with PGSERVICE and servicefile in URI',
+ sql => "SELECT 'connect3_4'",
+ expected_stdout => qr/connect3_4/);
+}
+
+# Check that the "servicefile" option takes priority over the PGSERVICEFILE
+# environment variable.
+{
+ local $ENV{PGSERVICEFILE} = 'non-existent-file.conf';
+
+ $dummy_node->connect_fails(
+ 'service=my_srv',
+ 'connection with invalid PGSERVICEFILE',
+ expected_stderr =>
+ qr/service file "non-existent-file\.conf" not found/);
+
+ $dummy_node->connect_ok(
+ q{service=my_srv servicefile='} . $srvfile_win_cared . q{'},
+ 'connection with both servicefile and PGSERVICEFILE',
+ sql => "SELECT 'connect4_1'",
+ expected_stdout => qr/connect4_1/);
}
$node->teardown_node;
diff --git a/src/test/authentication/t/003_peer.pl b/src/test/authentication/t/003_peer.pl
index f2320b62c87..c751fbdbaa5 100644
--- a/src/test/authentication/t/003_peer.pl
+++ b/src/test/authentication/t/003_peer.pl
@@ -171,7 +171,8 @@ test_role(
# Test with regular expression in user name map.
# Extract the last 3 characters from the system_user
-# or the entire system_user (if its length is <= -3).
+# or the entire system_user name (if its length is <= 3).
+# We trust this will not include any regex metacharacters.
my $regex_test_string = substr($system_user, -3);
# Success as the system user regular expression matches.
@@ -210,12 +211,17 @@ test_role(
log_like =>
[qr/connection authenticated: identity="$system_user" method=peer/]);
+# Create target role for \1 tests.
+my $mapped_name = "test${regex_test_string}map${regex_test_string}user";
+$node->safe_psql('postgres', "CREATE ROLE $mapped_name LOGIN");
+
# Success as the regular expression matches and \1 is replaced in the given
# subexpression.
-reset_pg_ident($node, 'mypeermap', qq{/^$system_user(.*)\$}, 'test\1mapuser');
+reset_pg_ident($node, 'mypeermap', qq{/^.*($regex_test_string)\$},
+ 'test\1map\1user');
test_role(
$node,
- qq{testmapuser},
+ $mapped_name,
'peer',
0,
'with regular expression in user name map with \1 replaced',
@@ -224,11 +230,11 @@ test_role(
# Success as the regular expression matches and \1 is replaced in the given
# subexpression, even if quoted.
-reset_pg_ident($node, 'mypeermap', qq{/^$system_user(.*)\$},
- '"test\1mapuser"');
+reset_pg_ident($node, 'mypeermap', qq{/^.*($regex_test_string)\$},
+ '"test\1map\1user"');
test_role(
$node,
- qq{testmapuser},
+ $mapped_name,
'peer',
0,
'with regular expression in user name map with quoted \1 replaced',