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