diff --git a/repos/gems/include/gems/vfs.h b/repos/gems/include/gems/vfs.h new file mode 100644 index 0000000000..3277be04b2 --- /dev/null +++ b/repos/gems/include/gems/vfs.h @@ -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 +#include +#include +#include + +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 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(_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 + 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 + * '' node as argument. + */ + template + void xml(FN const &fn) const + { + try { fn(Xml_node(_buffer, _size)); } + catch (Xml_node::Invalid_syntax) { fn(Xml_node("")); } + } + + /** + * Call functor 'fn' with each line of the file as argument + * + * \param STRING string type used for the line + */ + template + 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_ */ diff --git a/repos/gems/run/depot_query.run b/repos/gems/run/depot_query.run new file mode 100644 index 0000000000..18ec2be7b4 --- /dev/null +++ b/repos/gems/run/depot_query.run @@ -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 { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} + +build_boot_image { depot_query depot_deploy } + +run_genode_until {.*child "test-fs_report" exited with exit value 0.*\n} 30 + diff --git a/repos/gems/src/app/depot_deploy/main.cc b/repos/gems/src/app/depot_deploy/main.cc new file mode 100644 index 0000000000..9b60fa9f2b --- /dev/null +++ b/repos/gems/src/app/depot_deploy/main.cc @@ -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 +#include +#include + +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
_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 '' node. + */ + Name const name = pkg.attribute_value("name", Name()); + + if (!pkg.has_sub_node("runtime")) { + warning(" node for '", name, "' lacks node"); + return; + } + + Xml_node const runtime = pkg.sub_node("runtime"); + + if (!runtime.has_sub_node("binary")) { + warning(" node for '", name, "' lacks 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 '' 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); } + diff --git a/repos/gems/src/app/depot_deploy/target.mk b/repos/gems/src/app/depot_deploy/target.mk new file mode 100644 index 0000000000..ce426d3220 --- /dev/null +++ b/repos/gems/src/app/depot_deploy/target.mk @@ -0,0 +1,3 @@ +TARGET = depot_deploy +SRC_CC = main.cc +LIBS += base vfs diff --git a/repos/gems/src/app/depot_query/main.cc b/repos/gems/src/app/depot_query/main.cc new file mode 100644 index 0000000000..c43b69cf41 --- /dev/null +++ b/repos/gems/src/app/depot_query/main.cc @@ -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 +#include +#include +#include +#include + +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 + 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(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(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(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
_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 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") : ""; + + 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\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); } + diff --git a/repos/gems/src/app/depot_query/target.mk b/repos/gems/src/app/depot_query/target.mk new file mode 100644 index 0000000000..d2129486f8 --- /dev/null +++ b/repos/gems/src/app/depot_query/target.mk @@ -0,0 +1,3 @@ +TARGET = depot_query +SRC_CC = main.cc +LIBS += base vfs diff --git a/repos/os/recipes/pkg/test-fs_report/runtime b/repos/os/recipes/pkg/test-fs_report/runtime index 1ae13f8032..9da707c1c7 100644 --- a/repos/os/recipes/pkg/test-fs_report/runtime +++ b/repos/os/recipes/pkg/test-fs_report/runtime @@ -4,11 +4,11 @@ - - - - - + + + + + @@ -16,7 +16,7 @@ -