From 59ac5b10c7d243ee908b720616e8a8edf0b07db1 Mon Sep 17 00:00:00 2001 From: Emery Hemingway Date: Thu, 5 Jul 2018 16:50:14 +0200 Subject: [PATCH] Plugin for importing VFS content This new vfs_import plugin allows a VFS instance to be populated during construction using a sub-VFS configured in an '' configuration node. This allows the ram_fs File_system server to be replaced by the VFS server by reimplementing the ram_fs 'content' feature. At the moment the copying of symlinks is not enabled, and the resources obtained by the import file-system may not be freed after the import is finished. Fix #2906 --- repos/gems/include/gems/vfs.h | 49 ++- repos/gems/lib/mk/vfs_import.mk | 5 + repos/gems/recipes/src/vfs_import/content.mk | 9 + repos/gems/recipes/src/vfs_import/hash | 1 + repos/gems/recipes/src/vfs_import/used_apis | 5 + repos/gems/run/vfs_import.run | 90 ++++++ repos/gems/src/lib/vfs/import/plugin.cc | 298 +++++++++++++++++++ repos/gems/src/lib/vfs/import/target.mk | 2 + repos/os/src/lib/vfs/symlink_file_system.h | 63 ++-- tool/autopilot.list | 1 + 10 files changed, 488 insertions(+), 35 deletions(-) create mode 100644 repos/gems/lib/mk/vfs_import.mk create mode 100644 repos/gems/recipes/src/vfs_import/content.mk create mode 100644 repos/gems/recipes/src/vfs_import/hash create mode 100644 repos/gems/recipes/src/vfs_import/used_apis create mode 100644 repos/gems/run/vfs_import.run create mode 100644 repos/gems/src/lib/vfs/import/plugin.cc create mode 100644 repos/gems/src/lib/vfs/import/target.mk diff --git a/repos/gems/include/gems/vfs.h b/repos/gems/include/gems/vfs.h index 30fe66452..32d72d5ab 100644 --- a/repos/gems/include/gems/vfs.h +++ b/repos/gems/include/gems/vfs.h @@ -71,7 +71,9 @@ struct Genode::Directory : Noncopyable, Interface Vfs::Directory_service::Dirent_type type() const { return _dirent.type; } }; - typedef String<256> Path; + enum { MAX_PATH_LEN = 256 }; + + typedef String Path; static Path join(Path const &x, Path const &y) { @@ -232,6 +234,51 @@ struct Genode::Directory : Noncopyable, Interface throw Nonexistent_file(); return stat.size; } + + /** + * Return symlink content at specified directory-relative path + * + * \throw Nonexistent_file symlink at path does not exist or + * access is denied + * + */ + Path read_symlink(Path const &rel_path) const + { + using namespace Vfs; + Vfs_handle *link_handle; + + auto open_res = _nonconst_fs().openlink( + join(_path, rel_path).string(), + false, &link_handle, _alloc); + if (open_res != Directory_service::OPENLINK_OK) + throw Nonexistent_file(); + Vfs_handle::Guard guard(link_handle); + + char buf[MAX_PATH_LEN]; + + Vfs::file_size count = sizeof(buf)-1; + Vfs::file_size out_count = 0; + while (!link_handle->fs().queue_read(link_handle, count)) { + _ep.wait_and_dispatch_one_io_signal(); + } + + File_io_service::Read_result result; + + for (;;) { + result = link_handle->fs().complete_read( + link_handle, buf, count, out_count); + + if (result != File_io_service::READ_QUEUED) + break; + + _ep.wait_and_dispatch_one_io_signal(); + }; + + if (result != File_io_service::READ_OK) + throw Nonexistent_file(); + + return Path(Genode::Cstring(buf, out_count)); + } }; diff --git a/repos/gems/lib/mk/vfs_import.mk b/repos/gems/lib/mk/vfs_import.mk new file mode 100644 index 000000000..6310898a3 --- /dev/null +++ b/repos/gems/lib/mk/vfs_import.mk @@ -0,0 +1,5 @@ +SRC_CC = plugin.cc + +vpath %.cc $(REP_DIR)/src/lib/vfs/import + +SHARED_LIB = yes diff --git a/repos/gems/recipes/src/vfs_import/content.mk b/repos/gems/recipes/src/vfs_import/content.mk new file mode 100644 index 000000000..fe5d8ff0b --- /dev/null +++ b/repos/gems/recipes/src/vfs_import/content.mk @@ -0,0 +1,9 @@ +MIRROR_FROM_REP_DIR := lib/mk/vfs_import.mk src/lib/vfs/import + +content: $(MIRROR_FROM_REP_DIR) LICENSE + +$(MIRROR_FROM_REP_DIR): + $(mirror_from_rep_dir) + +LICENSE: + cp $(GENODE_DIR)/LICENSE $@ diff --git a/repos/gems/recipes/src/vfs_import/hash b/repos/gems/recipes/src/vfs_import/hash new file mode 100644 index 000000000..71be55abb --- /dev/null +++ b/repos/gems/recipes/src/vfs_import/hash @@ -0,0 +1 @@ +2018-07-09 880d61678496b719362414e3169d34224183982a diff --git a/repos/gems/recipes/src/vfs_import/used_apis b/repos/gems/recipes/src/vfs_import/used_apis new file mode 100644 index 000000000..1d6ba0fc7 --- /dev/null +++ b/repos/gems/recipes/src/vfs_import/used_apis @@ -0,0 +1,5 @@ +base +os +so +vfs +gems diff --git a/repos/gems/run/vfs_import.run b/repos/gems/run/vfs_import.run new file mode 100644 index 000000000..aea05a9b3 --- /dev/null +++ b/repos/gems/run/vfs_import.run @@ -0,0 +1,90 @@ +build { app/sequence server/vfs lib/vfs/import test/libc } + +create_boot_directory + +import_from_depot \ + genodelabs/src/[base_src] \ + genodelabs/src/coreutils \ + genodelabs/src/init \ + genodelabs/src/libc \ + genodelabs/src/noux \ + genodelabs/src/posix \ + +install_config { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Hello world! + + + + + + + + + + + + + + + + + + + + + + + + + +} + +build_boot_image { vfs_import.lib.so libc.lib.so vfs vfs.lib.so sequence } + +append qemu_args " -nographic -serial mon:stdio " + +run_genode_until {Hello world!} 30 diff --git a/repos/gems/src/lib/vfs/import/plugin.cc b/repos/gems/src/lib/vfs/import/plugin.cc new file mode 100644 index 000000000..a2218090a --- /dev/null +++ b/repos/gems/src/lib/vfs/import/plugin.cc @@ -0,0 +1,298 @@ +/* + * \brief VFS content initialization/import plugin + * \author Emery Hemingway + * \date 2018-07-05 + */ + +/* + * Copyright (C) 2018 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 +#include +#include + +namespace Vfs_import { + using namespace Vfs; + class Flush_guard; + class File_system; + using Genode::Directory; + using Genode::Root_directory; +} + + +/** + * Utility to flush or sync a handle upon + * leaving scope. Use with caution, syncing + * may block for I/O signals. + */ +class Vfs_import::Flush_guard +{ + private: + + Genode::Entrypoint &_ep; + Vfs_handle &_handle; + + public: + + Flush_guard(Vfs::Env &env, Vfs_handle &handle) + : _ep(env.env().ep()), _handle(handle) { } + + ~Flush_guard() + { + while (true) { + if ((_handle.fs().queue_sync(&_handle)) + && (_handle.fs().complete_sync(&_handle) + == Vfs::File_io_service::SYNC_OK)) + break; + _ep.wait_and_dispatch_one_io_signal(); + } + } +}; + + +class Vfs_import::File_system : public Vfs::File_system +{ + private: + + /** + * XXX: A would-be temporary heap, but + * deconstructing a VFS is not supported. + */ + Genode::Heap _heap; + + enum { CREATE_IT = true }; + + static void copy_symlink(Vfs::Env &env, + Root_directory &src, + Directory::Path const &path, + Genode::Allocator &alloc, + bool overwrite) + { + Directory::Path target = src.read_symlink(path); + + Vfs_handle *dst_handle = nullptr; + auto res = env.root_dir().openlink( + path.string(), true, &dst_handle, alloc); + if (res == OPENLINK_ERR_NODE_ALREADY_EXISTS && overwrite) { + res = env.root_dir().openlink( + path.string(), false, &dst_handle, alloc); + } + if (res != OPENLINK_OK) { + if (res != OPENLINK_ERR_NODE_ALREADY_EXISTS) + Genode::warning("skipping copy of symlink ", path, ", ", res); + return; + } + + Vfs_handle::Guard guard(dst_handle); + { + Flush_guard flush(env, *dst_handle); + + file_size count = target.length(); + for (;;) { + file_size out_count = 0; + auto wres = dst_handle->fs().write( + dst_handle, target.string(), count, out_count); + + switch (wres) { + case WRITE_ERR_AGAIN: + case WRITE_ERR_WOULD_BLOCK: + break; + default: + if (out_count < count) { + Genode::error("failed to write symlink ", path, ", ", wres); + env.root_dir().unlink(path.string()); + } + return; + } + } + } + } + + static void copy_file(Vfs::Env &env, + Root_directory &src, + Directory::Path const &path, + Genode::Allocator &alloc, + bool overwrite) + { + using Genode::Readonly_file; + + Readonly_file src_file(src, path); + Vfs_handle *dst_handle = nullptr; + + enum { + WRITE = OPEN_MODE_WRONLY, + CREATE = OPEN_MODE_WRONLY | OPEN_MODE_CREATE + }; + + auto res = env.root_dir().open( + path.string(), CREATE , &dst_handle, alloc); + if (res == OPEN_ERR_EXISTS && overwrite) { + res = env.root_dir().open( + path.string(), WRITE, &dst_handle, alloc); + } + if (res != OPEN_OK) { + Genode::warning("skipping copy of file ", path, ", ", res); + return; + } + + Vfs_handle::Guard guard(dst_handle); + + { + char buf[4096]; + Flush_guard flush(env, *dst_handle); + + Readonly_file::At at { }; + + while (true) { + file_size wn = 0; + file_size rn = src_file.read(at, buf, sizeof(buf)); + if (!rn) break; + + auto wres = dst_handle->fs().write(dst_handle, buf, rn, wn); + switch (wres) { + case WRITE_OK: + case WRITE_ERR_AGAIN: + case WRITE_ERR_WOULD_BLOCK: + break; + default: + Genode::error("failed to write to ", path, ", ", wres); + env.root_dir().unlink(path.string()); + return; + } + + dst_handle->advance_seek(wn); + at.value += wn; + } + } + } + + static void copy_dir(Vfs::Env &env, + Root_directory &src, + Directory::Path const &path, + Genode::Allocator &alloc, + bool overwrite) + { + { + Vfs_handle *dir_handle = nullptr; + env.root_dir().opendir( + path.string(), CREATE_IT, &dir_handle, alloc); + if (dir_handle) + dir_handle->close(); + } + + { + Directory dir(src, path); + dir.for_each_entry([&] (Directory::Entry const &e) { + auto entry_path = Directory::join(path, e.name()); + switch (e.type()) { + case DIRENT_TYPE_FILE: + copy_file(env, src, entry_path, alloc, overwrite); + break; + case DIRENT_TYPE_DIRECTORY: + copy_dir(env, src, entry_path, alloc, overwrite); + break; + case DIRENT_TYPE_SYMLINK: + copy_symlink(env, src, entry_path, alloc, overwrite); + break; + default: + Genode::warning("skipping copy of ", e); + break; + } + }); + } + } + + public: + + File_system(Vfs::Env &env, Genode::Xml_node config) + : _heap(env.env().pd(), env.env().rm()) + { + bool overwrite = config.attribute_value("overwrite", false); + + Root_directory content(env.env(), _heap, config); + copy_dir(env, content, Directory::Path(""), _heap, overwrite); + } + + const char* type() override { return "import"; } + + /*********************** + ** Directory service ** + ***********************/ + + Genode::Dataspace_capability dataspace(char const*) override { + return Genode::Dataspace_capability(); } + + void release(char const*, Dataspace_capability) override { } + + Open_result open(const char*, unsigned, Vfs::Vfs_handle**, Genode::Allocator&) override { + return Open_result::OPEN_ERR_UNACCESSIBLE; } + + Opendir_result opendir(char const*, bool, + Vfs_handle**, Allocator&) override { + return OPENDIR_ERR_LOOKUP_FAILED; } + + void close(Vfs::Vfs_handle*) override { } + + Stat_result stat(const char*, Vfs::Directory_service::Stat&) override { + return STAT_ERR_NO_ENTRY; } + + Unlink_result unlink(const char*) override { return UNLINK_ERR_NO_ENTRY; } + + Rename_result rename(const char*, const char*) override { + return RENAME_ERR_NO_ENTRY; } + + file_size num_dirent(const char*) override { + return 0; } + + bool directory(char const*) override { + return false; } + + const char* leaf_path(const char *) override { + return nullptr; } + + /********************** + ** File I/O service ** + **********************/ + + Write_result write(Vfs_handle*, + const char *, file_size, + file_size &) override { + return WRITE_ERR_INVALID; } + + Read_result complete_read(Vfs_handle*, + char*, file_size, + file_size&) override { + return READ_ERR_INVALID; } + + bool read_ready(Vfs_handle*) override { + return true; } + + bool notify_read_ready(Vfs_handle*) override { + return false; } + + Ftruncate_result ftruncate(Vfs_handle*, file_size) override { + return FTRUNCATE_ERR_NO_PERM; } + + Sync_result complete_sync(Vfs_handle*) override { + return SYNC_OK; } +}; + + +extern "C" Vfs::File_system_factory *vfs_file_system_factory(void) +{ + struct Factory : Vfs::File_system_factory + { + Vfs::File_system *create(Vfs::Env &env, Genode::Xml_node config) override + { + return new (env.alloc()) + Vfs_import::File_system(env, config); + } + }; + + static Factory f; + return &f; +} diff --git a/repos/gems/src/lib/vfs/import/target.mk b/repos/gems/src/lib/vfs/import/target.mk new file mode 100644 index 000000000..4af182f1f --- /dev/null +++ b/repos/gems/src/lib/vfs/import/target.mk @@ -0,0 +1,2 @@ +TARGET = dummy-vfs_import +LIBS = vfs_import diff --git a/repos/os/src/lib/vfs/symlink_file_system.h b/repos/os/src/lib/vfs/symlink_file_system.h index fdf1ed07b..57562b088 100644 --- a/repos/os/src/lib/vfs/symlink_file_system.h +++ b/repos/os/src/lib/vfs/symlink_file_system.h @@ -24,11 +24,38 @@ class Vfs::Symlink_file_system : public Single_file_system { private: - enum { FILENAME_MAX_LEN = 64 }; typedef Genode::String Target; Target const _target; + struct Symlink_handle final : Single_vfs_handle + { + Target const &_target; + + Symlink_handle(Directory_service &ds, + File_io_service &fs, + Genode::Allocator &alloc, + Target const &target) + : Single_vfs_handle(ds, fs, alloc, 0), _target(target) + { } + + + Read_result read(char *dst, file_size count, + file_size &out_count) override + { + auto n = min(count, _target.length()); + strncpy(dst, _target.string(), n); + out_count = n - 1; + return READ_OK; + } + + Write_result write(char const*, file_size, + file_size&) override { + return WRITE_ERR_INVALID; } + + bool read_ready() override { return true; } + }; + public: Symlink_file_system(Vfs::Env&, Genode::Xml_node config) @@ -57,44 +84,12 @@ class Vfs::Symlink_file_system : public Single_file_system return OPENLINK_ERR_NODE_ALREADY_EXISTS; try { - *out_handle = new (alloc) Vfs_handle(*this, *this, alloc, 0); + *out_handle = new (alloc) Symlink_handle(*this, *this, alloc, _target); return OPENLINK_OK; } catch (Genode::Out_of_ram) { return OPENLINK_ERR_OUT_OF_RAM; } catch (Genode::Out_of_caps) { return OPENLINK_ERR_OUT_OF_CAPS; } } - - void close(Vfs_handle *vfs_handle) override - { - if (!vfs_handle) - return; - - destroy(vfs_handle->alloc(), vfs_handle); - } - - - /******************************** - ** File I/O service interface ** - ********************************/ - - Write_result write(Vfs_handle *, char const *, - file_size, file_size &) override { - return WRITE_ERR_INVALID; } - - Read_result complete_read(Vfs_handle *, char *buf, file_size buf_len, - file_size &out_len) override - { - out_len = min(buf_len, (file_size)_target.length()-1); - memcpy(buf, _target.string(), out_len); - if (out_len < buf_len) - buf[out_len] = '\0'; - return READ_OK; - } - - bool read_ready(Vfs_handle *) override { return false; } - - Ftruncate_result ftruncate(Vfs_handle *, file_size) override { - return FTRUNCATE_ERR_NO_PERM; } }; #endif /* _INCLUDE__VFS__SYMLINK_FILE_SYSTEM_H_ */ diff --git a/tool/autopilot.list b/tool/autopilot.list index c951647f0..65d3b0385 100644 --- a/tool/autopilot.list +++ b/tool/autopilot.list @@ -122,6 +122,7 @@ vbox5_win7_64_multiple vbox5_win7_64_raw vbox5_win7_64_share verify +vfs_import vmm weak_ptr xml_generator