diff --git a/repos/libports/run/extract.run b/repos/libports/run/extract.run
new file mode 100644
index 0000000000..017540e54f
--- /dev/null
+++ b/repos/libports/run/extract.run
@@ -0,0 +1,45 @@
+create_boot_directory
+
+import_from_depot genodelabs/src/[base_src] \
+ genodelabs/src/init
+
+install_config {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+exec tar cJf [run_dir]/genode/test.tar.xz -C [genode_dir] tool/depot
+
+build { app/extract }
+
+build_boot_image {
+ extract
+ libc.lib.so libm.lib.so posix.lib.so
+ libarchive.lib.so liblzma.lib.so zlib.lib.so
+}
+
+append qemu_args " -nographic "
+
+run_genode_until {child "extract" exited with exit value 0} 30
diff --git a/repos/libports/src/app/extract/main.cc b/repos/libports/src/app/extract/main.cc
new file mode 100644
index 0000000000..da17ea5be4
--- /dev/null
+++ b/repos/libports/src/app/extract/main.cc
@@ -0,0 +1,236 @@
+/*
+ * \brief Tool for extracting archives
+ * \author Norman Feske
+ * \date 2017-12-20
+ */
+
+/*
+ * 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
+
+/* libc includes */
+#include
+#include
+
+/* libarchive includes */
+#include
+#include
+
+namespace Extract {
+ using namespace Genode;
+ struct Extracted_archive;
+ struct Main;
+}
+
+
+/**
+ * Create compound directories leading to the path
+ *
+ * \return true on success
+ */
+template
+bool create_directories(Genode::String const &path)
+{
+ using namespace Genode;
+
+ for (size_t sub_path_len = 0; ; sub_path_len++) {
+
+ char const c = path.string()[sub_path_len];
+
+ bool const end_of_path = (c == 0);
+ bool const end_of_elem = (c == '/');
+
+ if (!end_of_elem && !end_of_path)
+ continue;
+
+ Genode::String sub_path(Genode::Cstring(path.string(), sub_path_len));
+
+ /* create directory for sub path if it does not already exist */
+ struct stat sb;
+ sb.st_mode = 0;
+ stat(sub_path.string(), &sb);
+
+ if (!S_ISDIR(sb.st_mode)) {
+ if (mkdir(sub_path.string(), 0777) < 0)
+ return false;
+ }
+
+ if (end_of_path)
+ break;
+
+ /* skip '/' */
+ sub_path_len++;
+ }
+ return true;
+}
+
+
+struct Extract::Extracted_archive : Noncopyable
+{
+ struct Source : Noncopyable
+ {
+ archive * const ptr = archive_read_new();
+
+ ~Source()
+ {
+ archive_read_close(ptr);
+ archive_read_free(ptr);
+ }
+ } src { };
+
+ struct Destination : Noncopyable
+ {
+ archive * const ptr = archive_write_disk_new();
+
+ ~Destination()
+ {
+ archive_write_close(ptr);
+ archive_write_free(ptr);
+ }
+ } dst { };
+
+ typedef String<256> Path;
+
+ struct Exception : Genode::Exception { };
+ struct Open_failed : Exception { };
+ struct Read_failed : Exception { };
+ struct Write_failed : Exception { };
+
+ /**
+ * Constructor
+ *
+ * \throw Open_failed
+ * \throw Read_failed
+ * \throw Write_failed
+ */
+ explicit Extracted_archive(Path const &path)
+ {
+ archive_read_support_format_all(src.ptr);
+ archive_read_support_filter_all(src.ptr);
+
+ size_t const block_size = 10240;
+
+ if (archive_read_open_filename(src.ptr, path.string(), block_size))
+ throw Open_failed();
+
+ for (;;) {
+
+ struct archive_entry *entry = nullptr;
+
+ {
+ int const ret = archive_read_next_header(src.ptr, &entry);
+
+ if (ret == ARCHIVE_EOF)
+ break;
+
+ if (ret != ARCHIVE_OK)
+ throw Read_failed();
+ }
+
+ if (archive_write_header(dst.ptr, entry) != ARCHIVE_OK)
+ throw Write_failed();
+
+ for (;;) {
+ void const *buf = nullptr;
+ size_t size = 0;
+ ::int64_t offset = 0;
+
+ {
+ int const ret = archive_read_data_block(src.ptr, &buf, &size, &offset);
+
+ if (ret == ARCHIVE_EOF)
+ break;
+
+ if (ret != ARCHIVE_OK)
+ throw Read_failed();
+ }
+
+ if (archive_write_data_block(dst.ptr, buf, size, offset) != ARCHIVE_OK)
+ throw Write_failed();
+ }
+
+ if (archive_write_finish_entry(dst.ptr) != ARCHIVE_OK)
+ throw Write_failed();
+ }
+ }
+};
+
+
+struct Extract::Main
+{
+ Env &_env;
+
+ typedef Extracted_archive::Path Path;
+
+ Attached_rom_dataspace _config { _env, "config" };
+
+ bool _verbose = false;
+
+ void _process_config()
+ {
+ Xml_node const config = _config.xml();
+
+ _verbose = config.attribute_value("verbose", false);
+
+ config.for_each_sub_node("extract", [&] (Xml_node node) {
+
+ Path const src_path = node.attribute_value("archive", Path());
+ Path const dst_path = node.attribute_value("to", Path());
+
+ bool success = false;
+
+ struct Create_directories_failed { };
+
+ try {
+ if (!create_directories(dst_path))
+ throw Create_directories_failed();
+
+ chdir("/");
+ chdir(dst_path.string());
+
+ Extracted_archive extracted_archive(src_path);
+
+ success = true;
+ }
+ catch (Create_directories_failed) {
+ warning("failed to created directory '", dst_path, "'"); }
+ catch (Extracted_archive::Read_failed) {
+ warning("reading from archive ", src_path, " failed"); }
+ catch (Extracted_archive::Open_failed) {
+ warning("could not open archive ", src_path); }
+ catch (Extracted_archive::Write_failed) {
+ warning("writing to directory ", dst_path, " failed"); }
+
+ /* abort on first error */
+ if (!success)
+ throw Exception();
+
+ if (_verbose)
+ log("extracted '", src_path, "' to '", dst_path, "'");
+ });
+ }
+
+ Main(Env &env) : _env(env)
+ {
+ Libc::with_libc([&] () { _process_config(); });
+
+ env.parent().exit(0);
+ }
+};
+
+
+void Libc::Component::construct(Libc::Env &env)
+{
+ static Extract::Main main(env);
+}
+
+/* dummy to prevent warning printed by unimplemented libc function */
+extern "C" mode_t umask(mode_t value) { return value; }
+
diff --git a/repos/libports/src/app/extract/target.mk b/repos/libports/src/app/extract/target.mk
new file mode 100644
index 0000000000..7cf1743c2f
--- /dev/null
+++ b/repos/libports/src/app/extract/target.mk
@@ -0,0 +1,3 @@
+TARGET = extract
+SRC_CC = main.cc
+LIBS = libarchive libc