PostgreSQL Source Code  git master
injection_point.c
Go to the documentation of this file.
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 <sys/stat.h>
21 
22 #include "fmgr.h"
23 #include "miscadmin.h"
24 #include "storage/fd.h"
25 #include "storage/lwlock.h"
26 #include "storage/shmem.h"
27 #include "utils/hsearch.h"
28 #include "utils/injection_point.h"
29 #include "utils/memutils.h"
30 
31 #ifdef USE_INJECTION_POINTS
32 
33 /* Field sizes */
34 #define INJ_NAME_MAXLEN 64
35 #define INJ_LIB_MAXLEN 128
36 #define INJ_FUNC_MAXLEN 128
37 #define INJ_PRIVATE_MAXLEN 1024
38 
39 /* Single injection point stored in shared memory */
40 typedef struct InjectionPointEntry
41 {
42  /*
43  * Because injection points need to be usable without LWLocks, we use a
44  * generation counter on each entry to allow safe, lock-free reading.
45  *
46  * To read an entry, first read the current 'generation' value. If it's
47  * even, then the slot is currently unused, and odd means it's in use.
48  * When reading the other fields, beware that they may change while
49  * reading them, if the entry is released and reused! After reading the
50  * other fields, read 'generation' again: if its value hasn't changed, you
51  * can be certain that the other fields you read are valid. Otherwise,
52  * the slot was concurrently recycled, and you should ignore it.
53  *
54  * When adding an entry, you must store all the other fields first, and
55  * then update the generation number, with an appropriate memory barrier
56  * in between. In addition to that protocol, you must also hold
57  * InjectionPointLock, to prevent two backends from modifying the array at
58  * the same time.
59  */
60  pg_atomic_uint64 generation;
61 
62  char name[INJ_NAME_MAXLEN]; /* hash key */
63  char library[INJ_LIB_MAXLEN]; /* library */
64  char function[INJ_FUNC_MAXLEN]; /* function */
65 
66  /*
67  * Opaque data area that modules can use to pass some custom data to
68  * callbacks, registered when attached.
69  */
70  char private_data[INJ_PRIVATE_MAXLEN];
71 } InjectionPointEntry;
72 
73 #define MAX_INJECTION_POINTS 128
74 
75 /*
76  * Shared memory array of active injection points.
77  *
78  * 'max_inuse' is the highest index currently in use, plus one. It's just an
79  * optimization to avoid scanning through the whole entry, in the common case
80  * that there are no injection points, or only a few.
81  */
82 typedef struct InjectionPointsCtl
83 {
84  pg_atomic_uint32 max_inuse;
85  InjectionPointEntry entries[MAX_INJECTION_POINTS];
86 } InjectionPointsCtl;
87 
88 static InjectionPointsCtl *ActiveInjectionPoints;
89 
90 /*
91  * Backend local cache of injection callbacks already loaded, stored in
92  * TopMemoryContext.
93  */
94 typedef struct InjectionPointCacheEntry
95 {
96  char name[INJ_NAME_MAXLEN];
97  char private_data[INJ_PRIVATE_MAXLEN];
99 
100  /*
101  * Shmem slot and copy of its generation number when this cache entry was
102  * created. They can be used to validate if the cached entry is still
103  * valid.
104  */
105  int slot_idx;
106  uint64 generation;
107 } InjectionPointCacheEntry;
108 
109 static HTAB *InjectionPointCache = NULL;
110 
111 /*
112  * injection_point_cache_add
113  *
114  * Add an injection point to the local cache.
115  */
116 static InjectionPointCacheEntry *
117 injection_point_cache_add(const char *name,
118  int slot_idx,
119  uint64 generation,
121  const void *private_data)
122 {
123  InjectionPointCacheEntry *entry;
124  bool found;
125 
126  /* If first time, initialize */
127  if (InjectionPointCache == NULL)
128  {
129  HASHCTL hash_ctl;
130 
131  hash_ctl.keysize = sizeof(char[INJ_NAME_MAXLEN]);
132  hash_ctl.entrysize = sizeof(InjectionPointCacheEntry);
133  hash_ctl.hcxt = TopMemoryContext;
134 
135  InjectionPointCache = hash_create("InjectionPoint cache hash",
136  MAX_INJECTION_POINTS,
137  &hash_ctl,
139  }
140 
141  entry = (InjectionPointCacheEntry *)
142  hash_search(InjectionPointCache, name, HASH_ENTER, &found);
143 
144  Assert(!found);
145  strlcpy(entry->name, name, sizeof(entry->name));
146  entry->slot_idx = slot_idx;
147  entry->generation = generation;
148  entry->callback = callback;
149  memcpy(entry->private_data, private_data, INJ_PRIVATE_MAXLEN);
150 
151  return entry;
152 }
153 
154 /*
155  * injection_point_cache_remove
156  *
157  * Remove entry from the local cache. Note that this leaks a callback
158  * loaded but removed later on, which should have no consequence from
159  * a testing perspective.
160  */
161 static void
162 injection_point_cache_remove(const char *name)
163 {
164  bool found PG_USED_FOR_ASSERTS_ONLY;
165 
166  (void) hash_search(InjectionPointCache, name, HASH_REMOVE, &found);
167  Assert(found);
168 }
169 
170 /*
171  * injection_point_cache_load
172  *
173  * Load an injection point into the local cache.
174  */
175 static InjectionPointCacheEntry *
176 injection_point_cache_load(InjectionPointEntry *entry, int slot_idx, uint64 generation)
177 {
178  char path[MAXPGPATH];
179  void *injection_callback_local;
180 
181  snprintf(path, MAXPGPATH, "%s/%s%s", pkglib_path,
182  entry->library, DLSUFFIX);
183 
184  if (!pg_file_exists(path))
185  elog(ERROR, "could not find library \"%s\" for injection point \"%s\"",
186  path, entry->name);
187 
188  injection_callback_local = (void *)
189  load_external_function(path, entry->function, false, NULL);
190 
191  if (injection_callback_local == NULL)
192  elog(ERROR, "could not find function \"%s\" in library \"%s\" for injection point \"%s\"",
193  entry->function, path, entry->name);
194 
195  /* add it to the local cache */
196  return injection_point_cache_add(entry->name,
197  slot_idx,
198  generation,
199  injection_callback_local,
200  entry->private_data);
201 }
202 
203 /*
204  * injection_point_cache_get
205  *
206  * Retrieve an injection point from the local cache, if any.
207  */
208 static InjectionPointCacheEntry *
209 injection_point_cache_get(const char *name)
210 {
211  bool found;
212  InjectionPointCacheEntry *entry;
213 
214  /* no callback if no cache yet */
215  if (InjectionPointCache == NULL)
216  return NULL;
217 
218  entry = (InjectionPointCacheEntry *)
219  hash_search(InjectionPointCache, name, HASH_FIND, &found);
220 
221  if (found)
222  return entry;
223 
224  return NULL;
225 }
226 #endif /* USE_INJECTION_POINTS */
227 
228 /*
229  * Return the space for dynamic shared hash table.
230  */
231 Size
233 {
234 #ifdef USE_INJECTION_POINTS
235  Size sz = 0;
236 
237  sz = add_size(sz, sizeof(InjectionPointsCtl));
238  return sz;
239 #else
240  return 0;
241 #endif
242 }
243 
244 /*
245  * Allocate shmem space for dynamic shared hash.
246  */
247 void
249 {
250 #ifdef USE_INJECTION_POINTS
251  bool found;
252 
253  ActiveInjectionPoints = ShmemInitStruct("InjectionPoint hash",
254  sizeof(InjectionPointsCtl),
255  &found);
256  if (!IsUnderPostmaster)
257  {
258  Assert(!found);
259  pg_atomic_init_u32(&ActiveInjectionPoints->max_inuse, 0);
260  for (int i = 0; i < MAX_INJECTION_POINTS; i++)
261  pg_atomic_init_u64(&ActiveInjectionPoints->entries[i].generation, 0);
262  }
263  else
264  Assert(found);
265 #endif
266 }
267 
268 /*
269  * Attach a new injection point.
270  */
271 void
273  const char *library,
274  const char *function,
275  const void *private_data,
276  int private_data_size)
277 {
278 #ifdef USE_INJECTION_POINTS
279  InjectionPointEntry *entry;
280  uint64 generation;
281  uint32 max_inuse;
282  int free_idx;
283 
284  if (strlen(name) >= INJ_NAME_MAXLEN)
285  elog(ERROR, "injection point name %s too long (maximum of %u)",
287  if (strlen(library) >= INJ_LIB_MAXLEN)
288  elog(ERROR, "injection point library %s too long (maximum of %u)",
289  library, INJ_LIB_MAXLEN);
290  if (strlen(function) >= INJ_FUNC_MAXLEN)
291  elog(ERROR, "injection point function %s too long (maximum of %u)",
292  function, INJ_FUNC_MAXLEN);
293  if (private_data_size >= INJ_PRIVATE_MAXLEN)
294  elog(ERROR, "injection point data too long (maximum of %u)",
295  INJ_PRIVATE_MAXLEN);
296 
297  /*
298  * Allocate and register a new injection point. A new point should not
299  * exist. For testing purposes this should be fine.
300  */
301  LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE);
302  max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
303  free_idx = -1;
304 
305  for (int idx = 0; idx < max_inuse; idx++)
306  {
307  entry = &ActiveInjectionPoints->entries[idx];
308  generation = pg_atomic_read_u64(&entry->generation);
309  if (generation % 2 == 0)
310  {
311  /*
312  * Found a free slot where we can add the new entry, but keep
313  * going so that we will find out if the entry already exists.
314  */
315  if (free_idx == -1)
316  free_idx = idx;
317  }
318 
319  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 
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  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
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  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
532 {
533 #ifdef USE_INJECTION_POINTS
534  InjectionPointCacheRefresh(name);
535 #else
536  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
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);
552 #else
553  elog(ERROR, "Injection points are not supported by this build");
554 #endif
555 }
Datum idx(PG_FUNCTION_ARGS)
Definition: _int_op.c:259
static void pg_atomic_write_u64(volatile pg_atomic_uint64 *ptr, uint64 val)
Definition: atomics.h:478
#define pg_read_barrier()
Definition: atomics.h:149
static void pg_atomic_init_u32(volatile pg_atomic_uint32 *ptr, uint32 val)
Definition: atomics.h:214
#define pg_write_barrier()
Definition: atomics.h:150
static void pg_atomic_write_u32(volatile pg_atomic_uint32 *ptr, uint32 val)
Definition: atomics.h:269
static uint32 pg_atomic_read_u32(volatile pg_atomic_uint32 *ptr)
Definition: atomics.h:232
static void pg_atomic_init_u64(volatile pg_atomic_uint64 *ptr, uint64 val)
Definition: atomics.h:446
static uint64 pg_atomic_read_u64(volatile pg_atomic_uint64 *ptr)
Definition: atomics.h:460
unsigned int uint32
Definition: c.h:506
#define PG_USED_FOR_ASSERTS_ONLY
Definition: c.h:182
#define Assert(condition)
Definition: c.h:858
size_t Size
Definition: c.h:605
void * load_external_function(const char *filename, const char *funcname, bool signalNotFound, void **filehandle)
Definition: dfmgr.c:105
void hash_destroy(HTAB *hashp)
Definition: dynahash.c:865
void * hash_search(HTAB *hashp, const void *keyPtr, HASHACTION action, bool *foundPtr)
Definition: dynahash.c:955
HTAB * hash_create(const char *tabname, long nelem, const HASHCTL *info, int flags)
Definition: dynahash.c:352
#define ERROR
Definition: elog.h:39
#define elog(elevel,...)
Definition: elog.h:224
bool pg_file_exists(const char *name)
Definition: fd.c:503
char pkglib_path[MAXPGPATH]
Definition: globals.c:80
bool IsUnderPostmaster
Definition: globals.c:118
#define HASH_STRINGS
Definition: hsearch.h:96
@ HASH_FIND
Definition: hsearch.h:113
@ HASH_REMOVE
Definition: hsearch.h:115
@ HASH_ENTER
Definition: hsearch.h:114
#define HASH_CONTEXT
Definition: hsearch.h:102
#define HASH_ELEM
Definition: hsearch.h:95
void InjectionPointShmemInit(void)
Size InjectionPointShmemSize(void)
void InjectionPointLoad(const char *name)
bool InjectionPointDetach(const char *name)
void InjectionPointRun(const char *name)
void InjectionPointAttach(const char *name, const char *library, const char *function, const void *private_data, int private_data_size)
void(* InjectionPointCallback)(const char *name, const void *private_data)
#define INJ_NAME_MAXLEN
int i
Definition: isn.c:73
bool LWLockAcquire(LWLock *lock, LWLockMode mode)
Definition: lwlock.c:1168
void LWLockRelease(LWLock *lock)
Definition: lwlock.c:1781
@ LW_EXCLUSIVE
Definition: lwlock.h:114
MemoryContext TopMemoryContext
Definition: mcxt.c:149
#define MAXPGPATH
#define snprintf
Definition: port.h:238
size_t strlcpy(char *dst, const char *src, size_t siz)
Definition: strlcpy.c:45
Size add_size(Size s1, Size s2)
Definition: shmem.c:493
void * ShmemInitStruct(const char *name, Size size, bool *foundPtr)
Definition: shmem.c:387
Size keysize
Definition: hsearch.h:75
Size entrysize
Definition: hsearch.h:76
MemoryContext hcxt
Definition: hsearch.h:86
Definition: dynahash.c:220
static void callback(struct sockaddr *addr, struct sockaddr *mask, void *unused)
Definition: test_ifaddrs.c:46
const char * name