/*------------------------------------------------------------------------- * * injection_point.c * Routines to control and run injection points in the code. * * Injection points can be used to run arbitrary code by attaching callbacks * that would be executed in place of the named injection point. * * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * src/backend/utils/misc/injection_point.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include #include "fmgr.h" #include "miscadmin.h" #include "port/pg_bitutils.h" #include "storage/fd.h" #include "storage/lwlock.h" #include "storage/shmem.h" #include "utils/hsearch.h" #include "utils/injection_point.h" #include "utils/memutils.h" #ifdef USE_INJECTION_POINTS /* * Hash table for storing injection points. * * InjectionPointHash is used to find an injection point by name. */ static HTAB *InjectionPointHash; /* find points from names */ /* Field sizes */ #define INJ_NAME_MAXLEN 64 #define INJ_LIB_MAXLEN 128 #define INJ_FUNC_MAXLEN 128 /* Single injection point stored in InjectionPointHash */ typedef struct InjectionPointEntry { char name[INJ_NAME_MAXLEN]; /* hash key */ char library[INJ_LIB_MAXLEN]; /* library */ char function[INJ_FUNC_MAXLEN]; /* function */ } InjectionPointEntry; #define INJECTION_POINT_HASH_INIT_SIZE 16 #define INJECTION_POINT_HASH_MAX_SIZE 128 /* * Backend local cache of injection callbacks already loaded, stored in * TopMemoryContext. */ typedef struct InjectionPointCacheEntry { char name[INJ_NAME_MAXLEN]; InjectionPointCallback callback; } InjectionPointCacheEntry; static HTAB *InjectionPointCache = NULL; /* * injection_point_cache_add * * Add an injection point to the local cache. */ static void injection_point_cache_add(const char *name, InjectionPointCallback callback) { InjectionPointCacheEntry *entry; bool found; /* If first time, initialize */ if (InjectionPointCache == NULL) { HASHCTL hash_ctl; hash_ctl.keysize = sizeof(char[INJ_NAME_MAXLEN]); hash_ctl.entrysize = sizeof(InjectionPointCacheEntry); hash_ctl.hcxt = TopMemoryContext; InjectionPointCache = hash_create("InjectionPoint cache hash", INJECTION_POINT_HASH_MAX_SIZE, &hash_ctl, HASH_ELEM | HASH_STRINGS | HASH_CONTEXT); } entry = (InjectionPointCacheEntry *) hash_search(InjectionPointCache, name, HASH_ENTER, &found); Assert(!found); memcpy(entry->name, name, strlen(name)); entry->callback = callback; } /* * injection_point_cache_remove * * Remove entry from the local cache. Note that this leaks a callback * loaded but removed later on, which should have no consequence from * a testing perspective. */ static void injection_point_cache_remove(const char *name) { /* leave if no cache */ if (InjectionPointCache == NULL) return; (void) hash_search(InjectionPointCache, name, HASH_REMOVE, NULL); } /* * injection_point_cache_get * * Retrieve an injection point from the local cache, if any. */ static InjectionPointCallback injection_point_cache_get(const char *name) { bool found; InjectionPointCacheEntry *entry; /* no callback if no cache yet */ if (InjectionPointCache == NULL) return NULL; entry = (InjectionPointCacheEntry *) hash_search(InjectionPointCache, name, HASH_FIND, &found); if (found) return entry->callback; return NULL; } #endif /* USE_INJECTION_POINTS */ /* * Return the space for dynamic shared hash table. */ Size InjectionPointShmemSize(void) { #ifdef USE_INJECTION_POINTS Size sz = 0; sz = add_size(sz, hash_estimate_size(INJECTION_POINT_HASH_MAX_SIZE, sizeof(InjectionPointEntry))); return sz; #else return 0; #endif } /* * Allocate shmem space for dynamic shared hash. */ void InjectionPointShmemInit(void) { #ifdef USE_INJECTION_POINTS HASHCTL info; /* key is a NULL-terminated string */ info.keysize = sizeof(char[INJ_NAME_MAXLEN]); info.entrysize = sizeof(InjectionPointEntry); InjectionPointHash = ShmemInitHash("InjectionPoint hash", INJECTION_POINT_HASH_INIT_SIZE, INJECTION_POINT_HASH_MAX_SIZE, &info, HASH_ELEM | HASH_FIXED_SIZE | HASH_STRINGS); #endif } /* * Attach a new injection point. */ void InjectionPointAttach(const char *name, const char *library, const char *function) { #ifdef USE_INJECTION_POINTS InjectionPointEntry *entry_by_name; bool found; if (strlen(name) >= INJ_NAME_MAXLEN) elog(ERROR, "injection point name %s too long (maximum of %u)", name, INJ_NAME_MAXLEN); if (strlen(library) >= INJ_LIB_MAXLEN) elog(ERROR, "injection point library %s too long (maximum of %u)", library, INJ_LIB_MAXLEN); if (strlen(function) >= INJ_FUNC_MAXLEN) elog(ERROR, "injection point function %s too long (maximum of %u)", function, INJ_FUNC_MAXLEN); /* * Allocate and register a new injection point. A new point should not * exist. For testing purposes this should be fine. */ LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE); entry_by_name = (InjectionPointEntry *) hash_search(InjectionPointHash, name, HASH_ENTER, &found); if (found) { LWLockRelease(InjectionPointLock); elog(ERROR, "injection point \"%s\" already defined", name); } /* Save the entry */ memcpy(entry_by_name->name, name, sizeof(entry_by_name->name)); entry_by_name->name[INJ_NAME_MAXLEN - 1] = '\0'; memcpy(entry_by_name->library, library, sizeof(entry_by_name->library)); entry_by_name->library[INJ_LIB_MAXLEN - 1] = '\0'; memcpy(entry_by_name->function, function, sizeof(entry_by_name->function)); entry_by_name->function[INJ_FUNC_MAXLEN - 1] = '\0'; LWLockRelease(InjectionPointLock); #else elog(ERROR, "injection points are not supported by this build"); #endif } /* * Detach an existing injection point. */ void InjectionPointDetach(const char *name) { #ifdef USE_INJECTION_POINTS bool found; LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE); hash_search(InjectionPointHash, name, HASH_REMOVE, &found); LWLockRelease(InjectionPointLock); if (!found) elog(ERROR, "injection point \"%s\" not found", name); #else elog(ERROR, "Injection points are not supported by this build"); #endif } /* * Execute an injection point, if defined. * * Check first the shared hash table, and adapt the local cache depending * on that as it could be possible that an entry to run has been removed. */ void InjectionPointRun(const char *name) { #ifdef USE_INJECTION_POINTS InjectionPointEntry *entry_by_name; bool found; InjectionPointCallback injection_callback; LWLockAcquire(InjectionPointLock, LW_SHARED); entry_by_name = (InjectionPointEntry *) hash_search(InjectionPointHash, name, HASH_FIND, &found); LWLockRelease(InjectionPointLock); /* * If not found, do nothing and remove it from the local cache if it * existed there. */ if (!found) { injection_point_cache_remove(name); return; } /* * Check if the callback exists in the local cache, to avoid unnecessary * external loads. */ injection_callback = injection_point_cache_get(name); if (injection_callback == NULL) { char path[MAXPGPATH]; /* not found in local cache, so load and register */ snprintf(path, MAXPGPATH, "%s/%s%s", pkglib_path, entry_by_name->library, DLSUFFIX); if (!pg_file_exists(path)) elog(ERROR, "could not find library \"%s\" for injection point \"%s\"", path, name); injection_callback = (InjectionPointCallback) load_external_function(path, entry_by_name->function, true, NULL); if (injection_callback == NULL) elog(ERROR, "could not find function \"%s\" in library \"%s\" for injection point \"%s\"", name, entry_by_name->function, path); /* add it to the local cache when found */ injection_point_cache_add(name, injection_callback); } injection_callback(name); #else elog(ERROR, "Injection points are not supported by this build"); #endif }