fixture/
vhost_user.rs

1// Copyright 2022 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Provides `VhostUserBackend`, a fixture of a vhost-user backend process.
6
7use std::process;
8use std::process::Command;
9use std::process::Stdio;
10use std::thread;
11use std::time::Duration;
12
13use anyhow::Result;
14use base::test_utils::check_can_sudo;
15
16use crate::utils::find_crosvm_binary;
17
18pub enum CmdType {
19    /// `crosvm device` command
20    Device,
21    /// `crosvm devices` command that is newer and supports sandboxing and multiple device
22    /// processes.
23    Devices,
24}
25
26impl CmdType {
27    fn to_subcommand(&self) -> &str {
28        match self {
29            // `crosvm device`
30            CmdType::Device => "device",
31            // `crosvm devices`
32            CmdType::Devices => "devices",
33        }
34    }
35}
36
37pub struct Config {
38    cmd_type: CmdType,
39    dev_name: String,
40    extra_args: Vec<String>,
41}
42
43impl Config {
44    pub fn new(cmd_type: CmdType, name: &str) -> Self {
45        Config {
46            cmd_type,
47            dev_name: name.to_string(),
48            extra_args: Default::default(),
49        }
50    }
51
52    /// Uses extra arguments for `crosvm (device|devices)`.
53    pub fn extra_args(mut self, args: Vec<String>) -> Self {
54        self.extra_args = args;
55        self
56    }
57}
58
59#[derive(Default)]
60pub struct VhostUserBackend {
61    name: String,
62    process: Option<process::Child>,
63}
64
65impl VhostUserBackend {
66    pub fn new(cfg: Config) -> Result<Self> {
67        let cmd = Command::new(find_crosvm_binary());
68        Self::new_common(cmd, cfg)
69    }
70
71    /// Start up Vhost User Backend `sudo`.
72    pub fn new_sudo(cfg: Config) -> Result<Self> {
73        check_can_sudo();
74
75        let mut cmd = Command::new("sudo");
76        cmd.arg(find_crosvm_binary());
77        Self::new_common(cmd, cfg)
78    }
79
80    fn new_common(mut cmd: Command, cfg: Config) -> Result<Self> {
81        cmd.args([cfg.cmd_type.to_subcommand()]);
82        cmd.args(cfg.extra_args);
83
84        cmd.stdout(Stdio::piped());
85        cmd.stderr(Stdio::piped());
86
87        println!("$ {cmd:?}");
88
89        let process = Some(cmd.spawn()?);
90        // TODO(b/269174700): Wait for the VU socket to be available instead.
91        thread::sleep(Duration::from_millis(100));
92
93        Ok(Self {
94            name: cfg.dev_name,
95            process,
96        })
97    }
98}
99
100impl Drop for VhostUserBackend {
101    fn drop(&mut self) {
102        let output = self.process.take().unwrap().wait_with_output().unwrap();
103
104        // Print both the crosvm's stdout/stderr to stdout so that they'll be shown when the test
105        // is failed.
106        println!(
107            "VhostUserBackend {} stdout:\n{}",
108            self.name,
109            std::str::from_utf8(&output.stdout).unwrap()
110        );
111        println!(
112            "VhostUserBackend {} stderr:\n{}",
113            self.name,
114            std::str::from_utf8(&output.stderr).unwrap()
115        );
116
117        if !output.status.success() {
118            panic!(
119                "VhostUserBackend {} exited illegally: {}",
120                self.name, output.status
121            );
122        }
123    }
124}