Migrate the Rust system property bindings from bionic.
This mostly just migrates the existing code with a few minor changes
(renaming targets, using // for license comments).
It also adds a toplevel lib.rs and moves the .h file into a subfolder
to make it easier to add more libraries the future.
Bug: 182498247
Test: Build this and dependent libraries.
Change-Id: I3556b11bdd8edd8bd2cd91ce4c841615b27c721b
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..66606d4
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,30 @@
+rust_library {
+ name: "librustutils",
+ crate_name: "rustutils",
+ srcs: [
+ "lib.rs",
+ ],
+ rustlibs: [
+ "libanyhow",
+ "libsystem_properties_bindgen",
+ "libthiserror",
+ ],
+ shared_libs: [
+ "libbase",
+ ],
+}
+
+rust_bindgen {
+ name: "libsystem_properties_bindgen",
+ wrapper_src: "bindgen/system_properties.h",
+ 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",
+ ],
+}
diff --git a/bindgen/system_properties.h b/bindgen/system_properties.h
new file mode 100644
index 0000000..307cd6c
--- /dev/null
+++ b/bindgen/system_properties.h
@@ -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"
diff --git a/lib.rs b/lib.rs
new file mode 100644
index 0000000..2b2e6b0
--- /dev/null
+++ b/lib.rs
@@ -0,0 +1,17 @@
+// 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.
+
+//! Android rust utilities.
+
+pub mod system_properties;
diff --git a/system_properties.rs b/system_properties.rs
new file mode 100644
index 0000000..5dc7a57
--- /dev/null
+++ b/system_properties.rs
@@ -0,0 +1,225 @@
+// 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<T> = std::result::Result<T, PropertyWatcherError>;
+
+/// 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<Self> {
+ 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<T, F>(&mut self, f: F) -> Result<T>
+ where
+ F: FnOnce(&str, &str) -> anyhow::Result<T>,
+ {
+ 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<String> {
+ 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))
+ }
+}