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-2024, PostgreSQL Global Development Group
10 * Portions Copyright (c) 1994, Regents of the University of California
14 * src/backend/utils/misc/injection_point.c
16 *-------------------------------------------------------------------------
20 #include "utils/injection_point.h"
22 #ifdef USE_INJECTION_POINTS
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"
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 */
41 typedef 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
61 pg_atomic_uint64 generation
;
63 char name
[INJ_NAME_MAXLEN
]; /* point name */
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
];
72 } InjectionPointEntry
;
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.
83 typedef struct InjectionPointsCtl
85 pg_atomic_uint32 max_inuse
;
86 InjectionPointEntry entries
[MAX_INJECTION_POINTS
];
89 NON_EXEC_STATIC InjectionPointsCtl
*ActiveInjectionPoints
;
92 * Backend local cache of injection callbacks already loaded, stored in
95 typedef struct InjectionPointCacheEntry
97 char name
[INJ_NAME_MAXLEN
];
98 char private_data
[INJ_PRIVATE_MAXLEN
];
99 InjectionPointCallback callback
;
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
;
110 static HTAB
*InjectionPointCache
= NULL
;
113 * injection_point_cache_add
115 * Add an injection point to the local cache.
117 static InjectionPointCacheEntry
*
118 injection_point_cache_add(const char *name
,
121 InjectionPointCallback callback
,
122 const void *private_data
)
124 InjectionPointCacheEntry
*entry
;
127 /* If first time, initialize */
128 if (InjectionPointCache
== NULL
)
132 hash_ctl
.keysize
= sizeof(char[INJ_NAME_MAXLEN
]);
133 hash_ctl
.entrysize
= sizeof(InjectionPointCacheEntry
);
134 hash_ctl
.hcxt
= TopMemoryContext
;
136 InjectionPointCache
= hash_create("InjectionPoint cache hash",
137 MAX_INJECTION_POINTS
,
139 HASH_ELEM
| HASH_STRINGS
| HASH_CONTEXT
);
142 entry
= (InjectionPointCacheEntry
*)
143 hash_search(InjectionPointCache
, name
, HASH_ENTER
, &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
);
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.
163 injection_point_cache_remove(const char *name
)
165 bool found PG_USED_FOR_ASSERTS_ONLY
;
167 (void) hash_search(InjectionPointCache
, name
, HASH_REMOVE
, &found
);
172 * injection_point_cache_load
174 * Load an injection point into the local cache.
176 static InjectionPointCacheEntry
*
177 injection_point_cache_load(InjectionPointEntry
*entry
, int slot_idx
, uint64 generation
)
179 char path
[MAXPGPATH
];
180 void *injection_callback_local
;
182 snprintf(path
, MAXPGPATH
, "%s/%s%s", pkglib_path
,
183 entry
->library
, DLSUFFIX
);
185 if (!pg_file_exists(path
))
186 elog(ERROR
, "could not find library \"%s\" for injection point \"%s\"",
189 injection_callback_local
= (void *)
190 load_external_function(path
, entry
->function
, false, NULL
);
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.
209 static InjectionPointCacheEntry
*
210 injection_point_cache_get(const char *name
)
213 InjectionPointCacheEntry
*entry
;
215 /* no callback if no cache yet */
216 if (InjectionPointCache
== NULL
)
219 entry
= (InjectionPointCacheEntry
*)
220 hash_search(InjectionPointCache
, name
, HASH_FIND
, &found
);
227 #endif /* USE_INJECTION_POINTS */
230 * Return the space for dynamic shared hash table.
233 InjectionPointShmemSize(void)
235 #ifdef USE_INJECTION_POINTS
238 sz
= add_size(sz
, sizeof(InjectionPointsCtl
));
246 * Allocate shmem space for dynamic shared hash.
249 InjectionPointShmemInit(void)
251 #ifdef USE_INJECTION_POINTS
254 ActiveInjectionPoints
= ShmemInitStruct("InjectionPoint hash",
255 sizeof(InjectionPointsCtl
),
257 if (!IsUnderPostmaster
)
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);
270 * Attach a new injection point.
273 InjectionPointAttach(const char *name
,
275 const char *function
,
276 const void *private_data
,
277 int private_data_size
)
279 #ifdef USE_INJECTION_POINTS
280 InjectionPointEntry
*entry
;
285 if (strlen(name
) >= INJ_NAME_MAXLEN
)
286 elog(ERROR
, "injection point name %s too long (maximum of %u)",
287 name
, INJ_NAME_MAXLEN
);
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)",
293 function
, INJ_FUNC_MAXLEN
);
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.
302 LWLockAcquire(InjectionPointLock
, LW_EXCLUSIVE
);
303 max_inuse
= pg_atomic_read_u32(&ActiveInjectionPoints
->max_inuse
);
306 for (int idx
= 0; idx
< max_inuse
; idx
++)
308 entry
= &ActiveInjectionPoints
->entries
[idx
];
309 generation
= pg_atomic_read_u64(&entry
->generation
);
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)
320 elog(ERROR
, "injection point \"%s\" already defined", name
);
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
];
329 generation
= pg_atomic_read_u64(&entry
->generation
);
330 Assert(generation
% 2 == 0);
333 strlcpy(entry
->name
, name
, sizeof(entry
->name
));
334 entry
->name
[INJ_NAME_MAXLEN
- 1] = '\0';
335 strlcpy(entry
->library
, library
, sizeof(entry
->library
));
336 entry
->library
[INJ_LIB_MAXLEN
- 1] = '\0';
337 strlcpy(entry
->function
, function
, sizeof(entry
->function
));
338 entry
->function
[INJ_FUNC_MAXLEN
- 1] = '\0';
339 if (private_data
!= NULL
)
340 memcpy(entry
->private_data
, private_data
, private_data_size
);
343 pg_atomic_write_u64(&entry
->generation
, generation
+ 1);
345 if (free_idx
+ 1 > max_inuse
)
346 pg_atomic_write_u32(&ActiveInjectionPoints
->max_inuse
, free_idx
+ 1);
348 LWLockRelease(InjectionPointLock
);
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.
361 InjectionPointDetach(const char *name
)
363 #ifdef USE_INJECTION_POINTS
368 LWLockAcquire(InjectionPointLock
, LW_EXCLUSIVE
);
370 /* Find it in the shmem array, and mark the slot as unused */
371 max_inuse
= (int) pg_atomic_read_u32(&ActiveInjectionPoints
->max_inuse
);
372 for (idx
= max_inuse
- 1; idx
>= 0; --idx
)
374 InjectionPointEntry
*entry
= &ActiveInjectionPoints
->entries
[idx
];
377 generation
= pg_atomic_read_u64(&entry
->generation
);
378 if (generation
% 2 == 0)
379 continue; /* empty slot */
381 if (strcmp(entry
->name
, name
) == 0)
385 pg_atomic_write_u64(&entry
->generation
, generation
+ 1);
390 /* If we just removed the highest-numbered entry, update 'max_inuse' */
391 if (found
&& idx
== max_inuse
- 1)
393 for (; idx
>= 0; --idx
)
395 InjectionPointEntry
*entry
= &ActiveInjectionPoints
->entries
[idx
];
398 generation
= pg_atomic_read_u64(&entry
->generation
);
399 if (generation
% 2 != 0)
402 pg_atomic_write_u32(&ActiveInjectionPoints
->max_inuse
, idx
+ 1);
404 LWLockRelease(InjectionPointLock
);
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.
420 static InjectionPointCacheEntry
*
421 InjectionPointCacheRefresh(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.
434 max_inuse
= pg_atomic_read_u32(&ActiveInjectionPoints
->max_inuse
);
437 if (InjectionPointCache
)
439 hash_destroy(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
];
455 if (pg_atomic_read_u64(&entry
->generation
) == cached
->generation
)
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.
484 generation
= pg_atomic_read_u64(&entry
->generation
);
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
));
502 if (pg_atomic_read_u64(&entry
->generation
) != generation
)
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.
531 InjectionPointLoad(const char *name
)
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.
544 InjectionPointRun(const char *name
)
546 #ifdef USE_INJECTION_POINTS
547 InjectionPointCacheEntry
*cache_entry
;
549 cache_entry
= InjectionPointCacheRefresh(name
);
551 cache_entry
->callback(name
, cache_entry
->private_data
);
553 elog(ERROR
, "Injection points are not supported by this build");
558 * Execute an injection point directly from the cache, if defined.
561 InjectionPointCached(const char *name
)
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
);
570 elog(ERROR
, "Injection points are not supported by this build");
575 * Test if an injection point is defined.
578 IsInjectionPointAttached(const char *name
)
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 */