Skip to content

Commit 1e3c568

Browse files
committed
virtio/fs: implement an ioctl to receive exit_code
For the container use case, we need to relay the exit code from userspace in the microVM all the way to the process using libkrun to launch the VMM. Since ioctls are passed mostly unmodified from userspace in the guest to the virtio-fs device, we can use them as a mechanism for transporting this information without requiring any specific support in the guest's kernel. Here we create an AtomicI32 and wire it up between virtio-fs and Vmm, using it as exit code if userspace has set it to some value other than i32::MAX. Otherwise, we keep using the vCPU exit code, as we did before. Signed-off-by: Sergio Lopez <[email protected]>
1 parent 3722a25 commit 1e3c568

File tree

8 files changed

+94
-14
lines changed

8 files changed

+94
-14
lines changed

src/devices/src/virtio/fs/device.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
use crossbeam_channel::Sender;
33
use std::cmp;
44
use std::io::Write;
5-
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
5+
use std::sync::atomic::{AtomicI32, AtomicU64, AtomicUsize, Ordering};
66
use std::sync::Arc;
77
use std::thread::JoinHandle;
88

@@ -54,6 +54,7 @@ pub struct Fs {
5454
passthrough_cfg: passthrough::Config,
5555
worker_thread: Option<JoinHandle<()>>,
5656
worker_stopfd: EventFd,
57+
exit_code: Arc<AtomicI32>,
5758
#[cfg(target_os = "macos")]
5859
map_sender: Option<Sender<MemoryMapping>>,
5960
}
@@ -62,6 +63,7 @@ impl Fs {
6263
pub(crate) fn with_queues(
6364
fs_id: String,
6465
shared_dir: String,
66+
exit_code: Arc<AtomicI32>,
6567
queues: Vec<VirtQueue>,
6668
) -> super::Result<Fs> {
6769
let mut queue_events = Vec::new();
@@ -97,17 +99,18 @@ impl Fs {
9799
passthrough_cfg: fs_cfg,
98100
worker_thread: None,
99101
worker_stopfd: EventFd::new(EFD_NONBLOCK).map_err(FsError::EventFd)?,
102+
exit_code,
100103
#[cfg(target_os = "macos")]
101104
map_sender: None,
102105
})
103106
}
104107

105-
pub fn new(fs_id: String, shared_dir: String) -> super::Result<Fs> {
108+
pub fn new(fs_id: String, shared_dir: String, exit_code: Arc<AtomicI32>) -> super::Result<Fs> {
106109
let queues: Vec<VirtQueue> = defs::QUEUE_SIZES
107110
.iter()
108111
.map(|&max_size| VirtQueue::new(max_size))
109112
.collect();
110-
Self::with_queues(fs_id, shared_dir, queues)
113+
Self::with_queues(fs_id, shared_dir, exit_code, queues)
111114
}
112115

113116
pub fn id(&self) -> &str {
@@ -226,6 +229,7 @@ impl VirtioDevice for Fs {
226229
self.shm_region.clone(),
227230
self.passthrough_cfg.clone(),
228231
self.worker_stopfd.try_clone().unwrap(),
232+
self.exit_code.clone(),
229233
#[cfg(target_os = "macos")]
230234
self.map_sender.clone(),
231235
);

src/devices/src/virtio/fs/filesystem.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use std::ffi::{CStr, CString};
1313
use std::fs::File;
1414
use std::io;
1515
use std::mem;
16+
use std::sync::atomic::AtomicI32;
1617
use std::sync::{Arc, Mutex};
1718
use std::time::Duration;
1819

@@ -1160,6 +1161,7 @@ pub trait FileSystem {
11601161
arg: u64,
11611162
in_size: u32,
11621163
out_size: u32,
1164+
exit_code: &Arc<AtomicI32>,
11631165
) -> io::Result<Vec<u8>> {
11641166
Err(io::Error::from_raw_os_error(bindings::LINUX_ENOSYS))
11651167
}

src/devices/src/virtio/fs/linux/passthrough.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ use std::io;
1111
use std::mem::{self, size_of, MaybeUninit};
1212
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
1313
use std::str::FromStr;
14-
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
14+
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU64, Ordering};
1515
use std::sync::{Arc, RwLock};
1616
use std::time::Duration;
1717

1818
use caps::{has_cap, CapSet, Capability};
19-
use nix::request_code_read;
19+
use nix::{request_code_none, request_code_read};
2020

2121
use vm_memory::ByteValued;
2222

@@ -2107,11 +2107,13 @@ impl FileSystem for PassthroughFs {
21072107
handle: Self::Handle,
21082108
_flags: u32,
21092109
cmd: u32,
2110-
_arg: u64,
2110+
arg: u64,
21112111
_in_size: u32,
21122112
out_size: u32,
2113+
exit_code: &Arc<AtomicI32>,
21132114
) -> io::Result<Vec<u8>> {
21142115
const VIRTIO_IOC_MAGIC: u8 = b'v';
2116+
21152117
const VIRTIO_IOC_TYPE_EXPORT_FD: u8 = 1;
21162118
const VIRTIO_IOC_EXPORT_FD_SIZE: usize = 2 * mem::size_of::<u64>();
21172119
const VIRTIO_IOC_EXPORT_FD_REQ: u32 = request_code_read!(
@@ -2120,6 +2122,10 @@ impl FileSystem for PassthroughFs {
21202122
VIRTIO_IOC_EXPORT_FD_SIZE
21212123
) as u32;
21222124

2125+
const VIRTIO_IOC_TYPE_EXIT_CODE: u8 = 2;
2126+
const VIRTIO_IOC_EXIT_CODE_REQ: u32 =
2127+
request_code_none!(VIRTIO_IOC_MAGIC, VIRTIO_IOC_TYPE_EXIT_CODE) as u32;
2128+
21232129
match cmd {
21242130
VIRTIO_IOC_EXPORT_FD_REQ => {
21252131
if out_size as usize != VIRTIO_IOC_EXPORT_FD_SIZE {
@@ -2150,6 +2156,10 @@ impl FileSystem for PassthroughFs {
21502156
ret.extend_from_slice(&handle.to_ne_bytes());
21512157
Ok(ret)
21522158
}
2159+
VIRTIO_IOC_EXIT_CODE_REQ => {
2160+
exit_code.store(arg as i32, Ordering::SeqCst);
2161+
Ok(Vec::new())
2162+
}
21532163
_ => Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP)),
21542164
}
21552165
}

src/devices/src/virtio/fs/macos/passthrough.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use std::mem::MaybeUninit;
1414
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
1515
use std::ptr::null_mut;
1616
use std::str::FromStr;
17-
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
17+
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU64, Ordering};
1818
use std::sync::{Arc, Mutex, RwLock};
1919
use std::time::Duration;
2020

@@ -2105,4 +2105,29 @@ impl FileSystem for PassthroughFs {
21052105

21062106
Ok(())
21072107
}
2108+
2109+
fn ioctl(
2110+
&self,
2111+
_ctx: Context,
2112+
_inode: Self::Inode,
2113+
_handle: Self::Handle,
2114+
_flags: u32,
2115+
cmd: u32,
2116+
arg: u64,
2117+
_in_size: u32,
2118+
_out_size: u32,
2119+
exit_code: &Arc<AtomicI32>,
2120+
) -> io::Result<Vec<u8>> {
2121+
// We can't use nix::request_code_none here since it's system-dependent
2122+
// and we need the value from Linux.
2123+
const VIRTIO_IOC_EXIT_CODE_REQ: u32 = 0x7602;
2124+
2125+
match cmd {
2126+
VIRTIO_IOC_EXIT_CODE_REQ => {
2127+
exit_code.store(arg as i32, Ordering::SeqCst);
2128+
Ok(Vec::new())
2129+
}
2130+
_ => Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP)),
2131+
}
2132+
}
21082133
}

src/devices/src/virtio/fs/server.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ use std::ffi::{CStr, CString};
1212
use std::fs::File;
1313
use std::io::{self, Read, Write};
1414
use std::mem::size_of;
15-
use std::sync::atomic::{AtomicU64, Ordering};
15+
use std::sync::atomic::{AtomicI32, AtomicU64, Ordering};
16+
use std::sync::Arc;
1617

1718
use vm_memory::ByteValued;
1819

@@ -83,6 +84,7 @@ impl<F: FileSystem + Sync> Server<F> {
8384
mut r: Reader,
8485
w: Writer,
8586
shm_region: &Option<VirtioShmRegion>,
87+
exit_code: &Arc<AtomicI32>,
8688
#[cfg(target_os = "macos")] map_sender: &Option<Sender<MemoryMapping>>,
8789
) -> Result<usize> {
8890
let in_header: InHeader = r.read_obj().map_err(Error::DecodeMessage)?;
@@ -132,7 +134,7 @@ impl<F: FileSystem + Sync> Server<F> {
132134
x if x == Opcode::Interrupt as u32 => self.interrupt(in_header),
133135
x if x == Opcode::Bmap as u32 => self.bmap(in_header, r, w),
134136
x if x == Opcode::Destroy as u32 => self.destroy(),
135-
x if x == Opcode::Ioctl as u32 => self.ioctl(in_header, r, w),
137+
x if x == Opcode::Ioctl as u32 => self.ioctl(in_header, r, w, exit_code),
136138
x if x == Opcode::Poll as u32 => self.poll(in_header, r, w),
137139
x if x == Opcode::NotifyReply as u32 => self.notify_reply(in_header, r, w),
138140
x if x == Opcode::BatchForget as u32 => self.batch_forget(in_header, r, w),
@@ -1166,7 +1168,13 @@ impl<F: FileSystem + Sync> Server<F> {
11661168
Ok(0)
11671169
}
11681170

1169-
fn ioctl(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
1171+
fn ioctl(
1172+
&self,
1173+
in_header: InHeader,
1174+
mut r: Reader,
1175+
w: Writer,
1176+
exit_code: &Arc<AtomicI32>,
1177+
) -> Result<usize> {
11701178
let IoctlIn {
11711179
fh,
11721180
flags,
@@ -1185,6 +1193,7 @@ impl<F: FileSystem + Sync> Server<F> {
11851193
arg,
11861194
in_size,
11871195
out_size,
1196+
exit_code,
11881197
) {
11891198
Ok(data) => {
11901199
let out = IoctlOut {

src/devices/src/virtio/fs/worker.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crossbeam_channel::Sender;
44
use hvf::MemoryMapping;
55

66
use std::os::fd::AsRawFd;
7-
use std::sync::atomic::{AtomicUsize, Ordering};
7+
use std::sync::atomic::{AtomicI32, AtomicUsize, Ordering};
88
use std::sync::Arc;
99
use std::thread;
1010

@@ -32,6 +32,7 @@ pub struct FsWorker {
3232
shm_region: Option<VirtioShmRegion>,
3333
server: Server<PassthroughFs>,
3434
stop_fd: EventFd,
35+
exit_code: Arc<AtomicI32>,
3536
#[cfg(target_os = "macos")]
3637
map_sender: Option<Sender<MemoryMapping>>,
3738
}
@@ -49,6 +50,7 @@ impl FsWorker {
4950
shm_region: Option<VirtioShmRegion>,
5051
passthrough_cfg: passthrough::Config,
5152
stop_fd: EventFd,
53+
exit_code: Arc<AtomicI32>,
5254
#[cfg(target_os = "macos")] map_sender: Option<Sender<MemoryMapping>>,
5355
) -> Self {
5456
Self {
@@ -63,6 +65,7 @@ impl FsWorker {
6365
shm_region,
6466
server: Server::new(PassthroughFs::new(passthrough_cfg).unwrap()),
6567
stop_fd,
68+
exit_code,
6669
#[cfg(target_os = "macos")]
6770
map_sender,
6871
}
@@ -170,6 +173,7 @@ impl FsWorker {
170173
reader,
171174
writer,
172175
&self.shm_region,
176+
&self.exit_code,
173177
#[cfg(target_os = "macos")]
174178
&self.map_sender,
175179
) {

src/vmm/src/builder.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use std::io::{self, Read};
1818
#[cfg(target_os = "linux")]
1919
use std::os::fd::AsRawFd;
2020
use std::path::PathBuf;
21+
use std::sync::atomic::AtomicI32;
2122
use std::sync::{Arc, Mutex};
2223

2324
use super::{Error, Vmm};
@@ -763,13 +764,17 @@ pub fn build_microvm(
763764
)?;
764765
}
765766

767+
// We use this atomic to record the exit code set by init/init.c in the VM.
768+
let exit_code = Arc::new(AtomicI32::new(i32::MAX));
769+
766770
let mut vmm = Vmm {
767771
guest_memory,
768772
arch_memory_info,
769773
kernel_cmdline,
770774
vcpus_handles: Vec::new(),
771775
exit_evt,
772776
exit_observers: Vec::new(),
777+
exit_code: exit_code.clone(),
773778
vm,
774779
mmio_device_manager,
775780
#[cfg(target_arch = "x86_64")]
@@ -808,6 +813,7 @@ pub fn build_microvm(
808813
_map_sender.clone(),
809814
)?;
810815
}
816+
811817
#[cfg(not(feature = "tee"))]
812818
attach_fs_devices(
813819
&mut vmm,
@@ -816,6 +822,7 @@ pub fn build_microvm(
816822
#[cfg(not(feature = "tee"))]
817823
export_table,
818824
intc.clone(),
825+
exit_code,
819826
#[cfg(target_os = "macos")]
820827
_map_sender,
821828
)?;
@@ -1587,13 +1594,19 @@ fn attach_fs_devices(
15871594
shm_manager: &mut ShmManager,
15881595
#[cfg(not(feature = "tee"))] export_table: Option<ExportTable>,
15891596
intc: IrqChip,
1597+
exit_code: Arc<AtomicI32>,
15901598
#[cfg(target_os = "macos")] map_sender: Sender<MemoryMapping>,
15911599
) -> std::result::Result<(), StartMicrovmError> {
15921600
use self::StartMicrovmError::*;
15931601

15941602
for (i, config) in fs_devs.iter().enumerate() {
15951603
let fs = Arc::new(Mutex::new(
1596-
devices::virtio::Fs::new(config.fs_id.clone(), config.shared_dir.clone()).unwrap(),
1604+
devices::virtio::Fs::new(
1605+
config.fs_id.clone(),
1606+
config.shared_dir.clone(),
1607+
exit_code.clone(),
1608+
)
1609+
.unwrap(),
15971610
));
15981611

15991612
let id = format!("{}{}", String::from(fs.lock().unwrap().id()), i);

src/vmm/src/lib.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ use macos::vstate;
4040
use std::fmt::{Display, Formatter};
4141
use std::io;
4242
use std::os::unix::io::AsRawFd;
43+
use std::sync::atomic::{AtomicI32, Ordering};
4344
use std::sync::{Arc, Mutex};
4445
#[cfg(target_os = "linux")]
4546
use std::time::Duration;
@@ -202,6 +203,7 @@ pub struct Vmm {
202203
exit_evt: EventFd,
203204
vm: Vm,
204205
exit_observers: Vec<Arc<Mutex<dyn VmmExitObserver>>>,
206+
exit_code: Arc<AtomicI32>,
205207

206208
// Guest VM devices.
207209
mmio_device_manager: MMIODeviceManager,
@@ -394,15 +396,26 @@ impl Subscriber for Vmm {
394396
// If the exit_code can't be found on any vcpu, it means that the exit signal
395397
// has been issued by the i8042 controller in which case we exit with
396398
// FC_EXIT_CODE_OK.
397-
let exit_code = self
399+
//
400+
// The exit code set up by the guest takes preference over the one reported
401+
// by either a vcpu or the i8042 controller.
402+
let vcpu_exit_code = self
398403
.vcpus_handles
399404
.iter()
400405
.find_map(|handle| match handle.response_receiver().try_recv() {
401406
Ok(VcpuResponse::Exited(exit_code)) => Some(exit_code),
402407
_ => None,
403408
})
404409
.unwrap_or(FC_EXIT_CODE_OK);
405-
self.stop(i32::from(exit_code));
410+
let vmm_exit_code = self.exit_code.load(Ordering::SeqCst);
411+
let exit_code = if vmm_exit_code != i32::MAX {
412+
debug!("using vmm exit code: {vmm_exit_code}");
413+
vmm_exit_code
414+
} else {
415+
debug!("using vcpu exit code: {vcpu_exit_code}");
416+
vcpu_exit_code as i32
417+
};
418+
self.stop(exit_code);
406419
} else {
407420
error!("Spurious EventManager event for handler: Vmm");
408421
}

0 commit comments

Comments
 (0)