xdg_home/
lib.rs

1#![doc = include_str!("../README.md")]
2#![doc(test(attr(
3    warn(unused),
4    deny(warnings),
5    // W/o this, we seem to get some bogus warning about `extern crate ..`.
6    allow(unused_extern_crates),
7)))]
8
9use std::path::PathBuf;
10
11/// Get the path of the current user's home directory.
12///
13/// See the library documentation for more information.
14pub fn home_dir() -> Option<PathBuf> {
15    match std::env::var("HOME") {
16        Ok(home) => Some(home.into()),
17        Err(_) => {
18            #[cfg(unix)]
19            {
20                unix::home_dir()
21            }
22
23            #[cfg(windows)]
24            {
25                win32::home_dir()
26            }
27        }
28    }
29}
30
31#[cfg(unix)]
32mod unix {
33    use std::ffi::{CStr, OsStr};
34    use std::os::unix::ffi::OsStrExt;
35    use std::path::PathBuf;
36
37    pub(super) fn home_dir() -> Option<PathBuf> {
38        let uid = unsafe { libc::geteuid() };
39
40        // SAFETY: Not initalizing references here so it's safe.
41        let mut passwd: libc::passwd = unsafe { std::mem::zeroed() };
42        // This has to be enough for everyone.
43        let mut passwd_buf = [0_u8; 1024];
44        let mut result = std::ptr::null_mut();
45        let ret = unsafe {
46            libc::getpwuid_r(
47                uid,
48                &mut passwd,
49                passwd_buf.as_mut_ptr() as *mut _,
50                passwd_buf.len(),
51                &mut result,
52            )
53        };
54        if ret != 0 || result.is_null() || passwd.pw_dir.is_null() {
55            return None;
56        }
57
58        // SAFETY: `getpwuid()->pw_dir` is a valid pointer to a c-string.
59        let home_dir = unsafe { CStr::from_ptr(passwd.pw_dir) };
60
61        Some(PathBuf::from(OsStr::from_bytes(home_dir.to_bytes())))
62    }
63}
64
65#[cfg(windows)]
66mod win32 {
67    use std::{path::PathBuf, ptr::null_mut};
68
69    use windows_sys::Win32::Foundation::S_OK;
70    use windows_sys::Win32::System::Com::CoTaskMemFree;
71    use windows_sys::Win32::UI::Shell::FOLDERID_Profile;
72    use windows_sys::Win32::UI::Shell::SHGetKnownFolderPath;
73
74    pub(super) fn home_dir() -> Option<PathBuf> {
75        let rfid = FOLDERID_Profile;
76        let mut psz_path = null_mut();
77        let res = unsafe { SHGetKnownFolderPath(&rfid, 0, null_mut(), &mut psz_path as *mut _) };
78        if res != S_OK {
79            return None;
80        }
81
82        // Determine the length of the UTF-16 string.
83        let mut len = 0;
84        // SAFETY: `psz_path` guaranteed to be a valid pointer to a null-terminated UTF-16 string.
85        while unsafe { *(psz_path as *const u16).offset(len) } != 0 {
86            len += 1;
87        }
88        let slice = unsafe { std::slice::from_raw_parts(psz_path, len as usize) };
89        let path = String::from_utf16(slice).ok()?;
90        unsafe {
91            CoTaskMemFree(psz_path as *mut _);
92        }
93
94        Some(PathBuf::from(path))
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn home() {
104        let home = home_dir().unwrap();
105        assert!(home.is_dir());
106
107        if let Ok(env_home) = std::env::var("HOME") {
108            // If `HOME` is set, `home_dir` took the value from it.
109            let env_home = PathBuf::from(env_home);
110            assert_eq!(home, env_home);
111
112            // With `HOME` unset, `home_dir` should still return the same value.
113            std::env::remove_var("HOME");
114            assert_eq!(home_dir().unwrap(), env_home);
115        }
116    }
117}