1#[cfg(not(any(target_os = "linux", target_os = "android")))]
12std::compile_error!("letmeind server and letmein-systemd do not support non-Linux platforms.");
13
14use anyhow::{self as ah, format_err as err, Context as _};
15
16#[cfg(any(feature = "tcp", feature = "unix"))]
17use std::{
18 mem::size_of_val,
19 os::fd::{FromRawFd as _, RawFd},
20};
21
22#[cfg(feature = "udp")]
23use std::net::UdpSocket;
24
25#[cfg(feature = "tcp")]
26use std::net::TcpListener;
27
28#[cfg(feature = "unix")]
29use std::os::unix::net::UnixListener;
30
31#[cfg(any(feature = "udp", feature = "tcp"))]
32const INET46: [Option<libc::c_int>; 2] = [Some(libc::AF_INET), Some(libc::AF_INET6)];
33
34#[cfg(any(feature = "udp", feature = "tcp", feature = "unix"))]
36fn is_socket(fd: RawFd) -> bool {
37 let mut stat: libc::stat64 = unsafe { std::mem::zeroed() };
39 let ret = unsafe { libc::fstat64(fd, &mut stat) };
41 if ret == 0 {
42 const S_IFMT: libc::mode_t = libc::S_IFMT as libc::mode_t;
43 const S_IFSOCK: libc::mode_t = libc::S_IFSOCK as libc::mode_t;
44 (stat.st_mode as libc::mode_t & S_IFMT) == S_IFSOCK
45 } else {
46 false
47 }
48}
49
50#[cfg(any(feature = "udp", feature = "tcp", feature = "unix"))]
54unsafe fn get_socket_type(fd: RawFd) -> Option<libc::c_int> {
55 let mut sotype: libc::c_int = 0;
56 let mut len: libc::socklen_t = size_of_val(&sotype) as _;
57 let ret = unsafe {
59 libc::getsockopt(
60 fd,
61 libc::SOL_SOCKET,
62 libc::SO_TYPE,
63 &mut sotype as *mut _ as _,
64 &mut len,
65 )
66 };
67 if ret == 0 && len >= size_of_val(&sotype) as _ {
68 Some(sotype)
69 } else {
70 None
71 }
72}
73
74#[cfg(any(feature = "udp", feature = "tcp", feature = "unix"))]
78unsafe fn get_socket_family(fd: RawFd) -> Option<libc::c_int> {
79 let mut saddr: libc::sockaddr = unsafe { std::mem::zeroed() };
81 let mut len: libc::socklen_t = size_of_val(&saddr) as _;
82 let ret = unsafe { libc::getsockname(fd, &mut saddr, &mut len) };
84 if ret == 0 && len >= size_of_val(&saddr) as _ {
85 Some(saddr.sa_family.into())
86 } else {
87 None
88 }
89}
90
91#[cfg(feature = "udp")]
92fn is_udp_socket(fd: RawFd) -> bool {
93 unsafe {
95 is_socket(fd)
96 && get_socket_type(fd) == Some(libc::SOCK_DGRAM)
97 && INET46.contains(&get_socket_family(fd))
98 }
99}
100
101#[cfg(feature = "tcp")]
102fn is_tcp_socket(fd: RawFd) -> bool {
103 unsafe {
105 is_socket(fd)
106 && get_socket_type(fd) == Some(libc::SOCK_STREAM)
107 && INET46.contains(&get_socket_family(fd))
108 }
109}
110
111#[cfg(feature = "unix")]
112fn is_unix_socket(fd: RawFd) -> bool {
113 unsafe {
115 is_socket(fd)
116 && get_socket_type(fd) == Some(libc::SOCK_STREAM)
117 && get_socket_family(fd) == Some(libc::AF_UNIX)
118 }
119}
120
121#[derive(Debug)]
123#[non_exhaustive]
124pub enum SystemdSocket {
125 #[cfg(feature = "udp")]
127 Udp(UdpSocket),
128
129 #[cfg(feature = "tcp")]
131 Tcp(TcpListener),
132
133 #[cfg(feature = "unix")]
135 Unix(UnixListener),
136}
137
138impl SystemdSocket {
139 #[allow(unused_mut)]
143 pub fn get_all() -> ah::Result<Vec<SystemdSocket>> {
144 let mut sockets = vec![];
145 if sd_notify::booted().unwrap_or(false) {
146 for fd in sd_notify::listen_fds().context("Systemd listen_fds")? {
147 #[cfg(feature = "udp")]
148 if is_udp_socket(fd) {
149 let sock = unsafe { UdpSocket::from_raw_fd(fd) };
152 sockets.push(SystemdSocket::Udp(sock));
153 continue;
154 }
155
156 #[cfg(feature = "tcp")]
157 if is_tcp_socket(fd) {
158 let sock = unsafe { TcpListener::from_raw_fd(fd) };
161 sockets.push(SystemdSocket::Tcp(sock));
162 continue;
163 }
164
165 #[cfg(feature = "unix")]
166 if is_unix_socket(fd) {
167 let sock = unsafe { UnixListener::from_raw_fd(fd) };
170 sockets.push(SystemdSocket::Unix(sock));
171 continue;
172 }
173
174 let _ = fd;
175 return Err(err!("Received unknown socket from systemd"));
176 }
177 }
178 Ok(sockets)
179 }
180}
181
182pub fn systemd_notify_ready() -> ah::Result<()> {
186 sd_notify::notify(true, &[sd_notify::NotifyState::Ready])?;
187 Ok(())
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn test_systemd() {
196 assert!(SystemdSocket::get_all().unwrap().is_empty());
197
198 systemd_notify_ready().unwrap();
199 }
200}
201
202