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