From febf3a10de60d7dd62223b03fbb05363bd0e6f90 Mon Sep 17 00:00:00 2001 From: Jacob Pratt Date: Sun, 20 Oct 2024 02:52:32 -0400 Subject: [PATCH] Obtain local offset in multi-threaded situations --- tests/derives.rs | 2 - tests/main.rs | 7 +- tests/offset_date_time.rs | 101 +++++++++---------------- tests/utc_offset.rs | 35 +-------- tests/util.rs | 11 ++- time/src/sys/local_offset_at/mod.rs | 7 +- time/src/sys/local_offset_at/unix.rs | 61 +-------------- time/src/sys/mod.rs | 8 +- time/src/sys/refresh_tz/imp.rs | 9 +++ time/src/sys/refresh_tz/mod.rs | 17 +++++ time/src/sys/refresh_tz/unix.rs | 48 ++++++++++++ time/src/util.rs | 107 +++++++++++++-------------- 12 files changed, 179 insertions(+), 234 deletions(-) create mode 100644 time/src/sys/refresh_tz/imp.rs create mode 100644 time/src/sys/refresh_tz/mod.rs create mode 100644 time/src/sys/refresh_tz/unix.rs diff --git a/tests/derives.rs b/tests/derives.rs index 0165c03773..d2f638cc6f 100644 --- a/tests/derives.rs +++ b/tests/derives.rs @@ -65,7 +65,6 @@ fn clone() { assert_cloned_eq!(well_known::iso8601::FormattedComponents::None); assert_cloned_eq!(component_range_error()); assert_cloned_eq!(BorrowedFormatItem::Literal(b"")); - assert_cloned_eq!(time::util::local_offset::Soundness::Sound); assert_cloned_eq!(modifier::Day::default()); assert_cloned_eq!(modifier::MonthRepr::default()); @@ -166,7 +165,6 @@ fn debug() { well_known::iso8601::Config::DEFAULT; component_range_error(); Error::ConversionRange(ConversionRange); - time::util::local_offset::Soundness::Sound; modifier::Day::default(); modifier::MonthRepr::default(); diff --git a/tests/main.rs b/tests/main.rs index bada9e1cb8..522c55a01f 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -1,4 +1,5 @@ #![allow( + missing_docs, clippy::missing_const_for_fn, // irrelevant for tests clippy::std_instead_of_core, // irrelevant for tests clippy::std_instead_of_alloc, // irrelevant for tests @@ -71,12 +72,6 @@ macro_rules! require_all_features { } require_all_features! { - use std::sync::Mutex; - - /// A lock to ensure that certain tests don't run in parallel, which could lead to a test - /// unexpectedly failing. - static SOUNDNESS_LOCK: Mutex<()> = Mutex::new(()); - // Required by the crate for technical reasons. #[allow(clippy::single_component_path_imports)] use rstest_reuse; diff --git a/tests/offset_date_time.rs b/tests/offset_date_time.rs index 29b2eb4fd3..8024abdc06 100644 --- a/tests/offset_date_time.rs +++ b/tests/offset_date_time.rs @@ -28,19 +28,9 @@ fn now_utc() { assert_eq!(OffsetDateTime::now_utc().offset(), offset!(UTC)); } -#[cfg_attr(miri, ignore)] #[test] fn now_local() { - use time::util::local_offset::*; - - let _guard = crate::SOUNDNESS_LOCK.lock().expect("lock is poisoned"); - - // Safety: Technically not sound. However, this is a test, and it's highly improbable that we - // will run into issues with setting an environment variable a few times. - unsafe { set_soundness(Soundness::Unsound) }; assert!(OffsetDateTime::now_local().is_ok()); - // Safety: We're setting it back to sound. - unsafe { set_soundness(Soundness::Sound) }; } #[test] @@ -450,16 +440,12 @@ fn replace_year() { datetime!(2022 - 02 - 18 12:00 +01).replace_year(2019), Ok(datetime!(2019 - 02 - 18 12:00 +01)) ); - assert!( - datetime!(2022 - 02 - 18 12:00 +01) - .replace_year(-1_000_000_000) - .is_err() - ); // -1_000_000_000 isn't a valid year - assert!( - datetime!(2022 - 02 - 18 12:00 +01) - .replace_year(1_000_000_000) - .is_err() - ); // 1_000_000_000 isn't a valid year + assert!(datetime!(2022 - 02 - 18 12:00 +01) + .replace_year(-1_000_000_000) + .is_err()); // -1_000_000_000 isn't a valid year + assert!(datetime!(2022 - 02 - 18 12:00 +01) + .replace_year(1_000_000_000) + .is_err()); // 1_000_000_000 isn't a valid year } #[test] @@ -468,11 +454,9 @@ fn replace_month() { datetime!(2022 - 02 - 18 12:00 +01).replace_month(Month::January), Ok(datetime!(2022 - 01 - 18 12:00 +01)) ); - assert!( - datetime!(2022 - 01 - 30 12:00 +01) - .replace_month(Month::February) - .is_err() - ); // 30 isn't a valid day in February + assert!(datetime!(2022 - 01 - 30 12:00 +01) + .replace_month(Month::February) + .is_err()); // 30 isn't a valid day in February } #[test] @@ -482,7 +466,8 @@ fn replace_day() { Ok(datetime!(2022 - 02 - 01 12:00 +01)) ); assert!(datetime!(2022 - 02 - 18 12:00 +01).replace_day(0).is_err()); // 00 isn't a valid day - assert!(datetime!(2022 - 02 - 18 12:00 +01).replace_day(30).is_err()); // 30 isn't a valid day in February + assert!(datetime!(2022 - 02 - 18 12:00 +01).replace_day(30).is_err()); // 30 isn't a valid day + // in February } #[test] @@ -496,16 +481,12 @@ fn replace_ordinal() { Ok(datetime!(2024 - 366 12:00 +01)) ); assert!(datetime!(2022 - 049 12:00 +01).replace_ordinal(0).is_err()); // 0 isn't a valid day - assert!( - datetime!(2022 - 049 12:00 +01) - .replace_ordinal(366) - .is_err() - ); // 2022 isn't a leap year - assert!( - datetime!(2022 - 049 12:00 +01) - .replace_ordinal(367) - .is_err() - ); // 367 isn't a valid day + assert!(datetime!(2022 - 049 12:00 +01) + .replace_ordinal(366) + .is_err()); // 2022 isn't a leap year + assert!(datetime!(2022 - 049 12:00 +01) + .replace_ordinal(367) + .is_err()); // 367 isn't a valid day } #[test] @@ -514,11 +495,9 @@ fn replace_hour() { datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_hour(7), Ok(datetime!(2022 - 02 - 18 07:02:03.004_005_006 +01)) ); - assert!( - datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01) - .replace_hour(24) - .is_err() - ); // 24 isn't a valid hour + assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01) + .replace_hour(24) + .is_err()); // 24 isn't a valid hour } #[test] @@ -527,11 +506,9 @@ fn replace_minute() { datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_minute(7), Ok(datetime!(2022 - 02 - 18 01:07:03.004_005_006 +01)) ); - assert!( - datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01) - .replace_minute(60) - .is_err() - ); // 60 isn't a valid minute + assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01) + .replace_minute(60) + .is_err()); // 60 isn't a valid minute } #[test] @@ -540,11 +517,9 @@ fn replace_second() { datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_second(7), Ok(datetime!(2022 - 02 - 18 01:02:07.004_005_006 +01)) ); - assert!( - datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01) - .replace_second(60) - .is_err() - ); // 60 isn't a valid second + assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01) + .replace_second(60) + .is_err()); // 60 isn't a valid second } #[test] @@ -553,11 +528,9 @@ fn replace_millisecond() { datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_millisecond(7), Ok(datetime!(2022 - 02 - 18 01:02:03.007 +01)) ); - assert!( - datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01) - .replace_millisecond(1_000) - .is_err() - ); // 1_000 isn't a valid millisecond + assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01) + .replace_millisecond(1_000) + .is_err()); // 1_000 isn't a valid millisecond } #[test] @@ -566,11 +539,9 @@ fn replace_microsecond() { datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_microsecond(7_008), Ok(datetime!(2022 - 02 - 18 01:02:03.007_008 +01)) ); - assert!( - datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01) - .replace_microsecond(1_000_000) - .is_err() - ); // 1_000_000 isn't a valid microsecond + assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01) + .replace_microsecond(1_000_000) + .is_err()); // 1_000_000 isn't a valid microsecond } #[test] @@ -579,11 +550,9 @@ fn replace_nanosecond() { datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01).replace_nanosecond(7_008_009), Ok(datetime!(2022 - 02 - 18 01:02:03.007_008_009 +01)) ); - assert!( - datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01) - .replace_nanosecond(1_000_000_000) - .is_err() - ); // 1_000_000_000 isn't a valid nanosecond + assert!(datetime!(2022 - 02 - 18 01:02:03.004_005_006 +01) + .replace_nanosecond(1_000_000_000) + .is_err()); // 1_000_000_000 isn't a valid nanosecond } #[test] diff --git a/tests/utc_offset.rs b/tests/utc_offset.rs index d8054c3339..229bcaad87 100644 --- a/tests/utc_offset.rs +++ b/tests/utc_offset.rs @@ -155,51 +155,20 @@ fn neg(#[case] offset: UtcOffset, #[case] expected: UtcOffset) { assert_eq!(-offset, expected); } -#[cfg_attr(miri, ignore)] #[test] fn local_offset_at() { - use time::util::local_offset::*; - - let _guard = crate::SOUNDNESS_LOCK.lock().expect("lock is poisoned"); - - // Safety: Technically not sound. However, this is a test, and it's highly improbable that we - // will run into issues with setting an environment variable a few times. - unsafe { set_soundness(Soundness::Unsound) }; assert!(UtcOffset::local_offset_at(OffsetDateTime::UNIX_EPOCH).is_ok()); - // Safety: We're setting it back to sound. - unsafe { set_soundness(Soundness::Sound) }; } -#[cfg_attr(miri, ignore)] #[test] fn current_local_offset() { - use time::util::local_offset::*; - - let _guard = crate::SOUNDNESS_LOCK.lock().expect("lock is poisoned"); - - // Safety: Technically not sound. However, this is a test, and it's highly improbable that we - // will run into issues with setting an environment variable a few times. - unsafe { set_soundness(Soundness::Unsound) }; assert!(UtcOffset::current_local_offset().is_ok()); - // Safety: We're setting it back to sound. - unsafe { set_soundness(Soundness::Sound) }; } -// Note: This behavior is not guaranteed and will hopefully be changed in the future. #[test] -#[cfg_attr( - any( - target_os = "netbsd", - target_os = "illumos", - not(target_family = "unix") - ), - ignore -)] -fn local_offset_error_when_multithreaded() { - let _guard = crate::SOUNDNESS_LOCK.lock().expect("lock is poisoned"); - +fn local_offset_success_when_multithreaded() { std::thread::spawn(|| { - assert!(UtcOffset::current_local_offset().is_err()); + assert!(UtcOffset::current_local_offset().is_ok()); }) .join() .expect("failed to join thread"); diff --git a/tests/util.rs b/tests/util.rs index 8f0f67cf72..3534b1f66c 100644 --- a/tests/util.rs +++ b/tests/util.rs @@ -80,17 +80,16 @@ fn weeks_in_year() { } #[rstest] +#[allow(deprecated)] fn local_offset_soundness() { use time::util::local_offset::*; - let _guard = crate::SOUNDNESS_LOCK.lock().expect("lock is poisoned"); - + // These functions no longer do anything so they always return `Sound`. assert_eq!(get_soundness(), Soundness::Sound); - // Safety: Technically not sound. However, this is a test, and it's highly improbable that we - // will run into issues with setting an environment variable a few times. + // Safety: This no longer has any safety requirements. unsafe { set_soundness(Soundness::Unsound) }; - assert_eq!(get_soundness(), Soundness::Unsound); - // Safety: We're setting it back to sound. + assert_eq!(get_soundness(), Soundness::Sound); + // Safety: See above. unsafe { set_soundness(Soundness::Sound) }; assert_eq!(get_soundness(), Soundness::Sound); } diff --git a/time/src/sys/local_offset_at/mod.rs b/time/src/sys/local_offset_at/mod.rs index 3367ebb55c..04cfffd5b2 100644 --- a/time/src/sys/local_offset_at/mod.rs +++ b/time/src/sys/local_offset_at/mod.rs @@ -19,10 +19,5 @@ use crate::{OffsetDateTime, UtcOffset}; /// Attempt to obtain the system's UTC offset. If the offset cannot be determined, `None` is /// returned. pub(crate) fn local_offset_at(datetime: OffsetDateTime) -> Option { - // miri does not support tzset() - if cfg!(miri) { - None - } else { - imp::local_offset_at(datetime) - } + imp::local_offset_at(datetime) } diff --git a/time/src/sys/local_offset_at/unix.rs b/time/src/sys/local_offset_at/unix.rs index eddd8e2a95..d036a0e088 100644 --- a/time/src/sys/local_offset_at/unix.rs +++ b/time/src/sys/local_offset_at/unix.rs @@ -2,49 +2,16 @@ use core::mem::MaybeUninit; -use crate::util::local_offset::{self, Soundness}; use crate::{OffsetDateTime, UtcOffset}; -/// Whether the operating system has a thread-safe environment. This allows bypassing the check for -/// if the process is multi-threaded. -// This is the same value as `cfg!(target_os = "x")`. -// Use byte-strings to work around current limitations of const eval. -const OS_HAS_THREAD_SAFE_ENVIRONMENT: bool = match std::env::consts::OS.as_bytes() { - // https://github.com/illumos/illumos-gate/blob/0fb96ba1f1ce26ff8b286f8f928769a6afcb00a6/usr/src/lib/libc/port/gen/getenv.c - b"illumos" - // https://github.com/NetBSD/src/blob/f45028636a44111bc4af44d460924958a4460844/lib/libc/stdlib/getenv.c - // https://github.com/NetBSD/src/blob/f45028636a44111bc4af44d460924958a4460844/lib/libc/stdlib/setenv.c - | b"netbsd" - => true, - _ => false, -}; - /// 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(); - } - +fn timestamp_to_tm(timestamp: i64) -> Option { // 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()) }; @@ -128,27 +95,7 @@ fn tm_to_offset(unix_timestamp: i64, tm: libc::tm) -> Option { /// Obtain the system's UTC offset. pub(super) fn local_offset_at(datetime: OffsetDateTime) -> Option { - // Continue to obtaining the UTC offset if and only if the call is sound or the user has - // explicitly opted out of soundness. - // - // Soundness can be guaranteed either by knowledge of the operating system or knowledge that the - // process is single-threaded. If the process is single-threaded, then the environment cannot - // be 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 OS_HAS_THREAD_SAFE_ENVIRONMENT - || local_offset::get_soundness() == Soundness::Unsound - || num_threads::is_single_threaded() == Some(true) - { - let unix_timestamp = datetime.unix_timestamp(); - // 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(unix_timestamp) }?; - tm_to_offset(unix_timestamp, tm) - } else { - None - } + let unix_timestamp = datetime.unix_timestamp(); + let tm = timestamp_to_tm(unix_timestamp)?; + tm_to_offset(unix_timestamp, tm) } diff --git a/time/src/sys/mod.rs b/time/src/sys/mod.rs index 90bbf7ff3b..76ca93b5b2 100644 --- a/time/src/sys/mod.rs +++ b/time/src/sys/mod.rs @@ -1,9 +1,11 @@ //! Functions with a common interface that rely on system calls. -#![allow(unsafe_code)] // We're interfacing with system calls. - #[cfg(feature = "local-offset")] mod local_offset_at; +#[cfg(feature = "local-offset")] +mod refresh_tz; #[cfg(feature = "local-offset")] -pub(crate) use local_offset_at::local_offset_at; +pub(crate) use self::local_offset_at::local_offset_at; +#[cfg(feature = "local-offset")] +pub(crate) use self::refresh_tz::{refresh_tz, refresh_tz_unchecked}; diff --git a/time/src/sys/refresh_tz/imp.rs b/time/src/sys/refresh_tz/imp.rs new file mode 100644 index 0000000000..6f8b1328db --- /dev/null +++ b/time/src/sys/refresh_tz/imp.rs @@ -0,0 +1,9 @@ +//! A fallback for any OS not covered. + +#[allow(clippy::missing_docs_in_private_items)] +pub(super) unsafe fn refresh_tz_unchecked() {} + +#[allow(clippy::missing_docs_in_private_items)] +pub(super) fn refresh_tz() -> Option<()> { + Some(()) +} diff --git a/time/src/sys/refresh_tz/mod.rs b/time/src/sys/refresh_tz/mod.rs new file mode 100644 index 0000000000..90ddaa9981 --- /dev/null +++ b/time/src/sys/refresh_tz/mod.rs @@ -0,0 +1,17 @@ +#[cfg_attr(target_family = "unix", path = "unix.rs")] +mod imp; + +/// Update time zone information from the system. +/// +/// For safety documentation, see [`time::util::refresh_tz`]. +pub(crate) unsafe fn refresh_tz_unchecked() { + // Safety: The caller must uphold the safety requirements. + unsafe { imp::refresh_tz_unchecked() } +} + +/// Attempt to update time zone information from the system. +/// +/// Returns `None` if the call is not known to be sound. +pub(crate) fn refresh_tz() -> Option<()> { + imp::refresh_tz() +} diff --git a/time/src/sys/refresh_tz/unix.rs b/time/src/sys/refresh_tz/unix.rs new file mode 100644 index 0000000000..6c9813e294 --- /dev/null +++ b/time/src/sys/refresh_tz/unix.rs @@ -0,0 +1,48 @@ +/// Whether the operating system has a thread-safe environment. This allows bypassing the check for +/// if the process is multi-threaded. +// This is the same value as `cfg!(target_os = "x")`. +// Use byte-strings to work around current limitations of const eval. +const OS_HAS_THREAD_SAFE_ENVIRONMENT: bool = match std::env::consts::OS.as_bytes() { + // https://github.com/illumos/illumos-gate/blob/0fb96ba1f1ce26ff8b286f8f928769a6afcb00a6/usr/src/lib/libc/port/gen/getenv.c + b"illumos" + // https://github.com/NetBSD/src/blob/f45028636a44111bc4af44d460924958a4460844/lib/libc/stdlib/getenv.c + // https://github.com/NetBSD/src/blob/f45028636a44111bc4af44d460924958a4460844/lib/libc/stdlib/setenv.c + | b"netbsd" + => true, + _ => false, +}; + +/// Update time zone information from the system. +/// +/// For safety documentation, see [`time::util::refresh_tz`]. +pub(super) unsafe fn refresh_tz_unchecked() { + extern "C" { + #[cfg_attr(target_os = "netbsd", link_name = "__tzset50")] + fn tzset(); + } + + // Safety: The caller must uphold the safety requirements. + unsafe { tzset() }; +} + +/// Attempt to update time zone information from the system. Returns `None` if the call is not known +/// to be sound. +pub(super) fn refresh_tz() -> Option<()> { + // Refresh $TZ if and only if the call is known to be sound. + // + // Soundness can be guaranteed either by knowledge of the operating system or knowledge that the + // process is single-threaded. If the process is single-threaded, then the environment cannot + // be 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 OS_HAS_THREAD_SAFE_ENVIRONMENT || num_threads::is_single_threaded() == Some(true) { + // Safety: The caller must uphold the safety requirements. + unsafe { refresh_tz_unchecked() }; + Some(()) + } else { + None + } +} diff --git a/time/src/util.rs b/time/src/util.rs index 9bb9b0404d..622a23aac8 100644 --- a/time/src/util.rs +++ b/time/src/util.rs @@ -1,4 +1,4 @@ -//! Utility functions. +//! Utility functions, including updating time zone information. pub use time_core::util::{days_in_year, is_leap_year, weeks_in_year}; @@ -30,70 +30,67 @@ pub const fn days_in_year_month(year: i32, month: Month) -> u8 { } } +/// Update time zone information from the system. +/// +/// For a version of this function that is guaranteed to be sound, see [`refresh_tz`]. +/// +/// # Safety +/// +/// This is a system call with specific requirements. The following is from POSIX's description of +/// `tzset`: +/// +/// > If a thread accesses `tzname`, `daylight`, or `timezone` directly while another thread is in a +/// > call to `tzset()`, or to any function that is required or allowed to set timezone information +/// > as if by calling `tzset()`, the behavior is undefined. +/// +/// Effectively, this translates to the requirement that at least one of the following must be true: +/// +/// - The operating system provides a thread-safe environment. +/// - The process is single-threaded. +/// - The process is multi-threaded **and** no other thread is mutating the environment in any way +/// at the same time a call to a method that obtains the local UTC offset. This includes adding, +/// removing, or modifying an environment variable. +/// +/// ## Soundness is global +/// +/// You must not only verify this safety conditions for your code, but for **all** code that will be +/// included in the final binary. Notably, it applies to both direct and transitive dependencies and +/// to both Rust and non-Rust code. **For this reason it is not possible for a library to soundly +/// call this method.** +/// +/// ## Forward compatibility +/// +/// This currently only does anything on Unix-like systems. On other systems, it is a no-op. This +/// may change in the future if necessary, expanding the safety requirements. It is expected that, +/// at a minimum, calling this method when the process is single-threaded will remain sound. #[cfg(feature = "local-offset")] -/// Utility functions relating to the local UTC offset. -pub mod local_offset { - use core::sync::atomic::{AtomicBool, Ordering}; +pub unsafe fn refresh_tz_unchecked() { + // Safety: The caller must uphold the safety requirements. + unsafe { crate::sys::refresh_tz_unchecked() }; +} - /// Whether obtaining the local UTC offset is required to be sound. - static LOCAL_OFFSET_IS_SOUND: AtomicBool = AtomicBool::new(true); +/// Attempt to update time zone information from the system. +/// +/// Returns `None` if the call is not known to be sound. +#[cfg(feature = "local-offset")] +pub fn refresh_tz() -> Option<()> { + crate::sys::refresh_tz() +} - /// The soundness of obtaining the local UTC offset. +#[doc(hidden)] +#[cfg(feature = "local-offset")] +#[allow(clippy::missing_const_for_fn)] +#[deprecated(since = "0.3.37", note = "no longer needed; TZ is refreshed manually")] +pub mod local_offset { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Soundness { - /// Obtaining the local UTC offset is required to be sound. Undefined behavior will never - /// occur. This is the default. Sound, - /// Obtaining the local UTC offset is allowed to invoke undefined behavior. **Setting this - /// value is strongly discouraged.** To do so, you must comply with the safety requirements - /// of [`time::local_offset::set_soundness`](set_soundness). Unsound, } - /// Set whether obtaining the local UTC offset is allowed to invoke undefined behavior. **Use of - /// this function is heavily discouraged.** - /// - /// # Safety - /// - /// If this method is called with [`Soundness::Sound`], the call is always sound. If this method - /// is called with [`Soundness::Unsound`], the following conditions apply. - /// - /// - If the operating system provides a thread-safe environment, the call is sound. - /// - If the process is single-threaded, the call is sound. - /// - If the process is multi-threaded, no other thread may mutate the environment in any way at - /// the same time a call to a method that obtains the local UTC offset. This includes adding, - /// removing, or modifying an environment variable. - /// - /// The first two conditions are automatically checked by `time`, such that you do not need to - /// declare your code unsound. Currently, the only known operating systems that does _not_ - /// provide a thread-safe environment are some Unix-like OS's. All other operating systems - /// should succeed when attempting to obtain the local UTC offset. - /// - /// Note that you must not only verify this safety condition for your code, but for **all** code - /// that will be included in the final binary. Notably, it applies to both direct and transitive - /// dependencies and to both Rust and non-Rust code. **For this reason it is not possible to - /// soundly pass [`Soundness::Unsound`] to this method if you are writing a library that may - /// used by others.** - /// - /// If using this method is absolutely necessary, it is recommended to keep the time between - /// setting the soundness to [`Soundness::Unsound`] and setting it back to [`Soundness::Sound`] - /// as short as possible. - /// - /// The following methods currently obtain the local UTC offset: - /// - /// - [`OffsetDateTime::now_local`](crate::OffsetDateTime::now_local) - /// - [`UtcOffset::local_offset_at`](crate::UtcOffset::local_offset_at) - /// - [`UtcOffset::current_local_offset`](crate::UtcOffset::current_local_offset) - pub unsafe fn set_soundness(soundness: Soundness) { - LOCAL_OFFSET_IS_SOUND.store(soundness == Soundness::Sound, Ordering::Release); - } + pub unsafe fn set_soundness(_: Soundness) {} - /// Obtains the soundness of obtaining the local UTC offset. If it is [`Soundness::Unsound`], - /// it is allowed to invoke undefined behavior when obtaining the local UTC offset. pub fn get_soundness() -> Soundness { - match LOCAL_OFFSET_IS_SOUND.load(Ordering::Acquire) { - false => Soundness::Unsound, - true => Soundness::Sound, - } + Soundness::Sound } }