From 0fd73cdffc18d3914311122bf4fbb5acbae626f3 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Fri, 14 Aug 2026 14:44:48 +0900 Subject: [PATCH] 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 Reviewed-by: Bertrand Drouvot Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/CAA5RZ0tsGHu2h6YLnVu4HiK05q+gTE_9WVUAqihW2LSscAYS-g@mail.gmail.com --- .../expected/injection_points.out | 43 ++++++++++++++ .../injection_points--1.0.sql | 5 +- .../injection_points/injection_points.c | 59 +++++++++++++------ .../injection_points/injection_points.h | 11 +++- .../injection_points/sql/injection_points.sql | 14 +++++ 5 files changed, 110 insertions(+), 22 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..cc50b547654 100644 --- a/src/test/modules/injection_points/expected/injection_points.out +++ b/src/test/modules/injection_points/expected/injection_points.out @@ -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); 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..66d8158d0c2 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; + /* 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)); diff --git a/src/test/modules/injection_points/injection_points.h b/src/test/modules/injection_points/injection_points.h index caabc4ffb32..1f02642b492 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_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 */ diff --git a/src/test/modules/injection_points/sql/injection_points.sql b/src/test/modules/injection_points/sql/injection_points.sql index ba14df706ef..f9cc93b514b 100644 --- a/src/test/modules/injection_points/sql/injection_points.sql +++ b/src/test/modules/injection_points/sql/injection_points.sql @@ -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);