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-2024, 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 84 : 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 84 : if (InjectionPointCache == NULL)
129 : {
130 : HASHCTL hash_ctl;
131 :
132 54 : hash_ctl.keysize = sizeof(char[INJ_NAME_MAXLEN]);
133 54 : hash_ctl.entrysize = sizeof(InjectionPointCacheEntry);
134 54 : hash_ctl.hcxt = TopMemoryContext;
135 :
136 54 : 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 84 : hash_search(InjectionPointCache, name, HASH_ENTER, &found);
144 :
145 : Assert(!found);
146 84 : strlcpy(entry->name, name, sizeof(entry->name));
147 84 : entry->slot_idx = slot_idx;
148 84 : entry->generation = generation;
149 84 : entry->callback = callback;
150 84 : memcpy(entry->private_data, private_data, INJ_PRIVATE_MAXLEN);
151 :
152 84 : 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 18 : injection_point_cache_remove(const char *name)
164 : {
165 : bool found PG_USED_FOR_ASSERTS_ONLY;
166 :
167 18 : (void) hash_search(InjectionPointCache, name, HASH_REMOVE, &found);
168 : Assert(found);
169 18 : }
170 :
171 : /*
172 : * injection_point_cache_load
173 : *
174 : * Load an injection point into the local cache.
175 : */
176 : static InjectionPointCacheEntry *
177 84 : injection_point_cache_load(InjectionPointEntry *entry, int slot_idx, uint64 generation)
178 : {
179 : char path[MAXPGPATH];
180 : void *injection_callback_local;
181 :
182 84 : snprintf(path, MAXPGPATH, "%s/%s%s", pkglib_path,
183 84 : entry->library, DLSUFFIX);
184 :
185 84 : if (!pg_file_exists(path))
186 0 : elog(ERROR, "could not find library \"%s\" for injection point \"%s\"",
187 : path, entry->name);
188 :
189 84 : injection_callback_local = (void *)
190 84 : load_external_function(path, entry->function, false, NULL);
191 :
192 84 : 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 168 : return injection_point_cache_add(entry->name,
198 : slot_idx,
199 : generation,
200 : injection_callback_local,
201 84 : 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 4130 : injection_point_cache_get(const char *name)
211 : {
212 : bool found;
213 : InjectionPointCacheEntry *entry;
214 :
215 : /* no callback if no cache yet */
216 4130 : if (InjectionPointCache == NULL)
217 982 : return NULL;
218 :
219 : entry = (InjectionPointCacheEntry *)
220 3148 : hash_search(InjectionPointCache, name, HASH_FIND, &found);
221 :
222 3148 : if (found)
223 86 : return entry;
224 :
225 3062 : return NULL;
226 : }
227 : #endif /* USE_INJECTION_POINTS */
228 :
229 : /*
230 : * Return the space for dynamic shared hash table.
231 : */
232 : Size
233 3534 : InjectionPointShmemSize(void)
234 : {
235 : #ifdef USE_INJECTION_POINTS
236 3534 : Size sz = 0;
237 :
238 3534 : sz = add_size(sz, sizeof(InjectionPointsCtl));
239 3534 : return sz;
240 : #else
241 : return 0;
242 : #endif
243 : }
244 :
245 : /*
246 : * Allocate shmem space for dynamic shared hash.
247 : */
248 : void
249 1902 : InjectionPointShmemInit(void)
250 : {
251 : #ifdef USE_INJECTION_POINTS
252 : bool found;
253 :
254 1902 : ActiveInjectionPoints = ShmemInitStruct("InjectionPoint hash",
255 : sizeof(InjectionPointsCtl),
256 : &found);
257 1902 : if (!IsUnderPostmaster)
258 : {
259 : Assert(!found);
260 1902 : pg_atomic_init_u32(&ActiveInjectionPoints->max_inuse, 0);
261 245358 : for (int i = 0; i < MAX_INJECTION_POINTS; i++)
262 243456 : pg_atomic_init_u64(&ActiveInjectionPoints->entries[i].generation, 0);
263 : }
264 : else
265 : Assert(found);
266 : #endif
267 1902 : }
268 :
269 : /*
270 : * Attach a new injection point.
271 : */
272 : void
273 70 : 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 70 : if (strlen(name) >= INJ_NAME_MAXLEN)
286 0 : elog(ERROR, "injection point name %s too long (maximum of %u)",
287 : name, INJ_NAME_MAXLEN);
288 70 : if (strlen(library) >= INJ_LIB_MAXLEN)
289 0 : elog(ERROR, "injection point library %s too long (maximum of %u)",
290 : library, INJ_LIB_MAXLEN);
291 70 : if (strlen(function) >= INJ_FUNC_MAXLEN)
292 0 : elog(ERROR, "injection point function %s too long (maximum of %u)",
293 : function, INJ_FUNC_MAXLEN);
294 70 : if (private_data_size >= INJ_PRIVATE_MAXLEN)
295 0 : 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 70 : LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE);
303 70 : max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
304 70 : free_idx = -1;
305 :
306 100 : for (int idx = 0; idx < max_inuse; idx++)
307 : {
308 30 : entry = &ActiveInjectionPoints->entries[idx];
309 30 : generation = pg_atomic_read_u64(&entry->generation);
310 30 : 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 30 : else if (strcmp(entry->name, name) == 0)
320 0 : elog(ERROR, "injection point \"%s\" already defined", name);
321 : }
322 70 : if (free_idx == -1)
323 : {
324 70 : if (max_inuse == MAX_INJECTION_POINTS)
325 0 : elog(ERROR, "too many injection points");
326 70 : free_idx = max_inuse;
327 : }
328 70 : entry = &ActiveInjectionPoints->entries[free_idx];
329 70 : generation = pg_atomic_read_u64(&entry->generation);
330 : Assert(generation % 2 == 0);
331 :
332 : /* Save the entry */
333 70 : strlcpy(entry->name, name, sizeof(entry->name));
334 70 : entry->name[INJ_NAME_MAXLEN - 1] = '\0';
335 70 : strlcpy(entry->library, library, sizeof(entry->library));
336 70 : entry->library[INJ_LIB_MAXLEN - 1] = '\0';
337 70 : strlcpy(entry->function, function, sizeof(entry->function));
338 70 : entry->function[INJ_FUNC_MAXLEN - 1] = '\0';
339 70 : if (private_data != NULL)
340 70 : memcpy(entry->private_data, private_data, private_data_size);
341 :
342 70 : pg_write_barrier();
343 70 : pg_atomic_write_u64(&entry->generation, generation + 1);
344 :
345 70 : if (free_idx + 1 > max_inuse)
346 70 : pg_atomic_write_u32(&ActiveInjectionPoints->max_inuse, free_idx + 1);
347 :
348 70 : LWLockRelease(InjectionPointLock);
349 :
350 : #else
351 : elog(ERROR, "injection points are not supported by this build");
352 : #endif
353 70 : }
354 :
355 : /*
356 : * Detach an existing injection point.
357 : *
358 : * Returns true if the injection point was detached, false otherwise.
359 : */
360 : bool
361 162 : InjectionPointDetach(const char *name)
362 : {
363 : #ifdef USE_INJECTION_POINTS
364 162 : bool found = false;
365 : int idx;
366 : int max_inuse;
367 :
368 162 : LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE);
369 :
370 : /* Find it in the shmem array, and mark the slot as unused */
371 162 : max_inuse = (int) pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
372 182 : for (idx = max_inuse - 1; idx >= 0; --idx)
373 : {
374 74 : InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
375 : uint64 generation;
376 :
377 74 : generation = pg_atomic_read_u64(&entry->generation);
378 74 : if (generation % 2 == 0)
379 4 : continue; /* empty slot */
380 :
381 70 : if (strcmp(entry->name, name) == 0)
382 : {
383 : Assert(!found);
384 54 : found = true;
385 54 : pg_atomic_write_u64(&entry->generation, generation + 1);
386 54 : break;
387 : }
388 : }
389 :
390 : /* If we just removed the highest-numbered entry, update 'max_inuse' */
391 162 : if (found && idx == max_inuse - 1)
392 : {
393 98 : for (; idx >= 0; --idx)
394 : {
395 62 : InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
396 : uint64 generation;
397 :
398 62 : generation = pg_atomic_read_u64(&entry->generation);
399 62 : if (generation % 2 != 0)
400 8 : break;
401 : }
402 44 : pg_atomic_write_u32(&ActiveInjectionPoints->max_inuse, idx + 1);
403 : }
404 162 : LWLockRelease(InjectionPointLock);
405 :
406 162 : return found;
407 : #else
408 : 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 963002 : 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 963002 : max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
435 963002 : if (max_inuse == 0)
436 : {
437 959448 : if (InjectionPointCache)
438 : {
439 20 : hash_destroy(InjectionPointCache);
440 20 : InjectionPointCache = NULL;
441 : }
442 959448 : 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 3554 : cached = injection_point_cache_get(name);
450 3554 : if (cached)
451 : {
452 80 : int idx = cached->slot_idx;
453 80 : InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
454 :
455 80 : if (pg_atomic_read_u64(&entry->generation) == cached->generation)
456 : {
457 : /* still good */
458 62 : return cached;
459 : }
460 18 : injection_point_cache_remove(name);
461 18 : 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 3492 : namelen = strlen(name);
474 10234 : for (int idx = 0; idx < max_inuse; idx++)
475 : {
476 6826 : 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 6826 : generation = pg_atomic_read_u64(&entry->generation);
485 6826 : if (generation % 2 == 0)
486 18 : continue; /* empty slot */
487 6808 : pg_read_barrier();
488 :
489 : /* Is this the injection point we're looking for? */
490 6808 : if (memcmp(entry->name, name, namelen + 1) != 0)
491 6724 : 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 84 : memcpy(&local_copy, entry, sizeof(InjectionPointEntry));
500 :
501 84 : pg_read_barrier();
502 84 : 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 0 : continue;
514 : }
515 :
516 : /* Success! Load it into the cache and return it */
517 84 : return injection_point_cache_load(&local_copy, idx, generation);
518 : }
519 3408 : 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
531 576 : InjectionPointLoad(const char *name)
532 : {
533 : #ifdef USE_INJECTION_POINTS
534 576 : InjectionPointCacheRefresh(name);
535 : #else
536 : elog(ERROR, "Injection points are not supported by this build");
537 : #endif
538 576 : }
539 :
540 : /*
541 : * Execute an injection point, if defined.
542 : */
543 : void
544 936946 : InjectionPointRun(const char *name)
545 : {
546 : #ifdef USE_INJECTION_POINTS
547 : InjectionPointCacheEntry *cache_entry;
548 :
549 936946 : cache_entry = InjectionPointCacheRefresh(name);
550 936946 : if (cache_entry)
551 138 : cache_entry->callback(name, cache_entry->private_data);
552 : #else
553 : elog(ERROR, "Injection points are not supported by this build");
554 : #endif
555 936928 : }
556 :
557 : /*
558 : * Execute an injection point directly from the cache, if defined.
559 : */
560 : void
561 576 : InjectionPointCached(const char *name)
562 : {
563 : #ifdef USE_INJECTION_POINTS
564 : InjectionPointCacheEntry *cache_entry;
565 :
566 576 : cache_entry = injection_point_cache_get(name);
567 576 : if (cache_entry)
568 6 : cache_entry->callback(name, cache_entry->private_data);
569 : #else
570 : elog(ERROR, "Injection points are not supported by this build");
571 : #endif
572 576 : }
573 :
574 : /*
575 : * Test if an injection point is defined.
576 : */
577 : bool
578 25480 : IsInjectionPointAttached(const char *name)
579 : {
580 : #ifdef USE_INJECTION_POINTS
581 25480 : return InjectionPointCacheRefresh(name) != NULL;
582 : #else
583 : elog(ERROR, "Injection points are not supported by this build");
584 : return false; /* silence compiler */
585 : #endif
586 : }
|