genode/base-linux/src/platform/linux_socket.h

336 lines
7.9 KiB
C++

/*
* \brief Linux socket utilities
* \author Christian Helmuth
* \date 2012-01-17
*
* We create one socket under lx_rpath() for each 'Ipc_server'. The naming is
* 'ep-<thread id>-<role>'.
*/
/*
* Copyright (C) 2011-2012 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 _PLATFORM__LINUX_SOCKET_H_
#define _PLATFORM__LINUX_SOCKET_H_
/* Genode includes */
#include <base/ipc_generic.h>
#include <base/thread.h>
#include <base/blocking.h>
#include <base/snprintf.h>
#include <linux_rpath.h>
#include <linux_syscalls.h>
extern "C" void wait_for_continue(void);
extern "C" int raw_write_str(const char *str);
#define PRAW(fmt, ...) \
do { \
char str[128]; \
Genode::snprintf(str, sizeof(str), \
ESC_ERR fmt ESC_END "\n", ##__VA_ARGS__); \
raw_write_str(str); \
} while (0)
/**
* Utility: Create socket address for server entrypoint atthread ID
*/
static void lx_create_server_addr(sockaddr_un *addr, long thread_id)
{
addr->sun_family = AF_UNIX;
Genode::snprintf(addr->sun_path, sizeof(addr->sun_path), "%s/ep-%ld",
lx_rpath(), thread_id);
}
namespace {
/**
* Message object encapsulating data for sendmsg/recvmsg
*/
struct Message
{
public:
enum { MAX_SDS_PER_MSG = Genode::Msgbuf_base::MAX_CAPS_PER_MSG };
private:
msghdr _msg;
sockaddr_un _addr;
iovec _iovec;
char _cmsg_buf[CMSG_SPACE(MAX_SDS_PER_MSG*sizeof(int))];
unsigned _num_sds;
public:
Message(long server_thread_id = -1) : _num_sds(0)
{
memset(&_msg, 0, sizeof(_msg));
if (server_thread_id != -1) {
/* initialize receiver */
lx_create_server_addr(&_addr, server_thread_id);
_msg.msg_name = &_addr;
_msg.msg_namelen = sizeof(_addr);
}
/* initialize control message */
struct cmsghdr *cmsg;
_msg.msg_control = _cmsg_buf;
_msg.msg_controllen = sizeof(_cmsg_buf); /* buffer space available */
_msg.msg_flags |= MSG_CMSG_CLOEXEC;
cmsg = CMSG_FIRSTHDR(&_msg);
cmsg->cmsg_len = CMSG_LEN(0);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
_msg.msg_controllen = cmsg->cmsg_len; /* actual cmsg length */
}
msghdr * msg() { return &_msg; }
void buffer(void *buffer, size_t buffer_len)
{
/* initialize iovec */
_msg.msg_iov = &_iovec;
_msg.msg_iovlen = 1;
_iovec.iov_base = buffer;
_iovec.iov_len = buffer_len;
}
void marshal_socket(int sd)
{
*((int *)CMSG_DATA((cmsghdr *)_cmsg_buf) + _num_sds) = sd;
_num_sds++;
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&_msg);
cmsg->cmsg_len = CMSG_LEN(_num_sds*sizeof(int));
_msg.msg_controllen = cmsg->cmsg_len; /* actual cmsg length */
}
void accept_sockets(int num_sds)
{
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&_msg);
cmsg->cmsg_len = CMSG_LEN(num_sds*sizeof(int));
_msg.msg_controllen = cmsg->cmsg_len; /* actual cmsg length */
}
int unmarshal_socket()
{
int ret = *((int *)CMSG_DATA((cmsghdr *)_cmsg_buf) + _num_sds);
_num_sds++;
return ret;
}
/* XXX only for debugging */
int socket_at_index(int index)
{
return *((int *)CMSG_DATA((cmsghdr *)_cmsg_buf) + index);
}
unsigned num_sockets() const
{
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&_msg);
if (!cmsg)
return 0;
return (cmsg->cmsg_len - CMSG_ALIGN(sizeof(cmsghdr)))/sizeof(int);
}
};
} /* unnamed namespace */
/**
* Utility: Get server socket for given thread
*/
static int lx_server_socket(Genode::Thread_base *thread)
{
int sd = lx_socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
if (sd < 0)
return sd;
/*
* Main thread uses 'Ipc_server' for 'sleep_forever()' only. No need
* for binding.
*/
if (!thread)
return sd;
sockaddr_un addr;
lx_create_server_addr(&addr, thread->tid().tid);
/* make sure bind succeeds */
lx_unlink(addr.sun_path);
int const bind_ret = lx_bind(sd, (sockaddr *)&addr, sizeof(addr));
if (bind_ret < 0)
return bind_ret;
return sd;
}
/**
* Utility: Send request to server and wait for reply
*/
static void lx_call(long thread_id,
Genode::Msgbuf_base &send_msgbuf,
Genode::Msgbuf_base &recv_msgbuf)
{
int ret;
Message send_msg(thread_id);
/* create reply channel */
enum { LOCAL_SOCKET = 0, REMOTE_SOCKET = 1 };
int reply_channel[2];
ret = lx_socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0, reply_channel);
if (ret < 0) {
PRAW("lx_socketpair failed with %d", ret);
/* XXX */ wait_for_continue();
throw Genode::Ipc_error();
}
/* assemble message */
/* marshal reply capability */
send_msg.marshal_socket(reply_channel[REMOTE_SOCKET]);
/* marshal capabilities contained in 'send_msgbuf' */
if (send_msgbuf.used_caps() > 0)
PRAW("lx_call: marshal %d caps:", send_msgbuf.used_caps());
for (unsigned i = 0; i < send_msgbuf.used_caps(); i++) {
PRAW(" sd[%d]: %d", i, send_msgbuf.cap(i));
send_msg.marshal_socket(lx_dup(send_msgbuf.cap(i)));
}
send_msg.buffer(send_msgbuf.buf, send_msgbuf.used_size());
ret = lx_sendmsg(reply_channel[LOCAL_SOCKET], send_msg.msg(), 0);
if (ret < 0) {
PRAW("lx_sendmsg failed with %d in lx_call()", ret);
while (1);
/* XXX */ wait_for_continue();
throw Genode::Ipc_error();
}
Message recv_msg;
recv_msg.accept_sockets(Message::MAX_SDS_PER_MSG);
recv_msg.buffer(recv_msgbuf.buf, recv_msgbuf.size());
ret = lx_recvmsg(reply_channel[LOCAL_SOCKET], recv_msg.msg(), 0);
if (ret < 0) {
PRAW("lx_recvmsg failed with %d in lx_call()", ret);
/* XXX */ wait_for_continue();
throw Genode::Ipc_error();
}
/*
* 'lx_recvmsg()' returns the number of bytes received. remember this value
* in 'Genode::Msgbuf_base'
*
* XXX revisit whether we really need this information
*/
recv_msgbuf.used_size(ret);
if (recv_msg.num_sockets() > 0) {
PRAW("lx_call: got %d sockets in reply", recv_msg.num_sockets());
for (unsigned i = 0; i < recv_msg.num_sockets(); i++) {
PRAW(" sd[%d]: %d", i, recv_msg.socket_at_index(i));
lx_close(recv_msg.socket_at_index(i));
}
}
/* destroy reply channel */
lx_close(reply_channel[LOCAL_SOCKET]);
lx_close(reply_channel[REMOTE_SOCKET]);
}
/**
* Utility: Wait for request from client
*
* \return socket descriptor of reply capability
*/
static int lx_wait(Genode::Native_connection_state &cs,
Genode::Msgbuf_base &recv_msgbuf)
{
Message msg;
msg.accept_sockets(Message::MAX_SDS_PER_MSG);
msg.buffer(recv_msgbuf.buf, recv_msgbuf.size());
int ret = lx_recvmsg(cs, msg.msg(), 0);
if (ret < 0) {
PRAW("lx_recvmsg failed with %d in lx_wait()", ret);
/* XXX */ wait_for_continue();
throw Genode::Ipc_error();
}
/*
* 'lx_recvmsg()' returned message size, keep it in 'recv_msgbuf'.
*
* XXX revisit whether this information is actually needed.
*/
recv_msgbuf.used_size(ret);
if (msg.num_sockets() > 1) {
PRAW("lx_wait: got %d sockets from wait", msg.num_sockets());
for (unsigned i = 0; i < msg.num_sockets(); i++) {
PRAW(" sd[%d]: %d", i, msg.socket_at_index(i));
/* don't close reply channel */
if (i > 0)
lx_close(msg.socket_at_index(i));
}
}
int reply_socket = msg.unmarshal_socket();
/*
* Copy-out additional sds from msg to recv_msgbuf
*/
return reply_socket;
}
/**
* Utility: Send reply to client
*/
static void lx_reply(int reply_socket,
Genode::Msgbuf_base &send_msgbuf)
{
Message msg;
msg.buffer(send_msgbuf.buf, send_msgbuf.used_size());
int ret = lx_sendmsg(reply_socket, msg.msg(), 0);
if (ret < 0)
PRAW("lx_sendmsg failed with %d in lx_reply()", ret);
lx_close(reply_socket);
}
#endif /* _PLATFORM__LINUX_SOCKET_H_ */