cgroups_rs/systemd/
utils.rs

1// Copyright (c) 2025 Ant Group
2//
3// SPDX-License-Identifier: Apache-2.0 or MIT
4//
5
6use crate::systemd::error::{Error, Result};
7use crate::systemd::{SCOPE_SUFFIX, SLICE_SUFFIX};
8
9/// Check if a systemd unit name is a slice unit.
10pub fn is_slice_unit(name: &str) -> bool {
11    name.ends_with(SLICE_SUFFIX)
12}
13
14/// Check if a systemd unit name is a scope unit.
15pub fn is_scope_unit(name: &str) -> bool {
16    name.ends_with(SCOPE_SUFFIX)
17}
18
19/// Expand a slice name to a full path in the filesystem.
20///
21/// # Arguments
22///
23/// * `slice` - A string slice that holds the slice name in the format
24///   "xxx-yyy-zzz.slice".
25///
26/// # Returns
27///
28/// A string that represents the full path of the slice in the filesystem.
29/// In the above case, the value would be
30/// "xxx.slice/xxx-yyy.slice/xxx-yyy-zzz.slice".
31pub fn expand_slice(slice: &str) -> Result<String> {
32    // Name has to end with ".slice", but can't be just ".slice".
33    if !slice.ends_with(SLICE_SUFFIX) || slice.len() < SLICE_SUFFIX.len() {
34        return Err(Error::InvalidArgument);
35    }
36
37    // Path-separators are not allowed.
38    if slice.contains('/') {
39        return Err(Error::InvalidArgument);
40    }
41
42    let name = slice.trim_end_matches(SLICE_SUFFIX);
43
44    // If input was -.slice, we should just return root now
45    if name == "-" {
46        return Ok("".to_string());
47    }
48
49    let mut slice_path = String::new();
50    let mut prefix = String::new();
51    for sub_slice in name.split('-') {
52        if sub_slice.is_empty() {
53            return Err(Error::InvalidArgument);
54        }
55
56        slice_path = format!("{}/{}{}{}", slice_path, prefix, sub_slice, SLICE_SUFFIX);
57        prefix = format!("{}{}-", prefix, sub_slice);
58    }
59
60    // We need a relative path, so remove the first slash.
61    slice_path.remove(0);
62
63    Ok(slice_path)
64}
65
66#[cfg(test)]
67mod tests {
68    use crate::systemd::utils::*;
69
70    #[test]
71    fn test_is_slice_unit() {
72        assert!(is_slice_unit("test.slice"));
73        assert!(!is_slice_unit("test.scope"));
74    }
75
76    #[test]
77    fn test_is_scope_unit() {
78        assert!(is_scope_unit("test.scope"));
79        assert!(!is_scope_unit("test.slice"));
80    }
81
82    #[test]
83    fn test_expand_slice() {
84        assert_eq!(expand_slice("test.slice").unwrap(), "test.slice");
85        assert_eq!(
86            expand_slice("test-1.slice").unwrap(),
87            "test.slice/test-1.slice"
88        );
89        assert_eq!(
90            expand_slice("test-1-test-2.slice").unwrap(),
91            "test.slice/test-1.slice/test-1-test.slice/test-1-test-2.slice"
92        );
93        assert_eq!(
94            expand_slice("slice-slice.slice").unwrap(),
95            "slice.slice/slice-slice.slice"
96        );
97        assert_eq!(expand_slice("-.slice").unwrap(), "");
98        assert!(expand_slice("invalid/slice").is_err());
99        assert!(expand_slice("invalid-slice").is_err());
100    }
101}