From d7dc37ca47e6c3a368ed512961808b7b902d0d8b Mon Sep 17 00:00:00 2001 From: Jun Ryoung Ju Date: Thu, 8 Oct 2026 01:27:37 +0900 Subject: [PATCH] virtio-blk: add sync option to select libkrun's sync mode krun_add_disk2 picks the flush behavior itself, and on macOS that is KRUN_SYNC_RELAXED: guest flushes become fsync(2), which does not ask the drive to flush its buffers. Expose krun_add_disk3's sync_mode as sync=none|relaxed|full; without the option krunkit keeps calling krun_add_disk2, so the default is unchanged. Signed-off-by: Jun Ryoung Ju --- docs/usage.md | 8 ++++++ src/cmdline.rs | 27 +++++++++++++++++++- src/virtio.rs | 68 ++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 94 insertions(+), 9 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 3ea2e2b..a2835c3 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -93,6 +93,14 @@ as a virtual machine's data disk(s) (`/dev/vd[b-z]`). - `path`: Path to the disk image file. - `format`: Format of the disk image. Supported formats: raw, qcow2. +- `sync`: (Optional) How guest flush requests are carried out on the host. Unset, libkrun's +platform default applies, which on macOS is `relaxed`. + - `full`: on macOS, `fcntl(F_FULLFSYNC)`, which asks the drive to flush all buffered data to + permanent storage. + - `relaxed`: on macOS, `fsync(2)`, which moves data to the drive but does not ask the drive to + flush its own buffers; per fsync(2), on a drive power loss some or none of that data may be + written, possibly out of order. Same as `full` on Linux. + - `none`: the device does not offer flush to the guest. #### Example diff --git a/src/cmdline.rs b/src/cmdline.rs index 342ac00..a08f3f1 100644 --- a/src/cmdline.rs +++ b/src/cmdline.rs @@ -330,6 +330,29 @@ mod tests { assert_eq!(in_order, expected); } + #[test] + fn virtio_blk_sync_mode() { + use crate::virtio::{BlkConfig, DiskSyncMode}; + use std::str::FromStr; + + let blk = |s: &str| BlkConfig::from_str(s).map(|b| b.sync); + + assert_eq!(blk("path=/Users/user/d.raw").unwrap(), None); + assert_eq!( + blk("path=/Users/user/d.raw,sync=none").unwrap(), + Some(DiskSyncMode::None) + ); + assert_eq!( + blk("path=/Users/user/d.raw,sync=relaxed").unwrap(), + Some(DiskSyncMode::Relaxed) + ); + assert_eq!( + blk("path=/Users/user/d.raw,sync=full").unwrap(), + Some(DiskSyncMode::Full) + ); + assert!(blk("path=/Users/user/d.raw,sync=fsync").is_err()); + } + #[test] fn virtio_net_argument_ordering() { let in_order = super::parse_args(String::from( @@ -480,7 +503,7 @@ mod tests { "--device", "virtio-serial,logFilePath=/Users/user/serial.log", "--device", - "virtio-blk,path=/Users/user/data.raw,format=raw", + "virtio-blk,path=/Users/user/data.raw,format=raw,sync=full", "--device", "virtio-vsock,port=1024,socketURL=/Users/user/vsock1.sock,listen", "--device", @@ -703,6 +726,7 @@ mod tests { if let VirtioDeviceConfig::Blk(blk) = blk { assert_eq!(blk.path, PathBuf::from_str("/Users/user/data.raw").unwrap()); assert_eq!(blk.format, DiskImageFormat::Raw); + assert_eq!(blk.sync, Some(DiskSyncMode::Full)); } else { panic!("expected virtio-blk device as 4th device config argument"); } @@ -739,6 +763,7 @@ mod tests { PathBuf::from_str("/Users/user/root.qcow2").unwrap() ); assert_eq!(blk.format, DiskImageFormat::Qcow2); + assert_eq!(blk.sync, None); } else { panic!("expected virtio-blk device as 1st device config argument"); } diff --git a/src/virtio.rs b/src/virtio.rs index 1d3baf3..a71afc1 100644 --- a/src/virtio.rs +++ b/src/virtio.rs @@ -50,6 +50,15 @@ extern "C" { disk_format: u32, read_only: bool, ) -> i32; + fn krun_add_disk3( + ctx_id: u32, + c_block_id: *const c_char, + c_disk_path: *const c_char, + disk_format: u32, + read_only: bool, + direct_io: bool, + sync_mode: u32, + ) -> i32; fn krun_add_vsock_port2(ctx_id: u32, port: u32, c_filepath: *const c_char, listen: bool) -> i32; fn krun_add_virtiofs4( @@ -99,6 +108,31 @@ impl FromStr for DiskImageFormat { } } +/// How a virtio-blk device honors guest flush requests. Values match KRUN_SYNC_* in libkrun.h. +#[repr(u32)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum DiskSyncMode { + /// Ignore VIRTIO_BLK_F_FLUSH. + None = 0, + /// Honor flushes; on macOS, fsync(2) without asking the drive to flush its cache. + Relaxed = 1, + /// Honor flushes down to physical media (F_FULLFSYNC on macOS). + Full = 2, +} + +impl FromStr for DiskSyncMode { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "none" => Ok(DiskSyncMode::None), + "relaxed" => Ok(DiskSyncMode::Relaxed), + "full" => Ok(DiskSyncMode::Full), + _ => Err(anyhow!("unsupported disk sync mode")), + } + } +} + #[derive(Clone, Debug, Default, PartialEq)] pub enum FsPermissions { Complete = 0, @@ -194,6 +228,9 @@ pub struct BlkConfig { /// Format of the disk image. pub format: DiskImageFormat, + + /// Flush handling. None leaves the choice to libkrun's platform default. + pub sync: Option, } impl FromStr for BlkConfig { @@ -212,6 +249,10 @@ impl FromStr for BlkConfig { blk_config.format = DiskImageFormat::from_str(f.as_str())?; } + if let Some(s) = args.remove("sync") { + blk_config.sync = Some(DiskSyncMode::from_str(s.as_str())?); + } + check_unknown_args(args, "virtio-blk")?; Ok(blk_config) @@ -228,14 +269,25 @@ impl KrunContextSet for BlkConfig { let block_id_cstr = CString::new(basename).context("can't convert basename to cstring")?; let path_cstr = path_to_cstring(&self.path)?; - if krun_add_disk2( - id, - block_id_cstr.as_ptr(), - path_cstr.as_ptr(), - self.format as u32, - false, - ) < 0 - { + let ret = match self.sync { + None => krun_add_disk2( + id, + block_id_cstr.as_ptr(), + path_cstr.as_ptr(), + self.format as u32, + false, + ), + Some(sync) => krun_add_disk3( + id, + block_id_cstr.as_ptr(), + path_cstr.as_ptr(), + self.format as u32, + false, + false, + sync as u32, + ), + }; + if ret < 0 { return Err(anyhow!(format!( "unable to set virtio-blk disk for {}", self.path.display()