1use crate::prelude::*;
2
3use std::os::unix::fs::{DirBuilderExt as _, PermissionsExt as _};
4
5pub fn make_all() -> Result<()> {
6 create_dir_all_with_permissions(&config_dir(), 0o700)?;
7 create_dir_all_with_permissions(&cache_dir(), 0o700)?;
8 create_dir_all_with_permissions(&runtime_dir(), 0o700)?;
9 create_dir_all_with_permissions(&data_dir(), 0o700)?;
10
11 Ok(())
12}
13
14fn create_dir_all_with_permissions(
15 path: &std::path::Path,
16 mode: u32,
17) -> Result<()> {
18 std::fs::DirBuilder::new()
20 .recursive(true)
21 .mode(mode)
22 .create(path)
23 .map_err(|source| Error::CreateDirectory {
24 source,
25 file: path.to_path_buf(),
26 })?;
27 std::fs::set_permissions(path, std::fs::Permissions::from_mode(mode))
29 .map_err(|source| Error::CreateDirectory {
30 source,
31 file: path.to_path_buf(),
32 })?;
33 Ok(())
34}
35
36pub fn config_file() -> std::path::PathBuf {
37 config_dir().join("config.json")
38}
39
40pub fn db_file(server: &str, email: &str) -> std::path::PathBuf {
41 let server = urlencoding::encode(server).into_owned();
42 cache_dir().join(format!("{server}:{email}.json"))
43}
44
45pub fn pid_file() -> std::path::PathBuf {
46 runtime_dir().join("pidfile")
47}
48
49pub fn agent_stdout_file() -> std::path::PathBuf {
50 data_dir().join("agent.out")
51}
52
53pub fn agent_stderr_file() -> std::path::PathBuf {
54 data_dir().join("agent.err")
55}
56
57pub fn device_id_file() -> std::path::PathBuf {
58 data_dir().join("device_id")
59}
60
61pub fn socket_file() -> std::path::PathBuf {
62 runtime_dir().join("socket")
63}
64
65pub fn ssh_agent_socket_file() -> std::path::PathBuf {
66 runtime_dir().join("ssh-agent-socket")
67}
68
69fn home_dir() -> std::path::PathBuf {
70 std::env::var_os("HOME").map_or_else(
71 || std::path::PathBuf::from("/"),
72 std::path::PathBuf::from,
73 )
74}
75
76#[cfg(target_os = "macos")]
77fn config_dir() -> std::path::PathBuf {
78 home_dir()
79 .join("Library/Application Support")
80 .join(profile())
81}
82
83#[cfg(target_os = "macos")]
84fn cache_dir() -> std::path::PathBuf {
85 home_dir().join("Library/Caches").join(profile())
86}
87
88#[cfg(target_os = "macos")]
89fn data_dir() -> std::path::PathBuf {
90 config_dir()
91}
92
93#[cfg(not(target_os = "macos"))]
94fn xdg_or(env: &str, fallback_rel: &str) -> std::path::PathBuf {
95 std::env::var_os(env)
96 .filter(|v| std::path::Path::new(v).is_absolute())
97 .map_or_else(
98 || home_dir().join(fallback_rel),
99 std::path::PathBuf::from,
100 )
101}
102
103#[cfg(not(target_os = "macos"))]
104fn config_dir() -> std::path::PathBuf {
105 xdg_or("XDG_CONFIG_HOME", ".config").join(profile())
106}
107
108#[cfg(not(target_os = "macos"))]
109fn cache_dir() -> std::path::PathBuf {
110 xdg_or("XDG_CACHE_HOME", ".cache").join(profile())
111}
112
113#[cfg(not(target_os = "macos"))]
114fn data_dir() -> std::path::PathBuf {
115 xdg_or("XDG_DATA_HOME", ".local/share").join(profile())
116}
117
118fn runtime_dir() -> std::path::PathBuf {
119 if let Some(d) = std::env::var_os("XDG_RUNTIME_DIR") {
124 if std::path::Path::new(&d).is_absolute() {
125 return std::path::PathBuf::from(d).join(profile());
126 }
127 }
128 format!(
129 "{}/{}-{}",
130 std::env::temp_dir().to_string_lossy(),
131 profile(),
132 rustix::process::getuid().as_raw()
133 )
134 .into()
135}
136
137pub fn profile() -> String {
138 match std::env::var("BWX_PROFILE") {
139 Ok(profile) if !profile.is_empty() => format!("bwx-{profile}"),
140 _ => "bwx".to_string(),
141 }
142}