genode/repos/libports/src/lib/libc_pipe/plugin.cc

477 lines
10 KiB
C++

/*
* \brief Pipe plugin implementation
* \author Christian Prochaska
* \date 2014-07-11
*/
/*
* Copyright (C) 2014-2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode includes */
#include <base/env.h>
#include <base/heap.h>
#include <base/log.h>
#include <os/ring_buffer.h>
#include <util/misc_math.h>
/* libc includes */
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
/* libc plugin interface */
#include <libc-plugin/fd_alloc.h>
#include <libc-plugin/plugin_registry.h>
#include <libc-plugin/plugin.h>
/* function to notify libc about a socket event */
extern void (*libc_select_notify)();
namespace Libc_pipe {
using namespace Genode;
enum Type { READ_END, WRITE_END };
enum { PIPE_BUF_SIZE = 4096 };
typedef Ring_buffer<unsigned char, PIPE_BUF_SIZE+1> Pipe_buffer;
class Plugin_context : public Libc::Plugin_context
{
private:
Type _type;
Libc::File_descriptor *_partner;
Genode::Allocator &_alloc;
Pipe_buffer *_buffer;
Genode::Semaphore *_write_avail_sem;
bool _nonblock = false;
public:
/**
* Constructor
*
* \param type information if the file descriptor belongs to the
* read end or to the write end of the pipe
*
* \param partner the other pipe end
*/
Plugin_context(Type type, Libc::File_descriptor *partner,
Genode::Allocator &alloc);
~Plugin_context();
Type type() const { return _type; }
Pipe_buffer *buffer() const { return _buffer; }
Libc::File_descriptor *partner() const { return _partner; }
Genode::Semaphore *write_avail_sem() const { return _write_avail_sem; }
bool nonblock() const { return _nonblock; }
void set_partner(Libc::File_descriptor *partner) { _partner = partner; }
void set_nonblock(bool nonblock) { _nonblock = nonblock; }
};
class Plugin : public Libc::Plugin
{
private:
Genode::Constructible<Genode::Heap> _heap;
public:
/**
* Constructor
*/
Plugin();
void init(Genode::Env &env) override;
bool supports_pipe() override;
bool supports_poll() override;
bool supports_select(int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout) override;
int close(Libc::File_descriptor *pipefdo) override;
int fcntl(Libc::File_descriptor *pipefdo, int cmd, long arg) override;
int pipe(Libc::File_descriptor *pipefdo[2]) override;
bool poll(Libc::File_descriptor &, struct pollfd &) override;
ssize_t read(Libc::File_descriptor *pipefdo, void *buf,
::size_t count) override;
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout) override;
ssize_t write(Libc::File_descriptor *pipefdo, const void *buf,
::size_t count) override;
};
/***************
** Utilities **
***************/
Plugin_context *context(Libc::File_descriptor *fd)
{
return static_cast<Plugin_context *>(fd->context);
}
static inline bool read_end(Libc::File_descriptor *fdo)
{
return (context(fdo)->type() == READ_END);
}
static inline bool write_end(Libc::File_descriptor *fdo)
{
return (context(fdo)->type() == WRITE_END);
}
/********************
** Plugin_context **
********************/
Plugin_context::Plugin_context(Type type, Libc::File_descriptor *partner,
Genode::Allocator &alloc)
: _type(type), _partner(partner), _alloc(alloc)
{
if (!_partner) {
/* allocate shared resources */
_buffer = new (_alloc) Pipe_buffer;
_write_avail_sem = new (_alloc) Genode::Semaphore(PIPE_BUF_SIZE);
} else {
/* get shared resource pointers from partner */
_buffer = context(_partner)->buffer();
_write_avail_sem = context(_partner)->write_avail_sem();
}
}
Plugin_context::~Plugin_context()
{
if (_partner) {
/* remove the fd this context belongs to from the partner's context */
context(_partner)->set_partner(0);
} else {
/* partner fd is already destroyed -> free shared resources */
destroy(_alloc, _buffer);
destroy(_alloc, _write_avail_sem);
}
}
/************
** Plugin **
************/
Plugin::Plugin()
{
Genode::log("using the pipe libc plugin");
}
void Plugin::init(Genode::Env &env)
{
_heap.construct(env.ram(), env.rm());
}
bool Plugin::supports_pipe()
{
return true;
}
bool Plugin::supports_poll()
{
return true;
}
bool Plugin::supports_select(int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout)
{
/*
* Return true if any file descriptor which is marked set in one of
* the sets belongs to this plugin
*/
for (int libc_fd = 0; libc_fd < nfds; libc_fd++) {
if (FD_ISSET(libc_fd, readfds) ||
FD_ISSET(libc_fd, writefds) ||
FD_ISSET(libc_fd, exceptfds)) {
Libc::File_descriptor *fdo =
Libc::file_descriptor_allocator()->find_by_libc_fd(libc_fd);
if (fdo && (fdo->plugin == this))
return true;
}
}
return false;
}
int Plugin::close(Libc::File_descriptor *pipefdo)
{
Genode::destroy(*_heap, context(pipefdo));
Libc::file_descriptor_allocator()->free(pipefdo);
return 0;
}
int Plugin::fcntl(Libc::File_descriptor *pipefdo, int cmd, long arg)
{
switch (cmd) {
case F_SETFD:
{
const long supported_flags = FD_CLOEXEC;
/* if unsupported flags are used, fall through with error */
if (!(arg & ~supported_flags)) {
/* close fd if exec is called - no exec support -> ignore */
if (arg & FD_CLOEXEC)
return 0;
}
}
case F_GETFL:
if (write_end(pipefdo))
return O_WRONLY;
else
return O_RDONLY;
case F_SETFL:
{
/*
* O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_TRUNC, O_EXCL
* are ignored
*/
constexpr long supported_flags = O_NONBLOCK
| O_RDONLY | O_WRONLY | O_RDWR
| O_CREAT | O_TRUNC | O_EXCL;
context(pipefdo)->set_nonblock(arg & O_NONBLOCK);
if ((arg & ~supported_flags) == 0)
return 0;
/* unsupported flags present */
Genode::error(__PRETTY_FUNCTION__, ": "
"command F_SETFL arg ", arg, " not fully supported");
return -1;
}
default:
Genode::error(__PRETTY_FUNCTION__, "s: command ", cmd, " "
"arg ", arg, " not supported");
return -1;
}
return -1;
}
int Plugin::pipe(Libc::File_descriptor *pipefdo[2])
{
pipefdo[0] = Libc::file_descriptor_allocator()->alloc(this,
new (*_heap) Plugin_context(READ_END, 0, *_heap));
pipefdo[1] = Libc::file_descriptor_allocator()->alloc(this,
new (*_heap) Plugin_context(WRITE_END, pipefdo[0], *_heap));
static_cast<Plugin_context *>(pipefdo[0]->context)->set_partner(pipefdo[1]);
return 0;
}
bool Plugin::poll(Libc::File_descriptor &fdo, struct pollfd &pfd)
{
if (fdo.plugin != this) return false;
enum {
POLLIN_MASK = POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI,
POLLOUT_MASK = POLLOUT | POLLWRNORM | POLLWRBAND,
};
bool res { false };
if ((pfd.events & POLLIN_MASK)
&& read_end(&fdo)
&& !context(&fdo)->buffer()->empty())
{
pfd.revents |= pfd.events & POLLIN_MASK;
res = true;
}
if ((pfd.events & POLLOUT_MASK)
&& write_end(&fdo)
&& (context(&fdo)->buffer()->avail_capacity() > 0))
{
pfd.revents |= pfd.events & POLLOUT_MASK;
res = true;
}
return res;
}
ssize_t Plugin::read(Libc::File_descriptor *fdo, void *buf, ::size_t count)
{
if (!read_end(fdo)) {
Genode::error("cannot read from write end of pipe");
errno = EBADF;
return -1;
}
if (!context(fdo)->partner())
return 0;
if (context(fdo)->nonblock() && context(fdo)->buffer()->empty()) {
errno = EAGAIN;
return -1;
}
/* blocking mode, read at least one byte */
ssize_t num_bytes_read = 0;
do {
((unsigned char*)buf)[num_bytes_read] =
context(fdo)->buffer()->get();
num_bytes_read++;
context(fdo)->write_avail_sem()->up();
} while ((num_bytes_read < (ssize_t)count) &&
!context(fdo)->buffer()->empty());
return num_bytes_read;
}
/* no support for execptfds right now */
int Plugin::select(int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout)
{
int nready = 0;
Libc::File_descriptor *fdo;
fd_set in_readfds, in_writefds;
in_readfds = *readfds;
FD_ZERO(readfds);
in_writefds = *writefds;
FD_ZERO(writefds);
FD_ZERO(exceptfds);
for (int libc_fd = 0; libc_fd < nfds; libc_fd++) {
fdo = Libc::file_descriptor_allocator()->find_by_libc_fd(libc_fd);
/* handle only libc_fds that belong to this plugin */
if (!fdo || (fdo->plugin != this))
continue;
if (FD_ISSET(libc_fd, &in_readfds) &&
read_end(fdo) &&
!context(fdo)->buffer()->empty()) {
FD_SET(libc_fd, readfds);
nready++;
}
if (FD_ISSET(libc_fd, &in_writefds) &&
write_end(fdo) &&
(context(fdo)->buffer()->avail_capacity() > 0)) {
FD_SET(libc_fd, writefds);
nready++;
}
}
return nready;
}
ssize_t Plugin::write(Libc::File_descriptor *fdo, const void *buf,
::size_t count)
{
if (!write_end(fdo)) {
Genode::error("cannot write into read end of pipe");
errno = EBADF;
return -1;
}
if (context(fdo)->nonblock() &&
(context(fdo)->buffer()->avail_capacity() == 0)) {
errno = EAGAIN;
return -1;
}
::size_t num_bytes_written = 0;
while (num_bytes_written < count) {
if (context(fdo)->buffer()->avail_capacity() == 0) {
if (context(fdo)->nonblock())
return num_bytes_written;
if (libc_select_notify)
libc_select_notify();
Plugin::resume_all();
}
context(fdo)->write_avail_sem()->down();
context(fdo)->buffer()->add(((unsigned char*)buf)[num_bytes_written]);
num_bytes_written++;
}
if (libc_select_notify)
libc_select_notify();
Plugin::resume_all();
return num_bytes_written;
}
}
void __attribute__((constructor)) init_libc_pipe()
{
static Libc_pipe::Plugin plugin;
}