darkfi/util/
path.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2026 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use std::{
20    env,
21    ffi::OsString,
22    fs,
23    path::{Path, PathBuf},
24};
25
26use crate::{Error, Result};
27
28#[cfg(target_family = "unix")]
29mod home_dir_impl {
30    use std::{
31        env,
32        ffi::{CStr, OsString},
33        mem,
34        os::unix::prelude::OsStringExt,
35        path::PathBuf,
36        ptr,
37    };
38
39    /// Returns the path to the user's home directory.
40    /// Use `$HOME`, fallbacks to `libc::getpwuid_r`, otherwise `None`.
41    pub fn home_dir() -> Option<PathBuf> {
42        env::var_os("HOME")
43            .and_then(|h| if h.is_empty() { None } else { Some(h) })
44            .or_else(|| unsafe { home_fallback() })
45            .map(PathBuf::from)
46    }
47
48    /// Get the home directory from the passwd entry of the current user using
49    /// `getpwuid_r(3)`. If it manages, returns an `OsString`, otherwise returns `None`.
50    unsafe fn home_fallback() -> Option<OsString> {
51        let amt = match libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) {
52            n if n < 0 => 512_usize,
53            n => n as usize,
54        };
55
56        let mut buf = Vec::with_capacity(amt);
57        let mut passwd: libc::passwd = mem::zeroed();
58        let mut result = ptr::null_mut();
59
60        let r = libc::getpwuid_r(
61            libc::getuid(),
62            &mut passwd,
63            buf.as_mut_ptr(),
64            buf.capacity(),
65            &mut result,
66        );
67
68        match r {
69            0 if !result.is_null() => {
70                let ptr = passwd.pw_dir as *const _;
71                let bytes = CStr::from_ptr(ptr).to_bytes();
72                if bytes.is_empty() {
73                    return None
74                }
75
76                Some(OsStringExt::from_vec(bytes.to_vec()))
77            }
78
79            _ => None,
80        }
81    }
82}
83
84#[cfg(target_family = "windows")]
85mod home_dir_impl {
86    use std::{env, path::PathBuf};
87
88    pub fn home_dir() -> Option<PathBuf> {
89        env::var_os("APPDATA").map(PathBuf::from)
90    }
91}
92
93pub use home_dir_impl::home_dir;
94
95/// Returns `$XDG_CONFIG_HOME`, `$HOME/.config`, or `None`.
96pub fn config_dir() -> Option<PathBuf> {
97    env::var_os("XDG_CONFIG_HOME")
98        .and_then(is_absolute_path)
99        .or_else(|| home_dir().map(|h| h.join(".config")))
100}
101
102fn is_absolute_path(path: OsString) -> Option<PathBuf> {
103    let path = PathBuf::from(path);
104    if path.is_absolute() {
105        Some(path)
106    } else {
107        None
108    }
109}
110
111pub fn expand_path(path: &str) -> Result<PathBuf> {
112    let ret: PathBuf;
113
114    if path.starts_with("~/") {
115        if let Some(homedir) = home_dir() {
116            let remains = PathBuf::from(path.strip_prefix("~/").unwrap());
117            ret = [homedir, remains].iter().collect();
118        } else {
119            panic!("Could not fetch path for home directory");
120        }
121    } else if path.starts_with('~') {
122        if let Some(homedir) = home_dir() {
123            ret = homedir
124        } else {
125            panic!("Could not fetch path for home directory");
126        }
127    } else {
128        ret = PathBuf::from(path);
129    }
130
131    Ok(ret)
132}
133
134/// Join a path with `config_dir()/darkfi`.
135pub fn join_config_path(file: &Path) -> Result<PathBuf> {
136    let mut path = PathBuf::new();
137    let dfi_path = Path::new("darkfi");
138
139    if let Some(v) = config_dir() {
140        path.push(v);
141    }
142
143    path.push(dfi_path);
144    path.push(file);
145
146    Ok(path)
147}
148
149pub fn get_config_path(arg: Option<String>, fallback: &str) -> Result<PathBuf> {
150    if let Some(a) = arg {
151        expand_path(&a)
152    } else {
153        join_config_path(&PathBuf::from(fallback))
154    }
155}
156
157pub fn load_keypair_to_str(path: PathBuf) -> Result<String> {
158    if Path::new(&path).exists() {
159        let key = fs::read(&path)?;
160        let str_buff = std::str::from_utf8(&key)?;
161        Ok(str_buff.to_string())
162    } else {
163        println!("Could not parse keypair path");
164        Err(Error::KeypairPathNotFound)
165    }
166}