use std::{
ffi::CStr,
fmt::Debug,
fs::{File, OpenOptions},
mem,
os::unix::fs::OpenOptionsExt,
path::Path,
sync::atomic::AtomicU32,
};
use crate::errors::*;
use log::{debug, error, info, warn};
use rustix::{fs, mm};
use crate::property_info::PropertyInfo;
const PA_SIZE: u64 = 128 * 1024;
const PROP_AREA_MAGIC: u32 = 0x504f5250;
const PROP_AREA_VERSION: u32 = 0xfc6ed0ab;
pub(crate) unsafe trait MmapObject {}
unsafe impl MmapObject for PropertyArea {}
unsafe impl MmapObject for PropertyTrieNode {}
unsafe impl MmapObject for PropertyInfo {}
const _: () = {
assert!(mem::size_of::<PropertyTrieNode>() == 20);
assert!(mem::offset_of!(PropertyTrieNode, namelen) == 0);
assert!(mem::offset_of!(PropertyTrieNode, prop) == 4);
assert!(mem::offset_of!(PropertyTrieNode, left) == 8);
assert!(mem::offset_of!(PropertyTrieNode, right) == 12);
assert!(mem::offset_of!(PropertyTrieNode, children) == 16);
assert!(mem::size_of::<PropertyArea>() == 128);
assert!(mem::offset_of!(PropertyArea, bytes_used) == 0);
assert!(mem::offset_of!(PropertyArea, serial) == 4);
assert!(mem::offset_of!(PropertyArea, magic) == 8);
assert!(mem::offset_of!(PropertyArea, version) == 12);
assert!(mem::offset_of!(PropertyArea, reserved) == 16);
};
#[repr(C, align(4))]
pub(crate) struct PropertyTrieNode {
pub(crate) namelen: u32,
pub(crate) prop: AtomicU32,
pub(crate) left: AtomicU32,
pub(crate) right: AtomicU32,
pub(crate) children: AtomicU32,
}
impl PropertyTrieNode {
#[cfg(feature = "builder")]
fn init_header(&mut self, namelen: u32) {
self.prop.store(0, std::sync::atomic::Ordering::Relaxed);
self.left.store(0, std::sync::atomic::Ordering::Relaxed);
self.right.store(0, std::sync::atomic::Ordering::Relaxed);
self.children.store(0, std::sync::atomic::Ordering::Relaxed);
self.namelen = namelen;
}
}
impl Debug for PropertyTrieNode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PropertyTrieNode")
.field("namelen", &self.namelen)
.field("prop", &self.prop)
.field("left", &self.left)
.field("right", &self.right)
.field("children", &self.children)
.finish()
}
}
fn cmp_prop_name(a: &[u8], b: &[u8]) -> std::cmp::Ordering {
a.len().cmp(&b.len()).then_with(|| a.cmp(b))
}
#[derive(Debug)]
#[repr(C, align(4))]
pub(crate) struct PropertyArea {
bytes_used: u32,
serial: AtomicU32,
magic: u32,
version: u32,
reserved: [u32; 28],
}
impl PropertyArea {
fn init(&mut self, magic: u32, version: u32) {
self.serial.store(0, std::sync::atomic::Ordering::Relaxed);
self.magic = magic;
self.version = version;
self.reserved = [0; 28];
self.bytes_used = mem::size_of::<PropertyTrieNode>() as _;
self.bytes_used += crate::bionic_align(crate::PROP_VALUE_MAX, mem::size_of::<u32>()) as u32;
}
pub(crate) fn serial(&self) -> &AtomicU32 {
&self.serial
}
}
#[derive(Debug)]
pub(crate) struct PropertyAreaMap {
mmap: MemoryMap,
data_offset: usize,
pa_data_size: usize,
}
impl PropertyAreaMap {
pub(crate) fn new_rw(filename: &Path, context: Option<&CStr>) -> Result<Self> {
debug!("Creating new read-write property area map: {filename:?}");
match std::fs::remove_file(filename) {
Ok(()) => debug!("Removed stale property area file: {filename:?}"),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
Err(e) => warn!("Failed to remove stale property area file {filename:?}: {e}"),
}
let file = OpenOptions::new()
.read(true) .write(true) .create_new(true) .custom_flags(fs::OFlags::NOFOLLOW.bits() as _)
.mode(0o444) .open(filename)
.context_with_location(format!("Failed to create property area {filename:?}"))?;
if let Some(context) = context {
if fs::fsetxattr(
&file,
"security.selinux",
context.to_bytes_with_nul(),
fs::XattrFlags::empty(),
)
.is_err()
{
warn!("Failed to set SELinux context for {filename:?}");
}
}
fs::ftruncate(&file, PA_SIZE)
.map_err(Error::from)
.context_with_location(format!("Failed to size property area {filename:?}"))?;
let pa_size = PA_SIZE as usize;
let pa_data_size = pa_size - std::mem::size_of::<PropertyArea>();
let mut thiz = Self {
mmap: MemoryMap::new(file, pa_size, true)?,
data_offset: std::mem::size_of::<PropertyArea>(),
pa_data_size,
};
thiz.property_area_mut()?
.init(PROP_AREA_MAGIC, PROP_AREA_VERSION);
info!("Successfully created read-write property area map: {filename:?}");
Ok(thiz)
}
pub(crate) fn new_ro(filename: &Path) -> Result<Self> {
debug!("Opening read-only property area map: {filename:?}");
let file = OpenOptions::new()
.read(true) .custom_flags(fs::OFlags::NOFOLLOW.bits() as _) .open(filename)
.context_with_location(format!("Failed to open {filename:?}"))?;
let metadata = file
.metadata()
.context_with_location("Failed to get metadata")?;
crate::file_validation::validate_file_metadata(
&metadata,
filename,
mem::size_of::<PropertyArea>() as u64,
)?;
let pa_size = usize::try_from(metadata.len()).map_err(|_| {
Error::FileValidation(format!(
"File too large to map on this platform: {} bytes in {filename:?}",
metadata.len()
))
})?;
let pa_data_size = pa_size - std::mem::size_of::<PropertyArea>();
let thiz = Self {
mmap: MemoryMap::new(file, pa_size, false)?,
data_offset: std::mem::size_of::<PropertyArea>(),
pa_data_size,
};
let pa = thiz.property_area();
if pa.magic != PROP_AREA_MAGIC || pa.version != PROP_AREA_VERSION {
error!(
"Invalid magic ({:#x} != {:#x}) or version ({:#x} != {:#x}) for {:?}",
pa.magic, PROP_AREA_MAGIC, pa.version, PROP_AREA_VERSION, filename
);
Err(Error::FileValidation(
"Invalid magic or version".to_string(),
))
} else {
info!("Successfully opened read-only property area map: {filename:?}");
Ok(thiz)
}
}
pub(crate) fn property_area(&self) -> &PropertyArea {
self.mmap
.to_object::<PropertyArea>(0, 0)
.expect("PropertyArea's offset is zero. So, it must be valid.")
}
pub(crate) fn is_writable(&self) -> bool {
self.mmap.writable
}
fn property_area_mut(&mut self) -> Result<&mut PropertyArea> {
self.mmap.to_object_mut::<PropertyArea>(0, 0)
}
pub(crate) fn find(&self, name: &str) -> Result<(&PropertyInfo, u32)> {
let mut remaining_name = name;
let mut current_offset = 0usize;
loop {
let sep = remaining_name.find('.');
let substr_size = match sep {
Some(pos) => pos,
None => remaining_name.len(),
};
if substr_size == 0 {
error!("Invalid property name (empty segment): '{name}'");
return Err(Error::Parse(format!("Invalid property name: {name}")));
}
let subname = &remaining_name[0..substr_size];
let children_offset = self
.mmap
.to_object::<PropertyTrieNode>(current_offset, self.data_offset)?
.children
.load(std::sync::atomic::Ordering::Acquire);
if children_offset == 0 {
return Err(Error::NotFound(name.to_owned()));
}
current_offset = self.find_prop_trie_node(children_offset, subname)? as usize;
if sep.is_none() {
break;
}
remaining_name = &remaining_name[substr_size + 1..];
}
let prop_offset = self
.mmap
.to_object::<PropertyTrieNode>(current_offset, self.data_offset)?
.prop
.load(std::sync::atomic::Ordering::Acquire);
if prop_offset != 0 {
Ok((
self.mmap
.to_object(prop_offset as usize, self.data_offset)?,
prop_offset,
))
} else {
Err(Error::NotFound(name.to_owned()))
}
}
#[cfg(feature = "builder")]
pub(crate) fn add(&mut self, name: &str, value: &str) -> Result<()> {
debug!("Adding property: '{name}' = '{value}'");
crate::wire::validate_no_interior_nul("property name", name)?;
if name.is_empty() || name.split('.').any(str::is_empty) {
error!("Invalid property name (empty segment): '{name}'");
return Err(Error::Parse(format!("Invalid property name: {name}")));
}
let mut remaining_name = name;
let mut current = 0;
loop {
let sep = remaining_name.find('.');
let substr_size = match sep {
Some(pos) => pos,
None => remaining_name.len(),
};
let subname = &remaining_name[0..substr_size];
let children_offset = self
.mmap
.to_object::<PropertyTrieNode>(current, self.data_offset)?
.children
.load(std::sync::atomic::Ordering::Acquire);
let root_offset = if children_offset != 0 {
children_offset
} else {
let offset = self.new_prop_trie_node(subname)?;
self.mmap
.to_object::<PropertyTrieNode>(current, self.data_offset)?
.children
.store(offset, std::sync::atomic::Ordering::Release);
offset
};
current = self.add_prop_trie_node(root_offset, subname)? as _;
if sep.is_none() {
break;
}
remaining_name = &remaining_name[substr_size + 1..];
}
let prop_offset = self
.mmap
.to_object::<PropertyTrieNode>(current, self.data_offset)?
.prop
.load(std::sync::atomic::Ordering::Acquire);
if prop_offset == 0 {
let offset = self.new_prop_info(name, value)?;
self.mmap
.to_object::<PropertyTrieNode>(current, self.data_offset)?
.prop
.store(offset, std::sync::atomic::Ordering::Release);
}
Ok(())
}
pub(crate) fn read_dirty_backup(&self, dst: &mut [u8]) -> Result<()> {
let offset = mem::size_of::<PropertyTrieNode>();
let reserved = crate::bionic_align(crate::PROP_VALUE_MAX, mem::size_of::<u32>());
if dst.len() >= reserved {
return Err(Error::InvalidArgument(format!(
"Backup read too long: {} (max: {})",
dst.len(),
reserved - 1
)));
}
let src = self.mmap.atomic_data(offset, self.data_offset, dst.len())?;
for (d, s) in dst.iter_mut().zip(src) {
*d = s.load(std::sync::atomic::Ordering::Relaxed);
}
Ok(())
}
#[cfg(feature = "builder")]
pub(crate) fn backup_and_apply_write(
&mut self,
pi_offset: u32,
backup: &[u8],
value: &str,
) -> Result<()> {
self.set_dirty_backup_area(backup)?;
self.property_info_mut(pi_offset)?
.writer()
.apply_write(value)?;
Ok(())
}
#[cfg(feature = "builder")]
fn set_dirty_backup_area(&mut self, value: &[u8]) -> Result<()> {
self.mmap.require_writable()?;
let offset = mem::size_of::<PropertyTrieNode>();
let total_len = value.len().checked_add(1).ok_or_else(|| {
Error::InvalidArgument(format!("Backup value too long: {}", value.len()))
})?;
let reserved = crate::bionic_align(crate::PROP_VALUE_MAX, mem::size_of::<u32>());
if total_len > reserved {
error!("Backup value overflows the reserved slot: {total_len} > {reserved}");
return Err(Error::InvalidArgument(format!(
"Backup value too long: {} (max: {})",
value.len(),
reserved - 1
)));
}
std::sync::atomic::fence(std::sync::atomic::Ordering::Release);
let dst = self.mmap.atomic_data(offset, self.data_offset, total_len)?;
for (slot, &b) in dst.iter().zip(value) {
slot.store(b, std::sync::atomic::Ordering::Relaxed);
}
dst[value.len()].store(0, std::sync::atomic::Ordering::Relaxed);
Ok(())
}
#[cfg(feature = "builder")]
fn add_prop_trie_node(&mut self, trie_offset: u32, name: &str) -> Result<u32> {
let name_bytes = name.as_bytes();
let mut current_offset = trie_offset;
let max_steps = self.pa_data_size / mem::size_of::<PropertyTrieNode>();
for _ in 0..=max_steps {
let current_node = self
.mmap
.to_object::<PropertyTrieNode>(current_offset as usize, self.data_offset)?;
let node_name =
self.trie_node_name(current_offset as usize, current_node.namelen as usize)?;
let ordering = cmp_prop_name(name_bytes, node_name.to_bytes());
let child_offset = match ordering {
std::cmp::Ordering::Less => {
current_node.left.load(std::sync::atomic::Ordering::Acquire)
}
std::cmp::Ordering::Greater => current_node
.right
.load(std::sync::atomic::Ordering::Acquire),
std::cmp::Ordering::Equal => return Ok(current_offset),
};
if child_offset != 0 {
current_offset = child_offset;
continue;
}
let offset = self.new_prop_trie_node(name)?;
let current_node = self
.mmap
.to_object::<PropertyTrieNode>(current_offset as usize, self.data_offset)?;
let link = match ordering {
std::cmp::Ordering::Less => ¤t_node.left,
std::cmp::Ordering::Greater => ¤t_node.right,
std::cmp::Ordering::Equal => unreachable!(),
};
link.store(offset, std::sync::atomic::Ordering::Release);
return Ok(offset);
}
Err(Error::FileValidation(
"Trie node cycle detected while adding (corrupt property area)".into(),
))
}
fn trie_node_name(&self, node_offset: usize, namelen: usize) -> Result<&CStr> {
let name_offset = node_offset
.checked_add(mem::size_of::<PropertyTrieNode>())
.ok_or_else(|| {
Error::FileValidation(format!("Trie node name offset overflow: {node_offset}"))
})?;
let len_with_nul = namelen.checked_add(1).ok_or_else(|| {
Error::FileValidation(format!("Trie node namelen overflow: {namelen}"))
})?;
let bytes = self
.mmap
.data(name_offset, self.data_offset, len_with_nul)?;
CStr::from_bytes_until_nul(bytes).map_err(|e| {
Error::FileValidation(format!(
"Trie node name at offset {name_offset} missing NUL terminator: {e}"
))
})
}
fn find_prop_trie_node(&self, trie_offset: u32, name: &str) -> Result<u32> {
let name_bytes = name.as_bytes();
let mut current_offset = trie_offset;
let max_steps = self.pa_data_size / mem::size_of::<PropertyTrieNode>();
for _ in 0..=max_steps {
let current = self
.mmap
.to_object::<PropertyTrieNode>(current_offset as usize, self.data_offset)?;
let node_name =
self.trie_node_name(current_offset as usize, current.namelen as usize)?;
let next_offset = match cmp_prop_name(name_bytes, node_name.to_bytes()) {
std::cmp::Ordering::Less => current.left.load(std::sync::atomic::Ordering::Acquire),
std::cmp::Ordering::Greater => {
current.right.load(std::sync::atomic::Ordering::Acquire)
}
std::cmp::Ordering::Equal => return Ok(current_offset),
};
if next_offset == 0 {
return Err(Error::NotFound(name.to_owned()));
}
current_offset = next_offset;
}
Err(Error::FileValidation(
"Trie node cycle detected (corrupt property area)".into(),
))
}
#[cfg(feature = "builder")]
fn allocate_obj(&mut self, size: usize) -> Result<u32> {
let aligned = crate::bionic_align(size, mem::size_of::<u32>());
let offset = self.property_area().bytes_used;
let aligned_u32 = u32::try_from(aligned).map_err(|_| {
Error::FileSize(format!("Aligned size too large to fit in u32: {}", aligned))
})?;
let new_offset = offset.checked_add(aligned_u32).ok_or_else(|| {
Error::FileSize(format!(
"Offset overflow: {} + {} would exceed u32::MAX",
offset, aligned_u32
))
})?;
if u64::from(new_offset) > self.pa_data_size as u64 {
error!(
"Property area full: new_offset={} > pa_data_size={}",
new_offset, self.pa_data_size
);
return Err(Error::AreaFull(format!(
"property area full: {} + {} = {} > {}",
offset, aligned_u32, new_offset, self.pa_data_size
)));
}
self.property_area_mut()?.bytes_used = new_offset;
Ok(offset)
}
#[cfg(feature = "builder")]
fn write_trailing_name(
&mut self,
obj_offset: usize,
header_size: usize,
name: &str,
) -> Result<()> {
let name_offset = obj_offset.checked_add(header_size).ok_or_else(|| {
Error::FileValidation(format!("Trailing name offset overflow: {obj_offset}"))
})?;
let dst = self
.mmap
.data_mut(name_offset, self.data_offset, name.len() + 1)?;
dst[..name.len()].copy_from_slice(name.as_bytes());
dst[name.len()] = 0;
Ok(())
}
#[cfg(feature = "builder")]
fn new_prop_trie_node(&mut self, name: &str) -> Result<u32> {
let new_offset = self.allocate_obj(mem::size_of::<PropertyTrieNode>() + name.len() + 1)?;
let node = self
.mmap
.to_object_mut::<PropertyTrieNode>(new_offset as usize, self.data_offset)?;
node.init_header(name.len() as u32);
self.write_trailing_name(
new_offset as usize,
mem::size_of::<PropertyTrieNode>(),
name,
)?;
Ok(new_offset)
}
#[cfg(feature = "builder")]
fn new_prop_info(&mut self, name: &str, value: &str) -> Result<u32> {
let new_offset = self.allocate_obj(mem::size_of::<PropertyInfo>() + name.len() + 1)?;
if value.len() >= crate::PROP_VALUE_MAX {
let long_offset = self.allocate_obj(value.len() + 1)?;
let target =
self.mmap
.data_mut(long_offset as usize, self.data_offset, value.len() + 1)?;
target[0..value.len()].copy_from_slice(value.as_bytes());
target[value.len()] = 0;
let relative_offset = long_offset.checked_sub(new_offset).ok_or_else(|| {
Error::FileValidation(format!(
"Long allocation not after its entry: {long_offset} < {new_offset}"
))
})?;
let info = self
.mmap
.to_object_mut::<PropertyInfo>(new_offset as usize, self.data_offset)?;
info.init_with_long_offset(relative_offset as _);
} else {
let info = self
.mmap
.to_object_mut::<PropertyInfo>(new_offset as usize, self.data_offset)?;
info.init_with_value(value);
};
self.write_trailing_name(new_offset as usize, mem::size_of::<PropertyInfo>(), name)?;
Ok(new_offset)
}
pub(crate) fn property_info(&self, offset: u32) -> Result<&PropertyInfo> {
self.mmap.to_object(offset as usize, self.data_offset)
}
#[cfg(feature = "builder")]
fn property_info_mut(&mut self, offset: u32) -> Result<&mut PropertyInfo> {
self.mmap.to_object_mut(offset as usize, self.data_offset)
}
pub(crate) fn property_info_name(&self, offset: u32) -> Result<&CStr> {
let _ = self.property_info(offset)?;
let name_offset = (offset as usize)
.checked_add(mem::size_of::<PropertyInfo>())
.ok_or_else(|| {
Error::FileValidation(format!("PropertyInfo name offset overflow: {offset}"))
})?;
self.mmap.cstr_at(name_offset, self.data_offset)
}
#[cfg(feature = "builder")]
pub(crate) fn property_value_bytes<'a>(
&'a self,
pi_offset: u32,
buf: &'a mut [u8; crate::PROP_VALUE_MAX],
) -> Result<&'a [u8]> {
let pi = self.property_info(pi_offset)?;
if pi.is_long() {
self.long_property_value(pi_offset)
} else {
Ok(pi.short_value_bytes(buf))
}
}
pub(crate) fn long_property_value(&self, pi_offset: u32) -> Result<&[u8]> {
let pi = self.property_info(pi_offset)?;
let rel = pi.long_offset()? as usize;
let name_len = self.property_info_name(pi_offset)?.to_bytes().len();
let min_rel = crate::bionic_align(
mem::size_of::<PropertyInfo>() + name_len + 1,
mem::size_of::<u32>(),
);
if rel < min_rel {
return Err(Error::FileValidation(format!(
"Long property offset {rel} points inside the entry (min {min_rel})"
)));
}
let value_offset = (pi_offset as usize).checked_add(rel).ok_or_else(|| {
Error::FileValidation(format!("Long value offset overflow: {pi_offset} + {rel}"))
})?;
Ok(self
.mmap
.cstr_at(value_offset, self.data_offset)?
.to_bytes())
}
}
pub(crate) struct MemoryMap {
data: *mut u8,
size: usize,
writable: bool,
}
impl Debug for MemoryMap {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MemoryMap")
.field("size", &self.size)
.field("writable", &self.writable)
.finish_non_exhaustive()
}
}
unsafe impl Send for MemoryMap {}
unsafe impl Sync for MemoryMap {}
impl MemoryMap {
pub(crate) fn new(file: File, size: usize, writable: bool) -> Result<Self> {
debug!("Creating memory map: size={size}, writable={writable}");
if size == 0 {
return Err(Error::FileValidation(
"Cannot mmap zero-sized region".into(),
));
}
let flags = if writable {
mm::ProtFlags::READ.union(mm::ProtFlags::WRITE)
} else {
mm::ProtFlags::READ
};
let memory_area = unsafe {
mm::mmap(
std::ptr::null_mut(),
size,
flags,
mm::MapFlags::SHARED,
file,
0,
)
}
.map_err(Error::from)? as *mut u8;
Ok(Self {
data: memory_area,
size,
writable,
})
}
fn require_writable(&self) -> Result<()> {
if !self.writable {
return Err(Error::PermissionDenied(
"attempted mutable access to a read-only property mapping".into(),
));
}
Ok(())
}
pub(crate) fn size(&self) -> usize {
self.size
}
pub(crate) fn data(&self, offset: usize, base: usize, size: usize) -> Result<&[u8]> {
let offset = self.checked_offset(offset, base)?;
self.check_size(offset, size)?;
Ok(unsafe { std::slice::from_raw_parts(self.data.add(offset) as *const u8, size) })
}
#[cfg(feature = "builder")]
pub(crate) fn data_mut(
&mut self,
offset: usize,
base: usize,
size: usize,
) -> Result<&mut [u8]> {
self.require_writable()?;
let offset = self.checked_offset(offset, base)?;
self.check_size(offset, size)?;
Ok(unsafe { std::slice::from_raw_parts_mut(self.data.add(offset), size) })
}
fn checked_offset(&self, offset: usize, base: usize) -> Result<usize> {
offset
.checked_add(base)
.ok_or_else(|| Error::FileValidation(format!("Offset overflow: {offset} + {base}")))
}
fn check_size(&self, offset: usize, size: usize) -> Result<()> {
let end = offset
.checked_add(size)
.ok_or_else(|| Error::FileValidation(format!("Size overflow: {offset} + {size}")))?;
if end > self.size {
error!(
"Memory access out of bounds: {} + {} > {}",
offset, size, self.size
);
return Err(Error::FileValidation(format!(
"Invalid offset: {end} > {}",
self.size
)));
}
Ok(())
}
fn check_alignment<T>(&self, offset: usize) -> Result<()> {
let align = mem::align_of::<T>();
let ptr_addr = (self.data as usize).wrapping_add(offset);
if ptr_addr % align != 0 {
return Err(Error::FileValidation(format!(
"Misaligned object at offset {offset}: required align={align}, addr={ptr_addr:#x}"
)));
}
Ok(())
}
pub(crate) fn to_object<T: MmapObject>(&self, offset: usize, base: usize) -> Result<&T> {
let offset = self.checked_offset(offset, base)?;
self.check_size(offset, mem::size_of::<T>())?;
self.check_alignment::<T>(offset)?;
Ok(unsafe { &*(self.data.add(offset) as *const T) })
}
pub(crate) fn to_object_mut<T: MmapObject>(
&mut self,
offset: usize,
base: usize,
) -> Result<&mut T> {
self.require_writable()?;
let offset = self.checked_offset(offset, base)?;
self.check_size(offset, mem::size_of::<T>())?;
self.check_alignment::<T>(offset)?;
Ok(unsafe { &mut *(self.data.add(offset) as *mut T) })
}
pub(crate) fn cstr_at(&self, offset: usize, base: usize) -> Result<&CStr> {
let offset = self.checked_offset(offset, base)?;
let remaining = self.size.checked_sub(offset).ok_or_else(|| {
Error::FileValidation(format!("Offset past mmap: {offset} > {}", self.size))
})?;
let cells = self.atomic_data(offset, 0, remaining)?;
let nul = cells
.iter()
.position(|c| c.load(std::sync::atomic::Ordering::Relaxed) == 0)
.ok_or_else(|| {
Error::FileValidation(format!("No NUL terminator at offset {offset}"))
})?;
let bytes = self.data(offset, 0, nul + 1)?;
CStr::from_bytes_with_nul(bytes)
.map_err(|e| Error::FileValidation(format!("Invalid C string at offset {offset}: {e}")))
}
pub(crate) fn atomic_data(
&self,
offset: usize,
base: usize,
size: usize,
) -> Result<&[std::sync::atomic::AtomicU8]> {
let offset = self.checked_offset(offset, base)?;
self.check_size(offset, size)?;
Ok(unsafe {
std::slice::from_raw_parts(
self.data.add(offset) as *const std::sync::atomic::AtomicU8,
size,
)
})
}
}
impl std::ops::Drop for MemoryMap {
fn drop(&mut self) {
unsafe {
if let Err(e) = mm::munmap(self.data as _, self.size) {
error!("Failed to unmap memory: {e:?}");
}
}
}
}