std/os/unix/xdg.rs
1//! XDG (X Desktop Group) related functionality for Unix platforms.
2//!
3//! The [XDG Base Directory Specification][basedir] defines a set of base
4//! directories, relative to which user-specific files should be looked for. The
5//! functions in this module provide those directory paths as configured by
6//! the environment.
7//!
8//! Note that the use of these functions is not enforced by the system, and as
9//! such, not all programs will necessarily respect all details of the XDG path
10//! environment. This is a set of guidelines, and each program is ultimately
11//! responsible for defining where and how it both reads and writes files.
12//!
13//! Use of XDG paths can be generally considered the conventional expectation
14//! on Linux-based systems. Other Unix-based systems may or may not play well
15//! with the XDG conventions.
16//!
17//! Directories returned by this module are not guaranteed to exist yet. If the
18//! directory does not exist, an application should attempt to create it with
19//! [permissions mode][super::fs::PermissionsExt::from_mode] `0o700`.
20//!
21//! [basedir]: https://specifications.freedesktop.org/basedir/latest/
22#![unstable(feature = "xdg_basedir", issue = "157515")]
23
24use crate::env::{home_dir, split_paths, var_os};
25use crate::ffi::{OsStr, OsString};
26use crate::path::PathBuf;
27
28fn xdg_home_dir() -> PathBuf {
29 // Note: home_dir can return `Some("")` in some cases. We assume that in
30 // this case the expected behavior is for `$HOME/path` to become `/path`,
31 // i.e. the home directory is effectively `/`.
32 match home_dir() {
33 None => panic!("an XDG environment should have a home directory"),
34 Some(home) if home.is_empty() => PathBuf::from("/"),
35 Some(home) => home,
36 }
37}
38
39fn xdg_dir(env: &str, fallback_home_subdir: &str) -> PathBuf {
40 var_os(env)
41 .filter(|s| !s.is_empty())
42 .map(PathBuf::from)
43 .unwrap_or_else(|| xdg_home_dir().join(fallback_home_subdir))
44}
45
46/// A base directory relative to which user-specific data files should be written.
47///
48/// An application `appid` would typically be expected to write its data files
49/// to `{data_home_dir}/{appid}/**/*`.
50pub fn data_home_dir() -> PathBuf {
51 xdg_dir("XDG_DATA_HOME", ".local/share")
52}
53
54/// A base directory relative to which user-specific configuration files should be written.
55///
56/// An application `appid` would typically be expected to write its configuration
57/// files to `{config_home_dir}/{appid}/**/*`.
58pub fn config_home_dir() -> PathBuf {
59 xdg_dir("XDG_CONFIG_HOME", ".config")
60}
61
62/// A base directory relative to which user-specific state data should be written.
63///
64/// An application `appid` would typically be expected to write its state data to
65/// `{state_home_dir}/{appid}/**/*`.
66///
67/// Common kinds of state data include actions history (such as logs, history,
68/// recently used files, etc.) and state of the application that can be reused
69/// after application restart (such as view, layout, open files, undo history,
70/// etc.).
71pub fn state_home_dir() -> PathBuf {
72 xdg_dir("XDG_STATE_HOME", ".local/state")
73}
74
75/// A base directory relative to which user-specific non-essential (cached) data should be written.
76///
77/// An application `appid` would typically be expected to write its cache data to
78/// `{cache_home_dir}/{appid}/**/*`.
79pub fn cache_home_dir() -> PathBuf {
80 xdg_dir("XDG_CACHE_HOME", ".cache")
81}
82
83/// An iterator that produces directory paths from XDG environment configuration.
84///
85/// The iterator element type is [`PathBuf`].
86///
87/// This structure is created by [`xdg::data_dirs`] and [`xdg::config_dirs`].
88/// See the documentation of those functions for more.
89///
90/// [`xdg::data_dirs`]: data_dirs
91/// [`xdg::config_dirs`]: config_dirs
92#[derive(Debug, Clone)]
93pub struct XdgDirsIter {
94 list: OsString,
95 off: usize,
96}
97
98impl XdgDirsIter {
99 fn new(env: &str, default: &str) -> Self {
100 let dirs = var_os(env).filter(|s| !s.is_empty()).unwrap_or_else(|| default.into());
101 Self { list: dirs, off: 0 }
102 }
103
104 fn remaining(&self) -> Option<&OsStr> {
105 self.list.as_encoded_bytes().get(self.off..).map(|bytes| {
106 // SAFETY: `self.off` is the index after a path separator (or the
107 // start of the string), so is a valid OsStr boundary.
108 unsafe { OsStr::from_encoded_bytes_unchecked(bytes) }
109 })
110 }
111}
112
113impl Iterator for XdgDirsIter {
114 type Item = PathBuf;
115
116 fn next(&mut self) -> Option<Self::Item> {
117 let rest = self.remaining()?;
118 let next = split_paths(rest).next()?;
119 let len = next.as_os_str().len();
120 self.off += len + 1; // Offset after this path and the separator after it.
121 Some(next)
122 }
123
124 fn size_hint(&self) -> (usize, Option<usize>) {
125 let Some(dirs) = self.remaining() else { return (0, Some(0)) };
126 split_paths(dirs).size_hint()
127 }
128}
129
130/// A set of preference ordered directories relative to which data files should be searched.
131///
132/// If an application defines a data file to be at `$XDG_DATA_DIRS/appid/file.name`, this means that:
133///
134/// - The initial data file should be installed to `{system_data_dir}/appid/file.name`.
135/// - A user-specific version of the data file may be created at
136/// <code>{[data_home_dir][]()}/appid/file.name</code>.
137/// - Lookups for the data file should search for `./appid/file.name` relative to
138/// `data_home_dir` and each directory in `data_dirs`, giving preference to
139/// files found relative to an earlier directory in the search order.
140///
141/// An application may choose to handle a file being located under multiple base
142/// directories however it sees fit, so long as it respects the search order.
143/// For example, it could say that only the first file found is used, or that
144/// data within the files is merged in some way.
145pub fn data_dirs() -> XdgDirsIter {
146 // NB: the spec uses trailing slashes only for this default, for some reason
147 XdgDirsIter::new("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/")
148}
149
150/// A set of preference ordered directories relative to which configuration files should be searched.
151///
152/// If an application defines a configuration file to be at `$XDG_CONFIG_DIRS/appid/file.name`, this means that:
153///
154/// - The initial configuration file should be installed to `{system_config_dir}/xdg/appid/file.name`.
155/// - A user-specific version of the configuration file may be created at
156/// <code>{[config_home_dir][]()}/appid/file.name</code>.
157/// - Lookups for the configuration file should search for `./appid/file.name`
158/// relative to `config_home_dir` and each directory in `config_dirs`, giving
159/// preference to files found relative to an earlier directory in the search order.
160///
161/// An application may choose to handle a file being located under multiple base
162/// directories however it sees fit, so long as it respects the search order.
163/// For example, it could say that only the first file found is used, or that
164/// data within the files is merged in some way.
165pub fn config_dirs() -> XdgDirsIter {
166 XdgDirsIter::new("XDG_CONFIG_DIRS", "/etc/xdg")
167}