diff --git a/repos/gems/recipes/pkg/depot_download/README b/repos/gems/recipes/pkg/depot_download/README new file mode 100644 index 0000000000..d7a1efd8e5 --- /dev/null +++ b/repos/gems/recipes/pkg/depot_download/README @@ -0,0 +1,2 @@ + + Subsystem for downloading depot content diff --git a/repos/gems/recipes/pkg/depot_download/archives b/repos/gems/recipes/pkg/depot_download/archives new file mode 100644 index 0000000000..c1e4c50370 --- /dev/null +++ b/repos/gems/recipes/pkg/depot_download/archives @@ -0,0 +1,18 @@ +_/src/depot_download_manager +_/src/depot_query +_/src/verify +_/src/extract +_/src/report_rom +_/src/fs_rom +_/src/fetchurl +_/src/libc +_/src/libssh +_/src/libssl +_/src/libcrypto +_/src/zlib +_/src/curl +_/src/init +_/src/chroot +_/src/libarchive +_/src/liblzma +_/raw/depot_download diff --git a/repos/gems/recipes/pkg/depot_download/hash b/repos/gems/recipes/pkg/depot_download/hash new file mode 100644 index 0000000000..df34736788 --- /dev/null +++ b/repos/gems/recipes/pkg/depot_download/hash @@ -0,0 +1 @@ +2018-01-18 eef2e5112de6ba055ad6e57dffdbaeae2ec4746e diff --git a/repos/gems/recipes/raw/depot_download/content.mk b/repos/gems/recipes/raw/depot_download/content.mk new file mode 100644 index 0000000000..e58e243628 --- /dev/null +++ b/repos/gems/recipes/raw/depot_download/content.mk @@ -0,0 +1,4 @@ +content: depot_download.config + +depot_download.config: + cp $(REP_DIR)/recipes/raw/depot_download/$@ $@ diff --git a/repos/gems/recipes/raw/depot_download/depot_download.config b/repos/gems/recipes/raw/depot_download/depot_download.config new file mode 100644 index 0000000000..26101efb61 --- /dev/null +++ b/repos/gems/recipes/raw/depot_download/depot_download.config @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/repos/gems/recipes/raw/depot_download/hash b/repos/gems/recipes/raw/depot_download/hash new file mode 100644 index 0000000000..88be1c6574 --- /dev/null +++ b/repos/gems/recipes/raw/depot_download/hash @@ -0,0 +1 @@ +2018-01-18 4755c0e7f19bb5c44bdbd5c1f276127c9a2a1487 diff --git a/repos/gems/recipes/src/depot_download_manager/content.mk b/repos/gems/recipes/src/depot_download_manager/content.mk new file mode 100644 index 0000000000..0055b2857d --- /dev/null +++ b/repos/gems/recipes/src/depot_download_manager/content.mk @@ -0,0 +1,10 @@ +SRC_DIR := src/app/depot_download_manager + +include $(GENODE_DIR)/repos/base/recipes/src/content.inc + +MIRROR_FROM_REP_DIR := include/depot + +content: $(MIRROR_FROM_REP_DIR) + +$(MIRROR_FROM_REP_DIR): + $(mirror_from_rep_dir) diff --git a/repos/gems/recipes/src/depot_download_manager/hash b/repos/gems/recipes/src/depot_download_manager/hash new file mode 100644 index 0000000000..8c67708779 --- /dev/null +++ b/repos/gems/recipes/src/depot_download_manager/hash @@ -0,0 +1 @@ +2018-01-18 7e032cc2fd00d9b4f885c718917cbc4bf9c46119 diff --git a/repos/gems/recipes/src/depot_download_manager/used_apis b/repos/gems/recipes/src/depot_download_manager/used_apis new file mode 100644 index 0000000000..fc19ded9d5 --- /dev/null +++ b/repos/gems/recipes/src/depot_download_manager/used_apis @@ -0,0 +1,6 @@ +base +os +report_session +file_system_session +nic_session +timer_session diff --git a/repos/gems/run/depot_download.run b/repos/gems/run/depot_download.run new file mode 100644 index 0000000000..5c89642f4d --- /dev/null +++ b/repos/gems/run/depot_download.run @@ -0,0 +1,129 @@ +create_boot_directory + +import_from_depot genodelabs/src/[base_src] \ + genodelabs/src/report_rom \ + genodelabs/src/fs_rom \ + genodelabs/src/vfs \ + genodelabs/src/fetchurl \ + genodelabs/src/libc \ + genodelabs/src/libssh \ + genodelabs/src/libssl \ + genodelabs/src/libcrypto \ + genodelabs/src/zlib \ + genodelabs/src/curl \ + genodelabs/src/init \ + genodelabs/src/chroot \ + genodelabs/src/acpi_drv \ + genodelabs/src/ipxe_nic_drv \ + genodelabs/src/platform_drv \ + genodelabs/src/extract \ + genodelabs/src/libarchive \ + genodelabs/src/liblzma \ + genodelabs/src/verify + +source ${genode_dir}/repos/base/run/platform_drv.inc + +set config { + + + + + + + + + + + + + + + + + } + +append_platform_drv_config + +proc depot_user { } { return nfeske } + +proc depot_user_download { } { + return [exec cat [genode_dir]/depot/[depot_user]/download] } + +proc depot_user_pubkey { } { + return [exec cat [genode_dir]/depot/[depot_user]/pubkey] } + +append config { + + + + + + + + + + + + + + } [depot_user_download] { + } [depot_user_pubkey] { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} + + +set fd [open [run_dir]/genode/installation w] +puts $fd " + + +" +close $fd + + +install_config $config + +file copy -force [genode_dir]/repos/gems/recipes/raw/depot_download/depot_download.config \ + [run_dir]/genode/depot_download.config + +build { app/depot_download_manager app/depot_query } + +append boot_modules { depot_download_manager depot_query } + +build_boot_image $boot_modules + +append qemu_args " -nographic -net nic,model=e1000 -net user " + +run_genode_until forever + diff --git a/repos/gems/src/app/depot_download_manager/gen_chroot.cc b/repos/gems/src/app/depot_download_manager/gen_chroot.cc new file mode 100644 index 0000000000..c0966e44e7 --- /dev/null +++ b/repos/gems/src/app/depot_download_manager/gen_chroot.cc @@ -0,0 +1,44 @@ +/* + * \brief XML configuration for the chroot component + * \author Norman Feske + * \date 2017-12-08 + */ + +/* + * 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. + */ + +#include "xml.h" + +void Depot_download_manager::gen_chroot_start_content(Xml_generator &xml, + Archive::User const &user) +{ + gen_common_start_content(xml, Path("/depot/", user), + Cap_quota{100}, Ram_quota{1*1024*1024}); + + xml.node("binary", [&] () { xml.attribute("name", "chroot"); }); + + xml.node("config", [&] () { + xml.node("default-policy", [&] () { + xml.attribute("path", Path("/", user)); }); }); + + xml.node("provides", [&] () { + xml.node("service", [&] () { + xml.attribute("name", File_system::Session::service_name()); }); }); + + xml.node("route", [&] () { + xml.node("service", [&] () { + xml.attribute("name", File_system::Session::service_name()); + xml.node("parent", [&] () { + xml.attribute("label", "depot_rw"); }); + }); + gen_parent_unscoped_rom_route(xml, "chroot"); + gen_parent_unscoped_rom_route(xml, "ld.lib.so"); + gen_parent_route(xml); + gen_parent_route (xml); + gen_parent_route(xml); + }); +} diff --git a/repos/gems/src/app/depot_download_manager/gen_depot_query.cc b/repos/gems/src/app/depot_download_manager/gen_depot_query.cc new file mode 100644 index 0000000000..bbd37ee589 --- /dev/null +++ b/repos/gems/src/app/depot_download_manager/gen_depot_query.cc @@ -0,0 +1,61 @@ +/* + * \brief XML configuration for the depot-query tool + * \author Norman Feske + * \date 2017-12-08 + */ + +/* + * 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. + */ + +#include "xml.h" + +void Depot_download_manager::gen_depot_query_start_content(Xml_generator &xml, + Xml_node installation, + Archive::User const &next_user, + Depot_query_version version) +{ + gen_common_start_content(xml, "depot_query", + Cap_quota{100}, Ram_quota{2*1024*1024}); + + xml.node("config", [&] () { + xml.attribute("version", version.value); + typedef String<32> Arch; + xml.attribute("arch", installation.attribute_value("arch", Arch())); + xml.node("vfs", [&] () { + xml.node("dir", [&] () { + xml.attribute("name", "depot"); + xml.node("fs", [&] () { + xml.attribute("label", "depot"); }); + }); + }); + + installation.for_each_sub_node("archive", [&] (Xml_node archive) { + xml.node("dependencies", [&] () { + xml.attribute("path", archive.attribute_value("path", Archive::Path())); + xml.attribute("source", archive.attribute_value("source", true)); + xml.attribute("binary", archive.attribute_value("binary", true)); + }); + }); + + if (next_user.valid()) + xml.node("user", [&] () { xml.attribute("name", next_user); }); + }); + + xml.node("route", [&] () { + xml.node("service", [&] () { + xml.attribute("name", File_system::Session::service_name()); + xml.node("parent", [&] () { + xml.attribute("label", "depot"); }); + }); + gen_parent_unscoped_rom_route(xml, "depot_query"); + gen_parent_unscoped_rom_route(xml, "ld.lib.so"); + gen_parent_route (xml); + gen_parent_route (xml); + gen_parent_route (xml); + gen_parent_route(xml); + }); +} diff --git a/repos/gems/src/app/depot_download_manager/gen_extract.cc b/repos/gems/src/app/depot_download_manager/gen_extract.cc new file mode 100644 index 0000000000..dec22ea374 --- /dev/null +++ b/repos/gems/src/app/depot_download_manager/gen_extract.cc @@ -0,0 +1,95 @@ +/* + * \brief XML configuration for the extract tool + * \author Norman Feske + * \date 2017-12-08 + */ + +/* + * 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. + */ + +#include "xml.h" + +void Depot_download_manager::gen_extract_start_content(Xml_generator &xml, + Import const &import, + Path const &user_path, + Archive::User const &user) +{ + gen_common_start_content(xml, "extract", + Cap_quota{200}, Ram_quota{12*1024*1024}); + + xml.node("config", [&] () { + xml.attribute("verbose", "yes"); + + xml.node("libc", [&] () { + xml.attribute("stdout", "/dev/log"); + xml.attribute("stderr", "/dev/log"); + xml.attribute("rtc", "/dev/null"); + xml.attribute("cwd", user_path); + }); + + xml.node("vfs", [&] () { + xml.node("dir", [&] () { + xml.attribute("name", "public"); + xml.node("fs", [&] () { xml.attribute("label", "public"); }); + }); + xml.node("dir", [&] () { + xml.attribute("name", "depot"); + xml.node("dir", [&] () { + xml.attribute("name", user); + xml.node("fs", [&] () { + xml.attribute("label", user_path); }); + }); + }); + xml.node("dir", [&] () { + xml.attribute("name", "dev"); + xml.node("log", [&] () { }); + xml.node("null", [&] () { }); + }); + }); + + import.for_each_verified_archive([&] (Archive::Path const &path) { + + typedef String<160> Path; + typedef String<16> Ext; + + Ext const ext (".tar.xz"); + Path const tar_path ("/public/", path, ext); + Path const dst_path ("/depot/", without_last_path_element(path)); + + xml.node("extract", [&] () { + xml.attribute("archive", tar_path); + xml.attribute("to", dst_path); + }); + }); + }); + + xml.node("route", [&] () { + xml.node("service", [&] () { + xml.attribute("name", File_system::Session::service_name()); + xml.attribute("label", "public"); + xml.node("parent", [&] () { + xml.attribute("label", "public"); }); + }); + xml.node("service", [&] () { + xml.attribute("name", File_system::Session::service_name()); + xml.attribute("label", user_path); + xml.node("child", [&] () { + xml.attribute("name", user_path); }); + }); + gen_parent_unscoped_rom_route(xml, "extract"); + gen_parent_unscoped_rom_route(xml, "ld.lib.so"); + gen_parent_rom_route(xml, "libc.lib.so"); + gen_parent_rom_route(xml, "libm.lib.so"); + gen_parent_rom_route(xml, "posix.lib.so"); + gen_parent_rom_route(xml, "libarchive.lib.so"); + gen_parent_rom_route(xml, "zlib.lib.so"); + gen_parent_rom_route(xml, "liblzma.lib.so"); + gen_parent_route(xml); + gen_parent_route (xml); + gen_parent_route(xml); + }); +} diff --git a/repos/gems/src/app/depot_download_manager/gen_fetchurl.cc b/repos/gems/src/app/depot_download_manager/gen_fetchurl.cc new file mode 100644 index 0000000000..4fb9a84f40 --- /dev/null +++ b/repos/gems/src/app/depot_download_manager/gen_fetchurl.cc @@ -0,0 +1,94 @@ +/* + * \brief XML configuration for fetchurl + * \author Norman Feske + * \date 2017-12-08 + */ + +/* + * 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. + */ + +#include "xml.h" + +void Depot_download_manager::gen_fetchurl_start_content(Xml_generator &xml, + Import const &import, + Url const ¤t_user_url, + Fetchurl_version version) +{ + gen_common_start_content(xml, "fetchurl", + Cap_quota{500}, Ram_quota{8*1024*1024}); + + xml.attribute("version", version.value); + xml.node("config", [&] () { + xml.node("libc", [&] () { + xml.attribute("stdout", "/dev/log"); + xml.attribute("stderr", "/dev/log"); + xml.attribute("rtc", "/dev/rtc"); + }); + xml.node("vfs", [&] () { + xml.node("dir", [&] () { + xml.attribute("name", "download"); + xml.node("fs", [&] () { + xml.attribute("label", "download"); }); + }); + xml.node("dir", [&] () { + xml.attribute("name", "dev"); + xml.node("log", [&] () { }); + xml.node("null", [&] () { }); + xml.node("inline", [&] () { + xml.attribute("name", "rtc"); + String<64> date("2000-01-01 00:00"); + xml.append(date.string()); + }); + }); + }); + + import.for_each_download([&] (Archive::Path const &path) { + typedef String<160> Remote; + typedef String<160> Local; + typedef String<16> Ext; + + Ext const ext (".tar.xz"); + Remote const remote (current_user_url, "/", path, ext); + Local const local ("/download/", path, ext); + + xml.node("fetch", [&] () { + xml.attribute("url", remote); + xml.attribute("path", local); + }); + + xml.node("fetch", [&] () { + xml.attribute("url", Remote(remote, ".sig")); + xml.attribute("path", Local (local, ".sig")); + }); + }); + }); + + xml.node("route", [&] () { + xml.node("service", [&] () { + xml.attribute("name", File_system::Session::service_name()); + xml.attribute("label", "download"); + xml.node("parent", [&] () { + xml.attribute("label", "public_rw"); }); + }); + gen_parent_unscoped_rom_route(xml, "fetchurl"); + gen_parent_unscoped_rom_route(xml, "ld.lib.so"); + gen_parent_rom_route(xml, "libc.lib.so"); + gen_parent_rom_route(xml, "libm.lib.so"); + gen_parent_rom_route(xml, "lwip.lib.so"); + gen_parent_rom_route(xml, "curl.lib.so"); + gen_parent_rom_route(xml, "libssh.lib.so"); + gen_parent_rom_route(xml, "libssl.lib.so"); + gen_parent_rom_route(xml, "libcrypto.lib.so"); + gen_parent_rom_route(xml, "zlib.lib.so"); + gen_parent_rom_route(xml, "pthread.lib.so"); + gen_parent_route (xml); + gen_parent_route (xml); + gen_parent_route (xml); + gen_parent_route(xml); + gen_parent_route (xml); + }); +} diff --git a/repos/gems/src/app/depot_download_manager/gen_verify.cc b/repos/gems/src/app/depot_download_manager/gen_verify.cc new file mode 100644 index 0000000000..bc15d0712d --- /dev/null +++ b/repos/gems/src/app/depot_download_manager/gen_verify.cc @@ -0,0 +1,87 @@ +/* + * \brief XML configuration for the verify tool + * \author Norman Feske + * \date 2017-12-08 + */ + +/* + * 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. + */ + +#include "xml.h" + +void Depot_download_manager::gen_verify_start_content(Xml_generator &xml, + Import const &import, + Path const &user_path) +{ + gen_common_start_content(xml, "verify", + Cap_quota{200}, Ram_quota{12*1024*1024}); + + xml.node("config", [&] () { + xml.attribute("verbose", "yes"); + + xml.node("libc", [&] () { + xml.attribute("stdout", "/dev/null"); + xml.attribute("stderr", "/dev/null"); + xml.attribute("rtc", "/dev/null"); + }); + + xml.node("vfs", [&] () { + xml.node("dir", [&] () { + xml.attribute("name", "public"); + xml.node("fs", [&] () { xml.attribute("label", "public"); }); + }); + xml.node("dir", [&] () { + xml.attribute("name", "depot"); + xml.node("fs", [&] () { xml.attribute("label", "depot"); }); + }); + xml.node("dir", [&] () { + xml.attribute("name", "dev"); + xml.node("log", [&] () { }); + xml.node("null", [&] () { }); + }); + }); + + import.for_each_unverified_archive([&] (Archive::Path const &path) { + + typedef String<160> Path; + typedef String<16> Ext; + + Ext const ext (".tar.xz"); + Path const tar_path ("/public/", path, ext); + Path const pubkey_path (user_path, "/pubkey"); + + xml.node("verify", [&] () { + xml.attribute("path", tar_path); + xml.attribute("pubkey", pubkey_path); + }); + }); + }); + + xml.node("route", [&] () { + xml.node("service", [&] () { + xml.attribute("name", File_system::Session::service_name()); + xml.attribute("label", "public"); + xml.node("parent", [&] () { + xml.attribute("label", "public"); }); + }); + xml.node("service", [&] () { + xml.attribute("name", File_system::Session::service_name()); + xml.attribute("label", "depot"); + xml.node("parent", [&] () { + xml.attribute("label", "depot"); }); + }); + gen_parent_unscoped_rom_route(xml, "verify"); + gen_parent_unscoped_rom_route(xml, "ld.lib.so"); + gen_parent_rom_route(xml, "libc.lib.so"); + gen_parent_rom_route(xml, "libm.lib.so"); + gen_parent_rom_route(xml, "pthread.lib.so"); + gen_parent_route (xml); + gen_parent_route (xml); + gen_parent_route (xml); + gen_parent_route(xml); + }); +} diff --git a/repos/gems/src/app/depot_download_manager/import.h b/repos/gems/src/app/depot_download_manager/import.h new file mode 100644 index 0000000000..5a9895c192 --- /dev/null +++ b/repos/gems/src/app/depot_download_manager/import.h @@ -0,0 +1,176 @@ +/* + * \brief Data structure for tracking the state of imported archives + * \author Norman Feske + * \date 2018-01-11 + */ + +/* + * 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 _IMPORT_H_ +#define _IMPORT_H_ + +/* Genode includes */ +#include +#include +#include + +#include "types.h" + +namespace Depot_download_manager { + using namespace Depot; + struct Import; +} + + +class Depot_download_manager::Import +{ + private: + + struct Item + { + Registry::Element _element; + + Archive::Path const path; + + enum State { DOWNLOAD_IN_PROGRESS, + DOWNLOAD_COMPLETE, + VERIFIED, + VERIFICATION_FAILED, + UNPACKED }; + + State state = DOWNLOAD_IN_PROGRESS; + + Item(Registry ®istry, Archive::Path const &path) + : + _element(registry, *this), path(path) + { } + }; + + Allocator &_alloc; + + Registry _items { }; + + template + void _for_each_item(Item::State state, FN const &fn) const + { + _items.for_each([&] (Item const &item) { + if (item.state == state) + fn(item.path); }); + } + + /** + * Return true if at least one item is in the given 'state' + */ + bool _item_state_exists(Item::State state) const + { + bool result = false; + _items.for_each([&] (Item const &item) { + if (!result && item.state == state) + result = true; }); + return result; + } + + public: + + /** + * Constructor + * + * \param user depot origin to use for the import + * \param node XML node containing any number of '' sub nodes + * + * The import constructor considers only those '' sub nodes as + * items that match the 'user'. The remaining sub nodes are imported in + * a future iteration. + */ + Import(Allocator &alloc, Archive::User const &user, Xml_node node) + : + _alloc(alloc) + { + node.for_each_sub_node("missing", [&] (Xml_node item) { + Archive::Path const path = item.attribute_value("path", Archive::Path()); + if (Archive::user(path) == user) + new (alloc) Item(_items, path); + }); + } + + ~Import() + { + _items.for_each([&] (Item &item) { destroy(_alloc, &item); }); + } + + bool downloads_in_progress() const + { + return _item_state_exists(Item::DOWNLOAD_IN_PROGRESS); + } + + bool unverified_archives_available() const + { + return _item_state_exists(Item::DOWNLOAD_COMPLETE); + } + + bool verified_archives_available() const + { + return _item_state_exists(Item::VERIFIED); + } + + template + void for_each_download(FN const &fn) const + { + _for_each_item(Item::DOWNLOAD_IN_PROGRESS, fn); + } + + template + void for_each_unverified_archive(FN const &fn) const + { + _for_each_item(Item::DOWNLOAD_COMPLETE, fn); + } + + template + void for_each_verified_archive(FN const &fn) const + { + _for_each_item(Item::VERIFIED, fn); + } + + template + void for_each_ready_archive(FN const &fn) const + { + _for_each_item(Item::UNPACKED, fn); + } + + void all_downloads_completed() + { + _items.for_each([&] (Item &item) { + if (item.state == Item::DOWNLOAD_IN_PROGRESS) + item.state = Item::DOWNLOAD_COMPLETE; }); + } + + void archive_verified(Archive::Path const &archive) + { + _items.for_each([&] (Item &item) { + if (item.state == Item::DOWNLOAD_COMPLETE) + if (item.path == archive) + item.state = Item::VERIFIED; }); + } + + void archive_verification_failed(Archive::Path const &archive) + { + _items.for_each([&] (Item &item) { + if (item.state == Item::DOWNLOAD_COMPLETE) + if (item.path == archive) + item.state = Item::VERIFICATION_FAILED; }); + } + + void all_verified_archives_extracted() + { + _items.for_each([&] (Item &item) { + if (item.state == Item::VERIFIED) + item.state = Item::UNPACKED; }); + } +}; + +#endif /* _IMPORT_H_ */ diff --git a/repos/gems/src/app/depot_download_manager/main.cc b/repos/gems/src/app/depot_download_manager/main.cc new file mode 100644 index 0000000000..5a485a1140 --- /dev/null +++ b/repos/gems/src/app/depot_download_manager/main.cc @@ -0,0 +1,329 @@ +/* + * \brief Tool for managing the download of depot content + * \author Norman Feske + * \date 2017-12-08 + */ + +/* + * 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 + +/* local includes */ +#include "xml.h" + +namespace Depot_download_manager { + using namespace Depot; + struct Child_exit_state; + struct Main; +} + + +struct Depot_download_manager::Child_exit_state +{ + bool exists = false; + bool exited = false; + int code = 0; + + typedef String<64> Name; + + Child_exit_state(Xml_node init_state, Name const &name) + { + init_state.for_each_sub_node("child", [&] (Xml_node child) { + if (child.attribute_value("name", Name()) == name) { + exists = true; + if (child.has_attribute("exited")) { + exited = true; + code = child.attribute_value("exited", 0L); + } + } + }); + } +}; + + +struct Depot_download_manager::Main +{ + Env &_env; + + Heap _heap { _env.ram(), _env.rm() }; + + Attached_rom_dataspace _installation { _env, "installation" }; + Attached_rom_dataspace _dependencies { _env, "dependencies" }; + Attached_rom_dataspace _init_state { _env, "init_state" }; + + /** + * User identity, from which current downloads are fetched + */ + Attached_rom_dataspace _current_user { _env, "user" }; + + /** + * Result of signature verification, reported by the 'verify' component + */ + Attached_rom_dataspace _verified { _env, "verified" }; + + class Invalid_download_url : Exception { }; + + /** + * \throw Invalid_download_url + */ + Url _current_user_url() const; + + Archive::User _current_user_name() const + { + return _current_user.xml().attribute_value("name", Archive::User()); + } + + Path _current_user_path() const + { + return Path("/depot/", _current_user_name()); + } + + Expanding_reporter _init_config { _env, "config", "init_config" }; + + /** + * Version counters, used to enforce the restart or reconfiguration of + * components. + */ + Depot_query_version _depot_query_count { 1 }; + Fetchurl_version _fetchurl_count { 1 }; + + Archive::User _next_user { }; + + Constructible _import { }; + + void _generate_init_config(Xml_generator &); + + void _generate_init_config() + { + _init_config.generate([&] (Xml_generator &xml) { + _generate_init_config(xml); }); + } + + Signal_handler
_query_result_handler { + _env.ep(), *this, &Main::_handle_query_result }; + + void _handle_query_result(); + + Signal_handler
_init_state_handler { + _env.ep(), *this, &Main::_handle_init_state }; + + void _handle_init_state(); + + Main(Env &env) : _env(env) + { + _dependencies.sigh(_query_result_handler); + _current_user.sigh(_query_result_handler); + _init_state .sigh(_init_state_handler); + _verified .sigh(_init_state_handler); + + _generate_init_config(); + } +}; + + +Depot_download_manager::Url +Depot_download_manager::Main::_current_user_url() const +{ + if (!_current_user.xml().has_sub_node("url")) + throw Invalid_download_url(); + + Url const url = _current_user.xml().sub_node("url").decoded_content(); + + /* + * Ensure that the URL does not contain any '"' character because it will + * be taken as an XML attribute value. + * + * XXX This should better be addressed by sanitizing the argument of + * 'Xml_generator::attribute' (issue #1757). + */ + for (char const *s = url.string(); *s; s++) + if (*s == '"') + throw Invalid_download_url(); + + return url; +} + + +void Depot_download_manager::Main::_generate_init_config(Xml_generator &xml) +{ + xml.node("report", [&] () { + xml.attribute("delay_ms", 500); }); + + xml.node("parent-provides", [&] () { + gen_parent_service(xml); + gen_parent_service(xml); + gen_parent_service(xml); + gen_parent_service(xml); + gen_parent_service(xml); + gen_parent_service(xml); + gen_parent_service(xml); + gen_parent_service(xml); + }); + + xml.node("start", [&] () { + gen_depot_query_start_content(xml, _installation.xml(), + _next_user, _depot_query_count); }); + + if (_import.constructed() && _import->downloads_in_progress()) { + try { + xml.node("start", [&] () { + gen_fetchurl_start_content(xml, *_import, _current_user_url(), + _fetchurl_count); }); + } + catch (Invalid_download_url) { + error("invalid download URL for depot user:", _current_user.xml()); + } + } + + if (_import.constructed() && _import->unverified_archives_available()) + xml.node("start", [&] () { + gen_verify_start_content(xml, *_import, _current_user_path()); }); + + if (_import.constructed() && _import->verified_archives_available()) { + + xml.node("start", [&] () { + gen_chroot_start_content(xml, _current_user_name()); }); + + xml.node("start", [&] () { + gen_extract_start_content(xml, *_import, _current_user_path(), + _current_user_name()); }); + } +} + + +void Depot_download_manager::Main::_handle_query_result() +{ + /* finish current import before starting a new one */ + if (_import.constructed()) + return; + + _dependencies.update(); + _current_user.update(); + + Xml_node const dependencies = _dependencies.xml(); + if (dependencies.num_sub_nodes() == 0) + return; + + if (!dependencies.has_sub_node("missing")) { + log("installation complete."); + return; + } + + Archive::Path const path = + dependencies.sub_node("missing").attribute_value("path", Archive::Path()); + + if (Archive::user(path) != _current_user_name()) { + _next_user = Archive::user(path); + + /* query user info from 'depot_query' */ + _generate_init_config(); + return; + } + + /* start new import */ + _import.construct(_heap, _current_user_name(), _dependencies.xml()); + + /* spawn fetchurl */ + _generate_init_config(); +} + + +void Depot_download_manager::Main::_handle_init_state() +{ + _init_state.update(); + _verified.update(); + + bool reconfigure_init = false; + bool import_finished = false; + + if (!_import.constructed()) + return; + + Import &import = *_import; + + if (import.downloads_in_progress()) { + + Child_exit_state const fetchurl_state(_init_state.xml(), "fetchurl"); + + if (fetchurl_state.exited && fetchurl_state.code != 0) { + error("fetchurl failed with exit code ", fetchurl_state.code); + + /* retry by incrementing the version attribute of the start node */ + _fetchurl_count.value++; + reconfigure_init = true; + } + + if (fetchurl_state.exited && fetchurl_state.code == 0) { + import.all_downloads_completed(); + + /* kill fetchurl, start untar */ + reconfigure_init = true; + } + } + + if (import.unverified_archives_available()) { + + _verified.xml().for_each_sub_node([&] (Xml_node node) { + + /* path in the VFS name space of the 'verify' component */ + Path const abs_path = node.attribute_value("path", Archive::Path()); + + /* determine matching archive path */ + Path path; + import.for_each_unverified_archive([&] (Archive::Path const &archive) { + if (abs_path == Path("/public/", archive, ".tar.xz")) + path = archive; }); + + if (path.valid()) { + if (node.type() == "good") { + import.archive_verified(path); + } else { + error("signature check failed for '", path, "' " + "(", node.attribute_value("reason", String<64>()), ")"); + import.archive_verification_failed(path); + } + } + + reconfigure_init = true; + }); + } + + if (import.verified_archives_available()) { + + Child_exit_state const extract_state(_init_state.xml(), "extract"); + + if (extract_state.exited && extract_state.code != 0) + error("extract failed with exit code ", extract_state.code); + + if (extract_state.exited && extract_state.code == 0) { + import.all_verified_archives_extracted(); + import_finished = true; + + /* kill extract, re-issue new depot query to start next iteration */ + _depot_query_count.value++; + reconfigure_init = true; + } + } + + if (import_finished) + _import.destruct(); + + if (reconfigure_init) + _generate_init_config(); +} + + +void Component::construct(Genode::Env &env) +{ + static Depot_download_manager::Main main(env); +} + diff --git a/repos/gems/src/app/depot_download_manager/target.mk b/repos/gems/src/app/depot_download_manager/target.mk new file mode 100644 index 0000000000..3c0dedda91 --- /dev/null +++ b/repos/gems/src/app/depot_download_manager/target.mk @@ -0,0 +1,4 @@ +TARGET := depot_download_manager +SRC_CC := main.cc gen_depot_query.cc gen_fetchurl.cc gen_verify.cc \ + gen_chroot.cc gen_extract.cc +LIBS += base diff --git a/repos/gems/src/app/depot_download_manager/types.h b/repos/gems/src/app/depot_download_manager/types.h new file mode 100644 index 0000000000..07ca1cc9c3 --- /dev/null +++ b/repos/gems/src/app/depot_download_manager/types.h @@ -0,0 +1,52 @@ +/* + * \brief Types used the depot download manager + * \author Norman Feske + * \date 2018-01-11 + */ + +/* + * 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 _TYPES_H_ +#define _TYPES_H_ + +#include +#include +#include + +namespace Depot_download_manager { + + using namespace Depot; + + typedef String<32> Rom_name; + typedef String<160> Url; + typedef String<160> Path; + + struct Depot_query_version { unsigned value; }; + struct Fetchurl_version { unsigned value; }; +} + +namespace Genode { + + /** + * Strip off the last element from 'path' + */ + template + static String without_last_path_element(String const &path) + { + unsigned last_slash = 0; + char const * const s = path.string(); + for (unsigned i = 0; s[i]; i++) + if (s[i] == '/') + last_slash = i; + + return String(Cstring(s, last_slash)); + } +} + +#endif /* _TYPES_H_ */ + diff --git a/repos/gems/src/app/depot_download_manager/xml.h b/repos/gems/src/app/depot_download_manager/xml.h new file mode 100644 index 0000000000..5a6e39cb18 --- /dev/null +++ b/repos/gems/src/app/depot_download_manager/xml.h @@ -0,0 +1,100 @@ +/* + * \brief Utilities for generating XML + * \author Norman Feske + * \date 2018-01-11 + */ + +/* + * 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 _GENERATE_XML_H_ +#define _GENERATE_XML_H_ + +/* Genode includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* local includes */ +#include "import.h" + +namespace Depot_download_manager { + + template + static inline void gen_parent_service(Xml_generator &xml) + { + xml.node("service", [&] () { + xml.attribute("name", SESSION::service_name()); }); + }; + + template + static inline void gen_parent_route(Xml_generator &xml) + { + xml.node("service", [&] () { + xml.attribute("name", SESSION::service_name()); + xml.node("parent", [&] () { }); + }); + } + + static inline void gen_parent_unscoped_rom_route(Xml_generator &xml, + Rom_name const &name) + { + xml.node("service", [&] () { + xml.attribute("name", Rom_session::service_name()); + xml.attribute("unscoped_label", name); + xml.node("parent", [&] () { + xml.attribute("label", name); }); + }); + } + + static inline void gen_parent_rom_route(Xml_generator &xml, + Rom_name const &name) + { + xml.node("service", [&] () { + xml.attribute("name", Rom_session::service_name()); + xml.attribute("label", name); + xml.node("parent", [&] () { + xml.attribute("label", name); }); + }); + } + + static inline void gen_common_start_content(Xml_generator &xml, + Rom_name const &name, + Cap_quota const caps, + Ram_quota const ram) + { + xml.attribute("name", name); + xml.attribute("caps", caps.value); + xml.node("resource", [&] () { + xml.attribute("name", "RAM"); + xml.attribute("quantum", String<64>(Number_of_bytes(ram.value))); + }); + } + + void gen_depot_query_start_content(Xml_generator &, + Xml_node installation, + Archive::User const &, + Depot_query_version); + + void gen_fetchurl_start_content(Xml_generator &, Import const &, Url const &, + Fetchurl_version); + + void gen_verify_start_content(Xml_generator &, Import const &, Path const &); + + void gen_chroot_start_content(Xml_generator &, Archive::User const &); + + void gen_extract_start_content(Xml_generator &, Import const &, + Path const &, Archive::User const &); +} + +#endif /* _GENERATE_XML_H_ */