]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | #![warn(rust_2018_idioms)] |
2 | ||
3 | use std::ffi::OsString; | |
4 | use std::path::PathBuf; | |
5 | ||
6 | // we don't need to explicitly handle empty strings in the code above, | |
7 | // because an empty string is not considered to be a absolute path here. | |
8 | pub fn is_absolute_path(path: OsString) -> Option<PathBuf> { | |
9 | let path = PathBuf::from(path); | |
10 | if path.is_absolute() { | |
11 | Some(path) | |
12 | } else { | |
13 | None | |
14 | } | |
15 | } | |
16 | ||
17 | #[cfg(all(unix, not(target_os = "redox")))] | |
18 | mod target_unix_not_redox { | |
19 | ||
20 | use std::env; | |
21 | use std::ffi::{CStr, OsString}; | |
22 | use std::mem; | |
23 | use std::os::unix::ffi::OsStringExt; | |
24 | use std::path::PathBuf; | |
25 | use std::ptr; | |
26 | ||
27 | // https://github.com/rust-lang/rust/blob/ef3e3863939217678e5f7e755c4234d224107c64/library/std/src/sys/unix/os.rs#L587 | |
28 | pub fn home_dir() -> Option<PathBuf> { | |
29 | return env::var_os("HOME") | |
30 | .and_then(|h| if h.is_empty() { None } else { Some(h) }) | |
31 | .or_else(|| unsafe { fallback() }) | |
32 | .map(PathBuf::from); | |
33 | ||
34 | #[cfg(any(target_os = "android", target_os = "ios", target_os = "emscripten"))] | |
35 | unsafe fn fallback() -> Option<OsString> { | |
36 | None | |
37 | } | |
38 | #[cfg(not(any(target_os = "android", target_os = "ios", target_os = "emscripten")))] | |
39 | unsafe fn fallback() -> Option<OsString> { | |
40 | let amt = match libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) { | |
41 | n if n < 0 => 512 as usize, | |
42 | n => n as usize, | |
43 | }; | |
44 | let mut buf = Vec::with_capacity(amt); | |
45 | let mut passwd: libc::passwd = mem::zeroed(); | |
46 | let mut result = ptr::null_mut(); | |
47 | match libc::getpwuid_r(libc::getuid(), &mut passwd, buf.as_mut_ptr(), buf.capacity(), &mut result) { | |
48 | 0 if !result.is_null() => { | |
49 | let ptr = passwd.pw_dir as *const _; | |
50 | let bytes = CStr::from_ptr(ptr).to_bytes(); | |
51 | if bytes.is_empty() { | |
52 | None | |
53 | } else { | |
54 | Some(OsStringExt::from_vec(bytes.to_vec())) | |
55 | } | |
56 | } | |
57 | _ => None, | |
58 | } | |
59 | } | |
60 | } | |
61 | } | |
62 | ||
63 | #[cfg(all(unix, not(target_os = "redox")))] | |
64 | pub use self::target_unix_not_redox::home_dir; | |
65 | ||
66 | #[cfg(target_os = "redox")] | |
67 | mod target_redox { | |
68 | ||
69 | use std::path::PathBuf; | |
70 | ||
71 | use redox_users::{All, AllUsers, Config}; | |
72 | ||
73 | pub fn home_dir() -> Option<PathBuf> { | |
74 | let current_uid = redox_users::get_uid().ok()?; | |
75 | let users = AllUsers::basic(Config::default()).ok()?; | |
76 | let user = users.get_by_id(current_uid)?; | |
77 | ||
78 | Some(PathBuf::from(user.home.clone())) | |
79 | } | |
80 | } | |
81 | ||
82 | #[cfg(target_os = "redox")] | |
83 | pub use self::target_redox::home_dir; | |
84 | ||
85 | #[cfg(all(unix, not(any(target_os = "macos", target_os = "ios"))))] | |
86 | mod xdg_user_dirs; | |
87 | ||
88 | #[cfg(all(unix, not(any(target_os = "macos", target_os = "ios"))))] | |
89 | mod target_unix_not_mac { | |
90 | ||
91 | use std::collections::HashMap; | |
92 | use std::env; | |
93 | use std::path::{Path, PathBuf}; | |
94 | ||
95 | use super::xdg_user_dirs; | |
96 | use super::{home_dir, is_absolute_path}; | |
97 | ||
98 | fn user_dir_file(home_dir: &Path) -> PathBuf { | |
99 | env::var_os("XDG_CONFIG_HOME") | |
100 | .and_then(is_absolute_path) | |
101 | .unwrap_or_else(|| home_dir.join(".config")) | |
102 | .join("user-dirs.dirs") | |
103 | } | |
104 | ||
105 | // this could be optimized further to not create a map and instead retrieve the requested path only | |
106 | pub fn user_dir(user_dir_name: &str) -> Option<PathBuf> { | |
107 | if let Some(home_dir) = home_dir() { | |
108 | xdg_user_dirs::single(&home_dir, &user_dir_file(&home_dir), user_dir_name).remove(user_dir_name) | |
109 | } else { | |
110 | None | |
111 | } | |
112 | } | |
113 | ||
114 | pub fn user_dirs(home_dir_path: &Path) -> HashMap<String, PathBuf> { | |
115 | xdg_user_dirs::all(home_dir_path, &user_dir_file(home_dir_path)) | |
116 | } | |
117 | } | |
118 | ||
119 | #[cfg(all(unix, not(any(target_os = "macos", target_os = "ios"))))] | |
120 | pub use self::target_unix_not_mac::{user_dir, user_dirs}; | |
121 | ||
122 | #[cfg(target_os = "windows")] | |
123 | mod target_windows { | |
124 | ||
125 | use std::ffi::OsString; | |
126 | use std::os::windows::ffi::OsStringExt; | |
127 | use std::path::PathBuf; | |
128 | use std::ptr; | |
129 | use std::slice; | |
130 | ||
131 | use winapi::shared::winerror; | |
132 | use winapi::um::{combaseapi, knownfolders, shlobj, shtypes, winbase, winnt}; | |
133 | ||
134 | pub fn known_folder(folder_id: shtypes::REFKNOWNFOLDERID) -> Option<PathBuf> { | |
135 | unsafe { | |
136 | let mut path_ptr: winnt::PWSTR = ptr::null_mut(); | |
137 | let result = shlobj::SHGetKnownFolderPath(folder_id, 0, ptr::null_mut(), &mut path_ptr); | |
138 | if result == winerror::S_OK { | |
139 | let len = winbase::lstrlenW(path_ptr) as usize; | |
140 | let path = slice::from_raw_parts(path_ptr, len); | |
141 | let ostr: OsString = OsStringExt::from_wide(path); | |
142 | combaseapi::CoTaskMemFree(path_ptr as *mut winapi::ctypes::c_void); | |
143 | Some(PathBuf::from(ostr)) | |
144 | } else { | |
145 | None | |
146 | } | |
147 | } | |
148 | } | |
149 | ||
150 | pub fn known_folder_profile() -> Option<PathBuf> { | |
151 | known_folder(&knownfolders::FOLDERID_Profile) | |
152 | } | |
153 | ||
154 | pub fn known_folder_roaming_app_data() -> Option<PathBuf> { | |
155 | known_folder(&knownfolders::FOLDERID_RoamingAppData) | |
156 | } | |
157 | ||
158 | pub fn known_folder_local_app_data() -> Option<PathBuf> { | |
159 | known_folder(&knownfolders::FOLDERID_LocalAppData) | |
160 | } | |
161 | ||
162 | pub fn known_folder_music() -> Option<PathBuf> { | |
163 | known_folder(&knownfolders::FOLDERID_Music) | |
164 | } | |
165 | ||
166 | pub fn known_folder_desktop() -> Option<PathBuf> { | |
167 | known_folder(&knownfolders::FOLDERID_Desktop) | |
168 | } | |
169 | ||
170 | pub fn known_folder_documents() -> Option<PathBuf> { | |
171 | known_folder(&knownfolders::FOLDERID_Documents) | |
172 | } | |
173 | ||
174 | pub fn known_folder_downloads() -> Option<PathBuf> { | |
175 | known_folder(&knownfolders::FOLDERID_Downloads) | |
176 | } | |
177 | ||
178 | pub fn known_folder_pictures() -> Option<PathBuf> { | |
179 | known_folder(&knownfolders::FOLDERID_Pictures) | |
180 | } | |
181 | ||
182 | pub fn known_folder_public() -> Option<PathBuf> { | |
183 | known_folder(&knownfolders::FOLDERID_Public) | |
184 | } | |
185 | pub fn known_folder_templates() -> Option<PathBuf> { | |
186 | known_folder(&knownfolders::FOLDERID_Templates) | |
187 | } | |
188 | pub fn known_folder_videos() -> Option<PathBuf> { | |
189 | known_folder(&knownfolders::FOLDERID_Videos) | |
190 | } | |
191 | } | |
192 | ||
193 | #[cfg(target_os = "windows")] | |
194 | pub use self::target_windows::{ | |
195 | known_folder, known_folder_desktop, known_folder_documents, known_folder_downloads, known_folder_local_app_data, | |
196 | known_folder_music, known_folder_pictures, known_folder_profile, known_folder_public, | |
197 | known_folder_roaming_app_data, known_folder_templates, known_folder_videos, | |
198 | }; |