Merge "Set NoAuthRequired on boot level 0 key"
diff --git a/keystore2/Android.bp b/keystore2/Android.bp
index af177be..dfc1db0 100644
--- a/keystore2/Android.bp
+++ b/keystore2/Android.bp
@@ -54,6 +54,8 @@
"librand",
"librusqlite",
"libstatslog_rust",
+ "libstatslog_rust_header",
+ "libstatspull_rust",
"libthiserror",
],
shared_libs: [
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index c35956f..32e2c98 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -72,6 +72,9 @@
use android_security_remoteprovisioning::aidl::android::security::remoteprovisioning::{
AttestationPoolStatus::AttestationPoolStatus,
};
+use statslog_rust::keystore2_storage_stats::{
+ Keystore2StorageStats, StorageType as StatsdStorageType,
+};
use keystore2_crypto::ZVec;
use lazy_static::lazy_static;
@@ -829,6 +832,9 @@
const UNASSIGNED_KEY_ID: i64 = -1i64;
const PERBOOT_DB_FILE_NAME: &'static str = &"file:perboot.sqlite?mode=memory&cache=shared";
+ /// Name of the file that holds the cross-boot persistent database.
+ pub const PERSISTENT_DB_FILENAME: &'static str = &"persistent.sqlite";
+
/// This creates a PerBootDbKeepAlive object to keep the per boot database alive.
pub fn keep_perboot_db_alive() -> Result<PerBootDbKeepAlive> {
let conn = Connection::open_in_memory()
@@ -847,7 +853,7 @@
pub fn new(db_root: &Path, gc: Option<Gc>) -> Result<Self> {
// Build the path to the sqlite file.
let mut persistent_path = db_root.to_path_buf();
- persistent_path.push("persistent.sqlite");
+ persistent_path.push(Self::PERSISTENT_DB_FILENAME);
// Now convert them to strings prefixed with "file:"
let mut persistent_path_str = "file:".to_owned();
@@ -1042,6 +1048,100 @@
Ok(conn)
}
+ fn do_table_size_query(
+ &mut self,
+ storage_type: StatsdStorageType,
+ query: &str,
+ params: &[&str],
+ ) -> Result<Keystore2StorageStats> {
+ let (total, unused) = self.with_transaction(TransactionBehavior::Deferred, |tx| {
+ tx.query_row(query, params, |row| Ok((row.get(0)?, row.get(1)?)))
+ .with_context(|| {
+ format!("get_storage_stat: Error size of storage type {}", storage_type as i32)
+ })
+ .no_gc()
+ })?;
+ Ok(Keystore2StorageStats { storage_type, size: total, unused_size: unused })
+ }
+
+ fn get_total_size(&mut self) -> Result<Keystore2StorageStats> {
+ self.do_table_size_query(
+ StatsdStorageType::Database,
+ "SELECT page_count * page_size, freelist_count * page_size
+ FROM pragma_page_count('persistent'),
+ pragma_page_size('persistent'),
+ persistent.pragma_freelist_count();",
+ &[],
+ )
+ }
+
+ fn get_table_size(
+ &mut self,
+ storage_type: StatsdStorageType,
+ schema: &str,
+ table: &str,
+ ) -> Result<Keystore2StorageStats> {
+ self.do_table_size_query(
+ storage_type,
+ "SELECT pgsize,unused FROM dbstat(?1)
+ WHERE name=?2 AND aggregate=TRUE;",
+ &[schema, table],
+ )
+ }
+
+ /// Fetches a storage statisitics atom for a given storage type. For storage
+ /// types that map to a table, information about the table's storage is
+ /// returned. Requests for storage types that are not DB tables return None.
+ pub fn get_storage_stat(
+ &mut self,
+ storage_type: StatsdStorageType,
+ ) -> Result<Keystore2StorageStats> {
+ match storage_type {
+ StatsdStorageType::Database => self.get_total_size(),
+ StatsdStorageType::KeyEntry => {
+ self.get_table_size(storage_type, "persistent", "keyentry")
+ }
+ StatsdStorageType::KeyEntryIdIndex => {
+ self.get_table_size(storage_type, "persistent", "keyentry_id_index")
+ }
+ StatsdStorageType::KeyEntryDomainNamespaceIndex => {
+ self.get_table_size(storage_type, "persistent", "keyentry_domain_namespace_index")
+ }
+ StatsdStorageType::BlobEntry => {
+ self.get_table_size(storage_type, "persistent", "blobentry")
+ }
+ StatsdStorageType::BlobEntryKeyEntryIdIndex => {
+ self.get_table_size(storage_type, "persistent", "blobentry_keyentryid_index")
+ }
+ StatsdStorageType::KeyParameter => {
+ self.get_table_size(storage_type, "persistent", "keyparameter")
+ }
+ StatsdStorageType::KeyParameterKeyEntryIdIndex => {
+ self.get_table_size(storage_type, "persistent", "keyparameter_keyentryid_index")
+ }
+ StatsdStorageType::KeyMetadata => {
+ self.get_table_size(storage_type, "persistent", "keymetadata")
+ }
+ StatsdStorageType::KeyMetadataKeyEntryIdIndex => {
+ self.get_table_size(storage_type, "persistent", "keymetadata_keyentryid_index")
+ }
+ StatsdStorageType::Grant => self.get_table_size(storage_type, "persistent", "grant"),
+ StatsdStorageType::AuthToken => {
+ self.get_table_size(storage_type, "perboot", "authtoken")
+ }
+ StatsdStorageType::BlobMetadata => {
+ self.get_table_size(storage_type, "persistent", "blobmetadata")
+ }
+ StatsdStorageType::BlobMetadataBlobEntryIdIndex => {
+ self.get_table_size(storage_type, "persistent", "blobmetadata_blobentryid_index")
+ }
+ _ => Err(anyhow::Error::msg(format!(
+ "Unsupported storage type: {}",
+ storage_type as i32
+ ))),
+ }
+ }
+
/// This function is intended to be used by the garbage collector.
/// It deletes the blob given by `blob_id_to_delete`. It then tries to find a superseded
/// key blob that might need special handling by the garbage collector.
@@ -3072,6 +3172,8 @@
use rusqlite::NO_PARAMS;
use rusqlite::{Error, TransactionBehavior};
use std::cell::RefCell;
+ use std::collections::BTreeMap;
+ use std::fmt::Write;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::Arc;
use std::thread;
@@ -5172,4 +5274,192 @@
assert_eq!(secret_bytes, &*decrypted_secret_bytes);
Ok(())
}
+
+ fn get_valid_statsd_storage_types() -> Vec<StatsdStorageType> {
+ vec![
+ StatsdStorageType::KeyEntry,
+ StatsdStorageType::KeyEntryIdIndex,
+ StatsdStorageType::KeyEntryDomainNamespaceIndex,
+ StatsdStorageType::BlobEntry,
+ StatsdStorageType::BlobEntryKeyEntryIdIndex,
+ StatsdStorageType::KeyParameter,
+ StatsdStorageType::KeyParameterKeyEntryIdIndex,
+ StatsdStorageType::KeyMetadata,
+ StatsdStorageType::KeyMetadataKeyEntryIdIndex,
+ StatsdStorageType::Grant,
+ StatsdStorageType::AuthToken,
+ StatsdStorageType::BlobMetadata,
+ StatsdStorageType::BlobMetadataBlobEntryIdIndex,
+ ]
+ }
+
+ /// Perform a simple check to ensure that we can query all the storage types
+ /// that are supported by the DB. Check for reasonable values.
+ #[test]
+ fn test_query_all_valid_table_sizes() -> Result<()> {
+ const PAGE_SIZE: i64 = 4096;
+
+ let mut db = new_test_db()?;
+
+ for t in get_valid_statsd_storage_types() {
+ let stat = db.get_storage_stat(t)?;
+ assert!(stat.size >= PAGE_SIZE);
+ assert!(stat.size >= stat.unused_size);
+ }
+
+ Ok(())
+ }
+
+ fn get_storage_stats_map(db: &mut KeystoreDB) -> BTreeMap<i32, Keystore2StorageStats> {
+ get_valid_statsd_storage_types()
+ .into_iter()
+ .map(|t| (t as i32, db.get_storage_stat(t).unwrap()))
+ .collect()
+ }
+
+ fn assert_storage_increased(
+ db: &mut KeystoreDB,
+ increased_storage_types: Vec<StatsdStorageType>,
+ baseline: &mut BTreeMap<i32, Keystore2StorageStats>,
+ ) {
+ for storage in increased_storage_types {
+ // Verify the expected storage increased.
+ let new = db.get_storage_stat(storage).unwrap();
+ let storage = storage as i32;
+ let old = &baseline[&storage];
+ assert!(new.size >= old.size, "{}: {} >= {}", storage, new.size, old.size);
+ assert!(
+ new.unused_size <= old.unused_size,
+ "{}: {} <= {}",
+ storage,
+ new.unused_size,
+ old.unused_size
+ );
+
+ // Update the baseline with the new value so that it succeeds in the
+ // later comparison.
+ baseline.insert(storage, new);
+ }
+
+ // Get an updated map of the storage and verify there were no unexpected changes.
+ let updated_stats = get_storage_stats_map(db);
+ assert_eq!(updated_stats.len(), baseline.len());
+
+ for &k in baseline.keys() {
+ let stringify = |map: &BTreeMap<i32, Keystore2StorageStats>| -> String {
+ let mut s = String::new();
+ for &k in map.keys() {
+ writeln!(&mut s, " {}: {}, {}", &k, map[&k].size, map[&k].unused_size)
+ .expect("string concat failed");
+ }
+ s
+ };
+
+ assert!(
+ updated_stats[&k].size == baseline[&k].size
+ && updated_stats[&k].unused_size == baseline[&k].unused_size,
+ "updated_stats:\n{}\nbaseline:\n{}",
+ stringify(&updated_stats),
+ stringify(&baseline)
+ );
+ }
+ }
+
+ #[test]
+ fn test_verify_key_table_size_reporting() -> Result<()> {
+ let mut db = new_test_db()?;
+ let mut working_stats = get_storage_stats_map(&mut db);
+
+ let key_id = db.create_key_entry(&Domain::APP, &42, &KEYSTORE_UUID)?;
+ assert_storage_increased(
+ &mut db,
+ vec![
+ StatsdStorageType::KeyEntry,
+ StatsdStorageType::KeyEntryIdIndex,
+ StatsdStorageType::KeyEntryDomainNamespaceIndex,
+ ],
+ &mut working_stats,
+ );
+
+ let mut blob_metadata = BlobMetaData::new();
+ blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password));
+ db.set_blob(&key_id, SubComponentType::KEY_BLOB, Some(TEST_KEY_BLOB), None)?;
+ assert_storage_increased(
+ &mut db,
+ vec![
+ StatsdStorageType::BlobEntry,
+ StatsdStorageType::BlobEntryKeyEntryIdIndex,
+ StatsdStorageType::BlobMetadata,
+ StatsdStorageType::BlobMetadataBlobEntryIdIndex,
+ ],
+ &mut working_stats,
+ );
+
+ let params = make_test_params(None);
+ db.insert_keyparameter(&key_id, ¶ms)?;
+ assert_storage_increased(
+ &mut db,
+ vec![StatsdStorageType::KeyParameter, StatsdStorageType::KeyParameterKeyEntryIdIndex],
+ &mut working_stats,
+ );
+
+ let mut metadata = KeyMetaData::new();
+ metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789)));
+ db.insert_key_metadata(&key_id, &metadata)?;
+ assert_storage_increased(
+ &mut db,
+ vec![StatsdStorageType::KeyMetadata, StatsdStorageType::KeyMetadataKeyEntryIdIndex],
+ &mut working_stats,
+ );
+
+ let mut sum = 0;
+ for stat in working_stats.values() {
+ sum += stat.size;
+ }
+ let total = db.get_storage_stat(StatsdStorageType::Database)?.size;
+ assert!(sum <= total, "Expected sum <= total. sum: {}, total: {}", sum, total);
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_verify_auth_table_size_reporting() -> Result<()> {
+ let mut db = new_test_db()?;
+ let mut working_stats = get_storage_stats_map(&mut db);
+ db.insert_auth_token(&HardwareAuthToken {
+ challenge: 123,
+ userId: 456,
+ authenticatorId: 789,
+ authenticatorType: kmhw_authenticator_type::ANY,
+ timestamp: Timestamp { milliSeconds: 10 },
+ mac: b"mac".to_vec(),
+ })?;
+ assert_storage_increased(&mut db, vec![StatsdStorageType::AuthToken], &mut working_stats);
+ Ok(())
+ }
+
+ #[test]
+ fn test_verify_grant_table_size_reporting() -> Result<()> {
+ const OWNER: i64 = 1;
+ let mut db = new_test_db()?;
+ make_test_key_entry(&mut db, Domain::APP, OWNER, TEST_ALIAS, None)?;
+
+ let mut working_stats = get_storage_stats_map(&mut db);
+ db.grant(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: 0,
+ alias: Some(TEST_ALIAS.to_string()),
+ blob: None,
+ },
+ OWNER as u32,
+ 123,
+ key_perm_set![KeyPerm::use_()],
+ |_, _| Ok(()),
+ )?;
+
+ assert_storage_increased(&mut db, vec![StatsdStorageType::Grant], &mut working_stats);
+
+ Ok(())
+ }
}
diff --git a/keystore2/src/keystore2_main.rs b/keystore2/src/keystore2_main.rs
index b95add2..4d4a718 100644
--- a/keystore2/src/keystore2_main.rs
+++ b/keystore2/src/keystore2_main.rs
@@ -17,6 +17,7 @@
use keystore2::entropy;
use keystore2::globals::ENFORCEMENTS;
use keystore2::maintenance::Maintenance;
+use keystore2::metrics;
use keystore2::remote_provisioning::RemoteProvisioningService;
use keystore2::service::KeystoreService;
use keystore2::{apc::ApcManager, shared_secret_negotiation};
@@ -135,6 +136,13 @@
},
);
+ std::thread::spawn(|| {
+ match metrics::register_pull_metrics_callbacks() {
+ Err(e) => error!("register_pull_metrics_callbacks failed: {:?}.", e),
+ _ => info!("Pull metrics callbacks successfully registered."),
+ };
+ });
+
info!("Successfully registered Keystore 2.0 service.");
info!("Joining thread pool now.");
diff --git a/keystore2/src/legacy_blob.rs b/keystore2/src/legacy_blob.rs
index a3e440b..e631356 100644
--- a/keystore2/src/legacy_blob.rs
+++ b/keystore2/src/legacy_blob.rs
@@ -686,10 +686,18 @@
let user_id = uid_to_android_user(uid);
path.push(format!("user_{}", user_id));
let uid_str = uid.to_string();
- let dir =
- Self::with_retry_interrupted(|| fs::read_dir(path.as_path())).with_context(|| {
- format!("In list_vpn_profiles: Failed to open legacy blob database. {:?}", path)
- })?;
+ let dir = match Self::with_retry_interrupted(|| fs::read_dir(path.as_path())) {
+ Ok(dir) => dir,
+ Err(e) => match e.kind() {
+ ErrorKind::NotFound => return Ok(Default::default()),
+ _ => {
+ return Err(e).context(format!(
+ "In list_vpn_profiles: Failed to open legacy blob database. {:?}",
+ path
+ ))
+ }
+ },
+ };
let mut result: Vec<String> = Vec::new();
for entry in dir {
let file_name =
@@ -1370,4 +1378,14 @@
Ok(())
}
+
+ #[test]
+ fn list_vpn_profiles_on_non_existing_user() -> Result<()> {
+ let temp_dir = TempDir::new("list_vpn_profiles_on_non_existing_user")?;
+ let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
+
+ assert!(legacy_blob_loader.list_vpn_profiles(20)?.is_empty());
+
+ Ok(())
+ }
}
diff --git a/keystore2/src/metrics.rs b/keystore2/src/metrics.rs
index c5dd582..71c2f3f 100644
--- a/keystore2/src/metrics.rs
+++ b/keystore2/src/metrics.rs
@@ -14,6 +14,7 @@
//! This module provides convenience functions for keystore2 logging.
use crate::error::get_error_code;
+use crate::globals::DB;
use crate::key_parameter::KeyParameterValue as KsKeyParamValue;
use crate::operation::Outcome;
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
@@ -22,15 +23,22 @@
KeyParameter::KeyParameter, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode,
SecurityLevel::SecurityLevel,
};
-use statslog_rust::keystore2_key_creation_event_reported::{
- Algorithm as StatsdAlgorithm, EcCurve as StatsdEcCurve, KeyOrigin as StatsdKeyOrigin,
- Keystore2KeyCreationEventReported, SecurityLevel as StatsdKeyCreationSecurityLevel,
- UserAuthType as StatsdUserAuthType,
+use anyhow::Result;
+use keystore2_system_property::PropertyWatcher;
+use statslog_rust::{
+ keystore2_key_creation_event_reported::{
+ Algorithm as StatsdAlgorithm, EcCurve as StatsdEcCurve, KeyOrigin as StatsdKeyOrigin,
+ Keystore2KeyCreationEventReported, SecurityLevel as StatsdKeyCreationSecurityLevel,
+ UserAuthType as StatsdUserAuthType,
+ },
+ keystore2_key_operation_event_reported::{
+ Keystore2KeyOperationEventReported, Outcome as StatsdOutcome, Purpose as StatsdKeyPurpose,
+ SecurityLevel as StatsdKeyOperationSecurityLevel,
+ },
+ keystore2_storage_stats::StorageType as StatsdStorageType,
};
-use statslog_rust::keystore2_key_operation_event_reported::{
- Keystore2KeyOperationEventReported, Outcome as StatsdOutcome, Purpose as StatsdKeyPurpose,
- SecurityLevel as StatsdKeyOperationSecurityLevel,
-};
+use statslog_rust_header::Atoms;
+use statspull_rust::{set_pull_atom_callback, StatsPullResult};
fn create_default_key_creation_atom() -> Keystore2KeyCreationEventReported {
// If a value is not present, fields represented by bitmaps and i32 fields
@@ -75,7 +83,7 @@
pub fn log_key_creation_event_stats<U>(
sec_level: SecurityLevel,
key_params: &[KeyParameter],
- result: &anyhow::Result<U>,
+ result: &Result<U>,
) {
let key_creation_event_stats =
construct_key_creation_event_stats(sec_level, key_params, result);
@@ -119,7 +127,7 @@
fn construct_key_creation_event_stats<U>(
sec_level: SecurityLevel,
key_params: &[KeyParameter],
- result: &anyhow::Result<U>,
+ result: &Result<U>,
) -> Keystore2KeyCreationEventReported {
let mut key_creation_event_atom = create_default_key_creation_atom();
@@ -375,6 +383,55 @@
}
bitmap
}
+
+/// Registers pull metrics callbacks
+pub fn register_pull_metrics_callbacks() -> Result<()> {
+ // Before registering the callbacks with statsd, we have to wait for the system to finish
+ // booting up. This avoids possible races that may occur at startup. For example, statsd
+ // depends on a companion service, and if registration happens too soon it will fail since
+ // the companion service isn't up yet.
+ let mut watcher = PropertyWatcher::new("sys.boot_completed")?;
+ loop {
+ watcher.wait()?;
+ let value = watcher.read(|_name, value| Ok(value.trim().to_string()));
+ if value? == "1" {
+ set_pull_atom_callback(Atoms::Keystore2StorageStats, None, pull_metrics_callback);
+ break;
+ }
+ }
+ Ok(())
+}
+
+fn pull_metrics_callback() -> StatsPullResult {
+ let mut result = StatsPullResult::new();
+ let mut append = |stat| {
+ match stat {
+ Ok(s) => result.push(Box::new(s)),
+ Err(error) => {
+ log::error!("pull_metrics_callback: Error getting storage stat: {}", error)
+ }
+ };
+ };
+ DB.with(|db| {
+ let mut db = db.borrow_mut();
+ append(db.get_storage_stat(StatsdStorageType::Database));
+ append(db.get_storage_stat(StatsdStorageType::KeyEntry));
+ append(db.get_storage_stat(StatsdStorageType::KeyEntryIdIndex));
+ append(db.get_storage_stat(StatsdStorageType::KeyEntryDomainNamespaceIndex));
+ append(db.get_storage_stat(StatsdStorageType::BlobEntry));
+ append(db.get_storage_stat(StatsdStorageType::BlobEntryKeyEntryIdIndex));
+ append(db.get_storage_stat(StatsdStorageType::KeyParameter));
+ append(db.get_storage_stat(StatsdStorageType::KeyParameterKeyEntryIdIndex));
+ append(db.get_storage_stat(StatsdStorageType::KeyMetadata));
+ append(db.get_storage_stat(StatsdStorageType::KeyMetadataKeyEntryIdIndex));
+ append(db.get_storage_stat(StatsdStorageType::Grant));
+ append(db.get_storage_stat(StatsdStorageType::AuthToken));
+ append(db.get_storage_stat(StatsdStorageType::BlobMetadata));
+ append(db.get_storage_stat(StatsdStorageType::BlobMetadataBlobEntryIdIndex));
+ });
+ result
+}
+
/// Enum defining the bit position for each padding mode. Since padding mode can be repeatable, it
/// is represented using a bitmap.
#[allow(non_camel_case_types)]
diff --git a/keystore2/system_property/lib.rs b/keystore2/system_property/lib.rs
index f14cf0e..be13c88 100644
--- a/keystore2/system_property/lib.rs
+++ b/keystore2/system_property/lib.rs
@@ -15,8 +15,9 @@
//! This crate provides the PropertyWatcher type, which watches for changes
//! in Android system properties.
+use keystore2_system_property_bindgen::prop_info as PropInfo;
use std::os::raw::c_char;
-use std::ptr::null_mut;
+use std::ptr::null;
use std::{
ffi::{c_void, CStr, CString},
str::Utf8Error,
@@ -56,27 +57,34 @@
/// as `keystore.boot_level`; it can report the current value of this
/// property, or wait for it to change.
pub struct PropertyWatcher {
- prop_info: *const keystore2_system_property_bindgen::prop_info,
+ prop_name: CString,
+ prop_info: *const PropInfo,
serial: keystore2_system_property_bindgen::__uint32_t,
}
impl PropertyWatcher {
/// Create a PropertyWatcher for the named system property.
pub fn new(name: &str) -> Result<Self> {
- let cstr = CString::new(name)?;
- // Unsafe FFI call. We generate the CStr in this function
- // and so ensure it is valid during call.
- // Returned pointer is valid for the lifetime of the program.
- let prop_info =
- unsafe { keystore2_system_property_bindgen::__system_property_find(cstr.as_ptr()) };
- if prop_info.is_null() {
- Err(PropertyWatcherError::SystemPropertyAbsent)
+ 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 {
+ keystore2_system_property_bindgen::__system_property_find(self.prop_name.as_ptr())
+ };
+ }
+ if self.prop_info.is_null() {
+ None
} else {
- Ok(Self { prop_info, serial: 0 })
+ Some(self.prop_info)
}
}
- fn read_raw(&self, mut f: impl FnOnce(Option<&CStr>, Option<&CStr>)) {
+ 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.
@@ -98,7 +106,7 @@
// to a void pointer, and unwrap it in our callback.
unsafe {
keystore2_system_property_bindgen::__system_property_read_callback(
- self.prop_info,
+ prop_info,
Some(callback),
&mut f as *mut _ as *mut c_void,
)
@@ -108,12 +116,14 @@
/// Call the passed function, passing it the name and current value
/// of this system property. See documentation for
/// `__system_property_read_callback` for details.
- pub fn read<T, F>(&self, f: F) -> Result<T>
+ /// 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(|name, value| {
+ 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()?;
@@ -124,10 +134,43 @@
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.
+ keystore2_system_property_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
@@ -137,7 +180,7 @@
self.prop_info,
self.serial,
&mut new_serial,
- null_mut(),
+ null(),
)
} {
return Err(PropertyWatcherError::WaitFailed);
diff --git a/keystore2/vpnprofilestore/lib.rs b/keystore2/vpnprofilestore/lib.rs
index f5adc1b..5123837 100644
--- a/keystore2/vpnprofilestore/lib.rs
+++ b/keystore2/vpnprofilestore/lib.rs
@@ -75,15 +75,11 @@
}
fn is_locked_error(e: &anyhow::Error) -> bool {
- matches!(e.root_cause().downcast_ref::<rusqlite::ffi::Error>(),
- Some(rusqlite::ffi::Error {
- code: rusqlite::ErrorCode::DatabaseBusy,
- ..
- })
- | Some(rusqlite::ffi::Error {
- code: rusqlite::ErrorCode::DatabaseLocked,
- ..
- }))
+ matches!(
+ e.root_cause().downcast_ref::<rusqlite::ffi::Error>(),
+ Some(rusqlite::ffi::Error { code: rusqlite::ErrorCode::DatabaseBusy, .. })
+ | Some(rusqlite::ffi::Error { code: rusqlite::ErrorCode::DatabaseLocked, .. })
+ )
}
fn init_tables(&mut self) -> Result<()> {
@@ -192,7 +188,7 @@
{
result.map_or_else(
|e| {
- log::error!("{:#?}", e);
+ log::error!("{:?}", e);
let root_cause = e.root_cause();
let rc = match root_cause.downcast_ref::<Error>() {
Some(Error::Error(e)) => *e,