/*
 * \brief  Directory file system
 * \author Norman Feske
 * \author Emery Hemingway
 * \author Christian Helmuth
 * \date   2012-04-23
 */

/*
 * Copyright (C) 2011-2019 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__DIR_FILE_SYSTEM_H_
#define _INCLUDE__VFS__DIR_FILE_SYSTEM_H_

#include <base/registry.h>
#include <vfs/file_system_factory.h>
#include <vfs/vfs_handle.h>


namespace Vfs { class Dir_file_system; }


class Vfs::Dir_file_system : public File_system
{
	public:

		enum { MAX_NAME_LEN = 128 };

	private:

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

		Vfs::Env &_env;

		/**
		 * This instance is the root of VFS
		 *
		 * Additionally, the root has an empty _name.
		 */
		bool const _vfs_root;

		struct Dir_vfs_handle : Vfs_handle
		{
			struct Subdir_handle_element;

			typedef Genode::Registry<Subdir_handle_element> Subdir_handle_registry;

			struct Subdir_handle_element : Subdir_handle_registry::Element
			{
				bool synced { false };

				Vfs_handle &vfs_handle;
				Subdir_handle_element(Subdir_handle_registry &registry,
				                      Vfs_handle &vfs_handle)
				: Subdir_handle_registry::Element(registry, *this),
				  vfs_handle(vfs_handle) { }
			};

			Absolute_path             path;
			Vfs_handle               *queued_read_handle { nullptr };
			Subdir_handle_registry    subdir_handle_registry { };

			Dir_vfs_handle(Directory_service &ds,
			               File_io_service   &fs,
			               Genode::Allocator &alloc,
			               char const *path)
			: Vfs_handle(ds, fs, alloc, 0),
			  path(path) { }

			~Dir_vfs_handle()
			{
				/* close all sub-handles */
				auto f = [&] (Subdir_handle_element &e) {
					e.vfs_handle.close();
					destroy(alloc(), &e);
				};
				subdir_handle_registry.for_each(f);
			}

			private:

				/*
				 * Noncopyable
				 */
				Dir_vfs_handle(Dir_vfs_handle const &);
				Dir_vfs_handle &operator = (Dir_vfs_handle const &);
		};

		struct Dir_watch_handle : Vfs_watch_handle
		{
			struct Watch_handle_element;

			typedef Genode::Registry<Watch_handle_element> Watch_handle_registry;

			struct Watch_handle_element : Watch_handle_registry::Element
			{
				Vfs_watch_handle &watch_handle;
				Watch_handle_element(Watch_handle_registry &registry,
				                     Vfs_watch_handle &handle)
				: Watch_handle_registry::Element(registry, *this),
				  watch_handle(handle) { }
			};

			Watch_handle_registry  handle_registry { };

			Dir_watch_handle(File_system &fs, Genode::Allocator &alloc)
			: Vfs_watch_handle(fs, alloc) { }

			~Dir_watch_handle()
			{
				/* close all sub-handles */
				auto f = [&] (Watch_handle_element &e) {
					e.watch_handle.close();
					destroy(alloc(), &e);
				};
				handle_registry.for_each(f);
			}

			/**
			 * Propagate the response handler to each sub-handle
			 */
			void handler(Watch_response_handler *h) override
			{
				handle_registry.for_each( [&] (Watch_handle_element &elem) {
					elem.watch_handle.handler(h); } );
			}
		};


		/* pointer to first child file system */
		File_system *_first_file_system = nullptr;

		/* add new file system to the list of children */
		void _append_file_system(File_system *fs)
		{
			if (!_first_file_system) {
				_first_file_system = fs;
				return;
			}

			File_system *curr = _first_file_system;
			while (curr->next)
				curr = curr->next;

			curr->next = fs;
		}

		/**
		 * Directory name
		 */
		typedef String<MAX_NAME_LEN> Name;
		Name const _name;

		/**
		 * Returns if path corresponds to top directory of file system
		 */
		bool _top_dir(char const *path) const {	return strcmp(path, "/") == 0; }

		/**
		 * Perform operation on a file system
		 *
		 * \param fn  functor that takes a file-system reference and
		 *            the path as arguments
		 */
		template <typename RES, typename FN>
		RES _dir_op(RES const no_entry, RES const no_perm, RES const ok,
		            char const *path, FN const &fn)
		{
			path = _sub_path(path);

			/* path does not match directory name */
			if (!path)
				return no_entry;

			/*
			 * Prevent operation if path equals directory name defined
			 * via the static VFS configuration.
			 */
			if (strlen(path) == 0)
				return no_perm;

			/*
			 * If any of the sub file systems returns a permission error and
			 * there exists no sub file system that takes the request, we
			 * return the permission error.
			 */
			bool permission_denied = false;

			/*
			 * Keep the most meaningful error code. When using stacked file
			 * systems, most child file systems will eventually return no
			 * entry (or leave the error code unchanged). If any of those
			 * file systems has anything more interesting to tell, return
			 * this information after all file systems have been tried and
			 * none could handle the request.
			 */
			RES error = ok;

			/*
			 * The given path refers to at least one of our sub directories.
			 * Propagate the request into all of our file systems. If at least
			 * one operation succeeds, we return success.
			 */
			for (File_system *fs = _first_file_system; fs; fs = fs->next) {

				RES const err = fn(*fs, path);

				if (err == ok)
					return err;

				if (err != no_entry && err != no_perm) {
					error = err;
				}

				if (err == no_perm)
					permission_denied = true;
			}

			/* none of our file systems could successfully operate on the path */
			return error != ok ? error : permission_denied ? no_perm : no_entry;
		}

		/**
		 * Return portion of the path after the element corresponding to
		 * the current directory.
		 */
		char const *_sub_path(char const *path) const
		{
			/* do not strip anything from the path when we are root */
			if (_vfs_root)
				return path;

			if (_top_dir(path))
				return path;

			/* skip heading slash in path if present */
			if (path[0] == '/')
				path++;

			Genode::size_t const name_len = strlen(_name.string());
			if (strcmp(path, _name.string(), name_len) != 0)
				return 0;
			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;
		}

		/*
		 * Accumulate number of directory entries that match in any of
		 * our sub file systems.
		 */
		file_size _sum_dirents_of_file_systems(char const *path)
		{
			file_size cnt = 0;
			for (File_system *fs = _first_file_system; fs; fs = fs->next) {
				cnt += fs->num_dirent(path);
			}
			return cnt;
		}

		bool _queue_read_of_file_systems(Dir_vfs_handle *dir_vfs_handle)
		{
			bool result = true;

			dir_vfs_handle->queued_read_handle = nullptr;

			file_offset index = dir_vfs_handle->seek() / sizeof(Dirent);

			char const *sub_path = _sub_path(dir_vfs_handle->path.base());

			if (strlen(sub_path) == 0)
				sub_path = "/";

			/* base of composite directory index */
			int base = 0;

			auto f = [&] (Dir_vfs_handle::Subdir_handle_element &handle_element) {
				if (dir_vfs_handle->queued_read_handle) return; /* skip through */

				Vfs_handle &vfs_handle = handle_element.vfs_handle;

				/*
				 * Determine number of matching directory entries within
				 * the current file system.
				 */
				int const fs_num_dirent = (int)vfs_handle.ds().num_dirent(sub_path);

				/*
				 * Query directory entry if index lies with the file
				 * system.
				 */
				if (index - base < fs_num_dirent) {
					/* set this handle to be used for read completion */
					dir_vfs_handle->queued_read_handle = &vfs_handle;

					/* seek to file-system local index */
					index = index - base;
					vfs_handle.seek(index * sizeof(Dirent));

					/* forward the response handler */
					dir_vfs_handle->apply_handler([&] (Vfs::Read_ready_response_handler &h) {
						vfs_handle.handler(&h); });

					result = vfs_handle.fs().queue_read(&vfs_handle, sizeof(Dirent));
				}

				/* adjust base index for next file system */
				base += fs_num_dirent;
			};

			dir_vfs_handle->subdir_handle_registry.for_each(f);

			return result;
		}

		Read_result _complete_read_of_file_systems(Dir_vfs_handle *dir_vfs_handle,
		                                           Byte_range_ptr const &dst,
		                                           size_t &out_count)
		{
			if (!dir_vfs_handle->queued_read_handle) {

				/*
				 * no fs was found for the given index or
				 * fs->opendir() failed
				 */

				if (dst.num_bytes < sizeof(Dirent))
					return READ_ERR_INVALID;

				Dirent &dirent = *(Dirent*)dst.start;
				dirent = Dirent { };

				out_count = sizeof(Dirent);

				return READ_OK;
			}

			Read_result result = dir_vfs_handle->queued_read_handle->fs().
			                     complete_read(dir_vfs_handle->queued_read_handle,
			                                   dst, out_count);

			if (result == READ_QUEUED)
				return result;

			dir_vfs_handle->queued_read_handle = nullptr;

			return result;
		}

	public:

		Dir_file_system(Vfs::Env            &env,
		                Genode::Xml_node     node,
		                File_system_factory &fs_factory)
		:
			_env(env),
			_vfs_root(!node.has_type("dir")),
			_name(_vfs_root ? Name() : node.attribute_value("name", Name()))
		{
			using namespace Genode;

			for (unsigned i = 0; i < node.num_sub_nodes(); i++) {

				Xml_node sub_node = node.sub_node(i);

				/* traverse into <dir> nodes */
				if (sub_node.has_type("dir")) {
					_append_file_system(new (_env.alloc())
						Dir_file_system(_env, sub_node, fs_factory));
					continue;
				}

				File_system * const fs =
					fs_factory.create(_env, sub_node);

				if (fs) {
					_append_file_system(fs);
					continue;
				}

				Genode::error("failed to create <", sub_node.type(), "> VFS node");
				try {
					for (unsigned i = 0; i < 16; ++i) {

						Xml_attribute const attr = sub_node.attribute(i);

						String<64> value { };
						attr.value(value);

						Genode::error("\t", attr.name(), "=\"", value, "\"");
					}
				} catch (Xml_node::Nonexistent_attribute) { }
			}
		}

		/*********************************
		 ** Directory-service interface **
		 *********************************/

		Dataspace_capability dataspace(char const *path) override
		{
			path = _sub_path(path);
			if (!path)
				return Dataspace_capability();

			/*
			 * Query sub file systems for dataspace using the path local to
			 * the respective file system
			 */
			File_system *fs = _first_file_system;
			for (; fs; fs = fs->next) {
				Dataspace_capability ds = fs->dataspace(path);
				if (ds.valid())
					return ds;
			}

			return Dataspace_capability();
		}

		void release(char const *path, Dataspace_capability ds_cap) override
		{
			path = _sub_path(path);
			if (!path)
				return;

			for (File_system *fs = _first_file_system; fs; fs = fs->next)
				fs->release(path, ds_cap);
		}

		Stat_result stat(char const *path, Stat &out) override
		{
			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 = {
					.size              = 0,
					.type              = Node_type::DIRECTORY,
					.rwx               = Node_rwx::rwx(),
					.inode             = 1,
					.device            = (Genode::addr_t)this,
					.modification_time = { Vfs::Timestamp::INVALID },
				};
				return STAT_OK;
			}

			/*
			 * The given path refers to one of our sub directories.
			 * Propagate the request into our file systems.
			 */
			for (File_system *fs = _first_file_system; fs; fs = fs->next) {

				Stat_result const err = fs->stat(path, out);

				if (err == STAT_OK)
					return err;

				if (err != STAT_ERR_NO_ENTRY)
					return err;
			}

			/* none of our file systems felt responsible for the path */
			return STAT_ERR_NO_ENTRY;
		}

		file_size num_dirent(char const *path) override
		{
			if (_vfs_root) {
				return _sum_dirents_of_file_systems(path);

			} else {

				if (_top_dir(path))
					return 1;

				/*
				 * The path contains at least one element. Remove current
				 * element from path.
				 */
				path = _sub_path(path);

				/*
				 * If the resulting 'path' is non-null, the path lies
				 * within our tree. In this case, determine the sum of
				 * matching dirents of all our file systems. Otherwise,
				 * the specified path lies outside our directory node.
				 */
				return path ? _sum_dirents_of_file_systems(*path ? path : "/") : 0;
			}
		}

		/**
		 * Return true if specified path is a directory
		 */
		bool directory(char const *path) override
		{
			if (_top_dir(path))
				return true;

			path = _sub_path(path);

			if (!path)
				return false;

			if (strlen(path) == 0)
				return true;

			for (File_system *fs = _first_file_system; fs; fs = fs->next)
				if (fs->directory(path))
					return true;

			return false;
		}

		char const *leaf_path(char const *path) override
		{
			path = _sub_path(path);
			if (!path)
				return nullptr;

			if (strlen(path) == 0)
				return path;

			for (File_system *fs = _first_file_system; fs; fs = fs->next) {
				char const *leaf_path = fs->leaf_path(path);
				if (leaf_path)
					return leaf_path;
			}

			return nullptr;
		}

		Open_result open(char const  *path,
		                 unsigned     mode,
		                 Vfs_handle **out_handle,
		                 Allocator   &alloc) override
		{
			/*
			 * If 'path' is a directory, we create a 'Vfs_handle'
			 * for the root directory so that subsequent 'dirent' calls
			 * are subjected to the stacked file-system layout.
			 */
			if (directory(path)) {
				try {
					*out_handle = new (alloc) Dir_vfs_handle(*this, *this, alloc, path);
					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; }
			}

			/*
			 * If 'path' refers to a non-directory node, create a
			 * 'Vfs_handle' local to the file system that provides the
			 * file.
			 */

			path = _sub_path(path);

			/* check if path does not match directory name */
			if (!path)
				return OPEN_ERR_UNACCESSIBLE;

			/* path equals directory name */
			if (strlen(path) == 0) {
				try {
					*out_handle = new (alloc) Vfs_handle(*this, *this, alloc, 0);
					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; }
			}

			/* path refers to any of our sub file systems */
			for (File_system *fs = _first_file_system; fs; fs = fs->next) {

				Open_result const err = fs->open(path, mode, out_handle, alloc);
				switch (err) {
				case OPEN_ERR_UNACCESSIBLE:
					continue;
				default:
					return err;
				}
			}

			/* path does not match any existing file or directory */
			return OPEN_ERR_UNACCESSIBLE;
		}

		/**
		 * Call 'opendir()' on each file system and store handles in
		 * a registry.
		 */
		Opendir_result open_composite_dirs(char const *sub_path,
		                                   Dir_vfs_handle &dir_vfs_handle)
		{
			Opendir_result res;
			if (strcmp(sub_path, "/")) {

				/*
				 * If there are still directory names in the sub-path, we have
				 * not reached the leaf node of the original path so far.
				 * Therefore, if the current directory is empty, this means
				 * that the original path refers to a directory that doesn't
				 * exist. Consequently, the result defaults to a
				 * "lookup failed" error.
				 */
				res = OPENDIR_ERR_LOOKUP_FAILED;

			} else {

				/*
				 * We have reached the leaf node of the original path.
				 * Therefore the directory referenced by the original path is
				 * the one that we are at. Consequently, we can let the result
				 * default to "success" regardless of whether the directory is
				 * empty. However, if there are any sub-file-systems, we will
				 * go on and store handles for them in the registry.
				 */
				res = OPENDIR_OK;
			}
			try {
				for (File_system *fs = _first_file_system; fs; fs = fs->next) {
					Vfs_handle *sub_dir_handle = nullptr;

					Opendir_result r = fs->opendir(
						sub_path, false, &sub_dir_handle, dir_vfs_handle.alloc());

					switch (r) {
					case OPENDIR_OK:
						break;
					case OPENDIR_ERR_OUT_OF_RAM:
					case OPENDIR_ERR_OUT_OF_CAPS:
						return r;
					default:
						continue;
					}

					try {
						new (dir_vfs_handle.alloc())
							Dir_vfs_handle::Subdir_handle_element(
								dir_vfs_handle.subdir_handle_registry, *sub_dir_handle);
					}
					catch (...) {
						sub_dir_handle->close();
						throw;
					}
					/* return OK because at least one directory has been opened */
					res = OPENDIR_OK;
				}
			}
			catch (Genode::Out_of_ram)  { res = OPENDIR_ERR_OUT_OF_RAM; }
			catch (Genode::Out_of_caps) { res = OPENDIR_ERR_OUT_OF_CAPS; }

			return res;
		}

		Opendir_result opendir(char const *path, bool create,
		                       Vfs_handle **out_handle, Allocator &alloc) override
		{
			Opendir_result result = OPENDIR_OK;

			if (_top_dir(path)) {
				if (create)
					return OPENDIR_ERR_PERMISSION_DENIED;

				/*
				 * opendir with '/' (called from 'open_composite_dirs' returns handle
				 * only, VFS root additionally calls 'open_composite_dirs' in order to
				 * open its file systems
				 */
				Dir_vfs_handle *root_handle;
				try {
					root_handle = new (alloc)
						Dir_vfs_handle(*this, *this, alloc, path);
				}
				catch (Genode::Out_of_ram)  { return OPENDIR_ERR_OUT_OF_RAM; }
				catch (Genode::Out_of_caps) { return OPENDIR_ERR_OUT_OF_CAPS; }

				/* the VFS root may contain more file systems */
				if (_vfs_root)
					result = open_composite_dirs("/", *root_handle);

				if (result == OPENDIR_OK) {
					*out_handle = root_handle;
				} else {
					/* close the root handle and the rest will follow */
					close(root_handle);
				}
				return result;
			}

			char const *sub_path = _sub_path(path);

			if (!sub_path)
				return OPENDIR_ERR_LOOKUP_FAILED;

			if (create) {
				if (leaf_path(path) != nullptr)
					return OPENDIR_ERR_NODE_ALREADY_EXISTS;

				auto opendir_fn = [&] (File_system &fs, char const *path)
				{
					Vfs_handle *tmp_handle;
					Opendir_result opendir_result =
						fs.opendir(path, true, &tmp_handle, alloc);
					if (opendir_result == OPENDIR_OK) {
						tmp_handle->close();
					}
					return opendir_result; /* return from lambda */
				};

				Opendir_result opendir_result =
					_dir_op(OPENDIR_ERR_LOOKUP_FAILED,
				            OPENDIR_ERR_PERMISSION_DENIED,
				            OPENDIR_OK,
				            path, opendir_fn);

				if (opendir_result != OPENDIR_OK)
					return opendir_result;
			}

			Dir_vfs_handle *dir_vfs_handle;
			try {
				dir_vfs_handle = new (alloc)
					Dir_vfs_handle(*this, *this, alloc, path);
			}
			catch (Genode::Out_of_ram)  { return OPENDIR_ERR_OUT_OF_RAM; }
			catch (Genode::Out_of_caps) { return OPENDIR_ERR_OUT_OF_CAPS; }

			/* path equals "/" (for reading the name of this directory) */
			if (strlen(sub_path) == 0)
				sub_path = "/";

			result = open_composite_dirs(sub_path, *dir_vfs_handle);
			if (result == OPENDIR_OK) {
				*out_handle = dir_vfs_handle;
			} else {
				/* close the master handle and the rest will follow */
				close(dir_vfs_handle);
			}
			return result;
		}

		Openlink_result openlink(char const *path, bool create,
		                         Vfs_handle **out_handle,
		                         Allocator &alloc) override
		{
			auto openlink_fn = [&] (File_system &fs, char const *path)
			{
				return fs.openlink(path, create, out_handle, alloc);
			};

			return _dir_op(OPENLINK_ERR_LOOKUP_FAILED,
			               OPENLINK_ERR_PERMISSION_DENIED,
			               OPENLINK_OK,
			               path, openlink_fn);
		}

		void close(Vfs_handle *handle) override
		{
			if (handle && (&handle->ds() == this))
				destroy(handle->alloc(), handle);
		}

		Watch_result watch(char const      *path,
		                   Vfs_watch_handle **handle,
		                   Allocator        &alloc) override
		{
			Watch_result res = WATCH_ERR_UNACCESSIBLE;
			Dir_watch_handle *meta_handle = nullptr;

			char const *sub_path = _sub_path(path);
			if (!sub_path) return res;

			for (File_system *fs = _first_file_system; fs; fs = fs->next) {
				Vfs_watch_handle *sub_handle;

				if (fs->watch(sub_path, &sub_handle, alloc) == WATCH_OK) {
					if (meta_handle == nullptr) {
						/* at least one non-static FS, allocate handle */
						meta_handle = new (alloc) Dir_watch_handle(*this, alloc);
						*handle = meta_handle;
						res = WATCH_OK;
					}

					/* attach child FS handle to returned handle */
					new (alloc)
						Dir_watch_handle::Watch_handle_element(
							meta_handle->handle_registry, *sub_handle);
				}
			}

			return res;
		}

		void close(Vfs_watch_handle *handle) override
		{
			if (handle && (&handle->fs() == this))
				destroy(handle->alloc(), handle);
		}

		Unlink_result unlink(char const *path) override
		{
			auto unlink_fn = [] (File_system &fs, char const *path)
			{
				return fs.unlink(path);
			};

			return _dir_op(UNLINK_ERR_NO_ENTRY, UNLINK_ERR_NO_PERM, UNLINK_OK,
			               path, unlink_fn);
		}

		Rename_result rename(char const *from_path, char const *to_path) override
		{
			from_path = _sub_path(from_path);
			to_path = _sub_path(to_path);

			/* path does not match directory name */
			if (!from_path)
				return RENAME_ERR_NO_ENTRY;

			/*
			 * Cannot rename a path in the static VFS configuration.
			 */
			if (strlen(from_path) == 0)
				return RENAME_ERR_NO_PERM;

			/*
			 * Check if destination path resides within the same file
			 * system instance as the source path.
			 */
			if (!to_path)
				return RENAME_ERR_CROSS_FS;

			Rename_result final = RENAME_ERR_NO_ENTRY;
			for (File_system *fs = _first_file_system; fs; fs = fs->next) {
				switch (fs->rename(from_path, to_path)) {
				case RENAME_OK:           return RENAME_OK;
				case RENAME_ERR_NO_ENTRY: continue;
				case RENAME_ERR_NO_PERM:  return RENAME_ERR_NO_PERM;
				case RENAME_ERR_CROSS_FS: final = RENAME_ERR_CROSS_FS;
				}
			}
			return final;
		}

		/***************************
		 ** File_system interface **
		 ***************************/

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

		void apply_config(Genode::Xml_node const &node) override
		{
			using namespace Genode;

			File_system *curr = _first_file_system;
			for (unsigned i = 0; i < node.num_sub_nodes(); i++, curr = curr->next) {
				Xml_node const &sub_node = node.sub_node(i);

				/* check if type of XML node matches current file-system type */
				if (sub_node.has_type(curr->type()) == false) {
					Genode::error("VFS config update failed (node type '",
					               sub_node.type(), "' != fs type '", curr->type(),"')");
					return;
				}

				curr->apply_config(node.sub_node(i));
			}
		}


		/********************************
		 ** File I/O service interface **
		 ********************************/

		Write_result write(Vfs_handle *, Const_byte_range_ptr const &, size_t &) override
		{
			return WRITE_ERR_INVALID;
		}

		bool queue_read(Vfs_handle *vfs_handle, size_t) override
		{
			Dir_vfs_handle *dir_vfs_handle =
				static_cast<Dir_vfs_handle*>(vfs_handle);

			if (_vfs_root)
				return _queue_read_of_file_systems(dir_vfs_handle);

			if (_top_dir(dir_vfs_handle->path.base()))
				return true;

			return _queue_read_of_file_systems(dir_vfs_handle);
		}

		Read_result complete_read(Vfs_handle *vfs_handle,
		                          Byte_range_ptr const &dst,
		                          size_t &out_count) override
		{
			out_count = 0;

			if (dst.num_bytes < sizeof(Dirent))
				return READ_ERR_INVALID;

			Dir_vfs_handle *dir_vfs_handle =
				static_cast<Dir_vfs_handle*>(vfs_handle);

			if (_vfs_root)
				return _complete_read_of_file_systems(dir_vfs_handle, dst, out_count);

			if (_top_dir(dir_vfs_handle->path.base())) {

				Dirent &dirent = *(Dirent*)dst.start;

				file_offset const index = vfs_handle->seek() / sizeof(Dirent);

				if (index == 0) {

					dirent = {
						.fileno = 1,
						.type   = Dirent_type::DIRECTORY,
						.rwx    = Node_rwx::rwx(),
						.name   = { _name.string() }
					};

				} else {

					dirent = {
						.fileno = 0,
						.type   = Dirent_type::END,
						.rwx    = { },
						.name   = { }
					};
				}

				out_count = sizeof(Dirent);

				return READ_OK;
			}

			return _complete_read_of_file_systems(dir_vfs_handle, dst, out_count);
		}

		Ftruncate_result ftruncate(Vfs_handle *, file_size) override
		{
			return FTRUNCATE_ERR_NO_PERM;
		}

		bool read_ready(Vfs_handle const &handle) const override
		{
			if (&handle.fs() == this)
				return true;

			return handle.fs().read_ready(handle);
		}

		bool write_ready(Vfs_handle const &handle) const override
		{
			if (&handle.fs() == this)
				return false;

			return handle.fs().write_ready(handle);
		}

		bool notify_read_ready(Vfs_handle *handle) override
		{
			if (&handle->fs() == this)
				return true;

			return handle->fs().notify_read_ready(handle);
		}

		bool queue_sync(Vfs_handle *vfs_handle) override
		{
			bool result = true;

			Dir_vfs_handle *dir_vfs_handle =
				static_cast<Dir_vfs_handle*>(vfs_handle);

			auto f = [&result, dir_vfs_handle] (Dir_vfs_handle::Subdir_handle_element &e) {
				/* forward the response handler */
				dir_vfs_handle->apply_handler([&] (Read_ready_response_handler &h) {
					e.vfs_handle.handler(&h); });
				e.synced = false;

				if (!e.vfs_handle.fs().queue_sync(&e.vfs_handle)) {
					result = false;
				}
			};

			dir_vfs_handle->subdir_handle_registry.for_each(f);

			return result;
		}

		Sync_result complete_sync(Vfs_handle *vfs_handle) override
		{
			Sync_result result = SYNC_OK;

			Dir_vfs_handle *dir_vfs_handle =
				static_cast<Dir_vfs_handle*>(vfs_handle);

			auto f = [&result] (Dir_vfs_handle::Subdir_handle_element &e) {
				if (e.synced)
					return;

				Sync_result r = e.vfs_handle.fs().complete_sync(&e.vfs_handle);
				if (r != SYNC_OK)
					result = r;
				else
					e.synced = true;
			};

			dir_vfs_handle->subdir_handle_registry.for_each(f);

			return result;
		}
};

#endif /* _INCLUDE__VFS__DIR_FILE_SYSTEM_H_ */