diff --git a/.github/workflows/wasi.yml b/.github/workflows/wasi.yml index 5072ccc573b..7cb3709c8ca 100644 --- a/.github/workflows/wasi.yml +++ b/.github/workflows/wasi.yml @@ -25,7 +25,7 @@ jobs: matrix: job: - { target: wasm32-wasip1, rust-flags: "--cfg wasi_runner" } - - { target: wasm32-wasip2, rust-flags: "--cfg wasi_runner --cfg wasip2_runner" } + - { target: wasm32-wasip2, rust-flags: "--cfg wasi_runner --cfg wasip2_runner" } steps: - uses: actions/checkout@v7.0.1 with: @@ -50,28 +50,29 @@ jobs: UTILS=$(./util/show-utils.sh | tr ' ' '\n' | grep -vE "^($EXCLUDE)$" | sed 's/^/-p uu_/' | tr '\n' ' ') cargo test --target ${{ matrix.job.target }} --no-default-features $UTILS - name: Run integration tests via wasmtime - if: matrix.job.target == 'wasm32-wasip1' env: RUSTFLAGS: ${{ matrix.job.rust-flags }} run: | # Build the WASI binary - cargo build --target ${{ matrix.job.target }} --no-default-features --features feat_wasm + cargo build --target ${{ matrix.job.target }} --no-default-features --features feat_wasm,wasip2_exit_with_code # Run host-compiled integration tests against the WASI binary. # Tests incompatible with WASI are annotated with # #[cfg_attr(wasi_runner, ignore)] in the test source files. - # TODO: add integration tests for these tools as WASI support is extended: - # arch b2sum cat cksum cp csplit date dir dircolors fmt join - # ls md5sum mkdir mv nproc pathchk pr printenv ptx pwd readlink - # realpath rm rmdir seq sha1sum sha224sum sha256sum sha384sum - # sha512sum shred sleep sort split tail touch tsort uname uniq - # vdir UUTESTS_BINARY_PATH="$(pwd)/target/${{ matrix.job.target }}/debug/coreutils.wasm" \ UUTESTS_WASM_RUNNER=wasmtime \ + UUTESTS_WASM_RUNNER_ARGS="-S cli-exit-with-code=y" \ cargo test --test tests -- \ - 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_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:: + test_arch:: test_b2sum:: test_base32:: test_base64:: test_basenc:: \ + test_basename:: test_cat:: test_cksum:: test_comm:: test_cp:: \ + test_csplit:: test_cut:: test_date:: test_dir:: test_dircolors:: \ + test_dirname:: test_echo:: test_expand:: test_factor:: test_false:: \ + test_fmt:: test_fold:: test_head:: test_join:: test_link:: test_ln:: \ + test_ls:: test_md5sum:: test_mkdir:: test_mv:: test_nl:: test_nproc:: \ + test_numfmt:: test_od:: test_paste:: test_pathchk:: test_pr:: \ + test_printenv:: test_printf:: test_ptx:: test_pwd:: test_readlink:: \ + test_realpath:: test_rm:: test_rmdir:: test_seq:: test_sha1sum:: \ + test_sha224sum:: test_sha256sum:: test_sha384sum:: test_sha512sum:: \ + test_shred:: test_shuf:: test_sleep:: test_sort:: test_split:: \ + test_sum:: test_tail:: test_tee:: test_touch:: test_tr:: test_true:: \ + test_truncate:: test_tsort:: test_uname:: test_unexpand:: test_uniq:: \ + test_unlink:: test_vdir:: test_wc:: test_yes:: diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index 62b811ddf0f..166c0ea63db 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -138,6 +138,7 @@ ENODATA ENOENT ENOSPC ENOSYS +ENOTDIR ENOTEMPTY EOPNOTSUPP EPERM @@ -197,8 +198,10 @@ addrlen blocksize canonname chroot +cmdline dlsym execvp +fchmod fdatasync freeaddrinfo getaddrinfo @@ -218,6 +221,7 @@ inodes isatty lchown pathlen +reflink setgid setgroups settime @@ -232,6 +236,7 @@ strerror strlen syncfs umask +unflushed waitpid # * vars/nix diff --git a/Cargo.lock b/Cargo.lock index 92c5c07efaa..bb1fd424e9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,7 +77,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -88,7 +88,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -465,7 +465,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1015,7 +1015,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2041,7 +2041,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2672,7 +2672,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2905,7 +2905,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3001,7 +3001,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3011,7 +3011,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4600,6 +4600,7 @@ dependencies = [ "utmp-classic", "uucore_procs", "walkdir", + "wasip2", "wild", "winapi-util", "windows-sys 0.61.2", @@ -4684,6 +4685,15 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.117" @@ -4770,7 +4780,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5003,6 +5013,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +dependencies = [ + "bitflags 2.11.1", +] + [[package]] name = "write16" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index fa2382ab69f..62cc1fd6bac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,6 +78,11 @@ feat_systemd_logind = [ # NOTE: # * On linux, the posix-acl/acl-sys crate requires `libacl` headers and shared library to be accessible in the C toolchain at compile time. # * On FreeBSD and macOS this is not required. +# "wasip2_exit_with_code" == use the unstable `wasi:cli/exit#exit-with-code` +# function on wasm32-wasip2 so nonzero exit codes propagate correctly. +# NOTE: requires a WASI host that opts in to the unstable feature (e.g. +# wasmtime's `-S cli-exit-with-code=y`); otherwise the process traps. +wasip2_exit_with_code = ["uucore/wasip2-exit-with-code"] feat_acl = ["cp/feat_acl"] # "feat_selinux" == enable support for SELinux Security Context (by using `--features feat_selinux`) # NOTE: @@ -520,6 +525,7 @@ unexpected_cfgs = { level = "warn", check-cfg = [ 'cfg(fuzzing)', 'cfg(target_os, values("cygwin"))', 'cfg(wasi_runner)', + 'cfg(wasip2_runner)', ] } unused_qualifications = "warn" diff --git a/build.rs b/build.rs index 2b61b84d2e4..1aef6d373c6 100644 --- a/build.rs +++ b/build.rs @@ -50,7 +50,11 @@ pub fn main() { } "default" | "macos" | "unix" | "windows" | "selinux" | "zip" | "clap_complete" | "clap_mangen" | "fluent_syntax" | "openssl" => continue, // common/standard feature names - "nightly" | "test_unimplemented" | "expensive_tests" | "test_risky_names" => { + "nightly" + | "test_unimplemented" + | "expensive_tests" + | "test_risky_names" + | "wasip2_exit_with_code" => { continue; } // crate-local custom features "uudoc" => continue, // is not a utility diff --git a/docs/src/wasi-test-gaps.md b/docs/src/wasi-test-gaps.md index 4789ef03dc9..0da51af4a84 100644 --- a/docs/src/wasi-test-gaps.md +++ b/docs/src/wasi-test-gaps.md @@ -1,37 +1,183 @@ # WASI integration test gaps -Tests annotated with `#[cfg_attr(wasi_runner, ignore = "...")]` are skipped when running integration tests against a WASI binary via wasmtime. This document tracks the reasons so that gaps in WASI support are visible in one place. +Tests annotated with `#[cfg_attr(wasi_runner, ignore = "...")]` or `#[cfg_attr(wasip2_runner, ignore = "...")]` are skipped when running integration tests against a WASI binary via wasmtime. This document tracks the reasons so that gaps in WASI support are visible in one place. -To find all annotated tests: `grep -rn 'wasi_runner, ignore' tests/` +To find all annotated tests: `grep -rn 'wasi_runner, ignore\|wasip2_runner, ignore' tests/` -## Tools not yet covered by integration tests - -arch, b2sum, cat, cksum, cp, csplit, date, dir, dircolors, fmt, join, ls, md5sum, mkdir, mv, nproc, pathchk, pr, printenv, ptx, pwd, readlink, realpath, rm, rmdir, seq, sha1sum, sha224sum, sha256sum, sha384sum, sha512sum, shred, sleep, sort, split, tail, touch, tsort, uname, uniq, vdir, yes +To find the tests for a specific reason: `grep -rn '' tests/` ## WASI sandbox: host paths not visible -The WASI guest only sees directories explicitly mapped with `--dir`. Host paths like `/proc`, `/sys`, and `/dev` are not accessible. Affected tests include those that read `/proc/version`, `/proc/modules`, `/proc/cpuinfo`, `/proc/self/mem`, `/sys/kernel/profiling`, `/dev/null`, `/dev/zero`, `/dev/full`, and tests that rely on anonymous pipes or Linux-specific I/O error paths. +The WASI guest only sees directories explicitly mapped with `--dir`. Host paths outside those mappings are not accessible, so any test that reads or writes an absolute host path fails. This is the single largest gap and covers many sub-cases: + +- Generic unmapped absolute paths ("WASI sandbox: host paths not visible", "WASI sandbox: absolute host paths not visible", "WASI sandbox: cross-scenario absolute host path not visible"). +- `/dev` special files: `/dev/null`, `/dev/zero`, `/dev/random`, `/dev/console`, and `/dev` generally. +- `/proc` and `/sys`: `/proc/version`, `/proc/modules`, `/proc/cpuinfo`, `/proc/self/mem`, `/proc/1/cmdline`, `/sys/kernel/profiling`, and `/proc`/`/sys` generally. +- Locale and timezone databases, which live under host paths the guest never sees ("WASI sandbox: locale database not visible", "WASI sandbox: timezone database not visible", "WASI sandbox: timezone/locale database not visible", including the `--time-style=locale` and "host locale check passes but the wasm guest can't use it" variants). +- Path identity/display mismatches that stem from the same root cause — the guest's view of the filesystem is a virtual root, not the host's real one: `pwd`/`getcwd` report the guest's virtual root instead of the host absolute path ("WASI sandbox: pwd reports the guest's virtual root..."), canonicalized paths resolve to the virtual root instead of the host path ("WASI sandbox: canonicalized path resolves to the guest's virtual root..."), `current_directory_resolved` used to build expected hyperlink URIs differs from the host path, `/` inside the guest is its own writable root rather than the real filesystem root, and UNC-style paths resolve differently inside the guest root than on the host. ## WASI: argv/filenames must be valid UTF-8 -The WASI specification requires that argv entries and filenames are valid UTF-8. Tests that pass non-UTF-8 bytes as arguments or create files with non-UTF-8 names cannot run under WASI. +The WASI specification requires that argv entries, environment values, and filenames are valid UTF-8. Tests that pass non-UTF-8 bytes as arguments, environment values, or create files with non-UTF-8 names cannot run under WASI ("WASI: argv/filenames must be valid UTF-8", "WASI: argv must be valid UTF-8", "WASI: env values must be valid UTF-8", "WASI: preopened directories reject non-UTF-8 filenames", "WASI: non-utf8 arguments cannot be passed through the spawned test harness", "WASI preview2: OsString requires valid UTF-8, unlike unix/wasip1"). ## WASI: no FIFO/mkfifo support -WASI does not support creating or opening FIFOs (named pipes). Tests that use `mkfifo` are skipped. +WASI does not support creating or opening FIFOs (named pipes). Tests that use `mkfifo`, classify files via `FileTypeExt::is_fifo()`, or read from a FIFO (which surfaces as `EINVAL`/"Invalid seek" instead of blocking) are skipped. ## WASI: no pipe/signal support -WASI does not support Unix signals or pipe creation. Tests that rely on `SIGPIPE`, broken pipe detection, or pipe-based I/O are skipped. +WASI does not support Unix signals or pipe creation. Tests that rely on `SIGPIPE`, `SIGINT`, broken pipe detection, or pipe-based I/O are skipped ("WASI: no pipe/signal support", "WASI: no signal support", "WASI: no signal support (SIGINT)"). ## WASI: no subprocess spawning -WASI does not support spawning child processes. Tests that shell out to other commands or invoke a second binary are skipped. +WASI does not support spawning child processes. Tests that shell out to other commands, invoke a second binary, or rely on `--filter`'s process-spawning support are skipped ("WASI: no subprocess spawning", "WASI: --filter has no process-spawning support"). + +## WASI: no stdout-to-file redirection + +Tests that redirect a subprocess's stdout directly to a file outside the test harness's own plumbing are skipped; the wasmtime runner does not support this redirection path. + +## WASI: sparse/reflink/ACL copy-on-write features not supported + +`cp`'s sparse-file detection, reflink/copy-on-write (`--reflink`), and ACL preservation rely on Linux-specific filesystem features that wasmtime's virtualized filesystem does not implement. + +## WASI: follow mode (-f) is not supported on this platform + +`tail -f` and related follow-mode behavior depend on OS-level file-change notification that is not available to a WASI guest. + +## WASI: st_mode has no real permission bits + +WASI's `stat` only reports file type, not real Unix permission bits, so `st_mode` is a placeholder. Tests that assert on permission bits, mode-dependent coloring, or mode-dependent output are skipped ("WASI: st_mode has no real permission bits", "...only file-type; ls -l shows placeholder rwx", "...color/mode-dependent output differs"). + +## WASI: chmod/umask have no real effect in the guest sandbox + +`chmod` has no ENOSYS-free syscall in the WASI guest, so tests that expect `chmod` to restore write permission or make a directory read-only are skipped. Similarly `umask()` only affects the wasmtime host process, not the guest sandbox, so tests asserting on umask-influenced output are skipped. + +## WASI: sort -m spawns real OS threads for multi-file merge + +`sort -m`'s multi-file merge path spawns real OS threads, which is unsupported under wasmtime's default configuration. This also affects `--compress-program`, since `ext_sort` falls back to a single-threaded in-memory path that bypasses the external compress program entirely. + +## WASI: File::try_clone() is unsupported + +`shuf --random-source` (and similar) rely on `File::try_clone()`, which is unsupported under the WASI runtime. + +## WASI: read_link on absolute paths fails under wasmtime via spawned test harness + +`fs::read_link` on an absolute path inside the sandbox (e.g. `/file2`) returns `EPERM` when the WASI binary is launched through `std::process::Command` from the test harness, even though the same call works when wasmtime is invoked directly. This breaks `uucore::fs::canonicalize` for symlink sources, so tests that rely on following a symlink to compute a relative path are skipped ("WASI: read_link on absolute paths fails...", "WASI: read_link() not supported for symlinks"). ## WASI: stdin file position not preserved through wasmtime When stdin is a seekable file, wasmtime does not preserve the file position between the host and guest. Tests that validate stdin offset behavior after `head` reads are skipped. -## WASI: read_link on absolute paths fails under wasmtime via spawned test harness +## WASI: inode/ctime/atime metadata gaps + +Several `stat`-adjacent metadata fields are unreliable or unavailable under WASI: inode display needs a `rustix::fs::stat`-based path and is currently gated to unix only; `ctime` is unavailable via `std::fs::Metadata` on stable; access/change time tracking granularity does not match the host filesystem's; and `rustix::fs::stat` doesn't return stable inode identity across path lookups under wasmtime ("WASI preview2: rustix::fs::stat doesn't return stable inode identity..."). + +## WASI: utimensat rejects negative (pre-1970) timestamps + +Setting a file's timestamp to before the Unix epoch fails with `EINVAL` under WASI's `utimensat`, unlike native Unix. + +## WASI: setting the system clock is not supported at all + +Unlike native Unix, where changing the system clock is merely permission-gated, WASI does not support setting the system clock at all. + +## WASI: direct file descriptor manipulation not supported + +Tests that manipulate file descriptors directly (e.g. reusing a descriptor across `dup`-like operations to alias stdin/stdout to the same file) rely on primitives not supported under WASI. + +## WASI: no /dev/fd/0 support + +Without `/dev/fd/0`, a redirected-directory stdin hits the generic pipe error path (like macOS) instead of the regular-file path with the GNU-matching error message. + +## WASI: killing the wasmtime process discards the unflushed output buffer + +When a test kills the wasmtime process to check partial output, any unflushed output buffer is discarded, so streamed bytes never reach stdout the way they would with a natively killed process. + +## WASI: resource limits not supported + +Tests that use `rlimit` to constrain resources (file descriptors, address space) don't observe the expected behavior under wasmtime, since it doesn't enforce host-style resource limits inside the guest. This includes address-space-limit regression tests, for which "the WASI runner target is not suitable." + +## WASI Preview2: exit with code requires an opt-in feature + +`std::process::exit` on `wasm32-wasip2` goes through the *stable* `wasi:cli/exit#exit` +function, which only carries a success/failure bit, so every nonzero exit code +collapses to `1`. + +The [wasi:cli/exit#exit-with-code](https://github.com/WebAssembly/WASI/blob/a1fc383d01eabaf3fac01de03c0ab1a01bfdd099/proposals/cli/wit/exit.wit#L16) +function propagates the real exit code, but it is marked `@unstable` in the +WIT definition. `uucore`'s `wasip2-exit-with-code` Cargo feature (off by +default) switches `uucore::error::process_exit` to call it via the `wasip2` +crate. Because the function is unstable, any WASI host must explicitly opt +in or the process traps instead of exiting (e.g. wasmtime requires +`-S cli-exit-with-code=y`). CI builds with this feature enabled and passes +that flag, so integration tests see the real exit code. + +## WASI Preview2: OS error message text/mapping differs from native Unix + +Some I/O error paths produce a different underlying OS error, or a +different-but-equivalent error text, under WASI Preview2 than on native +Unix, even though the exit code is the same. Examples: reading a directory +as a file surfaces as `Bad file descriptor` instead of `Is a directory`; +`stat` on a dangling symlink with a trailing slash returns `ENOTDIR` instead +of `ENOENT`; `stat` on a trailing-slash path over a regular file surfaces a +raw `ENOTDIR` from the runtime instead of going through the +`CannotStatNotADirectory` path. Tests that assert on the exact error text or +error code for these paths are skipped under `wasip2_runner`/`wasi_runner`. + +## WASI P2: /dev/full filesystem not available + +`/dev/full` (a device that always reports "No space left on device" on +write) is a Linux/FreeBSD/NetBSD-specific device node not present in the +WASI Preview2 sandbox. Tests that pipe output to `/dev/full` to exercise a +write-failure path are skipped. + +## Harness/environment mismatches (not WASI capability gaps) + +A handful of skipped tests aren't blocked by a WASI capability at all — they're blocked by how the test harness or CI environment invokes the binary: + +- Tests that invoke the binary directly via a shell script or `std::process::Command`, bypassing the wasmtime runner entirely, can't run against the WASI binary as-is. +- A test that sets `HOME` to a relative path breaks wasmtime's own cache-dir resolution. +- A test that asserts on macOS-specific error text can't pass when the wasm guest under test isn't macOS, even though the test binary itself runs on macOS. +- `ls`'s open-fd-leak regression test hits wasmtime's own `--dir` sandbox fd/depth limit before reaching the 30-level depth the test is trying to probe, unrelated to whether `ls` itself leaks descriptors. +- `sort`'s buffer-size test with `u64::MAX`-sized arguments overflows differently on WASI because the wasm guest is always a 32-bit target regardless of the host's pointer width. + +## WASI: the `same-file` crate has no WASI backend + +`cp --link` on a symlinked directory needs to detect whether source and +destination are the same file. That check goes through the `same-file` +crate, which has no `wasm32-wasip1`/`wasm32-wasip2` implementation and always +returns "same-file is not supported on this platform" on that target. + +## Needs investigation + +A number of `cp`, `ls`, and `mv` tests were originally bulk-tagged with the +placeholder `ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)"` +pending a closer look. Most have since been triaged: 59 of them actually pass +under wasmtime (their `ignore` attribute has been removed) once the earlier +placeholder was replaced with real testing. Search for `needs investigation` +in `tests/by-util/test_cp.rs`, `tests/by-util/test_ls.rs`, and +`tests/by-util/test_mv.rs` to find the ones still pending. + +The tests still carrying the placeholder fall into a few buckets, none fixable +with a small coreutils-side change: -`fs::read_link` on an absolute path inside the sandbox (e.g. `/file2`) returns `EPERM` when the WASI binary is launched through `std::process::Command` from the test harness, even though the same call works when wasmtime is invoked directly. This breaks `uucore::fs::canonicalize` for symlink sources, so tests that rely on following a symlink to compute a relative path are skipped. +- **chmod/permission-mode preservation**: `cp --preserve=mode`, `-p`, `-a`, + and friends hit `ENOSYS` ("Function not implemented") when they try to + `chmod` under WASI, since there is no working `fchmod`/`chmod` syscall in + the guest sandbox. +- **File type gaps**: char devices, sockets, and other special files can't be + `stat`ed or copied (`cp: cannot stat`, `cp: ... Not supported`) because the + WASI guest doesn't expose real device/socket types. +- **`filetime` has no WASI backend**: preserving timestamps through symlinks + (`set_symlink_file_times`) hits the crate's `wasm.rs` stub, which + unconditionally returns "Wasm not implemented". +- **Debug/reflink text mismatches**: `cp --debug`'s `copy offload`/`reflink`/ + `sparse detection` fields, and the `--reflink` error message, are written + assuming a `target_os = "linux"`/`"macos"` host and don't have a WASI-specific + branch, so the text differs from what the test expects. +- **Virtual-root-relative absolute paths**: a few tests build an absolute + path via `root_dir_resolved()` or copy `.` into a sibling directory + reached through `..`; both hit the same "WASI sandbox: pwd reports the + guest's virtual root, not the host's absolute path" limitation described + above, just via a different code path (`cp: cannot stat` on the + synthesized absolute path, or `cp: cannot copy a directory ... into + itself` on the `..`-relative destination). diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index c7f8135b16c..c4126011f8b 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -9,7 +9,7 @@ use itertools::Itertools as _; use std::cmp; use std::ffi::OsString; use std::io::{self, Write}; -use std::process; +use uucore::error::process_exit; use uucore::{Args, error::strip_errno}; const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -45,7 +45,7 @@ Currently defined functions: && e.kind() != io::ErrorKind::BrokenPipe { let _ = writeln!(io::stderr(), "coreutils: {}", strip_errno(&e)); - process::exit(1); + process_exit(1); } } @@ -57,7 +57,7 @@ fn main() { let binary = validation::binary_path(&mut args); let binary_as_util = validation::name(&binary).unwrap_or_else(|| { usage(&utils, ""); - process::exit(0); + process_exit(0); }); // binary name ends with util name? @@ -88,7 +88,7 @@ fn main() { // we should fail with additional args https://github.com/uutils/coreutils/issues/11383#issuecomment-4082564058 if args.next().is_some() { let _ = writeln!(io::stderr(), "coreutils: invalid argument"); - process::exit(1); + process_exit(1); } let mut out = io::stdout().lock(); for util in utils.keys() { @@ -96,19 +96,19 @@ fn main() { && e.kind() != io::ErrorKind::BrokenPipe { let _ = writeln!(io::stderr(), "coreutils: {}", strip_errno(&e)); - process::exit(1); + process_exit(1); } } - process::exit(0); + process_exit(0); } "--version" | "-V" => { if let Err(e) = writeln!(io::stdout(), "coreutils {VERSION} (multi-call binary)") && e.kind() != io::ErrorKind::BrokenPipe { let _ = writeln!(io::stderr(), "coreutils: {}", strip_errno(&e)); - process::exit(1); + process_exit(1); } - process::exit(0); + process_exit(0); } // Not a special command: fallthrough to calling a util _ => {} @@ -121,12 +121,12 @@ fn main() { // Could be something like: // #[cfg(not(feature = "only_english"))] validation::setup_localization_or_exit(util); - process::exit(uumain(vec![util_os].into_iter().chain(args))); + process_exit(uumain(vec![util_os].into_iter().chain(args))); } // GNU coreutils --help string shows help for coreutils if util == "--help" || util == "-h" { usage(&utils, binary_as_util); - process::exit(0); + process_exit(0); } else if util.starts_with('-') { // Argument looks like an option but wasn't recognized validation::unrecognized_option(binary_as_util, &util_os); @@ -140,5 +140,5 @@ fn main() { } else { let _ = writeln!(io::stderr(), "coreutils: missing argument"); } - process::exit(1); + process_exit(1); } diff --git a/src/common/validation.rs b/src/common/validation.rs index b81cc1be074..f66053f6739 100644 --- a/src/common/validation.rs +++ b/src/common/validation.rs @@ -8,10 +8,10 @@ use std::ffi::{OsStr, OsString}; use std::io::{Write, stderr}; use std::path::{Path, PathBuf}; -use std::process; use uucore::Args; use uucore::display::Quotable; +use uucore::error::process_exit; use uucore::locale; /// Gets all available utilities including "coreutils" @@ -31,7 +31,7 @@ pub fn not_found(util: &OsStr) -> ! { "coreutils: unknown program '{}'", util.maybe_quote() ); - process::exit(1); + process_exit(1); } /// Prints an "unrecognized option" error and exits @@ -41,7 +41,7 @@ pub fn unrecognized_option(binary_name: &str, option: &OsStr) -> ! { "{binary_name}: unrecognized option '{}'", option.to_string_lossy() ); - process::exit(1); + process_exit(1); } /// Sets up localization for a utility with proper error handling @@ -55,7 +55,7 @@ pub fn setup_localization_or_exit(util_name: &str) { } => eprintln!("Localization parse error at {snippet}: {err_msg}"), other => eprintln!("Could not init the localization system: {other}"), } - process::exit(99) + process_exit(99) }); } diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index cfd236d5b8e..8a32b1ba936 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -19,7 +19,7 @@ use std::os::fd::AsFd; use std::os::unix::fs::FileTypeExt; use thiserror::Error; use uucore::display::Quotable; -use uucore::error::{UResult, strip_errno}; +use uucore::error::{UResult, process_exit, strip_errno}; use uucore::translate; use uucore::{fast_inc::fast_inc_one, format_usage}; @@ -383,7 +383,16 @@ fn cat_path(path: &OsString, options: &OutputOptions, state: &mut OutputState) - #[cfg(unix)] InputType::Socket => Err(CatError::NoSuchDeviceOrAddress), _ => { - let file = File::open(path)?; + let file = File::open(path).map_err(|e| { + let e = uucore::error::wasi_normalize_open_error(e); + match e.kind() { + ErrorKind::IsADirectory => CatError::IsDirectory, + // WASI: Check if this might be a symlink loop error (ELOOP == 32) + #[cfg(target_os = "wasi")] + _ if e.raw_os_error() == Some(32) => CatError::TooManySymlinks, + _ => CatError::from(e), + } + })?; if !is_safe_overwrite(&file, &io::stdout()) { return Err(CatError::OutputIsInput); } @@ -442,13 +451,20 @@ fn get_input_type(path: &OsString) -> CatResult { let ft = match metadata(path) { Ok(md) => md.file_type(), Err(e) => { + let e = uucore::error::wasi_normalize_open_error(e); + if e.kind() == ErrorKind::IsADirectory { + return Err(CatError::IsDirectory); + } if let Some(raw_error) = e.raw_os_error() { // On Unix-like systems, the error code for "Too many levels of symbolic links" is 40 (ELOOP). // we want to provide a proper error message in this case. - #[cfg(not(any(target_os = "macos", target_os = "freebsd")))] + #[cfg(not(any(target_os = "macos", target_os = "freebsd", target_os = "wasi")))] let too_many_symlink_code = 40; #[cfg(any(target_os = "macos", target_os = "freebsd"))] let too_many_symlink_code = 62; + // wasi-libc's ELOOP is 32, unlike the 63 used by some other targets. + #[cfg(target_os = "wasi")] + let too_many_symlink_code = 32; if raw_error == too_many_symlink_code { return Err(CatError::TooManySymlinks); } @@ -714,7 +730,7 @@ fn write_end_of_line( fn handle_broken_pipe(error: &io::Error) { // SIGPIPE is not available on Windows. if cfg!(target_os = "windows") && error.kind() == ErrorKind::BrokenPipe { - std::process::exit(13); + process_exit(13); } } diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index cf412e89be6..0b33f9ea9bc 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -38,7 +38,7 @@ walkdir = { workspace = true } indicatif = { workspace = true } thiserror = { workspace = true } fluent = { workspace = true } -rustix = { workspace = true } +rustix = { workspace = true, features = ["fs"] } [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] selinux = { workspace = true, optional = true } diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 75f79816a5a..1792964ec20 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1374,7 +1374,18 @@ fn is_enotsup_error(error: &CpError) -> bool { const EOPNOTSUPP: i32 = 95; match error { - CpError::IoErr(e) | CpError::IoErrContext(e, _) => e.raw_os_error() == Some(EOPNOTSUPP), + CpError::IoErr(e) | CpError::IoErrContext(e, _) => { + let raw = e.raw_os_error(); + // WASI's sandbox has no chmod/chown syscalls at all (not merely an + // unsupported combination of flags), so `fs::set_permissions` and + // friends always fail with ENOSYS there. Treat that the same as + // EOPNOTSUPP for optional preservation. + #[cfg(target_os = "wasi")] + if raw == Some(libc::ENOSYS) { + return true; + } + raw == Some(EOPNOTSUPP) + } _ => false, } } @@ -1903,8 +1914,28 @@ pub(crate) fn copy_attributes( })?; handle_preserve(attributes.timestamps, || -> CopyResult<()> { - let atime = FileTime::from_last_access_time(&source_metadata); - let mtime = FileTime::from_last_modification_time(&source_metadata); + // `filetime::FileTime::from_last_{access,modification}_time` panics on + // WASI (the `filetime` crate has no WASI-specific backend and falls + // back to its unimplemented generic wasm one). `Metadata::accessed`/ + // `modified` are stable and WASI-backed, so use those instead there. + #[cfg(target_os = "wasi")] + let (atime, mtime) = ( + FileTime::from( + source_metadata + .accessed() + .map_err(|e| CpError::IoErrContext(e, context.to_owned()))?, + ), + FileTime::from( + source_metadata + .modified() + .map_err(|e| CpError::IoErrContext(e, context.to_owned()))?, + ), + ); + #[cfg(not(target_os = "wasi"))] + let (atime, mtime) = ( + FileTime::from_last_access_time(&source_metadata), + FileTime::from_last_modification_time(&source_metadata), + ); // `set_file_times` opens the destination (O_RDONLY) before calling // futimens; opening a FIFO or device with no peer blocks forever, and a // socket cannot be opened at all. For symlinks and these special files @@ -1973,18 +2004,8 @@ pub(crate) fn copy_attributes( fn symlink_file( source: &Path, dest: &Path, - #[cfg(not(target_os = "wasi"))] symlinked_files: &mut HashSet, - #[cfg(target_os = "wasi")] _symlinked_files: &mut HashSet, + symlinked_files: &mut HashSet, ) -> CopyResult<()> { - #[cfg(target_os = "wasi")] - { - Err(CpError::IoErrContext( - io::Error::new(io::ErrorKind::Unsupported, "symlinks not supported"), - translate!("cp-error-cannot-create-symlink", - "dest" => get_filename(dest).unwrap_or("?").quote(), - "source" => get_filename(source).unwrap_or("?").quote()), - )) - } #[cfg(not(any(windows, target_os = "wasi")))] { std::os::unix::fs::symlink(source, dest).map_err(|e| { @@ -1996,6 +2017,20 @@ fn symlink_file( ) })?; } + // `std::os::unix::fs::symlink` is unavailable on WASI (`std::os::wasi` is + // nightly-only), but `rustix::fs::symlink` works on stable for both + // wasip1 and wasip2 (same approach `ln` already uses). + #[cfg(target_os = "wasi")] + { + rustix::fs::symlink(source, dest).map_err(|e| { + CpError::IoErrContext( + io::Error::from(e), + translate!("cp-error-cannot-create-symlink", + "dest" => get_filename(dest).unwrap_or("?").quote(), + "source" => get_filename(source).unwrap_or("?").quote()), + ) + })?; + } #[cfg(windows)] { std::os::windows::fs::symlink_file(source, dest).map_err(|e| { @@ -2007,13 +2042,10 @@ fn symlink_file( ) })?; } - #[cfg(not(target_os = "wasi"))] - { - if let Ok(file_info) = FileInformation::from_path(dest, false) { - symlinked_files.insert(file_info); - } - Ok(()) + if let Ok(file_info) = FileInformation::from_path(dest, false) { + symlinked_files.insert(file_info); } + Ok(()) } fn context_for(src: &Path, dest: &Path) -> String { diff --git a/src/uu/cp/src/platform/other.rs b/src/uu/cp/src/platform/other.rs index 143c35e185b..8b9bbfbe206 100644 --- a/src/uu/cp/src/platform/other.rs +++ b/src/uu/cp/src/platform/other.rs @@ -19,7 +19,7 @@ pub(crate) fn copy_on_write( sparse_mode: SparseMode, context: &str, ) -> CopyResult { - if reflink_mode != ReflinkMode::Never { + if reflink_mode == ReflinkMode::Always { return Err(translate!("cp-error-reflink-not-supported") .to_string() .into()); diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index b73b32176e6..fb3b94cfd63 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -95,7 +95,7 @@ impl Iterator for LinesWithNewlines { match self.inner.read_until(b'\n', &mut v) { Ok(0) => None, Ok(_) => Some(ret(v)), - Err(e) => Some(Err(e)), + Err(e) => Some(Err(uucore::error::wasi_normalize_open_error(e))), } } } diff --git a/src/uu/date/Cargo.toml b/src/uu/date/Cargo.toml index 2180ae8f5c9..e39edc7020c 100644 --- a/src/uu/date/Cargo.toml +++ b/src/uu/date/Cargo.toml @@ -46,6 +46,9 @@ uucore = { workspace = true, features = ["parser", "i18n-datetime"] } libc = { workspace = true } rustix = { workspace = true, features = ["time"] } +[target.'cfg(target_os = "wasi")'.dependencies] +libc = { workspace = true } + [target.'cfg(windows)'.dependencies] windows-sys = { workspace = true, features = [ "Win32_Foundation", diff --git a/src/uu/date/locales/en-US.ftl b/src/uu/date/locales/en-US.ftl index ea864904285..f4447827493 100644 --- a/src/uu/date/locales/en-US.ftl +++ b/src/uu/date/locales/en-US.ftl @@ -103,6 +103,7 @@ date-error-expected-file-got-directory = expected file, got directory {$path} date-error-date-overflow = date overflow '{$date}' date-error-setting-date-not-supported-macos = setting the date is not supported by macOS date-error-setting-date-not-supported-redox = setting the date is not supported by Redox +date-error-setting-date-not-supported-wasi = setting the date is not supported by WASI date-error-cannot-set-date = cannot set date date-error-extra-operand = extra operand '{$operand}' date-error-write = write error: {$error} diff --git a/src/uu/date/locales/fr-FR.ftl b/src/uu/date/locales/fr-FR.ftl index 9a67704af1e..1cc1fafc681 100644 --- a/src/uu/date/locales/fr-FR.ftl +++ b/src/uu/date/locales/fr-FR.ftl @@ -98,6 +98,7 @@ date-error-expected-file-got-directory = fichier attendu, répertoire obtenu {$p date-error-date-overflow = débordement de date '{$date}' date-error-setting-date-not-supported-macos = la définition de la date n'est pas prise en charge par macOS date-error-setting-date-not-supported-redox = la définition de la date n'est pas prise en charge par Redox +date-error-setting-date-not-supported-wasi = la définition de la date n'est pas prise en charge par WASI date-error-cannot-set-date = impossible de définir la date date-error-extra-operand = opérande supplémentaire '{$operand}' date-error-write = erreur d'écriture: {$error} diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 4a46249a3a0..0152fe830ad 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -1130,7 +1130,23 @@ fn parse_date>( } } -#[cfg(not(any(unix, windows)))] +#[cfg(target_os = "wasi")] +/// Returns the resolution of the system's realtime clock. +/// +/// `rustix::time::clock_getres` excludes WASI, so call `libc::clock_getres` +/// (available on WASI) directly instead. +fn get_clock_resolution() -> Timestamp { + let timespec = unsafe { + let mut timespec: libc::timespec = std::mem::zeroed(); + libc::clock_getres(libc::CLOCK_REALTIME, &raw mut timespec); + timespec + }; + + #[allow(clippy::unnecessary_cast, reason = "needed for 32 bit target")] + Timestamp::constant(timespec.tv_sec as _, timespec.tv_nsec as _) +} + +#[cfg(not(any(unix, windows, target_os = "wasi")))] fn get_clock_resolution() -> Timestamp { unimplemented!("getting clock resolution not implemented (unsupported target)"); } @@ -1169,7 +1185,7 @@ fn get_clock_resolution() -> Timestamp { Timestamp::constant(0, 100) } -#[cfg(not(any(unix, windows)))] +#[cfg(not(any(unix, windows, target_os = "wasi")))] fn set_system_datetime(_date: Zoned) -> UResult<()> { unimplemented!("setting date not implemented (unsupported target)"); } @@ -1191,6 +1207,15 @@ fn set_system_datetime(_date: Zoned) -> UResult<()> { )) } +#[cfg(target_os = "wasi")] +/// The WASI sandbox has no syscall for setting the wall clock. +fn set_system_datetime(_date: Zoned) -> UResult<()> { + Err(USimpleError::new( + 1, + translate!("date-error-setting-date-not-supported-wasi"), + )) +} + #[cfg(target_os = "redox")] fn set_system_datetime(_date: Zoned) -> UResult<()> { Err(USimpleError::new( diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index e917cf0a82f..f4bc09db432 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -22,7 +22,7 @@ use crossterm::{ terminal::{self, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen}, }; -use uucore::error::{UResult, USimpleError, UUsageError}; +use uucore::error::{UResult, USimpleError, UUsageError, process_exit}; use uucore::format_usage; use uucore::{display::Quotable, show}; @@ -681,7 +681,7 @@ impl<'a> Pager<'a> { }, ) => { reset_term()?; - std::process::exit(0); + process_exit(0); } // --- Forward Navigation --- diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 5e2fc6eb5c9..50e4bbd4e34 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -97,8 +97,14 @@ fn show_verbose_write_error(result: UResult<()>) -> bool { /// Helper function to show error with context and return error status fn show_removal_error(error: io::Error, path: &Path) -> bool { + let error = uucore::error::wasi_normalize_open_error(error); if error.kind() == io::ErrorKind::PermissionDenied { show_error!("cannot remove {}: Permission denied", path.quote()); + } else if error.kind() == io::ErrorKind::IsADirectory { + show_error!( + "{}", + RmError::CannotRemoveIsDirectory(path.as_os_str().to_os_string()) + ); } else { let e = error.map_err_context(|| translate!("rm-error-cannot-remove", "file" => path.quote())); @@ -624,11 +630,17 @@ fn is_readable_metadata(metadata: &Metadata) -> bool { } /// Whether the given file or directory is readable. -#[cfg(any(not(unix), target_os = "redox"))] +#[cfg(all(any(not(unix), target_os = "redox"), not(target_os = "wasi")))] fn is_readable(_path: &Path) -> bool { true } +/// WASI has no `PermissionsExt`, so probe readability by opening the directory. +#[cfg(target_os = "wasi")] +fn is_readable(path: &Path) -> bool { + fs::read_dir(path).is_ok() +} + #[cfg(unix)] fn is_writable_metadata(metadata: &Metadata) -> bool { let mode = metadata.permissions().mode(); @@ -1051,9 +1063,27 @@ fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata } } +#[cfg(target_os = "wasi")] +fn handle_writable_directory(path: &Path, options: &Options, _metadata: &Metadata) -> bool { + let stdin_ok = options.__presume_input_tty.unwrap_or(false) || stdin().is_terminal(); + + // Try to read the directory to check if it's accessible + let is_accessible = fs::read_dir(path).is_ok(); + + match (stdin_ok, is_accessible, options.interactive) { + (false, _, InteractiveMode::PromptProtected) => true, + (false, false, InteractiveMode::Never) => true, + (_, false, _) => prompt_yes!( + "attempt removal of inaccessible directory {}?", + path.quote() + ), + (_, _, InteractiveMode::Always) => prompt_yes!("remove directory {}?", path.quote()), + (_, _, _) => true, + } +} + // I have this here for completeness but it will always return "remove directory {}" because metadata.permissions().readonly() only works for file not directories -#[cfg(not(windows))] -#[cfg(not(unix))] +#[cfg(not(any(windows, unix, target_os = "wasi")))] fn handle_writable_directory(path: &Path, options: &Options, _metadata: &Metadata) -> bool { if options.interactive == InteractiveMode::Always { prompt_yes!("remove directory {}?", path.quote()) diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 64ab7100c4c..8f219d9d0c3 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -212,7 +212,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { match result { Ok(()) => Ok(()), - Err(err) if err.kind() == std::io::ErrorKind::BrokenPipe => { + Err(err) if uucore::error::wasi_is_broken_pipe(&err) => { // GNU seq prints the Broken pipe message but still exits with status 0 // unless SIGPIPE was explicitly ignored, in which case it should fail. let err = err.map_err_context(|| "write error".into()); diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index 25a12e87307..79b869149fc 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -17,7 +17,7 @@ use std::io::{self, Read, Seek, SeekFrom, Write}; use std::os::unix::prelude::PermissionsExt; use std::path::{Path, PathBuf}; use uucore::display::Quotable; -use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; +use uucore::error::{FromIo, UResult, USimpleError, UUsageError, process_exit}; use uucore::parser::parse_size::parse_size_u64; use uucore::parser::shortcut_value_parser::ShortcutValueParser; use uucore::translate; @@ -410,7 +410,7 @@ fn get_size(size_str_opt: Option) -> Option { translate!("shred-invalid-file-size", "size" => size.quote()) ); // TODO: replace with our error management - std::process::exit(1); + process_exit(1); } None }) @@ -820,7 +820,7 @@ fn wipe_name(orig_path: &Path, verbose: bool, remove_method: RemoveMethod) -> Pa let msg = translate!("shred-couldnt-rename", "file" => last_path.maybe_quote(), "new_name" => new_path.quote(), "error" => e); show_error!("{msg}"); // TODO: replace with our error management - std::process::exit(1); + process_exit(1); } } } diff --git a/src/uu/sort/src/check.rs b/src/uu/sort/src/check.rs index a826bc75507..f161426bd79 100644 --- a/src/uu/sort/src/check.rs +++ b/src/uu/sort/src/check.rs @@ -11,9 +11,9 @@ use crate::{ compare_by, open, }; use itertools::Itertools; +use std::{cmp::Ordering, ffi::OsStr}; +#[cfg(not(target_os = "wasi"))] use std::{ - cmp::Ordering, - ffi::OsStr, io::Read, iter, sync::mpsc::{Receiver, SyncSender, sync_channel}, @@ -21,11 +21,78 @@ use std::{ }; use uucore::error::UResult; +fn buffer_size(settings: &GlobalSettings) -> usize { + if settings.buffer_size < 100 * 1024 { + // when the buffer size is smaller than 100KiB we choose it instead of the default. + // this improves testability. + settings.buffer_size + } else { + 100 * 1024 + } +} + +/// Given the chunks of a file (in order), find the first pair of adjacent +/// lines that violates the requested ordering and report it as a +/// [`SortError::Disorder`]. +#[cfg(target_os = "wasi")] +fn check_chunks( + path: &OsStr, + settings: &GlobalSettings, + max_allowed_cmp: Ordering, + chunks: impl Iterator, +) -> UResult<()> { + let mut prev_chunk: Option = None; + let mut line_idx = 0; + for chunk in chunks { + line_idx += 1; + if let Some(prev_chunk) = &prev_chunk { + // Check if the first element of the new chunk is greater than the last + // element from the previous chunk + let prev_last = prev_chunk.lines().last().unwrap(); + let new_first = chunk.lines().first().unwrap(); + + if compare_by( + prev_last, + new_first, + settings, + prev_chunk.line_data(), + chunk.line_data(), + ) > max_allowed_cmp + { + return Err(SortError::Disorder { + file: path.to_owned(), + line_number: line_idx, + line: String::from_utf8_lossy(new_first.line).into_owned(), + silent: settings.check_silent, + } + .into()); + } + } + + for (a, b) in chunk.lines().iter().tuple_windows() { + line_idx += 1; + if compare_by(a, b, settings, chunk.line_data(), chunk.line_data()) > max_allowed_cmp { + return Err(SortError::Disorder { + file: path.to_owned(), + line_number: line_idx, + line: String::from_utf8_lossy(b.line).into_owned(), + silent: settings.check_silent, + } + .into()); + } + } + + prev_chunk = Some(chunk); + } + Ok(()) +} + /// Check if the file at `path` is ordered. /// /// # Returns /// /// The code we should exit with. +#[cfg(not(target_os = "wasi"))] pub fn check(path: &OsStr, settings: &GlobalSettings) -> UResult<()> { let max_allowed_cmp = if settings.unique { // If `unique` is enabled, the previous line must compare _less_ to the next one. @@ -42,13 +109,7 @@ pub fn check(path: &OsStr, settings: &GlobalSettings) -> UResult<()> { move || reader(file, &recycled_receiver, &loaded_sender, &settings) }); for _ in 0..2 { - let _ = recycled_sender.send(RecycledChunk::new(if settings.buffer_size < 100 * 1024 { - // when the buffer size is smaller than 100KiB we choose it instead of the default. - // this improves testability. - settings.buffer_size - } else { - 100 * 1024 - })); + let _ = recycled_sender.send(RecycledChunk::new(buffer_size(settings))); } let mut prev_chunk: Option = None; @@ -114,7 +175,27 @@ pub fn check(path: &OsStr, settings: &GlobalSettings) -> UResult<()> { result } +/// Check if the file at `path` is ordered. +/// +/// WASI has no thread support, so this reads every chunk up front on the +/// current thread instead of streaming them from a background reader thread. +/// +/// # Returns +/// +/// The code we should exit with. +#[cfg(target_os = "wasi")] +pub fn check(path: &OsStr, settings: &GlobalSettings) -> UResult<()> { + let max_allowed_cmp = if settings.unique { + Ordering::Less + } else { + Ordering::Equal + }; + let chunks = read_all_chunks(path, settings)?; + check_chunks(path, settings, max_allowed_cmp, chunks.into_iter()) +} + /// The function running on the reader thread. +#[cfg(not(target_os = "wasi"))] fn reader( mut file: Box, receiver: &Receiver, @@ -139,3 +220,33 @@ fn reader( } Ok(()) } + +/// Read every chunk of `path` up front, without any recycling or background +/// thread. Used on WASI, which has no thread support. +#[cfg(target_os = "wasi")] +fn read_all_chunks(path: &OsStr, settings: &GlobalSettings) -> UResult> { + let mut file = open(path)?; + let mut carry_over = vec![]; + let mut chunks = Vec::new(); + let (sender, receiver) = std::sync::mpsc::sync_channel(1); + loop { + let recycled = RecycledChunk::new(buffer_size(settings)); + let should_continue = chunks::read( + &sender, + recycled, + None, + &mut carry_over, + &mut file, + &mut std::iter::empty(), + settings.line_ending.into(), + settings, + )?; + while let Ok(chunk) = receiver.try_recv() { + chunks.push(chunk); + } + if !should_continue { + break; + } + } + Ok(chunks) +} diff --git a/src/uu/sort/src/ext_sort/wasi.rs b/src/uu/sort/src/ext_sort/wasi.rs index 50bd5f63033..d37c1f41cec 100644 --- a/src/uu/sort/src/ext_sort/wasi.rs +++ b/src/uu/sort/src/ext_sort/wasi.rs @@ -31,6 +31,13 @@ pub fn ext_sort( // moderately sized inputs; very large files may cause OOM. let mut input = Vec::new(); for file in files { + // Insert the separator between files whose preceding content doesn't + // already end with one; otherwise the last line of one file would + // merge with the first line of the next, e.g. "a\nb" + "b" -> + // "a\nbb" instead of "a\nb" + '\n' + "b". + if !input.is_empty() && input.last() != Some(&separator) { + input.push(separator); + } file?.read_to_end(&mut input)?; } if input.is_empty() { diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 2a80ae22cf3..f720c379980 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -2015,7 +2015,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { for (line_num, line_res) in buf_reader.split(b'\0').enumerate() { let line = line_res.map_err(|error| SortError::ReadFailed { path: files0_from.clone(), - error, + error: uucore::error::wasi_normalize_read_error(error), })?; if line.as_slice() == STDIN_FILE.as_bytes() { return Err(SortError::MinusInStdIn.into()); @@ -3142,7 +3142,7 @@ fn open_with_open_failed_error(path: impl AsRef) -> UResult Ok(Box::new(f) as Box), Err(error) => Err(SortError::OpenFailed { path: path.to_owned(), - error, + error: uucore::error::wasi_normalize_open_error(error), } .into()), } diff --git a/src/uu/sort/src/tmp_dir.rs b/src/uu/sort/src/tmp_dir.rs index 933278064ed..1d637b159dd 100644 --- a/src/uu/sort/src/tmp_dir.rs +++ b/src/uu/sort/src/tmp_dir.rs @@ -16,7 +16,10 @@ use std::{ use tempfile::TempDir; use uucore::error::UResult; #[cfg(not(any(target_os = "redox", target_os = "wasi")))] -use uucore::{error::USimpleError, show_error, translate}; +use uucore::{ + error::{USimpleError, process_exit}, + show_error, translate, +}; use crate::SortError; @@ -82,7 +85,7 @@ fn ensure_signal_handler_installed(state: Arc>) -> UR } } - std::process::exit(2) + process_exit(2) }) { HANDLER_INSTALLED.store(false, Ordering::Release); return Err(USimpleError::new( diff --git a/src/uu/split/src/platform/wasi.rs b/src/uu/split/src/platform/wasi.rs index acfff8bed53..edf1f6352f8 100644 --- a/src/uu/split/src/platform/wasi.rs +++ b/src/uu/split/src/platform/wasi.rs @@ -29,16 +29,22 @@ pub fn instantiate_current_writer( translate!("split-error-would-overwrite-input", "file" => filename.quote()), )); } + let open_file_error = |e: std::io::Error| { + let e = uucore::error::strip_errno(&e); + std::io::Error::other(format!("{}: {e}", filename.quote())) + }; let file = if is_new { std::fs::OpenOptions::new() .write(true) .create(true) .truncate(true) - .open(std::path::Path::new(filename))? + .open(std::path::Path::new(filename)) + .map_err(open_file_error)? } else { std::fs::OpenOptions::new() .append(true) - .open(std::path::Path::new(filename))? + .open(std::path::Path::new(filename)) + .map_err(open_file_error)? }; Ok(Writer::File(file)) } diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 892e70bf45c..bda1c42c838 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -559,7 +559,7 @@ fn unbounded_tail(reader: &mut BufReader, settings: &Settings) -> UR #[cfg(target_os = "windows")] writer.flush().inspect_err(|err| { if err.kind() == ErrorKind::BrokenPipe { - std::process::exit(13); + uucore::error::process_exit(13); } })?; Ok(()) diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index e43a0a02051..a14f4f1b6d4 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -214,6 +214,7 @@ impl MultiWriter { Ok(slice) => self.write_flush(slice)?, Err(e) if e.kind() == ErrorKind::Interrupted => {} Err(e) => { + let e = uucore::error::wasi_normalize_read_error(e); let _ = writeln!( stderr(), "tee: {}", diff --git a/src/uu/touch/Cargo.toml b/src/uu/touch/Cargo.toml index 447f795d1bf..ac3f77fde9d 100644 --- a/src/uu/touch/Cargo.toml +++ b/src/uu/touch/Cargo.toml @@ -35,6 +35,10 @@ tempfile = { workspace = true } libc = { workspace = true } rustix = { workspace = true, features = ["fs"] } +[target.'cfg(target_os = "wasi")'.dependencies] +libc = { workspace = true } +rustix = { workspace = true, features = ["fs"] } + [target.'cfg(target_os = "windows")'.dependencies] windows-sys = { workspace = true, features = [ "Win32_Storage_FileSystem", diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index fc9fcc57737..44074503dc7 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -10,16 +10,18 @@ pub mod error; use clap::builder::{PossibleValue, ValueParser}; use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command}; -#[cfg(any(not(unix), target_os = "redox"))] +use filetime::FileTime; +#[cfg(any(not(any(unix, target_os = "wasi")), target_os = "redox"))] use filetime::set_file_times; -use filetime::{FileTime, set_symlink_file_times}; +#[cfg(not(target_os = "wasi"))] +use filetime::set_symlink_file_times; use jiff::civil::Time; use jiff::fmt::strtime; use jiff::tz::TimeZone; use jiff::{Timestamp, ToSpan, Zoned}; #[cfg(unix)] use libc::O_NONBLOCK; -#[cfg(unix)] +#[cfg(any(unix, target_os = "wasi"))] use rustix::fs::Timestamps; #[cfg(unix)] use rustix::fs::futimens; @@ -595,11 +597,29 @@ fn update_times( // sets the file access and modification times for a file or a symbolic link. // The filename, access time (atime), and modification time (mtime) are provided as inputs. + #[cfg(not(target_os = "wasi"))] if opts.no_deref && !is_stdout { return set_symlink_file_times(path, atime, mtime).map_err_context( || translate!("touch-error-setting-times-of-path", "path" => path.quote()), ); } + // `filetime::set_symlink_file_times` always fails on WASI (the crate has + // no WASI-specific backend), but `rustix::fs::utimensat` with + // `AT_SYMLINK_NOFOLLOW` works there on stable. + #[cfg(target_os = "wasi")] + if opts.no_deref && !is_stdout { + let timestamps = build_timestamps(atime, mtime); + return rustix::fs::utimensat( + rustix::fs::CWD, + path, + ×tamps, + rustix::fs::AtFlags::SYMLINK_NOFOLLOW, + ) + .map_err(Error::from) + .map_err_context( + || translate!("touch-error-setting-times-of-path", "path" => path.quote()), + ); + } #[cfg(unix)] { @@ -629,7 +649,12 @@ fn update_times( set_times_by_path(path, atime, mtime) } - #[cfg(not(unix))] + #[cfg(target_os = "wasi")] + { + set_times_by_path(path, atime, mtime) + } + + #[cfg(not(any(unix, target_os = "wasi")))] { set_file_times(path, atime, mtime).map_err_context( || translate!("touch-error-setting-times-of-path", "path" => path.quote()), @@ -637,7 +662,7 @@ fn update_times( } } -#[cfg(unix)] +#[cfg(any(unix, target_os = "wasi"))] /// Build a rustix `Timestamps` from the access and modification `FileTime`s, /// preserving the `UTIME_NOW`/`UTIME_OMIT` sentinels in the nanoseconds field. fn build_timestamps(atime: FileTime, mtime: FileTime) -> Timestamps { @@ -653,11 +678,12 @@ fn build_timestamps(atime: FileTime, mtime: FileTime) -> Timestamps { } } -#[cfg(all(unix, not(target_os = "redox")))] -/// Set file times by path using `utimensat`, following symlinks. +#[cfg(all(any(unix, target_os = "wasi"), not(target_os = "redox")))] +/// Set file times by path using `utimensat`. /// -/// This never opens the file, so it does not block on special files such as -/// FIFOs. +/// This never opens the file on Unix, avoiding blocks on FIFOs. On WASI, if +/// `utimensat` fails on a non-symlink (e.g. unopenable mode 0 files), it retries +/// with `SYMLINK_NOFOLLOW`. fn set_times_by_path(path: &Path, atime: FileTime, mtime: FileTime) -> UResult<()> { let timestamps = build_timestamps(atime, mtime); rustix::fs::utimensat( @@ -666,6 +692,18 @@ fn set_times_by_path(path: &Path, atime: FileTime, mtime: FileTime) -> UResult<( ×tamps, rustix::fs::AtFlags::empty(), ) + .or_else(|err| { + if cfg!(target_os = "wasi") && !path.is_symlink() { + rustix::fs::utimensat( + rustix::fs::CWD, + path, + ×tamps, + rustix::fs::AtFlags::SYMLINK_NOFOLLOW, + ) + } else { + Err(err) + } + }) .map_err(|e| Error::from_raw_os_error(e.raw_os_error())) .map_err_context(|| translate!("touch-error-setting-times-of-path", "path" => path.quote())) } @@ -725,6 +763,16 @@ fn stat(path: &Path, follow: bool) -> std::io::Result<(FileTime, FileTime)> { fs::symlink_metadata(path)? }; + // `filetime::FileTime::from_last_{access,modification}_time` panics on + // WASI (the `filetime` crate has no WASI-specific backend and falls back + // to its unimplemented generic wasm one). `Metadata::accessed`/`modified` + // are stable and WASI-backed, so use those instead there. + #[cfg(target_os = "wasi")] + return Ok(( + FileTime::from(metadata.accessed()?), + FileTime::from(metadata.modified()?), + )); + #[cfg(not(target_os = "wasi"))] Ok(( FileTime::from_last_access_time(&metadata), FileTime::from_last_modification_time(&metadata), diff --git a/src/uu/tr/src/operation.rs b/src/uu/tr/src/operation.rs index 44c28c160c8..a36872b7da1 100644 --- a/src/uu/tr/src/operation.rs +++ b/src/uu/tr/src/operation.rs @@ -763,7 +763,10 @@ where Ok(0) => break, // EOF reached Ok(len) => len, Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue, - Err(e) => return Err(e.map_err_context(|| translate!("tr-error-read-error"))), + Err(e) => { + let e = uucore::error::wasi_normalize_read_error(e); + return Err(e.map_err_context(|| translate!("tr-error-read-error"))); + } }; // Process the buffer and collect translated chars to output @@ -794,7 +797,7 @@ pub fn flush_output(output: &mut W) -> UResult<()> { match output.flush() { Ok(()) => Ok(()), Err(err) if err.kind() == std::io::ErrorKind::BrokenPipe => { - std::process::exit(13); + uucore::error::process_exit(13); } Err(err) => Err(err.map_err_context(|| translate!("tr-error-write-error"))), } diff --git a/src/uu/tr/src/simd.rs b/src/uu/tr/src/simd.rs index 7bf2427daf6..34861fee869 100644 --- a/src/uu/tr/src/simd.rs +++ b/src/uu/tr/src/simd.rs @@ -75,7 +75,10 @@ where Ok(0) => break, Ok(len) => len, Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue, - Err(e) => return Err(e.map_err_context(|| translate!("tr-error-read-error"))), + Err(e) => { + let e = uucore::error::wasi_normalize_read_error(e); + return Err(e.map_err_context(|| translate!("tr-error-read-error"))); + } }; output_buf.clear(); @@ -101,7 +104,7 @@ pub fn write_output(output: &mut W, buf: &[u8]) -> UResult<()> { match output.write_all(buf) { Ok(()) => Ok(()), Err(err) if err.kind() == std::io::ErrorKind::BrokenPipe => { - std::process::exit(13); + uucore::error::process_exit(13); } Err(err) => Err(err.map_err_context(|| translate!("tr-error-write-error"))), } diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index e388f4e9385..9076ea3bb7f 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -55,7 +55,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { process_input(io::stdin().lock(), &mut g)?; } else { // some platforms cannot catch this as read error. Needs additional cost by stat - #[cfg(windows)] + #[cfg(any(windows, target_os = "wasi"))] { let input = std::path::Path::new(input); if input.is_dir() { diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index 504e34a88b2..b664b0ef2e5 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -7,7 +7,7 @@ use clap::{Arg, ArgAction, Command}; use std::io::{IsTerminal, Write}; -use uucore::error::{UResult, set_exit_code}; +use uucore::error::{UResult, process_exit, set_exit_code}; use uucore::format_usage; use uucore::translate; @@ -77,7 +77,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if write_result.is_err() || stdout.flush().is_err() { // Don't return to prevent a panic later when another flush is attempted // because the `uucore_procs::main` macro inserts a flush after execution for every utility. - std::process::exit(3); + process_exit(3); } Ok(()) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index a7fea0519e9..2aa514f4ceb 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -701,7 +701,10 @@ fn word_count_from_input(input: &Input<'_>, settings: &Settings) -> CountResult }; match maybe_err { None => CountResult::Success(total), - Some(err) => CountResult::Interrupted(total, err), + Some(err) => { + let err = uucore::error::wasi_normalize_open_error(err); + CountResult::Interrupted(total, err) + } } } @@ -775,16 +778,19 @@ fn files0_iter_stdin<'a>() -> impl Iterator> { fn files0_iter_file<'a>(path: &Path) -> UResult>> { match File::open(path) { Ok(f) => Ok(files0_iter(f, path.into())), - Err(e) => Err(e.map_err_context(|| { - translate!("wc-error-cannot-open-for-reading", - "path" => quoting_style::locale_aware_escape_name( - path.as_os_str(), - QuotingStyle::SHELL_ESCAPE_QUOTE, + Err(e) => { + let e = uucore::error::wasi_normalize_open_error(e); + Err(e.map_err_context(|| { + translate!("wc-error-cannot-open-for-reading", + "path" => quoting_style::locale_aware_escape_name( + path.as_os_str(), + QuotingStyle::SHELL_ESCAPE_QUOTE, + ) + .into_string() + .expect("All escaped names with the escaping option return valid strings.") ) - .into_string() - .expect("All escaped names with the escaping option return valid strings.") - ) - })), + })) + } } } @@ -813,9 +819,12 @@ fn files0_iter<'a>( Ok(Input::Path(PathBuf::from(s).into())) } } - Err(e) => Err(e.map_err_context( - || translate!("wc-error-read-error", "path" => escape_name_wrapper(&err_path)), - ) as Box), + Err(e) => { + let e = uucore::error::wasi_normalize_read_error(e); + Err(e.map_err_context( + || translate!("wc-error-read-error", "path" => escape_name_wrapper(&err_path)), + ) as Box) + } }), ); // Loop until there is an error; yield that error and then nothing else. diff --git a/src/uu/yes/src/yes.rs b/src/uu/yes/src/yes.rs index da5cc6cf583..959d5da3599 100644 --- a/src/uu/yes/src/yes.rs +++ b/src/uu/yes/src/yes.rs @@ -33,7 +33,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { Ok(()) => Ok(()), // On Windows and WASI, silently handle broken pipe since there's no SIGPIPE #[cfg(any(windows, target_os = "wasi"))] - Err(err) if err.kind() == io::ErrorKind::BrokenPipe => Ok(()), + Err(err) if uucore::error::wasi_is_broken_pipe(&err) => Ok(()), Err(err) => Err(USimpleError::new( 1, translate!("yes-error-standard-output", "error" => strip_errno(&err)), diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 9304681b318..602e14dd9de 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -142,6 +142,9 @@ windows-sys = { workspace = true, optional = true, default-features = false, fea [target.'cfg(target_os = "openbsd")'.dependencies] utmp-classic = { workspace = true, optional = true } +[target.'cfg(all(target_os = "wasi", target_env = "p2"))'.dependencies] +wasip2 = { version = "1.0", optional = true } + [features] default = ["signals"] # * non-default features @@ -225,3 +228,10 @@ tty = [] time = ["jiff"] uptime = ["jiff", "libc", "windows-sys", "utmpx", "utmp-classic"] benchmark = ["divan", "itertools", "tempfile"] +# Use the unstable `wasi:cli/exit#exit-with-code` function on wasm32-wasip2 +# so nonzero exit codes propagate correctly, instead of the stable +# `wasi:cli/exit#exit` function (used by `std::process::exit`) which only +# carries a success/failure bit and collapses every nonzero code to 1. +# The WASI host must opt in to this unstable feature (e.g. wasmtime's +# `-S cli-exit-with-code=y`) or the process traps instead of exiting. +wasip2-exit-with-code = ["dep:wasip2"] diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index 199d1ca6cbb..e32bd3defad 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -18,7 +18,7 @@ use std::fs::read_dir; use std::hash::Hash; use std::io::Stdin; use std::io::{Error, ErrorKind, Result as IOResult}; -#[cfg(any(unix, all(target_os = "wasi", target_env = "p2")))] +#[cfg(any(unix, target_os = "wasi"))] use std::os::fd::AsFd; #[cfg(unix)] use std::os::unix::fs::MetadataExt; @@ -52,15 +52,13 @@ macro_rules! has { /// Information to uniquely identify a file #[derive(Clone)] pub struct FileInformation( - #[cfg(unix)] rustix::fs::Stat, + #[cfg(any(unix, target_os = "wasi"))] rustix::fs::Stat, #[cfg(windows)] winapi_util::file::Information, - // WASI does not have nix::sys::stat, so we store std::fs::Metadata instead. - #[cfg(target_os = "wasi")] fs::Metadata, ); impl FileInformation { /// Get information from a currently open file - #[cfg(unix)] + #[cfg(any(unix, target_os = "wasi"))] pub fn from_file(file: &impl AsFd) -> IOResult { let stat = rustix::fs::fstat(file)?; Ok(Self(stat)) @@ -78,7 +76,7 @@ impl FileInformation { /// If `path` points to a symlink and `dereference` is true, information about /// the link's target will be returned. pub fn from_path(path: impl AsRef, dereference: bool) -> IOResult { - #[cfg(unix)] + #[cfg(any(unix, target_os = "wasi"))] { let stat = if dereference { rustix::fs::stat(path.as_ref()) @@ -102,20 +100,10 @@ impl FileInformation { let file = open_options.read(true).open(path.as_ref())?; Self::from_file(&file) } - // WASI: use std::fs::metadata / symlink_metadata since nix is not available - #[cfg(target_os = "wasi")] - { - let metadata = if dereference { - fs::metadata(path.as_ref()) - } else { - fs::symlink_metadata(path.as_ref()) - }; - Ok(Self(metadata?)) - } } pub fn file_size(&self) -> u64 { - #[cfg(unix)] + #[cfg(any(unix, target_os = "wasi"))] { assert!(self.0.st_size >= 0, "File size is negative"); self.0.st_size.try_into().unwrap() @@ -124,10 +112,6 @@ impl FileInformation { { self.0.file_size() } - #[cfg(target_os = "wasi")] - { - self.0.len() - } } #[cfg(windows)] @@ -178,37 +162,35 @@ impl FileInformation { return self.0.st_nlink.try_into().unwrap(); #[cfg(windows)] return self.0.number_of_links(); - // WASI: nlink is not available in std::fs::Metadata, return 1 #[cfg(target_os = "wasi")] - return 1; + #[allow(clippy::useless_conversion)] + return self.0.st_nlink.into(); } - #[cfg(unix)] + #[cfg(any(unix, target_os = "wasi"))] pub fn inode(&self) -> u64 { - #[cfg(all(not(any(target_os = "netbsd")), target_pointer_width = "64"))] + #[cfg(all( + not(any(target_os = "netbsd", target_os = "wasi")), + target_pointer_width = "64" + ))] return self.0.st_ino; - #[cfg(any(target_os = "netbsd", not(target_pointer_width = "64")))] + #[cfg(any( + target_os = "netbsd", + target_os = "wasi", + not(target_pointer_width = "64") + ))] #[allow(clippy::useless_conversion)] return self.0.st_ino.into(); } } -#[cfg(unix)] +#[cfg(any(unix, target_os = "wasi"))] impl PartialEq for FileInformation { fn eq(&self, other: &Self) -> bool { self.0.st_dev == other.0.st_dev && self.0.st_ino == other.0.st_ino } } -// WASI: compare by file type and size as a basic heuristic since -// device/inode numbers are not available through std::fs::Metadata. -#[cfg(target_os = "wasi")] -impl PartialEq for FileInformation { - fn eq(&self, other: &Self) -> bool { - self.0.file_type() == other.0.file_type() && self.0.len() == other.0.len() - } -} - #[cfg(target_os = "windows")] impl PartialEq for FileInformation { fn eq(&self, other: &Self) -> bool { @@ -221,7 +203,7 @@ impl Eq for FileInformation {} impl Hash for FileInformation { fn hash(&self, state: &mut H) { - #[cfg(unix)] + #[cfg(any(unix, target_os = "wasi"))] { self.0.st_dev.hash(state); self.0.st_ino.hash(state); @@ -231,11 +213,6 @@ impl Hash for FileInformation { self.0.volume_serial_number().hash(state); self.0.file_index().hash(state); } - #[cfg(target_os = "wasi")] - { - self.0.len().hash(state); - self.0.file_type().is_dir().hash(state); - } } } @@ -704,8 +681,8 @@ pub fn is_symlink_loop(path: &Path) -> bool { false } -#[cfg(not(unix))] -// Hard link comparison is not supported on non-Unix platforms +#[cfg(not(any(unix, target_os = "wasi")))] +// Hard link comparison is not supported on non-Unix, non-WASI platforms pub fn are_hardlinks_to_same_file(_source: &Path, _target: &Path) -> bool { false } @@ -731,7 +708,20 @@ pub fn are_hardlinks_to_same_file(source: &Path, target: &Path) -> bool { source_metadata.ino() == target_metadata.ino() && source_metadata.dev() == target_metadata.dev() } -#[cfg(not(unix))] +// `std::os::unix::fs::MetadataExt` is unavailable on WASI (`std::os::wasi` is +// nightly-only). `rustix::fs::stat` exposes the same st_ino/st_dev fields and +// works on stable for both wasip1 and wasip2. +#[cfg(target_os = "wasi")] +pub fn are_hardlinks_to_same_file(source: &Path, target: &Path) -> bool { + let (Ok(source_stat), Ok(target_stat)) = (rustix::fs::lstat(source), rustix::fs::lstat(target)) + else { + return false; + }; + + source_stat.st_ino == target_stat.st_ino && source_stat.st_dev == target_stat.st_dev +} + +#[cfg(not(any(unix, target_os = "wasi")))] pub fn are_hardlinks_or_one_way_symlink_to_same_file(_source: &Path, _target: &Path) -> bool { false } @@ -757,6 +747,16 @@ pub fn are_hardlinks_or_one_way_symlink_to_same_file(source: &Path, target: &Pat source_metadata.ino() == target_metadata.ino() && source_metadata.dev() == target_metadata.dev() } +#[cfg(target_os = "wasi")] +pub fn are_hardlinks_or_one_way_symlink_to_same_file(source: &Path, target: &Path) -> bool { + let (Ok(source_stat), Ok(target_stat)) = (rustix::fs::stat(source), rustix::fs::lstat(target)) + else { + return false; + }; + + source_stat.st_ino == target_stat.st_ino && source_stat.st_dev == target_stat.st_dev +} + /// Returns true if the passed `path` ends with a path terminator. /// /// This function examines the last character of the path to determine diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 3a2e274b217..1298fda30fc 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -170,7 +170,11 @@ fn metadata_get_change_time(md: &Metadata) -> Option { #[cfg(not(unix))] fn metadata_get_change_time(_md: &Metadata) -> Option { - // Not available. + // Not available: `std::fs::Metadata` has no ctime accessor without + // `std::os::unix::fs::MetadataExt` (unix-only) or `std::os::wasi::fs::MetadataExt` + // (WASI, but nightly-only). Getting ctime on WASI would require get stat of + // the path directly via `rustix::fs::stat` instead of going through + // `Metadata`, which isn't available at this call site. None } diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 5e91d163bc7..647d46fcb77 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -209,14 +209,14 @@ macro_rules! bin_inner { } => eprintln!("Localization parse error at {snippet}: {err_msg:?}"), other => eprintln!("Could not init the localization system: {other}"), } - std::process::exit(99) + uucore::error::process_exit(99) }); // execute utility code let code = $util::uumain(uucore::args_os()); $post - std::process::exit(code); + uucore::error::process_exit(code); } }; } @@ -574,8 +574,12 @@ pub fn read_byte_lines( pub fn read_os_string_lines( buf_reader: BufReader, ) -> impl Iterator> { - read_byte_lines(buf_reader) - .map(|byte_line_res| byte_line_res.map(|bl| os_string_from_vec(bl).expect("UTF-8 error"))) + read_byte_lines(buf_reader).map(|byte_line_res| { + byte_line_res.and_then(|bl| { + os_string_from_vec(bl) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())) + }) + }) } /// Prompt the user with a formatted string and returns `true` if they reply `'y'` or `'Y'` diff --git a/src/uucore/src/lib/mods/clap_localization.rs b/src/uucore/src/lib/mods/clap_localization.rs index 0249089c142..3366a086b20 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -113,7 +113,7 @@ impl<'a> ErrorFormatter<'a> { { let code = self.print_error(err, exit_code); callback(); - std::process::exit(code); + crate::error::process_exit(code); } /// Print error and return exit code (no exit call) diff --git a/src/uucore/src/lib/mods/error.rs b/src/uucore/src/lib/mods/error.rs index b4e35928c68..95469e43584 100644 --- a/src/uucore/src/lib/mods/error.rs +++ b/src/uucore/src/lib/mods/error.rs @@ -64,6 +64,40 @@ use std::{ static EXIT_CODE: AtomicI32 = AtomicI32::new(0); +/// Exit the process with `code`. +/// +/// On `wasm32-wasip2`, `std::process::exit` goes through the WASI Preview 2 +/// `wasi:cli/exit#exit` function, which only carries a success/failure bit, +/// so every nonzero code collapses to `1`. When the `wasip2-exit-with-code` +/// feature is enabled, this instead calls the unstable +/// `wasi:cli/exit#exit-with-code` function, which propagates the real code. +/// +/// That function is gated behind an unstable WASI feature: hosts that don't +/// explicitly opt in to it (e.g. `wasmtime run -S cli-exit-with-code=y`) will +/// trap instead of exiting. The feature is therefore off by default; only +/// enable it when the target WASI host is known to support it. +pub fn process_exit(code: i32) -> ! { + #[cfg(all( + target_os = "wasi", + target_env = "p2", + feature = "wasip2-exit-with-code" + ))] + { + // `std::process::exit` flushes stdout as part of its runtime cleanup + // (see rust-lang/rust#23818); calling the WASI import directly + // bypasses that, so flush explicitly to avoid losing buffered output. + let _ = std::io::stdout().flush(); + wasip2::cli::exit::exit_with_code(code as u8); + unreachable!("wasi:cli/exit#exit-with-code does not return"); + } + #[cfg(not(all( + target_os = "wasi", + target_env = "p2", + feature = "wasip2-exit-with-code" + )))] + std::process::exit(code) +} + /// Get the last exit code set with [`set_exit_code`]. /// The default value is `0`. pub fn get_exit_code() -> i32 { @@ -467,6 +501,67 @@ pub fn strip_errno(err: &std::io::Error) -> String { msg } +/// Remap the `EBADF`-instead-of-`EISDIR` error WASI raises for filesystem +/// operations attempted on a directory as if it were a regular file. +/// +/// On WASI (both preview 1 and preview 2), `std::fs::File::open`/`.read()`, +/// `std::fs::remove_file`, and similar calls surface raw errno `8` (`EBADF`) +/// when given a directory, instead of the `EISDIR` that Unix platforms +/// return, so `std::io::Error::kind()` comes back `Uncategorized` rather +/// than `IsADirectory`. Call this on such errors to normalize that case to +/// `ErrorKind::IsADirectory`, matching Unix behavior. +/// +/// This is a no-op on non-WASI targets. +pub fn wasi_normalize_open_error(err: std::io::Error) -> std::io::Error { + #[cfg(target_os = "wasi")] + if err.raw_os_error() == Some(8) { + return std::io::Error::new(std::io::ErrorKind::IsADirectory, "Is a directory"); + } + err +} + +/// Remap the error from reading a stream that turned out to be backed by a +/// directory file descriptor (e.g. stdin redirected from a directory, or a +/// raw `read()` on an fd already open on a directory). +/// +/// This covers two distinct WASI error shapes for the same underlying +/// condition: reading a directory fd that was already open when handed to +/// us (e.g. stdin redirected from a directory) surfaces `EISDIR`, under a +/// different errno per preview version (`31` on preview 1, `29` on preview +/// 2); reading a directory fd that we opened ourselves via +/// `std::fs::File::open` surfaces the same `EBADF` (errno `8`) that +/// [`wasi_normalize_open_error`] handles for `open()`-time failures, because +/// on WASI the directory check is deferred from `open` to the first `read`. +/// +/// This is a no-op on non-WASI targets. +pub fn wasi_normalize_read_error(err: std::io::Error) -> std::io::Error { + #[cfg(all(target_os = "wasi", target_env = "p1"))] + if matches!(err.raw_os_error(), Some(31) | Some(8)) { + return std::io::Error::new(std::io::ErrorKind::IsADirectory, "Is a directory"); + } + #[cfg(all(target_os = "wasi", target_env = "p2"))] + if matches!(err.raw_os_error(), Some(29) | Some(8)) { + return std::io::Error::new(std::io::ErrorKind::IsADirectory, "Is a directory"); + } + err +} + +/// Whether `err` represents a broken pipe on the current platform. +/// +/// On WASI preview 2, writing to stdout after the reader is gone surfaces a +/// raw `EIO` (errno `29`) instead of being mapped to `ErrorKind::BrokenPipe` +/// like preview 1 and Unix do; this checks for both. +pub fn wasi_is_broken_pipe(err: &std::io::Error) -> bool { + if err.kind() == std::io::ErrorKind::BrokenPipe { + return true; + } + #[cfg(all(target_os = "wasi", target_env = "p2"))] + if err.raw_os_error() == Some(29) { + return true; + } + false +} + /// Enables the conversion from [`std::io::Error`] to [`UError`] and from [`std::io::Result`] to /// [`UResult`]. pub trait FromIo { diff --git a/tests/by-util/test_b2sum.rs b/tests/by-util/test_b2sum.rs index 08b13fabf90..d7f8cd82d46 100644 --- a/tests/by-util/test_b2sum.rs +++ b/tests/by-util/test_b2sum.rs @@ -137,7 +137,7 @@ fn test_check_b2sum_length_option_0() { .ccmd("b2sum") .arg("--length=0") .arg("-c") - .arg(at.subdir.join("testf.b2sum")) + .arg("testf.b2sum") .succeeds() .stdout_only("testf: OK\n"); } @@ -170,7 +170,7 @@ fn test_check_b2sum_length_option_8() { .ccmd("b2sum") .arg("--length=8") .arg("-c") - .arg(at.subdir.join("testf.b2sum")) + .arg("testf.b2sum") .succeeds() .stdout_only("testf: OK\n"); } @@ -185,7 +185,7 @@ fn test_invalid_b2sum_length_option_not_multiple_of_8() { scene .ccmd("b2sum") .arg("--length=9") - .arg(at.subdir.join("testf")) + .arg("testf") .fails_with_code(1) .stderr_contains("b2sum: invalid length: '9'") .stderr_contains("b2sum: length is not a multiple of 8"); @@ -205,7 +205,7 @@ fn test_invalid_b2sum_length_option_too_large(#[case] len: &str) { .ccmd("b2sum") .arg("--length") .arg(len) - .arg(at.subdir.join("testf")) + .arg("testf") .fails_with_code(1) .no_stdout() .stderr_contains(format!("b2sum: invalid length: '{len}'")) @@ -288,7 +288,7 @@ fn test_check_b2sum_strict_check() { scene .ccmd("b2sum") .arg("-c") - .arg(at.subdir.join("ck")) + .arg("ck") .succeeds() .stdout_only(&output); @@ -296,7 +296,7 @@ fn test_check_b2sum_strict_check() { .ccmd("b2sum") .arg("--strict") .arg("-c") - .arg(at.subdir.join("ck")) + .arg("ck") .succeeds() .stdout_only(&output); } diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index e0baf5095b0..a70fdf5f0ca 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -87,6 +87,7 @@ fn test_no_options_big_input() { #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore)] fn test_fifo_symlink() { use std::io::Write; use std::thread; @@ -121,6 +122,7 @@ fn test_fifo_symlink() { // TODO(#7542): Re-enable on Android once we figure out why setting limit is broken. // #[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: resource limits not supported")] fn test_closes_file_descriptors() { // Each file creates a pipe, which has two file descriptors. // If they are not closed then five is certainly too many. @@ -138,6 +140,7 @@ fn test_closes_file_descriptors() { #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore)] fn test_broken_pipe() { let mut cmd = new_ucmd!(); let mut child = cmd @@ -199,6 +202,7 @@ fn test_piped_to_dev_null() { #[test] #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +#[cfg_attr(wasip2_runner, ignore = "WASI P2: /dev/full filesystem not available")] fn test_piped_to_dev_full() { for append in [true, false] { let s = TestScenario::new(util_name!()); @@ -514,6 +518,7 @@ fn test_squeeze_blank_before_numbering() { /// This tests reading from Unix character devices #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore)] fn test_dev_random() { #[cfg(any(target_os = "linux", target_os = "android"))] const DEV_RANDOM: &str = "/dev/urandom"; @@ -544,6 +549,7 @@ fn test_dev_random() { /// Wikipedia says there is support on Linux, FreeBSD, and `NetBSD`. #[test] #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +#[cfg_attr(wasi_runner, ignore)] fn test_dev_full() { let mut proc = new_ucmd!() .set_stdout(Stdio::piped()) @@ -559,6 +565,7 @@ fn test_dev_full() { #[test] #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +#[cfg_attr(wasi_runner, ignore)] fn test_dev_full_show_all() { let buf_len = 2048; let mut proc = new_ucmd!() @@ -581,6 +588,7 @@ fn test_dev_full_show_all() { // without additional flush output gets reversed. #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: /proc filesystem not available")] fn test_write_fast_fallthrough_uses_flush() { const PROC_INIT_CMDLINE: &str = "/proc/1/cmdline"; let cmdline = read_to_string(PROC_INIT_CMDLINE).unwrap(); @@ -603,10 +611,15 @@ fn test_domain_socket() { s.ucmd() .args(&[socket_path]) .fails() - .stderr_contains("No such device or address"); + .stderr_contains(if cfg!(wasi_runner) { + "No such file or directory" + } else { + "No such device or address" + }); } #[test] +#[cfg_attr(wasi_runner, ignore)] fn test_write_to_self_empty() { // it's ok if the input file is also the output file if it's empty let s = TestScenario::new(util_name!()); @@ -622,6 +635,7 @@ fn test_write_to_self_empty() { } #[test] +#[cfg_attr(wasi_runner, ignore)] fn test_write_to_self() { let s = TestScenario::new(util_name!()); let file_path = s.fixtures.plus("first_file"); @@ -678,6 +692,7 @@ fn test_successful_write_to_read_write_self() { /// /// `cat fx fx3 1<>fx3` #[test] +#[cfg_attr(wasi_runner, ignore)] fn test_failed_write_to_read_write_self() { let (at, mut ucmd) = at_and_ucmd!(); at.write("fx", "g"); @@ -746,6 +761,7 @@ fn test_write_fast_read_error() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore)] fn test_cat_non_utf8_paths() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; @@ -770,6 +786,10 @@ fn test_cat_non_utf8_paths() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: direct file descriptor manipulation not supported" +)] fn test_appending_same_input_output() { let (at, mut ucmd) = at_and_ucmd!(); @@ -858,6 +878,7 @@ fn test_write_error_handling() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasip2_runner, ignore = "WASI P2: /dev/full filesystem not available")] fn test_version_help_dev_full() { use std::fs::OpenOptions; diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 23b0341c080..6589b783fb8 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -970,6 +970,10 @@ fn test_check_error_incorrect_format() { #[cfg(unix)] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/null) not visible" +)] fn test_dev_null() { let scene = TestScenario::new(util_name!()); @@ -1031,7 +1035,7 @@ fn test_reset_binary() { .arg("--tag") .arg("--untagged") .arg("--algorithm=md5") - .arg(at.subdir.join("f")) + .arg("f") .succeeds() .stdout_contains("d41d8cd98f00b204e9800998ecf8427e *"); } @@ -1050,7 +1054,7 @@ fn test_reset_binary_but_set() { .arg("--untagged") .arg("--binary") .arg("--algorithm=md5") - .arg(at.subdir.join("f")) + .arg("f") .succeeds() .stdout_contains("d41d8cd98f00b204e9800998ecf8427e *"); } @@ -1066,7 +1070,7 @@ mod output_format { ucmd.arg("--text") .arg("--tag") .args(&["-a", "md5"]) - .arg(at.subdir.join("f")) + .arg("f") .fails(); } @@ -1078,7 +1082,7 @@ mod output_format { // --text without --untagged fails ucmd.arg("--text") .args(&["-a", "md5"]) - .arg(at.subdir.join("f")) + .arg("f") .fails_with_code(1) .stderr_contains("--text mode is only supported with --untagged"); } @@ -1092,7 +1096,7 @@ mod output_format { ucmd.arg("--text") .arg("--binary") .args(&["-a", "md5"]) - .arg(at.subdir.join("f")) + .arg("f") .succeeds() // No --untagged, tagged output is used .stdout_contains("f) = d41d8cd98f00b204e9800998ecf8427e"); @@ -1108,7 +1112,7 @@ mod output_format { .arg("--binary") .arg("--untagged") .args(&["-a", "md5"]) - .arg(at.subdir.join("f")) + .arg("f") .succeeds() // Untagged output is used .stdout_contains("d41d8cd98f00b204e9800998ecf8427e *"); @@ -1127,7 +1131,7 @@ fn test_binary_file() { .arg("--untagged") .arg("-b") .arg("--algorithm=md5") - .arg(at.subdir.join("f")) + .arg("f") .succeeds() .stdout_contains("d41d8cd98f00b204e9800998ecf8427e *"); @@ -1137,7 +1141,7 @@ fn test_binary_file() { .arg("--untagged") .arg("--binary") .arg("--algorithm=md5") - .arg(at.subdir.join("f")) + .arg("f") .succeeds() .stdout_contains("d41d8cd98f00b204e9800998ecf8427e *"); @@ -1753,7 +1757,7 @@ fn test_check_directory_error() { #[cfg(windows)] let err_msg = "cksum: d: Permission denied\n"; ucmd.arg("--check") - .arg(at.subdir.join("f")) + .arg("f") .fails() .stderr_contains(err_msg); } @@ -1771,7 +1775,7 @@ fn test_check_base64_hashes() { scene .ucmd() .arg("--check") - .arg(at.subdir.join("check")) + .arg("check") .succeeds() .stdout_is("empty: OK\nempty: OK\nempty: OK\n"); } @@ -1974,6 +1978,10 @@ mod check_encoding { // This test should pass on linux and macos. #[cfg(not(windows))] #[test] + #[cfg_attr( + wasip2_runner, + ignore = "WASI preview2: OsString requires valid UTF-8, unlike unix/wasip1" + )] fn test_check_non_utf8_comment() { use super::*; let hashes = @@ -1989,7 +1997,7 @@ mod check_encoding { at.write_bytes("check", hashes); cmd.arg("--check") - .arg(at.subdir.join("check")) + .arg("check") .succeeds() .stdout_is("empty: OK\nempty: OK\nempty: OK\n") .no_stderr(); @@ -1999,6 +2007,10 @@ mod check_encoding { // create a file which name contains '\xff'. #[cfg(target_os = "linux")] #[test] + #[cfg_attr( + wasi_runner, + ignore = "WASI: preopened directories reject non-UTF-8 filenames" + )] fn test_check_non_utf8_filename() { use super::*; use std::{ffi::OsString, os::unix::ffi::OsStringExt}; @@ -2014,7 +2026,7 @@ mod check_encoding { scene .ucmd() .arg("--check") - .arg(at.subdir.join("check")) + .arg("check") .succeeds() .stdout_is_bytes(b"'funky'$'\\377''name': OK\n") .no_stderr(); @@ -2025,7 +2037,7 @@ mod check_encoding { scene .ucmd() .arg("--check") - .arg(at.subdir.join("check")) + .arg("check") .fails() .stdout_is_bytes(b"'funky'$'\\377''name': FAILED\n") .stderr_contains("1 computed checksum did NOT match"); @@ -2036,7 +2048,7 @@ mod check_encoding { scene .ucmd() .arg("--check") - .arg(at.subdir.join("check")) + .arg("check") .fails() .stdout_is_bytes(b"'flakey'$'\\377''name': FAILED open or read\n") .stderr_contains("1 listed file could not be read"); @@ -2044,6 +2056,10 @@ mod check_encoding { #[cfg(target_os = "linux")] #[test] + #[cfg_attr( + wasi_runner, + ignore = "WASI: preopened directories reject non-UTF-8 filenames" + )] fn test_quoting_in_stderr() { use super::*; use std::{ffi::OsStr, os::unix::ffi::OsStrExt}; @@ -2089,7 +2105,7 @@ fn test_check_blake_length_guess() { scene .ucmd() .arg("--check") - .arg(at.subdir.join("foo.sums")) + .arg("foo.sums") .succeeds() .stdout_is("foo.dat: OK\n"); } @@ -2103,7 +2119,7 @@ fn test_check_blake_length_guess() { scene .ucmd() .arg("--check") - .arg(at.subdir.join("foo.sums")) + .arg("foo.sums") .fails() .stderr_contains("foo.sums: no properly formatted checksum lines found"); @@ -2114,7 +2130,7 @@ fn test_check_blake_length_guess() { scene .ucmd() .arg("--check") - .arg(at.subdir.join("foo.sums")) + .arg("foo.sums") .fails() .stderr_contains("foo.sums: no properly formatted checksum lines found"); @@ -2125,7 +2141,7 @@ fn test_check_blake_length_guess() { scene .ucmd() .arg("--check") - .arg(at.subdir.join("foo.sums")) + .arg("foo.sums") .fails() .stderr_contains("foo.sums: no properly formatted checksum lines found"); } @@ -2143,7 +2159,7 @@ fn test_check_confusing_base64() { scene .ucmd() .arg("--check") - .arg(at.subdir.join("foo.sums")) + .arg("foo.sums") .succeeds() .stdout_is("foo.dat: OK\n"); } @@ -2167,14 +2183,14 @@ fn test_check_mix_hex_base64() { scene .ucmd() .arg("--check") - .arg(at.subdir.join("hex_b64")) + .arg("hex_b64") .succeeds() .stdout_only("foo2.dat: OK\nfoo1.dat: OK\n"); scene .ucmd() .arg("--check") - .arg(at.subdir.join("b64_hex")) + .arg("b64_hex") .succeeds() .stdout_only("foo1.dat: OK\nfoo2.dat: OK\n"); } @@ -2561,6 +2577,10 @@ mod gnu_cksum_c { #[test] #[cfg_attr(not(unix), ignore = "/dev/null is only available on UNIX")] + #[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/null) not visible" + )] fn test_untagged_base64_matching_tag() { let (at, mut ucmd) = at_and_ucmd!(); @@ -3141,6 +3161,7 @@ mod debug_flag { #[test] #[cfg(all(target_os = "linux", not(target_env = "musl")))] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_check_file_with_io_error() { // /proc/self/mem causes EIO when read without proper seeking new_ucmd!() @@ -3155,6 +3176,7 @@ fn test_check_file_with_io_error() { #[test] #[cfg(all(target_os = "linux", not(target_env = "musl")))] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_check_checkfile_with_io_error() { // /proc/self/mem causes EIO when read without proper seeking new_ucmd!() diff --git a/tests/by-util/test_comm.rs b/tests/by-util/test_comm.rs index d082284485f..1c50710c7c6 100644 --- a/tests/by-util/test_comm.rs +++ b/tests/by-util/test_comm.rs @@ -455,17 +455,12 @@ fn test_sorted() { let at = &scene.fixtures; at.write("comm1", "1\n3"); at.write("comm2", "3\n2"); - let cmd = scene.ucmd().args(&["comm1", "comm2"]).run(); - // WASI's strcoll (C locale only) may not detect unsorted input, - // but the comparison output is still correct. - if std::env::var("UUTESTS_WASM_RUNNER").is_ok() { - cmd.success().stdout_is("1\n\t\t3\n\t2\n"); - } else { - cmd.failure() - .code_is(1) - .stdout_is("1\n\t\t3\n\t2\n") - .stderr_is("comm: file 2 is not in sorted order\ncomm: input is not in sorted order\n"); - } + scene + .ucmd() + .args(&["comm1", "comm2"]) + .fails_with_code(1) + .stdout_is("1\n\t\t3\n\t2\n") + .stderr_is("comm: file 2 is not in sorted order\ncomm: input is not in sorted order\n"); } #[test] @@ -492,19 +487,16 @@ fn test_both_inputs_out_of_order() { at.write("file_a", "3\n1\n0\n"); at.write("file_b", "3\n2\n0\n"); - let cmd = scene.ucmd().args(&["file_a", "file_b"]).run(); - if std::env::var("UUTESTS_WASM_RUNNER").is_ok() { - cmd.success().stdout_is("\t\t3\n1\n0\n\t2\n\t0\n"); - } else { - cmd.failure() - .code_is(1) - .stdout_is("\t\t3\n1\n0\n\t2\n\t0\n") - .stderr_is( - "comm: file 1 is not in sorted order\n\ - comm: file 2 is not in sorted order\n\ - comm: input is not in sorted order\n", - ); - } + scene + .ucmd() + .args(&["file_a", "file_b"]) + .fails_with_code(1) + .stdout_is("\t\t3\n1\n0\n\t2\n\t0\n") + .stderr_is( + "comm: file 1 is not in sorted order\n\ + comm: file 2 is not in sorted order\n\ + comm: input is not in sorted order\n", + ); } #[test] @@ -514,19 +506,16 @@ fn test_both_inputs_out_of_order_last_pair() { at.write("file_a", "3\n1\n"); at.write("file_b", "3\n2\n"); - let cmd = scene.ucmd().args(&["file_a", "file_b"]).run(); - if std::env::var("UUTESTS_WASM_RUNNER").is_ok() { - cmd.success().stdout_is("\t\t3\n1\n\t2\n"); - } else { - cmd.failure() - .code_is(1) - .stdout_is("\t\t3\n1\n\t2\n") - .stderr_is( - "comm: file 1 is not in sorted order\n\ - comm: file 2 is not in sorted order\n\ - comm: input is not in sorted order\n", - ); - } + scene + .ucmd() + .args(&["file_a", "file_b"]) + .fails_with_code(1) + .stdout_is("\t\t3\n1\n\t2\n") + .stderr_is( + "comm: file 1 is not in sorted order\n\ + comm: file 2 is not in sorted order\n\ + comm: input is not in sorted order\n", + ); } #[test] @@ -740,6 +729,7 @@ fn test_read_error() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasip2_runner, ignore = "WASI P2: /dev/full filesystem not available")] fn test_comm_write_error_dev_full() { use std::fs::OpenOptions; let scene = TestScenario::new(util_name!()); diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 9ad4ef75c4d..8c8b04ef0d0 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -95,6 +95,7 @@ macro_rules! assert_metadata_eq { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_cp_stream_to_full() { let (_, mut ucmd) = at_and_ucmd!(); ucmd.arg("/dev/zero") @@ -105,6 +106,7 @@ fn test_cp_stream_to_full() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasip2_runner, ignore = "WASI P2: /dev/full filesystem not available")] fn test_cp_verbose_write_error_is_reported() { let (at, mut ucmd) = at_and_ucmd!(); at.touch("source_file"); @@ -313,6 +315,10 @@ fn test_cp_multiple_files_with_nonexistent_file() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_multiple_files_with_empty_file_name() { #[cfg(windows)] let error_msg = "The system cannot find the path specified"; @@ -803,6 +809,10 @@ fn test_cp_arg_interactive_verbose_clobber() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_f_i_verbose_non_writeable_destination_y() { let (at, mut ucmd) = at_and_ucmd!(); @@ -821,6 +831,10 @@ fn test_cp_f_i_verbose_non_writeable_destination_y() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_f_i_verbose_non_writeable_destination_empty() { let (at, mut ucmd) = at_and_ucmd!(); @@ -1665,6 +1679,10 @@ fn test_cp_parents_dest_not_directory() { #[test] #[cfg(not(target_os = "openbsd"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_parents_with_permissions_copy_file() { let (at, mut ucmd) = at_and_ucmd!(); @@ -1706,6 +1724,10 @@ fn test_cp_parents_with_permissions_copy_file() { #[test] #[cfg(not(target_os = "openbsd"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_parents_with_permissions_copy_dir() { let (at, mut ucmd) = at_and_ucmd!(); @@ -1749,12 +1771,20 @@ fn test_cp_parents_with_permissions_copy_dir() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_writable_special_file_permissions() { new_ucmd!().arg("/dev/null").arg("/dev/zero").succeeds(); } #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_issue_1665() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.arg("/dev/null").arg("foo").succeeds(); @@ -1764,6 +1794,10 @@ fn test_cp_issue_1665() { #[test] #[cfg(not(target_os = "openbsd"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_preserve_no_args() { let (at, mut ucmd) = at_and_ucmd!(); let src_file = "a"; @@ -1792,6 +1826,10 @@ fn test_cp_preserve_no_args() { #[test] #[cfg(not(target_os = "openbsd"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_preserve_no_args_before_opts() { let (at, mut ucmd) = at_and_ucmd!(); let src_file = "a"; @@ -1819,6 +1857,10 @@ fn test_cp_preserve_no_args_before_opts() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_preserve_all() { for argument in ["--preserve=all", "--preserve=al"] { let (at, mut ucmd) = at_and_ucmd!(); @@ -2003,6 +2045,10 @@ fn test_cp_preserve_links_case_1() { #[test] // android platform will causing stderr = cp: Permission denied (os error 13) #[cfg(not(target_os = "android"))] +#[cfg_attr( + wasip2_runner, + ignore = "WASI preview2: rustix::fs::stat doesn't return stable inode identity across path lookups (wasmtime filesystem limitation)" +)] fn test_cp_preserve_links_case_2() { let (at, mut ucmd) = at_and_ucmd!(); @@ -2034,6 +2080,10 @@ fn test_cp_preserve_links_case_2() { #[test] // android platform will causing stderr = cp: Permission denied (os error 13) #[cfg(not(target_os = "android"))] +#[cfg_attr( + wasip2_runner, + ignore = "WASI preview2: rustix::fs::stat doesn't return stable inode identity across path lookups (wasmtime filesystem limitation)" +)] fn test_cp_preserve_links_case_3() { let (at, mut ucmd) = at_and_ucmd!(); @@ -2199,6 +2249,10 @@ fn test_cp_no_preserve_mode() { // For now, disable the test on Windows. Symlinks aren't well support on Windows. // It works on Unix for now and it works locally when run from a powershell #[cfg(not(windows))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_deref_folder_to_folder() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -2297,6 +2351,10 @@ fn test_cp_deref_folder_to_folder() { // For now, disable the test on Windows. Symlinks aren't well support on Windows. // It works on Unix for now and it works locally when run from a powershell #[cfg(not(windows))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_no_deref_folder_to_folder() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -2393,6 +2451,10 @@ fn test_cp_no_deref_folder_to_folder() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_archive() { let (at, mut ucmd) = at_and_ucmd!(); let ts = time::OffsetDateTime::now_utc(); @@ -2426,6 +2488,10 @@ fn test_cp_archive() { #[test] #[cfg(all(unix, not(target_os = "android")))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_archive_recursive() { let (at, mut ucmd) = at_and_ucmd!(); @@ -2568,6 +2634,7 @@ fn test_cp_no_preserve_timestamps() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_cp_target_file_dev_null() { let (at, mut ucmd) = at_and_ucmd!(); let file1 = "/dev/null"; @@ -2736,6 +2803,10 @@ fn test_cp_conflicting_update() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_reflink_insufficient_permission() { let (at, mut ucmd) = at_and_ucmd!(); @@ -2753,6 +2824,7 @@ fn test_cp_reflink_insufficient_permission() { #[cfg(target_os = "linux")] #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_closes_file_descriptors() { use rlimit::Resource; @@ -2780,6 +2852,10 @@ fn test_closes_file_descriptors() { #[cfg(any(target_os = "linux", target_os = "android"))] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_sparse_never_empty() { const BUFFER_SIZE: usize = 4096 * 4; let (at, mut ucmd) = at_and_ucmd!(); @@ -2800,6 +2876,10 @@ fn test_cp_sparse_never_empty() { #[cfg(any(target_os = "linux", target_os = "android"))] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_sparse_always_empty() { const BUFFER_SIZE: usize = 4096 * 4; for argument in ["--sparse=always", "--sparse=alway", "--sparse=al"] { @@ -2820,6 +2900,10 @@ fn test_cp_sparse_always_empty() { #[cfg(any(target_os = "linux", target_os = "android"))] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_sparse_always_non_empty() { const BUFFER_SIZE: usize = 4096 * 16 + 3; let (at, mut ucmd) = at_and_ucmd!(); @@ -3051,6 +3135,10 @@ fn test_cp_debug_sparse_always_windows() { #[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(feature = "truncate")] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_reflink_always_override() { const DISK: &str = "disk.img"; const ROOTDIR: &str = "disk_root/"; @@ -3141,6 +3229,10 @@ fn test_copy_dir_symlink() { #[test] #[cfg(not(target_os = "freebsd"))] // FIXME: fix this test for FreeBSD #[cfg(feature = "ln")] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: cross-scenario absolute host path not visible" +)] fn test_copy_dir_with_symlinks() { let (at, mut ucmd) = at_and_ucmd!(); at.mkdir("dir"); @@ -3173,6 +3265,10 @@ fn test_copy_symlink_force() { #[test] #[cfg(unix)] #[cfg(not(target_os = "openbsd"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_no_preserve_mode() { use std::os::unix::prelude::MetadataExt; @@ -3203,6 +3299,10 @@ fn test_no_preserve_mode() { #[test] #[cfg(unix)] #[cfg(not(target_os = "openbsd"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_preserve_mode() { use std::os::unix::prelude::MetadataExt; @@ -3358,6 +3458,10 @@ fn test_cp_dangling_symlink_inside_directory() { /// Test for copying a dangling symbolic link and its permissions. #[cfg(not(any(target_os = "freebsd", target_os = "openbsd")))] // FIXME: fix this test for FreeBSD/OpenBSD #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_copy_through_dangling_symlink_no_dereference_permissions() { let (at, mut ucmd) = at_and_ucmd!(); // target name link name @@ -3436,6 +3540,10 @@ fn test_cp_link_backup() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_fifo() { let (at, mut ucmd) = at_and_ucmd!(); at.mkfifo("fifo"); @@ -3456,6 +3564,10 @@ fn test_cp_fifo() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_fifo_preserve_timestamps() { // Preserving timestamps must not open the FIFO: opening a FIFO with no // writer blocks forever. If this regresses, the test hangs instead of @@ -3515,6 +3627,10 @@ fn test_cp_recursive_char_device(#[case] flag: &str) { #[case::recursive("-R")] #[case::archive("-a")] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/null) not visible" +)] fn test_cp_recursive_char_device_no_permission(#[case] flag: &str) { new_ucmd!() .args(&[flag, "/dev/null", "null2"]) @@ -3524,6 +3640,10 @@ fn test_cp_recursive_char_device_no_permission(#[case] flag: &str) { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_recursive_char_device_copy_contents() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["-R", "--copy-contents", "/dev/null", "null2"]) @@ -3535,6 +3655,10 @@ fn test_cp_recursive_char_device_copy_contents() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_char_device() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["/dev/null", "null2"]).succeeds().no_stderr(); @@ -3601,6 +3725,10 @@ fn test_cp_block_device_no_permission() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_socket() { let (at, mut ucmd) = at_and_ucmd!(); at.mksocket("socket"); @@ -3635,6 +3763,10 @@ fn find_other_group(_current: u32) -> Option { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_r_symlink() { let (at, mut ucmd) = at_and_ucmd!(); // Specifically test copying a link in a subdirectory, as the internal path @@ -3765,6 +3897,10 @@ fn test_cp_overriding_arguments() { } #[test] +#[cfg_attr( + wasip2_runner, + ignore = "WASI preview2: rustix::fs::stat doesn't return stable inode identity across path lookups (wasmtime filesystem limitation)" +)] fn test_copy_no_dereference_1() { let (at, mut ucmd) = at_and_ucmd!(); at.mkdir("a"); @@ -3813,6 +3949,10 @@ fn test_copy_same_symlink_no_dereference_dangling() { // TODO: enable for Android, when #3477 solved #[cfg(not(any(windows, target_os = "android", target_os = "openbsd")))] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_parents_2_dirs() { let (at, mut ucmd) = at_and_ucmd!(); at.mkdir_all("a/b/c"); @@ -4070,6 +4210,10 @@ fn test_copy_nested_directory_to_itself_disallowed() { /// Test for preserving permissions when copying a directory. #[cfg(all(not(windows), not(target_os = "freebsd"), not(target_os = "openbsd")))] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_copy_dir_preserve_permissions() { // Create a directory that has some non-default permissions. let (at, mut ucmd) = at_and_ucmd!(); @@ -4096,6 +4240,10 @@ fn test_copy_dir_preserve_permissions() { /// cp should preserve attributes of subdirectories when copying recursively. #[cfg(all(not(windows), not(target_os = "freebsd"), not(target_os = "openbsd")))] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_copy_dir_preserve_subdir_permissions() { let (at, mut ucmd) = at_and_ucmd!(); at.mkdir("a1"); @@ -4118,6 +4266,10 @@ fn test_copy_dir_preserve_subdir_permissions() { /// read-only mode, causing EPERM when copying files into it. #[cfg(all(not(windows), not(target_os = "freebsd"), not(target_os = "openbsd")))] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_copy_dir_preserve_readonly_source_with_files() { let (at, mut ucmd) = at_and_ucmd!(); at.mkdir("src"); @@ -4137,6 +4289,10 @@ fn test_copy_dir_preserve_readonly_source_with_files() { /// the face of an inaccessible file in that directory. #[cfg(all(not(windows), not(target_os = "freebsd"), not(target_os = "openbsd")))] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_copy_dir_preserve_permissions_inaccessible_file() { // Create a directory that has some non-default permissions and // contains an inaccessible file. @@ -4193,6 +4349,10 @@ fn test_same_file_force() { /// Test that copying file to itself with forced backup succeeds. #[cfg(not(windows))] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_same_file_force_backup() { let (at, mut ucmd) = at_and_ucmd!(); at.touch("f"); @@ -4205,6 +4365,10 @@ fn test_same_file_force_backup() { /// Test for copying the contents of a FIFO as opposed to the FIFO object itself. #[cfg(unix)] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_copy_contents_fifo() { let scenario = TestScenario::new(util_name!()); let at = &scenario.fixtures; @@ -4230,6 +4394,10 @@ fn test_copy_contents_fifo() { #[cfg(target_os = "linux")] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_reflink_never_sparse_always() { let (at, mut ucmd) = at_and_ucmd!(); @@ -4257,6 +4425,10 @@ fn test_reflink_never_sparse_always() { /// Test for preserving attributes of a hard link in a directory. #[test] #[cfg(not(any(target_os = "android", target_os = "openbsd")))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_preserve_hardlink_attributes_in_directory() { let (at, mut ucmd) = at_and_ucmd!(); @@ -4313,6 +4485,10 @@ fn test_symbolic_link_file() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_src_base_dot() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -4336,6 +4512,7 @@ fn non_utf8_name(suffix: &str) -> OsString { #[cfg(target_os = "linux")] #[test] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_non_utf8_src() { let (at, mut ucmd) = at_and_ucmd!(); let src = non_utf8_name("src"); @@ -4346,6 +4523,7 @@ fn test_non_utf8_src() { #[cfg(target_os = "linux")] #[test] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_non_utf8_dest() { let (at, mut ucmd) = at_and_ucmd!(); let dest = non_utf8_name("dest"); @@ -4357,6 +4535,7 @@ fn test_non_utf8_dest() { #[cfg(target_os = "linux")] #[test] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_non_utf8_target() { let (at, mut ucmd) = at_and_ucmd!(); let dest = non_utf8_name("dest"); @@ -4371,6 +4550,10 @@ fn test_non_utf8_target() { #[test] #[cfg(not(windows))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_archive_on_directory_ending_dot() { let (at, mut ucmd) = at_and_ucmd!(); at.mkdir("dir1"); @@ -4382,6 +4565,10 @@ fn test_cp_archive_on_directory_ending_dot() { #[test] #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_debug_default() { #[cfg(target_os = "macos")] let expected = "copy offload: unknown, reflink: unsupported, sparse detection: unsupported"; @@ -4404,6 +4591,10 @@ fn test_cp_debug_default() { #[test] #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_debug_multiple_default() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -4433,6 +4624,10 @@ fn test_cp_debug_multiple_default() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_sparse_reflink() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -4465,6 +4660,10 @@ fn test_cp_debug_no_update() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_sparse_always() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -4481,6 +4680,10 @@ fn test_cp_debug_sparse_always() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_sparse_never() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -4496,6 +4699,10 @@ fn test_cp_debug_sparse_never() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_debug_sparse_auto() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -4528,6 +4735,10 @@ fn test_cp_debug_sparse_auto() { #[test] #[cfg(any(target_os = "linux", target_os = "macos"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_debug_reflink_auto() { #[cfg(target_os = "macos")] let expected = "copy offload: unknown, reflink: unsupported, sparse detection: unsupported"; @@ -4549,6 +4760,10 @@ fn test_cp_debug_reflink_auto() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_sparse_always_reflink_auto() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -4593,6 +4808,10 @@ fn test_cp_dest_no_permissions() { /// Test readonly destination behavior with reflink options #[cfg(any(target_os = "linux", target_os = "macos"))] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_readonly_dest_with_reflink() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -4736,6 +4955,10 @@ fn test_cp_attributes_only_dest_open_error() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_cannot_create_regular_file() { let (at, mut ucmd) = at_and_ucmd!(); at.write("source.txt", "hello"); @@ -4747,6 +4970,10 @@ fn test_cp_cannot_create_regular_file() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_cannot_create_regular_file_attributes_only() { let (at, mut ucmd) = at_and_ucmd!(); at.write("source.txt", "hello"); @@ -4817,6 +5044,10 @@ fn test_cp_no_such() { not(any(target_os = "android", target_os = "macos", target_os = "openbsd")) ))] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_acl_preserve() { use std::process::Command; @@ -4857,6 +5088,10 @@ fn test_acl_preserve() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_reflink_never_with_hole() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -4882,6 +5117,10 @@ fn test_cp_debug_reflink_never_with_hole() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_reflink_never_empty_file_with_hole() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -4907,6 +5146,10 @@ fn test_cp_debug_reflink_never_empty_file_with_hole() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_default_with_hole() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -4933,6 +5176,10 @@ fn test_cp_debug_default_with_hole() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_default_less_than_512_bytes() { let ts = TestScenario::new(util_name!()); @@ -4956,6 +5203,10 @@ fn test_cp_debug_default_less_than_512_bytes() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_default_without_hole() { let ts = TestScenario::new(util_name!()); @@ -4976,6 +5227,10 @@ fn test_cp_debug_default_without_hole() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_default_empty_file_with_hole() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -5002,6 +5257,10 @@ fn test_cp_debug_default_empty_file_with_hole() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_reflink_never_sparse_always_with_hole() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -5028,6 +5287,10 @@ fn test_cp_debug_reflink_never_sparse_always_with_hole() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_reflink_never_sparse_always_without_hole() { let ts = TestScenario::new(util_name!()); let empty_bytes = [0_u8; 10000]; @@ -5053,6 +5316,10 @@ fn test_cp_debug_reflink_never_sparse_always_without_hole() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_reflink_never_sparse_always_empty_file_with_hole() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -5075,6 +5342,10 @@ fn test_cp_debug_reflink_never_sparse_always_empty_file_with_hole() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_default_virtual_file() { // This file has existed at least since 2008, so we assume that it is present on "all" Linux kernels. // https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-profiling @@ -5098,6 +5369,10 @@ fn test_cp_default_virtual_file() { } #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_reflink_auto_sparse_always_non_sparse_file_with_long_zero_sequence() { let ts = TestScenario::new(util_name!()); @@ -5124,6 +5399,10 @@ fn test_cp_debug_reflink_auto_sparse_always_non_sparse_file_with_long_zero_seque #[test] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_sparse_never_empty_sparse_file() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -5140,6 +5419,10 @@ fn test_cp_debug_sparse_never_empty_sparse_file() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_reflink_never_sparse_always_non_sparse_file_with_long_zero_sequence() { let ts = TestScenario::new(util_name!()); @@ -5167,6 +5450,10 @@ fn test_cp_debug_reflink_never_sparse_always_non_sparse_file_with_long_zero_sequ #[test] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_sparse_always_sparse_virtual_file() { // This file has existed at least since 2008, so we assume that it is present on "all" Linux kernels. // https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-profiling @@ -5191,6 +5478,10 @@ fn test_cp_debug_sparse_always_sparse_virtual_file() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_reflink_never_less_than_512_bytes() { let ts = TestScenario::new(util_name!()); @@ -5213,6 +5504,10 @@ fn test_cp_debug_reflink_never_less_than_512_bytes() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_reflink_never_sparse_never_empty_file_with_hole() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -5235,6 +5530,10 @@ fn test_cp_debug_reflink_never_sparse_never_empty_file_with_hole() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_reflink_never_file_with_hole() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -5258,6 +5557,10 @@ fn test_cp_debug_reflink_never_file_with_hole() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_sparse_never_less_than_512_bytes() { let ts = TestScenario::new(util_name!()); @@ -5281,6 +5584,10 @@ fn test_cp_debug_sparse_never_less_than_512_bytes() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_sparse_never_without_hole() { let ts = TestScenario::new(util_name!()); @@ -5303,6 +5610,10 @@ fn test_cp_debug_sparse_never_without_hole() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_sparse_never_empty_file_with_hole() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -5325,6 +5636,10 @@ fn test_cp_debug_sparse_never_empty_file_with_hole() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_sparse_never_file_with_hole() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -5348,6 +5663,10 @@ fn test_cp_debug_sparse_never_file_with_hole() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_default_sparse_virtual_file() { // This file has existed at least since 2008, so we assume that it is present on "all" Linux kernels. // https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-profiling @@ -5371,6 +5690,10 @@ fn test_cp_debug_default_sparse_virtual_file() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_sparse_never_zero_sized_virtual_file() { let ts = TestScenario::new(util_name!()); ts.ucmd() @@ -5384,6 +5707,10 @@ fn test_cp_debug_sparse_never_zero_sized_virtual_file() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_default_zero_sized_virtual_file() { let ts = TestScenario::new(util_name!()); ts.ucmd() @@ -5396,6 +5723,10 @@ fn test_cp_debug_default_zero_sized_virtual_file() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_debug_reflink_never_without_hole() { let ts = TestScenario::new(util_name!()); let filler_bytes = [0_u8; 1000]; @@ -5413,6 +5744,10 @@ fn test_cp_debug_reflink_never_without_hole() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_force_remove_destination_attributes_only_with_symlink() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -5482,6 +5817,10 @@ mod same_file { // the following tests tries to copy a file to the symlink of the same file with // various options #[test] + #[cfg_attr( + wasip2_runner, + ignore = "WASI preview2: rustix::fs::stat doesn't return stable inode identity across path lookups (wasmtime filesystem limitation)" + )] fn test_same_file_from_file_to_symlink() { for option in ["-d", "-f", "-df"] { let scene = TestScenario::new(util_name!()); @@ -5628,6 +5967,10 @@ mod same_file { // the following tests tries to copy a symlink to the file that symlink points to with // various options #[test] + #[cfg_attr( + wasip2_runner, + ignore = "WASI preview2: rustix::fs::stat doesn't return stable inode identity across path lookups (wasmtime filesystem limitation)" + )] fn test_same_file_from_symlink_to_file() { for option in ["-d", "-f", "-df", "--rem"] { let scene = TestScenario::new(util_name!()); @@ -5647,6 +5990,10 @@ mod same_file { } #[test] + #[cfg_attr( + wasip2_runner, + ignore = "WASI preview2: rustix::fs::stat doesn't return stable inode identity across path lookups (wasmtime filesystem limitation)" + )] fn test_same_file_from_symlink_to_file_with_option_backup() { for option in ["-b", "-bf"] { let scene = TestScenario::new(util_name!()); @@ -5686,6 +6033,10 @@ mod same_file { } #[test] + #[cfg_attr( + wasip2_runner, + ignore = "WASI preview2: rustix::fs::stat doesn't return stable inode identity across path lookups (wasmtime filesystem limitation)" + )] fn test_same_file_from_symlink_to_file_with_options_link() { for option in ["-l", "-dl", "-fl", "-bl", "-bfl"] { let scene = TestScenario::new(util_name!()); @@ -5704,6 +6055,10 @@ mod same_file { } #[test] + #[cfg_attr( + wasip2_runner, + ignore = "WASI preview2: rustix::fs::stat doesn't return stable inode identity across path lookups (wasmtime filesystem limitation)" + )] fn test_same_file_from_symlink_to_file_with_option_symlink() { for option in ["-s", "-sf"] { let scene = TestScenario::new(util_name!()); @@ -5755,6 +6110,10 @@ mod same_file { } #[test] + #[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: pwd reports the guest's virtual root, not the host's absolute path" + )] fn test_same_file_from_file_to_file_with_options_backup_and_no_deref() { for option in ["-bf", "-bdf"] { let scene = TestScenario::new(util_name!()); @@ -5773,6 +6132,10 @@ mod same_file { } #[test] + #[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: pwd reports the guest's virtual root, not the host's absolute path" + )] fn test_same_file_from_file_to_file_with_options_link() { for option in ["-l", "-dl", "-fl", "-dfl", "-bl", "-bdl"] { let scene = TestScenario::new(util_name!()); @@ -5790,6 +6153,10 @@ mod same_file { } #[test] + #[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: pwd reports the guest's virtual root, not the host's absolute path" + )] fn test_same_file_from_file_to_file_with_options_link_and_backup_and_force() { for option in ["-bfl", "-bdfl"] { let scene = TestScenario::new(util_name!()); @@ -5844,6 +6211,10 @@ mod same_file { } #[test] + #[cfg_attr( + wasip2_runner, + ignore = "WASI preview2: rustix::fs::stat doesn't return stable inode identity across path lookups (wasmtime filesystem limitation)" + )] fn test_same_file_from_symlink_to_symlink_with_option_force() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -6535,6 +6906,10 @@ mod link_deref { // cp --link -R 'dir_link' should create a new directory. #[test] + #[cfg_attr( + wasi_runner, + ignore = "WASI: the `same-file` crate has no WASI backend, so same-file detection always errors" + )] fn test_cp_dir_link_as_source_with_link_and_r() { for option in ["", "-L", "-H"] { let scene = TestScenario::new(util_name!()); @@ -6583,6 +6958,10 @@ mod link_deref { #[test] #[cfg(unix)] #[cfg(not(target_os = "openbsd"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_dir_perm_race_with_preserve_mode_and_ownership() { const SRC_DIR: &str = "src"; const DEST_DIR: &str = "dest"; @@ -6657,6 +7036,10 @@ fn test_preserve_attrs_overriding_1() { #[test] #[cfg(all(unix, not(target_os = "android")))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_preserve_attrs_overriding_2() { const FILE1: &str = "file1"; const FILE2: &str = "file2"; @@ -6706,6 +7089,10 @@ fn test_preserve_attrs_overriding_2() { /// Test the behavior of preserving permissions when copying through a symlink #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_symlink_permissions() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -6728,6 +7115,10 @@ fn test_cp_symlink_permissions() { /// Test the behavior of preserving permissions of parents when copying through a symlink #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_parents_symlink_permissions_file() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -6796,6 +7187,10 @@ fn test_cp_recursive_target_dir_symlink_still_allowed() { /// a symlink when source is a dir. #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_parents_symlink_permissions_dir() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -6818,6 +7213,10 @@ fn test_cp_parents_symlink_permissions_dir() { /// Test the behavior of copying a file to a destination with parents using absolute paths. #[cfg(unix)] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_parents_absolute_path() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -7006,6 +7405,10 @@ fn test_cp_preserve_xattr_readonly_source() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_archive_preserves_directory_permissions() { // Test for issue #8407 let (at, mut ucmd) = at_and_ucmd!(); @@ -7047,6 +7450,10 @@ fn test_cp_archive_preserves_directory_permissions() { #[test] #[cfg(unix)] #[cfg_attr(target_os = "macos", ignore = "Flaky on MacOS, see #8453")] +#[cfg_attr( + all(wasi_runner, not(target_os = "macos")), + ignore = "WASI sandbox: host paths not visible" +)] fn test_cp_from_stdin() { let (at, mut ucmd) = at_and_ucmd!(); let target = "target"; @@ -7115,6 +7522,10 @@ fn test_cp_update_none_interactive_prompt_no() { /// only unix has `/dev/fd/0` #[cfg(unix)] #[cfg_attr(target_os = "macos", ignore = "Flaky on MacOS, see #8453")] +#[cfg_attr( + all(wasi_runner, not(target_os = "macos")), + ignore = "WASI sandbox: host paths not visible" +)] #[test] fn test_cp_from_stream() { let target = "target"; @@ -7142,6 +7553,10 @@ fn test_cp_from_stream() { /// only unix has `/dev/fd/0` #[cfg(unix)] #[cfg_attr(target_os = "macos", ignore = "Flaky on MacOS, see #8453")] +#[cfg_attr( + all(wasi_runner, not(target_os = "macos")), + ignore = "WASI sandbox: host paths not visible" +)] #[test] fn test_cp_from_stream_permission() { let target = "target"; @@ -7565,6 +7980,10 @@ fn test_cp_preserve_context_root() { // to an existing directory, ensuring the directory name is properly // stripped from the descendant path. #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_current_directory_to_existing_directory() { let (at, mut ucmd) = at_and_ucmd!(); @@ -7597,6 +8016,10 @@ fn test_cp_current_directory_to_existing_directory() { // Test copying current directory (.) to a new directory. // This should create the new directory and copy contents. #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_current_directory_to_new_directory() { let (at, mut ucmd) = at_and_ucmd!(); @@ -7625,6 +8048,10 @@ fn test_cp_current_directory_to_new_directory() { // Test copying current directory (.) with verbose output. // This ensures the verbose output shows the correct paths. #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_current_directory_verbose() { let (at, mut ucmd) = at_and_ucmd!(); @@ -7660,6 +8087,10 @@ fn test_cp_current_directory_verbose() { // This ensures attributes are preserved when copying the current directory. #[test] #[cfg(all(not(windows), not(target_os = "freebsd"), not(target_os = "openbsd")))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_current_directory_preserve_attributes() { use filetime::FileTime; use std::os::unix::prelude::MetadataExt; @@ -7746,6 +8177,10 @@ fn test_cp_current_directory_to_itself_disallowed() { // Test copying current directory (.) with symlinks. // This ensures symlinks are handled correctly when copying the current directory. #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_current_directory_with_symlinks() { let (at, mut ucmd) = at_and_ucmd!(); @@ -7980,6 +8415,10 @@ fn test_cp_hlp_flag_ordering() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_archive_deref_flag_ordering() { // (flags, expect_symlink): last flag wins; a/d imply -P, H/L dereference for (flags, expect_symlink) in [ @@ -8005,6 +8444,10 @@ fn test_cp_archive_deref_flag_ordering() { /// https://github.com/uutils/coreutils/issues/13207 #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_archive_deref_preserves_recursive() { for flags in ["-afL", "-aLf", "-aHL", "-adL"] { let (at, mut ucmd) = at_and_ucmd!(); @@ -8022,6 +8465,10 @@ fn test_cp_archive_deref_preserves_recursive() { /// -aL should preserve file permissions (--preserve=all from -a). #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_archive_deref_preserves_mode() { let (at, mut ucmd) = at_and_ucmd!(); at.mkdir("srcdir"); @@ -8060,6 +8507,10 @@ fn test_cp_no_deref_preserve_with_deref_keeps_hardlinks() { /// while -a preserves them (last-flag-wins for dereference). #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_archive_deref_symlinks_inside_dir() { use std::os::unix::fs::symlink; let scene = TestScenario::new(util_name!()); @@ -8093,6 +8544,10 @@ fn test_cp_archive_deref_symlinks_inside_dir() { /// -aH: inner symlinks preserved (a wins for recursive), CLI symlinks followed (H wins for CLI). #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_archive_cli_deref_inner_preserved() { use std::os::unix::fs::symlink; let scene = TestScenario::new(util_name!()); @@ -8119,6 +8574,10 @@ fn test_cp_archive_cli_deref_inner_preserved() { /// Precedence: repeating the same flag should take the last position. #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_archive_deref_repeated_flag_last_wins() { use std::os::unix::fs::symlink; let scene = TestScenario::new(util_name!()); @@ -8264,6 +8723,10 @@ fn test_cp_xattr_enotsup_handling() { #[test] #[cfg(not(target_os = "windows"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_preserve_directory_permissions_by_default() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -8430,6 +8893,10 @@ fn test_cp_preserve_context_with_z_fails() { // is exercised by GNU's test suite; documenting here as future coverage. #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_cp_preserve_setuid_when_chown_succeeds() { let (at, mut ucmd) = at_and_ucmd!(); at.touch("src"); @@ -8447,6 +8914,7 @@ fn test_cp_preserve_setuid_when_chown_succeeds() { #[test] #[cfg(all(unix, not(target_os = "macos")))] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_cp_recursive_non_utf8_source() { use std::{ffi::OsStr, os::unix::ffi::OsStrExt}; let (at, mut ucmd) = at_and_ucmd!(); @@ -8532,6 +9000,10 @@ fn test_cp_d_overwrites_existing_symlink_dest() { // non-Linux CI do not flag spurious failures. #[test] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sparse/reflink/ACL copy-on-write features not supported" +)] fn test_cp_p_preserves_posix_acls() { use std::process::Command; diff --git a/tests/by-util/test_csplit.rs b/tests/by-util/test_csplit.rs index 2349fc34648..15336973c14 100644 --- a/tests/by-util/test_csplit.rs +++ b/tests/by-util/test_csplit.rs @@ -1500,6 +1500,10 @@ fn repeat_everything() { #[cfg(unix)] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: reading a FIFO returns EINVAL/'Invalid seek'" +)] fn test_named_pipe_input_file() { let (at, mut ucmd) = at_and_ucmd!(); @@ -1556,6 +1560,7 @@ fn test_stdin_no_trailing_newline() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_csplit_non_utf8_paths() { use std::os::unix::ffi::OsStringExt; let (at, mut ucmd) = at_and_ucmd!(); @@ -1569,6 +1574,10 @@ fn test_csplit_non_utf8_paths() { /// Test write error detection using /dev/full #[test] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/full) not visible" +)] fn test_write_error_dev_full() { let (at, mut ucmd) = at_and_ucmd!(); at.symlink_file("/dev/full", "xx01"); @@ -1585,6 +1594,10 @@ fn test_write_error_dev_full() { /// Test write error with -k keeps files #[test] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/full) not visible" +)] fn test_write_error_dev_full_keep_files() { let (at, mut ucmd) = at_and_ucmd!(); at.symlink_file("/dev/full", "xx01"); diff --git a/tests/by-util/test_cut.rs b/tests/by-util/test_cut.rs index 563c752b115..c62330334de 100644 --- a/tests/by-util/test_cut.rs +++ b/tests/by-util/test_cut.rs @@ -623,8 +623,9 @@ fn test_emoji_delim() { .stdout_only("🌹\n"); } -#[cfg(target_os = "linux")] #[test] +#[cfg(target_os = "linux")] +#[cfg_attr(wasip2_runner, ignore = "WASI P2: /dev/full filesystem not available")] fn test_failed_write_is_reported() { new_ucmd!() .arg("-d=") diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index 1b7f18dd7d8..97b0bf8680b 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -341,6 +341,10 @@ fn test_date_utc_with_d_flag() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: timezone/locale database not visible" +)] fn test_date_utc_vs_local() { let cases = [ ("-d", "2024-01-01 12:00", "+%H:%M %Z", "12:00 EST\n"), @@ -487,6 +491,10 @@ fn test_date_set_invalid() { #[test] #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: setting the system clock is not supported at all, not just permission-gated" +)] fn test_date_set_permissions_error() { if !(geteuid().is_root() || uucore::os::is_wsl_1()) { let result = new_ucmd!() @@ -500,6 +508,10 @@ fn test_date_set_permissions_error() { #[test] #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: setting the system clock is not supported at all, not just permission-gated" +)] fn test_date_set_hyphen_prefixed_values() { // test -s flag accepts hyphen-prefixed values like "-3 days" if !(geteuid().is_root() || uucore::os::is_wsl_1()) { @@ -520,6 +532,10 @@ fn test_date_set_hyphen_prefixed_values() { #[test] #[cfg(target_os = "macos")] +#[cfg_attr( + wasi_runner, + ignore = "test binary runs on macOS but the wasm guest under test does not, so the expected macOS-specific error text never appears" +)] fn test_date_set_mac_unavailable() { let result = new_ucmd!() .arg("--set") @@ -820,7 +836,7 @@ fn test_date_parse_from_format() { 2023-04-15 18:30:00", ); ucmd.arg("-f") - .arg(at.plus(FILE)) + .arg(FILE) .arg("+%Y-%m-%d %H:%M:%S") .succeeds(); } @@ -848,6 +864,10 @@ const JAN2: &str = "2024-01-02 12:00:00 +0000"; const JUL2: &str = "2024-07-02 12:00:00 +0000"; #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: timezone/locale database not visible" +)] fn test_date_tz() { fn test_tz(tz: &str, date: &str, output: &str) { println!("Test with TZ={tz}, date=\"{date}\"."); @@ -894,6 +914,10 @@ fn test_date_tz_with_utc_flag() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: timezone/locale database not visible" +)] fn test_date_tz_various_formats() { fn test_tz(tz: &str, date: &str, output: &str) { println!("Test with TZ={tz}, date=\"{date}\"."); @@ -922,6 +946,10 @@ fn test_date_tz_various_formats() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: timezone/locale database not visible" +)] fn test_date_tz_with_relative_time() { new_ucmd!() .env("TZ", "America/Vancouver") @@ -933,6 +961,10 @@ fn test_date_tz_with_relative_time() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: timezone/locale database not visible" +)] fn test_date_utc_time() { // Test that -u flag shows correct UTC time // We get 2 UTC times just in case we're really unlucky and this runs around @@ -1409,6 +1441,10 @@ fn test_date_empty_string_variations() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: timezone/locale database not visible" +)] fn test_date_relative_m9() { // Military timezone "m9" should be parsed as noon + 9 hours = 21:00 UTC // When displayed in TZ=UTC+9 (which is UTC-9), this shows as 12:00 local time @@ -1706,6 +1742,10 @@ fn test_date_locale_en_us_vs_c_difference() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: timezone/locale database not visible" +)] fn test_date_locale_hu_hungarian() { // Regression test for uutils/coreutils#11240: the GNU modifier fast-path // ("%-e") used to run before ICU localization, so "%b"/"%A" came out in @@ -1997,6 +2037,10 @@ fn test_date_input_hhmm_ampm() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: timezone/locale database not visible" +)] fn test_date_input_trailing_tz_abbrev_rezones() { // `TZ=UTC+1 date -d '2024-01-01 EST'` should display the instant in UTC+1 // (GNU: 04:00:00 -01:00), not leave it in EST (the pre-fix uutils @@ -2126,6 +2170,10 @@ fn test_date_parenthesis_vs_other_special_chars() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: timezone/locale database not visible" +)] fn test_date_iranian_locale_solar_hijri_calendar() { // Test Iranian locale uses Solar Hijri calendar // Verify the Solar Hijri calendar is used in the Iranian locale @@ -2193,6 +2241,10 @@ fn test_date_iranian_locale_solar_hijri_calendar() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: timezone/locale database not visible" +)] fn test_date_ethiopian_locale_calendar() { // Test Ethiopian locale uses Ethiopian calendar // Verify the Ethiopian calendar is used in the Ethiopian locale @@ -2340,6 +2392,10 @@ fn check_date(locale: &str, date: &str, fmt: &str, expected: &str) { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: timezone/locale database not visible" +)] fn test_locale_calendar_conversions() { // Persian (Solar Hijri) - Nowruz is March 20/21 for (d, e) in [ @@ -2391,6 +2447,10 @@ fn test_locale_calendar_conversions() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: timezone/locale database not visible" +)] fn test_locale_month_names() { // %B full month names: Jan, Jun, Dec for each locale for (loc, jan, jun, dec) in [ @@ -2411,6 +2471,10 @@ fn test_locale_month_names() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: timezone/locale database not visible" +)] fn test_locale_abbreviated_month_names() { // %b abbreviated month names: Feb, Jun, Dec for each locale // This test ensures we don't get double periods in locales like Hungarian @@ -2434,6 +2498,10 @@ fn test_locale_abbreviated_month_names() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: timezone/locale database not visible" +)] fn test_locale_day_names() { // %A full day names: Mon (26th), Sun (25th), Sat (24th) Jan 2026 for (loc, mon, sun, sat) in [ @@ -2535,6 +2603,10 @@ fn test_date_rel2b_month_arithmetic() { // Tests for GNU test cross-TZ-mishandled: embedded timezone parsing #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: timezone/locale database not visible" +)] fn test_date_cross_tz_mishandled() { // GNU test cross-TZ-mishandled: Parse date with embedded timezone // Date should be interpreted in embedded TZ, then displayed in environment TZ @@ -2551,6 +2623,7 @@ fn test_date_cross_tz_mishandled() { // Tests for GNU test invalid-high-bit-set: invalid UTF-8 in date string #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore = "WASI: argv must be valid UTF-8")] fn test_date_invalid_high_bit_set() { use std::os::unix::ffi::OsStrExt; diff --git a/tests/by-util/test_dir.rs b/tests/by-util/test_dir.rs index 0e1784fe4ff..753400de489 100644 --- a/tests/by-util/test_dir.rs +++ b/tests/by-util/test_dir.rs @@ -86,6 +86,7 @@ fn test_version() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasip2_runner, ignore = "WASI P2: /dev/full filesystem not available")] fn test_write_error() { let scene = TestScenario::new(util_name!()); scene.fixtures.touch("file"); diff --git a/tests/by-util/test_dircolors.rs b/tests/by-util/test_dircolors.rs index 871151fe3fc..d20b38d2775 100644 --- a/tests/by-util/test_dircolors.rs +++ b/tests/by-util/test_dircolors.rs @@ -11,6 +11,7 @@ use dircolors::StrUtils; #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_dircolors_non_utf8_paths() { use std::os::unix::ffi::OsStringExt; let (at, mut ucmd) = at_and_ucmd!(); diff --git a/tests/by-util/test_factor.rs b/tests/by-util/test_factor.rs index 8833c9d96d9..292e99f2a3b 100644 --- a/tests/by-util/test_factor.rs +++ b/tests/by-util/test_factor.rs @@ -387,6 +387,7 @@ fn fails_on_invalid_number() { #[test] #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +#[cfg_attr(wasip2_runner, ignore = "WASI P2: /dev/full filesystem not available")] fn short_circuit_write_error() { use std::fs::OpenOptions; diff --git a/tests/by-util/test_false.rs b/tests/by-util/test_false.rs index b5e2490f080..04134413ac4 100644 --- a/tests/by-util/test_false.rs +++ b/tests/by-util/test_false.rs @@ -51,6 +51,7 @@ fn test_conflict() { #[test] #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +#[cfg_attr(wasip2_runner, ignore = "WASI P2: /dev/full filesystem not available")] fn test_full() { for option in ["--version", "--help"] { let dev_full = OpenOptions::new().write(true).open("/dev/full").unwrap(); diff --git a/tests/by-util/test_fmt.rs b/tests/by-util/test_fmt.rs index fe27d4da612..2f1f8561b66 100644 --- a/tests/by-util/test_fmt.rs +++ b/tests/by-util/test_fmt.rs @@ -414,6 +414,7 @@ fn test_fmt_knuth_plass_line_breaking() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_fmt_non_utf8_paths() { use uutests::at_and_ucmd; diff --git a/tests/by-util/test_fold.rs b/tests/by-util/test_fold.rs index 1d8f3eb5978..adf32aec39e 100644 --- a/tests/by-util/test_fold.rs +++ b/tests/by-util/test_fold.rs @@ -285,8 +285,9 @@ fn test_zero_width_data_line_counts() { ); } -#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] #[test] +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +#[cfg_attr(wasip2_runner, ignore = "WASI P2: /dev/full filesystem not available")] fn test_fold_reports_no_space_left_on_dev_full() { use std::fs::OpenOptions; use std::process::Stdio; diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index fbd953a7c2b..6f60ac1a0c4 100644 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -897,8 +897,9 @@ fn test_all_but_last_lines() { .stdout_is_fixture("lorem_ipsum_backwards_15_lines.expected"); } -#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] #[test] +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +#[cfg_attr(wasip2_runner, ignore = "WASI P2: /dev/full filesystem not available")] fn test_write_to_dev_full() { use std::fs::OpenOptions; diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index 8e19b54c55f..62e77dcb181 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -792,8 +792,12 @@ fn strip_source_file() -> PathBuf { BINARY .get_or_init(|| { let dir = std::env::temp_dir(); - let source = dir.join("hello.rs"); - let binary = dir.join("hello_bin"); + // Include the PID so concurrent test processes (e.g. under `cargo + // nextest`, which runs each test in its own process) don't race + // on the same source/binary path. + let pid = process::id(); + let source = dir.join(format!("hello-{pid}.rs")); + let binary = dir.join(format!("hello_bin-{pid}")); let mut file = File::create(&source).unwrap(); file.write_all(b"fn main() {}").unwrap(); process::Command::new("rustc") diff --git a/tests/by-util/test_join.rs b/tests/by-util/test_join.rs index fbb6e88d3fb..be81ecb736c 100644 --- a/tests/by-util/test_join.rs +++ b/tests/by-util/test_join.rs @@ -447,6 +447,7 @@ fn non_line_feeds() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI: argv must be valid UTF-8")] fn non_unicode() { new_ucmd!() .arg("non-unicode_1.bin") @@ -522,6 +523,7 @@ fn null_line_endings() { #[test] #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +#[cfg_attr(wasip2_runner, ignore = "WASI P2: /dev/full filesystem not available")] fn test_full() { let dev_full = OpenOptions::new().write(true).open("/dev/full").unwrap(); new_ucmd!() @@ -534,6 +536,7 @@ fn test_full() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_join_non_utf8_paths() { use std::fs::File; use std::io::Write; @@ -581,6 +584,7 @@ fn join_emoji_delim_inner_key() { #[cfg(unix)] #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: locale database not visible")] fn test_locale_collation() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 50e92721bdf..dde7667d755 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -238,6 +238,7 @@ fn get_filesystem_type(scene: &TestScenario, path: &Path) -> String { #[cfg(all(feature = "truncate", feature = "dd"))] #[test] // FIXME: fix this test for FreeBSD and OpenBSD #[cfg(not(target_os = "openbsd"))] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_ls_allocation_size() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -479,6 +480,10 @@ fn test_ls_allocation_size() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/null) not visible" +)] fn test_ls_devices() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -1232,6 +1237,10 @@ fn test_ls_long_padding_of_size_column_with_multiple_files() { #[test] #[cfg(all(feature = "ln", feature = "mkdir", feature = "touch"))] #[allow(clippy::items_after_statements)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: st_mode has no real permission bits; color/mode-dependent output differs" +)] fn test_ls_long_symlink_color() { // If you break this test after breaking mkdir, touch, or ln, do not be alarmed! // This test is made for ls, but it attempts to run those utils in the process. @@ -1489,7 +1498,7 @@ fn test_ls_long_dangling_symlink_color() { // Ensure dangling link name uses `or=` and target uses `mi=`. let name_regex = Regex::new(r"(?:\x1b\[[0-9;]*m)*\x1b\[([0-9;]*)mdir1/dangling_symlink\x1b\[0m").unwrap(); - let target_path = regex::escape(&at.plus_as_string("foo")); + let target_path = regex::escape(&format!("..{}foo", std::path::MAIN_SEPARATOR)); let target_pattern = format!(r"(?:\x1b\[[0-9;]*m)*\x1b\[([0-9;]*)m{target_path}\x1b\[0m"); let target_regex = Regex::new(&target_pattern).unwrap(); @@ -1722,6 +1731,10 @@ fn test_ls_directory_dangling_symlink_uses_ln_when_or_blank() { #[test] #[cfg(not(target_os = "openbsd"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: st_mode has no real permission bits; color/mode-dependent output differs" +)] fn test_ls_long_total_size() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -2179,6 +2192,10 @@ fn test_ls_order_size() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: ctime is unavailable via std::fs::Metadata on stable" +)] fn test_ls_long_ctime() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -2222,6 +2239,10 @@ fn test_ls_order_birthtime() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: locale database not visible (time-style=locale)" +)] fn test_ls_time_styles() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -2510,6 +2531,10 @@ fn test_ls_time_recent_future() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: access/change time tracking granularity does not match the host filesystem's" +)] fn test_ls_order_time() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -2805,6 +2830,10 @@ mod quoting { // and must not escape embedded apostrophes or double quotes; in the C // locale they fall back to ASCII single/double quotes. #[test] + #[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: locale database not visible (host locale check passes but the wasm guest can't use it)" + )] fn test_ls_quoting_locale_utf8() { if !is_locale_available("en_US.UTF-8") { return; @@ -3278,6 +3307,7 @@ mod quoting { #[cfg(not(any(target_vendor = "apple", target_os = "windows", target_os = "openbsd")))] #[test] + #[cfg_attr(wasi_runner, ignore = "WASI sandbox: locale database not visible")] /// This test creates files with an UTF-8 encoded name and verify that it /// gets escaped depending on the used locale. fn test_locale_aware_quoting() { @@ -3342,6 +3372,7 @@ mod quoting { } #[test] + #[cfg_attr(wasi_runner, ignore = "WASI sandbox: locale database not visible")] fn test_c_dot_utf8_renders_utf8() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -3437,6 +3468,10 @@ fn test_ls_color() { #[test] #[cfg(not(feature = "feat_selinux"))] // Disabled on the SELinux runner for now +#[cfg_attr( + wasi_runner, + ignore = "WASI: inode display is gated to unix; needs a rustix::fs::stat-based path" +)] fn test_ls_inode() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -3479,6 +3514,10 @@ fn test_ls_inode() { #[test] #[cfg(not(windows))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: FileTypeExt::is_fifo() is unix-only, so FIFOs aren't classified" +)] fn test_ls_indicator_style() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -3594,7 +3633,7 @@ fn test_ls_indicator_style_symlink_target_long() { .succeeds() .stdout_contains("dir_link -> ") .stdout_does_not_contain("dir_link@ -> ") - .stdout_contains("/dir/"); + .stdout_contains(" dir/"); } #[test] @@ -4852,6 +4891,7 @@ fn test_ls_sort_extension() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: absolute host paths not visible")] fn test_ls_path() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -4894,6 +4934,10 @@ fn test_ls_path() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: inode display is gated to unix; needs a rustix::fs::stat-based path" +)] fn test_ls_dangling_symlinks() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -5453,6 +5497,10 @@ fn test_tabsize_formatting() { #[cfg(all(unix, not(target_os = "android")))] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/console) not visible" +)] fn test_device_number() { use std::fs::{metadata, read_dir}; use std::os::unix::fs::{FileTypeExt, MetadataExt}; @@ -5484,6 +5532,10 @@ fn test_device_number() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI: preopened directories reject non-UTF-8 filenames" +)] fn test_invalid_utf8() { let (at, mut ucmd) = at_and_ucmd!(); @@ -5528,6 +5580,10 @@ fn test_ls_dired_implies_long() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: current_directory_resolved differs from host path used in expected hyperlink URI" +)] fn test_ls_dired_hyperlink() { // we will have link but not the DIRED output // note that the order matters @@ -5953,6 +6009,10 @@ fn test_ls_cf_output_should_be_delimited_by_tab() { #[cfg(all(unix, feature = "dd"))] #[test] #[cfg(not(target_os = "openbsd"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/zero) not visible" +)] fn test_posixly_correct_and_block_size_env_vars() { let scene = TestScenario::new(util_name!()); @@ -6007,6 +6067,10 @@ fn test_posixly_correct_and_block_size_env_vars() { #[cfg(all(unix, feature = "dd"))] #[test] #[cfg(not(target_os = "openbsd"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/zero) not visible" +)] fn test_posixly_correct_and_block_size_env_vars_with_k() { let scene = TestScenario::new(util_name!()); @@ -6073,6 +6137,10 @@ fn test_ls_invalid_block_size() { #[cfg(all(unix, feature = "dd"))] #[test] #[cfg(not(target_os = "openbsd"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/zero) not visible" +)] fn test_ls_invalid_block_size_in_env_var() { let scene = TestScenario::new(util_name!()); @@ -6120,6 +6188,10 @@ fn test_ls_invalid_block_size_in_env_var() { #[cfg(all(unix, feature = "dd"))] #[test] #[cfg(not(target_os = "openbsd"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/zero) not visible" +)] fn test_ls_block_size_override() { let scene = TestScenario::new(util_name!()); @@ -6231,6 +6303,10 @@ fn test_ls_block_size_override_self() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: current_directory_resolved differs from host path used in expected hyperlink URI" +)] fn test_ls_hyperlink() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -6308,6 +6384,10 @@ fn test_ls_hyperlink_encode_link() { // spell-checker: enable #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: current_directory_resolved differs from host path used in expected hyperlink URI" +)] fn test_ls_hyperlink_dirs() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -6352,6 +6432,10 @@ fn test_ls_hyperlink_dirs() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: current_directory_resolved differs from host path used in expected hyperlink URI" +)] fn test_ls_hyperlink_recursive_dirs() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -6549,6 +6633,10 @@ fn test_term_colorterm() { #[cfg(all(unix, not(target_os = "macos")))] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_acl_display() { use std::process::Command; @@ -6605,6 +6693,10 @@ fn test_acl_display() { // Each file with an ACL must not inflate the link-count column width. #[cfg(all(unix, not(target_os = "macos")))] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_acl_padding_not_inflated() { use std::process::Command; @@ -6659,6 +6751,10 @@ fn test_acl_padding_not_inflated() { #[test] #[cfg(not(feature = "feat_selinux"))] // Disabled on the SELinux runner for now +#[cfg_attr( + wasi_runner, + ignore = "WASI: st_mode has no real permission bits; color/mode-dependent output differs" +)] fn test_ls_color_norm() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -7025,6 +7121,10 @@ fn test_unknown_format_specifier() { #[cfg(all(unix, not(target_os = "macos")))] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_acl_display_symlink() { use std::process::Command; @@ -7158,6 +7258,7 @@ fn test_ls_time_style_iso_recent_and_older() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: locale database not visible")] fn test_ls_time_style_posix_locale_override() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -7549,6 +7650,10 @@ fn test_f_flag_combined_behavior() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: st_mode has no real permission bits, only file-type; ls -l shows placeholder rwx" +)] fn test_f_with_long_format() { // Test that -f works with long format (-l) let scene = TestScenario::new(util_name!()); @@ -7572,6 +7677,7 @@ fn test_f_with_long_format() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_ls_proc_self_fd_no_errors() { // Regression test: ReadDir must stay alive until metadata() is called // to prevent "cannot access '/proc/self/fd/3'" errors. @@ -7587,6 +7693,10 @@ fn test_ls_proc_self_fd_no_errors() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "wasmtime's --dir sandbox hits its own fd/depth limit before 30 levels, unrelated to whether ls itself leaks fds" +)] fn test_ls_recursive_no_fd_leak() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -7608,6 +7718,10 @@ fn test_ls_recursive_no_fd_leak() { #[test] #[cfg(all(unix, not(target_os = "macos")))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: preopened directories reject non-UTF-8 filenames" +)] fn test_ls_non_utf8_hidden() { use std::{ffi::OsStr, os::unix::ffi::OsStrExt}; let scene = TestScenario::new(util_name!()); diff --git a/tests/by-util/test_md5sum.rs b/tests/by-util/test_md5sum.rs index e70381ab702..9044b9de09d 100644 --- a/tests/by-util/test_md5sum.rs +++ b/tests/by-util/test_md5sum.rs @@ -131,7 +131,7 @@ fn test_check_md5_ignore_missing() { scene .ccmd("md5sum") .arg("-c") - .arg(at.subdir.join("testf.sha1")) + .arg("testf.sha1") .fails() .stdout_contains("testf2: FAILED open or read"); @@ -139,14 +139,14 @@ fn test_check_md5_ignore_missing() { .ccmd("md5sum") .arg("-c") .arg("--ignore-missing") - .arg(at.subdir.join("testf.sha1")) + .arg("testf.sha1") .succeeds() .stdout_only("testf: OK\n"); scene .ccmd("md5sum") .arg("--ignore-missing") - .arg(at.subdir.join("testf.sha1")) + .arg("testf.sha1") .fails() .stderr_contains( "md5sum: the --ignore-missing option is meaningful only when verifying checksums", @@ -389,7 +389,7 @@ fn test_check_empty_line() { scene .ccmd("md5sum") .arg("--check") - .arg(at.subdir.join("in.md5")) + .arg("in.md5") .succeeds() .stderr_contains("WARNING: 1 line is improperly formatted"); } @@ -432,7 +432,7 @@ fn test_check_strict_error() { .ccmd("md5sum") .arg("--check") .arg("--strict") - .arg(at.subdir.join("in.md5")) + .arg("in.md5") .fails() .stderr_contains("WARNING: 3 lines are improperly formatted"); } @@ -451,7 +451,7 @@ fn test_check_warn() { .ccmd("md5sum") .arg("--check") .arg("--warn") - .arg(at.subdir.join("in.md5")) + .arg("in.md5") .succeeds() .stderr_contains("in.md5: 3: improperly formatted MD5 checksum line") .stderr_contains("WARNING: 1 line is improperly formatted"); @@ -461,7 +461,7 @@ fn test_check_warn() { .ccmd("md5sum") .arg("--check") .arg("--strict") - .arg(at.subdir.join("in.md5")) + .arg("in.md5") .fails(); } @@ -476,7 +476,7 @@ fn test_check_status() { .ccmd("md5sum") .arg("--check") .arg("--status") - .arg(at.subdir.join("in.md5")) + .arg("in.md5") .fails() .no_output(); } @@ -492,7 +492,7 @@ fn test_check_status_code() { .ccmd("md5sum") .arg("--check") .arg("--status") - .arg(at.subdir.join("in.md5")) + .arg("in.md5") .fails() .no_output(); } @@ -519,7 +519,7 @@ fn test_sha1_with_md5sum_should_fail() { scene .ccmd("md5sum") .arg("--check") - .arg(at.subdir.join("f.sha1")) + .arg("f.sha1") .fails() .stderr_contains("f.sha1: no properly formatted checksum lines found") .stderr_does_not_contain("WARNING: 1 line is improperly formatted"); @@ -540,7 +540,7 @@ fn test_check_one_two_space_star() { scene .ccmd("md5sum") .arg("--check") - .arg(at.subdir.join("in.md5")) + .arg("in.md5") .succeeds() .stdout_is("empty: OK\n"); @@ -550,7 +550,7 @@ fn test_check_one_two_space_star() { scene .ccmd("md5sum") .arg("--check") - .arg(at.subdir.join("in.md5")) + .arg("in.md5") .fails() .stdout_is("'*empty': FAILED open or read\n"); @@ -559,7 +559,7 @@ fn test_check_one_two_space_star() { scene .ccmd("md5sum") .arg("--check") - .arg(at.subdir.join("in.md5")) + .arg("in.md5") .succeeds() .stdout_is("'*empty': OK\n"); } @@ -584,7 +584,7 @@ fn test_check_space_star_or_not() { scene .ccmd("md5sum") .arg("--check") - .arg(at.subdir.join("in.md5")) + .arg("in.md5") .fails() .stdout_contains("c: FAILED") .stdout_does_not_contain("a: FAILED") @@ -600,7 +600,7 @@ fn test_check_space_star_or_not() { scene .ccmd("md5sum") .arg("--check") - .arg(at.subdir.join("in.md5")) + .arg("in.md5") .succeeds() .stdout_contains("a: OK") .stderr_contains("WARNING: 1 line is improperly formatted"); @@ -616,7 +616,7 @@ fn test_check_no_backslash_no_space() { scene .ccmd("md5sum") .arg("--check") - .arg(at.subdir.join("in.md5")) + .arg("in.md5") .succeeds() .stdout_is("f: OK\n"); } @@ -631,7 +631,7 @@ fn test_incomplete_format() { scene .ccmd("md5sum") .arg("--check") - .arg(at.subdir.join("in.md5")) + .arg("in.md5") .fails() .stderr_contains("no properly formatted checksum lines found"); } @@ -647,7 +647,7 @@ fn test_start_error() { .ccmd("md5sum") .arg("--check") .arg("--strict") - .arg(at.subdir.join("in.md5")) + .arg("in.md5") .fails() .stdout_is("f: OK\n") .stderr_contains("WARNING: 1 line is improperly formatted"); @@ -664,7 +664,7 @@ fn test_check_check_ignore_no_file() { .ccmd("md5sum") .arg("--check") .arg("--ignore-missing") - .arg(at.subdir.join("in.md5")) + .arg("in.md5") .fails() .stderr_contains("in.md5: no file was verified"); } @@ -683,7 +683,7 @@ fn test_check_directory_error() { scene .ccmd("md5sum") .arg("--check") - .arg(at.subdir.join("in.md5")) + .arg("in.md5") .fails() .stderr_contains(err_msg); } @@ -731,7 +731,7 @@ fn test_check_quiet() { .ccmd("md5sum") .arg("--quiet") .arg("--check") - .arg(at.subdir.join("in.md5")) + .arg("in.md5") .succeeds() .no_output(); @@ -741,7 +741,7 @@ fn test_check_quiet() { .ccmd("md5sum") .arg("--quiet") .arg("--check") - .arg(at.subdir.join("in.md5")) + .arg("in.md5") .fails() .stdout_contains("f: FAILED") .stderr_contains("WARNING: 1 computed checksum did NOT match"); @@ -749,13 +749,13 @@ fn test_check_quiet() { scene .ccmd("md5sum") .arg("--quiet") - .arg(at.subdir.join("in.md5")) + .arg("in.md5") .fails() .stderr_contains("md5sum: the --quiet option is meaningful only when verifying checksums"); scene .ccmd("md5sum") .arg("--strict") - .arg(at.subdir.join("in.md5")) + .arg("in.md5") .fails() .stderr_contains("md5sum: the --strict option is meaningful only when verifying checksums"); } @@ -770,7 +770,7 @@ fn test_star_to_start() { scene .ccmd("md5sum") .arg("--check") - .arg(at.subdir.join("in.md5")) + .arg("in.md5") .succeeds() .stdout_only("f: OK\n"); } diff --git a/tests/by-util/test_mkdir.rs b/tests/by-util/test_mkdir.rs index 5df3c778122..1753da86945 100644 --- a/tests/by-util/test_mkdir.rs +++ b/tests/by-util/test_mkdir.rs @@ -28,6 +28,10 @@ fn test_invalid_arg() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "spawns the binary directly via std::process::Command, bypassing the wasmtime runner" +)] fn test_version_no_path() { use std::process::Command; use uutests::get_tests_binary; @@ -150,6 +154,10 @@ fn test_mkdir_dup_dir_parent() { #[cfg(not(windows))] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: umask() only affects the wasmtime host process, not the guest sandbox" +)] fn test_mkdir_parent_mode() { let (at, mut ucmd) = at_and_ucmd!(); @@ -177,6 +185,10 @@ fn test_mkdir_parent_mode() { #[cfg(not(windows))] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: umask() only affects the wasmtime host process, not the guest sandbox" +)] fn test_mkdir_parent_mode_check_existing_parent() { let (at, mut ucmd) = at_and_ucmd!(); @@ -241,6 +253,7 @@ fn test_mkdir_dup_file() { #[test] #[cfg(not(windows))] +#[cfg_attr(wasi_runner, ignore = "WASI: st_mode has no real permission bits")] fn test_symbolic_mode() { let (at, mut ucmd) = at_and_ucmd!(); let test_dir = "test_dir"; @@ -252,6 +265,7 @@ fn test_symbolic_mode() { #[test] #[cfg(not(windows))] +#[cfg_attr(wasi_runner, ignore = "WASI: st_mode has no real permission bits")] fn test_symbolic_alteration() { let (at, mut ucmd) = at_and_ucmd!(); let test_dir = "test_dir"; @@ -269,6 +283,7 @@ fn test_symbolic_alteration() { #[test] #[cfg(not(windows))] +#[cfg_attr(wasi_runner, ignore = "WASI: st_mode has no real permission bits")] fn test_multi_symbolic() { let (at, mut ucmd) = at_and_ucmd!(); let test_dir = "test_dir"; @@ -426,6 +441,7 @@ fn test_mkdir_p_respects_umask_without_acl() { #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore = "WASI: st_mode has no real permission bits")] fn test_mkdir_explicit_mode_zero() { use std::os::unix::fs::PermissionsExt; let (at, mut ucmd) = at_and_ucmd!(); @@ -436,6 +452,10 @@ fn test_mkdir_explicit_mode_zero() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: umask() only affects the wasmtime host process, not the guest sandbox" +)] fn test_mkdir_explicit_mode_with_umask() { // -m must win over umask: requesting 0o777 with a restrictive umask must // still yield 0o777, since the umask is shaped to not block requested bits. @@ -824,6 +844,10 @@ fn test_mkdir_case_sensitivity() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: UNC-style path resolves differently inside the guest root than on the host" +)] fn test_mkdir_network_paths() { // Test network path formats (UNC paths on Windows) let scene = TestScenario::new(util_name!()); @@ -887,6 +911,10 @@ fn test_mkdir_environment_expansion() { /// Now it temporarily sets umask to 0 and creates with the exact mode. #[cfg(not(windows))] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: umask() only affects the wasmtime host process, not the guest sandbox" +)] fn test_mkdir_mode_ignores_umask() { // Test that -m 0700 with restrictive umask still creates 0700 { @@ -960,6 +988,10 @@ fn test_mkdir_mode_ignores_umask() { /// - Final directory uses the exact requested mode (ignoring umask) #[cfg(not(windows))] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: umask() only affects the wasmtime host process, not the guest sandbox" +)] fn test_mkdir_parent_mode_with_explicit_mode() { let (at, mut ucmd) = at_and_ucmd!(); let umask: mode_t = 0o022; @@ -1026,6 +1058,10 @@ fn test_mkdir_parent_inherits_setgid() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "spawns the binary directly via std::process::Command, bypassing the wasmtime runner" +)] fn test_mkdir_concurrent_creation() { // Test concurrent mkdir -p operations: 10 iterations, 8 threads, 40 levels nesting use std::thread; diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 727bd0cf7a9..34040d4bbd1 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -627,6 +627,7 @@ fn test_mv_symlink_into_target() { #[cfg(target_os = "linux")] #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_mv_broken_symlink_to_another_fs() { use tempfile::TempDir; @@ -750,6 +751,10 @@ fn test_mv_backup_simple_guard_allows_unrelated_source() { /// backup rename cannot destroy it. GNU allows this. #[test] #[cfg(all(unix, not(target_os = "android")))] +#[cfg_attr( + wasip2_runner, + ignore = "WASI preview2: rustix::fs::stat doesn't return stable inode identity across path lookups (wasmtime filesystem limitation)" +)] fn test_mv_backup_simple_guard_allows_symlink_source() { let (at, mut ucmd) = at_and_ucmd!(); at.write("test_mv_sym_a", "DSTDATA"); @@ -1635,6 +1640,10 @@ fn test_mv_verbose() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] // mkdir does not support -m on windows. Freebsd doesn't return a permission error either. #[cfg(feature = "mkdir")] +#[cfg_attr( + wasi_runner, + ignore = "WASI: needs investigation (chmod/interactive/device/mode gaps)" +)] fn test_mv_permission_error() { let scene = TestScenario::new("mkdir"); let folder1 = "bar"; @@ -1852,6 +1861,10 @@ fn test_mv_seen_multiple_files_to_directory() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: stat on a trailing-slash path over a regular file surfaces a raw ENOTDIR from the runtime instead of the CannotStatNotADirectory path" +)] fn test_mv_dir_into_file_where_both_are_files() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -1987,6 +2000,7 @@ mod inter_partition_copying { // Ensure that the copying code used in an inter-partition move unlinks the destination symlink. #[test] + #[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] pub(crate) fn test_mv_unlinks_dest_symlink() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -2036,6 +2050,7 @@ mod inter_partition_copying { // In an inter-partition move if unlinking the destination symlink fails, ensure // that it would output the proper error message. #[test] + #[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] pub(crate) fn test_mv_unlinks_dest_symlink_error_message() { use uutests::util::TestScenario; let scene = TestScenario::new(util_name!()); @@ -2068,6 +2083,7 @@ mod inter_partition_copying { // Test that hardlinks are preserved when moving files across partitions #[test] #[cfg(unix)] + #[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] pub(crate) fn test_mv_preserves_hardlinks_across_partitions() { use std::fs; use std::os::unix::fs::MetadataExt; @@ -2144,6 +2160,7 @@ mod inter_partition_copying { #[cfg(unix)] #[allow(clippy::too_many_lines)] #[allow(clippy::similar_names)] + #[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] pub(crate) fn test_mv_preserves_multiple_hardlink_groups_across_partitions() { use std::fs::metadata; use std::os::unix::fs::MetadataExt; @@ -2257,6 +2274,7 @@ mod inter_partition_copying { // Test the exact GNU test scenario: hardlinks within directories being moved #[test] #[cfg(unix)] + #[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] pub(crate) fn test_mv_preserves_hardlinks_in_directories_across_partitions() { use std::fs::metadata; use std::os::unix::fs::MetadataExt; @@ -2359,6 +2377,7 @@ mod inter_partition_copying { #[cfg(unix)] #[allow(clippy::too_many_lines)] #[allow(clippy::similar_names)] + #[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] pub(crate) fn test_mv_preserves_complex_hardlinks_across_nested_directories() { use std::fs::metadata; use std::os::unix::fs::MetadataExt; @@ -2505,6 +2524,7 @@ mod inter_partition_copying { #[test] #[cfg(unix)] + #[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] pub(crate) fn test_mv_dir_with_fifo_across_partitions() { use std::os::unix::fs::FileTypeExt; use tempfile::TempDir; @@ -2658,6 +2678,7 @@ fn test_special_file_different_filesystem() { /// a cross-device move fails due to permission errors when removing the target file #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_mv_cross_device_permission_denied() { use std::fs::{set_permissions, write}; use std::os::unix::fs::PermissionsExt; @@ -2706,6 +2727,7 @@ fn test_mv_cross_device_permission_denied() { /// than truncate `victim`. #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_mv_cross_device_refuses_planted_symlink_dest() { use std::os::unix::fs::symlink; use tempfile::TempDir; @@ -2863,6 +2885,7 @@ fn test_mv_error_usage_display_too_few() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_mv_verbose_directory_recursive() { use tempfile::TempDir; @@ -2918,6 +2941,7 @@ fn test_mv_verbose_directory_recursive() { #[cfg(unix)] #[test] +#[cfg_attr(wasi_runner, ignore = "WASI: st_mode has no real permission bits")] fn test_mv_prompt_unwriteable_file_when_using_tty() { let (at, mut ucmd) = at_and_ucmd!(); @@ -3011,6 +3035,7 @@ fn test_mv_xattr_enotsup_silent() { /// destination atomically, matching GNU. #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_mv_cross_device_symlink_onto_existing() { use std::fs; use std::os::unix::fs::symlink; @@ -3051,6 +3076,7 @@ fn test_mv_cross_device_symlink_onto_existing() { /// fail without destroying the directory or its contents. #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_mv_cross_device_symlink_onto_existing_dir() { use std::fs; use std::os::unix::fs::symlink; @@ -3092,6 +3118,7 @@ fn test_mv_cross_device_symlink_onto_existing_dir() { /// (not expanded into full copies of their targets) #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_mv_cross_device_symlink_preserved() { use std::fs; use std::os::unix::fs::symlink; @@ -3139,6 +3166,7 @@ fn test_mv_cross_device_symlink_preserved() { /// Test that broken/dangling symlinks are preserved during cross-device moves #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_mv_cross_device_broken_symlink_preserved() { use std::fs; use std::os::unix::fs::symlink; @@ -3184,6 +3212,7 @@ fn test_mv_cross_device_broken_symlink_preserved() { /// Test that symlinks to regular files are preserved during cross-device moves #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_mv_cross_device_file_symlink_preserved() { use std::fs; use std::os::unix::fs::symlink; @@ -3245,6 +3274,7 @@ fn find_other_group(current: u32) -> Option { /// See https://github.com/uutils/coreutils/issues/9714 #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_mv_cross_device_preserves_ownership() { use std::fs; use std::os::unix::fs::MetadataExt; @@ -3307,6 +3337,7 @@ fn test_mv_cross_device_preserves_ownership() { /// See https://github.com/uutils/coreutils/issues/9714 #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_mv_cross_device_preserves_ownership_recursive() { use std::fs; use std::os::unix::fs::MetadataExt; diff --git a/tests/by-util/test_nproc.rs b/tests/by-util/test_nproc.rs index 588e7a7150b..fb02dfeb535 100644 --- a/tests/by-util/test_nproc.rs +++ b/tests/by-util/test_nproc.rs @@ -50,7 +50,9 @@ fn test_nproc_all_omp() { assert_eq!(nproc, nproc_omp); // clamp overflow - #[cfg(target_pointer_width = "64")] + // The wasm guest is always a 32-bit target regardless of the host's + // pointer width, so it clamps to u32::MAX there instead of u64::MAX. + #[cfg(all(target_pointer_width = "64", not(wasi_runner)))] TestScenario::new(util_name!()) .ucmd() .env("OMP_NUM_THREADS", "99999999999999999999") diff --git a/tests/by-util/test_od.rs b/tests/by-util/test_od.rs index 09ffd3ebc2f..45b3cf0f1df 100644 --- a/tests/by-util/test_od.rs +++ b/tests/by-util/test_od.rs @@ -1330,6 +1330,7 @@ fn test_od_eintr_handling() { // Regression test: od should handle write errors to /dev/full without aborting. #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasip2_runner, ignore = "WASI P2: /dev/full filesystem not available")] fn test_write_error_dev_full() { use std::fs::File; diff --git a/tests/by-util/test_pathchk.rs b/tests/by-util/test_pathchk.rs index 47fb1e01d1b..85f3c3cbc14 100644 --- a/tests/by-util/test_pathchk.rs +++ b/tests/by-util/test_pathchk.rs @@ -24,6 +24,10 @@ fn test_invalid_arg() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: symlink_metadata(\"\") succeeds instead of returning ENOENT" +)] fn test_default_mode() { // accept some reasonable default new_ucmd!().args(&["dir/file"]).succeeds().no_stdout(); @@ -176,6 +180,7 @@ fn test_posix_all() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_pathchk_non_utf8_paths() { let filename = std::ffi::OsString::from_vec(vec![0xFF, 0xFE]); new_ucmd!().arg(&filename).succeeds(); diff --git a/tests/by-util/test_pr.rs b/tests/by-util/test_pr.rs index 3554ba6c2ae..3f1ace846ee 100644 --- a/tests/by-util/test_pr.rs +++ b/tests/by-util/test_pr.rs @@ -450,6 +450,10 @@ fn test_offset_too_large() { #[cfg(target_os = "linux")] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: wasmtime itself needs more memory than the guest's rlimit allows" +)] fn test_offset_large_value_does_not_abort_under_memory_limit() { use rlimit::Resource; use std::process::Stdio; @@ -517,6 +521,7 @@ fn test_with_date_format() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: locale database not visible")] fn test_with_date_format_env() { // POSIXLY_CORRECT + LC_ALL/TIME=POSIX uses "%b %e %H:%M %Y" date format let whitespace = " ".repeat(49); @@ -639,6 +644,10 @@ fn test_version() { #[cfg(unix)] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/null) not visible" +)] fn test_pr_char_device_dev_null() { new_ucmd!().arg("/dev/null").succeeds(); } @@ -1074,6 +1083,10 @@ fn test_negative_expand_tabs() { #[cfg(unix)] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/null) not visible" +)] fn test_merge_empty_input() { new_ucmd!() .args(&["-m", "/dev/null", "/dev/null"]) diff --git a/tests/by-util/test_printenv.rs b/tests/by-util/test_printenv.rs index 28ca045bd5c..48d2b825ca5 100644 --- a/tests/by-util/test_printenv.rs +++ b/tests/by-util/test_printenv.rs @@ -5,6 +5,10 @@ use uutests::new_ucmd; #[test] +#[cfg_attr( + wasi_runner, + ignore = "sets HOME to a relative path, which breaks wasmtime's own cache-dir resolution" +)] fn test_get_all() { new_ucmd!() .env("HOME", "FOO") @@ -68,6 +72,10 @@ fn test_invalid_option_exit_code() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "sets HOME to a relative path, which breaks wasmtime's own cache-dir resolution" +)] fn test_null_separator() { // printenv should use \x00 as separator if null option is provided for null_opt in ["-0", "--null"] { @@ -94,6 +102,7 @@ fn test_null_separator() { #[test] #[cfg(unix)] #[cfg(not(any(target_os = "freebsd", target_os = "android", target_os = "openbsd")))] +#[cfg_attr(wasi_runner, ignore = "WASI: env values must be valid UTF-8")] fn test_non_utf8_value() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; @@ -120,6 +129,7 @@ fn test_non_utf8_value() { #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore = "WASI: env values must be valid UTF-8")] fn test_non_utf8_env_vars() { use std::ffi::OsString; use std::os::unix::ffi::OsStringExt; diff --git a/tests/by-util/test_ptx.rs b/tests/by-util/test_ptx.rs index 777f6dc1311..10fab791622 100644 --- a/tests/by-util/test_ptx.rs +++ b/tests/by-util/test_ptx.rs @@ -234,6 +234,7 @@ fn test_format() { #[cfg(target_os = "linux")] #[test] +#[cfg_attr(wasip2_runner, ignore = "WASI P2: /dev/full filesystem not available")] fn test_failed_write_is_reported() { new_ucmd!() .arg("-G") diff --git a/tests/by-util/test_pwd.rs b/tests/by-util/test_pwd.rs index a6e1bd798bb..3882f6b528f 100644 --- a/tests/by-util/test_pwd.rs +++ b/tests/by-util/test_pwd.rs @@ -16,12 +16,20 @@ fn test_invalid_arg() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: pwd reports the guest's virtual root, not the host's absolute path" +)] fn test_default() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.succeeds().stdout_is(at.root_dir_resolved() + "\n"); } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: pwd reports the guest's virtual root, not the host's absolute path" +)] fn test_ignores_non_option_arguments() { // GNU pwd ignores non-option operands, warning on stderr but exiting 0. let (at, mut ucmd) = at_and_ucmd!(); @@ -33,6 +41,10 @@ fn test_ignores_non_option_arguments() { #[cfg(unix)] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: pwd reports the guest's virtual root, not the host's absolute path" +)] fn test_deleted_dir() { use std::process::Command; use uutests::util::TestScenario; @@ -85,24 +97,40 @@ fn symlinked_env() -> Env { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: pwd reports the guest's virtual root, not the host's absolute path" +)] fn test_symlinked_logical() { let mut env = symlinked_env(); env.ucmd.arg("-L").succeeds().stdout_is(env.symdir + "\n"); } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: pwd reports the guest's virtual root, not the host's absolute path" +)] fn test_symlinked_physical() { let mut env = symlinked_env(); env.ucmd.arg("-P").succeeds().stdout_is(env.subdir + "\n"); } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: pwd reports the guest's virtual root, not the host's absolute path" +)] fn test_symlinked_default() { let mut env = symlinked_env(); env.ucmd.succeeds().stdout_is(env.subdir + "\n"); } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: pwd reports the guest's virtual root, not the host's absolute path" +)] fn test_symlinked_default_posix() { let mut env = symlinked_env(); env.ucmd @@ -112,6 +140,10 @@ fn test_symlinked_default_posix() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: pwd reports the guest's virtual root, not the host's absolute path" +)] fn test_symlinked_default_posix_l() { let mut env = symlinked_env(); env.ucmd @@ -122,6 +154,10 @@ fn test_symlinked_default_posix_l() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: pwd reports the guest's virtual root, not the host's absolute path" +)] fn test_symlinked_default_posix_p() { let mut env = symlinked_env(); env.ucmd @@ -138,6 +174,10 @@ pub mod untrustworthy_pwd_var { use super::*; #[test] + #[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: pwd reports the guest's virtual root, not the host's absolute path" + )] fn test_nonexistent_logical() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.arg("-L") @@ -147,6 +187,10 @@ pub mod untrustworthy_pwd_var { } #[test] + #[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: pwd reports the guest's virtual root, not the host's absolute path" + )] fn test_wrong_logical() { let mut env = symlinked_env(); env.ucmd @@ -157,6 +201,10 @@ pub mod untrustworthy_pwd_var { } #[test] + #[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: pwd reports the guest's virtual root, not the host's absolute path" + )] fn test_redundant_logical() { let mut env = symlinked_env(); env.ucmd @@ -167,6 +215,10 @@ pub mod untrustworthy_pwd_var { } #[test] + #[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: pwd reports the guest's virtual root, not the host's absolute path" + )] fn test_relative_logical() { let mut env = symlinked_env(); env.ucmd diff --git a/tests/by-util/test_readlink.rs b/tests/by-util/test_readlink.rs index f27ad99e7db..81070cb24fb 100644 --- a/tests/by-util/test_readlink.rs +++ b/tests/by-util/test_readlink.rs @@ -39,6 +39,10 @@ fn test_resolve() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: canonicalized path resolves to the guest's virtual root, not the host's absolute path" +)] fn test_canonicalize() { let (at, mut ucmd) = at_and_ucmd!(); let actual = ucmd.arg("-f").arg(".").succeeds().stdout_move_str(); @@ -49,6 +53,10 @@ fn test_canonicalize() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: canonicalized path resolves to the guest's virtual root, not the host's absolute path" +)] fn test_canonicalize_existing() { let (at, mut ucmd) = at_and_ucmd!(); let actual = ucmd.arg("-e").arg(".").succeeds().stdout_move_str(); @@ -59,6 +67,10 @@ fn test_canonicalize_existing() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: canonicalized path resolves to the guest's virtual root, not the host's absolute path" +)] fn test_canonicalize_missing() { let (at, mut ucmd) = at_and_ucmd!(); let actual = ucmd.arg("-m").arg(GIBBERISH).succeeds().stdout_move_str(); @@ -70,6 +82,10 @@ fn test_canonicalize_missing() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: canonicalized path resolves to the guest's virtual root, not the host's absolute path" +)] fn test_canonicalize_symlink_before_parentdir() { // GNU readlink follows the symlink first and only then evaluates `..`. // Logical resolution would collapse `link/..` up front and return the current directory instead. @@ -84,6 +100,10 @@ fn test_canonicalize_symlink_before_parentdir() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: canonicalized path resolves to the guest's virtual root, not the host's absolute path" +)] fn test_long_redirection_to_current_dir() { let (at, mut ucmd) = at_and_ucmd!(); // Create a 256-character path to current directory @@ -430,6 +450,7 @@ fn test_delimiters() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_readlink_non_utf8_paths() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; diff --git a/tests/by-util/test_realpath.rs b/tests/by-util/test_realpath.rs index 08cf8c90c0a..1b8ba603f3a 100644 --- a/tests/by-util/test_realpath.rs +++ b/tests/by-util/test_realpath.rs @@ -15,6 +15,10 @@ use std::path::{MAIN_SEPARATOR, Path}; static GIBBERISH: &str = "supercalifragilisticexpialidocious"; #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: canonicalized path resolves to the guest's virtual root, not the host's absolute path" +)] fn test_realpath_current_directory() { let (at, mut ucmd) = at_and_ucmd!(); let expect = at.root_dir_resolved() + "\n"; @@ -22,6 +26,10 @@ fn test_realpath_current_directory() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: canonicalized path resolves to the guest's virtual root, not the host's absolute path" +)] fn test_realpath_long_redirection_to_current_dir() { let (at, mut ucmd) = at_and_ucmd!(); // Create a 256-character path to current directory @@ -161,6 +169,10 @@ fn test_realpath_logical_mode() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: canonicalized path resolves to the guest's virtual root, not the host's absolute path" +)] fn test_realpath_dangling() { let (at, mut ucmd) = at_and_ucmd!(); at.symlink_file("nonexistent-file", "link"); @@ -187,6 +199,10 @@ fn test_realpath_loop() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: canonicalized path resolves to the guest's virtual root, not the host's absolute path" +)] fn test_realpath_default_allows_final_non_existent() { let p = Path::new("").join(GIBBERISH); let (at, mut ucmd) = at_and_ucmd!(); @@ -201,6 +217,10 @@ fn test_realpath_default_forbids_non_final_non_existent() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: canonicalized path resolves to the guest's virtual root, not the host's absolute path" +)] fn test_realpath_existing() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.arg("-e") @@ -215,6 +235,10 @@ fn test_realpath_existing_error() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: canonicalized path resolves to the guest's virtual root, not the host's absolute path" +)] fn test_realpath_missing() { let p = Path::new("").join(GIBBERISH).join(GIBBERISH); let (at, mut ucmd) = at_and_ucmd!(); @@ -226,6 +250,10 @@ fn test_realpath_missing() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: canonicalized path resolves to the guest's virtual root, not the host's absolute path" +)] fn test_realpath_when_symlink_is_absolute_and_enoent() { let (at, mut ucmd) = at_and_ucmd!(); @@ -485,6 +513,7 @@ fn test_realpath_empty() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_realpath_non_utf8_paths() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; diff --git a/tests/by-util/test_rm.rs b/tests/by-util/test_rm.rs index f4a57c74be5..fad29e17a5f 100644 --- a/tests/by-util/test_rm.rs +++ b/tests/by-util/test_rm.rs @@ -338,6 +338,7 @@ fn test_verbose() { // write failure on stderr and exit 1 instead of panicking. #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasip2_runner, ignore = "WASI P2: /dev/full filesystem not available")] fn test_verbose_write_error_does_not_panic() { use std::fs::OpenOptions; @@ -362,6 +363,7 @@ fn test_verbose_write_error_does_not_panic() { // Directory variant of the #10551 regression test. #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasip2_runner, ignore = "WASI P2: /dev/full filesystem not available")] fn test_verbose_write_error_does_not_panic_dir() { use std::fs::OpenOptions; @@ -446,6 +448,7 @@ fn test_no_operand() { } #[test] +#[cfg_attr(wasi_runner, ignore)] fn test_verbose_slash() { let (at, mut ucmd) = at_and_ucmd!(); let dir = "test_rm_verbose_slash_directory"; @@ -883,6 +886,7 @@ fn test_current_or_parent_dir_rm4() { at.touch(file_1); at.touch(file_2); + #[cfg(not(wasi_runner))] let answers = [ "rm: refusing to remove '.' or '..' directory: skipping 'd/.'", "rm: refusing to remove '.' or '..' directory: skipping 'd/./'", @@ -894,6 +898,20 @@ fn test_current_or_parent_dir_rm4() { "rm: refusing to remove '.' or '..' directory: skipping '../'", "rm: refusing to remove '.' or '..' directory: skipping '..'", ]; + #[cfg(wasi_runner)] + let answers = [ + "rm: refusing to remove '.' or '..' directory: skipping 'd/.'", + "rm: refusing to remove '.' or '..' directory: skipping 'd/./'", + "rm: refusing to remove '.' or '..' directory: skipping 'd/./'", + "rm: refusing to remove '.' or '..' directory: skipping 'd/..'", + "rm: it is dangerous to operate recursively on 'd/../' (same as '/')", + "rm: use --no-preserve-root to override this failsafe", + "rm: refusing to remove '.' or '..' directory: skipping '.'", + "rm: it is dangerous to operate recursively on './' (same as '/')", + "rm: use --no-preserve-root to override this failsafe", + "rm: it is dangerous to operate recursively on '../' (same as '/')", + "rm: use --no-preserve-root to override this failsafe", + ]; let std_err_str = ts .ucmd() .arg("-rf") @@ -1125,6 +1143,7 @@ fn test_recursive_remove_unreadable_subdir() { at.set_mode("foo/bar", 0o0000); let result = ucmd.args(&["-r", "-f", "foo"]).fails(); + result.stderr_contains("Permission denied"); result.stderr_contains("foo/bar"); @@ -1181,12 +1200,13 @@ fn test_inaccessible_dir_recursive() { } #[test] -#[cfg(any(target_os = "linux", target_os = "wasi"))] +#[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore)] fn test_non_utf8_paths() { use std::ffi::OsStr; #[cfg(target_os = "linux")] use std::os::unix::ffi::OsStrExt; - #[cfg(target_os = "wasi")] + #[cfg(all(target_os = "wasi", target_env = "p1"))] use std::os::wasi::ffi::OsStrExt; let scene = TestScenario::new(util_name!()); @@ -1418,8 +1438,9 @@ fn test_symlink_to_readonly_no_prompt() { } /// Test that --preserve-root properly detects symlinks pointing to root. -#[cfg(unix)] #[test] +#[cfg(unix)] +#[cfg_attr(wasi_runner, ignore)] fn test_preserve_root_symlink_to_root() { let (at, mut ucmd) = at_and_ucmd!(); @@ -1440,8 +1461,9 @@ fn test_preserve_root_symlink_to_root() { } /// Test that --preserve-root properly detects nested symlinks pointing to root. -#[cfg(unix)] #[test] +#[cfg(unix)] +#[cfg_attr(wasi_runner, ignore)] fn test_preserve_root_nested_symlink_to_root() { let (at, mut ucmd) = at_and_ucmd!(); diff --git a/tests/by-util/test_rmdir.rs b/tests/by-util/test_rmdir.rs index 669884488a0..a382b3db05c 100644 --- a/tests/by-util/test_rmdir.rs +++ b/tests/by-util/test_rmdir.rs @@ -207,6 +207,7 @@ fn test_rmdir_ignore_nonempty_no_permissions() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI: read_link() not supported for symlinks")] fn test_rmdir_remove_symlink_file() { let (at, mut ucmd) = at_and_ucmd!(); @@ -219,8 +220,9 @@ fn test_rmdir_remove_symlink_file() { } // This behavior is known to happen on Linux but not all Unixes -#[cfg(any(target_os = "linux", target_os = "android"))] #[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr(wasi_runner, ignore = "WASI: read_link() not supported for symlinks")] fn test_rmdir_remove_symlink_dir() { let (at, mut ucmd) = at_and_ucmd!(); @@ -232,8 +234,9 @@ fn test_rmdir_remove_symlink_dir() { .stderr_is("rmdir: failed to remove 'dl/': Symbolic link not followed\n"); } -#[cfg(any(target_os = "linux", target_os = "android"))] #[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr(wasi_runner, ignore = "WASI: read_link() not supported for symlinks")] fn test_rmdir_remove_symlink_dangling() { let (at, mut ucmd) = at_and_ucmd!(); @@ -244,8 +247,9 @@ fn test_rmdir_remove_symlink_dangling() { .stderr_is("rmdir: failed to remove 'dl/': Symbolic link not followed\n"); } -#[cfg(any(target_os = "linux", target_os = "android"))] #[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr(wasi_runner, ignore = "WASI: read_link() not supported for symlinks")] fn test_rmdir_remove_symlink_dir_with_trailing_slashes() { // a symlink with trailing slashes should still be printing the 'Symbolic link not followed' // message diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index ec3c266bc4d..6b672d57c37 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -232,6 +232,10 @@ fn test_width_invalid_float() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "invokes the binary directly from a shell script, bypassing the wasmtime runner" +)] fn test_sigpipe_ignored_reports_write_error() { let scene = TestScenario::new(util_name!()); let seq_bin = scene.bin_path.clone().into_os_string(); @@ -296,6 +300,7 @@ fn test_separator_and_terminator() { #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_separator_non_utf8() { use std::{ffi::OsString, os::unix::ffi::OsStringExt}; @@ -319,6 +324,7 @@ fn test_separator_non_utf8() { #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_terminator_non_utf8() { use std::{ffi::OsString, os::unix::ffi::OsStringExt}; @@ -732,9 +738,11 @@ fn test_infinite_sequence(#[case] args: &[&str], #[case] expected_start: &[u8]) let result = new_ucmd!() .args(args) .run_stdout_starts_with(expected_start); - #[cfg(unix)] + // On Unix systems (not WASI), seq should be terminated by SIGPIPE when the pipe closes. + // On WASI and Windows, there are no signals, so just check the process succeeded. + #[cfg(all(unix, not(wasi_runner)))] result.signal_name_is("PIPE"); - #[cfg(not(unix))] + #[cfg(any(not(unix), wasi_runner))] result.success(); } diff --git a/tests/by-util/test_sha1sum.rs b/tests/by-util/test_sha1sum.rs index 8377bbe9e6a..3a823cf5035 100644 --- a/tests/by-util/test_sha1sum.rs +++ b/tests/by-util/test_sha1sum.rs @@ -132,7 +132,7 @@ fn test_check_sha1() { scene .ccmd("sha1sum") .arg("-c") - .arg(at.subdir.join("testf.sha1")) + .arg("testf.sha1") .succeeds() .stdout_is("testf: OK\n") .stderr_is(""); @@ -152,7 +152,7 @@ fn test_check_file_not_found_warning() { scene .ccmd("sha1sum") .arg("-c") - .arg(at.subdir.join("testf.sha1")) + .arg("testf.sha1") .fails() .stdout_is("testf: FAILED open or read\n") .stderr_is("sha1sum: testf: No such file or directory\nsha1sum: WARNING: 1 listed file could not be read\n"); diff --git a/tests/by-util/test_shred.rs b/tests/by-util/test_shred.rs index 2938c324707..29f31e324a0 100644 --- a/tests/by-util/test_shred.rs +++ b/tests/by-util/test_shred.rs @@ -134,6 +134,10 @@ fn test_shred_u() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: chmod has no ENOSYS-free syscall; -f can't restore write permission" +)] fn test_shred_force() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -265,6 +269,10 @@ fn test_all_patterns_present() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: File::try_clone() (used for --random-source) is unsupported" +)] fn test_random_source_regular_file() { let (at, mut ucmd) = at_and_ucmd!(); @@ -349,6 +357,7 @@ fn test_shred_rename_exhaustion() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_shred_non_utf8_paths() { use std::os::unix::ffi::OsStrExt; let ts = TestScenario::new(util_name!()); @@ -362,6 +371,10 @@ fn test_shred_non_utf8_paths() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: File::try_clone() (used for --random-source) is unsupported" +)] fn test_gnu_shred_passes_20() { let (at, mut ucmd) = at_and_ucmd!(); @@ -420,6 +433,10 @@ fn test_gnu_shred_passes_20() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: File::try_clone() (used for --random-source) is unsupported" +)] fn test_gnu_shred_passes_different_counts() { let (at, mut ucmd) = at_and_ucmd!(); diff --git a/tests/by-util/test_sleep.rs b/tests/by-util/test_sleep.rs index 5fd1d65789f..90cadb7dc71 100644 --- a/tests/by-util/test_sleep.rs +++ b/tests/by-util/test_sleep.rs @@ -141,6 +141,7 @@ fn test_sleep_wrong_time() { #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore = "WASI: no signal support")] fn test_sleep_stops_after_sigsegv() { let mut child = new_ucmd!() .arg("100") @@ -158,6 +159,7 @@ fn test_sleep_stops_after_sigsegv() { #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore = "WASI: no signal support")] fn test_sleep_stops_after_sigbus() { let mut child = new_ucmd!() .arg("100") diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 9dd0233b23c..e8377b43ada 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -36,6 +36,7 @@ fn test_helper(file_name: &str, possible_args: &[&str]) { } #[test] +#[cfg_attr(wasi_runner, ignore)] fn test_buffer_sizes() { #[cfg(target_os = "linux")] let buffer_sizes = ["0", "50K", "50k", "1M", "100M", "0%", "10%"]; @@ -52,6 +53,8 @@ fn test_buffer_sizes() { .stdout_is_fixture("ext_sort.expected"); } + // The wasm guest is always a 32-bit target regardless of the host's + // pointer width, so these overflow there even when the host is 64-bit. #[cfg(not(target_pointer_width = "32"))] { let buffer_sizes = ["1000G", "10T"]; @@ -666,6 +669,7 @@ fn month_sort_input_expected(months: &[String]) -> (String, String) { #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: locale database not visible")] fn test_month_sort_french_locale() { let locale = "fr_FR.UTF-8"; if !is_locale_available(locale) { @@ -697,6 +701,7 @@ fn test_month_sort_french_locale() { #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: locale database not visible")] fn test_month_sort_hungarian_locale() { let locale = "hu_HU.UTF-8"; if !is_locale_available(locale) { @@ -728,6 +733,7 @@ fn test_month_sort_hungarian_locale() { /// E.g. "av ril" should NOT match "avril" — GNU treats it as unknown. #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: locale database not visible")] fn test_month_sort_french_embedded_blanks() { let locale = "fr_FR.UTF-8"; if !is_locale_available(locale) { @@ -780,6 +786,7 @@ fn test_month_sort_french_embedded_blanks() { #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: locale database not visible")] fn test_month_sort_japanese_locale() { let locale = "ja_JP.UTF-8"; if !is_locale_available(locale) { @@ -1099,6 +1106,10 @@ fn test_multiple_files() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sort -m spawns real OS threads for multi-file merge, unsupported under wasmtime's default config" +)] fn test_merge_interleaved() { new_ucmd!() .arg("-m") @@ -1110,6 +1121,10 @@ fn test_merge_interleaved() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sort -m spawns real OS threads for multi-file merge, unsupported under wasmtime's default config" +)] fn test_merge_preserves_long_lines() { use std::fmt::Write; @@ -1142,6 +1157,10 @@ fn test_merge_preserves_long_lines() { // receivers while the reader was still sending, and `chunks::read` unwraps that send. #[test] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sort -m spawns real OS threads for multi-file merge, unsupported under wasmtime's default config" +)] fn test_merge_write_error_does_not_panic() { use std::fs::File; @@ -1173,6 +1192,10 @@ fn test_merge_write_error_does_not_panic() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sort -m spawns real OS threads for multi-file merge, unsupported under wasmtime's default config" +)] fn test_merge_unique() { new_ucmd!() .arg("-m") @@ -1188,6 +1211,10 @@ fn test_merge_unique() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sort -m spawns real OS threads for multi-file merge, unsupported under wasmtime's default config" +)] fn test_merge_stable() { new_ucmd!() .arg("-m") @@ -1200,6 +1227,10 @@ fn test_merge_stable() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sort -m spawns real OS threads for multi-file merge, unsupported under wasmtime's default config" +)] fn test_merge_reversed() { new_ucmd!() .arg("-m") @@ -1405,6 +1436,10 @@ fn test_compress() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sort -m spawns real OS threads for multi-file merge, unsupported under wasmtime's default config" +)] fn test_compress_merge() { new_ucmd!() .args(&[ @@ -1428,6 +1463,10 @@ fn test_compress_merge() { #[test] #[cfg(not(target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: ext_sort bypasses --compress-program entirely (single-threaded in-memory path)" +)] fn test_compress_fail() { let result = new_ucmd!() .args(&[ @@ -1490,7 +1529,7 @@ fn test_batch_size_too_large() { "--batch-size argument '{large_batch_size}' too large" )); - #[cfg(target_os = "linux")] + #[cfg(all(target_os = "linux", not(wasi_runner)))] new_ucmd!() .arg(format!("--batch-size={large_batch_size}")) .fails_with_code(2) @@ -1498,6 +1537,10 @@ fn test_batch_size_too_large() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sort -m spawns real OS threads for multi-file merge, unsupported under wasmtime's default config" +)] fn test_merge_batch_size() { new_ucmd!() .arg("--batch-size=2") @@ -1517,6 +1560,10 @@ fn test_merge_batch_size() { // TODO(#7542): Re-enable on Android once we figure out why setting limit is broken. // #[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sort -m spawns real OS threads for multi-file merge, unsupported under wasmtime's default config" +)] fn test_merge_batch_size_with_limit() { use rlimit::Resource; // Currently need... @@ -1612,6 +1659,10 @@ fn test_verifies_files_after_keys() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/random) not visible" +)] fn test_verifies_input_files() { new_ucmd!() .args(&["/dev/random", "nonexistent_file"]) @@ -1629,6 +1680,10 @@ fn test_separator_null() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sort -m spawns real OS threads, unsupported under wasmtime's default config" +)] fn test_output_is_input() { let input = "a\nb\nc\n"; let (at, mut ucmd) = at_and_ucmd!(); @@ -1643,6 +1698,10 @@ fn test_output_is_input() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/null) not visible" +)] fn test_output_device() { new_ucmd!() .args(&["-o", "/dev/null"]) @@ -1651,6 +1710,10 @@ fn test_output_device() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: sort -m spawns real OS threads for multi-file merge, unsupported under wasmtime's default config" +)] fn test_merge_empty_input() { new_ucmd!() .args(&["-m", "empty.txt"]) @@ -1676,6 +1739,7 @@ fn test_wrong_args_exit_code() { #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore = "WASI: no signal support (SIGINT)")] fn test_tmp_files_deleted_on_sigint() { use rand::{RngExt as _, SeedableRng, rngs::SmallRng}; use rustix::process::{Pid, Signal, kill_process}; @@ -1761,6 +1825,7 @@ fn test_args_check_conflict() { #[cfg(target_os = "linux")] #[test] +#[cfg_attr(wasip2_runner, ignore = "WASI P2: /dev/full filesystem not available")] fn test_failed_write_is_reported() { new_ucmd!() .pipe_in("hello") @@ -1863,6 +1928,10 @@ fn test_files0_from_empty() { #[test] #[cfg(unix)] +#[cfg_attr( + wasip2_runner, + ignore = "WASI preview2: error message text for this OS error differs from native Unix" +)] fn test_files0_from_non_utf8_name() { new_ucmd!() .args(&["--files0-from", "-"]) @@ -1873,6 +1942,10 @@ fn test_files0_from_non_utf8_name() { #[test] #[cfg(unix)] +#[cfg_attr( + wasip2_runner, + ignore = "WASI preview2: error message text for this OS error differs from native Unix" +)] fn test_files0_read_error() { new_ucmd!() .args(&["--files0-from", "."]) @@ -1883,6 +1956,7 @@ fn test_files0_read_error() { #[cfg(unix)] #[test] // Test for GNU tests/sort/sort-files0-from.pl "empty-non-regular" +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_files0_from_empty_non_regular() { new_ucmd!() .args(&["--files0-from", "/dev/null"]) @@ -1969,6 +2043,10 @@ fn test_files0_from_2a() { #[test] // Test for GNU tests/sort/sort-files0-from.pl "non-utf8" #[cfg(all(unix, not(target_os = "macos")))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: preopened directories reject non-UTF-8 filenames" +)] fn test_files0_from_non_utf8() { use std::os::unix::ffi::OsStringExt; let (at, mut ucmd) = at_and_ucmd!(); @@ -2957,6 +3035,7 @@ fn test_locale_collation_utf8() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: locale database not visible")] fn test_locale_interleaved_en_us_utf8() { // Test case for issue: locale-based collation support // In en_US.UTF-8, lowercase and uppercase letters should interleave @@ -3029,6 +3108,7 @@ fn test_locale_with_ignore_case_flag() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: locale database not visible")] fn test_locale_complex_utf8_sorting() { // More complex test with mixed case and special characters // In en_US.UTF-8, should respect locale collation rules @@ -3053,6 +3133,7 @@ fn test_locale_posix_sort_debug_message() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: locale database not visible")] fn test_locale_utf8_sort_debug_message() { new_ucmd!() .env("LC_ALL", "en_US.UTF-8") @@ -3073,7 +3154,7 @@ fn test_failed_to_set_locale_debug_message() { result.stderr_contains("text ordering performed using simple byte comparison"); - #[cfg(all(target_os = "linux", target_env = "gnu"))] + #[cfg(all(target_os = "linux", target_env = "gnu", not(wasi_runner)))] result.stderr_contains("failed to set locale"); } @@ -3095,6 +3176,7 @@ e f 5436 down data path1 path2 path3 path4 path5\n"; } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: locale database not visible")] fn test_consistent_sorting_with_i18n_collate() { // Regression test for issue #11980 // Lexicographic fallback sorting for equal sorting keys for 01 and 0_1 diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index bb76dceb31f..b333a578dff 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -134,6 +134,7 @@ fn test_invalid_arg() { #[test] #[cfg(all(unix, not(target_os = "android")))] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_split_to_non_seekable() { let (at, mut ucmd) = at_and_ucmd!(); at.symlink_file("/dev/stdout", "xaa"); @@ -311,6 +312,7 @@ fn test_split_additional_suffix_hyphen_value() { #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore = "WASI: --filter has no process-spawning support")] fn test_filter() { // like `test_split_default()` but run a command before writing let (at, mut ucmd) = at_and_ucmd!(); @@ -361,6 +363,7 @@ fn test_filter_with_env_var_set() { #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore = "WASI: --filter has no process-spawning support")] fn test_filter_command_fails() { let (at, mut ucmd) = at_and_ucmd!(); let name = "filter-will-fail"; @@ -967,6 +970,10 @@ creating file 'xaf' } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/null) not visible" +)] fn test_number_n() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["-n", "5", "asciilowercase.txt"]).succeeds(); @@ -983,6 +990,10 @@ fn test_number_n() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/null) not visible" +)] fn test_number_kth_of_n() { new_ucmd!() .args(&["--number=3/5", "asciilowercase.txt"]) @@ -1028,6 +1039,10 @@ fn test_number_kth_of_n() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "wasm guest is always 32-bit; u64::MAX-sized args overflow differently there" +)] fn test_number_kth_of_n_round_robin() { new_ucmd!() .args(&["--number", "r/2/3", "fivelines.txt"]) @@ -1196,6 +1211,10 @@ fn test_elide_empty_files_n_chunks() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/null) not visible" +)] fn test_elide_dev_null_n_chunks() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["-e", "-n", "3", "/dev/null"]) @@ -1208,6 +1227,10 @@ fn test_elide_dev_null_n_chunks() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/zero) not visible" +)] fn test_dev_zero() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["-n", "3", "/dev/zero"]) @@ -1235,6 +1258,10 @@ fn test_elide_empty_files_l_chunks() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/null) not visible" +)] fn test_elide_dev_null_l_chunks() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["-e", "-n", "l/3", "/dev/null"]) @@ -1247,6 +1274,10 @@ fn test_elide_dev_null_l_chunks() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/zero) not visible" +)] fn test_number_by_bytes_dev_zero() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["-n", "3", "/dev/zero"]) @@ -1277,6 +1308,10 @@ fn test_number_by_lines_kth() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/null) not visible" +)] fn test_number_by_lines_kth_dev_null() { new_ucmd!() .args(&["-n", "l/3/10", "/dev/null"]) @@ -1668,6 +1703,10 @@ fn test_round_robin() { // TODO(#7542): Re-enable on Android once we figure out why rlimit is broken. // #[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI: wasmtime itself needs more file descriptors than the guest's rlimit allows" +)] fn test_round_robin_limited_file_descriptors() { new_ucmd!() .args(&["-n", "r/40", "onehundredlines.txt"]) @@ -1712,6 +1751,7 @@ fn test_split_invalid_input() { /// clap is expected to fail/panic #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_split_non_utf8_argument_unix() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; @@ -2007,6 +2047,7 @@ fn test_long_lines() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_split_non_utf8_paths() { let (at, mut ucmd) = at_and_ucmd!(); @@ -2021,6 +2062,7 @@ fn test_split_non_utf8_paths() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_split_non_utf8_prefix_is_byte_preserving() { use std::ffi::OsStr; use std::os::unix::ffi::{OsStrExt, OsStringExt}; @@ -2060,6 +2102,7 @@ fn test_split_non_utf8_prefix_is_byte_preserving() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_split_non_utf8_additional_suffix_is_byte_preserving() { use std::ffi::OsStr; use std::os::unix::ffi::{OsStrExt, OsStringExt}; @@ -2107,6 +2150,7 @@ fn test_split_directory_already_exists() { #[test] #[cfg(all(target_os = "linux", target_env = "gnu"))] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_io_error() { // /proc/self/mem causes EIO new_ucmd!() @@ -2119,6 +2163,10 @@ fn test_io_error() { /// Writing a chunk to a full device must be reported and must stop the split. #[test] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/full) not visible" +)] fn test_write_error_on_full_device() { if !Path::new("/dev/full").exists() { return; diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 11480021a06..a6434699f52 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -227,6 +227,10 @@ fn test_nc_0_wo_follow2() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_n0_with_follow() { let (at, mut ucmd) = at_and_ucmd!(); let test_file = "test.txt"; @@ -318,6 +322,10 @@ fn test_follow_redirect_stdin_name_retry() { not(target_os = "freebsd"), not(target_os = "openbsd") ))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: no /dev/fd/0, so redirected-directory stdin hits the generic pipe path (like macOS) instead of the regular-file path with the GNU-matching error message" +)] fn test_stdin_redirect_dir() { // $ mkdir dir // $ tail < dir, $ tail - < dir @@ -349,6 +357,10 @@ fn test_stdin_redirect_dir() { // `test_stdin_redirect_dir` #[test] #[cfg(target_vendor = "apple")] +#[cfg_attr( + wasi_runner, + ignore = "test binary runs on macOS but the wasm guest under test does not, so the expected macOS-specific error text never appears" +)] fn test_stdin_redirect_dir_when_target_os_is_macos() { // $ mkdir dir // $ tail < dir, $ tail - < dir @@ -500,6 +512,10 @@ fn test_null_default() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_single() { let (at, mut ucmd) = at_and_ucmd!(); @@ -530,6 +546,10 @@ fn test_follow_single() { /// Test for following when bytes are written that are not valid UTF-8. #[test] #[cfg(not(target_os = "windows"))] // FIXME: test times out +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_non_utf8_bytes() { // Tail the test file and start following it. let (at, mut ucmd) = at_and_ucmd!(); @@ -587,6 +607,10 @@ fn test_permission_denied_is_not_reported_as_not_found() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_multiple() { let (at, mut ucmd) = at_and_ucmd!(); let mut child = ucmd @@ -622,6 +646,10 @@ fn test_follow_multiple() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_name_multiple() { // spell-checker:disable-next-line for argument in ["--follow=name", "--follo=nam", "--f=n"] { @@ -667,6 +695,10 @@ fn test_follow_name_multiple() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_multiple_untailable() { // $ tail -f DIR1 DIR2 // ==> DIR1 <== @@ -708,6 +740,10 @@ fn test_follow_stdin_pipe() { #[test] #[cfg(not(target_os = "windows"))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_invalid_pid() { new_ucmd!() .args(&["-f", "--pid=-1234"]) @@ -739,6 +775,10 @@ fn test_follow_invalid_pid() { not(target_os = "android"), not(target_os = "freebsd") ))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_with_pid() { use std::process::Command; @@ -936,6 +976,10 @@ fn test_multiple_input_files_missing() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_missing() { // Ensure that --follow=name does not imply --retry. // Ensure that --follow={descriptor,name} (without --retry) does *not wait* for the @@ -1004,6 +1048,10 @@ fn test_dir() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_dir_follow() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -1023,6 +1071,10 @@ fn test_dir_follow() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_dir_follow_retry() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -1285,6 +1337,10 @@ fn test_num_with_undocumented_sign_bytes() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/proc, /sys) not visible" +)] fn test_bytes_for_funny_unix_files() { // inspired by: gnu/tests/tail-2/tail-c.sh let ts = TestScenario::new(util_name!()); @@ -1346,6 +1402,10 @@ fn test_retry2() { not(target_os = "freebsd"), not(target_os = "openbsd") ))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_retry3() { // inspired by: gnu/tests/tail-2/retry.sh // Ensure that `tail --retry --follow=name` waits for the file to appear. @@ -1391,6 +1451,10 @@ fn test_retry3() { not(target_os = "freebsd"), not(target_os = "openbsd") ))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_retry4() { // inspired by: gnu/tests/tail-2/retry.sh // Ensure that `tail --retry --follow=descriptor` waits for the file to appear. @@ -1449,6 +1513,10 @@ fn test_retry4() { not(target_os = "freebsd"), not(target_os = "openbsd") ))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_retry5() { // inspired by: gnu/tests/tail-2/retry.sh // Ensure that `tail --follow=descriptor --retry` exits when the file appears untailable. @@ -1490,6 +1558,10 @@ fn test_retry5() { // >X #[test] #[cfg(all(not(target_os = "windows"), not(target_os = "android")))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_retry6() { // inspired by: gnu/tests/tail-2/retry.sh // Ensure that --follow=descriptor (without --retry) does *not* try @@ -1539,6 +1611,10 @@ fn test_retry6() { not(target_os = "freebsd"), not(target_os = "openbsd") ))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_retry7() { // inspired by: gnu/tests/tail-2/retry.sh // Ensure that `tail -F` retries when the file is initially untailable. @@ -1612,6 +1688,10 @@ fn test_retry7() { #[test] #[cfg(unix)] #[cfg(not(target_os = "android"))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_name_replaced_by_symlink_is_untailable() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -1651,6 +1731,10 @@ fn test_follow_name_replaced_by_symlink_is_untailable() { not(target_os = "freebsd"), not(target_os = "openbsd") ))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_retry8() { // Ensure that inotify will switch to polling mode if directory // of the watched file was initially missing and later created. @@ -1720,6 +1804,10 @@ fn test_retry8() { not(target_os = "freebsd"), not(target_os = "openbsd") ))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_retry9() { // inspired by: gnu/tests/tail-2/inotify-dir-recreate.sh // Ensure that inotify will switch to polling mode if directory @@ -1802,6 +1890,10 @@ fn test_retry9() { not(target_os = "freebsd"), not(target_os = "openbsd") ))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_descriptor_vs_rename1() { // inspired by: gnu/tests/tail-2/descriptor-vs-rename.sh // $ ((rm -f A && touch A && sleep 1 && echo -n "A\n" >> A && sleep 1 && \ @@ -1866,6 +1958,10 @@ fn test_follow_descriptor_vs_rename1() { not(target_os = "freebsd"), not(target_os = "openbsd") ))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_descriptor_vs_rename2() { // Ensure the headers are correct for --verbose. // NOTE: GNU's tail does not update the header from FILE_A to FILE_C after `mv FILE_A FILE_C` @@ -1919,6 +2015,10 @@ fn test_follow_descriptor_vs_rename2() { not(target_os = "freebsd"), not(target_os = "openbsd") ))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_name_retry_headers() { // inspired by: "gnu/tests/tail-2/F-headers.sh" // Ensure tail -F distinguishes output with the @@ -1985,6 +2085,10 @@ fn test_follow_name_retry_headers() { #[test] #[cfg(all(not(target_os = "windows"), not(target_os = "android")))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_name_remove() { // This test triggers a remove event while `tail --follow=name file` is running. // ((sleep 2 && rm file &)>/dev/null 2>&1 &) ; tail --follow=name file @@ -2045,6 +2149,10 @@ fn test_follow_name_remove() { #[test] #[cfg(all(not(target_os = "android"), not(target_os = "freebsd")))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_name_truncate1() { // This test triggers a truncate event while `tail --follow=name file` is running. // $ cp file backup && head file > file && sleep 1 && cp backup file @@ -2082,6 +2190,10 @@ fn test_follow_name_truncate1() { #[test] #[cfg(all(not(target_os = "android"), not(target_os = "freebsd")))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_name_truncate2() { // This test triggers a truncate event while `tail --follow=name file` is running. // $ ((sleep 1 && echo -n "x\nx\nx\n" >> file && sleep 1 && \ @@ -2124,6 +2236,10 @@ fn test_follow_name_truncate2() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_name_truncate3() { // Opening an empty file in truncate mode should not trigger a truncate event while // `tail --follow=name file` is running. @@ -2160,6 +2276,10 @@ fn test_follow_name_truncate3() { not(target_os = "windows"), not(feature = "feat_selinux") // flaky ))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_name_truncate4() { // Truncating a file with the same content it already has should not trigger a truncate event @@ -2195,6 +2315,10 @@ fn test_follow_name_truncate4() { #[test] #[cfg(not(target_os = "windows"))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_truncate_fast() { // inspired by: "gnu/tests/tail-2/truncate.sh" // Ensure all logs are output upon file truncation @@ -2249,6 +2373,10 @@ fn test_follow_truncate_fast() { not(target_os = "freebsd"), not(target_os = "openbsd") ))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_name_move_create1() { // This test triggers a move/create event while `tail --follow=name file` is running. // ((sleep 2 && mv file backup && sleep 2 && cp backup file &)>/dev/null 2>&1 &) ; tail --follow=name file @@ -2305,6 +2433,10 @@ fn test_follow_name_move_create1() { not(target_os = "freebsd"), not(target_os = "openbsd") ))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_name_move_create2() { // inspired by: "gnu/tests/tail-2/inotify-hash-abuse.sh" // Exercise an abort-inducing flaw in inotify-enabled tail -F @@ -2385,6 +2517,10 @@ fn test_follow_name_move_create2() { not(target_os = "freebsd"), not(target_os = "openbsd") ))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_name_move1() { // This test triggers a move event while `tail --follow=name file` is running. // ((sleep 2 && mv file backup &)>/dev/null 2>&1 &) ; tail --follow=name file @@ -2447,6 +2583,10 @@ fn test_follow_name_move1() { not(target_os = "freebsd"), not(target_os = "openbsd") ))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_name_move2() { // Like test_follow_name_move1, but move to a name that's already monitored. @@ -2535,6 +2675,10 @@ fn test_follow_name_move2() { not(target_os = "freebsd"), not(target_os = "openbsd") ))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_name_move_retry1() { // Similar to test_follow_name_move1 but with `--retry` (`-F`) // This test triggers two move/rename events while `tail --follow=name --retry file` is running. @@ -2595,6 +2739,10 @@ fn test_follow_name_move_retry1() { not(target_os = "freebsd"), not(target_os = "openbsd") ))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_name_move_retry2() { // inspired by: "gnu/tests/tail-2/F-vs-rename.sh" // Similar to test_follow_name_move2 (move to a name that's already monitored) @@ -2695,6 +2843,10 @@ fn test_follow_name_move_retry2() { #[test] #[cfg(not(target_os = "windows"))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_inotify_only_regular() { // The GNU test inotify-only-regular.sh uses strace to ensure that `tail -f` // doesn't make inotify syscalls and only uses inotify for regular files or fifos. @@ -2774,6 +2926,10 @@ fn test_fifo() { not(target_os = "freebsd"), not(target_os = "openbsd") ))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_fifo_with_pid() { use std::process::{Command, Stdio}; @@ -4016,6 +4172,10 @@ fn test_when_follow_retry_then_initial_print_of_file_is_written_to_stdout() { // TODO: Add test for the warning `--pid=PID is not supported on this system` #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_args_when_settings_check_warnings_then_shows_warnings() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -4084,6 +4244,10 @@ fn test_args_when_settings_check_warnings_then_shows_warnings() { /// TODO: Write similar tests for windows #[test] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_args_when_settings_check_warnings_follow_indefinitely_then_warning() { let scene = TestScenario::new(util_name!()); @@ -4208,6 +4372,10 @@ fn test_args_when_settings_check_warnings_follow_indefinitely_then_warning() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_args_when_settings_check_warnings_follow_indefinitely_then_no_warning() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -4522,6 +4690,10 @@ fn test_follow_when_file_and_symlink_are_pointing_to_same_file_and_append_data() } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_args_when_directory_given_shorthand_big_f_together_with_retry() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -4582,6 +4754,10 @@ fn test_args_when_directory_given_shorthand_big_f_together_with_retry() { not(target_os = "openbsd"), not(feature = "feat_selinux") // flaky ))] +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_when_files_are_pointing_to_same_relative_file_and_file_stays_same_size() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -4888,6 +5064,10 @@ fn test_gnu_args_err() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_gnu_args_f() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -4909,6 +5089,7 @@ fn test_gnu_args_f() { #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore = "WASI: argv must be valid UTF-8")] fn test_obsolete_encoding_unix() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; @@ -4943,6 +5124,10 @@ fn test_obsolete_encoding_windows() { #[test] #[cfg(not(target_vendor = "apple"))] // FIXME: for currently not working platforms +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_following_with_pid() { use std::process::Command; @@ -5019,6 +5204,10 @@ fn test_when_piped_input_then_no_broken_pipe() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/zero) not visible" +)] fn test_when_output_closed_then_no_broken_pie() { let mut cmd = new_ucmd!(); let mut child = cmd @@ -5050,6 +5239,7 @@ fn test_child_when_run_with_stderr_to_stdout() { #[cfg(target_os = "linux")] #[test] +#[cfg_attr(wasip2_runner, ignore = "WASI P2: /dev/full filesystem not available")] fn test_failed_write_is_reported() { new_ucmd!() .pipe_in("hello") @@ -5060,6 +5250,7 @@ fn test_failed_write_is_reported() { #[cfg(target_os = "linux")] #[test] +#[cfg_attr(wasip2_runner, ignore = "WASI P2: /dev/full filesystem not available")] fn test_failed_write_is_reported_on_seekable_input() { let ts = TestScenario::new("tail"); let at = &ts.fixtures; @@ -5075,6 +5266,10 @@ fn test_failed_write_is_reported_on_seekable_input() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: host paths (/dev/zero) not visible" +)] fn test_dev_zero() { new_ucmd!() .args(&["-c", "1", "/dev/zero"]) @@ -5134,6 +5329,10 @@ fn test_follow_stdout_pipe_close() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_debug_flag_with_polling() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -5154,6 +5353,10 @@ fn test_debug_flag_with_polling() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_debug_flag_with_inotify() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -5171,6 +5374,10 @@ fn test_debug_flag_with_inotify() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_dangling_symlink() { let (at, mut ucmd) = at_and_ucmd!(); at.symlink_file("target", "link"); @@ -5185,6 +5392,10 @@ fn test_follow_dangling_symlink() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr( + wasi_runner, + ignore = "WASI: follow mode (-f) is not supported on this platform" +)] fn test_follow_symlink_target_change() { let (at, mut ucmd) = at_and_ucmd!(); at.write("t1", "A\n"); diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index 2b8c0efc3d5..0d942a54982 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -3,6 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // spell-checker:ignore (formats) cymdhm cymdhms datetime mdhm mdhms mktime strtime ymdhm ymdhms +//spell-checker: ignore (wasi) utimensat use filetime::FileTime; #[cfg(not(target_os = "freebsd"))] @@ -166,6 +167,10 @@ fn test_touch_2_digit_years_2038() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: utimensat rejects negative (pre-1970) timestamps with EINVAL" +)] fn test_touch_2_digit_years_69() { // 69 and after are 19xx let (at, mut ucmd) = at_and_ucmd!(); @@ -623,6 +628,7 @@ fn test_touch_set_date7() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: timezone database not visible")] fn test_touch_set_date_without_leading_zeroes() { let (at, mut ucmd) = at_and_ucmd!(); let file = "test_touch_set_date_without_leading_zeroes"; @@ -646,6 +652,10 @@ fn test_touch_set_date_without_leading_zeroes() { /// (which uses i64 `tv_sec` natively), this should succeed on all targets. #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: utimensat rejects negative (pre-1970) timestamps with EINVAL" +)] fn test_touch_set_date_year_zero() { let (at, mut ucmd) = at_and_ucmd!(); let file = "test_touch_year_zero"; @@ -780,6 +790,7 @@ fn test_touch_mtime_dst_succeeds() { #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: timezone database not visible")] fn test_touch_mtime_dst_fails() { let file = "test_touch_set_mtime_dst_fails"; @@ -797,6 +808,10 @@ fn test_touch_mtime_dst_fails() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI sandbox: '/' is the guest's own writable root, not the real filesystem root" +)] fn test_touch_system_fails() { let file = "/"; new_ucmd!() @@ -874,6 +889,7 @@ fn test_touch_no_such_file_error_msg() { #[test] #[cfg(not(any(target_os = "freebsd", target_os = "openbsd")))] +#[cfg_attr(wasi_runner, ignore = "WASI: no stdout-to-file redirection")] fn test_touch_changes_time_of_file_in_stdout() { // command like: `touch - 1< ./c` // should change the timestamp of c @@ -896,6 +912,10 @@ fn test_touch_changes_time_of_file_in_stdout() { #[test] #[cfg(unix)] +#[cfg_attr( + wasi_runner, + ignore = "WASI: chmod has no ENOSYS-free syscall; directories can't be made read-only" +)] fn test_touch_permission_denied_error_msg() { let (at, mut ucmd) = at_and_ucmd!(); @@ -954,6 +974,10 @@ fn test_touch_leap_second() { #[test] #[cfg(not(windows))] // File::create doesn't support trailing separator in Windows +#[cfg_attr( + wasip2_runner, + ignore = "WASI preview2: stat on a dangling-symlink-with-trailing-slash returns ENOTDIR instead of ENOENT" +)] fn test_touch_trailing_slash_no_create() { let (at, mut ucmd) = at_and_ucmd!(); at.touch("file"); @@ -1016,6 +1040,7 @@ fn test_touch_no_dereference_dangling() { #[test] #[cfg(not(target_os = "openbsd"))] +#[cfg_attr(wasi_runner, ignore = "WASI: no stdout-to-file redirection")] fn test_touch_dash() { new_ucmd!().args(&["-h", "-"]).succeeds().no_output(); } @@ -1124,6 +1149,7 @@ fn test_touch_f_option() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_touch_non_utf8_paths() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; @@ -1140,6 +1166,7 @@ fn test_touch_non_utf8_paths() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_touch_device_files() { let (_, mut ucmd) = at_and_ucmd!(); ucmd.args(&["/dev/null", "/dev/zero", "/dev/full", "/dev/random"]) @@ -1156,11 +1183,9 @@ fn test_touch_device_files() { #[test] #[cfg(unix)] fn test_touch_does_not_truncate_symlink_target() { - use std::os::unix::fs::symlink; - let (at, mut ucmd) = at_and_ucmd!(); at.write("victim", "do not truncate me"); - symlink(at.plus("victim"), at.plus("link")).unwrap(); + at.symlink_file("victim", "link"); ucmd.arg("link").succeeds(); @@ -1171,10 +1196,8 @@ fn test_touch_does_not_truncate_symlink_target() { #[test] #[cfg(unix)] fn test_touch_through_dangling_symlink_creates_target() { - use std::os::unix::fs::symlink; - let (at, mut ucmd) = at_and_ucmd!(); - symlink(at.plus("missing"), at.plus("link")).unwrap(); + at.symlink_file("missing", "link"); ucmd.arg("link").succeeds(); diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index 642af8aab61..0a1ce8d5f90 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -1629,8 +1629,9 @@ fn test_octal_escape_ambiguous_followed_by_non_utf8() { .stderr_contains("warning: invalid utf8 sequence"); } -#[cfg(target_os = "linux")] #[test] +#[cfg(target_os = "linux")] +#[cfg_attr(wasip2_runner, ignore = "WASI P2: /dev/full filesystem not available")] fn test_failed_write_is_reported() { new_ucmd!() .pipe_in("hello") diff --git a/tests/by-util/test_true.rs b/tests/by-util/test_true.rs index 61527772805..62474783609 100644 --- a/tests/by-util/test_true.rs +++ b/tests/by-util/test_true.rs @@ -54,6 +54,7 @@ fn test_conflict() { #[test] #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +#[cfg_attr(wasip2_runner, ignore = "WASI P2: /dev/full filesystem not available")] fn test_full() { for option in ["--version", "--help"] { let dev_full = OpenOptions::new().write(true).open("/dev/full").unwrap(); diff --git a/tests/by-util/test_tsort.rs b/tests/by-util/test_tsort.rs index 64fe97385df..5353442f979 100644 --- a/tests/by-util/test_tsort.rs +++ b/tests/by-util/test_tsort.rs @@ -9,6 +9,7 @@ use uutests::new_ucmd; #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_tsort_non_utf8_paths() { use std::os::unix::ffi::OsStringExt; let (at, mut ucmd) = at_and_ucmd!(); diff --git a/tests/by-util/test_uname.rs b/tests/by-util/test_uname.rs index 25485f5c120..384f6c27b2e 100644 --- a/tests/by-util/test_uname.rs +++ b/tests/by-util/test_uname.rs @@ -71,11 +71,19 @@ fn test_uname_operating_system() { .arg("--operating-system") .succeeds() .stdout_is("Android\n"); - #[cfg(target_vendor = "apple")] + // The test binary runs on the host (e.g. macOS), but under the WASI + // runner the coreutils binary under test is the wasm guest, which + // correctly self-reports "WASI" rather than the host's OS name. + #[cfg(all(target_vendor = "apple", not(wasi_runner)))] new_ucmd!() .arg("--operating-system") .succeeds() .stdout_is("Darwin\n"); + #[cfg(all(target_vendor = "apple", wasi_runner))] + new_ucmd!() + .arg("--operating-system") + .succeeds() + .stdout_is("WASI\n"); #[cfg(target_os = "freebsd")] new_ucmd!() .arg("--operating-system") diff --git a/tests/by-util/test_uniq.rs b/tests/by-util/test_uniq.rs index 8e940a743cd..29271604614 100644 --- a/tests/by-util/test_uniq.rs +++ b/tests/by-util/test_uniq.rs @@ -1172,6 +1172,7 @@ fn gnu_tests() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: locale database not visible")] fn test_stdin_w1_multibyte() { let input = "à\ná\n"; new_ucmd!() @@ -1195,6 +1196,7 @@ fn test_c_locale_counts_bytes() { #[cfg(target_os = "linux")] #[test] +#[cfg_attr(wasip2_runner, ignore = "WASI P2: /dev/full filesystem not available")] fn test_failed_write_is_reported() { new_ucmd!() .pipe_in("hello") diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index 62f100b4293..6df23148b2a 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -433,10 +433,7 @@ fn test_file_bytes_dictate_width() { #[test] fn test_read_from_directory_error() { let cmd = new_ucmd!().args(&["."]).fails(); - if std::env::var("UUTESTS_WASM_RUNNER").is_ok() { - // wasi-libc may report a different error string than the host libc - cmd.stderr_contains("wc: .:"); - } else if cfg!(windows) { + if cfg!(windows) { cmd.stderr_contains(".: Permission denied").stdout_is(""); } else { cmd.stderr_contains(".: Is a directory") @@ -450,17 +447,14 @@ fn test_read_error_order_with_stderr_to_stdout() { let (at, mut ucmd) = at_and_ucmd!(); at.mkdir("ioerrdir"); - let cmd = ucmd.arg("ioerrdir").stderr_to_stdout().fails(); - if std::env::var("UUTESTS_WASM_RUNNER").is_ok() { - // wasi-libc may report a different error string than the host libc - cmd.stdout_contains("wc: ioerrdir:"); - } else { - let expected = format!( - "{:>7} {:>7} {:>7} ioerrdir\nwc: ioerrdir: Is a directory\n", - 0, 0, 0 - ); - cmd.stdout_only(expected); - } + let expected = format!( + "{:>7} {:>7} {:>7} ioerrdir\nwc: ioerrdir: Is a directory\n", + 0, 0, 0 + ); + ucmd.arg("ioerrdir") + .stderr_to_stdout() + .fails() + .stdout_only(expected); } /// Test that getting counts from nonexistent file is an error. @@ -812,23 +806,17 @@ fn files0_from_dir() { const DOT_ERR: &str = dir_err!("."); // On Unix, `read(open("."))` fails. On Windows, `open(".")` fails. Thus, the errors happen in - // different contexts. On WASI, the error string may differ (e.g., "Bad file descriptor"). - let wasm = std::env::var("UUTESTS_WASM_RUNNER").is_ok(); - - let cmd = new_ucmd!().args(&["--files0-from=dir with spaces"]).fails(); - if wasm { - cmd.stderr_contains("wc: 'dir with spaces': read error:"); - } else { - cmd.stderr_only(dir_err!("'dir with spaces'")); - } + // different contexts. + new_ucmd!() + .args(&["--files0-from=dir with spaces"]) + .fails() + .stderr_only(dir_err!("'dir with spaces'")); // Those contexts have different rules about quoting in errors... - let cmd = new_ucmd!().args(&["--files0-from=."]).fails(); - if wasm { - cmd.stderr_contains("wc: .: read error:"); - } else { - cmd.stderr_only(DOT_ERR); - } + new_ucmd!() + .args(&["--files0-from=."]) + .fails() + .stderr_only(DOT_ERR); // That also means you cannot `< . wc --files0-from=-` on Windows. #[cfg(not(windows))] diff --git a/tests/uutests/Cargo.toml b/tests/uutests/Cargo.toml index 7cac6ab71d2..6bae96d2cdf 100644 --- a/tests/uutests/Cargo.toml +++ b/tests/uutests/Cargo.toml @@ -34,6 +34,7 @@ uucore = { workspace = true, features = [ "process", "signals", "utmpx", + "fs", ] } [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] diff --git a/tests/uutests/src/lib/util.rs b/tests/uutests/src/lib/util.rs index 0325ba49e91..d8ecf08b8db 100644 --- a/tests/uutests/src/lib/util.rs +++ b/tests/uutests/src/lib/util.rs @@ -1212,7 +1212,19 @@ impl AtPath { self.plus_as_string(link) ), ); - symlink_file(self.plus(original), self.plus(link)).unwrap(); + // Use a relative target instead of `self.plus(original)`'s absolute + // host path: under the WASI test runner, the wasm guest only sees + // the sandbox root, so an absolute host path would always dangle. + // Callers that pass an already-absolute `original` (e.g. "/") mean + // it literally, so leave those alone. + let link_path = self.plus(link); + let target = match (Path::new(original).is_absolute(), link_path.parent()) { + (false, Some(link_dir)) => { + uucore::fs::make_path_relative_to(self.plus(original), link_dir) + } + _ => self.plus(original), + }; + symlink_file(target, link_path).unwrap(); } pub fn relative_symlink_file(&self, original: &str, link: &str) { @@ -1234,7 +1246,15 @@ impl AtPath { self.plus_as_string(link) ), ); - symlink_dir(self.plus(original), self.plus(link)).unwrap(); + // See the comment in `symlink_file` about relative vs. absolute targets. + let link_path = self.plus(link); + let target = match (Path::new(original).is_absolute(), link_path.parent()) { + (false, Some(link_dir)) => { + uucore::fs::make_path_relative_to(self.plus(original), link_dir) + } + _ => self.plus(original), + }; + symlink_dir(target, link_path).unwrap(); } pub fn relative_symlink_dir(&self, original: &str, link: &str) { @@ -1876,6 +1896,11 @@ impl UCommand { let mut command = if let Some(ref runner) = wasm_runner { let bin = self.bin_path.as_ref().unwrap(); let mut cmd = Command::new(runner); + // Extra runner flags, e.g. "-S cli-exit-with-code=y" to opt in to + // an unstable WASI Preview2 feature. Space-separated. + if let Ok(extra_args) = env::var("UUTESTS_WASM_RUNNER_ARGS") { + cmd.args(extra_args.split_whitespace()); + } // Map the working directory as the WASI guest's root. Only files // under this directory are visible to the guest; tests using // absolute host paths outside it must be skipped.