cli.rs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2025 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. path::Path,
  22. str,
  23. sync::{Arc, Mutex},
  24. time::Instant,
  25. };
  26. use simplelog::ConfigBuilder;
  27. use crate::Result;
  28. /*
  29. #[derive(Clone, Default)]
  30. pub struct Config<T> {
  31. config: PhantomData<T>,
  32. }
  33. impl<T: Serialize + DeserializeOwned> Config<T> {
  34. pub fn load(path: PathBuf) -> Result<T> {
  35. if Path::new(&path).exists() {
  36. let toml = fs::read(&path)?;
  37. let str_buff = str::from_utf8(&toml)?;
  38. let config: T = toml::from_str(str_buff)?;
  39. Ok(config)
  40. } else {
  41. let path = path.to_str();
  42. if path.is_some() {
  43. println!("Could not find/parse configuration file in: {}", path.unwrap());
  44. } else {
  45. println!("Could not find/parse configuration file");
  46. }
  47. println!("Please follow the instructions in the README");
  48. Err(Error::ConfigNotFound)
  49. }
  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.
  103. ///
  104. /// It also spawns a multithreaded async executor and passes it into the
  105. /// given function.
  106. ///
  107. /// The Cargo.toml dependencies needed for this are:
  108. /// ```text
  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 darkfi::{async_daemonize, cli_desc, Result};
  125. /// use smol::stream::StreamExt;
  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<'static>>) -> Result<()> {
  150. /// println!("Hello, world!");
  151. /// Ok(())
  152. /// }
  153. /// ```
  154. #[cfg(feature = "async-daemonize")]
  155. #[macro_export]
  156. macro_rules! async_daemonize {
  157. ($realmain:ident) => {
  158. fn main() -> Result<()> {
  159. let args = match Args::from_args_with_toml("") {
  160. Ok(v) => v,
  161. Err(e) => {
  162. eprintln!("Unable to get args: {e}");
  163. return Err(Error::ConfigInvalid)
  164. }
  165. };
  166. let cfg_path =
  167. match darkfi::util::path::get_config_path(args.config.clone(), CONFIG_FILE) {
  168. Ok(v) => v,
  169. Err(e) => {
  170. eprintln!("Unable to get config path `{:?}`: {e}", args.config);
  171. return Err(e)
  172. }
  173. };
  174. if let Err(e) =
  175. darkfi::util::cli::spawn_config(&cfg_path, CONFIG_FILE_CONTENTS.as_bytes())
  176. {
  177. eprintln!("Spawn config failed `{cfg_path:?}`: {e}");
  178. return Err(e)
  179. }
  180. let cfg_text = match std::fs::read_to_string(&cfg_path) {
  181. Ok(c) => c,
  182. Err(e) => {
  183. eprintln!("Read config failed `{cfg_path:?}`: {e}");
  184. return Err(e.into())
  185. }
  186. };
  187. let args = match Args::from_args_with_toml(&cfg_text) {
  188. Ok(v) => v,
  189. Err(e) => {
  190. eprintln!("Parsing config failed `{cfg_path:?}`: {e}");
  191. return Err(Error::ConfigInvalid)
  192. }
  193. };
  194. let log_level = darkfi::util::cli::get_log_level(args.verbose);
  195. let log_config = darkfi::util::cli::get_log_config(args.verbose);
  196. // Setup terminal logger
  197. let term_logger = simplelog::TermLogger::new(
  198. log_level,
  199. log_config.clone(),
  200. simplelog::TerminalMode::Mixed,
  201. simplelog::ColorChoice::Auto,
  202. );
  203. // If a log file has been configured, also create a write logger.
  204. // Otherwise, output to terminal logger only.
  205. match args.log {
  206. Some(ref log_path) => {
  207. let log_path = match darkfi::util::path::expand_path(log_path) {
  208. Ok(v) => v,
  209. Err(e) => {
  210. eprintln!("Expanding log path failed `{log_path:?}`: {e}");
  211. return Err(e)
  212. }
  213. };
  214. let log_file = match std::fs::File::create(&log_path) {
  215. Ok(v) => v,
  216. Err(e) => {
  217. eprintln!("Creating log file failed `{log_path:?}`: {e}");
  218. return Err(e.into())
  219. }
  220. };
  221. let write_logger = simplelog::WriteLogger::new(log_level, log_config, log_file);
  222. if let Err(e) = simplelog::CombinedLogger::init(vec![term_logger, write_logger])
  223. {
  224. eprintln!("Unable to init logger with term + logfile combo: {e}");
  225. return Err(e.into())
  226. }
  227. }
  228. None => {
  229. if let Err(e) = simplelog::CombinedLogger::init(vec![term_logger]) {
  230. eprintln!("Unable to init term logger: {e}");
  231. return Err(e.into())
  232. }
  233. }
  234. }
  235. // https://docs.rs/smol/latest/smol/struct.Executor.html#examples
  236. let n_threads = std::thread::available_parallelism().unwrap().get();
  237. let ex = std::sync::Arc::new(smol::Executor::new());
  238. let (signal, shutdown) = smol::channel::unbounded::<()>();
  239. let (_, result) = easy_parallel::Parallel::new()
  240. // Run four executor threads
  241. .each(0..n_threads, |_| smol::future::block_on(ex.run(shutdown.recv())))
  242. // Run the main future on the current thread.
  243. .finish(|| {
  244. smol::future::block_on(async {
  245. $realmain(args, ex.clone()).await?;
  246. drop(signal);
  247. Ok::<(), darkfi::Error>(())
  248. })
  249. });
  250. result
  251. }
  252. /// Auxiliary structure used to keep track of signals
  253. struct SignalHandler {
  254. /// Termination signal channel receiver
  255. term_rx: smol::channel::Receiver<()>,
  256. /// Signals handle
  257. handle: signal_hook_async_std::Handle,
  258. /// SIGHUP publisher to retrieve new configuration,
  259. sighup_pub: darkfi::system::PublisherPtr<Args>,
  260. }
  261. impl SignalHandler {
  262. fn new(
  263. ex: std::sync::Arc<smol::Executor<'static>>,
  264. ) -> Result<(Self, smol::Task<Result<()>>)> {
  265. let (term_tx, term_rx) = smol::channel::bounded::<()>(1);
  266. let signals = signal_hook_async_std::Signals::new([
  267. signal_hook::consts::SIGHUP,
  268. signal_hook::consts::SIGTERM,
  269. signal_hook::consts::SIGINT,
  270. signal_hook::consts::SIGQUIT,
  271. ])?;
  272. let handle = signals.handle();
  273. let sighup_pub = darkfi::system::Publisher::new();
  274. let signals_task =
  275. ex.spawn(handle_signals(signals, term_tx, sighup_pub.clone(), ex.clone()));
  276. Ok((Self { term_rx, handle, sighup_pub }, signals_task))
  277. }
  278. /// Handler waits for termination signal
  279. async fn wait_termination(&self, signals_task: smol::Task<Result<()>>) -> Result<()> {
  280. self.term_rx.recv().await?;
  281. print!("\r");
  282. self.handle.close();
  283. signals_task.await?;
  284. Ok(())
  285. }
  286. }
  287. /// Auxiliary task to handle SIGINT for forceful process abort
  288. async fn handle_abort(mut signals: signal_hook_async_std::Signals) {
  289. let mut n_sigint = 0;
  290. while let Some(signal) = signals.next().await {
  291. n_sigint += 1;
  292. if n_sigint == 2 {
  293. print!("\r");
  294. info!("Aborting. Good luck.");
  295. std::process::abort();
  296. }
  297. }
  298. }
  299. /// Auxiliary task to handle SIGHUP, SIGTERM, SIGINT and SIGQUIT signals
  300. async fn handle_signals(
  301. mut signals: signal_hook_async_std::Signals,
  302. term_tx: smol::channel::Sender<()>,
  303. publisher: darkfi::system::PublisherPtr<Args>,
  304. ex: std::sync::Arc<smol::Executor<'static>>,
  305. ) -> Result<()> {
  306. while let Some(signal) = signals.next().await {
  307. match signal {
  308. signal_hook::consts::SIGHUP => {
  309. let args = Args::from_args_with_toml("").unwrap();
  310. let cfg_path =
  311. darkfi::util::path::get_config_path(args.config, CONFIG_FILE)?;
  312. darkfi::util::cli::spawn_config(
  313. &cfg_path,
  314. CONFIG_FILE_CONTENTS.as_bytes(),
  315. )?;
  316. let args = Args::from_args_with_toml(&std::fs::read_to_string(cfg_path)?);
  317. if args.is_err() {
  318. println!("handle_signals():: Error parsing the config file");
  319. continue
  320. }
  321. publisher.notify(args.unwrap()).await;
  322. }
  323. signal_hook::consts::SIGINT => {
  324. // Spawn a new background task to listen for more SIGINT.
  325. // This lets us forcefully abort the process if necessary.
  326. let signals =
  327. signal_hook_async_std::Signals::new([signal_hook::consts::SIGINT])?;
  328. let handle = signals.handle();
  329. ex.spawn(handle_abort(signals)).detach();
  330. term_tx.send(()).await?;
  331. }
  332. signal_hook::consts::SIGTERM | signal_hook::consts::SIGQUIT => {
  333. term_tx.send(()).await?;
  334. }
  335. _ => println!("handle_signals():: Unsupported signal"),
  336. }
  337. }
  338. Ok(())
  339. }
  340. };
  341. }
  342. pub fn fg_red(message: &str) -> String {
  343. format!("\x1b[31m{}\x1b[0m", message)
  344. }
  345. pub fn fg_green(message: &str) -> String {
  346. format!("\x1b[32m{}\x1b[0m", message)
  347. }
  348. pub fn fg_reset() -> String {
  349. "\x1b[0m".to_string()
  350. }
  351. pub struct ProgressInc {
  352. position: Arc<Mutex<u64>>,
  353. timer: Arc<Mutex<Option<Instant>>>,
  354. }
  355. impl Default for ProgressInc {
  356. fn default() -> Self {
  357. Self::new()
  358. }
  359. }
  360. impl ProgressInc {
  361. pub fn new() -> Self {
  362. eprint!("\x1b[?25l");
  363. Self { position: Arc::new(Mutex::new(0)), timer: Arc::new(Mutex::new(None)) }
  364. }
  365. pub fn inc(&self, n: u64) {
  366. let mut position = self.position.lock().unwrap();
  367. if *position == 0 {
  368. *self.timer.lock().unwrap() = Some(Instant::now());
  369. }
  370. *position += n;
  371. let binding = self.timer.lock().unwrap();
  372. let Some(elapsed) = binding.as_ref() else { return };
  373. let elapsed = elapsed.elapsed();
  374. let pos = *position;
  375. eprint!("\r[{elapsed:?}] {pos} attempts");
  376. }
  377. pub fn position(&self) -> u64 {
  378. *self.position.lock().unwrap()
  379. }
  380. pub fn finish_and_clear(&self) {
  381. *self.timer.lock().unwrap() = None;
  382. eprint!("\r\x1b[2K\x1b[?25h");
  383. }
  384. }