use std::env;
use std::io;
use std::mem;
use std::net::{TcpListener, UdpSocket};
use std::os::unix::io::{FromRawFd, RawFd};
use std::os::unix::net::{UnixDatagram, UnixListener};
pub type FdType = RawFd;
fn is_sock(fd: FdType) -> bool {
unsafe {
let mut stat: libc::stat = mem::zeroed();
libc::fstat(fd as libc::c_int, &mut stat);
(stat.st_mode & libc::S_IFMT) == libc::S_IFSOCK
}
}
fn validate_socket(
fd: FdType,
sock_fam: libc::c_int,
sock_type: libc::c_int,
hint: &str,
) -> io::Result<FdType> {
if !is_sock(fd) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("fd {} is not a socket", fd),
));
}
let is_valid = unsafe {
let mut ty: libc::c_int = mem::zeroed();
let mut ty_len = mem::size_of_val(&ty) as libc::c_uint;
let mut sockaddr: libc::sockaddr = mem::zeroed();
let mut sockaddr_len = mem::size_of_val(&sockaddr) as libc::c_uint;
libc::getsockname(fd, &mut sockaddr, &mut sockaddr_len) == 0
&& libc::getsockopt(
fd,
libc::SOL_SOCKET,
libc::SO_TYPE,
&mut ty as *mut i32 as *mut libc::c_void,
&mut ty_len,
) == 0
&& ty == sock_type
&& (sockaddr.sa_family as libc::c_int == sock_fam
|| (sockaddr.sa_family as libc::c_int == libc::AF_INET6
&& sock_fam == libc::AF_INET))
};
if !is_valid {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("fd {} is not a valid {}", fd, hint),
));
}
Ok(fd)
}
fn mark_cloexec(fd: i32) -> io::Result<i32> {
let rv = unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) };
if rv < 0 {
return Err(io::Error::new(
io::ErrorKind::Other,
format!(
"fd {} cannot be marked as FD_CLOEXEC ({})",
fd,
io::Error::last_os_error()
),
));
}
Ok(fd)
}
pub fn make_tcp_listener(fd: FdType) -> io::Result<TcpListener> {
validate_socket(fd, libc::AF_INET, libc::SOCK_STREAM, "tcp socket")
.and_then(mark_cloexec)
.map(|fd| unsafe { FromRawFd::from_raw_fd(fd) })
}
pub fn make_unix_listener(fd: FdType) -> io::Result<UnixListener> {
validate_socket(fd, libc::AF_UNIX, libc::SOCK_STREAM, "unix stream socket")
.and_then(mark_cloexec)
.map(|fd| unsafe { FromRawFd::from_raw_fd(fd) })
}
pub fn make_udp_socket(fd: FdType) -> io::Result<UdpSocket> {
validate_socket(fd, libc::AF_INET, libc::SOCK_DGRAM, "udp socket")
.and_then(mark_cloexec)
.map(|fd| unsafe { FromRawFd::from_raw_fd(fd) })
}
pub fn make_unix_datagram(fd: FdType) -> io::Result<UnixDatagram> {
validate_socket(fd, libc::AF_UNIX, libc::SOCK_DGRAM, "unix datagram socket")
.and_then(mark_cloexec)
.map(|fd| unsafe { FromRawFd::from_raw_fd(fd) })
}
pub fn make_custom<T: FromRawFd>(
fd: FdType,
sock_fam: libc::c_int,
sock_type: libc::c_int,
hint: &str,
) -> io::Result<T> {
validate_socket(fd, sock_fam, sock_type, hint)
.and_then(mark_cloexec)
.map(|fd| unsafe { FromRawFd::from_raw_fd(fd) })
}
pub fn get_fds() -> Option<Vec<FdType>> {
if let Some(count) = env::var("LISTEN_FDS").ok().and_then(|x| x.parse().ok()) {
let ok = match env::var("LISTEN_PID").as_ref().map(|x| x.as_str()) {
Err(env::VarError::NotPresent) | Ok("") => true,
Ok(val) if val.parse().ok() == Some(unsafe { libc::getpid() }) => true,
_ => false,
};
let first_fd = env::var("LISTEN_FDS_FIRST_FD")
.ok()
.and_then(|x| x.parse().ok())
.unwrap_or(3);
env::remove_var("LISTEN_PID");
env::remove_var("LISTEN_FDS");
if ok {
return Some(
(0..count)
.map(|offset| first_fd + offset as FdType)
.collect(),
);
}
}
None
}