PostgreSQL Source Code git master
injection_point.c
Go to the documentation of this file.
1/*-------------------------------------------------------------------------
2 *
3 * injection_point.c
4 * Routines to control and run injection points in the code.
5 *
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.
8 *
9 * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
10 * Portions Copyright (c) 1994, Regents of the University of California
11 *
12 *
13 * IDENTIFICATION
14 * src/backend/utils/misc/injection_point.c
15 *
16 *-------------------------------------------------------------------------
17 */
18#include "postgres.h"
19
21
22#ifdef USE_INJECTION_POINTS
23
24#include <sys/stat.h>
25
26#include "fmgr.h"
27#include "miscadmin.h"
28#include "storage/fd.h"
29#include "storage/lwlock.h"
30#include "storage/shmem.h"
31#include "utils/hsearch.h"
32#include "utils/memutils.h"
33
34/* Field sizes */
35#define INJ_NAME_MAXLEN 64
36#define INJ_LIB_MAXLEN 128
37#define INJ_FUNC_MAXLEN 128
38#define INJ_PRIVATE_MAXLEN 1024
39
40/* Single injection point stored in shared memory */
41typedef struct InjectionPointEntry
42{
43 /*
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.
46 *
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.
54 *
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
59 * the same time.
60 */
61 pg_atomic_uint64 generation;
62
63 char name[INJ_NAME_MAXLEN]; /* point name */
64 char library[INJ_LIB_MAXLEN]; /* library */
65 char function[INJ_FUNC_MAXLEN]; /* function */
66
67 /*
68 * Opaque data area that modules can use to pass some custom data to
69 * callbacks, registered when attached.
70 */
71 char private_data[INJ_PRIVATE_MAXLEN];
72} InjectionPointEntry;
73
74#define MAX_INJECTION_POINTS 128
75
76/*
77 * Shared memory array of active injection points.
78 *
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.
82 */
83typedef struct InjectionPointsCtl
84{
85 pg_atomic_uint32 max_inuse;
86 InjectionPointEntry entries[MAX_INJECTION_POINTS];
87} InjectionPointsCtl;
88
89NON_EXEC_STATIC InjectionPointsCtl *ActiveInjectionPoints;
90
91/*
92 * Backend local cache of injection callbacks already loaded, stored in
93 * TopMemoryContext.
94 */
95typedef struct InjectionPointCacheEntry
96{
98 char private_data[INJ_PRIVATE_MAXLEN];
100
101 /*
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
104 * valid.
105 */
106 int slot_idx;
107 uint64 generation;
108} InjectionPointCacheEntry;
109
110static HTAB *InjectionPointCache = NULL;
111
112/*
113 * injection_point_cache_add
114 *
115 * Add an injection point to the local cache.
116 */
117static InjectionPointCacheEntry *
118injection_point_cache_add(const char *name,
119 int slot_idx,
120 uint64 generation,
122 const void *private_data)
123{
124 InjectionPointCacheEntry *entry;
125 bool found;
126
127 /* If first time, initialize */
128 if (InjectionPointCache == NULL)
129 {
130 HASHCTL hash_ctl;
131
132 hash_ctl.keysize = sizeof(char[INJ_NAME_MAXLEN]);
133 hash_ctl.entrysize = sizeof(InjectionPointCacheEntry);
134 hash_ctl.hcxt = TopMemoryContext;
135
136 InjectionPointCache = hash_create("InjectionPoint cache hash",
137 MAX_INJECTION_POINTS,
138 &hash_ctl,
140 }
141
142 entry = (InjectionPointCacheEntry *)
143 hash_search(InjectionPointCache, name, HASH_ENTER, &found);
144
145 Assert(!found);
146 strlcpy(entry->name, name, sizeof(entry->name));
147 entry->slot_idx = slot_idx;
148 entry->generation = generation;
149 entry->callback = callback;
150 memcpy(entry->private_data, private_data, INJ_PRIVATE_MAXLEN);
151
152 return entry;
153}
154
155/*
156 * injection_point_cache_remove
157 *
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.
161 */
162static void
163injection_point_cache_remove(const char *name)
164{
165 bool found PG_USED_FOR_ASSERTS_ONLY;
166
167 (void) hash_search(InjectionPointCache, name, HASH_REMOVE, &found);
168 Assert(found);
169}
170
171/*
172 * injection_point_cache_load
173 *
174 * Load an injection point into the local cache.
175 */
176static InjectionPointCacheEntry *
177injection_point_cache_load(InjectionPointEntry *entry, int slot_idx, uint64 generation)
178{
179 char path[MAXPGPATH];
180 void *injection_callback_local;
181
182 snprintf(path, MAXPGPATH, "%s/%s%s", pkglib_path,
183 entry->library, DLSUFFIX);
184
185 if (!pg_file_exists(path))
186 elog(ERROR, "could not find library \"%s\" for injection point \"%s\"",
187 path, entry->name);
188
189 injection_callback_local =
190 load_external_function(path, entry->function, false, NULL);
191
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);
195
196 /* add it to the local cache */
197 return injection_point_cache_add(entry->name,
198 slot_idx,
199 generation,
200 injection_callback_local,
201 entry->private_data);
202}
203
204/*
205 * injection_point_cache_get
206 *
207 * Retrieve an injection point from the local cache, if any.
208 */
209static InjectionPointCacheEntry *
210injection_point_cache_get(const char *name)
211{
212 bool found;
213 InjectionPointCacheEntry *entry;
214
215 /* no callback if no cache yet */
216 if (InjectionPointCache == NULL)
217 return NULL;
218
219 entry = (InjectionPointCacheEntry *)
220 hash_search(InjectionPointCache, name, HASH_FIND, &found);
221
222 if (found)
223 return entry;
224
225 return NULL;
226}
227#endif /* USE_INJECTION_POINTS */
228
229/*
230 * Return the space for dynamic shared hash table.
231 */
232Size
234{
235#ifdef USE_INJECTION_POINTS
236 Size sz = 0;
237
238 sz = add_size(sz, sizeof(InjectionPointsCtl));
239 return sz;
240#else
241 return 0;
242#endif
243}
244
245/*
246 * Allocate shmem space for dynamic shared hash.
247 */
248void
250{
251#ifdef USE_INJECTION_POINTS
252 bool found;
253
254 ActiveInjectionPoints = ShmemInitStruct("InjectionPoint hash",
255 sizeof(InjectionPointsCtl),
256 &found);
258 {
259 Assert(!found);
260 pg_atomic_init_u32(&ActiveInjectionPoints->max_inuse, 0);
261 for (int i = 0; i < MAX_INJECTION_POINTS; i++)
262 pg_atomic_init_u64(&ActiveInjectionPoints->entries[i].generation, 0);
263 }
264 else
265 Assert(found);
266#endif
267}
268
269/*
270 * Attach a new injection point.
271 */
272void
274 const char *library,
275 const char *function,
276 const void *private_data,
277 int private_data_size)
278{
279#ifdef USE_INJECTION_POINTS
280 InjectionPointEntry *entry;
281 uint64 generation;
282 uint32 max_inuse;
283 int free_idx;
284
285 if (strlen(name) >= INJ_NAME_MAXLEN)
286 elog(ERROR, "injection point name %s too long (maximum of %u characters)",
287 name, INJ_NAME_MAXLEN - 1);
288 if (strlen(library) >= INJ_LIB_MAXLEN)
289 elog(ERROR, "injection point library %s too long (maximum of %u characters)",
290 library, INJ_LIB_MAXLEN - 1);
291 if (strlen(function) >= INJ_FUNC_MAXLEN)
292 elog(ERROR, "injection point function %s too long (maximum of %u characters)",
293 function, INJ_FUNC_MAXLEN - 1);
294 if (private_data_size > INJ_PRIVATE_MAXLEN)
295 elog(ERROR, "injection point data too long (maximum of %u bytes)",
296 INJ_PRIVATE_MAXLEN);
297
298 /*
299 * Allocate and register a new injection point. A new point should not
300 * exist. For testing purposes this should be fine.
301 */
302 LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE);
303 max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
304 free_idx = -1;
305
306 for (int idx = 0; idx < max_inuse; idx++)
307 {
308 entry = &ActiveInjectionPoints->entries[idx];
309 generation = pg_atomic_read_u64(&entry->generation);
310 if (generation % 2 == 0)
311 {
312 /*
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.
315 */
316 if (free_idx == -1)
317 free_idx = idx;
318 }
319 else if (strcmp(entry->name, name) == 0)
320 elog(ERROR, "injection point \"%s\" already defined", name);
321 }
322 if (free_idx == -1)
323 {
324 if (max_inuse == MAX_INJECTION_POINTS)
325 elog(ERROR, "too many injection points");
326 free_idx = max_inuse;
327 }
328 entry = &ActiveInjectionPoints->entries[free_idx];
329 generation = pg_atomic_read_u64(&entry->generation);
330 Assert(generation % 2 == 0);
331
332 /* Save the entry */
333 strlcpy(entry->name, name, sizeof(entry->name));
334 strlcpy(entry->library, library, sizeof(entry->library));
335 strlcpy(entry->function, function, sizeof(entry->function));
336 if (private_data != NULL)
337 memcpy(entry->private_data, private_data, private_data_size);
338
340 pg_atomic_write_u64(&entry->generation, generation + 1);
341
342 if (free_idx + 1 > max_inuse)
343 pg_atomic_write_u32(&ActiveInjectionPoints->max_inuse, free_idx + 1);
344
345 LWLockRelease(InjectionPointLock);
346
347#else
348 elog(ERROR, "injection points are not supported by this build");
349#endif
350}
351
352/*
353 * Detach an existing injection point.
354 *
355 * Returns true if the injection point was detached, false otherwise.
356 */
357bool
359{
360#ifdef USE_INJECTION_POINTS
361 bool found = false;
362 int idx;
363 int max_inuse;
364
365 LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE);
366
367 /* Find it in the shmem array, and mark the slot as unused */
368 max_inuse = (int) pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
369 for (idx = max_inuse - 1; idx >= 0; --idx)
370 {
371 InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
372 uint64 generation;
373
374 generation = pg_atomic_read_u64(&entry->generation);
375 if (generation % 2 == 0)
376 continue; /* empty slot */
377
378 if (strcmp(entry->name, name) == 0)
379 {
380 Assert(!found);
381 found = true;
382 pg_atomic_write_u64(&entry->generation, generation + 1);
383 break;
384 }
385 }
386
387 /* If we just removed the highest-numbered entry, update 'max_inuse' */
388 if (found && idx == max_inuse - 1)
389 {
390 for (; idx >= 0; --idx)
391 {
392 InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
393 uint64 generation;
394
395 generation = pg_atomic_read_u64(&entry->generation);
396 if (generation % 2 != 0)
397 break;
398 }
399 pg_atomic_write_u32(&ActiveInjectionPoints->max_inuse, idx + 1);
400 }
401 LWLockRelease(InjectionPointLock);
402
403 return found;
404#else
405 elog(ERROR, "Injection points are not supported by this build");
406 return true; /* silence compiler */
407#endif
408}
409
410#ifdef USE_INJECTION_POINTS
411/*
412 * Common workhorse of InjectionPointRun() and InjectionPointLoad()
413 *
414 * Checks if an injection point exists in shared memory, and update
415 * the local cache entry accordingly.
416 */
417static InjectionPointCacheEntry *
418InjectionPointCacheRefresh(const char *name)
419{
420 uint32 max_inuse;
421 int namelen;
422 InjectionPointEntry local_copy;
423 InjectionPointCacheEntry *cached;
424
425 /*
426 * First read the number of in-use slots. More entries can be added or
427 * existing ones can be removed while we're reading them. If the entry
428 * we're looking for is concurrently added or removed, we might or might
429 * not see it. That's OK.
430 */
431 max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
432 if (max_inuse == 0)
433 {
434 if (InjectionPointCache)
435 {
436 hash_destroy(InjectionPointCache);
437 InjectionPointCache = NULL;
438 }
439 return NULL;
440 }
441
442 /*
443 * If we have this entry in the local cache already, check if the cached
444 * entry is still valid.
445 */
446 cached = injection_point_cache_get(name);
447 if (cached)
448 {
449 int idx = cached->slot_idx;
450 InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
451
452 if (pg_atomic_read_u64(&entry->generation) == cached->generation)
453 {
454 /* still good */
455 return cached;
456 }
457 injection_point_cache_remove(name);
458 cached = NULL;
459 }
460
461 /*
462 * Search the shared memory array.
463 *
464 * It's possible that the entry we're looking for is concurrently detached
465 * or attached. Or detached *and* re-attached, to the same slot or a
466 * different slot. Detach and re-attach is not an atomic operation, so
467 * it's OK for us to return the old value, NULL, or the new value in such
468 * cases.
469 */
470 namelen = strlen(name);
471 for (int idx = 0; idx < max_inuse; idx++)
472 {
473 InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
474 uint64 generation;
475
476 /*
477 * Read the generation number so that we can detect concurrent
478 * modifications. The read barrier ensures that the generation number
479 * is loaded before any of the other fields.
480 */
481 generation = pg_atomic_read_u64(&entry->generation);
482 if (generation % 2 == 0)
483 continue; /* empty slot */
485
486 /* Is this the injection point we're looking for? */
487 if (memcmp(entry->name, name, namelen + 1) != 0)
488 continue;
489
490 /*
491 * The entry can change at any time, if the injection point is
492 * concurrently detached. Copy it to local memory, and re-check the
493 * generation. If the generation hasn't changed, we know our local
494 * copy is coherent.
495 */
496 memcpy(&local_copy, entry, sizeof(InjectionPointEntry));
497
499 if (pg_atomic_read_u64(&entry->generation) != generation)
500 {
501 /*
502 * The entry was concurrently detached.
503 *
504 * Continue the search, because if the generation number changed,
505 * we cannot trust the result of the name comparison we did above.
506 * It's theoretically possible that it falsely matched a mixed-up
507 * state of the old and new name, if the slot was recycled with a
508 * different name.
509 */
510 continue;
511 }
512
513 /* Success! Load it into the cache and return it */
514 return injection_point_cache_load(&local_copy, idx, generation);
515 }
516 return NULL;
517}
518#endif
519
520/*
521 * Load an injection point into the local cache.
522 *
523 * This is useful to be able to load an injection point before running it,
524 * especially if the injection point is called in a code path where memory
525 * allocations cannot happen, like critical sections.
526 */
527void
529{
530#ifdef USE_INJECTION_POINTS
531 InjectionPointCacheRefresh(name);
532#else
533 elog(ERROR, "Injection points are not supported by this build");
534#endif
535}
536
537/*
538 * Execute an injection point, if defined.
539 */
540void
541InjectionPointRun(const char *name, void *arg)
542{
543#ifdef USE_INJECTION_POINTS
544 InjectionPointCacheEntry *cache_entry;
545
546 cache_entry = InjectionPointCacheRefresh(name);
547 if (cache_entry)
548 cache_entry->callback(name, cache_entry->private_data, arg);
549#else
550 elog(ERROR, "Injection points are not supported by this build");
551#endif
552}
553
554/*
555 * Execute an injection point directly from the cache, if defined.
556 */
557void
558InjectionPointCached(const char *name, void *arg)
559{
560#ifdef USE_INJECTION_POINTS
561 InjectionPointCacheEntry *cache_entry;
562
563 cache_entry = injection_point_cache_get(name);
564 if (cache_entry)
565 cache_entry->callback(name, cache_entry->private_data, arg);
566#else
567 elog(ERROR, "Injection points are not supported by this build");
568#endif
569}
570
571/*
572 * Test if an injection point is defined.
573 */
574bool
576{
577#ifdef USE_INJECTION_POINTS
578 return InjectionPointCacheRefresh(name) != NULL;
579#else
580 elog(ERROR, "Injection points are not supported by this build");
581 return false; /* silence compiler */
582#endif
583}
584
585/*
586 * Retrieve a list of all the injection points currently attached.
587 *
588 * This list is palloc'd in the current memory context.
589 */
590List *
592{
593#ifdef USE_INJECTION_POINTS
594 List *inj_points = NIL;
595 uint32 max_inuse;
596
597 LWLockAcquire(InjectionPointLock, LW_SHARED);
598
599 max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
600
601 for (uint32 idx = 0; idx < max_inuse; idx++)
602 {
603 InjectionPointEntry *entry;
604 InjectionPointData *inj_point;
605 uint64 generation;
606
607 entry = &ActiveInjectionPoints->entries[idx];
608 generation = pg_atomic_read_u64(&entry->generation);
609
610 /* skip free slots */
611 if (generation % 2 == 0)
612 continue;
613
615 inj_point->name = pstrdup(entry->name);
616 inj_point->library = pstrdup(entry->library);
617 inj_point->function = pstrdup(entry->function);
618 inj_points = lappend(inj_points, inj_point);
619 }
620
621 LWLockRelease(InjectionPointLock);
622
623 return inj_points;
624
625#else
626 elog(ERROR, "Injection points are not supported by this build");
627 return NIL; /* keep compiler quiet */
628#endif
629}
Datum idx(PG_FUNCTION_ARGS)
Definition: _int_op.c:262
static void pg_atomic_write_u64(volatile pg_atomic_uint64 *ptr, uint64 val)
Definition: atomics.h:485
#define pg_read_barrier()
Definition: atomics.h:154
static void pg_atomic_init_u32(volatile pg_atomic_uint32 *ptr, uint32 val)
Definition: atomics.h:219
#define pg_write_barrier()
Definition: atomics.h:155
static void pg_atomic_write_u32(volatile pg_atomic_uint32 *ptr, uint32 val)
Definition: atomics.h:274
static uint32 pg_atomic_read_u32(volatile pg_atomic_uint32 *ptr)
Definition: atomics.h:237
static void pg_atomic_init_u64(volatile pg_atomic_uint64 *ptr, uint64 val)
Definition: atomics.h:453
static uint64 pg_atomic_read_u64(volatile pg_atomic_uint64 *ptr)
Definition: atomics.h:467
#define PG_USED_FOR_ASSERTS_ONLY
Definition: c.h:229
uint64_t uint64
Definition: c.h:553
uint32_t uint32
Definition: c.h:552
size_t Size
Definition: c.h:625
void * load_external_function(const char *filename, const char *funcname, bool signalNotFound, void **filehandle)
Definition: dfmgr.c:95
void * hash_search(HTAB *hashp, const void *keyPtr, HASHACTION action, bool *foundPtr)
Definition: dynahash.c:952
HTAB * hash_create(const char *tabname, int64 nelem, const HASHCTL *info, int flags)
Definition: dynahash.c:358
void hash_destroy(HTAB *hashp)
Definition: dynahash.c:865
#define ERROR
Definition: elog.h:39
#define elog(elevel,...)
Definition: elog.h:226
bool pg_file_exists(const char *name)
Definition: fd.c:500
#define palloc0_object(type)
Definition: fe_memutils.h:75
char pkglib_path[MAXPGPATH]
Definition: globals.c:82
bool IsUnderPostmaster
Definition: globals.c:120
Assert(PointerIsAligned(start, uint64))
#define HASH_STRINGS
Definition: hsearch.h:96
@ HASH_FIND
Definition: hsearch.h:113
@ HASH_REMOVE
Definition: hsearch.h:115
@ HASH_ENTER
Definition: hsearch.h:114
#define HASH_CONTEXT
Definition: hsearch.h:102
#define HASH_ELEM
Definition: hsearch.h:95
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)
#define INJ_NAME_MAXLEN
int i
Definition: isn.c:77
List * lappend(List *list, void *datum)
Definition: list.c:339
bool LWLockAcquire(LWLock *lock, LWLockMode mode)
Definition: lwlock.c:1178
void LWLockRelease(LWLock *lock)
Definition: lwlock.c:1898
@ LW_SHARED
Definition: lwlock.h:113
@ LW_EXCLUSIVE
Definition: lwlock.h:112
char * pstrdup(const char *in)
Definition: mcxt.c:1781
MemoryContext TopMemoryContext
Definition: mcxt.c:166
on_exit_nicely_callback function
void * arg
#define MAXPGPATH
#define NIL
Definition: pg_list.h:68
#define snprintf
Definition: port.h:260
size_t strlcpy(char *dst, const char *src, size_t siz)
Definition: strlcpy.c:45
#define NON_EXEC_STATIC
Definition: postgres.h:556
Size add_size(Size s1, Size s2)
Definition: shmem.c:495
void * ShmemInitStruct(const char *name, Size size, bool *foundPtr)
Definition: shmem.c:389
Size keysize
Definition: hsearch.h:75
Size entrysize
Definition: hsearch.h:76
MemoryContext hcxt
Definition: hsearch.h:86
Definition: dynahash.c:222
const char * library
const char * function
Definition: pg_list.h:54
static void callback(struct sockaddr *addr, struct sockaddr *mask, void *unused)
Definition: test_ifaddrs.c:46
const char * name