diff --git a/libc/NOTICE b/libc/NOTICE index ff16da7aa..8260112ce 100644 --- a/libc/NOTICE +++ b/libc/NOTICE @@ -882,6 +882,22 @@ SUCH DAMAGE. ------------------------------------------------------------------- +Copyright (C) 2021 The Android Open Source Project + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +------------------------------------------------------------------- + Copyright (C) 2021 The Android Open Source Project All rights reserved. diff --git a/libc/rust/Android.bp b/libc/rust/Android.bp new file mode 100644 index 000000000..44cf848b4 --- /dev/null +++ b/libc/rust/Android.bp @@ -0,0 +1,30 @@ +rust_bindgen { + name: "libsystem_properties_bindgen", + wrapper_src: "system_properties_bindgen.hpp", + crate_name: "system_properties_bindgen", + source_stem: "bindings", + + bindgen_flags: [ + "--size_t-is-usize", + "--allowlist-function=__system_property_find", + "--allowlist-function=__system_property_read_callback", + "--allowlist-function=__system_property_set", + "--allowlist-function=__system_property_wait", + ], +} + +rust_library { + name: "libsystem_properties-rust", + crate_name: "system_properties", + srcs: [ + "system_properties.rs", + ], + rustlibs: [ + "libanyhow", + "libsystem_properties_bindgen", + "libthiserror", + ], + shared_libs: [ + "libbase", + ], +} diff --git a/libc/rust/system_properties.rs b/libc/rust/system_properties.rs new file mode 100644 index 000000000..189e8ee56 --- /dev/null +++ b/libc/rust/system_properties.rs @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! This crate provides the PropertyWatcher type, which watches for changes +//! in Android system properties. + +use anyhow::{anyhow, Context, Result as AnyhowResult}; +use system_properties_bindgen::prop_info as PropInfo; +use std::os::raw::c_char; +use std::ptr::null; +use std::{ + ffi::{c_void, CStr, CString}, + str::Utf8Error, +}; +use thiserror::Error; + +/// Errors this crate can generate +#[derive(Error, Debug)] +pub enum PropertyWatcherError { + /// We can't watch for a property whose name contains a NUL character. + #[error("Cannot convert name to C string")] + BadNameError(#[from] std::ffi::NulError), + /// We can only watch for properties that exist when the watcher is created. + #[error("System property is absent")] + SystemPropertyAbsent, + /// __system_property_wait timed out despite being given no timeout. + #[error("Wait failed")] + WaitFailed, + /// read callback was not called + #[error("__system_property_read_callback did not call callback")] + ReadCallbackNotCalled, + /// read callback gave us a NULL pointer + #[error("__system_property_read_callback gave us a NULL pointer instead of a string")] + MissingCString, + /// read callback gave us a bad C string + #[error("__system_property_read_callback gave us a non-UTF8 C string")] + BadCString(#[from] Utf8Error), + /// read callback returned an error + #[error("Callback failed")] + CallbackError(#[from] anyhow::Error), + /// Failure in setting the system property + #[error("__system_property_set failed.")] + SetPropertyFailed, +} + +/// Result type specific for this crate. +pub type Result = std::result::Result; + +/// PropertyWatcher takes the name of an Android system property such +/// as `keystore.boot_level`; it can report the current value of this +/// property, or wait for it to change. +pub struct PropertyWatcher { + prop_name: CString, + prop_info: *const PropInfo, + serial: system_properties_bindgen::__uint32_t, +} + +impl PropertyWatcher { + /// Create a PropertyWatcher for the named system property. + pub fn new(name: &str) -> Result { + Ok(Self { prop_name: CString::new(name)?, prop_info: null(), serial: 0 }) + } + + // Lazy-initializing accessor for self.prop_info. + fn get_prop_info(&mut self) -> Option<*const PropInfo> { + if self.prop_info.is_null() { + // Unsafe required for FFI call. Input and output are both const. + // The returned pointer is valid for the lifetime of the program. + self.prop_info = unsafe { + system_properties_bindgen::__system_property_find(self.prop_name.as_ptr()) + }; + } + if self.prop_info.is_null() { + None + } else { + Some(self.prop_info) + } + } + + fn read_raw(prop_info: *const PropInfo, mut f: impl FnOnce(Option<&CStr>, Option<&CStr>)) { + // Unsafe function converts values passed to us by + // __system_property_read_callback to Rust form + // and pass them to inner callback. + unsafe extern "C" fn callback( + res_p: *mut c_void, + name: *const c_char, + value: *const c_char, + _: system_properties_bindgen::__uint32_t, + ) { + let name = if name.is_null() { None } else { Some(CStr::from_ptr(name)) }; + let value = if value.is_null() { None } else { Some(CStr::from_ptr(value)) }; + let f = &mut *res_p.cast::<&mut dyn FnMut(Option<&CStr>, Option<&CStr>)>(); + f(name, value); + } + + let mut f: &mut dyn FnOnce(Option<&CStr>, Option<&CStr>) = &mut f; + + // Unsafe block for FFI call. We convert the FnOnce + // to a void pointer, and unwrap it in our callback. + unsafe { + system_properties_bindgen::__system_property_read_callback( + prop_info, + Some(callback), + &mut f as *mut _ as *mut c_void, + ) + } + } + + /// Call the passed function, passing it the name and current value + /// of this system property. See documentation for + /// `__system_property_read_callback` for details. + /// Returns an error if the property is empty or doesn't exist. + pub fn read(&mut self, f: F) -> Result + where + F: FnOnce(&str, &str) -> anyhow::Result, + { + let prop_info = self.get_prop_info().ok_or(PropertyWatcherError::SystemPropertyAbsent)?; + let mut result = Err(PropertyWatcherError::ReadCallbackNotCalled); + Self::read_raw(prop_info, |name, value| { + // use a wrapping closure as an erzatz try block. + result = (|| { + let name = name.ok_or(PropertyWatcherError::MissingCString)?.to_str()?; + let value = value.ok_or(PropertyWatcherError::MissingCString)?.to_str()?; + f(name, value).map_err(PropertyWatcherError::CallbackError) + })() + }); + result + } + + // Waits for the property that self is watching to be created. Returns immediately if the + // property already exists. + fn wait_for_property_creation(&mut self) -> Result<()> { + let mut global_serial = 0; + loop { + match self.get_prop_info() { + Some(_) => return Ok(()), + None => { + // Unsafe call for FFI. The function modifies only global_serial, and has + // no side-effects. + if !unsafe { + // Wait for a global serial number change, then try again. On success, + // the function will update global_serial with the last version seen. + system_properties_bindgen::__system_property_wait( + null(), + global_serial, + &mut global_serial, + null(), + ) + } { + return Err(PropertyWatcherError::WaitFailed); + } + } + } + } + } + + /// Wait for the system property to change. This + /// records the serial number of the last change, so + /// race conditions are avoided. + pub fn wait(&mut self) -> Result<()> { + // If the property is null, then wait for it to be created. Subsequent waits will + // skip this step and wait for our specific property to change. + if self.prop_info.is_null() { + return self.wait_for_property_creation(); + } + + let mut new_serial = self.serial; + // Unsafe block to call __system_property_wait. + // All arguments are private to PropertyWatcher so we + // can be confident they are valid. + if !unsafe { + system_properties_bindgen::__system_property_wait( + self.prop_info, + self.serial, + &mut new_serial, + null(), + ) + } { + return Err(PropertyWatcherError::WaitFailed); + } + self.serial = new_serial; + Ok(()) + } +} + +/// Reads a system property. +pub fn read(name: &str) -> AnyhowResult { + PropertyWatcher::new(name) + .context("Failed to create a PropertyWatcher.")? + .read(|_name, value| Ok(value.to_owned())) + .with_context(|| format!("Failed to read the system property {}.", name)) +} + +/// Writes a system property. +pub fn write(name: &str, value: &str) -> AnyhowResult<()> { + if + // Unsafe required for FFI call. Input and output are both const and valid strings. + unsafe { + // If successful, __system_property_set returns 0, otherwise, returns -1. + system_properties_bindgen::__system_property_set( + CString::new(name) + .context("Failed to construct CString from name.")? + .as_ptr(), + CString::new(value) + .context("Failed to construct CString from value.")? + .as_ptr(), + ) + } == 0 + { + Ok(()) + } else { + Err(anyhow!(PropertyWatcherError::SetPropertyFailed)) + } +} diff --git a/libc/rust/system_properties_bindgen.hpp b/libc/rust/system_properties_bindgen.hpp new file mode 100644 index 000000000..307cd6cff --- /dev/null +++ b/libc/rust/system_properties_bindgen.hpp @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "sys/system_properties.h"