1use std::collections::HashSet;
2use std::ffi::OsString;
3use std::os::unix::fs::PermissionsExt;
4use std::path::{Path, PathBuf};
5use std::{env, error, fmt, fs, io};
6
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9
10use self::ErrorKind::*;
11
12#[derive(Debug, Clone)]
82#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
83pub struct BaseDirectories {
84 shared_prefix: PathBuf,
85 user_prefix: PathBuf,
86 data_home: PathBuf,
87 config_home: PathBuf,
88 cache_home: PathBuf,
89 state_home: PathBuf,
90 data_dirs: Vec<PathBuf>,
91 config_dirs: Vec<PathBuf>,
92 runtime_dir: Option<PathBuf>,
93}
94
95pub struct Error {
96 kind: ErrorKind,
97}
98
99impl Error {
100 fn new(kind: ErrorKind) -> Error {
101 Error { kind }
102 }
103}
104
105impl fmt::Debug for Error {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 self.kind.fmt(f)
108 }
109}
110
111impl error::Error for Error {
112 fn description(&self) -> &str {
113 match self.kind {
114 HomeMissing => "$HOME must be set",
115 XdgRuntimeDirInaccessible(_, _) => {
116 "$XDG_RUNTIME_DIR must be accessible by the current user"
117 }
118 XdgRuntimeDirInsecure(_, _) => "$XDG_RUNTIME_DIR must be secure: have permissions 0700",
119 XdgRuntimeDirMissing => "$XDG_RUNTIME_DIR is not set",
120 }
121 }
122 fn cause(&self) -> Option<&dyn error::Error> {
123 match self.kind {
124 XdgRuntimeDirInaccessible(_, ref e) => Some(e),
125 _ => None,
126 }
127 }
128}
129
130impl fmt::Display for Error {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 match self.kind {
133 HomeMissing => write!(f, "$HOME must be set"),
134 XdgRuntimeDirInaccessible(ref dir, ref error) => {
135 write!(
136 f,
137 "$XDG_RUNTIME_DIR (`{}`) must be accessible \
138 by the current user (error: {})",
139 dir.display(),
140 error
141 )
142 }
143 XdgRuntimeDirInsecure(ref dir, permissions) => {
144 write!(
145 f,
146 "$XDG_RUNTIME_DIR (`{}`) must be secure: must have \
147 permissions 0o700, got {}",
148 dir.display(),
149 permissions
150 )
151 }
152 XdgRuntimeDirMissing => {
153 write!(f, "$XDG_RUNTIME_DIR must be set")
154 }
155 }
156 }
157}
158
159impl From<Error> for io::Error {
160 fn from(error: Error) -> io::Error {
161 match error.kind {
162 HomeMissing | XdgRuntimeDirMissing => io::Error::new(io::ErrorKind::NotFound, error),
163 _ => io::Error::new(io::ErrorKind::Other, error),
164 }
165 }
166}
167
168#[derive(Copy, Clone)]
169struct Permissions(u32);
170
171impl fmt::Debug for Permissions {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 let Permissions(p) = *self;
174 write!(f, "{:#05o}", p)
175 }
176}
177
178impl fmt::Display for Permissions {
179 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180 fmt::Debug::fmt(self, f)
181 }
182}
183
184#[derive(Debug)]
185enum ErrorKind {
186 HomeMissing,
187 XdgRuntimeDirInaccessible(PathBuf, io::Error),
188 XdgRuntimeDirInsecure(PathBuf, Permissions),
189 XdgRuntimeDirMissing,
190}
191
192impl BaseDirectories {
193 pub fn new() -> Result<BaseDirectories, Error> {
212 BaseDirectories::with_env("", "", &|name| env::var_os(name))
213 }
214
215 pub fn with_prefix<P: AsRef<Path>>(prefix: P) -> Result<BaseDirectories, Error> {
218 BaseDirectories::with_env(prefix, "", &|name| env::var_os(name))
219 }
220
221 pub fn with_profile<P1, P2>(prefix: P1, profile: P2) -> Result<BaseDirectories, Error>
240 where
241 P1: AsRef<Path>,
242 P2: AsRef<Path>,
243 {
244 BaseDirectories::with_env(prefix, profile, &|name| env::var_os(name))
245 }
246
247 fn with_env<P1, P2, T: ?Sized>(
248 prefix: P1,
249 profile: P2,
250 env_var: &T,
251 ) -> Result<BaseDirectories, Error>
252 where
253 P1: AsRef<Path>,
254 P2: AsRef<Path>,
255 T: Fn(&str) -> Option<OsString>,
256 {
257 BaseDirectories::with_env_impl(prefix.as_ref(), profile.as_ref(), env_var)
258 }
259
260 fn with_env_impl<T: ?Sized>(
261 prefix: &Path,
262 profile: &Path,
263 env_var: &T,
264 ) -> Result<BaseDirectories, Error>
265 where
266 T: Fn(&str) -> Option<OsString>,
267 {
268 fn abspath(path: OsString) -> Option<PathBuf> {
269 let path = PathBuf::from(path);
270 if path.is_absolute() {
271 Some(path)
272 } else {
273 None
274 }
275 }
276
277 fn abspaths(paths: OsString) -> Option<Vec<PathBuf>> {
278 let paths = env::split_paths(&paths)
279 .map(PathBuf::from)
280 .filter(|path| path.is_absolute())
281 .collect::<Vec<_>>();
282 if paths.is_empty() {
283 None
284 } else {
285 Some(paths)
286 }
287 }
288
289 #[allow(deprecated)]
292 let home = std::env::home_dir().ok_or(Error::new(HomeMissing))?;
293
294 let data_home = env_var("XDG_DATA_HOME")
295 .and_then(abspath)
296 .unwrap_or(home.join(".local/share"));
297 let config_home = env_var("XDG_CONFIG_HOME")
298 .and_then(abspath)
299 .unwrap_or(home.join(".config"));
300 let cache_home = env_var("XDG_CACHE_HOME")
301 .and_then(abspath)
302 .unwrap_or(home.join(".cache"));
303 let state_home = env_var("XDG_STATE_HOME")
304 .and_then(abspath)
305 .unwrap_or(home.join(".local/state"));
306 let data_dirs = env_var("XDG_DATA_DIRS").and_then(abspaths).unwrap_or(vec![
307 PathBuf::from("/usr/local/share"),
308 PathBuf::from("/usr/share"),
309 ]);
310 let config_dirs = env_var("XDG_CONFIG_DIRS")
311 .and_then(abspaths)
312 .unwrap_or(vec![PathBuf::from("/etc/xdg")]);
313 let runtime_dir = env_var("XDG_RUNTIME_DIR").and_then(abspath); let prefix = PathBuf::from(prefix);
316 Ok(BaseDirectories {
317 user_prefix: prefix.join(profile),
318 shared_prefix: prefix,
319 data_home,
320 config_home,
321 cache_home,
322 state_home,
323 data_dirs,
324 config_dirs,
325 runtime_dir,
326 })
327 }
328
329 pub fn get_runtime_directory(&self) -> Result<&PathBuf, Error> {
331 if let Some(ref runtime_dir) = self.runtime_dir {
332 fs::read_dir(runtime_dir)
335 .map_err(|e| Error::new(XdgRuntimeDirInaccessible(runtime_dir.clone(), e)))?;
336 let permissions = fs::metadata(runtime_dir)
337 .map_err(|e| Error::new(XdgRuntimeDirInaccessible(runtime_dir.clone(), e)))?
338 .permissions()
339 .mode();
340 if permissions & 0o077 != 0 {
341 Err(Error::new(XdgRuntimeDirInsecure(
342 runtime_dir.clone(),
343 Permissions(permissions),
344 )))
345 } else {
346 Ok(runtime_dir)
347 }
348 } else {
349 Err(Error::new(XdgRuntimeDirMissing))
350 }
351 }
352
353 pub fn has_runtime_directory(&self) -> bool {
355 self.get_runtime_directory().is_ok()
356 }
357
358 pub fn get_config_file<P: AsRef<Path>>(&self, path: P) -> PathBuf {
361 self.config_home.join(self.user_prefix.join(path))
362 }
363
364 pub fn get_data_file<P: AsRef<Path>>(&self, path: P) -> PathBuf {
367 self.data_home.join(self.user_prefix.join(path))
368 }
369
370 pub fn get_cache_file<P: AsRef<Path>>(&self, path: P) -> PathBuf {
373 self.cache_home.join(self.user_prefix.join(path))
374 }
375
376 pub fn get_state_file<P: AsRef<Path>>(&self, path: P) -> PathBuf {
379 self.state_home.join(self.user_prefix.join(path))
380 }
381
382 pub fn get_runtime_file<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
386 let runtime_dir = self.get_runtime_directory()?;
387 Ok(runtime_dir.join(self.user_prefix.join(path)))
388 }
389
390 pub fn place_config_file<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
395 write_file(&self.config_home, &self.user_prefix.join(path))
396 }
397
398 pub fn place_data_file<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
401 write_file(&self.data_home, &self.user_prefix.join(path))
402 }
403
404 pub fn place_cache_file<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
407 write_file(&self.cache_home, &self.user_prefix.join(path))
408 }
409
410 pub fn place_state_file<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
413 write_file(&self.state_home, &self.user_prefix.join(path))
414 }
415
416 pub fn place_runtime_file<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
420 write_file(self.get_runtime_directory()?, &self.user_prefix.join(path))
421 }
422
423 pub fn find_config_file<P: AsRef<Path>>(&self, path: P) -> Option<PathBuf> {
427 read_file(
428 &self.config_home,
429 &self.config_dirs,
430 &self.user_prefix,
431 &self.shared_prefix,
432 path.as_ref(),
433 )
434 }
435
436 pub fn find_config_files<P: AsRef<Path>>(&self, path: P) -> FileFindIterator {
441 FileFindIterator::new(
442 &self.config_home,
443 &self.config_dirs,
444 &self.user_prefix,
445 &self.shared_prefix,
446 path.as_ref(),
447 )
448 }
449
450 pub fn find_data_file<P: AsRef<Path>>(&self, path: P) -> Option<PathBuf> {
454 read_file(
455 &self.data_home,
456 &self.data_dirs,
457 &self.user_prefix,
458 &self.shared_prefix,
459 path.as_ref(),
460 )
461 }
462
463 pub fn find_data_files<P: AsRef<Path>>(&self, path: P) -> FileFindIterator {
468 FileFindIterator::new(
469 &self.data_home,
470 &self.data_dirs,
471 &self.user_prefix,
472 &self.shared_prefix,
473 path.as_ref(),
474 )
475 }
476
477 pub fn find_cache_file<P: AsRef<Path>>(&self, path: P) -> Option<PathBuf> {
480 read_file(
481 &self.cache_home,
482 &Vec::new(),
483 &self.user_prefix,
484 &self.shared_prefix,
485 path.as_ref(),
486 )
487 }
488
489 pub fn find_state_file<P: AsRef<Path>>(&self, path: P) -> Option<PathBuf> {
492 read_file(
493 &self.state_home,
494 &Vec::new(),
495 &self.user_prefix,
496 &self.shared_prefix,
497 path.as_ref(),
498 )
499 }
500
501 pub fn find_runtime_file<P: AsRef<Path>>(&self, path: P) -> Option<PathBuf> {
505 if let Ok(runtime_dir) = self.get_runtime_directory() {
506 read_file(
507 runtime_dir,
508 &Vec::new(),
509 &self.user_prefix,
510 &self.shared_prefix,
511 path.as_ref(),
512 )
513 } else {
514 None
515 }
516 }
517
518 pub fn create_config_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
523 create_directory(&self.config_home, &self.user_prefix.join(path))
524 }
525
526 pub fn create_data_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
529 create_directory(&self.data_home, &self.user_prefix.join(path))
530 }
531
532 pub fn create_cache_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
535 create_directory(&self.cache_home, &self.user_prefix.join(path))
536 }
537
538 pub fn create_state_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
541 create_directory(&self.state_home, &self.user_prefix.join(path))
542 }
543
544 pub fn create_runtime_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
548 create_directory(self.get_runtime_directory()?, &self.user_prefix.join(path))
549 }
550
551 pub fn list_config_files<P: AsRef<Path>>(&self, path: P) -> Vec<PathBuf> {
555 list_files(
556 &self.config_home,
557 &self.config_dirs,
558 &self.user_prefix,
559 &self.shared_prefix,
560 path.as_ref(),
561 )
562 }
563
564 pub fn list_config_files_once<P: AsRef<Path>>(&self, path: P) -> Vec<PathBuf> {
567 list_files_once(
568 &self.config_home,
569 &self.config_dirs,
570 &self.user_prefix,
571 &self.shared_prefix,
572 path.as_ref(),
573 )
574 }
575
576 pub fn list_data_files<P: AsRef<Path>>(&self, path: P) -> Vec<PathBuf> {
580 list_files(
581 &self.data_home,
582 &self.data_dirs,
583 &self.user_prefix,
584 &self.shared_prefix,
585 path.as_ref(),
586 )
587 }
588
589 pub fn list_data_files_once<P: AsRef<Path>>(&self, path: P) -> Vec<PathBuf> {
592 list_files_once(
593 &self.data_home,
594 &self.data_dirs,
595 &self.user_prefix,
596 &self.shared_prefix,
597 path.as_ref(),
598 )
599 }
600
601 pub fn list_cache_files<P: AsRef<Path>>(&self, path: P) -> Vec<PathBuf> {
604 list_files(
605 &self.cache_home,
606 &Vec::new(),
607 &self.user_prefix,
608 &self.shared_prefix,
609 path.as_ref(),
610 )
611 }
612
613 pub fn list_state_files<P: AsRef<Path>>(&self, path: P) -> Vec<PathBuf> {
616 list_files(
617 &self.state_home,
618 &Vec::new(),
619 &self.user_prefix,
620 &self.shared_prefix,
621 path.as_ref(),
622 )
623 }
624
625 pub fn list_runtime_files<P: AsRef<Path>>(&self, path: P) -> Vec<PathBuf> {
629 if let Ok(runtime_dir) = self.get_runtime_directory() {
630 list_files(
631 runtime_dir,
632 &Vec::new(),
633 &self.user_prefix,
634 &self.shared_prefix,
635 path.as_ref(),
636 )
637 } else {
638 Vec::new()
639 }
640 }
641
642 pub fn get_data_home(&self) -> PathBuf {
644 self.data_home.join(&self.user_prefix)
645 }
646
647 pub fn get_config_home(&self) -> PathBuf {
650 self.config_home.join(&self.user_prefix)
651 }
652
653 pub fn get_cache_home(&self) -> PathBuf {
656 self.cache_home.join(&self.user_prefix)
657 }
658
659 pub fn get_state_home(&self) -> PathBuf {
662 self.state_home.join(&self.user_prefix)
663 }
664
665 pub fn get_data_dirs(&self) -> Vec<PathBuf> {
669 self.data_dirs
670 .iter()
671 .map(|p| p.join(&self.shared_prefix))
672 .collect()
673 }
674
675 pub fn get_config_dirs(&self) -> Vec<PathBuf> {
678 self.config_dirs
679 .iter()
680 .map(|p| p.join(&self.shared_prefix))
681 .collect()
682 }
683}
684
685fn write_file(home: &Path, path: &Path) -> io::Result<PathBuf> {
686 match path.parent() {
687 Some(parent) => fs::create_dir_all(home.join(parent))?,
688 None => fs::create_dir_all(home)?,
689 }
690 Ok(home.join(path))
691}
692
693fn create_directory(home: &Path, path: &Path) -> io::Result<PathBuf> {
694 let full_path = home.join(path);
695 fs::create_dir_all(&full_path)?;
696 Ok(full_path)
697}
698
699fn path_exists(path: &Path) -> bool {
700 fs::metadata(path).is_ok()
701}
702
703fn read_file(
704 home: &Path,
705 dirs: &[PathBuf],
706 user_prefix: &Path,
707 shared_prefix: &Path,
708 path: &Path,
709) -> Option<PathBuf> {
710 let full_path = home.join(user_prefix).join(path);
711 if path_exists(&full_path) {
712 return Some(full_path);
713 }
714 for dir in dirs.iter() {
715 let full_path = dir.join(shared_prefix).join(path);
716 if path_exists(&full_path) {
717 return Some(full_path);
718 }
719 }
720 None
721}
722
723use std::vec::IntoIter as VecIter;
724pub struct FileFindIterator {
725 search_dirs: VecIter<PathBuf>,
726 relpath: PathBuf,
727}
728
729impl FileFindIterator {
730 fn new(
731 home: &Path,
732 dirs: &[PathBuf],
733 user_prefix: &Path,
734 shared_prefix: &Path,
735 path: &Path,
736 ) -> FileFindIterator {
737 let mut search_dirs = Vec::new();
738 for dir in dirs.iter().rev() {
739 search_dirs.push(dir.join(shared_prefix));
740 }
741 search_dirs.push(home.join(user_prefix));
742 FileFindIterator {
743 search_dirs: search_dirs.into_iter(),
744 relpath: path.to_path_buf(),
745 }
746 }
747}
748
749impl Iterator for FileFindIterator {
750 type Item = PathBuf;
751
752 fn next(&mut self) -> Option<Self::Item> {
753 loop {
754 let dir = self.search_dirs.next()?;
755 let candidate = dir.join(&self.relpath);
756 if path_exists(&candidate) {
757 return Some(candidate);
758 }
759 }
760 }
761}
762
763impl DoubleEndedIterator for FileFindIterator {
764 fn next_back(&mut self) -> Option<Self::Item> {
765 loop {
766 let dir = self.search_dirs.next_back()?;
767 let candidate = dir.join(&self.relpath);
768 if path_exists(&candidate) {
769 return Some(candidate);
770 }
771 }
772 }
773}
774
775fn list_files(
776 home: &Path,
777 dirs: &[PathBuf],
778 user_prefix: &Path,
779 shared_prefix: &Path,
780 path: &Path,
781) -> Vec<PathBuf> {
782 fn read_dir(dir: &Path, into: &mut Vec<PathBuf>) {
783 if let Ok(entries) = fs::read_dir(dir) {
784 into.extend(
785 entries
786 .filter_map(|entry| entry.ok())
787 .map(|entry| entry.path()),
788 )
789 }
790 }
791 let mut files = Vec::new();
792 read_dir(&home.join(user_prefix).join(path), &mut files);
793 for dir in dirs {
794 read_dir(&dir.join(shared_prefix).join(path), &mut files);
795 }
796 files
797}
798
799fn list_files_once(
800 home: &Path,
801 dirs: &[PathBuf],
802 user_prefix: &Path,
803 shared_prefix: &Path,
804 path: &Path,
805) -> Vec<PathBuf> {
806 let mut seen = HashSet::new();
807 list_files(home, dirs, user_prefix, shared_prefix, path)
808 .into_iter()
809 .filter(|path| match path.file_name() {
810 None => false,
811 Some(filename) => {
812 if seen.contains(filename) {
813 false
814 } else {
815 seen.insert(filename.to_owned());
816 true
817 }
818 }
819 })
820 .collect::<Vec<_>>()
821}
822
823#[cfg(test)]
824mod test {
825 use super::*;
826
827 const TARGET_TMPDIR: Option<&'static str> = option_env!("CARGO_TARGET_TMPDIR");
828 const TARGET_DIR: Option<&'static str> = option_env!("CARGO_TARGET_DIR");
829
830 fn get_test_dir() -> PathBuf {
831 match TARGET_TMPDIR {
832 Some(dir) => PathBuf::from(dir),
833 None => match TARGET_DIR {
834 Some(dir) => PathBuf::from(dir),
835 None => env::current_dir().unwrap(),
836 },
837 }
838 }
839
840 fn path_exists<P: AsRef<Path> + ?Sized>(path: &P) -> bool {
841 super::path_exists(path.as_ref())
842 }
843
844 fn path_is_dir<P: ?Sized + AsRef<Path>>(path: &P) -> bool {
845 fn inner(path: &Path) -> bool {
846 fs::metadata(path).map(|m| m.is_dir()).unwrap_or(false)
847 }
848 inner(path.as_ref())
849 }
850
851 fn make_absolute<P: AsRef<Path>>(path: P) -> PathBuf {
852 get_test_dir().join(path.as_ref())
853 }
854
855 fn iter_after<A, I, J>(mut iter: I, mut prefix: J) -> Option<I>
856 where
857 I: Iterator<Item = A> + Clone,
858 J: Iterator<Item = A>,
859 A: PartialEq,
860 {
861 loop {
862 let mut iter_next = iter.clone();
863 match (iter_next.next(), prefix.next()) {
864 (Some(x), Some(y)) => {
865 if x != y {
866 return None;
867 }
868 }
869 (Some(_), None) => return Some(iter),
870 (None, None) => return Some(iter),
871 (None, Some(_)) => return None,
872 }
873 iter = iter_next;
874 }
875 }
876
877 fn make_relative<P: AsRef<Path>>(path: P, reference: P) -> PathBuf {
878 iter_after(path.as_ref().components(), reference.as_ref().components())
879 .unwrap()
880 .as_path()
881 .to_owned()
882 }
883
884 fn make_env(vars: Vec<(&'static str, String)>) -> Box<dyn Fn(&str) -> Option<OsString>> {
885 return Box::new(move |name| {
886 for &(key, ref value) in vars.iter() {
887 if key == name {
888 return Some(OsString::from(value));
889 }
890 }
891 None
892 });
893 }
894
895 #[test]
896 fn test_files_exists() {
897 assert!(path_exists("test_files"));
898 assert!(
899 fs::metadata("test_files/runtime-bad")
900 .unwrap()
901 .permissions()
902 .mode()
903 & 0o077
904 != 0
905 );
906 }
907
908 #[test]
909 fn test_bad_environment() {
910 let xd = BaseDirectories::with_env(
911 "",
912 "",
913 &*make_env(vec![
914 ("HOME", "test_files/user".to_string()),
915 ("XDG_DATA_HOME", "test_files/user/data".to_string()),
916 ("XDG_CONFIG_HOME", "test_files/user/config".to_string()),
917 ("XDG_CACHE_HOME", "test_files/user/cache".to_string()),
918 ("XDG_DATA_DIRS", "test_files/user/data".to_string()),
919 ("XDG_CONFIG_DIRS", "test_files/user/config".to_string()),
920 ("XDG_RUNTIME_DIR", "test_files/runtime-bad".to_string()),
921 ]),
922 )
923 .unwrap();
924 assert_eq!(xd.find_data_file("everywhere"), None);
925 assert_eq!(xd.find_config_file("everywhere"), None);
926 assert_eq!(xd.find_cache_file("everywhere"), None);
927 }
928
929 #[test]
930 fn test_good_environment() {
931 let cwd = env::current_dir().unwrap().to_string_lossy().into_owned();
932 let xd = BaseDirectories::with_env("", "", &*make_env(vec![
933 ("HOME", format!("{}/test_files/user", cwd)),
934 ("XDG_DATA_HOME", format!("{}/test_files/user/data", cwd)),
935 ("XDG_CONFIG_HOME", format!("{}/test_files/user/config", cwd)),
936 ("XDG_CACHE_HOME", format!("{}/test_files/user/cache", cwd)),
937 ("XDG_DATA_DIRS", format!("{}/test_files/system0/data:{}/test_files/system1/data:{}/test_files/system2/data:{}/test_files/system3/data", cwd, cwd, cwd, cwd)),
938 ("XDG_CONFIG_DIRS", format!("{}/test_files/system0/config:{}/test_files/system1/config:{}/test_files/system2/config:{}/test_files/system3/config", cwd, cwd, cwd, cwd)),
939 ])).unwrap();
941 assert!(xd.find_data_file("everywhere") != None);
942 assert!(xd.find_config_file("everywhere") != None);
943 assert!(xd.find_cache_file("everywhere") != None);
944
945 let mut config_files = xd.find_config_files("everywhere");
946 assert_eq!(
947 config_files.next(),
948 Some(PathBuf::from(format!(
949 "{}/test_files/system2/config/everywhere",
950 cwd
951 )))
952 );
953 assert_eq!(
954 config_files.next(),
955 Some(PathBuf::from(format!(
956 "{}/test_files/system1/config/everywhere",
957 cwd
958 )))
959 );
960 assert_eq!(
961 config_files.next(),
962 Some(PathBuf::from(format!(
963 "{}/test_files/user/config/everywhere",
964 cwd
965 )))
966 );
967 assert_eq!(config_files.next(), None);
968
969 let mut data_files = xd.find_data_files("everywhere");
970 assert_eq!(
971 data_files.next(),
972 Some(PathBuf::from(format!(
973 "{}/test_files/system2/data/everywhere",
974 cwd
975 )))
976 );
977 assert_eq!(
978 data_files.next(),
979 Some(PathBuf::from(format!(
980 "{}/test_files/system1/data/everywhere",
981 cwd
982 )))
983 );
984 assert_eq!(
985 data_files.next(),
986 Some(PathBuf::from(format!(
987 "{}/test_files/user/data/everywhere",
988 cwd
989 )))
990 );
991 assert_eq!(data_files.next(), None);
992 }
993
994 #[test]
995 fn test_runtime_bad() {
996 let cwd = env::current_dir().unwrap().to_string_lossy().into_owned();
997 let xd = BaseDirectories::with_env(
998 "",
999 "",
1000 &*make_env(vec![
1001 ("HOME", format!("{}/test_files/user", cwd)),
1002 ("XDG_RUNTIME_DIR", format!("{}/test_files/runtime-bad", cwd)),
1003 ]),
1004 )
1005 .unwrap();
1006 assert!(xd.has_runtime_directory() == false);
1007 }
1008
1009 #[test]
1010 fn test_runtime_good() {
1011 use std::fs::File;
1012
1013 let test_runtime_dir = make_absolute(&"test_files/runtime-good");
1014 fs::create_dir_all(&test_runtime_dir).unwrap();
1015
1016 let mut perms = fs::metadata(&test_runtime_dir).unwrap().permissions();
1017 perms.set_mode(0o700);
1018 fs::set_permissions(&test_runtime_dir, perms).unwrap();
1019
1020 let test_dir = get_test_dir().to_string_lossy().into_owned();
1021 let xd = BaseDirectories::with_env(
1022 "",
1023 "",
1024 &*make_env(vec![
1025 ("HOME", format!("{}/test_files/user", test_dir)),
1026 (
1027 "XDG_RUNTIME_DIR",
1028 format!("{}/test_files/runtime-good", test_dir),
1029 ),
1030 ]),
1031 )
1032 .unwrap();
1033
1034 xd.create_runtime_directory("foo").unwrap();
1035 assert!(path_is_dir(&format!(
1036 "{}/test_files/runtime-good/foo",
1037 test_dir
1038 )));
1039 let w = xd.place_runtime_file("bar/baz").unwrap();
1040 assert!(path_is_dir(&format!(
1041 "{}/test_files/runtime-good/bar",
1042 test_dir
1043 )));
1044 assert!(!path_exists(&format!(
1045 "{}/test_files/runtime-good/bar/baz",
1046 test_dir
1047 )));
1048 File::create(&w).unwrap();
1049 assert!(path_exists(&format!(
1050 "{}/test_files/runtime-good/bar/baz",
1051 test_dir
1052 )));
1053 assert!(xd.find_runtime_file("bar/baz") == Some(w.clone()));
1054 File::open(&w).unwrap();
1055 fs::remove_file(&w).unwrap();
1056 let root = xd.list_runtime_files(".");
1057 let mut root = root
1058 .into_iter()
1059 .map(|p| make_relative(&p, &get_test_dir()))
1060 .collect::<Vec<_>>();
1061 root.sort();
1062 assert_eq!(
1063 root,
1064 vec![
1065 PathBuf::from("test_files/runtime-good/bar"),
1066 PathBuf::from("test_files/runtime-good/foo")
1067 ]
1068 );
1069 assert!(xd.list_runtime_files("bar").is_empty());
1070 assert!(xd.find_runtime_file("foo/qux").is_none());
1071 assert!(xd.find_runtime_file("qux/foo").is_none());
1072 assert!(!path_exists(&format!(
1073 "{}/test_files/runtime-good/qux",
1074 test_dir
1075 )));
1076 }
1077
1078 #[test]
1079 fn test_lists() {
1080 let cwd = env::current_dir().unwrap().to_string_lossy().into_owned();
1081 let xd = BaseDirectories::with_env("", "", &*make_env(vec![
1082 ("HOME", format!("{}/test_files/user", cwd)),
1083 ("XDG_DATA_HOME", format!("{}/test_files/user/data", cwd)),
1084 ("XDG_CONFIG_HOME", format!("{}/test_files/user/config", cwd)),
1085 ("XDG_CACHE_HOME", format!("{}/test_files/user/cache", cwd)),
1086 ("XDG_DATA_DIRS", format!("{}/test_files/system0/data:{}/test_files/system1/data:{}/test_files/system2/data:{}/test_files/system3/data", cwd, cwd, cwd, cwd)),
1087 ("XDG_CONFIG_DIRS", format!("{}/test_files/system0/config:{}/test_files/system1/config:{}/test_files/system2/config:{}/test_files/system3/config", cwd, cwd, cwd, cwd)),
1088 ])).unwrap();
1089
1090 let files = xd.list_config_files(".");
1091 let mut files = files
1092 .into_iter()
1093 .map(|p| make_relative(&p, &env::current_dir().unwrap()))
1094 .collect::<Vec<_>>();
1095 files.sort();
1096 assert_eq!(
1097 files,
1098 [
1099 "test_files/system1/config/both_system_config.file",
1100 "test_files/system1/config/everywhere",
1101 "test_files/system1/config/myapp",
1102 "test_files/system1/config/system1_config.file",
1103 "test_files/system2/config/both_system_config.file",
1104 "test_files/system2/config/everywhere",
1105 "test_files/system2/config/system2_config.file",
1106 "test_files/user/config/everywhere",
1107 "test_files/user/config/myapp",
1108 "test_files/user/config/user_config.file",
1109 ]
1110 .iter()
1111 .map(PathBuf::from)
1112 .collect::<Vec<_>>()
1113 );
1114
1115 let files = xd.list_config_files_once(".");
1116 let mut files = files
1117 .into_iter()
1118 .map(|p| make_relative(&p, &env::current_dir().unwrap()))
1119 .collect::<Vec<_>>();
1120 files.sort();
1121 assert_eq!(
1122 files,
1123 [
1124 "test_files/system1/config/both_system_config.file",
1125 "test_files/system1/config/system1_config.file",
1126 "test_files/system2/config/system2_config.file",
1127 "test_files/user/config/everywhere",
1128 "test_files/user/config/myapp",
1129 "test_files/user/config/user_config.file",
1130 ]
1131 .iter()
1132 .map(PathBuf::from)
1133 .collect::<Vec<_>>()
1134 );
1135 }
1136
1137 #[test]
1138 fn test_get_file() {
1139 let test_dir = get_test_dir().to_string_lossy().into_owned();
1140 let xd = BaseDirectories::with_env(
1141 "",
1142 "",
1143 &*make_env(vec![
1144 ("HOME", format!("{}/test_files/user", test_dir)),
1145 (
1146 "XDG_DATA_HOME",
1147 format!("{}/test_files/user/data", test_dir),
1148 ),
1149 (
1150 "XDG_CONFIG_HOME",
1151 format!("{}/test_files/user/config", test_dir),
1152 ),
1153 (
1154 "XDG_CACHE_HOME",
1155 format!("{}/test_files/user/cache", test_dir),
1156 ),
1157 (
1158 "XDG_RUNTIME_DIR",
1159 format!("{}/test_files/user/runtime", test_dir),
1160 ),
1161 ]),
1162 )
1163 .unwrap();
1164
1165 let path = format!("{}/test_files/user/runtime/", test_dir);
1166 fs::create_dir_all(&path).unwrap();
1167 let metadata = fs::metadata(&path).expect("Could not read metadata for runtime directory");
1168 let mut perms = metadata.permissions();
1169 perms.set_mode(0o700);
1170 fs::set_permissions(&path, perms).expect("Could not set permissions for runtime directory");
1171
1172 let file = xd.get_config_file("myapp/user_config.file");
1173 assert_eq!(
1174 file,
1175 PathBuf::from(&format!(
1176 "{}/test_files/user/config/myapp/user_config.file",
1177 test_dir
1178 ))
1179 );
1180
1181 let file = xd.get_data_file("user_data.file");
1182 assert_eq!(
1183 file,
1184 PathBuf::from(&format!("{}/test_files/user/data/user_data.file", test_dir))
1185 );
1186
1187 let file = xd.get_cache_file("user_cache.file");
1188 assert_eq!(
1189 file,
1190 PathBuf::from(&format!(
1191 "{}/test_files/user/cache/user_cache.file",
1192 test_dir
1193 ))
1194 );
1195
1196 let file = xd.get_runtime_file("user_runtime.file").unwrap();
1197 assert_eq!(
1198 file,
1199 PathBuf::from(&format!(
1200 "{}/test_files/user/runtime/user_runtime.file",
1201 test_dir
1202 ))
1203 );
1204 }
1205
1206 #[test]
1207 fn test_prefix() {
1208 let cwd = env::current_dir().unwrap().to_string_lossy().into_owned();
1209 let xd = BaseDirectories::with_env(
1210 "myapp",
1211 "",
1212 &*make_env(vec![
1213 ("HOME", format!("{}/test_files/user", cwd)),
1214 ("XDG_CACHE_HOME", format!("{}/test_files/user/cache", cwd)),
1215 ]),
1216 )
1217 .unwrap();
1218 assert_eq!(
1219 xd.get_cache_file("cache.db"),
1220 PathBuf::from(&format!("{}/test_files/user/cache/myapp/cache.db", cwd))
1221 );
1222 assert_eq!(
1223 xd.place_cache_file("cache.db").unwrap(),
1224 PathBuf::from(&format!("{}/test_files/user/cache/myapp/cache.db", cwd))
1225 );
1226 }
1227
1228 #[test]
1229 fn test_profile() {
1230 let cwd = env::current_dir().unwrap().to_string_lossy().into_owned();
1231 let xd = BaseDirectories::with_env(
1232 "myapp",
1233 "default_profile",
1234 &*make_env(vec![
1235 ("HOME", format!("{}/test_files/user", cwd)),
1236 ("XDG_CONFIG_HOME", format!("{}/test_files/user/config", cwd)),
1237 (
1238 "XDG_CONFIG_DIRS",
1239 format!("{}/test_files/system1/config", cwd),
1240 ),
1241 ]),
1242 )
1243 .unwrap();
1244 assert_eq!(
1245 xd.find_config_file("system1_config.file").unwrap(),
1246 PathBuf::from(&format!(
1248 "{}/test_files/system1/config/myapp/system1_config.file",
1249 cwd
1250 ))
1251 );
1252 assert_eq!(
1253 xd.find_config_file("user_config.file").unwrap(),
1254 PathBuf::from(&format!(
1256 "{}/test_files/user/config/myapp/default_profile/user_config.file",
1257 cwd
1258 ))
1259 );
1260 }
1261
1262 #[test]
1264 fn test_symlinks() {
1265 let cwd = env::current_dir().unwrap().to_string_lossy().into_owned();
1266 let symlinks_dir = format!("{}/test_files/symlinks", cwd);
1267 let config_dir = format!("{}/config", symlinks_dir);
1268 let myapp_dir = format!("{}/myapp", config_dir);
1269
1270 assert!(path_exists(&myapp_dir));
1271 assert!(path_exists(&config_dir));
1272 assert!(path_exists(&symlinks_dir));
1273
1274 let xd = BaseDirectories::with_env(
1275 "myapp",
1276 "",
1277 &*make_env(vec![
1278 ("HOME", symlinks_dir),
1279 ("XDG_CONFIG_HOME", config_dir),
1280 ]),
1281 )
1282 .unwrap();
1283 assert_eq!(
1284 xd.find_config_file("user_config.file").unwrap(),
1285 PathBuf::from(&format!("{}/user_config.file", myapp_dir))
1286 );
1287 }
1288}