From c878a00885ff4fe96787be872f72345f08e45827 Mon Sep 17 00:00:00 2001 From: WaylandYang Date: Mon, 14 Sep 2026 12:54:12 +0800 Subject: [PATCH 1/3] fix(vmm): use the real FICLONE ioctl number 0x40209409 encodes a 32-byte payload; FICLONE is _IOW(0x94, 9, int) = 0x40049409. The kernel answered ENOTTY, which the fallback reads as no reflink, so every reflink_copy streamed a full copy on every filesystem. Reported by @jrimmer in #321. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 8 ++++++++ crates/forkd-vmm/src/chain.rs | 22 +++++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00594206..00162fb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ Versioning](https://semver.org/spec/v2.0.0.html) once it reaches ## Unreleased +### Reflink copies actually reflink + +`chain::reflink_copy` passed `0x40209409` as `FICLONE`; the real number is +`_IOW(0x94, 9, int)` = `0x40049409`. The kernel answered the wrong value with +`ENOTTY`, which the helper treats as "filesystem has no reflink", so every +chain-assembly base copy and the bake's rootfs baseline clone streamed a full +copy even on btrfs, XFS, and ZFS 2.2+. Found by @jrimmer in #321. + ### Upgrade note: legacy sandbox rows block startup The controller now persists a boot identity (start time + boot id) for every diff --git a/crates/forkd-vmm/src/chain.rs b/crates/forkd-vmm/src/chain.rs index e1e62c3e..56988626 100644 --- a/crates/forkd-vmm/src/chain.rs +++ b/crates/forkd-vmm/src/chain.rs @@ -275,9 +275,6 @@ fn copy_base_memory(src: &Path, dst: &Path) -> Result { // EINVAL/EXDEV/EOPNOTSUPP signal "this FS doesn't support // reflink for this pair," in which case we fall back. ENOTSUP // sometimes appears too. - // ioctl number: _IO(0x94, 9). 0x94 is the BTRFS_IOCTL_MAGIC also - // used by ficlone (overlayfs, btrfs, xfs, ext4-reflink). - const FICLONE: libc::c_ulong = 0x4020_9409; // SAFETY: both fds are valid open file descriptors; FICLONE // takes the source fd as its argument. let rc = unsafe { libc::ioctl(dst_f.as_raw_fd(), FICLONE, src_f.as_raw_fd()) }; @@ -341,8 +338,27 @@ fn fallback_stream_copy( Ok(n) } +/// `FICLONE` is `_IOW(0x94, 9, int)` = `0x4004_9409`. The kernel matches +/// the whole number, size field included, so any other value — the old +/// `0x4020_9409` encoded a 32-byte payload — is answered with ENOTTY, +/// which the fallback below reads as "no reflink here" and silently +/// streams a full copy even on btrfs/XFS/ZFS. +#[cfg(target_os = "linux")] +const FICLONE: libc::c_ulong = 0x4004_9409; + #[cfg(test)] mod tests { + /// `_IOW(type, nr, size)` = write dir (1 << 30) | size << 16 | type << 8 | nr. + #[cfg(target_os = "linux")] + #[test] + fn ficlone_is_iow_0x94_9_int() { + let iow = (1u64 << 30) + | ((std::mem::size_of::() as u64) << 16) + | (0x94 << 8) + | 9; + assert_eq!(super::FICLONE as u64, iow); + } + use super::*; use crate::VolumeSpec; use std::collections::HashMap; From 36a9a07e9b9bdd20c939f977a5f790ae6dcf8b8b Mon Sep 17 00:00:00 2001 From: WaylandYang Date: Mon, 14 Sep 2026 12:59:51 +0800 Subject: [PATCH 2/3] style: rustfmt the FICLONE derivation test Co-Authored-By: Claude Opus 5 --- crates/forkd-vmm/src/chain.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/forkd-vmm/src/chain.rs b/crates/forkd-vmm/src/chain.rs index 56988626..7b8ca664 100644 --- a/crates/forkd-vmm/src/chain.rs +++ b/crates/forkd-vmm/src/chain.rs @@ -352,10 +352,8 @@ mod tests { #[cfg(target_os = "linux")] #[test] fn ficlone_is_iow_0x94_9_int() { - let iow = (1u64 << 30) - | ((std::mem::size_of::() as u64) << 16) - | (0x94 << 8) - | 9; + let iow = + (1u64 << 30) | ((std::mem::size_of::() as u64) << 16) | (0x94 << 8) | 9; assert_eq!(super::FICLONE as u64, iow); } From 81c74c9ad50c69eb9e46e71f216373d5ae41fcf5 Mon Sep 17 00:00:00 2001 From: WaylandYang Date: Mon, 14 Sep 2026 13:00:48 +0800 Subject: [PATCH 3/3] test(vmm): compare FICLONE without a cast Co-Authored-By: Claude Opus 5 --- crates/forkd-vmm/src/chain.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/forkd-vmm/src/chain.rs b/crates/forkd-vmm/src/chain.rs index 7b8ca664..78448caf 100644 --- a/crates/forkd-vmm/src/chain.rs +++ b/crates/forkd-vmm/src/chain.rs @@ -352,9 +352,9 @@ mod tests { #[cfg(target_os = "linux")] #[test] fn ficlone_is_iow_0x94_9_int() { - let iow = - (1u64 << 30) | ((std::mem::size_of::() as u64) << 16) | (0x94 << 8) | 9; - assert_eq!(super::FICLONE as u64, iow); + let int_size = std::mem::size_of::() as libc::c_ulong; + let iow: libc::c_ulong = (1 << 30) | (int_size << 16) | (0x94 << 8) | 9; + assert_eq!(super::FICLONE, iow); } use super::*;