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 7 use std::process; 8 use std::process::Command; 9 use std::process::Stdio; 10 use std::thread; 11 use std::time::Duration; 12 13 use anyhow::Result; 14 use base::test_utils::check_can_sudo; 15 16 use crate::utils::find_crosvm_binary; 17 18 pub 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 26 impl CmdType { to_subcommand(&self) -> &str27 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 37 pub struct Config { 38 cmd_type: CmdType, 39 dev_name: String, 40 extra_args: Vec<String>, 41 } 42 43 impl Config { new(cmd_type: CmdType, name: &str) -> Self44 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)`. extra_args(mut self, args: Vec<String>) -> Self53 pub fn extra_args(mut self, args: Vec<String>) -> Self { 54 self.extra_args = args; 55 self 56 } 57 } 58 59 #[derive(Default)] 60 pub struct VhostUserBackend { 61 name: String, 62 process: Option<process::Child>, 63 } 64 65 impl VhostUserBackend { new(cfg: Config) -> Result<Self>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`. new_sudo(cfg: Config) -> Result<Self>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 new_common(mut cmd: Command, cfg: Config) -> Result<Self>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 100 impl Drop for VhostUserBackend { drop(&mut self)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 } 125