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}