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 23740 : injection_point_cache_get(const char *name)
215 : {
216 : bool found;
217 : InjectionPointCacheEntry *entry;
218 :
219 : /* no callback if no cache yet */
220 23740 : if (InjectionPointCache == NULL)
221 14166 : return NULL;
222 :
223 : entry = (InjectionPointCacheEntry *)
224 9574 : hash_search(InjectionPointCache, name, HASH_FIND, &found);
225 :
226 9574 : if (found)
227 2806 : return entry;
228 :
229 6768 : 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 1234 : InjectionPointShmemRequest(void *arg)
242 : {
243 1234 : ShmemRequestStruct(.name = "InjectionPoint hash",
244 : .size = sizeof(InjectionPointsCtl),
245 : .ptr = (void **) &ActiveInjectionPoints,
246 : );
247 1234 : }
248 :
249 : static void
250 1231 : InjectionPointShmemInit(void *arg)
251 : {
252 1231 : pg_atomic_init_u32(&ActiveInjectionPoints->max_inuse, 0);
253 158799 : for (int i = 0; i < MAX_INJECTION_POINTS; i++)
254 157568 : pg_atomic_init_u64(&ActiveInjectionPoints->entries[i].generation, 0);
255 1231 : }
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 : if (private_data != NULL)
326 113 : memcpy(entry->private_data, private_data, private_data_size);
327 :
328 128 : pg_write_barrier();
329 128 : pg_atomic_write_u64(&entry->generation, generation + 1);
330 :
331 128 : if (free_idx + 1 > max_inuse)
332 128 : pg_atomic_write_u32(&ActiveInjectionPoints->max_inuse, free_idx + 1);
333 :
334 128 : LWLockRelease(InjectionPointLock);
335 :
336 : #else
337 : elog(ERROR, "injection points are not supported by this build");
338 : #endif
339 128 : }
340 :
341 : /*
342 : * Detach an existing injection point.
343 : *
344 : * Returns true if the injection point was detached, false otherwise.
345 : */
346 : bool
347 220 : InjectionPointDetach(const char *name)
348 : {
349 : #ifdef USE_INJECTION_POINTS
350 220 : bool found = false;
351 : int idx;
352 : int max_inuse;
353 :
354 220 : LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE);
355 :
356 : /* Find it in the shmem array, and mark the slot as unused */
357 220 : max_inuse = (int) pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
358 260 : for (idx = max_inuse - 1; idx >= 0; --idx)
359 : {
360 141 : InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
361 : uint64 generation;
362 :
363 141 : generation = pg_atomic_read_u64(&entry->generation);
364 141 : if (generation % 2 == 0)
365 2 : continue; /* empty slot */
366 :
367 139 : if (strcmp(entry->name, name) == 0)
368 : {
369 : Assert(!found);
370 101 : found = true;
371 101 : pg_atomic_write_u64(&entry->generation, generation + 1);
372 101 : break;
373 : }
374 : }
375 :
376 : /* If we just removed the highest-numbered entry, update 'max_inuse' */
377 220 : if (found && idx == max_inuse - 1)
378 : {
379 182 : for (; idx >= 0; --idx)
380 : {
381 124 : InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
382 : uint64 generation;
383 :
384 124 : generation = pg_atomic_read_u64(&entry->generation);
385 124 : if (generation % 2 != 0)
386 23 : break;
387 : }
388 81 : pg_atomic_write_u32(&ActiveInjectionPoints->max_inuse, idx + 1);
389 : }
390 220 : LWLockRelease(InjectionPointLock);
391 :
392 220 : return found;
393 : #else
394 : elog(ERROR, "Injection points are not supported by this build");
395 : return true; /* silence compiler */
396 : #endif
397 : }
398 :
399 : #ifdef USE_INJECTION_POINTS
400 : /*
401 : * Common workhorse of InjectionPointRun() and InjectionPointLoad()
402 : *
403 : * Checks if an injection point exists in shared memory, and update
404 : * the local cache entry accordingly.
405 : */
406 : static InjectionPointCacheEntry *
407 7358597 : InjectionPointCacheRefresh(const char *name)
408 : {
409 : uint32 max_inuse;
410 : int namelen;
411 : InjectionPointEntry local_copy;
412 : InjectionPointCacheEntry *cached;
413 :
414 : /*
415 : * First read the number of in-use slots. More entries can be added or
416 : * existing ones can be removed while we're reading them. If the entry
417 : * we're looking for is concurrently added or removed, we might or might
418 : * not see it. That's OK.
419 : */
420 7358597 : max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
421 7358597 : if (max_inuse == 0)
422 : {
423 7342177 : if (InjectionPointCache)
424 : {
425 54 : hash_destroy(InjectionPointCache);
426 54 : InjectionPointCache = NULL;
427 : }
428 7342177 : return NULL;
429 : }
430 :
431 : /*
432 : * If we have this entry in the local cache already, check if the cached
433 : * entry is still valid.
434 : */
435 16420 : cached = injection_point_cache_get(name);
436 16420 : if (cached)
437 : {
438 2791 : int idx = cached->slot_idx;
439 2791 : InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
440 :
441 2791 : if (pg_atomic_read_u64(&entry->generation) == cached->generation)
442 : {
443 : /* still good */
444 2770 : return cached;
445 : }
446 21 : injection_point_cache_remove(name);
447 21 : cached = NULL;
448 : }
449 :
450 : /*
451 : * Search the shared memory array.
452 : *
453 : * It's possible that the entry we're looking for is concurrently detached
454 : * or attached. Or detached *and* re-attached, to the same slot or a
455 : * different slot. Detach and re-attach is not an atomic operation, so
456 : * it's OK for us to return the old value, NULL, or the new value in such
457 : * cases.
458 : */
459 13650 : namelen = strlen(name);
460 34624 : for (int idx = 0; idx < max_inuse; idx++)
461 : {
462 21136 : InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
463 : uint64 generation;
464 :
465 : /*
466 : * Read the generation number so that we can detect concurrent
467 : * modifications. The read barrier ensures that the generation number
468 : * is loaded before any of the other fields.
469 : */
470 21136 : generation = pg_atomic_read_u64(&entry->generation);
471 21136 : if (generation % 2 == 0)
472 155 : continue; /* empty slot */
473 20981 : pg_read_barrier();
474 :
475 : /* Is this the injection point we're looking for? */
476 20981 : if (memcmp(entry->name, name, namelen + 1) != 0)
477 20819 : continue;
478 :
479 : /*
480 : * The entry can change at any time, if the injection point is
481 : * concurrently detached. Copy it to local memory, and re-check the
482 : * generation. If the generation hasn't changed, we know our local
483 : * copy is coherent.
484 : */
485 162 : memcpy(&local_copy, entry, sizeof(InjectionPointEntry));
486 :
487 162 : pg_read_barrier();
488 162 : if (pg_atomic_read_u64(&entry->generation) != generation)
489 : {
490 : /*
491 : * The entry was concurrently detached.
492 : *
493 : * Continue the search, because if the generation number changed,
494 : * we cannot trust the result of the name comparison we did above.
495 : * It's theoretically possible that it falsely matched a mixed-up
496 : * state of the old and new name, if the slot was recycled with a
497 : * different name.
498 : */
499 0 : continue;
500 : }
501 :
502 : /* Success! Load it into the cache and return it */
503 162 : return injection_point_cache_load(&local_copy, idx, generation);
504 : }
505 13488 : return NULL;
506 : }
507 : #endif
508 :
509 : /*
510 : * Load an injection point into the local cache.
511 : *
512 : * This is useful to be able to load an injection point before running it,
513 : * especially if the injection point is called in a code path where memory
514 : * allocations cannot happen, like critical sections.
515 : */
516 : void
517 7325 : InjectionPointLoad(const char *name)
518 : {
519 : #ifdef USE_INJECTION_POINTS
520 7325 : InjectionPointCacheRefresh(name);
521 : #else
522 : elog(ERROR, "Injection points are not supported by this build");
523 : #endif
524 7325 : }
525 :
526 : /*
527 : * Execute an injection point, if defined.
528 : */
529 : void
530 7263391 : InjectionPointRun(const char *name, void *arg)
531 : {
532 : #ifdef USE_INJECTION_POINTS
533 : InjectionPointCacheEntry *cache_entry;
534 :
535 7263391 : cache_entry = InjectionPointCacheRefresh(name);
536 7263391 : if (cache_entry)
537 2903 : cache_entry->callback(name, cache_entry->private_data, arg);
538 : #else
539 : elog(ERROR, "Injection points are not supported by this build");
540 : #endif
541 7263377 : }
542 :
543 : /*
544 : * Execute an injection point directly from the cache, if defined.
545 : */
546 : void
547 7320 : InjectionPointCached(const char *name, void *arg)
548 : {
549 : #ifdef USE_INJECTION_POINTS
550 : InjectionPointCacheEntry *cache_entry;
551 :
552 7320 : cache_entry = injection_point_cache_get(name);
553 7320 : if (cache_entry)
554 15 : cache_entry->callback(name, cache_entry->private_data, arg);
555 : #else
556 : elog(ERROR, "Injection points are not supported by this build");
557 : #endif
558 7320 : }
559 :
560 : /*
561 : * Test if an injection point is defined.
562 : */
563 : bool
564 87881 : IsInjectionPointAttached(const char *name)
565 : {
566 : #ifdef USE_INJECTION_POINTS
567 87881 : return InjectionPointCacheRefresh(name) != NULL;
568 : #else
569 : elog(ERROR, "Injection points are not supported by this build");
570 : return false; /* silence compiler */
571 : #endif
572 : }
573 :
574 : /*
575 : * Retrieve a list of all the injection points currently attached.
576 : *
577 : * This list is palloc'd in the current memory context.
578 : */
579 : List *
580 3 : InjectionPointList(void)
581 : {
582 : #ifdef USE_INJECTION_POINTS
583 3 : List *inj_points = NIL;
584 : uint32 max_inuse;
585 :
586 3 : LWLockAcquire(InjectionPointLock, LW_SHARED);
587 :
588 3 : max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
589 :
590 7 : for (uint32 idx = 0; idx < max_inuse; idx++)
591 : {
592 : InjectionPointEntry *entry;
593 : InjectionPointData *inj_point;
594 : uint64 generation;
595 :
596 4 : entry = &ActiveInjectionPoints->entries[idx];
597 4 : generation = pg_atomic_read_u64(&entry->generation);
598 :
599 : /* skip free slots */
600 4 : if (generation % 2 == 0)
601 0 : continue;
602 :
603 4 : inj_point = palloc0_object(InjectionPointData);
604 4 : inj_point->name = pstrdup(entry->name);
605 4 : inj_point->library = pstrdup(entry->library);
606 4 : inj_point->function = pstrdup(entry->function);
607 4 : inj_points = lappend(inj_points, inj_point);
608 : }
609 :
610 3 : LWLockRelease(InjectionPointLock);
611 :
612 3 : return inj_points;
613 :
614 : #else
615 : elog(ERROR, "Injection points are not supported by this build");
616 : return NIL; /* keep compiler quiet */
617 : #endif
618 : }
|