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 NON_EXEC_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  else if (strcmp(entry->name, name) == 0)
319  elog(ERROR, "injection point \"%s\" already defined", name);
320  }
321  if (free_idx == -1)
322  {
323  if (max_inuse == MAX_INJECTION_POINTS)
324  elog(ERROR, "too many injection points");
325  free_idx = max_inuse;
326  }
327  entry = &ActiveInjectionPoints->entries[free_idx];
328  generation = pg_atomic_read_u64(&entry->generation);
329  Assert(generation % 2 == 0);
330 
331  /* Save the entry */
332  strlcpy(entry->name, name, sizeof(entry->name));
333  entry->name[INJ_NAME_MAXLEN - 1] = '\0';
334  strlcpy(entry->library, library, sizeof(entry->library));
335  entry->library[INJ_LIB_MAXLEN - 1] = '\0';
336  strlcpy(entry->function, function, sizeof(entry->function));
337  entry->function[INJ_FUNC_MAXLEN - 1] = '\0';
338  if (private_data != NULL)
339  memcpy(entry->private_data, private_data, private_data_size);
340 
342  pg_atomic_write_u64(&entry->generation, generation + 1);
343 
344  if (free_idx + 1 > max_inuse)
345  pg_atomic_write_u32(&ActiveInjectionPoints->max_inuse, free_idx + 1);
346 
347  LWLockRelease(InjectionPointLock);
348 
349 #else
350  elog(ERROR, "injection points are not supported by this build");
351 #endif
352 }
353 
354 /*
355  * Detach an existing injection point.
356  *
357  * Returns true if the injection point was detached, false otherwise.
358  */
359 bool
361 {
362 #ifdef USE_INJECTION_POINTS
363  bool found = false;
364  int idx;
365  int max_inuse;
366 
367  LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE);
368 
369  /* Find it in the shmem array, and mark the slot as unused */
370  max_inuse = (int) pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
371  for (idx = max_inuse - 1; idx >= 0; --idx)
372  {
373  InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
374  uint64 generation;
375 
376  generation = pg_atomic_read_u64(&entry->generation);
377  if (generation % 2 == 0)
378  continue; /* empty slot */
379 
380  if (strcmp(entry->name, name) == 0)
381  {
382  Assert(!found);
383  found = true;
384  pg_atomic_write_u64(&entry->generation, generation + 1);
385  break;
386  }
387  }
388 
389  /* If we just removed the highest-numbered entry, update 'max_inuse' */
390  if (found && idx == max_inuse - 1)
391  {
392  for (; idx >= 0; --idx)
393  {
394  InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
395  uint64 generation;
396 
397  generation = pg_atomic_read_u64(&entry->generation);
398  if (generation % 2 != 0)
399  break;
400  }
401  pg_atomic_write_u32(&ActiveInjectionPoints->max_inuse, idx + 1);
402  }
403  LWLockRelease(InjectionPointLock);
404 
405  return found;
406 #else
407  elog(ERROR, "Injection points are not supported by this build");
408  return true; /* silence compiler */
409 #endif
410 }
411 
412 #ifdef USE_INJECTION_POINTS
413 /*
414  * Common workhorse of InjectionPointRun() and InjectionPointLoad()
415  *
416  * Checks if an injection point exists in shared memory, and update
417  * the local cache entry accordingly.
418  */
419 static InjectionPointCacheEntry *
420 InjectionPointCacheRefresh(const char *name)
421 {
422  uint32 max_inuse;
423  int namelen;
424  InjectionPointEntry local_copy;
425  InjectionPointCacheEntry *cached;
426 
427  /*
428  * First read the number of in-use slots. More entries can be added or
429  * existing ones can be removed while we're reading them. If the entry
430  * we're looking for is concurrently added or removed, we might or might
431  * not see it. That's OK.
432  */
433  max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
434  if (max_inuse == 0)
435  {
436  if (InjectionPointCache)
437  {
438  hash_destroy(InjectionPointCache);
439  InjectionPointCache = NULL;
440  }
441  return NULL;
442  }
443 
444  /*
445  * If we have this entry in the local cache already, check if the cached
446  * entry is still valid.
447  */
448  cached = injection_point_cache_get(name);
449  if (cached)
450  {
451  int idx = cached->slot_idx;
452  InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
453 
454  if (pg_atomic_read_u64(&entry->generation) == cached->generation)
455  {
456  /* still good */
457  return cached;
458  }
459  injection_point_cache_remove(name);
460  cached = NULL;
461  }
462 
463  /*
464  * Search the shared memory array.
465  *
466  * It's possible that the entry we're looking for is concurrently detached
467  * or attached. Or detached *and* re-attached, to the same slot or a
468  * different slot. Detach and re-attach is not an atomic operation, so
469  * it's OK for us to return the old value, NULL, or the new value in such
470  * cases.
471  */
472  namelen = strlen(name);
473  for (int idx = 0; idx < max_inuse; idx++)
474  {
475  InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
476  uint64 generation;
477 
478  /*
479  * Read the generation number so that we can detect concurrent
480  * modifications. The read barrier ensures that the generation number
481  * is loaded before any of the other fields.
482  */
483  generation = pg_atomic_read_u64(&entry->generation);
484  if (generation % 2 == 0)
485  continue; /* empty slot */
486  pg_read_barrier();
487 
488  /* Is this the injection point we're looking for? */
489  if (memcmp(entry->name, name, namelen + 1) != 0)
490  continue;
491 
492  /*
493  * The entry can change at any time, if the injection point is
494  * concurrently detached. Copy it to local memory, and re-check the
495  * generation. If the generation hasn't changed, we know our local
496  * copy is coherent.
497  */
498  memcpy(&local_copy, entry, sizeof(InjectionPointEntry));
499 
500  pg_read_barrier();
501  if (pg_atomic_read_u64(&entry->generation) != generation)
502  {
503  /*
504  * The entry was concurrently detached.
505  *
506  * Continue the search, because if the generation number changed,
507  * we cannot trust the result of the name comparison we did above.
508  * It's theoretically possible that it falsely matched a mixed-up
509  * state of the old and new name, if the slot was recycled with a
510  * different name.
511  */
512  continue;
513  }
514 
515  /* Success! Load it into the cache and return it */
516  return injection_point_cache_load(&local_copy, idx, generation);
517  }
518  return NULL;
519 }
520 #endif
521 
522 /*
523  * Load an injection point into the local cache.
524  *
525  * This is useful to be able to load an injection point before running it,
526  * especially if the injection point is called in a code path where memory
527  * allocations cannot happen, like critical sections.
528  */
529 void
531 {
532 #ifdef USE_INJECTION_POINTS
533  InjectionPointCacheRefresh(name);
534 #else
535  elog(ERROR, "Injection points are not supported by this build");
536 #endif
537 }
538 
539 /*
540  * Execute an injection point, if defined.
541  */
542 void
544 {
545 #ifdef USE_INJECTION_POINTS
546  InjectionPointCacheEntry *cache_entry;
547 
548  cache_entry = InjectionPointCacheRefresh(name);
549  if (cache_entry)
550  cache_entry->callback(name, cache_entry->private_data);
551 #else
552  elog(ERROR, "Injection points are not supported by this build");
553 #endif
554 }
555 
556 /*
557  * Execute an injection point directly from the cache, if defined.
558  */
559 void
561 {
562 #ifdef USE_INJECTION_POINTS
563  InjectionPointCacheEntry *cache_entry;
564 
565  cache_entry = injection_point_cache_get(name);
566  if (cache_entry)
567  cache_entry->callback(name, cache_entry->private_data);
568 #else
569  elog(ERROR, "Injection points are not supported by this build");
570 #endif
571 }
572 
573 /*
574  * Test if an injection point is defined.
575  */
576 bool
578 {
579 #ifdef USE_INJECTION_POINTS
580  return InjectionPointCacheRefresh(name) != NULL;
581 #else
582  elog(ERROR, "Injection points are not supported by this build");
583  return false; /* silence compiler */
584 #endif
585 }
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:485
#define pg_read_barrier()
Definition: atomics.h:156
static void pg_atomic_init_u32(volatile pg_atomic_uint32 *ptr, uint32 val)
Definition: atomics.h:221
#define pg_write_barrier()
Definition: atomics.h:157
static void pg_atomic_write_u32(volatile pg_atomic_uint32 *ptr, uint32 val)
Definition: atomics.h:276
static uint32 pg_atomic_read_u32(volatile pg_atomic_uint32 *ptr)
Definition: atomics.h:239
static void pg_atomic_init_u64(volatile pg_atomic_uint64 *ptr, uint64 val)
Definition: atomics.h:453
static uint64 pg_atomic_read_u64(volatile pg_atomic_uint64 *ptr)
Definition: atomics.h:467
unsigned int uint32
Definition: c.h:506
#define PG_USED_FOR_ASSERTS_ONLY
Definition: c.h:185
#define Assert(condition)
Definition: c.h:849
size_t Size
Definition: c.h:596
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:225
bool pg_file_exists(const char *name)
Definition: fd.c:503
char pkglib_path[MAXPGPATH]
Definition: globals.c:81
bool IsUnderPostmaster
Definition: globals.c:119
#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 InjectionPointCached(const char *name)
void InjectionPointLoad(const char *name)
bool InjectionPointDetach(const char *name)
void InjectionPointRun(const char *name)
bool IsInjectionPointAttached(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
#define NON_EXEC_STATIC
Definition: postgres.h:576
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