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}