Skip to content

Commit efa2d54

Browse files
committed
Dynamically load localtime_rs and friends from libc on Android.
These functions were added in Android API level 35, and provide a better interface to getting timezones, with no environment variable access to worry about.
1 parent 66addc7 commit efa2d54

File tree

2 files changed

+96
-12
lines changed

2 files changed

+96
-12
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"]
5050

5151
[target.'cfg(target_os = "android")'.dependencies]
5252
libc = { version = "0.2.146", optional = true }
53+
once_cell = "1.17.2"
5354

5455
[dev-dependencies]
5556
serde_json = { version = "1" }

src/offset/local/android.rs

+95-12
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,87 @@
11
use super::{FixedOffset, NaiveDateTime};
22
use crate::traits::{Datelike, Timelike};
33
use crate::LocalResult;
4-
use libc::{localtime_r, mktime, time_t};
4+
use libc::{c_char, c_void, dlsym, localtime_r, mktime, time_t, tm, RTLD_DEFAULT};
5+
use once_cell::sync::Lazy;
56
use std::io;
6-
use std::mem;
7+
use std::mem::{self, transmute};
8+
use std::ops::Deref;
79
use std::ptr::null;
810

11+
enum Timezone {}
12+
13+
#[allow(non_camel_case_types)]
14+
type timezone_t = *mut Timezone;
15+
16+
/// Timezone access functions added in API level 35. These are prefereable to `localtime_r` /
17+
/// `mktime` if they are available, as they don't access environment variables and so avoid any
18+
/// potential thread-safety issues with them.
19+
struct TimezoneFunctions {
20+
localtime_rz: unsafe extern "C" fn(timezone_t, *const time_t, *mut tm) -> *mut tm,
21+
mktime_z: unsafe extern "C" fn(timezone_t, *mut tm) -> time_t,
22+
tzalloc: unsafe extern "C" fn(*const c_char) -> timezone_t,
23+
tzfree: unsafe extern "C" fn(timezone_t),
24+
}
25+
26+
impl TimezoneFunctions {
27+
/// Loads the functions dynamically from libc if they are available, or returns `None` if any of
28+
/// the functions is not available.
29+
fn load() -> Option<Self> {
30+
// Safe because we give dlsym valid arguments, check all the return values for nulls, and
31+
// cast the function pointers to the correct types as defined in Bionic libc.
32+
unsafe {
33+
let localtime_rz = dlsym(RTLD_DEFAULT, b"localtime_rz\0".as_ptr());
34+
let mktime_z = dlsym(RTLD_DEFAULT, b"mktime_z\0".as_ptr());
35+
let tzalloc = dlsym(RTLD_DEFAULT, b"tzalloc\0".as_ptr());
36+
let tzfree = dlsym(RTLD_DEFAULT, b"tzfree\0".as_ptr());
37+
if localtime_rz.is_null() || mktime_z.is_null() || tzalloc.is_null() || tzfree.is_null()
38+
{
39+
return None;
40+
}
41+
Some(Self {
42+
localtime_rz: transmute::<*mut c_void, _>(localtime_rz),
43+
mktime_z: transmute::<*mut c_void, _>(mktime_z),
44+
tzalloc: transmute::<*mut c_void, _>(tzalloc),
45+
tzfree: transmute::<*mut c_void, _>(tzfree),
46+
})
47+
}
48+
}
49+
}
50+
51+
static TZ_FUNCTIONS: Lazy<Option<TimezoneFunctions>> = Lazy::new(|| TimezoneFunctions::load());
52+
953
pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult<FixedOffset> {
1054
let time = utc.timestamp() as time_t;
1155
// Safe because an all-zero `struct tm` is valid.
1256
let mut result = unsafe { mem::zeroed() };
13-
// Safe because localtime_r only accesses the pointers passed to it during the call. It does
14-
// also try to read the `TZ` environment variable, but we can assume it's not set on Android as
15-
// the timezone is read from a system property instead.
16-
unsafe {
17-
if localtime_r(&time, &mut result).is_null() {
18-
panic!("localtime_r failed: {}", io::Error::last_os_error());
57+
58+
if let Some(tz_functions) = TZ_FUNCTIONS.deref() {
59+
// Safe because:
60+
// - tzalloc accepts a null pointer to use the current system timezone.
61+
// - localtime_rz only accesses the pointers passed to it during the call, which are valid
62+
// at that point.
63+
// - tzfree is only called after the timezone_t is no longer used.
64+
unsafe {
65+
let timezone = (tz_functions.tzalloc)(null());
66+
if timezone.is_null() {
67+
panic!("tzalloc failed: {}", io::Error::last_os_error());
68+
}
69+
if (tz_functions.localtime_rz)(timezone, &time, &mut result).is_null() {
70+
panic!("localtime_rz failed: {}", io::Error::last_os_error());
71+
}
72+
(tz_functions.tzfree)(timezone);
73+
}
74+
} else {
75+
// Safe because localtime_r only accesses the pointers passed to it during the call. It does
76+
// also try to read the `TZ` environment variable, but we can assume it's not set on Android
77+
// as the timezone is read from a system property instead.
78+
unsafe {
79+
if localtime_r(&time, &mut result).is_null() {
80+
panic!("localtime_r failed: {}", io::Error::last_os_error());
81+
}
1982
}
2083
}
84+
2185
LocalResult::Single(
2286
FixedOffset::east_opt(
2387
result.tm_gmtoff.try_into().expect("localtime_r returned invalid UTC offset"),
@@ -68,9 +132,28 @@ fn mktime_with_dst(local: &NaiveDateTime, isdst: i32) -> (time_t, i32) {
68132
tm_gmtoff: 0,
69133
tm_zone: null(),
70134
};
71-
// Safe because mktime only accesses struct it is passed during the call, and doesn't store the
72-
// pointer to access later. It does also try to read the `TZ` environment variable, but we can
73-
// assume it's not set on Android as the timezone is read from a system property instead.
74-
let timestamp = unsafe { mktime(&mut tm) };
135+
let timestamp;
136+
if let Some(tz_functions) = TZ_FUNCTIONS.deref() {
137+
// Safe because:
138+
// - tzalloc accepts a null pointer to use the current system timezone.
139+
// - mktime only accesses the struct tm it is passed during the call, and doesn't store the
140+
// pointer to access later. The tm_zone it sets may only be valid as long as the timezone
141+
// is, but that's fine as we don't access tm_zone.
142+
// - tzfree is only called after the timezone_t is no longer used.
143+
unsafe {
144+
let timezone = (tz_functions.tzalloc)(null());
145+
if timezone.is_null() {
146+
panic!("tzalloc failed: {}", io::Error::last_os_error());
147+
}
148+
timestamp = (tz_functions.mktime_z)(timezone, &mut tm);
149+
(tz_functions.tzfree)(timezone);
150+
}
151+
} else {
152+
// Safe because mktime only accesses the struct it is passed during the call, and doesn't
153+
// store the pointer to access later. It does also try to read the `TZ` environment
154+
// variable, but we can assume it's not set on Android as the timezone is read from a system
155+
// property instead.
156+
timestamp = unsafe { mktime(&mut tm) };
157+
}
75158
(timestamp, tm.tm_isdst)
76159
}

0 commit comments

Comments
 (0)