Skip to main content

std/os/unix/fs/
dirs.rs

1use crate::env::{self, SplitPathsRef};
2use crate::ffi::{OsStr, OsString};
3use crate::fs::{self, HomeDirs, MediaDirs};
4use crate::io::{self, ErrorKind, const_error};
5use crate::macros::rustdoc_skip_body;
6use crate::path::{Path, PathBuf};
7
8#[derive(Debug, Default, Clone)]
9#[non_exhaustive]
10pub struct ExtraHomeDirs {
11    runtime: Option<PathBuf>,
12    config_path: Option<OsString>,
13    data_path: Option<OsString>,
14}
15
16#[derive(Debug, Default, Clone)]
17#[non_exhaustive]
18pub struct ExtraMediaDirs {
19    templates: Option<PathBuf>,
20}
21
22/// XDG-specific extensions to [`fs::HomeDirs`](HomeDirs).
23///
24/// The XDG conventions are defined by the Freedesktop.org project in the
25/// [XDG Base Directory Specification][xdg-basedir]. These conventions have
26/// been largely adopted by Linux distributions.
27///
28/// The XDG conventions are written to be usable on any Unix-like filesystem,
29/// thus this extension being provided in `os::unix` rather than `os::linux`.
30/// However, while some tooling does use XDG conventions on macOS, note that
31/// macOS has its own separate conventions for user directories. Consider
32/// carefully what conventions your users will expect your application to
33/// follow along with any legacy path compatibility you might need to support.
34///
35/// [xdg-basedir]: https://specifications.freedesktop.org/basedir/
36#[unstable(feature = "fs_home_dirs", issue = "162082")]
37pub impl(self) trait HomeDirsExt: Sized {
38    /// Load the user directory paths according to the
39    /// [XDG Base Directory Specification][xdg-basedir].
40    ///
41    /// Each base directory path is set to the value of its corresponding
42    /// `XDG_*` environment variable (if it is set and non-empty), else to
43    /// the default value defined by the specification.
44    ///
45    /// | Field | Environment Variable | Default Value |
46    /// | ----- | -------------------- | ------------- |
47    /// | [`cache_home`] | `XDG_CACHE_HOME` | `$HOME/.cache` |
48    /// | [`config_home`] | `XDG_CONFIG_HOME` | `$HOME/.config` |
49    /// | [`data_home`] | `XDG_DATA_HOME` | `$HOME/.local/share` |
50    /// | [`state_home`] | `XDG_STATE_HOME` | `$HOME/.local/state` |
51    /// | [`runtime_home`] | `XDG_RUNTIME_DIR` | (see method docs) |
52    /// | [`config_dirs`] | `XDG_CONFIG_DIRS` | `/etc/xdg` |
53    /// | [`data_dirs`] | `XDG_DATA_DIRS` | `/usr/local/share/`, `/usr/share/` |
54    ///
55    /// Note that `$HOME` here means [`env::home_dir`], which uses
56    /// `$HOME` if set and non-empty, but falls back to the system password
57    /// database if it isn't set.
58    ///
59    /// All paths are required to be absolute. If a relative path is configured
60    /// by the environment, it is ignored and the default value is used instead.
61    ///
62    /// `config_dirs` and `data_dirs` are a list of delimited paths using the
63    /// [`env::split_paths`] delimiter. If some but not all paths in the list are
64    /// relative, those relative paths are ignored and the remaining absolute
65    /// paths are used. If there are no valid absolute paths, the default value
66    /// is used instead.
67    ///
68    /// # Errors
69    ///
70    /// Errors if the user's home directory cannot be determined.
71    ///
72    /// [xdg-basedir]: https://specifications.freedesktop.org/basedir/
73    /// [`cache_home`]: HomeDirs::cache_home
74    /// [`config_home`]: HomeDirs::config_home
75    /// [`data_home`]: HomeDirs::data_home
76    /// [`state_home`]: HomeDirs::state_home
77    /// [`runtime_home`]: HomeDirsExt::runtime_home
78    /// [`config_dirs`]: HomeDirsExt::config_dirs
79    /// [`data_dirs`]: HomeDirsExt::data_dirs
80    /// [`env::split_paths`]: crate::env::split_paths
81    #[unstable(feature = "fs_home_dirs", issue = "162082")]
82    fn xdg() -> io::Result<Self>;
83
84    /// A base directory relative to which user-specific runtime files
85    /// (such as sockets, named pipes, etc) should be stored.
86    ///
87    /// Files in this directory may be subjected to periodic clean-up.
88    /// Larger files should not be placed here, since it might reside in
89    /// runtime memory and cannot necessarily be swapped out to disk.
90    ///
91    /// This path does not have a default if not set. If it isn't set,
92    /// applications should fall back to a replacement directory with
93    /// similar capabilities and print a warning message.
94    #[unstable(feature = "fs_home_dirs", issue = "162082")]
95    fn runtime_home(&self) -> Option<&Path>;
96
97    /// A preference-ordered list of base directories to search for config
98    /// files *in addition to* [`config_home`].
99    ///
100    /// The order of directories denotes their importance; the first directory
101    /// is the most important. Information defined relative to the more
102    /// important base directory takes precedent. [`config_home`] is not
103    /// necessarily present in this list, and is considered more important
104    /// than any base directory in this list.
105    ///
106    /// [`config_home`]: HomeDirs::config_home
107    #[unstable(feature = "fs_home_dirs", issue = "162082")]
108    fn config_dirs(&self) -> Option<XdgDirs<'_>>;
109
110    /// A preference-ordered list of base directories to search for data
111    /// files *in addition to* [`data_home`].
112    ///
113    /// The order of directories denotes their importance; the first directory
114    /// is the most important. Information defined relative to the more
115    /// important base directory takes precedent. [`data_home`] is not
116    /// necessarily present in this list, and is considered more important
117    /// than any base directory in this list.
118    ///
119    /// [`data_home`]: HomeDirs::data_home
120    #[unstable(feature = "fs_home_dirs", issue = "162082")]
121    fn data_dirs(&self) -> Option<XdgDirs<'_>>;
122
123    /// Set the path for [Self::runtime_home].
124    ///
125    /// # Panics
126    ///
127    /// Panics if the provided path is not absolute.
128    #[unstable(feature = "fs_home_dirs", issue = "162082")]
129    fn set_runtime_home(&mut self, path: PathBuf) -> &mut Self;
130
131    /// Set the paths for [Self::config_dirs].
132    ///
133    /// Takes one or more paths joined appropriately for the `PATH` environment
134    /// variable, as by [`env::join_paths`].
135    ///
136    /// # Panics
137    ///
138    /// Panics if any of the provided paths are not absolute.
139    #[unstable(feature = "fs_home_dirs", issue = "162082")]
140    fn set_config_dirs(&mut self, paths: OsString) -> &mut Self;
141
142    /// Set the paths for [Self::data_dirs].
143    ///
144    /// Takes one or more paths joined appropriately for the `PATH` environment
145    /// variable, as by [`env::join_paths`].
146    ///
147    /// # Panics
148    ///
149    /// Panics if any of the provided paths are not absolute.
150    #[unstable(feature = "fs_home_dirs", issue = "162082")]
151    fn set_data_dirs(&mut self, paths: OsString) -> &mut Self;
152}
153
154/// XDG-specific extensions to [`fs::MediaDirs`](MediaDirs).
155///
156/// The XDG conventions are defined by the Freedesktop.org project through the
157/// [xdg-user-dirs] tool. This configuration is generally present on desktop
158/// Linux distributions, although adoption is less widespread than the base
159/// directory specification.
160///
161/// The XDG conventions are written to be usable on any Unix-like filesystem,
162/// thus this extension being provided in `os::unix` rather than `os::linux`.
163/// However, while some tooling does use XDG conventions on macOS, note that
164/// macOS has its own separate conventions for user directories. Consider
165/// carefully what conventions your users will expect your application to
166/// follow along with any legacy path compatibility you might need to support.
167///
168/// [xdg-user-dirs]: https://www.freedesktop.org/wiki/Software/xdg-user-dirs/
169#[unstable(feature = "fs_media_dirs", issue = "162083")]
170pub impl(self) trait MediaDirsExt: Sized {
171    /// Load the user directory paths according to the [xdg-user-dirs] tool.
172    ///
173    /// This directly reads and parses the `$XDG_CONFIG_HOME/user-dirs.dirs`
174    /// file as defined and maintained by the [xdg-user-dirs] tool.
175    ///
176    /// # Errors
177    ///
178    /// Errors if the user's home directory cannot be determined or if the
179    /// `$XDG_CONFIG_HOME/user-dirs.dirs` file cannot be read.
180    ///
181    /// # Implementation-specific behavior
182    ///
183    /// Only the format maintained by xdg-user-dirs-update is supported. Any
184    /// configuration that does not match the expected format will result in
185    /// loading an unspecified path or `None` for that directory. To be more
186    /// specific:
187    ///
188    /// - Any line not in the format of `XDG_{NAME}_DIR={path}` where `{NAME}`
189    ///   is one of `DESKTOP`, `DOWNLOAD`, `TEMPLATES`, `PUBLICSHARE`,
190    ///   `DOCUMENTS`, `MUSIC`, `PICTURES`, or `VIDEOS` is ignored.
191    /// - `{path}` must be a `"`-quoted shell-escaped path.
192    /// - `{path}` may only start with `/` or `$HOME/`. A home-relative path
193    ///   is returned relative to [`env::home_dir`]; shell expansion is not
194    ///   performed.
195    /// - A directory set to just `$HOME` marks it as removed, and results in
196    ///   a `None` value for that path.
197    /// - If shell expansion syntax other than a leading `$HOME` is present,
198    ///   the produced directory path is unspecified. This is invalid config
199    ///   according to the xdg-user-dirs tooling.
200    ///
201    /// This behavior may change in the future. One example change that we
202    /// explicitly reserve the right to make is to load paths that we currently
203    /// ignore, such as path formats that are not canonically supported by
204    /// xdg-user-dirs but which may occur in manually-edited `user-dirs.dirs`.
205    ///
206    /// [xdg-user-dirs]: https://www.freedesktop.org/wiki/Software/xdg-user-dirs/
207    #[unstable(feature = "fs_media_dirs", issue = "162083")]
208    fn xdg() -> io::Result<Self>;
209
210    /// The OS-privileged user "Templates" directory, often the `Templates`
211    /// folder in the user's home directory.
212    ///
213    /// As a media directory, this should typically be used as a default path
214    /// for file selection dialogs, not for automatically accessed file paths.
215    #[unstable(feature = "fs_media_dirs", issue = "162083")]
216    fn templates(&self) -> Option<&Path>;
217
218    /// Set the paths for [Self::templates].
219    ///
220    /// # Panics
221    ///
222    /// Panics if the provided path is not absolute.
223    #[unstable(feature = "fs_media_dirs", issue = "162083")]
224    fn set_templates(&mut self, path: PathBuf) -> &mut Self;
225}
226
227#[unstable(feature = "fs_home_dirs", issue = "162082")]
228#[derive(Debug)]
229pub struct XdgDirs<'a>(SplitPathsRef<'a>);
230
231#[unstable(feature = "fs_home_dirs", issue = "162082")]
232impl HomeDirsExt for HomeDirs {
233    fn xdg() -> io::Result<Self> {
234        let mut dirs = HomeDirs::empty();
235        let user_home = xdg::user_home()?;
236
237        dirs.set_cache_home(xdg::dir_or_else(|| user_home.join(".cache"), "XDG_CACHE_HOME"));
238        dirs.set_config_home(xdg::dir_or_else(|| user_home.join(".config"), "XDG_CONFIG_HOME"));
239        dirs.set_data_home(xdg::dir_or_else(|| user_home.join(".local/share"), "XDG_DATA_HOME"));
240        dirs.set_state_home(xdg::dir_or_else(|| user_home.join(".local/state"), "XDG_STATE_HOME"));
241        if let Some(runtime) = xdg::dir("XDG_RUNTIME_DIR") {
242            dirs.set_runtime_home(runtime);
243        }
244
245        dirs.set_config_dirs(xdg::dirs_or("/etc/xdg", "XDG_CONFIG_DIRS"));
246        dirs.set_data_dirs(xdg::dirs_or("/usr/local/share/:/usr/share/", "XDG_DATA_DIRS"));
247
248        Ok(dirs)
249    }
250
251    #[rustdoc_skip_body]
252    fn runtime_home(&self) -> Option<&Path> {
253        self.extra.runtime.as_deref()
254    }
255
256    #[rustdoc_skip_body]
257    fn config_dirs(&self) -> Option<XdgDirs<'_>> {
258        self.extra.config_path.as_deref().map(XdgDirs::new)
259    }
260
261    #[rustdoc_skip_body]
262    fn data_dirs(&self) -> Option<XdgDirs<'_>> {
263        self.extra.data_path.as_deref().map(XdgDirs::new)
264    }
265
266    fn set_runtime_home(&mut self, path: PathBuf) -> &mut Self {
267        assert!(path.is_absolute(), "runtime directory path must be absolute");
268        self.extra.runtime = Some(path);
269        self
270    }
271
272    fn set_config_dirs(&mut self, paths: OsString) -> &mut Self {
273        for path in split_paths_ref(&paths) {
274            assert!(path.is_absolute(), "config directory paths must be absolute");
275        }
276        self.extra.config_path = Some(paths);
277        self
278    }
279
280    fn set_data_dirs(&mut self, paths: OsString) -> &mut Self {
281        for path in split_paths_ref(&paths) {
282            assert!(path.is_absolute(), "data directory paths must be absolute");
283        }
284        self.extra.data_path = Some(paths);
285        self
286    }
287}
288
289#[unstable(feature = "fs_media_dirs", issue = "162083")]
290impl MediaDirsExt for MediaDirs {
291    fn xdg() -> io::Result<Self> {
292        let user_home = xdg::user_home()?;
293        let config_home = xdg::dir_or_else(|| user_home.join(".config"), "XDG_CONFIG_HOME");
294
295        let spec = match fs::read(config_home.join("user-dirs.dirs")) {
296            Ok(spec) => spec,
297            Err(e) if e.kind() == ErrorKind::NotFound => {
298                return Err(const_error!(
299                    ErrorKind::NotFound,
300                    "missing `$XDG_CONFIG_HOME/user-dirs.dirs`",
301                ));
302            }
303            Err(e) => return Err(e),
304        };
305
306        Ok(xdg::parse_user_dirs(&spec, &user_home))
307    }
308
309    #[rustdoc_skip_body]
310    fn templates(&self) -> Option<&Path> {
311        self.extra.templates.as_deref()
312    }
313
314    fn set_templates(&mut self, path: PathBuf) -> &mut Self {
315        assert!(path.is_absolute(), "templates directory path must be absolute");
316        self.extra.templates = Some(path);
317        self
318    }
319}
320
321impl<'a> XdgDirs<'a> {
322    fn new(paths: &'a OsStr) -> Self {
323        XdgDirs(split_paths_ref(paths))
324    }
325}
326
327#[unstable(feature = "fs_home_dirs", issue = "162082")]
328impl<'a> Iterator for XdgDirs<'a> {
329    type Item = &'a Path;
330
331    fn next(&mut self) -> Option<Self::Item> {
332        self.0.next()
333    }
334}
335
336fn split_paths_ref<'a>(paths: &'a OsStr) -> SplitPathsRef<'a> {
337    // returns Some on cfg(unix)
338    env::split_paths_ref(paths).unwrap()
339}
340
341mod xdg {
342    use super::{MediaDirs, split_paths_ref};
343    use crate::env::{self, join_paths, var_os};
344    use crate::ffi::OsString;
345    use crate::io::{self, ErrorKind, const_error};
346    use crate::macros::rustdoc_skip_body;
347    use crate::ops::Deref;
348    use crate::os::unix::ffi::{OsStrExt, OsStringExt};
349    use crate::path::{Path, PathBuf};
350
351    pub fn user_home() -> io::Result<PathBuf> {
352        env::home_dir()
353            .filter(|p| p.is_absolute())
354            .ok_or(const_error!(ErrorKind::InvalidData, "user home directory path not absolute"))
355    }
356
357    pub fn dir(env: &str) -> Option<PathBuf> {
358        var_os(env).filter(|s| !s.is_empty()).map(PathBuf::from).filter(|path| path.is_absolute())
359    }
360
361    pub fn dir_or_else(fallback: impl FnOnce() -> PathBuf, env: &str) -> PathBuf {
362        dir(env).unwrap_or_else(fallback)
363    }
364
365    fn dirs(env: &str) -> Option<OsString> {
366        let dirs = var_os(env).filter(|s| !s.is_empty())?;
367        if split_paths_ref(&dirs).all(|p| p.is_absolute()) {
368            return Some(dirs);
369        }
370
371        struct DerefAsRef<T>(pub T);
372        impl<T: Deref<Target: AsRef<U>>, U: ?Sized> AsRef<U> for DerefAsRef<T> {
373            fn as_ref(&self) -> &U {
374                (*self.0).as_ref()
375            }
376        }
377
378        let paths = split_paths_ref(&dirs).filter(|p| p.is_absolute()).map(DerefAsRef);
379        join_paths(paths).ok().filter(|s| !s.is_empty())
380    }
381
382    pub fn dirs_or(fallback: impl Into<OsString>, env: &str) -> OsString {
383        dirs(env).unwrap_or_else(|| fallback.into())
384    }
385
386    #[rustdoc_skip_body]
387    pub fn parse_user_dirs<'a>(spec: &'a [u8], user_home: &Path) -> MediaDirs {
388        let mut dirs = MediaDirs::empty();
389
390        for (xdg, path) in
391            spec.split(|&b| b == b'\n').flat_map(|line| parse_user_dirs_line(line, user_home))
392        {
393            // load the known user directories
394            match xdg {
395                b"XDG_DESKTOP_DIR" => dirs.desktop = path,
396                b"XDG_DOCUMENTS_DIR" => dirs.documents = path,
397                b"XDG_DOWNLOAD_DIR" => dirs.downloads = path,
398                b"XDG_MUSIC_DIR" => dirs.music = path,
399                b"XDG_PICTURES_DIR" => dirs.pictures = path,
400                b"XDG_VIDEOS_DIR" => dirs.videos = path,
401                b"XDG_TEMPLATES_DIR" => dirs.extra.templates = path,
402                b"XDG_PUBLICSHARE_DIR" => {
403                    // we don't expose this directory yet as Windows also has a "public" directory
404                    // and it's not yet clear if we want to expose this at the target-agnostic level
405                }
406                _ => {
407                    // ignore unknown variable assignment
408                }
409            }
410        }
411
412        dirs
413    }
414
415    fn parse_user_dirs_line<'a>(
416        line: &'a [u8],
417        user_home: &Path,
418    ) -> Option<(&'a [u8], Option<PathBuf>)> {
419        // trim whitespace
420        let trimmed = line.trim_ascii();
421        // skip empty lines and comments
422        if trimmed.is_empty() || trimmed.starts_with(&[b'#']) {
423            return None;
424        }
425
426        // only variable assignment lines are allowed; split on `=`
427        let mut split = trimmed.splitn(2, |&b| b == b'=');
428        // extract assignment parts; ignore lines not in this format
429        let var = split.next()?;
430        let val = split.next()?;
431        debug_assert_eq!(split.next(), None);
432
433        // the path value is quoted; unquote it
434        let path = unquote(val, user_home)?;
435
436        let path = Some(path)
437            // ignore non-absolute paths
438            .filter(|path| path.is_absolute())
439            // setting to the home dir disables the directory configuration
440            .filter(|path| path != user_home);
441
442        Some((var, path))
443    }
444
445    fn unquote(bytes: &[u8], user_home: &Path) -> Option<PathBuf> {
446        let [b'"', rest @ .., b'"'] = bytes else { return None };
447
448        // setting to the home dir disables the directory configuration;
449        // if done symbolically, return None here and bypass later work
450        if matches!(rest, b"$HOME" | b"$HOME/") {
451            return None;
452        }
453
454        let mut rest = rest;
455        let mut s = Vec::with_capacity(rest.len());
456
457        // expand leading $HOME
458        if rest.starts_with(b"$HOME/") {
459            s.extend_from_slice(user_home.as_os_str().as_bytes());
460            if !user_home.has_trailing_sep() {
461                s.push(b'/');
462            }
463            rest = &rest[6..];
464        }
465
466        loop {
467            let i = rest
468                .iter()
469                .position(|&b| matches!(b, b'"' | b'\\' | b'$' | b'`'))
470                .unwrap_or(rest.len());
471            s.extend_from_slice(&rest[..i]);
472            match &rest[i..] {
473                [] => break,
474                [b'\\', c @ (b'"' | b'\\' | b'$' | b'`'), tail @ ..] => {
475                    // supported escapes
476                    s.push(*c);
477                    rest = tail;
478                }
479                [b'"' | b'\\' | b'$' | b'`', ..] => {
480                    // unsupported shell syntax
481                    return None;
482                }
483                _ => {
484                    if cfg!(debug_assertions) {
485                        unreachable!()
486                    } else {
487                        return None;
488                    }
489                }
490            }
491        }
492
493        Some(PathBuf::from(OsString::from_vec(s)))
494    }
495}
496
497#[cfg(test)]
498mod tests {
499    use super::*;
500
501    #[test]
502    fn can_fetch_xdg_base_dirs() {
503        let dirs = HomeDirs::xdg().unwrap();
504
505        assert!(dirs.cache_home().is_some());
506        assert!(dirs.config_home().is_some());
507        assert!(dirs.data_home().is_some());
508        assert!(dirs.state_home().is_some());
509        // dirs.runtime() may not exist
510        assert!(dirs.config_dirs().is_some());
511        assert!(dirs.data_dirs().is_some());
512    }
513
514    #[test]
515    fn can_fetch_xdg_media_dirs() {
516        let dirs = match MediaDirs::xdg() {
517            Ok(dirs) => dirs,
518            Err(e) if e.kind() == ErrorKind::NotFound => {
519                // xdg-user-dirs not initialized on this system, skip the test
520                return;
521            }
522            Err(e) => panic!("failed to fetch xdg user dirs: {e:?}"),
523        };
524
525        // even when user-dirs.dirs is present, directories may be unset,
526        // so we can't assert anything about the dir paths here
527        let _ = dirs;
528    }
529
530    #[test]
531    fn test_well_user_dirs_parsing() {
532        const TEST_USERDIRS: &'static str = r#"
533# This file is written by xdg-user-dirs-update
534# If you want to change or add directories, just edit the line you're
535# interested in. All local changes will be retained on the next run.
536# Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped
537# homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an
538# absolute path. No other format is supported.
539#
540XDG_DESKTOP_DIR="$HOME/Desktop"
541XDG_DOWNLOAD_DIR="$HOME/Downloads"
542XDG_TEMPLATES_DIR="/pub/Templates"
543XDG_PUBLICSHARE_DIR="/pub"
544XDG_DOCUMENTS_DIR="$HOME/Documents"
545XDG_MUSIC_DIR="$HOME/My \"Music\""
546XDG_PICTURES_DIR="$HOME/Pictures"
547XDG_VIDEOS_DIR="$HOME"
548"#;
549
550        let dirs = xdg::parse_user_dirs(TEST_USERDIRS.as_bytes(), Path::new("/home/ferris"));
551
552        assert_eq!(dirs.desktop(), Some("/home/ferris/Desktop".as_ref()));
553        assert_eq!(dirs.downloads(), Some("/home/ferris/Downloads".as_ref()));
554        assert_eq!(dirs.templates(), Some("/pub/Templates".as_ref()));
555        assert_eq!(dirs.documents(), Some("/home/ferris/Documents".as_ref()));
556        assert_eq!(dirs.music(), Some("/home/ferris/My \"Music\"".as_ref()));
557        assert_eq!(dirs.pictures(), Some("/home/ferris/Pictures".as_ref()));
558        assert_eq!(dirs.videos(), None);
559    }
560
561    #[test]
562    fn test_ill_user_dirs_parsing() {
563        const TEST_USERDIRS: &'static str = r#"
564# unquoted
565XDG_DESKTOP_DIR=$HOME/Desktop
566# mixed quoting
567XDG_DOWNLOAD_DIR="$HOME"/"Downloads"
568# non-HOME env
569XDG_TEMPLATES_DIR="/home/$USER/Templates"
570# missing PUBLICSHARE
571# command substitution
572XDG_DOCUMENTS_DIR="`pwd`/Documents"
573# non-symbolic homedir disabled
574XDG_MUSIC_DIR="/home/ferris"
575# set multiple times
576XDG_PICTURES_DIR="$HOME/Pictures1"
577XDG_PICTURES_DIR="$HOME/Pictures2"
578# relative path
579XDG_VIDEOS_DIR="Videos"
580"#;
581
582        let dirs = xdg::parse_user_dirs(TEST_USERDIRS.as_bytes(), Path::new("/home/ferris"));
583
584        assert_eq!(dirs.desktop(), None);
585        assert_eq!(dirs.downloads(), None);
586        assert_eq!(dirs.templates(), None);
587        assert_eq!(dirs.documents(), None);
588        assert_eq!(dirs.music(), None);
589        assert_eq!(dirs.pictures(), Some("/home/ferris/Pictures2".as_ref()));
590        assert_eq!(dirs.videos(), None);
591    }
592}