Skip to content
Closed
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 @@ -4407,7 +4419,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 @@ -4426,7 +4438,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
16 changes: 15 additions & 1 deletion packages/browser/src/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3247,7 +3247,10 @@ export const POLYFILL_CODE_MAP: Record<string, string> = {
}
return 0o100644;
},
path_mode(pathPtr, pathLen, followSymlinks) {
// Signature must match the node runner's host_fs.path_mode
// (fd, pathPtr, pathLen, followSymlinks). The guest passes the
// directory fd first (3 = cwd preopen); the path is at args 2/3.
path_mode(_fd, pathPtr, pathLen, followSymlinks) {
try {
const guestPath = resolveGuestPath(readString(pathPtr, pathLen));
const stat = Number(followSymlinks) === 0
Expand All @@ -3258,6 +3261,17 @@ export const POLYFILL_CODE_MAP: Record<string, string> = {
return 0;
}
},
// Matches node runner host_fs.chmod(fd, pathPtr, pathLen, mode):
// 0 on success, 1 on failure.
chmod(_fd, pathPtr, pathLen, mode) {
try {
const guestPath = resolveGuestPath(readString(pathPtr, pathLen));
fs().chmodSync(guestPath, Number(mode) >>> 0);
return 0;
} catch {
return 1;
}
},
},
host_process: {
proc_spawn(argvPtr, argvLen, envpPtr, envpLen, stdinFd, stdoutFd, stderrFd, cwdPtr, cwdLen, retPid) {
Expand Down
12 changes: 10 additions & 2 deletions registry/native/crates/libs/builtins/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ use std::os::unix::fs::MetadataExt;
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;
// Signature must match the sidecar host_fs.path_mode
// (dir_fd, path_ptr, path_len, follow_symlinks).
pub fn path_mode(
dir_fd: u32,
path_ptr: *const u8,
path_len: u32,
follow_symlinks: u32,
) -> u32;
}
}

Expand Down Expand Up @@ -380,7 +387,8 @@ fn permission_allows(_path: &str, metadata: &Metadata, requested_bit: u32) -> bo
#[cfg(target_os = "wasi")]
fn wasi_path_mode(path: &str) -> Option<u32> {
let bytes = path.as_bytes();
let mode = unsafe { host_fs::path_mode(bytes.as_ptr(), bytes.len() as u32, 1) };
// dir_fd 3 = cwd preopen; absolute paths ignore it.
let mode = unsafe { host_fs::path_mode(3, bytes.as_ptr(), bytes.len() as u32, 1) };
if mode == 0 {
None
} else {
Expand Down
12 changes: 10 additions & 2 deletions registry/native/crates/libs/shims/src/which.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@ use std::os::unix::fs::MetadataExt;
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;
// Signature must match the sidecar host_fs.path_mode
// (dir_fd, path_ptr, path_len, follow_symlinks).
pub fn path_mode(
dir_fd: u32,
path_ptr: *const u8,
path_len: u32,
follow_symlinks: u32,
) -> u32;
}
}

Expand Down Expand Up @@ -45,7 +52,8 @@ fn executable_mode_bits(path: &Path, _metadata: &fs::Metadata) -> bool {
let Ok(path_len) = u32::try_from(bytes.len()) else {
return false;
};
let mode = unsafe { host_fs::path_mode(bytes.as_ptr(), path_len, 1) };
// dir_fd 3 = cwd preopen; absolute paths ignore it.
let mode = unsafe { host_fs::path_mode(3, bytes.as_ptr(), path_len, 1) };
(mode & 0o111) != 0
}

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
Loading
Loading