Skip to main content

forest/cli_shared/
mod.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4pub mod cli;
5pub mod logger;
6
7use crate::cli_shared::cli::{Config, ConfigPath, find_config_path};
8use crate::networks::NetworkChain;
9use crate::utils::io::read_toml;
10use std::path::PathBuf;
11
12cfg_if::cfg_if! {
13    if #[cfg(feature = "rustalloc")] {
14    } else if #[cfg(feature = "jemalloc")] {
15        pub use tikv_jemallocator;
16    }
17}
18
19/// Environment variable that overrides the Forest data directory, taking
20/// precedence over both the configuration file and the built-in default. Named
21/// after Lotus' `LOTUS_PATH` to ease switching between implementations.
22pub const FOREST_DATA_DIR_ENV: &str = "FOREST_PATH";
23
24/// Gets chain data directory
25pub fn chain_path(config: &Config) -> PathBuf {
26    PathBuf::from(&config.client.data_dir).join(config.chain().to_string())
27}
28
29pub fn read_config(
30    config_path_opt: Option<&PathBuf>,
31    chain_opt: Option<NetworkChain>,
32) -> anyhow::Result<(Option<ConfigPath>, Config)> {
33    let (path, mut config) = match find_config_path(config_path_opt) {
34        Some(path) => {
35            // Read from config file
36            let toml = std::fs::read_to_string(path.to_path_buf())?;
37            // Parse and return the configuration file
38            (Some(path), read_toml(&toml)?)
39        }
40        None => (None, Config::default()),
41    };
42    if let Some(chain) = chain_opt {
43        config.chain = chain;
44    }
45    // The `FOREST_PATH` environment variable takes precedence over the data
46    // directory set in the configuration file (or the default one).
47    if let Some(data_dir) = data_dir_from_env() {
48        config.client.data_dir = data_dir;
49    }
50    Ok((path, config))
51}
52
53/// Returns the data directory set via the [`FOREST_DATA_DIR_ENV`] environment
54/// variable, if it is present and non-empty.
55fn data_dir_from_env() -> Option<PathBuf> {
56    match std::env::var(FOREST_DATA_DIR_ENV) {
57        Ok(s) if !s.trim().is_empty() => Some(PathBuf::from(s)),
58        _ => None,
59    }
60}
61
62/// Returns the effective Forest data directory: the [`FOREST_DATA_DIR_ENV`]
63/// environment variable if set, otherwise the built-in default. Unlike
64/// [`read_config`], this does not consult a configuration file and is meant for
65/// contexts (e.g. the RPC client) that need the data directory without loading
66/// the full configuration.
67pub fn default_data_dir() -> PathBuf {
68    data_dir_from_env().unwrap_or_else(|| crate::cli_shared::cli::Client::default().data_dir)
69}
70
71/// Returns the path to the RPC admin token within the effective data directory
72/// (see [`default_data_dir`]). This is where a daemon started with the same
73/// environment saves the token, so clients can read it back from here.
74pub fn default_token_path() -> PathBuf {
75    default_data_dir().join(crate::cli_shared::cli::Client::RPC_TOKEN_FILENAME)
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn read_config_default() {
84        let (config_path, config) = read_config(None, None).unwrap();
85
86        assert!(config_path.is_none());
87        assert_eq!(config.chain(), &NetworkChain::Mainnet);
88    }
89
90    #[test]
91    fn read_config_calibnet_override() {
92        let (config_path, config) = read_config(None, Some(NetworkChain::Calibnet)).unwrap();
93
94        assert!(config_path.is_none());
95        assert_eq!(config.chain(), &NetworkChain::Calibnet);
96    }
97
98    #[test]
99    fn read_config_butterflynet_override() {
100        let (config_path, config) = read_config(None, Some(NetworkChain::Butterflynet)).unwrap();
101
102        assert!(config_path.is_none());
103        assert_eq!(config.chain(), &NetworkChain::Butterflynet);
104    }
105
106    /// Runs `f` with [`FOREST_DATA_DIR_ENV`] set to `value`, restoring the
107    /// environment afterwards.
108    fn with_data_dir_env<T>(value: &str, f: impl FnOnce() -> T) -> T {
109        unsafe { std::env::set_var(FOREST_DATA_DIR_ENV, value) };
110        let result = f();
111        unsafe { std::env::remove_var(FOREST_DATA_DIR_ENV) };
112        result
113    }
114
115    #[test]
116    #[serial_test::serial]
117    fn read_config_data_dir_env_override() {
118        let data_dir = "/tmp/forest-path-env-override-test";
119        let (_, config) = with_data_dir_env(data_dir, || read_config(None, None).unwrap());
120
121        // The env variable takes precedence over the default data directory.
122        assert_eq!(config.client.data_dir, std::path::Path::new(data_dir));
123    }
124
125    #[test]
126    #[serial_test::serial]
127    fn default_data_dir_honors_env_override() {
128        let data_dir = "/tmp/forest-path-default-data-dir-test";
129        let resolved = with_data_dir_env(data_dir, default_data_dir);
130        assert_eq!(resolved, std::path::Path::new(data_dir));
131
132        // Without the env variable, it falls back to the default data directory.
133        assert_eq!(default_data_dir(), Config::default().client.data_dir);
134    }
135
136    #[test]
137    #[serial_test::serial]
138    fn read_config_data_dir_env_empty_is_ignored() {
139        let (_, config) = with_data_dir_env("", || read_config(None, None).unwrap());
140
141        // An empty env variable falls back to the default data directory.
142        assert_eq!(config.client.data_dir, Config::default().client.data_dir);
143    }
144
145    #[test]
146    #[serial_test::serial]
147    fn read_config_with_path() {
148        let default_config = Config::default();
149        let temp_dir = tempfile::tempdir().expect("couldn't create temp dir");
150        let config_file = temp_dir.path().join("config.toml");
151        let serialized_config = toml::to_string(&default_config).unwrap();
152        std::fs::write(&config_file, serialized_config).unwrap();
153
154        let (config_path, config) = read_config(Some(&config_file), None).unwrap();
155
156        assert_eq!(config_path.unwrap(), ConfigPath::Cli(config_file));
157        assert_eq!(config.chain(), &NetworkChain::Mainnet);
158        assert_eq!(config, default_config);
159    }
160}
161
162pub mod snapshot;