rootless_run/
rootlesskit.rs

1//! A rootless backend that uses [rootlesskit].
2//!
3//! [rootlesskit]: https://github.com/rootless-containers/rootlesskit
4
5#[cfg(doc)]
6use std::path::Path;
7use std::{
8    fmt::Display,
9    path::PathBuf,
10    process::{Command, Output},
11};
12
13use log::debug;
14
15use crate::{
16    Error,
17    RootlessBackend,
18    RootlessOptions,
19    utils::{detect_virt, get_command},
20};
21
22/// The `rootlesskit` `--debug` option.
23const ARG_DEBUG: &str = "--debug";
24/// The `rootlesskit` `--copy-up` option.
25const ARG_COPY_UP: &str = "--copy-up";
26/// The `rootlesskit` `--copy-up-mode` option.
27const ARG_COPY_UP_MODE: &str = "--copy-up-mode";
28/// The `rootlesskit` `--propagation` option.
29const ARG_PROPAGATION: &str = "--propagation";
30/// The `rootlesskit` `--net` option.
31const ARG_NET: &str = "--net";
32/// The `rootlesskit` `--mtu` option.
33const ARG_MTU: &str = "--mtu";
34/// The `rootlesskit` `--cidr` option.
35const ARG_CIDR: &str = "--cidr";
36/// The `rootlesskit` `--ifname` option.
37const ARG_IFNAME: &str = "--ifname";
38/// The `rootlesskit` `--disable-host-loopback` option.
39const ARG_DISABLE_HOST_LOOPBACK: &str = "--disable-host-loopback";
40/// The `rootlesskit` `--ipv6` option.
41const ARG_IPV6: &str = "--ipv6";
42/// The `rootlesskit` `--detach-netns` option.
43const ARG_DETACH_NETNS: &str = "--detach-netns";
44/// The `rootlesskit` `--lxc-user-nic-binary` option.
45const ARG_LXC_USER_NIC_BINARY: &str = "--lxc-user-nic-binary";
46/// The `rootlesskit` `--lxc-user-nic-bridge` option.
47const ARG_LXC_USER_NIC_BRIDGE: &str = "--lxc-user-nic-bridge";
48/// The `rootlesskit` `--pasta-binary` option.
49const ARG_PASTA_BINARY: &str = "--pasta-binary";
50/// The `rootlesskit` `--slirp4netns-binary` option.
51const ARG_SLIRP4NETNS_BINARY: &str = "--slirp4netns-binary";
52/// The `rootlesskit` `--slirp4netns-sandbox` option.
53const ARG_SLIRP4NETNS_SANDBOX: &str = "--slirp4netns-sandbox";
54/// The `rootlesskit` `--slirp4netns-seccomp` option.
55const ARG_SLIRP4NETNS_SECCOMP: &str = "--slirp4netns-seccomp";
56/// The `rootlesskit` `--vpnkit-binary` option.
57const ARG_VPNKIT_BINARY: &str = "--vpnkit-binary";
58/// The `rootlesskit` `--port-driver` option.
59const ARG_PORT_DRIVER: &str = "--port-driver";
60/// The `rootlesskit` `--publish` option.
61const ARG_PUBLISH: &str = "--publish";
62/// The `rootlesskit` `--pidns` option.
63const ARG_PIDNS: &str = "--pidns";
64/// The `rootlesskit` `--cgroupns` option.
65const ARG_CGROUPNS: &str = "--cgroupns";
66/// The `rootlesskit` `--utsns` option.
67const ARG_UTSNS: &str = "--utsns";
68/// The `rootlesskit` `--ipcns` option.
69const ARG_IPCNS: &str = "--ipcns";
70/// The `rootlesskit` `--reaper` option.
71const ARG_REAPER: &str = "--reaper";
72/// The `rootlesskit` `--evacuate-cgroup2` option.
73const ARG_EVACUATE_CGROUP2: &str = "--evacuate-cgroup2";
74/// The `rootlesskit` `--state-dir` option.
75const ARG_STATE_DIR: &str = "--state-dir";
76/// The `rootlesskit` `--subid-source` option.
77const ARG_SUBID_SOURCE: &str = "--subid-source";
78
79/// The mode used for [rootlesskit]'s `--copy-up` option.
80///
81/// Corresponds to the value passed to the `--copy-up-mode` option.
82///
83/// [rootlesskit]: https://github.com/rootless-containers/rootlesskit
84#[derive(Clone, Copy, Debug, Default, strum::Display, Eq, PartialEq)]
85#[strum(serialize_all = "lowercase")]
86pub enum CopyUpMode {
87    /// The `tmpfs+symlink` mode.
88    #[default]
89    #[strum(serialize = "tmpfs+symlink")]
90    TmpfsAndSymlink,
91}
92
93/// The propagation used for [rootlesskit]'s `--copy-up` option.
94///
95/// Corresponds to the value passed to the `--propagation` option.
96///
97/// [rootlesskit]: https://github.com/rootless-containers/rootlesskit
98#[derive(Clone, Copy, Debug, Default, strum::Display, Eq, PartialEq)]
99#[strum(serialize_all = "lowercase")]
100pub enum Propagation {
101    /// The `rprivate` propagation.
102    #[default]
103    Rprivate,
104
105    /// The `rslave` propagation.
106    Rslave,
107}
108
109/// A network driver used by [rootlesskit].
110///
111/// Corresponds to the value passed to the `--net` option.
112///
113/// [rootlesskit]: https://github.com/rootless-containers/rootlesskit
114#[derive(Clone, Copy, Debug, Default, strum::Display, Eq, PartialEq)]
115#[strum(serialize_all = "lowercase")]
116pub enum Net {
117    /// The `host` network driver.
118    #[default]
119    Host,
120
121    /// No (`none`) network driver.
122    None,
123
124    /// The `pasta` network driver.
125    Pasta,
126
127    /// The `slirp4netns` network driver.
128    Slirp4netns,
129
130    /// The `vpnkit` network driver.
131    Vpnkit,
132
133    /// The `lxc-user-nic` network driver.
134    #[strum(serialize = "lxc-user-nic")]
135    LxcUserNic,
136}
137
138/// An option that may be on, off or automatic.
139#[derive(Clone, Copy, Debug, strum::Display, Eq, PartialEq)]
140#[strum(serialize_all = "lowercase")]
141pub enum AutoOption {
142    /// The option is on.
143    True,
144
145    /// The option is off.
146    False,
147
148    /// The option is chosen automatically.
149    Auto,
150}
151
152/// A port driver for the non-host network of [rootlesskit].
153///
154/// [rootlesskit]: https://github.com/rootless-containers/rootlesskit
155#[derive(Clone, Copy, Debug, Default, strum::Display, Eq, PartialEq)]
156#[strum(serialize_all = "lowercase")]
157pub enum PortDriver {
158    /// No (`none`) port driver.
159    #[default]
160    None,
161
162    /// The `implicit` port driver (for [`Net::Pasta`]).
163    Implicit,
164
165    /// The `builtin` port driver.
166    Builtin,
167
168    /// The `slirp4netns` port driver.
169    Slirp4netns,
170}
171
172/// The source of subids for [rootlesskit].
173///
174/// [rootlesskit]: https://github.com/rootless-containers/rootlesskit
175#[derive(Clone, Copy, Debug, Default, strum::Display, Eq, PartialEq)]
176#[strum(serialize_all = "lowercase")]
177pub enum SubIdSource {
178    /// The source of subids is chosen automatically.
179    #[default]
180    Auto,
181
182    /// The subids are retrieved using [getsubids].
183    ///
184    /// [getsubids]: https://man.archlinux.org/man/getsubids.1
185    Dynamic,
186
187    /// The subids are retrieved from `/etc/sub{uid,gid}`.
188    Static,
189}
190
191/// Options for [rootlesskit].
192///
193/// [rootlesskit]: https://github.com/rootless-containers/rootlesskit
194#[derive(Clone, Debug, Default, Eq, PartialEq)]
195pub struct RootlesskitOptions {
196    /// Whether to use debug mode.
197    ///
198    /// Corresponds to `rootlesskit`'s `--debug` option.
199    pub debug: bool,
200
201    /// A list of filesystems to mount and copy-up the contents from.
202    ///
203    /// Corresponds to `rootlesskit`'s `--copy-up` option.
204    pub copy_up: Vec<String>,
205
206    /// The mode to use for [`RootlesskitOptions::copy_up`].
207    ///
208    /// Corresponds to `rootlesskit`'s `--copy-up-mode` option.
209    pub copy_up_mode: Option<CopyUpMode>,
210
211    /// The propagation to use for [`RootlesskitOptions::copy_up`].
212    ///
213    /// Corresponds to `rootlesskit`'s `--propagation` option.
214    pub propagation: Option<Propagation>,
215
216    /// The network driver to use.
217    ///
218    /// Corresponds to `rootlesskit`'s `--net` option.
219    pub net: Option<Net>,
220
221    /// The MTU to use for the network driver.
222    ///
223    /// Defaults to `65520` for [`Net::Pasta`] and [`Net::Slirp4netns`], `1500` for all other.
224    ///
225    /// Corresponds to `rootlesskit`'s `--mtu` option.
226    pub mtu: Option<usize>,
227
228    /// The CIDR to use for [`Net::Pasta`] and [`Net::Slirp4netns`].
229    ///
230    /// Defaults to `10.0.2.0/24` for [`Net::Pasta`] and [`Net::Slirp4netns`].
231    ///
232    /// Corresponds to `rootlesskit`'s `--cidr` option.
233    pub cidr: Option<String>,
234
235    /// The network interface name to use.
236    ///
237    /// Defaults to `tap0` for [`Net::Pasta`], [`Net::Slirp4netns`] and [`Net::Vpnkit`], `eth0` for
238    /// [`Net::LxcUserNic`].
239    ///
240    /// Corresponds to `rootlesskit`'s `--ifname` option.
241    pub ifname: Option<String>,
242
243    /// Whether to prohibit connecting to `127.0.0.1:*` on the host.
244    ///
245    /// Corresponds to `rootlesskit`'s `--disable-host-loopback` option.
246    pub disable_host_loopback: bool,
247
248    /// Whether to enable IPv6 routing.
249    ///
250    /// Requires `net` to either be set to [`Net::Pasta`] or [`Net::Slirp4netns`].
251    ///
252    /// Corresponds to `rootlesskit`'s `--ipv6` option.
253    pub ipv6: bool,
254
255    /// Whether to detach the network namespaces.
256    ///
257    /// Corresponds to `rootlesskit`'s `--detach-netns` option.
258    pub detach_netns: bool,
259
260    /// An alternative path for the `lxc-user-nic` binary.
261    ///
262    /// Corresponds to `rootlesskit`'s `--lxc-user-nic-binary` option.
263    pub lxc_user_nic_binary: Option<PathBuf>,
264
265    /// An alternative name for the `lxc-user-bridge` name.
266    ///
267    /// Corresponds to `rootlesskit`'s `--lxc-user-nic-bridge` option.
268    pub lxc_user_nic_bridge: Option<String>,
269
270    /// An alternative path for the `pasta` binary.
271    ///
272    /// Corresponds to `rootlesskit`'s `--pasta-binary` option.
273    pub pasta_binary: Option<PathBuf>,
274
275    /// An alternative path for the `slirp4netns` binary.
276    ///
277    /// Corresponds to `rootlesskit`'s `--slirp4netns-binary` option.
278    pub slirp4netns_binary: Option<PathBuf>,
279
280    /// Whether to enable `slirp4netns` sandbox.
281    ///
282    /// Corresponds to `rootlesskit`'s `--slirp4netns-sandbox` option.
283    pub slirp4netns_sandbox: Option<AutoOption>,
284
285    /// Whether to enable `slirp4netns` seccomp.
286    ///
287    /// Corresponds to `rootlesskit`'s `--slirp4netns-seccomp` option.
288    pub slirp4netns_seccomp: Option<AutoOption>,
289
290    /// An alternative path for the `vpnkit` binary.
291    ///
292    /// Corresponds to `rootlesskit`'s `--vpnkit-binary` option.
293    pub vpnkit_binary: Option<PathBuf>,
294
295    /// A port driver to use for the non-host network.
296    ///
297    /// Corresponds to `rootlesskit`'s `--port-driver` option.
298    pub port_driver: Option<PortDriver>,
299
300    /// A list of ports to publish.
301    ///
302    /// Corresponds to `rootlesskit`'s `-p`/`--publish` option.
303    pub publish: Vec<String>,
304
305    /// Whether to create a PID namespace.
306    ///
307    /// Corresponds to `rootlesskit`'s `--pidns` option.
308    pub pidns: bool,
309
310    /// Whether to create a cgroup namespace.
311    ///
312    /// Corresponds to `rootlesskit`'s `--cgroupns` option.
313    pub cgroupns: bool,
314
315    /// Whether to create a UTS namespace.
316    ///
317    /// Corresponds to `rootlesskit`'s `--utsns` option.
318    pub utsns: bool,
319
320    /// Whether to create an IPC namespace.
321    ///
322    /// Corresponds to `rootlesskit`'s `--ipcns` option.
323    pub ipcns: bool,
324
325    /// Whether to enable process reaper.
326    ///
327    /// Requires [`RootlesskitOptions::pidns`] to be set to `true`.
328    ///
329    /// Corresponds to `rootlesskit`'s `--reaper` option.
330    pub reaper: Option<AutoOption>,
331
332    /// A cgroup2 subgroup to evacuate processes into.
333    ///
334    /// Requires [`RootlesskitOptions::pidns`] and [`RootlesskitOptions::cgroupns`] to be set to
335    /// `true`.
336    ///
337    /// Corresponds to `rootlesskit`'s `--evacuate-cgroup2` option.
338    pub evacuate_cgroup2: Option<String>,
339
340    /// A state directory to use.
341    ///
342    /// Corresponds to `rootlesskit`'s `--state-dir` option.
343    pub state_dir: Option<PathBuf>,
344
345    /// The source of subids.
346    ///
347    /// Corresponds to `rootlesskit`'s `--subid-source` option.
348    pub subid_source: Option<SubIdSource>,
349}
350
351impl Display for RootlesskitOptions {
352    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
353        write!(f, "{}", self.to_vec().join(" "))
354    }
355}
356
357impl RootlessOptions for RootlesskitOptions {
358    /// Returns the options as a [`String`] [`Vec`].
359    ///
360    /// # Notes
361    ///
362    /// All [`PathBuf`] options are represented using [`Path::to_string_lossy`].
363    fn to_vec(&self) -> Vec<String> {
364        let mut options = Vec::new();
365        if self.debug {
366            options.push(ARG_DEBUG.to_string());
367        }
368        for option in self.copy_up.iter() {
369            options.push(ARG_COPY_UP.to_string());
370            options.push(option.to_string());
371        }
372        if let Some(option) = self.copy_up_mode {
373            options.push(ARG_COPY_UP_MODE.to_string());
374            options.push(option.to_string());
375        }
376        if let Some(option) = self.propagation {
377            options.push(ARG_PROPAGATION.to_string());
378            options.push(option.to_string());
379        }
380        if let Some(option) = self.net {
381            options.push(ARG_NET.to_string());
382            options.push(option.to_string());
383        }
384        if let Some(option) = self.mtu {
385            options.push(ARG_MTU.to_string());
386            options.push(option.to_string());
387        }
388        if let Some(option) = self.cidr.as_ref() {
389            options.push(ARG_CIDR.to_string());
390            options.push(option.to_string());
391        }
392        if let Some(option) = self.ifname.as_ref() {
393            options.push(ARG_IFNAME.to_string());
394            options.push(option.to_string());
395        }
396        if self.disable_host_loopback {
397            options.push(ARG_DISABLE_HOST_LOOPBACK.to_string());
398        }
399        if self.ipv6 {
400            options.push(ARG_IPV6.to_string());
401        }
402        if self.detach_netns {
403            options.push(ARG_DETACH_NETNS.to_string());
404        }
405        if let Some(option) = self.lxc_user_nic_binary.as_ref() {
406            options.push(ARG_LXC_USER_NIC_BINARY.to_string());
407            options.push(option.to_string_lossy().to_string());
408        }
409        if let Some(option) = self.lxc_user_nic_bridge.as_ref() {
410            options.push(ARG_LXC_USER_NIC_BRIDGE.to_string());
411            options.push(option.to_string());
412        }
413        if let Some(option) = self.pasta_binary.as_ref() {
414            options.push(ARG_PASTA_BINARY.to_string());
415            options.push(option.to_string_lossy().to_string());
416        }
417        if let Some(option) = self.slirp4netns_binary.as_ref() {
418            options.push(ARG_SLIRP4NETNS_BINARY.to_string());
419            options.push(option.to_string_lossy().to_string());
420        }
421        if let Some(option) = self.slirp4netns_sandbox {
422            options.push(ARG_SLIRP4NETNS_SANDBOX.to_string());
423            options.push(option.to_string());
424        }
425        if let Some(option) = self.slirp4netns_seccomp {
426            options.push(ARG_SLIRP4NETNS_SECCOMP.to_string());
427            options.push(option.to_string());
428        }
429        if let Some(option) = self.vpnkit_binary.as_ref() {
430            options.push(ARG_VPNKIT_BINARY.to_string());
431            options.push(option.to_string_lossy().to_string());
432        }
433        if let Some(option) = self.port_driver {
434            options.push(ARG_PORT_DRIVER.to_string());
435            options.push(option.to_string());
436        }
437        for option in self.publish.iter() {
438            options.push(ARG_PUBLISH.to_string());
439            options.push(option.to_string());
440        }
441        if self.pidns {
442            options.push(ARG_PIDNS.to_string());
443        }
444        if self.cgroupns {
445            options.push(ARG_CGROUPNS.to_string());
446        }
447        if self.utsns {
448            options.push(ARG_UTSNS.to_string());
449        }
450        if self.ipcns {
451            options.push(ARG_IPCNS.to_string());
452        }
453        if let Some(option) = self.reaper {
454            options.push(ARG_REAPER.to_string());
455            options.push(option.to_string());
456        }
457        if let Some(option) = self.evacuate_cgroup2.as_ref() {
458            options.push(ARG_EVACUATE_CGROUP2.to_string());
459            options.push(option.to_string());
460        }
461        if let Some(option) = self.state_dir.as_ref() {
462            options.push(ARG_STATE_DIR.to_string());
463            options.push(option.to_string_lossy().to_string());
464        }
465        if let Some(option) = self.subid_source {
466            options.push(ARG_SUBID_SOURCE.to_string());
467            options.push(option.to_string());
468        }
469
470        options
471    }
472}
473
474/// A rootless backend for running commands using [rootlesskit].
475///
476/// [rootlesskit]: https://github.com/rootless-containers/rootlesskit
477#[derive(Clone, Debug)]
478pub struct RootlesskitBackend(RootlesskitOptions);
479
480impl RootlessBackend<RootlesskitOptions> for RootlesskitBackend {
481    type Err = Error;
482
483    /// Creates a new [`RootlesskitBackend`] from [`RootlesskitOptions`].
484    fn new(options: RootlesskitOptions) -> Self {
485        debug!("Create a new rootlesskit backend with options \"{options}\"");
486        Self(options)
487    }
488
489    /// Returns the [`RootlesskitOptions`] used by the [`RootlesskitBackend`].
490    fn options(&self) -> &RootlesskitOptions {
491        &self.0
492    }
493
494    /// Runs a command using [rootlesskit] and returns its [`Output`].
495    ///
496    /// # Errors
497    ///
498    /// Returns an error if
499    ///
500    /// - the [rootlesskit] command cannot be found,
501    /// - or the provided `command` cannot be run using [rootlesskit].
502    ///
503    /// [rootlesskit]: https://github.com/rootless-containers/rootlesskit
504    fn run(&self, cmd: &[&str]) -> Result<Output, Self::Err> {
505        {
506            let virt = detect_virt()?;
507            if virt.uses_namespaces() {
508                return Err(Error::NamespacesInContainer { runtime: virt });
509            }
510        }
511
512        let command_name = get_command("rootlesskit")?;
513        let mut command = Command::new(command_name);
514
515        // Add all options to rootless as arguments.
516        if self.0.debug {
517            command.arg(ARG_DEBUG);
518        }
519        for option in self.0.copy_up.iter() {
520            command.arg(ARG_COPY_UP);
521            command.arg(option);
522        }
523        if let Some(option) = self.0.copy_up_mode {
524            command.arg(ARG_COPY_UP_MODE);
525            command.arg(option.to_string());
526        }
527        if let Some(option) = self.0.propagation {
528            command.arg(ARG_PROPAGATION);
529            command.arg(option.to_string());
530        }
531        if let Some(option) = self.0.net {
532            command.arg(ARG_NET);
533            command.arg(option.to_string());
534        }
535        if let Some(option) = self.0.mtu {
536            command.arg(ARG_MTU);
537            command.arg(option.to_string());
538        }
539        if let Some(option) = self.0.cidr.as_ref() {
540            command.arg(ARG_CIDR);
541            command.arg(option);
542        }
543        if let Some(option) = self.0.ifname.as_ref() {
544            command.arg(ARG_IFNAME);
545            command.arg(option);
546        }
547        if self.0.disable_host_loopback {
548            command.arg(ARG_DISABLE_HOST_LOOPBACK);
549        }
550        if self.0.ipv6 {
551            command.arg(ARG_IPV6);
552        }
553        if self.0.detach_netns {
554            command.arg(ARG_DETACH_NETNS);
555        }
556        if let Some(option) = self.0.lxc_user_nic_binary.as_ref() {
557            command.arg(ARG_LXC_USER_NIC_BINARY);
558            command.arg(option);
559        }
560        if let Some(option) = self.0.lxc_user_nic_bridge.as_ref() {
561            command.arg(ARG_LXC_USER_NIC_BRIDGE);
562            command.arg(option);
563        }
564        if let Some(option) = self.0.pasta_binary.as_ref() {
565            command.arg(ARG_PASTA_BINARY);
566            command.arg(option);
567        }
568        if let Some(option) = self.0.slirp4netns_binary.as_ref() {
569            command.arg(ARG_SLIRP4NETNS_BINARY);
570            command.arg(option);
571        }
572        if let Some(option) = self.0.slirp4netns_sandbox {
573            command.arg(ARG_SLIRP4NETNS_SANDBOX);
574            command.arg(option.to_string());
575        }
576        if let Some(option) = self.0.slirp4netns_seccomp {
577            command.arg(ARG_SLIRP4NETNS_SECCOMP);
578            command.arg(option.to_string());
579        }
580        if let Some(option) = self.0.vpnkit_binary.as_ref() {
581            command.arg(ARG_VPNKIT_BINARY);
582            command.arg(option);
583        }
584        if let Some(option) = self.0.port_driver {
585            command.arg(ARG_PORT_DRIVER);
586            command.arg(option.to_string());
587        }
588        for option in self.0.publish.iter() {
589            command.arg(ARG_PUBLISH);
590            command.arg(option);
591        }
592        if self.0.pidns {
593            command.arg(ARG_PIDNS);
594        }
595        if self.0.cgroupns {
596            command.arg(ARG_CGROUPNS);
597        }
598        if self.0.utsns {
599            command.arg(ARG_UTSNS);
600        }
601        if self.0.ipcns {
602            command.arg(ARG_IPCNS);
603        }
604        if let Some(option) = self.0.reaper {
605            command.arg(ARG_REAPER);
606            command.arg(option.to_string());
607        }
608        if let Some(option) = self.0.evacuate_cgroup2.as_ref() {
609            command.arg(ARG_EVACUATE_CGROUP2);
610            command.arg(option);
611        }
612        if let Some(option) = self.0.state_dir.as_ref() {
613            command.arg(ARG_STATE_DIR);
614            command.arg(option);
615        }
616        if let Some(option) = self.0.subid_source {
617            command.arg(ARG_SUBID_SOURCE);
618            command.arg(option.to_string());
619        }
620
621        // Add input cmd as arguments to rootlesskit.
622        for command_component in cmd.iter() {
623            command.arg(command_component);
624        }
625
626        debug!("Run rootless command: {command:?}");
627
628        command
629            .output()
630            .map_err(|source| crate::Error::CommandExec {
631                command: format!("{command:?}"),
632                source,
633            })
634    }
635}
636
637#[cfg(test)]
638mod tests {
639    use rstest::rstest;
640
641    use super::*;
642
643    /// Ensures that [`RootlesskitOptions`] are constructed as [`String`] [`Vec`] properly.
644    #[rstest]
645    #[case::all_options(
646        RootlesskitOptions{
647    debug: true,
648    copy_up: vec!["/etc".to_string(), "/usr".to_string()],
649    copy_up_mode: Some(CopyUpMode::default()),
650    propagation: Some(Propagation::default()),
651    net: Some(Net::Pasta),
652    mtu: Some(1500),
653    cidr: Some("10.0.1.0/24".to_string()),
654    ifname: Some("tap1".to_string()),
655    disable_host_loopback: true,
656    ipv6: true,
657    detach_netns: true,
658    lxc_user_nic_binary: Some(PathBuf::from("/usr/local/bin/lxc-user-nic")),
659    lxc_user_nic_bridge: Some("lxcbr1".to_string()),
660    pasta_binary: Some(PathBuf::from("/usr/local/bin/pasta")),
661    slirp4netns_binary: Some(PathBuf::from("/usr/local/bin/slirp4netns")),
662    slirp4netns_sandbox: Some(AutoOption::Auto),
663    slirp4netns_seccomp: Some(AutoOption::Auto),
664    vpnkit_binary: Some(PathBuf::from("/usr/local/bin/vpnkit")),
665    port_driver: Some(PortDriver::Implicit),
666    publish: vec![
667        "127.0.0.1:8080:80/tcp".to_string(),
668        "127.0.0.1:8443:443/tcp".to_string(),
669    ],
670    pidns: true,
671    cgroupns: true,
672    utsns: true,
673    ipcns: true,
674    reaper: Some(AutoOption::Auto),
675    evacuate_cgroup2: Some("testgroup".to_string()),
676    state_dir: Some(PathBuf::from("/var/foo")),
677    subid_source: Some(SubIdSource::Dynamic),
678        },
679        vec![
680            ARG_DEBUG.to_string(),
681            ARG_COPY_UP.to_string(),
682            "/etc".to_string(),
683            ARG_COPY_UP.to_string(),
684            "/usr".to_string(),
685            ARG_COPY_UP_MODE.to_string(),
686            "tmpfs+symlink".to_string(),
687            ARG_PROPAGATION.to_string(),
688            "rprivate".to_string(),
689            ARG_NET.to_string(),
690            "pasta".to_string(),
691            ARG_MTU.to_string(),
692            "1500".to_string(),
693            ARG_CIDR.to_string(),
694            "10.0.1.0/24".to_string(),
695            ARG_IFNAME.to_string(),
696            "tap1".to_string(),
697            ARG_DISABLE_HOST_LOOPBACK.to_string(),
698            ARG_IPV6.to_string(),
699            ARG_DETACH_NETNS.to_string(),
700            ARG_LXC_USER_NIC_BINARY.to_string(),
701            "/usr/local/bin/lxc-user-nic".to_string(),
702            ARG_LXC_USER_NIC_BRIDGE.to_string(),
703            "lxcbr1".to_string(),
704            ARG_PASTA_BINARY.to_string(),
705            "/usr/local/bin/pasta".to_string(),
706            ARG_SLIRP4NETNS_BINARY.to_string(),
707            "/usr/local/bin/slirp4netns".to_string(),
708            ARG_SLIRP4NETNS_SANDBOX.to_string(),
709            "auto".to_string(),
710            ARG_SLIRP4NETNS_SECCOMP.to_string(),
711            "auto".to_string(),
712            ARG_VPNKIT_BINARY.to_string(),
713            "/usr/local/bin/vpnkit".to_string(),
714            ARG_PORT_DRIVER.to_string(),
715            "implicit".to_string(),
716            ARG_PUBLISH.to_string(),
717            "127.0.0.1:8080:80/tcp".to_string(),
718            ARG_PUBLISH.to_string(),
719            "127.0.0.1:8443:443/tcp".to_string(),
720            ARG_PIDNS.to_string(),
721            ARG_CGROUPNS.to_string(),
722            ARG_UTSNS.to_string(),
723            ARG_IPCNS.to_string(),
724            ARG_REAPER.to_string(),
725            "auto".to_string(),
726            ARG_EVACUATE_CGROUP2.to_string(),
727            "testgroup".to_string(),
728            ARG_STATE_DIR.to_string(),
729            "/var/foo".to_string(),
730            ARG_SUBID_SOURCE.to_string(),
731            "dynamic".to_string(),
732        ]
733    )]
734    #[case::default_options(RootlesskitOptions::default(), Vec::new())]
735    fn rootlesskit_options_to_vec(
736        #[case] options: RootlesskitOptions,
737        #[case] to_vec: Vec<String>,
738    ) {
739        assert_eq!(options.to_vec(), to_vec);
740    }
741
742    /// Ensures that [`RootlesskitOptions`] is returned from [`RootlesskitBackend::options`].
743    #[test]
744    fn rootlesskit_backend_options() {
745        let backend = RootlesskitBackend::new(RootlesskitOptions::default());
746        assert_eq!(backend.options(), &RootlesskitOptions::default());
747    }
748}