Skip to main content

ckb_bin/
lib.rs

1//! CKB executable.
2//!
3//! This crate is created to reduce the link time to build CKB.
4#[allow(dead_code)]
5mod cli;
6mod helper;
7mod setup;
8mod setup_guard;
9mod subcommand;
10#[cfg(test)]
11mod tests;
12use ckb_app_config::ExitCode;
13use ckb_async_runtime::new_global_runtime;
14use ckb_build_info::Version;
15use ckb_logger::debug;
16use ckb_network::tokio;
17use clap::ArgMatches;
18use helper::raise_fd_limit;
19use setup::Setup;
20use setup_guard::SetupGuard;
21use time::OffsetDateTime;
22
23#[cfg(not(target_os = "windows"))]
24use colored::Colorize;
25#[cfg(not(target_os = "windows"))]
26use daemonize_me::Daemon;
27#[cfg(not(target_os = "windows"))]
28use subcommand::check_process;
29#[cfg(feature = "with_sentry")]
30pub(crate) const LOG_TARGET_SENTRY: &str = "sentry";
31
32/// Print a log-like message to stderr.
33/// Format: `YYYY-MM-DD HH:MM:SS.mmm +00:00 thread_name INFO module  message`
34fn log_println(message: &str) {
35    let now = OffsetDateTime::now_utc();
36    let thread = std::thread::current();
37    let thread_name = thread.name().unwrap_or("main");
38    eprintln!(
39        "{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:03} +00:00 {} INFO ckb_bin  {}",
40        now.year(),
41        now.month() as u8,
42        now.day(),
43        now.hour(),
44        now.minute(),
45        now.second(),
46        now.millisecond(),
47        thread_name,
48        message
49    );
50}
51
52/// The executable main entry.
53///
54/// It returns `Ok` when the process exist normally, otherwise the `ExitCode` is converted to the
55/// process exit status code.
56///
57/// ## Parameters
58///
59/// * `version` - The version is passed in so the bin crate can collect the version without trigger
60///   re-linking.
61pub fn run_app(version: Version) -> Result<(), ExitCode> {
62    // Always print backtrace on panic.
63    unsafe {
64        ::std::env::set_var("RUST_BACKTRACE", "full");
65    }
66
67    let (bin_name, app_matches) = cli::get_bin_name_and_matches(&version);
68    if let Some((cli, matches)) = app_matches.subcommand() {
69        match cli {
70            cli::CMD_INIT => {
71                return subcommand::init(Setup::init(matches)?);
72            }
73            cli::CMD_LIST_HASHES => {
74                return subcommand::list_hashes(Setup::root_dir_from_matches(matches)?, matches);
75            }
76            cli::CMD_PEERID => {
77                if let Some((cli, matches)) = matches.subcommand() {
78                    match cli {
79                        cli::CMD_GEN_SECRET => return Setup::generate(matches),
80                        cli::CMD_FROM_SECRET => {
81                            return subcommand::peer_id(Setup::peer_id(matches)?);
82                        }
83                        _ => {}
84                    }
85                }
86            }
87            _ => {}
88        }
89    }
90
91    let (cmd, matches) = app_matches
92        .subcommand()
93        .expect("SubcommandRequiredElseHelp");
94
95    #[cfg(not(target_os = "windows"))]
96    if run_daemon(cmd, matches) {
97        return run_app_in_daemon(version, bin_name, cmd, matches);
98    }
99
100    debug!("ckb version: {}", version);
101    run_app_inner(version, bin_name, cmd, matches)
102}
103
104#[cfg(not(target_os = "windows"))]
105fn run_app_in_daemon(
106    version: Version,
107    bin_name: String,
108    cmd: &str,
109    matches: &ArgMatches,
110) -> Result<(), ExitCode> {
111    eprintln!("starting CKB in daemon mode ...");
112    eprintln!("check status : `{}`", "ckb daemon --check".green());
113    eprintln!("stop daemon  : `{}`", "ckb daemon --stop".yellow());
114
115    assert!(matches!(cmd, cli::CMD_RUN));
116    let root_dir = Setup::root_dir_from_matches(matches)?;
117    let daemon_dir = root_dir.join("data/daemon");
118    // make sure daemon dir exists
119    std::fs::create_dir_all(daemon_dir)?;
120    let pid_file = Setup::daemon_pid_file_path(matches)?;
121
122    if check_process(&pid_file).is_ok() {
123        eprintln!("{}", "ckb is already running".red());
124        return Ok(());
125    }
126    eprintln!("no ckb process, starting ...");
127
128    let pwd = std::env::current_dir()?;
129    let daemon = Daemon::new().pid_file(pid_file, Some(false)).work_dir(pwd);
130
131    match daemon.start() {
132        Ok(_) => {
133            ckb_logger::info!("Success, daemonized ...");
134            run_app_inner(version, bin_name, cmd, matches)
135        }
136        Err(e) => {
137            ckb_logger::info!("daemonize error: {}", e);
138            Err(ExitCode::Failure)
139        }
140    }
141}
142
143fn run_app_inner(
144    version: Version,
145    bin_name: String,
146    cmd: &str,
147    matches: &ArgMatches,
148) -> Result<(), ExitCode> {
149    let is_silent_logging = is_silent_logging(cmd);
150    let (mut handle, mut handle_stop_rx, _runtime) = new_global_runtime(None);
151    let setup = Setup::from_matches(bin_name, cmd, matches)?;
152    // Disable logging here if the user is executing `ckb run`.
153    // Logs subscription of RPC service requires access to `struct Shared`,
154    // so logger of `ckb run` will be initialized in `subcommand::run`.
155    let (_guard, log_config) = SetupGuard::from_setup(
156        &setup,
157        &version,
158        handle.clone(),
159        is_silent_logging,
160        cmd != cli::CMD_RUN,
161    )?;
162
163    raise_fd_limit();
164
165    let ret = match cmd {
166        cli::CMD_RUN => subcommand::run(setup.run(matches)?, version, handle.clone(), log_config),
167        cli::CMD_MINER => subcommand::miner(setup.miner(matches)?, handle.clone()),
168        cli::CMD_REPLAY => subcommand::replay(setup.replay(matches)?, handle.clone()),
169        cli::CMD_EXPORT => subcommand::export(setup.export(matches)?, handle.clone()),
170        cli::CMD_IMPORT => subcommand::import(setup.import(matches)?, handle.clone()),
171        cli::CMD_STATS => subcommand::stats(setup.stats(matches)?, handle.clone()),
172        cli::CMD_RESET_DATA => subcommand::reset_data(setup.reset_data(matches)?),
173        cli::CMD_MIGRATE => subcommand::migrate(setup.migrate(matches)?),
174        #[cfg(not(target_os = "windows"))]
175        cli::CMD_DAEMON => subcommand::daemon(setup.daemon(matches)?),
176        _ => unreachable!(),
177    };
178
179    if matches!(cmd, cli::CMD_RUN) {
180        handle.drop_guard();
181
182        tokio::task::block_in_place(|| {
183            // Here we use `log_println` instead of `info!` because the Logger has already been
184            // shut down when `subcommand::run()` returned (LoggerGuard was dropped there).
185            //
186            // We cannot simply extend the LoggerGuard's lifetime to here because it would cause
187            // a deadlock: NotifyController (held by Logger's background thread) contains a clone
188            // of Handle, which holds a clone of `guard` (Sender). If LoggerGuard is not dropped
189            // before `handle_stop_rx.blocking_recv()`, the Logger thread won't terminate, and
190            // the Sender inside NotifyController won't be dropped, causing blocking_recv() to
191            // wait forever.
192            log_println("Waiting for all tokio tasks to exit...");
193            handle_stop_rx.blocking_recv();
194            log_println("All tokio tasks and threads have exited. CKB shutdown");
195        });
196    }
197
198    ret
199}
200
201#[cfg(not(target_os = "windows"))]
202fn run_daemon(cmd: &str, matches: &ArgMatches) -> bool {
203    match cmd {
204        cli::CMD_RUN => matches.get_flag(cli::ARG_DAEMON),
205        _ => false,
206    }
207}
208
209type Silent = bool;
210
211fn is_silent_logging(cmd: &str) -> Silent {
212    matches!(
213        cmd,
214        cli::CMD_EXPORT
215            | cli::CMD_IMPORT
216            | cli::CMD_STATS
217            | cli::CMD_MIGRATE
218            | cli::CMD_RESET_DATA
219            | cli::CMD_DAEMON
220    )
221}