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 <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];
64 : InjectionPointCallback callback;
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,
76 : InjectionPointCallback callback)
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,
93 : HASH_ELEM | HASH_STRINGS | HASH_CONTEXT);
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 : */
126 : static InjectionPointCallback
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
150 3298 : InjectionPointShmemSize(void)
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 3298 : return 0;
160 : #endif
161 : }
162 :
163 : /*
164 : * Allocate shmem space for dynamic shared hash.
165 : */
166 : void
167 1768 : InjectionPointShmemInit(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,
179 : HASH_ELEM | HASH_FIXED_SIZE | HASH_STRINGS);
180 : #endif
181 1768 : }
182 :
183 : /*
184 : * Attach a new injection point.
185 : */
186 : void
187 0 : InjectionPointAttach(const char *name,
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)",
197 : name, INJ_NAME_MAXLEN);
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 0 : elog(ERROR, "injection points are not supported by this build");
231 : #endif
232 : }
233 :
234 : /*
235 : * Detach an existing injection point.
236 : */
237 : void
238 0 : InjectionPointDetach(const char *name)
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 0 : 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
262 0 : InjectionPointRun(const char *name)
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 0 : elog(ERROR, "Injection points are not supported by this build");
316 : #endif
317 : }
|