1
0
mirror of https://github.com/kata-containers/kata-containers.git synced 2025-05-08 16:37:32 +00:00

Merge pull request from BbolroC/enable-qemu-on-s390x

runtime-rs: Enable qemu on s390x
This commit is contained in:
Hyounggyu Choi 2024-03-22 23:58:42 +01:00 committed by GitHub
commit d915a79e2d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 352 additions and 39 deletions
src

View File

@ -26,7 +26,7 @@ serde_json = "1.0.73"
thiserror = "1.0"
toml = "0.5.8"
serde-enum-str = "0.4"
sysinfo = "0.29.11"
sysinfo = "0.30.5"
oci = { path = "../oci" }
safe-path = { path = "../safe-path" }

View File

@ -33,7 +33,7 @@ use std::collections::HashMap;
use std::io::{self, Result};
use std::path::Path;
use std::sync::{Arc, Mutex};
use sysinfo::{System, SystemExt};
use sysinfo::System;
mod dragonball;
pub use self::dragonball::{DragonballConfig, HYPERVISOR_NAME_DRAGONBALL};

View File

@ -1674,7 +1674,7 @@ dependencies = [
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows",
"windows 0.48.0",
]
[[package]]
@ -1875,9 +1875,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.147"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "libz-sys"
@ -3899,9 +3899,9 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.29.11"
version = "0.30.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd727fc423c2060f6c92d9534cef765c65a6ed3f428a03d7def74a8c4348e666"
checksum = "0c385888ef380a852a16209afc8cfad22795dd8873d69c9a14d2e2088f118d18"
dependencies = [
"cfg-if 1.0.0",
"core-foundation-sys",
@ -3909,7 +3909,7 @@ dependencies = [
"ntapi",
"once_cell",
"rayon",
"winapi",
"windows 0.52.0",
]
[[package]]
@ -4722,6 +4722,25 @@ dependencies = [
"windows-targets 0.48.1",
]
[[package]]
name = "windows"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
dependencies = [
"windows-core",
"windows-targets 0.52.0",
]
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets 0.52.0",
]
[[package]]
name = "windows-sys"
version = "0.45.0"

View File

@ -273,7 +273,11 @@ ifneq (,$(QEMUCMD))
# qemu-specific options
DEFSANDBOXCGROUPONLY_QEMU := false
ifeq ($(ARCH), s390x)
VMROOTFSDRIVER_QEMU := virtio-blk-ccw
else
VMROOTFSDRIVER_QEMU := virtio-pmem
endif
DEFVCPUS_QEMU := 1
DEFMAXVCPUS_QEMU := 0
DEFSHAREDFS_QEMU_VIRTIOFS := virtio-fs

View File

@ -4,12 +4,9 @@
# SPDX-License-Identifier: Apache-2.0
#
MACHINETYPE :=
MACHINETYPE := s390-ccw-virtio
KERNELPARAMS :=
MACHINEACCELERATORS :=
CPUFEATURES := pmu=off
CPUFEATURES :=
QEMUCMD := qemu-system-s390x
# dragonball binary name
DBCMD := dragonball

View File

@ -14,8 +14,8 @@ use tokio::sync::{Mutex, RwLock};
use crate::{
vhost_user_blk::VhostUserBlkDevice, BlockConfig, BlockDevice, HybridVsockDevice, Hypervisor,
NetworkDevice, ShareFsDevice, VfioDevice, VhostUserConfig, VhostUserNetDevice, VsockDevice,
KATA_BLK_DEV_TYPE, KATA_MMIO_BLK_DEV_TYPE, KATA_NVDIMM_DEV_TYPE, VIRTIO_BLOCK_MMIO,
VIRTIO_BLOCK_PCI, VIRTIO_PMEM,
KATA_BLK_DEV_TYPE, KATA_CCW_DEV_TYPE, KATA_MMIO_BLK_DEV_TYPE, KATA_NVDIMM_DEV_TYPE,
VIRTIO_BLOCK_CCW, VIRTIO_BLOCK_MMIO, VIRTIO_BLOCK_PCI, VIRTIO_PMEM,
};
use super::{
@ -447,6 +447,9 @@ impl DeviceManager {
VIRTIO_BLOCK_PCI => {
block_config.driver_option = KATA_BLK_DEV_TYPE.to_string();
}
VIRTIO_BLOCK_CCW => {
block_config.driver_option = KATA_CCW_DEV_TYPE.to_string();
}
VIRTIO_PMEM => {
block_config.driver_option = KATA_NVDIMM_DEV_TYPE.to_string();
is_pmem = true;

View File

@ -20,8 +20,8 @@ pub use vfio::{
pub use vhost_user::{VhostUserConfig, VhostUserDevice, VhostUserType};
pub use vhost_user_net::VhostUserNetDevice;
pub use virtio_blk::{
BlockConfig, BlockDevice, KATA_BLK_DEV_TYPE, KATA_MMIO_BLK_DEV_TYPE, KATA_NVDIMM_DEV_TYPE,
VIRTIO_BLOCK_MMIO, VIRTIO_BLOCK_PCI, VIRTIO_PMEM,
BlockConfig, BlockDevice, KATA_BLK_DEV_TYPE, KATA_CCW_DEV_TYPE, KATA_MMIO_BLK_DEV_TYPE,
KATA_NVDIMM_DEV_TYPE, VIRTIO_BLOCK_CCW, VIRTIO_BLOCK_MMIO, VIRTIO_BLOCK_PCI, VIRTIO_PMEM,
};
pub use virtio_fs::{
ShareFsConfig, ShareFsDevice, ShareFsMountConfig, ShareFsMountOperation, ShareFsMountType,

View File

@ -15,9 +15,11 @@ use async_trait::async_trait;
/// VIRTIO_BLOCK_PCI indicates block driver is virtio-pci based
pub const VIRTIO_BLOCK_PCI: &str = "virtio-blk-pci";
pub const VIRTIO_BLOCK_MMIO: &str = "virtio-blk-mmio";
pub const VIRTIO_BLOCK_CCW: &str = "virtio-blk-ccw";
pub const VIRTIO_PMEM: &str = "virtio-pmem";
pub const KATA_MMIO_BLK_DEV_TYPE: &str = "mmioblk";
pub const KATA_BLK_DEV_TYPE: &str = "blk";
pub const KATA_CCW_DEV_TYPE: &str = "ccw";
pub const KATA_NVDIMM_DEV_TYPE: &str = "nvdimm";
#[derive(Debug, Clone, Default)]

View File

@ -7,8 +7,9 @@
use anyhow::{anyhow, Result};
use crate::{
VM_ROOTFS_DRIVER_BLK, VM_ROOTFS_DRIVER_MMIO, VM_ROOTFS_DRIVER_PMEM, VM_ROOTFS_FILESYSTEM_EROFS,
VM_ROOTFS_FILESYSTEM_EXT4, VM_ROOTFS_FILESYSTEM_XFS, VM_ROOTFS_ROOT_BLK, VM_ROOTFS_ROOT_PMEM,
VM_ROOTFS_DRIVER_BLK, VM_ROOTFS_DRIVER_BLK_CCW, VM_ROOTFS_DRIVER_MMIO, VM_ROOTFS_DRIVER_PMEM,
VM_ROOTFS_FILESYSTEM_EROFS, VM_ROOTFS_FILESYSTEM_EXT4, VM_ROOTFS_FILESYSTEM_XFS,
VM_ROOTFS_ROOT_BLK, VM_ROOTFS_ROOT_PMEM,
};
use kata_types::config::LOG_VPORT_OPTION;
@ -89,7 +90,7 @@ impl KernelParams {
}
}
}
VM_ROOTFS_DRIVER_BLK | VM_ROOTFS_DRIVER_MMIO => {
VM_ROOTFS_DRIVER_BLK | VM_ROOTFS_DRIVER_BLK_CCW | VM_ROOTFS_DRIVER_MMIO => {
params.push(Param::new("root", VM_ROOTFS_ROOT_BLK));
match rootfs_type {
VM_ROOTFS_FILESYSTEM_EXT4 | VM_ROOTFS_FILESYSTEM_XFS => {

View File

@ -34,6 +34,7 @@ pub use kata_types::config::hypervisor::HYPERVISOR_NAME_CH;
// Config which driver to use as vm root dev
const VM_ROOTFS_DRIVER_BLK: &str = "virtio-blk-pci";
const VM_ROOTFS_DRIVER_BLK_CCW: &str = "virtio-blk-ccw";
const VM_ROOTFS_DRIVER_PMEM: &str = "virtio-pmem";
const VM_ROOTFS_DRIVER_MMIO: &str = "virtio-blk-mmio";

View File

@ -10,6 +10,7 @@ use crate::{kernel_param::KernelParams, HypervisorConfig, NetworkConfig};
use anyhow::{anyhow, Context, Result};
use async_trait::async_trait;
use kata_types::config::hypervisor::NetworkInfo;
use std::fmt::Display;
use std::fs::{read_to_string, File};
use std::os::fd::AsRawFd;
use std::os::unix::io::RawFd;
@ -44,6 +45,27 @@ trait ToQemuParams: Send + Sync {
async fn qemu_params(&self) -> Result<Vec<String>>;
}
#[derive(Debug)]
enum VirtioBusType {
Pci,
Ccw,
}
impl VirtioBusType {
fn as_str(&self) -> &str {
match self {
VirtioBusType::Pci => "pci",
VirtioBusType::Ccw => "ccw",
}
}
}
impl Display for VirtioBusType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug)]
struct Kernel {
// PathBuf would seem more appropriae but since we get the kernel path
@ -263,6 +285,7 @@ struct Machine {
nvdimm: bool,
is_nvdimm_supported: bool,
memory_backend: Option<String>,
}
impl Machine {
@ -288,6 +311,7 @@ impl Machine {
options: config.machine_info.machine_accelerators.clone(),
nvdimm: false,
is_nvdimm_supported,
memory_backend: None,
}
}
@ -298,6 +322,11 @@ impl Machine {
self.nvdimm = is_on && self.is_nvdimm_supported;
self
}
fn set_memory_backend(&mut self, mem_backend: &str) -> &mut Self {
self.memory_backend = Some(mem_backend.to_owned());
self
}
}
#[async_trait]
@ -312,6 +341,9 @@ impl ToQemuParams for Machine {
if self.nvdimm {
params.push("nvdimm=on".to_owned());
}
if let Some(mem_backend) = &self.memory_backend {
params.push(format!("memory-backend={}", mem_backend));
}
Ok(vec!["-machine".to_owned(), params.join(",")])
}
}
@ -496,13 +528,11 @@ impl ChardevSocket {
}
}
#[allow(dead_code)]
fn set_server(&mut self, server: bool) -> &mut Self {
self.server = server;
self
}
#[allow(dead_code)]
fn set_wait(&mut self, wait: bool) -> &mut Self {
self.wait = wait;
self
@ -524,6 +554,8 @@ impl ToQemuParams for ChardevSocket {
params.push("server=on".to_owned());
if self.wait {
params.push("wait=on".to_owned());
} else {
params.push("wait=off".to_owned());
}
}
params.append(&mut self.protocol_options.qemu_params().await?);
@ -532,7 +564,8 @@ impl ToQemuParams for ChardevSocket {
}
#[derive(Debug)]
struct DeviceVhostUserFsPci {
struct DeviceVhostUserFs {
bus_type: VirtioBusType,
chardev: String,
tag: String,
queue_size: u64,
@ -540,9 +573,10 @@ struct DeviceVhostUserFsPci {
iommu_platform: bool,
}
impl DeviceVhostUserFsPci {
fn new(chardev: &str, tag: &str) -> DeviceVhostUserFsPci {
DeviceVhostUserFsPci {
impl DeviceVhostUserFs {
fn new(chardev: &str, tag: &str, bus_type: VirtioBusType) -> DeviceVhostUserFs {
DeviceVhostUserFs {
bus_type,
chardev: chardev.to_owned(),
tag: tag.to_owned(),
queue_size: 0,
@ -559,7 +593,9 @@ impl DeviceVhostUserFsPci {
// throughout runtime-rs
warn!(
sl!(),
"bad vhost-user-fs-pci queue_size (must be power of two): {}, ignoring", queue_size
"bad vhost-user-fs-{} queue_size (must be power of two): {}, ignoring",
self.bus_type,
queue_size
);
}
self
@ -578,10 +614,10 @@ impl DeviceVhostUserFsPci {
}
#[async_trait]
impl ToQemuParams for DeviceVhostUserFsPci {
impl ToQemuParams for DeviceVhostUserFs {
async fn qemu_params(&self) -> Result<Vec<String>> {
let mut params = Vec::new();
params.push("vhost-user-fs-pci".to_owned());
params.push(format!("vhost-user-fs-{}", self.bus_type));
params.push(format!("chardev={}", self.chardev));
params.push(format!("tag={}", self.tag));
if self.queue_size != 0 {
@ -625,15 +661,165 @@ impl ToQemuParams for DeviceNvdimm {
}
}
struct VhostVsockPci {
#[derive(Debug)]
struct BlockBackend {
driver: String,
id: String,
path: String,
aio: String,
cache_direct: bool,
cache_no_flush: bool,
read_only: bool,
}
impl BlockBackend {
fn new(id: &str, path: &str) -> BlockBackend {
BlockBackend {
driver: "file".to_owned(),
id: id.to_owned(),
path: path.to_owned(),
aio: "threads".to_owned(),
cache_direct: true,
cache_no_flush: false,
read_only: true,
}
}
#[allow(dead_code)]
fn set_driver(&mut self, driver: &str) -> &mut Self {
self.driver = driver.to_owned();
self
}
#[allow(dead_code)]
fn set_aio(&mut self, aio: &str) -> &mut Self {
self.aio = aio.to_owned();
self
}
#[allow(dead_code)]
fn set_cache_direct(&mut self, cache_direct: bool) -> &mut Self {
self.cache_direct = cache_direct;
self
}
#[allow(dead_code)]
fn set_cache_no_flush(&mut self, cache_no_flush: bool) -> &mut Self {
self.cache_no_flush = cache_no_flush;
self
}
#[allow(dead_code)]
fn set_read_only(&mut self, read_only: bool) -> &mut Self {
self.read_only = read_only;
self
}
}
#[async_trait]
impl ToQemuParams for BlockBackend {
async fn qemu_params(&self) -> Result<Vec<String>> {
let mut params = Vec::new();
params.push(format!("driver={}", self.driver));
params.push(format!("node-name=image-{}", self.id));
params.push(format!("filename={}", self.path));
params.push(format!("aio={}", self.aio));
if self.cache_direct {
params.push("cache.direct=on".to_owned());
} else {
params.push("cache.direct=off".to_owned());
}
if self.cache_no_flush {
params.push("cache.no-flush=on".to_owned());
} else {
params.push("cache.no-flush=off".to_owned());
}
if self.read_only {
params.push("auto-read-only=on".to_owned());
} else {
params.push("auto-read-only=off".to_owned());
}
Ok(vec!["-blockdev".to_owned(), params.join(",")])
}
}
#[derive(Debug)]
struct DeviceVirtioBlk {
bus_type: VirtioBusType,
id: String,
scsi: bool,
config_wce: bool,
share_rw: bool,
}
impl DeviceVirtioBlk {
fn new(id: &str, bus_type: VirtioBusType) -> DeviceVirtioBlk {
DeviceVirtioBlk {
bus_type,
id: id.to_owned(),
scsi: false,
config_wce: false,
share_rw: true,
}
}
#[allow(dead_code)]
fn set_scsi(&mut self, scsi: bool) -> &mut Self {
self.scsi = scsi;
self
}
#[allow(dead_code)]
fn set_config_wce(&mut self, config_wce: bool) -> &mut Self {
self.config_wce = config_wce;
self
}
#[allow(dead_code)]
fn set_share_rw(&mut self, share_rw: bool) -> &mut Self {
self.share_rw = share_rw;
self
}
}
#[async_trait]
impl ToQemuParams for DeviceVirtioBlk {
async fn qemu_params(&self) -> Result<Vec<String>> {
let mut params = Vec::new();
params.push(format!("virtio-blk-{}", self.bus_type));
params.push(format!("drive=image-{}", self.id));
if self.scsi {
params.push("scsi=on".to_owned());
} else {
params.push("scsi=off".to_owned());
}
if self.config_wce {
params.push("config-wce=on".to_owned());
} else {
params.push("config-wce=off".to_owned());
}
if self.share_rw {
params.push("share-rw=on".to_owned());
} else {
params.push("share-rw=off".to_owned());
}
params.push(format!("serial=image-{}", self.id));
Ok(vec!["-device".to_owned(), params.join(",")])
}
}
struct VhostVsock {
bus_type: VirtioBusType,
vhostfd: RawFd,
guest_cid: u32,
disable_modern: bool,
}
impl VhostVsockPci {
fn new(vhostfd: RawFd, guest_cid: u32) -> VhostVsockPci {
VhostVsockPci {
impl VhostVsock {
fn new(vhostfd: RawFd, guest_cid: u32, bus_type: VirtioBusType) -> VhostVsock {
VhostVsock {
bus_type,
vhostfd,
guest_cid,
disable_modern: false,
@ -647,10 +833,10 @@ impl VhostVsockPci {
}
#[async_trait]
impl ToQemuParams for VhostVsockPci {
impl ToQemuParams for VhostVsock {
async fn qemu_params(&self) -> Result<Vec<String>> {
let mut params = Vec::new();
params.push("vhost-vsock-pci".to_owned());
params.push(format!("vhost-vsock-{}", self.bus_type));
if self.disable_modern {
params.push("disable-modern=true".to_owned());
}
@ -725,6 +911,57 @@ impl ToQemuParams for NetDevice {
}
}
#[derive(Debug)]
struct DeviceVirtioSerial {
id: String,
bus_type: VirtioBusType,
}
impl DeviceVirtioSerial {
fn new(id: &str, bus_type: VirtioBusType) -> DeviceVirtioSerial {
DeviceVirtioSerial {
id: id.to_owned(),
bus_type,
}
}
}
#[async_trait]
impl ToQemuParams for DeviceVirtioSerial {
async fn qemu_params(&self) -> Result<Vec<String>> {
let mut params = Vec::new();
params.push(format!("virtio-serial-{}", self.bus_type));
params.push(format!("id={}", self.id));
Ok(vec!["-device".to_owned(), params.join(",")])
}
}
#[derive(Debug)]
struct DeviceVirtconsole {
id: String,
chardev: String,
}
impl DeviceVirtconsole {
fn new(id: &str, chardev: &str) -> DeviceVirtconsole {
DeviceVirtconsole {
id: id.to_owned(),
chardev: chardev.to_owned(),
}
}
}
#[async_trait]
impl ToQemuParams for DeviceVirtconsole {
async fn qemu_params(&self) -> Result<Vec<String>> {
let mut params = Vec::new();
params.push("virtconsole".to_owned());
params.push(format!("id={}", self.id));
params.push(format!("chardev={}", self.chardev));
Ok(vec!["-device".to_owned(), params.join(",")])
}
}
fn is_running_in_vm() -> Result<bool> {
let res = read_to_string("/proc/cpuinfo")?
.lines()
@ -767,6 +1004,14 @@ impl<'a> QemuCmdLine<'a> {
})
}
fn bus_type(&self) -> VirtioBusType {
if self.config.machine_info.machine_type.contains("-ccw-") {
VirtioBusType::Ccw
} else {
VirtioBusType::Pci
}
}
pub fn add_virtiofs_share(
&mut self,
virtiofsd_socket_path: &str,
@ -783,7 +1028,7 @@ impl<'a> QemuCmdLine<'a> {
self.devices.push(Box::new(virtiofsd_socket_chardev));
let mut virtiofs_device = DeviceVhostUserFsPci::new(chardev_name, mount_tag);
let mut virtiofs_device = DeviceVhostUserFs::new(chardev_name, mount_tag, self.bus_type());
virtiofs_device.set_queue_size(queue_size);
if self.config.device_info.enable_iommu_platform {
virtiofs_device.set_iommu_platform(true);
@ -799,14 +1044,21 @@ impl<'a> QemuCmdLine<'a> {
//self.devices.push(Box::new(mem_file));
self.memory.set_memory_backend_file(&mem_file);
self.machine.set_nvdimm(true);
self.devices.push(Box::new(NumaNode::new(&mem_file.id)));
match self.bus_type() {
VirtioBusType::Pci => {
self.machine.set_nvdimm(true);
self.devices.push(Box::new(NumaNode::new(&mem_file.id)));
}
VirtioBusType::Ccw => {
self.machine.set_memory_backend(&mem_file.id);
}
}
}
pub fn add_vsock(&mut self, vhostfd: RawFd, guest_cid: u32) -> Result<()> {
clear_fd_flags(vhostfd).context("clear flags failed")?;
let mut vhost_vsock_pci = VhostVsockPci::new(vhostfd, guest_cid);
let mut vhost_vsock_pci = VhostVsock::new(vhostfd, guest_cid, self.bus_type());
if !self.config.disable_nesting_checks {
let nested = match is_running_in_vm() {
@ -858,6 +1110,14 @@ impl<'a> QemuCmdLine<'a> {
Ok(())
}
pub fn add_block_device(&mut self, device_id: &str, path: &str) -> Result<()> {
self.devices
.push(Box::new(BlockBackend::new(device_id, path)));
self.devices
.push(Box::new(DeviceVirtioBlk::new(device_id, self.bus_type())));
Ok(())
}
#[allow(dead_code)]
pub fn add_serial_console(&mut self, character_device_file_path: &str) {
let serial = Serial::new(character_device_file_path);
@ -889,6 +1149,23 @@ impl<'a> QemuCmdLine<'a> {
Ok(fds)
}
pub fn add_console(&mut self, console_socket_path: &str) {
let serial_dev = DeviceVirtioSerial::new("serial0", self.bus_type());
self.devices.push(Box::new(serial_dev));
let chardev_name = "charconsole0";
let console_device = DeviceVirtconsole::new("console0", chardev_name);
self.devices.push(Box::new(console_device));
let mut console_socket_chardev = ChardevSocket::new(chardev_name);
console_socket_chardev.set_socket_opts(ProtocolOptions::Unix(UnixSocketOpts {
path: console_socket_path.to_owned(),
}));
console_socket_chardev.set_server(true);
console_socket_chardev.set_wait(false);
self.devices.push(Box::new(console_socket_chardev));
}
pub async fn build(&self) -> Result<Vec<String>> {
let mut result = Vec::new();

View File

@ -18,6 +18,7 @@ use kata_types::{
use persist::sandbox_persist::Persist;
use std::collections::HashMap;
use std::os::unix::io::AsRawFd;
use std::path::Path;
use std::process::Stdio;
use tokio::{
io::{AsyncBufReadExt, BufReader},
@ -101,6 +102,10 @@ impl QemuInner {
&block_dev.config.path_on_host,
block_dev.config.is_readonly,
)?,
"ccw" => cmdline.add_block_device(
block_dev.device_id.as_str(),
&block_dev.config.path_on_host,
)?,
unsupported => {
info!(sl!(), "unsupported block device driver: {}", unsupported)
}
@ -123,6 +128,10 @@ impl QemuInner {
// `tty` in it to get its device file path and use it as the argument).
//cmdline.add_serial_console("/dev/pts/23");
// Add a console to the devices of the cmdline
let console_socket_path = Path::new(&self.get_jailer_root().await?).join("console.sock");
cmdline.add_console(console_socket_path.to_str().unwrap());
info!(sl!(), "qemu args: {}", cmdline.build().await?.join(" "));
let mut command = Command::new(&self.config.path);
command.args(cmdline.build().await?);