use std::path::{Path, PathBuf};
use std::process;
use clap::Parser;
use clock_bound::daemon::{
Daemon,
config::{Config, LoggingConfig, SourcesConfig},
logging::subscriber::{self, FileStreams, LogSettings},
logging::synchronization::log_daemon_started,
};
use tokio::signal::unix::{SignalKind, signal};
use tokio_util::sync::CancellationToken;
use tracing::{Level, debug, error, info, warn};
const CONFIG_ERROR_EXIT: u8 = 2;
#[derive(Debug, Parser)]
struct Args {
#[clap(short, long)]
configuration: Option<PathBuf>,
#[clap(short, long)]
verbose: bool,
#[clap(long, requires = "configuration")]
check_configuration: bool,
}
#[tokio::main(flavor = "multi_thread", worker_threads = 4)]
async fn main() {
let args = Args::parse();
let config = match Config::load(args.configuration.clone()) {
Ok(config) => config,
Err(e) => {
eprintln!("{e}");
process::exit(CONFIG_ERROR_EXIT.into());
}
};
if args.check_configuration {
let path = args
.configuration
.expect("--check-configuration requires -c");
println!("{} is valid", path.display());
return;
}
let (logging, sources) = config.into_parts();
let streams = logging.log_directory().map(|directory| {
FileStreams::builder()
.directory(directory.to_path_buf())
.synchronization(logging.synchronization().is_enabled())
.ffevents(logging.ffevents().is_enabled())
.build()
});
let log_handles = subscriber::init(
&LogSettings::builder()
.level(if args.verbose {
Level::DEBUG
} else {
Level::INFO
})
.maybe_streams(streams)
.build(),
);
log_configuration(args.configuration.as_deref(), &logging, &sources);
let cargo_version = env!("CARGO_PKG_VERSION");
info!("ClockBound started. Version {cargo_version}.");
log_daemon_started();
let cancellation_token = CancellationToken::new();
let d = Daemon::construct(sources, cancellation_token.clone()).await;
let d = Box::new(d);
let mut daemon_handle = tokio::spawn(async move { d.run().await });
let mut sigint = signal(SignalKind::interrupt()).expect("failed to create SIGINT listener.");
let mut sighup = signal(SignalKind::hangup()).expect("failed to create SIGHUP listener.");
loop {
tokio::select! {
_ = sigint.recv() => {
info!("SIGINT received. Starting graceful shutdown of daemon.");
cancellation_token.cancel();
match daemon_handle.await {
Ok(()) => info!("Daemon exited gracefully."),
Err(e) => warn!(?e, "Daemon exited ungracefully.")
}
break;
}
_ = sighup.recv() => {
info!("SIGHUP received. Reopening log files.");
log_handles.reopen_all();
}
res = &mut daemon_handle => {
error!(?res, "Daemon exited unexpectedly.");
break;
}
}
}
}
fn log_configuration(
configuration: Option<&Path>,
logging: &LoggingConfig,
sources: &SourcesConfig,
) {
if let Some(path) = configuration {
info!(configuration = %path.display(), "Loaded configuration");
} else {
info!("No configuration file supplied, running on defaults");
}
let log_directory = logging
.log_directory()
.map_or_else(|| "none".to_owned(), |path| path.display().to_string());
debug!(
%log_directory,
synchronization = logging.synchronization().is_enabled(),
ffevents = logging.ffevents().is_enabled(),
ntp = ?sources.ntp(),
"Effective configuration"
);
}