gems: app/depot_query

This commit is contained in:
Norman Feske 2017-07-05 10:40:29 +02:00 committed by Christian Helmuth
parent c8b2222485
commit 8312950e2f
7 changed files with 949 additions and 6 deletions

View File

@ -0,0 +1,345 @@
/*
* \brief Front-end API for accessing a component-local virtual file system
* \author Norman Feske
* \date 2017-07-04
*/
/*
* Copyright (C) 2017 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.
*/
#ifndef _INCLUDE__GEMS__VFS_H_
#define _INCLUDE__GEMS__VFS_H_
/* Genode includes */
#include <base/env.h>
#include <base/allocator.h>
#include <vfs/dir_file_system.h>
#include <vfs/file_system_factory.h>
namespace Genode {
struct Directory;
struct Root_directory;
struct File;
struct Readonly_file;
struct File_content;
}
struct Genode::Directory : Noncopyable
{
public:
struct Open_failed : Exception { };
struct Read_dir_failed : Exception { };
class Entry
{
private:
Vfs::Directory_service::Dirent _dirent;
friend class Directory;
Entry() { }
public:
void print(Output &out) const
{
using Genode::print;
using Vfs::Directory_service;
print(out, _dirent.name, " (");
switch (_dirent.type) {
case Directory_service::DIRENT_TYPE_FILE: print(out, "file"); break;
case Directory_service::DIRENT_TYPE_DIRECTORY: print(out, "dir"); break;
case Directory_service::DIRENT_TYPE_SYMLINK: print(out, "symlink"); break;
default: print(out, "other"); break;
}
print(out, ")");
}
typedef String<Vfs::Directory_service::DIRENT_MAX_NAME_LEN> Name;
Name name() const { return Name(Cstring(_dirent.name)); }
};
typedef String<256> Path;
private:
Path const _path;
Vfs::File_system &_fs;
Allocator &_alloc;
friend class Readonly_file;
friend class Root_directory;
/**
* Constructor used by 'Root_directory'
*
* \throw Open_failed
*/
Directory(Vfs::File_system &fs, Allocator &alloc, Path const &path)
: _path(""), _fs(fs), _alloc(alloc)
{ }
/*
* Operations such as 'file_size' that are expected to be 'const' at
* the API level, do internally require I/O with the outside world,
* with involves non-const access to the VFS. This helper allows a
* 'const' method to perform I/O at the VFS.
*/
Vfs::File_system &_nonconst_fs() const
{
return const_cast<Vfs::File_system &>(_fs);
}
Vfs::Directory_service::Stat _stat(Path const &rel_path) const
{
Vfs::Directory_service::Stat stat;
/*
* Ignore return value as the validity of the result is can be
* checked by the caller via 'stat.mode != 0'.
*/
_nonconst_fs().stat(Path(_path, "/", rel_path).string(), stat);
return stat;
}
public:
struct Nonexistent_file : Exception { };
struct Nonexistent_directory : Exception { };
/**
* Open sub directory
*
* \throw Nonexistent_directory
*/
Directory(Directory &other, Path const &rel_path)
: _path(other._path, "/", rel_path), _fs(other._fs), _alloc(other._alloc)
{
if (!(other._stat(rel_path).mode & Vfs::Directory_service::STAT_MODE_DIRECTORY))
throw Nonexistent_directory();
}
template <typename FN>
void for_each_entry(FN const &fn)
{
for (unsigned i = 0;; i++) {
Entry entry;
Vfs::Directory_service::Dirent_result dirent_result =
_fs.dirent(_path.string(), i, entry._dirent);
if (dirent_result != Vfs::Directory_service::DIRENT_OK) {
error("could not access directory '", _path, "'");
throw Read_dir_failed();
}
if (entry._dirent.type == Vfs::Directory_service::DIRENT_TYPE_END)
return;
fn(entry);
}
}
bool file_exists(Path const &rel_path) const
{
return _stat(rel_path).mode & Vfs::Directory_service::STAT_MODE_FILE;
}
/**
* Return size of file at specified directory-relative path
*
* \throw Nonexistent_file file at path does not exist or
* the access to the file is denied
*
*/
Vfs::file_size file_size(Path const &rel_path) const
{
Vfs::Directory_service::Stat stat = _stat(rel_path);
if (!(stat.mode & Vfs::Directory_service::STAT_MODE_FILE))
throw Nonexistent_file();
return stat.size;
}
};
struct Genode::Root_directory : public Vfs::Io_response_handler,
private Vfs::Global_file_system_factory,
private Vfs::Dir_file_system,
public Directory
{
void handle_io_response(Vfs::Vfs_handle::Context*) override { }
Root_directory(Env &env, Allocator &alloc, Xml_node config)
:
Vfs::Global_file_system_factory(alloc),
Vfs::Dir_file_system(env, alloc, config, *this, *this),
Directory(*this, alloc, "/")
{ }
void apply_config(Xml_node config) { Vfs::Dir_file_system::apply_config(config); }
};
struct Genode::File : Noncopyable
{
struct Open_failed : Exception { };
struct Truncated_during_read : Exception { };
typedef Directory::Path Path;
};
class Genode::Readonly_file : public File
{
private:
Vfs::Vfs_handle *_handle = nullptr;
void _open(Vfs::File_system &fs, Allocator &alloc, Path const path)
{
Vfs::Directory_service::Open_result res =
fs.open(path.string(), Vfs::Directory_service::OPEN_MODE_RDONLY,
&_handle, alloc);
if (res != Vfs::Directory_service::OPEN_OK) {
error("failed to open file '", path, "'");
throw Open_failed();
}
}
public:
/**
* Constructor
*
* \throw File::Open_failed
*/
Readonly_file(Directory &dir, Path const &rel_path)
{
_open(dir._fs, dir._alloc, Path(dir._path, "/", rel_path));
}
~Readonly_file() { _handle->ds().close(_handle); }
/**
* Read number of 'bytes' from file into local memory buffer 'dst'
*
* \throw Truncated_during_read
*/
size_t read(char *dst, size_t bytes)
{
Vfs::file_size out_count = 0;
Vfs::File_io_service::Read_result const result =
_handle->fs().read(_handle, dst, bytes, out_count);
/*
* XXX handle READ_ERR_AGAIN, READ_ERR_WOULD_BLOCK, READ_QUEUED
*/
if (result != Vfs::File_io_service::READ_OK)
throw Truncated_during_read();
return out_count;
}
};
class Genode::File_content
{
private:
Allocator &_alloc;
size_t const _size;
char *_buffer = (char *)_alloc.alloc(_size);
public:
typedef Directory::Nonexistent_file Nonexistent_file;
typedef File::Truncated_during_read Truncated_during_read;
typedef Directory::Path Path;
struct Limit { size_t value; };
/**
* Constructor
*
* \throw Nonexistent_file
* \throw Truncated_during_read number of readable bytes differs
* from file status information
*/
File_content(Allocator &alloc, Directory &dir, Path const &rel_path,
Limit limit)
:
_alloc(alloc),
_size(min(dir.file_size(rel_path), (Vfs::file_size)limit.value))
{
if (Readonly_file(dir, rel_path).read(_buffer, _size) != _size)
throw Truncated_during_read();
}
~File_content() { _alloc.free(_buffer, _size); }
/**
* Call functor 'fn' with content as 'Xml_node' argument
*
* If the file does not contain valid XML, 'fn' is called with an
* '<empty/>' node as argument.
*/
template <typename FN>
void xml(FN const &fn) const
{
try { fn(Xml_node(_buffer, _size)); }
catch (Xml_node::Invalid_syntax) { fn(Xml_node("<empty/>")); }
}
/**
* Call functor 'fn' with each line of the file as argument
*
* \param STRING string type used for the line
*/
template <typename STRING, typename FN>
void for_each_line(FN const &fn) const
{
char const *src = _buffer;
char const *curr_line = src;
size_t curr_line_len = 0;
for (size_t n = 0; n < _size; n++) {
char const c = *src++;
bool const end_of_data = (c == 0 || n + 1 == _size);
bool const end_of_line = (c == '\n');
if (!end_of_data && !end_of_line) {
curr_line_len++;
continue;
}
fn(STRING(Cstring(curr_line, curr_line_len)));
if (end_of_data)
return;
curr_line = src;
curr_line_len = 0;
}
}
};
#endif /* _INCLUDE__GEMS__VFS_H_ */

View File

@ -0,0 +1,127 @@
build { app/depot_query app/depot_deploy }
create_boot_directory
import_from_depot genodelabs/src/[base_src] \
genodelabs/src/report_rom \
genodelabs/src/fs_rom \
genodelabs/src/vfs \
genodelabs/src/init
create_tar_from_depot_binaries [run_dir]/genode/depot.tar \
genodelabs/pkg/test-fs_report
proc query_pkg {} {
return [_versioned_depot_archive_name genodelabs pkg test-fs_report] }
install_config {
<config>
<parent-provides>
<service name="ROM"/>
<service name="IRQ"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
<service name="PD"/>
<service name="RM"/>
<service name="CPU"/>
<service name="LOG"/>
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<default caps="100"/>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Timer"/> </provides>
</start>
<start name="report_rom">
<binary name="report_rom"/>
<resource name="RAM" quantum="1M"/>
<provides> <service name="Report"/> <service name="ROM"/> </provides>
<config verbose="yes">
<policy label="depot_deploy -> blueprint" report="depot_query -> blueprint"/>
<policy label="subinit -> config" report="depot_deploy -> init.config"/>
</config>
</start>
<start name="vfs">
<resource name="RAM" quantum="4M"/>
<provides> <service name="File_system"/> </provides>
<config>
<vfs> <tar name="depot.tar"/> </vfs>
<policy label="depot_query -> depot" root="/" />
<policy label="fs_rom -> " root="/" />
</config>
</start>
<start name="fs_rom">
<resource name="RAM" quantum="4M"/>
<provides> <service name="ROM"/> </provides>
</start>
<start name="depot_query">
<resource name="RAM" quantum="1M"/>
<config arch="} [depot_spec] {">
<vfs> <dir name="depot"> <fs label="depot"/> </dir> </vfs>
<env>
<rom label="ld.lib.so"/>
<rom label="init"/>
</env>
<scan user="genodelabs"/>
<query pkg="genodelabs/pkg/} [query_pkg] {"/>
</config>
</start>
<start name="depot_deploy">
<resource name="RAM" quantum="1M"/>
<config>
<static>
<parent-provides>
<service name="ROM"/>
<service name="CPU"/>
<service name="PD"/>
<service name="LOG"/>
<service name="Timer"/>
</parent-provides>
</static>
<common_routes>
<service name="ROM" unscoped_label="init"> <parent/> </service>
<service name="ROM" unscoped_label="ld.lib.so"> <parent/> </service>
<service name="ROM" label_suffix="init"> <parent/> </service>
<service name="ROM" label_suffix="ld.lib.so"> <parent/> </service>
<service name="CPU"> <parent/> </service>
<service name="PD"> <parent/> </service>
<service name="LOG"> <parent/> </service>
<service name="Timer"> <parent/> </service>
</common_routes>
</config>
<route>
<service name="ROM" label="blueprint"> <child name="report_rom"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="subinit" caps="8000">
<resource name="RAM" quantum="64M"/>
<binary name="init"/>
<route>
<service name="ROM" unscoped_label="ld.lib.so"> <parent/> </service>
<service name="ROM" unscoped_label="init"> <parent/> </service>
<service name="ROM" label_suffix="ld.lib.so"> <parent/> </service>
<service name="ROM" label_suffix="init"> <parent/> </service>
<service name="ROM" label="config"> <child name="report_rom"/> </service>
<service name="ROM"> <child name="fs_rom"/> </service>
<service name="Timer"> <child name="timer"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
</config>}
build_boot_image { depot_query depot_deploy }
run_genode_until {.*child "test-fs_report" exited with exit value 0.*\n} 30

View File

@ -0,0 +1,164 @@
/*
* \brief Tool for turning a subsystem blueprint into an init configuration
* \author Norman Feske
* \date 2017-07-07
*/
/*
* Copyright (C) 2017 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.
*/
/* Genode includes */
#include <base/component.h>
#include <base/attached_rom_dataspace.h>
#include <os/reporter.h>
namespace Depot_deploy {
using namespace Genode;
struct Main;
}
struct Depot_deploy::Main
{
Env &_env;
Attached_rom_dataspace _config { _env, "config" };
Attached_rom_dataspace _blueprint { _env, "blueprint" };
Reporter _init_config_reporter { _env, "config", "init.config" };
Signal_handler<Main> _config_handler {
_env.ep(), *this, &Main::_handle_config };
typedef String<128> Name;
typedef String<80> Binary;
/**
* Generate start node of init configuration
*
* \param pkg pkg node of the subsystem blueprint
* \param common session routes to be added in addition to the ones
* found in the pkg blueprint
*/
static void _gen_start_node(Xml_generator &, Xml_node pkg, Xml_node common);
void _handle_config()
{
_config.update();
_blueprint.update();
Xml_node const config = _config.xml();
Xml_node const blueprint = _blueprint.xml();
Reporter::Xml_generator xml(_init_config_reporter, [&] () {
Xml_node static_config = config.sub_node("static");
xml.append(static_config.content_base(), static_config.content_size());
blueprint.for_each_sub_node("pkg", [&] (Xml_node pkg) {
/*
* Check preconditions for generating a '<start>' node.
*/
Name const name = pkg.attribute_value("name", Name());
if (!pkg.has_sub_node("runtime")) {
warning("<pkg> node for '", name, "' lacks <runtime> node");
return;
}
Xml_node const runtime = pkg.sub_node("runtime");
if (!runtime.has_sub_node("binary")) {
warning("<runtime> node for '", name, "' lacks <binary> node");
return;
}
xml.node("start", [&] () {
_gen_start_node(xml, pkg, config.sub_node("common_routes"));
});
});
});
}
Main(Env &env) : _env(env)
{
_init_config_reporter.enabled(true);
_config .sigh(_config_handler);
_blueprint.sigh(_config_handler);
_handle_config();
}
};
void Depot_deploy::Main::_gen_start_node(Xml_generator &xml, Xml_node pkg, Xml_node common)
{
typedef String<80> Name;
Name const name = pkg.attribute_value("name", Name());
Xml_node const runtime = pkg.sub_node("runtime");
size_t const caps = runtime.attribute_value("caps", 0UL);
Number_of_bytes const ram = runtime.attribute_value("ram", Number_of_bytes());
Binary const binary = runtime.sub_node("binary").attribute_value("name", Binary());
xml.attribute("name", name);
xml.attribute("caps", caps);
xml.node("binary", [&] () { xml.attribute("name", binary); });
xml.node("resource", [&] () {
xml.attribute("name", "RAM");
xml.attribute("quantum", String<32>(ram));
});
/*
* Insert inline '<config>' node if provided by the blueprint.
*/
if (runtime.has_sub_node("config")) {
Xml_node config = runtime.sub_node("config");
xml.node("config", [&] () {
xml.append(config.content_base(), config.content_size());
});
};
xml.node("route", [&] () {
/*
* Add ROM routing rule with the label rewritten to
* the path within the depot.
*/
pkg.for_each_sub_node("rom", [&] (Xml_node rom) {
if (!rom.has_attribute("path"))
return;
typedef String<160> Path;
typedef Name Label;
Path const path = rom.attribute_value("path", Path());
Label const label = rom.attribute_value("label", Label());
xml.node("service", [&] () {
xml.attribute("name", "ROM");
xml.attribute("label", label);
xml.node("parent", [&] () {
xml.attribute("label", path);
});
});
});
/*
* Add common routes as defined in our config.
*/
xml.append(common.content_base(), common.content_size());
});
}
void Component::construct(Genode::Env &env) { static Depot_deploy::Main main(env); }

View File

@ -0,0 +1,3 @@
TARGET = depot_deploy
SRC_CC = main.cc
LIBS += base vfs

View File

@ -0,0 +1,301 @@
/*
* \brief Tool for querying subsystem information from a depot
* \author Norman Feske
* \date 2017-07-04
*/
/*
* Copyright (C) 2017 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.
*/
/* Genode includes */
#include <base/component.h>
#include <base/heap.h>
#include <base/attached_rom_dataspace.h>
#include <os/reporter.h>
#include <gems/vfs.h>
namespace Depot_query {
using namespace Genode;
struct Archive;
struct Main;
}
struct Depot_query::Archive
{
typedef String<100> Path;
typedef String<64> User;
typedef String<80> Name;
enum Type { PKG, RAW, SRC };
struct Unknown_archive_type : Exception { };
/**
* Return Nth path element
*
* The first path element corresponds to n == 0.
*/
template <typename STRING>
static STRING _path_element(Path const &path, unsigned n)
{
char const *s = path.string();
/* skip 'n' path elements */
for (; n > 0; n--) {
/* search '/' */
while (*s && *s != '/')
s++;
if (*s == 0)
return STRING();
/* skip '/' */
s++;
}
/* find '/' marking the end of the path element */
unsigned i = 0;
while (s[i] != 0 && s[i] != '/')
i++;
return STRING(Cstring(s, i));
}
/**
* Return archive user of depot-local path
*/
static User user(Path const &path) { return _path_element<User>(path, 0); }
/**
* Return archive type of depot-local path
*
* \throw Unknown_archive_type
*/
static Type type(Path const &path)
{
typedef String<8> Name;
Name const name = _path_element<Name>(path, 1);
if (name == "src") return SRC;
if (name == "pkg") return PKG;
if (name == "raw") return RAW;
throw Unknown_archive_type();
}
static Name name(Path const &path) { return _path_element<Name>(path, 2); }
};
struct Depot_query::Main
{
Env &_env;
Heap _heap { _env.ram(), _env.rm() };
Attached_rom_dataspace _config { _env, "config" };
Root_directory _root { _env, _heap, _config.xml().sub_node("vfs") };
Signal_handler<Main> _config_handler {
_env.ep(), *this, &Main::_handle_config };
Reporter _directory_reporter { _env, "directory" };
Reporter _blueprint_reporter { _env, "blueprint" };
typedef String<64> Rom_label;
typedef String<16> Architecture;
Architecture _architecture;
Archive::Path _find_rom_in_pkg(Directory::Path const &pkg_path,
Rom_label const &rom_label,
unsigned const nesting_level);
void _scan_depot_user_pkg(Archive::User const &user, Directory &dir, Xml_generator &xml);
void _query_pkg(Directory::Path const &path, Xml_generator &xml);
void _handle_config()
{
_config.update();
Xml_node config = _config.xml();
_directory_reporter.enabled(config.has_sub_node("scan"));
_blueprint_reporter.enabled(config.has_sub_node("query"));
_root.apply_config(config.sub_node("vfs"));
if (!config.has_attribute("arch"))
warning("config lacks 'arch' attribute");
_architecture = config.attribute_value("arch", Architecture());
if (_directory_reporter.enabled()) {
Reporter::Xml_generator xml(_directory_reporter, [&] () {
config.for_each_sub_node("scan", [&] (Xml_node node) {
Archive::User const user = node.attribute_value("user", Archive::User());
Directory::Path path("depot/", user, "/pkg");
Directory pkg_dir(_root, path);
_scan_depot_user_pkg(user, pkg_dir, xml);
});
});
}
if (_blueprint_reporter.enabled()) {
Reporter::Xml_generator xml(_blueprint_reporter, [&] () {
config.for_each_sub_node("query", [&] (Xml_node node) {
_query_pkg(node.attribute_value("pkg", Directory::Path()), xml); });
});
}
}
Main(Env &env) : _env(env) { _handle_config(); }
};
void Depot_query::Main::_scan_depot_user_pkg(Archive::User const &user,
Directory &dir, Xml_generator &xml)
{
dir.for_each_entry([&] (Directory::Entry &entry) {
if (!dir.file_exists(Directory::Path(entry.name(), "/runtime")))
return;
Archive::Path const path(user, "/pkg/", entry.name());
xml.node("pkg", [&] () { xml.attribute("path", path); });
});
}
Depot_query::Archive::Path
Depot_query::Main::_find_rom_in_pkg(Directory::Path const &pkg_path,
Rom_label const &rom_label,
unsigned const nesting_level)
{
if (nesting_level == 0) {
error("too deeply nested pkg archives");
return Archive::Path();
}
/*
* \throw Directory::Nonexistent_directory
*/
Directory depot_dir(_root, Directory::Path("depot"));
Directory pkg_dir(depot_dir, pkg_path);
/*
* \throw Directory::Nonexistent_file
* \throw File::Truncated_during_read
*/
File_content archives(_heap, pkg_dir, "archives", File_content::Limit{16*1024});
Archive::Path result;
archives.for_each_line<Archive::Path>([&] (Archive::Path const &archive_path) {
/*
* \throw Archive::Unknown_archive_type
*/
switch (Archive::type(archive_path)) {
case Archive::SRC:
{
Archive::Path const
rom_path(Archive::user(archive_path), "/bin/",
_architecture, "/",
Archive::name(archive_path), "/", rom_label);
if (depot_dir.file_exists(rom_path))
result = rom_path;
}
break;
case Archive::RAW:
log(" ", archive_path, " (raw-data archive)");
break;
case Archive::PKG:
// XXX call recursively, adjust 'nesting_level'
log(" ", archive_path, " (pkg archive)");
break;
}
});
return result;
}
void Depot_query::Main::_query_pkg(Directory::Path const &pkg_path, Xml_generator &xml)
{
Directory pkg_dir(_root, Directory::Path("depot/", pkg_path));
File_content runtime(_heap, pkg_dir, "runtime", File_content::Limit{16*1024});
runtime.xml([&] (Xml_node node) {
xml.node("pkg", [&] () {
xml.attribute("name", Archive::name(pkg_path));
xml.attribute("path", pkg_path);
Xml_node env_xml = _config.xml().has_sub_node("env")
? _config.xml().sub_node("env") : "<env/>";
node.for_each_sub_node([&] (Xml_node node) {
/* skip non-rom nodes */
if (!node.has_type("rom") && !node.has_type("binary"))
return;
Rom_label const label = node.attribute_value("label", Rom_label());
/* skip ROM that is provided by the environment */
bool provided_by_env = false;
env_xml.for_each_sub_node("rom", [&] (Xml_node node) {
if (node.attribute_value("label", Rom_label()) == label)
provided_by_env = true; });
if (provided_by_env) {
xml.node("rom", [&] () {
xml.attribute("label", label);
xml.attribute("env", "yes");
});
return;
}
unsigned const max_nesting_levels = 8;
Archive::Path const rom_path =
_find_rom_in_pkg(pkg_path, label, max_nesting_levels);
if (rom_path.valid()) {
xml.node("rom", [&] () {
xml.attribute("label", label);
xml.attribute("path", rom_path);
});
} else {
xml.node("missing_rom", [&] () {
xml.attribute("label", label); });
}
});
String<160> comment("\n\n<!-- content of '", pkg_path, "/runtime' -->\n");
xml.append(comment.string());
xml.append(node.addr(), node.size());
xml.append("\n");
});
});
}
void Component::construct(Genode::Env &env) { static Depot_query::Main main(env); }

View File

@ -0,0 +1,3 @@
TARGET = depot_query
SRC_CC = main.cc
LIBS += base vfs

View File

@ -4,11 +4,11 @@
<binary name="init"/>
<rom name="ld.lib.so"/>
<rom name="ram_fs"/>
<rom name="fs_report"/>
<rom name="fs_rom"/>
<rom name="test-fs_report"/>
<rom label="ld.lib.so"/>
<rom label="ram_fs"/>
<rom label="fs_report"/>
<rom label="fs_rom"/>
<rom label="test-fs_report"/>
<config>
<parent-provides>
@ -16,7 +16,7 @@
<service name="LOG"/>
<service name="PD"/>
<service name="ROM"/>
<service name="Timer"/
<service name="Timer"/>
</parent-provides>
<default-route>