From 53cb84a661717cae179f09e8a16755202259c5b9 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 5 Jun 2024 07:41:20 +0900
Subject: [PATCH v2 2/2] Extend injection points with optional runtime
 arguments

This extends injections points with a 1-argument flavor, so as it
becomes possible for callbacks to pass down values coming from the code
paths where an injection point is defined.  The primary use case that
can be covered in this function is runtime manipulation of a given
stack, where it would be possible to corrupt a running state, based on a
runtime set of conditions.

For example, imagine a class of failures in the solar flare category
causing bits to flip on a page.
---
 src/include/utils/injection_point.h           |  9 ++-
 src/backend/utils/misc/injection_point.c      | 69 ++++++++++++++++---
 .../expected/injection_points.out             | 31 +++++++++
 .../injection_points--1.0.sql                 | 10 +++
 .../injection_points/injection_points.c       | 39 ++++++++++-
 .../injection_points/sql/injection_points.sql |  9 +++
 doc/src/sgml/xfunc.sgml                       |  9 ++-
 7 files changed, 162 insertions(+), 14 deletions(-)

diff --git a/src/include/utils/injection_point.h b/src/include/utils/injection_point.h
index bd3a62425c..08c4811e89 100644
--- a/src/include/utils/injection_point.h
+++ b/src/include/utils/injection_point.h
@@ -17,9 +17,11 @@
 #ifdef USE_INJECTION_POINTS
 #define INJECTION_POINT_LOAD(name) InjectionPointLoad(name)
 #define INJECTION_POINT(name) InjectionPointRun(name)
+#define INJECTION_POINT_1ARG(name, arg1) InjectionPointRun1Arg(name, arg1)
 #else
 #define INJECTION_POINT_LOAD(name) ((void) name)
 #define INJECTION_POINT(name) ((void) name)
+#define INJECTION_POINT_1ARG(name) ((void) name, (void) arg1)
 #endif
 
 /*
@@ -27,6 +29,9 @@
  */
 typedef void (*InjectionPointCallback) (const char *name,
 										const void *private_data);
+typedef void (*InjectionPointCallback1Arg) (const char *name,
+											const void *private_data,
+											const void *arg1);
 
 extern Size InjectionPointShmemSize(void);
 extern void InjectionPointShmemInit(void);
@@ -35,9 +40,11 @@ extern void InjectionPointAttach(const char *name,
 								 const char *library,
 								 const char *function,
 								 const void *private_data,
-								 int private_data_size);
+								 int private_data_size,
+								 int num_args);
 extern void InjectionPointLoad(const char *name);
 extern void InjectionPointRun(const char *name);
+extern void InjectionPointRun1Arg(const char *name, void *arg1);
 extern bool InjectionPointDetach(const char *name);
 
 #endif							/* INJECTION_POINT_H */
diff --git a/src/backend/utils/misc/injection_point.c b/src/backend/utils/misc/injection_point.c
index f02ed6c08b..af76642a5f 100644
--- a/src/backend/utils/misc/injection_point.c
+++ b/src/backend/utils/misc/injection_point.c
@@ -56,6 +56,9 @@ typedef struct InjectionPointEntry
 	 * callbacks, registered when attached.
 	 */
 	char		private_data[INJ_PRIVATE_MAXLEN];
+
+	/* Number of arguments used by the callback */
+	int			num_args;
 } InjectionPointEntry;
 
 #define INJECTION_POINT_HASH_INIT_SIZE	16
@@ -69,7 +72,12 @@ typedef struct InjectionPointCacheEntry
 {
 	char		name[INJ_NAME_MAXLEN];
 	char		private_data[INJ_PRIVATE_MAXLEN];
-	InjectionPointCallback callback;
+	int			num_args;
+	union
+	{
+		InjectionPointCallback callback;
+		InjectionPointCallback1Arg callback_1arg;
+	}			data;
 } InjectionPointCacheEntry;
 
 static HTAB *InjectionPointCache = NULL;
@@ -81,8 +89,9 @@ static HTAB *InjectionPointCache = NULL;
  */
 static void
 injection_point_cache_add(const char *name,
-						  InjectionPointCallback callback,
-						  const void *private_data)
+						  void *callback,
+						  const void *private_data,
+						  int num_args)
 {
 	InjectionPointCacheEntry *entry;
 	bool		found;
@@ -107,7 +116,14 @@ injection_point_cache_add(const char *name,
 
 	Assert(!found);
 	strlcpy(entry->name, name, sizeof(entry->name));
-	entry->callback = callback;
+	entry->num_args = num_args;
+	if (num_args == 0)
+		entry->data.callback = (InjectionPointCallback) callback;
+	else if (num_args == 1)
+		entry->data.callback_1arg = (InjectionPointCallback1Arg) callback;
+	else
+		elog(ERROR, "unsupported number of arguments for injection point \"%s\"",
+			 name);
 	if (private_data != NULL)
 		memcpy(entry->private_data, private_data, INJ_PRIVATE_MAXLEN);
 }
@@ -156,7 +172,8 @@ injection_point_cache_load(InjectionPointEntry *entry_by_name)
 
 	/* add it to the local cache when found */
 	injection_point_cache_add(entry_by_name->name, injection_callback_local,
-							  entry_by_name->private_data);
+							  entry_by_name->private_data,
+							  entry_by_name->num_args);
 }
 
 /*
@@ -229,7 +246,8 @@ InjectionPointAttach(const char *name,
 					 const char *library,
 					 const char *function,
 					 const void *private_data,
-					 int private_data_size)
+					 int private_data_size,
+					 int num_args)
 {
 #ifdef USE_INJECTION_POINTS
 	InjectionPointEntry *entry_by_name;
@@ -271,6 +289,7 @@ InjectionPointAttach(const char *name,
 	entry_by_name->function[INJ_FUNC_MAXLEN - 1] = '\0';
 	if (private_data != NULL)
 		memcpy(entry_by_name->private_data, private_data, private_data_size);
+	entry_by_name->num_args = num_args;
 
 	LWLockRelease(InjectionPointLock);
 
@@ -346,13 +365,13 @@ InjectionPointLoad(const char *name)
 }
 
 /*
- * Execute an injection point, if defined.
+ * Workhorse for execution of an injection point, if defined.
  *
  * Check first the shared hash table, and adapt the local cache depending
  * on that as it could be possible that an entry to run has been removed.
  */
-void
-InjectionPointRun(const char *name)
+static inline void
+InjectionPointRunInternal(const char *name, int num_args, void *arg1)
 {
 #ifdef USE_INJECTION_POINTS
 	InjectionPointEntry *entry_by_name;
@@ -387,8 +406,38 @@ InjectionPointRun(const char *name)
 
 	/* Now loaded, so get it. */
 	cache_entry = injection_point_cache_get(name);
-	cache_entry->callback(name, cache_entry->private_data);
+
+	/* Check the number of arguments with the cache. */
+	if (cache_entry->num_args != num_args)
+		elog(ERROR, "incorrect number of arguments for injection point \"%s\": defined %d but expected %d",
+			 name, cache_entry->num_args, num_args);
+
+	if (cache_entry->num_args == 0)
+		cache_entry->data.callback(name, cache_entry->private_data);
+	else if (cache_entry->num_args == 1)
+		cache_entry->data.callback_1arg(name, cache_entry->private_data, arg1);
+	else
+	{
+		/* cannot be reached */
+		Assert(false);
+	}
 #else
 	elog(ERROR, "Injection points are not supported by this build");
 #endif
 }
+
+/*
+ * Execute an injection point, with no arguments.
+ */
+void
+InjectionPointRun(const char *name)
+{
+	InjectionPointRunInternal(name, 0, NULL);
+}
+
+/* 1-argument version */
+void
+InjectionPointRun1Arg(const char *name, void *arg1)
+{
+	InjectionPointRunInternal(name, 1, arg1);
+}
diff --git a/src/test/modules/injection_points/expected/injection_points.out b/src/test/modules/injection_points/expected/injection_points.out
index 2f60da900b..86043ce1c6 100644
--- a/src/test/modules/injection_points/expected/injection_points.out
+++ b/src/test/modules/injection_points/expected/injection_points.out
@@ -122,12 +122,43 @@ NOTICE:  notice triggered for injection point TestInjectionLog2
  
 (1 row)
 
+-- 1-argument runs
+SELECT injection_points_run_1arg('TestInjectionLog2', 'blah'); -- error
+ERROR:  incorrect number of arguments for injection point "TestInjectionLog2": defined 0 but expected 1
+SELECT injection_points_attach('TestInjectionLogArg1', 'notice_1arg');
+ injection_points_attach 
+-------------------------
+ 
+(1 row)
+
+SELECT injection_points_run('TestInjectionLogArg1'); -- error
+ERROR:  incorrect number of arguments for injection point "TestInjectionLogArg1": defined 1 but expected 0
+SELECT injection_points_run_1arg('TestInjectionLogArg1', 'blah'); -- notice
+NOTICE:  notice triggered for injection point TestInjectionLogArg1: blah
+ injection_points_run_1arg 
+---------------------------
+ 
+(1 row)
+
+SELECT injection_points_run_1arg('TestInjectionLogArg1', 'foobar'); -- notice
+NOTICE:  notice triggered for injection point TestInjectionLogArg1: foobar
+ injection_points_run_1arg 
+---------------------------
+ 
+(1 row)
+
 SELECT injection_points_detach('TestInjectionLog2');
  injection_points_detach 
 -------------------------
  
 (1 row)
 
+SELECT injection_points_detach('TestInjectionLogArg1');
+ injection_points_detach 
+-------------------------
+ 
+(1 row)
+
 -- Loading
 SELECT injection_points_load('TestInjectionLogLoad'); -- nothing
  injection_points_load 
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 e275c2cf5b..5bd7dad8e2 100644
--- a/src/test/modules/injection_points/injection_points--1.0.sql
+++ b/src/test/modules/injection_points/injection_points--1.0.sql
@@ -55,6 +55,16 @@ RETURNS void
 AS 'MODULE_PATHNAME', 'injection_points_set_local'
 LANGUAGE C STRICT PARALLEL UNSAFE;
 
+--
+-- injection_points_run_1arg()
+--
+-- Executes the action attached to the injection point.
+--
+CREATE FUNCTION injection_points_run_1arg(IN point_name TEXT, IN arg1 TEXT)
+RETURNS void
+AS 'MODULE_PATHNAME', 'injection_points_run_1arg'
+LANGUAGE C STRICT PARALLEL UNSAFE;
+
 --
 -- injection_points_detach()
 --
diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c
index 2e3da997b1..3640ee6f93 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -90,6 +90,9 @@ extern PGDLLEXPORT void injection_error(const char *name,
 										const void *private_data);
 extern PGDLLEXPORT void injection_notice(const char *name,
 										 const void *private_data);
+extern PGDLLEXPORT void injection_notice_1arg(const char *name,
+											  const void *private_data,
+											  const void *arg1);
 extern PGDLLEXPORT void injection_wait(const char *name,
 									   const void *private_data);
 
@@ -260,6 +263,19 @@ injection_wait(const char *name, const void *private_data)
 	SpinLockRelease(&inj_state->lock);
 }
 
+void
+injection_notice_1arg(const char *name, const void *private_data,
+					  const void *arg1)
+{
+	InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
+	const char *str = (const char *) arg1;
+
+	if (!injection_point_allowed(condition))
+		return;
+
+	elog(NOTICE, "notice triggered for injection point %s: %s", name, str);
+}
+
 /*
  * SQL function for creating an injection point.
  */
@@ -271,6 +287,7 @@ injection_points_attach(PG_FUNCTION_ARGS)
 	char	   *action = text_to_cstring(PG_GETARG_TEXT_PP(1));
 	char	   *function;
 	InjectionPointCondition condition = {0};
+	int			num_args = 0;
 
 	if (strcmp(action, "error") == 0)
 		function = "injection_error";
@@ -278,6 +295,11 @@ injection_points_attach(PG_FUNCTION_ARGS)
 		function = "injection_notice";
 	else if (strcmp(action, "wait") == 0)
 		function = "injection_wait";
+	else if (strcmp(action, "notice_1arg") == 0)
+	{
+		function = "injection_notice_1arg";
+		num_args = 1;
+	}
 	else
 		elog(ERROR, "incorrect action \"%s\" for injection point creation", action);
 
@@ -288,7 +310,7 @@ injection_points_attach(PG_FUNCTION_ARGS)
 	}
 
 	InjectionPointAttach(name, "injection_points", function, &condition,
-						 sizeof(InjectionPointCondition));
+						 sizeof(InjectionPointCondition), num_args);
 
 	if (injection_point_local)
 	{
@@ -395,6 +417,21 @@ injection_points_set_local(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for triggering an injection point, 1-argument flavor.
+ */
+PG_FUNCTION_INFO_V1(injection_points_run_1arg);
+Datum
+injection_points_run_1arg(PG_FUNCTION_ARGS)
+{
+	char	   *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
+	char	   *arg1 = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+	INJECTION_POINT_1ARG(name, (void *) arg1);
+
+	PG_RETURN_VOID();
+}
+
 /*
  * SQL function for dropping an injection point.
  */
diff --git a/src/test/modules/injection_points/sql/injection_points.sql b/src/test/modules/injection_points/sql/injection_points.sql
index fabf0a8823..4e78a29154 100644
--- a/src/test/modules/injection_points/sql/injection_points.sql
+++ b/src/test/modules/injection_points/sql/injection_points.sql
@@ -39,7 +39,16 @@ SELECT injection_points_run('TestInjectionLog2'); -- notice
 SELECT injection_points_detach('TestInjectionLog'); -- fails
 
 SELECT injection_points_run('TestInjectionLog2'); -- notice
+
+-- 1-argument runs
+SELECT injection_points_run_1arg('TestInjectionLog2', 'blah'); -- error
+SELECT injection_points_attach('TestInjectionLogArg1', 'notice_1arg');
+SELECT injection_points_run('TestInjectionLogArg1'); -- error
+SELECT injection_points_run_1arg('TestInjectionLogArg1', 'blah'); -- notice
+SELECT injection_points_run_1arg('TestInjectionLogArg1', 'foobar'); -- notice
+
 SELECT injection_points_detach('TestInjectionLog2');
+SELECT injection_points_detach('TestInjectionLogArg1');
 
 -- Loading
 SELECT injection_points_load('TestInjectionLogLoad'); -- nothing
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index f9f46e1835..934d355d32 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3609,13 +3609,15 @@ uint32 WaitEventExtensionNew(const char *wait_event_name)
      macro:
 <programlisting>
 INJECTION_POINT(name);
+INJECTION_POINT_1ARG(name, arg1);
 </programlisting>
 
      There are a few injection points already declared at strategic points
      within the server code. After adding a new injection point the code needs
      to be compiled in order for that injection point to be available in the
      binary. Add-ins written in C-language can declare injection points in
-     their own code using the same macro.
+     their own code using the same macro. It is possible to pass down arguments
+     to callbacks for runtime checks.
     </para>
 
     <para>
@@ -3640,7 +3642,8 @@ extern void InjectionPointAttach(const char *name,
                                  const char *library,
                                  const char *function,
                                  const void *private_data,
-                                 int private_data_size);
+                                 int private_data_size,
+                                 int num_args);
 </programlisting>
 
      <literal>name</literal> is the name of the injection point, which when
@@ -3648,6 +3651,8 @@ extern void InjectionPointAttach(const char *name,
      loaded from <literal>library</literal>. <literal>private_data</literal>
      is a private area of data of size <literal>private_data_size</literal>
      given as argument to the callback when executed.
+     <literal>num_args</literal> is the number of arguments used by the
+     callback.
     </para>
 
     <para>
-- 
2.43.0

