use super::types::RestrictionsRules; use crate::restrictions::config_reloader::RestrictionsRulesReloaderState::{Config, Static}; use anyhow::Context; use log::trace; use notify::{EventKind, RecommendedWatcher, Watcher}; use parking_lot::Mutex; use std::path::PathBuf; use std::sync::Arc; use std::thread; use std::time::Duration; use tokio::sync::futures::Notified; use tokio::sync::Notify; use tracing::{error, info, warn}; struct ConfigReloaderState { fs_watcher: Mutex, config_path: PathBuf, should_reload_config: Notify, } enum RestrictionsRulesReloaderState { Static(Notify), Config(Arc), } impl RestrictionsRulesReloaderState { fn fs_watcher(&self) -> &Mutex { match self { Static(_) => unreachable!(), Config(this) => &this.fs_watcher, } } } pub struct RestrictionsRulesReloader { state: RestrictionsRulesReloaderState, restrictions: Arc, } impl RestrictionsRulesReloader { pub fn new(restrictions_rules: RestrictionsRules, config_path: Option) -> anyhow::Result { // If there is no custom certificate and private key, there is nothing to watch let config_path = if let Some(config_path) = config_path { config_path } else { return Ok(Self { state: Static(Notify::new()), restrictions: Arc::new(restrictions_rules), }); }; let this = Arc::new(ConfigReloaderState { fs_watcher: Mutex::new(notify::recommended_watcher(|_| {})?), should_reload_config: Notify::new(), config_path, }); info!("Starting to watch restriction config file for changes to reload them"); let mut watcher = notify::recommended_watcher({ let this = Config(this.clone()); move |event: notify::Result| Self::handle_config_fs_event(&this, event) }) .with_context(|| "Cannot create restriction config watcher")?; watcher.watch(&this.config_path, notify::RecursiveMode::NonRecursive)?; *this.fs_watcher.lock() = watcher; Ok(Self { state: Config(this), restrictions: Arc::new(restrictions_rules), }) } pub fn reload_restrictions_config(&mut self) { let restrictions = match &self.state { Static(_) => return, Config(st) => match RestrictionsRules::from_config_file(&st.config_path) { Ok(restrictions) => { info!("Restrictions config file has been reloaded"); restrictions } Err(err) => { error!("Cannot reload restrictions config file, keeping the old one. Error: {:?}", err); return; } }, }; self.restrictions = Arc::new(restrictions); } pub const fn restrictions_rules(&self) -> &Arc { &self.restrictions } pub fn reload_notifier(&self) -> Notified { match &self.state { Static(st) => st.notified(), Config(st) => st.should_reload_config.notified(), } } fn try_rewatch_config(this: RestrictionsRulesReloaderState, path: PathBuf) { thread::spawn(move || { while !path.exists() { warn!( "Restrictions config file {:?} does not exist anymore, waiting for it to be created", path ); thread::sleep(Duration::from_secs(10)); } let mut watcher = this.fs_watcher().lock(); let _ = watcher.unwatch(&path); let Ok(_) = watcher .watch(&path, notify::RecursiveMode::NonRecursive) .map_err(|err| { error!("Cannot re-set a watch for Restriction config file {:?}: {:?}", path, err); error!("Restriction config file will not be auto-reloaded anymore"); }) else { return; }; drop(watcher); // Generate a fake event to force-reload the certificate let event = notify::Event { kind: EventKind::Create(notify::event::CreateKind::Any), paths: vec![path], attrs: Default::default(), }; match &this { Static(_) => Self::handle_config_fs_event(&this, Ok(event)), Config(_) => Self::handle_config_fs_event(&this, Ok(event)), } }); } fn handle_config_fs_event(this: &RestrictionsRulesReloaderState, event: notify::Result) { let this = match this { Static(_) => return, Config(st) => st, }; let event = match event { Ok(event) => event, Err(err) => { error!("Error while watching restrictions config file for changes {:?}", err); return; } }; if event.kind.is_access() { return; } trace!("Received event: {:#?}", event); if let Some(path) = event.paths.iter().find(|p| p.ends_with(&this.config_path)) { match event.kind { EventKind::Create(_) | EventKind::Modify(_) => { this.should_reload_config.notify_one(); } EventKind::Remove(_) => { warn!("Restriction config file has been removed, trying to re-set a watch for it"); Self::try_rewatch_config(Config(this.clone()), path.to_path_buf()); } EventKind::Access(_) | EventKind::Other | EventKind::Any => { trace!("Ignoring event {:?}", event); } } } } }