Skip to content

Commit fb6b016

Browse files
committed
stdio
1 parent c1c0e94 commit fb6b016

5 files changed

Lines changed: 184 additions & 60 deletions

File tree

kernel/src/wasm/host_funcs/filesystem.rs

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
1313
use core::sync::atomic::{AtomicI32, Ordering};
1414
use crate::wasm::Linker;
15+
use crate::wasm::func::Caller;
16+
use super::mem_access::MemoryAccessor;
1517

1618
/// Error codes
1719
pub const ERRNO_SUCCESS: i32 = 0;
@@ -146,32 +148,53 @@ pub struct Dirent {
146148
/// Register filesystem host functions
147149
pub fn register<T>(linker: &mut Linker<T>) -> crate::Result<()> {
148150
// fd_prestat_get - Get preopened directory info
149-
linker.func_wrap(
151+
linker.func_wrap_with_memory(
150152
"wasi_snapshot_preview1",
151153
"fd_prestat_get",
152-
|fd: i32, prestat_ptr: i32| -> i32 {
154+
|mut caller: Caller<'_, T>, fd: i32, prestat_ptr: i32| -> i32 {
153155
tracing::debug!("[WASM FS] fd_prestat_get(fd={}, ptr={})", fd, prestat_ptr);
154156

155157
// Only fd=3 is preopened (root directory)
156158
if fd != 3 {
157159
return ERRNO_BADF;
158160
}
159161

160-
// TODO: Write prestat structure to memory
161-
// Structure should be:
162-
// - tag: u8 = 0 (PREOPENTYPE_DIR)
163-
// - padding: 3 bytes
164-
// - name_len: u32 = 1 (for "/")
162+
if prestat_ptr < 0 {
163+
return ERRNO_INVAL;
164+
}
165+
166+
// Get memory accessor
167+
let mem = match MemoryAccessor::new(&mut caller) {
168+
Some(m) => m,
169+
None => return ERRNO_INVAL,
170+
};
171+
172+
// Write prestat structure (8 bytes total)
173+
// tag: u8 = 0 (PREOPENTYPE_DIR)
174+
// padding: 3 bytes
175+
// name_len: u32 = 1 (for "/")
176+
let tag = PREOPENTYPE_DIR;
177+
let name_len = 1u32; // Length of "/"
178+
179+
// Write tag (1 byte)
180+
if !unsafe { mem.write::<u8>(prestat_ptr as u32, &tag) } {
181+
return ERRNO_INVAL;
182+
}
183+
184+
// Write name_len at offset 4 (after tag + 3 bytes padding)
185+
if !unsafe { mem.write::<u32>((prestat_ptr + 4) as u32, &name_len) } {
186+
return ERRNO_INVAL;
187+
}
165188

166189
ERRNO_SUCCESS
167190
},
168191
)?;
169192

170193
// fd_prestat_dir_name - Get preopened directory path
171-
linker.func_wrap(
194+
linker.func_wrap_with_memory(
172195
"wasi_snapshot_preview1",
173196
"fd_prestat_dir_name",
174-
|fd: i32, path_ptr: i32, path_len: i32| -> i32 {
197+
|mut caller: Caller<'_, T>, fd: i32, path_ptr: i32, path_len: i32| -> i32 {
175198
tracing::debug!("[WASM FS] fd_prestat_dir_name(fd={}, ptr={}, len={})",
176199
fd, path_ptr, path_len);
177200

@@ -180,36 +203,60 @@ pub fn register<T>(linker: &mut Linker<T>) -> crate::Result<()> {
180203
return ERRNO_BADF;
181204
}
182205

183-
if path_len < 1 {
206+
if path_len < 1 || path_ptr < 0 {
184207
return ERRNO_INVAL;
185208
}
186209

187-
// TODO: Write "/" to the buffer at path_ptr
210+
// Get memory accessor
211+
let mem = match MemoryAccessor::new(&mut caller) {
212+
Some(m) => m,
213+
None => return ERRNO_INVAL,
214+
};
215+
216+
// Write "/" to the buffer
217+
let path = b"/";
218+
if !mem.write_bytes(path_ptr as u32, path) {
219+
return ERRNO_INVAL;
220+
}
188221

189222
ERRNO_SUCCESS
190223
},
191224
)?;
192225

193226
// path_open - Open a file or directory
194-
linker.func_wrap(
227+
linker.func_wrap_with_memory(
195228
"wasi_snapshot_preview1",
196229
"path_open",
197-
|dirfd: i32,
230+
|mut caller: Caller<'_, T>,
231+
dirfd: i32,
198232
_dirflags: i32,
199233
_path_ptr: i32,
200234
_path_len: i32,
201235
oflags: i32,
202236
_fs_rights_base: i64,
203237
_fs_rights_inheriting: i64,
204238
_fdflags: i32,
205-
_fd_ptr: i32| -> i32 {
239+
fd_ptr: i32| -> i32 {
206240
tracing::debug!("[WASM FS] path_open(dirfd={}, path_ptr={}, path_len={}, oflags={})",
207241
dirfd, _path_ptr, _path_len, oflags);
208242

243+
if fd_ptr < 0 {
244+
return ERRNO_INVAL;
245+
}
246+
247+
// Get memory accessor
248+
let mem = match MemoryAccessor::new(&mut caller) {
249+
Some(m) => m,
250+
None => return ERRNO_INVAL,
251+
};
252+
209253
// Generate a new file descriptor
210254
let new_fd = NEXT_FD.fetch_add(1, Ordering::SeqCst);
211255

212-
// TODO: Write the new fd to memory at fd_ptr
256+
// Write the new fd to memory at fd_ptr
257+
if !unsafe { mem.write::<i32>(fd_ptr as u32, &new_fd) } {
258+
return ERRNO_INVAL;
259+
}
213260

214261
tracing::debug!("[WASM FS] path_open: returned fd={}", new_fd);
215262
ERRNO_SUCCESS

kernel/src/wasm/host_funcs/io.rs

Lines changed: 97 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
//! I/O host functions for WebAssembly modules
99
1010
use crate::wasm::Linker;
11+
use crate::wasm::func::Caller;
12+
use super::mem_access::MemoryAccessor;
13+
use alloc::string::String;
1114

1215
/// IoVec structure for scatter-gather I/O
1316
#[repr(C)]
@@ -32,10 +35,10 @@ pub const ERRNO_NOSYS: i32 = 52; // Function not implemented
3235

3336
pub fn register<T>(linker: &mut Linker<T>) -> crate::Result<()> {
3437
// fd_write - Write to a file descriptor
35-
linker.func_wrap(
38+
linker.func_wrap_with_memory(
3639
"wasi_snapshot_preview1",
3740
"fd_write",
38-
|fd: i32, iovs_ptr: i32, iovs_len: i32, nwritten_ptr: i32| -> i32 {
41+
|mut caller: Caller<'_, T>, fd: i32, iovs_ptr: i32, iovs_len: i32, nwritten_ptr: i32| -> i32 {
3942
// Validate parameters first
4043
if iovs_ptr < 0 || iovs_len < 0 || nwritten_ptr < 0 {
4144
return ERRNO_INVAL;
@@ -44,27 +47,60 @@ pub fn register<T>(linker: &mut Linker<T>) -> crate::Result<()> {
4447
// Check if fd is valid: stdout (1), stderr (2), or reasonable file descriptors (3-63)
4548
// Invalid: stdin (0), negative values, or unreasonably high values
4649
if fd == FD_STDOUT || fd == FD_STDERR || (fd >= 3 && fd < 64) {
47-
// Valid fd - log and proceed
48-
if fd == FD_STDOUT || fd == FD_STDERR {
49-
let prefix = if fd == FD_STDOUT { "stdout" } else { "stderr" };
50-
tracing::debug!("[WASM {}] fd_write called with {} iovecs", prefix, iovs_len);
50+
// Get memory accessor
51+
let mem = match MemoryAccessor::new(&mut caller) {
52+
Some(m) => m,
53+
None => return ERRNO_INVAL,
54+
};
55+
56+
let mut total_written = 0u32;
57+
58+
// Read IoVec array from WASM memory
59+
for i in 0..iovs_len {
60+
let iov_offset = iovs_ptr as u32 + (i as u32 * 8); // Each IoVec is 8 bytes
5161

52-
// For demonstration, log that we would output data
53-
// In a real implementation with memory access, we would read the IoVecs
54-
// and output the actual data
55-
tracing::info!("[WASM {}] Writing {} iovecs", prefix, iovs_len);
56-
} else {
57-
tracing::debug!("[WASM] fd_write called for fd={} with {} iovecs", fd, iovs_len);
62+
// Read IoVec structure
63+
let buf_ptr = match unsafe { mem.read::<u32>(iov_offset) } {
64+
Some(ptr) => ptr,
65+
None => return ERRNO_INVAL,
66+
};
67+
68+
let buf_len = match unsafe { mem.read::<u32>(iov_offset + 4) } {
69+
Some(len) => len,
70+
None => return ERRNO_INVAL,
71+
};
72+
73+
// Read actual data from WASM memory
74+
if buf_len > 0 {
75+
let data = match mem.read_bytes(buf_ptr, buf_len) {
76+
Some(d) => d,
77+
None => return ERRNO_INVAL,
78+
};
79+
80+
// Output to console based on fd
81+
if fd == FD_STDOUT || fd == FD_STDERR {
82+
let output = String::from_utf8_lossy(&data);
83+
// Use distinct targets and levels to differentiate streams
84+
if fd == FD_STDOUT {
85+
tracing::info!(target: "wasi::stdout", "{}", output);
86+
} else {
87+
tracing::error!(target: "wasi::stderr", "{}", output);
88+
}
89+
} else {
90+
// For file descriptors, just count bytes (stub)
91+
tracing::debug!("[WASM] fd_write to fd={}: {} bytes", fd, buf_len);
92+
}
93+
94+
total_written += buf_len;
95+
}
5896
}
5997

60-
// TODO: When we have proper memory access:
61-
// 1. Read IoVec array from WASM memory at iovs_ptr
62-
// 2. For each IoVec, read the actual data buffer
63-
// 3. Output the data to console or file
64-
// 4. Write total bytes written to nwritten_ptr
65-
66-
// For now, return success
67-
ERRNO_SUCCESS
98+
// Write bytes written count to nwritten_ptr
99+
if unsafe { mem.write::<u32>(nwritten_ptr as u32, &total_written) } {
100+
ERRNO_SUCCESS
101+
} else {
102+
ERRNO_INVAL
103+
}
68104
} else {
69105
// Invalid fd (includes stdin and any other invalid values)
70106
ERRNO_BADF
@@ -73,10 +109,10 @@ pub fn register<T>(linker: &mut Linker<T>) -> crate::Result<()> {
73109
)?;
74110

75111
// fd_read - Read from a file descriptor
76-
linker.func_wrap(
112+
linker.func_wrap_with_memory(
77113
"wasi_snapshot_preview1",
78114
"fd_read",
79-
|fd: i32, iovs_ptr: i32, iovs_len: i32, nread_ptr: i32| -> i32 {
115+
|mut caller: Caller<'_, T>, fd: i32, iovs_ptr: i32, iovs_len: i32, nread_ptr: i32| -> i32 {
80116
// Can't read from stdout/stderr
81117
if fd == FD_STDOUT || fd == FD_STDERR {
82118
return ERRNO_BADF;
@@ -92,17 +128,27 @@ pub fn register<T>(linker: &mut Linker<T>) -> crate::Result<()> {
92128
return ERRNO_INVAL;
93129
}
94130

95-
// TODO: Implement actual reading from console
96-
// For now, return 0 bytes read (EOF)
131+
// Get memory accessor
132+
let mem = match MemoryAccessor::new(&mut caller) {
133+
Some(m) => m,
134+
None => return ERRNO_INVAL,
135+
};
136+
137+
// For stub implementation, always return EOF (0 bytes read)
138+
let total_read = 0u32;
139+
97140
if fd == FD_STDIN {
98-
tracing::debug!("[WASM stdin] fd_read called");
141+
tracing::debug!("[WASM stdin] fd_read called, returning EOF");
99142
} else {
100-
tracing::debug!("[WASM] fd_read called for fd={}", fd);
143+
tracing::debug!("[WASM] fd_read called for fd={}, returning EOF", fd);
144+
}
145+
146+
// Write 0 to nread_ptr to indicate EOF
147+
if unsafe { mem.write::<u32>(nread_ptr as u32, &total_read) } {
148+
ERRNO_SUCCESS
149+
} else {
150+
ERRNO_INVAL
101151
}
102-
103-
// TODO: Write 0 to nread_ptr to indicate EOF
104-
// For now, just return success
105-
ERRNO_SUCCESS
106152
},
107153
)?;
108154

@@ -124,15 +170,31 @@ pub fn register<T>(linker: &mut Linker<T>) -> crate::Result<()> {
124170
})?;
125171

126172
// fd_seek - Seek in a file
127-
linker.func_wrap(
173+
linker.func_wrap_with_memory(
128174
"wasi_snapshot_preview1",
129175
"fd_seek",
130-
|fd: i32, offset: i64, whence: i32, _newoffset_ptr: i32| -> i32 {
176+
|mut caller: Caller<'_, T>, fd: i32, offset: i64, whence: i32, newoffset_ptr: i32| -> i32 {
131177
tracing::debug!("[WASM] fd_seek({}, {}, {})", fd, offset, whence);
132178

133-
// For stub, just return success
134-
// TODO: Write new position to newoffset_ptr
135-
ERRNO_SUCCESS
179+
if newoffset_ptr < 0 {
180+
return ERRNO_INVAL;
181+
}
182+
183+
// Get memory accessor
184+
let mem = match MemoryAccessor::new(&mut caller) {
185+
Some(m) => m,
186+
None => return ERRNO_INVAL,
187+
};
188+
189+
// For stub, just return the requested offset as new position
190+
let new_offset = offset as u64;
191+
192+
// Write new position to newoffset_ptr
193+
if unsafe { mem.write::<u64>(newoffset_ptr as u32, &new_offset) } {
194+
ERRNO_SUCCESS
195+
} else {
196+
ERRNO_INVAL
197+
}
136198
},
137199
)?;
138200

kernel/src/wasm/host_funcs/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub mod memory;
1818
pub mod process;
1919
pub mod time;
2020

21-
use crate::wasm::{Linker, Store};
21+
use crate::wasm::Linker;
2222

2323
/// Register all standard host functions with the provided linker.
2424
///

kernel/src/wasm/linker.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,25 @@ impl<T> Linker<T> {
229229
Ok(self)
230230
}
231231

232+
/// Register a host function that needs access to memory.
233+
/// This is specifically for functions that take `Caller` as their first parameter.
234+
/// The function signature should be: `Fn(Caller<'_, T>, args...) -> result`
235+
pub fn func_wrap_with_memory<Params, Results>(
236+
&mut self,
237+
module: &str,
238+
name: &str,
239+
func: impl IntoFunc<T, Params, Results>,
240+
) -> crate::Result<&mut Self>
241+
{
242+
let (func, ty) = HostFunc::wrap(self.engine(), func);
243+
244+
let key = self.import_key(module, Some(name));
245+
self.insert(key, Definition::HostFunc(Arc::new(func), ty.type_index()))?;
246+
247+
Ok(self)
248+
}
249+
250+
232251
fn insert(&mut self, key: ImportKey, item: Definition) -> crate::Result<()> {
233252
match self.map.entry(key) {
234253
Entry::Occupied(_) => {

tests/test_wasi_io.wast

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -106,18 +106,14 @@
106106

107107
;; Test assertions
108108
(assert_return (invoke "test_stdout_write") (i32.const 0)) ;; Should succeed
109-
;; Note: Without memory access, nwritten won't be set correctly
110-
;; (assert_return (invoke "check_nwritten") (i32.const 12)) ;; Would write 12 bytes if memory access worked
109+
(assert_return (invoke "check_nwritten") (i32.const 12)) ;; Should write 12 bytes
111110

112111
(assert_return (invoke "test_prestat_get") (i32.const 0)) ;; Should succeed
113-
;; Note: Without memory access, prestat won't be written
114-
;; (assert_return (invoke "check_prestat_tag") (i32.const 0)) ;; Would be 0 (DIR) if memory access worked
115-
;; (assert_return (invoke "check_prestat_namelen") (i32.const 1)) ;; Would be 1 if memory access worked
112+
(assert_return (invoke "check_prestat_tag") (i32.const 0)) ;; Tag should be 0 (DIR)
113+
(assert_return (invoke "check_prestat_namelen") (i32.const 1)) ;; Name length should be 1
116114

117115
(assert_return (invoke "test_prestat_dir_name") (i32.const 0)) ;; Should succeed
118-
;; Note: Without memory access, dir name won't be written
119-
;; (assert_return (invoke "check_dirname_byte") (i32.const 47)) ;; Would be '/' if memory access worked
116+
(assert_return (invoke "check_dirname_byte") (i32.const 47)) ;; Should be '/' (ASCII 47)
120117

121118
(assert_return (invoke "test_path_open") (i32.const 0)) ;; Should succeed
122-
;; Note: Without memory access, fd won't be written
123-
;; Can't assert exact fd value, but it would be >= 4 if memory access worked
119+
;; Can't assert exact fd value, but it should be >= 4

0 commit comments

Comments
 (0)