Skip to content

Commit 3e46947

Browse files
committed
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 <[email protected]>
1 parent 05bbf88 commit 3e46947

File tree

4 files changed

+330
-1
lines changed

4 files changed

+330
-1
lines changed

include/libkrun.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,27 @@ int32_t krun_set_kernel(uint32_t ctx_id,
488488
*/
489489
int32_t krun_set_env(uint32_t ctx_id, const char *const envp[]);
490490

491+
/**
492+
* Sets the kernel boot arguments for the root filesystem.
493+
*
494+
* Arguments:
495+
* "ctx_id" - the configuration context ID.
496+
* "device" - the device on which the root filesystem should be found. If NULL, it will default
497+
* to "/dev/vda1".
498+
* "fs_type" - the type of the filesystem on the "device" device. If NULL, it will default to
499+
* "virtiofs".
500+
* "mount_flags" - additional mount flags to be used when mounting the root filesystem.
501+
* "read_only" - when "true", mount the root filesystem read-only, otherwise mount rw.
502+
*
503+
* Returns:
504+
* Zero on success or a negative error number on failure.
505+
*/
506+
int32_t krun_set_rootfs_kernel_args(uint32_t ctx_id,
507+
const char *device,
508+
const char *fs_type,
509+
const char *mount_flags,
510+
bool read_only);
511+
491512
/**
492513
* Sets the file path to the TEE configuration file. Only available in libkrun-sev.
493514
*

src/libkrun/src/lib.rs

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ use vmm::vmm_config::kernel_bundle::{InitrdBundle, QbootBundle};
4848
use vmm::vmm_config::machine_config::VmConfig;
4949
#[cfg(feature = "net")]
5050
use vmm::vmm_config::net::NetworkInterfaceConfig;
51+
use vmm::vmm_config::rootfs::RootfsConfig;
5152
use vmm::vmm_config::vsock::VsockDeviceConfig;
5253

5354
// Value returned on success. We use libc's errors otherwise.
@@ -151,6 +152,8 @@ struct ContextConfig {
151152
console_output: Option<PathBuf>,
152153
vmm_uid: Option<libc::uid_t>,
153154
vmm_gid: Option<libc::gid_t>,
155+
// Custom rootfs kernel arguments
156+
rootfs_config: Option<RootfsConfig>,
154157
}
155158

156159
impl ContextConfig {
@@ -249,6 +252,43 @@ impl ContextConfig {
249252
self.mac = Some(mac);
250253
}
251254

255+
fn set_rootfs_config(&mut self, config: RootfsConfig) {
256+
self.rootfs_config = Some(config);
257+
}
258+
259+
fn get_rootfs_config(&self) -> Option<&RootfsConfig> {
260+
self.rootfs_config.as_ref()
261+
}
262+
263+
fn get_effective_kernel_cmdline(&self) -> String {
264+
if let Some(rootfs_config) = self.get_rootfs_config() {
265+
// Replace rootfs-related parameters in DEFAULT_KERNEL_CMDLINE with custom ones
266+
// DEFAULT_KERNEL_CMDLINE contains: "reboot=k panic=-1 panic_print=0 nomodule console=hvc0 rootfstype=virtiofs rw quiet no-kvmapf"
267+
// We need to replace "rootfstype=virtiofs rw" with the custom args
268+
let parts: Vec<&str> = DEFAULT_KERNEL_CMDLINE.split_whitespace().collect();
269+
let mut filtered_parts = Vec::new();
270+
271+
for part in parts {
272+
// Skip existing rootfs-related parameters that we want to override
273+
if !part.starts_with("rootfstype=")
274+
&& !part.starts_with("root=")
275+
&& !part.starts_with("rootflags=")
276+
&& part != "ro"
277+
&& part != "rw"
278+
{
279+
filtered_parts.push(part);
280+
}
281+
}
282+
283+
// Add custom rootfs arguments
284+
let rootfs_args = rootfs_config.to_kernel_args();
285+
filtered_parts.push(&rootfs_args);
286+
filtered_parts.join(" ")
287+
} else {
288+
DEFAULT_KERNEL_CMDLINE.to_string()
289+
}
290+
}
291+
252292
fn set_port_map(&mut self, new_port_map: HashMap<u16, u16>) -> Result<(), ()> {
253293
match &mut self.net_cfg {
254294
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)
945985
KRUN_SUCCESS
946986
}
947987

988+
#[allow(clippy::missing_safety_doc)]
989+
#[no_mangle]
990+
pub unsafe extern "C" fn krun_set_rootfs_kernel_args(
991+
ctx_id: u32,
992+
c_device: *const c_char,
993+
c_fs_type: *const c_char,
994+
c_mount_flags: *const c_char,
995+
read_only: bool,
996+
) -> i32 {
997+
let mut config = RootfsConfig::new();
998+
999+
// Set device if provided
1000+
if !c_device.is_null() {
1001+
let device = match CStr::from_ptr(c_device).to_str() {
1002+
Ok(s) => s,
1003+
Err(_) => return -libc::EINVAL,
1004+
};
1005+
if !device.is_empty() {
1006+
config = match config.with_device(device) {
1007+
Ok(cfg) => cfg,
1008+
Err(_) => return -libc::EINVAL,
1009+
};
1010+
}
1011+
}
1012+
1013+
// Set filesystem type if provided
1014+
if !c_fs_type.is_null() {
1015+
let fs_type = match CStr::from_ptr(c_fs_type).to_str() {
1016+
Ok(s) => s,
1017+
Err(_) => return -libc::EINVAL,
1018+
};
1019+
if !fs_type.is_empty() {
1020+
config = match config.with_fs_type(fs_type) {
1021+
Ok(cfg) => cfg,
1022+
Err(_) => return -libc::EINVAL,
1023+
};
1024+
}
1025+
}
1026+
1027+
// Set mount flags if provided
1028+
if !c_mount_flags.is_null() {
1029+
let mount_flags = match CStr::from_ptr(c_mount_flags).to_str() {
1030+
Ok(s) => s,
1031+
Err(_) => return -libc::EINVAL,
1032+
};
1033+
if !mount_flags.is_empty() {
1034+
config = match config.with_mount_flags(mount_flags) {
1035+
Ok(cfg) => cfg,
1036+
Err(_) => return -libc::EINVAL,
1037+
};
1038+
}
1039+
}
1040+
1041+
// Set read-only flag
1042+
config = config.with_read_only(read_only);
1043+
1044+
match CTX_MAP.lock().unwrap().entry(ctx_id) {
1045+
Entry::Occupied(mut ctx_cfg) => {
1046+
let cfg = ctx_cfg.get_mut();
1047+
cfg.set_rootfs_config(config);
1048+
}
1049+
Entry::Vacant(_) => return -libc::ENOENT,
1050+
}
1051+
1052+
KRUN_SUCCESS
1053+
}
1054+
9481055
#[allow(clippy::missing_safety_doc)]
9491056
#[no_mangle]
9501057
#[cfg(feature = "tee")]
@@ -1464,7 +1571,7 @@ pub extern "C" fn krun_start_enter(ctx_id: u32) -> i32 {
14641571
let boot_source = BootSourceConfig {
14651572
kernel_cmdline_prolog: Some(format!(
14661573
"{} init={} {} {} {} {}",
1467-
DEFAULT_KERNEL_CMDLINE,
1574+
ctx_cfg.get_effective_kernel_cmdline(),
14681575
INIT_PATH,
14691576
ctx_cfg.get_exec_path(),
14701577
ctx_cfg.get_workdir(),

src/vmm/src/vmm_config/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ pub mod boot_source;
1111
/// Wrapper for configuring an external kernel to be loaded in the microVM.
1212
pub mod external_kernel;
1313

14+
/// Wrapper for configuring the root filesystem kernel arguments.
15+
pub mod rootfs;
16+
1417
/// Wrapper for configuring the Fs devices attached to the microVM.
1518
#[cfg(not(feature = "tee"))]
1619
pub mod fs;

src/vmm/src/vmm_config/rootfs.rs

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use std::fmt;
5+
6+
/// Errors associated with actions on `RootfsConfig`.
7+
#[derive(Debug)]
8+
pub enum RootfsConfigError {
9+
/// Invalid device specification.
10+
InvalidDevice(String),
11+
/// Invalid filesystem type.
12+
InvalidFsType(String),
13+
/// Invalid mount flags.
14+
InvalidMountFlags(String),
15+
}
16+
17+
impl fmt::Display for RootfsConfigError {
18+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
19+
use self::RootfsConfigError::*;
20+
match *self {
21+
InvalidDevice(ref e) => write!(f, "Invalid device specification: {}", e),
22+
InvalidFsType(ref e) => write!(f, "Invalid filesystem type: {}", e),
23+
InvalidMountFlags(ref e) => write!(f, "Invalid mount flags: {}", e),
24+
}
25+
}
26+
}
27+
28+
type RootfsResult<T> = std::result::Result<T, RootfsConfigError>;
29+
30+
/// Strongly typed data structure used to configure the root filesystem
31+
/// kernel arguments for the microvm.
32+
#[derive(Clone, Debug, Eq, PartialEq, Default)]
33+
pub struct RootfsConfig {
34+
/// The device on which the root filesystem should be found.
35+
/// Defaults to "/dev/vda1" if not specified.
36+
pub device: Option<String>,
37+
/// The type of the filesystem on the device.
38+
/// Defaults to "virtiofs" if not specified.
39+
pub fs_type: Option<String>,
40+
/// Additional mount flags to be used when mounting the root filesystem.
41+
pub mount_flags: Option<String>,
42+
/// Whether to mount the root filesystem read-only.
43+
/// If false, mount read-write.
44+
pub read_only: bool,
45+
}
46+
47+
impl RootfsConfig {
48+
/// Create a new RootfsConfig with default values
49+
pub fn new() -> Self {
50+
Self::default()
51+
}
52+
53+
/// Set the device for the root filesystem
54+
pub fn with_device<S: Into<String>>(mut self, device: S) -> RootfsResult<Self> {
55+
let device_str = device.into();
56+
if device_str.is_empty() {
57+
return Err(RootfsConfigError::InvalidDevice(
58+
"Device cannot be empty".to_string(),
59+
));
60+
}
61+
self.device = Some(device_str);
62+
Ok(self)
63+
}
64+
65+
/// Set the filesystem type
66+
pub fn with_fs_type<S: Into<String>>(mut self, fs_type: S) -> RootfsResult<Self> {
67+
let fs_type_str = fs_type.into();
68+
if fs_type_str.is_empty() {
69+
return Err(RootfsConfigError::InvalidFsType(
70+
"Filesystem type cannot be empty".to_string(),
71+
));
72+
}
73+
self.fs_type = Some(fs_type_str);
74+
Ok(self)
75+
}
76+
77+
/// Set the mount flags
78+
pub fn with_mount_flags<S: Into<String>>(mut self, mount_flags: S) -> RootfsResult<Self> {
79+
self.mount_flags = Some(mount_flags.into());
80+
Ok(self)
81+
}
82+
83+
/// Set whether the filesystem should be mounted read-only
84+
pub fn with_read_only(mut self, read_only: bool) -> Self {
85+
self.read_only = read_only;
86+
self
87+
}
88+
89+
/// Get the device, with default fallback
90+
pub fn get_device(&self) -> &str {
91+
self.device.as_deref().unwrap_or("/dev/vda1")
92+
}
93+
94+
/// Get the filesystem type, with default fallback
95+
pub fn get_fs_type(&self) -> &str {
96+
self.fs_type.as_deref().unwrap_or("virtiofs")
97+
}
98+
99+
/// Get the mount flags, if any
100+
pub fn get_mount_flags(&self) -> Option<&str> {
101+
self.mount_flags.as_deref()
102+
}
103+
104+
/// Generate kernel command line arguments from this config
105+
pub fn to_kernel_args(&self) -> String {
106+
let mut args = Vec::new();
107+
108+
// Add root device
109+
args.push(format!("root={}", self.get_device()));
110+
111+
// Add filesystem type
112+
args.push(format!("rootfstype={}", self.get_fs_type()));
113+
114+
// Add mount flags if specified
115+
if let Some(flags) = self.get_mount_flags() {
116+
if !flags.is_empty() {
117+
args.push(format!("rootflags={}", flags));
118+
}
119+
}
120+
121+
// Add read-only or read-write
122+
if self.read_only {
123+
args.push("ro".to_string());
124+
} else {
125+
args.push("rw".to_string());
126+
}
127+
128+
args.join(" ")
129+
}
130+
}
131+
132+
#[cfg(test)]
133+
mod tests {
134+
use super::*;
135+
136+
#[test]
137+
fn test_default_config() {
138+
let config = RootfsConfig::new();
139+
assert_eq!(config.get_device(), "/dev/vda1");
140+
assert_eq!(config.get_fs_type(), "virtiofs");
141+
assert_eq!(config.get_mount_flags(), None);
142+
assert!(!config.read_only);
143+
}
144+
145+
#[test]
146+
fn test_custom_config() {
147+
let config = RootfsConfig::new()
148+
.with_device("/dev/vdb1")
149+
.unwrap()
150+
.with_fs_type("ext4")
151+
.unwrap()
152+
.with_mount_flags("noatime,discard")
153+
.unwrap()
154+
.with_read_only(true);
155+
156+
assert_eq!(config.get_device(), "/dev/vdb1");
157+
assert_eq!(config.get_fs_type(), "ext4");
158+
assert_eq!(config.get_mount_flags(), Some("noatime,discard"));
159+
assert!(config.read_only);
160+
}
161+
162+
#[test]
163+
fn test_to_kernel_args_default() {
164+
let config = RootfsConfig::new();
165+
let args = config.to_kernel_args();
166+
assert_eq!(args, "root=/dev/vda1 rootfstype=virtiofs rw");
167+
}
168+
169+
#[test]
170+
fn test_to_kernel_args_custom() {
171+
let config = RootfsConfig::new()
172+
.with_device("/dev/vdb1")
173+
.unwrap()
174+
.with_fs_type("ext4")
175+
.unwrap()
176+
.with_mount_flags("noatime,discard")
177+
.unwrap()
178+
.with_read_only(true);
179+
180+
let args = config.to_kernel_args();
181+
assert_eq!(
182+
args,
183+
"root=/dev/vdb1 rootfstype=ext4 rootflags=noatime,discard ro"
184+
);
185+
}
186+
187+
#[test]
188+
fn test_invalid_device() {
189+
let result = RootfsConfig::new().with_device("");
190+
assert!(result.is_err());
191+
}
192+
193+
#[test]
194+
fn test_invalid_fs_type() {
195+
let result = RootfsConfig::new().with_fs_type("");
196+
assert!(result.is_err());
197+
}
198+
}

0 commit comments

Comments
 (0)