|
1 | 1 | use super::{FixedOffset, NaiveDateTime};
|
2 | 2 | use crate::traits::{Datelike, Timelike};
|
3 | 3 | 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; |
5 | 6 | use std::io;
|
6 |
| -use std::mem; |
| 7 | +use std::mem::{self, transmute}; |
| 8 | +use std::ops::Deref; |
7 | 9 | use std::ptr::null;
|
8 | 10 |
|
| 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 | + |
9 | 53 | pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult<FixedOffset> {
|
10 | 54 | let time = utc.timestamp() as time_t;
|
11 | 55 | // Safe because an all-zero `struct tm` is valid.
|
12 | 56 | 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 | + } |
19 | 82 | }
|
20 | 83 | }
|
| 84 | + |
21 | 85 | LocalResult::Single(
|
22 | 86 | FixedOffset::east_opt(
|
23 | 87 | 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) {
|
68 | 132 | tm_gmtoff: 0,
|
69 | 133 | tm_zone: null(),
|
70 | 134 | };
|
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 | + } |
75 | 158 | (timestamp, tm.tm_isdst)
|
76 | 159 | }
|
0 commit comments