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 "port/pg_bitutils.h"
25 #include "storage/fd.h"
26 #include "storage/lwlock.h"
27 #include "storage/shmem.h"
28 #include "utils/hsearch.h"
29 #include "utils/injection_point.h"
30 #include "utils/memutils.h"
31 
32 #ifdef USE_INJECTION_POINTS
33 
34 /*
35  * Hash table for storing injection points.
36  *
37  * InjectionPointHash is used to find an injection point by name.
38  */
39 static HTAB *InjectionPointHash; /* find points from names */
40 
41 /* Field sizes */
42 #define INJ_NAME_MAXLEN 64
43 #define INJ_LIB_MAXLEN 128
44 #define INJ_FUNC_MAXLEN 128
45 
46 /* Single injection point stored in InjectionPointHash */
47 typedef struct InjectionPointEntry
48 {
49  char name[INJ_NAME_MAXLEN]; /* hash key */
50  char library[INJ_LIB_MAXLEN]; /* library */
51  char function[INJ_FUNC_MAXLEN]; /* function */
52 } InjectionPointEntry;
53 
54 #define INJECTION_POINT_HASH_INIT_SIZE 16
55 #define INJECTION_POINT_HASH_MAX_SIZE 128
56 
57 /*
58  * Backend local cache of injection callbacks already loaded, stored in
59  * TopMemoryContext.
60  */
61 typedef struct InjectionPointCacheEntry
62 {
63  char name[INJ_NAME_MAXLEN];
65 } InjectionPointCacheEntry;
66 
67 static HTAB *InjectionPointCache = NULL;
68 
69 /*
70  * injection_point_cache_add
71  *
72  * Add an injection point to the local cache.
73  */
74 static void
75 injection_point_cache_add(const char *name,
77 {
78  InjectionPointCacheEntry *entry;
79  bool found;
80 
81  /* If first time, initialize */
82  if (InjectionPointCache == NULL)
83  {
84  HASHCTL hash_ctl;
85 
86  hash_ctl.keysize = sizeof(char[INJ_NAME_MAXLEN]);
87  hash_ctl.entrysize = sizeof(InjectionPointCacheEntry);
88  hash_ctl.hcxt = TopMemoryContext;
89 
90  InjectionPointCache = hash_create("InjectionPoint cache hash",
91  INJECTION_POINT_HASH_MAX_SIZE,
92  &hash_ctl,
94  }
95 
96  entry = (InjectionPointCacheEntry *)
97  hash_search(InjectionPointCache, name, HASH_ENTER, &found);
98 
99  Assert(!found);
100  strlcpy(entry->name, name, sizeof(entry->name));
101  entry->callback = callback;
102 }
103 
104 /*
105  * injection_point_cache_remove
106  *
107  * Remove entry from the local cache. Note that this leaks a callback
108  * loaded but removed later on, which should have no consequence from
109  * a testing perspective.
110  */
111 static void
112 injection_point_cache_remove(const char *name)
113 {
114  /* leave if no cache */
115  if (InjectionPointCache == NULL)
116  return;
117 
118  (void) hash_search(InjectionPointCache, name, HASH_REMOVE, NULL);
119 }
120 
121 /*
122  * injection_point_cache_get
123  *
124  * Retrieve an injection point from the local cache, if any.
125  */
127 injection_point_cache_get(const char *name)
128 {
129  bool found;
130  InjectionPointCacheEntry *entry;
131 
132  /* no callback if no cache yet */
133  if (InjectionPointCache == NULL)
134  return NULL;
135 
136  entry = (InjectionPointCacheEntry *)
137  hash_search(InjectionPointCache, name, HASH_FIND, &found);
138 
139  if (found)
140  return entry->callback;
141 
142  return NULL;
143 }
144 #endif /* USE_INJECTION_POINTS */
145 
146 /*
147  * Return the space for dynamic shared hash table.
148  */
149 Size
151 {
152 #ifdef USE_INJECTION_POINTS
153  Size sz = 0;
154 
155  sz = add_size(sz, hash_estimate_size(INJECTION_POINT_HASH_MAX_SIZE,
156  sizeof(InjectionPointEntry)));
157  return sz;
158 #else
159  return 0;
160 #endif
161 }
162 
163 /*
164  * Allocate shmem space for dynamic shared hash.
165  */
166 void
168 {
169 #ifdef USE_INJECTION_POINTS
170  HASHCTL info;
171 
172  /* key is a NULL-terminated string */
173  info.keysize = sizeof(char[INJ_NAME_MAXLEN]);
174  info.entrysize = sizeof(InjectionPointEntry);
175  InjectionPointHash = ShmemInitHash("InjectionPoint hash",
176  INJECTION_POINT_HASH_INIT_SIZE,
177  INJECTION_POINT_HASH_MAX_SIZE,
178  &info,
180 #endif
181 }
182 
183 /*
184  * Attach a new injection point.
185  */
186 void
188  const char *library,
189  const char *function)
190 {
191 #ifdef USE_INJECTION_POINTS
192  InjectionPointEntry *entry_by_name;
193  bool found;
194 
195  if (strlen(name) >= INJ_NAME_MAXLEN)
196  elog(ERROR, "injection point name %s too long (maximum of %u)",
198  if (strlen(library) >= INJ_LIB_MAXLEN)
199  elog(ERROR, "injection point library %s too long (maximum of %u)",
200  library, INJ_LIB_MAXLEN);
201  if (strlen(function) >= INJ_FUNC_MAXLEN)
202  elog(ERROR, "injection point function %s too long (maximum of %u)",
203  function, INJ_FUNC_MAXLEN);
204 
205  /*
206  * Allocate and register a new injection point. A new point should not
207  * exist. For testing purposes this should be fine.
208  */
209  LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE);
210  entry_by_name = (InjectionPointEntry *)
211  hash_search(InjectionPointHash, name,
212  HASH_ENTER, &found);
213  if (found)
214  {
215  LWLockRelease(InjectionPointLock);
216  elog(ERROR, "injection point \"%s\" already defined", name);
217  }
218 
219  /* Save the entry */
220  strlcpy(entry_by_name->name, name, sizeof(entry_by_name->name));
221  entry_by_name->name[INJ_NAME_MAXLEN - 1] = '\0';
222  strlcpy(entry_by_name->library, library, sizeof(entry_by_name->library));
223  entry_by_name->library[INJ_LIB_MAXLEN - 1] = '\0';
224  strlcpy(entry_by_name->function, function, sizeof(entry_by_name->function));
225  entry_by_name->function[INJ_FUNC_MAXLEN - 1] = '\0';
226 
227  LWLockRelease(InjectionPointLock);
228 
229 #else
230  elog(ERROR, "injection points are not supported by this build");
231 #endif
232 }
233 
234 /*
235  * Detach an existing injection point.
236  */
237 void
239 {
240 #ifdef USE_INJECTION_POINTS
241  bool found;
242 
243  LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE);
244  hash_search(InjectionPointHash, name, HASH_REMOVE, &found);
245  LWLockRelease(InjectionPointLock);
246 
247  if (!found)
248  elog(ERROR, "injection point \"%s\" not found", name);
249 
250 #else
251  elog(ERROR, "Injection points are not supported by this build");
252 #endif
253 }
254 
255 /*
256  * Execute an injection point, if defined.
257  *
258  * Check first the shared hash table, and adapt the local cache depending
259  * on that as it could be possible that an entry to run has been removed.
260  */
261 void
263 {
264 #ifdef USE_INJECTION_POINTS
265  InjectionPointEntry *entry_by_name;
266  bool found;
267  InjectionPointCallback injection_callback;
268 
269  LWLockAcquire(InjectionPointLock, LW_SHARED);
270  entry_by_name = (InjectionPointEntry *)
271  hash_search(InjectionPointHash, name,
272  HASH_FIND, &found);
273  LWLockRelease(InjectionPointLock);
274 
275  /*
276  * If not found, do nothing and remove it from the local cache if it
277  * existed there.
278  */
279  if (!found)
280  {
281  injection_point_cache_remove(name);
282  return;
283  }
284 
285  /*
286  * Check if the callback exists in the local cache, to avoid unnecessary
287  * external loads.
288  */
289  injection_callback = injection_point_cache_get(name);
290  if (injection_callback == NULL)
291  {
292  char path[MAXPGPATH];
293 
294  /* not found in local cache, so load and register */
295  snprintf(path, MAXPGPATH, "%s/%s%s", pkglib_path,
296  entry_by_name->library, DLSUFFIX);
297 
298  if (!pg_file_exists(path))
299  elog(ERROR, "could not find library \"%s\" for injection point \"%s\"",
300  path, name);
301 
302  injection_callback = (InjectionPointCallback)
303  load_external_function(path, entry_by_name->function, false, NULL);
304 
305  if (injection_callback == NULL)
306  elog(ERROR, "could not find function \"%s\" in library \"%s\" for injection point \"%s\"",
307  entry_by_name->function, path, name);
308 
309  /* add it to the local cache when found */
310  injection_point_cache_add(name, injection_callback);
311  }
312 
313  injection_callback(name);
314 #else
315  elog(ERROR, "Injection points are not supported by this build");
316 #endif
317 }
#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_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
Size hash_estimate_size(long num_entries, Size entrysize)
Definition: dynahash.c:783
#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:79
#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
#define HASH_FIXED_SIZE
Definition: hsearch.h:105
void InjectionPointShmemInit(void)
Size InjectionPointShmemSize(void)
void InjectionPointRun(const char *name)
void InjectionPointDetach(const char *name)
void InjectionPointAttach(const char *name, const char *library, const char *function)
void(* InjectionPointCallback)(const char *name)
#define INJ_NAME_MAXLEN
bool LWLockAcquire(LWLock *lock, LWLockMode mode)
Definition: lwlock.c:1170
void LWLockRelease(LWLock *lock)
Definition: lwlock.c:1783
@ LW_SHARED
Definition: lwlock.h:115
@ 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
HTAB * ShmemInitHash(const char *name, long init_size, long max_size, HASHCTL *infoP, int hash_flags)
Definition: shmem.c:332
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