Skip to content

Commit f4d643c

Browse files
committed
fix(vfs): build WASI backend on stable Rust
Replace the unstable wasi_ext positioned I/O (fd_pread/fd_pwrite via std::os::wasi::fs::FileExt) with stable Seek + Read/Write under the file's Mutex. The mutex serialises each seek/transfer pair so the shared cursor is never observed torn. Replace the raw wasi::fd_sync directory-sync syscall with File::sync_all; runtimes that do not support syncing a directory fd are treated as a no-op (best-effort durability), matching the previous behaviour. This removes the wasi = "0.13" dependency entirely — wasm32-wasip1 now compiles on stable Rust without any nightly features.
1 parent 60ba03e commit f4d643c

3 files changed

Lines changed: 42 additions & 78 deletions

File tree

Cargo.lock

Lines changed: 3 additions & 31 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,6 @@ dispatch2 = "0.3"
5858
block2 = "0.6"
5959
libc = "0.2"
6060

61-
[target.'cfg(target_os = "wasi")'.dependencies]
62-
wasi = "0.13"
63-
6461
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
6562
tempfile = "3"
6663
memmap2 = "0.9"

src/vfs/wasi.rs

Lines changed: 39 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,18 @@
1919
//! # Directory sync
2020
//!
2121
//! WASI preview1 does not have a dedicated directory-sync syscall. `sync_dir`
22-
//! opens the directory as a file descriptor and calls `fd_sync`. Some sandboxed
23-
//! runtimes stub `fd_sync` out with `ENOTSUP`; that error is treated as a
24-
//! no-op (best-effort durability, with a `tracing::debug!` trace emitted so the
25-
//! caller has observability).
22+
//! opens the directory as a file descriptor and calls `sync_all` on it. Some
23+
//! sandboxed runtimes do not support syncing a directory fd and return an
24+
//! error; that is treated as a no-op (best-effort durability, with a
25+
//! `tracing::debug!` trace emitted so the caller has observability).
26+
//!
27+
//! # Positioned I/O
28+
//!
29+
//! Positioned reads and writes use the stable `Seek` + `Read` / `Write` traits
30+
//! under the file's `Mutex`: each operation seeks to the target offset and then
31+
//! transfers. The mutex serialises the seek/transfer pair, so the shared file
32+
//! cursor is never observed in a torn state. This keeps the backend on stable
33+
//! Rust (no `wasi_ext` / preview1 raw-syscall dependency).
2634
2735
// ── Real WASI implementation ──────────────────────────────────────────────────
2836

@@ -139,8 +147,9 @@ mod real {
139147
/// Handle to an open file on a WASI target.
140148
///
141149
/// Uses `std::fs::File`, which maps directly onto WASI preview1 `fd_*`
142-
/// syscalls. Positional reads and writes loop over `std::os::wasi::fs::FileExt`
143-
/// `read_at` / `write_at` which correspond to `fd_pread` / `fd_pwrite`.
150+
/// syscalls. Positional reads and writes seek the shared cursor under the
151+
/// `Mutex` and then transfer, which corresponds to `fd_seek` + `fd_read` /
152+
/// `fd_write`.
144153
pub struct WasiFile {
145154
/// Mutex so that `read_at(&self, …)` can seek without requiring `&mut`.
146155
/// On WASI every seek+read pair is already synchronous so this adds no
@@ -247,39 +256,26 @@ mod real {
247256
///
248257
/// WASI preview1 has no dedicated directory-sync syscall. This
249258
/// implementation opens the directory as a file descriptor and calls
250-
/// `wasi::fd_sync` on it. Runtimes that stub out `fd_sync` with
251-
/// `ENOTSUP` / `ENOSYS` are treated as a no-op so that pagedb still
259+
/// `sync_all` on it. Runtimes that do not support syncing a directory
260+
/// fd return an error, which is treated as a no-op so that pagedb still
252261
/// operates in those environments; a `tracing::debug!` message is
253262
/// emitted for observability.
254263
async fn sync_dir(&self, path: &str) -> Result<()> {
255-
use std::os::wasi::io::AsRawFd;
256-
257264
let p = self.resolve(path);
258265
let dir = match std::fs::File::open(&p) {
259266
Ok(d) => d,
260267
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()),
261268
Err(e) => return Err(PagedbError::Io(e)),
262269
};
263270

264-
// SAFETY: `dir.as_raw_fd()` is a valid, open file descriptor for
265-
// the duration of this call. `wasi::fd_sync` is a pure I/O syscall
266-
// that does not move or alias memory.
267-
let rc = unsafe { wasi::fd_sync(dir.as_raw_fd()) };
268-
match rc {
269-
Ok(()) => Ok(()),
270-
Err(e) if e == wasi::ERRNO_NOTSUP || e == wasi::ERRNO_NOSYS => {
271-
tracing::debug!(
272-
path = %p.display(),
273-
"sync_dir: fd_sync returned {:?}; treating as no-op \
274-
(runtime does not support directory sync)",
275-
e
276-
);
277-
Ok(())
278-
}
279-
Err(e) => Err(PagedbError::Io(std::io::Error::from_raw_os_error(
280-
e.raw() as i32
281-
))),
271+
if let Err(e) = dir.sync_all() {
272+
tracing::debug!(
273+
path = %p.display(),
274+
error = %e,
275+
"sync_dir: directory sync unsupported; treating as no-op"
276+
);
282277
}
278+
Ok(())
283279
}
284280

285281
async fn lock_exclusive(&self, path: &str) -> Result<Self::LockHandle> {
@@ -321,11 +317,11 @@ mod real {
321317

322318
impl VfsFile for WasiFile {
323319
async fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
324-
use std::os::wasi::fs::FileExt;
325-
let f = self.inner.lock();
320+
let mut f = self.inner.lock();
321+
f.seek(SeekFrom::Start(offset)).map_err(PagedbError::Io)?;
326322
let mut total = 0usize;
327323
while total < buf.len() {
328-
match f.read_at(&mut buf[total..], offset + total as u64) {
324+
match f.read(&mut buf[total..]) {
329325
Ok(0) => break,
330326
Ok(n) => total += n,
331327
Err(e) => return Err(PagedbError::Io(e)),
@@ -335,15 +331,13 @@ mod real {
335331
}
336332

337333
async fn read_at_vectored(&self, reqs: &mut [ReadReq<'_>]) -> Result<()> {
338-
use std::os::wasi::fs::FileExt;
339-
let f = self.inner.lock();
334+
let mut f = self.inner.lock();
340335
for req in reqs.iter_mut() {
336+
f.seek(SeekFrom::Start(req.offset))
337+
.map_err(PagedbError::Io)?;
341338
let mut total = 0usize;
342-
loop {
343-
if total == req.buf.len() {
344-
break;
345-
}
346-
match f.read_at(&mut req.buf[total..], req.offset + total as u64) {
339+
while total < req.buf.len() {
340+
match f.read(&mut req.buf[total..]) {
347341
Ok(0) => break,
348342
Ok(n) => total += n,
349343
Err(e) => return Err(PagedbError::Io(e)),
@@ -362,11 +356,11 @@ mod real {
362356
if !self.writable {
363357
return Err(PagedbError::ReadOnly);
364358
}
365-
use std::os::wasi::fs::FileExt;
366-
let f = self.inner.lock();
359+
let mut f = self.inner.lock();
360+
f.seek(SeekFrom::Start(offset)).map_err(PagedbError::Io)?;
367361
let mut total = 0usize;
368362
while total < buf.len() {
369-
match f.write_at(&buf[total..], offset + total as u64) {
363+
match f.write(&buf[total..]) {
370364
Ok(0) => {
371365
return Err(PagedbError::Io(std::io::Error::from(
372366
std::io::ErrorKind::WriteZero,
@@ -383,12 +377,13 @@ mod real {
383377
if !self.writable {
384378
return Err(PagedbError::ReadOnly);
385379
}
386-
use std::os::wasi::fs::FileExt;
387-
let f = self.inner.lock();
380+
let mut f = self.inner.lock();
388381
for req in reqs {
382+
f.seek(SeekFrom::Start(req.offset))
383+
.map_err(PagedbError::Io)?;
389384
let mut total = 0usize;
390385
while total < req.buf.len() {
391-
match f.write_at(&req.buf[total..], req.offset + total as u64) {
386+
match f.write(&req.buf[total..]) {
392387
Ok(0) => {
393388
return Err(PagedbError::Io(std::io::Error::from(
394389
std::io::ErrorKind::WriteZero,

0 commit comments

Comments
 (0)