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_ */