Age Owner Branch data TLA 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 : : 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 : : if (InjectionPointCache == NULL)
129 : : {
130 : : HASHCTL hash_ctl;
131 : :
132 : : hash_ctl.keysize = sizeof(char[INJ_NAME_MAXLEN]);
133 : : hash_ctl.entrysize = sizeof(InjectionPointCacheEntry);
134 : : hash_ctl.hcxt = TopMemoryContext;
135 : :
136 : : 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 : : hash_search(InjectionPointCache, name, HASH_ENTER, &found);
144 : :
145 : : Assert(!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);
151 : :
152 : : 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 : : injection_point_cache_remove(const char *name)
164 : : {
165 : : bool found PG_USED_FOR_ASSERTS_ONLY;
166 : :
167 : : (void) hash_search(InjectionPointCache, name, HASH_REMOVE, &found);
168 : : Assert(found);
169 : : }
170 : :
171 : : /*
172 : : * injection_point_cache_load
173 : : *
174 : : * Load an injection point into the local cache.
175 : : */
176 : : static InjectionPointCacheEntry *
177 : : injection_point_cache_load(InjectionPointEntry *entry, int slot_idx, uint64 generation)
178 : : {
179 : : char path[MAXPGPATH];
180 : : void *injection_callback_local;
181 : :
182 : : snprintf(path, MAXPGPATH, "%s/%s%s", pkglib_path,
183 : : entry->library, DLSUFFIX);
184 : :
185 : : if (!pg_file_exists(path))
186 : : elog(ERROR, "could not find library \"%s\" for injection point \"%s\"",
187 : : path, entry->name);
188 : :
189 : : injection_callback_local =
190 : : load_external_function(path, entry->function, false, NULL);
191 : :
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);
195 : :
196 : : /* add it to the local cache */
197 : : return injection_point_cache_add(entry->name,
198 : : slot_idx,
199 : : generation,
200 : : injection_callback_local,
201 : : 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 : : injection_point_cache_get(const char *name)
211 : : {
212 : : bool found;
213 : : InjectionPointCacheEntry *entry;
214 : :
215 : : /* no callback if no cache yet */
216 : : if (InjectionPointCache == NULL)
217 : : return NULL;
218 : :
219 : : entry = (InjectionPointCacheEntry *)
220 : : hash_search(InjectionPointCache, name, HASH_FIND, &found);
221 : :
222 : : if (found)
223 : : return entry;
224 : :
225 : : return NULL;
226 : : }
227 : : #endif /* USE_INJECTION_POINTS */
228 : :
229 : : /*
230 : : * Return the space for dynamic shared hash table.
231 : : */
232 : : Size
695 michael@paquier.xyz 233 :CBC 1986 : InjectionPointShmemSize(void)
234 : : {
235 : : #ifdef USE_INJECTION_POINTS
236 : : Size sz = 0;
237 : :
238 : : sz = add_size(sz, sizeof(InjectionPointsCtl));
239 : : return sz;
240 : : #else
241 : 1986 : return 0;
242 : : #endif
243 : : }
244 : :
245 : : /*
246 : : * Allocate shmem space for dynamic shared hash.
247 : : */
248 : : void
249 : 1069 : InjectionPointShmemInit(void)
250 : : {
251 : : #ifdef USE_INJECTION_POINTS
252 : : bool found;
253 : :
254 : : ActiveInjectionPoints = ShmemInitStruct("InjectionPoint hash",
255 : : sizeof(InjectionPointsCtl),
256 : : &found);
257 : : if (!IsUnderPostmaster)
258 : : {
259 : : Assert(!found);
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);
263 : : }
264 : : else
265 : : Assert(found);
266 : : #endif
267 : 1069 : }
268 : :
269 : : /*
270 : : * Attach a new injection point.
271 : : */
272 : : void
695 michael@paquier.xyz 273 :UBC 0 : 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 : : if (strlen(name) >= INJ_NAME_MAXLEN)
286 : : elog(ERROR, "injection point name %s too long (maximum of %u characters)",
287 : : name, INJ_NAME_MAXLEN - 1);
288 : : if (strlen(library) >= INJ_LIB_MAXLEN)
289 : : elog(ERROR, "injection point library %s too long (maximum of %u characters)",
290 : : library, INJ_LIB_MAXLEN - 1);
291 : : if (strlen(function) >= INJ_FUNC_MAXLEN)
292 : : elog(ERROR, "injection point function %s too long (maximum of %u characters)",
293 : : function, INJ_FUNC_MAXLEN - 1);
294 : : if (private_data_size > INJ_PRIVATE_MAXLEN)
295 : : 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 : : LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE);
303 : : max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
304 : : free_idx = -1;
305 : :
306 : : for (int idx = 0; idx < max_inuse; idx++)
307 : : {
308 : : entry = &ActiveInjectionPoints->entries[idx];
309 : : generation = pg_atomic_read_u64(&entry->generation);
310 : : 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 : : if (free_idx == -1)
317 : : free_idx = idx;
318 : : }
319 : : else if (strcmp(entry->name, name) == 0)
320 : : elog(ERROR, "injection point \"%s\" already defined", name);
321 : : }
322 : : if (free_idx == -1)
323 : : {
324 : : if (max_inuse == MAX_INJECTION_POINTS)
325 : : elog(ERROR, "too many injection points");
326 : : free_idx = max_inuse;
327 : : }
328 : : entry = &ActiveInjectionPoints->entries[free_idx];
329 : : generation = pg_atomic_read_u64(&entry->generation);
330 : : Assert(generation % 2 == 0);
331 : :
332 : : /* Save the entry */
333 : : strlcpy(entry->name, name, sizeof(entry->name));
334 : : strlcpy(entry->library, library, sizeof(entry->library));
335 : : strlcpy(entry->function, function, sizeof(entry->function));
336 : : if (private_data != NULL)
337 : : memcpy(entry->private_data, private_data, private_data_size);
338 : :
339 : : pg_write_barrier();
340 : : pg_atomic_write_u64(&entry->generation, generation + 1);
341 : :
342 : : if (free_idx + 1 > max_inuse)
343 : : pg_atomic_write_u32(&ActiveInjectionPoints->max_inuse, free_idx + 1);
344 : :
345 : : LWLockRelease(InjectionPointLock);
346 : :
347 : : #else
348 [ # # ]: 0 : elog(ERROR, "injection points are not supported by this build");
349 : : #endif
350 : : }
351 : :
352 : : /*
353 : : * Detach an existing injection point.
354 : : *
355 : : * Returns true if the injection point was detached, false otherwise.
356 : : */
357 : : bool
358 : 0 : InjectionPointDetach(const char *name)
359 : : {
360 : : #ifdef USE_INJECTION_POINTS
361 : : bool found = false;
362 : : int idx;
363 : : int max_inuse;
364 : :
365 : : LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE);
366 : :
367 : : /* Find it in the shmem array, and mark the slot as unused */
368 : : max_inuse = (int) pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
369 : : for (idx = max_inuse - 1; idx >= 0; --idx)
370 : : {
371 : : InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
372 : : uint64 generation;
373 : :
374 : : generation = pg_atomic_read_u64(&entry->generation);
375 : : if (generation % 2 == 0)
376 : : continue; /* empty slot */
377 : :
378 : : if (strcmp(entry->name, name) == 0)
379 : : {
380 : : Assert(!found);
381 : : found = true;
382 : : pg_atomic_write_u64(&entry->generation, generation + 1);
383 : : break;
384 : : }
385 : : }
386 : :
387 : : /* If we just removed the highest-numbered entry, update 'max_inuse' */
388 : : if (found && idx == max_inuse - 1)
389 : : {
390 : : for (; idx >= 0; --idx)
391 : : {
392 : : InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
393 : : uint64 generation;
394 : :
395 : : generation = pg_atomic_read_u64(&entry->generation);
396 : : if (generation % 2 != 0)
397 : : break;
398 : : }
399 : : pg_atomic_write_u32(&ActiveInjectionPoints->max_inuse, idx + 1);
400 : : }
401 : : LWLockRelease(InjectionPointLock);
402 : :
403 : : return found;
404 : : #else
405 [ # # ]: 0 : 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 : : 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 : : max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
432 : : if (max_inuse == 0)
433 : : {
434 : : if (InjectionPointCache)
435 : : {
436 : : hash_destroy(InjectionPointCache);
437 : : InjectionPointCache = NULL;
438 : : }
439 : : 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 : : cached = injection_point_cache_get(name);
447 : : if (cached)
448 : : {
449 : : int idx = cached->slot_idx;
450 : : InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
451 : :
452 : : if (pg_atomic_read_u64(&entry->generation) == cached->generation)
453 : : {
454 : : /* still good */
455 : : return cached;
456 : : }
457 : : injection_point_cache_remove(name);
458 : : 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 : : namelen = strlen(name);
471 : : for (int idx = 0; idx < max_inuse; idx++)
472 : : {
473 : : 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 : : generation = pg_atomic_read_u64(&entry->generation);
482 : : if (generation % 2 == 0)
483 : : continue; /* empty slot */
484 : : pg_read_barrier();
485 : :
486 : : /* Is this the injection point we're looking for? */
487 : : if (memcmp(entry->name, name, namelen + 1) != 0)
488 : : 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 : : memcpy(&local_copy, entry, sizeof(InjectionPointEntry));
497 : :
498 : : pg_read_barrier();
499 : : 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 : : continue;
511 : : }
512 : :
513 : : /* Success! Load it into the cache and return it */
514 : : return injection_point_cache_load(&local_copy, idx, generation);
515 : : }
516 : : 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
520 heikki.linnakangas@i 528 : 0 : InjectionPointLoad(const char *name)
529 : : {
530 : : #ifdef USE_INJECTION_POINTS
531 : : InjectionPointCacheRefresh(name);
532 : : #else
530 michael@paquier.xyz 533 [ # # ]: 0 : elog(ERROR, "Injection points are not supported by this build");
534 : : #endif
535 : : }
536 : :
537 : : /*
538 : : * Execute an injection point, if defined.
539 : : */
540 : : void
221 541 : 0 : InjectionPointRun(const char *name, void *arg)
542 : : {
543 : : #ifdef USE_INJECTION_POINTS
544 : : InjectionPointCacheEntry *cache_entry;
545 : :
546 : : cache_entry = InjectionPointCacheRefresh(name);
547 : : if (cache_entry)
548 : : cache_entry->callback(name, cache_entry->private_data, arg);
549 : : #else
695 550 [ # # ]: 0 : elog(ERROR, "Injection points are not supported by this build");
551 : : #endif
552 : : }
553 : :
554 : : /*
555 : : * Execute an injection point directly from the cache, if defined.
556 : : */
557 : : void
221 558 : 0 : InjectionPointCached(const char *name, void *arg)
559 : : {
560 : : #ifdef USE_INJECTION_POINTS
561 : : InjectionPointCacheEntry *cache_entry;
562 : :
563 : : cache_entry = injection_point_cache_get(name);
564 : : if (cache_entry)
565 : : cache_entry->callback(name, cache_entry->private_data, arg);
566 : : #else
517 567 [ # # ]: 0 : elog(ERROR, "Injection points are not supported by this build");
568 : : #endif
569 : : }
570 : :
571 : : /*
572 : : * Test if an injection point is defined.
573 : : */
574 : : bool
509 heikki.linnakangas@i 575 : 0 : IsInjectionPointAttached(const char *name)
576 : : {
577 : : #ifdef USE_INJECTION_POINTS
578 : : return InjectionPointCacheRefresh(name) != NULL;
579 : : #else
580 [ # # ]: 0 : 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 *
167 michael@paquier.xyz 591 :UNC 0 : InjectionPointList(void)
592 : : {
593 : : #ifdef USE_INJECTION_POINTS
594 : : List *inj_points = NIL;
595 : : uint32 max_inuse;
596 : :
597 : : LWLockAcquire(InjectionPointLock, LW_SHARED);
598 : :
599 : : max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
600 : :
601 : : for (uint32 idx = 0; idx < max_inuse; idx++)
602 : : {
603 : : InjectionPointEntry *entry;
604 : : InjectionPointData *inj_point;
605 : : uint64 generation;
606 : :
607 : : entry = &ActiveInjectionPoints->entries[idx];
608 : : generation = pg_atomic_read_u64(&entry->generation);
609 : :
610 : : /* skip free slots */
611 : : if (generation % 2 == 0)
612 : : continue;
613 : :
614 : : inj_point = palloc0_object(InjectionPointData);
615 : : inj_point->name = pstrdup(entry->name);
616 : : inj_point->library = pstrdup(entry->library);
617 : : inj_point->function = pstrdup(entry->function);
618 : : inj_points = lappend(inj_points, inj_point);
619 : : }
620 : :
621 : : LWLockRelease(InjectionPointLock);
622 : :
623 : : return inj_points;
624 : :
625 : : #else
626 [ # # ]: 0 : elog(ERROR, "Injection points are not supported by this build");
627 : : return NIL; /* keep compiler quiet */
628 : : #endif
629 : : }
|