1use std::fmt;
10use std::io;
11use std::sync::{Mutex, PoisonError};
12
13use crate::handoff::ListenerHandle;
14#[cfg(unix)]
15use crate::sys::KindError;
16
17#[non_exhaustive]
23#[derive(Debug)]
24pub enum InheritError {
25 BadCount,
27 TooMany {
29 max: u32,
31 },
32 NotASocket {
34 fd: i32,
36 },
37 NotListening {
39 fd: i32,
41 },
42 WrongKind {
44 fd: i32,
46 },
47 NamesMismatch,
49 PidFdMismatch,
51 Os(io::Error),
53}
54
55impl fmt::Display for InheritError {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 match self {
58 InheritError::BadCount => f.write_str("LISTEN_FDS is not a valid count"),
59 InheritError::TooMany { max } => write!(f, "more than {max} inherited descriptors"),
60 InheritError::NotASocket { fd } => write!(f, "descriptor {fd} is not a socket"),
61 InheritError::NotListening { fd } => {
62 write!(f, "descriptor {fd} is not a listening socket")
63 }
64 InheritError::WrongKind { fd } => {
65 write!(f, "descriptor {fd} is not a TCP or Unix stream socket")
66 }
67 InheritError::NamesMismatch => f.write_str("LISTEN_FDNAMES does not match LISTEN_FDS"),
68 InheritError::PidFdMismatch => {
69 f.write_str("LISTEN_PIDFDID does not match this process")
70 }
71 InheritError::Os(e) => write!(f, "{e}"),
72 }
73 }
74}
75
76impl std::error::Error for InheritError {
77 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
78 match self {
79 InheritError::Os(e) => Some(e),
80 _ => None,
81 }
82 }
83}
84
85#[derive(Debug)]
87pub struct InheritedListener {
88 pub name: Option<String>,
90 pub handle: ListenerHandle,
92}
93
94#[cfg(unix)]
96const MAX_FDS: u32 = 64;
97#[cfg(unix)]
99const FIRST_FD: i32 = 3;
100
101struct Taken {
103 read: bool,
104 left: Vec<InheritedListener>,
105}
106
107static SYSTEMD: Mutex<Taken> = Mutex::new(Taken {
108 read: false,
109 left: Vec::new(),
110});
111
112pub fn systemd() -> Result<Vec<InheritedListener>, InheritError> {
138 let mut taken = SYSTEMD.lock().unwrap_or_else(PoisonError::into_inner);
139 read_once(&mut taken)?;
140 Ok(std::mem::take(&mut taken.left))
141}
142
143pub fn systemd_named(name: &str) -> Result<Option<InheritedListener>, InheritError> {
150 let mut taken = SYSTEMD.lock().unwrap_or_else(PoisonError::into_inner);
151 read_once(&mut taken)?;
152 Ok(taken
153 .left
154 .iter()
155 .position(|l| l.name.as_deref() == Some(name))
156 .map(|i| taken.left.remove(i)))
157}
158
159fn read_once(taken: &mut Taken) -> Result<(), InheritError> {
160 if taken.read {
161 return Ok(());
162 }
163 #[cfg(unix)]
164 {
165 let env = |k: &str| std::env::var(k).ok();
166 taken.left = from_systemd(&env, std::process::id(), FIRST_FD)?;
167 }
168 taken.read = true;
169 Ok(())
170}
171
172#[cfg(unix)]
174fn from_systemd(
175 env: &dyn Fn(&str) -> Option<String>,
176 pid: u32,
177 first: i32,
178) -> Result<Vec<InheritedListener>, InheritError> {
179 match env("LISTEN_PID").and_then(|p| p.trim().parse::<u32>().ok()) {
181 Some(p) if p == pid => {}
182 _ => return Ok(Vec::new()),
183 }
184 #[cfg(target_os = "linux")]
185 if let Some(want) = env("LISTEN_PIDFDID") {
186 let want: u64 = want
187 .trim()
188 .parse()
189 .map_err(|_| InheritError::PidFdMismatch)?;
190 if crate::sys::pidfd_inode().map_err(InheritError::Os)? != want {
191 return Err(InheritError::PidFdMismatch);
192 }
193 }
194 let Some(count) = env("LISTEN_FDS") else {
195 return Ok(Vec::new());
196 };
197 let count: u32 = count.trim().parse().map_err(|_| InheritError::BadCount)?;
198 if count > MAX_FDS {
199 return Err(InheritError::TooMany { max: MAX_FDS });
200 }
201 let names: Option<Vec<String>> =
202 env("LISTEN_FDNAMES").map(|n| n.split(':').map(str::to_owned).collect());
203 if names.as_ref().is_some_and(|n| n.len() != count as usize) {
204 return Err(InheritError::NamesMismatch);
205 }
206 let fds: Vec<i32> = (0..count as i32).map(|i| first + i).collect();
208 for &fd in &fds {
209 crate::sys::inherited_kind(fd).map_err(|e| kind_error(e, fd))?;
210 }
211 let mut out = Vec::with_capacity(fds.len());
212 for (i, fd) in fds.into_iter().enumerate() {
213 let owned = crate::sys::claim_inherited(fd).map_err(InheritError::Os)?;
214 let name = names
215 .as_ref()
216 .and_then(|n| n.get(i))
217 .filter(|n| !n.is_empty())
218 .cloned();
219 let handle =
220 ListenerHandle::verified(owned, name.clone()).map_err(|e| kind_error(e, fd))?;
221 out.push(InheritedListener { name, handle });
222 }
223 Ok(out)
224}
225
226#[cfg(all(fuzzing, unix))]
230pub(crate) fn fuzz_systemd_env(data: &[u8]) {
231 let text = String::from_utf8_lossy(data);
232 let mut parts = text.split('\0');
233 let pid = std::process::id();
234 let (fds, names, pidfd) = (parts.next(), parts.next(), parts.next());
235 let env = |k: &str| match k {
236 "LISTEN_PID" => Some(pid.to_string()),
237 "LISTEN_FDS" => fds.map(str::to_owned),
238 "LISTEN_FDNAMES" => names.map(str::to_owned),
239 "LISTEN_PIDFDID" => pidfd.map(str::to_owned),
240 _ => None,
241 };
242 if let Ok(list) = from_systemd(&env, pid, 1_000_000) {
243 assert!(list.is_empty(), "no descriptor is open up there");
244 }
245}
246
247#[cfg(all(fuzzing, not(unix)))]
248pub(crate) fn fuzz_systemd_env(_data: &[u8]) {}
249
250#[cfg(unix)]
251fn kind_error(e: KindError, fd: i32) -> InheritError {
252 match e {
253 KindError::NotASocket => InheritError::NotASocket { fd },
254 KindError::NotListening => InheritError::NotListening { fd },
255 KindError::WrongKind => InheritError::WrongKind { fd },
256 KindError::Os(e) => InheritError::Os(e),
257 }
258}
259
260#[cfg(unix)]
261static FASTCGI: Mutex<bool> = Mutex::new(false);
262
263#[cfg(unix)]
279pub fn fastcgi_listener() -> Result<Option<ListenerHandle>, InheritError> {
280 let mut taken = FASTCGI.lock().unwrap_or_else(PoisonError::into_inner);
281 if *taken {
282 return Ok(None);
283 }
284 let got = fastcgi_from(0)?;
285 if got.is_some() {
286 *taken = true;
287 }
288 Ok(got)
289}
290
291#[cfg(unix)]
292fn fastcgi_from(fd: i32) -> Result<Option<ListenerHandle>, InheritError> {
293 match crate::sys::inherited_kind(fd) {
294 Ok(_) => {}
295 Err(KindError::Os(e)) => return Err(InheritError::Os(e)),
296 Err(_) => return Ok(None),
297 }
298 let owned = crate::sys::claim_inherited(fd).map_err(InheritError::Os)?;
301 ListenerHandle::verified(owned, None)
302 .map(Some)
303 .map_err(|e| kind_error(e, fd))
304}
305
306#[cfg(all(test, unix))]
307mod tests {
308 use super::*;
309 use std::collections::HashMap;
310 use std::os::fd::AsFd;
311
312 fn env(vars: &[(&str, &str)]) -> impl Fn(&str) -> Option<String> {
313 let map: HashMap<String, String> = vars
314 .iter()
315 .map(|(k, v)| ((*k).to_owned(), (*v).to_owned()))
316 .collect();
317 move |k| map.get(k).cloned()
318 }
319
320 fn place(base: i32, n: usize) -> Vec<std::net::TcpListener> {
322 (0..n)
323 .map(|i| {
324 let l = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
325 crate::sys::dup_to(l.as_fd(), base + i as i32).unwrap();
326 l
327 })
328 .collect()
329 }
330
331 #[test]
332 fn test_systemd_protocol() {
333 let me = std::process::id();
334 let pid = me.to_string();
335 assert!(from_systemd(&env(&[]), me, 900).unwrap().is_empty());
337 assert!(
338 from_systemd(&env(&[("LISTEN_PID", "1"), ("LISTEN_FDS", "1")]), me, 900)
339 .unwrap()
340 .is_empty()
341 );
342 assert!(
343 from_systemd(&env(&[("LISTEN_PID", &pid), ("LISTEN_FDS", "0")]), me, 900)
344 .unwrap()
345 .is_empty()
346 );
347 for bad in ["x", "-1", ""] {
349 assert!(matches!(
350 from_systemd(&env(&[("LISTEN_PID", &pid), ("LISTEN_FDS", bad)]), me, 900),
351 Err(InheritError::BadCount)
352 ));
353 }
354 assert!(matches!(
355 from_systemd(&env(&[("LISTEN_PID", &pid), ("LISTEN_FDS", "65")]), me, 900),
356 Err(InheritError::TooMany { max: 64 })
357 ));
358 assert!(matches!(
360 from_systemd(
361 &env(&[
362 ("LISTEN_PID", &pid),
363 ("LISTEN_FDS", "2"),
364 ("LISTEN_FDNAMES", "a")
365 ]),
366 me,
367 900
368 ),
369 Err(InheritError::NamesMismatch)
370 ));
371 assert!(matches!(
373 from_systemd(&env(&[("LISTEN_PID", &pid), ("LISTEN_FDS", "1")]), me, 990),
374 Err(InheritError::NotASocket { fd: 990 })
375 ));
376 let file = std::fs::File::open("/dev/null").unwrap();
377 crate::sys::dup_to(file.as_fd(), 991).unwrap();
378 assert!(matches!(
379 from_systemd(&env(&[("LISTEN_PID", &pid), ("LISTEN_FDS", "1")]), me, 991),
380 Err(InheritError::NotASocket { fd: 991 })
381 ));
382 crate::sys::close_number(991);
383 }
384
385 #[test]
386 fn test_systemd_takes_verified_listeners_with_names() {
387 let me = std::process::id();
388 let pid = me.to_string();
389 let ls = place(910, 2);
390 let got = from_systemd(
391 &env(&[
392 ("LISTEN_PID", &pid),
393 ("LISTEN_FDS", "2"),
394 ("LISTEN_FDNAMES", "web:"),
395 ]),
396 me,
397 910,
398 )
399 .unwrap();
400 assert_eq!(got.len(), 2);
401 assert_eq!(got[0].name.as_deref(), Some("web"));
402 assert_eq!(got[1].name, None, "an empty name is no name");
403 assert_eq!(
404 got[0].handle.local().socket_addr(),
405 Some(ls[0].local_addr().unwrap())
406 );
407 let l = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
409 let c = std::net::TcpStream::connect(l.local_addr().unwrap()).unwrap();
410 crate::sys::dup_to(c.as_fd(), 920).unwrap();
411 assert!(matches!(
412 from_systemd(&env(&[("LISTEN_PID", &pid), ("LISTEN_FDS", "1")]), me, 920),
413 Err(InheritError::NotListening { fd: 920 })
414 ));
415 crate::sys::close_number(920);
416 }
417
418 #[test]
419 fn test_fastcgi_descriptor_checks() {
420 let file = std::fs::File::open("/dev/null").unwrap();
422 crate::sys::dup_to(file.as_fd(), 930).unwrap();
423 assert!(fastcgi_from(930).unwrap().is_none());
424 crate::sys::close_number(930);
425 let l = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
426 let c = std::net::TcpStream::connect(l.local_addr().unwrap()).unwrap();
427 crate::sys::dup_to(c.as_fd(), 931).unwrap();
428 assert!(fastcgi_from(931).unwrap().is_none());
429 crate::sys::close_number(931);
430 crate::sys::dup_to(l.as_fd(), 932).unwrap();
431 let h = fastcgi_from(932).unwrap().unwrap();
432 assert_eq!(h.local().socket_addr(), Some(l.local_addr().unwrap()));
433 assert!(fastcgi_from(-1).unwrap().is_none());
434 }
435}