#![deny(
nonstandard_style,
const_err,
dead_code,
improper_ctypes,
non_shorthand_field_patterns,
no_mangle_generic_items,
overflowing_literals,
path_statements,
patterns_in_fns_without_body,
private_in_public,
unconditional_recursion,
unused,
unused_allocation,
unused_comparisons,
unused_parens,
while_true,
missing_debug_implementations,
missing_docs,
trivial_casts,
trivial_numeric_casts,
unused_extern_crates,
unused_import_braces,
unused_qualifications,
unused_results,
missing_copy_implementations
)]
#![allow(clippy::multiple_crate_versions)]
use anyhow::Result;
use log::{info, trace};
use parsec_service::utils::cli::Opts;
use parsec_service::utils::{config::ServiceConfig, ServiceBuilder};
use signal_hook::{consts::SIGHUP, consts::SIGINT, consts::SIGTERM, flag};
use std::io::{Error, ErrorKind};
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use std::time::Duration;
use structopt::StructOpt;
use users::get_current_uid;
const MAIN_LOOP_DEFAULT_SLEEP: u64 = 10;
fn main() -> Result<()> {
let opts: Opts = Opts::from_args();
let kill_signal = Arc::new(AtomicBool::new(false));
let reload_signal = Arc::new(AtomicBool::new(false));
let _ = flag::register(SIGTERM, kill_signal.clone())?;
let _ = flag::register(SIGINT, kill_signal.clone())?;
let _ = flag::register(SIGHUP, reload_signal.clone())?;
let mut config_file = ::std::fs::read_to_string(opts.config.clone()).map_err(|e| {
Error::new(
e.kind(),
format!("Failed to read config file from path: {}", opts.config),
)
})?;
let mut config: ServiceConfig = toml::from_str(&config_file).map_err(|e| {
Error::new(
ErrorKind::InvalidInput,
format!("Failed to parse service configuration ({})", e),
)
})?;
let allow_root = config.core_settings.allow_root.unwrap_or(false);
if !allow_root && get_current_uid() == 0 {
return Err(Error::new(
ErrorKind::Other,
"Insecure configuration; the Parsec service should not be running as root! You can \
modify `allow_root` in the config file to bypass this check (not recommended).",
)
.into());
}
log_setup(&config);
info!("Parsec started. Configuring the service...");
let front_end_handler = ServiceBuilder::build_service(&config)?;
let mut front_end_handler = Arc::from(front_end_handler);
let mut listener = ServiceBuilder::start_listener(config.listener)?;
let mut threadpool = ServiceBuilder::build_threadpool(config.core_settings.thread_pool_size);
let _ = sd_notify::notify(false, &[sd_notify::NotifyState::Ready]);
info!("Parsec is ready.");
while !kill_signal.load(Ordering::Relaxed) {
if reload_signal.swap(false, Ordering::Relaxed) {
let _ = sd_notify::notify(false, &[sd_notify::NotifyState::Reloading]);
info!("SIGHUP signal received. Reloading the configuration...");
threadpool.join();
drop(front_end_handler);
drop(listener);
drop(threadpool);
config_file = ::std::fs::read_to_string(opts.config.clone()).map_err(|e| {
Error::new(
e.kind(),
format!("Failed to read config file from path: {}", opts.config),
)
})?;
config = toml::from_str(&config_file).map_err(|e| {
Error::new(
ErrorKind::InvalidInput,
format!("Failed to parse service configuration ({})", e),
)
})?;
front_end_handler = Arc::from(ServiceBuilder::build_service(&config)?);
listener = ServiceBuilder::start_listener(config.listener)?;
threadpool = ServiceBuilder::build_threadpool(config.core_settings.thread_pool_size);
let _ = sd_notify::notify(false, &[sd_notify::NotifyState::Ready]);
info!("Parsec configuration reloaded.");
}
if let Some(connection) = listener.accept() {
let front_end_handler = front_end_handler.clone();
threadpool.execute(move || {
front_end_handler.handle_request(connection);
trace!("handle_request egress");
});
} else {
::std::thread::sleep(Duration::from_millis(
config
.core_settings
.idle_listener_sleep_duration
.unwrap_or(MAIN_LOOP_DEFAULT_SLEEP),
));
}
}
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Stopping]);
info!("SIGTERM or SIGINT signal received. Shutting down Parsec, waiting for all threads to finish...");
threadpool.join();
info!("Parsec is now terminated.");
Ok(())
}
fn log_setup(config: &ServiceConfig) {
let mut env_log_builder = env_logger::builder();
if let Some(level) = config.core_settings.log_level {
let _ = env_log_builder.filter_level(level);
}
if let Some(true) = config.core_settings.log_timestamp {
let _ = env_log_builder.format_timestamp_millis();
} else {
let _ = env_log_builder.format_timestamp(None);
}
env_log_builder.init();
}