Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor MacOS implementation a lot #67

Merged
merged 7 commits into from
Oct 8, 2022
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 106 additions & 54 deletions src/tz_macos.rs
Original file line number Diff line number Diff line change
@@ -1,80 +1,132 @@
use core_foundation_sys::base::{Boolean, CFRange, CFRelease, CFTypeRef};
use core_foundation_sys::string::{
kCFStringEncodingUTF8, CFStringGetBytes, CFStringGetCStringPtr, CFStringGetLength,
};
use core_foundation_sys::timezone::{CFTimeZoneCopySystem, CFTimeZoneGetName};

pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
unsafe { get_timezone().ok_or(crate::GetTimezoneError::OsError) }
get_timezone().ok_or(crate::GetTimezoneError::OsError)
}

#[inline]
unsafe fn get_timezone() -> Option<String> {
fn get_timezone() -> Option<String> {
// The longest name in the IANA time zone database is 25 ASCII characters long.
const MAX_LEN: usize = 32;
let mut buf = [0; MAX_LEN];

// Get system time zone, and borrow its name.
let tz = Dropping::new(CFTimeZoneCopySystem())?;
let name = CFTimeZoneGetName(tz.0);
if name.is_null() {
return None;
}
let tz = system_time_zone::SystemTimeZone::new()?;
let name = tz.name()?;

// If the name is encoded in UTF-8, copy it directly.
let cstr = CFStringGetCStringPtr(name, kCFStringEncodingUTF8);
if !cstr.is_null() {
let cstr = std::ffi::CStr::from_ptr(cstr);
if let Ok(name) = cstr.to_str() {
return Some(name.to_owned());
}
}

// Otherwise convert the name to UTF-8.
let mut buf = [0; MAX_LEN];
let mut buf_bytes = 0;
let range = CFRange {
location: 0,
length: CFStringGetLength(name),
let name = if let Some(name) = name.as_utf8() {
name
} else {
// Otherwise convert the name to UTF-8.
name.to_utf8(&mut buf)?
};
if CFStringGetBytes(
name,
range,
kCFStringEncodingUTF8,
b'\0',
false as Boolean,
buf.as_mut_ptr(),
buf.len() as isize,
&mut buf_bytes,
) != range.length
{
// Could not convert the name.
None
} else if buf_bytes < 1 || buf_bytes >= MAX_LEN as isize {

if name.is_empty() || name.len() >= MAX_LEN {
// The name should not be empty, or excessively long.
None
} else {
// Convert the name to a `String`.
let name = core::str::from_utf8(&buf[..buf_bytes as usize]).ok()?;
Some(name.to_owned())
}
}

struct Dropping<T>(*const T);
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};

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 {
pub(crate) fn new() -> Option<Self> {
// SAFETY: No invariants to uphold. We'll release the pointer when we don't need it anymore.
let v: CFTimeZoneRef = unsafe { CFTimeZoneCopySystem() };
if v.is_null() {
None
} else {
Some(SystemTimeZone(v))
}
}

impl<T> Drop for Dropping<T> {
#[inline]
fn drop(&mut self) {
unsafe { CFRelease(self.0 as CFTypeRef) };
pub(crate) fn name(&self) -> Option<super::string_ref::StringRef<Self>> {
astraw marked this conversation as resolved.
Show resolved Hide resolved
// SAFETY: `SystemTimeZone` is only ever created with a valid `CFTimeZoneRef`.
let string = unsafe { CFTimeZoneGetName(self.0) };
if string.is_null() {
None
} else {
Some(super::string_ref::StringRef::new(string, self))
}
}
}
}

impl<T> Dropping<T> {
#[inline]
unsafe fn new(v: *const T) -> Option<Self> {
if v.is_null() {
mod string_ref {
//! create safe wrapper around `CFStringRef`

use core_foundation_sys::{
base::{Boolean, CFRange},
string::{
kCFStringEncodingUTF8, CFStringGetBytes, CFStringGetCStringPtr, CFStringGetLength,
CFStringRef,
},
};

pub(crate) struct StringRef<'a, T> {
string: CFStringRef,
_parent: &'a T,
astraw marked this conversation as resolved.
Show resolved Hide resolved
}

impl<'a, T> StringRef<'a, T> {
pub(crate) fn new(string: CFStringRef, _parent: &'a T) -> Self {
astraw marked this conversation as resolved.
Show resolved Hide resolved
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.
let v = unsafe { std::ffi::CStr::from_ptr(v) };
if let Ok(v) = v.to_str() {
return Some(v);
}
}
None
} else {
Some(Dropping(v))
}

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,
};

let converted_bytes = unsafe {
// SAFETY: `StringRef` is only ever created with a valid `CFStringRef`.
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 || buf_bytes < 0 || buf_bytes as usize > buf.len() {
None
} else {
std::str::from_utf8(&buf[..buf_bytes as usize]).ok()
}
astraw marked this conversation as resolved.
Show resolved Hide resolved
}
}
}