| 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 | |