duende_mlock/
config.rs

1//! Configuration for memory locking behavior.
2
3/// Configuration for memory locking operations.
4///
5/// Use [`MlockConfig::builder()`] for a fluent configuration API:
6///
7/// ```rust
8/// use duende_mlock::MlockConfig;
9///
10/// let config = MlockConfig::builder()
11///     .current(true)      // Lock pages currently mapped
12///     .future(true)       // Lock pages mapped in the future
13///     .required(false)    // Don't fail if mlock fails
14///     .build();
15/// ```
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17#[allow(clippy::struct_excessive_bools)] // These are independent flags, not state
18pub struct MlockConfig {
19    /// Lock pages currently mapped into the address space (`MCL_CURRENT`).
20    current: bool,
21    /// Lock pages that become mapped in the future (`MCL_FUTURE`).
22    future: bool,
23    /// Whether memory locking failure is fatal.
24    ///
25    /// - `true`: Return `Err` if mlock fails
26    /// - `false`: Return `Ok(MlockStatus::Failed { .. })` if mlock fails
27    required: bool,
28    /// Lock pages only when they are faulted in (`MCL_ONFAULT`).
29    ///
30    /// Available on Linux 4.4+. Reduces initial memory footprint by
31    /// deferring locking until pages are actually accessed.
32    ///
33    /// Ignored on platforms that don't support it.
34    onfault: bool,
35}
36
37impl MlockConfig {
38    /// Create a new configuration builder.
39    ///
40    /// # Example
41    ///
42    /// ```rust
43    /// use duende_mlock::MlockConfig;
44    ///
45    /// let config = MlockConfig::builder()
46    ///     .required(false)
47    ///     .build();
48    /// ```
49    #[must_use]
50    pub const fn builder() -> MlockConfigBuilder {
51        MlockConfigBuilder::new()
52    }
53
54    /// Whether to lock currently mapped pages.
55    #[must_use]
56    pub const fn current(&self) -> bool {
57        self.current
58    }
59
60    /// Whether to lock future page mappings.
61    #[must_use]
62    pub const fn future(&self) -> bool {
63        self.future
64    }
65
66    /// Whether mlock failure is fatal.
67    #[must_use]
68    pub const fn required(&self) -> bool {
69        self.required
70    }
71
72    /// Whether to use on-fault locking (Linux 4.4+).
73    #[must_use]
74    pub const fn onfault(&self) -> bool {
75        self.onfault
76    }
77
78    /// Convert to libc mlockall flags.
79    #[cfg(unix)]
80    pub(crate) const fn as_flags(self) -> libc::c_int {
81        let mut flags = 0;
82
83        if self.current {
84            flags |= libc::MCL_CURRENT;
85        }
86
87        if self.future {
88            flags |= libc::MCL_FUTURE;
89        }
90
91        // MCL_ONFAULT is Linux 4.4+ only
92        #[cfg(target_os = "linux")]
93        if self.onfault {
94            // MCL_ONFAULT = 4 (not always in libc)
95            const MCL_ONFAULT: libc::c_int = 4;
96            flags |= MCL_ONFAULT;
97        }
98
99        flags
100    }
101}
102
103impl Default for MlockConfig {
104    /// Default configuration: lock current and future pages, required mode.
105    ///
106    /// Equivalent to:
107    /// ```rust
108    /// use duende_mlock::MlockConfig;
109    ///
110    /// let config = MlockConfig::builder()
111    ///     .current(true)
112    ///     .future(true)
113    ///     .required(true)
114    ///     .onfault(false)
115    ///     .build();
116    /// ```
117    fn default() -> Self {
118        Self {
119            current: true,
120            future: true,
121            required: true,
122            onfault: false,
123        }
124    }
125}
126
127/// Builder for [`MlockConfig`].
128///
129/// # Example
130///
131/// ```rust
132/// use duende_mlock::MlockConfig;
133///
134/// let config = MlockConfig::builder()
135///     .current(true)
136///     .future(true)
137///     .required(false)
138///     .build();
139/// ```
140#[derive(Debug, Clone, Copy)]
141pub struct MlockConfigBuilder {
142    config: MlockConfig,
143}
144
145impl MlockConfigBuilder {
146    /// Create a new builder with default values.
147    #[must_use]
148    pub const fn new() -> Self {
149        Self {
150            config: MlockConfig {
151                current: true,
152                future: true,
153                required: true,
154                onfault: false,
155            },
156        }
157    }
158
159    /// Lock pages currently mapped (`MCL_CURRENT`).
160    ///
161    /// Default: `true`
162    #[must_use]
163    pub const fn current(mut self, value: bool) -> Self {
164        self.config.current = value;
165        self
166    }
167
168    /// Lock pages mapped in the future (`MCL_FUTURE`).
169    ///
170    /// Default: `true`
171    #[must_use]
172    pub const fn future(mut self, value: bool) -> Self {
173        self.config.future = value;
174        self
175    }
176
177    /// Whether mlock failure should return an error.
178    ///
179    /// - `true` (default): Return `Err(MlockError)` on failure
180    /// - `false`: Return `Ok(MlockStatus::Failed { .. })` on failure
181    ///
182    /// Use `false` when mlock is optional and the daemon should continue
183    /// with degraded safety guarantees.
184    #[must_use]
185    pub const fn required(mut self, value: bool) -> Self {
186        self.config.required = value;
187        self
188    }
189
190    /// Use on-fault locking (`MCL_ONFAULT`, Linux 4.4+).
191    ///
192    /// When enabled, pages are locked only when they are first accessed
193    /// (faulted in), rather than immediately. This reduces initial memory
194    /// pressure for daemons with large potential address spaces.
195    ///
196    /// Default: `false`
197    ///
198    /// Ignored on platforms that don't support it.
199    #[must_use]
200    pub const fn onfault(mut self, value: bool) -> Self {
201        self.config.onfault = value;
202        self
203    }
204
205    /// Build the configuration.
206    #[must_use]
207    pub const fn build(self) -> MlockConfig {
208        self.config
209    }
210}
211
212impl Default for MlockConfigBuilder {
213    fn default() -> Self {
214        Self::new()
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221
222    #[test]
223    fn test_default_values() {
224        let config = MlockConfig::default();
225        assert!(config.current());
226        assert!(config.future());
227        assert!(config.required());
228        assert!(!config.onfault());
229    }
230
231    #[test]
232    fn test_builder_all_false() {
233        let config = MlockConfig::builder()
234            .current(false)
235            .future(false)
236            .required(false)
237            .onfault(false)
238            .build();
239
240        assert!(!config.current());
241        assert!(!config.future());
242        assert!(!config.required());
243        assert!(!config.onfault());
244    }
245
246    #[test]
247    fn test_builder_all_true() {
248        let config = MlockConfig::builder()
249            .current(true)
250            .future(true)
251            .required(true)
252            .onfault(true)
253            .build();
254
255        assert!(config.current());
256        assert!(config.future());
257        assert!(config.required());
258        assert!(config.onfault());
259    }
260
261    #[cfg(unix)]
262    #[test]
263    fn test_as_flags_default() {
264        let config = MlockConfig::default();
265        let flags = config.as_flags();
266        assert_eq!(flags, libc::MCL_CURRENT | libc::MCL_FUTURE);
267    }
268
269    #[cfg(unix)]
270    #[test]
271    fn test_as_flags_current_only() {
272        let config = MlockConfig::builder()
273            .current(true)
274            .future(false)
275            .build();
276        let flags = config.as_flags();
277        assert_eq!(flags, libc::MCL_CURRENT);
278    }
279
280    #[cfg(unix)]
281    #[test]
282    fn test_as_flags_none() {
283        let config = MlockConfig::builder()
284            .current(false)
285            .future(false)
286            .build();
287        let flags = config.as_flags();
288        assert_eq!(flags, 0);
289    }
290}