435 lines
12 KiB
C++
435 lines
12 KiB
C++
#include "io_unix.h"
|
|
|
|
#include <sstream>
|
|
|
|
namespace saw {
|
|
namespace unix {
|
|
i_fd_owner::i_fd_owner(unix_event_port &event_port, int file_descriptor,
|
|
int fd_flags, uint32_t event_mask)
|
|
: event_port_{event_port}, file_descriptor_{file_descriptor},
|
|
fd_flags_{fd_flags}, event_mask_{event_mask} {
|
|
event_port_.subscribe(*this, file_descriptor, event_mask);
|
|
}
|
|
|
|
i_fd_owner::~i_fd_owner() {
|
|
if (file_descriptor_ >= 0) {
|
|
event_port_.unsubscribe(file_descriptor_);
|
|
::close(file_descriptor_);
|
|
}
|
|
}
|
|
|
|
ssize_t unix_read(int fd, void *buffer, size_t length) {
|
|
return ::recv(fd, buffer, length, 0);
|
|
}
|
|
|
|
ssize_t unix_write(int fd, const void *buffer, size_t length) {
|
|
return ::send(fd, buffer, length, 0);
|
|
}
|
|
|
|
unix_io_stream::unix_io_stream(unix_event_port &event_port, int file_descriptor,
|
|
int fd_flags, uint32_t event_mask)
|
|
: i_fd_owner{event_port, file_descriptor, fd_flags,
|
|
event_mask | EPOLLRDHUP} {}
|
|
|
|
error_or<size_t> unix_io_stream::read(void *buffer, size_t length) {
|
|
ssize_t read_bytes = unix_read(fd(), buffer, length);
|
|
if (read_bytes > 0) {
|
|
return static_cast<size_t>(read_bytes);
|
|
} else if (read_bytes == 0) {
|
|
return critical_error("Disconnected", error::code::Disconnected);
|
|
}
|
|
|
|
return recoverable_error("Currently busy");
|
|
}
|
|
|
|
conveyor<void> unix_io_stream::read_ready() {
|
|
auto caf = new_conveyor_and_feeder<void>();
|
|
read_ready_ = std::move(caf.feeder);
|
|
return std::move(caf.conveyor);
|
|
}
|
|
|
|
conveyor<void> unix_io_stream::on_read_disconnected() {
|
|
auto caf = new_conveyor_and_feeder<void>();
|
|
on_read_disconnect_ = std::move(caf.feeder);
|
|
return std::move(caf.conveyor);
|
|
}
|
|
|
|
error_or<size_t> unix_io_stream::write(const void *buffer, size_t length) {
|
|
ssize_t write_bytes = unix_write(fd(), buffer, length);
|
|
if (write_bytes > 0) {
|
|
return static_cast<size_t>(write_bytes);
|
|
}
|
|
|
|
int error = errno;
|
|
|
|
if (error == EAGAIN || error == EWOULDBLOCK) {
|
|
return recoverable_error("Currently busy");
|
|
}
|
|
|
|
return critical_error("Disconnected", error::code::Disconnected);
|
|
}
|
|
|
|
conveyor<void> unix_io_stream::write_ready() {
|
|
auto caf = new_conveyor_and_feeder<void>();
|
|
write_ready_ = std::move(caf.feeder);
|
|
return std::move(caf.conveyor);
|
|
}
|
|
|
|
void unix_io_stream::notify(uint32_t mask) {
|
|
if (mask & EPOLLOUT) {
|
|
if (write_ready_) {
|
|
write_ready_->feed();
|
|
}
|
|
}
|
|
|
|
if (mask & EPOLLIN) {
|
|
if (read_ready_) {
|
|
read_ready_->feed();
|
|
}
|
|
}
|
|
|
|
if (mask & EPOLLRDHUP) {
|
|
if (on_read_disconnect_) {
|
|
on_read_disconnect_->feed();
|
|
}
|
|
}
|
|
}
|
|
|
|
unix_server::unix_server(unix_event_port &event_port, int file_descriptor,
|
|
int fd_flags)
|
|
: i_fd_owner{event_port, file_descriptor, fd_flags, EPOLLIN} {}
|
|
|
|
conveyor<own<io_stream>> unix_server::accept() {
|
|
auto caf = new_conveyor_and_feeder<own<io_stream>>();
|
|
accept_feeder_ = std::move(caf.feeder);
|
|
return std::move(caf.conveyor);
|
|
}
|
|
|
|
void unix_server::notify(uint32_t mask) {
|
|
if (mask & EPOLLIN) {
|
|
if (accept_feeder_) {
|
|
struct ::sockaddr_storage address;
|
|
socklen_t address_length = sizeof(address);
|
|
|
|
int accept_fd =
|
|
::accept4(fd(), reinterpret_cast<struct ::sockaddr *>(&address),
|
|
&address_length, SOCK_NONBLOCK | SOCK_CLOEXEC);
|
|
if (accept_fd < 0) {
|
|
return;
|
|
}
|
|
auto fd_stream = heap<unix_io_stream>(event_port_, accept_fd, 0,
|
|
EPOLLIN | EPOLLOUT);
|
|
accept_feeder_->feed(std::move(fd_stream));
|
|
}
|
|
}
|
|
}
|
|
|
|
unix_datagram::unix_datagram(unix_event_port &event_port, int file_descriptor,
|
|
int fd_flags)
|
|
: i_fd_owner{event_port, file_descriptor, fd_flags, EPOLLIN | EPOLLOUT} {}
|
|
|
|
namespace {
|
|
ssize_t unix_read_msg(int fd, void *buffer, size_t length) {
|
|
struct ::sockaddr_storage their_addr;
|
|
socklen_t addr_len = sizeof(sockaddr_storage);
|
|
return ::recvfrom(fd, buffer, length, 0,
|
|
reinterpret_cast<struct ::sockaddr *>(&their_addr),
|
|
&addr_len);
|
|
}
|
|
|
|
ssize_t unix_write_msg(int fd, const void *buffer, size_t length,
|
|
::sockaddr *dest_addr, socklen_t dest_addr_len) {
|
|
|
|
return ::sendto(fd, buffer, length, 0, dest_addr, dest_addr_len);
|
|
}
|
|
} // namespace
|
|
|
|
error_or<size_t> unix_datagram::read(void *buffer, size_t length) {
|
|
ssize_t read_bytes = unix_read_msg(fd(), buffer, length);
|
|
if (read_bytes > 0) {
|
|
return static_cast<size_t>(read_bytes);
|
|
}
|
|
return recoverable_error("Currently busy");
|
|
}
|
|
|
|
conveyor<void> unix_datagram::read_ready() {
|
|
auto caf = new_conveyor_and_feeder<void>();
|
|
read_ready_ = std::move(caf.feeder);
|
|
return std::move(caf.conveyor);
|
|
}
|
|
|
|
error_or<size_t> unix_datagram::write(const void *buffer, size_t length,
|
|
network_address &dest) {
|
|
unix_network_address &unix_dest = static_cast<unix_network_address &>(dest);
|
|
socket_address &sock_addr = unix_dest.unix_address();
|
|
socklen_t sock_addr_length = sock_addr.get_raw_length();
|
|
ssize_t write_bytes = unix_write_msg(fd(), buffer, length,
|
|
sock_addr.get_raw(), sock_addr_length);
|
|
if (write_bytes > 0) {
|
|
return static_cast<size_t>(write_bytes);
|
|
}
|
|
return recoverable_error("Currently busy");
|
|
}
|
|
|
|
conveyor<void> unix_datagram::write_ready() {
|
|
auto caf = new_conveyor_and_feeder<void>();
|
|
write_ready_ = std::move(caf.feeder);
|
|
return std::move(caf.conveyor);
|
|
}
|
|
|
|
void unix_datagram::notify(uint32_t mask) {
|
|
if (mask & EPOLLOUT) {
|
|
if (write_ready_) {
|
|
write_ready_->feed();
|
|
}
|
|
}
|
|
|
|
if (mask & EPOLLIN) {
|
|
if (read_ready_) {
|
|
read_ready_->feed();
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
bool begins_with(const std::string_view &viewed,
|
|
const std::string_view &begins) {
|
|
return viewed.size() >= begins.size() &&
|
|
viewed.compare(0, begins.size(), begins) == 0;
|
|
}
|
|
|
|
std::variant<unix_network_address, unix_network_address *>
|
|
translate_network_address_to_unix_network_address(network_address &addr) {
|
|
auto addr_variant = addr.representation();
|
|
std::variant<unix_network_address, unix_network_address *> os_addr =
|
|
std::visit(
|
|
[](auto &arg)
|
|
-> std::variant<unix_network_address, unix_network_address *> {
|
|
using T = std::decay_t<decltype(arg)>;
|
|
|
|
if constexpr (std::is_same_v<T, os_network_address *>) {
|
|
return static_cast<unix_network_address *>(arg);
|
|
}
|
|
|
|
auto sock_addrs = socket_address::resolve(
|
|
std::string_view{arg->address()}, arg->port());
|
|
|
|
return unix_network_address{arg->address(), arg->port(),
|
|
std::move(sock_addrs)};
|
|
},
|
|
addr_variant);
|
|
return os_addr;
|
|
}
|
|
|
|
unix_network_address &translate_to_unix_address_ref(
|
|
std::variant<unix_network_address, unix_network_address *> &addr_variant) {
|
|
return std::visit(
|
|
[](auto &arg) -> unix_network_address & {
|
|
using T = std::decay_t<decltype(arg)>;
|
|
|
|
if constexpr (std::is_same_v<T, unix_network_address>) {
|
|
return arg;
|
|
} else if constexpr (std::is_same_v<T, unix_network_address *>) {
|
|
return *arg;
|
|
} else {
|
|
static_assert(true, "Cases exhausted");
|
|
}
|
|
},
|
|
addr_variant);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
own<server> unix_network::listen(network_address &addr) {
|
|
auto unix_addr_storage =
|
|
translate_network_address_to_unix_network_address(addr);
|
|
unix_network_address &address =
|
|
translate_to_unix_address_ref(unix_addr_storage);
|
|
|
|
assert(address.unix_address_size() > 0);
|
|
if (address.unix_address_size() == 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
int fd = address.unix_address(0).socket(SOCK_STREAM);
|
|
if (fd < 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
int val = 1;
|
|
int rc = ::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
|
|
if (rc < 0) {
|
|
::close(fd);
|
|
return nullptr;
|
|
}
|
|
|
|
bool failed = address.unix_address(0).bind(fd);
|
|
if (failed) {
|
|
::close(fd);
|
|
return nullptr;
|
|
}
|
|
|
|
::listen(fd, SOMAXCONN);
|
|
|
|
return heap<unix_server>(event_port_, fd, 0);
|
|
}
|
|
|
|
conveyor<own<io_stream>> unix_network::connect(network_address &addr) {
|
|
auto unix_addr_storage =
|
|
translate_network_address_to_unix_network_address(addr);
|
|
unix_network_address &address =
|
|
translate_to_unix_address_ref(unix_addr_storage);
|
|
|
|
assert(address.unix_address_size() > 0);
|
|
if (address.unix_address_size() == 0) {
|
|
return conveyor<own<io_stream>>{critical_error("No address found")};
|
|
}
|
|
|
|
int fd = address.unix_address(0).socket(SOCK_STREAM);
|
|
if (fd < 0) {
|
|
return conveyor<own<io_stream>>{critical_error("Couldn't open socket")};
|
|
}
|
|
|
|
own<unix_io_stream> io_str =
|
|
heap<unix_io_stream>(event_port_, fd, 0, EPOLLIN | EPOLLOUT);
|
|
|
|
bool success = false;
|
|
for (size_t i = 0; i < address.unix_address_size(); ++i) {
|
|
socket_address &addr_iter = address.unix_address(i);
|
|
int status =
|
|
::connect(fd, addr_iter.get_raw(), addr_iter.get_raw_length());
|
|
if (status < 0) {
|
|
int error = errno;
|
|
/*
|
|
* It's not connected yet...
|
|
* But edge triggered epolling means that it'll
|
|
* be ready when the signal is triggered
|
|
*/
|
|
|
|
/// @todo Add limit node when implemented
|
|
if (error == EINPROGRESS) {
|
|
/*
|
|
Conveyor<void> write_ready = io_stream->writeReady();
|
|
return write_ready.then(
|
|
[ios{std::move(io_stream)}]() mutable {
|
|
ios->write_ready = nullptr;
|
|
return std::move(ios);
|
|
});
|
|
*/
|
|
success = true;
|
|
break;
|
|
} else if (error != EINTR) {
|
|
/// @todo Push error message from
|
|
return conveyor<own<io_stream>>{
|
|
critical_error("Couldn't connect")};
|
|
}
|
|
} else {
|
|
success = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!success) {
|
|
return critical_error("Couldn't connect");
|
|
}
|
|
|
|
return conveyor<own<io_stream>>{std::move(io_str)};
|
|
}
|
|
|
|
own<datagram> unix_network::datagram(network_address &addr) {
|
|
auto unix_addr_storage =
|
|
translate_network_address_to_unix_network_address(addr);
|
|
unix_network_address &address =
|
|
translate_to_unix_address_ref(unix_addr_storage);
|
|
|
|
SAW_ASSERT(address.unix_address_size() > 0) { return nullptr; }
|
|
|
|
int fd = address.unix_address(0).socket(SOCK_DGRAM);
|
|
|
|
int optval = 1;
|
|
int rc =
|
|
::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
|
|
if (rc < 0) {
|
|
::close(fd);
|
|
return nullptr;
|
|
}
|
|
|
|
bool failed = address.unix_address(0).bind(fd);
|
|
if (failed) {
|
|
::close(fd);
|
|
return nullptr;
|
|
}
|
|
/// @todo
|
|
return heap<unix_datagram>(event_port_, fd, 0);
|
|
}
|
|
|
|
const std::string &unix_network_address::address() const { return path_; }
|
|
|
|
uint16_t unix_network_address::port() const { return port_hint_; }
|
|
|
|
socket_address &unix_network_address::unix_address(size_t i) {
|
|
assert(i < addresses_.size());
|
|
/// @todo change from list to vector?
|
|
return addresses_.at(i);
|
|
}
|
|
|
|
size_t unix_network_address::unix_address_size() const {
|
|
return addresses_.size();
|
|
}
|
|
|
|
unix_network::unix_network(unix_event_port &event) : event_port_{event} {}
|
|
|
|
conveyor<own<network_address>>
|
|
unix_network::resolve_address(const std::string &path, uint16_t port_hint) {
|
|
std::string_view addr_view{path};
|
|
{
|
|
std::string_view str_begins_with = "unix:";
|
|
if (begins_with(addr_view, str_begins_with)) {
|
|
addr_view.remove_prefix(str_begins_with.size());
|
|
}
|
|
}
|
|
|
|
std::vector<socket_address> addresses =
|
|
socket_address::resolve(addr_view, port_hint);
|
|
|
|
return conveyor<own<network_address>>{
|
|
heap<unix_network_address>(path, port_hint, std::move(addresses))};
|
|
}
|
|
|
|
unix_io_provider::unix_io_provider(unix_event_port &port_ref,
|
|
own<event_port> port)
|
|
: event_port_{port_ref}, event_loop_{std::move(port)}, unix_network_{
|
|
port_ref} {}
|
|
|
|
own<input_stream> unix_io_provider::wrap_input_fd(int fd) {
|
|
return heap<unix_io_stream>(event_port_, fd, 0, EPOLLIN);
|
|
}
|
|
|
|
class network &unix_io_provider::network() {
|
|
return static_cast<class network &>(unix_network_);
|
|
}
|
|
|
|
class event_loop &unix_io_provider::event_loop() {
|
|
return event_loop_;
|
|
}
|
|
|
|
} // namespace unix
|
|
|
|
error_or<async_io_context> setup_async_io() {
|
|
using namespace unix;
|
|
try {
|
|
own<unix_event_port> prt = heap<unix_event_port>();
|
|
unix_event_port &prt_ref = *prt;
|
|
|
|
own<unix_io_provider> io_provider =
|
|
heap<unix_io_provider>(prt_ref, std::move(prt));
|
|
|
|
event_loop &loop_ref = io_provider->event_loop();
|
|
|
|
return {{std::move(io_provider), loop_ref, prt_ref}};
|
|
} catch (std::bad_alloc &) {
|
|
return critical_error("Out of memory");
|
|
}
|
|
}
|
|
} // namespace saw
|