From 92ab2449e4d692c1c282d0069d895bbc71490b5c Mon Sep 17 00:00:00 2001 From: kilyanni Date: Fri, 17 Jul 2026 18:03:39 +0200 Subject: [PATCH] pkgs: package rustfs (distributed S3 object store) to wasix Cross-compile rustfs (github.com/rustfs/rustfs, a MinIO-class erasure-coded S3 store, ~1070-crate lock incl. datafusion) to wasm32-wasmer-wasi and run its CLI under wasmer. Built with -p rustfs, no-default-features (drops the deeply-unix ftps/webdav servers), and a fast release profile override: the stock opt3+thin-LTO+cgu1 whole-program link on a ~190MB wasm froze for 1h+. Source mods (patches applied post-vendor so .rs edits don't re-vendor): - rustfs-manifest.patch: switch the whole rustls stack off native aws-lc-rs onto pure-Rust ring (AWS SDK, google-cloud, reqwest, vaultrs, lapin, mysql, nats, mqtt, otlp, libunftp, jsonwebtoken); drop mimalloc; stub tokio signal. - rustfs-code.patch: wasi arms for the storage core (rustfs is a filesystem: os get_info/same_disk/device-ids, O_DIRECT, statvfs, MetadataExt, device major/minor), mimalloc FFI gating, ring provider call sites, a 5GiB usize overflow on wasm32. Dependency crate-patches (stock upstream, applied by the vendor-patch hook): tokio (net/fs guard + I/O-driver ENOTSUP), getrandom, netif, rustls-native-certs, symlink, crc-fast, clocksource, async-rs, tonic, tokio-postgres, deadpool-postgres, google-cloud-auth, rustfs-kafka, and the two runtime fixes below. Runtime blockers found by the smoke tests and fixed at the source quirk: - wasite: whoami read the OS username via the WASI preview-2 wasi:cli environment interface, which a preview1 host cannot import; read std::env. - parking_lot_core: its atomic wasm parker was gated behind the cargo `nightly` feature, so wasix (which has atomics) fell to a panic stub; select wasm_atomic.rs on wasi. - tokio I/O driver: wasmer's poll_oneoff returns ENOTSUP (not EINVAL) for an empty subscription set, which tokio's wasi arm did not catch; accept it too. wasmer root cause tracked in WASIX-TODO.md. Tests: two mkWasixRun smoke tests (rustfs --version / --help) run the wasm under wasmer and pass cleanly (correct output, exit 0, no panics). server/info are not yet tested (server needs mio's unimplemented wasip1 Waker). Co-Authored-By: Claude Opus 4.8 (1M context) --- WASIX-TODO.md | 108 +++ flake.nix | 10 + .../wasmer-fd-sync-rights-durability.patch | 34 + patches/wasmer-futex-wake-lost-wakeup.patch | 92 ++ pkgs/lib/wasix-crate-patches/README.md | 10 + .../wasix-crate-patches/async-rs/0.8.11.patch | 41 + .../wasix-crate-patches/async-rs/edits.nix | 3 + .../clocksource/0.8.3.patch | 67 ++ .../wasix-crate-patches/clocksource/edits.nix | 3 + .../wasix-crate-patches/crc-fast/1.10.0.patch | 11 + .../wasix-crate-patches/crc-fast/edits.nix | 3 + .../deadpool-postgres/0.14.1.patch | 75 ++ .../deadpool-postgres/edits.nix | 3 + .../wasix-crate-patches/getrandom/0.4.3.patch | 11 + .../google-cloud-auth/1.14.0.patch | 35 + .../google-cloud-auth/edits.nix | 3 + .../lib/wasix-crate-patches/netif/0.1.6.patch | 42 + pkgs/lib/wasix-crate-patches/netif/edits.nix | 3 + .../parking_lot_core/0.9.12.patch | 27 + .../parking_lot_core/edits.nix | 3 + .../rustfs-kafka/1.2.0.patch | 11 + .../rustfs-kafka/edits.nix | 3 + .../rustls-native-certs/0.8.4.patch | 20 + .../wasix-crate-patches/symlink/0.1.0.patch | 82 ++ .../lib/wasix-crate-patches/symlink/edits.nix | 3 + .../tokio-postgres/0.7.18.patch | 146 +++ .../tokio-postgres/edits.nix | 3 + .../wasix-crate-patches/tokio/1.52.3.patch | 56 +- pkgs/lib/wasix-crate-patches/tokio/edits.nix | 2 +- .../wasix-crate-patches/tonic/0.14.6.patch | 29 + pkgs/lib/wasix-crate-patches/tonic/edits.nix | 3 + .../wasix-crate-patches/wasite/1.0.2.patch | 29 + pkgs/lib/wasix-crate-patches/wasite/edits.nix | 3 + pkgs/overlay/packages/rustfs/package.nix | 70 ++ .../packages/rustfs/patches/rustfs-code.patch | 512 ++++++++++ .../rustfs/patches/rustfs-manifest.patch | 886 ++++++++++++++++++ pkgs/overlay/packages/rustfs/tests/basic.nix | 24 + .../packages/rustfs/tests/s3-advanced.nix | 151 +++ .../packages/rustfs/tests/s3-roundtrip.nix | 65 ++ 39 files changed, 2661 insertions(+), 21 deletions(-) create mode 100644 patches/wasmer-fd-sync-rights-durability.patch create mode 100644 patches/wasmer-futex-wake-lost-wakeup.patch create mode 100644 pkgs/lib/wasix-crate-patches/async-rs/0.8.11.patch create mode 100644 pkgs/lib/wasix-crate-patches/async-rs/edits.nix create mode 100644 pkgs/lib/wasix-crate-patches/clocksource/0.8.3.patch create mode 100644 pkgs/lib/wasix-crate-patches/clocksource/edits.nix create mode 100644 pkgs/lib/wasix-crate-patches/crc-fast/1.10.0.patch create mode 100644 pkgs/lib/wasix-crate-patches/crc-fast/edits.nix create mode 100644 pkgs/lib/wasix-crate-patches/deadpool-postgres/0.14.1.patch create mode 100644 pkgs/lib/wasix-crate-patches/deadpool-postgres/edits.nix create mode 100644 pkgs/lib/wasix-crate-patches/getrandom/0.4.3.patch create mode 100644 pkgs/lib/wasix-crate-patches/google-cloud-auth/1.14.0.patch create mode 100644 pkgs/lib/wasix-crate-patches/google-cloud-auth/edits.nix create mode 100644 pkgs/lib/wasix-crate-patches/netif/0.1.6.patch create mode 100644 pkgs/lib/wasix-crate-patches/netif/edits.nix create mode 100644 pkgs/lib/wasix-crate-patches/parking_lot_core/0.9.12.patch create mode 100644 pkgs/lib/wasix-crate-patches/parking_lot_core/edits.nix create mode 100644 pkgs/lib/wasix-crate-patches/rustfs-kafka/1.2.0.patch create mode 100644 pkgs/lib/wasix-crate-patches/rustfs-kafka/edits.nix create mode 100644 pkgs/lib/wasix-crate-patches/rustls-native-certs/0.8.4.patch create mode 100644 pkgs/lib/wasix-crate-patches/symlink/0.1.0.patch create mode 100644 pkgs/lib/wasix-crate-patches/symlink/edits.nix create mode 100644 pkgs/lib/wasix-crate-patches/tokio-postgres/0.7.18.patch create mode 100644 pkgs/lib/wasix-crate-patches/tokio-postgres/edits.nix create mode 100644 pkgs/lib/wasix-crate-patches/tonic/0.14.6.patch create mode 100644 pkgs/lib/wasix-crate-patches/tonic/edits.nix create mode 100644 pkgs/lib/wasix-crate-patches/wasite/1.0.2.patch create mode 100644 pkgs/lib/wasix-crate-patches/wasite/edits.nix create mode 100644 pkgs/overlay/packages/rustfs/package.nix create mode 100644 pkgs/overlay/packages/rustfs/patches/rustfs-code.patch create mode 100644 pkgs/overlay/packages/rustfs/patches/rustfs-manifest.patch create mode 100644 pkgs/overlay/packages/rustfs/tests/basic.nix create mode 100644 pkgs/overlay/packages/rustfs/tests/s3-advanced.nix create mode 100644 pkgs/overlay/packages/rustfs/tests/s3-roundtrip.nix diff --git a/WASIX-TODO.md b/WASIX-TODO.md index 876e4f5d..5ba2039c 100644 --- a/WASIX-TODO.md +++ b/WASIX-TODO.md @@ -54,6 +54,114 @@ Status: 🔴 needs upstream fix · 🟡 workaround in place · 🟢 fixed. - Fix: wasmer's signal delivery must cancel in-flight blocking syscalls (EINTR or instance termination). +### tokio's I/O driver panics on an unexpected park errno 🟡 + +- tokio's I/O driver parks in `mio::Poll::poll` and `panic!`s ("unexpected error + when polling the I/O driver") on any errno it does not explicitly tolerate. + Upstream tolerates only `Interrupted` and, on wasi, `InvalidInput`, the errno + the reference runtime returns for a poll with zero subscriptions + (`runtime/io/driver.rs`, arm commented "poll without subscriptions"). Under + panic=abort a park panic takes the process down. +- Workaround (`tokio/1.52.3.patch`): also tolerate `Unsupported` (ENOTSUP, + errno 58) in that arm, treating it like the empty-poll case. +- The ENOTSUP was originally attributed to wasmer's `poll_oneoff` rejecting a + zero-length subscription set. That attribution does not hold against the + pinned wasmer: upstream fixed the empty-subscription case in January 2026 + (commit `350a7306`, "handle empty subscription list in poll_oneoff", with a + test), and `poll_oneoff` now returns `Errno::Inval` there, via both the early + return and `validate_poll_subscriptions_count`. Neither `poll_oneoff` nor + `epoll_wait` returns `Errno::Notsup` on any path in the pinned tree, and on + wasmer our mio uses the epoll backend (`mio/1.2.2.patch`), not `poll_oneoff`. +- So the tolerance is kept as cheap insurance, not as a workaround for a known + live wasmer bug: it converts a would-be abort into the same graceful park + return tokio already performs for the analogous `InvalidInput`. Drop it if the + panic never reappears; reopen this entry with a real trace if it does. + +### `futex_wake` lost a wakeup when a waiter was mid-registration 🟢 + +- wasmer's `futex_wake` woke "the first waiter" by taking the first entry of the + futex's `wakers` map and calling its stored `Waker`. But a thread that has + entered `futex_wait` and inserted its id with `None` (waker not installed yet) + sits in that map as `Some(None)`; `futex_wake` consumed that slot, woke nobody, + and skipped a truly sleeping waiter that still held its installed waker. A + tokio worker parked on the futex then never woke: `tokio::spawn` + a task doing + a WASI syscall + `join_all` hung (reproduced minimally; also stalled the rustfs + erasure-init metadata-read fanout, blocking `rustfs server` startup entirely). +- Fix (`patches/wasmer-futex-wake-lost-wakeup.patch`): wake the first waiter that + has an installed waker; if none is installed yet, increment a `pending_wakes` + counter on the futex that the next `futex_wait` consumes before parking, so the + wake survives the register race. Upstreamable to wasmer. +- A token is scoped to the waiters it was meant for: only recorded while `wakers` + is non-empty, only claimable by a poller that still holds its slot, and dropped + with the futex once the last waiter leaves. Without those three bounds a token + can outlive its waiters, and since a wake then finds no sleeper in the empty + map it records another one, so the counter grows unbounded and the futex entry + is never reclaimed. The slot check also stops a poller that `futex_wake` has + already claimed from swallowing a token owed to a waiter still asleep. + +### tokio's I/O driver compiles out the mio `Waker` on wasi 🟡 + +- tokio gates its `mio::Waker` (the handle that wakes the reactor parked in + `epoll_wait` from another thread) behind `#[cfg(not(target_os = "wasi"))]`, + because stock mio's wasip1 backend has no `Waker`. Our vendored mio backend + (`mio/1.2.2.patch`, the wasix-org wasi backend) does implement an eventfd + `Waker`, but tokio still skips it, so any completion that must re-schedule a + task through the I/O driver (e.g. `tokio::fs`, which rustfs uses for + `access()`) parks the reactor forever with nothing able to wake it. +- Workaround (`tokio/1.52.3.patch`): widen those four gates to + `any(not(target_os = "wasi"), all(target_vendor = "wasmer", tokio_wasix_waker))`, + and have the consumer opt in with `RUSTFLAGS = "--cfg tokio_wasix_waker"` + (rustfs does). With the futex_wake fix above, a spawned `tokio::fs` task + + `join_all` completes and rustfs storage init reaches "store ready". +- The opt-in is load-bearing, not caution: the Waker is only sound with a mio that + has the wasix backend. Enabling it for all of `target_vendor = "wasmer"` broke + primp, which pins mio 1.2.0 (`cannot find type Waker in crate mio` -> tokio + fails to compile). mio only exports `Waker` off-wasi, and its 1.2.0 wasi arm is + the `single_threaded` `AtomicBool` waker, which by its own docs cannot wake an + already-blocked thread. Backporting the wasix backend to 1.2.0 is not a fix + either: it adds a `wasix` crate dependency, and crate-patches apply post-vendor, + so every consumer's lock would need it. +- Fix: once mio's upstream wasi backend ships a real `Waker` on every version in + play, tokio can relax the blanket `not(wasi)` gate and the opt-in can go. + +### `fd_datasync`/`fd_sync` deny with EACCES under mapped host dirs 🟢 + +- wasmer's `fd_datasync`/`fd_sync` return `Errno::Access` unless the fd holds the + `FD_DATASYNC`/`FD_SYNC` right. But `path_open` computes a file's rights as + `(requested | implied_by_open_mode) & parent_dir.rights_inheriting` + (`path_open2.rs`, whose own TODO notes "WASI isn't giving appropriate rights + here"): the inheriting mask on a `--mapdir`/`--volume` host preopen strips the + implied sync rights, so files opened for writing under it cannot fsync/fdatasync. +- Symptom: rustfs object writes call `fdatasync` for durability; the EACCES maps + (ecstore `to_file_error`: PermissionDenied -> FileAccessDenied) to a 500 + `InternalError "File access denied"` on every `PutObject`/`UploadPart`, while + bucket/metadata ops (no data fsync) succeed. `mc mb` worked, `mc pipe` 500'd. +- Fix (`patches/wasmer-fd-sync-rights-durability.patch`): drop the rights gate in + both syscalls. fsync/fdatasync are durability barriers, not security + capabilities (POSIX needs no special permission); sync any regular-file fd. + With the fix a full `mc` put/get round-trip against `rustfs server` passes. +- Cleaner upstream fix would stop masking implied sync rights in path_open, but + the syscalls should not hard-deny a durability barrier either way. + +### wasix Rust std does not map wasi ENOTEMPTY to `DirectoryNotEmpty` 🟡 + +- wasix std's error decoding returns an uncategorized `io::ErrorKind` (not + `DirectoryNotEmpty`) for wasi `ENOTEMPTY` (errno 55). Rust code that branches on + `err.kind() == ErrorKind::DirectoryNotEmpty` (the common "rmdir of a non-empty + dir is fine" idiom) then falls through to its error path. +- Symptom: rustfs object DELETE (`mc rm`) 500'd with "File access denied". Its + `delete_file` (ecstore `disk/local.rs`) tolerates NotFound + DirectoryNotEmpty on + `remove_dir` and treats anything else as fatal `FileAccessDenied`; the object dir + is legitimately non-empty (the xl versioned-delete rename dance stages old data + in a sub-dir), so on wasix that ENOTEMPTY aborted + rolled back the delete. + Traced: `path_remove_directory -> Errno::notempty` on the object dir. +- Workaround (`rustfs-code.patch`): `delete_file` also tolerates + `err.raw_os_error() == Some(55)` under `cfg(target_os = "wasi")`. +- Fix: the wasix rust-std fork's `decode_error_kind` should map wasi ENOTEMPTY + (and any other missing codes) to the matching `ErrorKind`, so any package keying + on `DirectoryNotEmpty` works without per-crate patches. Broader than rustfs and + touches the toolchain (expensive rebuild), hence the local workaround for now. + ### spawned commands and PATH 🟢 - Fixed in current wasmer: `posix_spawnp` and fork + `execvp` both resolve the diff --git a/flake.nix b/flake.nix index 8a7ac248..b3290b9a 100644 --- a/flake.nix +++ b/flake.nix @@ -37,6 +37,14 @@ ++ [ # proc_fork must inherit the parent's signal dispositions; see WASIX-TODO.md ./patches/wasmer-signal-inherit-on-fork.patch + # futex_wake dropped a wake when the first waiter was still + # mid-registration (Some(None)), starving a genuinely-sleeping waiter; + # deadlocked tokio multi-thread spawn+blocking (e.g. rustfs server). + ./patches/wasmer-futex-wake-lost-wakeup.patch + # fd_datasync/fd_sync denied with EACCES when path_open's rights + # delegation masked the implied FD_DATASYNC/FD_SYNC off files under + # mapped host dirs; rustfs object writes (fdatasync durability) 500'd. + ./patches/wasmer-fd-sync-rights-durability.patch ]; passthru = (old.passthru or {}) @@ -46,6 +54,8 @@ noteVersion = "${old.version}-${wasmer.shortRev or "dirty"}"; updateNotes = [ {message = "check whether patches/wasmer-signal-inherit-on-fork.patch landed upstream (WASIX-TODO.md)";} + {message = "check whether patches/wasmer-futex-wake-lost-wakeup.patch landed upstream (WASIX-TODO.md)";} + {message = "check whether patches/wasmer-fd-sync-rights-durability.patch landed upstream (WASIX-TODO.md)";} ]; }; }; diff --git a/patches/wasmer-fd-sync-rights-durability.patch b/patches/wasmer-fd-sync-rights-durability.patch new file mode 100644 index 00000000..38f5f229 --- /dev/null +++ b/patches/wasmer-fd-sync-rights-durability.patch @@ -0,0 +1,34 @@ +--- a/lib/wasix/src/syscalls/wasi/fd_datasync.rs ++++ b/lib/wasix/src/syscalls/wasi/fd_datasync.rs +@@ -14,9 +14,11 @@ + let env = ctx.data(); + let (_, state) = unsafe { env.get_memory_and_wasi_state(&ctx, 0) }; + let fd_entry = wasi_try_ok!(state.fs.get_fd(fd)); +- if !fd_entry.inner.rights.contains(Rights::FD_DATASYNC) { +- return Ok(Errno::Access); +- } ++ // fdatasync is a durability barrier, not a security capability (POSIX needs no ++ // special permission). path_open's rights delegation masks the implied ++ // FD_DATASYNC off files opened under mapped host dirs (see path_open2.rs TODO ++ // on rights traversal), so gating on it here fails legitimate syncs (rustfs). ++ // Sync any regular-file fd regardless of the right. + + let file = { + let guard = fd_entry.inode.read(); +--- a/lib/wasix/src/syscalls/wasi/fd_sync.rs ++++ b/lib/wasix/src/syscalls/wasi/fd_sync.rs +@@ -17,9 +17,11 @@ + let env = ctx.data(); + let (_, mut state) = unsafe { env.get_memory_and_wasi_state(&ctx, 0) }; + let fd_entry = wasi_try_ok!(state.fs.get_fd(fd)); +- if !fd_entry.inner.rights.contains(Rights::FD_SYNC) { +- return Ok(Errno::Access); +- } ++ // fsync is a durability barrier, not a security capability (POSIX needs no ++ // special permission). path_open's rights delegation masks the implied ++ // FD_SYNC off files opened under mapped host dirs (see path_open2.rs TODO on ++ // rights traversal), so gating on it here fails legitimate syncs (rustfs). ++ // Sync any regular-file fd regardless of the right. + let inode = fd_entry.inode; + + // TODO: implement this for more than files diff --git a/patches/wasmer-futex-wake-lost-wakeup.patch b/patches/wasmer-futex-wake-lost-wakeup.patch new file mode 100644 index 00000000..fa2a8be2 --- /dev/null +++ b/patches/wasmer-futex-wake-lost-wakeup.patch @@ -0,0 +1,92 @@ +--- a/lib/wasix/src/state/mod.rs ++++ b/lib/wasix/src/state/mod.rs +@@ -76,6 +76,13 @@ + #[derive(Debug, Default)] + pub struct WasiFutex { + pub(crate) wakers: BTreeMap>, ++ /// Wakes that arrived while every registered waiter was still ++ /// mid-registration (slot reserved but `Waker` not yet installed by the ++ /// first poll). A waiter's first poll consumes one so a wake landing in the ++ /// registration gap is not lost (see `futex_wake`). Only ever non-zero while ++ /// `wakers` is non-empty: a token outlives neither its waiters nor this ++ /// futex, which is what keeps it from accumulating. ++ pub(crate) pending_wakes: u64, + } + + /// Structure that holds the state of BUS calls to this process and from +--- a/lib/wasix/src/syscalls/wasix/futex_wait.rs ++++ b/lib/wasix/src/syscalls/wasix/futex_wait.rs +@@ -22,13 +22,29 @@ + Some(f) => f, + None => return Poll::Ready(true), + }; +- let waker = match futex.wakers.get_mut(&self.poller_idx) { +- Some(w) => w, +- None => return Poll::Ready(true), +- }; ++ // If our slot is gone then `futex_wake` already claimed us and we are ++ // woken. Checked before `pending_wakes` so an already-woken poller cannot ++ // swallow a token meant for a waiter that is still asleep. ++ if !futex.wakers.contains_key(&self.poller_idx) { ++ return Poll::Ready(true); ++ } ++ // A wake that arrived while we were still mid-registration left a ++ // pending token; consume it and report woken instead of sleeping ++ // through it. ++ if futex.pending_wakes > 0 { ++ futex.pending_wakes -= 1; ++ futex.wakers.remove(&self.poller_idx); ++ // An unclaimed token must not outlive the waiters it was meant for. ++ if futex.wakers.is_empty() { ++ guard.futexes.remove(&self.futex_idx); ++ } ++ return Poll::Ready(true); ++ } + + // Register the waker +- waker.replace(cx.waker().clone()); ++ if let Some(waker) = futex.wakers.get_mut(&self.poller_idx) { ++ waker.replace(cx.waker().clone()); ++ } + + // Check for timeout + drop(guard); +--- a/lib/wasix/src/syscalls/wasix/futex_wake.rs ++++ b/lib/wasix/src/syscalls/wasix/futex_wake.rs +@@ -27,12 +27,31 @@ + let woken = { + let mut guard = state.futexs.lock().unwrap(); + if let Some(futex) = guard.futexes.get_mut(&pointer) { +- let first = futex.wakers.keys().copied().next(); +- if let Some(id) = first +- && let Some(Some(w)) = futex.wakers.remove(&id) +- { +- w.wake(); ++ // Wake the first waiter that has actually installed its `Waker` ++ // (i.e. is genuinely asleep). A slot mapped to `None` is a waiter ++ // that reserved its place but has not been polled yet to install its ++ // `Waker`; consuming that slot would drop the wake without waking a ++ // real sleeper (the old code did exactly that, because `remove` runs ++ // inside the `&&` even when the pattern fails). When no installed ++ // waker exists, record a pending wake for the imminent first poll to ++ // consume, so the wake is not lost in the registration gap. ++ let sleeper = futex ++ .wakers ++ .iter() ++ .find(|(_, w)| w.is_some()) ++ .map(|(id, _)| *id); ++ if let Some(id) = sleeper { ++ if let Some(Some(w)) = futex.wakers.remove(&id) { ++ w.wake(); ++ } ++ } else if !futex.wakers.is_empty() { ++ futex.pending_wakes += 1; + } ++ // A pending token only means anything while a registered waiter is ++ // still around to consume it, so drop the futex once the last waiter ++ // leaves. Keeping the entry alive for an unclaimed token would strand ++ // it: every later wake would find no sleeper in the empty map and ++ // increment the counter again, unbounded and never reclaimed. + if futex.wakers.is_empty() { + guard.futexes.remove(&pointer); + } diff --git a/pkgs/lib/wasix-crate-patches/README.md b/pkgs/lib/wasix-crate-patches/README.md index e90f5ec4..4eb638ec 100644 --- a/pkgs/lib/wasix-crate-patches/README.md +++ b/pkgs/lib/wasix-crate-patches/README.md @@ -52,6 +52,16 @@ loudly instead of miscompiling downstream. A crate with only `.patch` files needs just `{ edited = [...]; }`. +## Patches that add a dependency + +The hook runs post-vendor, so a patch that adds a `[dependencies]` entry cannot +pull the crate down: it must already be vendored, i.e. present in the consumer's +`Cargo.lock`. `mio/1.2.2.patch` does this (it adds `wasix`), so every package +locking mio 1.2.2 needs `wasix` in its lock too, or the build fails resolving it. +rustfs gets it via `rustfs-manifest.patch`; a package that later bumps into the +patched version has to do the same. Prefer not adding dependencies for this +reason; when unavoidable, say so here. + ## Rewriters `rewriters/.nix` is a script derivation run against the crate dir (`$PWD`), diff --git a/pkgs/lib/wasix-crate-patches/async-rs/0.8.11.patch b/pkgs/lib/wasix-crate-patches/async-rs/0.8.11.patch new file mode 100644 index 00000000..4c75c7ee --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/async-rs/0.8.11.patch @@ -0,0 +1,41 @@ +--- a/src/sys/mod.rs ++++ b/src/sys/mod.rs +@@ -7,3 +7,8 @@ + mod windows; + #[cfg(windows)] + pub(crate) use windows::*; ++ ++#[cfg(all(not(unix), not(windows)))] ++mod wasi; ++#[cfg(all(not(unix), not(windows)))] ++pub(crate) use wasi::*; +--- a/src/sys/wasi.rs ++++ b/src/sys/wasi.rs +@@ -0,0 +1,27 @@ ++// wasix: std::os::unix is the incomplete wasi_ext feature; use stable std::os::fd. ++use std::os::fd::{AsFd, AsRawFd}; ++ ++/// Abstract trait on top of AsFd + AsRawFd. ++pub trait AsSysFd: AsFd + AsRawFd {} ++impl AsSysFd for H {} ++ ++#[cfg(feature = "async-io")] ++mod async_io { ++ use crate::{sys::AsSysFd, util::IOHandle}; ++ use std::{ ++ io::{Read, Write}, ++ os::fd::{AsFd, AsRawFd, BorrowedFd, RawFd}, ++ }; ++ ++ impl AsFd for IOHandle { ++ fn as_fd(&self) -> BorrowedFd<'_> { ++ self.0.as_fd() ++ } ++ } ++ ++ impl AsRawFd for IOHandle { ++ fn as_raw_fd(&self) -> RawFd { ++ self.as_fd().as_raw_fd() ++ } ++ } ++} diff --git a/pkgs/lib/wasix-crate-patches/async-rs/edits.nix b/pkgs/lib/wasix-crate-patches/async-rs/edits.nix new file mode 100644 index 00000000..4b243b93 --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/async-rs/edits.nix @@ -0,0 +1,3 @@ +{...}: { + edited = [">=0.8.11"]; +} diff --git a/pkgs/lib/wasix-crate-patches/clocksource/0.8.3.patch b/pkgs/lib/wasix-crate-patches/clocksource/0.8.3.patch new file mode 100644 index 00000000..c28a12dc --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/clocksource/0.8.3.patch @@ -0,0 +1,67 @@ +--- a/src/sys/mod.rs ++++ b/src/sys/mod.rs +@@ -1,9 +1,62 @@ +-#[cfg(not(target_os = "windows"))] ++#[cfg(all(not(target_os = "windows"), not(target_os = "wasi")))] + mod unix; +-#[cfg(not(target_os = "windows"))] ++#[cfg(all(not(target_os = "windows"), not(target_os = "wasi")))] + pub use unix::*; + + #[cfg(target_os = "windows")] + mod windows; + #[cfg(target_os = "windows")] + pub use windows::*; ++ ++// wasix: clockid_t is a non-primitive newtype and CLOCK_*_COARSE are absent, so ++// the unix backend does not compile. Use clockid_t directly with the plain ++// (non-coarse) clocks. ++#[cfg(target_os = "wasi")] ++pub use wasi::*; ++#[cfg(target_os = "wasi")] ++mod wasi { ++ fn read_clock(clock: libc::clockid_t) -> libc::timespec { ++ let mut ts = libc::timespec { ++ tv_sec: 0, ++ tv_nsec: 0, ++ }; ++ unsafe { ++ libc::clock_gettime(clock, &mut ts); ++ } ++ ts ++ } ++ ++ pub mod monotonic { ++ pub fn coarse() -> crate::coarse::Instant { ++ let ts = super::read_clock(libc::CLOCK_MONOTONIC); ++ crate::coarse::Instant { ++ secs: ts.tv_sec as u32, ++ } ++ } ++ ++ pub fn precise() -> crate::precise::Instant { ++ let ts = super::read_clock(libc::CLOCK_MONOTONIC); ++ let now = (ts.tv_sec as u64) ++ .wrapping_mul(1_000_000_000) ++ .wrapping_add(ts.tv_nsec as u64); ++ crate::precise::Instant { ns: now } ++ } ++ } ++ ++ pub mod realtime { ++ pub fn coarse() -> crate::coarse::UnixInstant { ++ let ts = super::read_clock(libc::CLOCK_REALTIME); ++ crate::coarse::UnixInstant { ++ secs: ts.tv_sec as u32, ++ } ++ } ++ ++ pub fn precise() -> crate::precise::UnixInstant { ++ let ts = super::read_clock(libc::CLOCK_REALTIME); ++ let now = (ts.tv_sec as u64) ++ .wrapping_mul(1_000_000_000) ++ .wrapping_add(ts.tv_nsec as u64); ++ crate::precise::UnixInstant { ns: now } ++ } ++ } ++} diff --git a/pkgs/lib/wasix-crate-patches/clocksource/edits.nix b/pkgs/lib/wasix-crate-patches/clocksource/edits.nix new file mode 100644 index 00000000..96ff785b --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/clocksource/edits.nix @@ -0,0 +1,3 @@ +{...}: { + edited = [">=0.8.3"]; +} diff --git a/pkgs/lib/wasix-crate-patches/crc-fast/1.10.0.patch b/pkgs/lib/wasix-crate-patches/crc-fast/1.10.0.patch new file mode 100644 index 00000000..042b57a4 --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/crc-fast/1.10.0.patch @@ -0,0 +1,11 @@ +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -75,8 +75,6 @@ + name = "crc_fast" + crate-type = [ + "lib", +- "cdylib", +- "staticlib", + ] + path = "src/lib.rs" + bench = true diff --git a/pkgs/lib/wasix-crate-patches/crc-fast/edits.nix b/pkgs/lib/wasix-crate-patches/crc-fast/edits.nix new file mode 100644 index 00000000..dec101ca --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/crc-fast/edits.nix @@ -0,0 +1,3 @@ +{...}: { + edited = [">=1.10.0"]; +} diff --git a/pkgs/lib/wasix-crate-patches/deadpool-postgres/0.14.1.patch b/pkgs/lib/wasix-crate-patches/deadpool-postgres/0.14.1.patch new file mode 100644 index 00000000..f17339e8 --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/deadpool-postgres/0.14.1.patch @@ -0,0 +1,75 @@ +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -96,13 +96,13 @@ + "dep:serde", + ] + +-[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio-postgres] ++[target.'cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))'.dependencies.tokio-postgres] + version = "0.7.9" + +-[target.'cfg(target_arch = "wasm32")'.dependencies.getrandom] ++[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies.getrandom] + version = "0.2" + features = ["js"] + +-[target.'cfg(target_arch = "wasm32")'.dependencies.tokio-postgres] ++[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies.tokio-postgres] + version = "0.7.9" + default-features = false +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -37,7 +37,7 @@ + }; + + use deadpool::managed; +-#[cfg(not(target_arch = "wasm32"))] ++#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + use tokio::spawn; + use tokio::task::JoinHandle; + use tokio_postgres::{ +@@ -45,7 +45,7 @@ + Transaction as PgTransaction, TransactionBuilder as PgTransactionBuilder, + }; + +-#[cfg(not(target_arch = "wasm32"))] ++#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + use tokio_postgres::{ + tls::{MakeTlsConnect, TlsConnect}, + Socket, +@@ -89,7 +89,7 @@ + } + + impl Manager { +- #[cfg(not(target_arch = "wasm32"))] ++ #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + /// Creates a new [`Manager`] using the given [`tokio_postgres::Config`] and + /// `tls` connector. + pub fn new(pg_config: tokio_postgres::Config, tls: T) -> Self +@@ -102,7 +102,7 @@ + Self::from_config(pg_config, tls, ManagerConfig::default()) + } + +- #[cfg(not(target_arch = "wasm32"))] ++ #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + /// Create a new [`Manager`] using the given [`tokio_postgres::Config`], and + /// `tls` connector and [`ManagerConfig`]. + pub fn from_config(pg_config: tokio_postgres::Config, tls: T, config: ManagerConfig) -> Self +@@ -188,7 +188,7 @@ + ) -> BoxFuture<'_, Result<(PgClient, JoinHandle<()>), Error>>; + } + +-#[cfg(not(target_arch = "wasm32"))] ++#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + /// Provides an implementation of [`Connect`] that establishes the connection + /// using the `tokio_postgres` configuration itself. + #[derive(Debug)] +@@ -203,7 +203,7 @@ + pub tls: T, + } + +-#[cfg(not(target_arch = "wasm32"))] ++#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + impl Connect for ConfigConnectImpl + where + T: MakeTlsConnect + Clone + Sync + Send + 'static, diff --git a/pkgs/lib/wasix-crate-patches/deadpool-postgres/edits.nix b/pkgs/lib/wasix-crate-patches/deadpool-postgres/edits.nix new file mode 100644 index 00000000..e2c7968a --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/deadpool-postgres/edits.nix @@ -0,0 +1,3 @@ +{...}: { + edited = [">=0.14.1"]; +} diff --git a/pkgs/lib/wasix-crate-patches/getrandom/0.4.3.patch b/pkgs/lib/wasix-crate-patches/getrandom/0.4.3.patch new file mode 100644 index 00000000..97cc03ac --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/getrandom/0.4.3.patch @@ -0,0 +1,11 @@ +--- a/src/backends.rs ++++ b/src/backends.rs +@@ -138,7 +138,7 @@ + pub use apple_other::*; + } else if #[cfg(all(target_arch = "wasm32", target_os = "wasi"))] { + cfg_if! { +- if #[cfg(target_env = "p1")] { ++ if #[cfg(not(any(target_env = "p2", target_env = "p3")))] { + mod wasi_p1; + pub use wasi_p1::*; + } else { diff --git a/pkgs/lib/wasix-crate-patches/google-cloud-auth/1.14.0.patch b/pkgs/lib/wasix-crate-patches/google-cloud-auth/1.14.0.patch new file mode 100644 index 00000000..8f6573bd --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/google-cloud-auth/1.14.0.patch @@ -0,0 +1,35 @@ +--- a/src/credentials/external_account_sources/executable_sourced.rs ++++ b/src/credentials/external_account_sources/executable_sourced.rs +@@ -26,6 +26,7 @@ + fmt::{Display, Formatter}, + time::{Duration, SystemTime, UNIX_EPOCH}, + }; ++#[cfg(not(target_os = "wasi"))] + use tokio::{process::Command, time::timeout as tokio_timeout}; + + #[derive(Serialize, Deserialize, Debug, Clone)] +@@ -142,6 +143,7 @@ + + /// See details on security reason on [executable sourced credentials]. + /// [executable sourced credentials]: https://google.aip.dev/auth/4117#determining-the-subject-token-in-executable-sourced-credentials ++ #[cfg(not(target_os = "wasi"))] + async fn from_command(command: String, args: Vec, timeout: Duration) -> Result { + // For security reasons, we need our consumers to set this environment variable to allow executables to be run. + let allow_executable = std::env::var(ALLOW_EXECUTABLE_ENV) +@@ -178,6 +180,16 @@ + Self::parse_token(output) + } + ++ // wasix has no subprocess spawning via tokio::process; executable-sourced ++ // external-account credentials are unsupported. ++ #[cfg(target_os = "wasi")] ++ async fn from_command(_command: String, _args: Vec, _timeout: Duration) -> Result { ++ Err(CredentialsError::from_msg( ++ false, ++ "executable-sourced credentials are not supported on wasix", ++ )) ++ } ++ + /// Parses a full command string into a command and its arguments. + fn split_command(command: String) -> (String, Vec) { + let mut parts = command.split_whitespace(); diff --git a/pkgs/lib/wasix-crate-patches/google-cloud-auth/edits.nix b/pkgs/lib/wasix-crate-patches/google-cloud-auth/edits.nix new file mode 100644 index 00000000..47452e0f --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/google-cloud-auth/edits.nix @@ -0,0 +1,3 @@ +{...}: { + edited = [">=1.14.0"]; +} diff --git a/pkgs/lib/wasix-crate-patches/netif/0.1.6.patch b/pkgs/lib/wasix-crate-patches/netif/0.1.6.patch new file mode 100644 index 00000000..3650ed67 --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/netif/0.1.6.patch @@ -0,0 +1,42 @@ +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -61,9 +61,29 @@ + #[cfg(target_os = "windows")] + pub use windows::*; + +-#[cfg(not(target_os = "windows"))] ++#[cfg(all(not(target_os = "windows"), not(target_os = "wasi")))] + pub use unix::*; + ++#[cfg(target_os = "wasi")] ++pub use wasi_stub::*; ++ ++#[cfg(target_os = "wasi")] ++mod wasi_stub { ++ use super::Interface; ++ use std::io; ++ // wasix has no getifaddrs/AF_LINK interface enumeration; yield nothing. ++ pub fn up() -> io::Result { ++ Ok(Up) ++ } ++ pub struct Up; ++ impl Iterator for Up { ++ type Item = Interface; ++ fn next(&mut self) -> Option { ++ None ++ } ++ } ++} ++ + #[cfg(target_os = "windows")] + mod windows { + use super::Interface; +@@ -271,7 +291,7 @@ + } + } + +-#[cfg(not(target_os = "windows"))] ++#[cfg(all(not(target_os = "windows"), not(target_os = "wasi")))] + mod unix { + use super::Interface; + use libc as c; diff --git a/pkgs/lib/wasix-crate-patches/netif/edits.nix b/pkgs/lib/wasix-crate-patches/netif/edits.nix new file mode 100644 index 00000000..6ce711ba --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/netif/edits.nix @@ -0,0 +1,3 @@ +{...}: { + edited = [">=0.1.6"]; +} diff --git a/pkgs/lib/wasix-crate-patches/parking_lot_core/0.9.12.patch b/pkgs/lib/wasix-crate-patches/parking_lot_core/0.9.12.patch new file mode 100644 index 00000000..2cdc87b2 --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/parking_lot_core/0.9.12.patch @@ -0,0 +1,27 @@ +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -45,7 +45,11 @@ + )] + #![cfg_attr( + all( +- feature = "nightly", ++ // wasix: the wasix rust toolchain is always nightly-channel, so enable the ++ // unstable atomic-wait intrinsics on wasi too. The stock `nightly`-only gate ++ // otherwise falls through to `wasm.rs`, whose parker panics "Parking not ++ // supported"; multi-threaded wasix (tokio/datafusion) needs a real parker. ++ any(feature = "nightly", target_os = "wasi"), + target_family = "wasm", + target_feature = "atomics" + ), +--- a/src/thread_parker/mod.rs ++++ b/src/thread_parker/mod.rs +@@ -67,7 +67,8 @@ + #[path = "sgx.rs"] + mod imp; + } else if #[cfg(all( +- feature = "nightly", ++ // wasix: always nightly-channel; pick the atomic parker (see lib.rs). ++ any(feature = "nightly", target_os = "wasi"), + target_family = "wasm", + target_feature = "atomics" + ))] { diff --git a/pkgs/lib/wasix-crate-patches/parking_lot_core/edits.nix b/pkgs/lib/wasix-crate-patches/parking_lot_core/edits.nix new file mode 100644 index 00000000..c078faef --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/parking_lot_core/edits.nix @@ -0,0 +1,3 @@ +{...}: { + edited = [">=0.9.12"]; +} diff --git a/pkgs/lib/wasix-crate-patches/rustfs-kafka/1.2.0.patch b/pkgs/lib/wasix-crate-patches/rustfs-kafka/1.2.0.patch new file mode 100644 index 00000000..791fa053 --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/rustfs-kafka/1.2.0.patch @@ -0,0 +1,11 @@ +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -57,7 +57,7 @@ + "dep:sha2", + "dep:pbkdf2", + "dep:rand", +- "rustls/aws-lc-rs", ++ "rustls/ring", + ] + security-ring = [ + "dep:rustls", diff --git a/pkgs/lib/wasix-crate-patches/rustfs-kafka/edits.nix b/pkgs/lib/wasix-crate-patches/rustfs-kafka/edits.nix new file mode 100644 index 00000000..add55f7e --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/rustfs-kafka/edits.nix @@ -0,0 +1,3 @@ +{...}: { + edited = [">=1.2.0"]; +} diff --git a/pkgs/lib/wasix-crate-patches/rustls-native-certs/0.8.4.patch b/pkgs/lib/wasix-crate-patches/rustls-native-certs/0.8.4.patch new file mode 100644 index 00000000..61bac96a --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/rustls-native-certs/0.8.4.patch @@ -0,0 +1,20 @@ +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -43,6 +43,17 @@ + #[cfg(target_os = "macos")] + use macos as platform; + ++#[cfg(all(not(unix), not(windows)))] ++mod wasi_platform { ++ use super::CertificateResult; ++ // wasix has no native cert store; return empty (callers supply roots explicitly). ++ pub fn load_native_certs() -> CertificateResult { ++ CertificateResult::default() ++ } ++} ++#[cfg(all(not(unix), not(windows)))] ++use wasi_platform as platform; ++ + /// Load root certificates found in the platform's native certificate store. + /// + /// ## Environment Variables diff --git a/pkgs/lib/wasix-crate-patches/symlink/0.1.0.patch b/pkgs/lib/wasix-crate-patches/symlink/0.1.0.patch new file mode 100644 index 00000000..a02baf16 --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/symlink/0.1.0.patch @@ -0,0 +1,82 @@ +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -28,6 +28,25 @@ + symlink as symlink_dir}; + } + ++// wasix: std::os::unix::fs::symlink needs the unstable wasi_ext feature; provide ++// best-effort helpers (used only for a convenience "latest" log symlink). ++#[cfg(all(not(target_os = "redox"), not(unix), not(windows)))] ++mod internal { ++ use std::io; ++ use std::path::Path; ++ pub fn symlink_auto(_src: &Path, _dst: &Path) -> io::Result<()> { ++ Ok(()) ++ } ++ pub fn symlink_file(_src: &Path, _dst: &Path) -> io::Result<()> { ++ Ok(()) ++ } ++ pub fn symlink_dir(_src: &Path, _dst: &Path) -> io::Result<()> { ++ Ok(()) ++ } ++ pub use std::fs::remove_file as remove_symlink_dir; ++ pub use std::fs::remove_file as remove_symlink_auto; ++} ++ + /// Create a symlink (non-preferred way). + /// + /// On Windows, file and directory symlinks are created by distinct methods; to cope with that, +@@ -49,7 +68,7 @@ + /// + /// An error will be returned if the symlink cannot be created, or—on Windows—if the destination + /// does not exist or cannot be read. +-#[cfg(any(target_os = "redox", unix, windows))] ++#[cfg(any(target_os = "redox", unix, windows, target_os = "wasi"))] + #[inline] + pub fn symlink_auto, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { + internal::symlink_auto(src.as_ref(), dst.as_ref()) +@@ -67,7 +86,7 @@ + /// # Errors + /// + /// An error will be returned if the symlink cannot be created. +-#[cfg(any(target_os = "redox", unix, windows))] ++#[cfg(any(target_os = "redox", unix, windows, target_os = "wasi"))] + #[inline] + pub fn symlink_file, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { + internal::symlink_file(src.as_ref(), dst.as_ref()) +@@ -85,7 +104,7 @@ + /// # Errors + /// + /// An error will be returned if the symlink cannot be created. +-#[cfg(any(target_os = "redox", unix, windows))] ++#[cfg(any(target_os = "redox", unix, windows, target_os = "wasi"))] + #[inline] + pub fn symlink_dir, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { + internal::symlink_dir(src.as_ref(), dst.as_ref()) +@@ -107,7 +126,7 @@ + /// # Errors + /// + /// An error will be returned if the symlink cannot be removed. +-#[cfg(any(target_os = "redox", unix, windows))] ++#[cfg(any(target_os = "redox", unix, windows, target_os = "wasi"))] + #[inline] + pub fn remove_symlink_auto>(path: P) -> io::Result<()> { + internal::remove_symlink_auto(path) +@@ -118,7 +137,7 @@ + /// On Windows, this corresponds to `std::fs::remove_dir`. + /// + /// On Unix, this corresponds to `std::fs::remove_file`. +-#[cfg(any(target_os = "redox", unix, windows))] ++#[cfg(any(target_os = "redox", unix, windows, target_os = "wasi"))] + #[inline] + pub fn remove_symlink_dir>(path: P) -> io::Result<()> { + internal::remove_symlink_dir(path) +@@ -130,7 +149,7 @@ + /// `remove_symlink_dir`. + /// + /// On Unix, this corresponds to `std::fs::remove_file`. +-#[cfg(any(target_os = "redox", unix, windows))] ++#[cfg(any(target_os = "redox", unix, windows, target_os = "wasi"))] + #[inline] + pub fn remove_symlink_file>(path: P) -> io::Result<()> { + fs::remove_file(path) diff --git a/pkgs/lib/wasix-crate-patches/symlink/edits.nix b/pkgs/lib/wasix-crate-patches/symlink/edits.nix new file mode 100644 index 00000000..57766bf3 --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/symlink/edits.nix @@ -0,0 +1,3 @@ +{...}: { + edited = [">=0.1.0"]; +} diff --git a/pkgs/lib/wasix-crate-patches/tokio-postgres/0.7.18.patch b/pkgs/lib/wasix-crate-patches/tokio-postgres/0.7.18.patch new file mode 100644 index 00000000..5bce0436 --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/tokio-postgres/0.7.18.patch @@ -0,0 +1,146 @@ +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -230,6 +230,6 @@ + version = "1.0" + package = "uuid" + +-[target.'cfg(not(target_arch = "wasm32"))'.dependencies.socket2] ++[target.'cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))'.dependencies.socket2] + version = "0.6" + features = ["all"] +--- a/src/config.rs ++++ b/src/config.rs +@@ -7,7 +7,7 @@ + #[cfg(feature = "runtime")] + use crate::connect::connect; + use crate::connect_raw::connect_raw; +-#[cfg(not(target_arch = "wasm32"))] ++#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + use crate::keepalive::KeepaliveConfig; + #[cfg(feature = "runtime")] + use crate::tls::MakeTlsConnect; +@@ -230,7 +230,7 @@ + pub(crate) connect_timeout: Option, + pub(crate) tcp_user_timeout: Option, + pub(crate) keepalives: bool, +- #[cfg(not(target_arch = "wasm32"))] ++ #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + pub(crate) keepalive_config: KeepaliveConfig, + pub(crate) target_session_attrs: TargetSessionAttrs, + pub(crate) channel_binding: ChannelBinding, +@@ -260,7 +260,7 @@ + connect_timeout: None, + tcp_user_timeout: None, + keepalives: true, +- #[cfg(not(target_arch = "wasm32"))] ++ #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + keepalive_config: KeepaliveConfig { + idle: Duration::from_secs(2 * 60 * 60), + interval: None, +@@ -477,7 +477,7 @@ + /// Sets the amount of idle time before a keepalive packet is sent on the connection. + /// + /// This is ignored for Unix domain sockets, or if the `keepalives` option is disabled. Defaults to 2 hours. +- #[cfg(not(target_arch = "wasm32"))] ++ #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + pub fn keepalives_idle(&mut self, keepalives_idle: Duration) -> &mut Config { + self.keepalive_config.idle = keepalives_idle; + self +@@ -485,7 +485,7 @@ + + /// Gets the configured amount of idle time before a keepalive packet will + /// be sent on the connection. +- #[cfg(not(target_arch = "wasm32"))] ++ #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + pub fn get_keepalives_idle(&self) -> Duration { + self.keepalive_config.idle + } +@@ -494,14 +494,14 @@ + /// On Windows, this sets the value of the tcp_keepalive struct’s keepaliveinterval field. + /// + /// This is ignored for Unix domain sockets, or if the `keepalives` option is disabled. +- #[cfg(not(target_arch = "wasm32"))] ++ #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + pub fn keepalives_interval(&mut self, keepalives_interval: Duration) -> &mut Config { + self.keepalive_config.interval = Some(keepalives_interval); + self + } + + /// Gets the time interval between TCP keepalive probes. +- #[cfg(not(target_arch = "wasm32"))] ++ #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + pub fn get_keepalives_interval(&self) -> Option { + self.keepalive_config.interval + } +@@ -509,14 +509,14 @@ + /// Sets the maximum number of TCP keepalive probes that will be sent before dropping a connection. + /// + /// This is ignored for Unix domain sockets, or if the `keepalives` option is disabled. +- #[cfg(not(target_arch = "wasm32"))] ++ #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + pub fn keepalives_retries(&mut self, keepalives_retries: u32) -> &mut Config { + self.keepalive_config.retries = Some(keepalives_retries); + self + } + + /// Gets the maximum number of TCP keepalive probes that will be sent before dropping a connection. +- #[cfg(not(target_arch = "wasm32"))] ++ #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + pub fn get_keepalives_retries(&self) -> Option { + self.keepalive_config.retries + } +@@ -642,14 +642,14 @@ + self.tcp_user_timeout(Duration::from_secs(timeout as u64)); + } + } +- #[cfg(not(target_arch = "wasm32"))] ++ #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + "keepalives" => { + let keepalives = value + .parse::() + .map_err(|_| Error::config_parse(Box::new(InvalidValue("keepalives"))))?; + self.keepalives(keepalives != 0); + } +- #[cfg(not(target_arch = "wasm32"))] ++ #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + "keepalives_idle" => { + let keepalives_idle = value + .parse::() +@@ -658,7 +658,7 @@ + self.keepalives_idle(Duration::from_secs(keepalives_idle as u64)); + } + } +- #[cfg(not(target_arch = "wasm32"))] ++ #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + "keepalives_interval" => { + let keepalives_interval = value.parse::().map_err(|_| { + Error::config_parse(Box::new(InvalidValue("keepalives_interval"))) +@@ -667,7 +667,7 @@ + self.keepalives_interval(Duration::from_secs(keepalives_interval as u64)); + } + } +- #[cfg(not(target_arch = "wasm32"))] ++ #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + "keepalives_retries" => { + let keepalives_retries = value.parse::().map_err(|_| { + Error::config_parse(Box::new(InvalidValue("keepalives_retries"))) +@@ -785,7 +785,7 @@ + .field("tcp_user_timeout", &self.tcp_user_timeout) + .field("keepalives", &self.keepalives); + +- #[cfg(not(target_arch = "wasm32"))] ++ #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + { + config_dbg = config_dbg + .field("keepalives_idle", &self.keepalive_config.idle) +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -170,7 +170,7 @@ + mod copy_out; + pub mod error; + mod generic_client; +-#[cfg(not(target_arch = "wasm32"))] ++#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] + mod keepalive; + mod maybe_tls_stream; + mod portal; diff --git a/pkgs/lib/wasix-crate-patches/tokio-postgres/edits.nix b/pkgs/lib/wasix-crate-patches/tokio-postgres/edits.nix new file mode 100644 index 00000000..38e2a94a --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/tokio-postgres/edits.nix @@ -0,0 +1,3 @@ +{...}: { + edited = [">=0.7.18"]; +} diff --git a/pkgs/lib/wasix-crate-patches/tokio/1.52.3.patch b/pkgs/lib/wasix-crate-patches/tokio/1.52.3.patch index 1bdafd8d..aeb21299 100644 --- a/pkgs/lib/wasix-crate-patches/tokio/1.52.3.patch +++ b/pkgs/lib/wasix-crate-patches/tokio/1.52.3.patch @@ -1,6 +1,5 @@ -diff -ruN '--exclude=.cargo-checksum.json' '--exclude=Cargo.lock' a/Cargo.toml b/Cargo.toml ---- a/Cargo.toml 2006-07-24 03:21:28.000000000 +0200 -+++ b/Cargo.toml 2026-07-21 18:32:43.100402757 +0200 +--- a/Cargo.toml ++++ b/Cargo.toml @@ -978,5 +978,6 @@ "cfg(tokio_no_parking_lot)", "cfg(tokio_no_tuning_tests)", @@ -8,13 +7,17 @@ diff -ruN '--exclude=.cargo-checksum.json' '--exclude=Cargo.lock' a/Cargo.toml b + "cfg(tokio_wasix_waker)", 'cfg(target_os, values("cygwin"))', ] -diff -ruN '--exclude=.cargo-checksum.json' '--exclude=Cargo.lock' a/src/lib.rs b/src/lib.rs ---- a/src/lib.rs 2006-07-24 03:21:28.000000000 +0200 -+++ b/src/lib.rs 2026-07-21 18:32:43.087993058 +0200 -@@ -22,6 +22,10 @@ +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -22,6 +22,15 @@ #![cfg_attr(docsrs, allow(unused_attributes))] #![cfg_attr(loom, allow(dead_code, unreachable_pub))] #![cfg_attr(windows, allow(rustdoc::broken_intra_doc_links))] ++// The wasix std fork aliases `std::os::unix` to `std::os::wasi` on wasmer ++// (`library/std/src/os/mod.rs`), and its `os/wasix/fs.rs` carries ++// `#![unstable(feature = "wasi_ext")]`. So the fs-feature code paths that reach ++// the `std::os::unix::fs` extension traits land in a nightly-gated module and ++// need the feature opted in. Drop this once the fork stabilises those. +#![cfg_attr( + all(feature = "fs", target_os = "wasi", target_vendor = "wasmer"), + feature(wasi_ext) @@ -22,21 +25,32 @@ diff -ruN '--exclude=.cargo-checksum.json' '--exclude=Cargo.lock' a/src/lib.rs b //! A runtime for writing reliable network applications without compromising speed. //! -diff -ruN '--exclude=.cargo-checksum.json' '--exclude=Cargo.lock' a/src/net/tcp/stream.rs b/src/net/tcp/stream.rs ---- a/src/net/tcp/stream.rs 2006-07-24 03:21:28.000000000 +0200 -+++ b/src/net/tcp/stream.rs 2026-07-21 18:32:43.088980477 +0200 -@@ -1564,7 +1564,7 @@ +@@ -465,7 +474,7 @@ + + #[cfg(all( + not(tokio_unstable), +- target_family = "wasm", ++ all(target_family = "wasm", not(target_vendor = "wasmer")), + any( + feature = "fs", + feature = "io-std", +--- a/src/net/tcp/stream.rs ++++ b/src/net/tcp/stream.rs +@@ -1564,7 +1564,11 @@ } } -#[cfg(all(tokio_unstable, target_os = "wasi"))] ++// Upstream also gates this on `tokio_unstable`. We do not build with that cfg, ++// and the wasix stack takes raw fds from tokio sockets, so the impls have to be ++// available on a stable-cfg build. Additive (trait impls only), unlike the ++// `tokio_wasix_waker` gates below which must stay opt-in. +#[cfg(target_os = "wasi")] mod sys { use super::TcpStream; use std::os::fd::{AsFd, AsRawFd, BorrowedFd, RawFd}; -diff -ruN '--exclude=.cargo-checksum.json' '--exclude=Cargo.lock' a/src/runtime/io/driver.rs b/src/runtime/io/driver.rs ---- a/src/runtime/io/driver.rs 2006-07-24 03:21:28.000000000 +0200 -+++ b/src/runtime/io/driver.rs 2026-07-21 18:32:43.095970719 +0200 +--- a/src/runtime/io/driver.rs ++++ b/src/runtime/io/driver.rs @@ -46,7 +46,7 @@ /// Used to wake up the reactor from a call to `turn`. @@ -64,7 +78,7 @@ diff -ruN '--exclude=.cargo-checksum.json' '--exclude=Cargo.lock' a/src/runtime/ waker, metrics: IoDriverMetrics::default(), #[cfg(all( -@@ -189,9 +189,16 @@ +@@ -189,9 +189,18 @@ Ok(()) => {} Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} #[cfg(target_os = "wasi")] @@ -77,14 +91,16 @@ diff -ruN '--exclude=.cargo-checksum.json' '--exclude=Cargo.lock' a/src/runtime/ + io::ErrorKind::InvalidInput | io::ErrorKind::Unsupported + ) => + { -+ // wasm32-wasi: an empty subscription set has nothing to wait on. -+ // The reference runtime returns InvalidInput here; wasmer/wasix -+ // returns Unsupported (ENOTSUP). Either way just return from the -+ // park (see WASIX-TODO: wasmer poll_oneoff errno for empty sets). ++ // wasm32-wasi: InvalidInput is the reference runtime's errno for ++ // a poll with no subscriptions, i.e. nothing to wait on, so just ++ // return from the park. Unsupported is tolerated the same way so ++ // that an errno we did not anticipate cannot abort the process ++ // from inside the I/O driver (see WASIX-TODO.md, "tokio's I/O ++ // driver panics on an unexpected park errno"). } Err(e) => panic!("unexpected error when polling the I/O driver: {e:?}"), } -@@ -256,7 +263,7 @@ +@@ -256,7 +265,7 @@ /// blocked in `turn`, then the next call to `turn` will not block and /// return immediately. pub(crate) fn unpark(&self) { diff --git a/pkgs/lib/wasix-crate-patches/tokio/edits.nix b/pkgs/lib/wasix-crate-patches/tokio/edits.nix index ebd49a72..2a4e0168 100644 --- a/pkgs/lib/wasix-crate-patches/tokio/edits.nix +++ b/pkgs/lib/wasix-crate-patches/tokio/edits.nix @@ -15,7 +15,7 @@ hasResidual = lib.versionAtLeast version "1.52.3"; in { patches = lib.optional (!modern || hasResidual) floorPatch; - patchPhase = lib.optionalString modern '' + patchPhase = lib.optionalString (modern && !hasResidual) '' needle=' target_family = "wasm",' replacement=' all(target_family = "wasm", not(target_vendor = "wasmer")),' matches="$(grep -Fxc "$needle" src/lib.rs)" || { diff --git a/pkgs/lib/wasix-crate-patches/tonic/0.14.6.patch b/pkgs/lib/wasix-crate-patches/tonic/0.14.6.patch new file mode 100644 index 00000000..03a2c757 --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/tonic/0.14.6.patch @@ -0,0 +1,29 @@ +--- a/src/transport/channel/uds_connector.rs ++++ b/src/transport/channel/uds_connector.rs +@@ -9,10 +9,10 @@ + + use crate::status::ConnectError; + +-#[cfg(not(target_os = "windows"))] ++#[cfg(unix)] + use tokio::net::UnixStream; + +-#[cfg(not(target_os = "windows"))] ++#[cfg(unix)] + async fn connect_uds(uds_path: String) -> Result { + UnixStream::connect(uds_path) + .await +@@ -21,11 +21,11 @@ + + // Dummy type that will allow us to compile and match trait bounds + // but is never used. +-#[cfg(target_os = "windows")] ++#[cfg(not(unix))] + #[allow(dead_code)] + type UnixStream = tokio::io::DuplexStream; + +-#[cfg(target_os = "windows")] ++#[cfg(not(unix))] + async fn connect_uds(_uds_path: String) -> Result { + Err(ConnectError( + "uds connections are not allowed on windows".into(), diff --git a/pkgs/lib/wasix-crate-patches/tonic/edits.nix b/pkgs/lib/wasix-crate-patches/tonic/edits.nix new file mode 100644 index 00000000..065d8e1c --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/tonic/edits.nix @@ -0,0 +1,3 @@ +{...}: { + edited = [">=0.14.6"]; +} diff --git a/pkgs/lib/wasix-crate-patches/wasite/1.0.2.patch b/pkgs/lib/wasix-crate-patches/wasite/1.0.2.patch new file mode 100644 index 00000000..820e2d5f --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/wasite/1.0.2.patch @@ -0,0 +1,29 @@ +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -1,6 +1,11 @@ + //! Abstraction over Wasite - Terminal interface conventions for WASI + +-#![no_std] ++// wasix: the stock crate targets WASI preview 2 via the `wasi` component-model ++// crate (`wasi:cli/environment`), an import a preview1 host (wasmer/wasix) ++// cannot satisfy. On wasi read the environment through std (preview1 ++// `environ_get`); the unused terminal ops (`state`/`execute`) the object store ++// never calls are dropped by the linker. ++#![cfg_attr(not(target_os = "wasi"), no_std)] + #![forbid(unsafe_code)] + #![doc( + html_logo_url = "https://ardaku.github.io/mm/logo.svg", +@@ -159,7 +164,12 @@ + }, + }; + +- for (key, value) in wasi::cli::environment::get_environment() { ++ #[cfg(not(target_os = "wasi"))] ++ let vars = wasi::cli::environment::get_environment(); ++ #[cfg(target_os = "wasi")] ++ let vars = std::env::vars(); ++ ++ for (key, value) in vars { + match key.as_str() { + "USER" => env.user.username = value, + "HOSTNAME" => env.host.hostname = value, diff --git a/pkgs/lib/wasix-crate-patches/wasite/edits.nix b/pkgs/lib/wasix-crate-patches/wasite/edits.nix new file mode 100644 index 00000000..812115db --- /dev/null +++ b/pkgs/lib/wasix-crate-patches/wasite/edits.nix @@ -0,0 +1,3 @@ +{...}: { + edited = [">=1.0.2"]; +} diff --git a/pkgs/overlay/packages/rustfs/package.nix b/pkgs/overlay/packages/rustfs/package.nix new file mode 100644 index 00000000..44d64e65 --- /dev/null +++ b/pkgs/overlay/packages/rustfs/package.nix @@ -0,0 +1,70 @@ +# rustfs: MinIO-class distributed S3 object store, built to WASIX. +# Source mods (patches/rustfs-wasix.patch): drop mimalloc (native C allocator); +# switch the whole rustls stack off aws-lc-rs onto ring (feature swaps across the +# AWS SDK, google-cloud, reqwest, vaultrs, lapin, mysql, nats, mqtt, otlp, +# libunftp, jsonwebtoken) + the crypto-provider call sites; stub tokio signal for +# wasix. Leaf crate-patches (tokio, getrandom, rustfs-kafka) come from the tree. +# Built with -p rustfs to skip the e2e_test crate (pulls russh/aws-lc). +{ + final, + nix-update-script, + ... +}: +final.rustPlatform.buildRustPackage { + pname = "rustfs"; + version = "1.0.0-beta.9-unstable-2026-07-15"; + # Only the Cargo.toml/lock changes go into the vendored src (they drive + # cargoHash); the .rs changes are applied post-vendor via `patches` so editing + # rustfs source does not re-trigger the (huge, datafusion-heavy) vendor fetch. + src = final.applyPatches { + src = final.fetchFromGitHub { + owner = "rustfs"; + repo = "rustfs"; + rev = "5ef2731a6bc1299065c629298f78096152b2a696"; + hash = "sha256-7qvEVqwSVXtNsU+9KYFIef57K1MTL0Hr/zZv0gzdiFQ="; + }; + patches = [./patches/rustfs-manifest.patch]; + }; + cargoHash = "sha256-dDRFxlZ94CXwR8Rypi/kkxRVCgkkrgFpaWJv/PdoFi8="; + patches = [./patches/rustfs-code.patch]; + # ftps/webdav are default protocol-server features pulling libunftp + dav-server, + # both deeply unix (LocalFs, etc.); drop them for the core S3 build. + buildNoDefaultFeatures = true; + cargoBuildFlags = ["-p" "rustfs"]; + # pulsar's build.rs invokes system protoc (prost-build 0.14 elsewhere bundles it). + nativeBuildInputs = [final.buildPackages.protobuf]; + # rustfs's release profile is opt-level=3 + thin-LTO + codegen-units=1; on a + # ~100MB wasm that whole-program link takes hours / risks OOM. Override to a + # fast profile for the wasix build (feasibility, not a perf-optimized ship). + env = { + # tokio's I/O-driver mio Waker (tokio/1.52.3.patch) is only sound with a mio + # that has the wasix backend, i.e. a real eventfd Waker. Ours is mio >= 1.2.2 + # (mio/1.2.2.patch); packages pinned to mio 1.2.0/1.2.1 get no such Waker, so + # the patch keeps it behind this opt-in rather than all of target_vendor=wasmer. + RUSTFLAGS = "--cfg tokio_wasix_waker"; + PROTOC = "${final.buildPackages.protobuf}/bin/protoc"; + CARGO_PROFILE_RELEASE_LTO = "false"; + CARGO_PROFILE_RELEASE_CODEGEN_UNITS = "16"; + CARGO_PROFILE_RELEASE_OPT_LEVEL = "1"; + }; + passthru.wasix.shipped = true; + # Upstream's only tags are prereleases (1.0.0-beta.N): a published webc version + # is semver MAJOR.MINOR.PATCH, so the fourth component has nowhere to go, and + # the registry hides prereleases from `latest` anyway (WASIX-TODO.md). Track the + # default branch instead and publish date snapshots via the fold below; revisit + # both once upstream cuts a stable release. + passthru.updateScript = nix-update-script {extraArgs = ["--flake" "--version=branch"];}; + # 1.0.0-beta.9-unstable-YYYY-MM-DD folds to 7 numeric components; semver takes + # 3, so declare the rule. Upstream is still pre-1.0 (beta) and the snapshot + # date is what actually distinguishes builds, so the date is the whole patch; + # 0.0.x leaves room for a real 1.0.0 to sort above every snapshot. + passthru.wasmer.version = v: let + d = builtins.match ".*-unstable-([0-9]{4})-([0-9]{2})-([0-9]{2})" v; + in + assert final.lib.assertMsg (d != null) "rustfs: version ${v} is not -unstable-YYYY-MM-DD; update the semver fold"; "0.0.${final.lib.concatStrings d}"; + meta = { + description = "High-performance distributed S3-compatible object storage, built to WASIX"; + homepage = "https://github.com/rustfs/rustfs"; + mainProgram = "rustfs"; + }; +} diff --git a/pkgs/overlay/packages/rustfs/patches/rustfs-code.patch b/pkgs/overlay/packages/rustfs/patches/rustfs-code.patch new file mode 100644 index 00000000..b4200a85 --- /dev/null +++ b/pkgs/overlay/packages/rustfs/patches/rustfs-code.patch @@ -0,0 +1,512 @@ +--- a/crates/e2e_test/src/protocols/ftps_core.rs ++++ b/crates/e2e_test/src/protocols/ftps_core.rs +@@ -84,7 +84,7 @@ + } + + fn supported_verify_schemes(&self) -> Vec { +- rustls::crypto::aws_lc_rs::default_provider() ++ rustls::crypto::ring::default_provider() + .signature_verification_algorithms + .supported_schemes() + } +@@ -207,7 +207,7 @@ + + // Install the default crypto provider once for this process before any + // TLS handshake. Subsequent connections reuse it via `ftps_connect_secure`. +- rustls::crypto::aws_lc_rs::default_provider() ++ rustls::crypto::ring::default_provider() + .install_default() + .map_err(|e| anyhow::anyhow!("Failed to install crypto provider: {:?}", e))?; + +--- a/crates/e2e_test/src/tls_hot_reload_test.rs ++++ b/crates/e2e_test/src/tls_hot_reload_test.rs +@@ -84,7 +84,7 @@ + } + + fn supported_verify_schemes(&self) -> Vec { +- rustls::crypto::aws_lc_rs::default_provider() ++ rustls::crypto::ring::default_provider() + .signature_verification_algorithms + .supported_schemes() + } +@@ -246,7 +246,7 @@ + async fn test_tls_certificate_hot_reload_live_listener() -> TestResult { + init_logging(); + // Install the process-wide rustls crypto provider (idempotent). +- let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); ++ let _ = rustls::crypto::ring::default_provider().install_default(); + + let mut env = RustFSTestEnvironment::new().await?; + let tls_dir = std::path::PathBuf::from(format!("{}/tls", env.temp_dir)); +--- a/crates/ecstore/src/bucket/bucket_target_sys.rs ++++ b/crates/ecstore/src/bucket/bucket_target_sys.rs +@@ -910,7 +910,7 @@ + } + + fn supported_verify_schemes(&self) -> Vec { +- rustls::crypto::aws_lc_rs::default_provider() ++ rustls::crypto::ring::default_provider() + .signature_verification_algorithms + .supported_schemes() + } +@@ -958,7 +958,7 @@ + + fn ensure_rustls_crypto_provider() { + if rustls::crypto::CryptoProvider::get_default().is_none() { +- let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); ++ let _ = rustls::crypto::ring::default_provider().install_default(); + } + } + +@@ -1072,7 +1072,7 @@ + .map_err(|err| BucketTargetError::Io(std::io::Error::other(format!("invalid target CA PEM: {err}"))))?; + + Ok(SmithyHttpClientBuilder::new() +- .tls_provider(smithy_tls::Provider::rustls(smithy_tls::rustls_provider::CryptoMode::AwsLc)) ++ .tls_provider(smithy_tls::Provider::rustls(smithy_tls::rustls_provider::CryptoMode::Ring)) + .tls_context(tls_context) + .build_https()) + } +--- a/crates/ecstore/src/client/api_get_object_file.rs ++++ b/crates/ecstore/src/client/api_get_object_file.rs +@@ -15,7 +15,7 @@ + use std::io; + use std::path::{Path, PathBuf}; + +-#[cfg(not(windows))] ++#[cfg(unix)] + use std::os::unix::fs::PermissionsExt; + + use tokio::fs::{self, OpenOptions}; +@@ -40,7 +40,7 @@ + { + fs::create_dir_all(parent).await?; + +- #[cfg(not(windows))] ++ #[cfg(unix)] + { + let mut permissions = fs::metadata(parent).await?.permissions(); + permissions.set_mode(0o700); +@@ -59,7 +59,7 @@ + let mut options = OpenOptions::new(); + options.create(true).read(true).write(true); + +- #[cfg(not(windows))] ++ #[cfg(unix)] + options.mode(0o600); + + options.open(file_part_path).await +--- a/crates/ecstore/src/client/transition_api.rs ++++ b/crates/ecstore/src/client/transition_api.rs +@@ -234,7 +234,7 @@ + // No default provider is set yet; try to install aws-lc-rs. + // `install_default` can only fail if another thread races us and installs a provider + // between our check and this call, which is still safe to ignore. +- if rustls::crypto::aws_lc_rs::default_provider().install_default().is_err() { ++ if rustls::crypto::ring::default_provider().install_default().is_err() { + debug!("rustls crypto provider was installed concurrently, skipping aws-lc-rs install"); + } + } else { +@@ -1407,12 +1407,12 @@ + #[test] + fn provider_install_is_idempotent() { + // Install once (may already be set by another test in this binary — that's fine). +- let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); ++ let _ = rustls::crypto::ring::default_provider().install_default(); + + // A second install attempt on an already-set provider must not panic. + let outcome = std::panic::catch_unwind(|| { + if rustls::crypto::CryptoProvider::get_default().is_none() { +- let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); ++ let _ = rustls::crypto::ring::default_provider().install_default(); + } + // If a default is already present, the branch above is simply skipped. + }); +--- a/crates/ecstore/src/disk/fs.rs ++++ b/crates/ecstore/src/disk/fs.rs +@@ -50,7 +50,7 @@ + }) + } + +-#[cfg(not(windows))] ++#[cfg(unix)] + pub fn same_file(f1: &Metadata, f2: &Metadata) -> bool { + use std::os::unix::fs::MetadataExt; + +@@ -76,7 +76,7 @@ + true + } + +-#[cfg(windows)] ++#[cfg(not(unix))] + pub fn same_file(f1: &Metadata, f2: &Metadata) -> bool { + if f1.permissions() != f2.permissions() { + return false; +--- a/crates/ecstore/src/disk/local.rs ++++ b/crates/ecstore/src/disk/local.rs +@@ -4368,6 +4368,12 @@ + match err.kind() { + ErrorKind::NotFound => (), + ErrorKind::DirectoryNotEmpty => (), ++ // wasix std does not categorize wasi ENOTEMPTY (55) as ++ // DirectoryNotEmpty, so a non-empty object dir (left by the xl ++ // rename dance during a versioned delete) reaches the fatal arm ++ // below and aborts the delete. Tolerate it like the arm above. ++ #[cfg(target_os = "wasi")] ++ _ if err.raw_os_error() == Some(55) => (), + kind => { + warn!( + event = EVENT_DISK_LOCAL_DELETE_FAILED, +--- a/crates/ecstore/src/layout/endpoints.rs ++++ b/crates/ecstore/src/layout/endpoints.rs +@@ -1182,7 +1182,7 @@ + }; + let canonical_path = canonical.to_string_lossy().into_owned(); + diagnostic.canonical_path = Some(canonical_path.clone()); +- #[cfg(not(windows))] ++ #[cfg(unix)] + if let Ok(stat) = rustix::fs::stat(canonical.as_path()) { + diagnostic.device_numbers = Some(format!("{}:{}", rustix::fs::major(stat.st_dev), rustix::fs::minor(stat.st_dev))); + } +--- a/crates/ecstore/src/store/init.rs ++++ b/crates/ecstore/src/store/init.rs +@@ -282,8 +282,12 @@ + retry_delay_secs = interval, + "Retrying storage format load" + ); ++ #[cfg(not(target_os = "wasi"))] ++ let ctrl_c = tokio::signal::ctrl_c(); ++ #[cfg(target_os = "wasi")] ++ let ctrl_c = std::future::pending::>(); + select! { +- _ = tokio::signal::ctrl_c() => { ++ _ = ctrl_c => { + info!( + event = EVENT_STORE_FORMAT_RETRY, + component = LOG_COMPONENT_ECSTORE, +--- a/crates/protocols/src/ftps/server.rs ++++ b/crates/protocols/src/ftps/server.rs +@@ -189,7 +189,7 @@ + ); + + // Build ServerConfig with SNI support +- let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); ++ let _ = rustls::crypto::ring::default_provider().install_default(); + + let server_config = rustls::ServerConfig::builder() + .with_no_client_auth() +--- a/crates/protocols/src/webdav/server.rs ++++ b/crates/protocols/src/webdav/server.rs +@@ -122,7 +122,7 @@ + reload_shutdown_rx.clone(), + ); + +- let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); ++ let _ = rustls::crypto::ring::default_provider().install_default(); + + let server_config = ServerConfig::builder().with_no_client_auth().with_cert_resolver(resolver); + +--- a/crates/targets/src/runtime/tls/validate.rs ++++ b/crates/targets/src/runtime/tls/validate.rs +@@ -40,7 +40,7 @@ + // Parsing both files is not enough: an operator can supply a cert and a key + // that belong to different key pairs. Verify the private key's public key + // matches the certificate's SubjectPublicKeyInfo before accepting them. +- let signing_key = rustls::crypto::aws_lc_rs::sign::any_supported_type(&key) ++ let signing_key = rustls::crypto::ring::sign::any_supported_type(&key) + .map_err(|e| TargetError::Configuration(format!("Unsupported client private key '{key_path}': {e:?}")))?; + let certified = rustls::sign::CertifiedKey::new(certs, signing_key); + match certified.keys_match() { +--- a/crates/targets/src/target/mod.rs ++++ b/crates/targets/src/target/mod.rs +@@ -774,7 +774,7 @@ + if rustls::crypto::CryptoProvider::get_default().is_some() { + return; + } +- if let Err(err) = rustls::crypto::aws_lc_rs::default_provider().install_default() { ++ if let Err(err) = rustls::crypto::ring::default_provider().install_default() { + debug!("rustls provider already installed or unavailable: {err:?}"); + } + } +--- a/crates/targets/src/target/redis.rs ++++ b/crates/targets/src/target/redis.rs +@@ -33,8 +33,10 @@ + AsyncCommands, Client, ClientTlsConfig, ConnectionInfo, IntoConnectionInfo, RedisError, TlsCertificates, + aio::{ConnectionManager, ConnectionManagerConfig}, + cmd, +- io::tcp::{TcpSettings, socket2}, ++ io::tcp::TcpSettings, + }; ++#[cfg(not(target_family = "wasm"))] ++use redis::io::tcp::socket2; + use rustfs_config::{REDIS_TLS_CA, REDIS_TLS_CLIENT_CERT, REDIS_TLS_CLIENT_KEY, REDIS_TLS_POLICY}; + use rustls::pki_types::CertificateDer; + use rustls::pki_types::pem::PemObject; +--- a/crates/targets/tests/mysql_integration.rs ++++ b/crates/targets/tests/mysql_integration.rs +@@ -91,7 +91,7 @@ + + if parsed.tls { + if rustls::crypto::CryptoProvider::get_default().is_none() { +- rustls::crypto::aws_lc_rs::default_provider().install_default().ok(); ++ rustls::crypto::ring::default_provider().install_default().ok(); + } + builder = builder.ssl_opts(Some(SslOpts::default())); + } +--- a/crates/tls-runtime/src/certs.rs ++++ b/crates/tls-runtime/src/certs.rs +@@ -494,7 +494,7 @@ + let mut default_cert = None; + + for (domain, (certs, key)) in cert_key_pairs { +- let signing_key = rustls::crypto::aws_lc_rs::sign::any_supported_type(&key) ++ let signing_key = rustls::crypto::ring::sign::any_supported_type(&key) + .map_err(|e| certs_error(format!("unsupported private key types:{domain}, err:{e:?}")))?; + + let certified_key = CertifiedKey::new(certs, signing_key); +--- a/crates/tls-runtime/src/server.rs ++++ b/crates/tls-runtime/src/server.rs +@@ -69,7 +69,7 @@ + let fingerprint = server_material_fingerprint(&material); + + for (domain, (certs, key)) in entries { +- let signing_key = rustls::crypto::aws_lc_rs::sign::any_supported_type(&key) ++ let signing_key = rustls::crypto::ring::sign::any_supported_type(&key) + .map_err(|e| io::Error::other(format!("unsupported private key type for {domain}: {e:?}")))?; + let certified_key = CertifiedKey::new(certs, signing_key); + +--- a/crates/utils/src/os/mod.rs ++++ b/crates/utils/src/os/mod.rs +@@ -23,6 +23,9 @@ + #[cfg(target_os = "windows")] + mod windows; + ++#[cfg(all(not(unix), not(windows)))] ++mod wasi; ++ + #[cfg(target_os = "linux")] + pub use linux::{check_cross_device_mounts, get_drive_stats, get_info, get_physical_device_ids, same_disk}; + +@@ -34,6 +37,9 @@ + check_cross_device_mounts, get_drive_stats, get_info, get_physical_device_ids, get_volume_serial_number, same_disk, + }; + ++#[cfg(all(not(unix), not(windows)))] ++pub use wasi::{check_cross_device_mounts, get_drive_stats, get_info, get_physical_device_ids, same_disk}; ++ + #[derive(Debug, Default, PartialEq)] + pub struct IOStats { + pub read_ios: u64, +--- a/crates/utils/src/os/wasi.rs ++++ b/crates/utils/src/os/wasi.rs +@@ -0,0 +1,39 @@ ++// Copyright 2024 RustFS Team ++// ++// Licensed under the Apache License, Version 2.0 (the "License"); ++// you may not use this file except in compliance with the License. ++// You may obtain a copy of the License at ++// ++// http://www.apache.org/licenses/LICENSE-2.0 ++// ++// Unless required by applicable law or agreed to in writing, software ++// distributed under the License is distributed on an "AS IS" BASIS, ++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++// See the License for the specific language governing permissions and ++// limitations under the License. ++ ++// wasix has no statvfs, block-device introspection, or /proc; these are ++// best-effort stubs so the storage engine builds. Capacity/rotational/IO stats ++// are unavailable and reported as defaults. ++use super::{DiskInfo, IOStats}; ++use std::path::Path; ++ ++pub fn get_info(_p: impl AsRef) -> std::io::Result { ++ Ok(DiskInfo::default()) ++} ++ ++pub fn same_disk(_disk1: &str, _disk2: &str) -> std::io::Result { ++ Ok(false) ++} ++ ++pub fn get_physical_device_ids(_disk: &str) -> std::io::Result> { ++ Ok(Vec::new()) ++} ++ ++pub fn check_cross_device_mounts(_paths: &[String]) -> std::io::Result<()> { ++ Ok(()) ++} ++ ++pub fn get_drive_stats(_major: u32, _minor: u32) -> std::io::Result { ++ Ok(IOStats::default()) ++} +--- a/rustfs/src/admin/console.rs ++++ b/rustfs/src/admin/console.rs +@@ -567,7 +567,7 @@ + Duration::from_secs(auth_timeout), + )) + // Add request body limit (10MB for console uploads) +- .layer(RequestBodyLimitLayer::new(5 * 1024 * 1024 * 1024)); ++ .layer(RequestBodyLimitLayer::new(5usize.saturating_mul(1024 * 1024 * 1024))); + + // Add rate limiting if enabled + if rate_limit_enable { +--- a/rustfs/src/admin/handlers/site_replication.rs ++++ b/rustfs/src/admin/handlers/site_replication.rs +@@ -7389,7 +7389,7 @@ + } + + fn test_tls_identity() -> TestTlsIdentity { +- let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); ++ let _ = rustls::crypto::ring::default_provider().install_default(); + let certified = + rcgen::generate_simple_self_signed(vec!["127.0.0.1".to_string()]).expect("generate TLS server certificate"); + TestTlsIdentity { +@@ -7438,7 +7438,7 @@ + } + + async fn spawn_test_tls_server_with_response(response: &'static [u8]) -> (String, String, tokio::task::JoinHandle) { +- let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); ++ let _ = rustls::crypto::ring::default_provider().install_default(); + let certified = + rcgen::generate_simple_self_signed(vec!["127.0.0.1".to_string()]).expect("generate TLS server certificate"); + let ca_pem = certified.cert.pem(); +--- a/rustfs/src/allocator_reclaim.rs ++++ b/rustfs/src/allocator_reclaim.rs +@@ -111,12 +111,12 @@ + } + + pub fn allocator_backend() -> &'static str { +- #[cfg(not(target_os = "windows"))] ++ #[cfg(all(not(target_os = "windows"), not(target_family = "wasm")))] + { + "mimalloc" + } + +- #[cfg(target_os = "windows")] ++ #[cfg(any(target_os = "windows", target_family = "wasm"))] + { + "mimalloc-windows" + } +@@ -294,7 +294,7 @@ + ) + } + +-#[cfg(not(target_os = "windows"))] ++#[cfg(all(not(target_os = "windows"), not(target_family = "wasm")))] + #[allow(unsafe_code)] + fn collect_allocator_memory(force: bool) -> Result<(), String> { + // SAFETY: `mi_collect` is provided by the active global allocator backend +@@ -306,7 +306,7 @@ + Ok(()) + } + +-#[cfg(target_os = "windows")] ++#[cfg(any(target_os = "windows", target_family = "wasm"))] + fn collect_allocator_memory(_force: bool) -> Result<(), String> { + Err("allocator reclaim is not supported on Windows".to_string()) + } +--- a/rustfs/src/main.rs ++++ b/rustfs/src/main.rs +@@ -12,6 +12,7 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++#[cfg(not(target_family = "wasm"))] + #[global_allocator] + static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + +--- a/rustfs/src/memory_observability.rs ++++ b/rustfs/src/memory_observability.rs +@@ -19,7 +19,7 @@ + use serde::Serialize; + use serde_json::Value; + use std::collections::HashMap; +-#[cfg(not(target_os = "windows"))] ++#[cfg(all(not(target_os = "windows"), not(target_family = "wasm")))] + use std::ffi::CStr; + use std::path::Path; + use std::sync::{Arc, Mutex, OnceLock}; +@@ -273,7 +273,7 @@ + } + } + +-#[cfg(not(target_os = "windows"))] ++#[cfg(all(not(target_os = "windows"), not(target_family = "wasm")))] + #[allow(unsafe_code)] + fn read_allocator_memory_snapshot() -> Option { + // SAFETY: `mi_stats_get_json` returns a null-terminated JSON buffer owned by +@@ -296,7 +296,7 @@ + }) + } + +-#[cfg(target_os = "windows")] ++#[cfg(any(target_os = "windows", target_family = "wasm"))] + fn read_allocator_memory_snapshot() -> Option { + None + } +--- a/rustfs/src/server/http.rs ++++ b/rustfs/src/server/http.rs +@@ -348,6 +348,15 @@ + + // The listening address and port are obtained from the parameters + let listener = { ++ // wasix: socket2's raw socket creation (libc::socket with CLOEXEC + IPPROTO_TCP) ++ // returns ENOTSUP on wasmer; bind via tokio (native wasix sock_open through the ++ // mio wasi backend) and skip the socket2 tuning wasix does not support. ++ #[cfg(target_os = "wasi")] ++ { ++ TcpListener::bind(server_addr).await? ++ } ++ #[cfg(not(target_os = "wasi"))] ++ { + let mut server_addr = server_addr; + + // Try to create a socket for the address family; if that fails, fallback to IPv4. +@@ -504,6 +513,7 @@ + socket.listen(backlog)?; + } + TcpListener::from_std(socket.into())? ++ } + }; + + let tls_path = config.tls_path.as_deref().map(str::trim).unwrap_or_default(); +--- a/rustfs/src/server/service_state.rs ++++ b/rustfs/src/server/service_state.rs +@@ -78,7 +78,14 @@ + } + } + +-#[cfg(not(unix))] ++#[cfg(target_os = "wasi")] ++pub async fn wait_for_shutdown() -> ShutdownSignal { ++ // wasix has no tokio signal support; wasmer terminates the instance. ++ std::future::pending::<()>().await; ++ ShutdownSignal::CtrlC ++} ++ ++#[cfg(all(not(unix), not(target_os = "wasi")))] + pub async fn wait_for_shutdown() -> ShutdownSignal { + tokio::select! { + _ = tokio::signal::ctrl_c() => { +--- a/rustfs/src/server/tls_material.rs ++++ b/rustfs/src/server/tls_material.rs +@@ -642,7 +642,7 @@ + static INIT: Once = Once::new(); + INIT.call_once(|| { + if rustls::crypto::CryptoProvider::get_default().is_none() { +- let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); ++ let _ = rustls::crypto::ring::default_provider().install_default(); + } + }); + } +--- a/rustfs/src/startup_runtime_hooks.rs ++++ b/rustfs/src/startup_runtime_hooks.rs +@@ -14,7 +14,7 @@ + + use crate::license::license_status; + use crate::startup_runtime_sources; +-use rustls::crypto::aws_lc_rs::default_provider; ++use rustls::crypto::ring::default_provider; + use std::future::Future; + use std::io::Result; + use tracing::{debug, info, warn}; diff --git a/pkgs/overlay/packages/rustfs/patches/rustfs-manifest.patch b/pkgs/overlay/packages/rustfs/patches/rustfs-manifest.patch new file mode 100644 index 00000000..851f55a0 --- /dev/null +++ b/pkgs/overlay/packages/rustfs/patches/rustfs-manifest.patch @@ -0,0 +1,886 @@ +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -601,15 +601,15 @@ + + [[package]] + name = "astral-tokio-tar" +-version = "0.6.3" ++version = "0.6.4" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "08648fef353ab39a9d26f909ad53fc4f071be4c91853b78523f5cc3d9e5ebffd" ++checksum = "b18457efd137254e016bbde5e1d88df61c4e1a5ae2223746e56123bac6af2463" + dependencies = [ + "futures-core", + "libc", + "portable-atomic", + "rustc-hash", +- "rustix 0.38.44", ++ "rustix", + "tokio", + "tokio-stream", + "xattr", +@@ -856,8 +856,6 @@ + dependencies = [ + "aws-credential-types", + "aws-runtime", +- "aws-sdk-sso", +- "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", +@@ -869,14 +867,11 @@ + "aws-types", + "bytes", + "fastrand", +- "hex", + "http 1.4.2", +- "sha1 0.10.7", + "time", + "tokio", + "tracing", + "url", +- "zeroize", + ] + + [[package]] +@@ -981,58 +976,6 @@ + ] + + [[package]] +-name = "aws-sdk-sso" +-version = "1.103.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "0469f435f645ad2162cfb463b15bde37115966ee3acf2d87fb4871ee309b8401" +-dependencies = [ +- "arc-swap", +- "aws-credential-types", +- "aws-runtime", +- "aws-smithy-async", +- "aws-smithy-http", +- "aws-smithy-json", +- "aws-smithy-observability", +- "aws-smithy-runtime", +- "aws-smithy-runtime-api", +- "aws-smithy-schema", +- "aws-smithy-types", +- "aws-types", +- "bytes", +- "fastrand", +- "http 0.2.12", +- "http 1.4.2", +- "regex-lite", +- "tracing", +-] +- +-[[package]] +-name = "aws-sdk-ssooidc" +-version = "1.105.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "085faefb253f770655e162b9304321e62a1e71adf7f019ee1f4454228a377b3a" +-dependencies = [ +- "arc-swap", +- "aws-credential-types", +- "aws-runtime", +- "aws-smithy-async", +- "aws-smithy-http", +- "aws-smithy-json", +- "aws-smithy-observability", +- "aws-smithy-runtime", +- "aws-smithy-runtime-api", +- "aws-smithy-schema", +- "aws-smithy-types", +- "aws-types", +- "bytes", +- "fastrand", +- "http 0.2.12", +- "http 1.4.2", +- "regex-lite", +- "tracing", +-] +- +-[[package]] + name = "aws-sdk-sts" + version = "1.108.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +@@ -1213,7 +1156,6 @@ + dependencies = [ + "aws-smithy-async", + "aws-smithy-http", +- "aws-smithy-http-client", + "aws-smithy-observability", + "aws-smithy-runtime-api", + "aws-smithy-schema", +@@ -1497,9 +1439,9 @@ + + [[package]] + name = "bitflags" +-version = "2.13.0" ++version = "2.13.1" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" ++checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + dependencies = [ + "serde_core", + ] +@@ -1769,9 +1711,9 @@ + + [[package]] + name = "cfg_aliases" +-version = "0.2.1" ++version = "0.2.2" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" ++checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + + [[package]] + name = "chacha20" +@@ -1879,9 +1821,9 @@ + + [[package]] + name = "clap" +-version = "4.6.1" ++version = "4.6.2" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" ++checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011" + dependencies = [ + "clap_builder", + "clap_derive", +@@ -1889,9 +1831,9 @@ + + [[package]] + name = "clap_builder" +-version = "4.6.0" ++version = "4.6.2" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" ++checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" + dependencies = [ + "anstream", + "anstyle", +@@ -3609,7 +3551,7 @@ + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" + dependencies = [ +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + "objc2", + ] + +@@ -3625,12 +3567,6 @@ + ] + + [[package]] +-name = "doc-comment" +-version = "0.3.4" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9" +- +-[[package]] + name = "dunce" + version = "1.0.5" + source = "registry+https://github.com/rust-lang/crates.io-index" +@@ -4043,7 +3979,7 @@ + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "35f6839d7b3b98adde531effaf34f0c2badc6f4735d26fe74709d8e513a96ef3" + dependencies = [ +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + "rustc_version", + ] + +@@ -4349,7 +4285,6 @@ + checksum = "a3494870d06f3cbbb3561ada6f234982549e3a2fb31e719ef258e6eadb9ae09a" + dependencies = [ + "async-trait", +- "aws-lc-rs", + "base64 0.22.1", + "bytes", + "chrono", +@@ -4357,7 +4292,6 @@ + "hex", + "hmac 0.13.0", + "http 1.4.2", +- "jsonwebtoken", + "reqwest", + "rustc_version", + "rustls", +@@ -5289,7 +5223,7 @@ + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "9080b15e63775b9a2ac7dca720f7050a8b955e092ea0f6020a4a80f69998cdc0" + dependencies = [ +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + "cfg-if", + "libc", + ] +@@ -5499,13 +5433,19 @@ + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "eba32bfb4ffdeaca3e34431072faf01745c9b26d25504aa7a6cf5684334fc4fc" + dependencies = [ +- "aws-lc-rs", + "base64 0.22.1", ++ "ed25519-dalek 2.2.0", + "getrandom 0.2.17", ++ "hmac 0.12.1", + "js-sys", ++ "p256 0.13.2", ++ "p384 0.13.1", + "pem", ++ "rand 0.8.7", ++ "rsa 0.9.10", + "serde", + "serde_json", ++ "sha2 0.10.9", + "signature 2.2.0", + "simple_asn1", + "zeroize", +@@ -5793,7 +5733,7 @@ + checksum = "c9f8ff371890db2cf65a0758dba9a79f9cd965de369f6dbdc6581a22780af45e" + dependencies = [ + "async-trait", +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + "bytes", + "chrono", + "dashmap", +@@ -5803,8 +5743,6 @@ + "lazy_static", + "moka", + "nix 0.30.1", +- "prometheus", +- "proxy-protocol", + "rustls", + "slog", + "slog-stdlog", +@@ -5820,12 +5758,6 @@ + + [[package]] + name = "linux-raw-sys" +-version = "0.4.15" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +- +-[[package]] +-name = "linux-raw-sys" + version = "0.12.1" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +@@ -6223,6 +6155,7 @@ + dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", ++ "wasix", + "windows-sys 0.61.2", + ] + +@@ -6349,7 +6282,7 @@ + checksum = "0f27695f286b461da077b8c2f72f47feaa04ce3c3f9c0976257410e90e21208a" + dependencies = [ + "base64 0.22.1", +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + "btoi", + "byteorder", + "bytes", +@@ -6381,7 +6314,7 @@ + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "22f9786d56d972959e1408b6a93be6af13b9c1392036c5c1fafa08a1b0c6ee87" + dependencies = [ +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + "byteorder", + "derive_builder", + "getset", +@@ -6429,7 +6362,7 @@ + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" + dependencies = [ +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + "cfg-if", + "cfg_aliases", + "libc", +@@ -6442,7 +6375,7 @@ + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" + dependencies = [ +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + "cfg-if", + "cfg_aliases", + "libc", +@@ -6454,7 +6387,7 @@ + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" + dependencies = [ +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + "cfg-if", + "cfg_aliases", + "libc", +@@ -6632,7 +6565,7 @@ + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "f049ae562349fefb8e837eb15443da1e7c6dcbd8a11f52a228f92220c2e5c85e" + dependencies = [ +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + "libloading", + "nvml-wrapper-sys", + "static_assertions", +@@ -6655,7 +6588,7 @@ + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" + dependencies = [ +- "base64 0.22.1", ++ "base64 0.21.7", + "chrono", + "getrandom 0.2.17", + "http 1.4.2", +@@ -6683,7 +6616,7 @@ + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" + dependencies = [ +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + "dispatch2", + "objc2", + ] +@@ -6700,7 +6633,7 @@ + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" + dependencies = [ +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + "objc2", + ] + +@@ -7227,7 +7160,7 @@ + checksum = "97f6fccfd2d9d2df765ca23ff85fe5cc437fb0e6d3e164e4d3cbe09d14780c93" + dependencies = [ + "arrayvec", +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + "thiserror 2.0.18", + "zerocopy", + "zerocopy-derive", +@@ -7718,20 +7651,6 @@ + ] + + [[package]] +-name = "prometheus" +-version = "0.14.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "3ca5326d8d0b950a9acd87e6a3f94745394f62e4dae1b1ee22b2bc0c394af43a" +-dependencies = [ +- "cfg-if", +- "fnv", +- "lazy_static", +- "memchr", +- "parking_lot", +- "thiserror 2.0.18", +-] +- +-[[package]] + name = "proptest" + version = "1.11.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +@@ -7739,7 +7658,7 @@ + dependencies = [ + "bit-set", + "bit-vec 0.8.0", +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + "num-traits", + "rand 0.9.5", + "rand_chacha 0.9.0", +@@ -7777,7 +7696,7 @@ + checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" + dependencies = [ + "heck", +- "itertools 0.14.0", ++ "itertools 0.13.0", + "log", + "multimap", + "once_cell", +@@ -7797,7 +7716,7 @@ + checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" + dependencies = [ + "heck", +- "itertools 0.14.0", ++ "itertools 0.13.0", + "log", + "multimap", + "petgraph 0.8.3", +@@ -7818,7 +7737,7 @@ + checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" + dependencies = [ + "anyhow", +- "itertools 0.14.0", ++ "itertools 0.13.0", + "proc-macro2", + "quote", + "syn 2.0.119", +@@ -7831,7 +7750,7 @@ + checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" + dependencies = [ + "anyhow", +- "itertools 0.14.0", ++ "itertools 0.13.0", + "proc-macro2", + "quote", + "syn 2.0.119", +@@ -7856,16 +7775,6 @@ + ] + + [[package]] +-name = "proxy-protocol" +-version = "0.5.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "0e50c72c21c738f5c5f350cc33640aee30bf7cd20f9d9da20ed41bce2671d532" +-dependencies = [ +- "bytes", +- "snafu", +-] +- +-[[package]] + name = "psm" + version = "0.1.31" + source = "registry+https://github.com/rust-lang/crates.io-index" +@@ -7881,7 +7790,7 @@ + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e" + dependencies = [ +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + "memchr", + "unicase", + ] +@@ -8212,7 +8121,7 @@ + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" + dependencies = [ +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + ] + + [[package]] +@@ -8321,7 +8230,7 @@ + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" + dependencies = [ +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + ] + + [[package]] +@@ -8386,15 +8295,15 @@ + dependencies = [ + "cfg-if", + "libc", +- "rustix 1.1.4", ++ "rustix", + "windows", + ] + + [[package]] + name = "regex" +-version = "1.13.0" ++version = "1.13.1" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2" ++checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" + dependencies = [ + "aho-corasick", + "memchr", +@@ -8404,9 +8313,9 @@ + + [[package]] + name = "regex-automata" +-version = "0.4.15" ++version = "0.4.16" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db" ++checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" + dependencies = [ + "aho-corasick", + "memchr", +@@ -8635,7 +8544,7 @@ + dependencies = [ + "aes 0.9.1", + "aws-lc-rs", +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + "block-padding 0.4.2", + "byteorder", + "bytes", +@@ -8717,7 +8626,7 @@ + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "9ed8949eca4163c18a8f59ff96d32cf61e9c13b9735e21ef32b3907f4aafa1a9" + dependencies = [ +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + "bytes", + "chrono", + "dashmap", +@@ -9136,7 +9045,7 @@ + "rustfs-tls-runtime", + "rustfs-uring", + "rustfs-utils", +- "rustix 1.1.4", ++ "rustix", + "rustls", + "rustls-pki-types", + "s3s", +@@ -9572,7 +9481,7 @@ + "rustfs-security-governance", + "rustfs-storage-api", + "rustfs-utils", +- "rustix 1.1.4", ++ "rustix", + "serde", + "serde_json", + "sysinfo", +@@ -10040,7 +9949,7 @@ + "netif", + "proptest", + "regex", +- "rustix 1.1.4", ++ "rustix", + "serde", + "sha1 0.11.0", + "sha2 0.11.0", +@@ -10115,27 +10024,14 @@ + + [[package]] + name = "rustix" +-version = "0.38.44" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +-dependencies = [ +- "bitflags 2.13.0", +- "errno", +- "libc", +- "linux-raw-sys 0.4.15", +- "windows-sys 0.59.0", +-] +- +-[[package]] +-name = "rustix" + version = "1.1.4" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" + dependencies = [ +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + "errno", + "libc", +- "linux-raw-sys 0.12.1", ++ "linux-raw-sys", + "windows-sys 0.61.2", + ] + +@@ -10445,7 +10341,7 @@ + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" + dependencies = [ +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", +@@ -10913,31 +10809,10 @@ + ] + + [[package]] +-name = "snafu" +-version = "0.6.10" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7" +-dependencies = [ +- "doc-comment", +- "snafu-derive", +-] +- +-[[package]] +-name = "snafu-derive" +-version = "0.6.10" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b" +-dependencies = [ +- "proc-macro2", +- "quote", +- "syn 1.0.109", +-] +- +-[[package]] + name = "snap" +-version = "1.1.1" ++version = "1.1.2" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" ++checksum = "199905e6153d6405f9728fe44daace35f8f837bbf830bb6e85fbd5828709a886" + + [[package]] + name = "socket2" +@@ -11096,9 +10971,9 @@ + + [[package]] + name = "starshard" +-version = "2.2.1" ++version = "2.2.2" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "4dafd0cbefb050fa7a7f95ac229f7978ed4c99866a1a5b62e34a154b52d73d77" ++checksum = "5d298eb1bb81d6e5ddf447f3d26698d6ac5e5b9502b03004dbc2a2e2f8b572b6" + dependencies = [ + "async-trait", + "hashbrown 0.17.1", +@@ -11319,7 +11194,7 @@ + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" + dependencies = [ +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + "core-foundation 0.9.4", + "system-configuration-sys", + ] +@@ -11372,7 +11247,7 @@ + "fastrand", + "getrandom 0.4.3", + "once_cell", +- "rustix 1.1.4", ++ "rustix", + "windows-sys 0.61.2", + ] + +@@ -11590,9 +11465,9 @@ + + [[package]] + name = "tokio" +-version = "1.52.3" ++version = "1.52.4" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" ++checksum = "317fafbbe3f02fc663dad00ea6186197de963cd4190e86a26d8d0fae095539af" + dependencies = [ + "bytes", + "libc", +@@ -11851,7 +11726,7 @@ + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" + dependencies = [ +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + "bytes", + "futures-util", + "http 1.4.2", +@@ -11870,7 +11745,7 @@ + checksum = "b11f75e912b0c2be01b63d8cf8057b8c3f97cf34abb3d431a3a4c8675498e233" + dependencies = [ + "async-compression", +- "bitflags 2.13.0", ++ "bitflags 2.13.1", + "bytes", + "futures-core", + "futures-util", +@@ -12209,9 +12084,9 @@ + + [[package]] + name = "uuid" +-version = "1.23.5" ++version = "1.24.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "ea5fab0d6c3c01ae70085a09cb03d4c7a1d6314e2b3e075392783396d724ca0a" ++checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" + dependencies = [ + "getrandom 0.4.3", + "js-sys", +@@ -12319,6 +12194,15 @@ + ] + + [[package]] ++name = "wasix" ++version = "0.13.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "30d3a78f6dfda080ea3bcb6230bb4d1cf36ed04cf8a6ca9b91d1a97c3f4efa40" ++dependencies = [ ++ "wasi 0.11.1+wasi-snapshot-preview1", ++] ++ ++[[package]] + name = "wasm-bindgen" + version = "0.2.126" + source = "registry+https://github.com/rust-lang/crates.io-index" +@@ -12606,15 +12490,6 @@ + + [[package]] + name = "windows-sys" +-version = "0.59.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +-dependencies = [ +- "windows-targets", +-] +- +-[[package]] +-name = "windows-sys" + version = "0.61.2" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +@@ -12776,7 +12651,7 @@ + checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" + dependencies = [ + "libc", +- "rustix 1.1.4", ++ "rustix", + ] + + [[package]] +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -134,7 +134,7 @@ + # Async Runtime and Networking + async-channel = "2.5.0" + async_zip = { version = "0.0.18", default-features = false, features = ["tokio", "deflate"] } +-mysql_async = { version = "0.37", default-features = false, features = ["default-rustls", "tracing"] } ++mysql_async = { version = "0.37", default-features = false, features = ["default-rustls-ring", "tracing"] } + async-compression = { version = "0.4.42" } + async-recursion = "1.1.1" + async-trait = "0.1.89" +@@ -146,19 +146,19 @@ + futures-util = "0.3.32" + pollster = "1.0.1" + pulsar = { version = "6.8.0", default-features = false, features = ["tokio-rustls-runtime", "telemetry"] } +-lapin = { version = "4.10.0", default-features = false, features = ["tokio", "rustls", "rustls--aws_lc_rs"] } ++lapin = { version = "4.10.0", default-features = false, features = ["tokio", "rustls", "rustls--ring"] } + hyper = { version = "1.10.1", features = ["http2", "http1", "server"] } +-hyper-rustls = { version = "0.27.9", default-features = false, features = ["native-tokio", "http1", "tls12", "logging", "http2", "aws-lc-rs", "webpki-roots"] } ++hyper-rustls = { version = "0.27.9", default-features = false, features = ["native-tokio", "http1", "tls12", "logging", "http2", "ring", "webpki-roots"] } + hyper-util = { version = "0.1.20", features = ["tokio", "server-auto", "server-graceful", "tracing"] } + http = "1.4.2" + http-body = "1.1.0" + http-body-util = "0.1.4" + minlz = "1.2.3" +-reqwest = { version = "0.13.4", default-features = false, features = ["rustls", "charset", "http2", "system-proxy", "stream", "json", "blocking", "query", "form"] } ++reqwest = { version = "0.13.4", default-features = false, features = ["rustls-no-provider", "charset", "http2", "system-proxy", "stream", "json", "blocking", "query", "form"] } + rustfs-kafka-async = { version = "1.2.0" } + socket2 = { version = "0.6.5", features = ["all"] } + tokio = { version = "1.52.3", features = ["fs", "rt-multi-thread"] } +-tokio-rustls = { version = "0.26.4", default-features = false, features = ["logging", "tls12", "aws-lc-rs"] } ++tokio-rustls = { version = "0.26.4", default-features = false, features = ["logging", "tls12", "ring"] } + tokio-stream = { version = "0.1.18" } + tokio-test = "0.4.5" + tokio-util = { version = "0.7.18", features = ["io", "compat"] } +@@ -194,11 +194,11 @@ + chacha20poly1305 = { version = "=0.11.0" } + crc-fast = "1.10.0" + hmac = { version = "0.13.0" } +-jsonwebtoken = { version = "10.4.0", features = ["aws_lc_rs"] } ++jsonwebtoken = { version = "10.4.0", default-features = false, features = ["rust_crypto", "use_pem"] } + openidconnect = { version = "4.0", default-features = false, features = ["accept-rfc3339-timestamps"] } + pbkdf2 = "0.13.0" + rsa = { version = "=0.10.0-rc.18" } +-rustls = { version = "0.23.42", default-features = false, features = ["aws-lc-rs", "logging", "tls12", "prefer-post-quantum", "std"] } ++rustls = { version = "0.23.42", default-features = false, features = ["ring", "logging", "tls12", "std"] } + rustls-native-certs = "0.8" + rustls-pki-types = "1.15.0" + sha1 = "0.11.0" +@@ -223,10 +223,10 @@ + astral-tokio-tar = "0.6.3" + atoi = "3.1.0" + atomic_enum = "0.3.0" +-aws-config = { version = "1.9.0" } ++aws-config = { version = "1.9.0", default-features = false, features = ["rt-tokio", "behavior-version-latest"] } + aws-credential-types = { version = "1.3.0" } +-aws-sdk-s3 = { version = "1.138.0", default-features = false, features = ["sigv4a", "default-https-client", "rt-tokio"] } +-aws-smithy-http-client = { version = "1.2.0", default-features = false, features = ["default-client", "rustls-aws-lc"] } ++aws-sdk-s3 = { version = "1.138.0", default-features = false, features = ["sigv4a", "rt-tokio"] } ++aws-smithy-http-client = { version = "1.2.0", default-features = false, features = ["default-client", "rustls-ring"] } + aws-smithy-runtime-api = { version = "1.13.0", features = ["http-1x"] } + aws-smithy-types = { version = "1.6.1" } + base64 = "0.22.1" +@@ -246,8 +246,8 @@ + faster-hex = "0.10.0" + flate2 = "1.1.9" + glob = "0.3.3" +-google-cloud-storage = "1.16.0" +-google-cloud-auth = "1.14.0" ++google-cloud-storage = { version = "1.16.0", default-features = false } ++google-cloud-auth = { version = "1.14.0", default-features = false } + hashbrown = { version = "0.17.1", features = ["serde", "rayon"] } + hex = "0.4.3" + hex-simd = "0.8.0" +@@ -280,7 +280,7 @@ + #reed-solomon-erasure = { version = "6.0", features = ["simd-accel"], git = "https://github.com/houseme/reed-solomon-erasure",rev = "main" } + reed-solomon-simd = "3.1.0" + regex = { version = "1.13.0" } +-rumqttc = { package = "rumqttc-next", version = "0.33.2", features = ["websocket"] } ++rumqttc = { package = "rumqttc-next", version = "0.33.2", default-features = false, features = ["websocket", "use-rustls-ring"] } + redis = { version = "1.4.0", features = ["connection-manager", "tokio-rustls-comp", "tls-rustls-insecure"] } + rustix = { version = "1.1.4", features = ["fs"] } + rust-embed = { version = "8.12.0" } +@@ -308,7 +308,7 @@ + url = "2.5.8" + urlencoding = "2.1.3" + uuid = { version = "1.23.5", features = ["v4", "fast-rng", "macro-diagnostics"] } +-vaultrs = { version = "0.8.0" } ++vaultrs = { version = "0.8.0", default-features = false, features = ["rustls-no-provider"] } + walkdir = "2.5.0" + windows = { version = "0.62.2" } + xxhash-rust = { version = "0.8.17", features = ["xxh64", "xxh3"] } +@@ -320,14 +320,14 @@ + dial9-tokio-telemetry = "0.3" + opentelemetry = { version = "0.32.0" } + opentelemetry-appender-tracing = { version = "0.32.0", features = ["experimental_span_attributes", "experimental_metadata_attributes"] } +-opentelemetry-otlp = { version = "0.32.0", features = ["gzip-http", "reqwest-rustls"] } ++opentelemetry-otlp = { version = "0.32.0", default-features = false, features = ["http-proto", "reqwest-client", "trace", "metrics", "logs", "gzip-http"] } + opentelemetry_sdk = { version = "0.32.1", features = ["rt-tokio"] } + opentelemetry-semantic-conventions = { version = "0.32.1", features = ["semconv_experimental"] } + opentelemetry-stdout = { version = "0.32.0" } + pyroscope = { version = "2.1.0", features = ["backend-pprof-rs"] } + + # FTP and SFTP +-libunftp = { version = "0.23.0", features = ["experimental"] } ++libunftp = { version = "0.23.0", default-features = false, features = ["experimental", "ring"] } + unftp-core = "0.1.0" + suppaftp = { version = "10.0.1", features = ["tokio", "tokio-rustls-aws-lc-rs"] } + rcgen = "0.14.8" +--- a/crates/ecstore/Cargo.toml ++++ b/crates/ecstore/Cargo.toml +@@ -106,7 +106,7 @@ + hyper-rustls.workspace = true + rustls.workspace = true + rustls-pki-types.workspace = true +-tokio = { workspace = true, features = ["io-util", "sync", "signal"] } ++tokio = { workspace = true, features = ["io-util", "sync"] } + tonic.workspace = true + xxhash-rust = { workspace = true, features = ["xxh64", "xxh3"] } + tower.workspace = true +--- a/rustfs/Cargo.toml ++++ b/rustfs/Cargo.toml +@@ -119,7 +119,7 @@ + http-body-util.workspace = true + reqwest = { workspace = true } + socket2 = { workspace = true } +-tokio = { workspace = true, features = ["rt-multi-thread", "macros", "net", "signal", "process", "io-util"] } ++tokio = { workspace = true, features = ["rt-multi-thread", "macros", "net", "io-util"] } + tokio-rustls = { workspace = true } + aws-sdk-s3 = { workspace = true } + tokio-stream.workspace = true +@@ -187,13 +187,14 @@ + tracing-opentelemetry = { workspace = true } + # Data structures + hashbrown = { workspace = true } +-mimalloc = { workspace = true } ++# mimalloc is native C; not built for wasm (see [target...] block below) + + [target.'cfg(target_os = "linux")'.dependencies] + libsystemd.workspace = true + +-[target.'cfg(not(target_os = "windows"))'.dependencies] ++[target.'cfg(all(not(target_os = "windows"), not(target_family = "wasm")))'.dependencies] + libmimalloc-sys = { version = "0.1.49", features = ["extended"] } ++mimalloc = { workspace = true } + + [dev-dependencies] + uuid = { workspace = true, features = ["v4"] } diff --git a/pkgs/overlay/packages/rustfs/tests/basic.nix b/pkgs/overlay/packages/rustfs/tests/basic.nix new file mode 100644 index 00000000..d69f7ece --- /dev/null +++ b/pkgs/overlay/packages/rustfs/tests/basic.nix @@ -0,0 +1,24 @@ +# Runtime smoke tests for the rustfs webc under wasmer. `--version`/`--help` are +# handled by clap in Opt::parse_command and exit 0 before the server binds, so +# they exercise the whole startup path that matters for "does it run": the wasm +# module loads, std/env init and the tokio runtime come up, and argv parsing +# works — without needing storage volumes or a bound port. +{ + wasmerPkgs, + testLib, + ... +}: let + wasix = [wasmerPkgs.rustfs]; +in { + version = testLib.mkWasixRun { + name = "rustfs-version"; + wasixPkgs = wasix; + script = "rustfs --version"; + }; + + help = testLib.mkWasixRun { + name = "rustfs-help"; + wasixPkgs = wasix; + script = "rustfs --help"; + }; +} diff --git a/pkgs/overlay/packages/rustfs/tests/s3-advanced.nix b/pkgs/overlay/packages/rustfs/tests/s3-advanced.nix new file mode 100644 index 00000000..fcfd9338 --- /dev/null +++ b/pkgs/overlay/packages/rustfs/tests/s3-advanced.nix @@ -0,0 +1,151 @@ +# Harder S3 e2e tests against `rustfs server` under wasmer, driven by the minio +# client over the --net loopback bridge. Each pushes a path the 29-byte +# round-trip (s3-roundtrip.nix) does not: real multipart, listing + nested +# prefixes, concurrent writes (stresses the reactor/futex fixes), and re-reading +# a data dir across a server restart. +{ + pkgs, + wasmerPkgs, + testLib, + ... +}: let + # Boot rustfs + wait until the S3 endpoint answers; `mc alias set` pings it, so + # a successful alias doubles as readiness. stop_server waits for full exit so + # the next start_server can rebind the port (used by the restart test). + preamble = '' + export MC_CONFIG_DIR="$PWD/.mc" + export RUST_BACKTRACE=1 + mkdir -p data + # -E so the trap also fires for failures inside functions/subshells; the + # script runs under `set -e`, so without a trap an unguarded `mc` failure + # would abort with no server-side context. + set -E + trap 'rc=$?; echo "--- server.log (last 40) ---"; tail -40 server.log 2>/dev/null; exit $rc' ERR + start_server() { + ( rustfs server ./data --address 127.0.0.1:9000 >>server.log 2>&1 & echo $! >server.pid ) + for _i in $(seq 1 120); do + sleep 1 + mc -q alias set local http://127.0.0.1:9000 rustfsadmin rustfsadmin >/dev/null 2>&1 && return 0 + kill -0 "$(cat server.pid)" 2>/dev/null || { echo "server died during startup"; tail -30 server.log; return 1; } + done + echo "server never came up in 120s"; tail -30 server.log; return 1 + } + stop_server() { + pid=$(cat server.pid 2>/dev/null) || return 0 + kill "$pid" 2>/dev/null || true + for _ in $(seq 1 40); do kill -0 "$pid" 2>/dev/null || return 0; sleep 0.5; done + kill -9 "$pid" 2>/dev/null || true + } + ''; + mkTest = name: body: + testLib.mkWasixRun { + inherit name; + wasixPkgs = [wasmerPkgs.rustfs]; + nativePkgs = [pkgs.minio-client pkgs.coreutils pkgs.gnugrep pkgs.diffutils]; + wasmerArgs = ["--net"]; + forwardEnv = testLib.defaultForwardEnv ++ ["RUST_BACKTRACE"]; + script = preamble + "\n" + body; + }; +in { + # 24 MiB in 5 MiB parts: exercises CreateMultipartUpload / UploadPart x5 / + # CompleteMultipartUpload, streaming erasure write, and the fsync-per-part + # durability path. Verify byte-exact via sha256. + # + # --part-size is set explicitly rather than leaning on mc's 16 MiB default: at + # the default a change on either side (mc's default, or the object size here) + # could drop this to a single PutObject and the test would still pass while + # silently no longer covering UploadPart at all. 5 MiB is the S3 minimum, so + # the split is guaranteed for any object over that. + s3-multipart-large = mkTest "rustfs-s3-multipart-large" '' + start_server || exit 1 + head -c 25165824 /dev/urandom > big.bin + sha_in=$(sha256sum big.bin | cut -d' ' -f1) + mc -q mb local/bigbucket + mc -q put --part-size 5MiB big.bin local/bigbucket/big.bin + mc -q cp local/bigbucket/big.bin got.bin + sha_out=$(sha256sum got.bin | cut -d' ' -f1) + stop_server + if [ "$sha_in" = "$sha_out" ]; then + echo "PASS: 24MiB multipart round-trip byte-exact ($sha_in)" + else + echo "FAIL: sha mismatch in=$sha_in out=$sha_out"; ls -l big.bin got.bin; exit 1 + fi + ''; + + # CRUD + listing: nested-prefix keys (deep path creation on wasix), recursive + # list count, overwrite, binary integrity, delete -> 404. + s3-crud-list = mkTest "rustfs-s3-crud-list" '' + start_server || exit 1 + fail=0 + mc -q mb local/data + for k in a/b/c/deep.txt x/y.txt top.txt logs/2026/07/app.log; do + printf 'content-of-%s' "$k" | mc -q pipe "local/data/$k" + done + n=$(mc -q ls --recursive local/data | grep -c . || true) + [ "$n" -eq 4 ] || { echo "FAIL: expected 4 objects, listed $n"; mc -q ls --recursive local/data; fail=1; } + got=$(mc -q cat local/data/a/b/c/deep.txt || true) + [ "$got" = "content-of-a/b/c/deep.txt" ] || { echo "FAIL: nested get: '$got'"; fail=1; } + printf 'v2' | mc -q pipe local/data/top.txt + got=$(mc -q cat local/data/top.txt || true) + [ "$got" = "v2" ] || { echo "FAIL: overwrite: '$got'"; fail=1; } + head -c 4096 /dev/urandom > rnd.bin + mc -q cp rnd.bin local/data/rnd.bin + mc -q cp local/data/rnd.bin rnd.out + cmp rnd.bin rnd.out || { echo "FAIL: binary mismatch"; fail=1; } + mc -q rm local/data/top.txt + if mc -q stat local/data/top.txt >/dev/null 2>&1; then echo "FAIL: object present after rm"; fail=1; fi + stop_server + [ "$fail" -eq 0 ] && echo "PASS: crud + list + nested-prefix + binary + overwrite + delete" || exit 1 + ''; + + # 12 concurrent PUTs from independent mc processes: stresses cross-thread task + # wakeup (the futex_wake fix) and the reactor Waker under real load. + s3-concurrent = mkTest "rustfs-s3-concurrent" '' + start_server || exit 1 + mc -q mb local/concbucket + pids="" + for i in $(seq 1 12); do + ( printf 'payload-%s' "$i" | mc -q pipe "local/concbucket/obj-$i.txt" ) & + pids="$pids $!" + done + rc=0 + for p in $pids; do wait "$p" || rc=1; done + fail=0 + for i in $(seq 1 12); do + got=$(mc -q cat "local/concbucket/obj-$i.txt" 2>/dev/null || true) + [ "$got" = "payload-$i" ] || { echo "FAIL: obj-$i = '$got'"; fail=1; } + done + n=$(mc -q ls local/concbucket | grep -c . || true) + [ "$n" -eq 12 ] || { echo "FAIL: expected 12 objects, listed $n"; fail=1; } + stop_server + [ "$fail" -eq 0 ] && [ "$rc" -eq 0 ] && echo "PASS: 12 concurrent puts round-tripped" || exit 1 + ''; + + # Objects written before a kill must re-read after a restart on the same data + # dir: covers the on-disk xl layout being re-openable, erasure metadata being + # recovered on boot, and reads not depending on in-memory state from the write. + # + # This is deliberately not a durability test. Both runs share a host page cache + # (the data dir is a --volume mapping), so a write that never reached the + # platter still reads back and this would pass with fsync stubbed out. Proving + # the fd_datasync fix needs the write path to return at all, which is what + # s3-roundtrip.nix covers: without it PutObject 500s on EACCES. + s3-restart-persistence = mkTest "rustfs-s3-restart-persistence" '' + start_server || exit 1 + mc -q mb local/persist + payload="survives-a-restart" + printf '%s' "$payload" | mc -q pipe local/persist/keep.txt + head -c 8192 /dev/urandom > blob.bin + mc -q cp blob.bin local/persist/blob.bin + stop_server + echo "--- restarting server on the same data dir ---" + start_server || exit 1 + got=$(mc -q cat local/persist/keep.txt 2>/dev/null || true) + mc -q cp local/persist/blob.bin blob.out 2>/dev/null || true + stop_server + fail=0 + [ "$got" = "$payload" ] || { echo "FAIL: text object not re-read after restart: '$got'"; fail=1; } + cmp blob.bin blob.out 2>/dev/null || { echo "FAIL: binary object not re-read after restart"; fail=1; } + [ "$fail" -eq 0 ] && echo "PASS: objects re-read after a server restart" || exit 1 + ''; +} diff --git a/pkgs/overlay/packages/rustfs/tests/s3-roundtrip.nix b/pkgs/overlay/packages/rustfs/tests/s3-roundtrip.nix new file mode 100644 index 00000000..dc99fc7e --- /dev/null +++ b/pkgs/overlay/packages/rustfs/tests/s3-roundtrip.nix @@ -0,0 +1,65 @@ +# End-to-end S3 test: boot `rustfs server` under wasmer, then drive a real +# make-bucket / put / get round-trip with the minio client (`mc`) over the +# wasmer --net loopback bridge. Exercises the whole stack: erasure storage init, +# the tokio/mio reactor (needs the wasmer futex_wake fix + the tokio wasi Waker), +# the HTTP listener, SigV4 auth, and object read/write. +# +# This is also the test that guards the fd_datasync/fd_sync rights fix: rustfs +# fsyncs object writes for durability, and without the patch that EACCES surfaces +# as a 500 InternalError on PutObject, so `mc pipe` fails outright. The data dir +# is a --volume host mapping, which is exactly the case where path_open's rights +# delegation masks the implied sync rights. +{ + pkgs, + wasmerPkgs, + testLib, + ... +}: { + s3-roundtrip = testLib.mkWasixRun { + name = "rustfs-s3-roundtrip"; + wasixPkgs = [wasmerPkgs.rustfs]; + nativePkgs = [pkgs.minio-client pkgs.coreutils]; + wasmerArgs = ["--net"]; + forwardEnv = testLib.defaultForwardEnv ++ ["RUST_BACKTRACE"]; + script = '' + export RUST_BACKTRACE=1 + export MC_CONFIG_DIR="$PWD/.mc" + mkdir -p data + # -E so the trap also fires for failures inside functions/subshells; the + # script runs under `set -e`, so without a trap an unguarded `mc` failure + # would abort with no server-side context. + set -E + trap 'rc=$?; echo "--- server.log (last 40) ---"; tail -40 server.log 2>/dev/null; exit $rc' ERR + + ( rustfs server ./data --address 127.0.0.1:9000 >server.log 2>&1 & echo $! >server.pid ) + + # Storage init + listener take a while under wasmer; `mc alias set` pings the + # endpoint, so a successful alias doubles as the readiness probe. + up="" + for i in $(seq 1 90); do + sleep 1 + if mc alias set local http://127.0.0.1:9000 rustfsadmin rustfsadmin >/dev/null 2>&1; then up=1; break; fi + kill -0 "$(cat server.pid)" 2>/dev/null || { echo "FAIL: server exited during startup"; tail -40 server.log; exit 1; } + done + [ -n "$up" ] || { echo "FAIL: S3 endpoint never came up in 90s"; tail -40 server.log; exit 1; } + echo "server ready after ''${i}s" + + payload="hello from wasix under wasmer" + mc mb local/probe-bucket + printf '%s' "$payload" | mc pipe local/probe-bucket/greeting.txt + got=$(mc cat local/probe-bucket/greeting.txt || true) + + kill "$(cat server.pid)" 2>/dev/null || true + + if [ "$got" = "$payload" ]; then + echo "PASS: S3 put/get round-trip matched" + else + echo "FAIL: round-trip mismatch" + echo " wrote: $payload" + echo " read: $got" + tail -40 server.log + exit 1 + fi + ''; + }; +}