Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions crates/execution/assets/runners/wasm-runner.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2204,6 +2204,17 @@ function resolveSyntheticHostMapping(value, fromGuestDir = '/') {
return resolveModuleGuestPathToHostMapping(guestPath);
}

function chmodMappedGuestPath(guestPath, hostPath, mode) {
fsModule.chmodSync(hostPath, mode);
try {
if (typeof guestPath === 'string' && guestPath.length > 0) {
fsModule.chmodSync(guestPath, mode);
}
} catch {
// Best effort: host-mapped paths may not also exist as direct kernel paths.
}
}

function maybeCreateSyntheticCommandResult(command, args, cwd) {
const basename = path.posix.basename(String(command || ''));

Expand All @@ -2218,6 +2229,7 @@ function maybeCreateSyntheticCommandResult(command, args, cwd) {
const mode = Number.parseInt(modeArg, 8) >>> 0;
try {
for (const targetArg of args.slice(1)) {
const guestPath = resolveSyntheticGuestPath(targetArg, cwd || '/');
const mapping = resolveSyntheticHostMapping(targetArg, cwd || '/');
if (!mapping || typeof mapping.hostPath !== 'string') {
throw new Error(`No such file or directory: ${targetArg}`);
Expand All @@ -2227,7 +2239,7 @@ function maybeCreateSyntheticCommandResult(command, args, cwd) {
error.code = 'EROFS';
throw error;
}
fsModule.chmodSync(mapping.hostPath, mode);
chmodMappedGuestPath(guestPath, mapping.hostPath, mode);
}
return { exitCode: 0, stdout: '', stderr: '' };
} catch (error) {
Expand Down Expand Up @@ -4380,7 +4392,7 @@ const hostFsImport = {
hostPath: mapping.hostPath,
mode: Number(mode) >>> 0,
});
fsModule.chmodSync(mapping.hostPath, Number(mode) >>> 0);
chmodMappedGuestPath(target, mapping.hostPath, Number(mode) >>> 0);
return 0;
} catch {
traceHostProcess('host-fs-chmod-fault', {});
Expand All @@ -4399,7 +4411,7 @@ const hostFsImport = {
if (!mapping || typeof mapping.hostPath !== 'string' || mapping.readOnly) {
return 1;
}
fsModule.chmodSync(mapping.hostPath, Number(mode) >>> 0);
chmodMappedGuestPath(handle.guestPath, mapping.hostPath, Number(mode) >>> 0);
return 0;
}
const targetFd =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,19 +139,3 @@ diff -ruN '--exclude=*.orig' a/src/reedline/validator.rs b/src/reedline/validato
let shell = tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(self.shell.lock())
});
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -91,6 +91,13 @@
"signal",
]

+[target."cfg(target_arch = \"wasm32\")".dependencies.tokio]
+version = "1.48.0"
+features = [
+ "macros",
+ "sync",
+]
+
[target."cfg(unix)".dependencies.nix]
version = "0.30.1"
features = ["term"]
89 changes: 71 additions & 18 deletions registry/native/patches/crates/uu_chmod/0001-wasi-compat.patch
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
--- a/src/chmod.rs
+++ b/src/chmod.rs
@@ -8,16 +8,70 @@
@@ -8,16 +8,87 @@
use clap::{Arg, ArgAction, Command};
use std::ffi::OsString;
use std::fs;
Expand All @@ -27,23 +27,42 @@
+ mod host_fs {
+ #[link(wasm_import_module = "host_fs")]
+ unsafe extern "C" {
+ pub fn chmod(path_ptr: *const u8, path_len: u32, mode: u32) -> u32;
+ pub fn chmod(fd: u32, path_ptr: *const u8, path_len: u32, mode: u32) -> u32;
+ pub fn path_mode(
+ fd: u32,
+ path_ptr: *const u8,
+ path_len: u32,
+ follow_symlinks: u32,
+ ) -> u32;
+ }
+ }
+
+ /// Extension trait to provide mode() on WASI Metadata.
+ pub trait MetadataMode {
+ fn mode(&self) -> u32;
+ fn fallback_mode(meta: &fs::Metadata) -> u32 {
+ let ft = meta.file_type();
+ let base = if ft.is_dir() { 0o755 } else { 0o644 };
+ if meta.permissions().readonly() {
+ base & !0o222
+ } else {
+ base
+ }
+ }
+ impl MetadataMode for fs::Metadata {
+ fn mode(&self) -> u32 {
+ let ft = self.file_type();
+ let base = if ft.is_dir() { 0o755 } else { 0o644 };
+ if self.permissions().readonly() {
+ base & !0o222
+ } else {
+ base
+ }
+
+ pub fn mode_for_path(path: &Path, meta: &fs::Metadata, follow_symlinks: bool) -> u32 {
+ let Some(path_str) = path.to_str() else {
+ return fallback_mode(meta);
+ };
+ let mode = unsafe {
+ host_fs::path_mode(
+ 3,
+ path_str.as_ptr(),
+ path_str.len() as u32,
+ if follow_symlinks { 1 } else { 0 },
+ )
+ };
+ if mode == 0 {
+ fallback_mode(meta)
+ } else {
+ mode & 0o7777
+ }
+ }
+
Expand All @@ -55,7 +74,7 @@
+ .to_str()
+ .ok_or_else(|| Error::new(ErrorKind::InvalidInput, "path is not valid UTF-8"))?
+ .as_bytes();
+ let status = unsafe { host_fs::chmod(bytes.as_ptr(), bytes.len() as u32, mode) };
+ let status = unsafe { host_fs::chmod(3, bytes.as_ptr(), bytes.len() as u32, mode) };
+ if status == 0 {
+ return Ok(());
+ }
Expand All @@ -65,13 +84,47 @@
+ fs::set_permissions(path, perms)
+ }
+}
+#[cfg(target_os = "wasi")]
+use wasi_compat::MetadataMode;
+
#[cfg(all(unix, not(target_os = "redox")))]
use uucore::safe_traversal::{DirFd, SymlinkBehavior};
use uucore::{format_usage, show, show_error};
@@ -683,7 +737,12 @@
@@ -121,7 +192,16 @@
let preserve_root = matches.get_flag(options::PRESERVE_ROOT);
let fmode = match matches.get_one::<OsString>(options::REFERENCE) {
Some(fref) => match fs::metadata(fref) {
- Ok(meta) => Some(meta.mode() & 0o7777),
+ Ok(meta) => {
+ #[cfg(target_os = "wasi")]
+ {
+ Some(wasi_compat::mode_for_path(Path::new(fref), &meta, true))
+ }
+ #[cfg(not(target_os = "wasi"))]
+ {
+ Some(meta.mode() & 0o7777)
+ }
+ }
Err(_) => {
return Err(ChmodError::CannotStat(fref.into()).into());
}
@@ -623,7 +703,16 @@
let metadata = get_metadata(file, dereference);

let fperm = match metadata {
- Ok(meta) => meta.mode() & 0o7777,
+ Ok(meta) => {
+ #[cfg(target_os = "wasi")]
+ {
+ wasi_compat::mode_for_path(file, &meta, dereference)
+ }
+ #[cfg(not(target_os = "wasi"))]
+ {
+ meta.mode() & 0o7777
+ }
+ }
Err(err) => {
// Handle dangling symlinks or other errors
return if file.is_symlink() && !dereference {
@@ -683,7 +772,12 @@
// Use the helper method for consistent reporting
self.report_permission_change(file, fperm, mode);
Ok(())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
fsext::{MetadataTimeField, metadata_get_time},
line_ending::LineEnding,
os_str_as_bytes_lossy,
@@ -77,6 +77,38 @@
@@ -77,6 +77,60 @@
translate,
version_cmp::version_cmp,
};
Expand All @@ -18,37 +18,59 @@
+
+#[cfg(target_os = "wasi")]
+mod wasi_host_fs {
+ use std::env;
+
+ use super::{Metadata, Path};
+
+ mod host_fs {
+ #[link(wasm_import_module = "host_fs")]
+ unsafe extern "C" {
+ pub fn path_mode(path_ptr: *const u8, path_len: u32, follow_symlinks: u32) -> u32;
+ pub fn path_mode(
+ fd: u32,
+ path_ptr: *const u8,
+ path_len: u32,
+ follow_symlinks: u32,
+ ) -> u32;
+ }
+ }
+
+ pub fn mode_for_path(path: &Path, metadata: &Metadata, follow_symlinks: bool) -> u32 {
+ fn fallback_mode(metadata: &Metadata) -> u32 {
+ if metadata.is_dir() { 0o040755 } else { 0o100644 }
+ }
+
+ fn raw_mode_for_path(path: &Path, follow_symlinks: bool) -> u32 {
+ let Some(path_str) = path.to_str() else {
+ return if metadata.is_dir() { 0o040755 } else { 0o100644 };
+ return 0;
+ };
+ let mode = unsafe {
+ unsafe {
+ host_fs::path_mode(
+ 3,
+ path_str.as_ptr(),
+ path_str.len() as u32,
+ if follow_symlinks { 1 } else { 0 },
+ )
+ };
+ if mode == 0 {
+ if metadata.is_dir() { 0o040755 } else { 0o100644 }
+ } else {
+ }
+ }
+
+ pub fn mode_for_path(path: &Path, metadata: &Metadata, follow_symlinks: bool) -> u32 {
+ let mode = raw_mode_for_path(path, follow_symlinks);
+ if mode != 0 {
+ mode
+ } else if path.is_relative() {
+ env::current_dir()
+ .ok()
+ .map(|cwd| raw_mode_for_path(&cwd.join(path), follow_symlinks))
+ .filter(|mode| *mode != 0)
+ .unwrap_or_else(|| fallback_mode(metadata))
+ } else {
+ fallback_mode(metadata)
+ }
+ }
+}

mod dired;
use dired::{DiredOutput, is_dired_arg_present};
@@ -2982,7 +3014,15 @@
@@ -2982,7 +3036,15 @@
let is_acl_set = false;
#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
let is_acl_set = has_acl(item.path());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
use uucore::{entries, format_usage, show_error, show_warning};

use clap::{Arg, ArgAction, ArgMatches, Command};
@@ -23,10 +29,79 @@
@@ -23,10 +29,85 @@
use std::ffi::{OsStr, OsString};
use std::fs::{FileType, Metadata};
use std::io::Write;
Expand Down Expand Up @@ -79,7 +79,12 @@
+ mod host_fs {
+ #[link(wasm_import_module = "host_fs")]
+ unsafe extern "C" {
+ pub fn path_mode(path_ptr: *const u8, path_len: u32, follow_symlinks: u32) -> u32;
+ pub fn path_mode(
+ fd: u32,
+ path_ptr: *const u8,
+ path_len: u32,
+ follow_symlinks: u32,
+ ) -> u32;
+ }
+ }
+
Expand All @@ -89,6 +94,7 @@
+ };
+ let mode = unsafe {
+ host_fs::path_mode(
+ 3,
+ path_str.as_ptr(),
+ path_str.len() as u32,
+ if follow_symlinks { 1 } else { 0 },
Expand All @@ -105,15 +111,16 @@
use thiserror::Error;
use uucore::time::{FormatSystemTimeFallback, format_system_time, system_time_to_sec};

@@ -1048,11 +1123,16 @@
precision,
format,
@@ -1032,6 +1113,7 @@
display_name: &str,
file: &OsString,
file_type: FileType,
+ effective_mode: u32,
from_user: bool,
#[cfg(all(feature = "selinux", any(target_os = "linux", target_os = "android")))]
follow_symbolic_links: bool,
@@ -1050,9 +1132,9 @@
} => {
+ #[cfg(target_os = "wasi")]
+ let effective_mode =
+ wasi_host_fs::mode_for_path(Path::new(file), meta, self.follow);
+ #[cfg(not(target_os = "wasi"))]
+ let effective_mode = meta.mode();
let output = match format {
// access rights in octal
- 'a' => OutputType::UnsignedOct(0o7777 & meta.mode()),
Expand All @@ -124,7 +131,7 @@
// number of blocks allocated (see %B)
'b' => OutputType::Unsigned(meta.blocks()),

@@ -1096,9 +1176,9 @@
@@ -1096,9 +1178,9 @@
// device number in hex
'D' => OutputType::UnsignedHex(meta.dev()),
// raw mode in hex
Expand All @@ -136,3 +143,23 @@
// group ID of owner
'g' => OutputType::Unsigned(meta.gid() as u64),
// group name of owner
@@ -1234,6 +1316,11 @@
match result {
Ok(meta) => {
let file_type = meta.file_type();
+ #[cfg(target_os = "wasi")]
+ let effective_mode =
+ wasi_host_fs::mode_for_path(Path::new(&file), &meta, follow_symbolic_links);
+ #[cfg(not(target_os = "wasi"))]
+ let effective_mode = meta.mode();
let tokens = if self.from_user
|| !(file_type.is_char_device() || file_type.is_block_device())
{
@@ -1249,6 +1336,7 @@
&display_name,
&file,
file_type,
+ effective_mode,
self.from_user,
follow_symbolic_links,
) {
9 changes: 5 additions & 4 deletions registry/native/scripts/patch-vendor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,15 @@ for CRATE_DIR in $CRATE_DIRS; do
patch -p1 -d "$VENDOR_CRATE" < "$PATCH" > /dev/null 2>&1
echo "applied"
elif patch --dry-run -R -p1 -d "$VENDOR_CRATE" < "$PATCH" > /dev/null 2>&1; then
patch -R -p1 -d "$VENDOR_CRATE" < "$PATCH" > /dev/null 2>&1
patch -p1 -d "$VENDOR_CRATE" < "$PATCH" > /dev/null 2>&1
echo "reapplied"
echo "already applied"
else
# Mixed state (e.g. an interrupted earlier run left some
# hunks applied): apply the remaining hunks, tolerating
# already-applied ones; fail only on genuine rejects.
OUT=$(patch -p1 -N -r /dev/null -d "$VENDOR_CRATE" < "$PATCH" 2>&1); RC=$?
set +e
OUT=$(patch -p1 -N -r /dev/null -d "$VENDOR_CRATE" < "$PATCH" 2>&1)
RC=$?
set -e
if [ $RC -le 1 ] && ! echo "$OUT" | grep -q "FAILED"; then
echo "converged (mixed state)"
else
Expand Down
Loading
Loading