hermit/
kvm_address_space.rs1use std::fmt;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub struct KvmAddressSpaceRefusal {
34 soft: libc::rlim_t,
35 hard: libc::rlim_t,
36 guest_memory_bytes: usize,
37}
38
39impl KvmAddressSpaceRefusal {
40 pub fn for_limit(limit: libc::rlimit, guest_memory_bytes: usize) -> Option<Self> {
46 (limit.rlim_cur != libc::RLIM_INFINITY).then_some(Self {
47 soft: limit.rlim_cur,
48 hard: limit.rlim_max,
49 guest_memory_bytes,
50 })
51 }
52}
53
54fn size_text(bytes: u64) -> String {
57 const GIB: u64 = 1 << 30;
58 if bytes.is_multiple_of(GIB) {
59 format!("{} GiB ({bytes} bytes)", bytes / GIB)
60 } else {
61 format!("{:.2} GiB ({bytes} bytes)", bytes as f64 / GIB as f64)
62 }
63}
64
65fn limit_text(value: libc::rlim_t) -> String {
67 if value == libc::RLIM_INFINITY {
68 "unlimited".to_owned()
69 } else {
70 size_text(value)
71 }
72}
73
74impl fmt::Display for KvmAddressSpaceRefusal {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 let guest_memory = size_text(self.guest_memory_bytes as u64);
77 write!(
78 f,
79 "kvm_address_space_limit: --backend=kvm refuses to start under a finite RLIMIT_AS \
80 (soft {}, hard {}): the KVM backend reserves {guest_memory} of host address space \
81 for every guest process, plus {guest_memory} more for each exec in flight, and \
82 RLIMIT_AS counts every reserved byte although only the pages a guest touches use \
83 memory, so no finite limit fits every process tree, and under one a run would stop \
84 wherever the guest's process tree and host thread scheduling put the peak; Hermit \
85 does not raise the limit itself; run Hermit with an unlimited address-space limit \
86 (`ulimit -v unlimited`, which needs an unlimited hard limit or privilege) and bound \
87 its memory with a cgroup instead (memory.max, which charges the memory the run \
88 uses, not the address space it reserves); the guest has not started",
89 limit_text(self.soft),
90 limit_text(self.hard),
91 )
92 }
93}
94
95impl std::error::Error for KvmAddressSpaceRefusal {}
96
97pub fn admit_kvm_address_space(guest_memory_bytes: usize) -> anyhow::Result<()> {
101 let mut limit = libc::rlimit {
102 rlim_cur: 0,
103 rlim_max: 0,
104 };
105 if unsafe { libc::getrlimit(libc::RLIMIT_AS, &mut limit) } != 0 {
107 return Err(anyhow::Error::new(std::io::Error::last_os_error())
108 .context("failed to read RLIMIT_AS before starting the KVM backend"));
109 }
110 match KvmAddressSpaceRefusal::for_limit(limit, guest_memory_bytes) {
111 Some(refusal) => Err(anyhow::Error::new(refusal)),
112 None => Ok(()),
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 const GUEST_MEMORY: usize = 3 << 30;
121
122 #[test]
127 fn kvm_starts_only_under_an_unlimited_soft_address_space_limit() {
128 let limit = |soft, hard| libc::rlimit {
129 rlim_cur: soft,
130 rlim_max: hard,
131 };
132 let unlimited = libc::RLIM_INFINITY;
133 assert_eq!(
134 KvmAddressSpaceRefusal::for_limit(limit(unlimited, unlimited), GUEST_MEMORY),
135 None
136 );
137 for (soft, hard, soft_text, hard_text) in [
138 (
139 16 << 30,
140 16 << 30,
141 "16 GiB (17179869184 bytes)",
142 "16 GiB (17179869184 bytes)",
143 ),
144 (
145 16 << 30,
146 unlimited,
147 "16 GiB (17179869184 bytes)",
148 "unlimited",
149 ),
150 (
151 1 << 40,
152 unlimited,
153 "1024 GiB (1099511627776 bytes)",
154 "unlimited",
155 ),
156 (
157 3537227776,
158 unlimited,
159 "3.29 GiB (3537227776 bytes)",
160 "unlimited",
161 ),
162 (0, 0, "0 GiB (0 bytes)", "0 GiB (0 bytes)"),
163 ] {
164 let refusal = KvmAddressSpaceRefusal::for_limit(limit(soft, hard), GUEST_MEMORY)
165 .expect("a finite soft limit is refused");
166 let text = refusal.to_string();
167 assert!(
168 text.starts_with(&format!(
169 "kvm_address_space_limit: --backend=kvm refuses to start under a finite \
170 RLIMIT_AS (soft {soft_text}, hard {hard_text}): "
171 )),
172 "{text}"
173 );
174 assert!(
177 text.contains(
178 "reserves 3 GiB (3221225472 bytes) of host address space for every guest \
179 process, plus 3 GiB (3221225472 bytes) more for each exec in flight"
180 ),
181 "{text}"
182 );
183 assert!(
184 text.contains("Hermit does not raise the limit itself"),
185 "{text}"
186 );
187 assert!(text.contains("`ulimit -v unlimited`"), "{text}");
189 assert!(
190 text.contains("bound its memory with a cgroup instead (memory.max"),
191 "{text}"
192 );
193 assert!(text.ends_with("the guest has not started"), "{text}");
194 }
195 }
196}