/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2025 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use std::{ env, fs, io::Write, path::Path, str, sync::{Arc, Mutex}, time::Instant, }; use simplelog::ConfigBuilder; use crate::Result; /* #[derive(Clone, Default)] pub struct Config { config: PhantomData, } impl Config { pub fn load(path: PathBuf) -> Result { if Path::new(&path).exists() { let toml = fs::read(&path)?; let str_buff = str::from_utf8(&toml)?; let config: T = toml::from_str(str_buff)?; Ok(config) } else { let path = path.to_str(); if path.is_some() { println!("Could not find/parse configuration file in: {}", path.unwrap()); } else { println!("Could not find/parse configuration file"); } println!("Please follow the instructions in the README"); Err(Error::ConfigNotFound) } } } */ pub fn spawn_config(path: &Path, contents: &[u8]) -> Result<()> { if !path.exists() { if let Some(parent) = path.parent() { fs::create_dir_all(parent)?; } let mut file = fs::File::create(path)?; file.write_all(contents)?; println!("Config file created in {:?}. Please review it and try again.", path); std::process::exit(2); } Ok(()) } pub fn get_log_level(verbosity_level: u8) -> simplelog::LevelFilter { match verbosity_level { 0 => simplelog::LevelFilter::Info, 1 => simplelog::LevelFilter::Info, 2 => simplelog::LevelFilter::Debug, _ => simplelog::LevelFilter::Trace, } } pub fn get_log_config(verbosity_level: u8) -> simplelog::Config { match env::var("LOG_TARGETS") { Ok(x) => { let targets: Vec = x.split(',').map(|x| x.to_string()).collect(); let mut cfgbuilder = ConfigBuilder::new(); match verbosity_level { 0 => cfgbuilder.set_target_level(simplelog::LevelFilter::Debug), _ => cfgbuilder.set_target_level(simplelog::LevelFilter::Error), }; for i in targets { if i.starts_with('!') { cfgbuilder.add_filter_ignore(i.trim_start_matches('!').to_string()); } else { cfgbuilder.add_filter_allow(i); } } cfgbuilder.build() } Err(_) => { let mut cfgbuilder = ConfigBuilder::new(); match verbosity_level { 0 => cfgbuilder.set_target_level(simplelog::LevelFilter::Debug), _ => cfgbuilder.set_target_level(simplelog::LevelFilter::Error), }; cfgbuilder.build() } } } /// This macro is used for a standard way of daemonizing darkfi binaries /// with TOML config file configuration, and argument parsing. /// /// It also spawns a multithreaded async executor and passes it into the /// given function. /// /// The Cargo.toml dependencies needed for this are: /// ```text /// darkfi = { path = "../../", features = ["util"] } /// easy-parallel = "3.2.0" /// signal-hook-async-std = "0.2.2" /// signal-hook = "0.3.15" /// simplelog = "0.12.0" /// smol = "1.2.5" /// /// # Argument parsing /// serde = {version = "1.0.135", features = ["derive"]} /// structopt = "0.3.26" /// structopt-toml = "0.5.1" /// ``` /// /// Example usage: /// ``` /// use darkfi::{async_daemonize, cli_desc, Result}; /// use smol::stream::StreamExt; /// use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml}; /// /// const CONFIG_FILE: &str = "daemond_config.toml"; /// const CONFIG_FILE_CONTENTS: &str = include_str!("../daemond_config.toml"); /// /// #[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)] /// #[serde(default)] /// #[structopt(name = "daemond", about = cli_desc!())] /// struct Args { /// #[structopt(short, long)] /// /// Configuration file to use /// config: Option, /// /// #[structopt(short, long)] /// /// Set log file to ouput into /// log: Option, /// /// #[structopt(short, parse(from_occurrences))] /// /// Increase verbosity (-vvv supported) /// verbose: u8, /// } /// /// async_daemonize!(realmain); /// async fn realmain(args: Args, ex: Arc>) -> Result<()> { /// println!("Hello, world!"); /// Ok(()) /// } /// ``` #[cfg(feature = "async-daemonize")] #[macro_export] macro_rules! async_daemonize { ($realmain:ident) => { fn main() -> Result<()> { let args = match Args::from_args_with_toml("") { Ok(v) => v, Err(e) => { eprintln!("Unable to get args: {e}"); return Err(Error::ConfigInvalid) } }; let cfg_path = match darkfi::util::path::get_config_path(args.config.clone(), CONFIG_FILE) { Ok(v) => v, Err(e) => { eprintln!("Unable to get config path `{:?}`: {e}", args.config); return Err(e) } }; if let Err(e) = darkfi::util::cli::spawn_config(&cfg_path, CONFIG_FILE_CONTENTS.as_bytes()) { eprintln!("Spawn config failed `{cfg_path:?}`: {e}"); return Err(e) } let cfg_text = match std::fs::read_to_string(&cfg_path) { Ok(c) => c, Err(e) => { eprintln!("Read config failed `{cfg_path:?}`: {e}"); return Err(e.into()) } }; let args = match Args::from_args_with_toml(&cfg_text) { Ok(v) => v, Err(e) => { eprintln!("Parsing config failed `{cfg_path:?}`: {e}"); return Err(Error::ConfigInvalid) } }; let log_level = darkfi::util::cli::get_log_level(args.verbose); let log_config = darkfi::util::cli::get_log_config(args.verbose); // Setup terminal logger let term_logger = simplelog::TermLogger::new( log_level, log_config.clone(), simplelog::TerminalMode::Mixed, simplelog::ColorChoice::Auto, ); // If a log file has been configured, also create a write logger. // Otherwise, output to terminal logger only. match args.log { Some(ref log_path) => { let log_path = match darkfi::util::path::expand_path(log_path) { Ok(v) => v, Err(e) => { eprintln!("Expanding log path failed `{log_path:?}`: {e}"); return Err(e) } }; let log_file = match std::fs::File::create(&log_path) { Ok(v) => v, Err(e) => { eprintln!("Creating log file failed `{log_path:?}`: {e}"); return Err(e.into()) } }; let write_logger = simplelog::WriteLogger::new(log_level, log_config, log_file); if let Err(e) = simplelog::CombinedLogger::init(vec![term_logger, write_logger]) { eprintln!("Unable to init logger with term + logfile combo: {e}"); return Err(e.into()) } } None => { if let Err(e) = simplelog::CombinedLogger::init(vec![term_logger]) { eprintln!("Unable to init term logger: {e}"); return Err(e.into()) } } } // https://docs.rs/smol/latest/smol/struct.Executor.html#examples let n_threads = std::thread::available_parallelism().unwrap().get(); let ex = std::sync::Arc::new(smol::Executor::new()); let (signal, shutdown) = smol::channel::unbounded::<()>(); let (_, result) = easy_parallel::Parallel::new() // Run four executor threads .each(0..n_threads, |_| smol::future::block_on(ex.run(shutdown.recv()))) // Run the main future on the current thread. .finish(|| { smol::future::block_on(async { $realmain(args, ex.clone()).await?; drop(signal); Ok::<(), darkfi::Error>(()) }) }); result } /// Auxiliary structure used to keep track of signals struct SignalHandler { /// Termination signal channel receiver term_rx: smol::channel::Receiver<()>, /// Signals handle handle: signal_hook_async_std::Handle, /// SIGHUP publisher to retrieve new configuration, sighup_pub: darkfi::system::PublisherPtr, } impl SignalHandler { fn new( ex: std::sync::Arc>, ) -> Result<(Self, smol::Task>)> { let (term_tx, term_rx) = smol::channel::bounded::<()>(1); let signals = signal_hook_async_std::Signals::new([ signal_hook::consts::SIGHUP, signal_hook::consts::SIGTERM, signal_hook::consts::SIGINT, signal_hook::consts::SIGQUIT, ])?; let handle = signals.handle(); let sighup_pub = darkfi::system::Publisher::new(); let signals_task = ex.spawn(handle_signals(signals, term_tx, sighup_pub.clone(), ex.clone())); Ok((Self { term_rx, handle, sighup_pub }, signals_task)) } /// Handler waits for termination signal async fn wait_termination(&self, signals_task: smol::Task>) -> Result<()> { self.term_rx.recv().await?; print!("\r"); self.handle.close(); signals_task.await?; Ok(()) } } /// Auxiliary task to handle SIGINT for forceful process abort async fn handle_abort(mut signals: signal_hook_async_std::Signals) { let mut n_sigint = 0; while let Some(signal) = signals.next().await { n_sigint += 1; if n_sigint == 2 { print!("\r"); info!("Aborting. Good luck."); std::process::abort(); } } } /// Auxiliary task to handle SIGHUP, SIGTERM, SIGINT and SIGQUIT signals async fn handle_signals( mut signals: signal_hook_async_std::Signals, term_tx: smol::channel::Sender<()>, publisher: darkfi::system::PublisherPtr, ex: std::sync::Arc>, ) -> Result<()> { while let Some(signal) = signals.next().await { match signal { signal_hook::consts::SIGHUP => { let args = Args::from_args_with_toml("").unwrap(); let cfg_path = darkfi::util::path::get_config_path(args.config, CONFIG_FILE)?; darkfi::util::cli::spawn_config( &cfg_path, CONFIG_FILE_CONTENTS.as_bytes(), )?; let args = Args::from_args_with_toml(&std::fs::read_to_string(cfg_path)?); if args.is_err() { println!("handle_signals():: Error parsing the config file"); continue } publisher.notify(args.unwrap()).await; } signal_hook::consts::SIGINT => { // Spawn a new background task to listen for more SIGINT. // This lets us forcefully abort the process if necessary. let signals = signal_hook_async_std::Signals::new([signal_hook::consts::SIGINT])?; let handle = signals.handle(); ex.spawn(handle_abort(signals)).detach(); term_tx.send(()).await?; } signal_hook::consts::SIGTERM | signal_hook::consts::SIGQUIT => { term_tx.send(()).await?; } _ => println!("handle_signals():: Unsupported signal"), } } Ok(()) } }; } pub fn fg_red(message: &str) -> String { format!("\x1b[31m{}\x1b[0m", message) } pub fn fg_green(message: &str) -> String { format!("\x1b[32m{}\x1b[0m", message) } pub fn fg_reset() -> String { "\x1b[0m".to_string() } pub struct ProgressInc { position: Arc>, timer: Arc>>, } impl Default for ProgressInc { fn default() -> Self { Self::new() } } impl ProgressInc { pub fn new() -> Self { eprint!("\x1b[?25l"); Self { position: Arc::new(Mutex::new(0)), timer: Arc::new(Mutex::new(None)) } } pub fn inc(&self, n: u64) { let mut position = self.position.lock().unwrap(); if *position == 0 { *self.timer.lock().unwrap() = Some(Instant::now()); } *position += n; let binding = self.timer.lock().unwrap(); let Some(elapsed) = binding.as_ref() else { return }; let elapsed = elapsed.elapsed(); let pos = *position; eprint!("\r[{elapsed:?}] {pos} attempts"); } pub fn position(&self) -> u64 { *self.position.lock().unwrap() } pub fn finish_and_clear(&self) { *self.timer.lock().unwrap() = None; eprint!("\r\x1b[2K\x1b[?25h"); } }