PostgreSQL Source Code git master
Loading...
Searching...
No Matches
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 "storage/subsystems.h"
32#include "utils/hsearch.h"
33#include "utils/memutils.h"
34
35/* Field sizes */
36#define INJ_NAME_MAXLEN 64
37#define INJ_LIB_MAXLEN 128
38#define INJ_FUNC_MAXLEN 128
39#define INJ_PRIVATE_MAXLEN 1024
40
41/* Single injection point stored in shared memory */
42typedef struct InjectionPointEntry
43{
44 /*
45 * Because injection points need to be usable without LWLocks, we use a
46 * generation counter on each entry to allow safe, lock-free reading.
47 *
48 * To read an entry, first read the current 'generation' value. If it's
49 * even, then the slot is currently unused, and odd means it's in use.
50 * When reading the other fields, beware that they may change while
51 * reading them, if the entry is released and reused! After reading the
52 * other fields, read 'generation' again: if its value hasn't changed, you
53 * can be certain that the other fields you read are valid. Otherwise,
54 * the slot was concurrently recycled, and you should ignore it.
55 *
56 * When adding an entry, you must store all the other fields first, and
57 * then update the generation number, with an appropriate memory barrier
58 * in between. In addition to that protocol, you must also hold
59 * InjectionPointLock, to prevent two backends from modifying the array at
60 * the same time.
61 */
62 pg_atomic_uint64 generation;
63
64 char name[INJ_NAME_MAXLEN]; /* point name */
65 char library[INJ_LIB_MAXLEN]; /* library */
66 char function[INJ_FUNC_MAXLEN]; /* function */
67
68 /*
69 * Opaque data area that modules can use to pass some custom data to
70 * callbacks, registered when attached.
71 */
72 char private_data[INJ_PRIVATE_MAXLEN];
74
75#define MAX_INJECTION_POINTS 128
76
77/*
78 * Shared memory array of active injection points.
79 *
80 * 'max_inuse' is the highest index currently in use, plus one. It's just an
81 * optimization to avoid scanning through the whole entry, in the common case
82 * that there are no injection points, or only a few.
83 */
84typedef struct InjectionPointsCtl
85{
89
91
92/*
93 * Backend local cache of injection callbacks already loaded, stored in
94 * TopMemoryContext.
95 */
96typedef struct InjectionPointCacheEntry
97{
99 char private_data[INJ_PRIVATE_MAXLEN];
101
102 /*
103 * Shmem slot and copy of its generation number when this cache entry was
104 * created. They can be used to validate if the cached entry is still
105 * valid.
106 */
107 int slot_idx;
108 uint64 generation;
110
112
113static void InjectionPointShmemRequest(void *arg);
114static void InjectionPointShmemInit(void *arg);
115
116/*
117 * injection_point_cache_add
118 *
119 * Add an injection point to the local cache.
120 */
123 int slot_idx,
124 uint64 generation,
126 const void *private_data)
127{
129 bool found;
130
131 /* If first time, initialize */
133 {
135
136 hash_ctl.keysize = sizeof(char[INJ_NAME_MAXLEN]);
137 hash_ctl.entrysize = sizeof(InjectionPointCacheEntry);
139
140 InjectionPointCache = hash_create("InjectionPoint cache hash",
142 &hash_ctl,
144 }
145
146 entry = (InjectionPointCacheEntry *)
148
149 Assert(!found);
150 strlcpy(entry->name, name, sizeof(entry->name));
151 entry->slot_idx = slot_idx;
152 entry->generation = generation;
153 entry->callback = callback;
154 memcpy(entry->private_data, private_data, INJ_PRIVATE_MAXLEN);
155
156 return entry;
157}
158
159/*
160 * injection_point_cache_remove
161 *
162 * Remove entry from the local cache. Note that this leaks a callback
163 * loaded but removed later on, which should have no consequence from
164 * a testing perspective.
165 */
166static void
168{
169 bool found PG_USED_FOR_ASSERTS_ONLY;
170
172 Assert(found);
173}
174
175/*
176 * injection_point_cache_load
177 *
178 * Load an injection point into the local cache.
179 */
182{
183 char path[MAXPGPATH];
185
186 snprintf(path, MAXPGPATH, "%s/%s%s", pkglib_path,
187 entry->library, DLSUFFIX);
188
189 if (!pg_file_exists(path))
190 elog(ERROR, "could not find library \"%s\" for injection point \"%s\"",
191 path, entry->name);
192
194 load_external_function(path, entry->function, false, NULL);
195
197 elog(ERROR, "could not find function \"%s\" in library \"%s\" for injection point \"%s\"",
198 entry->function, path, entry->name);
199
200 /* add it to the local cache */
201 return injection_point_cache_add(entry->name,
202 slot_idx,
203 generation,
205 entry->private_data);
206}
207
208/*
209 * injection_point_cache_get
210 *
211 * Retrieve an injection point from the local cache, if any.
212 */
215{
216 bool found;
218
219 /* no callback if no cache yet */
221 return NULL;
222
223 entry = (InjectionPointCacheEntry *)
225
226 if (found)
227 return entry;
228
229 return NULL;
230}
231
234 .init_fn = InjectionPointShmemInit,
235};
236
237/*
238 * Reserve space for the dynamic shared hash table
239 */
240static void
242{
243 ShmemRequestStruct(.name = "InjectionPoint hash",
244 .size = sizeof(InjectionPointsCtl),
245 .ptr = (void **) &ActiveInjectionPoints,
246 );
247}
248
249static void
251{
253 for (int i = 0; i < MAX_INJECTION_POINTS; i++)
254 pg_atomic_init_u64(&ActiveInjectionPoints->entries[i].generation, 0);
255}
256#endif /* USE_INJECTION_POINTS */
257
258/*
259 * Attach a new injection point.
260 */
261void
263 const char *library,
264 const char *function,
265 const void *private_data,
267{
268#ifdef USE_INJECTION_POINTS
269 InjectionPointEntry *entry;
270 uint64 generation;
272 int free_idx;
273
275 elog(ERROR, "injection point name %s too long (maximum of %u characters)",
276 name, INJ_NAME_MAXLEN - 1);
277 if (strlen(library) >= INJ_LIB_MAXLEN)
278 elog(ERROR, "injection point library %s too long (maximum of %u characters)",
279 library, INJ_LIB_MAXLEN - 1);
281 elog(ERROR, "injection point function %s too long (maximum of %u characters)",
284 elog(ERROR, "injection point data too long (maximum of %u bytes)",
286
287 /*
288 * Allocate and register a new injection point. A new point should not
289 * exist. For testing purposes this should be fine.
290 */
293 free_idx = -1;
294
295 for (int idx = 0; idx < max_inuse; idx++)
296 {
297 entry = &ActiveInjectionPoints->entries[idx];
298 generation = pg_atomic_read_u64(&entry->generation);
299 if (generation % 2 == 0)
300 {
301 /*
302 * Found a free slot where we can add the new entry, but keep
303 * going so that we will find out if the entry already exists.
304 */
305 if (free_idx == -1)
306 free_idx = idx;
307 }
308 else if (strcmp(entry->name, name) == 0)
309 elog(ERROR, "injection point \"%s\" already defined", name);
310 }
311 if (free_idx == -1)
312 {
314 elog(ERROR, "too many injection points");
316 }
317 entry = &ActiveInjectionPoints->entries[free_idx];
318 generation = pg_atomic_read_u64(&entry->generation);
319 Assert(generation % 2 == 0);
320
321 /* Save the entry */
322 strlcpy(entry->name, name, sizeof(entry->name));
323 strlcpy(entry->library, library, sizeof(entry->library));
324 strlcpy(entry->function, function, sizeof(entry->function));
325 memset(entry->private_data, 0, INJ_PRIVATE_MAXLEN);
326 if (private_data != NULL)
327 memcpy(entry->private_data, private_data, private_data_size);
328
330 pg_atomic_write_u64(&entry->generation, generation + 1);
331
332 if (free_idx + 1 > max_inuse)
334
336
337#else
338 elog(ERROR, "injection points are not supported by this build");
339#endif
340}
341
342/*
343 * Detach an existing injection point.
344 *
345 * Returns true if the injection point was detached, false otherwise.
346 */
347bool
349{
350#ifdef USE_INJECTION_POINTS
351 bool found = false;
352 int idx;
353 int max_inuse;
354
356
357 /* Find it in the shmem array, and mark the slot as unused */
359 for (idx = max_inuse - 1; idx >= 0; --idx)
360 {
362 uint64 generation;
363
364 generation = pg_atomic_read_u64(&entry->generation);
365 if (generation % 2 == 0)
366 continue; /* empty slot */
367
368 if (strcmp(entry->name, name) == 0)
369 {
370 Assert(!found);
371 found = true;
372 pg_atomic_write_u64(&entry->generation, generation + 1);
373 break;
374 }
375 }
376
377 /* If we just removed the highest-numbered entry, update 'max_inuse' */
378 if (found && idx == max_inuse - 1)
379 {
380 for (; idx >= 0; --idx)
381 {
383 uint64 generation;
384
385 generation = pg_atomic_read_u64(&entry->generation);
386 if (generation % 2 != 0)
387 break;
388 }
390 }
392
393 return found;
394#else
395 elog(ERROR, "Injection points are not supported by this build");
396 return true; /* silence compiler */
397#endif
398}
399
400#ifdef USE_INJECTION_POINTS
401/*
402 * Common workhorse of InjectionPointRun() and InjectionPointLoad()
403 *
404 * Checks if an injection point exists in shared memory, and update
405 * the local cache entry accordingly.
406 */
409{
411 int namelen;
414
415 /*
416 * First read the number of in-use slots. More entries can be added or
417 * existing ones can be removed while we're reading them. If the entry
418 * we're looking for is concurrently added or removed, we might or might
419 * not see it. That's OK.
420 */
422 if (max_inuse == 0)
423 {
425 {
428 }
429 return NULL;
430 }
431
432 /*
433 * If we have this entry in the local cache already, check if the cached
434 * entry is still valid.
435 */
437 if (cached)
438 {
439 int idx = cached->slot_idx;
441
442 if (pg_atomic_read_u64(&entry->generation) == cached->generation)
443 {
444 /* still good */
445 return cached;
446 }
448 cached = NULL;
449 }
450
451 /*
452 * Search the shared memory array.
453 *
454 * It's possible that the entry we're looking for is concurrently detached
455 * or attached. Or detached *and* re-attached, to the same slot or a
456 * different slot. Detach and re-attach is not an atomic operation, so
457 * it's OK for us to return the old value, NULL, or the new value in such
458 * cases.
459 */
460 namelen = strlen(name);
461 for (int idx = 0; idx < max_inuse; idx++)
462 {
464 uint64 generation;
465
466 /*
467 * Read the generation number so that we can detect concurrent
468 * modifications. The read barrier ensures that the generation number
469 * is loaded before any of the other fields.
470 */
471 generation = pg_atomic_read_u64(&entry->generation);
472 if (generation % 2 == 0)
473 continue; /* empty slot */
475
476 /* Is this the injection point we're looking for? */
477 if (memcmp(entry->name, name, namelen + 1) != 0)
478 continue;
479
480 /*
481 * The entry can change at any time, if the injection point is
482 * concurrently detached. Copy it to local memory, and re-check the
483 * generation. If the generation hasn't changed, we know our local
484 * copy is coherent.
485 */
486 memcpy(&local_copy, entry, sizeof(InjectionPointEntry));
487
489 if (pg_atomic_read_u64(&entry->generation) != generation)
490 {
491 /*
492 * The entry was concurrently detached.
493 *
494 * Continue the search, because if the generation number changed,
495 * we cannot trust the result of the name comparison we did above.
496 * It's theoretically possible that it falsely matched a mixed-up
497 * state of the old and new name, if the slot was recycled with a
498 * different name.
499 */
500 continue;
501 }
502
503 /* Success! Load it into the cache and return it */
504 return injection_point_cache_load(&local_copy, idx, generation);
505 }
506 return NULL;
507}
508#endif
509
510/*
511 * Load an injection point into the local cache.
512 *
513 * This is useful to be able to load an injection point before running it,
514 * especially if the injection point is called in a code path where memory
515 * allocations cannot happen, like critical sections.
516 */
517void
519{
520#ifdef USE_INJECTION_POINTS
522#else
523 elog(ERROR, "Injection points are not supported by this build");
524#endif
525}
526
527/*
528 * Execute an injection point, if defined.
529 */
530void
531InjectionPointRun(const char *name, void *arg)
532{
533#ifdef USE_INJECTION_POINTS
535
537 if (cache_entry)
538 cache_entry->callback(name, cache_entry->private_data, arg);
539#else
540 elog(ERROR, "Injection points are not supported by this build");
541#endif
542}
543
544/*
545 * Execute an injection point directly from the cache, if defined.
546 */
547void
548InjectionPointCached(const char *name, void *arg)
549{
550#ifdef USE_INJECTION_POINTS
552
554 if (cache_entry)
555 cache_entry->callback(name, cache_entry->private_data, arg);
556#else
557 elog(ERROR, "Injection points are not supported by this build");
558#endif
559}
560
561/*
562 * Test if an injection point is defined.
563 */
564bool
566{
567#ifdef USE_INJECTION_POINTS
569#else
570 elog(ERROR, "Injection points are not supported by this build");
571 return false; /* silence compiler */
572#endif
573}
574
575/*
576 * Retrieve a list of all the injection points currently attached.
577 *
578 * This list is palloc'd in the current memory context.
579 */
580List *
582{
583#ifdef USE_INJECTION_POINTS
586
588
590
591 for (uint32 idx = 0; idx < max_inuse; idx++)
592 {
593 InjectionPointEntry *entry;
595 uint64 generation;
596
597 entry = &ActiveInjectionPoints->entries[idx];
598 generation = pg_atomic_read_u64(&entry->generation);
599
600 /* skip free slots */
601 if (generation % 2 == 0)
602 continue;
603
605 inj_point->name = pstrdup(entry->name);
606 inj_point->library = pstrdup(entry->library);
607 inj_point->function = pstrdup(entry->function);
609 }
610
612
613 return inj_points;
614
615#else
616 elog(ERROR, "Injection points are not supported by this build");
617 return NIL; /* keep compiler quiet */
618#endif
619}
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:249
#define Assert(condition)
Definition c.h:943
uint64_t uint64
Definition c.h:625
uint32_t uint32
Definition c.h:624
memcpy(sums, checksumBaseOffsets, sizeof(checksumBaseOffsets))
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:889
HTAB * hash_create(const char *tabname, int64 nelem, const HASHCTL *info, int flags)
Definition dynahash.c:360
void hash_destroy(HTAB *hashp)
Definition dynahash.c:802
Datum arg
Definition elog.c:1322
#define ERROR
Definition elog.h:40
#define elog(elevel,...)
Definition elog.h:228
bool pg_file_exists(const char *name)
Definition fd.c:504
#define palloc0_object(type)
Definition fe_memutils.h:75
char pkglib_path[MAXPGPATH]
Definition globals.c:84
#define HASH_STRINGS
Definition hsearch.h:91
@ HASH_FIND
Definition hsearch.h:108
@ HASH_REMOVE
Definition hsearch.h:110
@ HASH_ENTER
Definition hsearch.h:109
#define HASH_CONTEXT
Definition hsearch.h:97
#define HASH_ELEM
Definition hsearch.h:90
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:1150
void LWLockRelease(LWLock *lock)
Definition lwlock.c:1767
@ LW_SHARED
Definition lwlock.h:105
@ LW_EXCLUSIVE
Definition lwlock.h:104
char * pstrdup(const char *in)
Definition mcxt.c:1781
MemoryContext TopMemoryContext
Definition mcxt.c:166
on_exit_nicely_callback function
#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:560
static int fb(int x)
#define ShmemRequestStruct(...)
Definition shmem.h:176
Size keysize
Definition hsearch.h:69
Definition pg_list.h:54
ShmemRequestCallback request_fn
Definition shmem.h:133
static void callback(struct sockaddr *addr, struct sockaddr *mask, void *unused)
const char * name