/* * \brief Libc plugin that uses Genode's Terminal session * \author Norman Feske * \date 2011-09-09 */ /* * Copyright (C) 2011-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 plugin interface */ #include #include /* libc includes */ #include #include #include #include /* Genode includes */ #include #include #include #include #include extern void (*libc_select_notify)(); namespace { typedef Genode::Thread<4096> Read_sigh_thread; /** * Thread for receiving notifications about data available for reading * from terminal session */ class Read_sigh : Read_sigh_thread { private: Genode::Lock _startup_lock; Genode::Signal_context _sig_ctx; Genode::Signal_receiver _sig_rec; Genode::Signal_context_capability _sig_cap; void entry() { _sig_cap = _sig_rec.manage(&_sig_ctx); _startup_lock.unlock(); for (;;) { _sig_rec.wait_for_signal(); if (libc_select_notify) libc_select_notify(); } } public: Read_sigh() : Read_sigh_thread("read_sigh"), _startup_lock(Genode::Lock::LOCKED) { start(); /* wait until '_sig_cap' is initialized */ _startup_lock.lock(); } Genode::Signal_context_capability cap() { return _sig_cap; } }; /** * Return singleton instance of 'Read_sigh' */ static Genode::Signal_context_capability read_sigh() { static Read_sigh inst; return inst.cap(); } /** * An open file descriptor for this plugin corresponds to a terminal * connection * * The terminal connection is created along with the context. The * notifications about data available for reading are delivered to * the 'Read_sigh' thread, which cares about unblocking 'select()'. */ class Plugin_context : public Libc::Plugin_context, public Terminal::Connection { private: int _status_flags; public: Plugin_context() : _status_flags(0) { read_avail_sigh(read_sigh()); } /** * Set/get file status status flags */ void status_flags(int flags) { _status_flags = flags; } int status_flags() { return _status_flags; } }; static inline Plugin_context *context(Libc::File_descriptor *fd) { return static_cast(fd->context); } class Plugin : public Libc::Plugin { private: /** * File name this plugin feels responsible for */ static char const *_dev_name() { return "/dev/terminal"; } /* * Prioritize plugin over libc_vfs */ enum { PLUGIN_PRIORITY = 1 }; public: /** * Constructor */ Plugin() : Libc::Plugin(PLUGIN_PRIORITY) { } bool supports_stat(const char *path) { return (Genode::strcmp(path, "/dev") == 0) || (Genode::strcmp(path, _dev_name()) == 0); } bool supports_open(const char *path, int flags) { return (Genode::strcmp(path, _dev_name()) == 0); } Libc::File_descriptor *open(const char *pathname, int flags) { Plugin_context *context = new (Genode::env()->heap()) Plugin_context; context->status_flags(flags); return Libc::file_descriptor_allocator()->alloc(this, context); } int close(Libc::File_descriptor *fd) { Genode::destroy(Genode::env()->heap(), context(fd)); Libc::file_descriptor_allocator()->free(fd); return 0; } int stat(const char *path, struct stat *buf) { /* * We pretent to be a character device * * This is important, i.e., to convince the gdbserver code to * cooperate with us. */ if (buf) { Genode::memset(buf, 0, sizeof(struct stat)); if (Genode::strcmp(path, "/dev") == 0) buf->st_mode = S_IFDIR; else if (Genode::strcmp(path, _dev_name()) == 0) buf->st_mode = S_IFCHR; else { errno = ENOENT; return -1; } } return 0; } int fstat(Libc::File_descriptor *fd, struct stat *buf) { if (buf) { Genode::memset(buf, 0, sizeof(struct stat)); buf->st_mode = S_IFCHR; } return 0; } bool supports_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) { return true; } int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) { fd_set in_readfds; fd_set in_writefds; FD_ZERO(&in_readfds); FD_ZERO(&in_writefds); if (readfds) { in_readfds = *readfds; FD_ZERO(readfds); } if (writefds) { in_writefds = *writefds; FD_ZERO(writefds); } if (exceptfds) FD_ZERO(exceptfds); int nready = 0; for (int libc_fd = 0; libc_fd < nfds; libc_fd++) { Libc::File_descriptor *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) && context(fdo)->avail()) { if (readfds) FD_SET(libc_fd, readfds); nready++; } if (FD_ISSET(libc_fd, &in_writefds)) { if (writefds) FD_SET(libc_fd, writefds); nready++; } } return nready; } ssize_t write(Libc::File_descriptor *fd, const void *buf, ::size_t count) { Genode::size_t chunk_size = context(fd)->io_buffer_size(); Genode::size_t written_bytes = 0; while (written_bytes < count) { Genode::size_t n = Genode::min(count - written_bytes, chunk_size); context(fd)->write((char *)buf + written_bytes, n); written_bytes += n; } return count; } ssize_t read(Libc::File_descriptor *fd, void *buf, ::size_t count) { for (;;) { Genode::size_t num_bytes = context(fd)->read(buf, count); if (num_bytes) return num_bytes; /* read returned 0, block until data becomes available */ fd_set rfds; FD_ZERO(&rfds); FD_SET(fd->libc_fd, &rfds); ::select(fd->libc_fd + 1, &rfds, 0, 0, 0); } } int fcntl(Libc::File_descriptor *fd, int cmd, long arg) { switch (cmd) { case F_GETFL: return context(fd)->status_flags(); 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; } } default: PERR("fcntl(): command %d args %ld not supported - terminal", cmd, arg); return -1; } } int ioctl(Libc::File_descriptor *fd, int request, char *argp) { struct termios *t = (struct termios*)argp; switch (request) { case TIOCGETA: Genode::memset(t,0,sizeof(struct termios)); t->c_lflag = ECHO; return 0; case TIOCSETAW: return 0; case TIOCSETAF: return 0; case TIOCGWINSZ: { ::winsize *winsize = (::winsize *)argp; Terminal::Session::Size terminal_size = context(fd)->size(); winsize->ws_row = terminal_size.lines(); winsize->ws_col = terminal_size.columns(); return 0; } } return -1; } int dup2(Libc::File_descriptor *fd, Libc::File_descriptor *new_fd) { new_fd->context = fd->context; return new_fd->libc_fd; } }; } /* unnamed namespace */ void __attribute__((constructor)) init_libc_terminal(void) { static Plugin plugin; }