477 lines
10 KiB
C++
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;
|
|
}
|