Coverage Report

Created: 2025-10-09 06:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/utils/misc/injection_point.c
Line
Count
Source
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-2025, 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
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
  if (InjectionPointCache == NULL)
129
  {
130
    HASHCTL   hash_ctl;
131
132
    hash_ctl.keysize = sizeof(char[INJ_NAME_MAXLEN]);
133
    hash_ctl.entrysize = sizeof(InjectionPointCacheEntry);
134
    hash_ctl.hcxt = TopMemoryContext;
135
136
    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
    hash_search(InjectionPointCache, name, HASH_ENTER, &found);
144
145
  Assert(!found);
146
  strlcpy(entry->name, name, sizeof(entry->name));
147
  entry->slot_idx = slot_idx;
148
  entry->generation = generation;
149
  entry->callback = callback;
150
  memcpy(entry->private_data, private_data, INJ_PRIVATE_MAXLEN);
151
152
  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
injection_point_cache_remove(const char *name)
164
{
165
  bool    found PG_USED_FOR_ASSERTS_ONLY;
166
167
  (void) hash_search(InjectionPointCache, name, HASH_REMOVE, &found);
168
  Assert(found);
169
}
170
171
/*
172
 * injection_point_cache_load
173
 *
174
 * Load an injection point into the local cache.
175
 */
176
static InjectionPointCacheEntry *
177
injection_point_cache_load(InjectionPointEntry *entry, int slot_idx, uint64 generation)
178
{
179
  char    path[MAXPGPATH];
180
  void     *injection_callback_local;
181
182
  snprintf(path, MAXPGPATH, "%s/%s%s", pkglib_path,
183
       entry->library, DLSUFFIX);
184
185
  if (!pg_file_exists(path))
186
    elog(ERROR, "could not find library \"%s\" for injection point \"%s\"",
187
       path, entry->name);
188
189
  injection_callback_local = (void *)
190
    load_external_function(path, entry->function, false, NULL);
191
192
  if (injection_callback_local == NULL)
193
    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
  return injection_point_cache_add(entry->name,
198
                   slot_idx,
199
                   generation,
200
                   injection_callback_local,
201
                   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
injection_point_cache_get(const char *name)
211
{
212
  bool    found;
213
  InjectionPointCacheEntry *entry;
214
215
  /* no callback if no cache yet */
216
  if (InjectionPointCache == NULL)
217
    return NULL;
218
219
  entry = (InjectionPointCacheEntry *)
220
    hash_search(InjectionPointCache, name, HASH_FIND, &found);
221
222
  if (found)
223
    return entry;
224
225
  return NULL;
226
}
227
#endif              /* USE_INJECTION_POINTS */
228
229
/*
230
 * Return the space for dynamic shared hash table.
231
 */
232
Size
233
InjectionPointShmemSize(void)
234
0
{
235
#ifdef USE_INJECTION_POINTS
236
  Size    sz = 0;
237
238
  sz = add_size(sz, sizeof(InjectionPointsCtl));
239
  return sz;
240
#else
241
0
  return 0;
242
0
#endif
243
0
}
244
245
/*
246
 * Allocate shmem space for dynamic shared hash.
247
 */
248
void
249
InjectionPointShmemInit(void)
250
0
{
251
#ifdef USE_INJECTION_POINTS
252
  bool    found;
253
254
  ActiveInjectionPoints = ShmemInitStruct("InjectionPoint hash",
255
                      sizeof(InjectionPointsCtl),
256
                      &found);
257
  if (!IsUnderPostmaster)
258
  {
259
    Assert(!found);
260
    pg_atomic_init_u32(&ActiveInjectionPoints->max_inuse, 0);
261
    for (int i = 0; i < MAX_INJECTION_POINTS; i++)
262
      pg_atomic_init_u64(&ActiveInjectionPoints->entries[i].generation, 0);
263
  }
264
  else
265
    Assert(found);
266
#endif
267
0
}
268
269
/*
270
 * Attach a new injection point.
271
 */
272
void
273
InjectionPointAttach(const char *name,
274
           const char *library,
275
           const char *function,
276
           const void *private_data,
277
           int private_data_size)
278
0
{
279
#ifdef USE_INJECTION_POINTS
280
  InjectionPointEntry *entry;
281
  uint64    generation;
282
  uint32    max_inuse;
283
  int     free_idx;
284
285
  if (strlen(name) >= INJ_NAME_MAXLEN)
286
    elog(ERROR, "injection point name %s too long (maximum of %u)",
287
       name, INJ_NAME_MAXLEN);
288
  if (strlen(library) >= INJ_LIB_MAXLEN)
289
    elog(ERROR, "injection point library %s too long (maximum of %u)",
290
       library, INJ_LIB_MAXLEN);
291
  if (strlen(function) >= INJ_FUNC_MAXLEN)
292
    elog(ERROR, "injection point function %s too long (maximum of %u)",
293
       function, INJ_FUNC_MAXLEN);
294
  if (private_data_size >= INJ_PRIVATE_MAXLEN)
295
    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
  LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE);
303
  max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
304
  free_idx = -1;
305
306
  for (int idx = 0; idx < max_inuse; idx++)
307
  {
308
    entry = &ActiveInjectionPoints->entries[idx];
309
    generation = pg_atomic_read_u64(&entry->generation);
310
    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
      if (free_idx == -1)
317
        free_idx = idx;
318
    }
319
    else if (strcmp(entry->name, name) == 0)
320
      elog(ERROR, "injection point \"%s\" already defined", name);
321
  }
322
  if (free_idx == -1)
323
  {
324
    if (max_inuse == MAX_INJECTION_POINTS)
325
      elog(ERROR, "too many injection points");
326
    free_idx = max_inuse;
327
  }
328
  entry = &ActiveInjectionPoints->entries[free_idx];
329
  generation = pg_atomic_read_u64(&entry->generation);
330
  Assert(generation % 2 == 0);
331
332
  /* Save the entry */
333
  strlcpy(entry->name, name, sizeof(entry->name));
334
  entry->name[INJ_NAME_MAXLEN - 1] = '\0';
335
  strlcpy(entry->library, library, sizeof(entry->library));
336
  entry->library[INJ_LIB_MAXLEN - 1] = '\0';
337
  strlcpy(entry->function, function, sizeof(entry->function));
338
  entry->function[INJ_FUNC_MAXLEN - 1] = '\0';
339
  if (private_data != NULL)
340
    memcpy(entry->private_data, private_data, private_data_size);
341
342
  pg_write_barrier();
343
  pg_atomic_write_u64(&entry->generation, generation + 1);
344
345
  if (free_idx + 1 > max_inuse)
346
    pg_atomic_write_u32(&ActiveInjectionPoints->max_inuse, free_idx + 1);
347
348
  LWLockRelease(InjectionPointLock);
349
350
#else
351
0
  elog(ERROR, "injection points are not supported by this build");
352
0
#endif
353
0
}
354
355
/*
356
 * Detach an existing injection point.
357
 *
358
 * Returns true if the injection point was detached, false otherwise.
359
 */
360
bool
361
InjectionPointDetach(const char *name)
362
0
{
363
#ifdef USE_INJECTION_POINTS
364
  bool    found = false;
365
  int     idx;
366
  int     max_inuse;
367
368
  LWLockAcquire(InjectionPointLock, LW_EXCLUSIVE);
369
370
  /* Find it in the shmem array, and mark the slot as unused */
371
  max_inuse = (int) pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
372
  for (idx = max_inuse - 1; idx >= 0; --idx)
373
  {
374
    InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
375
    uint64    generation;
376
377
    generation = pg_atomic_read_u64(&entry->generation);
378
    if (generation % 2 == 0)
379
      continue;     /* empty slot */
380
381
    if (strcmp(entry->name, name) == 0)
382
    {
383
      Assert(!found);
384
      found = true;
385
      pg_atomic_write_u64(&entry->generation, generation + 1);
386
      break;
387
    }
388
  }
389
390
  /* If we just removed the highest-numbered entry, update 'max_inuse' */
391
  if (found && idx == max_inuse - 1)
392
  {
393
    for (; idx >= 0; --idx)
394
    {
395
      InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
396
      uint64    generation;
397
398
      generation = pg_atomic_read_u64(&entry->generation);
399
      if (generation % 2 != 0)
400
        break;
401
    }
402
    pg_atomic_write_u32(&ActiveInjectionPoints->max_inuse, idx + 1);
403
  }
404
  LWLockRelease(InjectionPointLock);
405
406
  return found;
407
#else
408
0
  elog(ERROR, "Injection points are not supported by this build");
409
0
  return true;       /* silence compiler */
410
0
#endif
411
0
}
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
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
  max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
435
  if (max_inuse == 0)
436
  {
437
    if (InjectionPointCache)
438
    {
439
      hash_destroy(InjectionPointCache);
440
      InjectionPointCache = NULL;
441
    }
442
    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
  cached = injection_point_cache_get(name);
450
  if (cached)
451
  {
452
    int     idx = cached->slot_idx;
453
    InjectionPointEntry *entry = &ActiveInjectionPoints->entries[idx];
454
455
    if (pg_atomic_read_u64(&entry->generation) == cached->generation)
456
    {
457
      /* still good */
458
      return cached;
459
    }
460
    injection_point_cache_remove(name);
461
    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
  namelen = strlen(name);
474
  for (int idx = 0; idx < max_inuse; idx++)
475
  {
476
    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
    generation = pg_atomic_read_u64(&entry->generation);
485
    if (generation % 2 == 0)
486
      continue;     /* empty slot */
487
    pg_read_barrier();
488
489
    /* Is this the injection point we're looking for? */
490
    if (memcmp(entry->name, name, namelen + 1) != 0)
491
      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
    memcpy(&local_copy, entry, sizeof(InjectionPointEntry));
500
501
    pg_read_barrier();
502
    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
      continue;
514
    }
515
516
    /* Success! Load it into the cache and return it */
517
    return injection_point_cache_load(&local_copy, idx, generation);
518
  }
519
  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
InjectionPointLoad(const char *name)
532
0
{
533
#ifdef USE_INJECTION_POINTS
534
  InjectionPointCacheRefresh(name);
535
#else
536
0
  elog(ERROR, "Injection points are not supported by this build");
537
0
#endif
538
0
}
539
540
/*
541
 * Execute an injection point, if defined.
542
 */
543
void
544
InjectionPointRun(const char *name, void *arg)
545
0
{
546
#ifdef USE_INJECTION_POINTS
547
  InjectionPointCacheEntry *cache_entry;
548
549
  cache_entry = InjectionPointCacheRefresh(name);
550
  if (cache_entry)
551
    cache_entry->callback(name, cache_entry->private_data, arg);
552
#else
553
0
  elog(ERROR, "Injection points are not supported by this build");
554
0
#endif
555
0
}
556
557
/*
558
 * Execute an injection point directly from the cache, if defined.
559
 */
560
void
561
InjectionPointCached(const char *name, void *arg)
562
0
{
563
#ifdef USE_INJECTION_POINTS
564
  InjectionPointCacheEntry *cache_entry;
565
566
  cache_entry = injection_point_cache_get(name);
567
  if (cache_entry)
568
    cache_entry->callback(name, cache_entry->private_data, arg);
569
#else
570
0
  elog(ERROR, "Injection points are not supported by this build");
571
0
#endif
572
0
}
573
574
/*
575
 * Test if an injection point is defined.
576
 */
577
bool
578
IsInjectionPointAttached(const char *name)
579
0
{
580
#ifdef USE_INJECTION_POINTS
581
  return InjectionPointCacheRefresh(name) != NULL;
582
#else
583
0
  elog(ERROR, "Injection points are not supported by this build");
584
0
  return false;       /* silence compiler */
585
0
#endif
586
0
}
587
588
/*
589
 * Retrieve a list of all the injection points currently attached.
590
 *
591
 * This list is palloc'd in the current memory context.
592
 */
593
List *
594
InjectionPointList(void)
595
0
{
596
#ifdef USE_INJECTION_POINTS
597
  List     *inj_points = NIL;
598
  uint32    max_inuse;
599
600
  LWLockAcquire(InjectionPointLock, LW_SHARED);
601
602
  max_inuse = pg_atomic_read_u32(&ActiveInjectionPoints->max_inuse);
603
604
  for (uint32 idx = 0; idx < max_inuse; idx++)
605
  {
606
    InjectionPointEntry *entry;
607
    InjectionPointData *inj_point;
608
    uint64    generation;
609
610
    entry = &ActiveInjectionPoints->entries[idx];
611
    generation = pg_atomic_read_u64(&entry->generation);
612
613
    /* skip free slots */
614
    if (generation % 2 == 0)
615
      continue;
616
617
    inj_point = (InjectionPointData *) palloc0(sizeof(InjectionPointData));
618
    inj_point->name = pstrdup(entry->name);
619
    inj_point->library = pstrdup(entry->library);
620
    inj_point->function = pstrdup(entry->function);
621
    inj_points = lappend(inj_points, inj_point);
622
  }
623
624
  LWLockRelease(InjectionPointLock);
625
626
  return inj_points;
627
628
#else
629
0
  elog(ERROR, "Injection points are not supported by this build");
630
0
  return NIL;          /* keep compiler quiet */
631
0
#endif
632
0
}