/* * \brief Lxip plugin implementation * \author Sebastian Sumpf * \author Christian Helmuth * \date 2013-09-04 */ /* * Copyright (C) 2010-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. */ /* Libc includes */ #include #include #include #include /* Genode includes */ #include #include /* Libc plugin includes */ #include #include /* Lxip includes */ #include /************************** ** Linux family numbers ** **************************/ enum { LINUX_AF_INET = 2 }; /********************** ** Plugin interface ** **********************/ extern "C" void wait_for_continue(); namespace { class Plugin_context : public Libc::Plugin_context { private: Lxip::Handle _handle; public: /** * Constructor */ Plugin_context(Lxip::Handle handle) : _handle(handle) { } Lxip::Handle handle() const { return _handle; } void non_block(bool nb) { _handle.non_block = nb; } }; static inline Plugin_context * context(Libc::File_descriptor *fd) { return static_cast(fd->context); } struct Plugin : Libc::Plugin { /** * Interface to LXIP stack */ struct Lxip::Socketcall &socketcall; /** * Constructor */ Plugin(); bool supports_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); bool supports_socket(int domain, int type, int protocol); Libc::File_descriptor *accept(Libc::File_descriptor *sockfdo, struct sockaddr *addr, socklen_t *addrlen); int bind(Libc::File_descriptor *sockfdo, const struct sockaddr *addr, socklen_t addrlen); int close(Libc::File_descriptor *fdo); int connect(Libc::File_descriptor *sockfdo, const struct sockaddr *addr, socklen_t addrlen); int fcntl(Libc::File_descriptor *sockfdo, int cmd, long val); int getpeername(Libc::File_descriptor *sockfdo, struct sockaddr *addr, socklen_t *addrlen); int getsockname(Libc::File_descriptor *sockfdo, struct sockaddr *addr, socklen_t *addrlen); int getsockopt(Libc::File_descriptor *sockfdo, int level, int optname, void *optval, socklen_t *optlen); int ioctl(Libc::File_descriptor *sockfdo, int request, char *argp); int listen(Libc::File_descriptor *sockfdo, int backlog); ssize_t read(Libc::File_descriptor *fdo, void *buf, ::size_t count); int shutdown(Libc::File_descriptor *fdo, int); int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); ssize_t send(Libc::File_descriptor *, const void *buf, ::size_t len, int flags); ssize_t sendto(Libc::File_descriptor *, const void *buf, ::size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); ssize_t recv(Libc::File_descriptor *, void *buf, ::size_t len, int flags); ssize_t recvfrom(Libc::File_descriptor *, void *buf, ::size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); int setsockopt(Libc::File_descriptor *sockfdo, int level, int optname, const void *optval, socklen_t optlen); Libc::File_descriptor *socket(int domain, int type, int protocol); ssize_t write(Libc::File_descriptor *fdo, const void *buf, ::size_t count); int linux_family(const struct sockaddr *addr, socklen_t addrlen); int bsd_family(struct sockaddr *addr); int retrieve_and_clear_fds(int nfds, struct fd_set *fds, struct fd_set *in); int translate_msg_flags(int bsd_flags); int translate_ops_linux(int optname); }; Plugin::Plugin() : socketcall(Lxip::init()) { PDBG("using the lxip libc plugin"); } /* TODO shameful copied from lwip... generalize this */ 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. */ Libc::File_descriptor *fdo; 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)) { fdo = Libc::file_descriptor_allocator()->find_by_libc_fd(libc_fd); if (fdo && (fdo->plugin == this)) { return true; } } } return false; } bool Plugin::supports_socket(int domain, int type, int) { if (domain == AF_INET && (type == SOCK_STREAM || type == SOCK_DGRAM)) return true; return false; } Libc::File_descriptor *Plugin::accept(Libc::File_descriptor *sockfdo, struct sockaddr *addr, socklen_t *addrlen) { Lxip::Handle handle; handle = socketcall.accept(context(sockfdo)->handle(), (void *)addr, addrlen); if (!handle.socket) return 0; if (addr) { addr->sa_family = bsd_family(addr); addr->sa_len = *addrlen; } Plugin_context *context = new (Genode::env()->heap()) Plugin_context(handle); Libc::File_descriptor *fd = Libc::file_descriptor_allocator()->alloc(this, context); return fd; } int Plugin::bind(Libc::File_descriptor *sockfdo, const struct sockaddr *addr, socklen_t addrlen) { int family; if (!(family = linux_family(addr, addrlen))) { errno = ENOTSUP; return -1; } errno = -socketcall.bind(context(sockfdo)->handle(), family, (void*)addr); return errno > 0 ? -1 : 0; } int Plugin::close(Libc::File_descriptor *sockfdo) { socketcall.close(context(sockfdo)->handle()); if (context(sockfdo)) Genode::destroy(Genode::env()->heap(), context(sockfdo)); Libc::file_descriptor_allocator()->free(sockfdo); return 0; } int Plugin::connect(Libc::File_descriptor *sockfdo, const struct sockaddr *addr, socklen_t addrlen) { int family; if (!(family = linux_family(addr, addrlen))) { errno = ENOTSUP; return -1; } errno = -socketcall.connect(context(sockfdo)->handle(), family, (void *)addr); return errno > 0 ? -1 : 0; } int Plugin::fcntl(Libc::File_descriptor *sockfdo, int cmd, long val) { switch (cmd) { case F_GETFL: return context(sockfdo)->handle().non_block ? O_NONBLOCK : 0; case F_SETFL: context(sockfdo)->non_block(!!(val & O_NONBLOCK)); return 0; default: PERR("unsupported fcntl() request: %d", cmd); errno = ENOSYS; return -1; } } /* XXX freeaddrinfo / getaddrinfo from libc_resolv.conf */ int Plugin::getpeername(Libc::File_descriptor *sockfdo, struct sockaddr *addr, socklen_t *addrlen) { if (!addr) { errno = EFAULT; return -1; } errno = -socketcall.getpeername(context(sockfdo)->handle(), (void *)addr, addrlen); addr->sa_family = bsd_family(addr); addr->sa_len = *addrlen; return errno > 0 ? -1 : 0; } int Plugin::getsockname(Libc::File_descriptor *sockfdo, struct sockaddr *addr, socklen_t *addrlen) { if (!addr) { errno = EFAULT; return -1; } errno = -socketcall.getsockname(context(sockfdo)->handle(), (void *)addr, addrlen); addr->sa_family = bsd_family(addr); addr->sa_len = *addrlen; return errno > 0 ? -1 : 0; } int Plugin::getsockopt(Libc::File_descriptor *sockfdo, int level, int optname, void *optval, socklen_t *optlen) { if (level != SOL_SOCKET) { PERR("%s: Unsupported level %d, we only support SOL_SOCKET for now", __func__, level); errno = EBADF; return -1; } optname = translate_ops_linux(optname); if (optname < 0) { errno = ENOPROTOOPT; return -1; } return socketcall.getsockopt(context(sockfdo)->handle(), Lxip::LINUX_SOL_SOCKET, optname, optval, (int *)optlen); } int Plugin::ioctl(Libc::File_descriptor *sockfdo, int request, char *argp) { switch (request) { case FIONBIO: context(sockfdo)->non_block(!!*argp); return 0; case FIONREAD: errno = -socketcall.ioctl(context(sockfdo)->handle(), Lxip::LINUX_FIONREAD, argp); return errno > 0 ? -1 : 0; default: PERR("unsupported ioctl() request"); errno = ENOSYS; return -1; } } int Plugin::listen(Libc::File_descriptor *sockfdo, int backlog) { errno = -socketcall.listen(context(sockfdo)->handle(), backlog); return errno > 0 ? -1 : 0; } int Plugin::shutdown(Libc::File_descriptor *sockfdo, int how) { errno = -socketcall.shutdown(context(sockfdo)->handle(), how); return errno > 0 ? -1 : 0; } /* TODO: Support timeouts */ /* XXX: Check blocking and non-blocking semantics */ int Plugin::select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) { Libc::File_descriptor *sockfdo; struct fd_set fds[3]; int bits = 0; if (nfds < 0) { errno = EINVAL; return -1; } for (int i = 0; i < 3; i++) FD_ZERO(&fds[i]); bool block = false; for (int fd = 0; fd <= nfds; fd++) { if (fd == nfds && !bits) { fd = -1; block = true; continue; } int set = 0; set |= FD_ISSET(fd, readfds); set |= FD_ISSET(fd, writefds); set |= FD_ISSET(fd, exceptfds); if (!set) continue; sockfdo = Libc::file_descriptor_allocator()->find_by_libc_fd(fd); /* handle only libc_fds that belong to this plugin */ if (!sockfdo || (sockfdo->plugin != this)) continue; /* call IP stack blocking/non-blocking */ int mask = socketcall.poll(context(sockfdo)->handle(), block); if (mask) block = false; if (readfds && (mask & Lxip::POLLIN) && FD_ISSET(fd, readfds) && ++bits) FD_SET(fd, &fds[0]); if (writefds && (mask & Lxip::POLLOUT) && FD_ISSET(fd, writefds) && ++bits) FD_SET(fd, &fds[1]); if (exceptfds && (mask & Lxip::POLLEX) && FD_ISSET(fd, exceptfds) && ++bits) FD_SET(fd, &fds[2]); } if (readfds) *readfds = fds[0]; if (writefds) *writefds = fds[1]; if (exceptfds) *exceptfds = fds[2]; return bits; } ssize_t Plugin::read(Libc::File_descriptor *fdo, void *buf, ::size_t count) { return recv(fdo, buf, count, 0); } ssize_t Plugin::recv(Libc::File_descriptor *sockfdo, void *buf, ::size_t len, int flags) { return recvfrom(sockfdo, buf, len, flags, 0, 0); } ssize_t Plugin::recvfrom(Libc::File_descriptor *sockfdo, void *buf, ::size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) { int family = 0; if (src_addr && addrlen && !(family = linux_family(src_addr, *addrlen))) { errno = ENOTSUP; return -1; } int recv = socketcall.recv(context(sockfdo)->handle(), buf, len, translate_msg_flags(flags), family, (void *)src_addr, addrlen); if (recv < 0) { errno = -recv; return errno == EAGAIN ? 0 : -1; } if (src_addr) { src_addr->sa_family = bsd_family(src_addr); src_addr->sa_len = *addrlen; } return recv; } ssize_t Plugin::send(Libc::File_descriptor *sockfdo, const void *buf, ::size_t len, int flags) { return sendto(sockfdo, buf, len, flags, 0, 0); } ssize_t Plugin::sendto(Libc::File_descriptor *sockfdo, const void *buf, ::size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) { int family = 0; if (dest_addr && addrlen && !(family = linux_family(dest_addr, addrlen))) { errno = ENOTSUP; return -1; } int send = socketcall.send(context(sockfdo)->handle(), buf, len, translate_msg_flags(flags), family, (void *)dest_addr); if (send < 0) errno = -send; return send < 0 ? -1 : send; } int Plugin::setsockopt(Libc::File_descriptor *sockfdo, int level, int optname, const void *optval, socklen_t optlen) { if (level != SOL_SOCKET) { PERR("%s: Unsupported level %d, we only support SOL_SOCKET for now", __func__, level); errno = EBADF; return -1; } optname = translate_ops_linux(optname); if (optname < 0) { errno = ENOPROTOOPT; return -1; } return socketcall.setsockopt(context(sockfdo)->handle(), Lxip::LINUX_SOL_SOCKET, optname, optval, optlen); } Libc::File_descriptor *Plugin::socket(int domain, int type, int protocol) { using namespace Lxip; Handle handle = socketcall.socket(type == SOCK_STREAM ? TYPE_STREAM : TYPE_DGRAM); if (!handle.socket) { errno = EBADF; return 0; } Plugin_context *context = new (Genode::env()->heap()) Plugin_context(handle); Libc::File_descriptor *fd = Libc::file_descriptor_allocator()->alloc(this, context); return fd; } ssize_t Plugin::write(Libc::File_descriptor *fdo, const void *buf, ::size_t count) { return send(fdo, buf, count, 0); } int Plugin::linux_family(const struct sockaddr *addr, socklen_t addrlen) { switch (addr->sa_family) { case AF_INET: return LINUX_AF_INET; default: PERR("Unsupported socket BSD-protocol %u\n", addr->sa_family); return 0; } return 0; } int Plugin::bsd_family(struct sockaddr *addr) { /* * Note: Since in Linux 'sa_family' is 16 bit while in BSD it is 8 bit (both * little endian), 'sa_len' will contain the actual family (or lower order * bits) of Linux */ addr->sa_family = addr->sa_len; switch (addr->sa_family) { case LINUX_AF_INET: return AF_INET; default: PERR("Unsupported socket Linux-protocol %u\n", addr->sa_family); return 0; } } int Plugin::translate_msg_flags(int bsd_flags) { using namespace Lxip; int f = 0; if (bsd_flags & MSG_OOB) f |= LINUX_MSG_OOB; if (bsd_flags & MSG_PEEK) f |= LINUX_MSG_PEEK; if (bsd_flags & MSG_DONTROUTE) f |= LINUX_MSG_DONTROUTE; if (bsd_flags & MSG_EOR) f |= LINUX_MSG_EOR; if (bsd_flags & MSG_TRUNC) f |= LINUX_MSG_TRUNC; if (bsd_flags & MSG_CTRUNC) f |= LINUX_MSG_CTRUNC; if (bsd_flags & MSG_WAITALL) f |= LINUX_MSG_WAITALL; if (bsd_flags & MSG_NOTIFICATION) PWRN("MSG_NOTIFICATION ignored"); if (bsd_flags & MSG_DONTWAIT) f |= LINUX_MSG_DONTWAIT; if (bsd_flags & MSG_EOF) f |= LINUX_MSG_EOF; if (bsd_flags & MSG_NBIO) PWRN("MSG_NBIO ignored"); if (bsd_flags & MSG_NOSIGNAL) f |= LINUX_MSG_NOSIGNAL; if (bsd_flags & MSG_COMPAT) f |= LINUX_MSG_COMPAT; return f; } /* index is Linux, value is BSD */ static int sockopts[] { 0, /* 0 */ SO_DEBUG, SO_REUSEADDR, SO_TYPE, SO_ERROR, SO_DONTROUTE, /* 5 */ SO_BROADCAST, SO_SNDBUF, SO_RCVBUF, SO_KEEPALIVE, SO_OOBINLINE, /* 10 */ /* SO_NOCHECK */ 0, /* SO_PRIORITY */ 0, SO_LINGER, /* SO_BSDCOMPAT */ 0, SO_REUSEPORT, /* 15 */ /* SO_PASSCRED */ 0, /* SO_PEERCRED */ 0, SO_RCVLOWAT, SO_SNDLOWAT, SO_RCVTIMEO, /* 20 */ SO_SNDTIMEO, 0, 0, 0, 0, /* 25 */ 0, 0, SO_PEERLABEL, SO_TIMESTAMP, SO_ACCEPTCONN, /* 30 */ -1 }; int Plugin::translate_ops_linux(int optname) { for (int i = 0; sockopts[i] > -1; i++) if (sockopts[i] == optname) return i; PERR("Unsupported sockopt %d\n", optname); return -1; } } /* unnamed namespace */ void create_lxip_plugin() { static Plugin lxip_plugin; }