Skip to main content

heed/
write_admission.rs

1use std::sync::Arc;
2use std::{fmt, io};
3
4type Callback = dyn Fn(i32, u64, usize) -> io::Result<()> + Send + Sync;
5
6struct Inner {
7    #[cfg_attr(not(unix), allow(dead_code))]
8    callback: Box<Callback>,
9}
10
11/// Optional admission immediately before each physical LMDB write.
12///
13/// The callback receives a borrowed Unix file descriptor, byte offset and exact
14/// syscall length. Commit metadata reserves twice its write length up front,
15/// allowing one mandatory best-effort restore after a partial write without a
16/// second admission check. It must not close/retain the descriptor or reenter LMDB. An
17/// error refuses the write; its positive OS errno is preserved. Panics and
18/// errors without a positive OS errno become `EIO`. Guarded opens on non-Unix
19/// systems, and guarded writable mappings, fail closed. Environment-copy output
20/// is outside this hook. Clone the same handle when reopening an environment:
21/// callback identity participates in the existing open-options compatibility check.
22#[derive(Clone)]
23pub struct WriteAdmission(Arc<Inner>);
24
25impl WriteAdmission {
26    /// Create a callback retained until its LMDB environment closes.
27    pub fn new<F>(callback: F) -> Self
28    where
29        F: Fn(i32, u64, usize) -> io::Result<()> + Send + Sync + 'static,
30    {
31        Self(Arc::new(Inner {
32            callback: Box::new(callback),
33        }))
34    }
35
36    #[cfg(unix)]
37    pub(crate) fn context(&self) -> *mut std::ffi::c_void {
38        Arc::as_ptr(&self.0).cast_mut().cast()
39    }
40}
41
42impl PartialEq for WriteAdmission {
43    fn eq(&self, other: &Self) -> bool {
44        Arc::ptr_eq(&self.0, &other.0)
45    }
46}
47
48impl fmt::Debug for WriteAdmission {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        f.debug_struct("WriteAdmission").finish_non_exhaustive()
51    }
52}
53
54#[cfg(unix)]
55pub(crate) unsafe extern "C" fn admit_write(
56    context: *mut std::ffi::c_void,
57    fd: crate::mdb::ffi::mdb_filehandle_t,
58    offset: u64,
59    length: usize,
60) -> libc::c_int {
61    if context.is_null() {
62        return libc::EIO;
63    }
64    let inner = unsafe { &*context.cast::<Inner>() };
65    let callback = &inner.callback;
66    match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
67        callback(fd, offset, length)
68    })) {
69        Ok(Ok(())) => 0,
70        Ok(Err(error)) => error
71            .raw_os_error()
72            .filter(|code| *code > 0)
73            .unwrap_or(libc::EIO),
74        Err(_) => libc::EIO,
75    }
76}
77
78// Serializing options must never silently turn a guarded open into an unguarded one.
79#[cfg(feature = "serde")]
80impl serde::Serialize for WriteAdmission {
81    fn serialize<S: serde::Serializer>(&self, _: S) -> Result<S::Ok, S::Error> {
82        Err(serde::ser::Error::custom(
83            "write admission callbacks cannot be serialized",
84        ))
85    }
86}
87
88#[cfg(feature = "serde")]
89impl<'de> serde::Deserialize<'de> for WriteAdmission {
90    fn deserialize<D: serde::Deserializer<'de>>(_: D) -> Result<Self, D::Error> {
91        Err(serde::de::Error::custom(
92            "write admission callbacks must be installed explicitly",
93        ))
94    }
95}
96
97#[cfg(all(test, unix))]
98mod tests;