/* * \brief Libc kernel for main and pthreads user contexts * \author Christian Helmuth * \author Emery Hemingway * \author Norman Feske * \date 2016-01-22 */ /* * Copyright (C) 2016-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. */ /* libc-internal includes */ #include Libc::Kernel * Libc::Kernel::_kernel_ptr; /** * Main context execution was suspended (on fork) * * This function is executed in the context of the initial thread. */ static void suspended_callback() { Libc::Kernel::kernel().entrypoint_suspended(); } /** * Resume main context execution (after fork) * * This function is executed in the context of the initial thread. */ static void resumed_callback() { Libc::Kernel::kernel().entrypoint_resumed(); } size_t Libc::Kernel::_user_stack_size() { size_t size = Component::stack_size(); if (!_cloned) return size; _libc_env.libc_config().with_sub_node("stack", [&] (Xml_node stack) { size = stack.attribute_value("size", 0UL); }); return size; } void Libc::Kernel::schedule_suspend(void(*original_suspended_callback) ()) { if (_state != USER) { Genode::error(__PRETTY_FUNCTION__, " called from non-user context"); return; } /* * We hook into suspend-resume callback chain to destruct and * reconstruct parts of the kernel from the context of the initial * thread, i.e., without holding any object locks. */ _original_suspended_callback = original_suspended_callback; _env.ep().schedule_suspend(suspended_callback, resumed_callback); if (!_setjmp(_user_context)) { _valid_user_context = true; _suspend_scheduled = true; _switch_to_kernel(); } else { _valid_user_context = false; } } void Libc::Kernel::reset_malloc_heap() { _malloc_ram.construct(_heap, _env.ram()); _cloned_heap_ranges.for_each([&] (Registered &r) { destroy(_heap, &r); }); Heap &raw_malloc_heap = *_malloc_heap; Genode::construct_at(&raw_malloc_heap, *_malloc_ram, _env.rm()); reinit_malloc(raw_malloc_heap); } void Libc::Kernel::_init_file_descriptors() { auto init_fd = [&] (Genode::Xml_node const &node, char const *attr, int libc_fd, unsigned flags) { if (!node.has_attribute(attr)) return; typedef Genode::String Path; Path const path = node.attribute_value(attr, Path()); struct stat out_stat { }; if (stat(path.string(), &out_stat) != 0) return; Libc::File_descriptor *fd = _vfs.open(path.string(), flags, libc_fd); if (fd->libc_fd != libc_fd) { Genode::error("could not allocate fd ",libc_fd," for ",path,", " "got fd ",fd->libc_fd); _vfs.close(fd); return; } /* * We need to manually register the path. Normally this is done * by '_open'. But we call the local 'open' function directly * because we want to explicitly specify the libc fd ID. */ if (fd->fd_path) Genode::warning("may leak former FD path memory"); { char *dst = (char *)_heap.alloc(path.length()); Genode::strncpy(dst, path.string(), path.length()); fd->fd_path = dst; } ::off_t const seek = node.attribute_value("seek", 0ULL); if (seek) _vfs.lseek(fd, seek, SEEK_SET); }; if (_vfs.root_dir_has_dirents()) { Xml_node const node = _libc_env.libc_config(); typedef Genode::String Path; if (node.has_attribute("cwd")) chdir(node.attribute_value("cwd", Path()).string()); init_fd(node, "stdin", 0, O_RDONLY); init_fd(node, "stdout", 1, O_WRONLY); init_fd(node, "stderr", 2, O_WRONLY); node.for_each_sub_node("fd", [&] (Xml_node fd) { unsigned const id = fd.attribute_value("id", 0U); bool const rd = fd.attribute_value("readable", false); bool const wr = fd.attribute_value("writeable", false); unsigned const flags = rd ? (wr ? O_RDWR : O_RDONLY) : (wr ? O_WRONLY : 0); if (!fd.has_attribute("path")) warning("Invalid node, 'path' attribute is missing"); init_fd(fd, "path", id, flags); }); /* prevent use of IDs of stdin, stdout, and stderr for other files */ for (unsigned fd = 0; fd <= 2; fd++) Libc::file_descriptor_allocator()->preserve(fd); } } void Libc::Kernel::_clone_state_from_parent() { struct Range { void *at; size_t size; }; auto range_attr = [&] (Xml_node node) { return Range { .at = (void *)node.attribute_value("at", 0UL), .size = node.attribute_value("size", 0UL) }; }; /* * Allocate local memory for the backing store of the application heap, * mirrored from the parent. * * This step must precede the creation of the 'Clone_connection' because * the shared-memory buffer of the clone session may otherwise potentially * interfere with such a heap region. */ _libc_env.libc_config().for_each_sub_node("heap", [&] (Xml_node node) { Range const range = range_attr(node); new (_heap) Registered(_cloned_heap_ranges, _env.ram(), _env.rm(), range.at, range.size); }); Clone_connection clone_connection(_env); /* fetch heap content */ _cloned_heap_ranges.for_each([&] (Cloned_malloc_heap_range &heap_range) { heap_range.import_content(clone_connection); }); /* fetch user contex of the parent's application */ clone_connection.memory_content(&_user_context, sizeof(_user_context)); _valid_user_context = true; _libc_env.libc_config().for_each_sub_node([&] (Xml_node node) { auto copy_from_parent = [&] (Range range) { clone_connection.memory_content(range.at, range.size); }; /* clone application stack */ if (node.type() == "stack") copy_from_parent(range_attr(node)); /* clone RW segment of a shared library or the binary */ if (node.type() == "rw") { typedef String<64> Name; Name const name = node.attribute_value("name", Name()); /* * The blacklisted segments are initialized via the * regular startup of the child. */ bool const blacklisted = (name == "ld.lib.so") || (name == "libc.lib.so") || (name == "libm.lib.so") || (name == "posix.lib.so") || (strcmp(name.string(), "vfs", 3) == 0); if (!blacklisted) copy_from_parent(range_attr(node)); } }); /* import application-heap state from parent */ clone_connection.object_content(_malloc_heap); init_malloc_cloned(clone_connection); } extern void (*libc_select_notify)(); void Libc::Kernel::handle_io_progress() { /* * TODO: make VFS I/O completion checks during * kernel time to avoid flapping between stacks */ if (_io_ready) { _io_ready = false; /* some contexts may have been deblocked from select() */ if (libc_select_notify) libc_select_notify(); /* * resume all as any VFS context may have * been deblocked from blocking I/O */ Kernel::resume_all(); } } void Libc::execute_in_application_context(Libc::Application_code &app_code) { /* * The libc execution model builds on the main entrypoint, which handles * all relevant signals (e.g., timing and VFS). Additional component * entrypoints or pthreads should never call with_libc() but we catch this * here and just execute the application code directly. */ if (!Kernel::kernel().main_context()) { app_code.execute(); return; } static bool nested = false; if (nested) { if (Kernel::kernel().main_suspended()) { Kernel::kernel().nested_execution(app_code); } else { app_code.execute(); } return; } nested = true; Kernel::kernel().run(app_code); nested = false; } Libc::Kernel::Kernel(Genode::Env &env, Genode::Allocator &heap) : _env(env), _heap(heap) { init_pthread_support(env, *this, *this); _env.ep().register_io_progress_handler(*this); if (_cloned) { _clone_state_from_parent(); } else { _malloc_heap.construct(*_malloc_ram, _env.rm()); init_malloc(*_malloc_heap); } init_fork(_env, _libc_env, _heap, *_malloc_heap, _pid, *this, *this, *this); init_execve(_env, _heap, _user_stack, *this); init_plugin(*this); init_sleep(*this); init_vfs_plugin(*this); init_time(*this, _rtc_path); init_poll(*this); init_select(*this, *this, *this); init_socket_fs(*this); _init_file_descriptors(); _kernel_ptr = this; }