qemu_command_builder/args/
rtc.rs1use 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 Utc,
23 Localtime,
25 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#[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#[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#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
137pub struct Rtc {
138 base: Option<RtcBase>,
140 clock: Option<RtcClock>,
142 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}