use std::io::Read; use std::{ fs::File, io::{BufWriter, Write}, ops::{Deref, DerefMut}, os::fd::FromRawFd, pin::Pin, }; use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer}; use base::libc::{O_CLOEXEC, O_RDONLY}; use base::{ clone_attr, cstr, debug, libc::mkstemp, Directory, FsPath, FsPathBuf, LibcReturn, LoggedResult, MappedFile, SilentResultExt, Utf8CStr, Utf8CStrBufArr, WalkResult, }; use crate::ffi::{prop_cb_exec, PropCb}; use crate::resetprop::proto::persistent_properties::{ mod_PersistentProperties::PersistentPropertyRecord, PersistentProperties, }; macro_rules! PERSIST_PROP_DIR { () => { "/data/property" }; } macro_rules! PERSIST_PROP { () => { concat!(PERSIST_PROP_DIR!(), "/persistent_properties") }; } trait PropCbExec { fn exec(&mut self, name: &Utf8CStr, value: &Utf8CStr); } impl PropCbExec for Pin<&mut PropCb> { fn exec(&mut self, name: &Utf8CStr, value: &Utf8CStr) { unsafe { prop_cb_exec(self.as_mut(), name.as_ptr(), value.as_ptr(), u32::MAX) } } } impl Deref for PersistentProperties { type Target = Vec; fn deref(&self) -> &Self::Target { &self.properties } } impl DerefMut for PersistentProperties { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.properties } } trait PropExt { fn find_index(&self, name: &Utf8CStr) -> Result; fn find(&mut self, name: &Utf8CStr) -> LoggedResult<&mut PersistentPropertyRecord>; } impl PropExt for PersistentProperties { fn find_index(&self, name: &Utf8CStr) -> Result { self.binary_search_by(|p| p.name.as_deref().cmp(&Some(name.deref()))) } fn find(&mut self, name: &Utf8CStr) -> LoggedResult<&mut PersistentPropertyRecord> { let idx = self.find_index(name).silent()?; Ok(&mut self[idx]) } } fn check_proto() -> bool { FsPath::from(cstr!(PERSIST_PROP!())).exists() } fn file_get_prop(name: &Utf8CStr) -> LoggedResult { let mut buf = Utf8CStrBufArr::default(); let path = FsPathBuf::new(&mut buf) .join(PERSIST_PROP_DIR!()) .join(name); let mut file = path.open(O_RDONLY | O_CLOEXEC).silent()?; debug!("resetprop: read prop from [{}]", path); let mut s = String::new(); file.read_to_string(&mut s)?; Ok(s) } fn file_set_prop(name: &Utf8CStr, value: Option<&Utf8CStr>) -> LoggedResult<()> { let mut buf = Utf8CStrBufArr::default(); let path = FsPathBuf::new(&mut buf) .join(PERSIST_PROP_DIR!()) .join(name); if let Some(value) = value { let mut buf = Utf8CStrBufArr::default(); let mut tmp = FsPathBuf::new(&mut buf) .join(PERSIST_PROP_DIR!()) .join("prop.XXXXXX"); { let mut f = unsafe { let fd = mkstemp(tmp.as_mut_ptr()).check_os_err()?; File::from_raw_fd(fd) }; f.write_all(value.as_bytes())?; } debug!("resetprop: write prop to [{}]", tmp); tmp.rename_to(path)? } else { path.remove().silent()?; debug!("resetprop: unlink [{}]", path); } Ok(()) } fn proto_read_props() -> LoggedResult { debug!("resetprop: decode with protobuf [{}]", PERSIST_PROP!()); let m = MappedFile::open(cstr!(PERSIST_PROP!()))?; let m = m.as_ref(); let mut r = BytesReader::from_bytes(m); let mut props = PersistentProperties::from_reader(&mut r, m)?; // Keep the list sorted for binary search props.sort_unstable_by(|a, b| a.name.cmp(&b.name)); Ok(props) } fn proto_write_props(props: &PersistentProperties) -> LoggedResult<()> { let mut buf = Utf8CStrBufArr::default(); let mut tmp = FsPathBuf::new(&mut buf).join(concat!(PERSIST_PROP!(), ".XXXXXX")); { let f = unsafe { let fd = mkstemp(tmp.as_mut_ptr()).check_os_err()?; File::from_raw_fd(fd) }; debug!("resetprop: encode with protobuf [{}]", tmp); props.write_message(&mut Writer::new(BufWriter::new(f)))?; } clone_attr(FsPath::from(cstr!(PERSIST_PROP!())), &tmp)?; tmp.rename_to(cstr!(PERSIST_PROP!()))?; Ok(()) } pub unsafe fn persist_get_prop(name: &Utf8CStr, prop_cb: Pin<&mut PropCb>) { fn inner(name: &Utf8CStr, mut prop_cb: Pin<&mut PropCb>) -> LoggedResult<()> { if check_proto() { let mut props = proto_read_props()?; let prop = props.find(name)?; if let PersistentPropertyRecord { name: Some(ref mut n), value: Some(ref mut v), } = prop { prop_cb.exec(Utf8CStr::from_string(n), Utf8CStr::from_string(v)); } } else { let mut value = file_get_prop(name)?; prop_cb.exec(name, Utf8CStr::from_string(&mut value)); debug!("resetprop: found prop [{}] = [{}]", name, value); } Ok(()) } inner(name, prop_cb).ok(); } pub unsafe fn persist_get_props(prop_cb: Pin<&mut PropCb>) { fn inner(mut prop_cb: Pin<&mut PropCb>) -> LoggedResult<()> { if check_proto() { let mut props = proto_read_props()?; props.iter_mut().for_each(|prop| { if let PersistentPropertyRecord { name: Some(ref mut n), value: Some(ref mut v), } = prop { prop_cb.exec(Utf8CStr::from_string(n), Utf8CStr::from_string(v)); } }); } else { let mut dir = Directory::open(cstr!(PERSIST_PROP_DIR!()))?; dir.pre_order_walk(|e| { if e.is_file() { if let Ok(name) = Utf8CStr::from_cstr(e.d_name()) { if let Ok(mut value) = file_get_prop(name) { prop_cb.exec(name, Utf8CStr::from_string(&mut value)); } } } // Do not traverse recursively Ok(WalkResult::Skip) })?; } Ok(()) } inner(prop_cb).ok(); } pub unsafe fn persist_delete_prop(name: &Utf8CStr) -> bool { fn inner(name: &Utf8CStr) -> LoggedResult<()> { if check_proto() { let mut props = proto_read_props()?; let idx = props.find_index(name).silent()?; props.remove(idx); proto_write_props(&props) } else { file_set_prop(name, None) } } inner(name).is_ok() } pub unsafe fn persist_set_prop(name: &Utf8CStr, value: &Utf8CStr) -> bool { unsafe fn inner(name: &Utf8CStr, value: &Utf8CStr) -> LoggedResult<()> { if check_proto() { let mut props = proto_read_props()?; match props.find_index(name) { Ok(idx) => props[idx].value = Some(value.to_string()), Err(idx) => props.insert( idx, PersistentPropertyRecord { name: Some(name.to_string()), value: Some(value.to_string()), }, ), } proto_write_props(&props) } else { file_set_prop(name, Some(value)) } } inner(name, value).is_ok() }