Skip to content

Commit

Permalink
Auto merge of #3461 - tiif:add_localtime_r_shim, r=RalfJung
Browse files Browse the repository at this point in the history
Add localtime_r shim

- Implement ``localtime_r`` shim as mentioned in #2057

Note:
- ``tm_zone``, ``tm_gmtoff`` might not be consistent with ``libc::localtime_r`` as custom implementation is provided through ``chrono``. Due to the lack of daylight saving information in ``chrono``, ``is_dst`` value will always be ``-1``.
  • Loading branch information
bors committed Apr 22, 2024
2 parents 4b4e1dd + 90b408c commit 8870881
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 0 deletions.
144 changes: 144 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ smallvec = "1.7"
aes = { version = "0.8.3", features = ["hazmat"] }
measureme = "11"
ctrlc = "3.2.5"
chrono = { version = "0.4.38", default-features = false, features = ["clock"] }

# Copied from `compiler/rustc/Cargo.toml`.
# But only for some targets, it fails for others. Rustc configures this in its CI, but we can't
Expand Down
78 changes: 78 additions & 0 deletions src/shims/time.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use std::ffi::OsString;
use std::fmt::Write;
use std::time::{Duration, SystemTime};

use chrono::{DateTime, Datelike, Local, Timelike, Utc};

use crate::concurrency::thread::MachineCallback;
use crate::*;

Expand Down Expand Up @@ -107,6 +111,80 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
Ok(0)
}

// The localtime() function shall convert the time in seconds since the Epoch pointed to by
// timer into a broken-down time, expressed as a local time.
// https://linux.die.net/man/3/localtime_r
fn localtime_r(
&mut self,
timep: &OpTy<'tcx, Provenance>,
result_op: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
let this = self.eval_context_mut();

this.assert_target_os_is_unix("localtime_r");
this.check_no_isolation("`localtime_r`")?;

let timep = this.deref_pointer(timep)?;
let result = this.deref_pointer_as(result_op, this.libc_ty_layout("tm"))?;

// The input "represents the number of seconds elapsed since the Epoch,
// 1970-01-01 00:00:00 +0000 (UTC)".
let sec_since_epoch: i64 = this
.read_scalar(&timep)?
.to_int(this.libc_ty_layout("time_t").size)?
.try_into()
.unwrap();
let dt_utc: DateTime<Utc> =
DateTime::from_timestamp(sec_since_epoch, 0).expect("Invalid timestamp");
// Convert that to local time, then return the broken-down time value.
let dt: DateTime<Local> = DateTime::from(dt_utc);

// This value is always set to -1, because there is no way to know if dst is in effect with
// chrono crate yet.
// This may not be consistent with libc::localtime_r's result.
let tm_isdst = -1;

// tm_zone represents the timezone value in the form of: +0730, +08, -0730 or -08.
// This may not be consistent with libc::localtime_r's result.
let offset_in_second = Local::now().offset().local_minus_utc();
let tm_gmtoff = offset_in_second;
let mut tm_zone = String::new();
if offset_in_second < 0 {
tm_zone.push('-');
} else {
tm_zone.push('+');
}
let offset_hour = offset_in_second.abs() / 3600;
write!(tm_zone, "{:02}", offset_hour).unwrap();
let offset_min = (offset_in_second.abs() % 3600) / 60;
if offset_min != 0 {
write!(tm_zone, "{:02}", offset_min).unwrap();
}

// FIXME: String de-duplication is needed so that we only allocate this string only once
// even when there are multiple calls to this function.
let tm_zone_ptr =
this.alloc_os_str_as_c_str(&OsString::from(tm_zone), MiriMemoryKind::Machine.into())?;

this.write_pointer(tm_zone_ptr, &this.project_field_named(&result, "tm_zone")?)?;
this.write_int_fields_named(
&[
("tm_sec", dt.second().into()),
("tm_min", dt.minute().into()),
("tm_hour", dt.hour().into()),
("tm_mday", dt.day().into()),
("tm_mon", dt.month0().into()),
("tm_year", dt.year().checked_sub(1900).unwrap().into()),
("tm_wday", dt.weekday().num_days_from_sunday().into()),
("tm_yday", dt.ordinal0().into()),
("tm_isdst", tm_isdst),
("tm_gmtoff", tm_gmtoff.into()),
],
&result,
)?;

Ok(result.ptr())
}
#[allow(non_snake_case, clippy::arithmetic_side_effects)]
fn GetSystemTimeAsFileTime(
&mut self,
Expand Down
5 changes: 5 additions & 0 deletions src/shims/unix/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let result = this.gettimeofday(tv, tz)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"localtime_r" => {
let [timep, result_op] = this.check_shim(abi, Abi::C {unwind: false}, link_name, args)?;
let result = this.localtime_r(timep, result_op)?;
this.write_pointer(result, dest)?;
}
"clock_gettime" => {
let [clk_id, tp] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
Expand Down
45 changes: 45 additions & 0 deletions tests/pass-dep/shims/libc-misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,50 @@ fn test_posix_gettimeofday() {
assert_eq!(is_error, -1);
}

fn test_localtime_r() {
use std::ffi::CStr;
use std::{env, ptr};

// Set timezone to GMT.
let key = "TZ";
env::set_var(key, "GMT");

const TIME_SINCE_EPOCH: libc::time_t = 1712475836;
let custom_time_ptr = &TIME_SINCE_EPOCH;
let mut tm = libc::tm {
tm_sec: 0,
tm_min: 0,
tm_hour: 0,
tm_mday: 0,
tm_mon: 0,
tm_year: 0,
tm_wday: 0,
tm_yday: 0,
tm_isdst: 0,
tm_gmtoff: 0,
tm_zone: std::ptr::null_mut::<libc::c_char>(),
};
let res = unsafe { libc::localtime_r(custom_time_ptr, &mut tm) };

assert_eq!(tm.tm_sec, 56);
assert_eq!(tm.tm_min, 43);
assert_eq!(tm.tm_hour, 7);
assert_eq!(tm.tm_mday, 7);
assert_eq!(tm.tm_mon, 3);
assert_eq!(tm.tm_year, 124);
assert_eq!(tm.tm_wday, 0);
assert_eq!(tm.tm_yday, 97);
assert_eq!(tm.tm_isdst, -1);
assert_eq!(tm.tm_gmtoff, 0);
unsafe { assert_eq!(CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00") };

// The returned value is the pointer passed in.
assert!(ptr::eq(res, &mut tm));

//Remove timezone setting.
env::remove_var(key);
}

fn test_isatty() {
// Testing whether our isatty shim returns the right value would require controlling whether
// these streams are actually TTYs, which is hard.
Expand Down Expand Up @@ -365,6 +409,7 @@ fn main() {
test_posix_realpath_errors();

test_thread_local_errno();
test_localtime_r();

test_isatty();

Expand Down

0 comments on commit 8870881

Please sign in to comment.