1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-25 01:39:28 +03:00

injection_points: Add support for string comparison in conditions

injection_points is extended so as a caller can attach a string value to
a point, checked with a value provided at runtime.  There are a couple
of use cases where this can be useful, to be able to target points for
specific object names.

PID and string conditions can be combined together, so as a point can be
allowed for a specific process and string value at the same time.

The condition string is capped at 255 characters.  As the attach SQL
function now takes an optional condition string, it is no longer STRICT,
and rejects NULL inputs.  Empty condition strings are also rejected, as
they don't make sense.

Author: Sami Imseih <samimseih@gmail.com>
Reviewed-by: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Discussion: https://postgr.es/m/CAA5RZ0tsGHu2h6YLnVu4HiK05q+gTE_9WVUAqihW2LSscAYS-g@mail.gmail.com
This commit is contained in:
Michael Paquier
2026-08-14 14:44:48 +09:00
parent 1f97b252df
commit 0fd73cdffc
5 changed files with 110 additions and 22 deletions

View File

@@ -307,6 +307,49 @@ SELECT injection_points_detach('TestConditionLocal1');
(1 row)
-- String match
SELECT injection_points_attach('TestConditionString', 'notice', 'MyString');
injection_points_attach
-------------------------
(1 row)
SELECT injection_points_run('TestConditionString', 'MyString'); -- notice
NOTICE: notice triggered for injection point TestConditionString (MyString)
injection_points_run
----------------------
(1 row)
SELECT injection_points_run('TestConditionString', 'WrongString'); -- nothing
injection_points_run
----------------------
(1 row)
SELECT injection_points_run('TestConditionString', NULL); -- nothing
injection_points_run
----------------------
(1 row)
SELECT injection_points_detach('TestConditionString');
injection_points_detach
-------------------------
(1 row)
-- string too long for attach
SELECT injection_points_attach('TestConditionTooLong', 'error', repeat('a', 256));
ERROR: injection point condition string too long (maximum of 255 characters)
-- empty condition string
SELECT injection_points_attach('TestConditionEmpty', 'error', '');
ERROR: injection point condition string must not be empty
-- NULL name or action (strictness)
SELECT injection_points_attach(NULL, 'error', 'MyString');
ERROR: injection point name must not be null
SELECT injection_points_attach('TestConditionNull', NULL, 'MyString');
ERROR: injection point action must not be null
-- Function variant for attach.
SELECT injection_points_attach(repeat('a', 64), 'injection_points',
'injection_notice', NULL);

View File

@@ -9,10 +9,11 @@
-- Attaches the action to the given injection point.
--
CREATE FUNCTION injection_points_attach(IN point_name TEXT,
IN action text)
IN action text,
IN condition_string text DEFAULT NULL)
RETURNS void
AS 'MODULE_PATHNAME', 'injection_points_attach'
LANGUAGE C STRICT PARALLEL UNSAFE;
LANGUAGE C PARALLEL UNSAFE;
--
-- injection_points_attach()

View File

@@ -151,21 +151,20 @@ injection_init_shmem(void)
* otherwise.
*/
static bool
injection_point_allowed(const InjectionPointCondition *condition)
injection_point_allowed(const InjectionPointCondition *condition,
const char *arg)
{
bool result = true;
/* Does not match the condition PID? */
if ((condition->type & INJ_CONDITION_PID) &&
MyProcPid != condition->pid)
return false;
switch (condition->type)
{
case INJ_CONDITION_PID:
if (MyProcPid != condition->pid)
result = false;
break;
case INJ_CONDITION_ALWAYS:
break;
}
/* Does not match the condition string? */
if ((condition->type & INJ_CONDITION_STRING) &&
(arg == NULL || strcmp(condition->str, arg) != 0))
return false;
return result;
return true;
}
/*
@@ -197,7 +196,7 @@ injection_error(const char *name, const void *private_data, void *arg)
const InjectionPointCondition *condition = private_data;
char *argstr = arg;
if (!injection_point_allowed(condition))
if (!injection_point_allowed(condition, argstr))
return;
if (argstr)
@@ -213,7 +212,7 @@ injection_notice(const char *name, const void *private_data, void *arg)
const InjectionPointCondition *condition = private_data;
char *argstr = arg;
if (!injection_point_allowed(condition))
if (!injection_point_allowed(condition, argstr))
return;
if (argstr)
@@ -244,12 +243,13 @@ injection_wait(const char *name, const void *private_data, void *arg)
int index = -1;
uint32 injection_wait_event = 0;
const InjectionPointCondition *condition = private_data;
char *argstr = arg;
int delay_us = 0;
if (inj_state == NULL)
injection_init_shmem();
if (!injection_point_allowed(condition))
if (!injection_point_allowed(condition, argstr))
return;
/*
@@ -312,11 +312,23 @@ PG_FUNCTION_INFO_V1(injection_points_attach);
Datum
injection_points_attach(PG_FUNCTION_ARGS)
{
char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
char *action = text_to_cstring(PG_GETARG_TEXT_PP(1));
char *name;
char *action;
char *str;
char *function;
InjectionPointCondition condition = {0};
if (PG_ARGISNULL(0))
elog(ERROR, "injection point name must not be null");
if (PG_ARGISNULL(1))
elog(ERROR, "injection point action must not be null");
name = text_to_cstring(PG_GETARG_TEXT_PP(0));
action = text_to_cstring(PG_GETARG_TEXT_PP(1));
str = PG_ARGISNULL(2) ? NULL
: text_to_cstring(PG_GETARG_TEXT_PP(2));
if (strcmp(action, "error") == 0)
function = "injection_error";
else if (strcmp(action, "notice") == 0)
@@ -328,10 +340,21 @@ injection_points_attach(PG_FUNCTION_ARGS)
if (injection_point_local)
{
condition.type = INJ_CONDITION_PID;
condition.type |= INJ_CONDITION_PID;
condition.pid = MyProcPid;
}
if (str)
{
if (str[0] == '\0')
elog(ERROR, "injection point condition string must not be empty");
if (strlen(str) >= INJ_DATA_MAXLEN)
elog(ERROR, "injection point condition string too long (maximum of %d characters)",
INJ_DATA_MAXLEN - 1);
condition.type |= INJ_CONDITION_STRING;
strlcpy(condition.str, str, INJ_DATA_MAXLEN);
}
InjectionPointAttach(name, "injection_points", function, &condition,
sizeof(InjectionPointCondition));

View File

@@ -15,10 +15,14 @@
#ifndef INJECTION_POINTS_H
#define INJECTION_POINTS_H
/* Maximum length for an injection point condition string */
#define INJ_DATA_MAXLEN 256
typedef enum InjectionPointConditionType
{
INJ_CONDITION_ALWAYS = 0, /* always run */
INJ_CONDITION_PID, /* PID restriction */
INJ_CONDITION_NONE = 0, /* no restrictions to apply */
INJ_CONDITION_PID = 1 << 0, /* PID restriction */
INJ_CONDITION_STRING = 1 << 1, /* string to compare with arg */
} InjectionPointConditionType;
typedef struct InjectionPointCondition
@@ -28,6 +32,9 @@ typedef struct InjectionPointCondition
/* ID of the process where the injection point is allowed to run */
int pid;
/* String to compare with an argument at runtime */
char str[INJ_DATA_MAXLEN];
} InjectionPointCondition;
#endif /* INJECTION_POINTS_H */

View File

@@ -88,6 +88,20 @@ SELECT injection_points_detach('TestConditionError');
SELECT injection_points_attach('TestConditionLocal1', 'error');
SELECT injection_points_detach('TestConditionLocal1');
-- String match
SELECT injection_points_attach('TestConditionString', 'notice', 'MyString');
SELECT injection_points_run('TestConditionString', 'MyString'); -- notice
SELECT injection_points_run('TestConditionString', 'WrongString'); -- nothing
SELECT injection_points_run('TestConditionString', NULL); -- nothing
SELECT injection_points_detach('TestConditionString');
-- string too long for attach
SELECT injection_points_attach('TestConditionTooLong', 'error', repeat('a', 256));
-- empty condition string
SELECT injection_points_attach('TestConditionEmpty', 'error', '');
-- NULL name or action (strictness)
SELECT injection_points_attach(NULL, 'error', 'MyString');
SELECT injection_points_attach('TestConditionNull', NULL, 'MyString');
-- Function variant for attach.
SELECT injection_points_attach(repeat('a', 64), 'injection_points',
'injection_notice', NULL);