Skip to content

Commit f8cebac

Browse files
alyssarosenzweigslp
authored andcommitted
Automatically manage FEX rootfs
This adds a mechanism for automatically managing the FEX rootfs, mounting and overlayfs'ing the relevant squashfs files from the host (as many as desired). Depends on: * libkrun containers/libkrun#217 * libkrunfw containers/libkrunfw#65 Signed-off-by: Alyssa Rosenzweig <[email protected]>
1 parent 44417f6 commit f8cebac

3 files changed

Lines changed: 143 additions & 17 deletions

File tree

crates/krun/src/bin/krun.rs

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ use krun::launch::{launch_or_lock, LaunchResult};
1111
use krun::net::{connect_to_passt, start_passt};
1212
use krun::types::MiB;
1313
use krun_sys::{
14-
krun_add_vsock_port, krun_create_ctx, krun_set_exec, krun_set_gpu_options, krun_set_log_level,
15-
krun_set_passt_fd, krun_set_root, krun_set_vm_config, krun_set_workdir, krun_start_enter,
16-
VIRGLRENDERER_DRM, VIRGLRENDERER_THREAD_SYNC, VIRGLRENDERER_USE_ASYNC_FENCE_CB,
17-
VIRGLRENDERER_USE_EGL,
14+
krun_add_disk, krun_add_vsock_port, krun_create_ctx, krun_set_exec, krun_set_gpu_options,
15+
krun_set_log_level, krun_set_passt_fd, krun_set_root, krun_set_vm_config, krun_set_workdir,
16+
krun_start_enter, VIRGLRENDERER_DRM, VIRGLRENDERER_THREAD_SYNC,
17+
VIRGLRENDERER_USE_ASYNC_FENCE_CB, VIRGLRENDERER_USE_EGL,
1818
};
1919
use log::debug;
2020
use nix::sys::sysinfo::sysinfo;
@@ -24,6 +24,23 @@ use rustix::process::{
2424
geteuid, getgid, getrlimit, getuid, sched_setaffinity, setrlimit, CpuSet, Resource,
2525
};
2626

27+
fn add_ro_disk(ctx_id: u32, label: &str, path: &str) -> Result<()> {
28+
let path_cstr = CString::new(path).unwrap();
29+
let path_ptr = path_cstr.as_ptr();
30+
31+
let label_cstr = CString::new(label).unwrap();
32+
let label_ptr = label_cstr.as_ptr();
33+
34+
// SAFETY: `path_ptr` and `label_ptr` are live pointers to C-strings
35+
let err = unsafe { krun_add_disk(ctx_id, label_ptr, path_ptr, true) };
36+
37+
if err < 0 {
38+
Err(Errno::from_raw_os_error(-err).into())
39+
} else {
40+
Ok(())
41+
}
42+
}
43+
2744
fn main() -> Result<()> {
2845
env_logger::init();
2946

@@ -122,6 +139,32 @@ fn main() -> Result<()> {
122139
setrlimit(Resource::Nofile, rlim).context("Failed to raise `RLIMIT_NOFILE`")?;
123140
}
124141

142+
// If the user specified a disk image, we want to load and fail if it's missing. If the user
143+
// did not specify a disk image, we want to load the system images if installed but fail
144+
// gracefully if missing. This follows the principle of least surprise.
145+
//
146+
// What we don't want is a clever autodiscovery mechanism that searches $HOME for images.
147+
// That's liable to blow up in exciting ways. Instead we require images to be selected
148+
// explicitly, either on the CLI or hardcoded here.
149+
let disks: Vec<String> = if !options.fex_images.is_empty() {
150+
options.fex_images
151+
} else {
152+
let default_disks = vec![
153+
"/usr/share/fex-emu/RootFS/default.erofs",
154+
"/usr/share/fex-emu/overlays/mesa.erofs",
155+
];
156+
157+
default_disks
158+
.iter()
159+
.map(|x| x.to_string())
160+
.filter(|x| Path::new(x).exists())
161+
.collect()
162+
};
163+
164+
for path in disks {
165+
add_ro_disk(ctx_id, &path, &path).context("Failed to configure disk")?;
166+
}
167+
125168
{
126169
// SAFETY: `root_path` is a pointer to a C-string literal.
127170
let err = unsafe { krun_set_root(ctx_id, c"/".as_ptr()) };

crates/krun/src/cli_options.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub struct Options {
1313
pub mem: Option<MiB>,
1414
pub passt_socket: Option<PathBuf>,
1515
pub server_port: u32,
16+
pub fex_images: Vec<String>,
1617
pub command: PathBuf,
1718
pub command_args: Vec<String>,
1819
}
@@ -66,6 +67,15 @@ pub fn options() -> OptionParser<Options> {
6667
)
6768
.argument("MEM")
6869
.optional();
70+
let fex_images = long("fex-image")
71+
.short('f')
72+
.help(
73+
"Adds an erofs file to be mounted as a FEX rootfs.
74+
May be specified multiple times.
75+
First the base image, then overlays in order.",
76+
)
77+
.argument::<String>("FEX_IMAGE")
78+
.many();
6979
let passt_socket = long("passt-socket")
7080
.help("Instead of starting passt, connect to passt socket at PATH")
7181
.argument("PATH")
@@ -89,6 +99,7 @@ pub fn options() -> OptionParser<Options> {
8999
mem,
90100
passt_socket,
91101
server_port,
102+
fex_images,
92103
// positionals
93104
command,
94105
command_args,

crates/krun/src/guest/mount.rs

Lines changed: 85 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,101 @@
1-
use std::fs::File;
1+
use std::ffi::CString;
2+
use std::fs::{read_dir, File};
3+
use std::io::Write;
24
use std::os::fd::AsFd;
35
use std::path::Path;
46

57
use anyhow::{Context, Result};
6-
use rustix::fs::CWD;
8+
use rustix::fs::{mkdir, symlink, Mode, CWD};
79
use rustix::mount::{
810
mount2, mount_bind, move_mount, open_tree, MountFlags, MoveMountFlags, OpenTreeFlags,
911
};
1012

11-
pub fn mount_filesystems() -> Result<()> {
13+
fn make_tmpfs(dir: &str) -> Result<()> {
1214
mount2(
1315
Some("tmpfs"),
14-
"/var/run",
16+
dir,
1517
Some("tmpfs"),
1618
MountFlags::NOEXEC | MountFlags::NOSUID | MountFlags::RELATIME,
1719
None,
1820
)
19-
.context("Failed to mount `/var/run`")?;
21+
.context("Failed to mount tmpfs")
22+
}
23+
24+
fn mkdir_fex(dir: &str) {
25+
// Must succeed since /run/ was just mounted and is now an empty tmpfs.
26+
mkdir(
27+
dir,
28+
Mode::RUSR | Mode::XUSR | Mode::RGRP | Mode::XGRP | Mode::ROTH | Mode::XOTH,
29+
)
30+
.unwrap();
31+
}
32+
33+
fn mount_fex_rootfs() -> Result<()> {
34+
let dir = "/run/fex-emu/";
35+
let dir_rootfs = dir.to_string() + "rootfs";
36+
37+
// Make base directories
38+
mkdir_fex(dir);
39+
40+
let flags = MountFlags::RDONLY;
41+
let mut images = Vec::new();
42+
43+
// Find /dev/vd*
44+
for x in read_dir("/dev").unwrap() {
45+
let file = x.unwrap();
46+
let name = file.file_name().into_string().unwrap();
47+
if !name.starts_with("vd") {
48+
continue;
49+
}
50+
51+
let path = file.path().into_os_string().into_string().unwrap();
52+
let dir = dir.to_string() + &name;
53+
54+
// Mount the erofs images.
55+
mkdir_fex(&dir);
56+
mount2(Some(path), dir.clone(), Some("erofs"), flags, None)
57+
.context("Failed to mount erofs")
58+
.unwrap();
59+
images.push(dir);
60+
}
61+
62+
if images.len() >= 2 {
63+
// Overlay the mounts together.
64+
let opts = format!(
65+
"lowerdir={}",
66+
images.into_iter().rev().collect::<Vec<String>>().join(":")
67+
);
68+
let opts = CString::new(opts).unwrap();
69+
let overlay = "overlay".to_string();
70+
let overlay_ = Some(&overlay);
71+
72+
mkdir_fex(&dir_rootfs);
73+
mount2(overlay_, &dir_rootfs, overlay_, flags, Some(&opts)).context("Failed to overlay")?;
74+
} else if images.len() == 1 {
75+
// Just expose the one mount
76+
symlink(&images[0], &dir_rootfs)?;
77+
}
78+
79+
// Now we need to tell FEX about this. One of the FEX share directories has an unmounted rootfs
80+
// and a Config.json telling FEX to use FUSE. Neither should be visible to the guest. Instead,
81+
// we want to replace the folders and tell FEX to use our mounted rootfs
82+
for base in ["/usr/share/fex-emu", "/usr/local/share/fex-emu"] {
83+
let json = format!("{{\"Config\":{{\"RootFS\":\"{dir_rootfs}\"}}}}\n");
84+
let path = base.to_string() + "/Config.json";
85+
86+
make_tmpfs(base)?;
87+
File::create(Path::new(&path))?.write_all(json.as_bytes())?;
88+
}
89+
90+
Ok(())
91+
}
92+
93+
pub fn mount_filesystems() -> Result<()> {
94+
make_tmpfs("/var/run")?;
95+
96+
if let Err(_) = mount_fex_rootfs() {
97+
println!("Failed to mount FEX rootfs, carrying on without.")
98+
}
2099

21100
let _ = File::options()
22101
.write(true)
@@ -60,14 +139,7 @@ pub fn mount_filesystems() -> Result<()> {
60139
if Path::new("/tmp/.X11-unix").exists() {
61140
// Mount a tmpfs for X11 sockets, so the guest doesn't clobber host X server
62141
// sockets
63-
mount2(
64-
Some("tmpfs"),
65-
"/tmp/.X11-unix",
66-
Some("tmpfs"),
67-
MountFlags::NOEXEC | MountFlags::NOSUID | MountFlags::RELATIME,
68-
None,
69-
)
70-
.context("Failed to mount `/tmp/.X11-unix`")?;
142+
make_tmpfs("/tmp/.X11-unix")?;
71143
}
72144

73145
Ok(())

0 commit comments

Comments
 (0)