Mostly implement file descriptor reception.
This still miss detection of Message Queues.
This commit is contained in:
parent
d670bad1e9
commit
e867697c2b
182
src/lib.rs
182
src/lib.rs
|
@ -3,10 +3,10 @@ use std::net::{TcpListener, TcpStream, UdpSocket};
|
|||
use std::os::unix::net::{UnixListener, UnixStream, UnixDatagram};
|
||||
use std::{env, process};
|
||||
use std::ffi::{OsString, OsStr};
|
||||
use std::os::fd::{RawFd, FromRawFd};
|
||||
use std::os::fd::RawFd;
|
||||
|
||||
use error::{ReceiveError, ReceiveNameError, GetFdsError};
|
||||
use rustix::fd::BorrowedFd;
|
||||
use rustix::fd::{BorrowedFd, OwnedFd};
|
||||
use rustix::fs::FileType;
|
||||
use rustix::net::SocketType;
|
||||
|
||||
|
@ -17,71 +17,68 @@ const PID_VAR: &str = "LISTEN_PID";
|
|||
const FD_NUMBER_VAR: &str = "LISTEN_FDS";
|
||||
const FD_NAMES_VAR: &str = "LISTEN_FDNAMES";
|
||||
|
||||
pub enum StoredFd {
|
||||
/// File Descriptor passed by systemd.
|
||||
///
|
||||
/// They are duplicated from the actual passed file descriptor so as to be safe to use from rust code.
|
||||
#[derive(Debug)]
|
||||
pub enum FileDescriptor {
|
||||
/// The file descriptor is a [`File`](std::fs::File).
|
||||
///
|
||||
/// If this is an FD provided by a .socket unit it corresponds to `ListenSpecial=`.
|
||||
File(File),
|
||||
Directory(RawFd),
|
||||
Fifo(RawFd),
|
||||
Special(RawFd),
|
||||
/// The file descriptor is a directory.
|
||||
Directory(OwnedFd),
|
||||
/// The file descriptor is a FIFO
|
||||
///
|
||||
/// If this is an FD provided by a .socket unit it corresponds to `ListenFIFO=`
|
||||
Fifo(OwnedFd),
|
||||
/// The file descriptor is a TCP socket listening for incoming connexions.
|
||||
///
|
||||
/// If this is an FD provided by a .socket unit it corresponds to `ListenStream=` with an IP address.
|
||||
TcpListener(TcpListener),
|
||||
/// The file descriptor is a TCP socket that doesn't listen for incoming connexions.
|
||||
TcpStream(TcpStream),
|
||||
/// The file descriptor is an UDP .
|
||||
///
|
||||
/// If this is an FD provided by a .socket unit it corresponds to `ListenDatagram=` with an IP address.
|
||||
UdpSocket(UdpSocket),
|
||||
InetOther(RawFd),
|
||||
/// The file descriptor is another inet socket
|
||||
///
|
||||
/// You should figure out what exactly it is before you use it.
|
||||
InetOther(OwnedFd),
|
||||
/// The file descriptor is a Unix Stream Socket listening for incoming connexions.
|
||||
///
|
||||
/// If this is an FD provided by a .socket unit it corresponds to `ListenStream=` with a path or starting with `@` (abstract socket).
|
||||
UnixListener(UnixListener),
|
||||
/// The file descriptor is a Unix Stream Socket.
|
||||
UnixStream(UnixStream),
|
||||
/// The file descriptor is a Unix Datagram Socket.
|
||||
///
|
||||
/// If this is an FD provided by a .socket unit it corresponds to `ListenDatagram=` with a path or starting with `@` (abstract socket).
|
||||
UnixDatagram(UnixDatagram),
|
||||
UnixOther(RawFd),
|
||||
SocketOther(RawFd),
|
||||
MessageQueue(RawFd),
|
||||
Other(RawFd),
|
||||
/// The file descriptor is a Unix SEQPACKET Socket.
|
||||
///
|
||||
/// If this is an FD provided by a .socket unit it corresponds to `ListenSequentialPacket=`
|
||||
UnixSequentialPaquet(OwnedFd),
|
||||
/// The file descriptor is another type of Unix Socket.
|
||||
UnixOther(OwnedFd),
|
||||
/// The file descriptor is some other socket type.
|
||||
///
|
||||
/// Probably a Netlink family socket if from a .socket unit but you should check yourself.
|
||||
SocketOther(OwnedFd),
|
||||
/// The file descriptor is a message queue.
|
||||
///
|
||||
/// If this is an FD provided by a .socket unit it corresponds to `ListenMessageQueue=`
|
||||
MessageQueue(OwnedFd),
|
||||
/// The file descriptor is something else.
|
||||
Other(OwnedFd),
|
||||
}
|
||||
|
||||
impl FromRawFd for StoredFd {
|
||||
unsafe fn from_raw_fd(fd: RawFd) -> Self {
|
||||
let stat = rustix::fs::fstat(BorrowedFd::borrow_raw(fd)).expect("This should only ever be called on a valid file descriptor.");
|
||||
let file_type = FileType::from_raw_mode(stat.st_mode);
|
||||
match file_type {
|
||||
FileType::RegularFile => Self::File(File::from_raw_fd(fd)),
|
||||
FileType::Directory => Self::Directory(fd),
|
||||
FileType::Fifo => Self::Fifo(fd),
|
||||
FileType::Socket => {
|
||||
let borrowed_fd = BorrowedFd::borrow_raw(fd);
|
||||
match rustix::net::getsockname(borrowed_fd).unwrap() {
|
||||
rustix::net::SocketAddrAny::V4(_) |
|
||||
rustix::net::SocketAddrAny::V6(_) => {
|
||||
match rustix::net::sockopt::get_socket_type(borrowed_fd).unwrap() {
|
||||
SocketType::DGRAM => Self::UdpSocket(UdpSocket::from_raw_fd(fd)),
|
||||
SocketType::STREAM => {
|
||||
if rustix::net::sockopt::get_socket_acceptconn(borrowed_fd).unwrap_or_default() {
|
||||
Self::TcpListener(TcpListener::from_raw_fd(fd))
|
||||
} else {
|
||||
Self::TcpStream(TcpStream::from_raw_fd(fd))
|
||||
}
|
||||
},
|
||||
_ => Self::InetOther(fd),
|
||||
}
|
||||
},
|
||||
rustix::net::SocketAddrAny::Unix(_) => {
|
||||
match rustix::net::sockopt::get_socket_type(borrowed_fd).unwrap() {
|
||||
SocketType::DGRAM => Self::UnixDatagram(UnixDatagram::from_raw_fd(fd)),
|
||||
SocketType::STREAM => {
|
||||
if rustix::net::sockopt::get_socket_acceptconn(borrowed_fd).unwrap_or_default() {
|
||||
Self::UnixListener(UnixListener::from_raw_fd(fd))
|
||||
} else {
|
||||
Self::UnixStream(UnixStream::from_raw_fd(fd))
|
||||
}
|
||||
},
|
||||
_ => Self::UnixOther(fd),
|
||||
}
|
||||
},
|
||||
_ => Self::SocketOther(fd),
|
||||
}
|
||||
},
|
||||
_ => Self::Other(fd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StoredFd {
|
||||
impl FileDescriptor {
|
||||
/// Get any file descriptor passed by systemd or anything implementing the `LISTEN_FD` protocol.
|
||||
///
|
||||
/// This isn't necessarily limited to File descriptor of listening sockets, IPCs or FIFOs but also anything that is in the file descriptor store.
|
||||
/// The file descriptores are duplicated using [`fcntl_dupfd_cloexec`](rustix::fs::fcntl_dupfd_cloexec) so they can safely be used from rust and will not be propagated to children process automatically.
|
||||
pub fn receive(unset_env: bool) -> Result<impl IntoIterator<Item = Self>, ReceiveError> {
|
||||
let pid = env::var_os(PID_VAR).ok_or(ReceiveError::NoListenPID)?;
|
||||
let fds = env::var_os(FD_NUMBER_VAR).ok_or(ReceiveError::NoListenFD)?;
|
||||
|
@ -113,7 +110,11 @@ impl StoredFd {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn receive_with_names(unset_env: bool) -> Result<Vec<(Self, OsString)>, ReceiveNameError> {
|
||||
/// Get any file descriptor passed using `LISTEN_FD` and their names.
|
||||
///
|
||||
/// This isn't necessarily limited to File descriptor of listening sockets, IPCs or FIFOs but also anything that is in the file descriptor store.
|
||||
/// The file descriptores are duplicated using [`fcntl_dupfd_cloexec`](rustix::fs::fcntl_dupfd_cloexec) so they can safely be used from rust and will not be propagated to children process automatically.
|
||||
pub fn receive_with_names(unset_env: bool) -> Result<impl IntoIterator<Item = (Self, OsString)>, ReceiveNameError> {
|
||||
let fd_names = env::var_os(FD_NAMES_VAR).ok_or(ReceiveNameError::NoListenFDName)?;
|
||||
tracing::trace!("{FD_NAMES_VAR} = {fd_names:?}");
|
||||
let fd_names = fd_names.as_encoded_bytes()
|
||||
|
@ -128,15 +129,68 @@ impl StoredFd {
|
|||
Ok(Self::receive(unset_env)?.into_iter().zip(fd_names).collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
fn from_fds(num_fds: usize) -> Result<Vec<StoredFd>, GetFdsError> {
|
||||
(0..num_fds)
|
||||
fn from_fds(num_fds: usize) -> Result<impl IntoIterator<Item = FileDescriptor>, GetFdsError> {
|
||||
if SD_LISTEN_FDS_START.checked_add(num_fds as RawFd).is_none() {
|
||||
return Err(GetFdsError::TooManyFDs(num_fds));
|
||||
}
|
||||
|
||||
Ok((0..num_fds)
|
||||
.map(|fd_offset| {
|
||||
SD_LISTEN_FDS_START
|
||||
.checked_add(fd_offset as RawFd)
|
||||
.ok_or(GetFdsError::TooManyFDs(num_fds))
|
||||
// SAFETY: We are receiving the fd so it should be safe
|
||||
.map(|fd| unsafe { StoredFd::from_raw_fd(fd) })
|
||||
.map(|fd| FileDescriptor::from_fd(fd, 0))
|
||||
.expect("Already checked against overflow.")
|
||||
})
|
||||
.collect()
|
||||
)
|
||||
}
|
||||
|
||||
fn from_fd(fd: RawFd, min_new: RawFd) -> Self {
|
||||
let fd = {
|
||||
// SAFETY: The file descriptor won't be closed by the time we duplicate it.
|
||||
let fd = unsafe { BorrowedFd::borrow_raw(fd) };
|
||||
rustix::fs::fcntl_dupfd_cloexec(fd, min_new).expect("Couldn't duplicate the file descriptor")
|
||||
};
|
||||
let stat = rustix::fs::fstat(&fd).expect("This should only ever be called on a valid file descriptor.");
|
||||
let file_type = FileType::from_raw_mode(stat.st_mode);
|
||||
match file_type {
|
||||
FileType::RegularFile => Self::File(fd.into()),
|
||||
FileType::Directory => Self::Directory(fd),
|
||||
FileType::Fifo => Self::Fifo(fd),
|
||||
FileType::Socket => {
|
||||
match rustix::net::getsockname(&fd).unwrap() {
|
||||
rustix::net::SocketAddrAny::V4(_) |
|
||||
rustix::net::SocketAddrAny::V6(_) => {
|
||||
match rustix::net::sockopt::get_socket_type(&fd).unwrap() {
|
||||
SocketType::DGRAM => Self::UdpSocket(fd.into()),
|
||||
SocketType::STREAM => {
|
||||
if rustix::net::sockopt::get_socket_acceptconn(&fd).unwrap_or_default() {
|
||||
Self::TcpListener(fd.into())
|
||||
} else {
|
||||
Self::TcpStream(fd.into())
|
||||
}
|
||||
},
|
||||
_ => Self::InetOther(fd),
|
||||
}
|
||||
},
|
||||
rustix::net::SocketAddrAny::Unix(_) => {
|
||||
match rustix::net::sockopt::get_socket_type(&fd).unwrap() {
|
||||
SocketType::DGRAM => Self::UnixDatagram(fd.into()),
|
||||
SocketType::STREAM => {
|
||||
if rustix::net::sockopt::get_socket_acceptconn(&fd).unwrap_or_default() {
|
||||
Self::UnixListener(fd.into())
|
||||
} else {
|
||||
Self::UnixStream(fd.into())
|
||||
}
|
||||
},
|
||||
SocketType::SEQPACKET => Self::UnixSequentialPaquet(fd),
|
||||
_ => Self::UnixOther(fd),
|
||||
}
|
||||
},
|
||||
_ => Self::SocketOther(fd),
|
||||
}
|
||||
},
|
||||
_ => Self::Other(fd)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue