use crate::ffi_utils::buffer::{tzname_buf, MAX_LEN}; pub(crate) fn get_timezone_inner() -> Result { get_timezone().ok_or(crate::GetTimezoneError::OsError) } #[inline] fn get_timezone() -> Option { let mut buf = tzname_buf(); // Get system time zone, and borrow its name. let tz = system_time_zone::SystemTimeZone::new()?; let name = tz.name()?; // If the name is encoded in UTF-8, copy it directly. let name = if let Some(name) = name.as_utf8() { name } else { // Otherwise convert the name to UTF-8. name.to_utf8(&mut buf)? }; if name.is_empty() || name.len() > MAX_LEN { // The name should not be empty, or excessively long. None } else { Some(name.to_owned()) } } mod system_time_zone { //! create a safe wrapper around `CFTimeZoneRef` use core_foundation_sys::base::{CFRelease, CFTypeRef}; use core_foundation_sys::timezone::{ CFTimeZoneCopySystem, CFTimeZoneGetName, CFTimeZoneRef, CFTimeZoneResetSystem, }; pub(crate) struct SystemTimeZone(CFTimeZoneRef); impl Drop for SystemTimeZone { fn drop(&mut self) { // SAFETY: `SystemTimeZone` is only ever created with a valid `CFTimeZoneRef`. unsafe { CFRelease(self.0 as CFTypeRef) }; } } impl SystemTimeZone { /// Creates a new `SystemTimeZone` by querying the current Darwin system /// timezone. /// /// This function implicitly calls `CFTimeZoneResetSystem` to invalidate /// the cached timezone and ensure we always retrieve the current system /// timezone. /// /// Due to CoreFoundation's internal caching mechanism, subsequent calls /// to `CFTimeZoneCopySystem` do not reflect system timezone changes /// made while the process is running. Thus, we explicitly call /// `CFTimeZoneResetSystem` first to invalidate the cached value and /// ensure we always retrieve the current system timezone. pub(crate) fn new() -> Option { // SAFETY: // - Both `CFTimeZoneResetSystem` and `CFTimeZoneCopySystem` are FFI // calls to macOS CoreFoundation. // - `CFTimeZoneResetSystem` safely invalidates the cached timezone // without any external invariants. // - The pointer returned by `CFTimeZoneCopySystem` is managed and // released properly within `Drop`. let v: CFTimeZoneRef = unsafe { // First, clear the potentially cached timezone. This call will // take the global lock on the timezone data. // // See // for context on why we reset the timezone here. CFTimeZoneResetSystem(); // Fetch the current value. This will likely allocate. This call // will again take the global lock on the timezone data. CFTimeZoneCopySystem() }; if v.is_null() { None } else { Some(SystemTimeZone(v)) } } /// Get the time zone name as a [`StringRef`]. /// /// The lifetime of the `StringRef` is bound to our lifetime. Mutable /// access is also prevented by taking a reference to `self`. /// /// [`StringRef`]: super::string_ref::StringRef pub(crate) fn name(&self) -> Option> { // SAFETY: `SystemTimeZone` is only ever created with a valid `CFTimeZoneRef`. let string = unsafe { CFTimeZoneGetName(self.0) }; if string.is_null() { None } else { // SAFETY: here we ensure that `string` is a valid pointer. Some(unsafe { super::string_ref::StringRef::new(string, self) }) } } } } mod string_ref { //! create safe wrapper around `CFStringRef` use core::convert::TryInto; use core_foundation_sys::base::{Boolean, CFRange}; use core_foundation_sys::string::{ kCFStringEncodingUTF8, CFStringGetBytes, CFStringGetCStringPtr, CFStringGetLength, CFStringRef, }; pub(crate) struct StringRef<'a, T> { string: CFStringRef, // We exclude mutable access to the parent by taking a reference to the // parent (rather than, for example, just using a marker to enforce the // parent's lifetime). _parent: &'a T, } impl<'a, T> StringRef<'a, T> { // SAFETY: `string` must be valid pointer pub(crate) unsafe fn new(string: CFStringRef, _parent: &'a T) -> Self { Self { string, _parent } } pub(crate) fn as_utf8(&self) -> Option<&'a str> { // SAFETY: `StringRef` is only ever created with a valid `CFStringRef`. let v = unsafe { CFStringGetCStringPtr(self.string, kCFStringEncodingUTF8) }; if !v.is_null() { // SAFETY: `CFStringGetCStringPtr()` returns NUL-terminated // strings and will return NULL if the internal representation // of the `CFString`` is not compatible with the requested // encoding. let v = unsafe { std::ffi::CStr::from_ptr(v) }; if let Ok(v) = v.to_str() { return Some(v); } } None } pub(crate) fn to_utf8<'b>(&self, buf: &'b mut [u8]) -> Option<&'b str> { // SAFETY: `StringRef` is only ever created with a valid `CFStringRef`. let length = unsafe { CFStringGetLength(self.string) }; let mut buf_bytes = 0; let range = CFRange { location: 0, length, }; // SAFETY: `StringRef` is only ever created with a valid `CFStringRef`. let converted_bytes = unsafe { CFStringGetBytes( self.string, range, kCFStringEncodingUTF8, b'\0', false as Boolean, buf.as_mut_ptr(), buf.len() as isize, &mut buf_bytes, ) }; if converted_bytes != length { return None; } let len = buf_bytes.try_into().ok()?; let s = buf.get(..len)?; std::str::from_utf8(s).ok() } } }