os_memlock/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![cfg_attr(docsrs, deny(broken_intra_doc_links))]
3#![doc = include_str!("../README.md")]
4#![warn(missing_docs)]
5
6use std::io;
7use std::os::raw::c_void;
8
9#[inline]
10fn unsupported(msg: &'static str) -> io::Result<()> {
11 Err(io::Error::new(io::ErrorKind::Unsupported, msg))
12}
13
14#[cfg(unix)]
15mod unix {
16 use super::{c_void, io};
17
18 /// Lock the pages containing the specified memory region to prevent swapping.
19 ///
20 /// Returns:
21 /// - Ok(()) on success
22 /// - Err(...) with last_os_error() on failure
23 /// - Err(Unsupported) on platforms where this call is not available
24 ///
25 /// # Safety
26 /// The caller must ensure that (addr, len) refers to a valid, non-null memory
27 /// region owned by this process for the duration of the call, and that the region
28 /// is not deallocated, unmapped, or remapped concurrently.
29 ///
30 /// # Examples
31 /// no_run
32 /// # use std::io;
33 /// # fn demo() -> io::Result<()> {
34 /// let mut buf = vec![0u8; 4096];
35 /// let ptr = buf.as_ptr() as *const std::os::raw::c_void;
36 /// let len = buf.len();
37 /// match unsafe { os_memlock::mlock(ptr, len) } {
38 /// Ok(()) => {
39 /// // do work...
40 /// unsafe { os_memlock::munlock(ptr, len)?; }
41 /// }
42 /// Err(e) if e.kind() == std::io::ErrorKind::Unsupported => {
43 /// // platform/build doesn't support mlock; proceed without locking
44 /// }
45 /// Err(e) => return Err(e),
46 /// }
47 /// # Ok(()) }
48 ///
49 pub unsafe fn mlock(addr: *const c_void, len: usize) -> io::Result<()> {
50 if len == 0 {
51 // Treat zero-length as a no-op success for ergonomic callers.
52 return Ok(());
53 }
54 // Safety:
55 // - We do not dereference addr.
56 // - Caller guarantees (addr, len) is a valid region they own during the call.
57 let rc = unsafe { libc::mlock(addr, len) };
58 if rc == 0 {
59 Ok(())
60 } else {
61 Err(io::Error::last_os_error())
62 }
63 }
64
65 /// Unlock the pages containing the specified memory region.
66 ///
67 /// Returns:
68 /// - Ok(()) on success
69 /// - Err(...) with last_os_error() on failure
70 /// - Err(Unsupported) on platforms where this call is not available
71 ///
72 /// # Safety
73 /// The caller must ensure that (addr, len) refers to a valid, non-null memory
74 /// region owned by this process for the duration of the call, and that the region
75 /// is not deallocated, unmapped, or remapped concurrently.
76 pub unsafe fn munlock(addr: *const c_void, len: usize) -> io::Result<()> {
77 if len == 0 {
78 // Treat zero-length as a no-op success for ergonomic callers.
79 return Ok(());
80 }
81 // Safety: same preconditions as mlock.
82 let rc = unsafe { libc::munlock(addr, len) };
83 if rc == 0 {
84 Ok(())
85 } else {
86 Err(io::Error::last_os_error())
87 }
88 }
89
90 /// Best-effort advisory to exclude the memory region from core dumps.
91 ///
92 /// On Linux, this wraps madvise(MADV_DONTDUMP). On FreeBSD, this wraps
93 /// madvise(MADV_NOCORE). On other Unix targets, this returns Unsupported.
94 ///
95 /// Returns:
96 /// - Ok(()) when the hint is applied
97 /// - Err(...) with last_os_error() if the call failed
98 /// - Err(Unsupported) if not supported on this platform
99 ///
100 /// # Safety
101 /// The caller must ensure that (addr, len) denotes a valid memory mapping for
102 /// this process and that the region is not deallocated or remapped concurrently.
103 #[cfg(target_os = "linux")]
104 pub unsafe fn madvise_dontdump(addr: *mut c_void, len: usize) -> io::Result<()> {
105 if len == 0 {
106 return Ok(());
107 }
108 // Safety:
109 // - We do not dereference addr.
110 // - Caller guarantees (addr, len) is a valid region they own during the call.
111 let rc = libc::madvise(addr, len, libc::MADV_DONTDUMP);
112 if rc == 0 {
113 Ok(())
114 } else {
115 Err(io::Error::last_os_error())
116 }
117 }
118
119 /// FreeBSD: use MADV_NOCORE to request exclusion from core dumps.
120 #[cfg(target_os = "freebsd")]
121 /// # Safety
122 /// The caller must ensure that (addr, len) denotes a valid memory mapping for
123 /// this process and that the region is not deallocated or remapped concurrently.
124 pub unsafe fn madvise_dontdump(addr: *mut c_void, len: usize) -> io::Result<()> {
125 if len == 0 {
126 return Ok(());
127 }
128 let rc = libc::madvise(addr, len, libc::MADV_NOCORE);
129 if rc == 0 {
130 Ok(())
131 } else {
132 Err(io::Error::last_os_error())
133 }
134 }
135
136 /// See madvise_dontdump above. On other Unix targets, this is unsupported.
137 #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
138 /// # Safety
139 /// This function is marked unsafe for signature consistency. On unsupported Unix
140 /// targets it always returns Unsupported; callers compiling cross-platform
141 /// must still treat (addr, len) as potentially unsafe inputs.
142 pub unsafe fn madvise_dontdump(_addr: *mut c_void, _len: usize) -> io::Result<()> {
143 super::unsupported("madvise-based dump exclusion unsupported on this platform")
144 }
145}
146
147#[cfg(not(unix))]
148mod non_unix {
149 use super::{c_void, io};
150
151 /// # Safety
152 /// This function is marked unsafe for signature consistency across platforms.
153 /// On non-Unix targets it always returns Unsupported; callers compiling
154 /// cross-platform must still treat (addr, len) as potentially unsafe inputs.
155 pub unsafe fn mlock(_addr: *const c_void, _len: usize) -> io::Result<()> {
156 super::unsupported("mlock unsupported on this platform")
157 }
158
159 /// # Safety
160 /// This function is marked unsafe for signature consistency across platforms.
161 /// On non-Unix targets it always returns Unsupported; callers compiling
162 /// cross-platform must still treat (addr, len) as potentially unsafe inputs.
163 pub unsafe fn munlock(_addr: *const c_void, _len: usize) -> io::Result<()> {
164 super::unsupported("munlock unsupported on this platform")
165 }
166
167 /// # Safety
168 /// This function is marked unsafe for signature consistency across platforms.
169 /// On non-Unix targets it always returns Unsupported; callers compiling
170 /// cross-platform must still treat (addr, len) as potentially unsafe inputs.
171 pub unsafe fn madvise_dontdump(_addr: *mut c_void, _len: usize) -> io::Result<()> {
172 super::unsupported("madvise(MADV_DONTDUMP) unsupported on this platform")
173 }
174}
175
176/// Disable core dumps for the current process on macOS by setting the RLIMIT_CORE soft limit to 0.
177///
178/// Platform:
179/// - macOS only. On other platforms, see the cross-platform stub which returns Unsupported.
180///
181/// Behavior:
182/// - This is a process-wide policy and is inherited by child processes.
183/// - Lowering the soft limit is typically permitted; raising it back may require extra privileges.
184/// - May fail in sandboxed or restricted environments; returns io::Error from the OS.
185///
186/// Returns:
187/// - Ok(()) on success.
188/// - Err(io::Error) with last_os_error() on failure.
189#[cfg(target_os = "macos")]
190#[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
191pub fn disable_core_dumps_for_process() -> io::Result<()> {
192 // Fetch existing limits so we can preserve the hard limit (rlim_max).
193 let mut old = libc::rlimit {
194 rlim_cur: 0,
195 rlim_max: 0,
196 };
197 let rc = unsafe { libc::getrlimit(libc::RLIMIT_CORE, &mut old as *mut _) };
198 if rc != 0 {
199 return Err(io::Error::last_os_error());
200 }
201 // Set the soft limit to 0 to disable core dump generation for the process.
202 let new_lim = libc::rlimit {
203 rlim_cur: 0,
204 rlim_max: old.rlim_max,
205 };
206 let rc2 = unsafe { libc::setrlimit(libc::RLIMIT_CORE, &new_lim as *const _) };
207 if rc2 != 0 {
208 return Err(io::Error::last_os_error());
209 }
210 Ok(())
211}
212
213/// Disable core dumps for the current process.
214///
215/// Platform:
216/// - This stub is compiled on non-macOS targets and always returns Unsupported.
217///
218/// See also:
219/// - On macOS, disable_core_dumps_for_process attempts to set RLIMIT_CORE to 0.
220#[cfg(not(target_os = "macos"))]
221#[cfg_attr(docsrs, doc(cfg(not(target_os = "macos"))))]
222pub fn disable_core_dumps_for_process() -> io::Result<()> {
223 unsupported("disable_core_dumps_for_process unsupported on this platform")
224}
225
226/// RAII guard that disables core dumps on macOS and restores the previous RLIMIT_CORE on drop.
227///
228/// On non-macOS platforms, this type is still defined to keep cross-platform signatures
229/// consistent, but creating it is not possible via this crate's API.
230#[derive(Debug)]
231pub struct CoreDumpsDisabledGuard {
232 #[cfg(target_os = "macos")]
233 old: libc::rlimit,
234}
235
236#[cfg(target_os = "macos")]
237impl Drop for CoreDumpsDisabledGuard {
238 fn drop(&mut self) {
239 // Best-effort: restore previous soft/hard core limits.
240 let rc = unsafe { libc::setrlimit(libc::RLIMIT_CORE, &self.old as *const _) };
241 if rc != 0 {
242 // Avoid panicking in Drop; emit a diagnostic.
243 eprintln!(
244 "os-memlock: failed to restore RLIMIT_CORE: {}",
245 io::Error::last_os_error()
246 );
247 }
248 }
249}
250
251/// Disable core dumps for the current process and return a guard that restores the previous limit on drop.
252///
253/// Platform:
254/// - macOS only. On other platforms, this function returns Unsupported.
255///
256/// Behavior:
257/// - Sets RLIMIT_CORE soft limit to 0; guard restores previous limit on Drop.
258#[cfg(target_os = "macos")]
259#[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
260pub fn disable_core_dumps_with_guard() -> io::Result<CoreDumpsDisabledGuard> {
261 let mut old = libc::rlimit {
262 rlim_cur: 0,
263 rlim_max: 0,
264 };
265 let rc = unsafe { libc::getrlimit(libc::RLIMIT_CORE, &mut old as *mut _) };
266 if rc != 0 {
267 return Err(io::Error::last_os_error());
268 }
269 let new_lim = libc::rlimit {
270 rlim_cur: 0,
271 rlim_max: old.rlim_max,
272 };
273 let rc2 = unsafe { libc::setrlimit(libc::RLIMIT_CORE, &new_lim as *const _) };
274 if rc2 != 0 {
275 return Err(io::Error::last_os_error());
276 }
277 Ok(CoreDumpsDisabledGuard { old })
278}
279
280#[cfg(not(target_os = "macos"))]
281#[cfg_attr(docsrs, doc(cfg(not(target_os = "macos"))))]
282pub fn disable_core_dumps_with_guard() -> io::Result<CoreDumpsDisabledGuard> {
283 Err(io::Error::new(
284 io::ErrorKind::Unsupported,
285 "disable_core_dumps_with_guard unsupported on this platform",
286 ))
287}
288
289// Re-export platform module functions at the crate root for a stable API.
290#[cfg(unix)]
291#[cfg_attr(docsrs, doc(cfg(unix)))]
292pub use unix::{madvise_dontdump, mlock, munlock};
293
294#[cfg(windows)]
295mod windows {
296 use super::{c_void, io};
297 use windows_sys::Win32::System::Memory::{VirtualLock, VirtualUnlock};
298
299 /// Lock the pages containing the specified memory region to prevent paging on Windows.
300 ///
301 /// Returns:
302 /// - `Ok(())` on success
303 /// - `Err(...)` with `last_os_error()` on failure
304 ///
305 /// # Safety
306 /// The caller must ensure that `(addr, len)` refers to a valid, non-null memory
307 /// region owned by this process for the duration of the call, and that the region
308 /// is not deallocated, unmapped, or remapped concurrently.
309 pub unsafe fn mlock(addr: *const c_void, len: usize) -> io::Result<()> {
310 if len == 0 {
311 return Ok(());
312 }
313 let ok = unsafe { VirtualLock(addr as *mut _, len) };
314 if ok != 0 {
315 Ok(())
316 } else {
317 Err(io::Error::last_os_error())
318 }
319 }
320
321 /// Unlock the pages containing the specified memory region on Windows.
322 ///
323 /// Returns:
324 /// - `Ok(())` on success
325 /// - `Err(...)` with `last_os_error()` on failure
326 ///
327 /// # Safety
328 /// The caller must ensure that `(addr, len)` refers to a valid, non-null memory
329 /// region owned by this process for the duration of the call, and that the region
330 /// is not deallocated, unmapped, or remapped concurrently.
331 pub unsafe fn munlock(addr: *const c_void, len: usize) -> io::Result<()> {
332 if len == 0 {
333 return Ok(());
334 }
335 let ok = unsafe { VirtualUnlock(addr as *mut _, len) };
336 if ok != 0 {
337 Ok(())
338 } else {
339 Err(io::Error::last_os_error())
340 }
341 }
342
343 /// Windows has no per-region equivalent of `MADV_DONTDUMP`; return Unsupported.
344 ///
345 /// # Safety
346 /// Signature kept for cross-platform parity; always returns Unsupported on Windows.
347 pub unsafe fn madvise_dontdump(_addr: *mut c_void, _len: usize) -> io::Result<()> {
348 super::unsupported("madvise_dontdump unsupported on Windows")
349 }
350
351 // ------------------------------------------------------------------------
352 // Windows process-level error mode helpers
353 // ------------------------------------------------------------------------
354
355 /// Common `SetErrorMode` flags for suppressing error dialogs on Windows.
356 ///
357 /// These constants mirror the Win32 SEM_* flags and can be combined.
358 /// Typical suppression includes:
359 /// - `SEM_FAILCRITICALERRORS` (0x0001)
360 /// - `SEM_NOGPFAULTERRORBOX` (0x0002)
361 /// - `SEM_NOOPENFILEERRORBOX` (0x8000)
362 ///
363 /// Note: These affect process-wide behavior and do not provide per-region
364 /// dump exclusion. They are best-effort controls over Windows error UI.
365 pub const SEM_FAILCRITICALERRORS: u32 = 0x0001;
366 /// See module-level docs for details.
367 pub const SEM_NOGPFAULTERRORBOX: u32 = 0x0002;
368 /// See module-level docs for details.
369 pub const SEM_NOOPENFILEERRORBOX: u32 = 0x8000;
370
371 unsafe extern "system" {
372 fn SetErrorMode(uMode: u32) -> u32;
373 }
374
375 /// Set the Windows process error mode and return the previous mode.
376 ///
377 /// Platform:
378 /// - Windows only. On non-Windows targets, use the cross-platform stub which returns `Unsupported`.
379 ///
380 /// Effect:
381 /// - This is process-wide and affects error dialog behavior (e.g., disabling GP fault dialog boxes).
382 /// - Use with caution: settings are inherited by child processes and impact global behavior.
383 ///
384 /// Returns:
385 /// - `Ok(previous_mode)` on success.
386 pub fn set_windows_error_mode(new_mode: u32) -> io::Result<u32> {
387 // SetErrorMode returns the previous mode; there is no explicit failure indicator.
388 let previous = unsafe { SetErrorMode(new_mode) };
389 Ok(previous)
390 }
391
392 /// Best-effort helper to suppress common Windows error dialogs for the current process.
393 ///
394 /// Sets a combination of `SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX`
395 /// via `SetErrorMode` and returns the previous mode so callers can restore it later.
396 ///
397 /// Returns:
398 /// - `Ok(previous_mode)` on success.
399 ///
400 /// Notes:
401 /// - Process-wide effect; inherited by child processes at `CreateProcess`.
402 /// - This does not influence what data is captured in crash dumps and is not a substitute
403 /// for per-region dump exclusion (which Windows does not provide).
404 pub fn suppress_windows_error_dialogs_for_process() -> io::Result<u32> {
405 let desired = SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX;
406 let previous = unsafe { SetErrorMode(desired) };
407 Ok(previous)
408 }
409}
410
411#[cfg(windows)]
412#[cfg_attr(docsrs, doc(cfg(windows)))]
413pub use windows::{
414 SEM_FAILCRITICALERRORS, SEM_NOGPFAULTERRORBOX, SEM_NOOPENFILEERRORBOX, madvise_dontdump, mlock,
415 munlock, set_windows_error_mode, suppress_windows_error_dialogs_for_process,
416};
417
418#[cfg(not(windows))]
419/// Set the Windows process error mode (stub).
420///
421/// This stub is compiled on non-Windows targets and always returns `Unsupported`.
422pub fn set_windows_error_mode(_new_mode: u32) -> io::Result<u32> {
423 Err(io::Error::new(
424 io::ErrorKind::Unsupported,
425 "set_windows_error_mode unsupported on this platform",
426 ))
427}
428
429#[cfg(not(windows))]
430/// Suppress common Windows error dialogs for the current process (stub).
431///
432/// This stub is compiled on non-Windows targets and always returns `Unsupported`.
433pub fn suppress_windows_error_dialogs_for_process() -> io::Result<u32> {
434 Err(io::Error::new(
435 io::ErrorKind::Unsupported,
436 "suppress_windows_error_dialogs_for_process unsupported on this platform",
437 ))
438}
439
440#[cfg(all(not(unix), not(windows)))]
441#[cfg_attr(docsrs, doc(cfg(all(not(unix), not(windows)))))]
442pub use non_unix::{madvise_dontdump, mlock, munlock};
443
444#[cfg(test)]
445mod tests {
446 #[test]
447 fn smoke_disable_core_dumps_for_process() {
448 let _ = crate::disable_core_dumps_for_process();
449 let _ = crate::disable_core_dumps_with_guard();
450 }
451}