aboutsummaryrefslogtreecommitdiff
path: root/src/sys/unix/uds
diff options
context:
space:
mode:
Diffstat (limited to 'src/sys/unix/uds')
-rw-r--r--src/sys/unix/uds/datagram.rs56
-rw-r--r--src/sys/unix/uds/listener.rs83
-rw-r--r--src/sys/unix/uds/mod.rs149
-rw-r--r--src/sys/unix/uds/socketaddr.rs120
-rw-r--r--src/sys/unix/uds/stream.rs39
5 files changed, 447 insertions, 0 deletions
diff --git a/src/sys/unix/uds/datagram.rs b/src/sys/unix/uds/datagram.rs
new file mode 100644
index 0000000..d3e5314
--- /dev/null
+++ b/src/sys/unix/uds/datagram.rs
@@ -0,0 +1,56 @@
+use super::{socket_addr, SocketAddr};
+use crate::sys::unix::net::new_socket;
+
+use std::io;
+use std::os::unix::io::{AsRawFd, FromRawFd};
+use std::os::unix::net;
+use std::path::Path;
+
+pub(crate) fn bind(path: &Path) -> io::Result<net::UnixDatagram> {
+ let fd = new_socket(libc::AF_UNIX, libc::SOCK_DGRAM)?;
+ // Ensure the fd is closed.
+ let socket = unsafe { net::UnixDatagram::from_raw_fd(fd) };
+ let (sockaddr, socklen) = socket_addr(path)?;
+ let sockaddr = &sockaddr as *const libc::sockaddr_un as *const _;
+ syscall!(bind(fd, sockaddr, socklen))?;
+ Ok(socket)
+}
+
+pub(crate) fn unbound() -> io::Result<net::UnixDatagram> {
+ new_socket(libc::AF_UNIX, libc::SOCK_DGRAM)
+ .map(|socket| unsafe { net::UnixDatagram::from_raw_fd(socket) })
+}
+
+pub(crate) fn pair() -> io::Result<(net::UnixDatagram, net::UnixDatagram)> {
+ super::pair(libc::SOCK_DGRAM)
+}
+
+pub(crate) fn local_addr(socket: &net::UnixDatagram) -> io::Result<SocketAddr> {
+ super::local_addr(socket.as_raw_fd())
+}
+
+pub(crate) fn peer_addr(socket: &net::UnixDatagram) -> io::Result<SocketAddr> {
+ super::peer_addr(socket.as_raw_fd())
+}
+
+pub(crate) fn recv_from(
+ socket: &net::UnixDatagram,
+ dst: &mut [u8],
+) -> io::Result<(usize, SocketAddr)> {
+ let mut count = 0;
+ let socketaddr = SocketAddr::new(|sockaddr, socklen| {
+ syscall!(recvfrom(
+ socket.as_raw_fd(),
+ dst.as_mut_ptr() as *mut _,
+ dst.len(),
+ 0,
+ sockaddr,
+ socklen,
+ ))
+ .map(|c| {
+ count = c;
+ c as libc::c_int
+ })
+ })?;
+ Ok((count as usize, socketaddr))
+}
diff --git a/src/sys/unix/uds/listener.rs b/src/sys/unix/uds/listener.rs
new file mode 100644
index 0000000..b8fb5a9
--- /dev/null
+++ b/src/sys/unix/uds/listener.rs
@@ -0,0 +1,83 @@
+use super::socket_addr;
+use crate::net::{SocketAddr, UnixStream};
+use crate::sys::unix::net::new_socket;
+use std::os::unix::io::{AsRawFd, FromRawFd};
+use std::os::unix::net;
+use std::path::Path;
+use std::{io, mem};
+
+pub(crate) fn bind(path: &Path) -> io::Result<net::UnixListener> {
+ let socket = new_socket(libc::AF_UNIX, libc::SOCK_STREAM)?;
+ let (sockaddr, socklen) = socket_addr(path)?;
+ let sockaddr = &sockaddr as *const libc::sockaddr_un as *const libc::sockaddr;
+
+ syscall!(bind(socket, sockaddr, socklen))
+ .and_then(|_| syscall!(listen(socket, 1024)))
+ .map_err(|err| {
+ // Close the socket if we hit an error, ignoring the error from
+ // closing since we can't pass back two errors.
+ let _ = unsafe { libc::close(socket) };
+ err
+ })
+ .map(|_| unsafe { net::UnixListener::from_raw_fd(socket) })
+}
+
+pub(crate) fn accept(listener: &net::UnixListener) -> io::Result<(UnixStream, SocketAddr)> {
+ let sockaddr = mem::MaybeUninit::<libc::sockaddr_un>::zeroed();
+
+ // This is safe to assume because a `libc::sockaddr_un` filled with `0`
+ // bytes is properly initialized.
+ //
+ // `0` is a valid value for `sockaddr_un::sun_family`; it is
+ // `libc::AF_UNSPEC`.
+ //
+ // `[0; 108]` is a valid value for `sockaddr_un::sun_path`; it begins an
+ // abstract path.
+ let mut sockaddr = unsafe { sockaddr.assume_init() };
+
+ sockaddr.sun_family = libc::AF_UNIX as libc::sa_family_t;
+ let mut socklen = mem::size_of_val(&sockaddr) as libc::socklen_t;
+
+ #[cfg(not(any(
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "solaris"
+ )))]
+ let socket = {
+ let flags = libc::SOCK_NONBLOCK | libc::SOCK_CLOEXEC;
+ syscall!(accept4(
+ listener.as_raw_fd(),
+ &mut sockaddr as *mut libc::sockaddr_un as *mut libc::sockaddr,
+ &mut socklen,
+ flags
+ ))
+ .map(|socket| unsafe { net::UnixStream::from_raw_fd(socket) })
+ };
+
+ #[cfg(any(
+ target_os = "ios",
+ target_os = "macos",
+ target_os = "netbsd",
+ target_os = "solaris"
+ ))]
+ let socket = syscall!(accept(
+ listener.as_raw_fd(),
+ &mut sockaddr as *mut libc::sockaddr_un as *mut libc::sockaddr,
+ &mut socklen,
+ ))
+ .and_then(|socket| {
+ // Ensure the socket is closed if either of the `fcntl` calls
+ // error below.
+ let s = unsafe { net::UnixStream::from_raw_fd(socket) };
+ syscall!(fcntl(socket, libc::F_SETFD, libc::FD_CLOEXEC)).map(|_| s)
+ });
+
+ socket
+ .map(UnixStream::from_std)
+ .map(|stream| (stream, SocketAddr::from_parts(sockaddr, socklen)))
+}
+
+pub(crate) fn local_addr(listener: &net::UnixListener) -> io::Result<SocketAddr> {
+ super::local_addr(listener.as_raw_fd())
+}
diff --git a/src/sys/unix/uds/mod.rs b/src/sys/unix/uds/mod.rs
new file mode 100644
index 0000000..3ec829f
--- /dev/null
+++ b/src/sys/unix/uds/mod.rs
@@ -0,0 +1,149 @@
+mod socketaddr;
+pub use self::socketaddr::SocketAddr;
+
+/// Get the `sun_path` field offset of `sockaddr_un` for the target OS.
+///
+/// On Linux, this funtion equates to the same value as
+/// `size_of::<sa_family_t>()`, but some other implementations include
+/// other fields before `sun_path`, so the expression more portably
+/// describes the size of the address structure.
+pub(in crate::sys) fn path_offset(sockaddr: &libc::sockaddr_un) -> usize {
+ let base = sockaddr as *const _ as usize;
+ let path = &sockaddr.sun_path as *const _ as usize;
+ path - base
+}
+
+cfg_os_poll! {
+ use std::cmp::Ordering;
+ use std::os::unix::ffi::OsStrExt;
+ use std::os::unix::io::{RawFd, FromRawFd};
+ use std::path::Path;
+ use std::{io, mem};
+
+ pub(crate) mod datagram;
+ pub(crate) mod listener;
+ pub(crate) mod stream;
+
+ pub(in crate::sys) fn socket_addr(path: &Path) -> io::Result<(libc::sockaddr_un, libc::socklen_t)> {
+ let sockaddr = mem::MaybeUninit::<libc::sockaddr_un>::zeroed();
+
+ // This is safe to assume because a `libc::sockaddr_un` filled with `0`
+ // bytes is properly initialized.
+ //
+ // `0` is a valid value for `sockaddr_un::sun_family`; it is
+ // `libc::AF_UNSPEC`.
+ //
+ // `[0; 108]` is a valid value for `sockaddr_un::sun_path`; it begins an
+ // abstract path.
+ let mut sockaddr = unsafe { sockaddr.assume_init() };
+
+ sockaddr.sun_family = libc::AF_UNIX as libc::sa_family_t;
+
+ let bytes = path.as_os_str().as_bytes();
+ match (bytes.get(0), bytes.len().cmp(&sockaddr.sun_path.len())) {
+ // Abstract paths don't need a null terminator
+ (Some(&0), Ordering::Greater) => {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "path must be no longer than libc::sockaddr_un.sun_path",
+ ));
+ }
+ (_, Ordering::Greater) | (_, Ordering::Equal) => {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "path must be shorter than libc::sockaddr_un.sun_path",
+ ));
+ }
+ _ => {}
+ }
+
+ for (dst, src) in sockaddr.sun_path.iter_mut().zip(bytes.iter()) {
+ *dst = *src as libc::c_char;
+ }
+
+ let offset = path_offset(&sockaddr);
+ let mut socklen = offset + bytes.len();
+
+ match bytes.get(0) {
+ // The struct has already been zeroes so the null byte for pathname
+ // addresses is already there.
+ Some(&0) | None => {}
+ Some(_) => socklen += 1,
+ }
+
+ Ok((sockaddr, socklen as libc::socklen_t))
+ }
+
+ fn pair<T>(flags: libc::c_int) -> io::Result<(T, T)>
+ where T: FromRawFd,
+ {
+ #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "solaris")))]
+ let flags = flags | libc::SOCK_NONBLOCK | libc::SOCK_CLOEXEC;
+
+ let mut fds = [-1; 2];
+ syscall!(socketpair(libc::AF_UNIX, flags, 0, fds.as_mut_ptr()))?;
+ let pair = unsafe { (T::from_raw_fd(fds[0]), T::from_raw_fd(fds[1])) };
+
+ // Darwin and Solaris do not have SOCK_NONBLOCK or SOCK_CLOEXEC.
+ //
+ // In order to set those flags, additional `fcntl` sys calls must be
+ // performed. If a `fnctl` fails after the sockets have been created,
+ // the file descriptors will leak. Creating `pair` above ensures that if
+ // there is an error, the file descriptors are closed.
+ #[cfg(any(target_os = "ios", target_os = "macos", target_os = "solaris"))]
+ {
+ syscall!(fcntl(fds[0], libc::F_SETFL, libc::O_NONBLOCK))?;
+ syscall!(fcntl(fds[0], libc::F_SETFD, libc::FD_CLOEXEC))?;
+ syscall!(fcntl(fds[1], libc::F_SETFL, libc::O_NONBLOCK))?;
+ syscall!(fcntl(fds[1], libc::F_SETFD, libc::FD_CLOEXEC))?;
+ }
+ Ok(pair)
+ }
+
+ // The following functions can't simply be replaced with a call to
+ // `net::UnixDatagram` because of our `SocketAddr` type.
+
+ fn local_addr(socket: RawFd) -> io::Result<SocketAddr> {
+ SocketAddr::new(|sockaddr, socklen| syscall!(getsockname(socket, sockaddr, socklen)))
+ }
+
+ fn peer_addr(socket: RawFd) -> io::Result<SocketAddr> {
+ SocketAddr::new(|sockaddr, socklen| syscall!(getpeername(socket, sockaddr, socklen)))
+ }
+
+ #[cfg(test)]
+ mod tests {
+ use super::{path_offset, socket_addr};
+ use std::path::Path;
+ use std::str;
+
+ #[test]
+ fn pathname_address() {
+ const PATH: &str = "./foo/bar.txt";
+ const PATH_LEN: usize = 13;
+
+ // Pathname addresses do have a null terminator, so `socklen` is
+ // expected to be `PATH_LEN` + `offset` + 1.
+ let path = Path::new(PATH);
+ let (sockaddr, actual) = socket_addr(path).unwrap();
+ let offset = path_offset(&sockaddr);
+ let expected = PATH_LEN + offset + 1;
+ assert_eq!(expected as libc::socklen_t, actual)
+ }
+
+ #[test]
+ fn abstract_address() {
+ const PATH: &[u8] = &[0, 116, 111, 107, 105, 111];
+ const PATH_LEN: usize = 6;
+
+ // Abstract addresses do not have a null terminator, so `socklen` is
+ // expected to be `PATH_LEN` + `offset`.
+ let abstract_path = str::from_utf8(PATH).unwrap();
+ let path = Path::new(abstract_path);
+ let (sockaddr, actual) = socket_addr(path).unwrap();
+ let offset = path_offset(&sockaddr);
+ let expected = PATH_LEN + offset;
+ assert_eq!(expected as libc::socklen_t, actual)
+ }
+ }
+}
diff --git a/src/sys/unix/uds/socketaddr.rs b/src/sys/unix/uds/socketaddr.rs
new file mode 100644
index 0000000..69f311a
--- /dev/null
+++ b/src/sys/unix/uds/socketaddr.rs
@@ -0,0 +1,120 @@
+use super::path_offset;
+use std::ffi::OsStr;
+use std::os::unix::ffi::OsStrExt;
+use std::path::Path;
+use std::{ascii, fmt};
+
+/// An address associated with a `mio` specific Unix socket.
+///
+/// This is implemented instead of imported from [`net::SocketAddr`] because
+/// there is no way to create a [`net::SocketAddr`]. One must be returned by
+/// [`accept`], so this is returned instead.
+///
+/// [`net::SocketAddr`]: std::os::unix::net::SocketAddr
+/// [`accept`]: #method.accept
+pub struct SocketAddr {
+ sockaddr: libc::sockaddr_un,
+ socklen: libc::socklen_t,
+}
+
+struct AsciiEscaped<'a>(&'a [u8]);
+
+enum AddressKind<'a> {
+ Unnamed,
+ Pathname(&'a Path),
+ Abstract(&'a [u8]),
+}
+
+impl SocketAddr {
+ fn address(&self) -> AddressKind<'_> {
+ let offset = path_offset(&self.sockaddr);
+ let len = self.socklen as usize - offset;
+ let path = unsafe { &*(&self.sockaddr.sun_path as *const [libc::c_char] as *const [u8]) };
+
+ // macOS seems to return a len of 16 and a zeroed sun_path for unnamed addresses
+ if len == 0
+ || (cfg!(not(any(target_os = "linux", target_os = "android")))
+ && self.sockaddr.sun_path[0] == 0)
+ {
+ AddressKind::Unnamed
+ } else if self.sockaddr.sun_path[0] == 0 {
+ AddressKind::Abstract(&path[1..len])
+ } else {
+ AddressKind::Pathname(OsStr::from_bytes(&path[..len - 1]).as_ref())
+ }
+ }
+}
+
+cfg_os_poll! {
+ use std::{io, mem};
+
+ impl SocketAddr {
+ pub(crate) fn new<F>(f: F) -> io::Result<SocketAddr>
+ where
+ F: FnOnce(*mut libc::sockaddr, &mut libc::socklen_t) -> io::Result<libc::c_int>,
+ {
+ let mut sockaddr = {
+ let sockaddr = mem::MaybeUninit::<libc::sockaddr_un>::zeroed();
+ unsafe { sockaddr.assume_init() }
+ };
+
+ let raw_sockaddr = &mut sockaddr as *mut libc::sockaddr_un as *mut libc::sockaddr;
+ let mut socklen = mem::size_of_val(&sockaddr) as libc::socklen_t;
+
+ f(raw_sockaddr, &mut socklen)?;
+ Ok(SocketAddr::from_parts(sockaddr, socklen))
+ }
+
+ pub(crate) fn from_parts(sockaddr: libc::sockaddr_un, socklen: libc::socklen_t) -> SocketAddr {
+ SocketAddr { sockaddr, socklen }
+ }
+
+ /// Returns `true` if the address is unnamed.
+ ///
+ /// Documentation reflected in [`SocketAddr`]
+ ///
+ /// [`SocketAddr`]: std::os::unix::net::SocketAddr
+ pub fn is_unnamed(&self) -> bool {
+ if let AddressKind::Unnamed = self.address() {
+ true
+ } else {
+ false
+ }
+ }
+
+ /// Returns the contents of this address if it is a `pathname` address.
+ ///
+ /// Documentation reflected in [`SocketAddr`]
+ ///
+ /// [`SocketAddr`]: std::os::unix::net::SocketAddr
+ pub fn as_pathname(&self) -> Option<&Path> {
+ if let AddressKind::Pathname(path) = self.address() {
+ Some(path)
+ } else {
+ None
+ }
+ }
+ }
+}
+
+impl fmt::Debug for SocketAddr {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self.address() {
+ AddressKind::Unnamed => write!(fmt, "(unnamed)"),
+ AddressKind::Abstract(name) => write!(fmt, "{} (abstract)", AsciiEscaped(name)),
+ AddressKind::Pathname(path) => write!(fmt, "{:?} (pathname)", path),
+ }
+ }
+}
+
+// ===== impl AsciiEscaped =====
+
+impl<'a> fmt::Display for AsciiEscaped<'a> {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(fmt, "\"")?;
+ for byte in self.0.iter().cloned().flat_map(ascii::escape_default) {
+ write!(fmt, "{}", byte as char)?;
+ }
+ write!(fmt, "\"")
+ }
+}
diff --git a/src/sys/unix/uds/stream.rs b/src/sys/unix/uds/stream.rs
new file mode 100644
index 0000000..149dd14
--- /dev/null
+++ b/src/sys/unix/uds/stream.rs
@@ -0,0 +1,39 @@
+use super::{socket_addr, SocketAddr};
+use crate::sys::unix::net::new_socket;
+
+use std::io;
+use std::os::unix::io::{AsRawFd, FromRawFd};
+use std::os::unix::net;
+use std::path::Path;
+
+pub(crate) fn connect(path: &Path) -> io::Result<net::UnixStream> {
+ let socket = new_socket(libc::AF_UNIX, libc::SOCK_STREAM)?;
+ let (sockaddr, socklen) = socket_addr(path)?;
+ let sockaddr = &sockaddr as *const libc::sockaddr_un as *const libc::sockaddr;
+
+ match syscall!(connect(socket, sockaddr, socklen)) {
+ Ok(_) => {}
+ Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {}
+ Err(e) => {
+ // Close the socket if we hit an error, ignoring the error
+ // from closing since we can't pass back two errors.
+ let _ = unsafe { libc::close(socket) };
+
+ return Err(e);
+ }
+ }
+
+ Ok(unsafe { net::UnixStream::from_raw_fd(socket) })
+}
+
+pub(crate) fn pair() -> io::Result<(net::UnixStream, net::UnixStream)> {
+ super::pair(libc::SOCK_STREAM)
+}
+
+pub(crate) fn local_addr(socket: &net::UnixStream) -> io::Result<SocketAddr> {
+ super::local_addr(socket.as_raw_fd())
+}
+
+pub(crate) fn peer_addr(socket: &net::UnixStream) -> io::Result<SocketAddr> {
+ super::peer_addr(socket.as_raw_fd())
+}