guc.rsannotatedguc.rssource64 lines · 2.1 KB · raw
1//! String settings validated when they are set. pgrx 0.19.2 made GUC
2//! hooks `unsafe` (#2348), so the hook lives here and callers supply the
3//! check as safe code through [`StringCheck`].
4
5use std::ffi::{CStr, CString, c_char, c_void};
6
7use pgrx::{GucCheckError, GucContext, GucFlags, GucRegistry, GucSetting, pg_guard, pg_sys};
8
9/// Decides whether a proposed value is valid; `Err` carries the detail
10/// shown to the user. An unset value (NULL) is not checked.
11pub trait StringCheck {
12    fn check(value: &str) -> Result<(), String>;
13}
14
15/// Defines a string setting whose every `SET`, config-file value and
16/// placeholder adopted at load passes `C::check` first. Postgres reports
17/// a refusal as `invalid value for parameter "<name>"` with the detail.
18pub fn define_checked_string<C: StringCheck>(
19    name: &'static CStr,
20    short_description: &'static CStr,
21    long_description: &'static CStr,
22    setting: &'static GucSetting<Option<CString>>,
23    context: GucContext,
24    flags: GucFlags,
25) {
26    // SAFETY: the check hook is #[pg_guard]ed, so a panic in `C::check`
27    // becomes an ERROR instead of unwinding into Postgres.
28    unsafe {
29        GucRegistry::define_string_guc_with_hooks(
30            name,
31            short_description,
32            long_description,
33            setting,
34            context,
35            flags,
36            Some(check_hook::<C>),
37            None,
38            None,
39        );
40    }
41}
42
43#[pg_guard]
44unsafe extern "C-unwind" fn check_hook<C: StringCheck>(
45    newval: *mut *mut c_char,
46    _extra: *mut *mut c_void,
47    _source: pg_sys::GucSource::Type,
48) -> bool {
49    // SAFETY: guc.c passes a valid pointer to the proposed value, which
50    // is NULL or a NUL-terminated string for the duration of the call.
51    let value = unsafe { *newval };
52    if value.is_null() {
53        return true;
54    }
55    let value = unsafe { CStr::from_ptr(value) }.to_string_lossy();
56    match C::check(&value) {
57        Ok(()) => true,
58        Err(detail) => {
59            // SAFETY: called from within a check hook, as `apply` requires.
60            unsafe { GucCheckError::default().with_detail(detail).apply() };
61            false
62        }
63    }
64}