/*
 * \brief  Block device file system
 * \author Josef Soentgen
 * \author Norman Feske
 * \date   2013-12-20
 */

/*
 * Copyright (C) 2013-2025 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU Affero General Public License version 3.
 */

#ifndef _INCLUDE__VFS__BLOCK_FILE_SYSTEM_H_
#define _INCLUDE__VFS__BLOCK_FILE_SYSTEM_H_

/* Genode includes */
#include <base/allocator_avl.h>
#include <block_session/connection.h>
#include <vfs/dir_file_system.h>
#include <vfs/readonly_value_file_system.h>
#include <vfs/single_file_system.h>


namespace Vfs_block {

	using namespace Genode;
	using namespace Genode::Vfs;

	using Name = String<64>;
	using block_number_t = Block::block_number_t;
	using off_t = Block::off_t;

	struct Block_count { Block::block_number_t blocks; };

	struct Operation_size
	{
		Block::block_count_t blocks;

		static Operation_size from_block_count(Block_count const count) {
			return { (Block::block_count_t)count.blocks }; }
	};

	struct Block_job;
	struct Block_connection;
	struct Data_file_system;
	struct File_system;
}


struct Vfs_block::Block_job : Block::Connection<Block_job>::Job
{
	/*
	 * The range covers the whole request from the client
	 * but the overall amount is limited to the block
	 * count in the operation and thus can be smaller.
	 */
	Byte_range_ptr const range;

	/*
	 * Handle unaligned reads by splitting the offset
	 * into the initial offset used to constrain the
	 * first read and the offset used afterwards to
	 * calculate the position in the destination buffer.
	 */
	size_t initial_read_offset;
	size_t read_offset;
	size_t bytes_read;

	/* store length for acking unaligned or partial requests */
	size_t actual_length { 0 };

	bool done    { false };
	bool success { false };

	Block_job(Block::Connection<Block_job> &conn,
	          char *           const  start,
	          size_t           const  num_bytes,
	          Block::Operation const &op,
	          size_t           const  initial_read_offset = 0)
	:
		Block::Connection<Block_job>::Job { conn, op },
		range               { start, num_bytes },
		initial_read_offset { initial_read_offset },
		read_offset         { 0 },
		bytes_read          { 0 }
	{ }

	size_t read_space_remaining() const {
		return range.num_bytes - bytes_read; }
};


struct Vfs_block::Block_connection : Block::Connection<Block_job>
{
	Block_connection(auto &&... args)
	: Block::Connection<Block_job>(args...) { }

	/*****************************************************
	 ** Block::Connection::Update_jobs_policy interface **
	 *****************************************************/

	void produce_write_content(Block_job &job, off_t offset,
	                           char *dst, size_t length)
	{
		/*
		 * In write jobs the length is always at most num_bytes
		 * as only full blocks are written. The transfer might
		 * be split but in this case the offset grows while
		 * the length shrinks.
		 */
		size_t const sz = min(length, job.range.num_bytes);
		char const * src = (char const*)job.range.start + offset;

		memcpy(dst, src, sz);
	}

	void consume_read_result(Block_job &job, off_t offset,
	                         char const *src, size_t length)
	{
		/*
		 * When reading we perform an offset calculation into
		 * the destination buffer so that we always start at
		 * the beginning.
		 *
		 * Handle the expected common-case first as with aligned or
		 * follow-up requests we only have to care about not copying
		 * too much.
		 */
		if (!job.initial_read_offset) {
			size_t const sz = min(length, job.read_space_remaining());

			char * const dst = (char*)job.range.start + offset - job.read_offset;
			memcpy(dst, src, sz);
			job.bytes_read += sz;

			return;
		}

		/*
		 * Unaligned requests alter the src offset to read directly
		 * into the supplied destination buffer and set this offset
		 * up for follow-up requests. The way the Job API is implemented
		 * the 'offset' argument is '0' on the first call and thus
		 * omitted here.
		 */
		size_t const sz = min(length - job.initial_read_offset,
		                      job.read_space_remaining());

		char       * const dst  = (char*)job.range.start;
		char const *       from = src + job.initial_read_offset;
		memcpy(dst, from, sz);
		job.bytes_read += sz;

		job.read_offset         = job.initial_read_offset;
		job.initial_read_offset = 0;
	}

	void completed(Block_job &job, bool success)
	{
		job.success = success;
		job.done    = true;
	}
};


class Vfs_block::Data_file_system : public Single_file_system
{
	private:

		/*
		 * Noncopyable
		 */
		Data_file_system(Data_file_system const &);
		Data_file_system &operator = (Data_file_system const &);

		Vfs::Env &_env;

		Block_connection &_block;

		class Block_vfs_handle : public Single_vfs_handle
		{
			private:

				/*
				 * Noncopyable
				 */
				Block_vfs_handle(Block_vfs_handle const &);
				Block_vfs_handle &operator = (Block_vfs_handle const &);

				friend class Data_file_system;

				Block_connection &_block;

				struct Size_helper
				{
					size_t    const _size;
					file_size const _mask, _mask_inv;

					Size_helper(size_t const block_size)
					:
						_size     { block_size },
						_mask     { _size - 1 },
						_mask_inv { ~_mask }
					{ }

					size_t block_size() const {
						return _size; }

					file_size mask(file_size value) const {
						return value & _mask; }

					file_size round_up(file_size value) const {
						return (value + _mask) & _mask_inv; }

					file_size round_down(file_size value) const {
						return value & _mask_inv; }

					Block_count blocks(file_size value) const {
						return Block_count { value / _size }; }

					block_number_t block_number(file_size value) const {
						return block_number_t { value / _size }; }
				};

				struct Read_handler
				{
					Constructible<Block_job> _job { };

					Size_helper const _helper;
					Block_count const _block_count;

					bool _any_pending_job() const {
						return _job.constructed() && !_job->done; }

					bool _any_finished_job() const {
						return _job.constructed() && _job->done; }

					Read_result _handle_finished_job(size_t &out_count)
					{
						out_count = _job->range.num_bytes;
						bool const success = _job->success;
						_job.destruct();
						return success ? Read_result::READ_OK
						               : Read_result::READ_ERR_IO;
					}

					Read_handler(Block::Session::Info const &info)
					:
						_helper       { info.block_size },
						_block_count  { info.block_count }
					{ }

					Read_result read(Block_connection            &block,
					                 file_size             const  seek_offset,
					                 Byte_range_ptr        const &dst,
					                 size_t                      &out_count)
					{
						/* fast-exit for pending jobs */
						if (_any_pending_job())
							return Read_result::READ_QUEUED;

						if (_any_finished_job())
							return _handle_finished_job(out_count);

						/* round down to cover first block for unaligned requests */
						block_number_t const block_number =
							_helper.block_number(_helper.round_down(seek_offset));

						file_size const block_offset =
							_helper.mask(seek_offset);

						/* always round up to cover last block for partial requests */
						file_size const rounded_length =
							_helper.round_up(dst.num_bytes + block_offset);

						Block_count block_count =
							_helper.blocks(rounded_length);

						if (block_number + block_count.blocks > _block_count.blocks)
							block_count = Block_count { _block_count.blocks - block_number };

						if (block_number >= _block_count.blocks || block_count.blocks == 0) {
							out_count = 0;
							return Read_result::READ_OK;
						}

						Block::Operation const op {
							.type         = Block::Operation::Type::READ,
							.block_number = block_number,
							.count        = Operation_size::from_block_count(block_count).blocks
						};

						/*
						 * The Job API handles splitting the request and the
						 * job will read up to dst.num_bytes at most.
						 */
						_job.construct(block, dst.start, dst.num_bytes, op,
						               (size_t)block_offset);

						block.update_jobs(block);
						return Read_result::READ_QUEUED;
					}
				};

				Read_handler _read_handler;

				struct Write_handler
				{
					Constructible<Block_job> _job { };

					Size_helper const _helper;
					Block_count const _block_count;

					char _unaligned_buffer[4096] { };

					bool _any_pending_job() const {
						return _job.constructed() && !_job->done; }

					bool _any_finished_job() const {
						return _job.constructed() && _job->done; }

					Write_result _handle_unaligned_or_partial(Block_connection       &block,
					                                          block_number_t   const  block_number)
					{
						Block::Operation const op {
							.type         = Block::Operation::Type::READ,
							.block_number = block_number,
							.count        = 1
						};

						_job.construct(block, _unaligned_buffer,
						               _helper.block_size(), op);

						block.update_jobs(block);
						return Write_result::WRITE_ERR_WOULD_BLOCK;
					}

					Write_result _handle_finished_job(Block_connection           &block,
					                                  Const_byte_range_ptr const &src,
					                                  file_size            const  block_offset,
					                                  block_number_t       const  block_number,
					                                  size_t                     &out_count)
					{
						/*
						 * We do not care whether READ (for unaligned or partial requests)
						 * or WRITE has failed and handle this first. Then handle
						 * successful WRITE requests as we can ack them directly.
						 */

						if (!_job->success) {
							_job.destruct();
							return Write_result::WRITE_ERR_IO;
						}

						if (_job->operation().type == Block::Operation::Type::WRITE) {
							out_count = _job->actual_length ? _job->actual_length
							                                : _job->range.num_bytes;
							_job.destruct();
							return Write_result::WRITE_OK;
						}

						/*
						 * Now we deal with completing a unaligned or partial request.
						 */
						file_size const partial_length =
							block_offset ? min(_helper.block_size() - block_offset, src.num_bytes)
							             : src.num_bytes;

						/* should never happen */
						if (!partial_length)
							return Write_result::WRITE_ERR_IO;

						memcpy(_unaligned_buffer + block_offset, src.start, (size_t)partial_length);

						Block::Operation const op {
							.type         = Block::Operation::Type::WRITE,
							.block_number = block_number,
							.count        = 1
						};

						_job.construct(block, _unaligned_buffer,
						               _helper.block_size(), op, 0);

						/* store partial length for setting the matching out_count later */
						_job->actual_length = (size_t)partial_length;

						block.update_jobs(block);
						return Write_result::WRITE_ERR_WOULD_BLOCK;
					}

					Write_handler(Block::Session::Info const &info)
					:
						_helper       { info.block_size },
						_block_count  { info.block_count }
					{ }

					Write_result write(Block_connection            &block,
					                   file_size             const  seek_offset,
					                   Const_byte_range_ptr  const &src,
					                   size_t                      &out_count)
					{
						/* fast-exit for pending jobs */
						if (_any_pending_job())
							return Write_result::WRITE_ERR_WOULD_BLOCK;

						file_size const block_offset =
							_helper.mask(seek_offset);

						/* round down to handle partial requests later on */
						file_size const rounded_length =
							_helper.round_down(src.num_bytes + block_offset);

						Block_count const block_count =
							_helper.blocks(rounded_length);

						block_number_t const block_number =
							_helper.block_number(_helper.round_down(seek_offset));

						if (block_number >= _block_count.blocks
						 || block_number + block_count.blocks > _block_count.blocks)
							return Write_result::WRITE_ERR_INVALID;

						/*
						 * The actual WRITE job will be set up here if the finished
						 * job was in charge of reading in the block for unaligned or
						 * partial requests.
						 */
						if (_any_finished_job())
							return _handle_finished_job(block, src, block_offset,
							                            block_number, out_count);

						/*
						 * If the request is unaligned or partial we first have to
						 * issue a READ request for the block covering the request.
						 */

						if (block_offset != 0)
							return _handle_unaligned_or_partial(block, block_number);

						if (!rounded_length && src.num_bytes < _helper.block_size())
							return _handle_unaligned_or_partial(block, block_number);

						/*
						 * Handle regular aligned, full-block request.
						 */

						Block::Operation const op {
							.type         = Block::Operation::Type::WRITE,
							.block_number = block_number,
							.count        = Operation_size::from_block_count(block_count).blocks
						};

						/*
						 * The Job API handles splitting the request and the
						 * job will write up to src.num_bytes at most.
						 */
						_job.construct(block, const_cast<char*>(src.start),
						               (size_t)rounded_length, op);

						block.update_jobs(block);
						return Write_result::WRITE_ERR_WOULD_BLOCK;
					}
				};

				Write_handler _write_handler;

				struct Sync_handler
				{
					Constructible<Block_job> _job {  };

					Block_count const _block_count;

					bool _any_pending_job() const {
						return _job.constructed() && !_job->done; }

					bool _any_finished_job() const {
						return _job.constructed() && _job->done; }

					Sync_result _handle_finished_job()
					{
						bool const success = _job->success;
						_job.destruct();
						return success ? Sync_result::SYNC_OK
						               : Sync_result::SYNC_ERR_INVALID;
					}

					Sync_handler(Block::Session::Info const &info)
					: _block_count { info.block_count } { }

					Sync_result sync(Block_connection &block)
					{
						if (_any_pending_job())
							return Sync_result::SYNC_QUEUED;

						if (_any_finished_job())
							return _handle_finished_job();

						Block::Operation const op {
							.type         = Block::Operation::Type::SYNC,
							.block_number = 0,
							.count        = Operation_size::from_block_count(_block_count).blocks
						};

						_job.construct(block, nullptr, 0, op);

						block.update_jobs(block);
						return Sync_result::SYNC_QUEUED;
					}
				};

				Sync_handler _sync_handler;

			public:

				Block_vfs_handle(Directory_service &ds,
				                 Allocator         &alloc,
				                 Block_connection  &block)
				:
					Single_vfs_handle { ds, alloc, 0 },
					_block            { block },
					_read_handler     { _block.info() },
					_write_handler    { _block.info() },
					_sync_handler     { _block.info() }
				{ }

				Read_result complete_read(Byte_range_ptr const &dst, size_t &out_count) override {
					return _read_handler.read(_block, seek(), dst, out_count); }

				Write_result write(Const_byte_range_ptr const &src, size_t &out_count) override
				{
					if (!_block.info().writeable)
						return WRITE_ERR_INVALID;

					/*
					 * Since there is no explicit queue reseult value we issue
					 * WRITE_ERR_WOULD_BLOCK for this case.
					 */
					return _write_handler.write(_block, seek(), src, out_count);
				}

				Sync_result complete_sync() override
				{
					return _sync_handler.sync(_block);
				}

				bool read_ready()  const override { return true; }
				bool write_ready() const override { return true; }

				Ftruncate_result ftruncate(file_size) override { return FTRUNCATE_OK; }
		};

	public:

		Data_file_system(Vfs::Env &env, Parent_fs &parent_fs,
		                 Block_connection &block, Name const &name)
		:
			Single_file_system { parent_fs,
			                     Node_type::CONTINUOUS_FILE, name.string(),
			                     block.info().writeable ? Node_rwx::rw()
			                                            : Node_rwx::ro(),
			                     Node() },
			_env   { env },
			_block { block }
		{
			/* prevent usage of unsupported block sizes */
			size_t const block_size = _block.info().block_size;
			if (block_size % 512 != 0 ||
			    block_size > sizeof(Block_vfs_handle::Write_handler::_unaligned_buffer)) {
				error("block-size: ", block_size, " of underlying session not supported");
				struct Unsupported_underlying_block_size { };
				throw Unsupported_underlying_block_size();
			}
		}

		~Data_file_system() { }

		static char const *name()   { return "data"; }
		char const *type() override { return "data"; }

		Open_result open(char const  *path, unsigned,
		                 Vfs_handle **out_handle,
		                 Allocator   &alloc) override
		{
			if (!_single_file(path))
				return OPEN_ERR_UNACCESSIBLE;

			try {
				*out_handle = new (alloc) Block_vfs_handle(*this, alloc, _block);
				return OPEN_OK;
			}
			catch (Out_of_ram)  { return OPEN_ERR_OUT_OF_RAM; }
			catch (Out_of_caps) { return OPEN_ERR_OUT_OF_CAPS; }
		}

		Stat_result stat(char const *path, Stat &out) override
		{
			Stat_result const result = Single_file_system::stat(path, out);
			out.size = _block.info().block_count * _block.info().block_size;
			return result;
		}
};


struct Vfs_block::File_system : Dir_file_system, File_system_factory
{
	using Label = String<64>;
	using Name  = Vfs_block::Name;

	Label const _label;

	Name const _name;

	Vfs::Env &_env;

	Allocator_avl _tx_block_alloc { &_env.alloc() };

	Block_connection _block;

	Io_signal_handler<File_system> _block_signal_handler {
		_env.env().ep(), *this, &File_system::_handle_block_signal };

	void _handle_block_signal()
	{
		_block.update_jobs(_block);
		_env.user().wakeup_vfs_user();
	}

	Data_file_system _data_fs;

	struct Info : Block::Session::Info
	{
		void print(Output &out) const
		{
			char buf[128] { };
			Generator::generate({ buf, sizeof(buf) }, "block",
				[&] (Generator &g) {
					g.attribute("count", Block::Session::Info::block_count);
					g.attribute("size",  Block::Session::Info::block_size);
			}).with_error([] (Buffer_error) {
				warning("VFS-block info exceeds maximum buffer size");
			});
			Genode::print(out, Cstring(buf));
		}
	};

	Readonly_value_file_system<Info>     _info_fs        { *this, "info",        Info { } };
	Readonly_value_file_system<uint64_t> _block_count_fs { *this, "block_count", 0 };
	Readonly_value_file_system<size_t>   _block_size_fs  { *this, "block_size",  0 };

	static Name name(Node const &config) {
		return config.attribute_value("name", Name("block")); }

	static constexpr Num_bytes DEFAULT_IO_BUFFER_SIZE = { (4u << 20) };

	/* payload size, a fixed-amount for meta-data is added below */
	static size_t io_buffer(Node const &config) {
		return config.attribute_value("io_buffer", DEFAULT_IO_BUFFER_SIZE); }

	Vfs::File_system *create(Vfs::Env &, Parent_fs &, Node const &node) override
	{
		if (node.has_type("data"))        return &_data_fs;
		if (node.has_type("info"))        return &_info_fs;
		if (node.has_type("block_count")) return &_block_count_fs;
		if (node.has_type("block_size"))  return &_block_size_fs;
		return nullptr;
	}

	using Config = String<200>;

	static Config _config(Name const &name)
	{
		char buf[Config::capacity()] { };

		/*
		 * By not using the node type "dir", we operate the
		 * 'Dir_file_system' in root mode, allowing multiple sibling nodes
		 * to be present at the mount point.
		 */
		Generator::generate({ buf, sizeof(buf) }, "compound",
			[&] (Generator &g) {

				g.node("data", [&] { g.attribute("name", name); });

				g.node("dir", [&] {
					g.attribute("name", Name(".", name));
					g.node("info");
					g.node("block_count");
					g.node("block_size");
				});

		}).with_error([&] (Buffer_error) {
			warning("VFS-block compound exceeds maximum buffer size");
		});

		return Config(Cstring(buf));
	}

	File_system(Vfs::Env &vfs_env, Parent_fs &parent_fs, Node const &node)
	:
		Dir_file_system { vfs_env, parent_fs, Node(_config(name(node))) },
		_label     { node.attribute_value("label", Label("")) },
		_name      { name(node) },
		_env       { vfs_env },
		_block     { _env.env(), &_tx_block_alloc, io_buffer(node) + (64u << 10),
		             _label.string() },
		_data_fs   { _env, *this, _block, name(node) }
	{
		if (node.has_attribute("block_buffer_count"))
			warning("'block_buffer_count' attribute is superseded by 'io_buffer'");

		_block.sigh(_block_signal_handler);
		_info_fs       .value(Info { _block.info() });
		_block_count_fs.value(_block.info().block_count);
		_block_size_fs .value(_block.info().block_size);

		Dir_file_system::update(Node(_config(name(node))), *this);
	}

	static const char *name() { return "block"; }

	char const *type() override { return name(); }
};

#endif /* _INCLUDE__VFS__BLOCK_FILE_SYSTEM_H_ */
