Skip to main content

chrony_confile/
ser.rs

1//! Serialization traits for chrony configuration types.
2//!
3//! Implements [`Display`](std::fmt::Display) for [`ChronyConfig`], [`ConfigNode`], and all
4//! directive types. When the `serde` feature is enabled, also provides `Serialize`
5//! for [`ChronyConfig`].
6
7use std::fmt;
8use crate::ast::*;
9
10impl fmt::Display for ChronyConfig {
11    /// Formats the entire configuration as a chrony config file string.
12    ///
13    /// Leading comments, trailing comments, and blank lines are preserved,
14    /// enabling lossless round-trip serialization.
15    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16        for node in &self.nodes {
17            write!(f, "{node}")?;
18        }
19        Ok(())
20    }
21}
22
23impl fmt::Display for ConfigNode {
24    /// Formats a single configuration node as it would appear in a chrony config file.
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        match self {
27            Self::Comment(s) => writeln!(f, "# {s}"),
28            Self::BlankLine => writeln!(f),
29            Self::Directive(d) => {
30                for comment in &d.leading_comments {
31                    writeln!(f, "# {comment}")?;
32                }
33                fmt_directive_kind(f, &d.kind)?;
34                if let Some(ref tc) = d.trailing_comment {
35                    write!(f, "  # {tc}")?;
36                }
37                writeln!(f)
38            }
39        }
40    }
41}
42
43fn fmt_directive_kind(f: &mut fmt::Formatter<'_>, kind: &DirectiveKind) -> fmt::Result {
44    match kind {
45        // Source
46        DirectiveKind::Server(c) => write_server(f, c),
47        DirectiveKind::Pool(c) => {
48            write!(f, "pool {}", c.source.hostname)?;
49            write_server_options(f, &c.source)?;
50            if c.max_sources != 4 {
51                write!(f, " maxsources {}", c.max_sources)?;
52            }
53            Ok(())
54        }
55        DirectiveKind::Peer(c) => {
56            write!(f, "peer {}", c.hostname)?;
57            write_server_options(f, c)?;
58            Ok(())
59        }
60        DirectiveKind::InitStepSlew(c) => {
61            write!(f, "initstepslew {}", c.threshold)?;
62            for hostname in &c.hostnames {
63                write!(f, " {hostname}")?;
64            }
65            Ok(())
66        }
67        DirectiveKind::RefClock(c) => write_refclock(f, c),
68        DirectiveKind::Manual => write!(f, "manual"),
69        DirectiveKind::AcquisitionPort(c) => write!(f, "acquisitionport {}", c.port),
70        DirectiveKind::BindAcqAddress(c) => write!(f, "bindacqaddress {}", c.address),
71        DirectiveKind::BindAcqDevice(c) => write!(f, "bindacqdevice {}", c.interface),
72        DirectiveKind::Dscp(c) => write!(f, "dscp {}", c.dscp),
73        DirectiveKind::DumpDir(c) => write!(f, "dumpdir {}", c.directory),
74        DirectiveKind::MaxSamples(c) => write!(f, "maxsamples {}", c.samples),
75        DirectiveKind::MinSamples(c) => write!(f, "minsamples {}", c.samples),
76        DirectiveKind::NtsDumpDir(c) => write!(f, "ntsdumpdir {}", c.directory),
77        DirectiveKind::NtsRefresh(c) => write!(f, "ntsrefresh {}", c.interval),
78        DirectiveKind::NtsTrustedCerts(c) => {
79            if c.set_id != 0 {
80                write!(f, "ntstrustedcerts {} {}", c.set_id, c.path)
81            } else {
82                write!(f, "ntstrustedcerts {}", c.path)
83            }
84        }
85        DirectiveKind::NoSystemCert => write!(f, "nosystemcert"),
86        DirectiveKind::NoCertTimeCheck(c) => write!(f, "nocerttimecheck {}", c.limit),
87        DirectiveKind::Refresh(c) => write!(f, "refresh {}", c.interval),
88
89        // Selection
90        DirectiveKind::AuthSelectMode(m) => write!(f, "authselectmode {}", format_enum(m)),
91        DirectiveKind::CombineLimit(c) => write!(f, "combinelimit {}", c.limit),
92        DirectiveKind::MaxDistance(c) => write!(f, "maxdistance {}", c.distance),
93        DirectiveKind::MaxJitter(c) => write!(f, "maxjitter {}", c.jitter),
94        DirectiveKind::MinSources(c) => write!(f, "minsources {}", c.sources),
95        DirectiveKind::ReselectDist(c) => write!(f, "reselectdist {}", c.distance),
96        DirectiveKind::StratumWeight(c) => write!(f, "stratumweight {}", c.distance),
97
98        // System clock
99        DirectiveKind::ClockPrecision(c) => write!(f, "clockprecision {}", c.precision),
100        DirectiveKind::CorrTimeRatio(c) => write!(f, "corrtimeratio {}", c.ratio),
101        DirectiveKind::DriftFile(c) => {
102            write!(f, "driftfile {}", c.path)?;
103            if let Some(interval) = c.interval {
104                write!(f, " interval {interval}")?;
105            }
106            Ok(())
107        }
108        DirectiveKind::FallbackDrift(c) => write!(f, "fallbackdrift {} {}", c.min, c.max),
109        DirectiveKind::LeapSecMode(m) => write!(f, "leapsecmode {}", format_enum(m)),
110        DirectiveKind::LeapSecTz(c) => write!(f, "leapsectz {}", c.timezone),
111        DirectiveKind::LeapSecList(c) => write!(f, "leapseclist {}", c.file),
112        DirectiveKind::MakeStep(c) => write!(f, "makestep {} {}", c.threshold, c.limit),
113        DirectiveKind::MaxChange(c) => write!(f, "maxchange {} {} {}", c.offset, c.start, c.ignore),
114        DirectiveKind::MaxClockError(c) => write!(f, "maxclockerror {}", c.error_ppm),
115        DirectiveKind::MaxDrift(c) => write!(f, "maxdrift {}", c.drift_ppm),
116        DirectiveKind::MaxUpdateSkew(c) => write!(f, "maxupdateskew {}", c.skew_ppm),
117        DirectiveKind::MaxSlewRate(c) => write!(f, "maxslewrate {}", c.rate_ppm),
118        DirectiveKind::TempComp(c) => match c {
119            TempCompConfig::Coefficients {
120                file,
121                interval,
122                t0,
123                k0,
124                k1,
125                k2,
126            } => write!(f, "tempcomp {file} {interval} {t0} {k0} {k1} {k2}"),
127            TempCompConfig::PointFile {
128                file,
129                interval,
130                points_file,
131            } => write!(f, "tempcomp {file} {interval} {points_file}"),
132        },
133
134        // NTP server
135        DirectiveKind::Allow(c) => write_allow_deny(f, "allow", c),
136        DirectiveKind::Deny(c) => write_allow_deny(f, "deny", c),
137        DirectiveKind::BindAddress(c) => write!(f, "bindaddress {}", c.address),
138        DirectiveKind::BindDevice(c) => write!(f, "binddevice {}", c.interface),
139        DirectiveKind::Broadcast(c) => write!(f, "broadcast {} {} {}", c.interval, c.address, c.port),
140        DirectiveKind::ClientLogLimit(c) => write!(f, "clientloglimit {}", c.limit),
141        DirectiveKind::NoClientLog => write!(f, "noclientlog"),
142        DirectiveKind::Local(c) => {
143            write!(f, "local")?;
144            if c.stratum.get() != 10 {
145                write!(f, " stratum {}", c.stratum)?;
146            }
147            if c.orphan {
148                write!(f, " orphan")?;
149            }
150            if (c.distance - 1.0).abs() > 1e-12 {
151                write!(f, " distance {}", c.distance)?;
152            }
153            Ok(())
154        }
155        DirectiveKind::NtpSignDSocket(c) => write!(f, "ntpsigndsocket {}", c.directory),
156        DirectiveKind::NtsPort(c) => write!(f, "ntsport {}", c.port),
157        DirectiveKind::NtsServerCert(c) => write!(f, "ntsservercert {}", c.file),
158        DirectiveKind::NtsServerKey(c) => write!(f, "ntsserverkey {}", c.file),
159        DirectiveKind::NtsProcesses(c) => write!(f, "ntsprocesses {}", c.processes),
160        DirectiveKind::MaxNtsConnections(c) => write!(f, "maxntsconnections {}", c.connections),
161        DirectiveKind::NtsNtpServer(c) => write!(f, "ntsntpserver {}", c.hostname),
162        DirectiveKind::NtsRotate(c) => write!(f, "ntsrotate {}", c.interval),
163        DirectiveKind::Port(c) => write!(f, "port {}", c.port),
164        DirectiveKind::RateLimit(c) => write!(
165            f,
166            "ratelimit interval {} burst {} leak {}{}",
167            c.interval,
168            c.burst,
169            c.leak,
170            c.kod.map_or(String::new(), |k| format!(" kod {k}"))
171        ),
172        DirectiveKind::NtsRateLimit(c) => {
173            write!(f, "ntsratelimit interval {} burst {} leak {}", c.interval, c.burst, c.leak)
174        }
175        DirectiveKind::SmoothTime(c) => {
176            write!(f, "smoothtime {} {}", c.max_freq, c.max_wander)?;
177            if c.leap_only {
178                write!(f, " leaponly")?;
179            }
180            Ok(())
181        }
182
183        // Command access
184        DirectiveKind::BindCmdAddress(c) => write!(f, "bindcmdaddress {}", c.address),
185        DirectiveKind::BindCmdDevice(c) => write!(f, "bindcmddevice {}", c.interface),
186        DirectiveKind::CmdAllow(c) => write_allow_deny(f, "cmdallow", c),
187        DirectiveKind::CmdDeny(c) => write_allow_deny(f, "cmddeny", c),
188        DirectiveKind::CmdPort(c) => write!(f, "cmdport {}", c.port),
189        DirectiveKind::CmdRateLimit(c) => {
190            write!(f, "cmdratelimit interval {} burst {} leak {}", c.interval, c.burst, c.leak)
191        }
192        DirectiveKind::OpenCommands(c) => {
193            write!(f, "opencommands")?;
194            for cmd in &c.commands {
195                write!(f, " {cmd}")?;
196            }
197            Ok(())
198        }
199
200        // RTC
201        DirectiveKind::HwClockFile(c) => write!(f, "hwclockfile {}", c.file),
202        DirectiveKind::RtcAutoTrim(c) => write!(f, "rtcautotrim {}", c.threshold),
203        DirectiveKind::RtcDevice(c) => write!(f, "rtcdevice {}", c.device),
204        DirectiveKind::RtcFile(c) => write!(f, "rtcfile {}", c.file),
205        DirectiveKind::RtcOnUtc => write!(f, "rtconutc"),
206        DirectiveKind::RtcSync => write!(f, "rtcsync"),
207
208        // Log
209        DirectiveKind::Log(c) => {
210            write!(f, "log")?;
211            if c.raw_measurements {
212                write!(f, " rawmeasurements")?;
213            }
214            if c.measurements && !c.raw_measurements {
215                write!(f, " measurements")?;
216            }
217            if c.selection {
218                write!(f, " selection")?;
219            }
220            if c.statistics {
221                write!(f, " statistics")?;
222            }
223            if c.tracking {
224                write!(f, " tracking")?;
225            }
226            if c.rtc {
227                write!(f, " rtc")?;
228            }
229            if c.refclocks {
230                write!(f, " refclocks")?;
231            }
232            if c.tempcomp {
233                write!(f, " tempcomp")?;
234            }
235            Ok(())
236        }
237        DirectiveKind::LogBanner(c) => write!(f, "logbanner {}", c.limit),
238        DirectiveKind::LogChange(c) => write!(f, "logchange {}", c.threshold),
239        DirectiveKind::LogDir(c) => write!(f, "logdir {}", c.directory),
240
241        // Hardware timestamping
242        DirectiveKind::HwTimestamp(c) => {
243            write!(f, "hwtimestamp {}", c.interface)?;
244            if c.minpoll.get() != 0 {
245                write!(f, " minpoll {}", c.minpoll)?;
246            }
247            if c.nocrossts {
248                write!(f, " nocrossts")?;
249            }
250            Ok(())
251        }
252        DirectiveKind::HwTsTimeout(c) => write!(f, "hwtstimeout {}", c.timeout),
253        DirectiveKind::MaxTxBuffers(c) => write!(f, "maxtxbuffers {}", c.buffers),
254        DirectiveKind::PtpPort(c) => write!(f, "ptpport {}", c.port),
255        DirectiveKind::PtpDomain(c) => write!(f, "ptpdomain {}", c.domain),
256
257        // NTS
258        DirectiveKind::NtsAeads(c) => {
259            write!(f, "ntsaeads")?;
260            for id in &c.ids {
261                write!(f, " {id}")?;
262            }
263            Ok(())
264        }
265
266        // Miscellaneous
267        DirectiveKind::ConfDir(c) => write!(f, "confdir {}", c.directory),
268        DirectiveKind::Include(c) => write!(f, "include {}", c.pattern),
269        DirectiveKind::SourceDir(c) => write!(f, "sourcedir {}", c.directory),
270        DirectiveKind::LockAll => write!(f, "lock_all"),
271        DirectiveKind::MailOnChange(c) => write!(f, "mailonchange {} {}", c.email, c.threshold),
272        DirectiveKind::PidFile(c) => write!(f, "pidfile {}", c.file),
273        DirectiveKind::SchedPriority(c) => write!(f, "sched_priority {}", c.priority),
274        DirectiveKind::User(c) => write!(f, "user {}", c.user),
275        DirectiveKind::KeyFile(c) => write!(f, "keyfile {}", c.file),
276        DirectiveKind::DumpOnExit => write!(f, "dumponexit"),
277    }
278}
279
280fn format_enum<T: fmt::Debug>(val: &T) -> String {
281    format!("{val:?}").to_lowercase()
282}
283
284fn write_server(f: &mut fmt::Formatter<'_>, c: &ServerConfig) -> fmt::Result {
285    write!(f, "server {}", c.hostname)?;
286    write_server_options(f, c)
287}
288
289fn write_server_options(f: &mut fmt::Formatter<'_>, c: &ServerConfig) -> fmt::Result {
290    if c.iburst {
291        write!(f, " iburst")?;
292    }
293    if c.burst {
294        write!(f, " burst")?;
295    }
296    if c.prefer() {
297        write!(f, " prefer")?;
298    }
299    if c.noselect() {
300        write!(f, " noselect")?;
301    }
302    if c.trust() {
303        write!(f, " trust")?;
304    }
305    if c.require() {
306        write!(f, " require")?;
307    }
308    if c.nts {
309        write!(f, " nts")?;
310    }
311    if c.xleave() {
312        write!(f, " xleave")?;
313    }
314    if c.copy {
315        write!(f, " copy")?;
316    }
317    if c.auto_offline {
318        write!(f, " auto_offline")?;
319    }
320    if matches!(c.connectivity, SourceConnectivity::Offline) {
321        write!(f, " offline")?;
322    }
323    if c.minpoll.get() != 6 {
324        write!(f, " minpoll {}", c.minpoll)?;
325    }
326    if c.maxpoll.get() != 10 {
327        write!(f, " maxpoll {}", c.maxpoll)?;
328    }
329    if c.port.get() != 123 {
330        write!(f, " port {}", c.port)?;
331    }
332    if (c.max_delay - 3.0).abs() > 1e-12 {
333        write!(f, " maxdelay {}", c.max_delay)?;
334    }
335    if (c.offset - 0.0).abs() > 1e-12 {
336        write!(f, " offset {}", c.offset)?;
337    }
338    if let Some(ref ver) = c.version {
339        write!(f, " version {}", ver)?;
340    }
341    if c.authkey != 0 {
342        write!(f, " key {}", c.authkey)?;
343    }
344    if c.cert_set != 0 {
345        write!(f, " certset {}", c.cert_set)?;
346    }
347    if (c.max_delay_ratio - 0.0).abs() > 1e-12 {
348        write!(f, " maxdelayratio {}", c.max_delay_ratio)?;
349    }
350    if (c.max_delay_dev_ratio - 10.0).abs() > 1e-12 {
351        write!(f, " maxdelaydevratio {}", c.max_delay_dev_ratio)?;
352    }
353    if (c.max_delay_quant - 0.0).abs() > 1e-12 {
354        write!(f, " maxdelayquant {}", c.max_delay_quant)?;
355    }
356    if (c.min_delay - 0.0).abs() > 1e-12 {
357        write!(f, " mindelay {}", c.min_delay)?;
358    }
359    if (c.asymmetry - 1.0).abs() > 1e-12 {
360        write!(f, " asymmetry {}", c.asymmetry)?;
361    }
362    if c.poll_target.get() != 8 {
363        write!(f, " polltarget {}", c.poll_target)?;
364    }
365    if c.min_stratum.get() != 0 {
366        write!(f, " minstratum {}", c.min_stratum)?;
367    }
368    if c.presend.get() != 0 {
369        write!(f, " presend {}", c.presend)?;
370    }
371    if let Some(ref filter) = c.filter {
372        write!(f, " filter {filter}")?;
373    }
374    if let Some(ref ms) = c.min_samples {
375        write!(f, " minsamples {ms}")?;
376    }
377    if let Some(ref ms) = c.max_samples {
378        write!(f, " maxsamples {ms}")?;
379    }
380    if c.ext_fields != 0 {
381        write!(f, " extfield {:x}", c.ext_fields)?;
382    }
383    if !matches!(c.family, AddressFamily::Unspec) {
384        match c.family {
385            AddressFamily::Inet4 => write!(f, " ipv4")?,
386            AddressFamily::Inet6 => write!(f, " ipv6")?,
387            _ => {}
388        }
389    }
390    Ok(())
391}
392
393fn write_allow_deny(f: &mut fmt::Formatter<'_>, name: &str, c: &AllowDenyConfig) -> fmt::Result {
394    write!(f, "{name}")?;
395    if c.all {
396        write!(f, " all")?;
397    }
398    if let Some(ref s) = c.subnet {
399        write!(f, " {s}")?;
400    }
401    Ok(())
402}
403
404fn write_refclock(f: &mut fmt::Formatter<'_>, c: &RefClockConfig) -> fmt::Result {
405    write!(f, "refclock {} {}", format_enum(&c.driver), c.parameter)?;
406    if c.poll.get() != 4 {
407        write!(f, " poll {}", c.poll)?;
408    }
409    if c.dpoll.get() != 0 {
410        write!(f, " dpoll {}", c.dpoll)?;
411    }
412    if c.pps_forced {
413        write!(f, " pps")?;
414    }
415    if c.tai {
416        write!(f, " tai")?;
417    }
418    if c.local {
419        write!(f, " local")?;
420    }
421    if c.select_opts.has(SelectOptions::PREFER) {
422        write!(f, " prefer")?;
423    }
424    if c.select_opts.has(SelectOptions::NOSELECT) {
425        write!(f, " noselect")?;
426    }
427    if c.select_opts.has(SelectOptions::TRUST) {
428        write!(f, " trust")?;
429    }
430    if c.select_opts.has(SelectOptions::REQUIRE) {
431        write!(f, " require")?;
432    }
433    Ok(())
434}
435
436// ---- serde feature gate ----
437
438#[cfg(feature = "serde")]
439mod serde_impl {
440    use serde::ser::{Serialize, Serializer, SerializeStruct};
441    use crate::ast::*;
442
443    impl Serialize for ChronyConfig {
444        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
445            let directives: Vec<String> = self
446                .nodes
447                .iter()
448                .filter_map(|n| match n {
449                    ConfigNode::Directive(d) => Some(d.kind.name().to_string()),
450                    _ => None,
451                })
452                .collect();
453            let mut s = serializer.serialize_struct("ChronyConfig", 1)?;
454            s.serialize_field("directives", &directives)?;
455            s.end()
456        }
457    }
458}