1//! Base paths for use with arti clients.
2//!
3//! This code is defined in `tor-config-path` rather than in `arti-client` so that other programs
4//! (notably `arti-rpc-client-core`) can use it.
56use std::{borrow::Cow, path::PathBuf};
78use directories::ProjectDirs;
9use once_cell::sync::Lazy;
1011use crate::{CfgPathError, CfgPathResolver};
1213/// A [`CfgPathResolver`] with the base variables configured for a `TorClientConfig`
14/// in `arti-client`.
15///
16/// A `TorClientConfig` may set additional variables on its resolver.
17///
18/// This should only be used by `TorClient` users
19/// and others that need to use the same variables.
20/// Libraries should be written in a
21/// resolver-agnostic way (shouldn't rely on resolving `ARTI_CONFIG` for example).
22///
23/// The supported variables are:
24/// * `ARTI_CACHE`: an arti-specific cache directory.
25/// * `ARTI_CONFIG`: an arti-specific configuration directory.
26/// * `ARTI_SHARED_DATA`: an arti-specific directory in the user's "shared
27/// data" space.
28/// * `ARTI_LOCAL_DATA`: an arti-specific directory in the user's "local
29/// data" space.
30/// * `PROGRAM_DIR`: the directory of the currently executing binary.
31/// See documentation for [`std::env::current_exe`] for security notes.
32/// * `USER_HOME`: the user's home directory.
33///
34/// These variables are implemented using the [`directories`] crate, and
35/// so should use appropriate system-specific overrides under the
36/// hood. (Some of those overrides are based on environment variables.)
37/// For more information, see that crate's documentation.
38pub fn arti_client_base_resolver() -> CfgPathResolver {
39let arti_cache = project_dirs().map(|x| Cow::Owned(x.cache_dir().to_owned()));
40let arti_config = project_dirs().map(|x| Cow::Owned(x.config_dir().to_owned()));
41let arti_shared_data = project_dirs().map(|x| Cow::Owned(x.data_dir().to_owned()));
42let arti_local_data = project_dirs().map(|x| Cow::Owned(x.data_local_dir().to_owned()));
43let program_dir = get_program_dir().map(Cow::Owned);
44let user_home = crate::home().map(Cow::Borrowed);
4546let mut resolver = CfgPathResolver::default();
4748 resolver.set_var("ARTI_CACHE", arti_cache);
49 resolver.set_var("ARTI_CONFIG", arti_config);
50 resolver.set_var("ARTI_SHARED_DATA", arti_shared_data);
51 resolver.set_var("ARTI_LOCAL_DATA", arti_local_data);
52 resolver.set_var("PROGRAM_DIR", program_dir);
53 resolver.set_var("USER_HOME", user_home);
5455 resolver
56}
5758/// Return the directory holding the currently executing program.
59fn get_program_dir() -> Result<PathBuf, CfgPathError> {
60let binary = std::env::current_exe().map_err(|_| CfgPathError::NoProgramPath)?;
61let directory = binary.parent().ok_or(CfgPathError::NoProgramDir)?;
62Ok(directory.to_owned())
63}
6465/// Return a ProjectDirs object for the Arti project.
66fn project_dirs() -> Result<&'static ProjectDirs, CfgPathError> {
67/// lazy cell holding the ProjectDirs object.
68static PROJECT_DIRS: Lazy<Option<ProjectDirs>> =
69 Lazy::new(|| ProjectDirs::from("org", "torproject", "Arti"));
7071 PROJECT_DIRS.as_ref().ok_or(CfgPathError::NoProjectDirs)
72}
7374#[cfg(test)]
75mod test {
76// @@ begin test lint list maintained by maint/add_warning @@
77#![allow(clippy::bool_assert_comparison)]
78 #![allow(clippy::clone_on_copy)]
79 #![allow(clippy::dbg_macro)]
80 #![allow(clippy::mixed_attributes_style)]
81 #![allow(clippy::print_stderr)]
82 #![allow(clippy::print_stdout)]
83 #![allow(clippy::single_char_pattern)]
84 #![allow(clippy::unwrap_used)]
85 #![allow(clippy::unchecked_duration_subtraction)]
86 #![allow(clippy::useless_vec)]
87 #![allow(clippy::needless_pass_by_value)]
88//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
8990use super::*;
91use crate::CfgPath;
9293fn cfg_variables() -> impl IntoIterator<Item = (&'static str, PathBuf)> {
94let list = [
95 ("ARTI_CACHE", project_dirs().unwrap().cache_dir()),
96 ("ARTI_CONFIG", project_dirs().unwrap().config_dir()),
97 ("ARTI_SHARED_DATA", project_dirs().unwrap().data_dir()),
98 ("ARTI_LOCAL_DATA", project_dirs().unwrap().data_local_dir()),
99 ("PROGRAM_DIR", &get_program_dir().unwrap()),
100 ("USER_HOME", crate::home().unwrap()),
101 ];
102103 list.into_iter()
104 .map(|(a, b)| (a, b.to_owned()))
105 .collect::<Vec<_>>()
106 }
107108#[cfg(not(target_family = "windows"))]
109 #[test]
110fn expand_variables() {
111let path_resolver = arti_client_base_resolver();
112113for (var, val) in cfg_variables() {
114let p = CfgPath::new(format!("${{{var}}}/example"));
115assert_eq!(p.to_string(), format!("${{{var}}}/example"));
116117let expected = val.join("example");
118assert_eq!(p.path(&path_resolver).unwrap().to_str(), expected.to_str());
119 }
120121let p = CfgPath::new("${NOT_A_REAL_VAR}/example".to_string());
122assert!(p.path(&path_resolver).is_err());
123 }
124125#[cfg(target_family = "windows")]
126 #[test]
127fn expand_variables() {
128let path_resolver = arti_client_base_resolver();
129130for (var, val) in cfg_variables() {
131let p = CfgPath::new(format!("${{{var}}}\\example"));
132assert_eq!(p.to_string(), format!("${{{var}}}\\example"));
133134let expected = val.join("example");
135assert_eq!(p.path(&path_resolver).unwrap().to_str(), expected.to_str());
136 }
137138let p = CfgPath::new("${NOT_A_REAL_VAR}\\example".to_string());
139assert!(p.path(&path_resolver).is_err());
140 }
141}