LCOV - code coverage report
Current view: top level - src/backend/utils/misc - injection_point.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 141 152 92.8 %
Date: 2024-11-21 08:14:44 Functions: 13 13 100.0 %
Legend: Lines: hit not hit

          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 "utils/injection_point.h"
      21             : 
      22             : #ifdef USE_INJECTION_POINTS
      23             : 
      24             : #include <sys/stat.h>
      25             : 
      26             : #include "fmgr.h"
      27             : #include "miscadmin.h"
      28             : #include "storage/fd.h"
      29             : #include "storage/lwlock.h"
      30             : #include "storage/shmem.h"
      31             : #include "utils/hsearch.h"
      32             : #include "utils/memutils.h"
      33             : 
      34             : /* Field sizes */
      35             : #define INJ_NAME_MAXLEN     64
      36             : #define INJ_LIB_MAXLEN      128
      37             : #define INJ_FUNC_MAXLEN     128
      38             : #define INJ_PRIVATE_MAXLEN  1024
      39             : 
      40             : /* Single injection point stored in shared memory */
      41             : typedef struct InjectionPointEntry
      42             : {
      43             :     /*
      44             :      * Because injection points need to be usable without LWLocks, we use a
      45             :      * generation counter on each entry to allow safe, lock-free reading.
      46             :      *
      47             :      * To read an entry, first read the current 'generation' value.  If it's
      48             :      * even, then the slot is currently unused, and odd means it's in use.
      49             :      * When reading the other fields, beware that they may change while
      50             :      * reading them, if the entry is released and reused!  After reading the
      51             :      * other fields, read 'generation' again: if its value hasn't changed, you
      52             :      * can be certain that the other fields you read are valid.  Otherwise,
      53             :      * the slot was concurrently recycled, and you should ignore it.
      54             :      *
      55             :      * When adding an entry, you must store all the other fields first, and
      56             :      * then update the generation number, with an appropriate memory barrier
      57             :      * in between. In addition to that protocol, you must also hold
      58             :      * InjectionPointLock, to prevent two backends from modifying the array at
      59             :      * the same time.
      60             :      */
      61             :     pg_atomic_uint64 generation;
      62             : 
      63             :     char        name[INJ_NAME_MAXLEN];  /* point name */
      64             :     char        library[INJ_LIB_MAXLEN];    /* library */
      65             :     char        function[INJ_FUNC_MAXLEN];  /* function */
      66             : 
      67             :     /*
      68             :      * Opaque data area that modules can use to pass some custom data to
      69             :      * callbacks, registered when attached.
      70             :      */
      71             :     char        private_data[INJ_PRIVATE_MAXLEN];
      72             : } InjectionPointEntry;
      73             : 
      74             : #define MAX_INJECTION_POINTS    128
      75             : 
      76             : /*
      77             :  * Shared memory array of active injection points.
      78             :  *
      79             :  * 'max_inuse' is the highest index currently in use, plus one.  It's just an
      80             :  * optimization to avoid scanning through the whole entry, in the common case
      81             :  * that there are no injection points, or only a few.
      82             :  */
      83             : typedef struct InjectionPointsCtl
      84             : {
      85             :     pg_atomic_uint32 max_inuse;
      86             :     InjectionPointEntry entries[MAX_INJECTION_POINTS];
      87             : } InjectionPointsCtl;
      88             : 
      89             : NON_EXEC_STATIC InjectionPointsCtl *ActiveInjectionPoints;
      90             : 
      91             : /*
      92             :  * Backend local cache of injection callbacks already loaded, stored in
      93             :  * TopMemoryContext.
      94             :  */
      95             : typedef struct InjectionPointCacheEntry
      96             : {
      97             :     char        name[INJ_NAME_MAXLEN];
      98             :     char        private_data[INJ_PRIVATE_MAXLEN];
      99             :     InjectionPointCallback callback;
     100             : 
     101             :     /*
     102             :      * Shmem slot and copy of its generation number when this cache entry was
     103             :      * created.  They can be used to validate if the cached entry is still
     104             :      * valid.
     105             :      */
     106             :     int         slot_idx;
     107             :     uint64      generation;
     108             : } InjectionPointCacheEntry;
     109             : 
     110             : static HTAB *InjectionPointCache = NULL;
     111             : 
     112             : /*
     113             :  * injection_point_cache_add
     114             :  *
     115             :  * Add an injection point to the local cache.
     116             :  */
     117             : static InjectionPointCacheEntry *
     118          84 : injection_point_cache_add(const char *name,
     119             :                           int slot_idx,
     120             :                           uint64 generation,
     121             :                           InjectionPointCallback callback,
     122             :                           const void *private_data)
     123             : {
     124             :     InjectionPointCacheEntry *entry;
     125             :     bool        found;
     126             : 
     127             :     /* If first time, initialize */
     128          84 :     if (InjectionPointCache == NULL)
     129             :     {
     130             :         HASHCTL     hash_ctl;
     131             : 
     132          54 :         hash_ctl.keysize = sizeof(char[INJ_NAME_MAXLEN]);
     133          54 :         hash_ctl.entrysize = sizeof(InjectionPointCacheEntry);
     134          54 :         hash_ctl.hcxt = TopMemoryContext;
     135             : 
     136          54 :         InjectionPointCache = hash_create("InjectionPoint cache hash",
     137             :                                           MAX_INJECTION_POINTS,
     138             :                                           &hash_ctl,
     139             :                                           HASH_ELEM | HASH_STRINGS | HASH_CONTEXT);
     140             :     }
     141             : 
     142             :     entry = (InjectionPointCacheEntry *)
     143          84 :         hash_search(InjectionPointCache, name, HASH_ENTER, &found);
     144             : 
     145             :     Assert(!found);
     146          84 :     strlcpy(entry->name, name, sizeof(entry->name));
     147          84 :     entry->slot_idx = slot_idx;
     148          84 :     entry->generation = generation;
     149          84 :     entry->callback = callback;
     150          84 :     memcpy(entry->private_data, private_data, INJ_PRIVATE_MAXLEN);
     151             : 
     152          84 :     return entry;
     153             : }
     154             : 
     155             : /*
     156             :  * injection_point_cache_remove
     157             :  *
     158             :  * Remove entry from the local cache.  Note that this leaks a callback
     159             :  * loaded but removed later on, which should have no consequence from
     160             :  * a testing perspective.
     161             :  */
     162             : static void
     163          18 : injection_point_cache_remove(const char *name)
     164             : {
     165             :     bool        found PG_USED_FOR_ASSERTS_ONLY;
     166             : 
     167          18 :     (void) hash_search(InjectionPointCache, name, HASH_REMOVE, &found);
     168             :     Assert(found);
     169          18 : }
     170             : 
     171             : /*
     172             :  * injection_point_cache_load
     173             :  *
     174             :  * Load an injection point into the local cache.
     175             :  */
     176             : static InjectionPointCacheEntry *
     177          84 : injection_point_cache_load(InjectionPointEntry *entry, int slot_idx, uint64 generation)
     178             : {
     179             :     char        path[MAXPGPATH];
     180             :     void       *injection_callback_local;
     181             : 
     182          84 :     snprintf(path, MAXPGPATH, "%s/%s%s", pkglib_path,
     183          84 :              entry->library, DLSUFFIX);
     184             : 
     185          84 :     if (!pg_file_exists(path))
     186           0 :         elog(ERROR, "could not find library \"%s\" for injection point \"%s\"",
     187             :              path, entry->name);
     188             : 
     189          84 :     injection_callback_local = (void *)
     190          84 :         load_external_function(path, entry->function, false, NULL);
     191             : 
     192          84 :     if (injection_callback_local == NULL)
     193           0 :         elog(ERROR, "could not find function \"%s\" in library \"%s\" for injection point \"%s\"",
     194             :              entry->function, path, entry->name);
     195             : 
     196             :     /* add it to the local cache */
     197         168 :     return injection_point_cache_add(entry->name,
     198             :                                      slot_idx,
     199             :                                      generation,
     200             :                                      injection_callback_local,
     201          84 :                                      entry->private_data);
     202             : }
     203             : 
     204             : /*
     205             :  * injection_point_cache_get
     206             :  *
     207             :  * Retrieve an injection point from the local cache, if any.
     208             :  */
     209             : static InjectionPointCacheEntry *
     210        4130 : injection_point_cache_get(const char *name)
     211             : {
     212             :     bool        found;
     213             :     InjectionPointCacheEntry *entry;
     214             : 
     215             :     /* no callback if no cache yet */
     216        4130 :     if (InjectionPointCache == NULL)
     217         982 :         return NULL;
     218             : 
     219             :     entry = (InjectionPointCacheEntry *)
     220        3148 :         hash_search(InjectionPointCache, name, HASH_FIND, &found);
     221             : 
     222        3148 :     if (found)
     223          86 :         return entry;
     224             : 
     225        3062 :     return NULL;
     226             : }
     227             : #endif                          /* USE_INJECTION_POINTS */
     228             : 
     229             : /*
     230             :  * Return the space for dynamic shared hash table.
     231             :  */
     232             : Size
     233        3534 : InjectionPointShmemSize(void)
     234             : {
     235             : #ifdef USE_INJECTION_POINTS
     236        3534 :     Size        sz = 0;
     237             : 
     238        3534 :     sz = add_size(sz, sizeof(InjectionPointsCtl));
     239        3534 :     return sz;
     240             : #else
     241             :     return 0;
     242             : #endif
     243             : }
     244             : 
     245             : /*
     246             :  * Allocate shmem space for dynamic shared hash.
     247             :  */
     248             : void
     249        1902 : InjectionPointShmemInit(void)
     250             : {
     251             : #ifdef USE_INJECTION_POINTS
     252             :     bool        found;
     253             : 
     254        1902 :     ActiveInjectionPoints = ShmemInitStruct("InjectionPoint hash",
     255             :                                             sizeof(InjectionPointsCtl),
     256             :                                             &found);
     257        1902 :     if (!IsUnderPostmaster)
     258             :     {
     259             :         Assert(!found);
     260        1902 :         pg_atomic_init_u32(&ActiveInjectionPoints->max_inuse, 0);
     261      245358 :         for (int i = 0; i < MAX_INJECTION_POINTS; i++)
     262      243456 :             pg_atomic_init_u64(&ActiveInjectionPoints->entries[i].generation, 0);
     263             :     }
     264             :     else
     265             :         Assert(found);
     266             : #endif
     267        1902 : }
     268             : 
     269             : /*
     270             :  * Attach a new injection point.
     271             :  */
     272             : void
     273          70 : InjectionPointAttach(const char *name,
     274             :                      const char *library,
     275             :                      const char *function,
     276             :                      const void *private_data,
     277             :                      int private_data_size)
     278             : {
     279             : #ifdef USE_INJECTION_POINTS
     280             :     InjectionPointEntry *entry;
     281             :     uint64      generation;
     282             :     uint32      max_inuse;
     283             :     int         free_idx;
     284             : 
     285          70 :     if (strlen(name) >= INJ_NAME_MAXLEN)
     286           0 :         elog(ERROR, "injection point name %s too long (maximum of %u)",
     287             :              name, INJ_NAME_MAXLEN);
     288          70 :     if (strlen(library) >= INJ_LIB_MAXLEN)
     289           0 :         elog(ERROR, "injection point library %s too long (maximum of %u)",
     290             :              library, INJ_LIB_MAXLEN);
     291          70 :     if (strlen(function) >= INJ_FUNC_MAXLEN)
     292           0 :         elog(ERROR, "injection point function %s too long (maximum of %u)",
     293             :              function, INJ_FUNC_MAXLEN);
     294          70 :     if (private_data_size >= INJ_PRIVATE_MAXLEN)
     295           0 :         elog(ERROR, "injection point data too long (maximum of %u)",
     296             :              INJ_PRIVATE_MAXLEN);
     297             : 
     298             :     /*
     299             :      * Allocate and register a new injection point.  A new point should not
     300             :      * exist.  For testing purposes this should be fine.
     301             :      */
     302          70 :     LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE);
     303          70 :     max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
     304          70 :     free_idx = -1;
     305             : 
     306         100 :     for (int idx = 0; idx < max_inuse; idx++)
     307             :     {
     308          30 :         entry = &ActiveInjectionPoints->entries[idx];
     309          30 :         generation = pg_atomic_read_u64(&entry->generation);
     310          30 :         if (generation % 2 == 0)
     311             :         {
     312             :             /*
     313             :              * Found a free slot where we can add the new entry, but keep
     314             :              * going so that we will find out if the entry already exists.
     315             :              */
     316           0 :             if (free_idx == -1)
     317           0 :                 free_idx = idx;
     318             :         }
     319          30 :         else if (strcmp(entry->name, name) == 0)
     320           0 :             elog(ERROR, "injection point \"%s\" already defined", name);
     321             :     }
     322          70 :     if (free_idx == -1)
     323             :     {
     324          70 :         if (max_inuse == MAX_INJECTION_POINTS)
     325           0 :             elog(ERROR, "too many injection points");
     326          70 :         free_idx = max_inuse;
     327             :     }
     328          70 :     entry = &ActiveInjectionPoints->entries[free_idx];
     329          70 :     generation = pg_atomic_read_u64(&entry->generation);
     330             :     Assert(generation % 2 == 0);
     331             : 
     332             :     /* Save the entry */
     333          70 :     strlcpy(entry->name, name, sizeof(entry->name));
     334          70 :     entry->name[INJ_NAME_MAXLEN - 1] = '\0';
     335          70 :     strlcpy(entry->library, library, sizeof(entry->library));
     336          70 :     entry->library[INJ_LIB_MAXLEN - 1] = '\0';
     337          70 :     strlcpy(entry->function, function, sizeof(entry->function));
     338          70 :     entry->function[INJ_FUNC_MAXLEN - 1] = '\0';
     339          70 :     if (private_data != NULL)
     340          70 :         memcpy(entry->private_data, private_data, private_data_size);
     341             : 
     342          70 :     pg_write_barrier();
     343          70 :     pg_atomic_write_u64(&entry->generation, generation + 1);
     344             : 
     345          70 :     if (free_idx + 1 > max_inuse)
     346          70 :         pg_atomic_write_u32(&ActiveInjectionPoints->max_inuse, free_idx + 1);
     347             : 
     348          70 :     LWLockRelease(InjectionPointLock);
     349             : 
     350             : #else
     351             :     elog(ERROR, "injection points are not supported by this build");
     352             : #endif
     353          70 : }
     354             : 
     355             : /*
     356             :  * Detach an existing injection point.
     357             :  *
     358             :  * Returns true if the injection point was detached, false otherwise.
     359             :  */
     360             : bool
     361         162 : InjectionPointDetach(const char *name)
     362             : {
     363             : #ifdef USE_INJECTION_POINTS
     364         162 :     bool        found = false;
     365             :     int         idx;
     366             :     int         max_inuse;
     367             : 
     368         162 :     LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE);
     369             : 
     370             :     /* Find it in the shmem array, and mark the slot as unused */
     371         162 :     max_inuse = (int) pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
     372         182 :     for (idx = max_inuse - 1; idx >= 0; --idx)
     373             :     {
     374          74 :         InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
     375             :         uint64      generation;
     376             : 
     377          74 :         generation = pg_atomic_read_u64(&entry->generation);
     378          74 :         if (generation % 2 == 0)
     379           4 :             continue;           /* empty slot */
     380             : 
     381          70 :         if (strcmp(entry->name, name) == 0)
     382             :         {
     383             :             Assert(!found);
     384          54 :             found = true;
     385          54 :             pg_atomic_write_u64(&entry->generation, generation + 1);
     386          54 :             break;
     387             :         }
     388             :     }
     389             : 
     390             :     /* If we just removed the highest-numbered entry, update 'max_inuse' */
     391         162 :     if (found && idx == max_inuse - 1)
     392             :     {
     393          98 :         for (; idx >= 0; --idx)
     394             :         {
     395          62 :             InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
     396             :             uint64      generation;
     397             : 
     398          62 :             generation = pg_atomic_read_u64(&entry->generation);
     399          62 :             if (generation % 2 != 0)
     400           8 :                 break;
     401             :         }
     402          44 :         pg_atomic_write_u32(&ActiveInjectionPoints->max_inuse, idx + 1);
     403             :     }
     404         162 :     LWLockRelease(InjectionPointLock);
     405             : 
     406         162 :     return found;
     407             : #else
     408             :     elog(ERROR, "Injection points are not supported by this build");
     409             :     return true;                /* silence compiler */
     410             : #endif
     411             : }
     412             : 
     413             : #ifdef USE_INJECTION_POINTS
     414             : /*
     415             :  * Common workhorse of InjectionPointRun() and InjectionPointLoad()
     416             :  *
     417             :  * Checks if an injection point exists in shared memory, and update
     418             :  * the local cache entry accordingly.
     419             :  */
     420             : static InjectionPointCacheEntry *
     421      963002 : InjectionPointCacheRefresh(const char *name)
     422             : {
     423             :     uint32      max_inuse;
     424             :     int         namelen;
     425             :     InjectionPointEntry local_copy;
     426             :     InjectionPointCacheEntry *cached;
     427             : 
     428             :     /*
     429             :      * First read the number of in-use slots.  More entries can be added or
     430             :      * existing ones can be removed while we're reading them.  If the entry
     431             :      * we're looking for is concurrently added or removed, we might or might
     432             :      * not see it.  That's OK.
     433             :      */
     434      963002 :     max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
     435      963002 :     if (max_inuse == 0)
     436             :     {
     437      959448 :         if (InjectionPointCache)
     438             :         {
     439          20 :             hash_destroy(InjectionPointCache);
     440          20 :             InjectionPointCache = NULL;
     441             :         }
     442      959448 :         return NULL;
     443             :     }
     444             : 
     445             :     /*
     446             :      * If we have this entry in the local cache already, check if the cached
     447             :      * entry is still valid.
     448             :      */
     449        3554 :     cached = injection_point_cache_get(name);
     450        3554 :     if (cached)
     451             :     {
     452          80 :         int         idx = cached->slot_idx;
     453          80 :         InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
     454             : 
     455          80 :         if (pg_atomic_read_u64(&entry->generation) == cached->generation)
     456             :         {
     457             :             /* still good */
     458          62 :             return cached;
     459             :         }
     460          18 :         injection_point_cache_remove(name);
     461          18 :         cached = NULL;
     462             :     }
     463             : 
     464             :     /*
     465             :      * Search the shared memory array.
     466             :      *
     467             :      * It's possible that the entry we're looking for is concurrently detached
     468             :      * or attached.  Or detached *and* re-attached, to the same slot or a
     469             :      * different slot.  Detach and re-attach is not an atomic operation, so
     470             :      * it's OK for us to return the old value, NULL, or the new value in such
     471             :      * cases.
     472             :      */
     473        3492 :     namelen = strlen(name);
     474       10234 :     for (int idx = 0; idx < max_inuse; idx++)
     475             :     {
     476        6826 :         InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
     477             :         uint64      generation;
     478             : 
     479             :         /*
     480             :          * Read the generation number so that we can detect concurrent
     481             :          * modifications.  The read barrier ensures that the generation number
     482             :          * is loaded before any of the other fields.
     483             :          */
     484        6826 :         generation = pg_atomic_read_u64(&entry->generation);
     485        6826 :         if (generation % 2 == 0)
     486          18 :             continue;           /* empty slot */
     487        6808 :         pg_read_barrier();
     488             : 
     489             :         /* Is this the injection point we're looking for? */
     490        6808 :         if (memcmp(entry->name, name, namelen + 1) != 0)
     491        6724 :             continue;
     492             : 
     493             :         /*
     494             :          * The entry can change at any time, if the injection point is
     495             :          * concurrently detached.  Copy it to local memory, and re-check the
     496             :          * generation.  If the generation hasn't changed, we know our local
     497             :          * copy is coherent.
     498             :          */
     499          84 :         memcpy(&local_copy, entry, sizeof(InjectionPointEntry));
     500             : 
     501          84 :         pg_read_barrier();
     502          84 :         if (pg_atomic_read_u64(&entry->generation) != generation)
     503             :         {
     504             :             /*
     505             :              * The entry was concurrently detached.
     506             :              *
     507             :              * Continue the search, because if the generation number changed,
     508             :              * we cannot trust the result of the name comparison we did above.
     509             :              * It's theoretically possible that it falsely matched a mixed-up
     510             :              * state of the old and new name, if the slot was recycled with a
     511             :              * different name.
     512             :              */
     513           0 :             continue;
     514             :         }
     515             : 
     516             :         /* Success! Load it into the cache and return it */
     517          84 :         return injection_point_cache_load(&local_copy, idx, generation);
     518             :     }
     519        3408 :     return NULL;
     520             : }
     521             : #endif
     522             : 
     523             : /*
     524             :  * Load an injection point into the local cache.
     525             :  *
     526             :  * This is useful to be able to load an injection point before running it,
     527             :  * especially if the injection point is called in a code path where memory
     528             :  * allocations cannot happen, like critical sections.
     529             :  */
     530             : void
     531         576 : InjectionPointLoad(const char *name)
     532             : {
     533             : #ifdef USE_INJECTION_POINTS
     534         576 :     InjectionPointCacheRefresh(name);
     535             : #else
     536             :     elog(ERROR, "Injection points are not supported by this build");
     537             : #endif
     538         576 : }
     539             : 
     540             : /*
     541             :  * Execute an injection point, if defined.
     542             :  */
     543             : void
     544      936946 : InjectionPointRun(const char *name)
     545             : {
     546             : #ifdef USE_INJECTION_POINTS
     547             :     InjectionPointCacheEntry *cache_entry;
     548             : 
     549      936946 :     cache_entry = InjectionPointCacheRefresh(name);
     550      936946 :     if (cache_entry)
     551         138 :         cache_entry->callback(name, cache_entry->private_data);
     552             : #else
     553             :     elog(ERROR, "Injection points are not supported by this build");
     554             : #endif
     555      936928 : }
     556             : 
     557             : /*
     558             :  * Execute an injection point directly from the cache, if defined.
     559             :  */
     560             : void
     561         576 : InjectionPointCached(const char *name)
     562             : {
     563             : #ifdef USE_INJECTION_POINTS
     564             :     InjectionPointCacheEntry *cache_entry;
     565             : 
     566         576 :     cache_entry = injection_point_cache_get(name);
     567         576 :     if (cache_entry)
     568           6 :         cache_entry->callback(name, cache_entry->private_data);
     569             : #else
     570             :     elog(ERROR, "Injection points are not supported by this build");
     571             : #endif
     572         576 : }
     573             : 
     574             : /*
     575             :  * Test if an injection point is defined.
     576             :  */
     577             : bool
     578       25480 : IsInjectionPointAttached(const char *name)
     579             : {
     580             : #ifdef USE_INJECTION_POINTS
     581       25480 :     return InjectionPointCacheRefresh(name) != NULL;
     582             : #else
     583             :     elog(ERROR, "Injection points are not supported by this build");
     584             :     return false;               /* silence compiler */
     585             : #endif
     586             : }

Generated by: LCOV version 1.14