/* * \brief Noux libc plugin * \author Norman Feske * \date 2011-02-14 */ /* * Copyright (C) 2011-2012 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. */ /* Genode includes */ #include #include #include #include #include #include /* noux includes */ #include #include /* libc plugin includes */ #include #include /* libc includes */ #include #include #include #include #include #include #include #include void *operator new (size_t, void *ptr) { return ptr; } class Noux_connection { private: Noux::Connection _connection; Noux::Sysio *_sysio; Noux::Sysio *_obtain_sysio() { return Genode::env()->rm_session()->attach(_connection.sysio_dataspace()); } public: Noux_connection() : _sysio(_obtain_sysio()) { } void reconnect() { new (&_connection) Noux_connection; Genode::env()->rm_session()->detach(_sysio); _sysio = _obtain_sysio(); } Noux::Session *session() { return &_connection; } Noux::Sysio *sysio() { return _sysio; } }; Noux_connection *noux_connection() { static Noux_connection inst; return &inst; } Noux::Session *noux() { return noux_connection()->session(); } Noux::Sysio *sysio() { return noux_connection()->sysio(); } enum { FS_BLOCK_SIZE = 1024 }; /*********************************************** ** Overrides of libc default implementations ** ***********************************************/ extern "C" int __getcwd(char *dst, Genode::size_t dst_size) { noux()->syscall(Noux::Session::SYSCALL_GETCWD); Genode::size_t path_size = Genode::strlen(sysio()->getcwd_out.path); if (dst_size < path_size + 1) return -ERANGE; Genode::strncpy(dst, sysio()->getcwd_out.path, dst_size); return 0; } /** * Utility to copy-out syscall results to buf struct * * Code shared between 'stat' and 'fstat'. */ static void _sysio_to_stat_struct(Noux::Sysio const *sysio, struct stat *buf) { Genode::memset(buf, 0, sizeof(*buf)); buf->st_uid = sysio->stat_out.st.uid; buf->st_gid = sysio->stat_out.st.gid; buf->st_mode = sysio->stat_out.st.mode; buf->st_size = sysio->stat_out.st.size; buf->st_blksize = FS_BLOCK_SIZE; buf->st_blocks = (buf->st_size + FS_BLOCK_SIZE - 1) / FS_BLOCK_SIZE; buf->st_ino = sysio->stat_out.st.inode; buf->st_dev = sysio->stat_out.st.device; } static int _stat(const char *path, struct stat *buf, bool lstat = false) { if ((path == NULL) or (buf == NULL)) { errno = EFAULT; return -1; } Genode::strncpy(sysio()->stat_in.path, path, sizeof(sysio()->stat_in.path)); if (!noux()->syscall(Noux::Session::SYSCALL_STAT)) { PWRN("stat syscall failed for path \"%s\"", path); switch (sysio()->error.stat) { case Noux::Sysio::STAT_ERR_NO_ENTRY: errno = ENOENT; break; } return -1; } _sysio_to_stat_struct(sysio(), buf); return 0; } extern "C" int lstat(const char *path, struct stat *buf) { return _stat(path, buf, true); } static bool serialize_string_array(char const * const * array, char *dst, Genode::size_t dst_len) { for (unsigned i = 0; array[i]; i++) { Genode::size_t const curr_len = Genode::strlen(array[i]) + 1; if (curr_len + 1 >= dst_len) return false; Genode::strncpy(dst, array[i], dst_len); dst += curr_len; dst_len -= curr_len; } dst[0] = 0; return true; } extern "C" int execve(const char *filename, char *const argv[], char *const envp[]) { PDBG("filename=%s", filename); for (int i = 0; argv[i]; i++) PDBG("argv[%d]='%s'", i, argv[i]); for (int i = 0; envp[i]; i++) PDBG("envp[%d]='%s'", i, envp[i]); Genode::strncpy(sysio()->execve_in.filename, filename, sizeof(sysio()->execve_in.filename)); if (!serialize_string_array(argv, sysio()->execve_in.args, sizeof(sysio()->execve_in.args))) { PERR("execve: argument buffer exceeded"); errno = E2BIG; return -1; } if (!serialize_string_array(envp, sysio()->execve_in.env, sizeof(sysio()->execve_in.env))) { PERR("execve: environment buffer exceeded"); errno = E2BIG; return -1; } if (!noux()->syscall(Noux::Session::SYSCALL_EXECVE)) { PWRN("exec syscall failed for path \"%s\"", filename); switch (sysio()->error.execve) { case Noux::Sysio::EXECVE_NONEXISTENT: errno = ENOENT; break; } return -1; } /* * In the success case, we never return from execve, the execution is * resumed in the new program. */ Genode::sleep_forever(); return 0; } /** * Return number of marhalled file descriptors into select argument buffer * * \return number of marshalled file descriptors, this value is guaranteed to * not exceed the 'dst_fds_len' argument */ static size_t marshal_fds(fd_set *src_fds, int nfds, int *dst_fds, size_t dst_fds_len) { if (!src_fds) return 0; size_t num_fds = 0; for (int fd = 0; (fd < nfds) && (num_fds < dst_fds_len); fd++) if (FD_ISSET(fd, src_fds)) dst_fds[num_fds++] = fd; return num_fds; } /** * Unmarshal result of select syscall into fd set */ static void unmarshal_fds(int *src_fds, size_t src_fds_len, fd_set *dst_fds) { if (!dst_fds) return; FD_ZERO(dst_fds); for (size_t i = 0; i < src_fds_len; i++) FD_SET(src_fds[i], dst_fds); } extern "C" int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) { /* * Marshal file descriptor into sysio page */ Noux::Sysio::Select_fds &in_fds = sysio()->select_in.fds; int *dst = in_fds.array; size_t dst_len = Noux::Sysio::Select_fds::MAX_FDS; in_fds.num_rd = marshal_fds(readfds, nfds, dst, dst_len); dst += in_fds.num_rd; dst_len -= in_fds.num_rd; in_fds.num_wr = marshal_fds(writefds, nfds, dst, dst_len); dst += in_fds.num_wr; dst_len -= in_fds.num_wr; in_fds.num_ex = marshal_fds(exceptfds, nfds, dst, dst_len); if (in_fds.max_fds_exceeded()) { errno = ENOMEM; return -1; } /* * Marshal timeout */ if (timeout) { sysio()->select_in.timeout.sec = timeout->tv_sec; sysio()->select_in.timeout.usec = timeout->tv_usec; if (!sysio()->select_in.timeout.zero()) { // PDBG("timeout=%d,%d -> replaced by zero timeout", // (int)timeout->tv_sec, (int)timeout->tv_usec); sysio()->select_in.timeout.sec = 0; sysio()->select_in.timeout.usec = 0; } } else { sysio()->select_in.timeout.set_infinite(); } /* * Perform syscall */ if (!noux()->syscall(Noux::Session::SYSCALL_SELECT)) { PWRN("select syscall failed"); // switch (sysio()->error.select) { // case Noux::Sysio::SELECT_NONEXISTENT: errno = ENOENT; break; // } return -1; } /* * Unmarshal file selectors reported by the select syscall */ Noux::Sysio::Select_fds &out_fds = sysio()->select_out.fds; int *src = out_fds.array; unmarshal_fds(src, out_fds.num_rd, readfds); src += out_fds.num_rd; unmarshal_fds(src, out_fds.num_wr, writefds); src += out_fds.num_wr; unmarshal_fds(src, out_fds.num_ex, exceptfds); return out_fds.total_fds(); } #include #include static jmp_buf fork_jmp_buf; static Genode::Capability new_parent_cap; extern "C" void stdout_reconnect(); /* provided by 'log_console.cc' */ /* * The new process created via fork will start its execution here. */ extern "C" void fork_trampoline() { static_cast(Genode::env())->reload_parent_cap(new_parent_cap); stdout_reconnect(); noux_connection()->reconnect(); longjmp(fork_jmp_buf, 1); } extern "C" pid_t fork(void) { /* stack used for executing 'fork_trampoline' */ enum { STACK_SIZE = 1024 }; static long stack[STACK_SIZE]; if (setjmp(fork_jmp_buf)) { /* * We got here via longjmp from 'fork_trampoline'. */ return 0; } else { /* got here during the normal control flow of the fork call */ sysio()->fork_in.ip = (Genode::addr_t)(&fork_trampoline); sysio()->fork_in.sp = (Genode::addr_t)(&stack[STACK_SIZE]); sysio()->fork_in.parent_cap_addr = (Genode::addr_t)(&new_parent_cap); if (!noux()->syscall(Noux::Session::SYSCALL_FORK)) { PERR("fork error %d", sysio()->error.general); } return sysio()->fork_out.pid; } } extern "C" pid_t getpid(void) { noux()->syscall(Noux::Session::SYSCALL_GETPID); return sysio()->getpid_out.pid; } extern "C" pid_t _wait4(pid_t pid, int *status, int options, struct rusage *rusage) { sysio()->wait4_in.pid = pid; sysio()->wait4_in.nohang = !!(options & WNOHANG); if (!noux()->syscall(Noux::Session::SYSCALL_WAIT4)) { PERR("wait4 error %d", sysio()->error.general); return -1; } if (status) *status = sysio()->wait4_out.status; return sysio()->wait4_out.pid; } /******************** ** Time functions ** ********************/ /* * The default implementations as provided by the libc rely on a dedicated * thread. But on Noux, no thread other than the main thread is allowed. For * this reason, we need to override the default implementations here. */ extern "C" int clock_gettime(clockid_t clk_id, struct timespec *tp) { PDBG("clock_gettime called - not implemented"); errno = EINVAL; return -1; } extern "C" int gettimeofday(struct timeval *tv, struct timezone *tz) { PDBG("gettimeofdaye called - not implemented"); errno = EINVAL; return -1; } /********************* ** File operations ** *********************/ static int noux_fd(Libc::Plugin_context *context) { /* * We use the 'context' pointer only as container for an int value. It is * never used as a pointer. To make 64-bit compilers happy, we need to keep * the bit width of the cast intact. The upper 32 bit are discarded when * leaving the function. */ return (long)context; } static Libc::Plugin_context *noux_context(int noux_fd) { return reinterpret_cast(noux_fd); } namespace { class Plugin : public Libc::Plugin { private: Libc::File_descriptor *_stdin; Libc::File_descriptor *_stdout; Libc::File_descriptor *_stderr; public: /** * Constructor */ Plugin() : _stdin (Libc::file_descriptor_allocator()->alloc(this, noux_context(0), 0)), _stdout(Libc::file_descriptor_allocator()->alloc(this, noux_context(1), 1)), _stderr(Libc::file_descriptor_allocator()->alloc(this, noux_context(2), 2)) { } bool supports_chdir(const char *) { return true; } bool supports_open(const char *, int) { return true; } bool supports_stat(const char *) { return true; } Libc::File_descriptor *open(const char *, int); ssize_t write(Libc::File_descriptor *, const void *, ::size_t); int close(Libc::File_descriptor *); int fstat(Libc::File_descriptor *, struct stat *); int fstatfs(Libc::File_descriptor *, struct statfs *); int fcntl(Libc::File_descriptor *, int, long); ssize_t getdirentries(Libc::File_descriptor *, char *, ::size_t, ::off_t *); ::off_t lseek(Libc::File_descriptor *, ::off_t offset, int whence); int fchdir(Libc::File_descriptor *); ssize_t read(Libc::File_descriptor *, void *, ::size_t); int stat(const char *, struct stat *); int ioctl(Libc::File_descriptor *, int request, char *argp); }; int Plugin::stat(const char *path, struct stat *buf) { return _stat(path, buf, false); } Libc::File_descriptor *Plugin::open(const char *pathname, int flags) { if (Genode::strlen(pathname) + 1 > sizeof(sysio()->open_in.path)) { errno = ENAMETOOLONG; return 0; } Genode::strncpy(sysio()->open_in.path, pathname, sizeof(sysio()->open_in.path)); sysio()->open_in.mode = flags; if (!noux()->syscall(Noux::Session::SYSCALL_OPEN)) { /* * XXX we should return meaningful errno values */ errno = ENOENT; return 0; } Libc::Plugin_context *context = noux_context(sysio()->open_out.fd); return Libc::file_descriptor_allocator()->alloc(this, context); } int Plugin::fstatfs(Libc::File_descriptor *, struct statfs *buf) { return 0; } ssize_t Plugin::write(Libc::File_descriptor *fd, const void *buf, ::size_t count) { if (fd != _stdout && fd != _stderr) { errno = EBADF; return -1; } /* remember original len for the return value */ int const orig_count = count; char *src = (char *)buf; while (count > 0) { Genode::size_t curr_count = Genode::min((::size_t)Noux::Sysio::CHUNK_SIZE, count); sysio()->write_in.fd = noux_fd(fd->context); sysio()->write_in.count = curr_count; Genode::memcpy(sysio()->write_in.chunk, src, curr_count); if (!noux()->syscall(Noux::Session::SYSCALL_WRITE)) { PERR("write error %d", sysio()->error.general); } count -= curr_count; src += curr_count; } return orig_count; } ssize_t Plugin::read(Libc::File_descriptor *fd, void *buf, ::size_t count) { Genode::size_t sum_read_count = 0; while (count) { Genode::size_t curr_count = Genode::min(count, sizeof(sysio()->read_out.chunk)); sysio()->read_in.fd = noux_fd(fd->context); sysio()->read_in.count = curr_count; if (!noux()->syscall(Noux::Session::SYSCALL_READ)) { PERR("read error"); /* XXX set errno */ return -1; } Genode::memcpy(buf, sysio()->read_out.chunk, sysio()->read_out.count); // for (int i = 0; i < sysio()->read_out.count; i++) // Genode::printf("read %d\n", ((char *)buf)[i]); sum_read_count += sysio()->read_out.count; if (sysio()->read_out.count < sysio()->read_in.count) break; /* end of file */ if (sysio()->read_out.count <= count) count -= sysio()->read_out.count; else break; /* should not happen */ } return sum_read_count; } int Plugin::close(Libc::File_descriptor *fd) { sysio()->close_in.fd = noux_fd(fd->context); if (!noux()->syscall(Noux::Session::SYSCALL_CLOSE)) { PERR("close error"); /* XXX set errno */ return -1; } return 0; } int Plugin::ioctl(Libc::File_descriptor *fd, int request, char *argp) { /* * Marshal ioctl arguments */ sysio()->ioctl_in.fd = noux_fd(fd->context); sysio()->ioctl_in.request = Noux::Sysio::Ioctl_in::OP_UNDEFINED; switch (request) { case TIOCGWINSZ: sysio()->ioctl_in.request = Noux::Sysio::Ioctl_in::OP_TIOCGWINSZ; break; case TIOCGETA: { PDBG("TIOCGETA - argp=0x%p", argp); ::termios *termios = (::termios *)argp; /* * Set 'ECHO' flag, needed by libreadline. Otherwise, echoing * user input doesn't work in bash. */ termios->c_lflag = ECHO; return 0; } break; default: PWRN("unsupported ioctl (request=0x%x", request); break; } if (sysio()->ioctl_in.request == Noux::Sysio::Ioctl_in::OP_UNDEFINED) { errno = ENOTTY; return -1; } /* perform syscall */ if (!noux()->syscall(Noux::Session::SYSCALL_IOCTL)) { PERR("ioctl error"); /* XXX set errno */ return -1; } /* * Unmarshal ioctl results */ switch (request) { case TIOCGWINSZ: { ::winsize *winsize = (::winsize *)argp; winsize->ws_row = sysio()->ioctl_out.tiocgwinsz.rows; winsize->ws_col = sysio()->ioctl_out.tiocgwinsz.columns; return 0; } default: return -1; } } int Plugin::fstat(Libc::File_descriptor *fd, struct stat *buf) { sysio()->fstat_in.fd = noux_fd(fd->context); if (!noux()->syscall(Noux::Session::SYSCALL_FSTAT)) { PERR("fstat error"); /* XXX set errno */ return -1; } _sysio_to_stat_struct(sysio(), buf); return 0; } int Plugin::fcntl(Libc::File_descriptor *fd, int cmd, long arg) { /* copy arguments to sysio */ sysio()->fcntl_in.fd = noux_fd(fd->context); switch (cmd) { case F_SETFD: sysio()->fcntl_in.cmd = Noux::Sysio::FCNTL_CMD_SET_FD_FLAGS; sysio()->fcntl_in.long_arg = arg; break; case F_GETFL: PINF("fcntl: F_GETFL for libc_fd=%d", fd->libc_fd); sysio()->fcntl_in.cmd = Noux::Sysio::FCNTL_CMD_GET_FILE_STATUS_FLAGS; break; default: PERR("fcntl: unsupported command %d", cmd); errno = EINVAL; return -1; }; /* invoke system call */ if (!noux()->syscall(Noux::Session::SYSCALL_FCNTL)) { PWRN("fcntl failed (libc_fd= %d, cmd=%x)", fd->libc_fd, cmd); /* XXX read error code from sysio */ errno = EINVAL; return -1; } /* read result from sysio */ return sysio()->fcntl_out.result; } ssize_t Plugin::getdirentries(Libc::File_descriptor *fd, char *buf, ::size_t nbytes, ::off_t *basep) { if (nbytes < sizeof(struct dirent)) { PERR("buf too small"); return -1; } unsigned const curr_offset = *basep; sysio()->dirent_in.fd = noux_fd(fd->context); sysio()->dirent_in.index = curr_offset / sizeof(struct dirent); struct dirent *dirent = (struct dirent *)buf; Genode::memset(dirent, 0, sizeof(struct dirent)); if (!noux()->syscall(Noux::Session::SYSCALL_DIRENT)) { switch (sysio()->error.general) { case Noux::Sysio::ERR_FD_INVALID: errno = EBADF; PERR("dirent: ERR_FD_INVALID"); return -1; case Noux::Sysio::NUM_GENERAL_ERRORS: return -1; } } switch (sysio()->dirent_out.entry.type) { case Noux::Sysio::DIRENT_TYPE_DIRECTORY: dirent->d_type = DT_DIR; break; case Noux::Sysio::DIRENT_TYPE_FILE: dirent->d_type = DT_REG; break; case Noux::Sysio::DIRENT_TYPE_SYMLINK: dirent->d_type = DT_LNK; break; case Noux::Sysio::DIRENT_TYPE_FIFO: dirent->d_type = DT_FIFO; break; case Noux::Sysio::DIRENT_TYPE_END: return 0; } dirent->d_fileno = sysio()->dirent_out.entry.fileno; dirent->d_reclen = sizeof(struct dirent); Genode::strncpy(dirent->d_name, sysio()->dirent_out.entry.name, sizeof(dirent->d_name)); dirent->d_namlen = Genode::strlen(dirent->d_name); *basep += sizeof(struct dirent); return sizeof(struct dirent); } ::off_t Plugin::lseek(Libc::File_descriptor *fd, ::off_t offset, int whence) { PWRN("lseek - not implemented: fd=%d, offset=%ld, whence=%d", noux_fd(fd->context), (long)offset, whence); if (whence == SEEK_SET) return offset; return 0; } int Plugin::fchdir(Libc::File_descriptor *fd) { sysio()->fchdir_in.fd = noux_fd(fd->context); if (!noux()->syscall(Noux::Session::SYSCALL_FCHDIR)) { PERR("fchdir error"); /* XXX set errno */ return -1; } return 0; } } /* unnamed namespace */ /************************************** ** Obtaining command-line arguments ** **************************************/ /* external symbols provided by Genode's startup code */ extern char **genode_argv; extern int genode_argc; /* pointer to environment, provided by libc */ extern char **environ; __attribute__((constructor)) void init_libc_noux(void) { /* copy command-line arguments from 'args' ROM dataspace */ Genode::Rom_connection args_rom("args"); char *args = (char *)Genode::env()->rm_session()-> attach(args_rom.dataspace()); enum { MAX_ARGS = 256, ARG_BUF_SIZE = 4096 }; static char *argv[MAX_ARGS]; static char arg_buf[ARG_BUF_SIZE]; Genode::memcpy(arg_buf, args, ARG_BUF_SIZE); int argc = 0; for (unsigned i = 0; arg_buf[i] && (i < ARG_BUF_SIZE - 2); ) { if (i >= ARG_BUF_SIZE - 2) { PWRN("command-line argument buffer exceeded\n"); break; } if (argc >= MAX_ARGS - 1) { PWRN("number of command-line arguments exceeded\n"); break; } argv[argc] = &arg_buf[i]; i += Genode::strlen(&args[i]) + 1; /* skip null-termination */ argc++; } /* register command-line arguments at Genode's startup code */ genode_argv = argv; genode_argc = argc; /* * Make environment variables from 'env' ROM dataspace available to libc's * 'environ'. */ Genode::Rom_connection env_rom("env"); Genode::Dataspace_capability env_ds = env_rom.dataspace(); char *env_string = (char *)Genode::env()->rm_session()->attach(env_ds); enum { ENV_MAX_SIZE = 4096, ENV_MAX_ENTRIES = 128, ENV_KEY_MAX_SIZE = 256, ENV_VALUE_MAX_SIZE = 1024 }; static char env_buf[ENV_MAX_SIZE]; static char *env_array[ENV_MAX_ENTRIES]; unsigned num_entries = 0; /* index within 'env_array' */ Genode::size_t i = 0; /* index within 'env_buf' */ while ((num_entries < ENV_MAX_ENTRIES - 2) && (i < ENV_MAX_SIZE)) { Genode::Arg arg = Genode::Arg_string::first_arg(env_string); if (!arg.valid()) break; char key_buf[ENV_KEY_MAX_SIZE], value_buf[ENV_VALUE_MAX_SIZE]; arg.key(key_buf, sizeof(key_buf)); arg.string(value_buf, sizeof(value_buf), ""); env_array[num_entries++] = env_buf + i; Genode::snprintf(env_buf + i, ENV_MAX_SIZE - i, "%s=%s", key_buf, value_buf); i += Genode::strlen(env_buf + i) + 1; /* remove processed arg from 'env_string' */ Genode::Arg_string::remove_arg(env_string, key_buf); } /* register list of environment variables at libc 'environ' pointer */ environ = env_array; /* initialize noux libc plugin */ static Plugin noux_plugin; }