Skip to main content

systemd_transient/props/
mod.rs

1//! Typed properties for `org.freedesktop.systemd1.Manager.StartTransientUnit`
2//! (`.service` units, systemd v261).
3//!
4//! `src/props/generated.rs` is produced by `tools/gen.py` from the systemd source tree;
5//! this file holds the macros and the composite types it refers to.
6
7use std::net::IpAddr;
8use std::time::Duration;
9
10use zbus::{Signature, Structure, Type, Variant};
11
12macro_rules! unit_properties {
13    ($( $name:ident : $ty:ty = $sig:literal ),* $(,)?) => {
14        /// Every key `StartTransientUnit` accepts for a `.service` unit.
15        #[allow(clippy::upper_case_acronyms)]
16        ///
17        /// Non-exhaustive: new systemd releases add keys.
18        #[derive(Debug, Clone)]
19        #[non_exhaustive]
20        pub enum UnitProperty {
21            $( #[doc = concat!("`", $sig, "`")] $name($ty), )*
22            /// Escape hatch for keys this table does not know.
23            Custom(std::borrow::Cow<'static, str>, zbus::Variant<'static>),
24        }
25
26        impl UnitProperty {
27            pub fn into_pair(self) -> (std::borrow::Cow<'static, str>, zbus::Variant<'static>) {
28                match self {
29                    $( Self::$name(v) => (std::borrow::Cow::Borrowed(stringify!($name)), v.into()), )*
30                    Self::Custom(k, v) => (k, v),
31                }
32            }
33        }
34
35        /// The derived Rust type must serialise to exactly what systemd reads.
36        #[test]
37        fn signatures() {
38            $( assert_eq!(<$ty as zbus::Type>::SIGNATURE.to_string(), $sig, stringify!($name)); )*
39        }
40    };
41}
42
43macro_rules! str_enum {
44    ($name:ident { $($var:ident => $s:literal),+ $(,)? }) => {
45        #[allow(non_camel_case_types)]
46        /// Non-exhaustive: new systemd releases add values.
47        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
48        #[non_exhaustive]
49        pub enum $name { $($var),+ }
50
51        impl $name {
52            pub const fn as_str(self) -> &'static str {
53                match self { $(Self::$var => $s),+ }
54            }
55        }
56
57        impl std::str::FromStr for $name {
58            type Err = zbus::Error;
59            fn from_str(s: &str) -> Result<Self, Self::Err> {
60                match s {
61                    $($s => Ok(Self::$var),)+
62                    _ => Err(zbus::Error::Failure(format!(concat!("invalid ", stringify!($name), ": {}"), s))),
63                }
64            }
65        }
66
67        impl zbus::Type for $name {
68            const SIGNATURE: &'static zbus::Signature = &zbus::Signature::Str;
69        }
70
71        impl From<$name> for zbus::Variant<'_> {
72            fn from(v: $name) -> Self { zbus::Variant::from(v.as_str()) }
73        }
74
75        impl TryFrom<zbus::Variant<'_>> for $name {
76            type Error = zbus::Error;
77            fn try_from(v: zbus::Variant<'_>) -> Result<Self, Self::Error> {
78                <&str>::try_from(&v)?.parse()
79            }
80        }
81    };
82}
83
84mod generated;
85pub use generated::*;
86
87// ---- scalar wrappers ----------------------------------------------------------
88
89/// Microseconds. `Usec::INFINITY` disables the timeout.
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub struct Usec(pub u64);
92
93impl Usec {
94  pub const INFINITY: Self = Self(u64::MAX);
95}
96
97impl From<Duration> for Usec {
98  fn from(d: Duration) -> Self {
99    Self(u64::try_from(d.as_micros()).unwrap_or(u64::MAX))
100  }
101}
102
103impl From<Usec> for Duration {
104  fn from(value: Usec) -> Self {
105    Self::from_micros(value.0)
106  }
107}
108
109impl Type for Usec {
110  const SIGNATURE: &'static Signature = &Signature::U64;
111}
112
113impl From<Usec> for Variant<'_> {
114  fn from(u: Usec) -> Self {
115    Variant::from(u.0)
116  }
117}
118
119// ---- composite types: field order is wire order --------------------------------
120
121#[derive(Debug, Clone, Type, Variant)]
122pub struct ExecCommand {
123  pub path: String,
124  /// Includes `argv[0]`.
125  pub argv: Vec<String>,
126  pub ignore_failure: bool,
127}
128
129impl ExecCommand {
130  /// Runs `path` with `args`, with `path` as `argv[0]`.
131  pub fn new(path: impl Into<String>, args: impl IntoIterator<Item = impl Into<String>>) -> Self {
132    let path = path.into();
133    let argv = std::iter::once(path.clone())
134      .chain(args.into_iter().map(Into::into))
135      .collect();
136    Self {
137      path,
138      argv,
139      ignore_failure: false,
140    }
141  }
142}
143
144#[derive(Debug, Clone, Type, Variant)]
145pub struct ExecCommandEx {
146  pub path: String,
147  pub argv: Vec<String>,
148  pub flags: Vec<ExecCommandFlag>,
149}
150
151#[derive(Debug, Clone, Type, Variant)]
152pub struct Condition {
153  /// e.g. "ConditionPathExists", "AssertUser".
154  pub kind: String,
155  /// `|` prefix in unit files.
156  pub trigger: bool,
157  /// `!` prefix in unit files.
158  pub negate: bool,
159  pub parameter: String,
160}
161
162#[derive(Debug, Clone, Default, Type, Variant)]
163pub struct ExitStatusSet {
164  pub statuses: Vec<i32>,
165  pub signals: Vec<i32>,
166}
167
168#[derive(Debug, Clone, Type, Variant)]
169pub struct OpenFile {
170  pub path: String,
171  pub fd_name: String,
172  /// OPENFILE_* bits: 1 read-only, 2 append, 4 truncate, 8 graceful.
173  pub flags: u64,
174}
175
176#[derive(Debug, Clone, Type, Variant)]
177pub struct RefreshOnReload {
178  /// `~` prefix in unit files.
179  pub invert: bool,
180  /// e.g. "credentials", "extensions".
181  pub flag: String,
182}
183
184#[derive(Debug, Clone, Type, Variant)]
185pub struct SecurityLabel {
186  /// `-` prefix in unit files.
187  pub ignore_failure: bool,
188  pub label: String,
189}
190
191pub const MS_REC: u64 = 16384;
192
193#[derive(Debug, Clone, Type, Variant)]
194pub struct BindPath {
195  pub source: String,
196  pub destination: String,
197  /// `-` prefix in unit files.
198  pub ignore_missing: bool,
199  /// Mount flags; `MS_REC` for a recursive bind.
200  pub flags: u64,
201}
202
203/// `{State,Cache,Logs}DirectoryQuota=`. Encoded as `(tus)`; `enforce` travels as "yes"/"no".
204#[derive(Debug, Clone, Copy)]
205pub struct DirectoryQuota {
206  /// Bytes, or `u64::MAX` if `scale` is used.
207  pub absolute: u64,
208  /// Fraction of the backing file system scaled to `u32::MAX`, or `u32::MAX` if unset.
209  pub scale: u32,
210  pub enforce: bool,
211}
212
213impl Type for DirectoryQuota {
214  const SIGNATURE: &'static Signature = <(u64, u32, String)>::SIGNATURE;
215}
216
217impl From<DirectoryQuota> for Variant<'_> {
218  fn from(q: DirectoryQuota) -> Self {
219    Structure::from((q.absolute, q.scale, if q.enforce { "yes" } else { "no" })).into()
220  }
221}
222
223#[derive(Debug, Clone, Type, Variant)]
224pub struct DirectorySymlink {
225  pub source: String,
226  pub destination: String,
227  /// EXEC_DIRECTORY_* bits. 0 for a plain symlink.
228  pub flags: u64,
229}
230
231#[derive(Debug, Clone, Type, Variant)]
232pub struct EnvironmentFile {
233  pub path: String,
234  pub ignore_missing: bool,
235}
236
237#[derive(Debug, Clone, Type, Variant)]
238pub struct MountOption {
239  /// e.g. "root", "usr".
240  pub partition: String,
241  pub options: String,
242}
243
244#[derive(Debug, Clone, Type, Variant)]
245pub struct ExtensionImage {
246  pub source: String,
247  pub ignore_missing: bool,
248  pub options: Vec<MountOption>,
249}
250
251#[derive(Debug, Clone, Type, Variant)]
252pub struct MountImage {
253  pub source: String,
254  pub destination: String,
255  pub ignore_missing: bool,
256  pub options: Vec<MountOption>,
257}
258
259#[derive(Debug, Clone, Type, Variant)]
260pub struct ImportCredential {
261  pub glob: String,
262  /// Empty for no rename.
263  pub rename: String,
264}
265
266#[derive(Debug, Clone, Type, Variant)]
267pub struct LoadCredential {
268  pub id: String,
269  pub path: String,
270}
271
272#[derive(Debug, Clone, Type, Variant)]
273pub struct SetCredential {
274  pub id: String,
275  pub data: Vec<u8>,
276}
277
278#[derive(Debug, Clone, Type, Variant)]
279pub struct LogFilterPattern {
280  /// false means the pattern was prefixed with `~` (deny).
281  pub allow: bool,
282  pub pattern: String,
283}
284
285#[derive(Debug, Clone, Type, Variant)]
286pub struct ProtectHostnameEx {
287  pub mode: ProtectHostname,
288  /// Empty to keep the host's name.
289  pub hostname: String,
290}
291
292/// SystemCallFilter, SystemCallLog, RestrictAddressFamilies,
293/// RestrictFileSystems, RestrictNetworkInterfaces.
294#[derive(Debug, Clone, Type, Variant)]
295pub struct FilterList {
296  /// true: allow list. false: deny list (`~` prefix).
297  pub allow_list: bool,
298  pub items: Vec<String>,
299}
300
301#[derive(Debug, Clone, Type, Variant)]
302pub struct TmpFs {
303  pub path: String,
304  pub options: String,
305}
306
307#[derive(Debug, Clone, Type, Variant)]
308pub struct BpfProgram {
309  /// e.g. "ingress", "egress", "sock_create".
310  pub attach_type: String,
311  pub path: String,
312}
313
314#[derive(Debug, Clone, Type, Variant)]
315pub struct DeviceAllow {
316  pub path: String,
317  /// Any combination of "r", "w", "m".
318  pub access: String,
319}
320
321/// Device path plus weight, bandwidth, IOPS or latency, depending on the key.
322#[derive(Debug, Clone, Type, Variant)]
323pub struct DeviceValue {
324  pub path: String,
325  pub value: u64,
326}
327
328#[derive(Debug, Clone, Copy, Type, Variant)]
329pub struct SocketBind {
330  /// AF_INET, AF_INET6 or AF_UNSPEC (0).
331  pub family: i32,
332  /// IPPROTO_TCP, IPPROTO_UDP or 0 for any.
333  pub protocol: i32,
334  /// 0 means any port.
335  pub port_count: u16,
336  pub port_min: u16,
337}
338
339#[derive(Debug, Clone, Type, Variant)]
340pub struct NftSet {
341  /// NFT_SET_SOURCE_*: 0 address, 1 prefix, 2 ifindex, 3 cgroup, 4 user, 5 group.
342  pub source: i32,
343  /// NFPROTO_*: 1 inet, 2 ipv4, 10 ipv6.
344  pub nfproto: i32,
345  pub table: String,
346  pub set: String,
347}
348
349/// Encoded as `(iayu)`: address family, address bytes, prefix length.
350#[derive(Debug, Clone, Copy)]
351pub struct IpPrefix {
352  pub address: IpAddr,
353  pub prefix_len: u32,
354}
355
356impl Type for IpPrefix {
357  const SIGNATURE: &'static Signature = <(i32, Vec<u8>, u32)>::SIGNATURE;
358}
359
360impl From<IpPrefix> for Variant<'_> {
361  fn from(p: IpPrefix) -> Self {
362    const AF_INET: i32 = 2;
363    const AF_INET6: i32 = 10;
364    let (af, bytes) = match p.address {
365      IpAddr::V4(a) => (AF_INET, a.octets().to_vec()),
366      IpAddr::V6(a) => (AF_INET6, a.octets().to_vec()),
367    };
368    Structure::from((af, bytes, p.prefix_len)).into()
369  }
370}
371
372#[cfg(test)]
373mod tests {
374  use super::*;
375
376  #[test]
377  fn exec_start_wire_format() {
378    let (k, v) = UnitProperty::ExecStart(vec![ExecCommand {
379      path: "/bin/true".into(),
380      argv: vec!["true".into()],
381      ignore_failure: false,
382    }])
383    .into_pair();
384    assert_eq!(k, "ExecStart");
385    assert_eq!(v.value_signature().to_string(), "a(sasb)");
386  }
387
388  #[test]
389  fn exec_command_new_sets_argv0() {
390    let c = ExecCommand::new("/usr/bin/sleep", ["10"]);
391    assert_eq!(c.argv, ["/usr/bin/sleep", "10"]);
392  }
393
394  #[test]
395  fn enum_round_trip() {
396    let v: Variant = ServiceType::NotifyReload.into();
397    assert_eq!(ServiceType::try_from(v).unwrap(), ServiceType::NotifyReload);
398  }
399}