diff --git a/cloud-hypervisor/src/main.rs b/cloud-hypervisor/src/main.rs index 5f55dd537..bc7a0d12e 100644 --- a/cloud-hypervisor/src/main.rs +++ b/cloud-hypervisor/src/main.rs @@ -32,9 +32,10 @@ use vmm::vm_config::FwCfgConfig; #[cfg(feature = "ivshmem")] use vmm::vm_config::IvshmemConfig; use vmm::vm_config::{ - BalloonConfig, DeviceConfig, DiskConfig, FsConfig, GenericVhostUserConfig, LandlockConfig, - NetConfig, NumaConfig, PciSegmentConfig, PlatformConfig, PmemConfig, RateLimiterGroupConfig, - RngConfig, TpmConfig, UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig, + BalloonConfig, ConsoleConfig, DeviceConfig, DiskConfig, FsConfig, GenericVhostUserConfig, + LandlockConfig, NetConfig, NumaConfig, PciSegmentConfig, PlatformConfig, PmemConfig, + RateLimiterGroupConfig, RngConfig, SerialConfig, TpmConfig, UserDeviceConfig, VdpaConfig, + VmConfig, VsockConfig, }; use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::signal::block_signal; @@ -165,7 +166,7 @@ fn get_cli_options_sorted( .group("vm-config"), Arg::new("console") .long("console") - .help("Control (virtio) console: \"off|null|pty|tty|file=,iommu=on|off\"") + .help(ConsoleConfig::SYNTAX) .default_value("tty") .group("vm-config"), Arg::new("cpus") @@ -405,7 +406,7 @@ fn get_cli_options_sorted( .default_value("true"), Arg::new("serial") .long("serial") - .help("Control serial port: off|null|pty|tty|file=|socket=") + .help(SerialConfig::SYNTAX) .default_value("null") .group("vm-config"), Arg::new("tpm") @@ -920,8 +921,9 @@ mod unit_tests { #[cfg(target_arch = "x86_64")] use vmm::vm_config::DebugConsoleConfig; use vmm::vm_config::{ - ConsoleConfig, ConsoleOutputMode, CoreScheduling, CpuFeatures, CpusConfig, HotplugMethod, - MemoryConfig, PayloadConfig, PciDeviceCommonConfig, RngConfig, VmConfig, + CommonConsoleConfig, ConsoleConfig, ConsoleOutputMode, CoreScheduling, CpuFeatures, + CpusConfig, HotplugMethod, MemoryConfig, PayloadConfig, PciDeviceCommonConfig, RngConfig, + SerialConfig, VmConfig, }; use crate::test_util::assert_args_sorted; @@ -1010,17 +1012,21 @@ mod unit_tests { fs: None, generic_vhost_user: None, pmem: None, - serial: ConsoleConfig { - file: None, - mode: ConsoleOutputMode::Null, + serial: SerialConfig { + common: CommonConsoleConfig { + file: None, + mode: ConsoleOutputMode::Null, + socket: None, + }, iommu: false, - socket: None, }, console: ConsoleConfig { - file: None, - mode: ConsoleOutputMode::Tty, + common: CommonConsoleConfig { + file: None, + mode: ConsoleOutputMode::Tty, + socket: None, + }, iommu: false, - socket: None, }, #[cfg(target_arch = "x86_64")] debug_console: DebugConsoleConfig::default(), diff --git a/fuzz/fuzz_targets/http_api.rs b/fuzz/fuzz_targets/http_api.rs index 79b8fe215..41246392f 100644 --- a/fuzz/fuzz_targets/http_api.rs +++ b/fuzz/fuzz_targets/http_api.rs @@ -171,17 +171,21 @@ impl RequestHandler for StubApiRequestHandler { fs: None, generic_vhost_user: None, pmem: None, - serial: ConsoleConfig { - file: None, - mode: ConsoleOutputMode::Null, + serial: SerialConfig { + common: CommonConsoleConfig { + file: None, + mode: ConsoleOutputMode::Tty, + socket: None, + }, iommu: false, - socket: None, }, console: ConsoleConfig { - file: None, - mode: ConsoleOutputMode::Tty, + common: CommonConsoleConfig { + file: None, + mode: ConsoleOutputMode::Tty, + socket: None, + }, iommu: false, - socket: None, }, #[cfg(target_arch = "x86_64")] debug_console: DebugConsoleConfig::default(), diff --git a/vmm/src/config.rs b/vmm/src/config.rs index d680c23d3..217e07aaa 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -128,9 +128,12 @@ pub enum Error { /// Error parsing generic vhost-user parameters #[error("Error parsing --generic-vhost-user")] ParseGenericVhostUser(#[source] OptionParserError), - /// Failed parsing console + /// Failed parsing console parameters #[error("Error parsing --console")] ParseConsole(#[source] OptionParserError), + /// Failed parsing serial parameters + #[error("Error parsing --serial")] + ParseSerial(#[source] OptionParserError), #[cfg(target_arch = "x86_64")] /// Failed parsing debug-console #[error("Error parsing --debug-console")] @@ -2137,17 +2140,18 @@ impl PmemConfig { } } -impl ConsoleConfig { - pub fn parse(console: &str) -> Result { +impl CommonConsoleConfig { + const VALUELESS_OPTIONS: &[&str] = &["off", "pty", "tty", "null"]; + const VALUE_OPTIONS: &[&str] = &["file", "socket"]; + + fn parse(console: &str, map_err: impl Fn(OptionParserError) -> Error) -> Result { let mut parser = OptionParser::new(); parser - .add_all_valueless(&["off", "pty", "tty", "null"]) - .add("file") - .add("iommu") - .add("socket"); - parser.parse(console).map_err(Error::ParseConsole)?; + .add_all_valueless(Self::VALUELESS_OPTIONS) + .add_all(Self::VALUE_OPTIONS); + parser.parse_subset(console).map_err(map_err)?; - let mut file: Option = default_consoleconfig_file(); + let mut file: Option = None; let mut socket: Option = None; let mut mode: ConsoleOutputMode = ConsoleOutputMode::Off; @@ -2172,18 +2176,49 @@ impl ConsoleConfig { } else { return Err(Error::ParseConsoleInvalidModeGiven); } + + Ok(Self { mode, file, socket }) + } +} + +impl ConsoleConfig { + pub fn parse(console: &str) -> Result { + let mut parser = OptionParser::new(); + parser + .add_all_valueless(CommonConsoleConfig::VALUELESS_OPTIONS) + .add_all(CommonConsoleConfig::VALUE_OPTIONS) + .add("iommu"); + parser.parse(console).map_err(Error::ParseConsole)?; + let iommu = parser .convert::("iommu") - .map_err(Error::ParseConsole)? + .map_err(Error::ParsePciDeviceCommonConfig)? .unwrap_or(Toggle(false)) .0; - Ok(Self { - file, - mode, - iommu, - socket, - }) + let common = CommonConsoleConfig::parse(console, Error::ParseConsole)?; + + Ok(Self { common, iommu }) + } +} + +impl SerialConfig { + pub fn parse(serial: &str) -> Result { + let mut parser = OptionParser::new(); + parser + .add_all_valueless(CommonConsoleConfig::VALUELESS_OPTIONS) + .add_all(CommonConsoleConfig::VALUE_OPTIONS) + .add("iommu"); + parser.parse(serial).map_err(Error::ParseSerial)?; + + let iommu = parser + .convert::("iommu") + .map_err(Error::ParsePciDeviceCommonConfig)? + .unwrap_or(Toggle(false)) + .0; + + let common = CommonConsoleConfig::parse(serial, Error::ParseSerial)?; + Ok(Self { common, iommu }) } } @@ -2202,7 +2237,7 @@ impl DebugConsoleConfig { .parse(debug_console_ops) .map_err(Error::ParseConsole)?; - let mut file: Option = default_consoleconfig_file(); + let mut file: Option = None; let mut iobase: Option = None; let mut mode: ConsoleOutputMode = ConsoleOutputMode::Off; @@ -2894,10 +2929,10 @@ impl VmConfig { // "console=hvc0 earlyprintk=ttyS0" let mut tty_consoles = Vec::new(); - if self.console.mode == ConsoleOutputMode::Tty { + if self.console.common.mode == ConsoleOutputMode::Tty { tty_consoles.push("virtio-console"); } - if self.serial.mode == ConsoleOutputMode::Tty { + if self.serial.common.mode == ConsoleOutputMode::Tty { tty_consoles.push("serial-console"); } #[cfg(target_arch = "x86_64")] @@ -2908,11 +2943,12 @@ impl VmConfig { warn!("Using TTY output for multiple consoles: {tty_consoles:?}"); } - if self.console.mode == ConsoleOutputMode::File && self.console.file.is_none() { + if self.console.common.mode == ConsoleOutputMode::File && self.console.common.file.is_none() + { return Err(ValidationError::ConsoleFileMissing); } - if self.serial.mode == ConsoleOutputMode::File && self.serial.file.is_none() { + if self.serial.common.mode == ConsoleOutputMode::File && self.serial.common.file.is_none() { return Err(ValidationError::ConsoleFileMissing); } @@ -3298,7 +3334,7 @@ impl VmConfig { } let console = ConsoleConfig::parse(vm_params.console)?; - let serial = ConsoleConfig::parse(vm_params.serial)?; + let serial = SerialConfig::parse(vm_params.serial)?; #[cfg(target_arch = "x86_64")] let debug_console = DebugConsoleConfig::parse(vm_params.debug_console)?; @@ -4380,79 +4416,59 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" #[test] fn test_console_parsing() -> Result<()> { + let console_config = |mode, file, socket, iommu| ConsoleConfig { + common: CommonConsoleConfig { file, mode, socket }, + iommu, + }; + ConsoleConfig::parse("").unwrap_err(); ConsoleConfig::parse("badmode").unwrap_err(); assert_eq!( ConsoleConfig::parse("off")?, - ConsoleConfig { - mode: ConsoleOutputMode::Off, - iommu: false, - file: None, - socket: None, - } + console_config(ConsoleOutputMode::Off, None, None, false) ); assert_eq!( ConsoleConfig::parse("pty")?, - ConsoleConfig { - mode: ConsoleOutputMode::Pty, - iommu: false, - file: None, - socket: None, - } + console_config(ConsoleOutputMode::Pty, None, None, false) ); assert_eq!( ConsoleConfig::parse("tty")?, - ConsoleConfig { - mode: ConsoleOutputMode::Tty, - iommu: false, - file: None, - socket: None, - } + console_config(ConsoleOutputMode::Tty, None, None, false) ); assert_eq!( ConsoleConfig::parse("null")?, - ConsoleConfig { - mode: ConsoleOutputMode::Null, - iommu: false, - file: None, - socket: None, - } + console_config(ConsoleOutputMode::Null, None, None, false) ); assert_eq!( ConsoleConfig::parse("file=/tmp/console")?, - ConsoleConfig { - mode: ConsoleOutputMode::File, - iommu: false, - file: Some(PathBuf::from("/tmp/console")), - socket: None, - } + console_config( + ConsoleOutputMode::File, + Some(PathBuf::from("/tmp/console")), + None, + false + ) ); assert_eq!( ConsoleConfig::parse("null,iommu=on")?, - ConsoleConfig { - mode: ConsoleOutputMode::Null, - iommu: true, - file: None, - socket: None, - } + console_config(ConsoleOutputMode::Null, None, None, true) ); assert_eq!( ConsoleConfig::parse("file=/tmp/console,iommu=on")?, - ConsoleConfig { - mode: ConsoleOutputMode::File, - iommu: true, - file: Some(PathBuf::from("/tmp/console")), - socket: None, - } + console_config( + ConsoleOutputMode::File, + Some(PathBuf::from("/tmp/console")), + None, + true + ) ); assert_eq!( ConsoleConfig::parse("socket=/tmp/serial.sock,iommu=on")?, - ConsoleConfig { - mode: ConsoleOutputMode::Socket, - iommu: true, - file: None, - socket: Some(PathBuf::from("/tmp/serial.sock")), - } + console_config( + ConsoleOutputMode::Socket, + None, + Some(PathBuf::from("/tmp/serial.sock")), + true + ) ); Ok(()) } @@ -4798,8 +4814,8 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" balloon: None, fs: None, pmem: None, - serial: default_serial(), - console: default_console(), + serial: SerialConfig::default(), + console: ConsoleConfig::default(), #[cfg(target_arch = "x86_64")] debug_console: DebugConsoleConfig::default(), devices: None, @@ -5032,17 +5048,21 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" fs: None, generic_vhost_user: None, pmem: None, - serial: ConsoleConfig { - file: None, - mode: ConsoleOutputMode::Null, + serial: SerialConfig { + common: CommonConsoleConfig { + file: None, + mode: ConsoleOutputMode::Null, + socket: None, + }, iommu: false, - socket: None, }, console: ConsoleConfig { - file: None, - mode: ConsoleOutputMode::Tty, + common: CommonConsoleConfig { + file: None, + mode: ConsoleOutputMode::Tty, + socket: None, + }, iommu: false, - socket: None, }, #[cfg(target_arch = "x86_64")] debug_console: DebugConsoleConfig::default(), @@ -5071,8 +5091,8 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" valid_config.validate().unwrap(); let mut invalid_config = valid_config.clone(); - invalid_config.serial.mode = ConsoleOutputMode::Tty; - invalid_config.console.mode = ConsoleOutputMode::Tty; + invalid_config.serial.common.mode = ConsoleOutputMode::Tty; + invalid_config.console.common.mode = ConsoleOutputMode::Tty; valid_config.validate().unwrap(); let mut invalid_config = valid_config.clone(); @@ -5112,8 +5132,8 @@ id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" } let mut invalid_config = valid_config.clone(); - invalid_config.serial.mode = ConsoleOutputMode::File; - invalid_config.serial.file = None; + invalid_config.serial.common.mode = ConsoleOutputMode::File; + invalid_config.serial.common.file = None; assert_eq!( invalid_config.validate(), Err(ValidationError::ConsoleFileMissing) diff --git a/vmm/src/console_devices.rs b/vmm/src/console_devices.rs index 1b21440a2..a1f3493fd 100644 --- a/vmm/src/console_devices.rs +++ b/vmm/src/console_devices.rs @@ -181,9 +181,9 @@ pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult { - let file = File::create(vmconfig.console.file.as_ref().unwrap()) + let file = File::create(vmconfig.console.common.file.as_ref().unwrap()) .map_err(ConsoleDeviceError::CreateConsoleDevice)?; ConsoleTransport::File(Arc::new(file)) } @@ -191,7 +191,7 @@ pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult ConsoleDeviceResult ConsoleTransport::Null, ConsoleOutputMode::Off => ConsoleTransport::Off, }, - serial: match vmconfig.serial.mode { + serial: match vmconfig.serial.common.mode { ConsoleOutputMode::File => { - let file = File::create(vmconfig.serial.file.as_ref().unwrap()) + let file = File::create(vmconfig.serial.common.file.as_ref().unwrap()) .map_err(ConsoleDeviceError::CreateConsoleDevice)?; ConsoleTransport::File(Arc::new(file)) } @@ -240,7 +240,7 @@ pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult { @@ -260,7 +260,7 @@ pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult { - let listener = UnixListener::bind(vmconfig.serial.socket.as_ref().unwrap()) + let listener = UnixListener::bind(vmconfig.serial.common.socket.as_ref().unwrap()) .map_err(ConsoleDeviceError::CreateConsoleDevice)?; ConsoleTransport::Socket(Arc::new(listener)) } diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 640181d70..739bf424b 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -2440,11 +2440,13 @@ impl DeviceManager { .insert(id.clone(), device_node!(id, virtio_console_device)); // Only provide a resizer (for SIGWINCH handling) if the console is attached to the TTY - Ok(if matches!(console_config.mode, ConsoleOutputMode::Tty) { - Some(console_resizer) - } else { - None - }) + Ok( + if matches!(console_config.common.mode, ConsoleOutputMode::Tty) { + Some(console_resizer) + } else { + None + }, + ) } /// Adds all devices that behave like a console with respect to the VM @@ -2482,9 +2484,12 @@ impl DeviceManager { ConsoleTransport::Pty(_) | ConsoleTransport::Tty(_) | ConsoleTransport::Socket(_) => { - let serial_manager = - SerialManager::new(serial, console_info.serial, serial_config.socket) - .map_err(DeviceManagerError::CreateSerialManager)?; + let serial_manager = SerialManager::new( + serial, + console_info.serial, + serial_config.common.socket, + ) + .map_err(DeviceManagerError::CreateSerialManager)?; if let Some(mut serial_manager) = serial_manager { serial_manager .start_thread( @@ -5442,18 +5447,18 @@ impl Aml for DeviceManager { #[cfg(target_arch = "x86_64")] let serial_irq = 4; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] - let serial_irq = - if self.config.lock().unwrap().serial.clone().mode == ConsoleOutputMode::Off { - // If serial is turned off, add a fake device with invalid irq. - 31 - } else { - self.get_device_info() - .clone() - .get(&(DeviceType::Serial, DeviceType::Serial.to_string())) - .unwrap() - .irq() - }; - if self.config.lock().unwrap().serial.mode != ConsoleOutputMode::Off { + let serial_irq = if self.config.lock().unwrap().serial.common.mode == ConsoleOutputMode::Off + { + // If serial is turned off, add a fake device with invalid irq. + 31 + } else { + self.get_device_info() + .clone() + .get(&(DeviceType::Serial, DeviceType::Serial.to_string())) + .unwrap() + .irq() + }; + if self.config.lock().unwrap().serial.common.mode != ConsoleOutputMode::Off { aml::Device::new( "_SB_.COM1".into(), vec![ diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 5ebcf250e..eb996bc89 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -2653,8 +2653,9 @@ mod unit_tests { #[cfg(target_arch = "x86_64")] use crate::vm_config::DebugConsoleConfig; use crate::vm_config::{ - ConsoleConfig, ConsoleOutputMode, CoreScheduling, CpuFeatures, CpusConfig, HotplugMethod, - MemoryConfig, PayloadConfig, PciDeviceCommonConfig, RngConfig, + CommonConsoleConfig, ConsoleConfig, ConsoleOutputMode, CoreScheduling, CpuFeatures, + CpusConfig, HotplugMethod, MemoryConfig, PayloadConfig, PciDeviceCommonConfig, RngConfig, + SerialConfig, }; fn create_dummy_vmm() -> Vmm { @@ -2722,18 +2723,22 @@ mod unit_tests { fs: None, generic_vhost_user: None, pmem: None, - serial: ConsoleConfig { - file: None, - mode: ConsoleOutputMode::Null, + serial: SerialConfig { + common: CommonConsoleConfig { + file: None, + mode: ConsoleOutputMode::Null, + socket: None, + }, iommu: false, - socket: None, }, console: ConsoleConfig { - file: None, - // Caution: Don't use `Tty` to not mess with users terminal - mode: ConsoleOutputMode::Off, + common: CommonConsoleConfig { + file: None, + // Caution: Don't use `Tty` to not mess with users terminal + mode: ConsoleOutputMode::Off, + socket: None, + }, iommu: false, - socket: None, }, #[cfg(target_arch = "x86_64")] debug_console: DebugConsoleConfig::default(), diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 349fac613..e6552f997 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -545,21 +545,19 @@ pub enum ConsoleOutputMode { Null, } +/// Common configuration for plain console configs. +/// +/// Independent of PCI or legacy devices. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct ConsoleConfig { - #[serde(default = "default_consoleconfig_file")] +pub struct CommonConsoleConfig { + #[serde(default)] pub file: Option, pub mode: ConsoleOutputMode, #[serde(default)] - pub iommu: bool, pub socket: Option, } -pub fn default_consoleconfig_file() -> Option { - None -} - -impl ApplyLandlock for ConsoleConfig { +impl ApplyLandlock for CommonConsoleConfig { fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { if self.mode == ConsoleOutputMode::Pty { landlock.add_rule_with_access(Path::new("/dev/pts"), "rw")?; @@ -575,6 +573,76 @@ impl ApplyLandlock for ConsoleConfig { } } +/// Configuration for a legacy serial console device. +/// +/// - On x86_64, this is a port I/O-mapped UART16550-compatible device +/// - On aarch64, this is a MMIO-mapped PL011 device +/// - On RISCV, this is a MMIO-mapped UART16550-compatible device +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct SerialConfig { + #[serde(flatten)] + pub common: CommonConsoleConfig, + #[serde(default, skip_serializing_if = "<&bool as std::ops::Not>::not")] + pub iommu: bool, +} + +impl SerialConfig { + pub const SYNTAX: &str = + "Control serial port: \"off|null|pty|tty|file=|socket=,iommu=on|off\""; +} + +impl Default for SerialConfig { + fn default() -> Self { + Self { + common: CommonConsoleConfig { + file: None, + mode: ConsoleOutputMode::Null, + socket: None, + }, + iommu: false, + } + } +} + +impl ApplyLandlock for SerialConfig { + fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { + self.common.apply_landlock(landlock) + } +} + +/// Configuration for a virtio-console device. +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct ConsoleConfig { + #[serde(flatten)] + pub common: CommonConsoleConfig, + #[serde(default, skip_serializing_if = "<&bool as std::ops::Not>::not")] + pub iommu: bool, +} + +impl ConsoleConfig { + pub const SYNTAX: &str = + "Control (virtio) console: \"off|null|pty|tty|file=,iommu=on|off\""; +} + +impl Default for ConsoleConfig { + fn default() -> Self { + Self { + common: CommonConsoleConfig { + file: None, + mode: ConsoleOutputMode::Tty, + socket: None, + }, + iommu: false, + } + } +} + +impl ApplyLandlock for ConsoleConfig { + fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { + self.common.apply_landlock(landlock) + } +} + #[cfg(target_arch = "x86_64")] #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct DebugConsoleConfig { @@ -929,24 +997,6 @@ impl ApplyLandlock for PayloadConfig { } } -pub fn default_serial() -> ConsoleConfig { - ConsoleConfig { - file: None, - mode: ConsoleOutputMode::Null, - iommu: false, - socket: None, - } -} - -pub fn default_console() -> ConsoleConfig { - ConsoleConfig { - file: None, - mode: ConsoleOutputMode::Tty, - iommu: false, - socket: None, - } -} - #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct TpmConfig { pub socket: PathBuf, @@ -988,9 +1038,9 @@ pub struct VmConfig { pub generic_vhost_user: Option>, pub fs: Option>, pub pmem: Option>, - #[serde(default = "default_serial")] - pub serial: ConsoleConfig, - #[serde(default = "default_console")] + #[serde(default)] + pub serial: SerialConfig, + #[serde(default)] pub console: ConsoleConfig, #[cfg(target_arch = "x86_64")] #[serde(default)]