Skip to content

Implement Unix domain sockets in libnative #11484

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/libnative/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,10 @@ impl rtio::IoFactory for IoFactory {
net::UdpSocket::bind(addr).map(|u| ~u as ~RtioUdpSocket)
}
fn unix_bind(&mut self, _path: &CString) -> IoResult<~RtioUnixListener> {
Err(unimpl())
net::UnixListener::bind(_path).map(|s| ~s as ~RtioUnixListener)
}
fn unix_connect(&mut self, _path: &CString) -> IoResult<~RtioPipe> {
Err(unimpl())
net::UnixStream::connect(_path, libc::SOCK_STREAM).map(|s| ~s as ~RtioPipe)
}
fn get_host_addresses(&mut self, _host: Option<&str>, _servname: Option<&str>,
_hint: Option<ai::Hint>) -> IoResult<~[ai::Info]> {
Expand Down
286 changes: 286 additions & 0 deletions src/libnative/io/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use std::libc;
use std::mem;
use std::rt::rtio;
use std::unstable::intrinsics;
use std::c_str::CString;

use super::{IoResult, retry};
use super::file::keep_going;
Expand Down Expand Up @@ -88,6 +89,22 @@ fn addr_to_sockaddr(addr: ip::SocketAddr) -> (libc::sockaddr_storage, uint) {
}
}

fn addr_to_sockaddr_un(addr: &CString) -> (libc::sockaddr_storage, uint) {
unsafe {
let storage: libc::sockaddr_storage = intrinsics::init();
let s: *mut libc::sockaddr_un = cast::transmute(&storage);
(*s).sun_family = libc::AF_UNIX as libc::sa_family_t;
let mut i = 0;
for c in addr.iter() {
(*s).sun_path[i] = c;
i += 1;
}

let len = mem::size_of::<libc::sa_family_t>() + i + 1; //count the null terminator
return (storage, len);
}
}

fn socket(addr: ip::SocketAddr, ty: libc::c_int) -> IoResult<sock_t> {
unsafe {
let fam = match addr.ip {
Expand All @@ -101,6 +118,15 @@ fn socket(addr: ip::SocketAddr, ty: libc::c_int) -> IoResult<sock_t> {
}
}

fn unix_socket(ty: libc::c_int) -> IoResult<sock_t> {
unsafe {
match libc::socket(libc::AF_UNIX, ty, 0) {
-1 => Err(super::last_error()),
fd => Ok(fd)
}
}
}

fn setsockopt<T>(fd: sock_t, opt: libc::c_int, val: libc::c_int,
payload: T) -> IoResult<()> {
unsafe {
Expand Down Expand Up @@ -176,6 +202,25 @@ fn sockaddr_to_addr(storage: &libc::sockaddr_storage,
}
}

fn sockaddr_to_unix(storage: &libc::sockaddr_storage,
len: uint) -> IoResult<CString> {
match storage.ss_family as libc::c_int {
libc::AF_UNIX => {
assert!(len as uint <= mem::size_of::<libc::sockaddr_un>());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This conditions goes the wrong way, it should be that the provided length is greater than or equal to the size of sockaddr_un

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok. Why an assert, instead of an Err?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An Err would also be fine, I don't think the assert should reasonably trip, but might as well err on the side of caution.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this condition still needs to be reversed.

let storage: &libc::sockaddr_un = unsafe {
cast::transmute(storage)
};
unsafe {
let buf:[libc::c_char, ..libc::sun_len] = storage.sun_path;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to copy sun_path into a local, you can call as_ptr on it directly.

Ok(CString::new(buf.as_ptr(), false))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very unsafe because there's nothing tying the lifetime of the CString to the storage, meaning when you return it down below you're pointing to free'd memory. This should create a Path which owns its memory.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, on second thought, a Path may not make sense for windows, so for now we should probably keep returning a CString. The return value of this function should be a CString which owns its memory, however (probably via strdup)

}
}
_ => {
Err(io::standard_error(io::OtherIoError))
}
}
}

#[cfg(unix)]
pub fn init() {}

Expand Down Expand Up @@ -584,3 +629,244 @@ impl rtio::RtioUdpSocket for UdpSocket {
impl Drop for UdpSocket {
fn drop(&mut self) { unsafe { close(self.fd) } }
}


#[cfg(not(windows))]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably get removed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should still get removed.

////////////////////////////////////////////////////////////////////////////////
// Unix
////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
// Unix streams
////////////////////////////////////////////////////////////////////////////////

pub struct UnixStream {
priv fd: sock_t,
}

impl UnixStream {
pub fn connect(addr: &CString, ty: libc::c_int) -> IoResult<UnixStream> {
// the sun_path length is limited to SUN_LEN (with null)
if(addr.len() > libc::sun_len -1) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These checks should go into addr_to_sockaddr_un

return Err(io::IoError {
kind: io::OtherIoError,
desc: "path must be smaller than SUN_LEN",
detail: None,
})
}
unsafe {
unix_socket(ty).and_then(|fd| {
let (addr, len) = addr_to_sockaddr_un(addr);
let ret = UnixStream{ fd: fd };
let addrp = &addr as *libc::sockaddr_storage;
match retry(|| {
libc::connect(fd, addrp as *libc::sockaddr,
len as libc::socklen_t)
}) {
-1 => return Err(super::last_error()),
_ => return Ok(ret)
}
})
}
}

pub fn dgram_bind(addr: &CString) -> IoResult<UnixStream> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should return a UnixStream. This should probably be entirely separate in a UnixSocket implementation because a dgram pipe (which I assume is the same as UDP) is not a stream at all.

// the sun_path length is limited to SUN_LEN (with null)
if(addr.len() > libc::sun_len - 1) {
return Err(io::IoError {
kind: io::OtherIoError,
desc: "path must be smaller than SUN_LEN",
detail: None,
})
}
unsafe {
unix_socket(libc::SOCK_DGRAM).and_then(|fd| {
let (addr, len) = addr_to_sockaddr_un(addr);
let ret = UnixStream{ fd: fd };
let addrp = &addr as *libc::sockaddr_storage;
match libc::bind(fd, addrp as *libc::sockaddr,
len as libc::socklen_t) {
-1 => return Err(super::last_error()),
_ => return Ok(ret)
}
})
}
}

pub fn fd(&self) -> sock_t { self.fd }
}

impl rtio::RtioPipe for UnixStream {
fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
let ret = retry(|| {
unsafe {
libc::recv(self.fd,
buf.as_ptr() as *mut libc::c_void,
buf.len() as wrlen,
0) as libc::c_int
}
});
if ret == 0 {
Err(io::standard_error(io::EndOfFile))
} else if ret < 0 {
Err(super::last_error())
} else {
Ok(ret as uint)
}
}
fn write(&mut self, buf: &[u8]) -> IoResult<()> {
let ret = keep_going(buf, |buf, len| {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is necessarily correct for a dgram unix socket. For a stream I would expect to continue writing until the whole buffer is gone, but for a dgram version I would expect write to send one packet and then tell me if the packet was too large.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah nevermind, forgot the traits were separate.

unsafe {
libc::send(self.fd,
buf as *mut libc::c_void,
len as wrlen,
0) as i64
}
});
if ret < 0 {
Err(super::last_error())
} else {
Ok(())
}
}
}

impl rtio::RtioDatagramPipe for UnixStream {
fn recvfrom(&mut self, buf: &mut [u8]) -> IoResult<(uint, CString)> {
unsafe {
let mut storage: libc::sockaddr_storage = intrinsics::init();
let storagep = &mut storage as *mut libc::sockaddr_storage;
let mut addrlen: libc::socklen_t =
mem::size_of::<libc::sockaddr_storage>() as libc::socklen_t;
let ret = retry(|| {
libc::recvfrom(self.fd,
buf.as_ptr() as *mut libc::c_void,
buf.len() as msglen_t,
0,
storagep as *mut libc::sockaddr,
&mut addrlen) as libc::c_int
});
if ret < 0 { return Err(super::last_error()) }
sockaddr_to_unix(&storage, addrlen as uint).and_then(|addr| {
Ok((ret as uint, addr))
})
}
}

fn sendto(&mut self, buf: &[u8], dst: &CString) -> IoResult<()> {
let (dst, len) = addr_to_sockaddr_un(dst);
let dstp = &dst as *libc::sockaddr_storage;
unsafe {
let ret = retry(|| {
libc::sendto(self.fd,
buf.as_ptr() as *libc::c_void,
buf.len() as msglen_t,
0,
dstp as *libc::sockaddr,
len as libc::socklen_t) as libc::c_int
});
match ret {
-1 => Err(super::last_error()),
n if n as uint != buf.len() => {
Err(io::IoError {
kind: io::OtherIoError,
desc: "couldn't send entire packet at once",
detail: None,
})
}
_ => Ok(())
}
}

}
}

impl Drop for UnixStream {
fn drop(&mut self) { unsafe { close(self.fd); } }
}

////////////////////////////////////////////////////////////////////////////////
// Unix Listener
////////////////////////////////////////////////////////////////////////////////

pub struct UnixListener {
priv fd: sock_t,
}

impl UnixListener {
pub fn bind(addr: &CString) -> IoResult<UnixListener> {
// the sun_path length is limited to SUN_LEN (with null)
if(addr.len() > libc::sun_len - 1) {
return Err(io::IoError {
kind: io::OtherIoError,
desc: "path must be smaller than SUN_LEN",
detail: None,
})
}
unsafe {
unix_socket(libc::SOCK_STREAM).and_then(|fd| {
let (addr, len) = addr_to_sockaddr_un(addr);
let ret = UnixListener{ fd: fd };
let addrp = &addr as *libc::sockaddr_storage;
match libc::bind(fd, addrp as *libc::sockaddr,
len as libc::socklen_t) {
-1 => return Err(super::last_error()),
_ => return Ok(ret)
}
})
}
}

pub fn fd(&self) -> sock_t { self.fd }

pub fn native_listen(self, backlog: int) -> IoResult<UnixAcceptor> {
match unsafe { libc::listen(self.fd, backlog as libc::c_int) } {
-1 => Err(super::last_error()),
_ => Ok(UnixAcceptor { listener: self })
}
}
}

impl rtio::RtioUnixListener for UnixListener {
fn listen(~self) -> IoResult<~rtio::RtioUnixAcceptor> {
self.native_listen(128).map(|a| ~a as ~rtio::RtioUnixAcceptor)
}
}

impl Drop for UnixListener {
fn drop(&mut self) { unsafe { close(self.fd); } }
}

pub struct UnixAcceptor {
priv listener: UnixListener,
}

impl UnixAcceptor {
pub fn fd(&self) -> sock_t { self.listener.fd }

pub fn native_accept(&mut self) -> IoResult<UnixStream> {
unsafe {
let mut storage: libc::sockaddr_storage = intrinsics::init();
let storagep = &mut storage as *mut libc::sockaddr_storage;
let size = mem::size_of::<libc::sockaddr_storage>();
let mut size = size as libc::socklen_t;
match retry(|| {
libc::accept(self.fd(),
storagep as *mut libc::sockaddr,
&mut size as *mut libc::socklen_t) as libc::c_int
}) as sock_t {
-1 => Err(super::last_error()),
fd => Ok(UnixStream { fd: fd })
}
}
}
}

impl rtio::RtioUnixAcceptor for UnixAcceptor {
fn accept(&mut self) -> IoResult<~rtio::RtioPipe> {
self.native_accept().map(|s| ~s as ~rtio::RtioPipe)
}
}


9 changes: 6 additions & 3 deletions src/libstd/io/net/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,14 @@ impl UnixStream {
///
/// # Example
///
///```rust
/// use std::io::net::unix::UnixStream;
/// use std::path::posix::Path;
///
/// let server = Path("path/to/my/socket");
/// let server = Path::new("path/to/my/socket");
/// let mut stream = UnixStream::connect(&server);
/// stream.write([1, 2, 3]);
///
///```
pub fn connect<P: ToCStr>(path: &P) -> Option<UnixStream> {
LocalIo::maybe_raise(|io| {
io.unix_connect(&path.to_c_str()).map(UnixStream::new)
Expand Down Expand Up @@ -91,6 +93,7 @@ impl UnixListener {
///
/// # Example
///
///````rust
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would work, but may as well switch it to three ticks instead of 4

/// use std::io::net::unix::UnixListener;
///
/// let server = Path("path/to/my/socket");
Expand All @@ -99,7 +102,7 @@ impl UnixListener {
/// let mut client = client;
/// client.write([1, 2, 3, 4]);
/// }
///
///```
pub fn bind<P: ToCStr>(path: &P) -> Option<UnixListener> {
LocalIo::maybe_raise(|io| {
io.unix_bind(&path.to_c_str()).map(|s| UnixListener { obj: s })
Expand Down
Loading