#![warn(missing_docs)]
#![doc = document_features::document_features!()]
#![cfg_attr(not(feature = "sd_listen"), deny(unsafe_code))]
#![cfg_attr(
not(feature = "default"),
allow(unused_imports, irrefutable_let_patterns, unused_variables)
)]
use std::{
fmt::Display,
net::SocketAddr,
path::PathBuf,
pin::Pin,
str::FromStr,
task::{ready, Context, Poll},
time::Duration, ffi::c_int,
};
#[cfg(unix)]
use std::os::fd::RawFd;
use futures_core::Future;
use pin_project::pin_project;
use tokio::{
io::{AsyncRead, AsyncWrite, Stdin, Stdout},
net::{TcpListener, TcpStream},
sync::oneshot::{channel, Receiver, Sender},
time::Sleep,
};
use tracing::{debug, error, info, trace, warn};
#[cfg(unix)]
use tokio::net::{UnixListener, UnixStream};
#[cfg(feature = "unix_path_tools")]
#[non_exhaustive]
#[cfg_attr(
feature = "serde",
derive(serde_with::DeserializeFromStr, serde_with::SerializeDisplay)
)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum UnixChmodVariant {
Owner,
Group,
Everybody,
}
#[cfg(feature = "unix_path_tools")]
impl Display for UnixChmodVariant {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
UnixChmodVariant::Owner => "owner".fmt(f),
UnixChmodVariant::Group => "group".fmt(f),
UnixChmodVariant::Everybody => "everybody".fmt(f),
}
}
}
#[cfg(feature = "unix_path_tools")]
impl FromStr for UnixChmodVariant {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.eq_ignore_ascii_case("owner") {
Ok(UnixChmodVariant::Owner)
} else if s.eq_ignore_ascii_case("group") {
Ok(UnixChmodVariant::Group)
} else if s.eq_ignore_ascii_case("everybody") {
Ok(UnixChmodVariant::Everybody)
} else {
Err("Unknown chmod variant. Expected `owner`, `group` or `everybody`.")
}
}
}
#[cfg(feature = "socket_options")]
#[cfg_attr(
feature = "serde",
derive(serde_with::DeserializeFromStr, serde_with::SerializeDisplay)
)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct TcpKeepaliveParams {
pub timeout_ms: Option<u32>,
pub count: Option<u32>,
pub interval_ms: Option<u32>,
}
#[cfg(feature = "socket_options")]
impl Display for TcpKeepaliveParams {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(x) = self.timeout_ms {
x.fmt(f)?;
}
':'.fmt(f)?;
if let Some(x) = self.count {
x.fmt(f)?;
}
':'.fmt(f)?;
if let Some(x) = self.interval_ms {
x.fmt(f)?;
}
Ok(())
}
}
#[cfg(feature = "socket_options")]
impl FromStr for TcpKeepaliveParams {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let chunks: Vec<&str> = s.split(':').collect();
if chunks.len() > 3 {
return Err("Too many colon-separated chunks");
}
let t = chunks.get(0).unwrap_or(&"").trim();
let n = chunks.get(1).unwrap_or(&"").trim();
let i = chunks.get(2).unwrap_or(&"").trim();
let mut k = TcpKeepaliveParams::default();
if !t.is_empty() {
k.timeout_ms = Some(
t.parse()
.map_err(|_| "failed to parse timeout as a number")?,
)
}
if !n.is_empty() {
k.count = Some(n.parse().map_err(|_| "failed to parse count as a number")?)
}
if !i.is_empty() {
k.interval_ms = Some(
i.parse()
.map_err(|_| "failed to parse interval as a number")?,
)
}
Ok(k)
}
}
#[cfg(feature = "socket_options")]
impl TcpKeepaliveParams {
pub fn to_socket2(&self) -> socket2::TcpKeepalive {
let mut k = socket2::TcpKeepalive::new();
if let Some(x) = self.timeout_ms {
k = k.with_time(Duration::from_millis(x as u64));
}
#[cfg(any(
target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "illumos",
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
target_os = "tvos",
target_os = "watchos",
))]
if let Some(x) = self.count {
k = k.with_retries(x)
}
#[cfg(any(
target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "illumos",
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
target_os = "tvos",
target_os = "watchos",
target_os = "windows",
))]
if let Some(x) = self.interval_ms {
k = k.with_interval(Duration::from_millis(x as u64));
}
k
}
}
#[cfg_attr(feature = "clap", derive(clap::Args))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct UserOptions {
#[cfg(feature = "unix_path_tools")]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(all(feature = "clap", not(unix)), clap(hide = true))]
pub unix_listen_unlink: bool,
#[cfg(feature = "unix_path_tools")]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(all(feature = "clap", not(unix)), clap(hide = true))]
#[cfg_attr(feature = "serde", serde(default))]
pub unix_listen_chmod: Option<UnixChmodVariant>,
#[cfg(feature = "unix_path_tools")]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(all(feature = "clap", not(unix)), clap(hide = true))]
pub unix_listen_uid: Option<u32>,
#[cfg(feature = "unix_path_tools")]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(all(feature = "clap", not(unix)), clap(hide = true))]
pub unix_listen_gid: Option<u32>,
#[cfg(feature = "sd_listen")]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(all(feature = "clap", not(unix)), clap(hide = true))]
pub sd_accept_ignore_environment: bool,
#[cfg(feature = "socket_options")]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "serde", serde(default))]
pub tcp_keepalive: Option<TcpKeepaliveParams>,
#[cfg(feature = "socket_options")]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "serde", serde(default))]
pub tcp_reuse_port: bool,
#[cfg(feature = "socket_options")]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "serde", serde(default))]
pub recv_buffer_size: Option<usize>,
#[cfg(feature = "socket_options")]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "serde", serde(default))]
pub send_buffer_size: Option<usize>,
#[cfg(feature = "socket_options")]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "serde", serde(default))]
pub tcp_only_v6: bool,
#[cfg(feature = "socket_options")]
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "serde", serde(default))]
pub tcp_listen_backlog: Option<u32>,
}
#[non_exhaustive]
#[cfg_attr(
feature = "serde",
derive(serde_with::DeserializeFromStr, serde_with::SerializeDisplay)
)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ListenerAddress {
Tcp(SocketAddr),
Path(PathBuf),
Abstract(String),
Inetd,
FromFdTcp(i32),
FromFdUnix(i32),
}
#[cfg(feature = "clap")]
mod claptools {
use clap::Parser;
#[derive(Parser)]
pub struct ListenerAddressPositional {
#[cfg_attr(
not(any(target_os = "linux", target_os = "android")),
doc = "Note that this platform does not support all the modes described above."
)]
#[cfg_attr(
not(feature = "user_facing_default"),
doc = "Note that some features may be disabled by compile-time settings."
)]
pub listen_address: crate::ListenerAddress,
#[allow(missing_docs)]
#[clap(flatten)]
pub listener_options: crate::UserOptions,
}
#[derive(Parser)]
pub struct ListenerAddressLFlag {
#[cfg_attr(
not(any(target_os = "linux", target_os = "android")),
doc = "Note that this platform does not support all the modes described above."
)]
#[cfg_attr(
not(feature = "user_facing_default"),
doc = "Note that some features may be disabled by compile-time settings."
)]
#[clap(short = 'l', long = "listen-address")]
pub listen_address: Option<crate::ListenerAddress>,
#[allow(missing_docs)]
#[clap(flatten)]
pub listener_options: crate::UserOptions,
}
impl ListenerAddressPositional {
pub async fn bind(&self) -> std::io::Result<crate::Listener> {
crate::Listener::bind(
&self.listen_address,
&crate::SystemOptions::default(),
&self.listener_options,
)
.await
}
}
impl ListenerAddressLFlag {
pub async fn bind(&self) -> Option<std::io::Result<crate::Listener>> {
if let Some(addr) = self.listen_address.as_ref() {
Some(
crate::Listener::bind(
addr,
&crate::SystemOptions::default(),
&self.listener_options,
)
.await,
)
} else {
None
}
}
}
}
#[cfg(feature = "clap")]
pub use claptools::{ListenerAddressLFlag, ListenerAddressPositional};
const SD_LISTEN_FDS_START: u32 = 3;
impl FromStr for ListenerAddress {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with("/") || s.starts_with("./") {
Ok(ListenerAddress::Path(s.into()))
} else if s.starts_with('@') {
Ok(ListenerAddress::Abstract(s[1..].to_owned()))
} else if s.eq_ignore_ascii_case("inetd") || s.eq_ignore_ascii_case("stdio") || s == "-" {
Ok(ListenerAddress::Inetd)
} else if s.eq_ignore_ascii_case("sd-listen") || s.eq_ignore_ascii_case("sd_listen") {
Ok(ListenerAddress::FromFdTcp(SD_LISTEN_FDS_START as i32))
} else if s.eq_ignore_ascii_case("sd-listen-unix")
|| s.eq_ignore_ascii_case("sd_listen_unix")
{
Ok(ListenerAddress::FromFdUnix(SD_LISTEN_FDS_START as i32))
} else if let Ok(a) = s.parse() {
Ok(ListenerAddress::Tcp(a))
} else {
Err("Invalid tokio-listener address type")
}
}
}
impl Display for ListenerAddress {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ListenerAddress::Tcp(a) => a.fmt(f),
ListenerAddress::Path(p) => {
if let Some(s) = p.to_str() {
if p.is_absolute() {
s.fmt(f)
} else {
if s.starts_with("./") {
s.fmt(f)
} else {
write!(f, "./{s}")
}
}
} else {
if p.is_absolute() {
"/???".fmt(f)
} else {
"./???".fmt(f)
}
}
}
ListenerAddress::Abstract(p) => {
write!(f, "@{p}")
}
ListenerAddress::Inetd => "inetd".fmt(f),
ListenerAddress::FromFdTcp(fd) => {
if *fd == SD_LISTEN_FDS_START as i32 {
"sd-listen".fmt(f)
} else {
write!(f, "accept-tcp-from-fd:{}", fd)
}
}
ListenerAddress::FromFdUnix(fd) => {
if *fd == SD_LISTEN_FDS_START as i32 {
"sd-listen-unix".fmt(f)
} else {
write!(f, "accept-unix-from-fd:{}", fd)
}
}
}
}
}
#[non_exhaustive]
#[derive(Debug, Default, Clone, Copy)]
pub struct SystemOptions {
pub sleep_on_errors: bool,
pub nodelay: bool,
}
pub struct Listener {
i: ListenerImpl,
sleep_on_errors: bool,
timeout: Option<Pin<Box<Sleep>>>,
}
#[cfg(feature = "sd_listen")]
#[allow(unused)]
fn check_env_for_fd(fdnum: i32) -> Option<()> {
let listen_pid = std::env::var("LISTEN_PID").ok()?;
let listen_pid: u32 = listen_pid.parse().ok()?;
let listen_fds = std::env::var("LISTEN_FDS").ok()?;
let listen_fds: u32 = listen_fds.parse().ok()?;
debug!("Parsed LISTEN_PID and LISTEN_FDS");
if listen_pid != std::process::id() {
error!(expected = %std::process::id(), actual=listen_pid, "Failed LISTEN_PID check");
return None;
}
if fdnum < SD_LISTEN_FDS_START as i32 || fdnum >= (SD_LISTEN_FDS_START + listen_fds) as i32 {
error!(fdnum, listen_fds, "Failed LISTEN_FDS check");
return None;
}
Some(())
}
impl Listener {
pub async fn bind(
addr: &ListenerAddress,
sopts: &SystemOptions,
uopts: &UserOptions,
) -> std::io::Result<Self> {
let i: ListenerImpl = match addr {
ListenerAddress::Tcp(a) => {
#[cfg(not(feature="socket_options"))]
let s = TcpListener::bind(a).await?;
#[cfg(feature="socket_options")]
let s = if uopts.tcp_only_v6 || uopts.tcp_reuse_port || uopts.tcp_listen_backlog.is_some() {
let s = socket2::Socket::new(socket2::Domain::for_address(*a), socket2::Type::STREAM, None)?;
if uopts.tcp_only_v6 {
s.set_only_v6(true)?;
}
if uopts.tcp_reuse_port {
s.set_reuse_port(true)?;
}
s.bind(&socket2::SockAddr::from(*a))?;
s.listen(uopts.tcp_listen_backlog.unwrap_or(1024) as c_int)?;
s.set_nonblocking(true)?;
TcpListener::from_std(std::net::TcpListener::from(s))?
} else {
TcpListener::bind(a).await?
};
ListenerImpl::Tcp {
s,
nodelay: sopts.nodelay,
#[cfg(feature = "socket_options")]
keepalive: uopts
.tcp_keepalive
.as_ref()
.map(TcpKeepaliveParams::to_socket2),
#[cfg(feature = "socket_options")]
recv_buffer_size: uopts.recv_buffer_size,
#[cfg(feature = "socket_options")]
send_buffer_size: uopts.send_buffer_size,
}},
#[cfg(all(unix, feature = "unix"))]
ListenerAddress::Path(p) => {
#[cfg(feature = "unix_path_tools")]
if uopts.unix_listen_unlink {
if std::fs::remove_file(&p).is_ok() {
debug!(file=?p, "removed UNIX socket before listening")
}
}
let i = ListenerImpl::Unix {
s: UnixListener::bind(&p)?,
#[cfg(feature = "socket_options")]
recv_buffer_size: uopts.recv_buffer_size,
#[cfg(feature = "socket_options")]
send_buffer_size: uopts.send_buffer_size,
};
#[cfg(feature = "unix_path_tools")]
{
if let Some(chmod) = uopts.unix_listen_chmod {
let mode = match chmod {
UnixChmodVariant::Owner => 0o006,
UnixChmodVariant::Group => 0o066,
UnixChmodVariant::Everybody => 0o666,
};
use std::os::unix::fs::PermissionsExt;
let perms = std::fs::Permissions::from_mode(mode);
std::fs::set_permissions(&p, perms)?;
}
if (uopts.unix_listen_uid, uopts.unix_listen_gid) != (None, None) {
let uid = uopts.unix_listen_uid.map(Into::into);
let gid = uopts.unix_listen_gid.map(Into::into);
nix::unistd::chown(p, uid, gid)?;
}
}
i
}
#[cfg(all(feature = "unix", any(target_os = "linux", target_os = "android")))]
ListenerAddress::Abstract(a) => {
use std::os::linux::net::SocketAddrExt;
let a = std::os::unix::net::SocketAddr::from_abstract_name(a)?;
let s = std::os::unix::net::UnixListener::bind_addr(&a)?;
s.set_nonblocking(true)?;
ListenerImpl::Unix {
s: UnixListener::from_std(s)?,
#[cfg(feature = "socket_options")]
recv_buffer_size: uopts.recv_buffer_size,
#[cfg(feature = "socket_options")]
send_buffer_size: uopts.send_buffer_size,
}
}
#[cfg(feature = "inetd")]
ListenerAddress::Inetd => {
let (tx, rx) = channel();
ListenerImpl::Stdio(StdioListener {
rx,
token: Some(tx),
})
}
#[cfg(all(feature = "sd_listen", unix))]
ListenerAddress::FromFdTcp(fdnum) => {
if !uopts.sd_accept_ignore_environment {
if check_env_for_fd(*fdnum).is_none() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Failed LISTEN_PID or LISTEN_FDS environment check for sd-listen mode",
));
}
}
let fd: RawFd = (*fdnum).into();
use std::os::fd::FromRawFd;
let s = unsafe { std::net::TcpListener::from_raw_fd(fd) };
s.set_nonblocking(true)?;
ListenerImpl::Tcp {
s: TcpListener::from_std(s)?,
nodelay: sopts.nodelay,
#[cfg(feature = "socket_options")]
keepalive: uopts
.tcp_keepalive
.as_ref()
.map(TcpKeepaliveParams::to_socket2),
#[cfg(feature = "socket_options")]
recv_buffer_size: uopts.recv_buffer_size,
#[cfg(feature = "socket_options")]
send_buffer_size: uopts.send_buffer_size,
}
}
#[cfg(all(feature = "sd_listen", feature = "unix", unix))]
ListenerAddress::FromFdUnix(fdnum) => {
if !uopts.sd_accept_ignore_environment {
if check_env_for_fd(*fdnum).is_none() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Failed LISTEN_PID or LISTEN_FDS environment check for sd-listen mode",
));
}
}
let fd: RawFd = (*fdnum).into();
use std::os::fd::FromRawFd;
let s = unsafe { std::os::unix::net::UnixListener::from_raw_fd(fd) };
s.set_nonblocking(true)?;
ListenerImpl::Unix {
s: UnixListener::from_std(s)?,
#[cfg(feature = "socket_options")]
send_buffer_size: uopts.send_buffer_size,
#[cfg(feature = "socket_options")]
recv_buffer_size: uopts.recv_buffer_size,
}
}
#[allow(unreachable_patterns)]
_ => {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"This tokio-listener mode is not supported on this platform or is disabled during compilation",
));
}
};
Ok(Listener {
i,
sleep_on_errors: sopts.sleep_on_errors,
timeout: None,
})
}
}
#[cfg(feature = "inetd")]
struct StdioListener {
rx: Receiver<()>,
token: Option<Sender<()>>,
}
enum ListenerImpl {
Tcp {
s: TcpListener,
nodelay: bool,
#[cfg(feature = "socket_options")]
keepalive: Option<socket2::TcpKeepalive>,
#[cfg(feature = "socket_options")]
recv_buffer_size: Option<usize>,
#[cfg(feature = "socket_options")]
send_buffer_size: Option<usize>,
},
#[cfg(all(feature = "unix", unix))]
Unix {
s: UnixListener,
#[cfg(feature = "socket_options")]
recv_buffer_size: Option<usize>,
#[cfg(feature = "socket_options")]
send_buffer_size: Option<usize>,
},
#[cfg(feature = "inetd")]
Stdio(StdioListener),
}
impl Listener {
#[allow(missing_docs)]
pub fn try_borrow_tcp_listener(&self) -> Option<&TcpListener> {
if let ListenerImpl::Tcp { ref s, .. } = self.i {
Some(s)
} else {
None
}
}
#[allow(missing_docs)]
#[cfg(all(feature = "unix", unix))]
pub fn try_borrow_unix_listener(&self) -> Option<&UnixListener> {
if let ListenerImpl::Unix { s: ref x, .. } = self.i {
Some(x)
} else {
None
}
}
#[allow(missing_docs)]
pub fn try_into_tcp_listener(self) -> Result<TcpListener, Self> {
if let ListenerImpl::Tcp { s, .. } = self.i {
Ok(s)
} else {
Err(self)
}
}
#[allow(missing_docs)]
#[cfg(all(feature = "unix", unix))]
pub fn try_into_unix_listener(self) -> Result<UnixListener, Self> {
if let ListenerImpl::Unix { s: x, .. } = self.i {
Ok(x)
} else {
Err(self)
}
}
#[allow(unreachable_code)]
pub fn no_more_connections(&self) -> bool {
#[cfg(feature = "inetd")]
return if let ListenerImpl::Stdio(ref x) = self.i {
x.token.is_none()
} else {
false
};
return false;
}
pub fn poll_accept(
&mut self,
cx: &mut Context<'_>,
) -> Poll<std::io::Result<(Connection, SomeSocketAddr)>> {
loop {
if let Some(ref mut to) = self.timeout {
ready!(Pin::new(to).poll(cx));
}
self.timeout = None;
let e: std::io::Error = match &mut self.i {
ListenerImpl::Tcp {
s,
nodelay,
#[cfg(feature = "socket_options")]
keepalive,
#[cfg(feature = "socket_options")]
recv_buffer_size,
#[cfg(feature = "socket_options")]
send_buffer_size,
} => match s.poll_accept(cx) {
Poll::Ready(Err(e)) => e,
Poll::Ready(Ok((c, a))) => {
debug!(fromaddr=%a, r#type="tcp", "incoming connection");
if *nodelay {
c.set_nodelay(true)?;
}
#[cfg(feature = "socket_options")]
{
if let Some(ka) = keepalive {
let sock_ref = socket2::SockRef::from(&c);
sock_ref.set_tcp_keepalive(ka)?;
}
if let Some(n) = recv_buffer_size {
let sock_ref = socket2::SockRef::from(&c);
sock_ref.set_recv_buffer_size(*n)?;
}
if let Some(n) = send_buffer_size {
let sock_ref = socket2::SockRef::from(&c);
sock_ref.set_send_buffer_size(*n)?;
}
}
return Poll::Ready(Ok((
Connection(ConnectionImpl::Tcp(c)),
SomeSocketAddr::Tcp(a),
)));
}
Poll::Pending => return Poll::Pending,
},
#[cfg(all(feature = "unix", unix))]
ListenerImpl::Unix {
s: x,
#[cfg(feature = "socket_options")]
recv_buffer_size,
#[cfg(feature = "socket_options")]
send_buffer_size,
} => match x.poll_accept(cx) {
Poll::Ready(Err(e)) => e,
Poll::Ready(Ok((c, a))) => {
debug!(r#type = "unix", "incoming connection");
#[cfg(feature = "socket_options")]
{
if let Some(n) = recv_buffer_size {
let sock_ref = socket2::SockRef::from(&c);
sock_ref.set_recv_buffer_size(*n)?;
}
if let Some(n) = send_buffer_size {
let sock_ref = socket2::SockRef::from(&c);
sock_ref.set_send_buffer_size(*n)?;
}
}
return Poll::Ready(Ok((
Connection(ConnectionImpl::Unix(c)),
SomeSocketAddr::Unix(a),
)));
}
Poll::Pending => return Poll::Pending,
},
#[cfg(feature = "inetd")]
ListenerImpl::Stdio(x) => {
match x.token.take() {
Some(tx) => {
debug!(r#type = "stdio", "incoming connection");
return Poll::Ready(Ok((
Connection(ConnectionImpl::Stdio(
tokio::io::stdin(),
tokio::io::stdout(),
Some(tx),
)),
SomeSocketAddr::Stdio,
)));
}
None => match Pin::new(&mut x.rx).poll(cx) {
Poll::Ready(..) => {
trace!("finished waiting for liberation of stdout to stop listening loop");
return Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::Other,
"stdin/stdout pseudosocket is already used",
)));
}
Poll::Pending => return Poll::Pending,
},
}
}
};
if is_connection_error(&e) {
info!(action = "retry", "failed_accept");
continue;
}
if self.sleep_on_errors {
info!(action = "sleep_retry", "failed_accept");
self.timeout = Some(Box::pin(tokio::time::sleep(Duration::from_secs(1))));
} else {
info!(action = "error", "failed_accept");
return Poll::Ready(Err(e));
}
}
}
pub async fn accept(&mut self) -> std::io::Result<(Connection, SomeSocketAddr)> {
std::future::poll_fn(|cx| self.poll_accept(cx)).await
}
}
fn is_connection_error(e: &std::io::Error) -> bool {
matches!(
e.kind(),
std::io::ErrorKind::ConnectionRefused
| std::io::ErrorKind::ConnectionAborted
| std::io::ErrorKind::ConnectionReset
)
}
#[pin_project]
pub struct Connection(#[pin] ConnectionImpl);
#[derive(Debug)]
#[pin_project(project = ConnectionImplProj)]
enum ConnectionImpl {
Tcp(#[pin] TcpStream),
#[cfg(all(feature = "unix", unix))]
Unix(#[pin] UnixStream),
#[cfg(feature = "inetd")]
Stdio(
#[pin] tokio::io::Stdin,
#[pin] tokio::io::Stdout,
Option<Sender<()>>,
),
}
impl Connection {
#[allow(missing_docs)]
pub fn try_into_tcp(self) -> Result<TcpStream, Self> {
if let ConnectionImpl::Tcp(s) = self.0 {
Ok(s)
} else {
Err(self)
}
}
#[allow(missing_docs)]
#[cfg(all(feature = "unix", unix))]
pub fn try_into_unix(self) -> Result<UnixStream, Self> {
if let ConnectionImpl::Unix(s) = self.0 {
Ok(s)
} else {
Err(self)
}
}
#[cfg(feature = "inetd")]
#[allow(missing_docs)]
pub fn try_into_stdio(self) -> Result<(Stdin, Stdout, Option<Sender<()>>), Self> {
if let ConnectionImpl::Stdio(i, o, f) = self.0 {
Ok((i, o, f))
} else {
Err(self)
}
}
#[allow(missing_docs)]
pub fn try_borrow_tcp(&self) -> Option<&TcpStream> {
if let ConnectionImpl::Tcp(ref s) = self.0 {
Some(s)
} else {
None
}
}
#[cfg(all(feature = "unix", unix))]
#[allow(missing_docs)]
pub fn try_borrow_unix(&self) -> Option<&UnixStream> {
if let ConnectionImpl::Unix(ref s) = self.0 {
Some(s)
} else {
None
}
}
#[cfg(feature = "inetd")]
#[allow(missing_docs)]
pub fn try_borrow_stdio(&self) -> Option<(&Stdin, &Stdout)> {
if let ConnectionImpl::Stdio(ref i, ref o, ..) = self.0 {
Some((i, o))
} else {
None
}
}
}
impl From<TcpStream> for Connection {
fn from(s: TcpStream) -> Self {
Connection(ConnectionImpl::Tcp(s))
}
}
#[cfg(all(feature = "unix", unix))]
impl From<UnixStream> for Connection {
fn from(s: UnixStream) -> Self {
Connection(ConnectionImpl::Unix(s))
}
}
#[cfg(feature = "inetd")]
impl From<(Stdin, Stdout, Option<Sender<()>>)> for Connection {
fn from(s: (Stdin, Stdout, Option<Sender<()>>)) -> Self {
Connection(ConnectionImpl::Stdio(s.0, s.1, s.2))
}
}
impl AsyncRead for Connection {
#[inline]
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> Poll<std::io::Result<()>> {
let q: Pin<&mut ConnectionImpl> = self.project().0;
match q.project() {
ConnectionImplProj::Tcp(s) => s.poll_read(cx, buf),
#[cfg(all(feature = "unix", unix))]
ConnectionImplProj::Unix(s) => s.poll_read(cx, buf),
#[cfg(feature = "inetd")]
ConnectionImplProj::Stdio(s, _, _) => s.poll_read(cx, buf),
}
}
}
impl AsyncWrite for Connection {
#[inline]
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, std::io::Error>> {
let q: Pin<&mut ConnectionImpl> = self.project().0;
match q.project() {
ConnectionImplProj::Tcp(s) => s.poll_write(cx, buf),
#[cfg(all(feature = "unix", unix))]
ConnectionImplProj::Unix(s) => s.poll_write(cx, buf),
#[cfg(feature = "inetd")]
ConnectionImplProj::Stdio(_, s, _) => s.poll_write(cx, buf),
}
}
#[inline]
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), std::io::Error>> {
let q: Pin<&mut ConnectionImpl> = self.project().0;
match q.project() {
ConnectionImplProj::Tcp(s) => s.poll_flush(cx),
#[cfg(all(feature = "unix", unix))]
ConnectionImplProj::Unix(s) => s.poll_flush(cx),
#[cfg(feature = "inetd")]
ConnectionImplProj::Stdio(_, s, _) => s.poll_flush(cx),
}
}
#[inline]
fn poll_shutdown(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), std::io::Error>> {
let q: Pin<&mut ConnectionImpl> = self.project().0;
match q.project() {
ConnectionImplProj::Tcp(s) => s.poll_shutdown(cx),
#[cfg(all(feature = "unix", unix))]
ConnectionImplProj::Unix(s) => s.poll_shutdown(cx),
#[cfg(feature = "inetd")]
ConnectionImplProj::Stdio(_, s, tx) => match s.poll_shutdown(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(ret) => {
if let Some(tx) = tx.take() {
if tx.send(()).is_err() {
warn!("stdout wrapper for inetd mode failed to notify the listener to abort listening loop");
} else {
debug!("stdout finished in inetd mode. Aborting the listening loop.")
}
}
Poll::Ready(ret)
}
},
}
}
#[inline]
fn poll_write_vectored(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &[std::io::IoSlice<'_>],
) -> Poll<Result<usize, std::io::Error>> {
let q: Pin<&mut ConnectionImpl> = self.project().0;
match q.project() {
ConnectionImplProj::Tcp(s) => s.poll_write_vectored(cx, bufs),
#[cfg(all(feature = "unix", unix))]
ConnectionImplProj::Unix(s) => s.poll_write_vectored(cx, bufs),
#[cfg(feature = "inetd")]
ConnectionImplProj::Stdio(_, s, _) => s.poll_write_vectored(cx, bufs),
}
}
#[inline]
fn is_write_vectored(&self) -> bool {
match &self.0 {
ConnectionImpl::Tcp(s) => s.is_write_vectored(),
#[cfg(all(feature = "unix", unix))]
ConnectionImpl::Unix(s) => s.is_write_vectored(),
#[cfg(feature = "inetd")]
ConnectionImpl::Stdio(_, s, _) => s.is_write_vectored(),
}
}
}
#[derive(Debug)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum SomeSocketAddr {
Tcp(SocketAddr),
#[cfg(all(feature = "unix", unix))]
Unix(tokio::net::unix::SocketAddr),
#[cfg(feature = "inetd")]
Stdio,
}
impl Display for SomeSocketAddr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SomeSocketAddr::Tcp(x) => x.fmt(f),
#[cfg(all(feature = "unix", unix))]
SomeSocketAddr::Unix(_x) => "unix".fmt(f),
#[cfg(feature = "inetd")]
SomeSocketAddr::Stdio => "stdio".fmt(f),
}
}
}
#[cfg(feature = "hyper014")]
mod hyper014 {
use std::{
pin::Pin,
task::{self, Poll},
};
use hyper::server::accept::Accept;
use crate::Listener;
impl Accept for Listener {
type Conn = crate::Connection;
type Error = std::io::Error;
fn poll_accept(
self: std::pin::Pin<&mut Self>,
cx: &mut task::Context<'_>,
) -> Poll<Option<Result<Self::Conn, Self::Error>>> {
let nmc = self.no_more_connections();
match crate::Listener::poll_accept(Pin::<_>::into_inner(self), cx) {
Poll::Ready(Ok((s, _a))) => Poll::Ready(Some(Ok(s))),
Poll::Ready(Err(e)) => {
if nmc {
return Poll::Ready(None);
}
Poll::Ready(Some(Err(e)))
}
Poll::Pending => Poll::Pending,
}
}
}
}