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}