1/*-------------------------------------------------------------------------
4 * Routines to control and run injection points in the code.
6 * Injection points can be used to run arbitrary code by attaching callbacks
7 * that would be executed in place of the named injection point.
9 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
10 * Portions Copyright (c) 1994, Regents of the University of California
14 * src/backend/utils/misc/injection_point.c
16 *-------------------------------------------------------------------------
22#ifdef USE_INJECTION_POINTS
35#define INJ_NAME_MAXLEN 64
36#define INJ_LIB_MAXLEN 128
37#define INJ_FUNC_MAXLEN 128
38#define INJ_PRIVATE_MAXLEN 1024
40/* Single injection point stored in shared memory */
41typedef struct InjectionPointEntry
44 * Because injection points need to be usable without LWLocks, we use a
45 * generation counter on each entry to allow safe, lock-free reading.
47 * To read an entry, first read the current 'generation' value. If it's
48 * even, then the slot is currently unused, and odd means it's in use.
49 * When reading the other fields, beware that they may change while
50 * reading them, if the entry is released and reused! After reading the
51 * other fields, read 'generation' again: if its value hasn't changed, you
52 * can be certain that the other fields you read are valid. Otherwise,
53 * the slot was concurrently recycled, and you should ignore it.
55 * When adding an entry, you must store all the other fields first, and
56 * then update the generation number, with an appropriate memory barrier
57 * in between. In addition to that protocol, you must also hold
58 * InjectionPointLock, to prevent two backends from modifying the array at
64 char library[INJ_LIB_MAXLEN];
/* library */
65 char function[INJ_FUNC_MAXLEN];
/* function */
68 * Opaque data area that modules can use to pass some custom data to
69 * callbacks, registered when attached.
71 char private_data[INJ_PRIVATE_MAXLEN];
74#define MAX_INJECTION_POINTS 128
77 * Shared memory array of active injection points.
79 * 'max_inuse' is the highest index currently in use, plus one. It's just an
80 * optimization to avoid scanning through the whole entry, in the common case
81 * that there are no injection points, or only a few.
83typedef struct InjectionPointsCtl
86 InjectionPointEntry entries[MAX_INJECTION_POINTS];
92 * Backend local cache of injection callbacks already loaded, stored in
95typedef struct InjectionPointCacheEntry
98 char private_data[INJ_PRIVATE_MAXLEN];
102 * Shmem slot and copy of its generation number when this cache entry was
103 * created. They can be used to validate if the cached entry is still
108} InjectionPointCacheEntry;
110static HTAB *InjectionPointCache = NULL;
113 * injection_point_cache_add
115 * Add an injection point to the local cache.
117static InjectionPointCacheEntry *
118injection_point_cache_add(
const char *
name,
122 const void *private_data)
124 InjectionPointCacheEntry *entry;
127 /* If first time, initialize */
128 if (InjectionPointCache == NULL)
133 hash_ctl.
entrysize =
sizeof(InjectionPointCacheEntry);
136 InjectionPointCache =
hash_create(
"InjectionPoint cache hash",
137 MAX_INJECTION_POINTS,
142 entry = (InjectionPointCacheEntry *)
147 entry->slot_idx = slot_idx;
148 entry->generation = generation;
150 memcpy(entry->private_data, private_data, INJ_PRIVATE_MAXLEN);
156 * injection_point_cache_remove
158 * Remove entry from the local cache. Note that this leaks a callback
159 * loaded but removed later on, which should have no consequence from
160 * a testing perspective.
163injection_point_cache_remove(
const char *
name)
172 * injection_point_cache_load
174 * Load an injection point into the local cache.
176static InjectionPointCacheEntry *
177injection_point_cache_load(InjectionPointEntry *entry,
int slot_idx,
uint64 generation)
180 void *injection_callback_local;
183 entry->library, DLSUFFIX);
186 elog(
ERROR,
"could not find library \"%s\" for injection point \"%s\"",
189 injection_callback_local = (
void *)
192 if (injection_callback_local == NULL)
193 elog(
ERROR,
"could not find function \"%s\" in library \"%s\" for injection point \"%s\"",
194 entry->function, path, entry->name);
196 /* add it to the local cache */
197 return injection_point_cache_add(entry->name,
200 injection_callback_local,
201 entry->private_data);
205 * injection_point_cache_get
207 * Retrieve an injection point from the local cache, if any.
209static InjectionPointCacheEntry *
210injection_point_cache_get(
const char *
name)
213 InjectionPointCacheEntry *entry;
215 /* no callback if no cache yet */
216 if (InjectionPointCache == NULL)
219 entry = (InjectionPointCacheEntry *)
227#endif /* USE_INJECTION_POINTS */
230 * Return the space for dynamic shared hash table.
235#ifdef USE_INJECTION_POINTS
238 sz =
add_size(sz,
sizeof(InjectionPointsCtl));
246 * Allocate shmem space for dynamic shared hash.
251#ifdef USE_INJECTION_POINTS
255 sizeof(InjectionPointsCtl),
261 for (
int i = 0;
i < MAX_INJECTION_POINTS;
i++)
270 * Attach a new injection point.
276 const void *private_data,
277 int private_data_size)
279#ifdef USE_INJECTION_POINTS
280 InjectionPointEntry *entry;
286 elog(
ERROR,
"injection point name %s too long (maximum of %u)",
288 if (strlen(library) >= INJ_LIB_MAXLEN)
289 elog(
ERROR,
"injection point library %s too long (maximum of %u)",
290 library, INJ_LIB_MAXLEN);
291 if (strlen(
function) >= INJ_FUNC_MAXLEN)
292 elog(
ERROR,
"injection point function %s too long (maximum of %u)",
294 if (private_data_size >= INJ_PRIVATE_MAXLEN)
295 elog(
ERROR,
"injection point data too long (maximum of %u)",
299 * Allocate and register a new injection point. A new point should not
300 * exist. For testing purposes this should be fine.
306 for (
int idx = 0;
idx < max_inuse;
idx++)
308 entry = &ActiveInjectionPoints->entries[
idx];
310 if (generation % 2 == 0)
313 * Found a free slot where we can add the new entry, but keep
314 * going so that we will find out if the entry already exists.
319 else if (strcmp(entry->name,
name) == 0)
324 if (max_inuse == MAX_INJECTION_POINTS)
325 elog(
ERROR,
"too many injection points");
326 free_idx = max_inuse;
328 entry = &ActiveInjectionPoints->entries[free_idx];
330 Assert(generation % 2 == 0);
335 strlcpy(entry->library, library,
sizeof(entry->library));
336 entry->library[INJ_LIB_MAXLEN - 1] =
'0円';
338 entry->function[INJ_FUNC_MAXLEN - 1] =
'0円';
339 if (private_data != NULL)
340 memcpy(entry->private_data, private_data, private_data_size);
345 if (free_idx + 1 > max_inuse)
351 elog(
ERROR,
"injection points are not supported by this build");
356 * Detach an existing injection point.
358 * Returns true if the injection point was detached, false otherwise.
363#ifdef USE_INJECTION_POINTS
370 /* Find it in the shmem array, and mark the slot as unused */
372 for (
idx = max_inuse - 1;
idx >= 0; --
idx)
374 InjectionPointEntry *entry = &ActiveInjectionPoints->entries[
idx];
378 if (generation % 2 == 0)
379 continue;
/* empty slot */
381 if (strcmp(entry->name,
name) == 0)
390 /* If we just removed the highest-numbered entry, update 'max_inuse' */
391 if (found &&
idx == max_inuse - 1)
395 InjectionPointEntry *entry = &ActiveInjectionPoints->entries[
idx];
399 if (generation % 2 != 0)
408 elog(
ERROR,
"Injection points are not supported by this build");
409 return true;
/* silence compiler */
413#ifdef USE_INJECTION_POINTS
415 * Common workhorse of InjectionPointRun() and InjectionPointLoad()
417 * Checks if an injection point exists in shared memory, and update
418 * the local cache entry accordingly.
420static InjectionPointCacheEntry *
421InjectionPointCacheRefresh(
const char *
name)
425 InjectionPointEntry local_copy;
426 InjectionPointCacheEntry *cached;
429 * First read the number of in-use slots. More entries can be added or
430 * existing ones can be removed while we're reading them. If the entry
431 * we're looking for is concurrently added or removed, we might or might
432 * not see it. That's OK.
437 if (InjectionPointCache)
440 InjectionPointCache = NULL;
446 * If we have this entry in the local cache already, check if the cached
447 * entry is still valid.
449 cached = injection_point_cache_get(
name);
452 int idx = cached->slot_idx;
453 InjectionPointEntry *entry = &ActiveInjectionPoints->entries[
idx];
460 injection_point_cache_remove(
name);
465 * Search the shared memory array.
467 * It's possible that the entry we're looking for is concurrently detached
468 * or attached. Or detached *and* re-attached, to the same slot or a
469 * different slot. Detach and re-attach is not an atomic operation, so
470 * it's OK for us to return the old value, NULL, or the new value in such
473 namelen = strlen(
name);
474 for (
int idx = 0;
idx < max_inuse;
idx++)
476 InjectionPointEntry *entry = &ActiveInjectionPoints->entries[
idx];
480 * Read the generation number so that we can detect concurrent
481 * modifications. The read barrier ensures that the generation number
482 * is loaded before any of the other fields.
485 if (generation % 2 == 0)
486 continue;
/* empty slot */
489 /* Is this the injection point we're looking for? */
490 if (memcmp(entry->name,
name, namelen + 1) != 0)
494 * The entry can change at any time, if the injection point is
495 * concurrently detached. Copy it to local memory, and re-check the
496 * generation. If the generation hasn't changed, we know our local
499 memcpy(&local_copy, entry,
sizeof(InjectionPointEntry));
505 * The entry was concurrently detached.
507 * Continue the search, because if the generation number changed,
508 * we cannot trust the result of the name comparison we did above.
509 * It's theoretically possible that it falsely matched a mixed-up
510 * state of the old and new name, if the slot was recycled with a
516 /* Success! Load it into the cache and return it */
517 return injection_point_cache_load(&local_copy,
idx, generation);
524 * Load an injection point into the local cache.
526 * This is useful to be able to load an injection point before running it,
527 * especially if the injection point is called in a code path where memory
528 * allocations cannot happen, like critical sections.
533#ifdef USE_INJECTION_POINTS
534 InjectionPointCacheRefresh(
name);
536 elog(
ERROR,
"Injection points are not supported by this build");
541 * Execute an injection point, if defined.
546#ifdef USE_INJECTION_POINTS
547 InjectionPointCacheEntry *cache_entry;
549 cache_entry = InjectionPointCacheRefresh(
name);
551 cache_entry->callback(
name, cache_entry->private_data,
arg);
553 elog(
ERROR,
"Injection points are not supported by this build");
558 * Execute an injection point directly from the cache, if defined.
563#ifdef USE_INJECTION_POINTS
564 InjectionPointCacheEntry *cache_entry;
566 cache_entry = injection_point_cache_get(
name);
568 cache_entry->callback(
name, cache_entry->private_data,
arg);
570 elog(
ERROR,
"Injection points are not supported by this build");
575 * Test if an injection point is defined.
580#ifdef USE_INJECTION_POINTS
581 return InjectionPointCacheRefresh(
name) != NULL;
583 elog(
ERROR,
"Injection points are not supported by this build");
584 return false;
/* silence compiler */
589 * Retrieve a list of all the injection points currently attached.
591 * This list is palloc'd in the current memory context.
596#ifdef USE_INJECTION_POINTS
606 InjectionPointEntry *entry;
610 entry = &ActiveInjectionPoints->entries[
idx];
613 /* skip free slots */
614 if (generation % 2 == 0)
621 inj_points =
lappend(inj_points, inj_point);
629 elog(
ERROR,
"Injection points are not supported by this build");
630 return NIL;
/* keep compiler quiet */
Datum idx(PG_FUNCTION_ARGS)
static void pg_atomic_write_u64(volatile pg_atomic_uint64 *ptr, uint64 val)
#define pg_read_barrier()
static void pg_atomic_init_u32(volatile pg_atomic_uint32 *ptr, uint32 val)
#define pg_write_barrier()
static void pg_atomic_write_u32(volatile pg_atomic_uint32 *ptr, uint32 val)
static uint32 pg_atomic_read_u32(volatile pg_atomic_uint32 *ptr)
static void pg_atomic_init_u64(volatile pg_atomic_uint64 *ptr, uint64 val)
static uint64 pg_atomic_read_u64(volatile pg_atomic_uint64 *ptr)
#define PG_USED_FOR_ASSERTS_ONLY
void * load_external_function(const char *filename, const char *funcname, bool signalNotFound, void **filehandle)
void * hash_search(HTAB *hashp, const void *keyPtr, HASHACTION action, bool *foundPtr)
HTAB * hash_create(const char *tabname, int64 nelem, const HASHCTL *info, int flags)
void hash_destroy(HTAB *hashp)
bool pg_file_exists(const char *name)
char pkglib_path[MAXPGPATH]
Assert(PointerIsAligned(start, uint64))
void InjectionPointShmemInit(void)
Size InjectionPointShmemSize(void)
void InjectionPointLoad(const char *name)
bool InjectionPointDetach(const char *name)
List * InjectionPointList(void)
void InjectionPointRun(const char *name, void *arg)
bool IsInjectionPointAttached(const char *name)
void InjectionPointAttach(const char *name, const char *library, const char *function, const void *private_data, int private_data_size)
void InjectionPointCached(const char *name, void *arg)
void(* InjectionPointCallback)(const char *name, const void *private_data, void *arg)
List * lappend(List *list, void *datum)
bool LWLockAcquire(LWLock *lock, LWLockMode mode)
void LWLockRelease(LWLock *lock)
char * pstrdup(const char *in)
void * palloc0(Size size)
MemoryContext TopMemoryContext
on_exit_nicely_callback function
size_t strlcpy(char *dst, const char *src, size_t siz)
Size add_size(Size s1, Size s2)
void * ShmemInitStruct(const char *name, Size size, bool *foundPtr)
static void callback(struct sockaddr *addr, struct sockaddr *mask, void *unused)