/* * \brief Integration of the Consistent Block Encrypter (CBE) * \author Martin Stein * \author Josef Soentgen * \date 2020-11-10 */ /* * Copyright (C) 2020 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 /* CBE includes */ #include #include /* local includes */ #include namespace Vfs_cbe { using namespace Vfs; using namespace Genode; class Data_file_system; class Extend_file_system; class Rekey_file_system; class Deinitialize_file_system; class Create_snapshot_file_system; class Discard_snapshot_file_system; struct Control_local_factory; class Control_file_system; struct Snapshot_local_factory; class Snapshot_file_system; struct Snapshots_local_factory; class Snapshots_file_system; struct Local_factory; class File_system; class Backend_file; class Wrapper; template class Pointer { private: T *_obj; public: struct Invalid : Genode::Exception { }; Pointer() : _obj(nullptr) { } Pointer(T &obj) : _obj(&obj) { } T &obj() const { if (_obj == nullptr) throw Invalid(); return *_obj; } bool valid() const { return _obj != nullptr; } }; } extern "C" void adainit(); extern "C" void print_u8(unsigned char const u) { Genode::log(u); } class Vfs_cbe::Wrapper { private: Vfs::Env &_env; Vfs_handle *_backend_handle { nullptr }; Constructible _backend_job { }; friend struct Backend_io_response_handler; struct Backend_io_response_handler : Vfs::Io_response_handler { Vfs_cbe::Wrapper &_wrapper; Genode::Signal_context_capability _io_sigh; Backend_io_response_handler(Vfs_cbe::Wrapper &wrapper, Genode::Signal_context_capability io_sigh) : _wrapper(wrapper), _io_sigh(io_sigh) { } void read_ready_response() override { } void io_progress_response() override { if (_io_sigh.valid()) { Genode::Signal_transmitter(_io_sigh).submit(); } } }; Genode::Io_signal_handler _io_handler { _env.env().ep(), *this, &Wrapper::_handle_io }; void _handle_io() { _notify_backend_io_progress(); } void _notify_backend_io_progress() { if (_enqueued_vfs_handle) { _enqueued_vfs_handle->io_progress_response(); } else { handle_frontend_request(); _io_progress_pending = true; } } Backend_io_response_handler _backend_io_response_handler { *this, _io_handler }; Vfs_handle *_add_key_handle { nullptr }; Vfs_handle *_remove_key_handle { nullptr }; struct Crypto_file { Vfs_handle *encrypt_handle; Vfs_handle *decrypt_handle; uint32_t key_id; }; enum { NUM_CRYPTO_FILES = 2, }; Crypto_file _crypto_file[NUM_CRYPTO_FILES] { { .encrypt_handle = nullptr, .decrypt_handle = nullptr, .key_id = 0 }, { .encrypt_handle = nullptr, .decrypt_handle = nullptr, .key_id = 0 } }; Crypto_file *_get_unused_crypto_file() { for (Crypto_file &file : _crypto_file) { if (file.key_id == 0) { return &file; } } struct No_unused_crypt_file_left { }; throw No_unused_crypt_file_left(); } Crypto_file *_lookup_crypto_file(uint32_t key_id) { for (Crypto_file &file : _crypto_file) { if (file.key_id == key_id) { return &file; } } struct Crypt_file_not_found { }; throw Crypt_file_not_found(); } Cbe::Io_buffer _io_data { }; Cbe::Crypto_cipher_buffer _cipher_data { }; Cbe::Crypto_plain_buffer _plain_data { }; Constructible _trust_anchor { }; Constructible _cbe; public: struct Rekeying { enum State { UNKNOWN, IDLE, IN_PROGRESS, }; enum Result { NONE, SUCCESS, FAILED, }; State state; Result last_result; uint32_t key_id; static char const *state_to_cstring(State const s) { switch (s) { case State::UNKNOWN: return "unknown"; case State::IDLE: return "idle"; case State::IN_PROGRESS: return "in-progress"; } return "-"; } }; struct Deinitialize { enum State { IDLE, IN_PROGRESS, }; enum Result { NONE, SUCCESS, FAILED, }; State state; Result last_result; uint32_t key_id; static char const *state_to_cstring(State const s) { switch (s) { case State::IDLE: return "idle"; case State::IN_PROGRESS: return "in-progress"; } return "-"; } }; struct Extending { enum Type { INVALID, VBD, FT }; enum State { UNKNOWN, IDLE, IN_PROGRESS, }; enum Result { NONE, SUCCESS, FAILED, }; Type type; State state; Result last_result; static char const *state_to_cstring(State const s) { switch (s) { case State::UNKNOWN: return "unknown"; case State::IDLE: return "idle"; case State::IN_PROGRESS: return "in-progress"; } return "-"; } static Type string_to_type(char const *s) { if (Genode::strcmp("vbd", s, 3) == 0) { return Type::VBD; } else if (Genode::strcmp("ft", s, 2) == 0) { return Type::FT; } return Type::INVALID; } }; private: Rekeying _rekey_obj { .state = Rekeying::State::UNKNOWN, .last_result = Rekeying::Result::NONE, .key_id = 0, }; Deinitialize _deinit_obj { .state = Deinitialize::State::IDLE, .last_result = Deinitialize::Result::NONE }; Extending _extend_obj { .type = Extending::Type::INVALID, .state = Extending::State::UNKNOWN, .last_result = Extending::Result::NONE, }; Pointer _snapshots_fs { }; Pointer _extend_fs { }; Pointer _rekey_fs { }; Pointer _deinit_fs { }; /* configuration options */ bool _verbose { false }; bool _debug { false }; using Backend_device_path = Genode::String<32>; Backend_device_path _block_device { "/dev/block" }; using Crypto_device_path = Genode::String<32>; Crypto_device_path _crypto_device { "/dev/cbe_crypto" }; using Trust_anchor_device_path = Genode::String<64>; Trust_anchor_device_path _trust_anchor_device { "/dev/cbe_trust_anchor" }; void _read_config(Xml_node config) { _verbose = config.attribute_value("verbose", _verbose); _debug = config.attribute_value("debug", _debug); _block_device = config.attribute_value("block", _block_device); _crypto_device = config.attribute_value("crypto", _crypto_device); _trust_anchor_device = config.attribute_value("trust_anchor", _trust_anchor_device); } struct Could_not_open_block_backend : Genode::Exception { }; struct No_valid_superblock_found : Genode::Exception { }; void _initialize_cbe() { using Result = Vfs::Directory_service::Open_result; Result res = _env.root_dir().open(_block_device.string(), Vfs::Directory_service::OPEN_MODE_RDWR, (Vfs::Vfs_handle **)&_backend_handle, _env.alloc()); if (res != Result::OPEN_OK) { error("cbe_fs: Could not open back end block device: '", _block_device, "'"); throw Could_not_open_block_backend(); } _backend_handle->handler(&_backend_io_response_handler); { Genode::String<128> crypto_add_key_file { _crypto_device.string(), "/add_key" }; res = _env.root_dir().open(crypto_add_key_file.string(), Vfs::Directory_service::OPEN_MODE_WRONLY, (Vfs::Vfs_handle **)&_add_key_handle, _env.alloc()); if (res != Result::OPEN_OK) { error("cbe_fs: Could not open '", crypto_add_key_file, "' file"); throw Could_not_open_block_backend(); } } { Genode::String<128> crypto_remove_key_file { _crypto_device.string(), "/remove_key" }; res = _env.root_dir().open(crypto_remove_key_file.string(), Vfs::Directory_service::OPEN_MODE_WRONLY, (Vfs::Vfs_handle **)&_remove_key_handle, _env.alloc()); if (res != Result::OPEN_OK) { error("cbe_fs: Could not open '", crypto_remove_key_file, "' file"); throw Could_not_open_block_backend(); } } _trust_anchor.construct(_env.root_dir(), _env.alloc(), _trust_anchor_device.string(), _io_handler); _cbe.construct(); } public: void manage_snapshots_file_system(Snapshots_file_system &snapshots_fs) { if (_snapshots_fs.valid()) { class Already_managing_an_snapshots_file_system { }; throw Already_managing_an_snapshots_file_system { }; } _snapshots_fs = snapshots_fs; } void dissolve_snapshots_file_system(Snapshots_file_system &snapshots_fs) { if (_snapshots_fs.valid()) { if (&_snapshots_fs.obj() != &snapshots_fs) { class Snapshots_file_system_not_managed { }; throw Snapshots_file_system_not_managed { }; } _snapshots_fs = Pointer { }; } else { class No_snapshots_file_system_managed { }; throw No_snapshots_file_system_managed { }; } } void manage_extend_file_system(Extend_file_system &extend_fs) { if (_extend_fs.valid()) { class Already_managing_an_extend_file_system { }; throw Already_managing_an_extend_file_system { }; } _extend_fs = extend_fs; } void dissolve_extend_file_system(Extend_file_system &extend_fs) { if (_extend_fs.valid()) { if (&_extend_fs.obj() != &extend_fs) { class Extend_file_system_not_managed { }; throw Extend_file_system_not_managed { }; } _extend_fs = Pointer { }; } else { class No_extend_file_system_managed { }; throw No_extend_file_system_managed { }; } } void manage_rekey_file_system(Rekey_file_system &rekey_fs) { if (_rekey_fs.valid()) { class Already_managing_an_rekey_file_system { }; throw Already_managing_an_rekey_file_system { }; } _rekey_fs = rekey_fs; } void dissolve_rekey_file_system(Rekey_file_system &rekey_fs) { if (_rekey_fs.valid()) { if (&_rekey_fs.obj() != &rekey_fs) { class Rekey_file_system_not_managed { }; throw Rekey_file_system_not_managed { }; } _rekey_fs = Pointer { }; } else { class No_rekey_file_system_managed { }; throw No_rekey_file_system_managed { }; } } void manage_deinit_file_system(Deinitialize_file_system &deinit_fs) { if (_deinit_fs.valid()) { class Already_managing_an_deinit_file_system { }; throw Already_managing_an_deinit_file_system { }; } _deinit_fs = deinit_fs; } void dissolve_deinit_file_system(Deinitialize_file_system &deinit_fs) { if (_deinit_fs.valid()) { if (&_deinit_fs.obj() != &deinit_fs) { class Deinitialize_file_system_not_managed { }; throw Deinitialize_file_system_not_managed { }; } _deinit_fs = Pointer { }; } else { class No_deinit_file_system_managed { }; throw No_deinit_file_system_managed { }; } } Wrapper(Vfs::Env &env, Xml_node config) : _env(env) { _read_config(config); _initialize_cbe(); } Cbe::Library &cbe() { if (!_cbe.constructed()) { struct Cbe_Not_Initialized { }; throw Cbe_Not_Initialized(); } return *_cbe; } struct Invalid_Request : Genode::Exception { }; struct Helper_request { enum { BLOCK_SIZE = 512, }; enum State { NONE, PENDING, IN_PROGRESS, COMPLETE, ERROR }; State state { NONE }; Cbe::Block_data block_data { }; Cbe::Request cbe_request { }; bool pending() const { return state == PENDING; } bool in_progress() const { return state == IN_PROGRESS; } bool complete() const { return state == COMPLETE; } }; Helper_request _helper_read_request { }; Helper_request _helper_write_request { }; struct Frontend_request { enum State { NONE, PENDING, IN_PROGRESS, COMPLETE, ERROR, ERROR_EOF }; State state { NONE }; file_size count { 0 }; Cbe::Request cbe_request { }; uint32_t snap_id { 0 }; uint64_t offset { 0 }; uint64_t helper_offset { 0 }; bool pending() const { return state == PENDING; } bool in_progress() const { return state == IN_PROGRESS; } bool complete() const { return state == COMPLETE; } static char const *state_to_string(State s) { switch (s) { case State::NONE: return "NONE"; case State::PENDING: return "PENDING"; case State::IN_PROGRESS: return "IN_PROGRESS"; case State::COMPLETE: return "COMPLETE"; case State::ERROR: return "ERROR"; case State::ERROR_EOF: return "ERROR_EOF"; } return ""; } }; Frontend_request _frontend_request { }; Frontend_request const & frontend_request() const { return _frontend_request; } // XXX needs to be a list when snapshots are used Vfs_handle *_enqueued_vfs_handle { nullptr }; bool _io_progress_pending { false }; void enqueue_handle(Vfs_handle &handle) { _enqueued_vfs_handle = &handle; if (_io_progress_pending) { _enqueued_vfs_handle->io_progress_response(); _io_progress_pending = false; } } void ack_frontend_request(Vfs_handle &handle) { // assert current state was *_COMPLETE _frontend_request.state = Frontend_request::State::NONE; _frontend_request.cbe_request = Cbe::Request { }; _enqueued_vfs_handle = nullptr; } bool submit_frontend_request(Vfs_handle &handle, char *data, file_size count, Cbe::Request::Operation op, uint32_t snap_id) { if (_frontend_request.state != Frontend_request::State::NONE) { return false; } /* short-cut for SYNC requests */ if (op == Cbe::Request::Operation::SYNC) { _frontend_request.cbe_request = Cbe::Request( op, false, 0, 0, 1, 0, 0); _frontend_request.count = 0; _frontend_request.snap_id = 0; _frontend_request.state = Frontend_request::State::PENDING; if (_verbose) { Genode::log("Req: (front req: ", _frontend_request.cbe_request, ")"); } return true; } file_size const offset = handle.seek(); bool unaligned_request = false; /* unaligned request if any condition is true */ unaligned_request |= (offset % Cbe::BLOCK_SIZE) != 0; unaligned_request |= (count < Cbe::BLOCK_SIZE); if ((count % Cbe::BLOCK_SIZE) != 0 && !unaligned_request) { count = count - (count % Cbe::BLOCK_SIZE); } if (unaligned_request) { _helper_read_request.cbe_request = Cbe::Request( Cbe::Request::Operation::READ, false, offset / Cbe::BLOCK_SIZE, (uint64_t)&_helper_read_request.block_data, 1, 0, 0); _helper_read_request.state = Helper_request::State::PENDING; _frontend_request.helper_offset = (offset % Cbe::BLOCK_SIZE); if (count >= (Cbe::BLOCK_SIZE - _frontend_request.helper_offset)) { _frontend_request.count = Cbe::BLOCK_SIZE - _frontend_request.helper_offset; } else { _frontend_request.count = count; } /* skip handling by the CBE, helper requests will do that for us */ _frontend_request.state = Frontend_request::State::IN_PROGRESS; } else { _frontend_request.count = count; _frontend_request.state = Frontend_request::State::PENDING; } _frontend_request.offset = offset; _frontend_request.cbe_request = Cbe::Request( op, false, offset / Cbe::BLOCK_SIZE, (uint64_t)data, (uint32_t)(count / Cbe::BLOCK_SIZE), 0, 0); if (_verbose) { if (unaligned_request) { Genode::log("Unaligned req: ", "off: ", offset, " bytes: ", count, " (front req: ", _frontend_request.cbe_request, " (helper req: ", _helper_read_request.cbe_request, " off: ", _frontend_request.helper_offset, " count: ", _frontend_request.count, ")"); } else { Genode::log("Req: ", "off: ", offset, " bytes: ", count, " (front req: ", _frontend_request.cbe_request, ")"); } } _frontend_request.snap_id = snap_id; return true; } bool _handle_cbe_backend(Cbe::Library &cbe, Cbe::Io_buffer &io_data) { Cbe::Io_buffer::Index data_index { 0 }; Cbe::Request cbe_request = cbe.has_io_request(data_index); if (cbe_request.valid() && !_backend_job.constructed()) { file_offset const base_offset = cbe_request.block_number() * Cbe::BLOCK_SIZE; file_size const count = cbe_request.count() * Cbe::BLOCK_SIZE; _backend_job.construct(*_backend_handle, cbe_request.operation(), data_index, base_offset, count); } if (!_backend_job.constructed()) { return false; } bool progress = _backend_job->execute(cbe, io_data); if (_backend_job->completed()) { _backend_job.destruct(); } return progress; } void _snapshots_fs_update_snapshot_registry(); void _extend_fs_trigger_watch_response(); void _rekey_fs_trigger_watch_response(); void _deinit_fs_trigger_watch_response(); bool _handle_cbe_frontend(Cbe::Library &cbe, Frontend_request &frontend_request) { if (_helper_read_request.pending()) { if (_cbe->client_request_acceptable()) { _cbe->submit_client_request(_helper_read_request.cbe_request, frontend_request.snap_id); _helper_read_request.state = Helper_request::State::IN_PROGRESS; } } if (_helper_write_request.pending()) { if (_cbe->client_request_acceptable()) { _cbe->submit_client_request(_helper_write_request.cbe_request, frontend_request.snap_id); _helper_write_request.state = Helper_request::State::IN_PROGRESS; } } if (frontend_request.pending()) { using ST = Frontend_request::State; Cbe::Request const &request = frontend_request.cbe_request; Cbe::Virtual_block_address const vba = request.block_number(); uint32_t const snap_id = frontend_request.snap_id; if (vba > cbe.max_vba()) { warning("reject request with out-of-range virtual block start address ", vba); _frontend_request.state = ST::ERROR_EOF; return false; } if (vba + request.count() < vba) { warning("reject wraping request", vba); _frontend_request.state = ST::ERROR_EOF; return false; } if (vba + request.count() > (cbe.max_vba() + 1)) { warning("reject invalid request ", vba, " ", request.count()); _frontend_request.state = ST::ERROR_EOF; return false; } if (cbe.client_request_acceptable()) { cbe.submit_client_request(request, snap_id); frontend_request.state = ST::IN_PROGRESS; } } cbe.execute(_io_data, _plain_data, _cipher_data); bool progress = cbe.execute_progress(); using ST = Frontend_request::State; while (true) { Cbe::Request const cbe_request = cbe.peek_completed_client_request(); if (!cbe_request.valid()) { break; } cbe.drop_completed_client_request(cbe_request); progress = true; if (cbe_request.operation() == Cbe::Request::Operation::REKEY) { bool const req_sucess = cbe_request.success(); if (_verbose) { log("Complete request: backend request (", cbe_request, ")"); } _rekey_obj.state = Rekeying::State::IDLE; _rekey_obj.last_result = req_sucess ? Rekeying::Result::SUCCESS : Rekeying::Result::FAILED; _rekey_fs_trigger_watch_response(); continue; } if (cbe_request.operation() == Cbe::Request::Operation::DEINITIALIZE) { bool const req_sucess = cbe_request.success(); if (_verbose) { log("Complete request: backend request (", cbe_request, ")"); } _deinit_obj.state = Deinitialize::State::IDLE; _deinit_obj.last_result = req_sucess ? Deinitialize::Result::SUCCESS : Deinitialize::Result::FAILED; _deinit_fs_trigger_watch_response(); continue; } if (cbe_request.operation() == Cbe::Request::Operation::EXTEND_VBD) { bool const req_sucess = cbe_request.success(); if (_verbose) { log("Complete request: backend request (", cbe_request, ")"); } _extend_obj.state = Extending::State::IDLE; _extend_obj.last_result = req_sucess ? Extending::Result::SUCCESS : Extending::Result::FAILED; _extend_fs_trigger_watch_response(); continue; } if (cbe_request.operation() == Cbe::Request::Operation::EXTEND_FT) { bool const req_sucess = cbe_request.success(); if (_verbose) { log("Complete request: backend request (", cbe_request, ")"); } _extend_obj.state = Extending::State::IDLE; _extend_obj.last_result = req_sucess ? Extending::Result::SUCCESS : Extending::Result::FAILED; _extend_fs_trigger_watch_response(); continue; } if (cbe_request.operation() == Cbe::Request::Operation::CREATE_SNAPSHOT) { if (_verbose) { log("Complete request: (", cbe_request, ")"); } _create_snapshot_request.cbe_request = Cbe::Request(); _snapshots_fs_update_snapshot_registry(); continue; } if (cbe_request.operation() == Cbe::Request::Operation::DISCARD_SNAPSHOT) { if (_verbose) { log("Complete request: (", cbe_request, ")"); } _discard_snapshot_request.cbe_request = Cbe::Request(); _snapshots_fs_update_snapshot_registry(); continue; } if (!cbe_request.success()) { _helper_read_request.state = Helper_request::State::NONE; _helper_write_request.state = Helper_request::State::NONE; frontend_request.state = ST::COMPLETE; frontend_request.cbe_request.success(cbe_request.success()); break; } if (_helper_read_request.in_progress()) { _helper_read_request.state = Helper_request::State::COMPLETE; _helper_read_request.cbe_request.success( cbe_request.success()); } else if (_helper_write_request.in_progress()) { _helper_write_request.state = Helper_request::State::COMPLETE; _helper_write_request.cbe_request.success( cbe_request.success()); } else { frontend_request.state = ST::COMPLETE; frontend_request.cbe_request.success(cbe_request.success()); if (_verbose) { Genode::log("Complete request: ", " (frontend request: ", _frontend_request.cbe_request, " count: ", _frontend_request.count, ")"); } } } if (_helper_read_request.complete()) { if (frontend_request.cbe_request.read()) { char * dst = reinterpret_cast (frontend_request.cbe_request.offset()); char const * src = reinterpret_cast (&_helper_read_request.block_data) + frontend_request.helper_offset; Genode::memcpy(dst, src, _frontend_request.count); _helper_read_request.state = Helper_request::State::NONE; frontend_request.state = ST::COMPLETE; frontend_request.cbe_request.success( _helper_read_request.cbe_request.success()); if (_verbose) { Genode::log("Complete unaligned READ request: ", " (frontend request: ", _frontend_request.cbe_request, " (helper request: ", _helper_read_request.cbe_request, " offset: ", _frontend_request.helper_offset, " count: ", _frontend_request.count, ")"); } } if (frontend_request.cbe_request.write()) { /* copy whole block first */ { char * dst = reinterpret_cast (&_helper_write_request.block_data); char const * src = reinterpret_cast (&_helper_read_request.block_data); Genode::memcpy(dst, src, sizeof (Cbe::Block_data)); } /* and than actual request data */ { char * dst = reinterpret_cast (&_helper_write_request.block_data) + frontend_request.helper_offset; char const * src = reinterpret_cast (frontend_request.cbe_request.offset()); Genode::memcpy(dst, src, _frontend_request.count); } /* re-use request */ _helper_write_request.cbe_request = Cbe::Request( Cbe::Request::Operation::WRITE, false, _helper_read_request.cbe_request.block_number(), (uint64_t) &_helper_write_request.block_data, _helper_read_request.cbe_request.count(), _helper_read_request.cbe_request.key_id(), _helper_read_request.cbe_request.tag()); _helper_write_request.state = Helper_request::State::PENDING; _helper_read_request.state = Helper_request::State::NONE; } progress = true; } if (_helper_write_request.complete()) { if (_verbose) { Genode::log("Complete unaligned WRITE request: ", " (frontend request: ", _frontend_request.cbe_request, " (helper request: ", _helper_read_request.cbe_request, " offset: ", _frontend_request.helper_offset, " count: ", _frontend_request.count, ")"); } _helper_write_request.state = Helper_request::State::NONE; frontend_request.state = ST::COMPLETE; progress = true; } /* read */ { struct Read_data_pointer_is_null : Genode::Exception { }; struct Front_end_read_request_should_be_in_progress : Genode::Exception { }; Cbe::Request cbe_req { }; uint64_t vba { 0 }; Cbe::Crypto_plain_buffer::Index plain_buf_idx { 0 }; _cbe->client_transfer_read_data_required( cbe_req, vba, plain_buf_idx); if (cbe_req.valid()) { Cbe::Block_data *data { nullptr }; if (_helper_read_request.in_progress()) { data = reinterpret_cast( &_helper_read_request.block_data); } else { if (_frontend_request.in_progress()) { // XXX check after helper request because it will be IN_PROGRESS // in case helper request is used uint64_t buf_base { cbe_req.offset() }; uint64_t blk_off { vba - cbe_req.block_number() }; data = reinterpret_cast( buf_base + (blk_off * Cbe::BLOCK_SIZE)); } else { throw Front_end_read_request_should_be_in_progress(); } } if (data == nullptr) { throw Read_data_pointer_is_null(); } Genode::memcpy( data, &_plain_data.item(plain_buf_idx), sizeof (Cbe::Block_data)); _cbe->client_transfer_read_data_in_progress( plain_buf_idx); _cbe->client_transfer_read_data_completed( plain_buf_idx, true); progress = true; } } /* write */ { struct Write_data_pointer_is_null : Genode::Exception { }; struct Front_end_write_request_should_be_in_progress : Genode::Exception { }; Cbe::Request cbe_req { }; uint64_t vba { 0 }; Cbe::Crypto_plain_buffer::Index plain_buf_idx { 0 }; _cbe->client_transfer_write_data_required( cbe_req, vba, plain_buf_idx); if (cbe_req.valid()) { Cbe::Block_data *data { nullptr }; if (_helper_write_request.in_progress()) { data = reinterpret_cast( &_helper_write_request.block_data); } else { if (_frontend_request.in_progress()) { // XXX check after helper request because it will be IN_PROGRESS // in case helper request is used uint64_t buf_base { cbe_req.offset() }; uint64_t blk_off { vba - cbe_req.block_number() }; data = reinterpret_cast( buf_base + (blk_off * Cbe::BLOCK_SIZE)); } else { throw Front_end_write_request_should_be_in_progress(); } } if (data == nullptr) { throw Write_data_pointer_is_null(); } Genode::memcpy( &_plain_data.item(plain_buf_idx), data, sizeof (Cbe::Block_data)); _cbe->client_transfer_write_data_in_progress( plain_buf_idx); _cbe->client_transfer_write_data_completed( plain_buf_idx, true); progress = true; } } return progress; } bool _handle_ta(Cbe::Library &cbe) { bool progress = false; Util::Trust_anchor_vfs &ta = *_trust_anchor; progress |= ta.execute(); using Op = Cbe::Trust_anchor_request::Operation; while (true) { Cbe::Trust_anchor_request const request = _cbe->peek_generated_ta_request(); if (!request.valid()) { break; } if (!ta.request_acceptable()) { break; } switch (request.operation()) { case Op::CREATE_KEY: ta.submit_create_key_request(request); break; case Op::SECURE_SUPERBLOCK: { Cbe::Hash const sb_hash = _cbe->peek_generated_ta_sb_hash(request); ta.submit_secure_superblock_request(request, sb_hash); break; } case Op::ENCRYPT_KEY: { Cbe::Key_plaintext_value const pk = _cbe->peek_generated_ta_key_value_plaintext(request); ta.submit_encrypt_key_request(request, pk); break; } case Op::DECRYPT_KEY: { Cbe::Key_ciphertext_value const ck = _cbe->peek_generated_ta_key_value_ciphertext(request); ta.submit_decrypt_key_request(request, ck); break; } case Op::LAST_SB_HASH: ta.submit_superblock_hash_request(request); break; case Op::INITIALIZE: class Bad_operation { }; throw Bad_operation { }; case Op::INVALID: /* never reached */ break; } _cbe->drop_generated_ta_request(request); progress |= true; } while (true) { Cbe::Trust_anchor_request const request = ta.peek_completed_request(); if (!request.valid()) { break; } switch (request.operation()) { case Op::CREATE_KEY: { Cbe::Key_plaintext_value const pk = ta.peek_completed_key_value_plaintext(request); _cbe->mark_generated_ta_create_key_request_complete(request, pk); break; } case Op::SECURE_SUPERBLOCK: { _cbe->mark_generated_ta_secure_sb_request_complete(request); break; } case Op::ENCRYPT_KEY: { Cbe::Key_ciphertext_value const ck = ta.peek_completed_key_value_ciphertext(request); _cbe->mark_generated_ta_encrypt_key_request_complete(request, ck); break; } case Op::DECRYPT_KEY: { Cbe::Key_plaintext_value const pk = ta.peek_completed_key_value_plaintext(request); _cbe->mark_generated_ta_decrypt_key_request_complete(request, pk); break; } case Op::LAST_SB_HASH: { Cbe::Hash const hash = ta.peek_completed_superblock_hash(request); _cbe->mark_generated_ta_last_sb_hash_request_complete(request, hash); break; } case Op::INITIALIZE: class Bad_operation { }; throw Bad_operation { }; case Op::INVALID: /* never reached */ break; } ta.drop_completed_request(request); progress |= true; } return progress; } bool _handle_crypto_add_key() { bool progress = false; do { Cbe::Key key { }; Cbe::Request request = _cbe->crypto_add_key_required(key); if (!request.valid()) { break; } char buffer[sizeof (key.value) + sizeof (key.id.value)] { }; memcpy(buffer, &key.id.value, sizeof (key.id.value)); memcpy(buffer + sizeof (key.id.value), key.value, sizeof (key.value)); file_size written = 0; _add_key_handle->seek(0); try { _add_key_handle->fs().write(_add_key_handle, buffer, sizeof (buffer), written); (void)written; } catch (Vfs::File_io_service::Insufficient_buffer) { /* try again later */ break; } /* * Instead of acknowledge the CBE's request before we write * the key above do it afterwards. That allows us to perform the * request multiple times in case the above exception is thrown. */ _cbe->crypto_add_key_requested(request); uint32_t const key_id_value = key.id.value; Crypto_file *cf = nullptr; try { cf = _get_unused_crypto_file(); } catch (...) { error("cannot manage key id: ", key_id_value); request.success(false); _cbe->crypto_add_key_completed(request); continue; } using Encrypt_file = Genode::String<128>; Encrypt_file encrypt_file { _crypto_device.string(), "/keys/", key_id_value, "/encrypt" }; using Result = Vfs::Directory_service::Open_result; Result res = _env.root_dir().open(encrypt_file.string(), Vfs::Directory_service::OPEN_MODE_RDWR, (Vfs::Vfs_handle **)&cf->encrypt_handle, _env.alloc()); request.success(res == Result::OPEN_OK); if (!request.success()) { error("could not open encrypt '", encrypt_file, "' file for key id: ", key_id_value); request.success(false); _cbe->crypto_add_key_completed(request); continue; } using Decrypt_file = Genode::String<128>; Decrypt_file decrypt_file { _crypto_device.string(), "/keys/", key_id_value, "/decrypt" }; res = _env.root_dir().open(decrypt_file.string(), Vfs::Directory_service::OPEN_MODE_RDWR, (Vfs::Vfs_handle **)&cf->decrypt_handle, _env.alloc()); request.success(res == Result::OPEN_OK); if (!request.success()) { _env.root_dir().close(cf->encrypt_handle); error("could not open decrypt '", decrypt_file, "' file for key id: ", key_id_value); request.success(false); _cbe->crypto_add_key_completed(request); continue; } /* set key id to make file valid */ cf->key_id = key_id_value; cf->encrypt_handle->handler(&_backend_io_response_handler); cf->decrypt_handle->handler(&_backend_io_response_handler); request.success(true); _cbe->crypto_add_key_completed(request); progress |= true; } while (false); return progress; } bool _handle_crypto_remove_key() { bool progress = false; do { Cbe::Key::Id key_id { }; Cbe::Request request = _cbe->crypto_remove_key_required(key_id); if (!request.valid()) { break; } file_size written = 0; _remove_key_handle->seek(0); try { _remove_key_handle->fs().write(_remove_key_handle, (char const*)&key_id.value, sizeof (key_id.value), written); (void)written; } catch (Vfs::File_io_service::Insufficient_buffer) { /* try again later */ break; } Crypto_file *cf = nullptr; try { cf = _lookup_crypto_file(key_id.value); _env.root_dir().close(cf->encrypt_handle); cf->encrypt_handle = nullptr; _env.root_dir().close(cf->decrypt_handle); cf->decrypt_handle = nullptr; cf->key_id = 0; } catch (...) { uint32_t const key_id_value = key_id.value; Genode::warning("could not look up handles for key id: ", key_id_value); } /* * Instead of acknowledge the CBE's request before we write * the key do it afterwards. That allows us to perform the * request multiple times in case the above exception is thrown. */ _cbe->crypto_remove_key_requested(request); request.success(true); _cbe->crypto_remove_key_completed(request); progress |= true; } while (false); return progress; } struct Crypto_job { struct Invalid_operation : Genode::Exception { }; enum State { IDLE, SUBMITTED, PENDING, IN_PROGRESS, COMPLETE }; enum Operation { INVALID, DECRYPT, ENCRYPT }; Crypto_file *file; Vfs_handle *_handle; State state; Operation op; uint32_t data_index; file_offset offset; Cbe::Crypto_cipher_buffer::Index cipher_index; Cbe::Crypto_plain_buffer::Index plain_index; static bool _read_queued(Vfs::File_io_service::Read_result r) { using Result = Vfs::File_io_service::Read_result; switch (r) { case Result::READ_QUEUED: [[fallthrough]]; case Result::READ_ERR_INTERRUPT: [[fallthrough]]; case Result::READ_ERR_AGAIN: [[fallthrough]]; case Result::READ_ERR_WOULD_BLOCK: return true; default: break; } return false; } struct Result { bool progress; bool complete; bool success; }; bool request_acceptable() const { return state == State::IDLE; } template void submit_request(Crypto_file *cf, uint32_t data_index, file_offset offset) { file = cf; state = Crypto_job::State::SUBMITTED; op = OP; data_index = data_index; offset = offset; /* store both in regardless of operation */ cipher_index.value = data_index; plain_index.value = data_index; switch (op) { case Crypto_job::Operation::ENCRYPT: _handle = cf->encrypt_handle; break; case Crypto_job::Operation::DECRYPT: _handle = cf->decrypt_handle; break; case Crypto_job::Operation::INVALID: throw Invalid_operation(); break; } } Result execute(Cbe::Library &cbe, Cbe::Crypto_cipher_buffer &cipher, Cbe::Crypto_plain_buffer &plain) { Result result { false, false, false }; switch (state) { case Crypto_job::State::IDLE: break; case Crypto_job::State::SUBMITTED: try { char const *data = nullptr; if (op == Crypto_job::Operation::ENCRYPT) { data = reinterpret_cast(&plain.item(plain_index)); } else if (op == Crypto_job::Operation::DECRYPT) { data = reinterpret_cast(&cipher.item(cipher_index)); } file_size out = 0; _handle->seek(offset); _handle->fs().write(_handle, data, file_size(sizeof (Cbe::Block_data)), out); if (op == Crypto_job::Operation::ENCRYPT) { cbe.crypto_cipher_data_requested(plain_index); } else if (op == Crypto_job::Operation::DECRYPT) { cbe.crypto_plain_data_requested(cipher_index); } state = Crypto_job::State::PENDING; result.progress |= true; } catch (Vfs::File_io_service::Insufficient_buffer) { } [[fallthrough]]; case Crypto_job::State::PENDING: _handle->seek(offset); if (!_handle->fs().queue_read(_handle, sizeof (Cbe::Block_data))) { break; } state = Crypto_job::State::IN_PROGRESS; result.progress |= true; [[fallthrough]]; case Crypto_job::State::IN_PROGRESS: { using Result = Vfs::File_io_service::Read_result; file_size out = 0; char *data = nullptr; if (op == Crypto_job::Operation::ENCRYPT) { data = reinterpret_cast(&cipher.item(cipher_index)); } else if (op == Crypto_job::Operation::DECRYPT) { data = reinterpret_cast(&plain.item(plain_index)); } Result const res = _handle->fs().complete_read(_handle, data, sizeof (Cbe::Block_data), out); if (_read_queued(res)) { break; } result.success = res == Result::READ_OK; state = Crypto_job::State::COMPLETE; result.progress |= true; [[fallthrough]]; } case Crypto_job::State::COMPLETE: if (op == Crypto_job::Operation::ENCRYPT) { if (!result.success) { error("encryption request failed"); // XXX be more informative } cbe.supply_crypto_cipher_data(cipher_index, result.success); } else if (op == Crypto_job::Operation::DECRYPT) { if (!result.success) { error("decryption request failed"); // XXX be more informative } cbe.supply_crypto_plain_data(plain_index, result.success); } state = Crypto_job::State::IDLE; result.complete |= true; result.progress |= true; break; } return result; } }; Crypto_job _crypto_job { nullptr, nullptr, Crypto_job::State::IDLE, Crypto_job::Operation::INVALID, 0 , 0, Cbe::Crypto_cipher_buffer::Index { 0 }, Cbe::Crypto_plain_buffer::Index { 0 } }; // XXX make that more than 1 job bool _handle_crypto_request(Cbe::Library &cbe, Cbe::Crypto_cipher_buffer &cipher, Cbe::Crypto_plain_buffer &plain) { bool progress = false; /* encrypt */ while (true) { Cbe::Crypto_plain_buffer::Index data_index { 0 }; Cbe::Request request = cbe.crypto_cipher_data_required(data_index); if (!request.valid() || !_crypto_job.request_acceptable()) { break; } Crypto_file *cf = nullptr; try { cf = _lookup_crypto_file(request.key_id()); } catch (...) { cbe.crypto_cipher_data_requested(data_index); Cbe::Crypto_cipher_buffer::Index const index { data_index.value }; cbe.supply_crypto_cipher_data(index, false); continue; } using Op = Crypto_job::Operation; file_offset const offset = request.block_number() * Cbe::BLOCK_SIZE; _crypto_job.submit_request(cf, data_index.value, offset); progress |= true; } /* decrypt */ while (true) { Cbe::Crypto_cipher_buffer::Index data_index { 0 }; Cbe::Request request = cbe.crypto_plain_data_required(data_index); if (!request.valid() || !_crypto_job.request_acceptable()) { break; } Crypto_file *cf = nullptr; try { cf = _lookup_crypto_file(request.key_id()); } catch (...) { cbe.crypto_plain_data_requested(data_index); Cbe::Crypto_plain_buffer::Index const index { data_index.value }; cbe.supply_crypto_plain_data(index, false); continue; } using Op = Crypto_job::Operation; file_offset const offset = request.block_number() * Cbe::BLOCK_SIZE; _crypto_job.submit_request(cf, data_index.value, offset); progress |= true; } Crypto_job::Result const result = _crypto_job.execute(cbe, cipher, plain); progress |= result.progress; return progress; } bool _handle_crypto() { bool progress = false; bool const add_key_progress = _handle_crypto_add_key(); progress |= add_key_progress; bool const remove_key_progress = _handle_crypto_remove_key(); progress |= remove_key_progress; bool const request_progress = _handle_crypto_request(*_cbe, _cipher_data, _plain_data); progress |= request_progress; return progress; } void _dump_state() { if (_debug) { static uint64_t cnt = 0; log("FE: ", Frontend_request::state_to_string(_frontend_request.state), " (", _frontend_request.cbe_request, ") ", "BE: ", *_backend_job, " ", ++cnt); } } void handle_frontend_request() { while (true) { bool progress = false; bool const frontend_progress = _handle_cbe_frontend(*_cbe, _frontend_request); progress |= frontend_progress; bool const backend_progress = _handle_cbe_backend(*_cbe, _io_data); progress |= backend_progress; bool const crypto_progress = _handle_crypto(); progress |= crypto_progress; bool const ta_progress = _handle_ta(*_cbe); progress |= ta_progress; if (!progress) { _dump_state(); } if (_debug) { log("frontend_progress: ", frontend_progress, " backend_progress: ", backend_progress, " crypto_progress: ", crypto_progress); } if (!progress) { break; } } Cbe::Info const info = _cbe->info(); using ES = Extending::State; if (_extend_obj.state == ES::UNKNOWN && info.valid) { if (info.extending_ft) { _extend_obj.state = ES::IN_PROGRESS; _extend_obj.type = Extending::Type::FT; _extend_fs_trigger_watch_response(); } else if (info.extending_vbd) { _extend_obj.state = ES::IN_PROGRESS; _extend_obj.type = Extending::Type::VBD; _extend_fs_trigger_watch_response(); } else { _extend_obj.state = ES::IDLE; _extend_fs_trigger_watch_response(); } } using RS = Rekeying::State; if (_rekey_obj.state == RS::UNKNOWN && info.valid) { _rekey_obj.state = info.rekeying ? RS::IN_PROGRESS : RS::IDLE; _rekey_fs_trigger_watch_response(); } } bool client_request_acceptable() const { return _cbe->client_request_acceptable(); } bool start_rekeying() { if (!_cbe->client_request_acceptable()) { return false; } Cbe::Request req( Cbe::Request::Operation::REKEY, false, 0, 0, 0, _rekey_obj.key_id, 0); if (_verbose) { Genode::log("Req: (background req: ", req, ")"); } _cbe->submit_client_request(req, 0); _rekey_obj.state = Rekeying::State::IN_PROGRESS; _rekey_obj.last_result = Rekeying::Rekeying::FAILED; _rekey_fs_trigger_watch_response(); // XXX kick-off rekeying handle_frontend_request(); return true; } Rekeying const rekeying_progress() const { return _rekey_obj; } bool start_deinitialize() { if (!_cbe->client_request_acceptable()) { return false; } Cbe::Request req( Cbe::Request::Operation::DEINITIALIZE, false, 0, 0, 0, 0, 0); if (_verbose) { Genode::log("Req: (background req: ", req, ")"); } _cbe->submit_client_request(req, 0); _deinit_obj.state = Deinitialize::State::IN_PROGRESS; _deinit_obj.last_result = Deinitialize::Deinitialize::FAILED; _deinit_fs_trigger_watch_response(); // XXX kick-off deinitialize handle_frontend_request(); return true; } Deinitialize const deinitialize_progress() const { return _deinit_obj; } bool start_extending(Extending::Type type, Cbe::Number_of_blocks blocks) { if (!_cbe->client_request_acceptable()) { return false; } Cbe::Request::Operation op = Cbe::Request::Operation::INVALID; switch (type) { case Extending::Type::VBD: op = Cbe::Request::Operation::EXTEND_VBD; break; case Extending::Type::FT: op = Cbe::Request::Operation::EXTEND_FT; break; case Extending::Type::INVALID: return false; } Cbe::Request req(op, false, 0, 0, blocks, 0, 0); if (_verbose) { Genode::log("Req: (background req: ", req, ")"); } _cbe->submit_client_request(req, 0); _extend_obj.type = type; _extend_obj.state = Extending::State::IN_PROGRESS; _extend_obj.last_result = Extending::Result::NONE; _extend_fs_trigger_watch_response(); // XXX kick-off extending handle_frontend_request(); return true; } Extending const extending_progress() const { return _extend_obj; } void active_snapshot_ids(Cbe::Active_snapshot_ids &ids) { if (!_cbe.constructed()) { _initialize_cbe(); } _cbe->active_snapshot_ids(ids); handle_frontend_request(); } Frontend_request _create_snapshot_request { }; bool create_snapshot() { if (!_cbe.constructed()) { _initialize_cbe(); } if (!_cbe->client_request_acceptable()) { return false; } if (_create_snapshot_request.cbe_request.valid()) { return false; } Cbe::Request::Operation const op = Cbe::Request::Operation::CREATE_SNAPSHOT; _create_snapshot_request.cbe_request = Cbe::Request(op, false, 0, 0, 1, 0, 0); if (_verbose) { Genode::log("Req: (req: ", _create_snapshot_request.cbe_request, ")"); } _cbe->submit_client_request(_create_snapshot_request.cbe_request, 0); _create_snapshot_request.state = Frontend_request::State::IN_PROGRESS; // XXX kick-off snapshot creation request handle_frontend_request(); return true; } Frontend_request _discard_snapshot_request { }; bool discard_snapshot(Cbe::Generation id) { if (!_cbe.constructed()) { _initialize_cbe(); } if (!_cbe->client_request_acceptable()) { return false; } if (_discard_snapshot_request.cbe_request.valid()) { return false; } Cbe::Request::Operation const op = Cbe::Request::Operation::DISCARD_SNAPSHOT; _discard_snapshot_request.cbe_request = Cbe::Request(op, false, 0, 0, 1, 0, 0); if (_verbose) { Genode::log("Req: (req: ", _discard_snapshot_request.cbe_request, ")"); } _cbe->submit_client_request(_discard_snapshot_request.cbe_request, id); _discard_snapshot_request.state = Frontend_request::State::IN_PROGRESS; // XXX kick-off snapshot creation request handle_frontend_request(); return true; } Genode::Mutex _frontend_mtx { }; Genode::Mutex &frontend_mtx() { return _frontend_mtx; } }; class Vfs_cbe::Data_file_system : public Single_file_system { private: Wrapper &_w; uint32_t _snap_id; public: struct Vfs_handle : Single_vfs_handle { Wrapper &_w; uint32_t _snap_id { 0 }; Vfs_handle(Directory_service &ds, File_io_service &fs, Genode::Allocator &alloc, Wrapper &w, uint32_t snap_id) : Single_vfs_handle(ds, fs, alloc, 0), _w(w), _snap_id(snap_id) { } Read_result read(char *dst, file_size count, file_size &out_count) override { Genode::Mutex::Guard guard { _w.frontend_mtx() }; using State = Wrapper::Frontend_request::State; State state = _w.frontend_request().state; if (state == State::NONE) { if (!_w.client_request_acceptable()) { return READ_QUEUED; } using Op = Cbe::Request::Operation; bool const accepted = _w.submit_frontend_request(*this, dst, count, Op::READ, _snap_id); if (!accepted) { return READ_ERR_IO; } } _w.handle_frontend_request(); state = _w.frontend_request().state; if ( state == State::PENDING || state == State::IN_PROGRESS) { _w.enqueue_handle(*this); return READ_QUEUED; } if (state == State::COMPLETE) { out_count = _w.frontend_request().count; _w.ack_frontend_request(*this); return READ_OK; } if (state == State::ERROR_EOF) { out_count = 0; _w.ack_frontend_request(*this); return READ_OK; } if (state == State::ERROR) { out_count = 0; _w.ack_frontend_request(*this); return READ_ERR_IO; } return READ_ERR_IO; } Write_result write(char const *src, file_size count, file_size &out_count) override { Genode::Mutex::Guard guard { _w.frontend_mtx() }; using State = Wrapper::Frontend_request::State; State state = _w.frontend_request().state; if (state == State::NONE) { if (!_w.client_request_acceptable()) { throw Insufficient_buffer(); } using Op = Cbe::Request::Operation; bool const accepted = _w.submit_frontend_request(*this, const_cast(src), count, Op::WRITE, _snap_id); if (!accepted) { return WRITE_ERR_IO; } } _w.handle_frontend_request(); state = _w.frontend_request().state; if ( state == State::PENDING || state == State::IN_PROGRESS) { _w.enqueue_handle(*this); throw Insufficient_buffer(); } if (state == State::COMPLETE) { out_count = _w.frontend_request().count; _w.ack_frontend_request(*this); return WRITE_OK; } if (state == State::ERROR_EOF) { out_count = 0; _w.ack_frontend_request(*this); return WRITE_OK; } if (state == State::ERROR) { out_count = 0; _w.ack_frontend_request(*this); return WRITE_ERR_IO; } return WRITE_ERR_IO; } Sync_result sync() override { Genode::Mutex::Guard guard { _w.frontend_mtx() }; using State = Wrapper::Frontend_request::State; State state = _w.frontend_request().state; if (state == State::NONE) { if (!_w.client_request_acceptable()) { return SYNC_QUEUED; } using Op = Cbe::Request::Operation; bool const accepted = _w.submit_frontend_request(*this, nullptr, 0, Op::SYNC, 0); if (!accepted) { return SYNC_ERR_INVALID; } } _w.handle_frontend_request(); state = _w.frontend_request().state; if ( state == State::PENDING || state == State::IN_PROGRESS) { _w.enqueue_handle(*this); return SYNC_QUEUED; } if (state == State::COMPLETE) { _w.ack_frontend_request(*this); return SYNC_OK; } if (state == State::ERROR) { _w.ack_frontend_request(*this); return SYNC_ERR_INVALID; } return SYNC_ERR_INVALID; } bool read_ready() override { return true; } }; Data_file_system(Wrapper &w, uint32_t snap_id) : Single_file_system(Node_type::CONTINUOUS_FILE, type_name(), Node_rwx::rw(), Xml_node("")), _w(w), _snap_id(snap_id) { } ~Data_file_system() { /* XXX sync on close */ } /********************************* ** Directory-service interface ** *********************************/ Stat_result stat(char const *path, Stat &out) override { try { (void)_w.cbe(); } catch (...) { return STAT_ERR_NO_ENTRY; } Stat_result result = Single_file_system::stat(path, out); /* max_vba range is from 0 ... N - 1 */ out.size = (_w.cbe().max_vba() + 1) * Cbe::BLOCK_SIZE; return result; } /******************************** ** File I/O service interface ** ********************************/ Ftruncate_result ftruncate(Vfs::Vfs_handle *handle, file_size) override { return FTRUNCATE_OK; } /*************************** ** File-system interface ** ***************************/ Open_result open(char const *path, unsigned, Vfs::Vfs_handle **out_handle, Allocator &alloc) override { if (!_single_file(path)) return OPEN_ERR_UNACCESSIBLE; try { (void)_w.cbe(); } catch (...) { return OPEN_ERR_UNACCESSIBLE; } *out_handle = new (alloc) Vfs_handle(*this, *this, alloc, _w, _snap_id); return OPEN_OK; } static char const *type_name() { return "data"; } char const *type() override { return type_name(); } }; class Vfs_cbe::Extend_file_system : public Vfs::Single_file_system { private: typedef Registered Registered_watch_handle; typedef Registry Watch_handle_registry; Watch_handle_registry _handle_registry { }; Wrapper &_w; using Content_string = String<32>; static Content_string content_string(Wrapper const &wrapper) { Wrapper::Extending const & extending_progress { wrapper.extending_progress() }; bool const in_progress { extending_progress.state == Wrapper::Extending::State::IN_PROGRESS }; bool const last_result { !in_progress && extending_progress.last_result != Wrapper::Extending::Result::NONE }; bool const success { extending_progress.last_result == Wrapper::Extending::Result::SUCCESS }; Content_string const result { Wrapper::Extending::state_to_cstring(extending_progress.state), " last-result:", last_result ? success ? "success" : "failed" : "none", "\n" }; return result; } struct Vfs_handle : Single_vfs_handle { Wrapper &_w; Vfs_handle(Directory_service &ds, File_io_service &fs, Genode::Allocator &alloc, Wrapper &w) : Single_vfs_handle(ds, fs, alloc, 0), _w(w) { } Read_result read(char *dst, file_size count, file_size &out_count) override { if (seek() != 0) { out_count = 0; return READ_OK; } Content_string const result { content_string(_w) }; copy_cstring(dst, result.string(), count); size_t const length_without_nul = result.length() - 1; out_count = count > length_without_nul - 1 ? length_without_nul : count; return READ_OK; } Write_result write(char const *src, file_size count, file_size &out_count) override { using Type = Wrapper::Extending::Type; using State = Wrapper::Extending::State; if (_w.extending_progress().state != State::IDLE) { return WRITE_ERR_IO; } char tree[16]; Arg_string::find_arg(src, "tree").string(tree, sizeof (tree), "-"); Type type = Wrapper::Extending::string_to_type(tree); if (type == Type::INVALID) { return WRITE_ERR_IO; } unsigned long blocks = Arg_string::find_arg(src, "blocks").ulong_value(0); if (blocks == 0) { return WRITE_ERR_IO; } bool const okay = _w.start_extending(type, blocks); if (!okay) { return WRITE_ERR_IO; } out_count = count; return WRITE_OK; } bool read_ready() override { return true; } }; public: Extend_file_system(Wrapper &w) : Single_file_system(Node_type::TRANSACTIONAL_FILE, type_name(), Node_rwx::rw(), Xml_node("")), _w(w) { _w.manage_extend_file_system(*this); } ~Extend_file_system() { _w.dissolve_extend_file_system(*this); } static char const *type_name() { return "extend"; } char const *type() override { return type_name(); } void trigger_watch_response() { _handle_registry.for_each([this] (Registered_watch_handle &handle) { handle.watch_response(); }); } Watch_result watch(char const *path, Vfs_watch_handle **handle, Allocator &alloc) override { if (!_single_file(path)) return WATCH_ERR_UNACCESSIBLE; try { *handle = new (alloc) Registered_watch_handle(_handle_registry, *this, alloc); return WATCH_OK; } catch (Out_of_ram) { return WATCH_ERR_OUT_OF_RAM; } catch (Out_of_caps) { return WATCH_ERR_OUT_OF_CAPS; } } void close(Vfs_watch_handle *handle) override { destroy(handle->alloc(), static_cast(handle)); } /********************************* ** Directory-service interface ** *********************************/ Open_result open(char const *path, unsigned, Vfs::Vfs_handle **out_handle, Genode::Allocator &alloc) override { if (!_single_file(path)) return OPEN_ERR_UNACCESSIBLE; try { *out_handle = new (alloc) Vfs_handle(*this, *this, alloc, _w); return OPEN_OK; } catch (Genode::Out_of_ram) { return OPEN_ERR_OUT_OF_RAM; } catch (Genode::Out_of_caps) { return OPEN_ERR_OUT_OF_CAPS; } } Stat_result stat(char const *path, Stat &out) override { Stat_result result = Single_file_system::stat(path, out); out.size = content_string(_w).length() - 1; return result; } /******************************** ** File I/O service interface ** ********************************/ Ftruncate_result ftruncate(Vfs::Vfs_handle *handle, file_size) override { return FTRUNCATE_OK; } }; class Vfs_cbe::Rekey_file_system : public Vfs::Single_file_system { private: typedef Registered Registered_watch_handle; typedef Registry Watch_handle_registry; Watch_handle_registry _handle_registry { }; Wrapper &_w; using Content_string = String<32>; static Content_string content_string(Wrapper const &wrapper) { Wrapper::Rekeying const & rekeying_progress { wrapper.rekeying_progress() }; bool const in_progress { rekeying_progress.state == Wrapper::Rekeying::State::IN_PROGRESS }; bool const last_result { !in_progress && rekeying_progress.last_result != Wrapper::Rekeying::Result::NONE }; bool const success { rekeying_progress.last_result == Wrapper::Rekeying::Result::SUCCESS }; Content_string const result { Wrapper::Rekeying::state_to_cstring(rekeying_progress.state), " last-result:", last_result ? success ? "success" : "failed" : "none", "\n" }; return result; } struct Vfs_handle : Single_vfs_handle { Wrapper &_w; Vfs_handle(Directory_service &ds, File_io_service &fs, Genode::Allocator &alloc, Wrapper &w) : Single_vfs_handle(ds, fs, alloc, 0), _w(w) { } Read_result read(char *dst, file_size count, file_size &out_count) override { if (seek() != 0) { out_count = 0; return READ_OK; } Content_string const result { content_string(_w) }; copy_cstring(dst, result.string(), count); size_t const length_without_nul = result.length() - 1; out_count = count > length_without_nul - 1 ? length_without_nul : count; return READ_OK; } Write_result write(char const *src, file_size count, file_size &out_count) override { using State = Wrapper::Rekeying::State; if (_w.rekeying_progress().state != State::IDLE) { return WRITE_ERR_IO; } bool start_rekeying { false }; Genode::ascii_to(src, start_rekeying); if (!start_rekeying) { return WRITE_ERR_IO; } if (!_w.start_rekeying()) { return WRITE_ERR_IO; } out_count = count; return WRITE_OK; } bool read_ready() override { return true; } }; public: Rekey_file_system(Wrapper &w) : Single_file_system(Node_type::TRANSACTIONAL_FILE, type_name(), Node_rwx::rw(), Xml_node("")), _w(w) { _w.manage_rekey_file_system(*this); } ~Rekey_file_system() { _w.dissolve_rekey_file_system(*this); } static char const *type_name() { return "rekey"; } char const *type() override { return type_name(); } void trigger_watch_response() { _handle_registry.for_each([this] (Registered_watch_handle &handle) { handle.watch_response(); }); } Watch_result watch(char const *path, Vfs_watch_handle **handle, Allocator &alloc) override { if (!_single_file(path)) return WATCH_ERR_UNACCESSIBLE; try { *handle = new (alloc) Registered_watch_handle(_handle_registry, *this, alloc); return WATCH_OK; } catch (Out_of_ram) { return WATCH_ERR_OUT_OF_RAM; } catch (Out_of_caps) { return WATCH_ERR_OUT_OF_CAPS; } } void close(Vfs_watch_handle *handle) override { destroy(handle->alloc(), static_cast(handle)); } /********************************* ** Directory-service interface ** *********************************/ Open_result open(char const *path, unsigned, Vfs::Vfs_handle **out_handle, Genode::Allocator &alloc) override { if (!_single_file(path)) return OPEN_ERR_UNACCESSIBLE; try { *out_handle = new (alloc) Vfs_handle(*this, *this, alloc, _w); return OPEN_OK; } catch (Genode::Out_of_ram) { return OPEN_ERR_OUT_OF_RAM; } catch (Genode::Out_of_caps) { return OPEN_ERR_OUT_OF_CAPS; } } Stat_result stat(char const *path, Stat &out) override { Stat_result result = Single_file_system::stat(path, out); out.size = content_string(_w).length() - 1; return result; } /******************************** ** File I/O service interface ** ********************************/ Ftruncate_result ftruncate(Vfs::Vfs_handle *handle, file_size) override { return FTRUNCATE_OK; } }; class Vfs_cbe::Deinitialize_file_system : public Vfs::Single_file_system { private: typedef Registered Registered_watch_handle; typedef Registry Watch_handle_registry; Watch_handle_registry _handle_registry { }; Wrapper &_w; using Content_string = String<32>; static Content_string content_string(Wrapper const &wrapper) { Wrapper::Deinitialize const & deinitialize_progress { wrapper.deinitialize_progress() }; bool const in_progress { deinitialize_progress.state == Wrapper::Deinitialize::State::IN_PROGRESS }; bool const last_result { !in_progress && deinitialize_progress.last_result != Wrapper::Deinitialize::Result::NONE }; bool const success { deinitialize_progress.last_result == Wrapper::Deinitialize::Result::SUCCESS }; Content_string const result { Wrapper::Deinitialize::state_to_cstring(deinitialize_progress.state), " last-result:", last_result ? success ? "success" : "failed" : "none", "\n" }; return result; } struct Vfs_handle : Single_vfs_handle { Wrapper &_w; Vfs_handle(Directory_service &ds, File_io_service &fs, Genode::Allocator &alloc, Wrapper &w) : Single_vfs_handle(ds, fs, alloc, 0), _w(w) { } Read_result read(char *dst, file_size count, file_size &out_count) override { if (seek() != 0) { out_count = 0; return READ_OK; } Content_string const result { content_string(_w) }; copy_cstring(dst, result.string(), count); size_t const length_without_nul = result.length() - 1; out_count = count > length_without_nul - 1 ? length_without_nul : count; return READ_OK; } Write_result write(char const *src, file_size count, file_size &out_count) override { using State = Wrapper::Deinitialize::State; if (_w.deinitialize_progress().state != State::IDLE) { return WRITE_ERR_IO; } bool start_deinitialize { false }; Genode::ascii_to(src, start_deinitialize); if (!start_deinitialize) { return WRITE_ERR_IO; } if (!_w.start_deinitialize()) { return WRITE_ERR_IO; } out_count = count; return WRITE_OK; } bool read_ready() override { return true; } }; public: Deinitialize_file_system(Wrapper &w) : Single_file_system(Node_type::TRANSACTIONAL_FILE, type_name(), Node_rwx::rw(), Xml_node("")), _w(w) { _w.manage_deinit_file_system(*this); } ~Deinitialize_file_system() { _w.dissolve_deinit_file_system(*this); } static char const *type_name() { return "deinitialize"; } char const *type() override { return type_name(); } void trigger_watch_response() { _handle_registry.for_each([this] (Registered_watch_handle &handle) { handle.watch_response(); }); } Watch_result watch(char const *path, Vfs_watch_handle **handle, Allocator &alloc) override { if (!_single_file(path)) return WATCH_ERR_UNACCESSIBLE; try { *handle = new (alloc) Registered_watch_handle(_handle_registry, *this, alloc); return WATCH_OK; } catch (Out_of_ram) { return WATCH_ERR_OUT_OF_RAM; } catch (Out_of_caps) { return WATCH_ERR_OUT_OF_CAPS; } } void close(Vfs_watch_handle *handle) override { destroy(handle->alloc(), static_cast(handle)); } /********************************* ** Directory-service interface ** *********************************/ Open_result open(char const *path, unsigned, Vfs::Vfs_handle **out_handle, Genode::Allocator &alloc) override { if (!_single_file(path)) return OPEN_ERR_UNACCESSIBLE; try { *out_handle = new (alloc) Vfs_handle(*this, *this, alloc, _w); return OPEN_OK; } catch (Genode::Out_of_ram) { return OPEN_ERR_OUT_OF_RAM; } catch (Genode::Out_of_caps) { return OPEN_ERR_OUT_OF_CAPS; } } Stat_result stat(char const *path, Stat &out) override { Stat_result result = Single_file_system::stat(path, out); out.size = content_string(_w).length() - 1; return result; } /******************************** ** File I/O service interface ** ********************************/ Ftruncate_result ftruncate(Vfs::Vfs_handle *handle, file_size) override { return FTRUNCATE_OK; } }; class Vfs_cbe::Create_snapshot_file_system : public Vfs::Single_file_system { private: Wrapper &_w; struct Vfs_handle : Single_vfs_handle { Wrapper &_w; Vfs_handle(Directory_service &ds, File_io_service &fs, Genode::Allocator &alloc, Wrapper &w) : Single_vfs_handle(ds, fs, alloc, 0), _w(w) { } Read_result read(char *dst, file_size count, file_size &out_count) override { return READ_ERR_IO; } Write_result write(char const *src, file_size count, file_size &out_count) override { bool create_snapshot { false }; Genode::ascii_to(src, create_snapshot); Genode::String<64> str(Genode::Cstring(src, count)); if (!create_snapshot) { return WRITE_ERR_IO; } if (!_w.create_snapshot()) { out_count = 0; return WRITE_OK; } out_count = count; return WRITE_OK; } bool read_ready() override { return true; } }; public: Create_snapshot_file_system(Wrapper &w) : Single_file_system(Node_type::TRANSACTIONAL_FILE, type_name(), Node_rwx::wo(), Xml_node("")), _w(w) { } static char const *type_name() { return "create_snapshot"; } char const *type() override { return type_name(); } /********************************* ** Directory-service interface ** *********************************/ Open_result open(char const *path, unsigned, Vfs::Vfs_handle **out_handle, Genode::Allocator &alloc) override { if (!_single_file(path)) return OPEN_ERR_UNACCESSIBLE; try { *out_handle = new (alloc) Vfs_handle(*this, *this, alloc, _w); return OPEN_OK; } catch (Genode::Out_of_ram) { return OPEN_ERR_OUT_OF_RAM; } catch (Genode::Out_of_caps) { return OPEN_ERR_OUT_OF_CAPS; } } Stat_result stat(char const *path, Stat &out) override { Stat_result result = Single_file_system::stat(path, out); return result; } /******************************** ** File I/O service interface ** ********************************/ Ftruncate_result ftruncate(Vfs::Vfs_handle *handle, file_size) override { return FTRUNCATE_OK; } }; class Vfs_cbe::Discard_snapshot_file_system : public Vfs::Single_file_system { private: Wrapper &_w; struct Vfs_handle : Single_vfs_handle { Wrapper &_w; Vfs_handle(Directory_service &ds, File_io_service &fs, Genode::Allocator &alloc, Wrapper &w) : Single_vfs_handle(ds, fs, alloc, 0), _w(w) { } Read_result read(char *, file_size, file_size &) override { return READ_ERR_IO; } Write_result write(char const *src, file_size count, file_size &out_count) override { out_count = 0; Genode::uint64_t id { 0 }; Genode::ascii_to(src, id); if (id == 0) { return WRITE_ERR_IO; } if (!_w.discard_snapshot(Cbe::Generation { id })) { out_count = 0; return WRITE_OK; } return WRITE_ERR_IO; } bool read_ready() override { return true; } }; public: Discard_snapshot_file_system(Wrapper &w) : Single_file_system(Node_type::TRANSACTIONAL_FILE, type_name(), Node_rwx::wo(), Xml_node("")), _w(w) { } static char const *type_name() { return "discard_snapshot"; } char const *type() override { return type_name(); } /********************************* ** Directory-service interface ** *********************************/ Open_result open(char const *path, unsigned, Vfs::Vfs_handle **out_handle, Genode::Allocator &alloc) override { if (!_single_file(path)) return OPEN_ERR_UNACCESSIBLE; try { *out_handle = new (alloc) Vfs_handle(*this, *this, alloc, _w); return OPEN_OK; } catch (Genode::Out_of_ram) { return OPEN_ERR_OUT_OF_RAM; } catch (Genode::Out_of_caps) { return OPEN_ERR_OUT_OF_CAPS; } } Stat_result stat(char const *path, Stat &out) override { Stat_result result = Single_file_system::stat(path, out); return result; } /******************************** ** File I/O service interface ** ********************************/ Ftruncate_result ftruncate(Vfs::Vfs_handle *handle, file_size) override { return FTRUNCATE_OK; } }; struct Vfs_cbe::Snapshot_local_factory : File_system_factory { Data_file_system _block_fs; Snapshot_local_factory(Vfs::Env &env, Wrapper &cbe, uint32_t snap_id) : _block_fs(cbe, snap_id) { } Vfs::File_system *create(Vfs::Env&, Xml_node node) override { if (node.has_type(Data_file_system::type_name())) return &_block_fs; return nullptr; } }; class Vfs_cbe::Snapshot_file_system : private Snapshot_local_factory, public Vfs::Dir_file_system { private: Genode::uint32_t _snap_id; typedef String<128> Config; static Config _config(Genode::uint32_t snap_id, bool readonly) { char buf[Config::capacity()] { }; Xml_generator xml(buf, sizeof(buf), "dir", [&] () { xml.attribute("name", !readonly ? String<16>("current") : String<16>(snap_id)); xml.node("data", [&] () { xml.attribute("readonly", readonly); }); }); return Config(Cstring(buf)); } public: Snapshot_file_system(Vfs::Env &vfs_env, Wrapper &cbe, Genode::uint32_t snap_id, bool readonly = false) : Snapshot_local_factory(vfs_env, cbe, snap_id), Vfs::Dir_file_system(vfs_env, Xml_node(_config(snap_id, readonly).string()), *this), _snap_id(snap_id) { } static char const *type_name() { return "snapshot"; } char const *type() override { return type_name(); } Genode::uint32_t snapshot_id() const { return _snap_id; } }; class Vfs_cbe::Snapshots_file_system : public Vfs::File_system { private: typedef Registered Registered_watch_handle; typedef Registry Watch_handle_registry; Watch_handle_registry _handle_registry { }; Vfs::Env &_vfs_env; bool _root_dir(char const *path) { return strcmp(path, "/snapshots") == 0; } bool _top_dir(char const *path) { return strcmp(path, "/") == 0; } struct Snapshot_registry { Genode::Allocator &_alloc; Wrapper &_wrapper; Snapshots_file_system &_snapshots_fs; uint32_t _number_of_snapshots { 0 }; Genode::Registry> _registry { }; struct Invalid_index : Genode::Exception { }; struct Invalid_path : Genode::Exception { }; Snapshot_registry(Genode::Allocator &alloc, Wrapper &wrapper, Snapshots_file_system &snapshots_fs) : _alloc { alloc }, _wrapper { wrapper }, _snapshots_fs { snapshots_fs } { } void update(Vfs::Env &vfs_env); uint32_t number_of_snapshots() const { return _number_of_snapshots; } Snapshot_file_system const &by_index(uint32_t idx) const { uint32_t i = 0; Snapshot_file_system const *fsp { nullptr }; auto lookup = [&] (Snapshot_file_system const &fs) { if (i == idx) { fsp = &fs; } ++i; }; _registry.for_each(lookup); if (fsp == nullptr) { throw Invalid_index(); } return *fsp; } Snapshot_file_system &_by_id(uint32_t id) { Snapshot_file_system *fsp { nullptr }; auto lookup = [&] (Snapshot_file_system &fs) { if (fs.snapshot_id() == id) { fsp = &fs; } }; _registry.for_each(lookup); if (fsp == nullptr) { throw Invalid_path(); } return *fsp; } Snapshot_file_system &by_path(char const *path) { if (!path) { throw Invalid_path(); } if (path[0] == '/') { path++; } uint32_t id { 0 }; Genode::ascii_to(path, id); return _by_id(id); } }; public: void update_snapshot_registry() { _snap_reg.update(_vfs_env); } void trigger_watch_response() { _handle_registry.for_each([this] (Registered_watch_handle &handle) { handle.watch_response(); }); } Watch_result watch(char const *path, Vfs_watch_handle **handle, Allocator &alloc) override { if (!_root_dir(path)) return WATCH_ERR_UNACCESSIBLE; try { *handle = new (alloc) Registered_watch_handle(_handle_registry, *this, alloc); return WATCH_OK; } catch (Out_of_ram) { return WATCH_ERR_OUT_OF_RAM; } catch (Out_of_caps) { return WATCH_ERR_OUT_OF_CAPS; } } void close(Vfs_watch_handle *handle) override { destroy(handle->alloc(), static_cast(handle)); } struct Snap_vfs_handle : Vfs::Vfs_handle { using Vfs_handle::Vfs_handle; virtual Read_result read(char *dst, file_size count, file_size &out_count) = 0; virtual Write_result write(char const *src, file_size count, file_size &out_count) = 0; virtual Sync_result sync() { return SYNC_OK; } virtual bool read_ready() = 0; }; struct Dir_vfs_handle : Snap_vfs_handle { Snapshot_registry const &_snap_reg; bool const _root_dir { false }; Read_result _query_snapshots(file_size index, file_size &out_count, Dirent &out) { if (index >= _snap_reg.number_of_snapshots()) { out_count = sizeof(Dirent); out.type = Dirent_type::END; return READ_OK; } try { Snapshot_file_system const &fs = _snap_reg.by_index(index); Genode::String<32> name { fs.snapshot_id() }; out = { .fileno = (Genode::addr_t)this | index, .type = Dirent_type::DIRECTORY, .rwx = Node_rwx::rx(), .name = { name.string() }, }; out_count = sizeof(Dirent); return READ_OK; } catch (Snapshot_registry::Invalid_index) { return READ_ERR_INVALID; } } Read_result _query_root(file_size index, file_size &out_count, Dirent &out) { if (index == 0) { out = { .fileno = (Genode::addr_t)this, .type = Dirent_type::DIRECTORY, .rwx = Node_rwx::rx(), .name = { "snapshots" } }; } else { out.type = Dirent_type::END; } out_count = sizeof(Dirent); return READ_OK; } Dir_vfs_handle(Directory_service &ds, File_io_service &fs, Genode::Allocator &alloc, Snapshot_registry const &snap_reg, bool root_dir) : Snap_vfs_handle(ds, fs, alloc, 0), _snap_reg(snap_reg), _root_dir(root_dir) { } Read_result read(char *dst, file_size count, file_size &out_count) override { out_count = 0; if (count < sizeof(Dirent)) return READ_ERR_INVALID; file_size index = seek() / sizeof(Dirent); Dirent &out = *(Dirent*)dst; if (!_root_dir) { /* opended as "/snapshots" */ return _query_snapshots(index, out_count, out); } else { /* opened as "/" */ return _query_root(index, out_count, out); } } Write_result write(char const *, file_size, file_size &) override { return WRITE_ERR_INVALID; } bool read_ready() override { return true; } }; struct Dir_snap_vfs_handle : Vfs::Vfs_handle { Vfs_handle &vfs_handle; Dir_snap_vfs_handle(Directory_service &ds, File_io_service &fs, Genode::Allocator &alloc, Vfs::Vfs_handle &vfs_handle) : Vfs_handle(ds, fs, alloc, 0), vfs_handle(vfs_handle) { } ~Dir_snap_vfs_handle() { vfs_handle.close(); } }; Snapshot_registry _snap_reg; Wrapper &_wrapper; char const *_sub_path(char const *path) const { /* skip heading slash in path if present */ if (path[0] == '/') { path++; } Genode::size_t const name_len = strlen(type_name()); if (strcmp(path, type_name(), name_len) != 0) { return nullptr; } path += name_len; /* * The first characters of the first path element are equal to * the current directory name. Let's check if the length of the * first path element matches the name length. */ if (*path != 0 && *path != '/') { return 0; } return path; } Snapshots_file_system(Vfs::Env &vfs_env, Genode::Xml_node node, Wrapper &wrapper) : _vfs_env { vfs_env }, _snap_reg { vfs_env.alloc(), wrapper, *this }, _wrapper { wrapper } { _wrapper.manage_snapshots_file_system(*this); } ~Snapshots_file_system() { _wrapper.dissolve_snapshots_file_system(*this); } static char const *type_name() { return "snapshots"; } char const *type() override { return type_name(); } /********************************* ** Directory service interface ** *********************************/ Dataspace_capability dataspace(char const *path) { return Genode::Dataspace_capability(); } void release(char const *path, Dataspace_capability) { } Open_result open(char const *path, unsigned mode, Vfs::Vfs_handle **out_handle, Allocator &alloc) override { path = _sub_path(path); if (!path || path[0] != '/') { return OPEN_ERR_UNACCESSIBLE; } try { Snapshot_file_system &fs = _snap_reg.by_path(path); return fs.open(path, mode, out_handle, alloc); } catch (Snapshot_registry::Invalid_path) { } return OPEN_ERR_UNACCESSIBLE; } Opendir_result opendir(char const *path, bool create, Vfs::Vfs_handle **out_handle, Allocator &alloc) override { if (create) { return OPENDIR_ERR_PERMISSION_DENIED; } bool const top = _top_dir(path); if (_root_dir(path) || top) { _snap_reg.update(_vfs_env); *out_handle = new (alloc) Dir_vfs_handle(*this, *this, alloc, _snap_reg, top); return OPENDIR_OK; } else { char const *sub_path = _sub_path(path); if (!sub_path) { return OPENDIR_ERR_LOOKUP_FAILED; } try { Snapshot_file_system &fs = _snap_reg.by_path(sub_path); Vfs::Vfs_handle *handle = nullptr; Opendir_result const res = fs.opendir(sub_path, create, &handle, alloc); if (res != OPENDIR_OK) { return OPENDIR_ERR_LOOKUP_FAILED; } *out_handle = new (alloc) Dir_snap_vfs_handle(*this, *this, alloc, *handle); return OPENDIR_OK; } catch (Snapshot_registry::Invalid_path) { } } return OPENDIR_ERR_LOOKUP_FAILED; } void close(Vfs_handle *handle) override { if (handle && (&handle->ds() == this)) destroy(handle->alloc(), handle); } Stat_result stat(char const *path, Stat &out_stat) override { out_stat = Stat { }; path = _sub_path(path); /* path does not match directory name */ if (!path) { return STAT_ERR_NO_ENTRY; } /* * If path equals directory name, return information about the * current directory. */ if (strlen(path) == 0 || _top_dir(path)) { out_stat.type = Node_type::DIRECTORY; out_stat.inode = 1; out_stat.device = (Genode::addr_t)this; return STAT_OK; } if (!path || path[0] != '/') { return STAT_ERR_NO_ENTRY; } try { Snapshot_file_system &fs = _snap_reg.by_path(path); Stat_result const res = fs.stat(path, out_stat); return res; } catch (Snapshot_registry::Invalid_path) { } return STAT_ERR_NO_ENTRY; } Unlink_result unlink(char const *path) { return UNLINK_ERR_NO_PERM; } Rename_result rename(char const *from, char const *to) override { return RENAME_ERR_NO_PERM; } file_size num_dirent(char const *path) override { if (_top_dir(path)) { return 1; } if (_root_dir(path)) { _snap_reg.update(_vfs_env); file_size const num = _snap_reg.number_of_snapshots(); return num; } _snap_reg.update(_vfs_env); path = _sub_path(path); if (!path) { return 0; } try { Snapshot_file_system &fs = _snap_reg.by_path(path); file_size const num = fs.num_dirent(path); return num; } catch (Snapshot_registry::Invalid_path) { return 0; } } bool directory(char const *path) override { if (_root_dir(path)) { return true; } path = _sub_path(path); if (!path) { return false; } try { Snapshot_file_system &fs = _snap_reg.by_path(path); return fs.directory(path); } catch (Snapshot_registry::Invalid_path) { } return false; } char const *leaf_path(char const *path) override { path = _sub_path(path); if (!path) { return nullptr; } if (strlen(path) == 0 || strcmp(path, "") == 0) { return path; } try { Snapshot_file_system &fs = _snap_reg.by_path(path); char const *leaf_path = fs.leaf_path(path); if (leaf_path) { return leaf_path; } } catch (Snapshot_registry::Invalid_path) { } return nullptr; } /******************************** ** File I/O service interface ** ********************************/ Write_result write(Vfs::Vfs_handle *vfs_handle, char const *buf, file_size buf_size, file_size &out_count) override { return WRITE_ERR_IO; } bool queue_read(Vfs::Vfs_handle *vfs_handle, file_size size) override { Dir_snap_vfs_handle *dh = dynamic_cast(vfs_handle); if (dh) { return dh->vfs_handle.fs().queue_read(&dh->vfs_handle, size); } return true; } Read_result complete_read(Vfs::Vfs_handle *vfs_handle, char *dst, file_size count, file_size & out_count) override { Snap_vfs_handle *sh = dynamic_cast(vfs_handle); if (sh) { Read_result const res = sh->read(dst, count, out_count); return res; } Dir_snap_vfs_handle *dh = dynamic_cast(vfs_handle); if (dh) { return dh->vfs_handle.fs().complete_read(&dh->vfs_handle, dst, count, out_count); } return READ_ERR_IO; } bool read_ready(Vfs::Vfs_handle *) override { return true; } Ftruncate_result ftruncate(Vfs::Vfs_handle *vfs_handle, file_size len) override { return FTRUNCATE_OK; } }; struct Vfs_cbe::Control_local_factory : File_system_factory { Rekey_file_system _rekeying_fs; Deinitialize_file_system _deinitialize_fs; Create_snapshot_file_system _create_snapshot_fs; Discard_snapshot_file_system _discard_snapshot_fs; Extend_file_system _extend_fs; Control_local_factory(Vfs::Env &env, Xml_node config, Wrapper &cbe) : _rekeying_fs(cbe), _deinitialize_fs(cbe), _create_snapshot_fs(cbe), _discard_snapshot_fs(cbe), _extend_fs(cbe) { } Vfs::File_system *create(Vfs::Env&, Xml_node node) override { if (node.has_type(Rekey_file_system::type_name())) { return &_rekeying_fs; } if (node.has_type(Deinitialize_file_system::type_name())) { return &_deinitialize_fs; } if (node.has_type(Create_snapshot_file_system::type_name())) { return &_create_snapshot_fs; } if (node.has_type(Discard_snapshot_file_system::type_name())) { return &_discard_snapshot_fs; } if (node.has_type(Extend_file_system::type_name())) { return &_extend_fs; } return nullptr; } }; class Vfs_cbe::Control_file_system : private Control_local_factory, public Vfs::Dir_file_system { private: typedef String<128> Config; static Config _config(Xml_node node) { char buf[Config::capacity()] { }; Xml_generator xml(buf, sizeof(buf), "dir", [&] () { xml.attribute("name", "control"); xml.node("rekey", [&] () { }); xml.node("extend", [&] () { }); xml.node("create_snapshot", [&] () { }); xml.node("discard_snapshot", [&] () { }); xml.node("deinitialize", [&] () { }); }); return Config(Cstring(buf)); } public: Control_file_system(Vfs::Env &vfs_env, Genode::Xml_node node, Wrapper &cbe) : Control_local_factory(vfs_env, node, cbe), Vfs::Dir_file_system(vfs_env, Xml_node(_config(node).string()), *this) { } static char const *type_name() { return "control"; } char const *type() override { return type_name(); } }; struct Vfs_cbe::Local_factory : File_system_factory { Snapshot_file_system _current_snapshot_fs; Snapshots_file_system _snapshots_fs; Control_file_system _control_fs; Local_factory(Vfs::Env &env, Xml_node config, Wrapper &cbe) : _current_snapshot_fs(env, cbe, 0, false), _snapshots_fs(env, config, cbe), _control_fs(env, config, cbe) { } Vfs::File_system *create(Vfs::Env&, Xml_node node) override { using Name = String<64>; if (node.has_type(Snapshot_file_system::type_name()) && node.attribute_value("name", Name()) == "current") return &_current_snapshot_fs; if (node.has_type(Control_file_system::type_name())) return &_control_fs; if (node.has_type(Snapshots_file_system::type_name())) return &_snapshots_fs; return nullptr; } }; class Vfs_cbe::File_system : private Local_factory, public Vfs::Dir_file_system { private: Wrapper &_wrapper; typedef String<256> Config; static Config _config(Xml_node node) { char buf[Config::capacity()] { }; Xml_generator xml(buf, sizeof(buf), "dir", [&] () { typedef String<64> Name; xml.attribute("name", node.attribute_value("name", Name("cbe"))); xml.node("control", [&] () { }); xml.node("snapshot", [&] () { xml.attribute("name", "current"); }); xml.node("snapshots", [&] () { }); }); return Config(Cstring(buf)); } public: File_system(Vfs::Env &vfs_env, Genode::Xml_node node, Wrapper &wrapper) : Local_factory(vfs_env, node, wrapper), Vfs::Dir_file_system(vfs_env, Xml_node(_config(node).string()), *this), _wrapper(wrapper) { } ~File_system() { // XXX rather then destroying the wrapper here, it should be // done on the out-side where it was allocated in the first // place but the factory interface does not support that yet // destroy(vfs_env.alloc().alloc()), &_wrapper); } }; /************************** ** VFS plugin interface ** **************************/ extern "C" Vfs::File_system_factory *vfs_file_system_factory(void) { struct Factory : Vfs::File_system_factory { Vfs::File_system *create(Vfs::Env &vfs_env, Genode::Xml_node node) override { try { /* XXX wrapper is not managed and will leak */ Vfs_cbe::Wrapper *wrapper = new (vfs_env.alloc()) Vfs_cbe::Wrapper { vfs_env, node }; return new (vfs_env.alloc()) Vfs_cbe::File_system(vfs_env, node, *wrapper); } catch (...) { Genode::error("could not create 'cbe_fs' "); } return nullptr; } }; /* the CBE library requires a stack larger than the default */ Genode::Thread::myself()->stack_size(64*1024); Cbe::assert_valid_object_size(); cbe_cxx_init(); static Factory factory; return &factory; } /* * The SPARK compiler might generate a call to memcmp when it wants to * compare objects. For the time being we implement here and hopefully * any other memcmp symbol has at least the same semantics. */ extern "C" int memcmp(const void *s1, const void *s2, Genode::size_t n) { return Genode::memcmp(s1, s2, n); } /********************** ** Vfs_cbe::Wrapper ** **********************/ void Vfs_cbe::Wrapper::_snapshots_fs_update_snapshot_registry() { if (_snapshots_fs.valid()) { _snapshots_fs.obj().update_snapshot_registry(); } } void Vfs_cbe::Wrapper::_extend_fs_trigger_watch_response() { if (_extend_fs.valid()) { _extend_fs.obj().trigger_watch_response(); } } void Vfs_cbe::Wrapper::_rekey_fs_trigger_watch_response() { if (_rekey_fs.valid()) { _rekey_fs.obj().trigger_watch_response(); } } void Vfs_cbe::Wrapper::_deinit_fs_trigger_watch_response() { if (_deinit_fs.valid()) { _deinit_fs.obj().trigger_watch_response(); } } /******************************************************* ** Vfs_cbe::Snapshots_file_system::Snapshot_registry ** *******************************************************/ void Vfs_cbe::Snapshots_file_system::Snapshot_registry::update(Vfs::Env &vfs_env) { Cbe::Active_snapshot_ids list { }; _wrapper.active_snapshot_ids(list); bool trigger_watch_response { false }; /* alloc new */ for (size_t i = 0; i < sizeof (list.values) / sizeof (list.values[0]); i++) { uint32_t const id = list.values[i]; if (!id) { continue; } bool is_old = false; auto find_old = [&] (Snapshot_file_system const &fs) { is_old |= (fs.snapshot_id() == id); }; _registry.for_each(find_old); if (!is_old) { new (_alloc) Genode::Registered { _registry, vfs_env, _wrapper, id, true }; ++_number_of_snapshots; trigger_watch_response = true; } } /* destroy old */ auto find_stale = [&] (Snapshot_file_system const &fs) { bool is_stale = true; for (size_t i = 0; i < sizeof (list.values) / sizeof (list.values[0]); i++) { uint32_t const id = list.values[i]; if (!id) { continue; } if (fs.snapshot_id() == id) { is_stale = false; break; } } if (is_stale) { destroy(&_alloc, &const_cast(fs)); --_number_of_snapshots; trigger_watch_response = true; } }; _registry.for_each(find_stale); if (trigger_watch_response) { _snapshots_fs.trigger_watch_response(); } }