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 | |
9 | use std::path::PathBuf; |
10 | |
11 | /// Get the path of the current user's home directory. |
12 | /// |
13 | /// See the library documentation for more information. |
14 | pub fn home_dir() -> Option<PathBuf> { |
15 | match std::env::var(key:"HOME" ) { |
16 | Ok(home: String) => 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)] |
32 | mod 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)] |
66 | mod 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)] |
99 | mod 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 | } |
118 | |