use crate::config::MlockConfig;
use crate::error::MlockError;
use crate::status::MlockStatus;
pub fn lock_with_config(config: MlockConfig) -> Result<MlockStatus, MlockError> {
let flags = config.as_flags();
if flags == 0 {
return Ok(MlockStatus::Locked { bytes_locked: 0 });
}
let result = unsafe { libc::mlockall(flags) };
if result == 0 {
let bytes_locked = locked_bytes();
Ok(MlockStatus::Locked { bytes_locked })
} else {
let errno = std::io::Error::last_os_error()
.raw_os_error()
.unwrap_or(-1);
if config.required() {
Err(MlockError::from_errno(errno))
} else {
Ok(MlockStatus::Failed { errno })
}
}
}
pub fn unlock_all() -> Result<(), MlockError> {
let result = unsafe { libc::munlockall() };
if result == 0 {
Ok(())
} else {
let errno = std::io::Error::last_os_error()
.raw_os_error()
.unwrap_or(-1);
Err(MlockError::from_errno(errno))
}
}
#[cfg(target_os = "linux")]
pub fn is_locked() -> bool {
locked_bytes() > 0
}
#[cfg(not(target_os = "linux"))]
pub fn is_locked() -> bool {
false
}
#[cfg(target_os = "linux")]
pub fn locked_bytes() -> usize {
let Ok(status) = std::fs::read_to_string("/proc/self/status") else {
return 0;
};
for line in status.lines() {
if let Some(rest) = line.strip_prefix("VmLck:") {
let trimmed = rest.trim();
if let Some(kb_str) = trimmed.strip_suffix(" kB") {
if let Ok(kb) = kb_str.trim().parse::<usize>() {
return kb * 1024;
}
}
}
}
0
}
#[cfg(not(target_os = "linux"))]
pub fn locked_bytes() -> usize {
0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_locked_bytes_parses_correctly() {
let bytes = locked_bytes();
let _ = bytes;
}
#[test]
fn test_is_locked_returns_bool() {
let locked = is_locked();
let _ = locked;
}
#[test]
fn test_lock_with_empty_flags() {
let config = MlockConfig::builder()
.current(false)
.future(false)
.build();
let result = lock_with_config(config);
assert!(result.is_ok());
if let Ok(status) = result {
assert!(status.is_locked());
}
}
#[test]
fn test_lock_non_required_mode() {
let config = MlockConfig::builder().required(false).build();
let result = lock_with_config(config);
assert!(result.is_ok());
}
#[test]
fn test_unlock_all_does_not_error() {
let result = unlock_all();
assert!(result.is_ok());
}
#[test]
fn test_lock_current_only() {
let config = MlockConfig::builder()
.current(true)
.future(false)
.required(false)
.build();
let result = lock_with_config(config);
assert!(result.is_ok());
let _ = unlock_all();
}
#[test]
fn test_lock_future_only() {
let config = MlockConfig::builder()
.current(false)
.future(true)
.required(false)
.build();
let result = lock_with_config(config);
assert!(result.is_ok());
let _ = unlock_all();
}
#[test]
fn test_locked_bytes_is_zero_or_positive() {
let bytes = locked_bytes();
assert!(bytes < usize::MAX);
}
#[test]
fn test_mlock_with_onfault() {
let config = MlockConfig::builder()
.current(true)
.future(true)
.onfault(true)
.required(false)
.build();
let result = lock_with_config(config);
assert!(result.is_ok());
let _ = unlock_all();
}
}