From 8c8d3564c124dd9f1069d7cca3438079cfff0703 Mon Sep 17 00:00:00 2001 From: Sami Imseih Date: Thu, 9 Apr 2026 17:19:45 +0000 Subject: [PATCH v2 1/2] Allow a condition string in an injection point Extend the injection_points test module so a caller can attach an arbitrary condition string that is later matched against the argument passed when the injection point is run. This makes conditions more flexible, for example matching on a relation name or OID. The condition string is capped at 255 characters, which seems good enough for now. This could be raised later, and a variable-length string could be supported, but that hardly seems worth it at this point. As the attach function now takes an optional condition string, it is no longer STRICT, so it explicitly rejects a NULL name or action. Empty condition strings are also rejected at attach time. --- .../expected/injection_points.out | 75 +++++++++++++++++++ .../injection_points--1.0.sql | 5 +- .../injection_points/injection_points.c | 63 +++++++++++----- .../injection_points/injection_points.h | 9 ++- .../injection_points/sql/injection_points.sql | 24 ++++++ 5 files changed, 155 insertions(+), 21 deletions(-) diff --git a/src/test/modules/injection_points/expected/injection_points.out b/src/test/modules/injection_points/expected/injection_points.out index a3ccaee5472..2c9d7e4548e 100644 --- a/src/test/modules/injection_points/expected/injection_points.out +++ b/src/test/modules/injection_points/expected/injection_points.out @@ -262,6 +262,32 @@ NOTICE: notice triggered for injection point TestConditionLocal2 (1 row) +-- Local injection point condition string matching. +SELECT injection_points_attach('TestConditionLocalString', 'notice', 'LocalData'); + injection_points_attach +------------------------- + +(1 row) + +SELECT injection_points_run('TestConditionLocalString', 'LocalData'); -- notice +NOTICE: notice triggered for injection point TestConditionLocalString (LocalData) + injection_points_run +---------------------- + +(1 row) + +SELECT injection_points_run('TestConditionLocalString', 'WrongData'); -- nothing + injection_points_run +---------------------- + +(1 row) + +SELECT injection_points_run('TestConditionLocalString', NULL); -- nothing + injection_points_run +---------------------- + +(1 row) + SELECT pg_backend_pid() AS oldpid \gset -- reload, local injection points should be gone. \c @@ -285,6 +311,12 @@ SELECT injection_points_run('TestConditionLocal2'); -- nothing (1 row) +SELECT injection_points_run('TestConditionLocalString', 'LocalData'); -- nothing + injection_points_run +---------------------- + +(1 row) + SELECT injection_points_run('TestConditionError'); -- error ERROR: error triggered for injection point TestConditionError SELECT injection_points_detach('TestConditionError'); @@ -307,6 +339,49 @@ SELECT injection_points_detach('TestConditionLocal1'); (1 row) +-- injection point condition string matching. +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) + +-- condition string too long for attach. +SELECT injection_points_attach('TestInjectionError', 'error', repeat('a', 256)); +ERROR: injection point condition string too long (maximum of 255 characters) +-- empty condition string is rejected at attach time. +SELECT injection_points_attach('TestConditionEmpty', 'error', ''); +ERROR: injection point condition string must not be empty +-- NULL name or action is rejected (function is not STRICT). +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); diff --git a/src/test/modules/injection_points/injection_points--1.0.sql b/src/test/modules/injection_points/injection_points--1.0.sql index 861c7355d4e..2efb307f5bf 100644 --- a/src/test/modules/injection_points/injection_points--1.0.sql +++ b/src/test/modules/injection_points/injection_points--1.0.sql @@ -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() diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c index 0ed1dc7c8a7..0208946fc70 100644 --- a/src/test/modules/injection_points/injection_points.c +++ b/src/test/modules/injection_points/injection_points.c @@ -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; + /* did 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; - } + /* did 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,27 @@ 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)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("injection point name must not be null"))); + + if (PG_ARGISNULL(1)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("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 +344,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)); diff --git a/src/test/modules/injection_points/injection_points.h b/src/test/modules/injection_points/injection_points.h index caabc4ffb32..3e8c6191ef0 100644 --- a/src/test/modules/injection_points/injection_points.h +++ b/src/test/modules/injection_points/injection_points.h @@ -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_PID = 1 << 0, /* PID restriction */ + INJ_CONDITION_STRING = 1 << 1, /* generic string match against 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 match against the argument at run time */ + char str[INJ_DATA_MAXLEN]; } InjectionPointCondition; #endif /* INJECTION_POINTS_H */ diff --git a/src/test/modules/injection_points/sql/injection_points.sql b/src/test/modules/injection_points/sql/injection_points.sql index ba14df706ef..b8ef4dce99b 100644 --- a/src/test/modules/injection_points/sql/injection_points.sql +++ b/src/test/modules/injection_points/sql/injection_points.sql @@ -72,6 +72,12 @@ SELECT injection_points_attach('TestConditionLocal2', 'notice'); SELECT injection_points_run('TestConditionLocal1'); -- error SELECT injection_points_run('TestConditionLocal2'); -- notice +-- Local injection point condition string matching. +SELECT injection_points_attach('TestConditionLocalString', 'notice', 'LocalData'); +SELECT injection_points_run('TestConditionLocalString', 'LocalData'); -- notice +SELECT injection_points_run('TestConditionLocalString', 'WrongData'); -- nothing +SELECT injection_points_run('TestConditionLocalString', NULL); -- nothing + SELECT pg_backend_pid() AS oldpid \gset -- reload, local injection points should be gone. @@ -81,6 +87,7 @@ SELECT pg_backend_pid() AS oldpid \gset SELECT wait_pid(:'oldpid'); SELECT injection_points_run('TestConditionLocal1'); -- nothing SELECT injection_points_run('TestConditionLocal2'); -- nothing +SELECT injection_points_run('TestConditionLocalString', 'LocalData'); -- nothing SELECT injection_points_run('TestConditionError'); -- error SELECT injection_points_detach('TestConditionError'); -- Attaching injection points that use the same name as one defined locally @@ -88,6 +95,23 @@ SELECT injection_points_detach('TestConditionError'); SELECT injection_points_attach('TestConditionLocal1', 'error'); SELECT injection_points_detach('TestConditionLocal1'); +-- injection point condition string matching. +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'); + +-- condition string too long for attach. +SELECT injection_points_attach('TestInjectionError', 'error', repeat('a', 256)); + +-- empty condition string is rejected at attach time. +SELECT injection_points_attach('TestConditionEmpty', 'error', ''); + +-- NULL name or action is rejected (function is not STRICT). +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); -- 2.47.3