Line data Source code
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 :
20 : #include "utils/injection_point.h"
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 */
42 : typedef 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];
73 : } InjectionPointEntry;
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 : */
84 : typedef struct InjectionPointsCtl
85 : {
86 : pg_atomic_uint32 max_inuse;
87 : InjectionPointEntry entries[MAX_INJECTION_POINTS];
88 : } InjectionPointsCtl;
89 :
90 : NON_EXEC_STATIC InjectionPointsCtl *ActiveInjectionPoints;
91 :
92 : /*
93 : * Backend local cache of injection callbacks already loaded, stored in
94 : * TopMemoryContext.
95 : */
96 : typedef struct InjectionPointCacheEntry
97 : {
98 : char name[INJ_NAME_MAXLEN];
99 : char private_data[INJ_PRIVATE_MAXLEN];
100 : InjectionPointCallback callback;
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;
109 : } InjectionPointCacheEntry;
110 :
111 : static HTAB *InjectionPointCache = NULL;
112 :
113 : static void InjectionPointShmemRequest(void *arg);
114 : static void InjectionPointShmemInit(void *arg);
115 :
116 : /*
117 : * injection_point_cache_add
118 : *
119 : * Add an injection point to the local cache.
120 : */
121 : static InjectionPointCacheEntry *
122 162 : injection_point_cache_add(const char *name,
123 : int slot_idx,
124 : uint64 generation,
125 : InjectionPointCallback callback,
126 : const void *private_data)
127 : {
128 : InjectionPointCacheEntry *entry;
129 : bool found;
130 :
131 : /* If first time, initialize */
132 162 : if (InjectionPointCache == NULL)
133 : {
134 : HASHCTL hash_ctl;
135 :
136 108 : hash_ctl.keysize = sizeof(char[INJ_NAME_MAXLEN]);
137 108 : hash_ctl.entrysize = sizeof(InjectionPointCacheEntry);
138 108 : hash_ctl.hcxt = TopMemoryContext;
139 :
140 108 : InjectionPointCache = hash_create("InjectionPoint cache hash",
141 : MAX_INJECTION_POINTS,
142 : &hash_ctl,
143 : HASH_ELEM | HASH_STRINGS | HASH_CONTEXT);
144 : }
145 :
146 : entry = (InjectionPointCacheEntry *)
147 162 : hash_search(InjectionPointCache, name, HASH_ENTER, &found);
148 :
149 : Assert(!found);
150 162 : strlcpy(entry->name, name, sizeof(entry->name));
151 162 : entry->slot_idx = slot_idx;
152 162 : entry->generation = generation;
153 162 : entry->callback = callback;
154 162 : memcpy(entry->private_data, private_data, INJ_PRIVATE_MAXLEN);
155 :
156 162 : 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 : */
166 : static void
167 21 : injection_point_cache_remove(const char *name)
168 : {
169 : bool found PG_USED_FOR_ASSERTS_ONLY;
170 :
171 21 : (void) hash_search(InjectionPointCache, name, HASH_REMOVE, &found);
172 : Assert(found);
173 21 : }
174 :
175 : /*
176 : * injection_point_cache_load
177 : *
178 : * Load an injection point into the local cache.
179 : */
180 : static InjectionPointCacheEntry *
181 162 : injection_point_cache_load(InjectionPointEntry *entry, int slot_idx, uint64 generation)
182 : {
183 : char path[MAXPGPATH];
184 : void *injection_callback_local;
185 :
186 162 : snprintf(path, MAXPGPATH, "%s/%s%s", pkglib_path,
187 162 : entry->library, DLSUFFIX);
188 :
189 162 : if (!pg_file_exists(path))
190 0 : elog(ERROR, "could not find library \"%s\" for injection point \"%s\"",
191 : path, entry->name);
192 :
193 : injection_callback_local =
194 162 : load_external_function(path, entry->function, false, NULL);
195 :
196 162 : if (injection_callback_local == NULL)
197 0 : 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 324 : return injection_point_cache_add(entry->name,
202 : slot_idx,
203 : generation,
204 : injection_callback_local,
205 162 : entry->private_data);
206 : }
207 :
208 : /*
209 : * injection_point_cache_get
210 : *
211 : * Retrieve an injection point from the local cache, if any.
212 : */
213 : static InjectionPointCacheEntry *
214 24109 : injection_point_cache_get(const char *name)
215 : {
216 : bool found;
217 : InjectionPointCacheEntry *entry;
218 :
219 : /* no callback if no cache yet */
220 24109 : if (InjectionPointCache == NULL)
221 14566 : return NULL;
222 :
223 : entry = (InjectionPointCacheEntry *)
224 9543 : hash_search(InjectionPointCache, name, HASH_FIND, &found);
225 :
226 9543 : if (found)
227 2807 : return entry;
228 :
229 6736 : return NULL;
230 : }
231 :
232 : const ShmemCallbacks InjectionPointShmemCallbacks = {
233 : .request_fn = InjectionPointShmemRequest,
234 : .init_fn = InjectionPointShmemInit,
235 : };
236 :
237 : /*
238 : * Reserve space for the dynamic shared hash table
239 : */
240 : static void
241 1238 : InjectionPointShmemRequest(void *arg)
242 : {
243 1238 : ShmemRequestStruct(.name = "InjectionPoint hash",
244 : .size = sizeof(InjectionPointsCtl),
245 : .ptr = (void **) &ActiveInjectionPoints,
246 : );
247 1238 : }
248 :
249 : static void
250 1235 : InjectionPointShmemInit(void *arg)
251 : {
252 1235 : pg_atomic_init_u32(&ActiveInjectionPoints->max_inuse, 0);
253 159315 : for (int i = 0; i < MAX_INJECTION_POINTS; i++)
254 158080 : pg_atomic_init_u64(&ActiveInjectionPoints->entries[i].generation, 0);
255 1235 : }
256 : #endif /* USE_INJECTION_POINTS */
257 :
258 : /*
259 : * Attach a new injection point.
260 : */
261 : void
262 132 : InjectionPointAttach(const char *name,
263 : const char *library,
264 : const char *function,
265 : const void *private_data,
266 : int private_data_size)
267 : {
268 : #ifdef USE_INJECTION_POINTS
269 : InjectionPointEntry *entry;
270 : uint64 generation;
271 : uint32 max_inuse;
272 : int free_idx;
273 :
274 132 : if (strlen(name) >= INJ_NAME_MAXLEN)
275 1 : elog(ERROR, "injection point name %s too long (maximum of %u characters)",
276 : name, INJ_NAME_MAXLEN - 1);
277 131 : if (strlen(library) >= INJ_LIB_MAXLEN)
278 1 : elog(ERROR, "injection point library %s too long (maximum of %u characters)",
279 : library, INJ_LIB_MAXLEN - 1);
280 130 : if (strlen(function) >= INJ_FUNC_MAXLEN)
281 1 : elog(ERROR, "injection point function %s too long (maximum of %u characters)",
282 : function, INJ_FUNC_MAXLEN - 1);
283 129 : if (private_data_size > INJ_PRIVATE_MAXLEN)
284 1 : elog(ERROR, "injection point data too long (maximum of %u bytes)",
285 : INJ_PRIVATE_MAXLEN);
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 : */
291 128 : LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE);
292 128 : max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
293 128 : free_idx = -1;
294 :
295 212 : for (int idx = 0; idx < max_inuse; idx++)
296 : {
297 84 : entry = &ActiveInjectionPoints->entries[idx];
298 84 : generation = pg_atomic_read_u64(&entry->generation);
299 84 : 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 0 : if (free_idx == -1)
306 0 : free_idx = idx;
307 : }
308 84 : else if (strcmp(entry->name, name) == 0)
309 0 : elog(ERROR, "injection point \"%s\" already defined", name);
310 : }
311 128 : if (free_idx == -1)
312 : {
313 128 : if (max_inuse == MAX_INJECTION_POINTS)
314 0 : elog(ERROR, "too many injection points");
315 128 : free_idx = max_inuse;
316 : }
317 128 : entry = &ActiveInjectionPoints->entries[free_idx];
318 128 : generation = pg_atomic_read_u64(&entry->generation);
319 : Assert(generation % 2 == 0);
320 :
321 : /* Save the entry */
322 128 : strlcpy(entry->name, name, sizeof(entry->name));
323 128 : strlcpy(entry->library, library, sizeof(entry->library));
324 128 : strlcpy(entry->function, function, sizeof(entry->function));
325 128 : memset(entry->private_data, 0, INJ_PRIVATE_MAXLEN);
326 128 : if (private_data != NULL)
327 113 : memcpy(entry->private_data, private_data, private_data_size);
328 :
329 128 : pg_write_barrier();
330 128 : pg_atomic_write_u64(&entry->generation, generation + 1);
331 :
332 128 : if (free_idx + 1 > max_inuse)
333 128 : pg_atomic_write_u32(&ActiveInjectionPoints->max_inuse, free_idx + 1);
334 :
335 128 : LWLockRelease(InjectionPointLock);
336 :
337 : #else
338 : elog(ERROR, "injection points are not supported by this build");
339 : #endif
340 128 : }
341 :
342 : /*
343 : * Detach an existing injection point.
344 : *
345 : * Returns true if the injection point was detached, false otherwise.
346 : */
347 : bool
348 220 : InjectionPointDetach(const char *name)
349 : {
350 : #ifdef USE_INJECTION_POINTS
351 220 : bool found = false;
352 : int idx;
353 : int max_inuse;
354 :
355 220 : LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE);
356 :
357 : /* Find it in the shmem array, and mark the slot as unused */
358 220 : max_inuse = (int) pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
359 260 : for (idx = max_inuse - 1; idx >= 0; --idx)
360 : {
361 141 : InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
362 : uint64 generation;
363 :
364 141 : generation = pg_atomic_read_u64(&entry->generation);
365 141 : if (generation % 2 == 0)
366 2 : continue; /* empty slot */
367 :
368 139 : if (strcmp(entry->name, name) == 0)
369 : {
370 : Assert(!found);
371 101 : found = true;
372 101 : pg_atomic_write_u64(&entry->generation, generation + 1);
373 101 : break;
374 : }
375 : }
376 :
377 : /* If we just removed the highest-numbered entry, update 'max_inuse' */
378 220 : if (found && idx == max_inuse - 1)
379 : {
380 182 : for (; idx >= 0; --idx)
381 : {
382 124 : InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
383 : uint64 generation;
384 :
385 124 : generation = pg_atomic_read_u64(&entry->generation);
386 124 : if (generation % 2 != 0)
387 23 : break;
388 : }
389 81 : pg_atomic_write_u32(&ActiveInjectionPoints->max_inuse, idx + 1);
390 : }
391 220 : LWLockRelease(InjectionPointLock);
392 :
393 220 : 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 : */
407 : static InjectionPointCacheEntry *
408 7344081 : InjectionPointCacheRefresh(const char *name)
409 : {
410 : uint32 max_inuse;
411 : int namelen;
412 : InjectionPointEntry local_copy;
413 : InjectionPointCacheEntry *cached;
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 : */
421 7344081 : max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
422 7344081 : if (max_inuse == 0)
423 : {
424 7327297 : if (InjectionPointCache)
425 : {
426 55 : hash_destroy(InjectionPointCache);
427 55 : InjectionPointCache = NULL;
428 : }
429 7327297 : 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 : */
436 16784 : cached = injection_point_cache_get(name);
437 16784 : if (cached)
438 : {
439 2792 : int idx = cached->slot_idx;
440 2792 : InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
441 :
442 2792 : if (pg_atomic_read_u64(&entry->generation) == cached->generation)
443 : {
444 : /* still good */
445 2771 : return cached;
446 : }
447 21 : injection_point_cache_remove(name);
448 21 : 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 14013 : namelen = strlen(name);
461 35351 : for (int idx = 0; idx < max_inuse; idx++)
462 : {
463 21500 : InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
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 21500 : generation = pg_atomic_read_u64(&entry->generation);
472 21500 : if (generation % 2 == 0)
473 155 : continue; /* empty slot */
474 21345 : pg_read_barrier();
475 :
476 : /* Is this the injection point we're looking for? */
477 21345 : if (memcmp(entry->name, name, namelen + 1) != 0)
478 21183 : 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 162 : memcpy(&local_copy, entry, sizeof(InjectionPointEntry));
487 :
488 162 : pg_read_barrier();
489 162 : 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 0 : continue;
501 : }
502 :
503 : /* Success! Load it into the cache and return it */
504 162 : return injection_point_cache_load(&local_copy, idx, generation);
505 : }
506 13851 : 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 : */
517 : void
518 7326 : InjectionPointLoad(const char *name)
519 : {
520 : #ifdef USE_INJECTION_POINTS
521 7326 : InjectionPointCacheRefresh(name);
522 : #else
523 : elog(ERROR, "Injection points are not supported by this build");
524 : #endif
525 7326 : }
526 :
527 : /*
528 : * Execute an injection point, if defined.
529 : */
530 : void
531 7248754 : InjectionPointRun(const char *name, void *arg)
532 : {
533 : #ifdef USE_INJECTION_POINTS
534 : InjectionPointCacheEntry *cache_entry;
535 :
536 7248754 : cache_entry = InjectionPointCacheRefresh(name);
537 7248754 : if (cache_entry)
538 2904 : 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 7248740 : }
543 :
544 : /*
545 : * Execute an injection point directly from the cache, if defined.
546 : */
547 : void
548 7325 : InjectionPointCached(const char *name, void *arg)
549 : {
550 : #ifdef USE_INJECTION_POINTS
551 : InjectionPointCacheEntry *cache_entry;
552 :
553 7325 : cache_entry = injection_point_cache_get(name);
554 7325 : if (cache_entry)
555 15 : 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 7325 : }
560 :
561 : /*
562 : * Test if an injection point is defined.
563 : */
564 : bool
565 88001 : IsInjectionPointAttached(const char *name)
566 : {
567 : #ifdef USE_INJECTION_POINTS
568 88001 : return InjectionPointCacheRefresh(name) != NULL;
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 : */
580 : List *
581 3 : InjectionPointList(void)
582 : {
583 : #ifdef USE_INJECTION_POINTS
584 3 : List *inj_points = NIL;
585 : uint32 max_inuse;
586 :
587 3 : LWLockAcquire(InjectionPointLock, LW_SHARED);
588 :
589 3 : max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
590 :
591 7 : for (uint32 idx = 0; idx < max_inuse; idx++)
592 : {
593 : InjectionPointEntry *entry;
594 : InjectionPointData *inj_point;
595 : uint64 generation;
596 :
597 4 : entry = &ActiveInjectionPoints->entries[idx];
598 4 : generation = pg_atomic_read_u64(&entry->generation);
599 :
600 : /* skip free slots */
601 4 : if (generation % 2 == 0)
602 0 : continue;
603 :
604 4 : inj_point = palloc0_object(InjectionPointData);
605 4 : inj_point->name = pstrdup(entry->name);
606 4 : inj_point->library = pstrdup(entry->library);
607 4 : inj_point->function = pstrdup(entry->function);
608 4 : inj_points = lappend(inj_points, inj_point);
609 : }
610 :
611 3 : LWLockRelease(InjectionPointLock);
612 :
613 3 : 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 : }
|