#[cfg(target_os = "linux")]
use std::ffi::OsString;
#[cfg(any(unix, windows))]
use std::{borrow::Cow, io, sync::Arc};
use std::{
ffi::OsStr,
fmt::{Display, Formatter},
path::PathBuf,
};
#[cfg(unix)]
use super::encode_percents;
#[cfg(any(unix, windows))]
use crate::runtime::io::{RegisteredIo, UnixOps};
use crate::{Address, Error, Result, connection::socket::BoxedSplit, runtime::Runtime};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Unix {
path: UnixSocket,
}
impl Unix {
pub fn new(path: UnixSocket) -> Self {
Self { path }
}
pub fn path(&self) -> &UnixSocket {
&self.path
}
pub fn take_path(self) -> UnixSocket {
self.path
}
#[cfg(any(unix, windows))]
pub(super) async fn connect(&self, address: &Address, runtime: &Runtime) -> Result<BoxedSplit> {
let path = match self.path() {
UnixSocket::File(path) => Cow::Borrowed(path.as_path()),
#[cfg(target_os = "linux")]
UnixSocket::Abstract(name) => Cow::Owned(abstract_path(name)),
UnixSocket::Dir(_) | UnixSocket::TmpDir(_) => return Err(Error::Unsupported),
};
let source = runtime
.connect_unix(&path)
.await
.map_err(|e| match e.kind() {
io::ErrorKind::Unsupported => Error::Unsupported,
_ => Error::Connection(Arc::new(e), Box::new(address.clone())),
})?;
Ok(RegisteredIo::new(runtime, source, UnixOps)?.into())
}
#[cfg(target_os = "wasi")]
pub(super) async fn connect(
&self,
_address: &Address,
_runtime: &Runtime,
) -> Result<BoxedSplit> {
Err(Error::Unsupported)
}
pub(super) fn from_options(opts: std::collections::HashMap<&str, &str>) -> crate::Result<Self> {
let path = opts.get("path");
let abs = opts.get("abstract");
let dir = opts.get("dir");
let tmpdir = opts.get("tmpdir");
let path = match (path, abs, dir, tmpdir) {
(Some(p), None, None, None) => UnixSocket::File(PathBuf::from(p)),
#[cfg(target_os = "linux")]
(None, Some(p), None, None) => UnixSocket::Abstract(OsString::from(p)),
#[cfg(not(target_os = "linux"))]
(None, Some(_), None, None) => {
return Err(crate::Error::Address(
"abstract sockets currently Linux-only".to_owned(),
));
}
(None, None, Some(p), None) => UnixSocket::Dir(PathBuf::from(p)),
(None, None, None, Some(p)) => UnixSocket::TmpDir(PathBuf::from(p)),
_ => {
return Err(crate::Error::Address("unix: address is invalid".to_owned()));
}
};
Ok(Self::new(path))
}
}
#[cfg(target_os = "linux")]
fn abstract_path(name: &OsStr) -> PathBuf {
use std::os::unix::ffi::{OsStrExt, OsStringExt};
let mut bytes = vec![0];
bytes.extend_from_slice(name.as_bytes());
PathBuf::from(OsString::from_vec(bytes))
}
impl Display for Unix {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "unix:{}", self.path)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum UnixSocket {
File(PathBuf),
#[cfg(target_os = "linux")]
Abstract(OsString),
Dir(PathBuf),
TmpDir(PathBuf),
}
impl Display for UnixSocket {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt_unix_path(f: &mut Formatter<'_>, path: &OsStr) -> std::fmt::Result {
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
encode_percents(f, path.as_bytes())?;
}
#[cfg(not(unix))]
write!(f, "{}", path.to_str().ok_or(std::fmt::Error)?)?;
Ok(())
}
match self {
UnixSocket::File(path) => {
f.write_str("path=")?;
fmt_unix_path(f, path.as_os_str())?;
}
#[cfg(target_os = "linux")]
UnixSocket::Abstract(name) => {
f.write_str("abstract=")?;
fmt_unix_path(f, name)?;
}
UnixSocket::Dir(path) => {
f.write_str("dir=")?;
fmt_unix_path(f, path.as_os_str())?;
}
UnixSocket::TmpDir(path) => {
f.write_str("tmpdir=")?;
fmt_unix_path(f, path.as_os_str())?;
}
}
Ok(())
}
}