use std::{
ffi::{c_char, c_void},
sync::OnceLock,
};
use alloc::vec::Vec;
use core::{ffi::CStr, mem, ptr::NonNull};
use crate::tz::{TimeZone, TimeZoneDatabase};
pub(super) fn get(db: &TimeZoneDatabase) -> Option<TimeZone> {
static PROPERTY_NAME: &str = "persist.sys.timezone\0";
static GETTER: OnceLock<Option<PropertyGetter>> = OnceLock::new();
let Some(getter) = GETTER.get_or_init(|| PropertyGetter::new()) else {
return None;
};
let tzname = getter.get(cstr(PROPERTY_NAME))?;
let Some(tzname) = core::str::from_utf8(&tzname).ok() else {
warn!(
"found `{PROPERTY_NAME}` name `{name}` on Android, \
but it's not valid UTF-8",
name = crate::util::escape::Bytes(&tzname),
);
return None;
};
let tz = match db.get(tzname) {
Ok(tz) => tz,
Err(_err) => {
warn!(
"found `{PROPERTY_NAME}` name `{tzname}` on Android, \
but could not find it in time zone database {db:?}",
);
return None;
}
};
debug!(
"found system time zone `{tzname}` from Android property \
`{PROPERTY_NAME}` and found entry for it in time zone \
database {db:?}",
);
Some(tz)
}
pub(super) fn read(_db: &TimeZoneDatabase, path: &str) -> Option<TimeZone> {
match super::read_unnamed_tzif_file(path) {
Ok(tz) => Some(tz),
Err(_err) => {
trace!("failed to read {path} as unnamed time zone: {_err}");
None
}
}
}
struct PropertyGetter {
_libc: NonNull<c_void>,
system_property_find: SystemPropertyFind,
system_property_read: SystemPropertyRead,
}
unsafe impl Send for PropertyGetter {}
unsafe impl Sync for PropertyGetter {}
impl PropertyGetter {
fn new() -> Option<PropertyGetter> {
let handle = unsafe { dlopen(cstr("libc.so\0").as_ptr(), 0) };
let Some(libc) = NonNull::new(handle) else {
let _msg = dlerror_message();
warn!(
"could not open libc.so via `dlopen`: {err}",
err = crate::util::escape::Bytes(&_msg),
);
return None;
};
let system_property_find: SystemPropertyFind =
unsafe { load_symbol(libc, cstr("__system_property_find\0"))? };
let system_property_read: SystemPropertyRead = unsafe {
load_symbol(libc, cstr("__system_property_read_callback\0"))?
};
Some(PropertyGetter {
_libc: libc,
system_property_find,
system_property_read,
})
}
fn get(&self, name: &CStr) -> Option<Vec<u8>> {
unsafe extern "C" fn callback(
buf: *mut c_void,
_name: *const c_char,
value: *const c_char,
_serial: u32,
) {
let buf = buf.cast::<Vec<u8>>();
let value = unsafe { CStr::from_ptr(value) };
unsafe {
(*buf).extend_from_slice(value.to_bytes());
}
}
let prop_info = unsafe { (self.system_property_find)(name.as_ptr()) };
if prop_info.is_null() {
warn!(
"Android property name `{name}` not found",
name = crate::util::escape::Bytes(name.to_bytes()),
);
return None;
}
let mut buf = Vec::new();
unsafe {
let buf: *mut Vec<u8> = &mut buf;
(self.system_property_read)(
prop_info,
callback,
buf.cast::<c_void>(),
);
}
if buf.is_empty() {
warn!(
"reading Android property `{name}` resulted in empty value",
name = crate::util::escape::Bytes(name.to_bytes()),
);
return None;
}
Some(buf)
}
}
unsafe fn load_symbol<F>(handle: NonNull<c_void>, symbol: &CStr) -> Option<F> {
let sym =
unsafe { dlsym(handle.as_ptr(), symbol.as_ptr()) };
if sym.is_null() {
let _ = unsafe { dlclose(handle.as_ptr()) };
let _msg = dlerror_message();
warn!(
"could not load `{symbol}` \
symbol from `libc.so: {err}",
symbol = crate::util::escape::Bytes(symbol.to_bytes()),
err = crate::util::escape::Bytes(&_msg),
);
return None;
}
let function = unsafe { mem::transmute_copy::<*mut c_void, F>(&sym) };
Some(function)
}
fn dlerror_message() -> Vec<u8> {
let msg = unsafe { dlerror() };
if msg.is_null() {
return b"unknown error".to_vec();
}
let cstr = unsafe { CStr::from_ptr(msg) };
cstr.to_bytes().to_vec()
}
fn cstr(string: &'static str) -> &'static CStr {
CStr::from_bytes_with_nul(string.as_bytes()).unwrap()
}
extern "C" {
fn dlopen(filename: *const c_char, flag: i32) -> *mut c_void;
fn dlclose(handle: *mut c_void) -> i32;
fn dlerror() -> *mut c_char;
fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
}
type PropInfo = c_void;
type SystemPropertyFind =
unsafe extern "C" fn(*const c_char) -> *const PropInfo;
type SystemPropertyRead = unsafe extern "C" fn(
*const PropInfo,
SystemPropertyReadCallback,
*mut c_void,
);
type SystemPropertyReadCallback =
unsafe extern "C" fn(*mut c_void, *const c_char, *const c_char, u32);