From ed617725d1e6a156363384ed5fcbf4e79b5f8ab4 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 20 May 2024 11:50:42 +0900
Subject: [PATCH 1/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      | 92 +++++++++++++------
 .../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, 169 insertions(+), 30 deletions(-)

diff --git a/src/include/utils/injection_point.h b/src/include/utils/injection_point.h
index a61d5d4439..c2c0840706 100644
--- a/src/include/utils/injection_point.h
+++ b/src/include/utils/injection_point.h
@@ -16,8 +16,10 @@
  */
 #ifdef USE_INJECTION_POINTS
 #define INJECTION_POINT(name) InjectionPointRun(name)
+#define INJECTION_POINT_1ARG(name, arg1) InjectionPointRun1Arg(name, arg1)
 #else
 #define INJECTION_POINT(name) ((void) name)
+#define INJECTION_POINT_1ARG(name) ((void) name, (void) arg1)
 #endif
 
 /*
@@ -25,6 +27,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);
@@ -33,8 +38,10 @@ 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 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 5c2a0d2297..2bcdb2708c 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);
 }
@@ -134,15 +150,12 @@ injection_point_cache_remove(const char *name)
  *
  * Retrieve an injection point from the local cache, if any.
  */
-static InjectionPointCallback
-injection_point_cache_get(const char *name, const void **private_data)
+static InjectionPointCacheEntry *
+injection_point_cache_get(const char *name)
 {
 	bool		found;
 	InjectionPointCacheEntry *entry;
 
-	if (private_data)
-		*private_data = NULL;
-
 	/* no callback if no cache yet */
 	if (InjectionPointCache == NULL)
 		return NULL;
@@ -151,11 +164,7 @@ injection_point_cache_get(const char *name, const void **private_data)
 		hash_search(InjectionPointCache, name, HASH_FIND, &found);
 
 	if (found)
-	{
-		if (private_data)
-			*private_data = entry->private_data;
-		return entry->callback;
-	}
+		return entry;
 
 	return NULL;
 }
@@ -206,7 +215,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;
@@ -248,6 +258,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);
 
@@ -282,19 +293,18 @@ InjectionPointDetach(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;
 	bool		found;
-	InjectionPointCallback injection_callback;
-	const void *private_data;
+	InjectionPointCacheEntry *cache_entry;
 
 	LWLockAcquire(InjectionPointLock, LW_SHARED);
 	entry_by_name = (InjectionPointEntry *)
@@ -316,10 +326,10 @@ InjectionPointRun(const char *name)
 	 * Check if the callback exists in the local cache, to avoid unnecessary
 	 * external loads.
 	 */
-	if (injection_point_cache_get(name, NULL) == NULL)
+	if (injection_point_cache_get(name) == NULL)
 	{
 		char		path[MAXPGPATH];
-		InjectionPointCallback injection_callback_local;
+		void	   *injection_callback_local;
 
 		/* not found in local cache, so load and register */
 		snprintf(path, MAXPGPATH, "%s/%s%s", pkglib_path,
@@ -329,7 +339,7 @@ InjectionPointRun(const char *name)
 			elog(ERROR, "could not find library \"%s\" for injection point \"%s\"",
 				 path, name);
 
-		injection_callback_local = (InjectionPointCallback)
+		injection_callback_local = (void *)
 			load_external_function(path, entry_by_name->function, false, NULL);
 
 		if (injection_callback_local == NULL)
@@ -338,13 +348,43 @@ InjectionPointRun(const char *name)
 
 		/* add it to the local cache when found */
 		injection_point_cache_add(name, injection_callback_local,
-								  entry_by_name->private_data);
+								  entry_by_name->private_data,
+								  entry_by_name->num_args);
 	}
 
 	/* Now loaded, so get it. */
-	injection_callback = injection_point_cache_get(name, &private_data);
-	injection_callback(name, private_data);
+	cache_entry = injection_point_cache_get(name);
+
+	/* 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 dd9db06e10..740bdc8cfd 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)
+
 -- Runtime conditions
 SELECT injection_points_attach('TestConditionError', 'error');
  injection_points_attach 
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 c16a33b08d..074b7b2ea7 100644
--- a/src/test/modules/injection_points/injection_points--1.0.sql
+++ b/src/test/modules/injection_points/injection_points--1.0.sql
@@ -45,6 +45,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 5c44625d1d..7ef1c471a0 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)
 	{
@@ -378,6 +400,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 71e2972a7e..5b612a75c3 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');
 
 -- Runtime conditions
 SELECT injection_points_attach('TestConditionError', 'error');
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index a7c170476a..b74a2c1040 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>
@@ -3626,7 +3628,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
@@ -3634,6 +3637,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

