std/os/windows/fs/dirs.rs
1use crate::fs::{HomeDirs, MediaDirs};
2use crate::io::{ErrorKind, const_error};
3use crate::path::PathBuf;
4use crate::{env, io};
5
6/// Windows-specific extensions to [`fs::HomeDirs`](HomeDirs).
7#[unstable(feature = "fs_home_dirs", issue = "162082")]
8pub impl(self) trait HomeDirsExt: Sized {
9 /// Load the known user folder paths from environment variables.
10 ///
11 /// The loaded known folders are:
12 ///
13 /// | `HomeDirs` | Environment Variable |
14 /// | ---------- | -------------------- |
15 /// | [`cache_home`] | `%LOCALAPPDATA%` (`%USERPROFILE%\AppData\Local`) |
16 /// | [`config_home`] | `%APPDATA%` (`%USERPROFILE%\AppData\Roaming`) |
17 /// | [`data_home`] | `%APPDATA%` (`%USERPROFILE%\AppData\Roaming`) |
18 /// | [`state_home`] | `%LOCALAPPDATA%` (`%USERPROFILE%\AppData\Local`) |
19 ///
20 /// Note that caches/state are both put in `AppData\Local`, and config/data
21 /// in `AppData\Roaming`. It is always possible for multiple user directories
22 /// to be configured to the same path, but this is the common configuration
23 /// on Windows platforms, making it even more important to not assume files
24 /// in different user directories cannot alias each other.
25 ///
26 /// # Errors
27 ///
28 /// Errors if `%APPDATA%` or `%LOCALAPPDATA%` are not set to absolute paths.
29 ///
30 /// # Implementation-specific behavior
31 ///
32 /// Windows keeps these environment variables updated to contain the paths
33 /// to the configured folder path, but it is possible for the environment
34 /// variables to not match the underlying system, such as when the user or
35 /// a program modifies the environment directly, or if the configuration
36 /// changed after the environment block was copied from the system.
37 ///
38 /// Unlike [`known_folders`](Self::known_folders), this does not require
39 /// `Shell32.dll` and thus does not require the overhead of linking in
40 /// DLLs that may result in Windows considering the application as a
41 /// graphical application.
42 ///
43 /// This behavior may change in the future. One example change that we
44 /// explicitly reserve the right to make is to load additional common
45 /// directories not currently in this list.
46 ///
47 /// [`cache_home`]: HomeDirs::cache_home
48 /// [`config_home`]: HomeDirs::config_home
49 /// [`data_home`]: HomeDirs::data_home
50 /// [`state_home`]: HomeDirs::state_home
51 #[unstable(feature = "fs_home_dirs", issue = "162082")]
52 fn appdata_env() -> io::Result<Self>;
53
54 /// Load the known user folder paths using the [Known Folders] API.
55 ///
56 /// The loaded known folders are:
57 ///
58 /// | `HomeDirs` | [`KNOWNFOLDERID`] |
59 /// | ---------- | ----------------- |
60 /// | [`cache_home`] | [`FOLDERID_LocalAppData`] (`%LOCALAPPDATA%`) |
61 /// | [`config_home`] | [`FOLDERID_RoamingAppData`] (`%APPDATA%`) |
62 /// | [`data_home`] | [`FOLDERID_RoamingAppData`] (`%APPDATA%`) |
63 /// | [`state_home`] | [`FOLDERID_LocalAppData`] (`%LOCALAPPDATA%`) |
64 ///
65 /// Note that caches/state are both put in LocalAppData, and config/data
66 /// in RoamingAppData. It is always possible for multiple user directories
67 /// to be configured to the same path, but this is the common configuration
68 /// on Windows platforms, making it even more important to not assume files
69 /// in different user directories cannot alias each other.
70 ///
71 /// # Errors
72 ///
73 /// Errors if the underlying system discovery API returns an error. The
74 /// lack of a configured path is not considered an error and results in a
75 /// `None` value.
76 ///
77 /// # Implementation-specific behavior
78 ///
79 /// Calls [`SHGetKnownFolderPath`] for the current user once for each known
80 /// folder. Does not create the folder if missing.
81 ///
82 /// COM should be initialized on the thread that calls this function or
83 /// else it may return unexpected errors.
84 ///
85 /// This behavior may change in the future. One example change that we
86 /// explicitly reserve the right to make is to load additional common
87 /// directories not currently in this list.
88 ///
89 /// [Known Folders]: https://learn.microsoft.com/en-us/windows/win32/shell/known-folders
90 /// [`SHGetKnownFolderPath`]: https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath
91 /// [`KNOWNFOLDERID`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid
92 /// [`FOLDERID_LocalAppData`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_localappdata
93 /// [`FOLDERID_RoamingAppData`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_roamingappdata
94 /// [`cache_home`]: HomeDirs::cache_home
95 /// [`config_home`]: HomeDirs::config_home
96 /// [`data_home`]: HomeDirs::data_home
97 /// [`state_home`]: HomeDirs::state_home
98 #[unstable(feature = "fs_home_dirs", issue = "162082")]
99 fn known_folders() -> io::Result<Self>;
100}
101
102/// Windows-specific extensions to [`fs::MediaDirs`](MediaDirs).
103#[unstable(feature = "fs_media_dirs", issue = "162083")]
104pub impl(self) trait MediaDirsExt: Sized {
105 /// Load the known user folder paths using the [Known Folders] API.
106 ///
107 /// The loaded known folders are:
108 ///
109 /// | `MediaDirs` | [`KNOWNFOLDERID`] |
110 /// | ---------- | ----------------- |
111 /// | [`desktop`] | [`FOLDERID_Desktop`] (`%USERPROFILE%\Desktop`) |
112 /// | [`documents`] | [`FOLDERID_Documents`] (`%USERPROFILE%\Documents`) |
113 /// | [`downloads`] | [`FOLDERID_Downloads`] (`%USERPROFILE%\Downloads`) |
114 /// | [`music`] | [`FOLDERID_Music`] (`%USERPROFILE%\Music`) |
115 /// | [`pictures`] | [`FOLDERID_Pictures`] (`%USERPROFILE%\Pictures`) |
116 /// | [`videos`] | [`FOLDERID_Videos`] (`%USERPROFILE%\Videos`) |
117 ///
118 /// # Errors
119 ///
120 /// Errors if the underlying system discovery API returns an error. The
121 /// lack of a configured path is not considered an error and results in a
122 /// `None` value.
123 ///
124 /// # Implementation-specific behavior
125 ///
126 /// Calls [`SHGetKnownFolderPath`] for the current user once for each known
127 /// folder. Does not create the folder if missing.
128 ///
129 /// COM should be initialized on the thread that calls this function or
130 /// else it may return unexpected errors.
131 ///
132 /// This behavior may change in the future. One example change that we
133 /// explicitly reserve the right to make is to load additional common
134 /// directories not currently in this list.
135 ///
136 /// [Known Folders]: https://learn.microsoft.com/en-us/windows/win32/shell/known-folders
137 /// [`SHGetKnownFolderPath`]: https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath
138 /// [`KNOWNFOLDERID`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid
139 /// [`FOLDERID_Desktop`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_desktop
140 /// [`FOLDERID_Documents`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_documents
141 /// [`FOLDERID_Downloads`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_downloads
142 /// [`FOLDERID_Music`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_music
143 /// [`FOLDERID_Pictures`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_pictures
144 /// [`FOLDERID_Videos`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_videos
145 /// [`desktop`]: MediaDirs::desktop
146 /// [`documents`]: MediaDirs::documents
147 /// [`downloads`]: MediaDirs::downloads
148 /// [`music`]: MediaDirs::music
149 /// [`pictures`]: MediaDirs::pictures
150 /// [`videos`]: MediaDirs::videos
151 #[unstable(feature = "fs_media_dirs", issue = "162083")]
152 fn known_folders() -> io::Result<Self>;
153}
154
155#[cfg(windows)]
156#[unstable(feature = "fs_home_dirs", issue = "162082")]
157impl HomeDirsExt for HomeDirs {
158 fn appdata_env() -> io::Result<Self> {
159 let roaming_app_data = env::var_os("APPDATA")
160 .map(PathBuf::from)
161 .filter(|p| p.is_absolute())
162 .ok_or(const_error!(ErrorKind::InvalidData, "non-absolute %APPDATA%"))?;
163 let local_app_data = env::var_os("LOCALAPPDATA")
164 .map(PathBuf::from)
165 .filter(|p| p.is_absolute())
166 .ok_or(const_error!(ErrorKind::InvalidData, "non-absolute %LOCALAPPDATA%"))?;
167
168 // AppData/Local -- system-local, doesn't make sense to sync to another
169 // AppData/Roaming -- data that makes sense to sync across machines
170
171 let mut dirs = HomeDirs::empty();
172
173 dirs.cache = Some(local_app_data.clone());
174 dirs.config = Some(roaming_app_data.clone());
175 dirs.data = Some(roaming_app_data);
176 dirs.state = Some(local_app_data);
177
178 Ok(dirs)
179 }
180
181 fn known_folders() -> io::Result<Self> {
182 use crate::sys::c;
183
184 let local_app_data = sys::get_known_folder_path(&c::FOLDERID_LocalAppData)?;
185 let roaming_app_data = sys::get_known_folder_path(&c::FOLDERID_RoamingAppData)?;
186
187 // AppData/Local -- system-local, doesn't make sense to sync to another
188 // AppData/Roaming -- data that makes sense to sync across machines
189
190 let mut dirs = HomeDirs::empty();
191
192 dirs.cache = local_app_data.clone();
193 dirs.config = roaming_app_data.clone();
194 dirs.data = roaming_app_data;
195 dirs.state = local_app_data;
196
197 Ok(dirs)
198 }
199}
200
201#[cfg(windows)]
202#[unstable(feature = "fs_media_dirs", issue = "162083")]
203impl MediaDirsExt for MediaDirs {
204 fn known_folders() -> io::Result<Self> {
205 use crate::sys::c;
206
207 let desktop = sys::get_known_folder_path(&c::FOLDERID_Desktop)?;
208 let documents = sys::get_known_folder_path(&c::FOLDERID_Documents)?;
209 let downloads = sys::get_known_folder_path(&c::FOLDERID_Downloads)?;
210 let music = sys::get_known_folder_path(&c::FOLDERID_Music)?;
211 let pictures = sys::get_known_folder_path(&c::FOLDERID_Pictures)?;
212 let videos = sys::get_known_folder_path(&c::FOLDERID_Videos)?;
213
214 let mut dirs = MediaDirs::empty();
215
216 dirs.desktop = desktop;
217 dirs.documents = documents;
218 dirs.downloads = downloads;
219 dirs.music = music;
220 dirs.pictures = pictures;
221 dirs.videos = videos;
222
223 Ok(dirs)
224 }
225}
226
227#[cfg(windows)]
228mod sys {
229 use crate::io::{self, ErrorKind, const_error};
230 use crate::path::PathBuf;
231 use crate::sys::{c, os2path};
232 use crate::{ptr, slice};
233
234 /// Retrieve a known folder path from the Windows API.
235 pub fn get_known_folder_path(id: &c::GUID) -> io::Result<Option<PathBuf>> {
236 // Get the known folder path. hToken = NULL requests the current user
237 // scope, and we set KF_FLAG_DONT_VERIFY because it's a bit faster and
238 // we don't guarantee that the directories at the paths exist.
239 let mut pszPath = ptr::null_mut();
240 // SAFETY: rfid/ppszPath are valid pointers, flags are appropriate, and
241 // a NULL hToken is supported by SHGetKnownFolderPath.
242 let hr = unsafe {
243 c::SHGetKnownFolderPath(
244 /* rfid */ id,
245 /* dwFlags */ c::KF_FLAG_DONT_VERIFY as _,
246 /* hToken */ ptr::null_mut(),
247 /* ppszPath */ &mut pszPath,
248 )
249 };
250
251 let result = match hr {
252 c::S_OK => {
253 // SAFETY: pszPath was populated by a successful call to SHGetKnownFolderPath
254 // and is valid up to and including its nul terminator
255 let len = unsafe { c::lstrlenW(pszPath) };
256 // SAFETY: *pszPath is valid up to and including its nul terminator
257 Ok(Some(os2path(unsafe { slice::from_raw_parts(pszPath, len as usize) })))
258 }
259 c::E_FAIL => {
260 // This known folder id exists but does not have a path
261 if cfg!(debug_assertions) {
262 unreachable!("should not call get_known_folder_path on a virtual folder");
263 } else {
264 Err(const_error!(
265 ErrorKind::InvalidInput,
266 "virtual known folders do not have paths"
267 ))
268 }
269 }
270 c::E_INVALIDARG => {
271 // This known folder id is not present on the system
272 Ok(None)
273 }
274 _ => {
275 // Miscellaneous error
276 Err(io::Error::from_raw_os_error(hr))
277 }
278 };
279
280 // SAFETY: The caller is responsible for freeing the path returned by
281 // SHGetKnownFolderPath by calling CoTaskMemFree, whether it
282 // succeeds or not.
283 unsafe { c::CoTaskMemFree(pszPath.cast()) };
284
285 result
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292
293 #[test]
294 fn can_fetch_known_folder_paths() {
295 let dirs = HomeDirs::known_folders().unwrap();
296 assert!(dirs.cache_home().is_some());
297 assert!(dirs.config_home().is_some());
298 assert!(dirs.data_home().is_some());
299 assert!(dirs.state_home().is_some());
300
301 let dirs = MediaDirs::known_folders().unwrap();
302 assert!(dirs.desktop().is_some());
303 assert!(dirs.documents().is_some());
304 assert!(dirs.downloads().is_some());
305 assert!(dirs.music().is_some());
306 assert!(dirs.pictures().is_some());
307 assert!(dirs.videos().is_some());
308 }
309}