Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/vmm/src/devices/virtio/block/vhost_user/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ impl TryFrom<&BlockDeviceConfig> for VhostUserBlockConfig {
&& value.path_on_host.is_none()
&& value.rate_limiter.is_none()
&& value.file_engine_type.is_none()
&& value.is_direct.is_none()
{
Ok(Self {
drive_id: value.drive_id.clone(),
Expand Down Expand Up @@ -101,6 +102,7 @@ impl From<VhostUserBlockConfig> for BlockDeviceConfig {
path_on_host: None,
rate_limiter: None,
file_engine_type: None,
is_direct: None,

socket: Some(value.socket),
}
Expand Down Expand Up @@ -405,6 +407,7 @@ mod tests {
path_on_host: None,
rate_limiter: None,
file_engine_type: None,
is_direct: None,

socket: Some("sock".to_string()),
};
Expand All @@ -420,6 +423,7 @@ mod tests {
path_on_host: Some("path".to_string()),
rate_limiter: None,
file_engine_type: Some(FileEngineType::Sync),
is_direct: Some(true),

socket: None,
};
Expand All @@ -435,6 +439,7 @@ mod tests {
path_on_host: Some("path".to_string()),
rate_limiter: None,
file_engine_type: Some(FileEngineType::Sync),
is_direct: Some(true),

socket: Some("sock".to_string()),
};
Expand Down
50 changes: 39 additions & 11 deletions src/vmm/src/devices/virtio/block/virtio/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::fs::{File, OpenOptions};
use std::io::{Seek, SeekFrom};
use std::ops::Deref;
use std::os::linux::fs::MetadataExt;
use std::os::unix::fs::OpenOptionsExt;
use std::path::PathBuf;
use std::sync::Arc;

Expand Down Expand Up @@ -63,10 +64,19 @@ pub struct DiskProperties {

impl DiskProperties {
// Helper function that opens the file with the proper access permissions
fn open_file(disk_image_path: &str, is_disk_read_only: bool) -> Result<File, VirtioBlockError> {
OpenOptions::new()
.read(true)
.write(!is_disk_read_only)
fn open_file(
disk_image_path: &str,
is_disk_read_only: bool,
is_direct: bool,
) -> Result<File, VirtioBlockError> {
let mut options = OpenOptions::new();
options.read(true).write(!is_disk_read_only);

if is_direct {
options.custom_flags(libc::O_DIRECT);
}

options
.open(PathBuf::from(&disk_image_path))
.map_err(|x| VirtioBlockError::BackingFile(x, disk_image_path.to_string()))
}
Expand Down Expand Up @@ -95,8 +105,9 @@ impl DiskProperties {
disk_image_path: String,
is_disk_read_only: bool,
file_engine_type: FileEngineType,
is_direct: bool,
) -> Result<Self, VirtioBlockError> {
let mut disk_image = Self::open_file(&disk_image_path, is_disk_read_only)?;
let mut disk_image = Self::open_file(&disk_image_path, is_disk_read_only, is_direct)?;
let disk_size = Self::file_size(&disk_image_path, &mut disk_image)?;
let image_id = Self::build_disk_image_id(&disk_image);

Expand All @@ -114,8 +125,9 @@ impl DiskProperties {
&mut self,
disk_image_path: String,
is_disk_read_only: bool,
is_direct: bool,
) -> Result<(), VirtioBlockError> {
let mut disk_image = Self::open_file(&disk_image_path, is_disk_read_only)?;
let mut disk_image = Self::open_file(&disk_image_path, is_disk_read_only, is_direct)?;
let disk_size = Self::file_size(&disk_image_path, &mut disk_image)?;

self.image_id = Self::build_disk_image_id(&disk_image);
Expand Down Expand Up @@ -198,6 +210,8 @@ pub struct VirtioBlockConfig {
#[serde(default)]
#[serde(rename = "io_engine")]
pub file_engine_type: FileEngineType,
/// If set to true, the drive is opened with O_DIRECT.
pub is_direct: bool,
}

impl TryFrom<&BlockDeviceConfig> for VirtioBlockConfig {
Expand All @@ -215,6 +229,7 @@ impl TryFrom<&BlockDeviceConfig> for VirtioBlockConfig {
path_on_host: value.path_on_host.as_ref().unwrap().clone(),
rate_limiter: value.rate_limiter,
file_engine_type: value.file_engine_type.unwrap_or_default(),
is_direct: value.is_direct.unwrap_or(false),
})
} else {
Err(VirtioBlockError::Config)
Expand All @@ -234,6 +249,7 @@ impl From<VirtioBlockConfig> for BlockDeviceConfig {
path_on_host: Some(value.path_on_host),
rate_limiter: value.rate_limiter,
file_engine_type: Some(value.file_engine_type),
is_direct: Some(value.is_direct),

socket: None,
}
Expand All @@ -260,6 +276,7 @@ pub struct VirtioBlock {
pub cache_type: CacheType,
pub root_device: bool,
pub read_only: bool,
pub direct: bool,

// Host file and properties.
pub disk: DiskProperties,
Expand Down Expand Up @@ -289,6 +306,7 @@ impl VirtioBlock {
config.path_on_host,
config.is_read_only,
config.file_engine_type,
config.is_direct,
)?;

let rate_limiter = config
Expand Down Expand Up @@ -331,6 +349,7 @@ impl VirtioBlock {
cache_type: config.cache_type,
root_device: config.is_root_device,
read_only: config.is_read_only,
direct: config.is_direct,

disk: disk_properties,
rate_limiter,
Expand All @@ -351,6 +370,7 @@ impl VirtioBlock {
cache_type: self.cache_type,
rate_limiter: rl.into_option(),
file_engine_type: self.file_engine_type(),
is_direct: self.direct,
}
}

Expand Down Expand Up @@ -536,7 +556,8 @@ impl VirtioBlock {

/// Update the backing file and the config space of the block device.
pub fn update_disk_image(&mut self, disk_image_path: String) -> Result<(), VirtioBlockError> {
self.disk.update(disk_image_path, self.read_only)?;
self.disk
.update(disk_image_path, self.read_only, self.direct)?;
self.config_space.capacity = self.disk.nsectors.to_le(); // virtio_block_config_space();

// Kick the driver to pick up the changes. (But only if the device is already activated).
Expand Down Expand Up @@ -723,6 +744,7 @@ mod tests {
path_on_host: Some("path".to_string()),
rate_limiter: None,
file_engine_type: Default::default(),
is_direct: Some(true),

socket: None,
};
Expand All @@ -738,6 +760,7 @@ mod tests {
path_on_host: None,
rate_limiter: None,
file_engine_type: Default::default(),
is_direct: None,

socket: Some("sock".to_string()),
};
Expand All @@ -753,6 +776,7 @@ mod tests {
path_on_host: Some("path".to_string()),
rate_limiter: None,
file_engine_type: Default::default(),
is_direct: Some(true),

socket: Some("sock".to_string()),
};
Expand All @@ -767,16 +791,20 @@ mod tests {
f.as_file().set_len(size).unwrap();

for engine in [FileEngineType::Sync, FileEngineType::Async] {
let disk_properties =
DiskProperties::new(String::from(f.as_path().to_str().unwrap()), true, engine)
.unwrap();
let disk_properties = DiskProperties::new(
String::from(f.as_path().to_str().unwrap()),
true,
engine,
true,
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test now calls DiskProperties::new(..., is_direct: true) on a TempFile. Opening temp files with O_DIRECT is not supported on some common CI filesystems (e.g., tmpfs), which can cause spurious EINVAL failures. Consider keeping is_direct as false for these temp-file-based tests, or explicitly skip/feature-gate the test when O_DIRECT is unsupported on the backing filesystem.

Suggested change
true,
false,

Copilot uses AI. Check for mistakes.
)
.unwrap();
Comment on lines +794 to +800
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test_disk_backing_file_helper unit test now uses is_direct = true with a TempFile. O_DIRECT is not supported on some common temp filesystems (e.g. tmpfs), which can make this test fail depending on where it runs. Consider using is_direct = false here, or conditionally skipping when opening with O_DIRECT returns EINVAL.

Copilot uses AI. Check for mistakes.

assert_eq!(size, u64::from(SECTOR_SIZE) * num_sectors);
assert_eq!(disk_properties.nsectors, num_sectors);
// Testing `backing_file.virtio_block_disk_image_id()` implies
// duplicating that logic in tests, so skipping it.

let res = DiskProperties::new("invalid-disk-path".to_string(), true, engine);
let res = DiskProperties::new("invalid-disk-path".to_string(), true, engine, true);
assert!(
matches!(res, Err(VirtioBlockError::BackingFile(_, _))),
"{:?}",
Expand Down
7 changes: 7 additions & 0 deletions src/vmm/src/devices/virtio/block/virtio/persist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ pub struct VirtioBlockState {
pub virtio_state: VirtioDeviceState,
rate_limiter_state: RateLimiterState,
file_engine_type: FileEngineTypeState,
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VirtioBlockState adds a new required field is_direct: bool without a serde default. Because snapshots accept any patch version within the same major.minor, restoring an older snapshot that lacks this field will fail deserialization. Make this field backward-compatible (e.g., add #[serde(default)] and default to false, or change to Option<bool> and treat None as false during restore).

Suggested change
file_engine_type: FileEngineTypeState,
file_engine_type: FileEngineTypeState,
#[serde(default)]

Copilot uses AI. Check for mistakes.
direct: Option<bool>,
}

impl Persist<'_> for VirtioBlock {
Expand All @@ -79,6 +80,7 @@ impl Persist<'_> for VirtioBlock {
virtio_state: VirtioDeviceState::from_device(self),
rate_limiter_state: self.rate_limiter.save(),
file_engine_type: FileEngineTypeState::from(self.file_engine_type()),
direct: Some(self.direct),
}
}

Expand All @@ -89,11 +91,13 @@ impl Persist<'_> for VirtioBlock {
let is_read_only = state.virtio_state.avail_features & (1u64 << VIRTIO_BLK_F_RO) != 0;
let rate_limiter = RateLimiter::restore((), &state.rate_limiter_state)
.map_err(VirtioBlockError::RateLimiter)?;
let is_direct = state.direct.unwrap_or(false);

let disk_properties = DiskProperties::new(
state.disk_path.clone(),
is_read_only,
state.file_engine_type.into(),
is_direct,
)?;

let queue_evts = [EventFd::new(libc::EFD_NONBLOCK).map_err(VirtioBlockError::EventFd)?];
Expand Down Expand Up @@ -130,6 +134,7 @@ impl Persist<'_> for VirtioBlock {
cache_type: state.cache_type,
root_device: state.root_device,
read_only: is_read_only,
direct: is_direct,

disk: disk_properties,
rate_limiter,
Expand Down Expand Up @@ -164,6 +169,7 @@ mod tests {
cache_type: CacheType::Writeback,
rate_limiter: None,
file_engine_type: FileEngineType::default(),
is_direct: false,
};

let block = VirtioBlock::new(config).unwrap();
Expand Down Expand Up @@ -208,6 +214,7 @@ mod tests {
cache_type: CacheType::Unsafe,
rate_limiter: None,
file_engine_type: FileEngineType::default(),
is_direct: false,
};

let block = VirtioBlock::new(config).unwrap();
Expand Down
1 change: 1 addition & 0 deletions src/vmm/src/devices/virtio/block/virtio/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub fn default_block_with_path(path: String, file_engine_type: FileEngineType) -
}),
}),
file_engine_type,
is_direct: false,
};

// The default block device is read-write and non-root.
Expand Down
2 changes: 2 additions & 0 deletions src/vmm/src/vmm_config/drive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ pub struct BlockDeviceConfig {
// pub file_engine_type: FileEngineType,
#[serde(rename = "io_engine")]
pub file_engine_type: Option<FileEngineType>,
/// If set to true, the drive is opened with O_DIRECT.
pub is_direct: Option<bool>,
Comment on lines +63 to +64
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BlockDeviceConfig gained a new is_direct field, but the test-only manual Clone impl and the many BlockDeviceConfig { ... } struct literals in this file haven't been updated to include it. As written, this will fail to compile; please add is_direct to the Clone impl and initialize it (e.g. None) in test configs as needed (or use ..Default::default()).

Copilot uses AI. Check for mistakes.

// VhostUserBlock specific fields
/// Path to the vhost-user socket.
Expand Down
Loading