1 #![cfg(any(unix, target_os = "redox"))]
2 
3 extern crate dirs;
4 
5 use std::fmt;
6 use std::convert;
7 use std::error;
8 use std::io;
9 use std::env;
10 use std::fs;
11 use std::path::{Path, PathBuf};
12 use std::ffi::OsString;
13 
14 use std::os::unix::fs::PermissionsExt;
15 
16 use BaseDirectoriesErrorKind::*;
17 use BaseDirectoriesError as Error;
18 
19 /// BaseDirectories allows to look up paths to configuration, data,
20 /// cache and runtime files in well-known locations according to
21 /// the [X Desktop Group Base Directory specification][xdg-basedir].
22 /// [xdg-basedir]: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
23 ///
24 /// The Base Directory specification defines five kinds of files:
25 ///
26 ///   * **Configuration files** store the application's settings and
27 ///     are often modified during runtime;
28 ///   * **Data files** store supplementary data, such as graphic assets,
29 ///     precomputed tables, documentation, or architecture-independent
30 ///     source code;
31 ///   * **Cache files** store non-essential, transient data that provides
32 ///     a runtime speedup;
33 ///   * **State files** store logs, history, recently used files and application
34 ///     state (window size, open files, unsaved changes, …);
35 ///   * **Runtime files** include filesystem objects such are sockets or
36 ///     named pipes that are used for communication internal to the application.
37 ///     Runtime files must not be accessible to anyone except current user.
38 ///
39 /// # Examples
40 ///
41 /// To configure paths for application `myapp`:
42 ///
43 /// ```
44 /// extern crate xdg;
45 /// let xdg_dirs = xdg::BaseDirectories::with_prefix("myapp").unwrap();
46 /// ```
47 ///
48 /// To store configuration:
49 ///
50 /// ```
51 /// let config_path = xdg_dirs.place_config_file("config.ini")
52 ///                           .expect("cannot create configuration directory");
53 /// let mut config_file = File::create(config_path)?;
54 /// write!(&mut config_file, "configured = 1")?;
55 /// ```
56 ///
57 /// The `config.ini` file will appear in the proper location for desktop
58 /// configuration files, most likely `~/.config/myapp/config.ini`.
59 /// The leading directories will be automatically created.
60 ///
61 /// To retrieve supplementary data:
62 ///
63 /// ```
64 /// let logo_path = xdg_dirs.find_data_file("logo.png")
65 ///                         .expect("application data not present");
66 /// let mut logo_file = File::open(logo_path)?;
67 /// let mut logo = Vec::new();
68 /// logo_file.read_to_end(&mut logo)?;
69 /// ```
70 ///
71 /// The `logo.png` will be searched in the proper locations for
72 /// supplementary data files, most likely `~/.local/share/myapp/logo.png`,
73 /// then `/usr/local/share/myapp/logo.png` and `/usr/share/myapp/logo.png`.
74 #[derive(Debug, Clone)]
75 pub struct BaseDirectories {
76     shared_prefix: PathBuf,
77     user_prefix: PathBuf,
78     data_home: PathBuf,
79     config_home: PathBuf,
80     cache_home: PathBuf,
81     state_home: PathBuf,
82     data_dirs: Vec<PathBuf>,
83     config_dirs: Vec<PathBuf>,
84     runtime_dir: Option<PathBuf>,
85 }
86 
87 pub struct BaseDirectoriesError {
88     kind: BaseDirectoriesErrorKind,
89 }
90 
91 impl BaseDirectoriesError {
new(kind: BaseDirectoriesErrorKind) -> BaseDirectoriesError92     fn new(kind: BaseDirectoriesErrorKind) -> BaseDirectoriesError {
93         BaseDirectoriesError {
94             kind: kind,
95         }
96     }
97 }
98 
99 impl fmt::Debug for BaseDirectoriesError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result100     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
101         self.kind.fmt(f)
102     }
103 }
104 
105 impl error::Error for BaseDirectoriesError {
description(&self) -> &str106     fn description(&self) -> &str {
107         match self.kind {
108             HomeMissing => "$HOME must be set",
109             XdgRuntimeDirInaccessible(_, _) =>
110                 "$XDG_RUNTIME_DIR must be accessible by the current user",
111             XdgRuntimeDirInsecure(_, _) =>
112                 "$XDG_RUNTIME_DIR must be secure: have permissions 0700",
113             XdgRuntimeDirMissing =>
114                 "$XDG_RUNTIME_DIR is not set",
115         }
116     }
cause(&self) -> Option<&dyn error::Error>117     fn cause(&self) -> Option<&dyn error::Error> {
118         match self.kind {
119             XdgRuntimeDirInaccessible(_, ref e) => Some(e),
120             _ => None,
121         }
122     }
123 }
124 
125 impl fmt::Display for BaseDirectoriesError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result126     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127         match self.kind {
128             HomeMissing => write!(f, "{}", self.to_string()),
129             XdgRuntimeDirInaccessible(ref dir, ref error) => {
130                 write!(f, "$XDG_RUNTIME_DIR (`{}`) must be accessible \
131                            by the current user (error: {})", dir.display(), error)
132             },
133             XdgRuntimeDirInsecure(ref dir, permissions) => {
134                 write!(f, "$XDG_RUNTIME_DIR (`{}`) must be secure: must have \
135                            permissions 0o700, got {}", dir.display(), permissions)
136             },
137             XdgRuntimeDirMissing => {
138                 write!(f, "$XDG_RUNTIME_DIR must be set")
139             },
140         }
141     }
142 }
143 
144 impl convert::From<BaseDirectoriesError> for io::Error {
from(error: BaseDirectoriesError) -> io::Error145     fn from(error: BaseDirectoriesError) -> io::Error {
146         match error.kind {
147             HomeMissing | XdgRuntimeDirMissing =>
148                 io::Error::new(io::ErrorKind::NotFound, error),
149             _ => io::Error::new(io::ErrorKind::Other, error)
150         }
151     }
152 
153 }
154 
155 #[derive(Copy, Clone)]
156 struct Permissions(u32);
157 
158 impl fmt::Debug for Permissions {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result159     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
160         let Permissions(p) = *self;
161         write!(f, "{:#05o}", p)
162     }
163 }
164 
165 impl fmt::Display for Permissions {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result166     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
167         fmt::Debug::fmt(self, f)
168     }
169 }
170 
171 #[derive(Debug)]
172 enum BaseDirectoriesErrorKind {
173     HomeMissing,
174     XdgRuntimeDirInaccessible(PathBuf, io::Error),
175     XdgRuntimeDirInsecure(PathBuf, Permissions),
176     XdgRuntimeDirMissing,
177 }
178 
179 impl BaseDirectories {
180     /// Reads the process environment, determines the XDG base directories,
181     /// and returns a value that can be used for lookup.
182     /// The following environment variables are examined:
183     ///
184     ///   * `HOME`; if not set: use the same fallback as `dirs::home_dir()`;
185     ///     if still not available: return an error.
186     ///   * `XDG_DATA_HOME`; if not set: assumed to be `$HOME/.local/share`.
187     ///   * `XDG_CONFIG_HOME`; if not set: assumed to be `$HOME/.config`.
188     ///   * `XDG_CACHE_HOME`; if not set: assumed to be `$HOME/.cache`.
189     ///   * `XDG_STATE_HOME`; if not set: assumed to be `$HOME/.local/state`.
190     ///   * `XDG_DATA_DIRS`; if not set: assumed to be `/usr/local/share:/usr/share`.
191     ///   * `XDG_CONFIG_DIRS`; if not set: assumed to be `/etc/xdg`.
192     ///   * `XDG_RUNTIME_DIR`; if not accessible or permissions are not `0700`:
193     ///     record as inaccessible (can be queried with
194     ///     [has_runtime_directory](method.has_runtime_directory)).
195     ///
196     /// As per specification, if an environment variable contains a relative path,
197     /// the behavior is the same as if it was not set.
new() -> Result<BaseDirectories, BaseDirectoriesError>198     pub fn new() -> Result<BaseDirectories, BaseDirectoriesError> {
199         BaseDirectories::with_env("", "", &|name| env::var_os(name))
200     }
201 
202     /// Same as [`new()`](#method.new), but `prefix` is implicitly prepended to
203     /// every path that is looked up.
with_prefix<P>(prefix: P) -> Result<BaseDirectories, BaseDirectoriesError> where P: AsRef<Path>204     pub fn with_prefix<P>(prefix: P) -> Result<BaseDirectories, BaseDirectoriesError>
205             where P: AsRef<Path> {
206         BaseDirectories::with_env(prefix, "", &|name| env::var_os(name))
207     }
208 
209     /// Same as [`with_prefix()`](#method.with_prefix),
210     /// with `profile` also implicitly prepended to every path that is looked up,
211     /// but only for user-specific directories.
212     ///
213     /// This allows each user to have mutliple "profiles" with different user-specific data.
214     ///
215     /// For example:
216     ///
217     /// ```rust
218     /// let dirs = BaseDirectories::with_profile("program-name", "profile-name")
219     ///                            .unwrap();
220     /// dirs.find_data_file("bar.jpg");
221     /// dirs.find_config_file("foo.conf");
222     /// ```
223     ///
224     /// will find `/usr/share/program-name/bar.jpg` (without `profile-name`)
225     /// and `~/.config/program-name/profile-name/foo.conf`.
with_profile<P1, P2>(prefix: P1, profile: P2) -> Result<BaseDirectories, BaseDirectoriesError> where P1: AsRef<Path>, P2: AsRef<Path>226     pub fn with_profile<P1, P2>(prefix: P1, profile: P2)
227             -> Result<BaseDirectories, BaseDirectoriesError>
228             where P1: AsRef<Path>, P2: AsRef<Path> {
229         BaseDirectories::with_env(prefix, profile, &|name| env::var_os(name))
230     }
231 
with_env<P1, P2, T: ?Sized>(prefix: P1, profile: P2, env_var: &T) -> Result<BaseDirectories, BaseDirectoriesError> where P1: AsRef<Path>, P2: AsRef<Path>, T: Fn(&str) -> Option<OsString>232     fn with_env<P1, P2, T: ?Sized>(prefix: P1, profile: P2, env_var: &T)
233             -> Result<BaseDirectories, BaseDirectoriesError>
234             where P1: AsRef<Path>, P2: AsRef<Path>, T: Fn(&str) -> Option<OsString> {
235         BaseDirectories::with_env_impl(prefix.as_ref(), profile.as_ref(), env_var)
236     }
237 
with_env_impl<T: ?Sized>(prefix: &Path, profile: &Path, env_var: &T) -> Result<BaseDirectories, BaseDirectoriesError> where T: Fn(&str) -> Option<OsString>238     fn with_env_impl<T: ?Sized>(prefix: &Path, profile: &Path, env_var: &T)
239             -> Result<BaseDirectories, BaseDirectoriesError>
240             where T: Fn(&str) -> Option<OsString> {
241         fn abspath(path: OsString) -> Option<PathBuf> {
242             let path = PathBuf::from(path);
243             if path.is_absolute() {
244                 Some(path)
245             } else {
246                 None
247             }
248         }
249 
250         fn abspaths(paths: OsString) -> Option<Vec<PathBuf>> {
251             let paths = env::split_paths(&paths)
252                             .map(PathBuf::from)
253                             .filter(|ref path| path.is_absolute())
254                             .collect::<Vec<_>>();
255             if paths.is_empty() {
256                 None
257             } else {
258                 Some(paths)
259             }
260         }
261 
262         let home = dirs::home_dir().ok_or(Error::new(HomeMissing))?;
263 
264         let data_home   = env_var("XDG_DATA_HOME")
265                               .and_then(abspath)
266                               .unwrap_or(home.join(".local/share"));
267         let config_home = env_var("XDG_CONFIG_HOME")
268                               .and_then(abspath)
269                               .unwrap_or(home.join(".config"));
270         let cache_home  = env_var("XDG_CACHE_HOME")
271                               .and_then(abspath)
272                               .unwrap_or(home.join(".cache"));
273         let state_home  = env_var("XDG_STATE_HOME")
274                               .and_then(abspath)
275                               .unwrap_or(home.join(".local/state"));
276         let data_dirs   = env_var("XDG_DATA_DIRS")
277                               .and_then(abspaths)
278                               .unwrap_or(vec![PathBuf::from("/usr/local/share"),
279                                               PathBuf::from("/usr/share")]);
280         let config_dirs = env_var("XDG_CONFIG_DIRS")
281                               .and_then(abspaths)
282                               .unwrap_or(vec![PathBuf::from("/etc/xdg")]);
283         let runtime_dir = env_var("XDG_RUNTIME_DIR")
284                               .and_then(abspath); // optional
285 
286         let prefix = PathBuf::from(prefix);
287         Ok(BaseDirectories {
288             user_prefix: prefix.join(profile),
289             shared_prefix: prefix,
290             data_home,
291             config_home,
292             cache_home,
293             state_home,
294             data_dirs,
295             config_dirs,
296             runtime_dir,
297         })
298     }
299 
300     /// Returns the user-specific runtime directory (set by `XDG_RUNTIME_DIR`).
get_runtime_directory(&self) -> Result<&PathBuf, BaseDirectoriesError>301     pub fn get_runtime_directory(&self) -> Result<&PathBuf, BaseDirectoriesError> {
302         if let Some(ref runtime_dir) = self.runtime_dir {
303             // If XDG_RUNTIME_DIR is in the environment but not secure,
304             // do not allow recovery.
305             fs::read_dir(runtime_dir).map_err(|e| {
306                 Error::new(XdgRuntimeDirInaccessible(runtime_dir.clone(), e))
307             })?;
308             let permissions = fs::metadata(runtime_dir).map_err(|e| {
309                 Error::new(XdgRuntimeDirInaccessible(runtime_dir.clone(), e))
310             })?.permissions().mode() as u32;
311             if permissions & 0o077 != 0 {
312                 Err(Error::new(XdgRuntimeDirInsecure(runtime_dir.clone(),
313                                                      Permissions(permissions))))
314             } else {
315                 Ok(&runtime_dir)
316             }
317         } else {
318             Err(Error::new(XdgRuntimeDirMissing))
319         }
320     }
321 
322     /// Returns `true` if `XDG_RUNTIME_DIR` is available, `false` otherwise.
has_runtime_directory(&self) -> bool323     pub fn has_runtime_directory(&self) -> bool {
324         match self.get_runtime_directory() {
325             Ok(_) => true,
326             _ => false
327         }
328     }
329 
330     /// Like [`place_config_file()`](#method.place_config_file), but does
331     /// not create any directories.
get_config_file<P>(&self, path: P) -> PathBuf where P: AsRef<Path>332     pub fn get_config_file<P>(&self, path: P) -> PathBuf
333             where P: AsRef<Path> {
334         self.config_home.join(self.user_prefix.join(path))
335     }
336 
337     /// Like [`place_data_file()`](#method.place_data_file), but does
338     /// not create any directories.
get_data_file<P>(&self, path: P) -> PathBuf where P: AsRef<Path>339     pub fn get_data_file<P>(&self, path: P) -> PathBuf
340             where P: AsRef<Path> {
341         self.data_home.join(self.user_prefix.join(path))
342     }
343 
344     /// Like [`place_cache_file()`](#method.place_cache_file), but does
345     /// not create any directories.
get_cache_file<P>(&self, path: P) -> PathBuf where P: AsRef<Path>346     pub fn get_cache_file<P>(&self, path: P) -> PathBuf
347             where P: AsRef<Path> {
348         self.cache_home.join(self.user_prefix.join(path))
349     }
350 
351     /// Like [`place_state_file()`](#method.place_state_file), but does
352     /// not create any directories.
get_state_file<P>(&self, path: P) -> PathBuf where P: AsRef<Path>353     pub fn get_state_file<P>(&self, path: P) -> PathBuf
354             where P: AsRef<Path> {
355         self.state_home.join(self.user_prefix.join(path))
356     }
357 
358     /// Like [`place_runtime_file()`](#method.place_runtime_file), but does
359     /// not create any directories.
360     /// If `XDG_RUNTIME_DIR` is not available, returns an error.
get_runtime_file<P>(&self, path: P) -> io::Result<PathBuf> where P: AsRef<Path>361     pub fn get_runtime_file<P>(&self, path: P) -> io::Result<PathBuf>
362             where P: AsRef<Path> {
363         let runtime_dir = self.get_runtime_directory()?;
364         Ok(runtime_dir.join(self.user_prefix.join(path)))
365     }
366 
367     /// Given a relative path `path`, returns an absolute path in
368     /// `XDG_CONFIG_HOME` where a configuration file may be stored.
369     /// Leading directories in the returned path are pre-created;
370     /// if that is not possible, an error is returned.
place_config_file<P>(&self, path: P) -> io::Result<PathBuf> where P: AsRef<Path>371     pub fn place_config_file<P>(&self, path: P) -> io::Result<PathBuf>
372             where P: AsRef<Path> {
373         write_file(&self.config_home, self.user_prefix.join(path))
374     }
375 
376     /// Like [`place_config_file()`](#method.place_config_file), but for
377     /// a data file in `XDG_DATA_HOME`.
place_data_file<P>(&self, path: P) -> io::Result<PathBuf> where P: AsRef<Path>378     pub fn place_data_file<P>(&self, path: P) -> io::Result<PathBuf>
379             where P: AsRef<Path> {
380         write_file(&self.data_home, self.user_prefix.join(path))
381     }
382 
383     /// Like [`place_config_file()`](#method.place_config_file), but for
384     /// a cache file in `XDG_CACHE_HOME`.
place_cache_file<P>(&self, path: P) -> io::Result<PathBuf> where P: AsRef<Path>385     pub fn place_cache_file<P>(&self, path: P) -> io::Result<PathBuf>
386             where P: AsRef<Path> {
387         write_file(&self.cache_home, self.user_prefix.join(path))
388     }
389 
390     /// Like [`place_config_file()`](#method.place_config_file), but for
391     /// an application state file in `XDG_STATE_HOME`.
place_state_file<P>(&self, path: P) -> io::Result<PathBuf> where P: AsRef<Path>392     pub fn place_state_file<P>(&self, path: P) -> io::Result<PathBuf>
393             where P: AsRef<Path> {
394         write_file(&self.state_home, self.user_prefix.join(path))
395     }
396 
397     /// Like [`place_config_file()`](#method.place_config_file), but for
398     /// a runtime file in `XDG_RUNTIME_DIR`.
399     /// If `XDG_RUNTIME_DIR` is not available, returns an error.
place_runtime_file<P>(&self, path: P) -> io::Result<PathBuf> where P: AsRef<Path>400     pub fn place_runtime_file<P>(&self, path: P) -> io::Result<PathBuf>
401             where P: AsRef<Path> {
402         write_file(self.get_runtime_directory()?, self.user_prefix.join(path))
403     }
404 
405     /// Given a relative path `path`, returns an absolute path to an existing
406     /// configuration file, or `None`. Searches `XDG_CONFIG_HOME` and then
407     /// `XDG_CONFIG_DIRS`.
find_config_file<P>(&self, path: P) -> Option<PathBuf> where P: AsRef<Path>408     pub fn find_config_file<P>(&self, path: P) -> Option<PathBuf>
409             where P: AsRef<Path> {
410         read_file(&self.config_home, &self.config_dirs,
411                   &self.user_prefix, &self.shared_prefix, path.as_ref())
412     }
413 
414     /// Given a relative path `path`, returns an iterator yielding absolute
415     /// paths to existing configuration files, in `XDG_CONFIG_DIRS` and
416     /// `XDG_CONFIG_HOME`. Paths are produced in order from lowest priority
417     /// to highest.
find_config_files<P>(&self, path: P) -> FileFindIterator where P: AsRef<Path>418     pub fn find_config_files<P>(&self, path: P) -> FileFindIterator
419             where P: AsRef<Path> {
420         FileFindIterator::new(&self.config_home, &self.config_dirs,
421                     &self.user_prefix, &self.shared_prefix, path.as_ref())
422     }
423 
424     /// Given a relative path `path`, returns an absolute path to an existing
425     /// data file, or `None`. Searches `XDG_DATA_HOME` and then
426     /// `XDG_DATA_DIRS`.
find_data_file<P>(&self, path: P) -> Option<PathBuf> where P: AsRef<Path>427     pub fn find_data_file<P>(&self, path: P) -> Option<PathBuf>
428             where P: AsRef<Path> {
429         read_file(&self.data_home, &self.data_dirs,
430                   &self.user_prefix, &self.shared_prefix, path.as_ref())
431     }
432 
433     /// Given a relative path `path`, returns an iterator yielding absolute
434     /// paths to existing data files, in `XDG_DATA_DIRS` and
435     /// `XDG_DATA_HOME`. Paths are produced in order from lowest priority
436     /// to highest.
find_data_files<P>(&self, path: P) -> FileFindIterator where P: AsRef<Path>437     pub fn find_data_files<P>(&self, path: P) -> FileFindIterator
438             where P: AsRef<Path> {
439         FileFindIterator::new(&self.data_home, &self.data_dirs,
440                     &self.user_prefix, &self.shared_prefix, path.as_ref())
441     }
442 
443     /// Given a relative path `path`, returns an absolute path to an existing
444     /// cache file, or `None`. Searches `XDG_CACHE_HOME`.
find_cache_file<P>(&self, path: P) -> Option<PathBuf> where P: AsRef<Path>445     pub fn find_cache_file<P>(&self, path: P) -> Option<PathBuf>
446             where P: AsRef<Path> {
447         read_file(&self.cache_home, &Vec::new(),
448                   &self.user_prefix, &self.shared_prefix, path.as_ref())
449     }
450 
451     /// Given a relative path `path`, returns an absolute path to an existing
452     /// application state file, or `None`. Searches `XDG_STATE_HOME`.
find_state_file<P>(&self, path: P) -> Option<PathBuf> where P: AsRef<Path>453     pub fn find_state_file<P>(&self, path: P) -> Option<PathBuf>
454             where P: AsRef<Path> {
455         read_file(&self.state_home, &Vec::new(),
456                   &self.user_prefix, &self.shared_prefix, path.as_ref())
457     }
458 
459     /// Given a relative path `path`, returns an absolute path to an existing
460     /// runtime file, or `None`. Searches `XDG_RUNTIME_DIR`.
461     /// If `XDG_RUNTIME_DIR` is not available, returns `None`.
find_runtime_file<P>(&self, path: P) -> Option<PathBuf> where P: AsRef<Path>462     pub fn find_runtime_file<P>(&self, path: P) -> Option<PathBuf>
463             where P: AsRef<Path> {
464         if let Ok(runtime_dir) = self.get_runtime_directory() {
465             read_file(runtime_dir, &Vec::new(),
466                       &self.user_prefix, &self.shared_prefix, path.as_ref())
467         } else {
468             None
469         }
470     }
471 
472     /// Given a relative path `path`, returns an absolute path to a configuration
473     /// directory in `XDG_CONFIG_HOME`. The directory and all directories
474     /// leading to it are created if they did not exist;
475     /// if that is not possible, an error is returned.
create_config_directory<P>(&self, path: P) -> io::Result<PathBuf> where P: AsRef<Path>476     pub fn create_config_directory<P>(&self, path: P) -> io::Result<PathBuf>
477             where P: AsRef<Path> {
478         create_directory(&self.config_home,
479                          self.user_prefix.join(path))
480     }
481 
482     /// Like [`create_config_directory()`](#method.create_config_directory),
483     /// but for a data directory in `XDG_DATA_HOME`.
create_data_directory<P>(&self, path: P) -> io::Result<PathBuf> where P: AsRef<Path>484     pub fn create_data_directory<P>(&self, path: P) -> io::Result<PathBuf>
485             where P: AsRef<Path> {
486         create_directory(&self.data_home,
487                          self.user_prefix.join(path))
488     }
489 
490     /// Like [`create_config_directory()`](#method.create_config_directory),
491     /// but for a cache directory in `XDG_CACHE_HOME`.
create_cache_directory<P>(&self, path: P) -> io::Result<PathBuf> where P: AsRef<Path>492     pub fn create_cache_directory<P>(&self, path: P) -> io::Result<PathBuf>
493             where P: AsRef<Path> {
494         create_directory(&self.cache_home,
495                          self.user_prefix.join(path))
496     }
497 
498     /// Like [`create_config_directory()`](#method.create_config_directory),
499     /// but for an application state directory in `XDG_STATE_HOME`.
create_state_directory<P>(&self, path: P) -> io::Result<PathBuf> where P: AsRef<Path>500     pub fn create_state_directory<P>(&self, path: P) -> io::Result<PathBuf>
501             where P: AsRef<Path> {
502         create_directory(&self.state_home,
503                          self.user_prefix.join(path))
504     }
505 
506     /// Like [`create_config_directory()`](#method.create_config_directory),
507     /// but for a runtime directory in `XDG_RUNTIME_DIR`.
508     /// If `XDG_RUNTIME_DIR` is not available, returns an error.
create_runtime_directory<P>(&self, path: P) -> io::Result<PathBuf> where P: AsRef<Path>509     pub fn create_runtime_directory<P>(&self, path: P) -> io::Result<PathBuf>
510             where P: AsRef<Path> {
511         create_directory(self.get_runtime_directory()?,
512                          self.user_prefix.join(path))
513     }
514 
515     /// Given a relative path `path`, list absolute paths to all files
516     /// in directories with path `path` in `XDG_CONFIG_HOME` and
517     /// `XDG_CONFIG_DIRS`.
list_config_files<P>(&self, path: P) -> Vec<PathBuf> where P: AsRef<Path>518     pub fn list_config_files<P>(&self, path: P) -> Vec<PathBuf>
519             where P: AsRef<Path> {
520         list_files(&self.config_home, &self.config_dirs,
521                    &self.user_prefix, &self.shared_prefix, path.as_ref())
522     }
523 
524     /// Like [`list_config_files`](#method.list_config_files), but
525     /// only the first occurence of every distinct filename is returned.
list_config_files_once<P>(&self, path: P) -> Vec<PathBuf> where P: AsRef<Path>526     pub fn list_config_files_once<P>(&self, path: P) -> Vec<PathBuf>
527             where P: AsRef<Path> {
528         list_files_once(&self.config_home, &self.config_dirs,
529                         &self.user_prefix, &self.shared_prefix, path.as_ref())
530     }
531 
532     /// Given a relative path `path`, lists absolute paths to all files
533     /// in directories with path `path` in `XDG_DATA_HOME` and
534     /// `XDG_DATA_DIRS`.
list_data_files<P>(&self, path: P) -> Vec<PathBuf> where P: AsRef<Path>535     pub fn list_data_files<P>(&self, path: P) -> Vec<PathBuf>
536             where P: AsRef<Path> {
537         list_files(&self.data_home, &self.data_dirs,
538                    &self.user_prefix, &self.shared_prefix, path.as_ref())
539     }
540 
541     /// Like [`list_data_files`](#method.list_data_files), but
542     /// only the first occurence of every distinct filename is returned.
list_data_files_once<P>(&self, path: P) -> Vec<PathBuf> where P: AsRef<Path>543     pub fn list_data_files_once<P>(&self, path: P) -> Vec<PathBuf>
544             where P: AsRef<Path> {
545         list_files_once(&self.data_home, &self.data_dirs,
546                         &self.user_prefix, &self.shared_prefix, path.as_ref())
547     }
548 
549     /// Given a relative path `path`, lists absolute paths to all files
550     /// in directories with path `path` in `XDG_CACHE_HOME`.
list_cache_files<P>(&self, path: P) -> Vec<PathBuf> where P: AsRef<Path>551     pub fn list_cache_files<P>(&self, path: P) -> Vec<PathBuf>
552             where P: AsRef<Path> {
553         list_files(&self.cache_home, &Vec::new(),
554                    &self.user_prefix, &self.shared_prefix, path.as_ref())
555     }
556 
557     /// Given a relative path `path`, lists absolute paths to all files
558     /// in directories with path `path` in `XDG_STATE_HOME`.
list_state_files<P>(&self, path: P) -> Vec<PathBuf> where P: AsRef<Path>559     pub fn list_state_files<P>(&self, path: P) -> Vec<PathBuf>
560             where P: AsRef<Path> {
561         list_files(&self.state_home, &Vec::new(),
562                    &self.user_prefix, &self.shared_prefix, path.as_ref())
563     }
564 
565     /// Given a relative path `path`, lists absolute paths to all files
566     /// in directories with path `path` in `XDG_RUNTIME_DIR`.
567     /// If `XDG_RUNTIME_DIR` is not available, returns an empty `Vec`.
list_runtime_files<P>(&self, path: P) -> Vec<PathBuf> where P: AsRef<Path>568     pub fn list_runtime_files<P>(&self, path: P) -> Vec<PathBuf>
569             where P: AsRef<Path> {
570         if let Ok(runtime_dir) = self.get_runtime_directory() {
571             list_files(runtime_dir, &Vec::new(),
572                        &self.user_prefix, &self.shared_prefix, path.as_ref())
573         } else {
574             Vec::new()
575         }
576     }
577 
578     /// Returns the user-specific data directory (set by `XDG_DATA_HOME`).
get_data_home(&self) -> PathBuf579     pub fn get_data_home(&self) -> PathBuf {
580         self.data_home.join(&self.user_prefix)
581     }
582 
583     /// Returns the user-specific configuration directory (set by
584     /// `XDG_CONFIG_HOME`).
get_config_home(&self) -> PathBuf585     pub fn get_config_home(&self) -> PathBuf {
586         self.config_home.join(&self.user_prefix)
587     }
588 
589     /// Returns the user-specific directory for non-essential (cached) data
590     /// (set by `XDG_CACHE_HOME`).
get_cache_home(&self) -> PathBuf591     pub fn get_cache_home(&self) -> PathBuf {
592         self.cache_home.join(&self.user_prefix)
593     }
594 
595     /// Returns the user-specific directory for application state data
596     /// (set by `XDG_STATE_HOME`).
get_state_home(&self) -> PathBuf597     pub fn get_state_home(&self) -> PathBuf {
598         self.state_home.join(&self.user_prefix)
599     }
600 
601     /// Returns a preference ordered (preferred to less preferred) list of
602     /// supplementary data directories, ordered by preference (set by
603     /// `XDG_DATA_DIRS`).
get_data_dirs(&self) -> Vec<PathBuf>604     pub fn get_data_dirs(&self) -> Vec<PathBuf> {
605         self.data_dirs.iter().map(|p| p.join(&self.shared_prefix)).collect()
606     }
607 
608     /// Returns a preference ordered (preferred to less preferred) list of
609     /// supplementary configuration directories (set by `XDG_CONFIG_DIRS`).
get_config_dirs(&self) -> Vec<PathBuf>610     pub fn get_config_dirs(&self) -> Vec<PathBuf> {
611         self.config_dirs.iter().map(|p| p.join(&self.shared_prefix)).collect()
612     }
613 }
614 
write_file<P>(home: &PathBuf, path: P) -> io::Result<PathBuf> where P: AsRef<Path>615 fn write_file<P>(home: &PathBuf, path: P) -> io::Result<PathBuf>
616         where P: AsRef<Path> {
617     match path.as_ref().parent() {
618         Some(parent) => fs::create_dir_all(home.join(parent))?,
619         None => fs::create_dir_all(home)?,
620     }
621     Ok(PathBuf::from(home.join(path.as_ref())))
622 }
623 
create_directory<P>(home: &PathBuf, path: P) -> io::Result<PathBuf> where P: AsRef<Path>624 fn create_directory<P>(home: &PathBuf, path: P) -> io::Result<PathBuf>
625         where P: AsRef<Path> {
626     let full_path = home.join(path.as_ref());
627     fs::create_dir_all(&full_path)?;
628     Ok(full_path)
629 }
630 
path_exists<P: ?Sized + AsRef<Path>>(path: &P) -> bool631 fn path_exists<P: ?Sized + AsRef<Path>>(path: &P) -> bool {
632     fn inner(path: &Path) -> bool {
633         fs::metadata(path).is_ok()
634     }
635     inner(path.as_ref())
636 }
637 
638 #[cfg(test)]
path_is_dir<P: ?Sized + AsRef<Path>>(path: &P) -> bool639 fn path_is_dir<P: ?Sized + AsRef<Path>>(path: &P) -> bool {
640     fn inner(path: &Path) -> bool {
641         fs::metadata(path).map(|m| m.is_dir()).unwrap_or(false)
642     }
643     inner(path.as_ref())
644 }
645 
read_file(home: &PathBuf, dirs: &Vec<PathBuf>, user_prefix: &Path, shared_prefix: &Path, path: &Path) -> Option<PathBuf>646 fn read_file(home: &PathBuf, dirs: &Vec<PathBuf>,
647              user_prefix: &Path, shared_prefix: &Path, path: &Path)
648              -> Option<PathBuf> {
649     let full_path = home.join(user_prefix).join(path);
650     if path_exists(&full_path) {
651         return Some(full_path)
652     }
653     for dir in dirs.iter() {
654         let full_path = dir.join(shared_prefix).join(path);
655         if path_exists(&full_path) {
656             return Some(full_path)
657         }
658     }
659     None
660 }
661 
662 use std::vec::IntoIter as VecIter;
663 pub struct FileFindIterator {
664     search_dirs: VecIter<PathBuf>,
665     relpath: PathBuf,
666 }
667 
668 impl FileFindIterator {
new(home: &PathBuf, dirs: &Vec<PathBuf>, user_prefix: &Path, shared_prefix: &Path, path: &Path) -> FileFindIterator669     fn new(home: &PathBuf, dirs: &Vec<PathBuf>,
670            user_prefix: &Path, shared_prefix: &Path, path: &Path)
671            -> FileFindIterator {
672        let mut search_dirs = Vec::new();
673        for dir in dirs.iter().rev() {
674            search_dirs.push(dir.join(shared_prefix));
675        }
676        search_dirs.push(home.join(user_prefix));
677        FileFindIterator {
678            search_dirs: search_dirs.into_iter(),
679            relpath: path.to_path_buf(),
680        }
681    }
682 }
683 
684 impl Iterator for FileFindIterator {
685     type Item = PathBuf;
686 
next(&mut self) -> Option<Self::Item>687     fn next(&mut self) -> Option<Self::Item> {
688         loop {
689             let dir = self.search_dirs.next()?;
690             let candidate = dir.join(self.relpath.clone());
691             if path_exists(&candidate) {
692                 return Some(candidate)
693             }
694         }
695     }
696 }
697 
698 impl DoubleEndedIterator for FileFindIterator {
next_back(&mut self) -> Option<Self::Item>699     fn next_back(&mut self) -> Option<Self::Item> {
700         loop {
701             let dir = self.search_dirs.next_back()?;
702             let candidate = dir.join(self.relpath.clone());
703             if path_exists(&candidate) {
704                 return Some(candidate)
705             }
706         }
707     }
708 }
709 
list_files(home: &Path, dirs: &[PathBuf], user_prefix: &Path, shared_prefix: &Path, path: &Path) -> Vec<PathBuf>710 fn list_files(home: &Path, dirs: &[PathBuf],
711               user_prefix: &Path, shared_prefix: &Path, path: &Path)
712               -> Vec<PathBuf> {
713     fn read_dir(dir: &Path, into: &mut Vec<PathBuf>) {
714         if let Ok(entries) = fs::read_dir(dir) {
715             into.extend(
716                 entries
717                 .filter_map(|entry| entry.ok())
718                 .map(|entry| entry.path()))
719         }
720     }
721     let mut files = Vec::new();
722     read_dir(&home.join(user_prefix).join(path), &mut files);
723     for dir in dirs {
724         read_dir(&dir.join(shared_prefix).join(path), &mut files);
725     }
726     files
727 }
728 
list_files_once(home: &Path, dirs: &[PathBuf], user_prefix: &Path, shared_prefix: &Path, path: &Path) -> Vec<PathBuf>729 fn list_files_once(home: &Path, dirs: &[PathBuf],
730                    user_prefix: &Path, shared_prefix: &Path, path: &Path)
731                    -> Vec<PathBuf> {
732     let mut seen = std::collections::HashSet::new();
733     list_files(home, dirs, user_prefix, shared_prefix, path).into_iter().filter(|path| {
734         match path.clone().file_name() {
735             None => false,
736             Some(filename) => {
737                 if seen.contains(filename) {
738                     false
739                 } else {
740                     seen.insert(filename.to_owned());
741                     true
742                 }
743             }
744         }
745     }).collect::<Vec<_>>()
746 }
747 
748 #[cfg(test)]
make_absolute<P>(path: P) -> PathBuf where P: AsRef<Path>749 fn make_absolute<P>(path: P) -> PathBuf where P: AsRef<Path> {
750     env::current_dir().unwrap().join(path.as_ref())
751 }
752 
753 #[cfg(test)]
iter_after<A, I, J>(mut iter: I, mut prefix: J) -> Option<I> where I: Iterator<Item=A> + Clone, J: Iterator<Item=A>, A: PartialEq754 fn iter_after<A, I, J>(mut iter: I, mut prefix: J) -> Option<I> where
755     I: Iterator<Item=A> + Clone, J: Iterator<Item=A>, A: PartialEq
756 {
757     loop {
758         let mut iter_next = iter.clone();
759         match (iter_next.next(), prefix.next()) {
760             (Some(x), Some(y)) => {
761                 if x != y { return None }
762             }
763             (Some(_), None) => return Some(iter),
764             (None, None) => return Some(iter),
765             (None, Some(_)) => return None,
766         }
767         iter = iter_next;
768     }
769 }
770 
771 #[cfg(test)]
make_relative<P>(path: P) -> PathBuf where P: AsRef<Path>772 fn make_relative<P>(path: P) -> PathBuf where P: AsRef<Path> {
773     iter_after(path.as_ref().components(), env::current_dir().unwrap().components())
774         .unwrap().as_path().to_owned()
775 }
776 
777 #[cfg(test)]
make_env(vars: Vec<(&'static str, String)>) -> Box<dyn Fn(&str)->Option<OsString>>778 fn make_env(vars: Vec<(&'static str, String)>) ->
779         Box<dyn Fn(&str)->Option<OsString>> {
780     return Box::new(move |name| {
781         for &(key, ref value) in vars.iter() {
782             if key == name { return Some(OsString::from(value)) }
783         }
784         None
785     })
786 }
787 
788 #[test]
test_files_exists()789 fn test_files_exists() {
790     assert!(path_exists("test_files"));
791     assert!(fs::metadata("test_files/runtime-bad")
792                  .unwrap().permissions().mode() & 0o077 != 0);
793 }
794 
795 #[test]
test_bad_environment()796 fn test_bad_environment() {
797     let xd = BaseDirectories::with_env("", "", &*make_env(vec![
798             ("HOME", "test_files/user".to_string()),
799             ("XDG_DATA_HOME", "test_files/user/data".to_string()),
800             ("XDG_CONFIG_HOME", "test_files/user/config".to_string()),
801             ("XDG_CACHE_HOME", "test_files/user/cache".to_string()),
802             ("XDG_DATA_DIRS", "test_files/user/data".to_string()),
803             ("XDG_CONFIG_DIRS", "test_files/user/config".to_string()),
804             ("XDG_RUNTIME_DIR", "test_files/runtime-bad".to_string())
805         ])).unwrap();
806     assert_eq!(xd.find_data_file("everywhere"), None);
807     assert_eq!(xd.find_config_file("everywhere"), None);
808     assert_eq!(xd.find_cache_file("everywhere"), None);
809 }
810 
811 #[test]
test_good_environment()812 fn test_good_environment() {
813     let cwd = env::current_dir().unwrap().to_string_lossy().into_owned();
814     let xd = BaseDirectories::with_env("", "", &*make_env(vec![
815             ("HOME", format!("{}/test_files/user", cwd)),
816             ("XDG_DATA_HOME", format!("{}/test_files/user/data", cwd)),
817             ("XDG_CONFIG_HOME", format!("{}/test_files/user/config", cwd)),
818             ("XDG_CACHE_HOME", format!("{}/test_files/user/cache", cwd)),
819             ("XDG_DATA_DIRS", format!("{}/test_files/system0/data:{}/test_files/system1/data:{}/test_files/system2/data:{}/test_files/system3/data", cwd, cwd, cwd, cwd)),
820             ("XDG_CONFIG_DIRS", format!("{}/test_files/system0/config:{}/test_files/system1/config:{}/test_files/system2/config:{}/test_files/system3/config", cwd, cwd, cwd, cwd)),
821             // ("XDG_RUNTIME_DIR", format!("{}/test_files/runtime-bad", cwd)),
822         ])).unwrap();
823     assert!(xd.find_data_file("everywhere") != None);
824     assert!(xd.find_config_file("everywhere") != None);
825     assert!(xd.find_cache_file("everywhere") != None);
826 
827     let mut config_files = xd.find_config_files("everywhere");
828     assert_eq!(config_files.next(),
829         Some(PathBuf::from(format!("{}/test_files/system2/config/everywhere", cwd))));
830     assert_eq!(config_files.next(),
831         Some(PathBuf::from(format!("{}/test_files/system1/config/everywhere", cwd))));
832     assert_eq!(config_files.next(),
833         Some(PathBuf::from(format!("{}/test_files/user/config/everywhere", cwd))));
834     assert_eq!(config_files.next(), None);
835 
836     let mut data_files = xd.find_data_files("everywhere");
837     assert_eq!(data_files.next(),
838         Some(PathBuf::from(format!("{}/test_files/system2/data/everywhere", cwd))));
839     assert_eq!(data_files.next(),
840         Some(PathBuf::from(format!("{}/test_files/system1/data/everywhere", cwd))));
841     assert_eq!(data_files.next(),
842         Some(PathBuf::from(format!("{}/test_files/user/data/everywhere", cwd))));
843     assert_eq!(data_files.next(), None);
844 }
845 
846 #[test]
test_runtime_bad()847 fn test_runtime_bad() {
848     let cwd = env::current_dir().unwrap().to_string_lossy().into_owned();
849     let xd = BaseDirectories::with_env("", "", &*make_env(vec![
850             ("HOME", format!("{}/test_files/user", cwd)),
851             ("XDG_RUNTIME_DIR", format!("{}/test_files/runtime-bad", cwd)),
852         ])).unwrap();
853     assert!(xd.has_runtime_directory() == false);
854 }
855 
856 #[test]
test_runtime_good()857 fn test_runtime_good() {
858     use std::fs::File;
859 
860     let test_runtime_dir = make_absolute(&"test_files/runtime-good");
861     let _ = fs::remove_dir_all(&test_runtime_dir);
862     fs::create_dir_all(&test_runtime_dir).unwrap();
863 
864     let mut perms = fs::metadata(&test_runtime_dir).unwrap().permissions();
865     perms.set_mode(0o700);
866     fs::set_permissions(&test_runtime_dir, perms).unwrap();
867 
868     let cwd = env::current_dir().unwrap().to_string_lossy().into_owned();
869     let xd = BaseDirectories::with_env("", "", &*make_env(vec![
870             ("HOME", format!("{}/test_files/user", cwd)),
871             ("XDG_RUNTIME_DIR", format!("{}/test_files/runtime-good", cwd)),
872         ])).unwrap();
873 
874     xd.create_runtime_directory("foo").unwrap();
875     assert!(path_is_dir("test_files/runtime-good/foo"));
876     let w = xd.place_runtime_file("bar/baz").unwrap();
877     assert!(path_is_dir("test_files/runtime-good/bar"));
878     assert!(!path_exists("test_files/runtime-good/bar/baz"));
879     File::create(&w).unwrap();
880     assert!(path_exists("test_files/runtime-good/bar/baz"));
881     assert!(xd.find_runtime_file("bar/baz") == Some(w.clone()));
882     File::open(&w).unwrap();
883     fs::remove_file(&w).unwrap();
884     let root = xd.list_runtime_files(".");
885     let mut root = root.into_iter().map(|p| make_relative(&p)).collect::<Vec<_>>();
886     root.sort();
887     assert_eq!(root,
888                vec![PathBuf::from("test_files/runtime-good/bar"),
889                     PathBuf::from("test_files/runtime-good/foo")]);
890     assert!(xd.list_runtime_files("bar").is_empty());
891     assert!(xd.find_runtime_file("foo/qux").is_none());
892     assert!(xd.find_runtime_file("qux/foo").is_none());
893     assert!(!path_exists("test_files/runtime-good/qux"));
894 }
895 
896 #[test]
test_lists()897 fn test_lists() {
898     let cwd = env::current_dir().unwrap().to_string_lossy().into_owned();
899     let xd = BaseDirectories::with_env("", "", &*make_env(vec![
900             ("HOME", format!("{}/test_files/user", cwd)),
901             ("XDG_DATA_HOME", format!("{}/test_files/user/data", cwd)),
902             ("XDG_CONFIG_HOME", format!("{}/test_files/user/config", cwd)),
903             ("XDG_CACHE_HOME", format!("{}/test_files/user/cache", cwd)),
904             ("XDG_DATA_DIRS", format!("{}/test_files/system0/data:{}/test_files/system1/data:{}/test_files/system2/data:{}/test_files/system3/data", cwd, cwd, cwd, cwd)),
905             ("XDG_CONFIG_DIRS", format!("{}/test_files/system0/config:{}/test_files/system1/config:{}/test_files/system2/config:{}/test_files/system3/config", cwd, cwd, cwd, cwd)),
906         ])).unwrap();
907 
908     let files = xd.list_config_files(".");
909     let mut files = files.into_iter().map(|p| make_relative(&p)).collect::<Vec<_>>();
910     files.sort();
911     assert_eq!(files,
912         [
913             "test_files/system1/config/both_system_config.file",
914             "test_files/system1/config/everywhere",
915             "test_files/system1/config/myapp",
916             "test_files/system1/config/system1_config.file",
917             "test_files/system2/config/both_system_config.file",
918             "test_files/system2/config/everywhere",
919             "test_files/system2/config/system2_config.file",
920             "test_files/user/config/everywhere",
921             "test_files/user/config/myapp",
922             "test_files/user/config/user_config.file",
923         ].iter().map(PathBuf::from).collect::<Vec<_>>());
924 
925     let files = xd.list_config_files_once(".");
926     let mut files = files.into_iter().map(|p| make_relative(&p)).collect::<Vec<_>>();
927     files.sort();
928     assert_eq!(files,
929         [
930             "test_files/system1/config/both_system_config.file",
931             "test_files/system1/config/system1_config.file",
932             "test_files/system2/config/system2_config.file",
933             "test_files/user/config/everywhere",
934             "test_files/user/config/myapp",
935             "test_files/user/config/user_config.file",
936         ].iter().map(PathBuf::from).collect::<Vec<_>>());
937 }
938 
939 #[test]
test_get_file()940 fn test_get_file() {
941     let cwd = env::current_dir().unwrap().to_string_lossy().into_owned();
942     let xd = BaseDirectories::with_env("", "", &*make_env(vec![
943             ("HOME", format!("{}/test_files/user", cwd)),
944             ("XDG_DATA_HOME", format!("{}/test_files/user/data", cwd)),
945             ("XDG_CONFIG_HOME", format!("{}/test_files/user/config", cwd)),
946             ("XDG_CACHE_HOME", format!("{}/test_files/user/cache", cwd)),
947             ("XDG_RUNTIME_DIR", format!("{}/test_files/user/runtime", cwd)),
948         ])).unwrap();
949 
950     let path = format!("{}/test_files/user/runtime/", cwd);
951     let metadata = fs::metadata(&path).expect("Could not read metadata for runtime directory");
952     let mut perms = metadata.permissions();
953     perms.set_mode(0o700);
954     fs::set_permissions(&path, perms).expect("Could not set permissions for runtime directory");
955 
956     let file = xd.get_config_file("myapp/user_config.file");
957     assert_eq!(file, PathBuf::from(&format!("{}/test_files/user/config/myapp/user_config.file", cwd)));
958 
959     let file = xd.get_data_file("user_data.file");
960     assert_eq!(file, PathBuf::from(&format!("{}/test_files/user/data/user_data.file", cwd)));
961 
962     let file = xd.get_cache_file("user_cache.file");
963     assert_eq!(file, PathBuf::from(&format!("{}/test_files/user/cache/user_cache.file", cwd)));
964 
965     let file = xd.get_runtime_file("user_runtime.file").unwrap();
966     assert_eq!(file, PathBuf::from(&format!("{}/test_files/user/runtime/user_runtime.file", cwd)));
967 }
968 
969 #[test]
test_prefix()970 fn test_prefix() {
971     let cwd = env::current_dir().unwrap().to_string_lossy().into_owned();
972     let xd = BaseDirectories::with_env("myapp", "", &*make_env(vec![
973             ("HOME", format!("{}/test_files/user", cwd)),
974             ("XDG_CACHE_HOME", format!("{}/test_files/user/cache", cwd)),
975         ])).unwrap();
976     assert_eq!(xd.get_cache_file("cache.db"),
977         PathBuf::from(&format!("{}/test_files/user/cache/myapp/cache.db", cwd)));
978     assert_eq!(xd.place_cache_file("cache.db").unwrap(),
979                PathBuf::from(&format!("{}/test_files/user/cache/myapp/cache.db", cwd)));
980 }
981 
982 #[test]
test_profile()983 fn test_profile() {
984     let cwd = env::current_dir().unwrap().to_string_lossy().into_owned();
985     let xd = BaseDirectories::with_env("myapp", "default_profile", &*make_env(vec![
986             ("HOME", format!("{}/test_files/user", cwd)),
987             ("XDG_CONFIG_HOME", format!("{}/test_files/user/config", cwd)),
988             ("XDG_CONFIG_DIRS", format!("{}/test_files/system1/config", cwd)),
989        ])).unwrap();
990     assert_eq!(xd.find_config_file("system1_config.file").unwrap(),
991                // Does *not* include default_profile
992                PathBuf::from(&format!("{}/test_files/system1/config/myapp/system1_config.file", cwd)));
993     assert_eq!(xd.find_config_file("user_config.file").unwrap(),
994                // Includes default_profile
995                PathBuf::from(&format!("{}/test_files/user/config/myapp/default_profile/user_config.file", cwd)));
996 }
997 
998 /// Ensure that entries in XDG_CONFIG_DIRS can be replaced with symlinks.
999 #[test]
test_symlinks()1000 fn test_symlinks() {
1001     let cwd = env::current_dir().unwrap().to_string_lossy().into_owned();
1002     let symlinks_dir = format!("{}/test_files/symlinks", cwd);
1003     let config_dir = format!("{}/config", symlinks_dir);
1004     let myapp_dir = format!("{}/myapp", config_dir);
1005 
1006     assert!(path_exists(&myapp_dir));
1007     assert!(path_exists(&config_dir));
1008     assert!(path_exists(&symlinks_dir));
1009 
1010     let xd = BaseDirectories::with_env(
1011         "myapp", "", &*make_env(vec![
1012             ("HOME", symlinks_dir),
1013             ("XDG_CONFIG_HOME", config_dir),
1014         ])
1015     ).unwrap();
1016     assert_eq!(xd.find_config_file("user_config.file").unwrap(),
1017                PathBuf::from(&format!("{}/user_config.file", myapp_dir)));
1018 }
1019