From 4e503fbb226681378132b5e278ee119302149f75 Mon Sep 17 00:00:00 2001 From: Eduardo Rodrigues <16357187+eduardomourar@users.noreply.github.com> Date: Fri, 7 Aug 2026 02:07:50 +0100 Subject: [PATCH] fix(mktemp): add wasi platform support for temporary directory handling - Add conditional compilation for WASI target to avoid panics from `std::env::temp_dir()` - Implement fallback logic in `Options` to read TMPDIR environment variable directly on WASI - Update `get_tmpdir_env_or_default()` with WASI-specific handling for temp directory resolution - Add `#[cfg_attr]` annotations to skip host path-dependent tests on WASI sandbox environment - Ignore 14 tests that require host filesystem access when running under wasi_runner - Ignore directory permission test on WASI due to lack of `chmod` support and mode parameter limitations --- .github/workflows/wasi.yml | 2 +- src/uu/mktemp/src/mktemp.rs | 15 ++++++++++++++- tests/by-util/test_install.rs | 20 ++++++++++++++++++-- tests/by-util/test_mktemp.rs | 16 ++++++++++++++++ 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/.github/workflows/wasi.yml b/.github/workflows/wasi.yml index 5072ccc573b..87943cc2f40 100644 --- a/.github/workflows/wasi.yml +++ b/.github/workflows/wasi.yml @@ -71,7 +71,7 @@ jobs: test_base32:: test_base64:: test_basenc:: test_basename:: \ test_comm:: test_cut:: test_dirname:: test_echo:: \ test_expand:: test_factor:: test_false:: test_fold:: \ - test_head:: test_link:: test_ln:: test_nl:: test_numfmt:: \ + test_head:: test_link:: test_ln:: test_mktemp:: test_nl:: test_numfmt:: \ test_od:: test_paste:: test_printf:: test_shuf:: test_sum:: \ test_tee:: test_tr:: test_true:: test_truncate:: \ test_unexpand:: test_unlink:: test_wc:: test_yes:: diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index 1f872714f09..f2efc8e6c1a 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -143,7 +143,13 @@ impl Options { } else if matches.get_flag(OPT_T) || matches.contains_id(OPT_TMPDIR) { // If --tmpdir is given without an argument, or -t is given // export in TMPDIR - Some(env::temp_dir()) + #[cfg(target_os = "wasi")] + // WASI's `std::env::temp_dir()` unconditionally panics + let default_tmp_dir = env::var_os(TMPDIR_ENV_VAR) + .map_or_else(|| PathBuf::from(FALLBACK_TMPDIR), PathBuf::from); + #[cfg(not(target_os = "wasi"))] + let default_tmp_dir = env::temp_dir(); + Some(default_tmp_dir) } else { None }; @@ -629,6 +635,13 @@ fn exec(dir: &Path, prefix: &str, rand: usize, suffix: &str, make_dir: bool) -> fn get_tmpdir_env_or_default() -> PathBuf { match env::var_os(TMPDIR_ENV_VAR) { Some(val) if val.is_empty() => PathBuf::from(FALLBACK_TMPDIR), + // WASI's `std::env::temp_dir()` unconditionally panics, + // so read `TMPDIR` directly. + #[cfg(target_os = "wasi")] + Some(val) => PathBuf::from(val), + #[cfg(target_os = "wasi")] + None => PathBuf::from(FALLBACK_TMPDIR), + #[cfg(not(target_os = "wasi"))] _ => env::temp_dir(), } } diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index f4e98f1b7a4..145aa306081 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -789,7 +789,15 @@ fn test_install_and_strip() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - at.write("strip", STRIP_PROGRAM); + // Write the strip script and sync to disk to avoid ETXTBSY race on some + // platforms (observed on ARM64 Linux CI runners). + let strip_path = at.plus("strip"); + { + use std::io::Write; + let mut f = fs::File::create(&strip_path).unwrap(); + f.write_all(STRIP_PROGRAM.as_bytes()).unwrap(); + f.sync_all().unwrap(); + } at.set_mode("strip", 0o755); at.write("source", "file contents"); let path = format!( @@ -830,7 +838,15 @@ fn test_install_and_strip_with_program() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - at.write("strip-program", STRIP_PROGRAM); + // Write the strip script and sync to disk to avoid ETXTBSY race on some + // platforms (observed on ARM64 Linux CI runners). + let strip_path = at.plus("strip-program"); + { + use std::io::Write; + let mut f = fs::File::create(&strip_path).unwrap(); + f.write_all(STRIP_PROGRAM.as_bytes()).unwrap(); + f.sync_all().unwrap(); + } at.set_mode("strip-program", 0o755); at.write("source", "file contents"); diff --git a/tests/by-util/test_mktemp.rs b/tests/by-util/test_mktemp.rs index 0fa2896bb6f..0a9b439871c 100644 --- a/tests/by-util/test_mktemp.rs +++ b/tests/by-util/test_mktemp.rs @@ -118,6 +118,7 @@ fn test_mktemp_mktemp() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_mktemp_mktemp_t() { let scene = TestScenario::new(util_name!()); @@ -399,6 +400,7 @@ fn test_mktemp_suffix() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_mktemp_tmpdir() { let scene = TestScenario::new(util_name!()); let dir = tempdir().unwrap(); @@ -462,6 +464,7 @@ fn test_mktemp_tmpdir() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_mktemp_empty_tmpdir() { let scene = TestScenario::new(util_name!()); let pathname = scene.fixtures.as_string(); @@ -482,6 +485,7 @@ fn test_mktemp_empty_tmpdir() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_mktemp_tmpdir_one_arg() { let scene = TestScenario::new(util_name!()); @@ -495,6 +499,7 @@ fn test_mktemp_tmpdir_one_arg() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_mktemp_directory_tmpdir() { let scene = TestScenario::new(util_name!()); @@ -599,6 +604,11 @@ fn test_respect_template_directory() { #[cfg(unix)] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: path_create_directory has no mode parameter and chmod returns ENOSYS, \ + so directories can't be created with restricted permissions" +)] fn test_directory_permissions() { let (at, mut ucmd) = at_and_ucmd!(); let result = ucmd.args(&["-d", "XXX"]).succeeds(); @@ -869,6 +879,7 @@ fn test_nonexistent_tmpdir_env_var() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_empty_tmpdir_env_var() { #[cfg(not(any(windows, target_os = "android")))] { @@ -967,11 +978,13 @@ fn test_nonexistent_dir_prefix() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_default_missing_value() { new_ucmd!().arg("-d").arg("--tmpdir").succeeds(); } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_default_issue_4821_t_tmpdir() { let scene = TestScenario::new(util_name!()); let pathname = scene.fixtures.as_string(); @@ -987,6 +1000,7 @@ fn test_default_issue_4821_t_tmpdir() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_default_issue_4821_t_tmpdir_p() { let scene = TestScenario::new(util_name!()); let pathname = scene.fixtures.as_string(); @@ -1003,6 +1017,7 @@ fn test_default_issue_4821_t_tmpdir_p() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_t_ensure_tmpdir_has_higher_priority_than_p() { let scene = TestScenario::new(util_name!()); let pathname = scene.fixtures.as_string(); @@ -1183,6 +1198,7 @@ fn test_non_utf8_tmpdir_directory_creation() { #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_mktemp_hidden_file_single_dot() { let scene = TestScenario::new(util_name!()); let dir = tempdir().unwrap();