use std::io;
use libc::time_t;
use libc::c_char;
extern "C" {
fn rl_localtime_r(sec: *const time_t, out: *mut libc::tm) -> *mut libc::tm;
fn rl_timegm(tm: *mut libc::tm) -> time_t;
fn rl_mktime(tm: *mut libc::tm) -> time_t;
}
pub fn localtime(sec: time_t) -> io::Result<libc::tm> {
unsafe {
let mut out = std::mem::zeroed();
if rl_localtime_r(&sec, &mut out).is_null() {
return Err(io::Error::last_os_error());
}
Ok(out)
}
}
pub fn timegm(mut tm: libc::tm) -> time_t {
unsafe {
rl_timegm(&mut tm)
}
}
pub fn mktime(mut tm: libc::tm) -> time_t {
unsafe {
rl_mktime(&mut tm)
}
}
#[repr(C)]
struct COsString {
ptr: *const c_char,
len: usize,
capacity: usize,
}
impl COsString {
fn null() -> Self {
COsString {
ptr: std::ptr::null(),
len: 0,
capacity: 0,
}
}
fn empty() -> Self {
static EMPTY: c_char = 0;
COsString {
ptr: &EMPTY,
len: 1,
capacity: 0,
}
}
unsafe fn dealloc(self) {
if self.capacity > 0 {
Vec::from_raw_parts(self.ptr as *mut u8, self.len, self.capacity);
}
}
}
impl From<std::ffi::OsString> for COsString {
fn from(value: std::ffi::OsString) -> Self {
use std::os::unix::ffi::OsStringExt;
let mut vec = value.into_vec();
if !vec.is_empty() {
vec.push(0);
let ptr = vec.as_mut_ptr();
let len = vec.len();
let capacity = vec.capacity();
std::mem::forget(vec);
COsString {
ptr: ptr as *const c_char,
len,
capacity,
}
} else {
COsString::empty()
}
}
}
impl From<Option<std::ffi::OsString>> for COsString {
fn from(value: Option<std::ffi::OsString>) -> Self {
value
.map(Into::into)
.unwrap_or(COsString::null())
}
}
#[no_mangle]
extern "C" fn rust_getenv(name: *const c_char, name_len: usize) -> COsString {
use std::os::unix::ffi::OsStrExt;
use std::ffi::OsStr;
let name = unsafe {
let name = std::slice::from_raw_parts(name as *const u8, name_len);
OsStr::from_bytes(name)
};
std::env::var_os(name).into()
}
#[no_mangle]
extern "C" fn rust_os_string_dealloc(string: COsString) {
unsafe {
string.dealloc();
}
}
#[cfg(test)]
mod tests {
#[test]
fn basic_test() {
std::env::set_var("TZ", "");
let time = super::localtime(0).unwrap();
assert_eq!(time.tm_sec, 0);
assert_eq!(time.tm_min, 0);
assert_eq!(time.tm_hour, 0);
assert_eq!(time.tm_mday, 1);
assert_eq!(time.tm_mon, 0);
assert_eq!(time.tm_year, 70);
assert_eq!(time.tm_yday, 0);
assert_eq!(time.tm_wday, 4);
assert_eq!(time.tm_gmtoff, 0);
assert!(time.tm_isdst < 1);
let setter_thread = std::thread::spawn(|| {
for _ in 0..1000000 {
std::env::set_var("TZ", "");
}
});
for _ in 0..1000000 {
super::localtime(0).unwrap();
}
setter_thread.join().unwrap();
assert_eq!(super::timegm(time), 0);
}
}