Martin Stein 1b4a80ffae vfs/cbe: control/deinitialize file
There were no means for issuing a Deinitialize request at the CBE using the
CBE VFS plugin. The new control/deinitialize file fixes this. When writing
"true" to the file, a Deinitialize request is submitted at the CBE. When
reading the file, the state of the operation is returned as a string of the
format "[current_state] last-result: [last_result]" where [current_state] can
be "idle" or "in-progress" and [last_result] can be "none", "success", or
"failed".

Ref #4032
2021-10-13 14:50:45 +02:00

3775 lines
93 KiB
C++

/*
* \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 <vfs/dir_file_system.h>
#include <vfs/single_file_system.h>
#include <util/arg_string.h>
#include <util/xml_generator.h>
#include <trace/timestamp.h>
/* CBE includes */
#include <cbe/library.h>
#include <cbe/vfs/trust_anchor_vfs.h>
/* local includes */
#include <io_job.h>
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 <typename T>
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<Io_job> _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<Wrapper> _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<Util::Trust_anchor_vfs> _trust_anchor { };
Constructible<Cbe::Library> _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_file_system> _snapshots_fs { };
Pointer<Extend_file_system> _extend_fs { };
Pointer<Rekey_file_system> _rekey_fs { };
Pointer<Deinitialize_file_system> _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<Snapshots_file_system> { };
} 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<Extend_file_system> { };
} 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<Rekey_file_system> { };
} 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<Deinitialize_file_system> { };
} 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 "<unknown>";
}
};
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<char*>
(frontend_request.cbe_request.offset());
char const * src = reinterpret_cast<char const*>
(&_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<char*>
(&_helper_write_request.block_data);
char const * src = reinterpret_cast<char const*>
(&_helper_read_request.block_data);
Genode::memcpy(dst, src, sizeof (Cbe::Block_data));
}
/* and than actual request data */
{
char * dst = reinterpret_cast<char*>
(&_helper_write_request.block_data) + frontend_request.helper_offset;
char const * src = reinterpret_cast<char const*>
(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<Cbe::Block_data*>(
&_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<Cbe::Block_data*>(
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<Cbe::Block_data*>(
&_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<Cbe::Block_data*>(
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 <Crypto_job::Operation OP>
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<char const*>(&plain.item(plain_index));
} else
if (op == Crypto_job::Operation::DECRYPT) {
data = reinterpret_cast<char const*>(&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<char *>(&cipher.item(cipher_index));
} else
if (op == Crypto_job::Operation::DECRYPT) {
data = reinterpret_cast<char *>(&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<Op::ENCRYPT>(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<Op::DECRYPT>(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<char*>(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("<data/>")),
_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<Vfs_watch_handle> Registered_watch_handle;
typedef Registry<Registered_watch_handle> 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("<extend/>")),
_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<Registered_watch_handle *>(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<Vfs_watch_handle> Registered_watch_handle;
typedef Registry<Registered_watch_handle> 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("<rekey/>")),
_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<Registered_watch_handle *>(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<Vfs_watch_handle> Registered_watch_handle;
typedef Registry<Registered_watch_handle> 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("<deinitialize/>")),
_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<Registered_watch_handle *>(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("<create_snapshot/>")),
_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("<discard_snapshot/>")),
_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<Vfs_watch_handle> Registered_watch_handle;
typedef Registry<Registered_watch_handle> 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<Genode::Registered<Snapshot_file_system>> _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<Registered_watch_handle *>(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<Dir_snap_vfs_handle*>(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<Snap_vfs_handle*>(vfs_handle);
if (sh) {
Read_result const res = sh->read(dst, count, out_count);
return res;
}
Dir_snap_vfs_handle *dh =
dynamic_cast<Dir_snap_vfs_handle*>(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::Library>();
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<Snapshot_file_system> {
_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<Snapshot_file_system&>(fs));
--_number_of_snapshots;
trigger_watch_response = true;
}
};
_registry.for_each(find_stale);
if (trigger_watch_response) {
_snapshots_fs.trigger_watch_response();
}
}