/* * \brief Libc pseudo plugin for socket fs * \author Christian Helmuth * \author Christian Prochaska * \author Norman Feske * \author Emery Hemingway * \date 2015-06-23 */ /* * Copyright (C) 2015-2019 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 #include #include #include #include /* libc includes */ #include #include #include #include #include #include #include #include #include #include #include #include /* libc-internal includes */ #include #include #include #include #include namespace Libc { extern char const *config_socket(); bool read_ready(File_descriptor *); } static Libc::Suspend *_suspend_ptr; void Libc::init_socket_fs(Suspend &suspend) { _suspend_ptr = &suspend; } /*************** ** Utilities ** ***************/ namespace Libc { namespace Socket_fs { struct Absolute_path : Vfs::Absolute_path { Absolute_path() { } Absolute_path(char const *path, char const *pwd = 0) : Vfs::Absolute_path(path, pwd) { remove_trailing('\n'); } }; template class String; typedef String Host_string; typedef String Port_string; struct Sockaddr_string; struct New_socket_failed : Exception { }; struct Address_conversion_failed : Exception { }; struct Context; struct Plugin; struct Sockaddr_functor; struct Remote_functor; struct Local_functor; Plugin & plugin(); enum { MAX_CONTROL_PATH_LEN = 16 }; } } using namespace Libc::Socket_fs; struct Libc::Socket_fs::Context : Plugin_context { private: int const _handle_fd; Absolute_path _read_socket_path() { Absolute_path path; int const n = read(_handle_fd, path.base(), Absolute_path::capacity()-1); if (n == -1 || !n || n >= (int)Absolute_path::capacity() - 1) throw New_socket_failed(); *(path.base()+n) = '\0'; return path; } public: enum Proto { TCP, UDP }; enum State { UNCONNECTED, ACCEPT_ONLY, CONNECTING, CONNECTED, CONNECT_ABORTED }; struct Inaccessible { }; /* exception */ Absolute_path const path { _read_socket_path().base(), config_socket() }; private: enum Fd : unsigned { DATA, CONNECT, BIND, LISTEN, ACCEPT, LOCAL, REMOTE, MAX }; struct { char const *name; int num; File_descriptor *file; } _fd[Fd::MAX] = { { "data", -1, nullptr }, { "connect", -1, nullptr }, { "bind", -1, nullptr }, { "listen", -1, nullptr }, { "accept", -1, nullptr }, { "local", -1, nullptr }, { "remote", -1, nullptr } }; int _fd_flags = 0; Proto const _proto; State _state { UNCONNECTED }; template void _fd_apply(FUNC const &fn) { for (unsigned i = 0; i < Fd::MAX; ++i) if (_fd[i].num != -1) fn(_fd[i].num); } int _fd_for_type(Fd type, int flags) { /* open file on demand */ if (_fd[type].num == -1) { Absolute_path file(_fd[type].name, path.base()); int const fd = open(file.base(), flags|_fd_flags); if (fd == -1) { error(__func__, ": ", _fd[type].name, " file not accessible at ", file); throw Inaccessible(); } _fd[type].num = fd; _fd[type].file = file_descriptor_allocator()->find_by_libc_fd(fd); } return _fd[type].num; } bool _fd_read_ready(Fd type) { if (_fd[type].file) return Libc::read_ready(_fd[type].file); else return false; } public: Context(Proto proto, int handle_fd) : _handle_fd(handle_fd), _proto(proto) { } ~Context() { _fd_apply([] (int fd) { ::close(fd); }); ::close(_handle_fd); } Proto proto() const { return _proto; } int fd_flags() const { return _fd_flags; } void fd_flags(int flags) { _fd_flags = flags; _fd_apply([flags] (int fd) { fcntl(fd, F_SETFL, flags); }); } int data_fd() { return _fd_for_type(Fd::DATA, O_RDWR); } int connect_fd() { return _fd_for_type(Fd::CONNECT, O_RDWR); } int bind_fd() { return _fd_for_type(Fd::BIND, O_WRONLY); } int listen_fd() { return _fd_for_type(Fd::LISTEN, O_WRONLY); } int accept_fd() { return _fd_for_type(Fd::ACCEPT, O_RDONLY); } int local_fd() { return _fd_for_type(Fd::LOCAL, O_RDWR); } int remote_fd() { return _fd_for_type(Fd::REMOTE, O_RDWR); } /* request the appropriate fd to ensure the file is open */ bool connect_read_ready() { connect_fd(); return _fd_read_ready(Fd::CONNECT); } bool data_read_ready() { data_fd(); return _fd_read_ready(Fd::DATA); } bool accept_read_ready() { accept_fd(); return _fd_read_ready(Fd::ACCEPT); } bool local_read_ready() { local_fd(); return _fd_read_ready(Fd::LOCAL); } bool remote_read_ready() { remote_fd(); return _fd_read_ready(Fd::REMOTE); } void state(State state) { _state = state; } State state() const { return _state; } bool read_ready() { return (_state == ACCEPT_ONLY) ? accept_read_ready() : data_read_ready(); } bool write_ready() { if (_state == CONNECTING) return connect_read_ready(); /* XXX ask if "data" is writeable */ return true; } /* * Read the connect status from the connect file and return 0 if connected * or -1 with errno set to the error code. */ int read_connect_status() { char connect_status[32] = { 0 }; ssize_t connect_status_len; connect_status_len = read(connect_fd(), connect_status, sizeof(connect_status)); if (connect_status_len <= 0) { error("socket_fs: reading from the connect file failed"); return -1; } using ::strcmp; if (strcmp(connect_status, "connected") == 0) return 0; if (strcmp(connect_status, "connection refused") == 0) return Errno(ECONNREFUSED); if (strcmp(connect_status, "not connected") == 0) return Errno(ENOTCONN); error("socket_fs: unhandled connection state"); return Errno(ECONNREFUSED); } }; struct Libc::Socket_fs::Sockaddr_functor : Suspend_functor { Socket_fs::Context &context; bool const nonblocking; Sockaddr_functor(Socket_fs::Context &context, bool nonblocking) : context(context), nonblocking(nonblocking) { } virtual int fd() = 0; }; struct Libc::Socket_fs::Remote_functor : Sockaddr_functor { Remote_functor(Socket_fs::Context &context, bool nonblocking) : Sockaddr_functor(context, nonblocking) { } bool suspend() override { return !nonblocking && !context.remote_read_ready(); } int fd() override { return context.remote_fd(); } }; struct Libc::Socket_fs::Local_functor : Sockaddr_functor { Local_functor(Context &context, bool nonblocking) : Sockaddr_functor(context, nonblocking) { } bool suspend() override { return !nonblocking && !context.local_read_ready(); } int fd() override { return context.local_fd(); } }; struct Libc::Socket_fs::Plugin : Libc::Plugin { bool supports_poll() override { return true; } bool supports_select(int, fd_set *, fd_set *, fd_set *, timeval *) override; ssize_t read(File_descriptor *, void *, ::size_t) override; ssize_t write(File_descriptor *, const void *, ::size_t) override; int fcntl(File_descriptor *, int, long) override; int close(File_descriptor *) override; bool poll(File_descriptor &fd, struct pollfd &pfd) override; int select(int, fd_set *, fd_set *, fd_set *, timeval *) override; int ioctl(File_descriptor *, int, char *) override; }; template class Libc::Socket_fs::String { private: char _buf[CAPACITY] { 0 }; public: String() { } constexpr size_t capacity() { return CAPACITY; } char const * base() const { return _buf; } char * base() { return _buf; } void terminate(size_t at) { _buf[at] = 0; } void remove_trailing_newline() { int i = 0; while (_buf[i] && _buf[i + 1]) i++; if (i > 0 && _buf[i] == '\n') _buf[i] = 0; } }; /* * Both NI_MAXHOST and NI_MAXSERV include the terminating 0, which allows * use to put ':' between host and port on concatenation. */ struct Libc::Socket_fs::Sockaddr_string : String { Sockaddr_string() { } Sockaddr_string(Host_string const &host, Port_string const &port) { char *b = base(); b = stpcpy(b, host.base()); b = stpcpy(b, ":"); b = stpcpy(b, port.base()); } Host_string host() const { Host_string host; Genode::strncpy(host.base(), base(), host.capacity()); char *at = strstr(host.base(), ":"); if (!at) throw Address_conversion_failed(); *at = 0; return host; } Port_string port() const { Port_string port; char *at = strstr(base(), ":"); if (!at) throw Address_conversion_failed(); Genode::strncpy(port.base(), ++at, port.capacity()); return port; } }; using namespace Libc; static Port_string port_string(sockaddr_in const &addr) { Port_string port; if (getnameinfo((sockaddr *)&addr, sizeof(addr), nullptr, 0, /* no host conversion */ port.base(), port.capacity(), NI_NUMERICHOST | NI_NUMERICSERV) != 0) throw Address_conversion_failed(); return port; } static Host_string host_string(sockaddr_in const &addr) { Host_string host; if (getnameinfo((sockaddr *)&addr, sizeof(addr), host.base(), host.capacity(), nullptr, 0, /* no port conversion */ NI_NUMERICHOST | NI_NUMERICSERV) != 0) throw Address_conversion_failed(); return host; } static sockaddr_in sockaddr_in_struct(Host_string const &host, Port_string const &port) { addrinfo hints; addrinfo *info = nullptr; ::memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; if (getaddrinfo(host.base(), port.base(), &hints, &info)) throw Address_conversion_failed(); sockaddr_in addr = *(sockaddr_in*)info->ai_addr; freeaddrinfo(info); return addr; } static int read_sockaddr_in(Socket_fs::Sockaddr_functor &func, struct sockaddr_in *addr, socklen_t *addrlen) { if (!addr) return Errno(EFAULT); if (!addrlen || *addrlen <= 0) return Errno(EINVAL); while (!func.nonblocking && func.suspend()) { struct Missing_call_of_init_socket_fs : Exception { }; if (!_suspend_ptr) throw Missing_call_of_init_socket_fs(); _suspend_ptr->suspend(func); } Sockaddr_string addr_string; int const n = read(func.fd(), addr_string.base(), addr_string.capacity() - 1); if (n == -1) return Errno(errno); if (!n) switch (func.context.proto()) { case Socket_fs::Context::Proto::UDP: return Errno(EAGAIN); case Socket_fs::Context::Proto::TCP: return Errno(ENOTCONN); } if (n >= (int)addr_string.capacity() - 1) return Errno(EINVAL); addr_string.terminate(n); addr_string.remove_trailing_newline(); try { /* convert the address but do not exceed the caller's buffer */ sockaddr_in saddr = sockaddr_in_struct(addr_string.host(), addr_string.port()); ::memcpy(addr, &saddr, *addrlen); *addrlen = sizeof(saddr); return 0; } catch (Address_conversion_failed) { warning("IP address conversion failed"); return Errno(ENOBUFS); } } /*********************** ** Address functions ** ***********************/ extern "C" int socket_fs_getpeername(int libc_fd, sockaddr *addr, socklen_t *addrlen) { File_descriptor *fd = file_descriptor_allocator()->find_by_libc_fd(libc_fd); if (!fd) return Errno(EBADF); Socket_fs::Context *context = dynamic_cast(fd->context); if (!context) return Errno(ENOTSOCK); switch (context->proto()) { case Socket_fs::Context::Proto::UDP: return Errno(ENOTCONN); case Socket_fs::Context::Proto::TCP: { Socket_fs::Remote_functor func(*context, false); return read_sockaddr_in(func, (sockaddr_in *)addr, addrlen); } } return 0; } extern "C" int socket_fs_getsockname(int libc_fd, sockaddr *addr, socklen_t *addrlen) { File_descriptor *fd = file_descriptor_allocator()->find_by_libc_fd(libc_fd); if (!fd) return Errno(EBADF); Socket_fs::Context *context = dynamic_cast(fd->context); if (!context) return Errno(ENOTSOCK); Socket_fs::Local_functor func(*context, false); return read_sockaddr_in(func, (sockaddr_in *)addr, addrlen); } /************************** ** Socket transport API ** **************************/ extern "C" int socket_fs_accept(int libc_fd, sockaddr *addr, socklen_t *addrlen) { File_descriptor *fd = file_descriptor_allocator()->find_by_libc_fd(libc_fd); if (!fd) return Errno(EBADF); Socket_fs::Context *listen_context = dynamic_cast(fd->context); if (!listen_context) return Errno(ENOTSOCK); /* TODO EOPNOTSUPP - no SOCK_STREAM */ /* TODO ECONNABORTED */ char accept_buf[MAX_CONTROL_PATH_LEN]; { int n = 0; /* XXX currently reading accept may return without new connection */ do { n = read(listen_context->accept_fd(), accept_buf, sizeof(accept_buf)); } while (n == 0); if (n == -1 && errno == EAGAIN) return Errno(EAGAIN); if (n == -1) return Errno(EINVAL); } Socket_fs::Absolute_path path = listen_context->path; path.append("/accept_socket"); int handle_fd = ::open(path.base(), O_RDONLY); if (handle_fd < 0) { error("failed to open accept socket at ", path); return Errno(EACCES); } Socket_fs::Context *accept_context; try { Libc::Allocator alloc { }; accept_context = new (alloc) Socket_fs::Context(listen_context->proto(), handle_fd); } catch (New_socket_failed) { return Errno(EACCES); } File_descriptor *accept_fd = file_descriptor_allocator()->alloc(&plugin(), accept_context); /* inherit the O_NONBLOCK flag if set */ accept_context->fd_flags(listen_context->fd_flags()); if (addr && addrlen) { Socket_fs::Remote_functor func(*accept_context, false); int ret = read_sockaddr_in(func, (sockaddr_in *)addr, addrlen); if (ret == -1) return ret; } return accept_fd->libc_fd; } extern "C" int socket_fs_bind(int libc_fd, sockaddr const *addr, socklen_t addrlen) { File_descriptor *fd = file_descriptor_allocator()->find_by_libc_fd(libc_fd); if (!fd) return Errno(EBADF); Socket_fs::Context *context = dynamic_cast(fd->context); if (!context) return Errno(ENOTSOCK); if (!addr) return Errno(EFAULT); if (addr->sa_family != AF_INET) { error(__func__, ": family not supported"); return Errno(EAFNOSUPPORT); } Sockaddr_string addr_string; try { addr_string = Sockaddr_string(host_string(*(sockaddr_in *)addr), port_string(*(sockaddr_in *)addr)); } catch (Address_conversion_failed) { return Errno(EINVAL); } try { int const len = ::strlen(addr_string.base()); int const n = write(context->bind_fd(), addr_string.base(), len); if (n != len) return Errno(EACCES); /* sync to block for write completion */ return fsync(context->bind_fd()); } catch (Socket_fs::Context::Inaccessible) { return Errno(EINVAL); } } extern "C" int socket_fs_connect(int libc_fd, sockaddr const *addr, socklen_t addrlen) { File_descriptor *fd = file_descriptor_allocator()->find_by_libc_fd(libc_fd); if (!fd) return Errno(EBADF); Socket_fs::Context *context = dynamic_cast(fd->context); if (!context) return Errno(ENOTSOCK); if (!addr) return Errno(EFAULT); switch (addr->sa_family) { case AF_UNSPEC: case AF_INET: break; default: return Errno(EAFNOSUPPORT); } switch (context->state()) { case Context::UNCONNECTED: { Sockaddr_string addr_string; try { addr_string = Sockaddr_string(host_string(*(sockaddr_in const *)addr), port_string(*(sockaddr_in const *)addr)); } catch (Address_conversion_failed) { return Errno(EINVAL); } context->state(Context::CONNECTING); int const len = ::strlen(addr_string.base()); int const n = write(context->connect_fd(), addr_string.base(), len); if (n != len) return Errno(ECONNREFUSED); if (context->fd_flags() & O_NONBLOCK) return Errno(EINPROGRESS); /* block until socket is ready for writing */ fd_set writefds; FD_ZERO(&writefds); FD_SET(libc_fd, &writefds); enum { CONNECT_TIMEOUT_S = 10 }; struct timeval timeout {CONNECT_TIMEOUT_S, 0}; int res = select(libc_fd + 1, NULL, &writefds, NULL, &timeout); if (res < 0) { /* errno has been set by select() */ return res; } if (res == 0) { context->state(Context::CONNECT_ABORTED); return Errno(ETIMEDOUT); } int connect_status = context->read_connect_status(); if (connect_status == 0) context->state(Context::CONNECTED); else context->state(Context::CONNECT_ABORTED); /* errno has been set by context->read_connect_status() */ return connect_status; } break; case Context::ACCEPT_ONLY: return Errno(EINVAL); case Context::CONNECTING: { if (!context->connect_read_ready()) return Errno(EALREADY); int connect_status = context->read_connect_status(); if (connect_status == 0) context->state(Context::CONNECTED); else context->state(Context::CONNECT_ABORTED); /* errno was set by context->read_connect_status() */ return connect_status; } case Context::CONNECTED: return Errno(EISCONN); case Context::CONNECT_ABORTED: return Errno(ECONNABORTED); } return Errno(ECONNREFUSED); } extern "C" int socket_fs_listen(int libc_fd, int backlog) { File_descriptor *fd = file_descriptor_allocator()->find_by_libc_fd(libc_fd); if (!fd) return Errno(EBADF); Socket_fs::Context *context = dynamic_cast(fd->context); if (!context) return Errno(ENOTSOCK); char buf[MAX_CONTROL_PATH_LEN]; int const len = ::snprintf(buf, sizeof(buf), "%d", backlog); int const n = write(context->listen_fd(), buf, len); if (n != len) return Errno(EOPNOTSUPP); /* sync to block for write completion */ int const res = fsync(context->listen_fd()); if (res != 0) return res; context->state(Context::ACCEPT_ONLY); return 0; } static ssize_t do_recvfrom(File_descriptor *fd, void *buf, ::size_t len, int flags, struct sockaddr *src_addr, socklen_t *src_addrlen) { Socket_fs::Context *context = dynamic_cast(fd->context); if (!context) return Errno(ENOTSOCK); if (!buf) return Errno(EFAULT); if (!len) return Errno(EINVAL); if (src_addr) { Socket_fs::Remote_functor func(*context, context->fd_flags() & O_NONBLOCK); int const res = read_sockaddr_in(func, (sockaddr_in *)src_addr, src_addrlen); if (res < 0) return res; } /* TODO ENOTCONN */ /* TODO ECONNREFUSED */ try { lseek(context->data_fd(), 0, 0); ssize_t out_len = read(context->data_fd(), buf, len); return out_len; } catch (Socket_fs::Context::Inaccessible) { return Errno(EINVAL); } } extern "C" ssize_t socket_fs_recvfrom(int libc_fd, void *buf, ::size_t len, int flags, sockaddr *src_addr, socklen_t *src_addrlen) { File_descriptor *fd = file_descriptor_allocator()->find_by_libc_fd(libc_fd); if (!fd) return Errno(EBADF); return do_recvfrom(fd, buf, len, flags, src_addr, src_addrlen); } extern "C" ssize_t socket_fs_recv(int libc_fd, void *buf, ::size_t len, int flags) { /* identical to recvfrom() with a NULL src_addr argument */ return socket_fs_recvfrom(libc_fd, buf, len, flags, nullptr, nullptr); } extern "C" ssize_t socket_fs_recvmsg(int libc_fd, msghdr *msg, int flags) { warning("########## TODO ########## ", __func__); return 0; } static ssize_t do_sendto(File_descriptor *fd, void const *buf, ::size_t len, int flags, sockaddr const *dest_addr, socklen_t dest_addrlen) { Socket_fs::Context *context = dynamic_cast(fd->context); if (!context) return Errno(ENOTSOCK); if (!buf) return Errno(EFAULT); if (!len) return Errno(EINVAL); /* TODO ENOTCONN, EISCONN, EDESTADDRREQ */ try { if (dest_addr && context->proto() == Context::Proto::UDP) { try { Sockaddr_string addr_string(host_string(*(sockaddr_in const *)dest_addr), port_string(*(sockaddr_in const *)dest_addr)); int const len = ::strlen(addr_string.base()); int const n = write(context->remote_fd(), addr_string.base(), len); if (n != len) return Errno(EIO); } catch (Address_conversion_failed) { return Errno(EINVAL); } } lseek(context->data_fd(), 0, 0); ssize_t out_len = write(context->data_fd(), buf, len); switch (context->proto()) { case Socket_fs::Context::Proto::UDP: if (out_len == 0) return Errno(ENETDOWN); break; case Socket_fs::Context::Proto::TCP: if (out_len == 0) return Errno(EAGAIN); /* * Write errors to TCP-data files are reflected as EPIPE, which * means the connection-mode socket is no longer connected. This * explicitly does not differentiate ECONNRESET, which means the * peer closed the connection while there was still unhandled data * in the socket buffer on the remote side and sent an RST packet. * * TODO If the MSG_NOSIGNAL flag is not set, the SIGPIPE signal is * generated to the calling thread. */ if (out_len == -1) return Errno(EPIPE); break; } return out_len; } catch (Socket_fs::Context::Inaccessible) { return Errno(EINVAL); } } extern "C" ssize_t socket_fs_sendto(int libc_fd, void const *buf, ::size_t len, int flags, sockaddr const *dest_addr, socklen_t dest_addrlen) { File_descriptor *fd = file_descriptor_allocator()->find_by_libc_fd(libc_fd); if (!fd) return Errno(EBADF); return do_sendto(fd, buf, len, flags, dest_addr, dest_addrlen); } extern "C" ssize_t socket_fs_send(int libc_fd, void const *buf, ::size_t len, int flags) { /* identical to sendto() with a NULL dest_addr argument */ return socket_fs_sendto(libc_fd, buf, len, flags, nullptr, 0); } extern "C" int socket_fs_getsockopt(int libc_fd, int level, int optname, void *optval, socklen_t *optlen) { File_descriptor *fd = file_descriptor_allocator()->find_by_libc_fd(libc_fd); if (!fd) return Errno(EBADF); Socket_fs::Context *context = dynamic_cast(fd->context); if (!context) return Errno(ENOTSOCK); if (!optval) return Errno(EFAULT); switch (level) { case SOL_SOCKET: switch (optname) { case SO_REUSEADDR: /* not yet implemented - but return true */ *(int *)optval = 1; return 0; case SO_ERROR: if (context->state() == Context::CONNECTING) { int connect_status = context->read_connect_status(); if (connect_status == 0) { *(int*)optval = 0; context->state(Context::CONNECTED); } else { *(int*)optval = errno; context->state(Context::CONNECT_ABORTED); } return 0; } /* not yet implemented - but return true */ *(int *)optval = 0; return 0; case SO_TYPE: switch (context->proto()) { case Socket_fs::Context::Proto::UDP: *(int *)optval = SOCK_DGRAM; break; case Socket_fs::Context::Proto::TCP: *(int *)optval = SOCK_STREAM; break; } return 0; default: return Errno(ENOPROTOOPT); } default: return Errno(EINVAL); } } extern "C" int socket_fs_setsockopt(int libc_fd, int level, int optname, void const *optval, socklen_t optlen) { File_descriptor *fd = file_descriptor_allocator()->find_by_libc_fd(libc_fd); if (!fd) return Errno(EBADF); Socket_fs::Context *context = dynamic_cast(fd->context); if (!context) return Errno(ENOTSOCK); if (!optval) return Errno(EFAULT); switch (level) { case SOL_SOCKET: switch (optname) { case SO_REUSEADDR: /* not yet implemented - always return true */ return 0; case SO_LINGER: { linger *l = (linger *)optval; if (l->l_onoff == 0) return 0; } default: return Errno(ENOPROTOOPT); } case IPPROTO_TCP: switch (optname) { case TCP_NODELAY: return 0; default: return Errno(ENOPROTOOPT); } default: return Errno(EINVAL); } } extern "C" int socket_fs_shutdown(int libc_fd, int how) { File_descriptor *fd = file_descriptor_allocator()->find_by_libc_fd(libc_fd); if (!fd) return Errno(EBADF); Socket_fs::Context *context = dynamic_cast(fd->context); if (!context) return Errno(ENOTSOCK); /* TODO ENOTCONN */ /* TODO EINVAL - returned if 'how' is not supported but we don't support shutdown at all currently */ return 0; } extern "C" int socket_fs_socket(int domain, int type, int protocol) { Socket_fs::Absolute_path path(config_socket()); if (path == "") { error(__func__, ": socket fs not mounted"); return Errno(EACCES); } if (((type&7) != SOCK_STREAM || (protocol != 0 && protocol != IPPROTO_TCP)) && ((type&7) != SOCK_DGRAM || (protocol != 0 && protocol != IPPROTO_UDP))) { error(__func__, ": socket with type=", (Hex)type, " protocol=", (Hex)protocol, " not supported"); return Errno(EAFNOSUPPORT); } /* socket is ensured to be TCP or UDP */ typedef Socket_fs::Context::Proto Proto; Proto proto = (type == SOCK_STREAM) ? Proto::TCP : Proto::UDP; Socket_fs::Context *context = nullptr; try { switch (proto) { case Proto::TCP: path.append("/tcp"); break; case Proto::UDP: path.append("/udp"); break; } path.append("/new_socket"); int handle_fd = ::open(path.base(), O_RDONLY); if (handle_fd < 0) { error("failed to open new socket at ", path); return Errno(EACCES); } Libc::Allocator alloc { }; context = new (alloc) Socket_fs::Context(proto, handle_fd); } catch (New_socket_failed) { return Errno(EACCES); } File_descriptor *fd = file_descriptor_allocator()->alloc(&plugin(), context); return fd->libc_fd; } static int read_ifaddr_file(sockaddr_in &sockaddr, Socket_fs::Absolute_path const &path) { Host_string address; Port_string service; *service.base() = '0'; { FILE *fp = ::fopen(path.base(), "r"); if (!fp) return -1; ::fscanf(fp, "%s\n", address.base()); ::fclose(fp); } try { sockaddr = sockaddr_in_struct(address, service); } catch (...) { return -1; } return 0; } extern "C" int getifaddrs(struct ifaddrs **ifap) { static Lock lock; Lock::Guard guard(lock); static sockaddr_in address; static sockaddr_in netmask { 0 }; static sockaddr_in broadcast { 0 }; static char name[1] { }; static ifaddrs ifaddr { .ifa_name = name, .ifa_flags = IFF_UP, .ifa_addr = (sockaddr*)&address, .ifa_netmask = (sockaddr*)&netmask, .ifa_broadaddr = (sockaddr*)&broadcast, }; *ifap = &ifaddr; using Socket_fs::Absolute_path; Absolute_path const root(config_socket()); if (read_ifaddr_file(address, Absolute_path("address", root.base()))) return -1; read_ifaddr_file(netmask, Absolute_path("netmask", root.base())); return 0; } extern "C" void freeifaddrs(struct ifaddrs *) { } /**************************** ** File-plugin operations ** ****************************/ int Socket_fs::Plugin::fcntl(File_descriptor *fd, int cmd, long arg) { Socket_fs::Context *context = dynamic_cast(fd->context); if (!context) return Errno(EBADF); switch (cmd) { case F_GETFL: return context->fd_flags() | O_RDWR; case F_SETFL: context->fd_flags(arg); return 0; default: error(__func__, " command ", cmd, " not supported on sockets"); return Errno(EINVAL); } } ssize_t Socket_fs::Plugin::read(File_descriptor *fd, void *buf, ::size_t count) { ssize_t const ret = do_recvfrom(fd, buf, count, 0, nullptr, nullptr); if (ret != -1) return ret; /* TODO map recvfrom errno to write errno */ switch (errno) { default: return Errno(errno); } } ssize_t Socket_fs::Plugin::write(File_descriptor *fd, const void *buf, ::size_t count) { ssize_t const ret = do_sendto(fd, buf, count, 0, nullptr, 0); if (ret != -1) return ret; /* TODO map sendto errno to write errno */ switch (errno) { default: return Errno(errno); } } bool Socket_fs::Plugin::poll(File_descriptor &fdo, struct pollfd &pfd) { if (fdo.plugin != this) return false; Socket_fs::Context *context { nullptr }; try { context = dynamic_cast(fdo.context); } catch (Socket_fs::Context::Inaccessible) { pfd.revents |= POLLNVAL; return true; } enum { POLLIN_MASK = POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI, POLLOUT_MASK = POLLOUT | POLLWRNORM | POLLWRBAND, }; bool res { false }; if ((pfd.events & POLLIN_MASK) && context->read_ready()) { pfd.revents |= pfd.events & POLLIN_MASK; res = true; } if ((pfd.events & POLLOUT_MASK) && context->write_ready()) { pfd.revents |= pfd.events & POLLOUT_MASK; res = true; } return res; } bool Socket_fs::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 set) belongs to the VFS */ for (int fd = 0; fd < nfds; ++fd) { if (FD_ISSET(fd, readfds) || FD_ISSET(fd, writefds) || FD_ISSET(fd, exceptfds)) { File_descriptor *fdo = file_descriptor_allocator()->find_by_libc_fd(fd); if (fdo && (fdo->plugin == this)) return true; } } return false; } int Socket_fs::Plugin::select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, timeval *timeout) { int nready = 0; fd_set const in_readfds = *readfds; fd_set const in_writefds = *writefds; /* XXX exceptfds not supported */ /* clear fd sets */ FD_ZERO(readfds); FD_ZERO(writefds); FD_ZERO(exceptfds); for (int fd = 0; fd < nfds; ++fd) { File_descriptor *fdo = file_descriptor_allocator()->find_by_libc_fd(fd); /* handle only fds that belong to this plugin */ if (!fdo || (fdo->plugin != this)) continue; if (FD_ISSET(fd, &in_readfds)) { try { Socket_fs::Context *context = dynamic_cast(fdo->context); if (context->read_ready()) { FD_SET(fd, readfds); ++nready; } } catch (Socket_fs::Context::Inaccessible) { } } if (FD_ISSET(fd, &in_writefds)) { try { Socket_fs::Context *context = dynamic_cast(fdo->context); if (context->write_ready()) { FD_SET(fd, writefds); ++nready; } } catch (Socket_fs::Context::Inaccessible) { } } /* XXX exceptfds not supported */ } return nready; } int Socket_fs::Plugin::close(File_descriptor *fd) { Socket_fs::Context *context = dynamic_cast(fd->context); if (!context) return Errno(EBADF); Libc::Allocator alloc { }; destroy(alloc, context); file_descriptor_allocator()->free(fd); /* * the socket is freed when the initial handle * on 'new_socket' is released at the VFS plugin */ return 0; } int Socket_fs::Plugin::ioctl(File_descriptor *, int request, char*) { if (request == FIONREAD) { /* * This request occurs quite often when using the Arora web browser, * so print the error message only once. */ static bool print_fionread_error_message = true; if (print_fionread_error_message) { error(__func__, " request FIONREAD not supported on sockets" " (this message will not be shown again)"); print_fionread_error_message = false; } return -1; } error(__func__, " request ", request, " not supported on sockets"); return -1; } Libc::Socket_fs::Plugin &Libc::Socket_fs::plugin() { static Socket_fs::Plugin inst; return inst; }