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}