Skip to content

Commit 62c084e

Browse files
committed
Use virtiofsd for sharing file system data with host
TODO: Unfinished. TODO: Adjust README to mention required configs. TODO: Adjust default configs to work with the new stuff. We solely rely on guest level read-only mounts to enforce read-only state. The recommended way is to use read-only bind mounts [0], but doing so would require root. [0] https://gitlab.com/virtio-fs/virtiofsd/-/blob/main/README.md?ref_type=heads#faq Resources: https://virtio-fs.gitlab.io/howto-boot.html khttps://virtio-fs.gitlab.io/howto-qemu.html khttps://wiki.hexchain.org/linux/generic/network-root/ khttps://gitlab.com/virtio-fs/virtiofsd/-/blob/main/README.md?ref_type=heads#examples
1 parent b713eba commit 62c084e

File tree

6 files changed

+211
-54
lines changed

6 files changed

+211
-54
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ The following are required dependencies, grouped by location:
2626

2727
Host machine:
2828

29-
* [`qemu`](https://pkgs.org/download/qemu)
29+
* [`qemu`](https://pkgs.org/download/qemu) (version 5.9 or higher)
3030
* [`qemu-guest-agent`](https://pkgs.org/search/?q=qemu-guest-agent)
3131
* [`OVMF`](https://pkgs.org/download/ovmf)
3232

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ pub use crate::vmtest::*;
1717
mod qemu;
1818
mod qga;
1919
mod util;
20+
mod virtiofsd;

src/output.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ use anyhow::Result;
1010
/// Receivers should treat failures as terminal and not expect any more
1111
/// updates.
1212
pub enum Output {
13+
/// On-host initialization starts
14+
InitializeStart,
15+
/// Initialization finished with provided with provided result
16+
InitializeEnd(Result<()>),
17+
1318
/// VM boot begins
1419
BootStart,
1520
/// Output related to VM boot

src/qemu.rs

Lines changed: 76 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,17 @@ use tinytemplate::{format_unescaped, TinyTemplate};
2727
use crate::output::Output;
2828
use crate::qga::QgaWrapper;
2929
use crate::util::gen_sock;
30+
use crate::virtiofsd::Virtiofsd;
3031
use crate::{Mount, Target, VMConfig};
3132

3233
const INIT_TEMPLATE: &str = include_str!("init/init.sh.template");
3334
const COMMAND_TEMPLATE: &str = include_str!("init/command.template");
34-
// Needs to be `/dev/root` for kernel to "find" the 9pfs as rootfs
35-
const ROOTFS_9P_FS_MOUNT_TAG: &str = "/dev/root";
36-
const SHARED_9P_FS_MOUNT_TAG: &str = "vmtest-shared";
35+
const ROOT_FS_MOUNT_TAG: &str = "rootfs";
36+
const SHARED_FS_MOUNT_TAG: &str = "vmtest-shared";
3737
const COMMAND_OUTPUT_PORT_NAME: &str = "org.qemu.virtio_serial.0";
3838
const MAGIC_INTERACTIVE_COMMAND: &str = "-";
3939

40-
const SHARED_9P_FS_MOUNT_PATH: &str = "/mnt/vmtest";
41-
const MOUNT_OPTS_9P_FS: &str = "trans=virtio,cache=mmap,msize=1048576";
40+
const SHARED_FS_MOUNT_PATH: &str = "/mnt/vmtest";
4241
const OVMF_PATHS: &[&str] = &[
4342
// Fedora
4443
"/usr/share/edk2/ovmf/OVMF_CODE.fd",
@@ -55,6 +54,8 @@ type QmpUnixStream = qapi::Stream<BufReader<UnixStream>, UnixStream>;
5554
/// Represents a single QEMU instance
5655
pub struct Qemu {
5756
process: Command,
57+
/// `virtiofsd` instances for each of the mounts in use.
58+
virtiofsds: Vec<Virtiofsd>,
5859
qga_sock: PathBuf,
5960
qmp_sock: PathBuf,
6061
command: String,
@@ -241,6 +242,18 @@ fn guest_agent_args(sock: &Path) -> Vec<OsString> {
241242
args
242243
}
243244

245+
/// Generate general arguments necessary for working with `virtiofs`.
246+
fn virtiofs_general_args(vm: &VMConfig) -> Vec<OsString> {
247+
let mut args: Vec<OsString> = Vec::new();
248+
249+
args.push("-object".into());
250+
args.push(format!("memory-backend-memfd,id=mem,share=on,size={}", vm.memory.as_str()).into());
251+
args.push("-numa".into());
252+
args.push("node,memdev=mem".into());
253+
254+
args
255+
}
256+
244257
/// Generate arguments for full KVM virtualization if host supports it
245258
fn kvm_args(arch: &str) -> Vec<&'static str> {
246259
let mut args = Vec::new();
@@ -291,30 +304,17 @@ fn machine_protocol_args(sock: &Path) -> Vec<OsString> {
291304
args
292305
}
293306

294-
/// Generate arguments for setting up 9p FS server on host
307+
/// Generate per-file-system arguments necessary for working with `virtiofs`.
295308
///
296-
/// `id` is the ID for the FS export (currently unused AFAICT)
309+
/// `id` is the ID for the FS export
297310
/// `mount_tag` is used inside guest to find the export
298-
fn plan9_fs_args(host_shared: &Path, id: &str, mount_tag: &str, ro: bool) -> Vec<OsString> {
311+
fn virtiofs_per_fs_args(virtiofsd: &Virtiofsd, id: &str, mount_tag: &str) -> Vec<OsString> {
299312
let mut args: Vec<OsString> = Vec::new();
300313

301-
args.push("-virtfs".into());
302-
303-
let mut arg = OsString::new();
304-
arg.push(format!("local,id={id},path="));
305-
arg.push(if host_shared.as_os_str().is_empty() {
306-
// This case occurs when the config file path is just "vmtest.toml"
307-
Path::new(".")
308-
} else {
309-
host_shared
310-
});
311-
arg.push(format!(
312-
",mount_tag={mount_tag},security_model=none,multidevs=remap"
313-
));
314-
if ro {
315-
arg.push(",readonly=on")
316-
}
317-
args.push(arg);
314+
args.push("-chardev".into());
315+
args.push(format!("socket,id={id},path={}", virtiofsd.socket_path().display()).into());
316+
args.push("-device".into());
317+
args.push(format!("vhost-user-fs-pci,queue-size=1024,chardev={id},tag={mount_tag}").into());
318318

319319
args
320320
}
@@ -371,9 +371,9 @@ fn kernel_args(
371371
// The guest kernel command line args
372372
let mut cmdline: Vec<OsString> = Vec::new();
373373

374-
// Tell kernel the rootfs is 9p
375-
cmdline.push("rootfstype=9p".into());
376-
cmdline.push(format!("rootflags={}", MOUNT_OPTS_9P_FS).into());
374+
// Tell kernel the rootfs is on a virtiofs and what "tag" it uses.
375+
cmdline.push("rootfstype=virtiofs".into());
376+
cmdline.push(format!("root={ROOT_FS_MOUNT_TAG}").into());
377377

378378
// Mount rootfs readable/writable to make experience more smooth.
379379
// Lots of tools expect to be able to write logs or change global
@@ -455,16 +455,6 @@ fn vmconfig_args(vm: &VMConfig) -> Vec<OsString> {
455455
vm.memory.clone().into(),
456456
];
457457

458-
for mount in vm.mounts.values() {
459-
let name = format!("mount{}", hash(&mount.host_path));
460-
args.append(&mut plan9_fs_args(
461-
&mount.host_path,
462-
&name,
463-
&name,
464-
!mount.writable,
465-
));
466-
}
467-
468458
let mut extra_args = vm
469459
.extra_args
470460
.clone()
@@ -650,6 +640,7 @@ impl Qemu {
650640
let command_sock = gen_sock("cmdout");
651641
let (init, guest_init) = gen_init(&target.rootfs).context("Failed to generate init")?;
652642

643+
let mut virtiofsds = Vec::new();
653644
let mut c = Command::new(format!("qemu-system-{}", target.arch));
654645

655646
c.args(QEMU_DEFAULT_ARGS)
@@ -660,6 +651,7 @@ impl Qemu {
660651
.args(machine_args(&target.arch))
661652
.args(machine_protocol_args(&qmp_sock))
662653
.args(guest_agent_args(&qga_sock))
654+
.args(virtiofs_general_args(&target.vm))
663655
.args(virtio_serial_args(&command_sock));
664656
// Always ensure the rootfs is first.
665657
if let Some(image) = &target.image {
@@ -668,28 +660,42 @@ impl Qemu {
668660
c.args(uefi_firmware_args(target.vm.bios.as_deref()));
669661
}
670662
} else if let Some(kernel) = &target.kernel {
671-
c.args(plan9_fs_args(
672-
target.rootfs.as_path(),
663+
let virtiofsd = Virtiofsd::new(target.rootfs.as_path())?;
664+
c.args(virtiofs_per_fs_args(
665+
&virtiofsd,
673666
"root",
674-
ROOTFS_9P_FS_MOUNT_TAG,
675-
false,
667+
ROOT_FS_MOUNT_TAG,
676668
));
677669
c.args(kernel_args(
678670
kernel,
679671
&target.arch,
680672
guest_init.as_path(),
681673
target.kernel_args.as_ref(),
682674
));
675+
virtiofsds.push(virtiofsd);
683676
} else {
684677
panic!("Config validation should've enforced XOR");
685678
}
679+
686680
// Now add the shared mount and other extra mounts.
687-
c.args(plan9_fs_args(
688-
host_shared,
681+
let virtiofsd = Virtiofsd::new(host_shared)?;
682+
c.args(virtiofs_per_fs_args(
683+
&virtiofsd,
689684
"shared",
690-
SHARED_9P_FS_MOUNT_TAG,
691-
false,
685+
SHARED_FS_MOUNT_TAG,
692686
));
687+
virtiofsds.push(virtiofsd);
688+
689+
for mount in target.vm.mounts.values() {
690+
let name = format!("mount{}", hash(&mount.host_path));
691+
let virtiofsd = Virtiofsd::new(&mount.host_path)?;
692+
c.args(virtiofs_per_fs_args(
693+
&virtiofsd,
694+
&name,
695+
&name,
696+
));
697+
virtiofsds.push(virtiofsd);
698+
}
693699
c.args(vmconfig_args(&target.vm));
694700

695701
if log_enabled!(Level::Error) {
@@ -706,6 +712,7 @@ impl Qemu {
706712

707713
let mut qemu = Self {
708714
process: c,
715+
virtiofsds,
709716
qga_sock,
710717
qmp_sock,
711718
command: target.command.to_string(),
@@ -838,16 +845,18 @@ impl Qemu {
838845
// We can race with VM/qemu coming up. So retry a few times with growing backoff.
839846
let mut rc = 0;
840847
for i in 0..5 {
841-
let mount_opts = if ro {
842-
format!("{},ro", MOUNT_OPTS_9P_FS)
843-
} else {
844-
MOUNT_OPTS_9P_FS.into()
845-
};
848+
let mut args = vec![
849+
"-t", "virtiofs", mount_tag, guest_path
850+
];
851+
if ro {
852+
args.push("-oro")
853+
}
854+
846855
rc = run_in_vm(
847856
qga,
848857
&output_fn,
849858
"mount",
850-
&["-t", "9p", "-o", &mount_opts, mount_tag, guest_path],
859+
&args,
851860
false,
852861
None,
853862
)?;
@@ -1052,7 +1061,7 @@ impl Qemu {
10521061
// Mount shared directory inside guest
10531062
let _ = self.updates.send(Output::SetupStart);
10541063
if let Err(e) =
1055-
self.mount_in_guest(qga, SHARED_9P_FS_MOUNT_PATH, SHARED_9P_FS_MOUNT_TAG, false)
1064+
self.mount_in_guest(qga, SHARED_FS_MOUNT_PATH, SHARED_FS_MOUNT_TAG, false)
10561065
{
10571066
return Err(e).context("Failed to mount shared directory in guest");
10581067
}
@@ -1075,6 +1084,20 @@ impl Qemu {
10751084
/// Errors and return status are reported through the `updates` channel passed into the
10761085
/// constructor.
10771086
pub fn run(mut self) {
1087+
let _ = self.updates.send(Output::InitializeStart);
1088+
for virtiofsd in self.virtiofsds.iter_mut() {
1089+
match virtiofsd.launch() {
1090+
Ok(()) => (),
1091+
Err(e) => {
1092+
let _ = self.updates.send(Output::InitializeEnd(Err(e)));
1093+
return;
1094+
}
1095+
}
1096+
}
1097+
1098+
// TODO: Wait for all sockets to appear as well.
1099+
let _ = self.updates.send(Output::InitializeEnd(Ok(())));
1100+
10781101
// Start QEMU
10791102
let (mut child, qga, mut qmp) = match self.boot_vm() {
10801103
Ok((c, qga, qmp)) => (c, qga, qmp),

src/ui.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,16 @@ impl Ui {
177177
};
178178

179179
match &msg {
180+
Output::InitializeStart => {
181+
stage = Stage::new(term.clone(), &heading("Initializing host environment", 2), Some(stage));
182+
stages += 1;
183+
}
184+
Output::InitializeEnd(r) => {
185+
if let Err(e) = r {
186+
error_out_stage(&mut stage, e);
187+
errors += 1;
188+
}
189+
}
180190
Output::BootStart => {
181191
stage = Stage::new(term.clone(), &heading("Booting", 2), Some(stage));
182192
stages += 1;

0 commit comments

Comments
 (0)