cli.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  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. fs,
  20. io::Write,
  21. path::Path,
  22. str,
  23. sync::{Arc, Mutex},
  24. time::Instant,
  25. };
  26. use crate::Result;
  27. /*
  28. #[derive(Clone, Default)]
  29. pub struct Config<T> {
  30. config: PhantomData<T>,
  31. }
  32. impl<T: Serialize + DeserializeOwned> Config<T> {
  33. pub fn load(path: PathBuf) -> Result<T> {
  34. if Path::new(&path).exists() {
  35. let toml = fs::read(&path)?;
  36. let str_buff = str::from_utf8(&toml)?;
  37. let config: T = toml::from_str(str_buff)?;
  38. Ok(config)
  39. } else {
  40. let path = path.to_str();
  41. if path.is_some() {
  42. println!("Could not find/parse configuration file in: {}", path.unwrap());
  43. } else {
  44. println!("Could not find/parse configuration file");
  45. }
  46. println!("Please follow the instructions in the README");
  47. Err(Error::ConfigNotFound)
  48. }
  49. }
  50. }
  51. */
  52. pub fn spawn_config(path: &Path, contents: &[u8]) -> Result<()> {
  53. if !path.exists() {
  54. if let Some(parent) = path.parent() {
  55. fs::create_dir_all(parent)?;
  56. }
  57. let mut file = fs::File::create(path)?;
  58. file.write_all(contents)?;
  59. println!("Config file created in {path:?}. Please review it and try again.");
  60. std::process::exit(2);
  61. }
  62. Ok(())
  63. }
  64. /// This macro is used for a standard way of daemonizing darkfi binaries
  65. /// with TOML config file configuration, and argument parsing.
  66. ///
  67. /// It also spawns a multithreaded async executor and passes it into the
  68. /// given function.
  69. ///
  70. /// The Cargo.toml dependencies needed for this are:
  71. /// ```text
  72. /// darkfi = { path = "../../", features = ["util"] }
  73. /// easy-parallel = "3.2.0"
  74. /// signal-hook-async-std = "0.2.2"
  75. /// signal-hook = "0.3.15"
  76. /// tracing-subscriber = "0.3.19"
  77. /// tracing-appender = "0.2.3"
  78. /// smol = "1.2.5"
  79. ///
  80. /// # Argument parsing
  81. /// serde = {version = "1.0.135", features = ["derive"]}
  82. /// structopt = "0.3.26"
  83. /// structopt-toml = "0.5.1"
  84. /// ```
  85. ///
  86. /// Example usage:
  87. /// ```
  88. /// use darkfi::{async_daemonize, cli_desc, Result};
  89. /// use smol::stream::StreamExt;
  90. /// use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
  91. ///
  92. /// const CONFIG_FILE: &str = "daemond_config.toml";
  93. /// const CONFIG_FILE_CONTENTS: &str = include_str!("../daemond_config.toml");
  94. ///
  95. /// #[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
  96. /// #[serde(default)]
  97. /// #[structopt(name = "daemond", about = cli_desc!())]
  98. /// struct Args {
  99. /// #[structopt(short, long)]
  100. /// /// Configuration file to use
  101. /// config: Option<String>,
  102. ///
  103. /// #[structopt(short, long)]
  104. /// /// Set log file to ouput into
  105. /// log: Option<String>,
  106. ///
  107. /// #[structopt(short, parse(from_occurrences))]
  108. /// /// Increase verbosity (-vvv supported)
  109. /// verbose: u8,
  110. /// }
  111. ///
  112. /// async_daemonize!(realmain);
  113. /// async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
  114. /// println!("Hello, world!");
  115. /// Ok(())
  116. /// }
  117. /// ```
  118. #[cfg(feature = "async-daemonize")]
  119. #[macro_export]
  120. macro_rules! async_daemonize {
  121. ($realmain:ident) => {
  122. fn main() -> Result<()> {
  123. let args = match Args::from_args_with_toml("") {
  124. Ok(v) => v,
  125. Err(e) => {
  126. eprintln!("Unable to get args: {e}");
  127. return Err(Error::ConfigInvalid)
  128. }
  129. };
  130. let cfg_path =
  131. match darkfi::util::path::get_config_path(args.config.clone(), CONFIG_FILE) {
  132. Ok(v) => v,
  133. Err(e) => {
  134. eprintln!("Unable to get config path `{:?}`: {e}", args.config);
  135. return Err(e)
  136. }
  137. };
  138. if let Err(e) =
  139. darkfi::util::cli::spawn_config(&cfg_path, CONFIG_FILE_CONTENTS.as_bytes())
  140. {
  141. eprintln!("Spawn config failed `{cfg_path:?}`: {e}");
  142. return Err(e)
  143. }
  144. let cfg_text = match std::fs::read_to_string(&cfg_path) {
  145. Ok(c) => c,
  146. Err(e) => {
  147. eprintln!("Read config failed `{cfg_path:?}`: {e}");
  148. return Err(e.into())
  149. }
  150. };
  151. let args = match Args::from_args_with_toml(&cfg_text) {
  152. Ok(v) => v,
  153. Err(e) => {
  154. eprintln!("Parsing config failed `{cfg_path:?}`: {e}");
  155. return Err(Error::ConfigInvalid)
  156. }
  157. };
  158. // If a log file has been configured, create a terminal and file logger.
  159. // Otherwise, output to terminal logger only.
  160. let (non_blocking, file_guard) = match args.log {
  161. Some(ref log_path) => {
  162. let log_path = match darkfi::util::path::expand_path(log_path) {
  163. Ok(v) => v,
  164. Err(e) => {
  165. eprintln!("Expanding log path failed `{log_path:?}`: {e}");
  166. return Err(e)
  167. }
  168. };
  169. let log_file = match std::fs::File::create(&log_path) {
  170. Ok(v) => v,
  171. Err(e) => {
  172. eprintln!("Creating log file failed `{log_path:?}`: {e}");
  173. return Err(e.into())
  174. }
  175. };
  176. // Hold guard until process stops to ensure buffer logs are flushed to file
  177. let (non_blocking, guard) = tracing_appender::non_blocking(log_file);
  178. (Some(non_blocking), Some(guard))
  179. }
  180. None => (None, None),
  181. };
  182. if let Err(e) = darkfi::util::logger::setup_logging(args.verbose, non_blocking) {
  183. if args.log.is_some() {
  184. eprintln!("Unable to init logger with term + logfile combo: {e}");
  185. } else {
  186. eprintln!("Unable to init term logger: {e}");
  187. }
  188. return Err(e.into())
  189. }
  190. // https://docs.rs/smol/latest/smol/struct.Executor.html#examples
  191. let n_threads = std::thread::available_parallelism().unwrap().get();
  192. let ex = std::sync::Arc::new(smol::Executor::new());
  193. let (signal, shutdown) = smol::channel::unbounded::<()>();
  194. let (_, result) = easy_parallel::Parallel::new()
  195. // Run four executor threads
  196. .each(0..n_threads, |_| smol::future::block_on(ex.run(shutdown.recv())))
  197. // Run the main future on the current thread.
  198. .finish(|| {
  199. smol::future::block_on(async {
  200. $realmain(args, ex.clone()).await?;
  201. drop(signal);
  202. Ok::<(), darkfi::Error>(())
  203. })
  204. });
  205. result
  206. }
  207. /// Auxiliary structure used to keep track of signals
  208. struct SignalHandler {
  209. /// Termination signal channel receiver
  210. term_rx: smol::channel::Receiver<()>,
  211. /// Signals handle
  212. handle: signal_hook_async_std::Handle,
  213. /// SIGHUP publisher to retrieve new configuration,
  214. sighup_pub: darkfi::system::PublisherPtr<Args>,
  215. }
  216. impl SignalHandler {
  217. fn new(
  218. ex: std::sync::Arc<smol::Executor<'static>>,
  219. ) -> Result<(Self, smol::Task<Result<()>>)> {
  220. let (term_tx, term_rx) = smol::channel::bounded::<()>(1);
  221. let signals = signal_hook_async_std::Signals::new([
  222. signal_hook::consts::SIGHUP,
  223. signal_hook::consts::SIGTERM,
  224. signal_hook::consts::SIGINT,
  225. signal_hook::consts::SIGQUIT,
  226. ])?;
  227. let handle = signals.handle();
  228. let sighup_pub = darkfi::system::Publisher::new();
  229. let signals_task =
  230. ex.spawn(handle_signals(signals, term_tx, sighup_pub.clone(), ex.clone()));
  231. Ok((Self { term_rx, handle, sighup_pub }, signals_task))
  232. }
  233. /// Handler waits for termination signal
  234. async fn wait_termination(&self, signals_task: smol::Task<Result<()>>) -> Result<()> {
  235. self.term_rx.recv().await?;
  236. print!("\r");
  237. self.handle.close();
  238. signals_task.await?;
  239. Ok(())
  240. }
  241. }
  242. /// Auxiliary task to handle SIGINT for forceful process abort
  243. async fn handle_abort(mut signals: signal_hook_async_std::Signals) {
  244. let mut n_sigint = 0;
  245. while let Some(signal) = signals.next().await {
  246. n_sigint += 1;
  247. if n_sigint == 2 {
  248. print!("\r");
  249. info!("Aborting. Good luck.");
  250. std::process::abort();
  251. }
  252. }
  253. }
  254. /// Auxiliary task to handle SIGHUP, SIGTERM, SIGINT and SIGQUIT signals
  255. async fn handle_signals(
  256. mut signals: signal_hook_async_std::Signals,
  257. term_tx: smol::channel::Sender<()>,
  258. publisher: darkfi::system::PublisherPtr<Args>,
  259. ex: std::sync::Arc<smol::Executor<'static>>,
  260. ) -> Result<()> {
  261. while let Some(signal) = signals.next().await {
  262. match signal {
  263. signal_hook::consts::SIGHUP => {
  264. let args = Args::from_args_with_toml("").unwrap();
  265. let cfg_path =
  266. darkfi::util::path::get_config_path(args.config, CONFIG_FILE)?;
  267. darkfi::util::cli::spawn_config(
  268. &cfg_path,
  269. CONFIG_FILE_CONTENTS.as_bytes(),
  270. )?;
  271. let args = Args::from_args_with_toml(&std::fs::read_to_string(cfg_path)?);
  272. if args.is_err() {
  273. println!("handle_signals():: Error parsing the config file");
  274. continue
  275. }
  276. publisher.notify(args.unwrap()).await;
  277. }
  278. signal_hook::consts::SIGINT => {
  279. // Spawn a new background task to listen for more SIGINT.
  280. // This lets us forcefully abort the process if necessary.
  281. let signals =
  282. signal_hook_async_std::Signals::new([signal_hook::consts::SIGINT])?;
  283. let handle = signals.handle();
  284. ex.spawn(handle_abort(signals)).detach();
  285. term_tx.send(()).await?;
  286. }
  287. signal_hook::consts::SIGTERM | signal_hook::consts::SIGQUIT => {
  288. term_tx.send(()).await?;
  289. }
  290. _ => println!("handle_signals():: Unsupported signal"),
  291. }
  292. }
  293. Ok(())
  294. }
  295. };
  296. }
  297. pub fn fg_red(message: &str) -> String {
  298. format!("\x1b[31m{message}\x1b[0m")
  299. }
  300. pub fn fg_green(message: &str) -> String {
  301. format!("\x1b[32m{message}\x1b[0m")
  302. }
  303. pub fn fg_reset() -> String {
  304. "\x1b[0m".to_string()
  305. }
  306. pub struct ProgressInc {
  307. position: Arc<Mutex<u64>>,
  308. timer: Arc<Mutex<Option<Instant>>>,
  309. }
  310. impl Default for ProgressInc {
  311. fn default() -> Self {
  312. Self::new()
  313. }
  314. }
  315. impl ProgressInc {
  316. pub fn new() -> Self {
  317. eprint!("\x1b[?25l");
  318. Self { position: Arc::new(Mutex::new(0)), timer: Arc::new(Mutex::new(None)) }
  319. }
  320. pub fn inc(&self, n: u64) {
  321. let mut position = self.position.lock().unwrap();
  322. if *position == 0 {
  323. *self.timer.lock().unwrap() = Some(Instant::now());
  324. }
  325. *position += n;
  326. let binding = self.timer.lock().unwrap();
  327. let Some(elapsed) = binding.as_ref() else { return };
  328. let elapsed = elapsed.elapsed();
  329. let pos = *position;
  330. eprint!("\r[{elapsed:?}] {pos} attempts");
  331. }
  332. pub fn position(&self) -> u64 {
  333. *self.position.lock().unwrap()
  334. }
  335. pub fn finish_and_clear(&self) {
  336. *self.timer.lock().unwrap() = None;
  337. eprint!("\r\x1b[2K\x1b[?25h");
  338. }
  339. }