use crate::CoreError;
use std::os::unix::io::AsRawFd;
use std::path::Path;
use std::time::UNIX_EPOCH;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PathFingerprint {
pub len: u64,
pub modified_ns: u128,
}
pub fn path_fingerprint(path: &Path) -> Result<PathFingerprint, CoreError> {
let metadata = std::fs::metadata(path).map_err(|err| {
CoreError::sys(err.raw_os_error().unwrap_or(libc::EIO), "path_fingerprint")
})?;
let modified_ns = metadata
.modified()
.ok()
.and_then(|modified| modified.duration_since(UNIX_EPOCH).ok())
.map(|duration| duration.as_nanos())
.unwrap_or_default();
Ok(PathFingerprint {
len: metadata.len(),
modified_ns,
})
}
pub fn path_exists(path: &str) -> bool {
match std::ffi::CString::new(path) {
Ok(c) => unsafe { libc::access(c.as_ptr(), libc::F_OK) == 0 },
Err(_) => false,
}
}
pub fn path_lstat_exists(path: &str) -> bool {
match std::ffi::CString::new(path) {
Ok(c) => unsafe {
let mut stat = std::mem::zeroed();
libc::lstat(c.as_ptr(), &mut stat) == 0
},
Err(_) => false,
}
}
pub fn read_to_string(path: &str) -> Result<String, CoreError> {
std::fs::read_to_string(path)
.map_err(|err| CoreError::sys(err.raw_os_error().unwrap_or(libc::EIO), "read_to_string"))
}
pub fn readahead(fd: impl AsRawFd, offset: u64, len: usize) -> Result<(), CoreError> {
readahead_raw(fd.as_raw_fd(), offset, len)
}
pub fn mmap_madvise(
fd: impl AsRawFd,
offset: u64,
len: usize,
touch: bool,
) -> Result<(), CoreError> {
mmap_madvise_raw(fd.as_raw_fd(), offset, len, touch)
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn mmap_madvise_raw(
fd: libc::c_int,
offset: u64,
len: usize,
touch: bool,
) -> Result<(), CoreError> {
if len == 0 {
return Ok(());
}
let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
if page_size <= 0 {
return Err(CoreError::sys(libc::EINVAL, "sysconf(_SC_PAGESIZE)"));
}
let page_size = page_size as u64;
if offset % page_size != 0 || offset > libc::off_t::MAX as u64 {
return Err(CoreError::sys(libc::EINVAL, "mmap"));
}
let ptr = unsafe {
libc::mmap(
std::ptr::null_mut(),
len,
libc::PROT_READ,
libc::MAP_PRIVATE,
fd,
offset as libc::off_t,
)
};
if ptr == libc::MAP_FAILED {
let code = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
return Err(CoreError::sys(code, "mmap"));
}
let result = if unsafe { libc::madvise(ptr, len, libc::MADV_WILLNEED) } == -1 {
let code = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
Err(CoreError::sys(code, "madvise"))
} else {
if touch {
let mut pos = 0usize;
let page_size = page_size as usize;
while pos < len {
unsafe {
std::ptr::read_volatile((ptr as *const u8).add(pos));
}
pos = pos.saturating_add(page_size);
}
}
Ok(())
};
if unsafe { libc::munmap(ptr, len) } == -1 {
let code = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
return Err(CoreError::sys(code, "munmap"));
}
result
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
fn mmap_madvise_raw(
_fd: libc::c_int,
_offset: u64,
_len: usize,
_touch: bool,
) -> Result<(), CoreError> {
Err(CoreError::sys(libc::ENOSYS, "mmap"))
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn readahead_raw(fd: libc::c_int, offset: u64, len: usize) -> Result<(), CoreError> {
if offset > libc::off64_t::MAX as u64 {
return Err(CoreError::sys(libc::EINVAL, "readahead"));
}
let count = len as libc::size_t;
let offset = offset as libc::off64_t;
loop {
let ret = unsafe { libc::syscall(readahead_syscall_number(), fd, offset, count) };
if ret == -1 {
let code = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
if code == libc::EINTR {
continue;
}
return Err(CoreError::sys(code, "readahead"));
}
return Ok(());
}
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
fn readahead_raw(_fd: libc::c_int, _offset: u64, _len: usize) -> Result<(), CoreError> {
Err(CoreError::sys(libc::ENOSYS, "readahead"))
}
#[cfg(target_os = "linux")]
#[inline(always)]
const fn readahead_syscall_number() -> libc::c_long {
libc::SYS_readahead
}
#[cfg(all(target_os = "android", target_arch = "aarch64"))]
#[inline(always)]
const fn readahead_syscall_number() -> libc::c_long {
213
}
#[cfg(all(target_os = "android", target_arch = "arm"))]
#[inline(always)]
const fn readahead_syscall_number() -> libc::c_long {
225
}
#[cfg(all(target_os = "android", target_arch = "x86_64"))]
#[inline(always)]
const fn readahead_syscall_number() -> libc::c_long {
187
}
#[cfg(all(target_os = "android", target_arch = "x86"))]
#[inline(always)]
const fn readahead_syscall_number() -> libc::c_long {
225
}
#[cfg(test)]
mod tests {
#[cfg(target_os = "linux")]
#[test]
fn test_readahead_syscall_number_linux_matches_libc() {
assert_eq!(super::readahead_syscall_number(), libc::SYS_readahead);
}
#[cfg(all(target_os = "android", target_arch = "aarch64"))]
#[test]
fn test_readahead_syscall_number_android_aarch64() {
assert_eq!(super::readahead_syscall_number(), 213);
}
#[cfg(all(target_os = "android", target_arch = "arm"))]
#[test]
fn test_readahead_syscall_number_android_arm() {
assert_eq!(super::readahead_syscall_number(), 225);
}
#[cfg(all(target_os = "android", target_arch = "x86_64"))]
#[test]
fn test_readahead_syscall_number_android_x86_64() {
assert_eq!(super::readahead_syscall_number(), 187);
}
#[cfg(all(target_os = "android", target_arch = "x86"))]
#[test]
fn test_readahead_syscall_number_android_x86() {
assert_eq!(super::readahead_syscall_number(), 225);
}
}