Age Owner Branch data TLA 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
83 michael@paquier.xyz 75 :GNC 16 : injection_point_cache_add(const char *name,
76 : : InjectionPointCallback callback)
77 : : {
78 : : InjectionPointCacheEntry *entry;
79 : : bool found;
80 : :
81 : : /* If first time, initialize */
82 [ + + ]: 16 : if (InjectionPointCache == NULL)
83 : : {
84 : : HASHCTL hash_ctl;
85 : :
86 : 8 : hash_ctl.keysize = sizeof(char[INJ_NAME_MAXLEN]);
87 : 8 : hash_ctl.entrysize = sizeof(InjectionPointCacheEntry);
88 : 8 : hash_ctl.hcxt = TopMemoryContext;
89 : :
90 : 8 : 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 : 16 : hash_search(InjectionPointCache, name, HASH_ENTER, &found);
98 : :
99 [ - + ]: 16 : Assert(!found);
heikki.linnakangas@i 100 : 16 : strlcpy(entry->name, name, sizeof(entry->name));
michael@paquier.xyz 101 : 16 : entry->callback = callback;
102 : 16 : }
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 : 2402 : injection_point_cache_remove(const char *name)
113 : : {
114 : : /* leave if no cache */
115 [ + + ]: 2402 : if (InjectionPointCache == NULL)
116 : 1791 : return;
117 : :
118 : 611 : (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 : 20 : injection_point_cache_get(const char *name)
128 : : {
129 : : bool found;
130 : : InjectionPointCacheEntry *entry;
131 : :
132 : : /* no callback if no cache yet */
133 [ + + ]: 20 : if (InjectionPointCache == NULL)
134 : 8 : return NULL;
135 : :
136 : : entry = (InjectionPointCacheEntry *)
137 : 12 : hash_search(InjectionPointCache, name, HASH_FIND, &found);
138 : :
139 [ + + ]: 12 : if (found)
140 : 4 : return entry->callback;
141 : :
142 : 8 : return NULL;
143 : : }
144 : : #endif /* USE_INJECTION_POINTS */
145 : :
146 : : /*
147 : : * Return the space for dynamic shared hash table.
148 : : */
149 : : Size
150 : 1679 : InjectionPointShmemSize(void)
151 : : {
152 : : #ifdef USE_INJECTION_POINTS
153 : 1679 : Size sz = 0;
154 : :
155 : 1679 : sz = add_size(sz, hash_estimate_size(INJECTION_POINT_HASH_MAX_SIZE,
156 : : sizeof(InjectionPointEntry)));
157 : 1679 : return sz;
158 : : #else
159 : : return 0;
160 : : #endif
161 : : }
162 : :
163 : : /*
164 : : * Allocate shmem space for dynamic shared hash.
165 : : */
166 : : void
167 : 898 : InjectionPointShmemInit(void)
168 : : {
169 : : #ifdef USE_INJECTION_POINTS
170 : : HASHCTL info;
171 : :
172 : : /* key is a NULL-terminated string */
173 : 898 : info.keysize = sizeof(char[INJ_NAME_MAXLEN]);
174 : 898 : info.entrysize = sizeof(InjectionPointEntry);
175 : 898 : 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 : 898 : }
182 : :
183 : : /*
184 : : * Attach a new injection point.
185 : : */
186 : : void
187 : 14 : 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 [ - + ]: 14 : if (strlen(name) >= INJ_NAME_MAXLEN)
83 michael@paquier.xyz 196 [ # # ]:UNC 0 : elog(ERROR, "injection point name %s too long (maximum of %u)",
197 : : name, INJ_NAME_MAXLEN);
83 michael@paquier.xyz 198 [ - + ]:GNC 14 : if (strlen(library) >= INJ_LIB_MAXLEN)
83 michael@paquier.xyz 199 [ # # ]:UNC 0 : elog(ERROR, "injection point library %s too long (maximum of %u)",
200 : : library, INJ_LIB_MAXLEN);
83 michael@paquier.xyz 201 [ - + ]:GNC 14 : if (strlen(function) >= INJ_FUNC_MAXLEN)
83 michael@paquier.xyz 202 [ # # ]:UNC 0 : 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 : : */
83 michael@paquier.xyz 209 :GNC 14 : LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE);
210 : : entry_by_name = (InjectionPointEntry *)
211 : 14 : hash_search(InjectionPointHash, name,
212 : : HASH_ENTER, &found);
213 [ - + ]: 14 : if (found)
214 : : {
83 michael@paquier.xyz 215 :UNC 0 : LWLockRelease(InjectionPointLock);
216 [ # # ]: 0 : elog(ERROR, "injection point \"%s\" already defined", name);
217 : : }
218 : :
219 : : /* Save the entry */
83 heikki.linnakangas@i 220 :GNC 14 : strlcpy(entry_by_name->name, name, sizeof(entry_by_name->name));
michael@paquier.xyz 221 : 14 : entry_by_name->name[INJ_NAME_MAXLEN - 1] = '\0';
heikki.linnakangas@i 222 : 14 : strlcpy(entry_by_name->library, library, sizeof(entry_by_name->library));
michael@paquier.xyz 223 : 14 : entry_by_name->library[INJ_LIB_MAXLEN - 1] = '\0';
heikki.linnakangas@i 224 : 14 : strlcpy(entry_by_name->function, function, sizeof(entry_by_name->function));
michael@paquier.xyz 225 : 14 : entry_by_name->function[INJ_FUNC_MAXLEN - 1] = '\0';
226 : :
227 : 14 : LWLockRelease(InjectionPointLock);
228 : :
229 : : #else
230 : : elog(ERROR, "injection points are not supported by this build");
231 : : #endif
232 : 14 : }
233 : :
234 : : /*
235 : : * Detach an existing injection point.
236 : : */
237 : : void
238 : 11 : InjectionPointDetach(const char *name)
239 : : {
240 : : #ifdef USE_INJECTION_POINTS
241 : : bool found;
242 : :
243 : 11 : LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE);
244 : 11 : hash_search(InjectionPointHash, name, HASH_REMOVE, &found);
245 : 11 : LWLockRelease(InjectionPointLock);
246 : :
247 [ + + ]: 11 : if (!found)
248 [ + - ]: 1 : 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 : 10 : }
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 : 2422 : InjectionPointRun(const char *name)
263 : : {
264 : : #ifdef USE_INJECTION_POINTS
265 : : InjectionPointEntry *entry_by_name;
266 : : bool found;
267 : : InjectionPointCallback injection_callback;
268 : :
269 : 2422 : LWLockAcquire(InjectionPointLock, LW_SHARED);
270 : : entry_by_name = (InjectionPointEntry *)
271 : 2422 : hash_search(InjectionPointHash, name,
272 : : HASH_FIND, &found);
273 : 2422 : LWLockRelease(InjectionPointLock);
274 : :
275 : : /*
276 : : * If not found, do nothing and remove it from the local cache if it
277 : : * existed there.
278 : : */
279 [ + + ]: 2422 : if (!found)
280 : : {
281 : 2402 : injection_point_cache_remove(name);
282 : 2402 : return;
283 : : }
284 : :
285 : : /*
286 : : * Check if the callback exists in the local cache, to avoid unnecessary
287 : : * external loads.
288 : : */
289 : 20 : injection_callback = injection_point_cache_get(name);
290 [ + + ]: 20 : if (injection_callback == NULL)
291 : : {
292 : : char path[MAXPGPATH];
293 : :
294 : : /* not found in local cache, so load and register */
295 : 16 : snprintf(path, MAXPGPATH, "%s/%s%s", pkglib_path,
296 : 16 : entry_by_name->library, DLSUFFIX);
297 : :
298 [ - + ]: 16 : if (!pg_file_exists(path))
83 michael@paquier.xyz 299 [ # # ]:UNC 0 : elog(ERROR, "could not find library \"%s\" for injection point \"%s\"",
300 : : path, name);
301 : :
83 michael@paquier.xyz 302 :GNC 16 : injection_callback = (InjectionPointCallback)
82 303 : 16 : load_external_function(path, entry_by_name->function, false, NULL);
304 : :
83 305 [ - + ]: 16 : if (injection_callback == NULL)
83 michael@paquier.xyz 306 [ # # ]:UNC 0 : 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 */
83 michael@paquier.xyz 310 :GNC 16 : injection_point_cache_add(name, injection_callback);
311 : : }
312 : :
313 : 20 : injection_callback(name);
314 : : #else
315 : : elog(ERROR, "Injection points are not supported by this build");
316 : : #endif
317 : : }
|