1#![doc = include_str!("../README.md")]
2#![doc(test(attr(
3 warn(unused),
4 deny(warnings),
5 allow(unused_extern_crates),
7)))]
8
9use std::path::PathBuf;
10
11pub 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 let mut passwd: libc::passwd = unsafe { std::mem::zeroed() };
42 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 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 let mut len = 0;
84 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 let env_home = PathBuf::from(env_home);
110 assert_eq!(home, env_home);
111
112 std::env::remove_var("HOME");
114 assert_eq!(home_dir().unwrap(), env_home);
115 }
116 }
117}