#![allow(clippy::result_large_err)]
pub mod lexer;
pub mod grammar;
pub mod source_file;
use std::path::PathBuf;
use crate::ast::*;
use crate::error::{DirectiveError, ParseError};
use crate::span::Span;
use lexer::{LineType, MAX_LINE_LENGTH};
impl ChronyConfig {
pub fn parse(input: &str) -> Result<Self, ParseError> {
parse_internal(input, None, false).map(|c| c.0)
}
pub fn parse_lenient(input: &str) -> (Self, Vec<ParseError>) {
parse_internal(input, None, true).unwrap_or_else(|e| (ChronyConfig::default(), vec![e]))
}
}
fn parse_internal(input: &str, file: Option<PathBuf>, lenient: bool) -> Result<(ChronyConfig, Vec<ParseError>), ParseError> {
let mut nodes = Vec::new();
let mut leading_comments: Vec<String> = Vec::new();
let mut warnings = Vec::new();
for (line_num, raw_line) in input.lines().enumerate() {
let line_number = line_num + 1;
if raw_line.len() > MAX_LINE_LENGTH {
let err = ParseError::LineTooLong { file: file.clone(), line: line_number, len: raw_line.len() };
if lenient { warnings.push(err); continue; }
return Err(err);
}
match lexer::normalize_line(raw_line) {
LineType::Blank => {
flush_comments(&mut leading_comments, &mut nodes);
nodes.push(ConfigNode::BlankLine);
}
LineType::Comment(comment) => {
leading_comments.push(comment);
}
LineType::Directive { command, args } => {
let span = Span::new(file.clone(), line_number, 1, raw_line.len());
let trailing = extract_trailing_comment(raw_line);
let clean_args = strip_inline_comment(&args);
match dispatch(&command, clean_args, span.clone()) {
Ok(kind) => {
let mut directive = Box::new(Directive::new(kind, span));
directive.leading_comments = std::mem::take(&mut leading_comments);
directive.trailing_comment = trailing;
nodes.push(ConfigNode::Directive(directive));
}
Err(e) => {
if lenient {
leading_comments.clear();
warnings.push(ParseError::Parse { file: file.clone(), inner: e });
continue;
}
return Err(ParseError::Parse { file, inner: e });
}
}
}
}
}
flush_comments(&mut leading_comments, &mut nodes);
Ok((ChronyConfig { nodes }, warnings))
}
fn flush_comments(comments: &mut Vec<String>, nodes: &mut Vec<ConfigNode>) {
for c in comments.drain(..) {
nodes.push(ConfigNode::Comment(c));
}
}
fn extract_trailing_comment(raw_line: &str) -> Option<String> {
let trimmed = raw_line.trim_start();
for (i, ch) in trimmed.char_indices() {
if matches!(ch, '#' | ';') && i > 0 {
let comment = trimmed[i+1..].trim();
if !comment.is_empty() {
return Some(comment.to_string());
}
break;
}
}
None
}
fn strip_inline_comment(args: &str) -> &str {
for (i, ch) in args.char_indices() {
if matches!(ch, '#' | ';') && i > 0
&& args.as_bytes().get(i - 1).copied() == Some(b' ')
{
return args[..i - 1].trim_end();
}
}
args
}
fn dispatch(command: &str, args: &str, span: Span) -> Result<DirectiveKind, DirectiveError> {
use grammar::*;
match command.to_lowercase().as_str() {
"server" => parse_source_options(args, span, "server"),
"pool" => parse_source_options(args, span, "pool"),
"peer" => parse_source_options(args, span, "peer"),
"initstepslew" => parse_initstepslew(args, span),
"refclock" => parse_refclock(args, span),
"manual" => parse_manual(args, span),
"acquisitionport" => parse_acquisition_port(args, span),
"bindacqaddress" => parse_bind_acq_address(args, span),
"bindacqdevice" => parse_bind_acq_device(args, span),
"dscp" => parse_dscp(args, span),
"dumpdir" => parse_dumpdir(args, span),
"maxsamples" => parse_max_samples(args, span),
"minsamples" => parse_min_samples(args, span),
"ntscachedir" | "ntsdumpdir" => parse_nts_dump_dir(args, span),
"ntsrefresh" => parse_nts_refresh(args, span),
"ntstrustedcerts" => parse_ntstrustedcerts(args, span),
"nosystemcert" => parse_no_system_cert(args, span),
"nocerttimecheck" => parse_no_cert_time_check(args, span),
"refresh" => parse_refresh(args, span),
"authselectmode" => parse_authselectmode(args, span),
"combinelimit" => parse_combine_limit(args, span),
"maxdistance" => parse_max_distance(args, span),
"maxjitter" => parse_max_jitter(args, span),
"minsources" => parse_minsources(args, span),
"reselectdist" => parse_reselect_dist(args, span),
"stratumweight" => parse_stratum_weight(args, span),
"clockprecision" => parse_clock_precision(args, span),
"corrtimeratio" => parse_corr_time_ratio(args, span),
"driftfile" => parse_driftfile(args, span),
"fallbackdrift" => parse_fallbackdrift(args, span),
"leapsecmode" => parse_leapsecmode(args, span),
"leapsectz" => parse_leap_sec_tz(args, span),
"leapseclist" => parse_leap_sec_list(args, span),
"makestep" => parse_makestep(args, span),
"maxchange" => parse_maxchange(args, span),
"maxclockerror" => parse_max_clock_error(args, span),
"maxdrift" => parse_max_drift(args, span),
"maxslewrate" => parse_max_slew_rate(args, span),
"maxupdateskew" => parse_max_update_skew(args, span),
"tempcomp" => parse_tempcomp(args, span),
"allow" => parse_allow(args, span),
"deny" => parse_deny(args, span),
"bindaddress" => parse_bind_address(args, span),
"binddevice" => parse_bind_device(args, span),
"broadcast" => parse_broadcast(args, span),
"clientloglimit" => parse_client_log_limit(args, span),
"noclientlog" => parse_no_client_log(args, span),
"local" => parse_local(args, span),
"ntpsigndsocket" => parse_ntp_signd_socket(args, span),
"ntsport" => parse_nts_port(args, span),
"ntsservercert" => parse_nts_server_cert(args, span),
"ntsserverkey" => parse_nts_server_key(args, span),
"ntsprocesses" => parse_nts_processes(args, span),
"maxntsconnections" => parse_max_nts_connections(args, span),
"ntsntpserver" => parse_nts_ntp_server(args, span),
"ntsrotate" => parse_nts_rotate(args, span),
"port" => parse_port(args, span),
"ratelimit" => parse_ratelimit(args, span),
"ntsratelimit" => parse_nts_ratelimit(args, span),
"smoothtime" => parse_smoothtime(args, span),
"bindcmdaddress" => parse_bind_cmd_address(args, span),
"bindcmddevice" => parse_bind_cmd_device(args, span),
"cmdallow" => parse_cmd_allow(args, span),
"cmddeny" => parse_cmd_deny(args, span),
"cmdport" => parse_cmd_port(args, span),
"cmdratelimit" => parse_cmd_ratelimit(args, span),
"opencommands" => parse_open_commands(args, span),
"hwclockfile" => parse_hw_clock_file(args, span),
"rtcautotrim" => parse_rtc_auto_trim(args, span),
"rtcdevice" => parse_rtc_device(args, span),
"rtcfile" => parse_rtc_file(args, span),
"rtconutc" => parse_rtconutc(args, span),
"rtcsync" => parse_rtcsync(args, span),
"log" => parse_log(args, span),
"logbanner" => parse_log_banner(args, span),
"logchange" => parse_log_change(args, span),
"logdir" => parse_logdir(args, span),
"hwtimestamp" => parse_hwtimestamp(args, span),
"hwtstimeout" => parse_hw_ts_timeout(args, span),
"maxtxbuffers" => parse_max_tx_buffers(args, span),
"ptpport" => parse_ptp_port(args, span),
"ptpdomain" => parse_ptp_domain(args, span),
"ntsaeads" => parse_nts_aeads(args, span),
"confdir" => parse_confdir(args, span),
"include" => parse_include(args, span),
"sourcedir" => parse_source_dir(args, span),
"lock_all" => parse_lock_all(args, span),
"mailonchange" => parse_mailonchange(args, span),
"pidfile" => parse_pidfile(args, span),
"sched_priority" => parse_sched_priority(args, span),
"user" => parse_user(args, span),
"keyfile" => parse_keyfile(args, span),
"dumponexit" => parse_dump_on_exit(args, span),
"commandkey" | "generatecommandkey" | "linux_freq_scale" | "linux_hz" => {
Ok(DirectiveKind::DumpOnExit)
}
_ => Err(DirectiveError::InvalidDirective { name: command.to_string(), span }),
}
}