genode/ports/src/noux/net/socket_io_channel.h

520 lines
13 KiB
C++

/*
* \brief I/O channel for sockets
* \author Josef Söntgen
* \date 2012-04-12
*/
/*
* Copyright (C) 2011-2013 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _NOUX__SOCKET_IO_CHANNEL_H_
#define _NOUX__SOCKET_IO_CHANNEL_H_
/* Genode includes */
#include <base/printf.h>
/* Noux includes */
#include <io_channel.h>
#include <noux_session/sysio.h>
/* Libc includes */
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netdb.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <errno.h>
namespace Noux {
class Socket_io_channel_backend : public Io_channel_backend
{
private:
int _socket;
public:
Socket_io_channel_backend()
:
_socket(-1)
{ }
Socket_io_channel_backend(int s)
:
_socket(s)
{ }
~Socket_io_channel_backend()
{
if (_socket != -1) {
::shutdown(_socket, SHUT_RDWR);
::close(_socket);
}
}
int type() const { return 1; }
int socket() const { return _socket; }
/**
* Io_channel interface implementation (only needed methods)
*/
bool write(Sysio *sysio, size_t &count)
{
ssize_t result = ::write(_socket, sysio->write_in.chunk,
sysio->write_in.count);
if (result > -1) {
sysio->write_out.count = result;
count = result;
return true;
}
switch (errno) {
/* case EAGAIN: sysio->error.read = Sysio::READ_ERR_AGAIN; break; */
case EWOULDBLOCK: sysio->error.read = Vfs::File_io_service::READ_ERR_WOULD_BLOCK; break;
case EINVAL: sysio->error.read = Vfs::File_io_service::READ_ERR_INVALID; break;
case EIO: sysio->error.read = Vfs::File_io_service::READ_ERR_IO; break;
default:
PDBG("unhandled errno: %d", errno);
break;
}
return false;
}
bool read(Sysio *sysio)
{
size_t const max_count = Genode::min(sysio->read_in.count, sizeof(sysio->read_out.chunk));
ssize_t result = ::read(_socket, sysio->read_out.chunk, max_count);
if (result > -1) {
sysio->read_out.count = result;
return true;
}
switch (errno) {
/* case EAGAIN: sysio->error.read = Sysio::READ_ERR_AGAIN; break; */
case EWOULDBLOCK: sysio->error.read = Vfs::File_io_service::READ_ERR_WOULD_BLOCK; break;
case EINVAL: sysio->error.read = Vfs::File_io_service::READ_ERR_INVALID; break;
case EIO: sysio->error.read = Vfs::File_io_service::READ_ERR_IO; break;
default:
PDBG("unhandled errno: %d", errno);
break;
}
return false;
}
bool fcntl(Sysio *sysio)
{
int cmd = -1;
switch (sysio->fcntl_in.cmd) {
case Sysio::FCNTL_CMD_GET_FILE_STATUS_FLAGS: cmd = F_GETFL; break;
case Sysio::FCNTL_CMD_SET_FILE_STATUS_FLAGS: cmd = F_SETFL; break;
default:
PDBG("invalid fcntl command: %d", sysio->fcntl_in.cmd);
sysio->error.fcntl = Sysio::FCNTL_ERR_CMD_INVALID;
return false;
}
int result = ::fcntl(_socket, cmd, sysio->fcntl_in.long_arg);
sysio->fcntl_out.result = result;
return true;
}
bool dirent(Sysio *sysio) { return false; }
bool ioctl(Sysio *sysio)
{
int request;
switch (sysio->ioctl_in.request) {
case Vfs::File_io_service::IOCTL_OP_FIONBIO: request = FIONBIO; break;
default:
PDBG("invalid ioctl request: %d", sysio->ioctl_in.request);
return false;
}
int result = ::ioctl(_socket, request, NULL);
return result ? false : true;
}
bool check_unblock(bool rd, bool wr, bool ex) const
{
fd_set readfds;
fd_set writefds;
fd_set exceptfds;
int ready;
/**
* The timeout will be overriden in libc's select() function
* but we still need a valid pointer because libc's select()
* will block forever otherwise.
*/
struct timeval timeout = { 0, 0 };
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_ZERO(&exceptfds);
FD_SET(_socket, &readfds);
FD_SET(_socket, &writefds);
FD_SET(_socket, &exceptfds);
ready = ::select(_socket + 1, &readfds, &writefds, &exceptfds, &timeout);
if (ready > 0) {
if (rd) {
if (FD_ISSET(_socket, &readfds))
return true;
}
if (wr) {
if (FD_ISSET(_socket, &writefds))
return true;
}
if (ex) {
if (FD_ISSET(_socket, &exceptfds))
return true;
}
}
/**
* HACK: Since lwip won't mark fds as writable, even if they
* are, if asked multiple times we return true in this
* case. Hopefully that won't break any time soon.
*/
if (wr)
return true;
return false;
}
/**
* Socket methods
*/
int accept(Sysio *sysio)
{
int result;
if (sysio->accept_in.addrlen == 0) {
result = ::accept(_socket, NULL, NULL);
}
else {
result = ::accept(_socket, (sockaddr *)&sysio->accept_in.addr,
&sysio->accept_in.addrlen);
}
if (result == -1) {
switch (errno) {
/* case EAGAIN: sysio->error.accept = Sysio::ACCEPT_ERR_AGAIN; break; */
case ENOMEM: sysio->error.accept = Sysio::ACCEPT_ERR_NO_MEMORY; break;
case EINVAL: sysio->error.accept = Sysio::ACCEPT_ERR_INVALID; break;
case EOPNOTSUPP: sysio->error.accept = Sysio::ACCEPT_ERR_NOT_SUPPORTED; break;
case EWOULDBLOCK: sysio->error.accept = Sysio::ACCEPT_ERR_WOULD_BLOCK; break;
default:
PDBG("unhandled errno: %d", errno);
break;
}
}
return result;
}
int bind(Sysio *sysio)
{
int result = ::bind(_socket, (const struct sockaddr *)&sysio->bind_in.addr,
sysio->bind_in.addrlen);
if (result == -1) {
switch (errno) {
case EACCES: sysio->error.bind = Sysio::BIND_ERR_ACCESS; break;
case EADDRINUSE: sysio->error.bind = Sysio::BIND_ERR_ADDR_IN_USE; break;
case EINVAL: sysio->error.bind = Sysio::BIND_ERR_INVALID; break;
case ENOMEM: sysio->error.bind = Sysio::BIND_ERR_NO_MEMORY; break;
default:
PDBG("unhandled errno: %d", errno);
break;
}
}
return result;
}
int connect(Sysio *sysio)
{
int result = ::connect(_socket, (struct sockaddr *)&sysio->connect_in.addr,
sysio->connect_in.addrlen);
if (result == -1) {
switch (errno) {
case EAGAIN: sysio->error.connect = Sysio::CONNECT_ERR_AGAIN; break;
case EALREADY: sysio->error.connect = Sysio::CONNECT_ERR_ALREADY; break;
case EADDRINUSE: sysio->error.connect = Sysio::CONNECT_ERR_ADDR_IN_USE; break;
case EINPROGRESS: sysio->error.connect = Sysio::CONNECT_ERR_IN_PROGRESS; break;
case EISCONN: sysio->error.connect = Sysio::CONNECT_ERR_IS_CONNECTED; break;
default:
PDBG("unhandled errno: %d", errno);
break;
}
}
return result;
}
int getpeername(Sysio *sysio)
{
return ::getpeername(_socket, (struct sockaddr *)&sysio->getpeername_in.addr,
(socklen_t *)&sysio->getpeername_in.addrlen);
}
bool getsockopt(Sysio *sysio)
{
int result = ::getsockopt(_socket, sysio->getsockopt_in.level,
sysio->getsockopt_in.optname,
sysio->getsockopt_in.optval,
&sysio->getsockopt_in.optlen);
return (result == -1) ? false : true;
}
int listen(Sysio *sysio)
{
int result = ::listen(_socket, sysio->listen_in.backlog);
if (result == -1) {
switch (errno) {
case EADDRINUSE: sysio->error.listen = Sysio::LISTEN_ERR_ADDR_IN_USE; break;
case EOPNOTSUPP: sysio->error.listen = Sysio::LISTEN_ERR_NOT_SUPPORTED; break;
default:
PDBG("unhandled errno: %d", errno);
break;
}
}
return result;
}
ssize_t recv(Sysio *sysio)
{
ssize_t result = ::recv(_socket, sysio->recv_in.buf, sysio->recv_in.len, sysio->recv_in.flags);
if (result == -1) {
switch (errno) {
/*case EAGAIN: sysio->error.recv = Sysio::RECV_ERR_AGAIN; break; */
case EWOULDBLOCK: sysio->error.recv = Sysio::RECV_ERR_WOULD_BLOCK; break;
case EINVAL: sysio->error.recv = Sysio::RECV_ERR_INVALID; break;
case ENOTCONN: sysio->error.recv = Sysio::RECV_ERR_NOT_CONNECTED; break;
default:
PDBG("unhandled errno: %d", errno);
break;
}
}
sysio->recv_out.len = result;
return result;
}
ssize_t recvfrom(Sysio *sysio)
{
ssize_t result = ::recvfrom(_socket, sysio->recv_in.buf, sysio->recv_in.len,
sysio->recv_in.flags, (struct sockaddr *)&sysio->recvfrom_in.src_addr,
&sysio->recvfrom_in.addrlen);
if (result == -1) {
switch (errno) {
/*case EAGAIN: sysio->error.recv = Sysio::RECV_ERR_AGAIN; break; */
case EWOULDBLOCK: sysio->error.recv = Sysio::RECV_ERR_WOULD_BLOCK; break;
case EINVAL: sysio->error.recv = Sysio::RECV_ERR_INVALID; break;
case ENOTCONN: sysio->error.recv = Sysio::RECV_ERR_NOT_CONNECTED; break;
default:
PDBG("unhandled errno: %d", errno);
break;
}
}
sysio->recvfrom_out.len = result;
return result;
}
bool setsockopt(Sysio *sysio)
{
/*
* Filter options out because lwip only supports several socket
* options. Therefore for now we silently return 0 and notify
* the user via debug message.
*/
switch (sysio->setsockopt_in.optname) {
case SO_DEBUG:
case SO_LINGER:
PWRN("SOL_SOCKET option '%d' is currently not supported, however we report success",
sysio->setsockopt_in.optname);
return true;
}
int result = ::setsockopt(_socket, sysio->setsockopt_in.level,
sysio->setsockopt_in.optname,
sysio->setsockopt_in.optval,
sysio->setsockopt_in.optlen);
return (result == -1) ? false : true;
}
ssize_t send(Sysio *sysio)
{
ssize_t result = ::send(_socket, sysio->send_in.buf, sysio->send_in.len,
sysio->send_in.flags);
if (result == -1) {
switch (errno) {
/*case EAGAIN: sysio->error.send = Sysio::SEND_ERR_AGAIN; break; */
case EWOULDBLOCK: sysio->error.send = Sysio::SEND_ERR_WOULD_BLOCK; break;
case ECONNRESET: sysio->error.send = Sysio::SEND_ERR_CONNECTION_RESET; break;
case EINVAL: sysio->error.send = Sysio::SEND_ERR_INVALID; break;
case EISCONN: sysio->error.send = Sysio::SEND_ERR_IS_CONNECTED; break;
case ENOMEM: sysio->error.send = Sysio::SEND_ERR_NO_MEMORY; break;
default:
PDBG("unhandled errno: %d", errno);
break;
}
}
sysio->send_out.len = result;
return result;
}
ssize_t sendto(Sysio *sysio)
{
ssize_t result = ::sendto(_socket, sysio->sendto_in.buf, sysio->sendto_in.len,
sysio->sendto_in.flags,
(const struct sockaddr *) &sysio->sendto_in.dest_addr,
sysio->sendto_in.addrlen);
if (result == -1) {
switch (errno) {
/*case EAGAIN: sysio->error.send = Sysio::SEND_ERR_AGAIN; break; */
case EWOULDBLOCK: sysio->error.send = Sysio::SEND_ERR_WOULD_BLOCK; break;
case ECONNRESET: sysio->error.send = Sysio::SEND_ERR_CONNECTION_RESET; break;
case EINVAL: sysio->error.send = Sysio::SEND_ERR_INVALID; break;
case EISCONN: sysio->error.send = Sysio::SEND_ERR_IS_CONNECTED; break;
case ENOMEM: sysio->error.send = Sysio::SEND_ERR_NO_MEMORY; break;
default:
PDBG("unhandled errno: %d", errno);
break;
}
}
sysio->sendto_out.len = result;
return result;
}
int shutdown(Sysio *sysio)
{
int result = ::shutdown(_socket, sysio->shutdown_in.how);
if (result == -1) {
switch (errno) {
case ENOTCONN: sysio->error.shutdown = Sysio::SHUTDOWN_ERR_NOT_CONNECTED; break;
default:
PDBG("unhandled errno: %d", errno);
break;
}
}
return result;
}
bool socket(Sysio *sysio)
{
_socket = ::socket(sysio->socket_in.domain,
sysio->socket_in.type,
sysio->socket_in.protocol);
return (_socket == -1) ? false : true;
}
};
class Socket_io_channel : public Io_channel
{
private:
Socket_io_channel_backend *_backend;
public:
Socket_io_channel()
:
_backend(new (env()->heap()) Socket_io_channel_backend())
{ }
Socket_io_channel(int s)
:
_backend(new (env()->heap()) Socket_io_channel_backend(s))
{ }
~Socket_io_channel()
{
destroy(env()->heap(), _backend);
}
/**
* Io_channel interface (only needed methods)
*/
Io_channel_backend *backend() { return _backend; }
bool write(Sysio *sysio, size_t &count)
{
return _backend->write(sysio, count);
}
bool read(Sysio *sysio)
{
return _backend->read(sysio);
}
bool fcntl(Sysio* sysio)
{
return _backend->fcntl(sysio);
}
bool ioctl(Sysio *sysio)
{
return _backend->ioctl(sysio);
}
bool check_unblock(bool rd, bool wr, bool ex) const
{
return _backend->check_unblock(rd, wr, ex);
}
};
}
#endif /* _NOUX__SOCKET_IO_CHANNEL_H_ */