Skip to content

Commit 7cc713f

Browse files
committed
fix(wasi): date clock resolution and system datetime support
date implements get_clock_resolution via libc::clock_getres (rustix's excludes WASI) and set_system_datetime for WASI, the latter reporting "not supported" since the sandbox has no wall-clock-set syscall.
1 parent 6cdc202 commit 7cc713f

7 files changed

Lines changed: 145 additions & 6 deletions

File tree

.github/workflows/wasi.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,15 @@ jobs:
6363
# caret diagnostics; the host test crate does have them, hence the
6464
# not(wasi_runner) in the cfg guarding every `mod diagnostics`.
6565
# TODO: add integration tests for these tools as WASI support is extended:
66-
# arch b2sum cksum csplit date dir dircolors fmt join
66+
# arch b2sum cat cksum cp csplit date dir dircolors fmt join
6767
# ls md5sum mkdir mv nproc pathchk pr printenv ptx pwd readlink
6868
# realpath rm rmdir seq sha1sum sha224sum sha256sum sha384sum
6969
# sha512sum shred sleep sort split tsort uname uniq vdir
7070
UUTESTS_BINARY_PATH="$(pwd)/target/${{ matrix.job.target }}/debug/coreutils.wasm" \
7171
UUTESTS_WASM_RUNNER=wasmtime \
7272
cargo test --test tests -- \
7373
test_base32:: test_base64:: test_basenc:: test_basename:: \
74-
test_cat:: test_comm:: test_cp:: test_cut:: test_dirname:: test_echo:: \
74+
test_cat:: test_comm:: test_cp:: test_cut:: test_date:: test_dirname:: test_echo:: \
7575
test_expand:: test_expr:: test_factor:: test_false:: test_fold:: \
7676
test_head:: test_link:: test_ln:: test_nl:: test_numfmt:: \
7777
test_od:: test_paste:: test_printf:: test_shuf:: test_sum:: \

src/uu/date/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ rustix = { workspace = true, features = ["time"] }
5050
# no environment injection).
5151
jiff-tzdb = "0.1"
5252

53+
[target.'cfg(target_os = "wasi")'.dependencies]
54+
libc = { workspace = true }
55+
5356
[target.'cfg(windows)'.dependencies]
5457
windows-sys = { workspace = true, features = [
5558
"Win32_Foundation",

src/uu/date/locales/en-US.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ date-error-expected-file-got-directory = expected file, got directory {$path}
103103
date-error-date-overflow = date overflow '{$date}'
104104
date-error-setting-date-not-supported-macos = setting the date is not supported by macOS
105105
date-error-setting-date-not-supported-redox = setting the date is not supported by Redox
106+
date-error-setting-date-not-supported-wasi = setting the date is not supported by WASI
106107
date-error-cannot-set-date = cannot set date
107108
date-error-extra-operand = extra operand '{$operand}'
108109
date-error-write = write error: {$error}

src/uu/date/locales/fr-FR.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ date-error-expected-file-got-directory = fichier attendu, répertoire obtenu {$p
9898
date-error-date-overflow = débordement de date '{$date}'
9999
date-error-setting-date-not-supported-macos = la définition de la date n'est pas prise en charge par macOS
100100
date-error-setting-date-not-supported-redox = la définition de la date n'est pas prise en charge par Redox
101+
date-error-setting-date-not-supported-wasi = la définition de la date n'est pas prise en charge par WASI
101102
date-error-cannot-set-date = impossible de définir la date
102103
date-error-extra-operand = opérande supplémentaire '{$operand}'
103104
date-error-write = erreur d'écriture: {$error}

src/uu/date/src/date.rs

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1261,7 +1261,23 @@ fn parse_date<S: AsRef<str>>(
12611261
}
12621262
}
12631263

1264-
#[cfg(not(any(unix, windows)))]
1264+
#[cfg(target_os = "wasi")]
1265+
/// Returns the resolution of the system's realtime clock.
1266+
///
1267+
/// `rustix::time::clock_getres` excludes WASI, so call `libc::clock_getres`
1268+
/// (available on WASI) directly instead.
1269+
fn get_clock_resolution() -> Timestamp {
1270+
let timespec = unsafe {
1271+
let mut timespec: libc::timespec = std::mem::zeroed();
1272+
libc::clock_getres(libc::CLOCK_REALTIME, &raw mut timespec);
1273+
timespec
1274+
};
1275+
1276+
#[allow(clippy::unnecessary_cast, reason = "needed for 32 bit target")]
1277+
Timestamp::constant(timespec.tv_sec as _, timespec.tv_nsec as _)
1278+
}
1279+
1280+
#[cfg(not(any(unix, windows, target_os = "wasi")))]
12651281
fn get_clock_resolution() -> Timestamp {
12661282
unimplemented!("getting clock resolution not implemented (unsupported target)");
12671283
}
@@ -1300,7 +1316,7 @@ fn get_clock_resolution() -> Timestamp {
13001316
Timestamp::constant(0, 100)
13011317
}
13021318

1303-
#[cfg(not(any(unix, windows)))]
1319+
#[cfg(not(any(unix, windows, target_os = "wasi")))]
13041320
fn set_system_datetime(_date: Zoned) -> UResult<()> {
13051321
unimplemented!("setting date not implemented (unsupported target)");
13061322
}
@@ -1319,6 +1335,15 @@ fn set_system_datetime(_date: Zoned) -> UResult<()> {
13191335
Err(Box::new(DateError::SettingDateNotSupportedMacOs))
13201336
}
13211337

1338+
#[cfg(target_os = "wasi")]
1339+
/// The WASI sandbox has no syscall for setting the wall clock.
1340+
fn set_system_datetime(_date: Zoned) -> UResult<()> {
1341+
Err(USimpleError::new(
1342+
1,
1343+
translate!("date-error-setting-date-not-supported-wasi"),
1344+
))
1345+
}
1346+
13221347
#[cfg(target_os = "redox")]
13231348
fn set_system_datetime(_date: Zoned) -> UResult<()> {
13241349
Err(Box::new(DateError::SettingDateNotSupportedRedox))
@@ -1471,4 +1496,39 @@ mod tests {
14711496
assert_eq!(strip_parenthesized_comments("a(b(c)d"), "a"); // Nested unbalanced
14721497
assert_eq!(strip_parenthesized_comments("a(b)c(d)e(f"), "ace"); // Multiple groups, last unmatched
14731498
}
1499+
1500+
#[test]
1501+
fn test_escape_invalid_bytes() {
1502+
// Printable ASCII preserved, boundaries escaped
1503+
assert_eq!(escape_invalid_bytes(b"hello"), "hello");
1504+
assert_eq!(escape_invalid_bytes(b""), "");
1505+
// High-bit, control chars, DEL, and backslash all escaped as octal
1506+
assert_eq!(escape_invalid_bytes(b"\xb0"), "\\260");
1507+
assert_eq!(escape_invalid_bytes(b"\x00"), "\\000");
1508+
assert_eq!(escape_invalid_bytes(b"\x7f"), "\\177");
1509+
assert_eq!(escape_invalid_bytes(b"\\"), "\\134");
1510+
// Mixed content
1511+
assert_eq!(escape_invalid_bytes(b"a\xb0b\\c"), "a\\260b\\134c");
1512+
}
1513+
1514+
#[test]
1515+
fn test_get_clock_resolution() {
1516+
let res = get_clock_resolution();
1517+
assert!(res.as_second() >= 0 && res.as_second() <= 1);
1518+
assert!(res.as_second() > 0 || res.subsec_nanosecond() > 0);
1519+
}
1520+
1521+
#[test]
1522+
fn test_convert_for_set() {
1523+
let date = "2025-03-15T10:30:00+05:00[Asia/Karachi]"
1524+
.parse::<Zoned>()
1525+
.unwrap();
1526+
// UTC mode converts to UTC
1527+
let utc = convert_for_set(date.clone(), true);
1528+
assert_eq!(utc.time_zone(), &TimeZone::UTC);
1529+
assert_eq!((utc.hour(), utc.minute()), (5, 30));
1530+
// Local mode returns unchanged
1531+
let local = convert_for_set(date.clone(), false);
1532+
assert_eq!((local.hour(), local.minute()), (date.hour(), date.minute()));
1533+
}
14741534
}

tests/by-util/test_date.rs

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,10 @@ fn test_date_utc_with_d_flag() {
386386
}
387387

388388
#[test]
389+
#[cfg_attr(
390+
wasi_runner,
391+
ignore = "WASI sandbox: timezone/locale database not visible"
392+
)]
389393
fn test_date_utc_vs_local() {
390394
let cases = [
391395
("-d", "2024-01-01 12:00", "+%H:%M %Z", "12:00 EST\n"),
@@ -532,6 +536,10 @@ fn test_date_set_invalid() {
532536

533537
#[test]
534538
#[cfg(all(unix, not(any(target_vendor = "apple", target_os = "android"))))]
539+
#[cfg_attr(
540+
wasi_runner,
541+
ignore = "WASI: setting the system clock is not supported at all, not just permission-gated"
542+
)]
535543
fn test_date_set_permissions_error() {
536544
if !(geteuid().is_root() || uucore::os::is_wsl_1()) {
537545
let result = new_ucmd!()
@@ -545,6 +553,10 @@ fn test_date_set_permissions_error() {
545553

546554
#[test]
547555
#[cfg(all(unix, not(any(target_vendor = "apple", target_os = "android"))))]
556+
#[cfg_attr(
557+
wasi_runner,
558+
ignore = "WASI: setting the system clock is not supported at all, not just permission-gated"
559+
)]
548560
fn test_date_set_hyphen_prefixed_values() {
549561
// test -s flag accepts hyphen-prefixed values like "-3 days"
550562
if !(geteuid().is_root() || uucore::os::is_wsl_1()) {
@@ -565,6 +577,10 @@ fn test_date_set_hyphen_prefixed_values() {
565577

566578
#[test]
567579
#[cfg(target_vendor = "apple")]
580+
#[cfg_attr(
581+
wasi_runner,
582+
ignore = "test binary runs on macOS but the wasm guest under test does not, so the expected macOS-specific error text never appears"
583+
)]
568584
fn test_date_set_mac_unavailable() {
569585
let result = new_ucmd!()
570586
.arg("--set")
@@ -905,7 +921,7 @@ fn test_date_parse_from_format() {
905921
2023-04-15 18:30:00",
906922
);
907923
ucmd.arg("-f")
908-
.arg(at.plus(FILE))
924+
.arg(FILE)
909925
.arg("+%Y-%m-%d %H:%M:%S")
910926
.succeeds();
911927
}
@@ -933,6 +949,10 @@ const JAN2: &str = "2024-01-02 12:00:00 +0000";
933949
const JUL2: &str = "2024-07-02 12:00:00 +0000";
934950

935951
#[test]
952+
#[cfg_attr(
953+
wasi_runner,
954+
ignore = "WASI sandbox: timezone/locale database not visible"
955+
)]
936956
fn test_date_tz() {
937957
fn test_tz(tz: &str, date: &str, output: &str) {
938958
println!("Test with TZ={tz}, date=\"{date}\".");
@@ -979,6 +999,10 @@ fn test_date_tz_with_utc_flag() {
979999
}
9801000

9811001
#[test]
1002+
#[cfg_attr(
1003+
wasi_runner,
1004+
ignore = "WASI sandbox: timezone/locale database not visible"
1005+
)]
9821006
fn test_date_tz_various_formats() {
9831007
fn test_tz(tz: &str, date: &str, output: &str) {
9841008
println!("Test with TZ={tz}, date=\"{date}\".");
@@ -1007,6 +1031,10 @@ fn test_date_tz_various_formats() {
10071031
}
10081032

10091033
#[test]
1034+
#[cfg_attr(
1035+
wasi_runner,
1036+
ignore = "WASI sandbox: timezone/locale database not visible"
1037+
)]
10101038
fn test_date_tz_with_relative_time() {
10111039
new_ucmd!()
10121040
.env("TZ", "America/Vancouver")
@@ -1018,6 +1046,10 @@ fn test_date_tz_with_relative_time() {
10181046
}
10191047

10201048
#[test]
1049+
#[cfg_attr(
1050+
wasi_runner,
1051+
ignore = "WASI sandbox: timezone/locale database not visible"
1052+
)]
10211053
fn test_date_utc_time() {
10221054
// Test that -u flag shows correct UTC time
10231055
// We get 2 UTC times just in case we're really unlucky and this runs around
@@ -1514,6 +1546,10 @@ fn test_date_whitespace_between_items() {
15141546
}
15151547

15161548
#[test]
1549+
#[cfg_attr(
1550+
wasi_runner,
1551+
ignore = "WASI sandbox: timezone/locale database not visible"
1552+
)]
15171553
fn test_date_relative_m9() {
15181554
// Military timezone "m9" should be parsed as noon + 9 hours = 21:00 UTC
15191555
// When displayed in TZ=UTC+9 (which is UTC-9), this shows as 12:00 local time
@@ -1811,6 +1847,10 @@ fn test_date_locale_en_us_vs_c_difference() {
18111847

18121848
#[test]
18131849
#[cfg(unix)]
1850+
#[cfg_attr(
1851+
wasi_runner,
1852+
ignore = "WASI sandbox: timezone/locale database not visible"
1853+
)]
18141854
fn test_date_locale_hu_hungarian() {
18151855
// Regression test for uutils/coreutils#11240: the GNU modifier fast-path
18161856
// ("%-e") used to run before ICU localization, so "%b"/"%A" came out in
@@ -2102,6 +2142,10 @@ fn test_date_input_hhmm_ampm() {
21022142
}
21032143

21042144
#[test]
2145+
#[cfg_attr(
2146+
wasi_runner,
2147+
ignore = "WASI sandbox: timezone/locale database not visible"
2148+
)]
21052149
fn test_date_input_trailing_tz_abbrev_rezones() {
21062150
// `TZ=UTC+1 date -d '2024-01-01 EST'` should display the instant in UTC+1
21072151
// (GNU: 04:00:00 -01:00), not leave it in EST (the pre-fix uutils
@@ -2230,6 +2274,10 @@ fn test_date_parenthesis_vs_other_special_chars() {
22302274

22312275
#[test]
22322276
#[cfg(unix)]
2277+
#[cfg_attr(
2278+
wasi_runner,
2279+
ignore = "WASI sandbox: timezone/locale database not visible"
2280+
)]
22332281
fn test_date_iranian_locale_solar_hijri_calendar() {
22342282
// Test Iranian locale uses Solar Hijri calendar
22352283
// Verify the Solar Hijri calendar is used in the Iranian locale
@@ -2297,6 +2345,10 @@ fn test_date_iranian_locale_solar_hijri_calendar() {
22972345

22982346
#[test]
22992347
#[cfg(unix)]
2348+
#[cfg_attr(
2349+
wasi_runner,
2350+
ignore = "WASI sandbox: timezone/locale database not visible"
2351+
)]
23002352
fn test_date_ethiopian_locale_calendar() {
23012353
// Test Ethiopian locale uses Ethiopian calendar
23022354
// Verify the Ethiopian calendar is used in the Ethiopian locale
@@ -2444,6 +2496,10 @@ fn check_date(locale: &str, date: &str, fmt: &str, expected: &str) {
24442496

24452497
#[test]
24462498
#[cfg(unix)]
2499+
#[cfg_attr(
2500+
wasi_runner,
2501+
ignore = "WASI sandbox: timezone/locale database not visible"
2502+
)]
24472503
fn test_locale_calendar_conversions() {
24482504
// Persian (Solar Hijri) - Nowruz is March 20/21
24492505
for (d, e) in [
@@ -2495,6 +2551,10 @@ fn test_locale_calendar_conversions() {
24952551

24962552
#[test]
24972553
#[cfg(unix)]
2554+
#[cfg_attr(
2555+
wasi_runner,
2556+
ignore = "WASI sandbox: timezone/locale database not visible"
2557+
)]
24982558
fn test_locale_month_names() {
24992559
// %B full month names: Jan, Jun, Dec for each locale
25002560
for (loc, jan, jun, dec) in [
@@ -2515,6 +2575,10 @@ fn test_locale_month_names() {
25152575

25162576
#[test]
25172577
#[cfg(unix)]
2578+
#[cfg_attr(
2579+
wasi_runner,
2580+
ignore = "WASI sandbox: timezone/locale database not visible"
2581+
)]
25182582
fn test_locale_abbreviated_month_names() {
25192583
// %b abbreviated month names: Feb, Jun, Dec for each locale
25202584
// This test ensures we don't get double periods in locales like Hungarian
@@ -2538,6 +2602,10 @@ fn test_locale_abbreviated_month_names() {
25382602

25392603
#[test]
25402604
#[cfg(unix)]
2605+
#[cfg_attr(
2606+
wasi_runner,
2607+
ignore = "WASI sandbox: timezone/locale database not visible"
2608+
)]
25412609
fn test_locale_day_names() {
25422610
// %A full day names: Mon (26th), Sun (25th), Sat (24th) Jan 2026
25432611
for (loc, mon, sun, sat) in [
@@ -2639,8 +2707,11 @@ fn test_date_month_subtraction_keeps_day() {
26392707

26402708
// Tests for embedded timezone parsing
26412709
#[test]
2710+
#[cfg_attr(
2711+
wasi_runner,
2712+
ignore = "WASI sandbox: timezone/locale database not visible"
2713+
)]
26422714
fn test_date_embedded_timezone_conversion() {
2643-
// Parse date with embedded timezone
26442715
// Date should be interpreted in embedded TZ, then displayed in environment TZ
26452716
new_ucmd!()
26462717
.env("TZ", "UTC0")
@@ -2655,6 +2726,7 @@ fn test_date_embedded_timezone_conversion() {
26552726
// Tests for invalid UTF-8 in date string
26562727
#[test]
26572728
#[cfg(unix)]
2729+
#[cfg_attr(wasi_runner, ignore = "WASI: argv must be valid UTF-8")]
26582730
fn test_date_invalid_utf8_byte_rejected() {
26592731
use std::os::unix::ffi::OsStrExt;
26602732

tests/by-util/test_sort.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1932,6 +1932,7 @@ fn test_separator_attached_equals_double() {
19321932
new_ucmd!()
19331933
.args(&["-t==", "-k", "2"])
19341934
.pipe_in("a=b=c\n")
1935+
.ignore_stdin_write_error()
19351936
.fails()
19361937
.stderr_contains("separator must be exactly one character long: '=='");
19371938
}
@@ -1942,6 +1943,7 @@ fn test_separator_attached_equals_multi_char() {
19421943
new_ucmd!()
19431944
.args(&["-t=a", "-k", "2"])
19441945
.pipe_in("a=b=c\n")
1946+
.ignore_stdin_write_error()
19451947
.fails()
19461948
.stderr_contains("separator must be exactly one character long: '=a'");
19471949
}

0 commit comments

Comments
 (0)