systemd_directories/
lib.rs

1//! The _systemd-directories_ crate is a tiny library to retrieve systemd directories following
2//! [systemd.exec(5)](https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#RuntimeDirectory=).
3//!
4//! The library can be used in two ways:
5//! - As standalone functions to retrieve the directories.
6//! - As a struct to snapshot the environment at the time of creating [`SystemdDirs`]
7//!
8//! # Examples
9//! ## Standalone functions
10//! ```
11//! use systemd_directories;
12//! let runtime_dir = systemd_directories::runtime_dir();
13//! ```
14//!
15//! ## [`SystemdDirs`] Struct
16//! ```
17//! use systemd_directories::SystemdDirs;
18//! let dirs = SystemdDirs::new();
19//! let runtime_dir = dirs.runtime_dir();
20//! ```
21
22#![deny(missing_docs)]
23#![forbid(unsafe_code)]
24#![warn(clippy::missing_docs_in_private_items)]
25
26use std::env;
27use std::path::{Path, PathBuf};
28
29/// A struct to hold colon-separated paths.
30struct ColonSeparatedPaths {
31    /// The colon-separated paths.
32    paths: String,
33}
34
35impl ColonSeparatedPaths {
36    /// Returns a new [`ColonSeparatedPaths`] struct with the given paths.
37    fn new(paths: String) -> Self {
38        Self { paths }
39    }
40
41    /// Returns a new [`ColonSeparatedPaths`] struct with the environment variable `env_key`.
42    fn from_env_key(env_key: &str) -> Self {
43        Self::new(env::var(env_key).unwrap_or_default())
44    }
45
46    /// Returns an iterator over the paths.
47    fn iter(&self) -> impl Iterator<Item = &Path> {
48        self.paths
49            .split(':')
50            .map(Path::new)
51            .filter(|p| !p.as_os_str().is_empty())
52    }
53}
54
55/// Returns all runtime directories as defined by `RuntimeDirectory` in the unit file.
56///
57/// If the environment variable `RUNTIME_DIRECTORY` is not set, it returns an empty vector.
58/// If it is set, it returns all paths in the colon-separated list.
59/// To get just the first path, use [`runtime_dir`].
60///
61/// # Examples
62/// ```
63/// let runtime_dirs = systemd_directories::runtime_dirs();
64/// ```
65pub fn runtime_dirs() -> Vec<PathBuf> {
66    ColonSeparatedPaths::from_env_key("RUNTIME_DIRECTORY")
67        .iter()
68        .map(PathBuf::from)
69        .collect()
70}
71
72/// Returns the first runtime directory as defined by `RuntimeDirectory` in the unit file.
73///
74/// If the environment variable `RUNTIME_DIRECTORY` is not set, it returns [`None`].
75/// If it is set, it returns the first path in the colon-separated list.
76/// To get all paths, use [`runtime_dirs`].
77///
78/// # Examples
79/// ```
80/// # use std::env;
81/// # env::set_var("RUNTIME_DIRECTORY", "/run/foo");
82/// let runtime_dir = systemd_directories::runtime_dir().unwrap_or_default();
83/// ```
84///
85/// The function returns [`Option<PathBuf>`] which can be used in `if let` statements to handle the case where the environment variable is not set.
86///
87/// ```
88/// if let Some(runtime_dir) = systemd_directories::runtime_dir() {
89///     // --snip--
90/// }
91/// ```
92pub fn runtime_dir() -> Option<PathBuf> {
93    ColonSeparatedPaths::from_env_key("RUNTIME_DIRECTORY")
94        .iter()
95        .next()
96        .map(PathBuf::from)
97}
98
99/// Returns all state directories as defined by `StateDirectory` in the unit file.
100///
101/// If the environment variable `STATE_DIRECTORY` is not set, it returns an empty vector.
102/// If it is set, it returns all paths in the colon-separated list.
103/// To get just the first path, use [`state_dir`].
104///
105/// # Examples
106/// ```
107/// let state_dirs = systemd_directories::state_dirs();
108/// ```
109pub fn state_dirs() -> Vec<PathBuf> {
110    ColonSeparatedPaths::from_env_key("STATE_DIRECTORY")
111        .iter()
112        .map(PathBuf::from)
113        .collect()
114}
115
116/// Returns the first state directory as defined by `StateDirectory` in the unit file.
117///
118/// If the environment variable `STATE_DIRECTORY` is not set, it returns [`None`].
119/// If it is set, it returns the first path in the colon-separated list.
120/// To get all paths, use [`state_dirs`].
121///
122/// # Examples
123/// ```
124/// # use std::env;
125/// # env::set_var("STATE_DIRECTORY", "/var/lib/foo");
126/// let state_dir = systemd_directories::state_dir().unwrap_or_default();
127/// ```
128///
129/// The function returns [`Option<PathBuf>`] which can be used in `if let` statements to handle the case where the environment variable is not set.
130///
131/// ```
132/// if let Some(state_dir) = systemd_directories::state_dir() {
133///     // --snip--
134/// }
135/// ```
136pub fn state_dir() -> Option<PathBuf> {
137    ColonSeparatedPaths::from_env_key("STATE_DIRECTORY")
138        .iter()
139        .next()
140        .map(PathBuf::from)
141}
142
143/// Returns all cache directories as defined by `CacheDirectory` in the unit file.
144///
145/// If the environment variable `CACHE_DIRECTORY` is not set, it returns an empty vector.
146/// If it is set, it returns all paths in the colon-separated list.
147/// To get just the first path, use [`cache_dir`].
148///
149/// # Examples
150/// ```
151/// let cache_dirs = systemd_directories::cache_dirs();
152/// ```
153pub fn cache_dirs() -> Vec<PathBuf> {
154    ColonSeparatedPaths::from_env_key("CACHE_DIRECTORY")
155        .iter()
156        .map(PathBuf::from)
157        .collect()
158}
159
160/// Returns the first cache directory as defined by `CacheDirectory` in the unit file.
161///
162/// If the environment variable `CACHE_DIRECTORY` is not set, it returns [`None`].
163/// If it is set, it returns the first path in the colon-separated list.
164/// To get all paths, use [`cache_dirs`].
165///
166/// # Examples
167/// ```
168/// # use std::env;
169/// # env::set_var("CACHE_DIRECTORY", "/var/cache/foo");
170/// let cache_dir = systemd_directories::cache_dir().unwrap_or_default();
171/// ```
172///
173/// The function returns [`Option<PathBuf>`] which can be used in `if let` statements to handle the case where the environment variable is not set.
174///
175/// ```
176/// if let Some(cache_dir) = systemd_directories::cache_dir() {
177///     // --snip--
178/// }
179/// ```
180pub fn cache_dir() -> Option<PathBuf> {
181    ColonSeparatedPaths::from_env_key("CACHE_DIRECTORY")
182        .iter()
183        .next()
184        .map(PathBuf::from)
185}
186
187/// Returns all logs directories as defined by `LogsDirectory` in the unit file.
188///
189/// If the environment variable `LOGS_DIRECTORY` is not set, it returns an empty vector.
190/// If it is set, it returns all paths in the colon-separated list.
191/// To get just the first path, use [`logs_dir`].
192///
193/// # Examples
194/// ```
195/// let logs_dirs = systemd_directories::logs_dirs();
196/// ```
197pub fn logs_dirs() -> Vec<PathBuf> {
198    ColonSeparatedPaths::from_env_key("LOGS_DIRECTORY")
199        .iter()
200        .map(PathBuf::from)
201        .collect()
202}
203
204/// Returns the first logs directory as defined by `LogsDirectory` in the unit file.
205///
206/// If the environment variable `LOGS_DIRECTORY` is not set, it returns [`None`].
207/// If it is set, it returns the first path in the colon-separated list.
208/// To get all paths, use [`logs_dirs`].
209///
210/// # Examples
211/// ```
212/// # use std::env;
213/// # env::set_var("LOGS_DIRECTORY", "/var/log/foo");
214/// let logs_dir = systemd_directories::logs_dir().unwrap_or_default();
215/// ```
216///
217/// The function returns [`Option<PathBuf>`] which can be used in `if let` statements to handle the case where the environment variable is not set.
218///
219/// ```
220/// if let Some(logs_dir) = systemd_directories::logs_dir() {
221///     // --snip--
222/// }
223/// ```
224pub fn logs_dir() -> Option<PathBuf> {
225    ColonSeparatedPaths::from_env_key("LOGS_DIRECTORY")
226        .iter()
227        .next()
228        .map(PathBuf::from)
229}
230
231/// Returns all configuration directories as defined by `ConfigurationDirectory` in the unit file.
232///
233/// If the environment variable `CONFIGURATION_DIRECTORY` is not set, it returns an empty vector.
234/// If it is set, it returns all paths in the colon-separated list.
235/// To get just the first path, use [`config_dir`].
236///
237/// # Examples
238/// ```
239/// let config_dirs = systemd_directories::config_dirs();
240/// ```
241pub fn config_dirs() -> Vec<PathBuf> {
242    ColonSeparatedPaths::from_env_key("CONFIGURATION_DIRECTORY")
243        .iter()
244        .map(PathBuf::from)
245        .collect()
246}
247
248/// Returns the first configuration directory as defined by `ConfigurationDirectory` in the unit file.
249///
250/// If the environment variable `CONFIGURATION_DIRECTORY` is not set, it returns [`None`].
251/// If it is set, it returns the first path in the colon-separated list.
252/// To get all paths, use [`config_dirs`].
253///
254/// # Examples
255/// ```
256/// # use std::env;
257/// # env::set_var("CONFIGURATION_DIRECTORY", "/etc/foo");
258/// let config_dir = systemd_directories::config_dir().unwrap_or_default();
259/// ```
260///
261/// The function returns [`Option<PathBuf>`] which can be used in `if let` statements to handle the case where the environment variable is not set.
262///
263/// ```
264/// if let Some(config_dir) = systemd_directories::config_dir() {
265///     // --snip--
266/// }
267/// ```
268pub fn config_dir() -> Option<PathBuf> {
269    ColonSeparatedPaths::from_env_key("CONFIGURATION_DIRECTORY")
270        .iter()
271        .next()
272        .map(PathBuf::from)
273}
274
275/// A struct to snapshot the environment at the time of creation.
276///
277/// The [`SystemdDirs`] methods return [`Path`] objects as immutable references to the paths as available when
278/// [`Self::new`] was called. This differs from the standalone functions, which return [`PathBuf`] objects.
279#[derive(Debug, Clone)]
280pub struct SystemdDirs {
281    /// All runtime directories when the struct was created.
282    runtime_dirs: Vec<PathBuf>,
283
284    /// All state directories when the struct was created.
285    state_dirs: Vec<PathBuf>,
286
287    /// All cache directories when the struct was created.
288    cache_dirs: Vec<PathBuf>,
289
290    /// All logs directories when the struct was created.
291    logs_dirs: Vec<PathBuf>,
292
293    /// All configuration directories when the struct was created.
294    config_dirs: Vec<PathBuf>,
295}
296
297impl SystemdDirs {
298    /// Returns a new [`SystemdDirs`] struct with a snapshot of the current environment.
299    ///
300    /// # Examples
301    /// ```
302    /// use systemd_directories::SystemdDirs;
303    /// let dirs = SystemdDirs::new();
304    /// ```
305    pub fn new() -> Self {
306        // TODO: Use env::vars to snapshot the environment or is separate env::var calls sufficient?
307        Self {
308            runtime_dirs: runtime_dirs(),
309            state_dirs: state_dirs(),
310            cache_dirs: cache_dirs(),
311            logs_dirs: logs_dirs(),
312            config_dirs: config_dirs(),
313        }
314    }
315
316    /// Returns all runtime directories as defined by `RuntimeDirectory` in the unit file.
317    ///
318    /// If the environment variable `RUNTIME_DIRECTORY` was not set when [`SystemdDirs`] was created, it returns an empty vector.
319    /// If it was set, it returns all paths in the colon-separated list.
320    /// To get just the first path, use [`Self::runtime_dir`].
321    ///
322    /// # Examples
323    /// ```
324    /// let dirs = systemd_directories::SystemdDirs::new();
325    /// let runtime_dirs = dirs.runtime_dirs();
326    /// ```
327    pub fn runtime_dirs(&self) -> Vec<&Path> {
328        self.as_paths(&self.runtime_dirs).collect()
329    }
330
331    /// Returns the first runtime directory as defined by `RuntimeDirectory` in the unit file.
332    ///
333    /// If the environment variable `RUNTIME_DIRECTORY` was not set when [`SystemdDirs`] was created, it returns [`None`].
334    /// If it was set, it returns the first path in the colon-separated list.
335    /// To get all paths, use [`Self::runtime_dirs`].
336    ///
337    /// # Examples
338    /// ```
339    /// let dirs = systemd_directories::SystemdDirs::new();
340    /// let runtime_dir = dirs.runtime_dir();
341    pub fn runtime_dir(&self) -> Option<&Path> {
342        self.as_paths(&self.runtime_dirs).next()
343    }
344
345    /// Returns all state directories as defined by `StateDirectory` in the unit file.
346    ///
347    /// If the environment variable `STATE_DIRECTORY` was not set when [`SystemdDirs`] was created, it returns an empty vector.
348    /// If it was set, it returns all paths in the colon-separated list.
349    /// To get just the first path, use [`Self::state_dir`].
350    ///
351    /// # Examples
352    /// ```
353    /// let dirs = systemd_directories::SystemdDirs::new();
354    /// let state_dirs = dirs.state_dirs();
355    /// ```
356    pub fn state_dirs(&self) -> Vec<&Path> {
357        self.as_paths(&self.state_dirs).collect()
358    }
359
360    /// Returns the first state directory as defined by `StateDirectory` in the unit file.
361    ///
362    /// If the environment variable `STATE_DIRECTORY` was not set when [`SystemdDirs`] was created, it returns [`None`].
363    /// If it was set, it returns the first path in the colon-separated list.
364    /// To get all paths, use [`Self::state_dirs`].
365    ///
366    /// # Examples
367    /// ```
368    /// let dirs = systemd_directories::SystemdDirs::new();
369    /// let state_dir = dirs.state_dir();
370    /// ```
371    pub fn state_dir(&self) -> Option<&Path> {
372        self.as_paths(&self.state_dirs).next()
373    }
374
375    /// Returns all cache directories as defined by `CacheDirectory` in the unit file.
376    ///
377    /// If the environment variable `CACHE_DIRECTORY` was not set when [`SystemdDirs`] was created, it returns an empty vector.
378    /// If it was set, it returns all paths in the colon-separated list.
379    /// To get just the first path, use [`Self::cache_dir`].
380    ///
381    /// # Examples
382    /// ```
383    /// let dirs = systemd_directories::SystemdDirs::new();
384    /// let cache_dirs = dirs.cache_dirs();
385    /// ```
386    pub fn cache_dirs(&self) -> Vec<&Path> {
387        self.as_paths(&self.cache_dirs).collect()
388    }
389
390    /// Returns the first cache directory as defined by `CacheDirectory` in the unit file.
391    ///
392    /// If the environment variable `CACHE_DIRECTORY` was not set when [`SystemdDirs`] was created, it returns [`None`].
393    /// If it was set, it returns the first path in the colon-separated list.
394    /// To get all paths, use [`Self::cache_dirs`].
395    ///
396    /// # Examples
397    /// ```
398    /// let dirs = systemd_directories::SystemdDirs::new();
399    /// let cache_dir = dirs.cache_dir();
400    /// ```
401    pub fn cache_dir(&self) -> Option<&Path> {
402        self.as_paths(&self.cache_dirs).next()
403    }
404
405    /// Returns all logs directories as defined by `LogsDirectory` in the unit file.
406    ///
407    /// If the environment variable `LOGS_DIRECTORY` was not set when [`SystemdDirs`] was created, it returns an empty vector.
408    /// If it was set, it returns all paths in the colon-separated list.
409    /// To get just the first path, use [`Self::logs_dir`].
410    ///
411    /// # Examples
412    /// ```
413    /// let dirs = systemd_directories::SystemdDirs::new();
414    /// let logs_dirs = dirs.logs_dirs();
415    /// ```
416    pub fn logs_dirs(&self) -> Vec<&Path> {
417        self.as_paths(&self.logs_dirs).collect()
418    }
419
420    /// Returns the first logs directory as defined by `LogsDirectory` in the unit file.
421    ///
422    /// If the environment variable `LOGS_DIRECTORY` was not set when [`SystemdDirs`] was created, it returns [`None`].
423    /// If it was set, it returns the first path in the colon-separated list.
424    /// To get all paths, use [`Self::logs_dirs`].
425    ///
426    /// # Examples
427    /// ```
428    /// let dirs = systemd_directories::SystemdDirs::new();
429    /// let logs_dir = dirs.logs_dir();
430    /// ```
431    pub fn logs_dir(&self) -> Option<&Path> {
432        self.as_paths(&self.logs_dirs).next()
433    }
434
435    /// Returns all configuration directories as defined by `ConfigurationDirectory` in the unit file.
436    ///
437    /// If the environment variable `CONFIGURATION_DIRECTORY` was not set when [`SystemdDirs`] was created, it returns an empty vector.
438    /// If it was set, it returns all paths in the colon-separated list.
439    /// To get just the first path, use [`Self::config_dir`].
440    ///
441    /// # Examples
442    /// ```
443    /// let dirs = systemd_directories::SystemdDirs::new();
444    /// let config_dirs = dirs.config_dirs();
445    /// ```
446    pub fn config_dirs(&self) -> Vec<&Path> {
447        self.as_paths(&self.config_dirs).collect()
448    }
449
450    /// Returns the first configuration directory as defined by `ConfigurationDirectory` in the unit file.
451    ///
452    /// If the environment variable `CONFIGURATION_DIRECTORY` was not set when [`SystemdDirs`] was created, it returns [`None`].
453    /// If it was set, it returns the first path in the colon-separated list.
454    /// To get all paths, use [`Self::config_dirs`].
455    ///
456    /// # Examples
457    /// ```
458    /// let dirs = systemd_directories::SystemdDirs::new();
459    /// let config_dir = dirs.config_dir();
460    /// ```
461    pub fn config_dir(&self) -> Option<&Path> {
462        self.as_paths(&self.config_dirs).next()
463    }
464
465    /// Helper to map a slice of `PathBuf` to an iterator of `&Path`.
466    fn as_paths<'a>(&'a self, dirs: &'a [PathBuf]) -> impl Iterator<Item = &Path> + 'a {
467        dirs.iter().map(|p| p.as_path())
468    }
469}
470
471impl Default for SystemdDirs {
472    fn default() -> Self {
473        Self::new()
474    }
475}
476
477#[cfg(test)]
478mod tests {
479    use super::*;
480    use std::env;
481    use std::path::PathBuf;
482
483    fn test_set(
484        env_key: &str,
485        dirs: &[&str],
486        standalone_single: fn() -> Option<PathBuf>,
487        standalone_all: fn() -> Vec<PathBuf>,
488        method_single: fn(&SystemdDirs) -> Option<&Path>,
489        method_all: fn(&SystemdDirs) -> Vec<&Path>,
490    ) {
491        env::set_var(env_key, dirs.join(":"));
492        assert_eq!(standalone_single(), Some(PathBuf::from(dirs[0])));
493        assert_eq!(
494            standalone_all(),
495            dirs.iter().map(PathBuf::from).collect::<Vec<PathBuf>>()
496        );
497        let systemd_dirs = SystemdDirs::new();
498        assert_eq!(method_single(&systemd_dirs), Some(Path::new(dirs[0])));
499        assert_eq!(
500            method_all(&systemd_dirs),
501            dirs.iter().map(Path::new).collect::<Vec<&Path>>()
502        );
503    }
504
505    fn test_unset(
506        env_key: &str,
507        standalone_single: fn() -> Option<PathBuf>,
508        standalone_all: fn() -> Vec<PathBuf>,
509        method_single: fn(&SystemdDirs) -> Option<&Path>,
510        method_all: fn(&SystemdDirs) -> Vec<&Path>,
511    ) {
512        env::remove_var(env_key);
513        assert_eq!(standalone_single(), None);
514        assert!(standalone_all().is_empty());
515        let systemd_dirs = SystemdDirs::new();
516        assert_eq!(method_single(&systemd_dirs), None);
517        assert!(method_all(&systemd_dirs).is_empty());
518    }
519
520    #[test]
521    fn test_runtime_directory() {
522        test_set(
523            "RUNTIME_DIRECTORY",
524            &["/run/foo", "/run/bar"],
525            runtime_dir,
526            runtime_dirs,
527            SystemdDirs::runtime_dir,
528            SystemdDirs::runtime_dirs,
529        );
530
531        test_unset(
532            "RUNTIME_DIRECTORY",
533            runtime_dir,
534            runtime_dirs,
535            SystemdDirs::runtime_dir,
536            SystemdDirs::runtime_dirs,
537        );
538    }
539
540    #[test]
541    fn test_state_directory() {
542        test_set(
543            "STATE_DIRECTORY",
544            &["/var/lib/foo", "/var/lib/bar"],
545            state_dir,
546            state_dirs,
547            SystemdDirs::state_dir,
548            SystemdDirs::state_dirs,
549        );
550
551        test_unset(
552            "STATE_DIRECTORY",
553            state_dir,
554            state_dirs,
555            SystemdDirs::state_dir,
556            SystemdDirs::state_dirs,
557        );
558    }
559
560    #[test]
561    fn test_cache_directory() {
562        test_set(
563            "CACHE_DIRECTORY",
564            &["/var/cache/foo", "/var/cache/bar"],
565            cache_dir,
566            cache_dirs,
567            SystemdDirs::cache_dir,
568            SystemdDirs::cache_dirs,
569        );
570
571        test_unset(
572            "CACHE_DIRECTORY",
573            cache_dir,
574            cache_dirs,
575            SystemdDirs::cache_dir,
576            SystemdDirs::cache_dirs,
577        );
578    }
579
580    #[test]
581    fn test_logs_directory() {
582        test_set(
583            "LOGS_DIRECTORY",
584            &["/var/log/foo", "/var/log/bar"],
585            logs_dir,
586            logs_dirs,
587            SystemdDirs::logs_dir,
588            SystemdDirs::logs_dirs,
589        );
590
591        test_unset(
592            "LOGS_DIRECTORY",
593            logs_dir,
594            logs_dirs,
595            SystemdDirs::logs_dir,
596            SystemdDirs::logs_dirs,
597        );
598    }
599
600    #[test]
601    fn test_configuration_directory() {
602        test_set(
603            "CONFIGURATION_DIRECTORY",
604            &["/etc/foo", "/etc/bar"],
605            config_dir,
606            config_dirs,
607            SystemdDirs::config_dir,
608            SystemdDirs::config_dirs,
609        );
610
611        test_unset(
612            "CONFIGURATION_DIRECTORY",
613            config_dir,
614            config_dirs,
615            SystemdDirs::config_dir,
616            SystemdDirs::config_dirs,
617        );
618    }
619}