genode/repos/gems/src/lib/vfs/trace/vfs.cc

475 lines
11 KiB
C++

/*
* \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 <base/attached_rom_dataspace.h>
#include <vfs/dir_file_system.h>
#include <vfs/single_file_system.h>
#include <gems/vfs.h>
#include <util/xml_generator.h>
#include <trace_session/connection.h>
#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<Trace_buffer> _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 <typename FUNC>
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<bool, 6> _enabled_fs
{ "enable", "false\n"};
Value_file_system<Number_of_bytes, 16> _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<unsigned>::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<Subject> _enable_handler {
_enabled_fs, "/enable",
Subject_factory::_env.alloc(),
*this, &Subject::_enable_subject };
Watch_handler<Subject> _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<Attached_rom_dataspace> 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<void*>(), 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;
}