From 64d98f8ee037282c35007b64c2649055c56af1db Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:19:03 +0200 Subject: Merging upstream version 1.68.2+dfsg1. Signed-off-by: Daniel Baumann --- vendor/time/src/sys/local_offset_at/unix.rs | 169 ++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 vendor/time/src/sys/local_offset_at/unix.rs (limited to 'vendor/time/src/sys/local_offset_at/unix.rs') diff --git a/vendor/time/src/sys/local_offset_at/unix.rs b/vendor/time/src/sys/local_offset_at/unix.rs new file mode 100644 index 000000000..6e849892d --- /dev/null +++ b/vendor/time/src/sys/local_offset_at/unix.rs @@ -0,0 +1,169 @@ +//! Get the system's UTC offset on Unix. + +use core::mem::MaybeUninit; + +use crate::{OffsetDateTime, UtcOffset}; + +/// Convert the given Unix timestamp to a `libc::tm`. Returns `None` on any error. +/// +/// # Safety +/// +/// This method must only be called when the process is single-threaded. +/// +/// This method will remain `unsafe` until `std::env::set_var` is deprecated or has its behavior +/// altered. This method is, on its own, safe. It is the presence of a safe, unsound way to set +/// environment variables that makes it unsafe. +unsafe fn timestamp_to_tm(timestamp: i64) -> Option { + extern "C" { + #[cfg_attr(target_os = "netbsd", link_name = "__tzset50")] + fn tzset(); + } + + // The exact type of `timestamp` beforehand can vary, so this conversion is necessary. + #[allow(clippy::useless_conversion)] + let timestamp = timestamp.try_into().ok()?; + + let mut tm = MaybeUninit::uninit(); + + // Update timezone information from system. `localtime_r` does not do this for us. + // + // Safety: tzset is thread-safe. + unsafe { tzset() }; + + // Safety: We are calling a system API, which mutates the `tm` variable. If a null + // pointer is returned, an error occurred. + let tm_ptr = unsafe { libc::localtime_r(×tamp, tm.as_mut_ptr()) }; + + if tm_ptr.is_null() { + None + } else { + // Safety: The value was initialized, as we no longer have a null pointer. + Some(unsafe { tm.assume_init() }) + } +} + +/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error. +// This is available to any target known to have the `tm_gmtoff` extension. +#[cfg(any( + target_os = "redox", + target_os = "linux", + target_os = "l4re", + target_os = "android", + target_os = "emscripten", + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd", + target_os = "netbsd", + target_os = "haiku", +))] +fn tm_to_offset(tm: libc::tm) -> Option { + let seconds: i32 = tm.tm_gmtoff.try_into().ok()?; + UtcOffset::from_hms( + (seconds / 3_600) as _, + ((seconds / 60) % 60) as _, + (seconds % 60) as _, + ) + .ok() +} + +/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error. +#[cfg(all( + not(unsound_local_offset), + not(any( + target_os = "redox", + target_os = "linux", + target_os = "l4re", + target_os = "android", + target_os = "emscripten", + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd", + target_os = "netbsd", + target_os = "haiku", + )) +))] +#[allow(unused_variables, clippy::missing_const_for_fn)] +fn tm_to_offset(tm: libc::tm) -> Option { + None +} + +/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error. +// This method can return an incorrect value, as it only approximates the `tm_gmtoff` field. As such +// it is gated behind `--cfg unsound_local_offset`. The reason it can return an incorrect value is +// that daylight saving time does not start on the same date every year, nor are the rules for +// daylight saving time the same for every year. This implementation assumes 1970 is equivalent to +// every other year, which is not always the case. +#[cfg(all( + unsound_local_offset, + not(any( + target_os = "redox", + target_os = "linux", + target_os = "l4re", + target_os = "android", + target_os = "emscripten", + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd", + target_os = "netbsd", + target_os = "haiku", + )) +))] +fn tm_to_offset(tm: libc::tm) -> Option { + use crate::Date; + + let mut tm = tm; + if tm.tm_sec == 60 { + // Leap seconds are not currently supported. + tm.tm_sec = 59; + } + + let local_timestamp = + Date::from_ordinal_date(1900 + tm.tm_year, u16::try_from(tm.tm_yday).ok()? + 1) + .ok()? + .with_hms( + tm.tm_hour.try_into().ok()?, + tm.tm_min.try_into().ok()?, + tm.tm_sec.try_into().ok()?, + ) + .ok()? + .assume_utc() + .unix_timestamp(); + + let diff_secs: i32 = (local_timestamp - datetime.unix_timestamp()) + .try_into() + .ok()?; + + UtcOffset::from_hms( + (diff_secs / 3_600) as _, + ((diff_secs / 60) % 60) as _, + (diff_secs % 60) as _, + ) + .ok() +} + +/// Obtain the system's UTC offset. +pub(super) fn local_offset_at(datetime: OffsetDateTime) -> Option { + // Ensure that the process is single-threaded unless the user has explicitly opted out of this + // check. This is to prevent issues with the environment being mutated by a different thread in + // the process while execution of this function is taking place, which can cause a segmentation + // fault by dereferencing a dangling pointer. + // If the `num_threads` crate is incapable of determining the number of running threads, then + // we conservatively return `None` to avoid a soundness bug. + if !cfg!(unsound_local_offset) && num_threads::is_single_threaded() != Some(true) { + return None; + } + + // Safety: We have just confirmed that the process is single-threaded or the user has explicitly + // opted out of soundness. + let tm = unsafe { timestamp_to_tm(datetime.unix_timestamp()) }?; + tm_to_offset(tm) +} -- cgit v1.2.3