Skip to content

Commit 8145b07

Browse files
committed
fix(registry): coreutils stat/chmod/ls show real permission bits
1 parent 3fdd7fb commit 8145b07

11 files changed

Lines changed: 241 additions & 78 deletions

File tree

crates/execution/assets/runners/wasm-runner.mjs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2204,6 +2204,17 @@ function resolveSyntheticHostMapping(value, fromGuestDir = '/') {
22042204
return resolveModuleGuestPathToHostMapping(guestPath);
22052205
}
22062206

2207+
function chmodMappedGuestPath(guestPath, hostPath, mode) {
2208+
fsModule.chmodSync(hostPath, mode);
2209+
try {
2210+
if (typeof guestPath === 'string' && guestPath.length > 0) {
2211+
fsModule.chmodSync(guestPath, mode);
2212+
}
2213+
} catch {
2214+
// Best effort: host-mapped paths may not also exist as direct kernel paths.
2215+
}
2216+
}
2217+
22072218
function maybeCreateSyntheticCommandResult(command, args, cwd) {
22082219
const basename = path.posix.basename(String(command || ''));
22092220

@@ -2218,6 +2229,7 @@ function maybeCreateSyntheticCommandResult(command, args, cwd) {
22182229
const mode = Number.parseInt(modeArg, 8) >>> 0;
22192230
try {
22202231
for (const targetArg of args.slice(1)) {
2232+
const guestPath = resolveSyntheticGuestPath(targetArg, cwd || '/');
22212233
const mapping = resolveSyntheticHostMapping(targetArg, cwd || '/');
22222234
if (!mapping || typeof mapping.hostPath !== 'string') {
22232235
throw new Error(`No such file or directory: ${targetArg}`);
@@ -2227,7 +2239,7 @@ function maybeCreateSyntheticCommandResult(command, args, cwd) {
22272239
error.code = 'EROFS';
22282240
throw error;
22292241
}
2230-
fsModule.chmodSync(mapping.hostPath, mode);
2242+
chmodMappedGuestPath(guestPath, mapping.hostPath, mode);
22312243
}
22322244
return { exitCode: 0, stdout: '', stderr: '' };
22332245
} catch (error) {
@@ -4407,7 +4419,7 @@ const hostFsImport = {
44074419
hostPath: mapping.hostPath,
44084420
mode: Number(mode) >>> 0,
44094421
});
4410-
fsModule.chmodSync(mapping.hostPath, Number(mode) >>> 0);
4422+
chmodMappedGuestPath(target, mapping.hostPath, Number(mode) >>> 0);
44114423
return 0;
44124424
} catch {
44134425
traceHostProcess('host-fs-chmod-fault', {});
@@ -4426,7 +4438,7 @@ const hostFsImport = {
44264438
if (!mapping || typeof mapping.hostPath !== 'string' || mapping.readOnly) {
44274439
return 1;
44284440
}
4429-
fsModule.chmodSync(mapping.hostPath, Number(mode) >>> 0);
4441+
chmodMappedGuestPath(handle.guestPath, mapping.hostPath, Number(mode) >>> 0);
44304442
return 0;
44314443
}
44324444
const targetFd =

packages/browser/src/runtime.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3247,7 +3247,10 @@ export const POLYFILL_CODE_MAP: Record<string, string> = {
32473247
}
32483248
return 0o100644;
32493249
},
3250-
path_mode(pathPtr, pathLen, followSymlinks) {
3250+
// Signature must match the node runner's host_fs.path_mode
3251+
// (fd, pathPtr, pathLen, followSymlinks). The guest passes the
3252+
// directory fd first (3 = cwd preopen); the path is at args 2/3.
3253+
path_mode(_fd, pathPtr, pathLen, followSymlinks) {
32513254
try {
32523255
const guestPath = resolveGuestPath(readString(pathPtr, pathLen));
32533256
const stat = Number(followSymlinks) === 0
@@ -3258,6 +3261,17 @@ export const POLYFILL_CODE_MAP: Record<string, string> = {
32583261
return 0;
32593262
}
32603263
},
3264+
// Matches node runner host_fs.chmod(fd, pathPtr, pathLen, mode):
3265+
// 0 on success, 1 on failure.
3266+
chmod(_fd, pathPtr, pathLen, mode) {
3267+
try {
3268+
const guestPath = resolveGuestPath(readString(pathPtr, pathLen));
3269+
fs().chmodSync(guestPath, Number(mode) >>> 0);
3270+
return 0;
3271+
} catch {
3272+
return 1;
3273+
}
3274+
},
32613275
},
32623276
host_process: {
32633277
proc_spawn(argvPtr, argvLen, envpPtr, envpLen, stdinFd, stdoutFd, stderrFd, cwdPtr, cwdLen, retPid) {

registry/native/crates/libs/builtins/src/lib.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,14 @@ use std::os::unix::fs::MetadataExt;
1515
mod host_fs {
1616
#[link(wasm_import_module = "host_fs")]
1717
unsafe extern "C" {
18-
pub fn path_mode(path_ptr: *const u8, path_len: u32, follow_symlinks: u32) -> u32;
18+
// Signature must match the sidecar host_fs.path_mode
19+
// (dir_fd, path_ptr, path_len, follow_symlinks).
20+
pub fn path_mode(
21+
dir_fd: u32,
22+
path_ptr: *const u8,
23+
path_len: u32,
24+
follow_symlinks: u32,
25+
) -> u32;
1926
}
2027
}
2128

@@ -380,7 +387,8 @@ fn permission_allows(_path: &str, metadata: &Metadata, requested_bit: u32) -> bo
380387
#[cfg(target_os = "wasi")]
381388
fn wasi_path_mode(path: &str) -> Option<u32> {
382389
let bytes = path.as_bytes();
383-
let mode = unsafe { host_fs::path_mode(bytes.as_ptr(), bytes.len() as u32, 1) };
390+
// dir_fd 3 = cwd preopen; absolute paths ignore it.
391+
let mode = unsafe { host_fs::path_mode(3, bytes.as_ptr(), bytes.len() as u32, 1) };
384392
if mode == 0 {
385393
None
386394
} else {

registry/native/crates/libs/shims/src/which.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,14 @@ use std::os::unix::fs::MetadataExt;
1717
mod host_fs {
1818
#[link(wasm_import_module = "host_fs")]
1919
unsafe extern "C" {
20-
pub fn path_mode(path_ptr: *const u8, path_len: u32, follow_symlinks: u32) -> u32;
20+
// Signature must match the sidecar host_fs.path_mode
21+
// (dir_fd, path_ptr, path_len, follow_symlinks).
22+
pub fn path_mode(
23+
dir_fd: u32,
24+
path_ptr: *const u8,
25+
path_len: u32,
26+
follow_symlinks: u32,
27+
) -> u32;
2128
}
2229
}
2330

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

registry/native/patches/crates/brush-interactive/0001-wasi-support.patch

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -139,19 +139,3 @@ diff -ruN '--exclude=*.orig' a/src/reedline/validator.rs b/src/reedline/validato
139139
let shell = tokio::task::block_in_place(|| {
140140
tokio::runtime::Handle::current().block_on(self.shell.lock())
141141
});
142-
--- a/Cargo.toml
143-
+++ b/Cargo.toml
144-
@@ -91,6 +91,13 @@
145-
"signal",
146-
]
147-
148-
+[target."cfg(target_arch = \"wasm32\")".dependencies.tokio]
149-
+version = "1.48.0"
150-
+features = [
151-
+ "macros",
152-
+ "sync",
153-
+]
154-
+
155-
[target."cfg(unix)".dependencies.nix]
156-
version = "0.30.1"
157-
features = ["term"]

registry/native/patches/crates/uu_chmod/0001-wasi-compat.patch

Lines changed: 71 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
--- a/src/chmod.rs
22
+++ b/src/chmod.rs
3-
@@ -8,16 +8,70 @@
3+
@@ -8,16 +8,87 @@
44
use clap::{Arg, ArgAction, Command};
55
use std::ffi::OsString;
66
use std::fs;
@@ -27,23 +27,42 @@
2727
+ mod host_fs {
2828
+ #[link(wasm_import_module = "host_fs")]
2929
+ unsafe extern "C" {
30-
+ pub fn chmod(path_ptr: *const u8, path_len: u32, mode: u32) -> u32;
30+
+ pub fn chmod(fd: u32, path_ptr: *const u8, path_len: u32, mode: u32) -> u32;
31+
+ pub fn path_mode(
32+
+ fd: u32,
33+
+ path_ptr: *const u8,
34+
+ path_len: u32,
35+
+ follow_symlinks: u32,
36+
+ ) -> u32;
3137
+ }
3238
+ }
3339
+
34-
+ /// Extension trait to provide mode() on WASI Metadata.
35-
+ pub trait MetadataMode {
36-
+ fn mode(&self) -> u32;
40+
+ fn fallback_mode(meta: &fs::Metadata) -> u32 {
41+
+ let ft = meta.file_type();
42+
+ let base = if ft.is_dir() { 0o755 } else { 0o644 };
43+
+ if meta.permissions().readonly() {
44+
+ base & !0o222
45+
+ } else {
46+
+ base
47+
+ }
3748
+ }
38-
+ impl MetadataMode for fs::Metadata {
39-
+ fn mode(&self) -> u32 {
40-
+ let ft = self.file_type();
41-
+ let base = if ft.is_dir() { 0o755 } else { 0o644 };
42-
+ if self.permissions().readonly() {
43-
+ base & !0o222
44-
+ } else {
45-
+ base
46-
+ }
49+
+
50+
+ pub fn mode_for_path(path: &Path, meta: &fs::Metadata, follow_symlinks: bool) -> u32 {
51+
+ let Some(path_str) = path.to_str() else {
52+
+ return fallback_mode(meta);
53+
+ };
54+
+ let mode = unsafe {
55+
+ host_fs::path_mode(
56+
+ 3,
57+
+ path_str.as_ptr(),
58+
+ path_str.len() as u32,
59+
+ if follow_symlinks { 1 } else { 0 },
60+
+ )
61+
+ };
62+
+ if mode == 0 {
63+
+ fallback_mode(meta)
64+
+ } else {
65+
+ mode & 0o7777
4766
+ }
4867
+ }
4968
+
@@ -55,7 +74,7 @@
5574
+ .to_str()
5675
+ .ok_or_else(|| Error::new(ErrorKind::InvalidInput, "path is not valid UTF-8"))?
5776
+ .as_bytes();
58-
+ let status = unsafe { host_fs::chmod(bytes.as_ptr(), bytes.len() as u32, mode) };
77+
+ let status = unsafe { host_fs::chmod(3, bytes.as_ptr(), bytes.len() as u32, mode) };
5978
+ if status == 0 {
6079
+ return Ok(());
6180
+ }
@@ -65,13 +84,47 @@
6584
+ fs::set_permissions(path, perms)
6685
+ }
6786
+}
68-
+#[cfg(target_os = "wasi")]
69-
+use wasi_compat::MetadataMode;
7087
+
7188
#[cfg(all(unix, not(target_os = "redox")))]
7289
use uucore::safe_traversal::{DirFd, SymlinkBehavior};
7390
use uucore::{format_usage, show, show_error};
74-
@@ -683,7 +737,12 @@
91+
@@ -121,7 +192,16 @@
92+
let preserve_root = matches.get_flag(options::PRESERVE_ROOT);
93+
let fmode = match matches.get_one::<OsString>(options::REFERENCE) {
94+
Some(fref) => match fs::metadata(fref) {
95+
- Ok(meta) => Some(meta.mode() & 0o7777),
96+
+ Ok(meta) => {
97+
+ #[cfg(target_os = "wasi")]
98+
+ {
99+
+ Some(wasi_compat::mode_for_path(Path::new(fref), &meta, true))
100+
+ }
101+
+ #[cfg(not(target_os = "wasi"))]
102+
+ {
103+
+ Some(meta.mode() & 0o7777)
104+
+ }
105+
+ }
106+
Err(_) => {
107+
return Err(ChmodError::CannotStat(fref.into()).into());
108+
}
109+
@@ -623,7 +703,16 @@
110+
let metadata = get_metadata(file, dereference);
111+
112+
let fperm = match metadata {
113+
- Ok(meta) => meta.mode() & 0o7777,
114+
+ Ok(meta) => {
115+
+ #[cfg(target_os = "wasi")]
116+
+ {
117+
+ wasi_compat::mode_for_path(file, &meta, dereference)
118+
+ }
119+
+ #[cfg(not(target_os = "wasi"))]
120+
+ {
121+
+ meta.mode() & 0o7777
122+
+ }
123+
+ }
124+
Err(err) => {
125+
// Handle dangling symlinks or other errors
126+
return if file.is_symlink() && !dereference {
127+
@@ -683,7 +772,12 @@
75128
// Use the helper method for consistent reporting
76129
self.report_permission_change(file, fperm, mode);
77130
Ok(())

registry/native/patches/crates/uu_ls/0001-wasi-host-fs-mode-display.patch

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
fsext::{MetadataTimeField, metadata_get_time},
1010
line_ending::LineEnding,
1111
os_str_as_bytes_lossy,
12-
@@ -77,6 +77,38 @@
12+
@@ -77,6 +77,60 @@
1313
translate,
1414
version_cmp::version_cmp,
1515
};
@@ -18,37 +18,59 @@
1818
+
1919
+#[cfg(target_os = "wasi")]
2020
+mod wasi_host_fs {
21+
+ use std::env;
22+
+
2123
+ use super::{Metadata, Path};
2224
+
2325
+ mod host_fs {
2426
+ #[link(wasm_import_module = "host_fs")]
2527
+ unsafe extern "C" {
26-
+ pub fn path_mode(path_ptr: *const u8, path_len: u32, follow_symlinks: u32) -> u32;
28+
+ pub fn path_mode(
29+
+ fd: u32,
30+
+ path_ptr: *const u8,
31+
+ path_len: u32,
32+
+ follow_symlinks: u32,
33+
+ ) -> u32;
2734
+ }
2835
+ }
2936
+
30-
+ pub fn mode_for_path(path: &Path, metadata: &Metadata, follow_symlinks: bool) -> u32 {
37+
+ fn fallback_mode(metadata: &Metadata) -> u32 {
38+
+ if metadata.is_dir() { 0o040755 } else { 0o100644 }
39+
+ }
40+
+
41+
+ fn raw_mode_for_path(path: &Path, follow_symlinks: bool) -> u32 {
3142
+ let Some(path_str) = path.to_str() else {
32-
+ return if metadata.is_dir() { 0o040755 } else { 0o100644 };
43+
+ return 0;
3344
+ };
34-
+ let mode = unsafe {
45+
+ unsafe {
3546
+ host_fs::path_mode(
47+
+ 3,
3648
+ path_str.as_ptr(),
3749
+ path_str.len() as u32,
3850
+ if follow_symlinks { 1 } else { 0 },
3951
+ )
40-
+ };
41-
+ if mode == 0 {
42-
+ if metadata.is_dir() { 0o040755 } else { 0o100644 }
43-
+ } else {
52+
+ }
53+
+ }
54+
+
55+
+ pub fn mode_for_path(path: &Path, metadata: &Metadata, follow_symlinks: bool) -> u32 {
56+
+ let mode = raw_mode_for_path(path, follow_symlinks);
57+
+ if mode != 0 {
4458
+ mode
59+
+ } else if path.is_relative() {
60+
+ env::current_dir()
61+
+ .ok()
62+
+ .map(|cwd| raw_mode_for_path(&cwd.join(path), follow_symlinks))
63+
+ .filter(|mode| *mode != 0)
64+
+ .unwrap_or_else(|| fallback_mode(metadata))
65+
+ } else {
66+
+ fallback_mode(metadata)
4567
+ }
4668
+ }
4769
+}
4870

4971
mod dired;
5072
use dired::{DiredOutput, is_dired_arg_present};
51-
@@ -2982,7 +3014,15 @@
73+
@@ -2982,7 +3036,15 @@
5274
let is_acl_set = false;
5375
#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
5476
let is_acl_set = has_acl(item.path());

0 commit comments

Comments
 (0)