Skip to content

Commit 20e0e7d

Browse files
committed
Add test for early backend startup errors
The new test tests the libpq fallback behavior on an early error, which was fixed in the previous commit. This adds an IS_INJECTION_POINT_ATTACHED() macro, to allow writing injected test code alongside the normal source code. In principle, the new test could've been implemented by an extra test module with a callback that sets the FrontendProtocol global variable, but I think it's more clear to have the test code right where the injection point is, because it has pretty intimate knowledge of the surrounding context it runs in. Reviewed-by: Michael Paquier Discussion: https://www.postgresql.org/message-id/CAOYmi%2Bnwvu21mJ4DYKUa98HdfM_KZJi7B1MhyXtnsyOO-PB6Ww%40mail.gmail.com
1 parent b9e5249 commit 20e0e7d

File tree

7 files changed

+118
-1
lines changed

7 files changed

+118
-1
lines changed

doc/src/sgml/xfunc.sgml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3672,6 +3672,31 @@ custom_injection_callback(const char *name, const void *private_data)
36723672
logic.
36733673
</para>
36743674

3675+
<para>
3676+
An alternative way to define the action to take when an injection point
3677+
is reached is to add the testing code alongside the normal source
3678+
code. This can be useful if the action e.g. depends on local variables
3679+
that are not accessible to loaded modules. The
3680+
<function>IS_INJECTION_POINT_ATTACHED</function> macro can then be used
3681+
to check if an injection point is attached, for example:
3682+
<programlisting>
3683+
#ifdef USE_INJECTION_POINTS
3684+
if (IS_INJECTION_POINT_ATTACHED("before-foobar"))
3685+
{
3686+
/* change a local variable if injection point is attached */
3687+
local_var = 123;
3688+
3689+
/* also execute the callback */
3690+
INJECTION_POINT_CACHED("before-foobar");
3691+
}
3692+
#endif
3693+
</programlisting>
3694+
Note that the callback attached to the injection point will not be
3695+
executed by the <function>IS_INJECTION_POINT_ATTACHED</function>
3696+
macro. If you want to execute the callback, you must also call
3697+
<function>INJECTION_POINT_CACHED</function> like in the above example.
3698+
</para>
3699+
36753700
<para>
36763701
Optionally, it is possible to detach an injection point by calling:
36773702
<programlisting>

src/backend/tcop/backend_startup.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "tcop/backend_startup.h"
3434
#include "tcop/tcopprot.h"
3535
#include "utils/builtins.h"
36+
#include "utils/injection_point.h"
3637
#include "utils/memutils.h"
3738
#include "utils/ps_status.h"
3839
#include "utils/timeout.h"
@@ -213,6 +214,21 @@ BackendInitialize(ClientSocket *client_sock, CAC_state cac)
213214
remote_host)));
214215
}
215216

217+
/* For testing client error handling */
218+
#ifdef USE_INJECTION_POINTS
219+
INJECTION_POINT("backend-initialize");
220+
if (IS_INJECTION_POINT_ATTACHED("backend-initialize-v2-error"))
221+
{
222+
/*
223+
* This simulates an early error from a pre-v14 server, which used the
224+
* version 2 protocol for any errors that occurred before processing
225+
* the startup packet.
226+
*/
227+
FrontendProtocol = PG_PROTOCOL(2, 0);
228+
elog(FATAL, "protocol version 2 error triggered");
229+
}
230+
#endif
231+
216232
/*
217233
* If we did a reverse lookup to name, we might as well save the results
218234
* rather than possibly repeating the lookup during authentication.

src/backend/utils/misc/injection_point.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,3 +570,17 @@ InjectionPointCached(const char *name)
570570
elog(ERROR, "Injection points are not supported by this build");
571571
#endif
572572
}
573+
574+
/*
575+
* Test if an injection point is defined.
576+
*/
577+
bool
578+
IsInjectionPointAttached(const char *name)
579+
{
580+
#ifdef USE_INJECTION_POINTS
581+
return InjectionPointCacheRefresh(name) != NULL;
582+
#else
583+
elog(ERROR, "Injection points are not supported by this build");
584+
return false; /* silence compiler */
585+
#endif
586+
}

src/include/utils/injection_point.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
#define INJECTION_POINT_LOAD(name) InjectionPointLoad(name)
1919
#define INJECTION_POINT(name) InjectionPointRun(name)
2020
#define INJECTION_POINT_CACHED(name) InjectionPointCached(name)
21+
#define IS_INJECTION_POINT_ATTACHED(name) IsInjectionPointAttached(name)
2122
#else
2223
#define INJECTION_POINT_LOAD(name) ((void) name)
2324
#define INJECTION_POINT(name) ((void) name)
2425
#define INJECTION_POINT_CACHED(name) ((void) name)
26+
#define IS_INJECTION_POINT_ATTACHED(name) (false)
2527
#endif
2628

2729
/*
@@ -41,6 +43,7 @@ extern void InjectionPointAttach(const char *name,
4143
extern void InjectionPointLoad(const char *name);
4244
extern void InjectionPointRun(const char *name);
4345
extern void InjectionPointCached(const char *name);
46+
extern bool IsInjectionPointAttached(const char *name);
4447
extern bool InjectionPointDetach(const char *name);
4548

4649
#ifdef EXEC_BACKEND

src/interfaces/libpq/Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
#
1010
#-------------------------------------------------------------------------
1111

12+
EXTRA_INSTALL=src/test/modules/injection_points
13+
1214
subdir = src/interfaces/libpq
1315
top_builddir = ../../..
1416
include $(top_builddir)/src/Makefile.global
1517

16-
export with_ssl with_gssapi with_krb_srvnam
18+
export with_ssl with_gssapi with_krb_srvnam enable_injection_points
1719

1820
PGFILEDESC = "PostgreSQL Access Library"
1921

src/interfaces/libpq/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ tests += {
121121
't/005_negotiate_encryption.pl',
122122
],
123123
'env': {
124+
'enable_injection_points': get_option('injection_points') ? 'yes' : 'no',
124125
'with_ssl': ssl_library,
125126
'with_gssapi': gssapi.found() ? 'yes' : 'no',
126127
'with_krb_srvnam': 'postgres',

src/interfaces/libpq/t/005_negotiate_encryption.pl

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@
9090
$ENV{PG_TEST_EXTRA} && $ENV{PG_TEST_EXTRA} =~ /\bkerberos\b/;
9191
my $ssl_supported = $ENV{with_ssl} eq 'openssl';
9292

93+
my $injection_points_supported = $ENV{enable_injection_points} eq 'yes';
94+
9395
###
9496
### Prepare test server for GSSAPI and SSL authentication, with a few
9597
### different test users and helper functions. We don't actually
@@ -155,6 +157,10 @@
155157
$node->safe_psql('postgres', 'CREATE USER nossluser;');
156158
$node->safe_psql('postgres', 'CREATE USER gssuser;');
157159
$node->safe_psql('postgres', 'CREATE USER nogssuser;');
160+
if ($injection_points_supported != 0)
161+
{
162+
$node->safe_psql('postgres', 'CREATE EXTENSION injection_points;');
163+
}
158164

159165
my $unixdir = $node->safe_psql('postgres', 'SHOW unix_socket_directories;');
160166
chomp($unixdir);
@@ -312,6 +318,29 @@ BEGIN
312318
['disable'], \@all_sslmodes, \@all_sslnegotiations,
313319
parse_table($test_table));
314320

321+
if ($injection_points_supported != 0)
322+
{
323+
$node->safe_psql(
324+
'postgres',
325+
"SELECT injection_points_attach('backend-initialize', 'error');",
326+
connstr => "user=localuser host=$unixdir");
327+
connect_test(
328+
$node,
329+
"user=testuser sslmode=prefer",
330+
'connect, backenderror -> fail');
331+
$node->restart;
332+
333+
$node->safe_psql(
334+
'postgres',
335+
"SELECT injection_points_attach('backend-initialize-v2-error', 'error');",
336+
connstr => "user=localuser host=$unixdir");
337+
connect_test(
338+
$node,
339+
"user=testuser sslmode=prefer",
340+
'connect, v2error -> fail');
341+
$node->restart;
342+
}
343+
315344
# Disable SSL again
316345
$node->adjust_conf('postgresql.conf', 'ssl', 'off');
317346
$node->reload;
@@ -393,6 +422,29 @@ BEGIN
393422
test_matrix($node, [ 'testuser', 'gssuser', 'nogssuser' ],
394423
\@all_gssencmodes, $sslmodes, $sslnegotiations,
395424
parse_table($test_table));
425+
426+
if ($injection_points_supported != 0)
427+
{
428+
$node->safe_psql(
429+
'postgres',
430+
"SELECT injection_points_attach('backend-initialize', 'error');",
431+
connstr => "user=localuser host=$unixdir");
432+
connect_test(
433+
$node,
434+
"user=testuser gssencmode=prefer sslmode=disable",
435+
'connect, backenderror, reconnect, backenderror -> fail');
436+
$node->restart;
437+
438+
$node->safe_psql(
439+
'postgres',
440+
"SELECT injection_points_attach('backend-initialize-v2-error', 'error');",
441+
connstr => "user=localuser host=$unixdir");
442+
connect_test(
443+
$node,
444+
"user=testuser gssencmode=prefer sslmode=disable",
445+
'connect, v2error -> fail');
446+
$node->restart;
447+
}
396448
}
397449

398450
###
@@ -738,6 +790,10 @@ sub parse_log_events
738790
push @events, "gssreject" if $line =~ /GSSENCRequest rejected/;
739791
push @events, "authfail" if $line =~ /no pg_hba.conf entry/;
740792
push @events, "authok" if $line =~ /connection authenticated/;
793+
push @events, "backenderror"
794+
if $line =~ /error triggered for injection point backend-/;
795+
push @events, "v2error"
796+
if $line =~ /protocol version 2 error triggered/;
741797
}
742798

743799
# No events at all is represented by "-"

0 commit comments

Comments
 (0)