/* * \brief File system for trace buffer access * \author Sebastian Sumpf * \date 2019-06-13 */ /* * Copyright (C) 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. */ #include #include #include #include #include #include #include "directory_tree.h" #include "trace_buffer.h" #include "value_file_system.h" namespace Vfs_trace { using namespace Vfs; using namespace Genode; using Name = String<32>; struct File_system; class Local_factory; class Subject; struct Subject_factory; class Trace_buffer_file_system; } class Vfs_trace::Trace_buffer_file_system : public Single_file_system { private: /** * Trace_buffer wrapper */ struct Trace_entries { Vfs::Env &_env; Constructible _buffer { }; Trace_entries(Vfs::Env &env) : _env(env) { } void setup(Dataspace_capability ds) { _buffer.construct(*((Trace::Buffer *)_env.env().rm().attach(ds))); } void flush() { if (!_buffer.constructed()) return; _env.env().rm().detach(_buffer->address()); _buffer.destruct(); } template void for_each_new_entry(FUNC && functor, bool update = true) { if (!_buffer.constructed()) return; _buffer->for_each_new_entry(functor, update); } }; enum State { OFF, TRACE, PAUSED } _state { OFF }; Vfs::Env &_env; Trace::Connection &_trace; Trace::Policy_id _policy; Trace::Subject_id _id; size_t _buffer_size { 1024 * 1024 }; size_t _stat_size { 0 }; Trace_entries _entries { _env }; typedef String<32> Config; static Config _config() { char buf[Config::capacity()] { }; Xml_generator xml(buf, sizeof(buf), type_name(), [&] () { }); return Config(Cstring(buf)); } void _setup_and_trace() { _entries.flush(); try { _trace.trace(_id, _policy, _buffer_size); } catch (...) { error("failed to start tracing"); return; } _entries.setup(_trace.buffer(_id)); } public: struct Vfs_handle : Single_vfs_handle { Trace_entries &_entries; Vfs_handle(Directory_service &ds, File_io_service &fs, Genode::Allocator &alloc, Trace_entries &entries) : Single_vfs_handle(ds, fs, alloc, 0), _entries(entries) { } Read_result read(char *dst, file_size count, file_size &out_count) override { out_count = 0; _entries.for_each_new_entry([&](Trace::Buffer::Entry entry) { file_size size = min(count - out_count, entry.length()); memcpy(dst + out_count, entry.data(), size); out_count += size; if (out_count == count) return false; return true; }); return READ_OK; } Write_result write(char const *, file_size, file_size &out_count) override { out_count = 0; return WRITE_ERR_INVALID; } bool read_ready() override { return true; } }; Trace_buffer_file_system(Vfs::Env &env, Trace::Connection &trace, Trace::Policy_id policy, Trace::Subject_id id) : Single_file_system(Node_type::TRANSACTIONAL_FILE, type_name(), Node_rwx::rw(), Xml_node(_config().string())), _env(env), _trace(trace), _policy(policy), _id(id) { } static char const *type_name() { return "trace_buffer"; } char const *type() override { return type_name(); } /*************************** ** File-system interface ** ***************************/ Open_result open(char const *path, unsigned, Vfs::Vfs_handle **out_handle, Allocator &alloc) override { if (!_single_file(path)) return OPEN_ERR_UNACCESSIBLE; *out_handle = new (alloc) Vfs_handle(*this, *this, alloc, _entries); return OPEN_OK; } Stat_result stat(char const *path, Stat &out) override { Stat_result res = Single_file_system::stat(path, out); if (res != STAT_OK) return res; /* update file size */ if (_state == TRACE) _entries.for_each_new_entry([&](Trace::Buffer::Entry entry) { _stat_size += entry.length(); return true; }, false); out.size = _stat_size; return res; } /*********************** ** FS event handlers ** ***********************/ bool resize_buffer(size_t size) { if (size == 0) return false; _buffer_size = size; switch (_state) { case TRACE: _trace.pause(_id); _setup_and_trace(); break; case PAUSED: _state = OFF; break; case OFF: break; } return true; } void trace(bool enable) { if (enable) { switch (_state) { case TRACE: break; case OFF: _setup_and_trace(); break; case PAUSED: _trace.resume(_id); break; } _state = TRACE; } else { switch (_state) { case OFF: return; case PAUSED: return; case TRACE: _trace.pause(_id); _state = PAUSED; return; } } } }; struct Vfs_trace::Subject_factory : File_system_factory { Vfs::Env &_env; Value_file_system _enabled_fs { "enable", "false\n"}; Value_file_system _buffer_size_fs { "buffer_size", "1M\n"}; String<17> _buffer_string { _buffer_size_fs.buffer() }; Trace_buffer_file_system _trace_fs; Subject_factory(Vfs::Env &env, Trace::Connection &trace, Trace::Policy_id policy, Trace::Subject_id id) : _env(env), _trace_fs(env, trace, policy, id) { } Vfs::File_system *create(Vfs::Env &, Xml_node node) override { if (node.has_type(Value_file_system::type_name())) { if (_enabled_fs.matches(node)) return &_enabled_fs; if (_buffer_size_fs.matches(node)) return &_buffer_size_fs; } if (node.has_type(Trace_buffer_file_system::type_name())) return &_trace_fs; return nullptr; } }; class Vfs_trace::Subject : private Subject_factory, public Vfs::Dir_file_system { private: typedef String<200> Config; Watch_handler _enable_handler { _enabled_fs, "/enable", Subject_factory::_env.alloc(), *this, &Subject::_enable_subject }; Watch_handler _buffer_size_handler { _buffer_size_fs, "/buffer_size", Subject_factory::_env.alloc(), *this, &Subject::_buffer_size }; static Config _config(Xml_node node) { char buf[Config::capacity()] { }; Xml_generator xml(buf, sizeof(buf), "dir", [&] () { xml.attribute("name", node.attribute_value("name", Vfs_trace::Name())); xml.node("value", [&] () { xml.attribute("name", "enable"); }); xml.node("value", [&] () { xml.attribute("name", "buffer_size"); }); xml.node(Trace_buffer_file_system::type_name(), [&] () {}); }); return Config(Cstring(buf)); } /******************** ** Watch handlers ** ********************/ void _enable_subject() { _enabled_fs.value(_enabled_fs.value() ? "true\n" : "false\n"); _trace_fs.trace(_enabled_fs.value()); } void _buffer_size() { Number_of_bytes size = _buffer_size_fs.value(); if (_trace_fs.resize_buffer(size) == false) { /* restore old value */ _buffer_size_fs.value(_buffer_string); return; } _buffer_string = _buffer_size_fs.buffer(); } public: Subject(Vfs::Env &env, Trace::Connection &trace, Trace::Policy_id policy, Xml_node node) : Subject_factory(env, trace, policy, node.attribute_value("id", 0u)), Dir_file_system(env, Xml_node(_config(node).string()), *this) { } static char const *type_name() { return "trace_node"; } char const *type() override { return type_name(); } }; struct Vfs_trace::Local_factory : File_system_factory { Vfs::Env &_env; Trace::Connection _trace; enum { MAX_SUBJECTS = 128 }; Trace::Subject_id _subjects[MAX_SUBJECTS]; unsigned _subject_count { 0 }; Trace::Policy_id _policy_id { 0 }; Directory_tree _tree { _env.alloc() }; void _install_null_policy() { using namespace Genode; Constructible null_policy; try { null_policy.construct(_env.env(), "null"); _policy_id = _trace.alloc_policy(null_policy->size()); } catch (Out_of_caps) { throw; } catch (Out_of_ram) { throw; } catch (...) { error("failed to attach 'null' trace policy." "Please make sure it is provided as a ROM module."); throw; } /* copy policy into trace session */ void *dst = _env.env().rm().attach(_trace.policy(_policy_id)); memcpy(dst, null_policy->local_addr(), null_policy->size()); _env.env().rm().detach(dst); } size_t _config_session_ram(Xml_node config) { try { Genode::Number_of_bytes ram; config.attribute("ram").value(&ram); return ram; } catch (...) { Genode::error("mandatory 'ram' attribute missing"); throw Genode::Exception(); } } Local_factory(Vfs::Env &env, Xml_node config) : _env(env), _trace(env.env(), _config_session_ram(config), 512*1024, 0) { bool success = false; while (!success) { try { _subject_count = _trace.subjects(_subjects, MAX_SUBJECTS); success = true; } catch(Genode::Out_of_ram) { _trace.upgrade_ram(4096); success = false; } } for (unsigned i = 0; i < _subject_count; i++) { _tree.insert(_trace.subject_info(_subjects[i]), _subjects[i]); } _install_null_policy(); } Vfs::File_system *create(Vfs::Env&, Xml_node node) override { if (node.has_type(Subject::type_name())) return new (_env.alloc()) Subject(_env, _trace, _policy_id, node); return nullptr; } }; class Vfs_trace::File_system : private Local_factory, public Vfs::Dir_file_system { private: typedef String<512*1024> Config; static char const *_config(Vfs::Env &vfs_env, Directory_tree &tree) { char *buf = (char *)vfs_env.alloc().alloc(Config::capacity()); Xml_generator xml(buf, Config::capacity(), "node", [&] () { tree.xml(xml); }); return buf; } public: File_system(Vfs::Env &vfs_env, Genode::Xml_node node) : Local_factory(vfs_env, node), Vfs::Dir_file_system(vfs_env, Xml_node(_config(vfs_env, _tree)), *this) { } char const *type() override { return "trace"; } }; /************************** ** VFS plugin interface ** **************************/ extern "C" Vfs::File_system_factory *vfs_file_system_factory(void) { struct Factory : Vfs::File_system_factory { Vfs::File_system *create(Vfs::Env &vfs_env, Genode::Xml_node node) override { try { return new (vfs_env.alloc()) Vfs_trace::File_system(vfs_env, node); } catch (...) { Genode::error("could not create 'trace_fs' "); } return nullptr; } }; static Factory factory; return &factory; }