rootless_run/
utils.rs

1//! Utils for the running of rootless backends.
2
3use std::{path::PathBuf, process::Command, str::FromStr};
4
5use strum::EnumString;
6use which::which;
7
8use crate::Error;
9
10/// A VM environment detected by [systemd-detect-virt].
11///
12/// [systemd-detect-virt]: https://man.archlinux.org/man/systemd-detect-virt.1
13#[derive(Clone, Copy, Debug, strum::Display, EnumString, Eq, PartialEq)]
14#[strum(serialize_all = "lowercase")]
15pub enum SystemdDetectVirtVm {
16    /// QEMU software virtualization, without KVM.
17    Qemu,
18    /// Linux KVM kernel virtual machine, in combination with QEMU.
19    Kvm,
20    /// Amazon EC2 Nitro using Linux KVM.
21    Amazon,
22    /// s390 z/VM.
23    Zvm,
24    /// VMware Workstation or Server, and related products.
25    Vmware,
26    /// Hyper-V, also known as Viridian or Windows Server Virtualization.
27    Microsoft,
28    /// Oracle VM VirtualBox, for legacy and KVM hypervisor.
29    Oracle,
30    /// IBM PowerVM hypervisor.
31    PowerVm,
32    /// Xen hypervisor (only domU, not dom0).
33    Xen,
34    /// Bochs Emulator.
35    Bochs,
36    /// User-mode Linux.
37    Uml,
38    /// Parallels Desktop, Parallels Server.
39    Parallels,
40    /// bhyve FreeBSD hypervisor.
41    Bhyve,
42    /// QNX hypervisor.
43    Qnx,
44    /// ACRN hypervisor.
45    Acrn,
46    /// Apple virtualization framework.
47    Apple,
48    /// LMHS SRE hypervisor.
49    Sre,
50    /// Google Compute Engine.
51    Google,
52}
53
54/// A container environment detected by [systemd-detect-virt].
55///
56/// [systemd-detect-virt]: https://man.archlinux.org/man/systemd-detect-virt.1
57#[derive(Clone, Copy, Debug, strum::Display, EnumString, Eq, PartialEq)]
58#[strum(serialize_all = "lowercase")]
59pub enum SystemdDetectVirtContainer {
60    /// OpenVZ/Virtuozzo.
61    OpenVc,
62    /// Linux container implementation by LXC.
63    Lxc,
64    /// Linux container implementation by libvirt.
65    #[strum(serialize = "lxc-libvirt")]
66    LxcLibvirt,
67    /// Systemd's minimal container implementation (systemd-nspawn).
68    #[strum(serialize = "systemd-nspawn")]
69    SystemdNspawn,
70    /// Docker container manager.
71    Docker,
72    /// Podman container manager.
73    Podman,
74    /// Rkt app container runtime.
75    Rkt,
76    /// Windows Subsystem for Linux.
77    Wsl,
78    /// proot userspace chroot/bind mount emulation
79    Proot,
80    /// Pouch container engine.
81    Pouch,
82}
83
84/// A confidential virtualization technology detected by [systemd-detect-virt].
85///
86/// [systemd-detect-virt]: https://man.archlinux.org/man/systemd-detect-virt.1
87#[derive(Clone, Copy, Debug, strum::Display, EnumString, Eq, PartialEq)]
88#[strum(serialize_all = "lowercase")]
89pub enum ConfidentialVirtualizationTechnology {
90    /// AMD Secure Encrypted Virtualization (x86_64).
91    Sev,
92    /// AMD Secure Encrypted Virtualization - Encrypted State (x86_64).
93    #[strum(serialize = "sev-es")]
94    SevEs,
95    /// AMD Secure Encrypted Virtualization - Secure Nested Paging (x86_64).
96    #[strum(serialize = "sev-snp")]
97    SevSnp,
98    /// Intel Trust Domain Extension (x86_64).
99    Tdx,
100    /// IBM Protected Virtualization (Secure Execution) (s390x).
101    Protvirt,
102}
103
104/// The output of [systemd-detect-virt].
105///
106/// [systemd-detect-virt]: https://man.archlinux.org/man/systemd-detect-virt.1
107#[derive(Clone, Copy, Debug, strum::Display, Eq, PartialEq)]
108#[strum(serialize_all = "lowercase")]
109pub enum SystemdDetectVirtOutput {
110    /// A confidential virtualization technology.
111    #[strum(to_string = "{0}")]
112    Cms(ConfidentialVirtualizationTechnology),
113    /// A VM environment.
114    #[strum(to_string = "{0}")]
115    Vm(SystemdDetectVirtVm),
116    /// A container environment.
117    #[strum(to_string = "{0}")]
118    Container(SystemdDetectVirtContainer),
119    /// No virtual environment.
120    None,
121}
122
123impl SystemdDetectVirtOutput {
124    /// Evaluate whether the [`SystemdDetectVirtOutput`] requires kernel namespaces.
125    pub fn uses_namespaces(&self) -> bool {
126        match self {
127            Self::Vm(_) | Self::Cms(_) | Self::None => false,
128            Self::Container(container) => match container {
129                SystemdDetectVirtContainer::OpenVc
130                | SystemdDetectVirtContainer::Lxc
131                | SystemdDetectVirtContainer::LxcLibvirt
132                | SystemdDetectVirtContainer::SystemdNspawn
133                | SystemdDetectVirtContainer::Rkt
134                | SystemdDetectVirtContainer::Pouch
135                | SystemdDetectVirtContainer::Docker
136                | SystemdDetectVirtContainer::Podman => true,
137                SystemdDetectVirtContainer::Wsl | SystemdDetectVirtContainer::Proot => false,
138            },
139        }
140    }
141}
142
143impl FromStr for SystemdDetectVirtOutput {
144    type Err = Error;
145
146    fn from_str(s: &str) -> Result<Self, Self::Err> {
147        // Strip any trailing newline
148        let s = s.strip_suffix("\n").unwrap_or(s);
149
150        Ok(
151            if let Ok(cms) = ConfidentialVirtualizationTechnology::from_str(s) {
152                SystemdDetectVirtOutput::Cms(cms)
153            } else if let Ok(vm) = SystemdDetectVirtVm::try_from(s) {
154                SystemdDetectVirtOutput::Vm(vm)
155            } else if let Ok(container) = SystemdDetectVirtContainer::try_from(s) {
156                SystemdDetectVirtOutput::Container(container)
157            } else if s == "none" {
158                SystemdDetectVirtOutput::None
159            } else {
160                return Err(Error::UnknownSystemdDetectVirtOutput {
161                    output: s.to_string(),
162                });
163            },
164        )
165    }
166}
167
168/// Detects whether currently running in a virtualized or containerized Linux environment.
169///
170/// Uses [systemd-detect-virt] to detect virtualization and containerization environments and
171/// returns its output in the form of a [`SystemdDetectVirtOutput`].
172///
173/// # Errors
174///
175/// Returns an error if
176///
177/// - [systemd-detect-virt] cannot be found,
178/// - [systemd-detect-virt] cannot be executed successfully,
179/// - or the output of [systemd-detect-virt] does not match any variant of
180///   [`SystemdDetectVirtOutput`].
181///
182/// [systemd-detect-virt]: https://man.archlinux.org/man/systemd-detect-virt.1
183pub fn detect_virt() -> Result<SystemdDetectVirtOutput, Error> {
184    let command_name = get_command("systemd-detect-virt")?;
185    let mut command = Command::new(command_name);
186    let output = command
187        .output()
188        .map_err(|source| crate::Error::CommandExec {
189            command: format!("{command:?}"),
190            source,
191        })?;
192
193    SystemdDetectVirtOutput::from_str(&String::from_utf8_lossy(&output.stdout))
194}
195
196/// Returns the path to a `command`.
197///
198/// Searches for an executable in `$PATH` of the current environment and returns the first one
199/// found.
200///
201/// # Errors
202///
203/// Returns an error if no executable matches the provided `command`.
204pub fn get_command(command: &str) -> Result<PathBuf, Error> {
205    which(command).map_err(|source| Error::ExecutableNotFound {
206        executable: command.to_string(),
207        source,
208    })
209}
210
211#[cfg(test)]
212mod tests {
213    use rstest::rstest;
214    use testresult::TestResult;
215
216    use super::*;
217
218    /// Ensures that [`SystemdDetectVirtOutput`] is properly roundtripped from/to [`str`].
219    #[rstest]
220    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Qemu), "qemu")]
221    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Kvm), "kvm")]
222    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Amazon), "amazon")]
223    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Zvm), "zvm")]
224    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Vmware), "vmware")]
225    #[case(
226        SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Microsoft),
227        "microsoft"
228    )]
229    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Oracle), "oracle")]
230    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::PowerVm), "powervm")]
231    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Xen), "xen")]
232    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Bochs), "bochs")]
233    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Uml), "uml")]
234    #[case(
235        SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Parallels),
236        "parallels"
237    )]
238    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Bhyve), "bhyve")]
239    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Qnx), "qnx")]
240    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Acrn), "acrn")]
241    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Apple), "apple")]
242    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Sre), "sre")]
243    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Google), "google")]
244    #[case(
245        SystemdDetectVirtOutput::Container(SystemdDetectVirtContainer::OpenVc),
246        "openvc"
247    )]
248    #[case(
249        SystemdDetectVirtOutput::Container(SystemdDetectVirtContainer::Lxc),
250        "lxc"
251    )]
252    #[case(
253        SystemdDetectVirtOutput::Container(SystemdDetectVirtContainer::LxcLibvirt),
254        "lxc-libvirt"
255    )]
256    #[case(
257        SystemdDetectVirtOutput::Container(SystemdDetectVirtContainer::SystemdNspawn),
258        "systemd-nspawn"
259    )]
260    #[case(
261        SystemdDetectVirtOutput::Container(SystemdDetectVirtContainer::Docker),
262        "docker"
263    )]
264    #[case(
265        SystemdDetectVirtOutput::Container(SystemdDetectVirtContainer::Podman),
266        "podman"
267    )]
268    #[case(
269        SystemdDetectVirtOutput::Container(SystemdDetectVirtContainer::Rkt),
270        "rkt"
271    )]
272    #[case(
273        SystemdDetectVirtOutput::Container(SystemdDetectVirtContainer::Wsl),
274        "wsl"
275    )]
276    #[case(
277        SystemdDetectVirtOutput::Container(SystemdDetectVirtContainer::Proot),
278        "proot"
279    )]
280    #[case(
281        SystemdDetectVirtOutput::Container(SystemdDetectVirtContainer::Pouch),
282        "pouch"
283    )]
284    #[case(
285        SystemdDetectVirtOutput::Cms(ConfidentialVirtualizationTechnology::Sev),
286        "sev"
287    )]
288    #[case(
289        SystemdDetectVirtOutput::Cms(ConfidentialVirtualizationTechnology::SevEs),
290        "sev-es"
291    )]
292    #[case(
293        SystemdDetectVirtOutput::Cms(ConfidentialVirtualizationTechnology::SevSnp),
294        "sev-snp"
295    )]
296    #[case(
297        SystemdDetectVirtOutput::Cms(ConfidentialVirtualizationTechnology::Tdx),
298        "tdx"
299    )]
300    #[case(
301        SystemdDetectVirtOutput::Cms(ConfidentialVirtualizationTechnology::Protvirt),
302        "protvirt"
303    )]
304    #[case(SystemdDetectVirtOutput::None, "none")]
305    fn systemd_detect_virt_output_serialize_deserialize(
306        #[case] output: SystemdDetectVirtOutput,
307        #[case] to_string: &str,
308    ) -> TestResult {
309        assert_eq!(output.to_string(), to_string);
310        assert_eq!(SystemdDetectVirtOutput::from_str(to_string)?, output);
311        Ok(())
312    }
313
314    /// Ensures that [`SystemdDetectVirtOutput::uses_namespaces`] works as intended.
315    #[rstest]
316    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Qemu), false)]
317    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Kvm), false)]
318    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Amazon), false)]
319    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Zvm), false)]
320    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Vmware), false)]
321    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Microsoft), false)]
322    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Oracle), false)]
323    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::PowerVm), false)]
324    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Xen), false)]
325    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Bochs), false)]
326    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Uml), false)]
327    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Parallels), false)]
328    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Bhyve), false)]
329    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Qnx), false)]
330    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Acrn), false)]
331    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Apple), false)]
332    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Sre), false)]
333    #[case(SystemdDetectVirtOutput::Vm(SystemdDetectVirtVm::Google), false)]
334    #[case(
335        SystemdDetectVirtOutput::Container(SystemdDetectVirtContainer::OpenVc),
336        true
337    )]
338    #[case(
339        SystemdDetectVirtOutput::Container(SystemdDetectVirtContainer::Lxc),
340        true
341    )]
342    #[case(
343        SystemdDetectVirtOutput::Container(SystemdDetectVirtContainer::LxcLibvirt),
344        true
345    )]
346    #[case(
347        SystemdDetectVirtOutput::Container(SystemdDetectVirtContainer::SystemdNspawn),
348        true
349    )]
350    #[case(
351        SystemdDetectVirtOutput::Container(SystemdDetectVirtContainer::Docker),
352        true
353    )]
354    #[case(
355        SystemdDetectVirtOutput::Container(SystemdDetectVirtContainer::Podman),
356        true
357    )]
358    #[case(
359        SystemdDetectVirtOutput::Container(SystemdDetectVirtContainer::Rkt),
360        true
361    )]
362    #[case(
363        SystemdDetectVirtOutput::Container(SystemdDetectVirtContainer::Wsl),
364        false
365    )]
366    #[case(
367        SystemdDetectVirtOutput::Container(SystemdDetectVirtContainer::Proot),
368        false
369    )]
370    #[case(
371        SystemdDetectVirtOutput::Container(SystemdDetectVirtContainer::Pouch),
372        true
373    )]
374    #[case(
375        SystemdDetectVirtOutput::Cms(ConfidentialVirtualizationTechnology::Sev),
376        false
377    )]
378    #[case(
379        SystemdDetectVirtOutput::Cms(ConfidentialVirtualizationTechnology::SevEs),
380        false
381    )]
382    #[case(
383        SystemdDetectVirtOutput::Cms(ConfidentialVirtualizationTechnology::SevSnp),
384        false
385    )]
386    #[case(
387        SystemdDetectVirtOutput::Cms(ConfidentialVirtualizationTechnology::Tdx),
388        false
389    )]
390    #[case(
391        SystemdDetectVirtOutput::Cms(ConfidentialVirtualizationTechnology::Protvirt),
392        false
393    )]
394    #[case(SystemdDetectVirtOutput::None, false)]
395    fn systemd_detect_virt_output_uses_namespaces(
396        #[case] output: SystemdDetectVirtOutput,
397        #[case] uses_namespaces: bool,
398    ) -> TestResult {
399        assert_eq!(output.uses_namespaces(), uses_namespaces);
400        Ok(())
401    }
402}