Skip to main content

qemu_command_builder/args/
rtc.rs

1use crate::parsers::ARG_RTC;
2use crate::parsers::DELIM_COMMA;
3use crate::shell_string::ShellStringError;
4use crate::to_command::{ToArg, ToCommand};
5use crate::{QDateTime, qao};
6use bon::Builder;
7use chrono::NaiveDate;
8use chrono::NaiveDateTime;
9use proptest_derive::Arbitrary;
10use std::fmt::{Display, Formatter};
11use std::str::FromStr;
12
13const KEY_CLOCK: &str = "clock=";
14const KEY_DRIFT: &str = "driftfix=";
15const KEY_BASE: &str = "base=";
16const KEY_BASE_UTC: &str = "utc";
17const KEY_BASE_LOCALTIME: &str = "localtime";
18
19#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
20pub enum RtcBase {
21    /// Use the host UTC time.
22    Utc,
23    /// Use the host local time.
24    Localtime,
25    /// Start from a specific date/time.
26    Datetime(QDateTime),
27}
28
29impl Display for RtcBase {
30    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
31        match self {
32            RtcBase::Utc => {
33                write!(f, "{}{}", KEY_BASE, KEY_BASE_UTC)
34            }
35            RtcBase::Localtime => {
36                write!(f, "{}{}", KEY_BASE, KEY_BASE_LOCALTIME)
37            }
38            RtcBase::Datetime(dt) => {
39                let formatted = format!("{}{}", KEY_BASE, dt.0.format("%Y-%m-%dT%H:%M:%S"));
40                write!(f, "{}", formatted)
41            }
42        }
43    }
44}
45
46impl FromStr for RtcBase {
47    type Err = ();
48    fn from_str(s: &str) -> Result<Self, Self::Err> {
49        match s {
50            KEY_BASE_UTC => Ok(RtcBase::Utc),
51            KEY_BASE_LOCALTIME => Ok(RtcBase::Localtime),
52            maybe_dt => match NaiveDateTime::parse_from_str(maybe_dt, "%Y-%m-%dT%H:%M:%S") {
53                Ok(dt) => Ok(RtcBase::Datetime(QDateTime(dt.and_utc()))),
54                Err(_) => match NaiveDate::parse_from_str(maybe_dt, "%Y-%m-%d") {
55                    Ok(date) => Ok(RtcBase::Datetime(QDateTime(date.and_hms_opt(0, 0, 0).unwrap().and_utc()))),
56                    Err(_) => Err(()),
57                },
58            },
59        }
60    }
61}
62/// Supported RTC clock sources.
63#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
64pub enum RtcClock {
65    Host,
66    Rt,
67    Vm,
68}
69
70impl Display for RtcClock {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        match self {
73            RtcClock::Host => write!(f, "host"),
74            RtcClock::Rt => write!(f, "rt"),
75            RtcClock::Vm => write!(f, "vm"),
76        }
77    }
78}
79
80impl FromStr for RtcClock {
81    type Err = ();
82    fn from_str(s: &str) -> Result<Self, Self::Err> {
83        match s {
84            "host" => Ok(RtcClock::Host),
85            "rt" => Ok(RtcClock::Rt),
86            "vm" => Ok(RtcClock::Vm),
87            _ => Err(()),
88        }
89    }
90}
91impl ToArg for RtcClock {
92    fn to_arg(&self) -> &str {
93        match self {
94            RtcClock::Host => "host",
95            RtcClock::Rt => "rt",
96            RtcClock::Vm => "vm",
97        }
98    }
99}
100
101/// Supported QEMU RTC drift correction modes.
102#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
103pub enum RtcDriftFix {
104    None,
105    Slew,
106}
107
108impl Display for RtcDriftFix {
109    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
110        match self {
111            RtcDriftFix::None => write!(f, "none"),
112            RtcDriftFix::Slew => write!(f, "slew"),
113        }
114    }
115}
116
117impl FromStr for RtcDriftFix {
118    type Err = ();
119    fn from_str(s: &str) -> Result<Self, Self::Err> {
120        match s {
121            "none" => Ok(RtcDriftFix::None),
122            "slew" => Ok(RtcDriftFix::Slew),
123            _ => Err(()),
124        }
125    }
126}
127impl ToArg for RtcDriftFix {
128    fn to_arg(&self) -> &str {
129        match self {
130            RtcDriftFix::None => "none",
131            RtcDriftFix::Slew => "slew",
132        }
133    }
134}
135/// A QEMU `-rtc` definition.
136#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
137pub struct Rtc {
138    /// The base RTC time.
139    base: Option<RtcBase>,
140    /// The RTC clock source.
141    clock: Option<RtcClock>,
142    /// The RTC drift correction mode.
143    drift_fix: Option<RtcDriftFix>,
144}
145
146impl ToCommand for Rtc {
147    fn has_args(&self) -> bool {
148        self.base.is_some() || self.clock.is_some() || self.drift_fix.is_some()
149    }
150    fn command(&self) -> String {
151        ARG_RTC.to_string()
152    }
153    fn to_args(&self) -> Vec<String> {
154        let mut args = vec![];
155
156        if let Some(base) = &self.base {
157            args.push(base.to_string());
158        }
159        qao!(&self.clock, args, KEY_CLOCK);
160        qao!(&self.drift_fix, args, KEY_DRIFT);
161
162        vec![args.join(DELIM_COMMA)]
163    }
164}
165
166impl FromStr for Rtc {
167    type Err = ShellStringError;
168
169    fn from_str(s: &str) -> Result<Self, Self::Err> {
170        let mut base = None;
171        let mut clock = None;
172        let mut drift_fix = None;
173
174        for part in s.split(DELIM_COMMA).filter(|part| !part.is_empty()) {
175            let (key, value) = part.split_once('=').ok_or_else(|| ShellStringError::new(format!("invalid -rtc option: {part}")))?;
176            match key {
177                "base" => base = Some(value.parse::<RtcBase>().map_err(|_| ShellStringError::new(format!("invalid base value: {value}")))?),
178                "clock" => clock = Some(value.parse::<RtcClock>().map_err(|_| ShellStringError::new(format!("invalid clock value: {value}")))?),
179                "driftfix" => drift_fix = Some(value.parse::<RtcDriftFix>().map_err(|_| ShellStringError::new(format!("invalid driftfix value: {value}")))?),
180                "drift" => drift_fix = Some(value.parse::<RtcDriftFix>().map_err(|_| ShellStringError::new(format!("invalid drift value: {value}")))?),
181                other => return Err(ShellStringError::new(format!("unsupported -rtc option: {other}"))),
182            }
183        }
184
185        Ok(Rtc { base, clock, drift_fix })
186    }
187}