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