use std::cell::UnsafeCell;
use std::ffi::CStr;
use std::mem;
#[cfg(feature = "builder")]
use std::ptr;
use std::sync::atomic::{AtomicU32, AtomicU8, Ordering};
use crate::errors::{Error, Result};
use crate::system_properties::PROP_VALUE_MAX;
#[cfg(feature = "builder")]
const LONG_LEGACY_ERROR: &str = "Must use __system_property_read_callback() to read";
const LONG_FLAG: u32 = 1 << 16;
const LONG_LEGACY_ERROR_BUFFER_SIZE: usize = 56;
const _: () = assert!(mem::size_of::<AtomicU8>() == mem::size_of::<u8>());
const _: () = assert!(mem::align_of::<AtomicU8>() == mem::align_of::<u8>());
const _: () = assert!(mem::size_of::<AtomicU32>() == mem::size_of::<u32>());
const _: () = assert!(mem::align_of::<AtomicU32>() == mem::align_of::<u32>());
const _: () = assert!(mem::size_of::<[AtomicU8; PROP_VALUE_MAX]>() == PROP_VALUE_MAX);
#[repr(C)]
struct LongProperty {
error_message: [u8; LONG_LEGACY_ERROR_BUFFER_SIZE],
offset: AtomicU32,
}
#[repr(C)]
union Union {
value: mem::ManuallyDrop<[AtomicU8; PROP_VALUE_MAX]>,
long_property: mem::ManuallyDrop<LongProperty>,
}
#[repr(C, align(4))]
pub struct PropertyInfo {
pub(crate) serial: AtomicU32,
data: UnsafeCell<Union>,
}
unsafe impl Sync for PropertyInfo {}
impl PropertyInfo {
#[cfg(feature = "builder")]
pub(crate) fn init_with_long_offset(&mut self, name: &str, offset: u32) {
init_name_with_trailing_data(self, name);
let error_bytes = LONG_LEGACY_ERROR.as_bytes();
let serial_value = ((error_bytes.len() as u32) << 24) | LONG_FLAG;
self.serial.store(serial_value, Ordering::Relaxed);
unsafe {
let long_property = &mut *(*self.data.get()).long_property;
long_property.error_message =
error_bytes_padded::<LONG_LEGACY_ERROR_BUFFER_SIZE>(error_bytes);
*long_property.offset.get_mut() = offset;
}
}
#[cfg(feature = "builder")]
pub(crate) fn init_with_value(&mut self, name: &str, value: &str) {
init_name_with_trailing_data(self, name);
let serial_value = (value.len() as u32) << 24;
self.serial.store(serial_value, Ordering::Relaxed);
unsafe {
let slot = &mut *(*self.data.get()).value;
init_value_bytes(slot, value.as_bytes());
}
}
#[cfg(feature = "builder")]
pub(crate) fn name(&self, bound: usize) -> Result<&CStr> {
let header = mem::size_of::<PropertyInfo>();
if bound <= header {
return Err(Error::Encoding(
"PropertyInfo name has no room for terminator".into(),
));
}
let len = bound - header - 1;
name_from_trailing_data(self, Some(len))
}
pub(crate) fn value_bytes<'a>(
&'a self,
long_value_bound: usize,
buf: &'a mut [u8; PROP_VALUE_MAX],
) -> Result<std::borrow::Cow<'a, [u8]>> {
if self.is_long() {
let bytes = unsafe {
let long_property = &*(*self.data.get()).long_property;
let offset = long_property.offset.load(Ordering::Relaxed) as usize;
long_value_bytes(self, offset, long_value_bound)?
};
Ok(std::borrow::Cow::Borrowed(bytes))
} else {
unsafe {
let slot = &*(*self.data.get()).value;
Ok(std::borrow::Cow::Borrowed(read_value_atomic(slot, buf)))
}
}
}
pub(crate) fn is_long(&self) -> bool {
let serial = self.serial.load(Ordering::Relaxed);
serial & LONG_FLAG != 0
}
#[cfg(feature = "builder")]
pub(crate) fn writer(&mut self) -> PropertyInfoWriter<'_> {
PropertyInfoWriter(self)
}
}
#[cfg(feature = "builder")]
pub(crate) struct PropertyInfoWriter<'a>(&'a mut PropertyInfo);
#[cfg(feature = "builder")]
const SERIAL_COUNTER_MASK: u32 = 0x00ff_ffff & !LONG_FLAG;
#[cfg(feature = "builder")]
impl PropertyInfoWriter<'_> {
pub(crate) fn apply_write(self, value: &str) -> Result<u32> {
let current = self.0.serial.load(Ordering::Relaxed);
if current & LONG_FLAG != 0 {
return Err(Error::FileValidation(format!(
"in-place update of long property is not supported (serial={current:#x})"
)));
}
let len_u32 = u32::try_from(value.len()).map_err(|_| {
Error::FileValidation(format!("Value length exceeds u32: {}", value.len()))
})?;
if value.len() >= PROP_VALUE_MAX {
return Err(Error::FileValidation(format!(
"Value too long: {} (max: {})",
value.len(),
PROP_VALUE_MAX
)));
}
let dirty_serial = current | 1;
let counter_next = (dirty_serial.wrapping_add(1)) & SERIAL_COUNTER_MASK;
let new_serial = (len_u32 << 24) | counter_next;
self.0.serial.store(dirty_serial, Ordering::Release);
unsafe {
let slot = &*(*self.0.data.get()).value;
write_value_atomic(slot, value.as_bytes());
}
self.0.serial.store(new_serial, Ordering::Release);
Ok(new_serial)
}
}
#[cfg(feature = "builder")]
fn error_bytes_padded<const N: usize>(src: &[u8]) -> [u8; N] {
let mut buf = [0u8; N];
let copy_len = src.len().min(N);
buf[..copy_len].copy_from_slice(&src[..copy_len]);
buf
}
fn read_value_atomic<'a>(
slot: &[AtomicU8; PROP_VALUE_MAX],
buf: &'a mut [u8; PROP_VALUE_MAX],
) -> &'a [u8] {
let mut len = 0;
for (i, cell) in slot.iter().enumerate() {
let b = cell.load(Ordering::Relaxed);
if b == 0 {
break;
}
buf[i] = b;
len = i + 1;
}
&buf[..len]
}
#[cfg(feature = "builder")]
fn write_value_atomic(slot: &[AtomicU8; PROP_VALUE_MAX], bytes: &[u8]) {
let copy_len = bytes.len().min(PROP_VALUE_MAX - 1);
for (i, &b) in bytes[..copy_len].iter().enumerate() {
slot[i].store(b, Ordering::Relaxed);
}
slot[copy_len].store(0, Ordering::Relaxed);
}
#[cfg(feature = "builder")]
fn init_value_bytes(slot: &mut [AtomicU8; PROP_VALUE_MAX], bytes: &[u8]) {
let copy_len = bytes.len().min(PROP_VALUE_MAX - 1);
for (i, &b) in bytes[..copy_len].iter().enumerate() {
*slot[i].get_mut() = b;
}
*slot[copy_len].get_mut() = 0;
}
unsafe fn long_value_bytes(
info: &PropertyInfo,
offset: usize,
bound_from_info: usize,
) -> Result<&[u8]> {
let scan_len = bound_from_info.checked_sub(offset).ok_or_else(|| {
Error::Encoding(format!(
"Long property offset {offset} exceeds mmap bound {bound_from_info}"
))
})?;
if scan_len == 0 {
return Err(Error::Encoding("Long property scan length is zero".into()));
}
let self_ptr = info as *const _ as *const u8;
let value_ptr = unsafe { self_ptr.add(offset) };
let bytes = unsafe { std::slice::from_raw_parts(value_ptr, scan_len) };
let cstr = CStr::from_bytes_until_nul(bytes).map_err(|e| {
Error::Encoding(format!("Long property value missing NUL within bound: {e}"))
})?;
Ok(cstr.to_bytes())
}
#[inline(always)]
pub(crate) fn name_from_trailing_data<I: Sized>(thiz: &I, len: Option<usize>) -> Result<&CStr> {
let len = len.ok_or_else(|| {
Error::Encoding("name_from_trailing_data requires a bounded length".into())
})?;
unsafe {
let thiz_ptr = thiz as *const _ as *const u8;
let name_ptr = thiz_ptr.add(mem::size_of::<I>());
CStr::from_bytes_until_nul(std::slice::from_raw_parts(name_ptr, len + 1))
.map_err(|e| Error::Encoding(format!("Failed to convert name to CStr: {e}")))
}
}
#[cfg(feature = "builder")]
#[inline(always)]
pub(crate) fn init_name_with_trailing_data<I: Sized>(thiz: &mut I, name: &str) {
unsafe {
let thiz_ptr = thiz as *mut _ as *mut u8;
let name_ptr = thiz_ptr.add(mem::size_of::<I>());
ptr::copy_nonoverlapping(name.as_ptr(), name_ptr, name.len());
*name_ptr.add(name.len()) = 0; }
}