systemd_run/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use byte_unit::Byte;
4use std::num::NonZeroU64;
5use std::time::Duration;
6use zbus::fdo::{PropertiesChangedStream, PropertiesProxy};
7use zbus::zvariant::{ObjectPath, Value};
8use zbus::Connection;
9
10mod cpu_sched;
11mod error;
12mod identity;
13mod ioredirect;
14mod mount;
15mod sd;
16
17pub use cpu_sched::CpuScheduling;
18pub use error::{Error, Result};
19pub use identity::Identity;
20pub use ioredirect::{InputSpec, OutputSpec};
21pub use mount::Mount;
22
23#[allow(dead_code)]
24enum ProtectProcInternal {
25    NoAccess,
26    Invisible,
27    Ptraceable,
28    Default,
29}
30
31/// Controls the `hidepid=` mount option of the `procfs` instance in the
32/// private namespace of the unit.
33///
34/// Read `ProtectProc=` in [systemd.exec(5)](man:systemd.exec(5)) for
35/// details.
36#[cfg(feature = "systemd_247")]
37pub struct ProtectProc(ProtectProcInternal);
38
39#[cfg(feature = "systemd_247")]
40impl ProtectProc {
41    /// Take away the ability to access most of other users' process
42    /// metadata
43    pub fn no_access() -> Self {
44        Self(ProtectProcInternal::NoAccess)
45    }
46    /// Processes owned by other users are hidden
47    pub fn invisible() -> Self {
48        Self(ProtectProcInternal::Invisible)
49    }
50    /// Processes not traceable by the unit are hidden
51    pub fn ptraceable() -> Self {
52        Self(ProtectProcInternal::Ptraceable)
53    }
54}
55
56#[cfg(feature = "systemd_247")]
57impl Default for ProtectProc {
58    /// No protection
59    fn default() -> Self {
60        Self(ProtectProcInternal::Default)
61    }
62}
63
64/// Information of a transient service for running on the system service
65/// manager.
66pub struct RunSystem {
67    path: String,
68    args: Vec<String>,
69    service_name: Option<String>,
70    collect_on_fail: bool,
71    identity: identity::Identity,
72    runtime_max: Option<Duration>,
73    memory_max: Option<Byte>,
74    memory_swap_max: Option<Byte>,
75    allowed_cpus: Vec<usize>,
76    cpu_quota: Option<u64>,
77    private_network: bool,
78    private_ipc: bool,
79    mount: Vec<(String, Mount)>,
80    mount_api_vfs: bool,
81    private_devices: bool,
82    no_new_privileges: bool,
83    limit_fsize: Option<Byte>,
84    limit_fsize_soft: Option<Byte>,
85    limit_stack: Option<Byte>,
86    limit_stack_soft: Option<Byte>,
87    limit_core: Option<Byte>,
88    limit_core_soft: Option<Byte>,
89    limit_nofile: Option<u64>,
90    limit_nofile_soft: Option<u64>,
91    limit_nproc: Option<u64>,
92    limit_nproc_soft: Option<u64>,
93    stdin: Option<InputSpec>,
94    stdout: Option<OutputSpec>,
95    stderr: Option<OutputSpec>,
96    current_dir: Option<String>,
97    protect_proc: ProtectProcInternal,
98    slice: Option<String>,
99    private_users: bool,
100    timeout_stop: Option<Duration>,
101    cpu_sched: CpuScheduling,
102    joins_namespace_of: Vec<String>,
103}
104
105/// Information of a transient service for running on the per-user service
106/// manager.
107pub struct RunUser(RunSystem);
108
109/// A transient service running.
110pub struct StartedRun<'a> {
111    proxy: zbus::fdo::PropertiesProxy<'a>,
112    stream: PropertiesChangedStream,
113}
114
115/// A transient service finished.
116#[derive(Debug)]
117pub struct FinishedRun {
118    failed: bool,
119    wall_time_usage: Duration,
120}
121
122// The logic is "borrowed" from systemd/src/run.c.
123fn default_unit_name(bus: &zbus::Connection) -> Result<String> {
124    bus.unique_name()
125        .map_or_else(
126            || {
127                // We couldn't get the unique name, which is a pretty
128                // common case if we are connected to systemd directly.
129                // In that case, just pick a random uuid as name.
130                Ok(('r', uuid::Uuid::new_v4().simple().to_string()))
131            },
132            |s| {
133                for p in [":1.", ":"] {
134                    if let Some(s) = s.strip_prefix(p) {
135                        return Ok(('u', s.to_owned()));
136                    }
137                }
138                unreachable!("zbus should have rejected invalid name");
139            },
140        )
141        .map(|(tp, id)| format!("run-{}{}.service", tp, id))
142}
143
144fn escape_byte_for_object_path(b: u8) -> String {
145    if b.is_ascii_alphanumeric() {
146        std::str::from_utf8(&[b])
147            .expect("[0-9a-zA-Z] is valid UTF-8")
148            .to_owned()
149    } else {
150        format!("_{:02x}", b)
151    }
152}
153
154fn object_path_from_unit_name<'a>(s: &str) -> Result<ObjectPath<'a>> {
155    let path_string = "/org/freedesktop/systemd1/unit/".to_owned()
156        + &s.bytes()
157            .map(escape_byte_for_object_path)
158            .collect::<Vec<_>>()
159            .join("");
160    ObjectPath::try_from(path_string).map_err(Error::DBusInvalidPath)
161}
162
163async fn listen_unit_property_change<'a>(
164    bus: &Connection,
165    unit: &ObjectPath<'a>,
166) -> Result<(PropertiesProxy<'a>, PropertiesChangedStream)> {
167    let proxy = PropertiesProxy::builder(bus)
168        .path(unit)
169        .expect("should not fail with validated path")
170        .destination("org.freedesktop.systemd1")
171        .expect("should not fail with hardcode dest")
172        .build()
173        .await
174        .expect("should not fail with all info provided");
175    let stream = proxy
176        .receive_properties_changed()
177        .await
178        .map_err(Error::ListenPropertyChangeFail)?;
179    Ok((proxy, stream))
180}
181
182impl RunUser {
183    /// Create a new [RunUser] from a path to executable.
184    pub fn new<T: AsRef<str>>(path: T) -> Self {
185        Self(RunSystem {
186            identity: identity::session(),
187            ..RunSystem::new(path)
188        })
189    }
190
191    /// Append an argument to the command line.
192    pub fn arg<T: AsRef<str>>(self, arg: T) -> Self {
193        Self(self.0.arg(arg))
194    }
195
196    /// Append multiple arguments to the command line.
197    pub fn args<T: AsRef<str>, I: IntoIterator<Item = T>>(self, args: I) -> Self {
198        Self(self.0.args(args))
199    }
200
201    /// Set a custom name for the transient service.
202    ///
203    /// If the name is not terminated with `.service`, it will be appended
204    /// automatically.
205    pub fn service_name<T: AsRef<str>>(self, name: T) -> Self {
206        Self(self.0.service_name(name))
207    }
208
209    /// Unload the transient service even if it fails.
210    ///
211    /// This is not available if `systemd_236` is disabled.
212    ///
213    /// Read `CollectMode=` in [systemd.unit(5)](man:systemd.unit(5))
214    /// for details.
215    #[cfg(feature = "systemd_236")]
216    pub fn collect_on_fail(self) -> Self {
217        Self(self.0.collect_on_fail())
218    }
219
220    /// Configure a maximum time for the service to run.  If this is used
221    /// and the service has been active for longer than the specified time
222    /// it is terminated and put into a failure state.
223    ///
224    /// A [Duration] exceeding [u64::MAX] microseconds is trimmed to
225    /// [u64::MAX] microseconds silently.
226    ///
227    /// Read `RuntimeMaxSec=` in
228    /// [systemd.service(5)](man:systemd.service(5)) for details.
229    ///
230    /// This setting will be unavailable with the feature `systemd_229`
231    /// disabled.
232    #[cfg(feature = "systemd_229")]
233    pub fn runtime_max(self, d: Duration) -> Self {
234        Self(self.0.runtime_max(d))
235    }
236
237    /// Specify the absolute limit on memory usage of the executed
238    /// processes in this unit. If memory usage cannot be contained under
239    /// the limit, out-of-memory killer is invoked inside the unit.
240    ///
241    /// A [Byte] exceeding [u64::MAX] bytes is trimmed to [u64::MAX] bytes
242    /// silently.
243    ///
244    /// Read `MemoryMax=` in
245    /// [systemd.resource-control(5)](man:systemd.resource-control(5))
246    /// for details.
247    ///
248    /// If the feature `systemd_231` is disabled, `MemoryLimit=` will be
249    /// used instead if `MemoryMax=` for compatibility.
250    pub fn memory_max(self, d: Byte) -> Self {
251        Self(self.0.memory_max(d))
252    }
253
254    /// Specify the absolute limit on swap usage of the executed
255    /// processes in this unit.
256    ///
257    /// This setting is supported only if the unified control group is used,
258    /// so it's not available if the feature `unified_cgroup` is disabled.
259    /// And it will be unavailable with `systemd_232` disabled.
260    ///
261    /// A [Byte] exceeding [u64::MAX] bytes is trimmed to [u64::MAX] bytes
262    /// silently.
263    ///
264    /// Read `MemorySwapMax=` in
265    /// [systemd.resource-control(5)](man:systemd.resource-control(5))
266    /// for details.
267    #[cfg(feature = "unified_cgroup")]
268    #[cfg(feature = "systemd_232")]
269    pub fn memory_swap_max(self, d: Byte) -> Self {
270        Self(self.0.memory_swap_max(d))
271    }
272
273    /// Set soft and hard limits of the maximum size in bytes of files that
274    /// the process may create.
275    ///
276    /// Read `LimitFSIZE=` in [systemd.exec(5)](man:systemd.exec(5)) and
277    /// `RLIMIT_FSIZE` in [prlimit(2)](man:prlimit(2)) for details.
278    ///
279    /// Any setting exceeding [u64::MAX] bytes will be trimmed to [u64::MAX]
280    /// bytes silently.  And, if `soft` is greater than `hard`, it will be
281    /// trimmed to `hard` silently.
282    ///
283    /// Unlike [RunSystem::limit_fsize_soft_hard], this can't be used to
284    /// increase the hard limit because of insufficient privileges.
285    pub fn limit_fsize_soft_hard(self, soft: Byte, hard: Byte) -> Self {
286        Self(self.0.limit_fsize_soft_hard(soft, hard))
287    }
288
289    /// Shorthand for `self.limit_fsize_soft_hard(lim, lim)`.
290    pub fn limit_fsize(self, lim: Byte) -> Self {
291        self.limit_fsize_soft_hard(lim, lim)
292    }
293
294    /// Set soft and hard limits of the maximum size in bytes of files that
295    /// the process may create.
296    ///
297    /// Any setting exceeding [u64::MAX] bytes will be trimmed to
298    /// [u64::MAX] bytes silently.  And, if `soft` is greater than `hard`,
299    /// it will be trimmed to `hard` silently.
300    ///
301    /// Read `LimitCORE=` in [systemd.exec(5)](man:systemd.exec(5)) and
302    /// `RLIMIT_CORE` in [prlimit(2)](man:prlimit(2)) for details.
303    ///
304    /// Unlike [RunSystem::limit_core_soft_hard], this can't be used to
305    /// increase the hard limit because of insufficient privileges.
306    pub fn limit_core_soft_hard(self, soft: Byte, hard: Byte) -> Self {
307        Self(self.0.limit_core_soft_hard(soft, hard))
308    }
309
310    /// Shorthand for `self.limit_fsize_soft_hard(lim, lim)`.
311    pub fn limit_core(self, lim: Byte) -> Self {
312        self.limit_core_soft_hard(lim, lim)
313    }
314
315    /// Set soft and hard limits of the number of threads for the real user
316    /// ID of the process.
317    ///
318    /// If `soft` is greater than `hard`, it will be trimmed to `hard`
319    /// silently.
320    ///
321    /// Read `LimitNPROC=` in [systemd.exec(5)](man:systemd.exec(5)) and
322    /// `RLIMIT_NPROC` in [prlimit(2)](man:prlimit(2)) for details.
323    ///
324    /// Unlike [RunSystem::limit_nproc_soft_hard], this can't be used to
325    /// increase the hard limit because of insufficient privileges.
326    pub fn limit_nproc_soft_hard(self, soft: NonZeroU64, hard: NonZeroU64) -> Self {
327        Self(self.0.limit_nproc_soft_hard(soft, hard))
328    }
329
330    /// Shorthand for `self.limit_nproc_soft_hard(lim, lim)`.
331    pub fn limit_nproc(self, lim: NonZeroU64) -> Self {
332        self.limit_nproc_soft_hard(lim, lim)
333    }
334
335    /// Set soft and hard limits of the number of threads for the real user
336    /// ID of the process.
337    ///
338    /// If `soft` is greater than `hard`, it will be trimmed to `hard`
339    /// silently.
340    ///
341    /// Read `LimitNOFILE=` in [systemd.exec(5)](man:systemd.exec(5)) and
342    /// `RLIMIT_NOFILE` in [prlimit(2)](man:prlimit(2)) for details.
343    ///
344    /// Unlike [RunSystem::limit_nofile_soft_hard], this can't be used to
345    /// increase the hard limit because of insufficient privileges.
346    pub fn limit_nofile_soft_hard(self, soft: NonZeroU64, hard: NonZeroU64) -> Self {
347        Self(self.0.limit_nofile_soft_hard(soft, hard))
348    }
349
350    /// Shorthand for `self.limit_nofile_soft_hard(lim, lim)`.
351    pub fn limit_nofile(self, lim: NonZeroU64) -> Self {
352        self.limit_nofile_soft_hard(lim, lim)
353    }
354
355    /// Set the soft and hard limit on the size of the process stack.
356    ///
357    /// If `soft` is greater than `hard`, it will be trimmed to `hard`
358    /// silently.
359    ///
360    /// Read `LimitSTACK=` in [systemd.exec(5)](man:systemd.exec(5)) and
361    /// `RLIMIT_STACK` in [prlimit(2)](man:prlimit(2)) for details.
362    ///
363    /// Unlike [RunSystem::limit_stack_soft_hard], this can't be used to
364    /// increase the hard limit because of insufficient privileges.
365    pub fn limit_stack_soft_hard(self, soft: Byte, hard: Byte) -> Self {
366        Self(self.0.limit_stack_soft_hard(soft, hard))
367    }
368
369    /// Shorthand for `self.limit_stack_soft_hard(lim, lim)`.
370    pub fn limit_stack(self, lim: Byte) -> Self {
371        self.limit_stack_soft_hard(lim, lim)
372    }
373
374    /// Controls where file descriptor 0 (STDIN) of the executed processes
375    /// is connected to.
376    ///
377    /// Read [InputSpec] and `StandardInput=` in
378    /// [systemd.exec(5)](man:systemd.exec(5)) for details.
379    ///
380    /// The default is [InputSpec::null()].
381    pub fn stdin(self, spec: InputSpec) -> Self {
382        Self(self.0.stdin(spec))
383    }
384
385    /// Controls where file descriptor 1 (STDOUT) of the executed processes
386    /// is connected to.
387    ///
388    /// Read [OutputSpec] and `StandardOutput=` in
389    /// [systemd.exec(5)](man:systemd.exec(5)) for details.
390    ///
391    /// The default depends on system configuration.
392    pub fn stdout(self, spec: OutputSpec) -> Self {
393        Self(self.0.stdout(spec))
394    }
395
396    /// Controls where file descriptor 2 (STDERR) of the executed processes
397    /// is connected to.
398    ///
399    /// Read [OutputSpec] and `StandardError=` in
400    /// [systemd.exec(5)](man:systemd.exec(5)) for details.
401    ///
402    /// The default depends on system configuration.
403    pub fn stderr(self, spec: OutputSpec) -> Self {
404        Self(self.0.stderr(spec))
405    }
406
407    /// Sets the working directory for executed processes.
408    ///
409    /// Read `WorkingDirectory=` in
410    /// [systemd.exec(5)](man:systemd.exec(5)) for details.
411    ///
412    /// This setting is unavailable with the feature `systemd_227`
413    /// disabled.
414    #[cfg(feature = "systemd_227")]
415    pub fn current_dir<P: AsRef<str>>(self, path: P) -> Self {
416        Self(self.0.current_dir(path))
417    }
418
419    /// Put the transient service into a slice.
420    ///
421    /// Read `Slice=` in
422    /// [systemd.resource-control(5)](man:systemd.resource-control(5))
423    /// for details.
424    pub fn slice<S: AsRef<str>>(self, slice: S) -> Self {
425        Self(self.0.slice(slice))
426    }
427
428    /// Sets up a new user namespace for the executed processes and
429    /// configures a minimal user and group mapping.
430    ///
431    /// Read `PrivateUsers=` in [systemd.exec(5)](man:systemd.exec(5))
432    /// for details.
433    ///
434    /// This setting is unavailable with the feature `systemd_251`
435    /// disabled.
436    #[cfg(feature = "systemd_251")]
437    pub fn private_users(self) -> Self {
438        Self(self.0.private_users())
439    }
440
441    /// Configure the time to wait for the service itself to stop.
442    /// If the service doesn't terminate in the specified time, it will be
443    /// forcibly terminated by SIGKILL.
444    ///
445    /// A [Duration] exceeding [u64::MAX] microseconds is trimmed to
446    /// [u64::MAX] microseconds silently.
447    ///
448    /// Read `TimeoutStopSec=` in
449    /// [systemd.service(5)](man:systemd.service(5)) for details.
450    ///
451    /// This setting will be unavailable with the feature `systemd_188`
452    /// disabled.
453    #[cfg(feature = "systemd_188")]
454    pub fn timeout_stop(self, d: Duration) -> Self {
455        Self(self.0.timeout_stop(d))
456    }
457
458    /// Start the transient service.
459    pub async fn start<'a>(self) -> Result<StartedRun<'a>> {
460        self.0.start().await
461    }
462}
463
464impl RunSystem {
465    /// Create a new [RunSystem] from a path to executable.
466    pub fn new<T: AsRef<str>>(path: T) -> Self {
467        Self {
468            path: path.as_ref().to_string(),
469            args: vec![],
470            service_name: None,
471            collect_on_fail: false,
472            identity: Identity::root(),
473            runtime_max: None,
474            memory_max: None,
475            memory_swap_max: None,
476            allowed_cpus: vec![],
477            cpu_quota: None,
478            private_network: false,
479            private_ipc: false,
480            mount: vec![],
481            mount_api_vfs: false,
482            private_devices: false,
483            no_new_privileges: false,
484            limit_fsize: None,
485            limit_fsize_soft: None,
486            limit_stack: None,
487            limit_stack_soft: None,
488            limit_nofile: None,
489            limit_nofile_soft: None,
490            limit_nproc: None,
491            limit_nproc_soft: None,
492            limit_core: None,
493            limit_core_soft: None,
494            stdin: None,
495            stdout: None,
496            stderr: None,
497            current_dir: None,
498            protect_proc: ProtectProcInternal::Default,
499            slice: None,
500            private_users: false,
501            timeout_stop: None,
502            cpu_sched: CpuScheduling::default(),
503            joins_namespace_of: vec![],
504        }
505    }
506
507    /// Append an argument to the command line.
508    pub fn arg<T: AsRef<str>>(mut self, arg: T) -> Self {
509        self.args.push(arg.as_ref().to_string());
510        self
511    }
512
513    /// Append multiple arguments to the command line.
514    pub fn args<T: AsRef<str>, I: IntoIterator<Item = T>>(mut self, args: I) -> Self {
515        self.args
516            .extend(args.into_iter().map(|x| x.as_ref().to_owned()));
517        self
518    }
519
520    /// Set a custom name for the transient service.
521    ///
522    /// If the name is not terminated with `.service`, it will be appended
523    /// automatically.
524    pub fn service_name<T: AsRef<str>>(mut self, name: T) -> Self {
525        let mut name = name.as_ref().to_owned();
526        if !name.ends_with(".service") {
527            name += ".service";
528        }
529        self.service_name = Some(name);
530        self
531    }
532
533    /// Set an identity to run the transient service.  The default is
534    /// [Identity::root()].
535    pub fn identity(mut self, i: Identity) -> Self {
536        self.identity = i;
537        self
538    }
539
540    /// Unload the transient service even if it fails.
541    ///
542    /// This is not available if `systemd_236` is disabled.
543    ///
544    /// Read `CollectMode=` in [systemd.unit(5)](man:systemd.unit(5))
545    /// for details.
546    #[cfg(feature = "systemd_236")]
547    pub fn collect_on_fail(mut self) -> Self {
548        self.collect_on_fail = true;
549        self
550    }
551
552    /// Configure a maximum time for the service to run.  If this is used
553    /// and the service has been active for longer than the specified time
554    /// it is terminated and put into a failure state.
555    ///
556    /// A [Duration] exceeding [u64::MAX] microseconds is trimmed to
557    /// [u64::MAX] microseconds silently.
558    ///
559    /// Read `RuntimeMaxSec=` in
560    /// [systemd.service(5)](man:systemd.service(5)) for details.
561    ///
562    /// This setting will be unavailable with the feature `systemd_229`
563    /// disabled.
564    #[cfg(feature = "systemd_229")]
565    pub fn runtime_max(mut self, d: Duration) -> Self {
566        self.runtime_max = Some(d);
567        self
568    }
569
570    /// Specify the absolute limit on memory usage of the executed
571    /// processes in this unit. If memory usage cannot be contained under
572    /// the limit, out-of-memory killer is invoked inside the unit.
573    ///
574    /// A [Byte] exceeding [u64::MAX] bytes is trimmed to [u64::MAX] bytes
575    /// silently.
576    ///
577    /// Read `MemoryMax=` in
578    /// [systemd.resource-control(5)](man:systemd.resource-control(5))
579    /// for details.
580    ///
581    /// If the feature `systemd_231` is disabled, `MemoryLimit=` will be
582    /// used instead if `MemoryMax=` for compatibility.
583    pub fn memory_max(mut self, d: Byte) -> Self {
584        self.memory_max = Some(d);
585        self
586    }
587
588    /// Specify the absolute limit on swap usage of the executed
589    /// processes in this unit.
590    ///
591    /// This setting is supported only if the unified control group is used,
592    /// so it's not available if the feature `unified_cgroup` is disabled.
593    /// And, if `systemd_232` is disabled, this setting will also be
594    /// unavailable.
595    ///
596    /// A [Byte] exceeding [u64::MAX] bytes is trimmed to [u64::MAX] bytes
597    /// silently.
598    ///
599    /// Read `MemorySwapMax=` in
600    /// [systemd.resource-control(5)](man:systemd.resource-control(5))
601    /// for details.
602    #[cfg(feature = "unified_cgroup")]
603    #[cfg(feature = "systemd_232")]
604    pub fn memory_swap_max(mut self, d: Byte) -> Self {
605        self.memory_swap_max = Some(d);
606        self
607    }
608
609    /// Assign the specified CPU time quota to the processes executed.
610    /// Takes a percentage value.  The percentage specifies how much CPU
611    /// time the unit shall get at maximum, relativeto the total CPU time
612    /// available on one CPU. Use values > 100 for allotting CPU time on
613    /// more than one CPU.
614    ///
615    /// The value will be trimmed to [u64::MAX] / 10000 silently if it
616    /// exceeds this upper limit.
617    ///
618    /// Read `CPUQuota=` in
619    /// [systemd.resource-control(5)](man:systemd.resource-control(5)) for
620    /// details.
621    #[cfg(feature = "systemd_213")]
622    pub fn cpu_quota(mut self, percent: NonZeroU64) -> Self {
623        self.cpu_quota = Some(percent.into());
624        self
625    }
626
627    /// Restrict processes to be executed on specific CPUs.
628    ///
629    /// This setting doesn't guarantee that
630    /// all of the CPUs will be used by the processes as it may be limited
631    /// by parent units.
632    ///
633    /// Setting an empty list of CPUs will allow the processes of the unit
634    /// to run on **all** CPUs.  This is also the default behavior if this
635    /// is not used.
636    ///
637    /// Referring to an offline or non-existing CPU in this setting causes
638    /// Systemd to **ignore this setting silently**.
639    ///
640    /// Read `AllowedCPUs=` in
641    /// [systemd.resource-control(5)](man:systemd.resource-control(5))
642    /// for details.
643    ///
644    /// This setting is supported only if the unified control group is used,
645    /// so it's not available if the feature `unified_cgroup` is disabled.
646    /// And, this setting is not available if the feature `systemd_244` is
647    /// disabled.
648    #[cfg(feature = "systemd_244")]
649    #[cfg(feature = "unified_cgroup")]
650    pub fn allowed_cpus<'a, I: IntoIterator<Item = &'a usize>>(mut self, cpus: I) -> Self {
651        self.allowed_cpus = cpus.into_iter().copied().collect();
652        self
653    }
654
655    /// If this setting is used, sets up a new network namespace
656    /// for the executed processes and configures only the loopback network
657    /// device "lo" inside it. No other network devices will be available
658    /// to the executed process. This is useful to turn off network access
659    /// by the executed process.
660    ///
661    /// Read `PrivateNetwork=` in [systemd.exec(5)](man:systemd.exec(5)) for
662    /// details.
663    ///
664    /// This setting is not available if the feature `systemd_227` is
665    /// disabled.  And, it will be ignored silently if `CONFIG_NET_NS` is
666    /// not enabled in the configuration of the running kernel.
667    #[cfg(feature = "systemd_227")]
668    pub fn private_network(mut self) -> Self {
669        self.private_network = true;
670        self
671    }
672
673    /// If this setting is used, sets up a new IPC namespace
674    /// for the executed processes. Each IPC namespace has its own set of
675    /// System V IPC identifiers and its own POSIX message queue file
676    /// system. This is useful to avoid name clash of IPC identifiers.
677    ///
678    /// Read `PrivateIPC=` in [systemd.exec(5)](man:systemd.exec(5)) for
679    /// details.
680    ///
681    /// This setting is not available if the feature `systemd_249` is
682    /// disabled.  And, it will be ignored silently if `CONFIG_IPC_NS` is
683    /// not enabled in the configuration of the running kernel.
684    #[cfg(feature = "systemd_248")]
685    pub fn private_ipc(mut self) -> Self {
686        self.private_ipc = true;
687        self
688    }
689
690    /// Set up a mount point for the transient service.  See [Mount] for
691    /// details.
692    ///
693    /// This setting is not available if the feature `systemd_233` is
694    /// disabled.
695    #[cfg(feature = "systemd_233")]
696    pub fn mount<T: AsRef<str>>(mut self, mount_point: T, mount: Mount) -> Self {
697        self.mount.push((mount_point.as_ref().to_owned(), mount));
698        self
699    }
700
701    /// Mount the API file systems `/proc`, `/sys`, `/dev`, and `/run`
702    /// for the private mount namespace of the transient service.
703    ///
704    /// Read `MountAPIVFS=` in [systemd.exec(5)](man:systemd.exec(5)) for
705    /// details.
706    ///
707    /// Implied by [Identity::dynamic].
708    ///
709    /// This setting is mostly useful with [RunSystem::mount] at `/`, but
710    /// you'll need to ensure the mount points for the API file systems
711    /// existing first if this setting is specified or implied.
712    ///
713    /// This setting is not available if the feature `systemd_233` is
714    /// disabled.  And, if the version of systemd is less than 248, `/run`
715    /// is not affected by this setting.  You may use [RunSystem::mount] to
716    /// control `/run` more precisely anyway.
717    #[cfg(feature = "systemd_233")]
718    pub fn mount_api_vfs(self) -> Self {
719        Self {
720            mount_api_vfs: true,
721            ..self
722        }
723    }
724
725    /// Sets up a new `/dev` mount for the executed processes and only adds
726    /// API pseudo devices such as `/dev/null` to it, but no physical
727    /// devices such as `/dev/sda`, system memory `/dev/mem`, system ports
728    /// `/dev/port` and others.
729    ///
730    /// Read `PrivateDevices=` in [systemd.exec(5)](man:systemd.exec(5)) for
731    /// details.
732    ///
733    /// This setting is not available if the feature `systemd_227` is
734    /// disabled.
735    #[cfg(feature = "systemd_227")]
736    pub fn private_devices(self) -> Self {
737        Self {
738            private_devices: true,
739            ..self
740        }
741    }
742
743    /// Ensures that the service process and all its children can never gain
744    /// new privileges through `execve()` (e.g. via setuid or setgid bits,
745    /// or filesystem capabilities).
746    ///
747    /// Read `NoNewPrivileges=` in [systemd.exec(5)](man:systemd.exec(5))
748    /// for details.
749    ///
750    /// Implied by [Identity::dynamic].
751    ///
752    /// This setting is not available if the feature `systemd_227` is
753    /// disabled.
754    #[cfg(feature = "systemd_227")]
755    pub fn no_new_privileges(self) -> Self {
756        Self {
757            no_new_privileges: true,
758            ..self
759        }
760    }
761
762    /// Set soft and hard limits of the maximum size in bytes of files that
763    /// the process may create.
764    ///
765    /// Any setting exceeding [u64::MAX] bytes will be trimmed to [u64::MAX]
766    /// bytes silently.  And, if `soft` is greater than `hard`, it will be
767    /// trimmed to `hard` silently.
768    ///
769    /// Read `LimitFSIZE=` in [systemd.exec(5)](man:systemd.exec(5)) and
770    /// `RLIMIT_FSIZE` in [prlimit(2)](man:prlimit(2)) for details.
771    pub fn limit_fsize_soft_hard(self, soft: Byte, hard: Byte) -> Self {
772        let soft = std::cmp::min(soft, hard);
773        Self {
774            limit_fsize: Some(hard),
775            limit_fsize_soft: Some(soft),
776            ..self
777        }
778    }
779
780    /// Shorthand for `self.limit_fsize_soft_hard(lim, lim)`.
781    pub fn limit_fsize(self, lim: Byte) -> Self {
782        self.limit_fsize_soft_hard(lim, lim)
783    }
784
785    /// Set soft and hard limits of the maximum size in bytes of files that
786    /// the process may create.
787    ///
788    /// Any setting exceeding [u64::MAX] bytes will be trimmed to
789    /// [u64::MAX] bytes silently.  And, if `soft` is greater than `hard`,
790    /// it will be trimmed to `hard` silently.
791    ///
792    /// Read `LimitCORE=` in [systemd.exec(5)](man:systemd.exec(5)) and
793    /// `RLIMIT_CORE` in [prlimit(2)](man:prlimit(2)) for details.
794    pub fn limit_core_soft_hard(self, soft: Byte, hard: Byte) -> Self {
795        let soft = std::cmp::min(soft, hard);
796        Self {
797            limit_core: Some(hard),
798            limit_core_soft: Some(soft),
799            ..self
800        }
801    }
802
803    /// Shorthand for `self.limit_fsize_soft_hard(lim, lim)`.
804    pub fn limit_core(self, lim: Byte) -> Self {
805        self.limit_core_soft_hard(lim, lim)
806    }
807
808    /// Set soft and hard limits of the number of threads for the real user
809    /// ID of the process.
810    ///
811    /// If `soft` is greater than `hard`, it will be trimmed to `hard`
812    /// silently.
813    ///
814    /// Read `LimitNPROC=` in [systemd.exec(5)](man:systemd.exec(5)) and
815    /// `RLIMIT_NPROC` in [prlimit(2)](man:prlimit(2)) for details.
816    pub fn limit_nproc_soft_hard(self, soft: NonZeroU64, hard: NonZeroU64) -> Self {
817        let soft = std::cmp::min(soft, hard);
818        Self {
819            limit_nproc: Some(hard.into()),
820            limit_nproc_soft: Some(soft.into()),
821            ..self
822        }
823    }
824
825    /// Shorthand for `self.limit_nproc_soft_hard(lim, lim)`.
826    pub fn limit_nproc(self, lim: NonZeroU64) -> Self {
827        self.limit_nproc_soft_hard(lim, lim)
828    }
829
830    /// Set **the value one greater than** soft and hard limits of the
831    /// number of file descriptors opened by the process.
832    ///
833    /// If `soft` is greater than `hard`, it will be trimmed to `hard`
834    /// silently.
835    ///
836    /// Read `LimitNOFILE=` in [systemd.exec(5)](man:systemd.exec(5)) and
837    /// `RLIMIT_NOFILE` in [prlimit(2)](man:prlimit(2)) for details.
838    pub fn limit_nofile_soft_hard(self, soft: NonZeroU64, hard: NonZeroU64) -> Self {
839        let soft = std::cmp::min(soft, hard);
840        Self {
841            limit_nofile: Some(hard.into()),
842            limit_nofile_soft: Some(soft.into()),
843            ..self
844        }
845    }
846
847    /// Shorthand for `self.limit_nofile_soft_hard(lim, lim)`.
848    pub fn limit_nofile(self, lim: NonZeroU64) -> Self {
849        self.limit_nofile_soft_hard(lim, lim)
850    }
851
852    /// Set the soft and hard limit on the size of the process stack.
853    ///
854    /// If `soft` is greater than `hard`, it will be trimmed to `hard`
855    /// silently.
856    ///
857    /// Read `LimitSTACK=` in [systemd.exec(5)](man:systemd.exec(5)) and
858    /// `RLIMIT_STACK` in [prlimit(2)](man:prlimit(2)) for details.
859    pub fn limit_stack_soft_hard(self, soft: Byte, hard: Byte) -> Self {
860        let soft = std::cmp::min(soft, hard);
861        Self {
862            limit_stack: Some(hard),
863            limit_stack_soft: Some(soft),
864            ..self
865        }
866    }
867
868    /// Shorthand for `self.limit_stack_soft_hard(lim, lim)`.
869    pub fn limit_stack(self, lim: Byte) -> Self {
870        self.limit_stack_soft_hard(lim, lim)
871    }
872
873    /// Controls where file descriptor 0 (STDIN) of the executed processes
874    /// is connected to.
875    ///
876    /// Read [InputSpec] and `StandardInput=` in
877    /// [systemd.exec(5)](man:systemd.exec(5)) for details.
878    ///
879    /// The default is [InputSpec::null()].
880    pub fn stdin(self, spec: InputSpec) -> Self {
881        Self {
882            stdin: Some(spec),
883            ..self
884        }
885    }
886
887    /// Controls where file descriptor 1 (STDOUT) of the executed processes
888    /// is connected to.
889    ///
890    /// Read [OutputSpec] and `StandardOutput=` in
891    /// [systemd.exec(5)](man:systemd.exec(5)) for details.
892    ///
893    /// The default depends on system configuration.
894    pub fn stdout(self, spec: OutputSpec) -> Self {
895        Self {
896            stdout: Some(spec),
897            ..self
898        }
899    }
900
901    /// Controls where file descriptor 2 (STDERR) of the executed processes
902    /// is connected to.
903    ///
904    /// Read [OutputSpec] and `StandardError=` in
905    /// [systemd.exec(5)](man:systemd.exec(5)) for details.
906    ///
907    /// The default depends on system configuration.
908    pub fn stderr(self, spec: OutputSpec) -> Self {
909        Self {
910            stderr: Some(spec),
911            ..self
912        }
913    }
914
915    /// Sets the working directory for executed processes.
916    ///
917    /// Read `WorkingDirectory=` in
918    /// [systemd.exec(5)](man:systemd.exec(5)) for details.
919    ///
920    /// This setting is unavailable with the feature `systemd_227`
921    /// disabled.
922    #[cfg(feature = "systemd_227")]
923    pub fn current_dir<P: AsRef<str>>(self, path: P) -> Self {
924        Self {
925            current_dir: Some(path.as_ref().to_owned()),
926            ..self
927        }
928    }
929
930    /// Read [ProtectProc] for details.
931    ///
932    /// This setting will be unavailable if the feature `systemd_247` is
933    /// disabled.
934    #[cfg(feature = "systemd_247")]
935    pub fn protect_proc(self, x: ProtectProc) -> Self {
936        Self {
937            protect_proc: x.0,
938            ..self
939        }
940    }
941
942    /// Put the transient service into a slice.
943    ///
944    /// Read `Slice=` in
945    /// [systemd.resource-control(5)](man:systemd.resource-control(5))
946    /// for details.
947    pub fn slice<S: AsRef<str>>(self, slice: S) -> Self {
948        Self {
949            slice: Some(slice.as_ref().to_owned()),
950            ..self
951        }
952    }
953
954    /// Sets up a new user namespace for the executed processes and
955    /// configures a minimal user and group mapping.
956    ///
957    /// Read `PrivateUsers=` in [systemd.exec(5)](man:systemd.exec(5))
958    /// for details.
959    ///
960    /// This setting is unavailable with the feature `systemd_232`
961    /// disabled.
962    #[cfg(feature = "systemd_232")]
963    pub fn private_users(self) -> Self {
964        Self {
965            private_users: true,
966            ..self
967        }
968    }
969
970    /// Configure the time to wait for the service itself to stop.
971    /// If the service doesn't terminate in the specified time, it will be
972    /// forcibly terminated by SIGKILL.
973    ///
974    /// A [Duration] exceeding [u64::MAX] microseconds is trimmed to
975    /// [u64::MAX] microseconds silently.
976    ///
977    /// Read `TimeoutStopSec=` in
978    /// [systemd.service(5)](man:systemd.service(5)) for details.
979    ///
980    /// This setting will be unavailable with the feature `systemd_188`
981    /// disabled.
982    #[cfg(feature = "systemd_188")]
983    pub fn timeout_stop(self, d: Duration) -> Self {
984        Self {
985            timeout_stop: Some(d),
986            ..self
987        }
988    }
989
990    /// Specify CPU scheduling policy and real-time priority.
991    /// See [CpuScheduling] for details.
992    pub fn cpu_schedule(self, cpu_sched: CpuScheduling) -> Self {
993        Self { cpu_sched, ..self }
994    }
995
996    /// See the same `/tmp/`, `/var/tmp/`, IPC namespace, and network
997    /// namespace as one unit that is already started and specified with
998    /// this setting.  If this setting is used multiple times and the
999    /// specified units are started but not sharing their namespace, then
1000    /// it is not defined which namespace is joined.  Note that this
1001    /// setting only has an effect if [Self::private_network],
1002    /// [Self::private_ipc], and/or [Identity::dynamic] is in effect for
1003    /// both this unit and the unit whose namespace is joined.
1004    ///
1005    /// Read `JoinsNamespaceOf=` in [systemd.unit(5)](man:systemd.unit(5))
1006    /// for details.
1007    ///
1008    /// This setting is unavailable with the feature `systemd_227`
1009    /// disabled.
1010    #[cfg(feature = "systemd_227")]
1011    pub fn joins_namespace_of<S: AsRef<str>>(mut self, unit: S) -> Self {
1012        self.joins_namespace_of.push(unit.as_ref().into());
1013        self
1014    }
1015
1016    /// Start the transient service.
1017    pub async fn start<'a>(mut self) -> Result<StartedRun<'a>> {
1018        let mut argv = vec![&self.path];
1019        argv.extend(&self.args);
1020
1021        let exec_start = vec![(&self.path, &argv, false)];
1022
1023        let mut properties = vec![
1024            ("Description", Value::from(&self.path)),
1025            ("ExecStart", Value::from(&exec_start)),
1026            ("AddRef", Value::from(true)),
1027        ];
1028
1029        if self.collect_on_fail {
1030            let prop = ("CollectMode", Value::from("inactive-or-failed"));
1031            properties.push(prop);
1032        }
1033
1034        for (k, v) in [
1035            ("WorkingDirectory", self.current_dir),
1036            ("Slice", self.slice),
1037        ] {
1038            if let Some(v) = v {
1039                properties.push((k, Value::from(v)));
1040            }
1041        }
1042
1043        let join_ns = self.joins_namespace_of;
1044        if !join_ns.is_empty() {
1045            properties.push(("JoinsNamespaceOf", Value::from(join_ns)));
1046        }
1047
1048        let proc = match self.protect_proc {
1049            ProtectProcInternal::NoAccess => Some("noaccess"),
1050            ProtectProcInternal::Invisible => Some("invisible"),
1051            ProtectProcInternal::Ptraceable => Some("ptraceable"),
1052            ProtectProcInternal::Default => None,
1053        };
1054
1055        if let Some(v) = proc {
1056            properties.push(("ProtectProc", Value::from(v)));
1057        }
1058
1059        let identity_prop = identity::unit_properties(&self.identity);
1060        properties.extend(identity_prop);
1061
1062        for (k, v) in [
1063            ("RuntimeMaxUSec", &self.runtime_max),
1064            ("TimeoutStopUSec", &self.timeout_stop),
1065        ] {
1066            if let Some(d) = v {
1067                let usec = u64::try_from(d.as_micros()).unwrap_or(u64::MAX);
1068                properties.push((k, Value::from(usec)));
1069            }
1070        }
1071
1072        if !self.allowed_cpus.is_empty() {
1073            let mut cpu_set = vec![];
1074            for &cpu in &self.allowed_cpus {
1075                let (x, y) = (cpu / 8, cpu % 8);
1076                if cpu_set.len() <= x {
1077                    cpu_set.resize(x + 1, 0u8);
1078                }
1079                cpu_set[x] |= 1 << y;
1080            }
1081            properties.push(("AllowedCPUs", Value::from(cpu_set)));
1082        }
1083
1084        for (k, v) in [
1085            ("LimitNPROC", &self.limit_nproc),
1086            ("LimitNPROCSoft", &self.limit_nproc_soft),
1087            ("LimitNOFILE", &self.limit_nofile),
1088            ("LimitNOFILESoft", &self.limit_nofile_soft),
1089        ] {
1090            if let Some(v) = v {
1091                properties.push((k, Value::from(v)))
1092            }
1093        }
1094
1095        let memory_max_name = if cfg!(feature = "systemd_231") {
1096            "MemoryMax"
1097        } else {
1098            "MemoryLimit"
1099        };
1100
1101        for (k, v) in [
1102            (memory_max_name, &self.memory_max),
1103            ("MemorySwapMax", &self.memory_swap_max),
1104            ("LimitFSIZE", &self.limit_fsize),
1105            ("LimitFSIZESoft", &self.limit_fsize_soft),
1106            ("LimitSTACK", &self.limit_stack),
1107            ("LimitSTACKSoft", &self.limit_stack_soft),
1108            ("LimitCORE", &self.limit_core),
1109            ("LimitCORESoft", &self.limit_core_soft),
1110        ] {
1111            if let Some(v) = v {
1112                properties.push((k, Value::from(v.as_u64())))
1113            }
1114        }
1115
1116        if let Some(v) = self.cpu_quota {
1117            let v = std::cmp::min(v, u64::MAX / 10000);
1118            properties.push(("CPUQuotaPerSecUSec", Value::from(v * 10000)));
1119        }
1120
1121        for (k, v) in [
1122            ("PrivateNetwork", self.private_network),
1123            ("PrivateIPC", self.private_ipc),
1124            ("MountAPIVFS", self.mount_api_vfs),
1125            ("PrivateDevices", self.private_devices),
1126            ("NoNewPrivileges", self.no_new_privileges),
1127            ("PrivateUsers", self.private_users),
1128        ] {
1129            // Don't push false values as they may break on old Systemd.
1130            if v {
1131                properties.push((k, Value::from(true)))
1132            }
1133        }
1134
1135        let mut p_bind = vec![];
1136        let mut p_bind_ro = vec![];
1137        let mut p_image = vec![];
1138        let mut p_tmpfs = vec![];
1139        for mnt in self.mount.into_iter().map(|(x, y)| mount::marshal(x, y)) {
1140            use mount::MarshaledMount::*;
1141            match mnt {
1142                Bind(a, b, c, d) => p_bind.push((a, b, c, d)),
1143                BindReadOnly(a, b, c, d) => p_bind_ro.push((a, b, c, d)),
1144                Normal(a, b, c, d) => p_image.push((a, b, c, d)),
1145                Tmpfs(a, b) => p_tmpfs.push((a, b)),
1146            }
1147        }
1148
1149        if !p_bind.is_empty() {
1150            properties.push(("BindPaths", Value::from(p_bind)));
1151        }
1152
1153        if !p_bind_ro.is_empty() {
1154            properties.push(("BindReadOnlyPaths", Value::from(p_bind_ro)));
1155        }
1156
1157        if !p_image.is_empty() {
1158            properties.push(("MountImages", Value::from(p_image)));
1159        }
1160
1161        if !p_tmpfs.is_empty() {
1162            properties.push(("TemporaryFileSystem", Value::from(p_tmpfs)));
1163        }
1164
1165        let mut io_prop = vec![];
1166
1167        for (pfx, (sfx, val)) in [
1168            ("StandardInput", self.stdin.map(ioredirect::marshal_input)),
1169            (
1170                "StandardOutput",
1171                self.stdout.map(ioredirect::marshal_output),
1172            ),
1173            ("StandardError", self.stderr.map(ioredirect::marshal_output)),
1174        ]
1175        .into_iter()
1176        .filter_map(|(a, b)| Some(a).zip(b))
1177        {
1178            let key = pfx.to_owned() + sfx;
1179            io_prop.push((key, val))
1180        }
1181
1182        for (k, v) in io_prop.iter() {
1183            properties.push((k, Value::from(v)))
1184        }
1185
1186        let (policy, priority, reset_on_fork) = cpu_sched::marshal(self.cpu_sched);
1187
1188        for (k, v) in [
1189            ("CPUSchedulingPolicy", Value::from(policy)),
1190            ("CPUSchedulingResetOnFork", Value::from(reset_on_fork)),
1191        ] {
1192            properties.push((k, v));
1193        }
1194
1195        if let Some(v) = priority {
1196            properties.push(("CPUSchedulingPriority", Value::from(v)));
1197        }
1198
1199        let properties = properties.iter().map(|(x, y)| (*x, y)).collect::<Vec<_>>();
1200
1201        let bus = if identity::is_session(&self.identity) {
1202            Connection::session().await
1203        } else {
1204            Connection::system().await
1205        }
1206        .map_err(Error::DBusConnectionFail)?;
1207        if self.service_name.is_none() {
1208            self.service_name = Some(default_unit_name(&bus)?);
1209        }
1210        let unit_name = self.service_name.as_ref().unwrap();
1211        let unit_path = object_path_from_unit_name(unit_name)?;
1212
1213        // We must do this before really telling systemd to start the
1214        // service.  Or we may miss D-Bus signals, causing StartedRun::wait
1215        // to hang forever.  And this also prevents the start of the
1216        // transient service in case this fails.
1217        let (proxy, stream) = listen_unit_property_change(&bus, &unit_path).await?;
1218
1219        sd::SystemdManagerProxy::builder(&bus)
1220            .build()
1221            .await
1222            .expect("should not fail with hardcoded parameters in sd.rs")
1223            .start_transient_unit(unit_name, "fail", &properties, &[])
1224            .await
1225            .map_err(Error::StartFail)
1226            .map(|_| StartedRun { stream, proxy })
1227    }
1228}
1229
1230impl<'a> StartedRun<'a> {
1231    /// Wait until a [StartedRun] is finished.
1232    pub async fn wait(self) -> Result<FinishedRun> {
1233        let mut stream = self.stream;
1234        let mut has_job = false;
1235        let mut active_state = None;
1236        let no_job = Value::from((0u32, ObjectPath::try_from("/").unwrap()));
1237        use futures::stream::StreamExt;
1238        while let Some(ev) = stream.next().await {
1239            let changed = &ev
1240                .args()
1241                .map_err(Error::ParsePropertyChangeFail)?
1242                .changed_properties;
1243            if let Some(Value::Str(state)) = changed.get("ActiveState") {
1244                active_state = Some(state.as_str().to_owned());
1245            }
1246            if let Some(job) = changed.get("Job") {
1247                has_job = job != &no_job;
1248            }
1249            match (has_job, active_state.as_deref()) {
1250                (false, Some("inactive")) => break,
1251                (false, Some("failed")) => break,
1252                _ => {}
1253            }
1254        }
1255
1256        let iface = zbus_names::InterfaceName::try_from("org.freedesktop.systemd1.Unit")
1257            .expect("should not fail with hardcoded str");
1258
1259        let t0 = self
1260            .proxy
1261            .get(iface.as_ref(), "InactiveExitTimestampMonotonic")
1262            .await
1263            .map_err(Error::QueryPropertyFail)?;
1264
1265        let t1 = self
1266            .proxy
1267            .get(iface.as_ref(), "InactiveEnterTimestampMonotonic")
1268            .await
1269            .map_err(Error::QueryPropertyFail)?;
1270
1271        let time_usage_us = match (t0.downcast_ref(), t1.downcast_ref()) {
1272            (Ok(Value::U64(t0)), Ok(Value::U64(t1))) => t1 - t0,
1273            _ => {
1274                let t0 = Box::new(t0);
1275                let t1 = Box::new(t1);
1276                return Err(Error::TimeUsageFail("wall", t0, t1));
1277            }
1278        };
1279
1280        let failed = active_state.unwrap() == "failed";
1281        let wall_time_usage = Duration::from_micros(time_usage_us);
1282        Ok(FinishedRun {
1283            failed,
1284            wall_time_usage,
1285        })
1286    }
1287}
1288
1289impl FinishedRun {
1290    /// Check if the `FinishedRun` has failed.
1291    ///
1292    /// Read `SuccessExitStatus=` in
1293    /// [systemd.service(5)](man:systemd.service(5)) for details.
1294    pub fn is_failed(&self) -> bool {
1295        self.failed
1296    }
1297
1298    /// Get the usage of wall-clock time of the finished transient service.
1299    pub fn wall_time_usage(&self) -> Duration {
1300        self.wall_time_usage
1301    }
1302}