cli.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2023 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use std::{
  19. env, fs,
  20. io::Write,
  21. marker::PhantomData,
  22. path::{Path, PathBuf},
  23. str,
  24. sync::{Arc, Mutex},
  25. time::Instant,
  26. };
  27. use serde::{de::DeserializeOwned, Serialize};
  28. use simplelog::ConfigBuilder;
  29. use crate::{Error, Result};
  30. #[derive(Clone, Default)]
  31. pub struct Config<T> {
  32. config: PhantomData<T>,
  33. }
  34. impl<T: Serialize + DeserializeOwned> Config<T> {
  35. pub fn load(path: PathBuf) -> Result<T> {
  36. if Path::new(&path).exists() {
  37. let toml = fs::read(&path)?;
  38. let str_buff = str::from_utf8(&toml)?;
  39. let config: T = toml::from_str(str_buff)?;
  40. Ok(config)
  41. } else {
  42. let path = path.to_str();
  43. if path.is_some() {
  44. println!("Could not find/parse configuration file in: {}", path.unwrap());
  45. } else {
  46. println!("Could not find/parse configuration file");
  47. }
  48. println!("Please follow the instructions in the README");
  49. Err(Error::ConfigNotFound)
  50. }
  51. }
  52. }
  53. pub fn spawn_config(path: &Path, contents: &[u8]) -> Result<()> {
  54. if !path.exists() {
  55. if let Some(parent) = path.parent() {
  56. fs::create_dir_all(parent)?;
  57. }
  58. let mut file = fs::File::create(path)?;
  59. file.write_all(contents)?;
  60. println!("Config file created in {:?}. Please review it and try again.", path);
  61. std::process::exit(2);
  62. }
  63. Ok(())
  64. }
  65. pub fn get_log_level(verbosity_level: u8) -> simplelog::LevelFilter {
  66. match verbosity_level {
  67. 0 => simplelog::LevelFilter::Info,
  68. 1 => simplelog::LevelFilter::Info,
  69. 2 => simplelog::LevelFilter::Debug,
  70. _ => simplelog::LevelFilter::Trace,
  71. }
  72. }
  73. pub fn get_log_config(verbosity_level: u8) -> simplelog::Config {
  74. match env::var("LOG_TARGETS") {
  75. Ok(x) => {
  76. let targets: Vec<String> = x.split(',').map(|x| x.to_string()).collect();
  77. let mut cfgbuilder = ConfigBuilder::new();
  78. match verbosity_level {
  79. 0 => cfgbuilder.set_target_level(simplelog::LevelFilter::Debug),
  80. _ => cfgbuilder.set_target_level(simplelog::LevelFilter::Error),
  81. };
  82. for i in targets {
  83. if i.starts_with('!') {
  84. cfgbuilder.add_filter_ignore(i.trim_start_matches('!').to_string());
  85. } else {
  86. cfgbuilder.add_filter_allow(i);
  87. }
  88. }
  89. cfgbuilder.build()
  90. }
  91. Err(_) => {
  92. let mut cfgbuilder = ConfigBuilder::new();
  93. match verbosity_level {
  94. 0 => cfgbuilder.set_target_level(simplelog::LevelFilter::Debug),
  95. _ => cfgbuilder.set_target_level(simplelog::LevelFilter::Error),
  96. };
  97. cfgbuilder.build()
  98. }
  99. }
  100. }
  101. /// This macro is used for a standard way of daemonizing darkfi binaries
  102. /// with TOML config file configuration, and argument parsing. It also
  103. /// spawns a multithreaded async executor and passes it into the given
  104. /// function.
  105. ///
  106. /// The Cargo.toml dependencies needed for this are:
  107. /// ```text
  108. /// async-std = "1.12.0"
  109. /// darkfi = { path = "../../", features = ["util"] }
  110. /// easy-parallel = "3.2.0"
  111. /// signal-hook-async-std = "0.2.2"
  112. /// signal-hook = "0.3.15"
  113. /// simplelog = "0.12.0"
  114. /// smol = "1.2.5"
  115. ///
  116. /// # Argument parsing
  117. /// serde = {version = "1.0.135", features = ["derive"]}
  118. /// structopt = "0.3.26"
  119. /// structopt-toml = "0.5.1"
  120. /// ```
  121. ///
  122. /// Example usage:
  123. /// ```
  124. /// use async_std::{stream::StreamExt, sync::Arc};
  125. // use darkfi::{async_daemonize, cli_desc, Result};
  126. /// use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
  127. ///
  128. /// const CONFIG_FILE: &str = "daemond_config.toml";
  129. /// const CONFIG_FILE_CONTENTS: &str = include_str!("../daemond_config.toml");
  130. ///
  131. /// #[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
  132. /// #[serde(default)]
  133. /// #[structopt(name = "daemond", about = cli_desc!())]
  134. /// struct Args {
  135. /// #[structopt(short, long)]
  136. /// /// Configuration file to use
  137. /// config: Option<String>,
  138. ///
  139. /// #[structopt(short, long)]
  140. /// /// Set log file to ouput into
  141. /// log: Option<String>,
  142. ///
  143. /// #[structopt(short, parse(from_occurrences))]
  144. /// /// Increase verbosity (-vvv supported)
  145. /// verbose: u8,
  146. /// }
  147. ///
  148. /// async_daemonize!(realmain);
  149. /// async fn realmain(args: Args, ex: Arc<smol::Executor<'_>>) -> Result<()> {
  150. /// println!("Hello, world!");
  151. /// Ok(())
  152. /// }
  153. /// ```
  154. #[cfg(feature = "async-runtime")]
  155. #[macro_export]
  156. macro_rules! async_daemonize {
  157. ($realmain:ident) => {
  158. fn main() -> Result<()> {
  159. let args = Args::from_args_with_toml("").unwrap();
  160. let cfg_path = darkfi::util::path::get_config_path(args.config, CONFIG_FILE)?;
  161. darkfi::util::cli::spawn_config(&cfg_path, CONFIG_FILE_CONTENTS.as_bytes())?;
  162. let args = Args::from_args_with_toml(&std::fs::read_to_string(cfg_path)?).unwrap();
  163. let log_level = darkfi::util::cli::get_log_level(args.verbose);
  164. let log_config = darkfi::util::cli::get_log_config(args.verbose);
  165. // Setup terminal logger
  166. let term_logger = simplelog::TermLogger::new(
  167. log_level,
  168. log_config.clone(),
  169. simplelog::TerminalMode::Mixed,
  170. simplelog::ColorChoice::Auto,
  171. );
  172. // If a log file has been configured, also create a write logger.
  173. // Otherwise, output to terminal logger only.
  174. match args.log {
  175. Some(ref log_path) => {
  176. let log_path = darkfi::util::path::expand_path(log_path)?;
  177. let log_file = std::fs::File::create(log_path)?;
  178. let write_logger = simplelog::WriteLogger::new(log_level, log_config, log_file);
  179. simplelog::CombinedLogger::init(vec![term_logger, write_logger])?;
  180. }
  181. None => {
  182. simplelog::CombinedLogger::init(vec![term_logger])?;
  183. }
  184. }
  185. // https://docs.rs/smol/latest/smol/struct.Executor.html#examples
  186. let n_threads = std::thread::available_parallelism().unwrap().get();
  187. let ex = async_std::sync::Arc::new(smol::Executor::new());
  188. let (signal, shutdown) = smol::channel::unbounded::<()>();
  189. let (_, result) = easy_parallel::Parallel::new()
  190. // Run four executor threads
  191. .each(0..n_threads, |_| smol::future::block_on(ex.run(shutdown.recv())))
  192. // Run the main future on the current thread.
  193. .finish(|| {
  194. smol::future::block_on(async {
  195. $realmain(args, ex.clone()).await?;
  196. drop(signal);
  197. Ok::<(), darkfi::Error>(())
  198. })
  199. });
  200. result
  201. }
  202. /// Auxiliary structure used to keep track of signals
  203. struct SignalHandler {
  204. /// Termination signal channel receiver
  205. term_rx: smol::channel::Receiver<()>,
  206. /// Signals handle
  207. handle: signal_hook_async_std::Handle,
  208. /// SIGHUP subscriber to retrieve new configuration,
  209. sighup_sub: darkfi::system::SubscriberPtr<Args>,
  210. }
  211. impl SignalHandler {
  212. fn new() -> Result<(Self, async_std::task::JoinHandle<Result<()>>)> {
  213. let (term_tx, term_rx) = smol::channel::bounded::<()>(1);
  214. let signals = signal_hook_async_std::Signals::new([
  215. signal_hook::consts::SIGHUP,
  216. signal_hook::consts::SIGTERM,
  217. signal_hook::consts::SIGINT,
  218. signal_hook::consts::SIGQUIT,
  219. ])?;
  220. let handle = signals.handle();
  221. let sighup_sub = darkfi::system::Subscriber::new();
  222. let signals_task =
  223. async_std::task::spawn(handle_signals(signals, term_tx, sighup_sub.clone()));
  224. Ok((Self { term_rx, handle, sighup_sub }, signals_task))
  225. }
  226. /// Handler waits for termination signal
  227. async fn wait_termination(
  228. &self,
  229. signals_task: async_std::task::JoinHandle<Result<()>>,
  230. ) -> Result<()> {
  231. self.term_rx.recv().await?;
  232. print!("\r");
  233. self.handle.close();
  234. signals_task.await?;
  235. Ok(())
  236. }
  237. }
  238. /// Auxiliary task to handle SIGHUP, SIGTERM, SIGINT and SIGQUIT signals
  239. async fn handle_signals(
  240. mut signals: signal_hook_async_std::Signals,
  241. term_tx: smol::channel::Sender<()>,
  242. subscriber: darkfi::system::SubscriberPtr<Args>,
  243. ) -> Result<()> {
  244. while let Some(signal) = signals.next().await {
  245. match signal {
  246. signal_hook::consts::SIGHUP => {
  247. let args = Args::from_args_with_toml("").unwrap();
  248. let cfg_path =
  249. darkfi::util::path::get_config_path(args.config, CONFIG_FILE)?;
  250. darkfi::util::cli::spawn_config(
  251. &cfg_path,
  252. CONFIG_FILE_CONTENTS.as_bytes(),
  253. )?;
  254. let args = Args::from_args_with_toml(&std::fs::read_to_string(cfg_path)?);
  255. if args.is_err() {
  256. println!("handle_signals():: Error parsing the config file");
  257. continue
  258. }
  259. subscriber.notify(args.unwrap()).await;
  260. }
  261. signal_hook::consts::SIGTERM |
  262. signal_hook::consts::SIGINT |
  263. signal_hook::consts::SIGQUIT => {
  264. term_tx.send(()).await?;
  265. }
  266. _ => println!("handle_signals():: Unsupported signal"),
  267. }
  268. }
  269. Ok(())
  270. }
  271. };
  272. }
  273. pub fn fg_red(message: &str) -> String {
  274. format!("\x1b[31m{}\x1b[0m", message)
  275. }
  276. pub fn fg_green(message: &str) -> String {
  277. format!("\x1b[32m{}\x1b[0m", message)
  278. }
  279. pub fn fg_reset() -> String {
  280. "\x1b[0m".to_string()
  281. }
  282. pub struct ProgressInc {
  283. position: Arc<Mutex<u64>>,
  284. timer: Arc<Mutex<Option<Instant>>>,
  285. }
  286. impl Default for ProgressInc {
  287. fn default() -> Self {
  288. Self::new()
  289. }
  290. }
  291. impl ProgressInc {
  292. pub fn new() -> Self {
  293. eprint!("\x1b[?25l");
  294. Self { position: Arc::new(Mutex::new(0)), timer: Arc::new(Mutex::new(None)) }
  295. }
  296. pub fn inc(&self, n: u64) {
  297. let mut position = self.position.lock().unwrap();
  298. if *position == 0 {
  299. *self.timer.lock().unwrap() = Some(Instant::now());
  300. }
  301. *position += n;
  302. let binding = self.timer.lock().unwrap();
  303. let Some(elapsed) = binding.as_ref() else { return };
  304. let elapsed = elapsed.elapsed();
  305. let pos = *position;
  306. eprint!("\r[{elapsed:?}] {pos} attempts");
  307. }
  308. pub fn position(&self) -> u64 {
  309. *self.position.lock().unwrap()
  310. }
  311. pub fn finish_and_clear(&self) {
  312. *self.timer.lock().unwrap() = None;
  313. eprint!("\r\x1b[2K\x1b[?25h");
  314. }
  315. }