rbw/
dirs.rs

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(&cache_dir(), 0o700)?;
7    create_dir_all_with_permissions(&runtime_dir(), 0o700)?;
8    create_dir_all_with_permissions(&data_dir(), 0o700)?;
9
10    Ok(())
11}
12
13fn create_dir_all_with_permissions(
14    path: &std::path::Path,
15    mode: u32,
16) -> Result<()> {
17    // ensure the initial directory creation happens with the correct mode,
18    // to avoid race conditions
19    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    // but also make sure to forcibly set the mode, in case the directory
28    // already existed
29    std::fs::set_permissions(path, std::fs::Permissions::from_mode(mode))
30        .map_err(|source| Error::CreateDirectory {
31            source,
32            file: path.to_path_buf(),
33        })?;
34    Ok(())
35}
36
37pub fn config_file() -> std::path::PathBuf {
38    config_dir().join("config.json")
39}
40
41const INVALID_PATH: &percent_encoding::AsciiSet =
42    &percent_encoding::CONTROLS.add(b'/').add(b'%').add(b':');
43pub fn db_file(server: &str, email: &str) -> std::path::PathBuf {
44    let server =
45        percent_encoding::percent_encode(server.as_bytes(), INVALID_PATH)
46            .to_string();
47    cache_dir().join(format!("{server}:{email}.json"))
48}
49
50pub fn pid_file() -> std::path::PathBuf {
51    runtime_dir().join("pidfile")
52}
53
54pub fn agent_stdout_file() -> std::path::PathBuf {
55    data_dir().join("agent.out")
56}
57
58pub fn agent_stderr_file() -> std::path::PathBuf {
59    data_dir().join("agent.err")
60}
61
62pub fn device_id_file() -> std::path::PathBuf {
63    data_dir().join("device_id")
64}
65
66pub fn socket_file() -> std::path::PathBuf {
67    runtime_dir().join("socket")
68}
69
70pub fn ssh_agent_socket_file() -> std::path::PathBuf {
71    runtime_dir().join("ssh-agent-socket")
72}
73
74fn config_dir() -> std::path::PathBuf {
75    let project_dirs =
76        directories::ProjectDirs::from("", "", &profile()).unwrap();
77    project_dirs.config_dir().to_path_buf()
78}
79
80fn cache_dir() -> std::path::PathBuf {
81    let project_dirs =
82        directories::ProjectDirs::from("", "", &profile()).unwrap();
83    project_dirs.cache_dir().to_path_buf()
84}
85
86fn data_dir() -> std::path::PathBuf {
87    let project_dirs =
88        directories::ProjectDirs::from("", "", &profile()).unwrap();
89    project_dirs.data_dir().to_path_buf()
90}
91
92fn runtime_dir() -> std::path::PathBuf {
93    let project_dirs =
94        directories::ProjectDirs::from("", "", &profile()).unwrap();
95    project_dirs.runtime_dir().map_or_else(
96        || {
97            format!(
98                "{}/{}-{}",
99                std::env::temp_dir().to_string_lossy(),
100                &profile(),
101                rustix::process::getuid().as_raw()
102            )
103            .into()
104        },
105        std::path::Path::to_path_buf,
106    )
107}
108
109pub fn profile() -> String {
110    match std::env::var("RBW_PROFILE") {
111        Ok(profile) if !profile.is_empty() => format!("rbw-{profile}"),
112        _ => "rbw".to_string(),
113    }
114}