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-2025, 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 "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 */
41 : typedef 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 : */
83 : typedef struct InjectionPointsCtl
84 : {
85 : pg_atomic_uint32 max_inuse;
86 : InjectionPointEntry entries[MAX_INJECTION_POINTS];
87 : } InjectionPointsCtl;
88 :
89 : NON_EXEC_STATIC InjectionPointsCtl *ActiveInjectionPoints;
90 :
91 : /*
92 : * Backend local cache of injection callbacks already loaded, stored in
93 : * TopMemoryContext.
94 : */
95 : typedef struct InjectionPointCacheEntry
96 : {
97 : char name[INJ_NAME_MAXLEN];
98 : char private_data[INJ_PRIVATE_MAXLEN];
99 : InjectionPointCallback callback;
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 :
110 : static HTAB *InjectionPointCache = NULL;
111 :
112 : /*
113 : * injection_point_cache_add
114 : *
115 : * Add an injection point to the local cache.
116 : */
117 : static InjectionPointCacheEntry *
118 144 : injection_point_cache_add(const char *name,
119 : int slot_idx,
120 : uint64 generation,
121 : InjectionPointCallback callback,
122 : const void *private_data)
123 : {
124 : InjectionPointCacheEntry *entry;
125 : bool found;
126 :
127 : /* If first time, initialize */
128 144 : if (InjectionPointCache == NULL)
129 : {
130 : HASHCTL hash_ctl;
131 :
132 92 : hash_ctl.keysize = sizeof(char[INJ_NAME_MAXLEN]);
133 92 : hash_ctl.entrysize = sizeof(InjectionPointCacheEntry);
134 92 : hash_ctl.hcxt = TopMemoryContext;
135 :
136 92 : InjectionPointCache = hash_create("InjectionPoint cache hash",
137 : MAX_INJECTION_POINTS,
138 : &hash_ctl,
139 : HASH_ELEM | HASH_STRINGS | HASH_CONTEXT);
140 : }
141 :
142 : entry = (InjectionPointCacheEntry *)
143 144 : hash_search(InjectionPointCache, name, HASH_ENTER, &found);
144 :
145 : Assert(!found);
146 144 : strlcpy(entry->name, name, sizeof(entry->name));
147 144 : entry->slot_idx = slot_idx;
148 144 : entry->generation = generation;
149 144 : entry->callback = callback;
150 144 : memcpy(entry->private_data, private_data, INJ_PRIVATE_MAXLEN);
151 :
152 144 : 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 : */
162 : static void
163 22 : injection_point_cache_remove(const char *name)
164 : {
165 : bool found PG_USED_FOR_ASSERTS_ONLY;
166 :
167 22 : (void) hash_search(InjectionPointCache, name, HASH_REMOVE, &found);
168 : Assert(found);
169 22 : }
170 :
171 : /*
172 : * injection_point_cache_load
173 : *
174 : * Load an injection point into the local cache.
175 : */
176 : static InjectionPointCacheEntry *
177 144 : injection_point_cache_load(InjectionPointEntry *entry, int slot_idx, uint64 generation)
178 : {
179 : char path[MAXPGPATH];
180 : void *injection_callback_local;
181 :
182 144 : snprintf(path, MAXPGPATH, "%s/%s%s", pkglib_path,
183 144 : entry->library, DLSUFFIX);
184 :
185 144 : if (!pg_file_exists(path))
186 0 : elog(ERROR, "could not find library \"%s\" for injection point \"%s\"",
187 : path, entry->name);
188 :
189 : injection_callback_local =
190 144 : load_external_function(path, entry->function, false, NULL);
191 :
192 144 : if (injection_callback_local == NULL)
193 0 : 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 288 : return injection_point_cache_add(entry->name,
198 : slot_idx,
199 : generation,
200 : injection_callback_local,
201 144 : entry->private_data);
202 : }
203 :
204 : /*
205 : * injection_point_cache_get
206 : *
207 : * Retrieve an injection point from the local cache, if any.
208 : */
209 : static InjectionPointCacheEntry *
210 38382 : injection_point_cache_get(const char *name)
211 : {
212 : bool found;
213 : InjectionPointCacheEntry *entry;
214 :
215 : /* no callback if no cache yet */
216 38382 : if (InjectionPointCache == NULL)
217 23604 : return NULL;
218 :
219 : entry = (InjectionPointCacheEntry *)
220 14778 : hash_search(InjectionPointCache, name, HASH_FIND, &found);
221 :
222 14778 : if (found)
223 3418 : return entry;
224 :
225 11360 : return NULL;
226 : }
227 : #endif /* USE_INJECTION_POINTS */
228 :
229 : /*
230 : * Return the space for dynamic shared hash table.
231 : */
232 : Size
233 4226 : InjectionPointShmemSize(void)
234 : {
235 : #ifdef USE_INJECTION_POINTS
236 4226 : Size sz = 0;
237 :
238 4226 : sz = add_size(sz, sizeof(InjectionPointsCtl));
239 4226 : return sz;
240 : #else
241 : return 0;
242 : #endif
243 : }
244 :
245 : /*
246 : * Allocate shmem space for dynamic shared hash.
247 : */
248 : void
249 2266 : InjectionPointShmemInit(void)
250 : {
251 : #ifdef USE_INJECTION_POINTS
252 : bool found;
253 :
254 2266 : ActiveInjectionPoints = ShmemInitStruct("InjectionPoint hash",
255 : sizeof(InjectionPointsCtl),
256 : &found);
257 2266 : if (!IsUnderPostmaster)
258 : {
259 : Assert(!found);
260 2266 : pg_atomic_init_u32(&ActiveInjectionPoints->max_inuse, 0);
261 292314 : for (int i = 0; i < MAX_INJECTION_POINTS; i++)
262 290048 : pg_atomic_init_u64(&ActiveInjectionPoints->entries[i].generation, 0);
263 : }
264 : else
265 : Assert(found);
266 : #endif
267 2266 : }
268 :
269 : /*
270 : * Attach a new injection point.
271 : */
272 : void
273 148 : InjectionPointAttach(const char *name,
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 148 : if (strlen(name) >= INJ_NAME_MAXLEN)
286 2 : elog(ERROR, "injection point name %s too long (maximum of %u characters)",
287 : name, INJ_NAME_MAXLEN - 1);
288 146 : if (strlen(library) >= INJ_LIB_MAXLEN)
289 2 : elog(ERROR, "injection point library %s too long (maximum of %u characters)",
290 : library, INJ_LIB_MAXLEN - 1);
291 144 : if (strlen(function) >= INJ_FUNC_MAXLEN)
292 2 : elog(ERROR, "injection point function %s too long (maximum of %u characters)",
293 : function, INJ_FUNC_MAXLEN - 1);
294 142 : if (private_data_size > INJ_PRIVATE_MAXLEN)
295 2 : 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 140 : LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE);
303 140 : max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
304 140 : free_idx = -1;
305 :
306 218 : for (int idx = 0; idx < max_inuse; idx++)
307 : {
308 78 : entry = &ActiveInjectionPoints->entries[idx];
309 78 : generation = pg_atomic_read_u64(&entry->generation);
310 78 : 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 0 : if (free_idx == -1)
317 0 : free_idx = idx;
318 : }
319 78 : else if (strcmp(entry->name, name) == 0)
320 0 : elog(ERROR, "injection point \"%s\" already defined", name);
321 : }
322 140 : if (free_idx == -1)
323 : {
324 140 : if (max_inuse == MAX_INJECTION_POINTS)
325 0 : elog(ERROR, "too many injection points");
326 140 : free_idx = max_inuse;
327 : }
328 140 : entry = &ActiveInjectionPoints->entries[free_idx];
329 140 : generation = pg_atomic_read_u64(&entry->generation);
330 : Assert(generation % 2 == 0);
331 :
332 : /* Save the entry */
333 140 : strlcpy(entry->name, name, sizeof(entry->name));
334 140 : strlcpy(entry->library, library, sizeof(entry->library));
335 140 : strlcpy(entry->function, function, sizeof(entry->function));
336 140 : if (private_data != NULL)
337 130 : memcpy(entry->private_data, private_data, private_data_size);
338 :
339 140 : pg_write_barrier();
340 140 : pg_atomic_write_u64(&entry->generation, generation + 1);
341 :
342 140 : if (free_idx + 1 > max_inuse)
343 140 : pg_atomic_write_u32(&ActiveInjectionPoints->max_inuse, free_idx + 1);
344 :
345 140 : LWLockRelease(InjectionPointLock);
346 :
347 : #else
348 : elog(ERROR, "injection points are not supported by this build");
349 : #endif
350 140 : }
351 :
352 : /*
353 : * Detach an existing injection point.
354 : *
355 : * Returns true if the injection point was detached, false otherwise.
356 : */
357 : bool
358 270 : InjectionPointDetach(const char *name)
359 : {
360 : #ifdef USE_INJECTION_POINTS
361 270 : bool found = false;
362 : int idx;
363 : int max_inuse;
364 :
365 270 : LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE);
366 :
367 : /* Find it in the shmem array, and mark the slot as unused */
368 270 : max_inuse = (int) pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
369 328 : for (idx = max_inuse - 1; idx >= 0; --idx)
370 : {
371 164 : InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
372 : uint64 generation;
373 :
374 164 : generation = pg_atomic_read_u64(&entry->generation);
375 164 : if (generation % 2 == 0)
376 4 : continue; /* empty slot */
377 :
378 160 : if (strcmp(entry->name, name) == 0)
379 : {
380 : Assert(!found);
381 106 : found = true;
382 106 : pg_atomic_write_u64(&entry->generation, generation + 1);
383 106 : break;
384 : }
385 : }
386 :
387 : /* If we just removed the highest-numbered entry, update 'max_inuse' */
388 270 : if (found && idx == max_inuse - 1)
389 : {
390 188 : for (; idx >= 0; --idx)
391 : {
392 118 : InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
393 : uint64 generation;
394 :
395 118 : generation = pg_atomic_read_u64(&entry->generation);
396 118 : if (generation % 2 != 0)
397 12 : break;
398 : }
399 82 : pg_atomic_write_u32(&ActiveInjectionPoints->max_inuse, idx + 1);
400 : }
401 270 : LWLockRelease(InjectionPointLock);
402 :
403 270 : 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 : */
417 : static InjectionPointCacheEntry *
418 9043622 : InjectionPointCacheRefresh(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 9043622 : max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
432 9043622 : if (max_inuse == 0)
433 : {
434 9019614 : if (InjectionPointCache)
435 : {
436 56 : hash_destroy(InjectionPointCache);
437 56 : InjectionPointCache = NULL;
438 : }
439 9019614 : 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 24008 : cached = injection_point_cache_get(name);
447 24008 : if (cached)
448 : {
449 3390 : int idx = cached->slot_idx;
450 3390 : InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
451 :
452 3390 : if (pg_atomic_read_u64(&entry->generation) == cached->generation)
453 : {
454 : /* still good */
455 3368 : return cached;
456 : }
457 22 : injection_point_cache_remove(name);
458 22 : 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 20640 : namelen = strlen(name);
471 50354 : for (int idx = 0; idx < max_inuse; idx++)
472 : {
473 29858 : 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 29858 : generation = pg_atomic_read_u64(&entry->generation);
482 29858 : if (generation % 2 == 0)
483 10 : continue; /* empty slot */
484 29848 : pg_read_barrier();
485 :
486 : /* Is this the injection point we're looking for? */
487 29848 : if (memcmp(entry->name, name, namelen + 1) != 0)
488 29704 : 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 144 : memcpy(&local_copy, entry, sizeof(InjectionPointEntry));
497 :
498 144 : pg_read_barrier();
499 144 : 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 0 : continue;
511 : }
512 :
513 : /* Success! Load it into the cache and return it */
514 144 : return injection_point_cache_load(&local_copy, idx, generation);
515 : }
516 20496 : 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 : */
527 : void
528 14358 : InjectionPointLoad(const char *name)
529 : {
530 : #ifdef USE_INJECTION_POINTS
531 14358 : InjectionPointCacheRefresh(name);
532 : #else
533 : elog(ERROR, "Injection points are not supported by this build");
534 : #endif
535 14358 : }
536 :
537 : /*
538 : * Execute an injection point, if defined.
539 : */
540 : void
541 8884592 : InjectionPointRun(const char *name, void *arg)
542 : {
543 : #ifdef USE_INJECTION_POINTS
544 : InjectionPointCacheEntry *cache_entry;
545 :
546 8884592 : cache_entry = InjectionPointCacheRefresh(name);
547 8884592 : if (cache_entry)
548 3478 : 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 8884566 : }
553 :
554 : /*
555 : * Execute an injection point directly from the cache, if defined.
556 : */
557 : void
558 14374 : InjectionPointCached(const char *name, void *arg)
559 : {
560 : #ifdef USE_INJECTION_POINTS
561 : InjectionPointCacheEntry *cache_entry;
562 :
563 14374 : cache_entry = injection_point_cache_get(name);
564 14374 : if (cache_entry)
565 28 : 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 14374 : }
570 :
571 : /*
572 : * Test if an injection point is defined.
573 : */
574 : bool
575 144672 : IsInjectionPointAttached(const char *name)
576 : {
577 : #ifdef USE_INJECTION_POINTS
578 144672 : 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 : */
590 : List *
591 6 : InjectionPointList(void)
592 : {
593 : #ifdef USE_INJECTION_POINTS
594 6 : List *inj_points = NIL;
595 : uint32 max_inuse;
596 :
597 6 : LWLockAcquire(InjectionPointLock, LW_SHARED);
598 :
599 6 : max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
600 :
601 14 : for (uint32 idx = 0; idx < max_inuse; idx++)
602 : {
603 : InjectionPointEntry *entry;
604 : InjectionPointData *inj_point;
605 : uint64 generation;
606 :
607 8 : entry = &ActiveInjectionPoints->entries[idx];
608 8 : generation = pg_atomic_read_u64(&entry->generation);
609 :
610 : /* skip free slots */
611 8 : if (generation % 2 == 0)
612 0 : continue;
613 :
614 8 : inj_point = palloc0_object(InjectionPointData);
615 8 : inj_point->name = pstrdup(entry->name);
616 8 : inj_point->library = pstrdup(entry->library);
617 8 : inj_point->function = pstrdup(entry->function);
618 8 : inj_points = lappend(inj_points, inj_point);
619 : }
620 :
621 6 : LWLockRelease(InjectionPointLock);
622 :
623 6 : 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 : }
|