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 = (void *)
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
593 michael@paquier.xyz 233 :CBC 1909 : 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 : 1909 : return 0;
242 : : #endif
243 : : }
244 : :
245 : : /*
246 : : * Allocate shmem space for dynamic shared hash.
247 : : */
248 : : void
249 : 1029 : 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 : 1029 : }
268 : :
269 : : /*
270 : : * Attach a new injection point.
271 : : */
272 : : void
593 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)",
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)",
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 : : 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);
341 : :
342 : : pg_write_barrier();
343 : : pg_atomic_write_u64(&entry->generation, generation + 1);
344 : :
345 : : if (free_idx + 1 > max_inuse)
346 : : pg_atomic_write_u32(&ActiveInjectionPoints->max_inuse, free_idx + 1);
347 : :
348 : : LWLockRelease(InjectionPointLock);
349 : :
350 : : #else
351 [ # # ]: 0 : elog(ERROR, "injection points are not supported by this build");
352 : : #endif
353 : : }
354 : :
355 : : /*
356 : : * Detach an existing injection point.
357 : : *
358 : : * Returns true if the injection point was detached, false otherwise.
359 : : */
360 : : bool
361 : 0 : InjectionPointDetach(const char *name)
362 : : {
363 : : #ifdef USE_INJECTION_POINTS
364 : : bool found = false;
365 : : int idx;
366 : : int max_inuse;
367 : :
368 : : LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE);
369 : :
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)
373 : : {
374 : : InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
375 : : uint64 generation;
376 : :
377 : : generation = pg_atomic_read_u64(&entry->generation);
378 : : if (generation % 2 == 0)
379 : : continue; /* empty slot */
380 : :
381 : : if (strcmp(entry->name, name) == 0)
382 : : {
383 : : Assert(!found);
384 : : found = true;
385 : : pg_atomic_write_u64(&entry->generation, generation + 1);
386 : : break;
387 : : }
388 : : }
389 : :
390 : : /* If we just removed the highest-numbered entry, update 'max_inuse' */
391 : : if (found && idx == max_inuse - 1)
392 : : {
393 : : for (; idx >= 0; --idx)
394 : : {
395 : : InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
396 : : uint64 generation;
397 : :
398 : : generation = pg_atomic_read_u64(&entry->generation);
399 : : if (generation % 2 != 0)
400 : : break;
401 : : }
402 : : pg_atomic_write_u32(&ActiveInjectionPoints->max_inuse, idx + 1);
403 : : }
404 : : LWLockRelease(InjectionPointLock);
405 : :
406 : : return found;
407 : : #else
408 [ # # ]: 0 : elog(ERROR, "Injection points are not supported by this build");
409 : : return true; /* silence compiler */
410 : : #endif
411 : : }
412 : :
413 : : #ifdef USE_INJECTION_POINTS
414 : : /*
415 : : * Common workhorse of InjectionPointRun() and InjectionPointLoad()
416 : : *
417 : : * Checks if an injection point exists in shared memory, and update
418 : : * the local cache entry accordingly.
419 : : */
420 : : static InjectionPointCacheEntry *
421 : : InjectionPointCacheRefresh(const char *name)
422 : : {
423 : : uint32 max_inuse;
424 : : int namelen;
425 : : InjectionPointEntry local_copy;
426 : : InjectionPointCacheEntry *cached;
427 : :
428 : : /*
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.
433 : : */
434 : : max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
435 : : if (max_inuse == 0)
436 : : {
437 : : if (InjectionPointCache)
438 : : {
439 : : hash_destroy(InjectionPointCache);
440 : : InjectionPointCache = NULL;
441 : : }
442 : : return NULL;
443 : : }
444 : :
445 : : /*
446 : : * If we have this entry in the local cache already, check if the cached
447 : : * entry is still valid.
448 : : */
449 : : cached = injection_point_cache_get(name);
450 : : if (cached)
451 : : {
452 : : int idx = cached->slot_idx;
453 : : InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
454 : :
455 : : if (pg_atomic_read_u64(&entry->generation) == cached->generation)
456 : : {
457 : : /* still good */
458 : : return cached;
459 : : }
460 : : injection_point_cache_remove(name);
461 : : cached = NULL;
462 : : }
463 : :
464 : : /*
465 : : * Search the shared memory array.
466 : : *
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
471 : : * cases.
472 : : */
473 : : namelen = strlen(name);
474 : : for (int idx = 0; idx < max_inuse; idx++)
475 : : {
476 : : InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
477 : : uint64 generation;
478 : :
479 : : /*
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.
483 : : */
484 : : generation = pg_atomic_read_u64(&entry->generation);
485 : : if (generation % 2 == 0)
486 : : continue; /* empty slot */
487 : : pg_read_barrier();
488 : :
489 : : /* Is this the injection point we're looking for? */
490 : : if (memcmp(entry->name, name, namelen + 1) != 0)
491 : : continue;
492 : :
493 : : /*
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
497 : : * copy is coherent.
498 : : */
499 : : memcpy(&local_copy, entry, sizeof(InjectionPointEntry));
500 : :
501 : : pg_read_barrier();
502 : : if (pg_atomic_read_u64(&entry->generation) != generation)
503 : : {
504 : : /*
505 : : * The entry was concurrently detached.
506 : : *
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
511 : : * different name.
512 : : */
513 : : continue;
514 : : }
515 : :
516 : : /* Success! Load it into the cache and return it */
517 : : return injection_point_cache_load(&local_copy, idx, generation);
518 : : }
519 : : return NULL;
520 : : }
521 : : #endif
522 : :
523 : : /*
524 : : * Load an injection point into the local cache.
525 : : *
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.
529 : : */
530 : : void
418 heikki.linnakangas@i 531 : 0 : InjectionPointLoad(const char *name)
532 : : {
533 : : #ifdef USE_INJECTION_POINTS
534 : : InjectionPointCacheRefresh(name);
535 : : #else
428 michael@paquier.xyz 536 [ # # ]: 0 : elog(ERROR, "Injection points are not supported by this build");
537 : : #endif
538 : : }
539 : :
540 : : /*
541 : : * Execute an injection point, if defined.
542 : : */
543 : : void
119 544 : 0 : InjectionPointRun(const char *name, void *arg)
545 : : {
546 : : #ifdef USE_INJECTION_POINTS
547 : : InjectionPointCacheEntry *cache_entry;
548 : :
549 : : cache_entry = InjectionPointCacheRefresh(name);
550 : : if (cache_entry)
551 : : cache_entry->callback(name, cache_entry->private_data, arg);
552 : : #else
593 553 [ # # ]: 0 : elog(ERROR, "Injection points are not supported by this build");
554 : : #endif
555 : : }
556 : :
557 : : /*
558 : : * Execute an injection point directly from the cache, if defined.
559 : : */
560 : : void
119 561 : 0 : InjectionPointCached(const char *name, void *arg)
562 : : {
563 : : #ifdef USE_INJECTION_POINTS
564 : : InjectionPointCacheEntry *cache_entry;
565 : :
566 : : cache_entry = injection_point_cache_get(name);
567 : : if (cache_entry)
568 : : cache_entry->callback(name, cache_entry->private_data, arg);
569 : : #else
415 570 [ # # ]: 0 : elog(ERROR, "Injection points are not supported by this build");
571 : : #endif
572 : : }
573 : :
574 : : /*
575 : : * Test if an injection point is defined.
576 : : */
577 : : bool
407 heikki.linnakangas@i 578 : 0 : IsInjectionPointAttached(const char *name)
579 : : {
580 : : #ifdef USE_INJECTION_POINTS
581 : : return InjectionPointCacheRefresh(name) != NULL;
582 : : #else
583 [ # # ]: 0 : elog(ERROR, "Injection points are not supported by this build");
584 : : return false; /* silence compiler */
585 : : #endif
586 : : }
587 : :
588 : : /*
589 : : * Retrieve a list of all the injection points currently attached.
590 : : *
591 : : * This list is palloc'd in the current memory context.
592 : : */
593 : : List *
65 michael@paquier.xyz 594 :UNC 0 : InjectionPointList(void)
595 : : {
596 : : #ifdef USE_INJECTION_POINTS
597 : : List *inj_points = NIL;
598 : : uint32 max_inuse;
599 : :
600 : : LWLockAcquire(InjectionPointLock, LW_SHARED);
601 : :
602 : : max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
603 : :
604 : : for (uint32 idx = 0; idx < max_inuse; idx++)
605 : : {
606 : : InjectionPointEntry *entry;
607 : : InjectionPointData *inj_point;
608 : : uint64 generation;
609 : :
610 : : entry = &ActiveInjectionPoints->entries[idx];
611 : : generation = pg_atomic_read_u64(&entry->generation);
612 : :
613 : : /* skip free slots */
614 : : if (generation % 2 == 0)
615 : : continue;
616 : :
617 : : inj_point = (InjectionPointData *) palloc0(sizeof(InjectionPointData));
618 : : inj_point->name = pstrdup(entry->name);
619 : : inj_point->library = pstrdup(entry->library);
620 : : inj_point->function = pstrdup(entry->function);
621 : : inj_points = lappend(inj_points, inj_point);
622 : : }
623 : :
624 : : LWLockRelease(InjectionPointLock);
625 : :
626 : : return inj_points;
627 : :
628 : : #else
629 [ # # ]: 0 : elog(ERROR, "Injection points are not supported by this build");
630 : : return NIL; /* keep compiler quiet */
631 : : #endif
632 : : }
|