From d1c5524ec6ded3f11005f25a2666a8cbc54ed5f5 Mon Sep 17 00:00:00 2001 From: Geoffrey Goodman Date: Thu, 5 Jun 2025 15:59:23 -0400 Subject: [PATCH] Add krun_set_rootfs_kernel_args API This commit introduces a new API function `krun_set_rootfs_kernel_args` that allows callers to override kernel arguments related to the root filesystem, replacing the default "rootfstype=virtiofs rw" parameters in the kernel command line. New features: - Added RootfsConfig struct with builder pattern for type-safe configuration - Supports customizing root device, filesystem type, mount flags, and read-only mode - Falls back to sensible defaults: /dev/vda1, virtiofs, rw when parameters are NULL - Integrates with existing kernel command line construction in get_effective_kernel_cmdline() - Follows established patterns used by other config types like BlockDeviceConfig API signature: ```c int32_t krun_set_rootfs_kernel_args(uint32_t ctx_id, const char *device, const char *fs_type, const char *mount_flags, bool read_only); ``` Signed-off-by: Geoffrey Goodman --- include/libkrun.h | 21 ++++ src/libkrun/src/lib.rs | 109 ++++++++++++++++- src/vmm/src/vmm_config/mod.rs | 3 + src/vmm/src/vmm_config/rootfs.rs | 198 +++++++++++++++++++++++++++++++ 4 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 src/vmm/src/vmm_config/rootfs.rs diff --git a/include/libkrun.h b/include/libkrun.h index ffd12135c..cbb63cc3b 100644 --- a/include/libkrun.h +++ b/include/libkrun.h @@ -488,6 +488,27 @@ int32_t krun_set_kernel(uint32_t ctx_id, */ int32_t krun_set_env(uint32_t ctx_id, const char *const envp[]); +/** + * Sets the kernel boot arguments for the root filesystem. + * + * Arguments: + * "ctx_id" - the configuration context ID. + * "device" - the device on which the root filesystem should be found. If NULL, it will default + * to "/dev/vda1". + * "fs_type" - the type of the filesystem on the "device" device. If NULL, it will default to + * "virtiofs". + * "mount_flags" - additional mount flags to be used when mounting the root filesystem. + * "read_only" - when "true", mount the root filesystem read-only, otherwise mount rw. + * + * Returns: + * Zero on success or a negative error number on failure. + */ +int32_t krun_set_rootfs_kernel_args(uint32_t ctx_id, + const char *device, + const char *fs_type, + const char *mount_flags, + bool read_only); + /** * Sets the file path to the TEE configuration file. Only available in libkrun-sev. * diff --git a/src/libkrun/src/lib.rs b/src/libkrun/src/lib.rs index a778db2fe..6774e7e4f 100644 --- a/src/libkrun/src/lib.rs +++ b/src/libkrun/src/lib.rs @@ -48,6 +48,7 @@ use vmm::vmm_config::kernel_bundle::{InitrdBundle, QbootBundle}; use vmm::vmm_config::machine_config::VmConfig; #[cfg(feature = "net")] use vmm::vmm_config::net::NetworkInterfaceConfig; +use vmm::vmm_config::rootfs::RootfsConfig; use vmm::vmm_config::vsock::VsockDeviceConfig; // Value returned on success. We use libc's errors otherwise. @@ -151,6 +152,8 @@ struct ContextConfig { console_output: Option, vmm_uid: Option, vmm_gid: Option, + // Custom rootfs kernel arguments + rootfs_config: Option, } impl ContextConfig { @@ -249,6 +252,43 @@ impl ContextConfig { self.mac = Some(mac); } + fn set_rootfs_config(&mut self, config: RootfsConfig) { + self.rootfs_config = Some(config); + } + + fn get_rootfs_config(&self) -> Option<&RootfsConfig> { + self.rootfs_config.as_ref() + } + + fn get_effective_kernel_cmdline(&self) -> String { + if let Some(rootfs_config) = self.get_rootfs_config() { + // Replace rootfs-related parameters in DEFAULT_KERNEL_CMDLINE with custom ones + // DEFAULT_KERNEL_CMDLINE contains: "reboot=k panic=-1 panic_print=0 nomodule console=hvc0 rootfstype=virtiofs rw quiet no-kvmapf" + // We need to replace "rootfstype=virtiofs rw" with the custom args + let parts: Vec<&str> = DEFAULT_KERNEL_CMDLINE.split_whitespace().collect(); + let mut filtered_parts = Vec::new(); + + for part in parts { + // Skip existing rootfs-related parameters that we want to override + if !part.starts_with("rootfstype=") + && !part.starts_with("root=") + && !part.starts_with("rootflags=") + && part != "ro" + && part != "rw" + { + filtered_parts.push(part); + } + } + + // Add custom rootfs arguments + let rootfs_args = rootfs_config.to_kernel_args(); + filtered_parts.push(&rootfs_args); + filtered_parts.join(" ") + } else { + DEFAULT_KERNEL_CMDLINE.to_string() + } + } + fn set_port_map(&mut self, new_port_map: HashMap) -> Result<(), ()> { match &mut self.net_cfg { NetworkConfig::Tsi(tsi_config) => { @@ -945,6 +985,73 @@ pub unsafe extern "C" fn krun_set_env(ctx_id: u32, c_envp: *const *const c_char) KRUN_SUCCESS } +#[allow(clippy::missing_safety_doc)] +#[no_mangle] +pub unsafe extern "C" fn krun_set_rootfs_kernel_args( + ctx_id: u32, + c_device: *const c_char, + c_fs_type: *const c_char, + c_mount_flags: *const c_char, + read_only: bool, +) -> i32 { + let mut config = RootfsConfig::new(); + + // Set device if provided + if !c_device.is_null() { + let device = match CStr::from_ptr(c_device).to_str() { + Ok(s) => s, + Err(_) => return -libc::EINVAL, + }; + if !device.is_empty() { + config = match config.with_device(device) { + Ok(cfg) => cfg, + Err(_) => return -libc::EINVAL, + }; + } + } + + // Set filesystem type if provided + if !c_fs_type.is_null() { + let fs_type = match CStr::from_ptr(c_fs_type).to_str() { + Ok(s) => s, + Err(_) => return -libc::EINVAL, + }; + if !fs_type.is_empty() { + config = match config.with_fs_type(fs_type) { + Ok(cfg) => cfg, + Err(_) => return -libc::EINVAL, + }; + } + } + + // Set mount flags if provided + if !c_mount_flags.is_null() { + let mount_flags = match CStr::from_ptr(c_mount_flags).to_str() { + Ok(s) => s, + Err(_) => return -libc::EINVAL, + }; + if !mount_flags.is_empty() { + config = match config.with_mount_flags(mount_flags) { + Ok(cfg) => cfg, + Err(_) => return -libc::EINVAL, + }; + } + } + + // Set read-only flag + config = config.with_read_only(read_only); + + match CTX_MAP.lock().unwrap().entry(ctx_id) { + Entry::Occupied(mut ctx_cfg) => { + let cfg = ctx_cfg.get_mut(); + cfg.set_rootfs_config(config); + } + Entry::Vacant(_) => return -libc::ENOENT, + } + + KRUN_SUCCESS +} + #[allow(clippy::missing_safety_doc)] #[no_mangle] #[cfg(feature = "tee")] @@ -1464,7 +1571,7 @@ pub extern "C" fn krun_start_enter(ctx_id: u32) -> i32 { let boot_source = BootSourceConfig { kernel_cmdline_prolog: Some(format!( "{} init={} {} {} {} {}", - DEFAULT_KERNEL_CMDLINE, + ctx_cfg.get_effective_kernel_cmdline(), INIT_PATH, ctx_cfg.get_exec_path(), ctx_cfg.get_workdir(), diff --git a/src/vmm/src/vmm_config/mod.rs b/src/vmm/src/vmm_config/mod.rs index 9bd6dcf42..b7030bb79 100644 --- a/src/vmm/src/vmm_config/mod.rs +++ b/src/vmm/src/vmm_config/mod.rs @@ -11,6 +11,9 @@ pub mod boot_source; /// Wrapper for configuring an external kernel to be loaded in the microVM. pub mod external_kernel; +/// Wrapper for configuring the root filesystem kernel arguments. +pub mod rootfs; + /// Wrapper for configuring the Fs devices attached to the microVM. #[cfg(not(feature = "tee"))] pub mod fs; diff --git a/src/vmm/src/vmm_config/rootfs.rs b/src/vmm/src/vmm_config/rootfs.rs new file mode 100644 index 000000000..c22060e2e --- /dev/null +++ b/src/vmm/src/vmm_config/rootfs.rs @@ -0,0 +1,198 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use std::fmt; + +/// Errors associated with actions on `RootfsConfig`. +#[derive(Debug)] +pub enum RootfsConfigError { + /// Invalid device specification. + InvalidDevice(String), + /// Invalid filesystem type. + InvalidFsType(String), + /// Invalid mount flags. + InvalidMountFlags(String), +} + +impl fmt::Display for RootfsConfigError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::RootfsConfigError::*; + match *self { + InvalidDevice(ref e) => write!(f, "Invalid device specification: {}", e), + InvalidFsType(ref e) => write!(f, "Invalid filesystem type: {}", e), + InvalidMountFlags(ref e) => write!(f, "Invalid mount flags: {}", e), + } + } +} + +type RootfsResult = std::result::Result; + +/// Strongly typed data structure used to configure the root filesystem +/// kernel arguments for the microvm. +#[derive(Clone, Debug, Eq, PartialEq, Default)] +pub struct RootfsConfig { + /// The device on which the root filesystem should be found. + /// Defaults to "/dev/vda1" if not specified. + pub device: Option, + /// The type of the filesystem on the device. + /// Defaults to "virtiofs" if not specified. + pub fs_type: Option, + /// Additional mount flags to be used when mounting the root filesystem. + pub mount_flags: Option, + /// Whether to mount the root filesystem read-only. + /// If false, mount read-write. + pub read_only: bool, +} + +impl RootfsConfig { + /// Create a new RootfsConfig with default values + pub fn new() -> Self { + Self::default() + } + + /// Set the device for the root filesystem + pub fn with_device>(mut self, device: S) -> RootfsResult { + let device_str = device.into(); + if device_str.is_empty() { + return Err(RootfsConfigError::InvalidDevice( + "Device cannot be empty".to_string(), + )); + } + self.device = Some(device_str); + Ok(self) + } + + /// Set the filesystem type + pub fn with_fs_type>(mut self, fs_type: S) -> RootfsResult { + let fs_type_str = fs_type.into(); + if fs_type_str.is_empty() { + return Err(RootfsConfigError::InvalidFsType( + "Filesystem type cannot be empty".to_string(), + )); + } + self.fs_type = Some(fs_type_str); + Ok(self) + } + + /// Set the mount flags + pub fn with_mount_flags>(mut self, mount_flags: S) -> RootfsResult { + self.mount_flags = Some(mount_flags.into()); + Ok(self) + } + + /// Set whether the filesystem should be mounted read-only + pub fn with_read_only(mut self, read_only: bool) -> Self { + self.read_only = read_only; + self + } + + /// Get the device, with default fallback + pub fn get_device(&self) -> &str { + self.device.as_deref().unwrap_or("/dev/vda1") + } + + /// Get the filesystem type, with default fallback + pub fn get_fs_type(&self) -> &str { + self.fs_type.as_deref().unwrap_or("virtiofs") + } + + /// Get the mount flags, if any + pub fn get_mount_flags(&self) -> Option<&str> { + self.mount_flags.as_deref() + } + + /// Generate kernel command line arguments from this config + pub fn to_kernel_args(&self) -> String { + let mut args = Vec::new(); + + // Add root device + args.push(format!("root={}", self.get_device())); + + // Add filesystem type + args.push(format!("rootfstype={}", self.get_fs_type())); + + // Add mount flags if specified + if let Some(flags) = self.get_mount_flags() { + if !flags.is_empty() { + args.push(format!("rootflags={}", flags)); + } + } + + // Add read-only or read-write + if self.read_only { + args.push("ro".to_string()); + } else { + args.push("rw".to_string()); + } + + args.join(" ") + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_default_config() { + let config = RootfsConfig::new(); + assert_eq!(config.get_device(), "/dev/vda1"); + assert_eq!(config.get_fs_type(), "virtiofs"); + assert_eq!(config.get_mount_flags(), None); + assert!(!config.read_only); + } + + #[test] + fn test_custom_config() { + let config = RootfsConfig::new() + .with_device("/dev/vdb1") + .unwrap() + .with_fs_type("ext4") + .unwrap() + .with_mount_flags("noatime,discard") + .unwrap() + .with_read_only(true); + + assert_eq!(config.get_device(), "/dev/vdb1"); + assert_eq!(config.get_fs_type(), "ext4"); + assert_eq!(config.get_mount_flags(), Some("noatime,discard")); + assert!(config.read_only); + } + + #[test] + fn test_to_kernel_args_default() { + let config = RootfsConfig::new(); + let args = config.to_kernel_args(); + assert_eq!(args, "root=/dev/vda1 rootfstype=virtiofs rw"); + } + + #[test] + fn test_to_kernel_args_custom() { + let config = RootfsConfig::new() + .with_device("/dev/vdb1") + .unwrap() + .with_fs_type("ext4") + .unwrap() + .with_mount_flags("noatime,discard") + .unwrap() + .with_read_only(true); + + let args = config.to_kernel_args(); + assert_eq!( + args, + "root=/dev/vdb1 rootfstype=ext4 rootflags=noatime,discard ro" + ); + } + + #[test] + fn test_invalid_device() { + let result = RootfsConfig::new().with_device(""); + assert!(result.is_err()); + } + + #[test] + fn test_invalid_fs_type() { + let result = RootfsConfig::new().with_fs_type(""); + assert!(result.is_err()); + } +}