diff --git a/repos/libports/run/libc_vfs_fs.run b/repos/libports/run/libc_vfs_fs.run new file mode 100644 index 000000000..77d5e3dfe --- /dev/null +++ b/repos/libports/run/libc_vfs_fs.run @@ -0,0 +1,76 @@ +# +# \brief Test for using the vfs server +# \author Emery Hemingway +# \date 2015-08-17 +# + +# +# Build +# + +build { core init server/vfs test/libc_vfs } + +create_boot_directory + +# +# Generate config +# + +install_config { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} + +# +# Boot modules +# + +build_boot_image { + core init vfs + ld.lib.so libc.lib.so + test-libc_vfs +} + +# +# Execute test case +# + +append qemu_args " -m 128 -nographic " +run_genode_until {.*child "test-libc_vfs" exited with exit value 0.*} 60 + +# vi: set ft=tcl : diff --git a/repos/os/src/server/vfs/assert.h b/repos/os/src/server/vfs/assert.h new file mode 100644 index 000000000..a4ffe6fe9 --- /dev/null +++ b/repos/os/src/server/vfs/assert.h @@ -0,0 +1,110 @@ +/* + * \brief VFS result checks + * \author Emery Hemingway + * \date 2015-08-19 + */ + +/* + * Copyright (C) 2015 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _VFS__ASSERT_H_ +#define _VFS__ASSERT_H_ + +/* Genode includes */ +#include +#include + +namespace File_system { + + using namespace Vfs; + + static inline void assert_mkdir(Directory_service::Mkdir_result r) + { + typedef Directory_service::Mkdir_result Result; + + switch (r) { + case Result::MKDIR_ERR_NAME_TOO_LONG: throw Name_too_long(); + case Result::MKDIR_ERR_NO_ENTRY: throw Lookup_failed(); + case Result::MKDIR_ERR_NO_SPACE: throw No_space(); + case Result::MKDIR_ERR_NO_PERM: throw Permission_denied(); + case Result::MKDIR_ERR_EXISTS: throw Node_already_exists(); + case Result::MKDIR_OK: break; + } + } + + static inline void assert_open(Directory_service::Open_result r) + { + typedef Directory_service::Open_result Result; + + switch (r) { + case Result::OPEN_ERR_NAME_TOO_LONG: throw Invalid_name(); + case Result::OPEN_ERR_UNACCESSIBLE: throw Lookup_failed(); + case Result::OPEN_ERR_NO_SPACE: throw No_space(); + case Result::OPEN_ERR_NO_PERM: throw Permission_denied(); + case Result::OPEN_ERR_EXISTS: throw Node_already_exists(); + case Result::OPEN_OK: break; + } + } + + static inline void assert_symlink(Directory_service::Symlink_result r) + { + typedef Directory_service::Symlink_result Result; + + switch (r) { + case Result::SYMLINK_ERR_NAME_TOO_LONG: throw Invalid_name(); + case Result::SYMLINK_ERR_NO_ENTRY: throw Lookup_failed(); + case Result::SYMLINK_ERR_NO_SPACE: throw No_space(); + case Result::SYMLINK_ERR_NO_PERM: throw Permission_denied(); + case Result::SYMLINK_ERR_EXISTS: throw Node_already_exists(); + case Result::SYMLINK_OK: break; + } + } + + static inline void assert_truncate(File_io_service::Ftruncate_result r) + { + typedef File_io_service::Ftruncate_result Result; + + switch (r) { + case Result::FTRUNCATE_ERR_INTERRUPT: throw Invalid_handle(); + case Result::FTRUNCATE_ERR_NO_SPACE: throw No_space(); + case Result::FTRUNCATE_ERR_NO_PERM: throw Permission_denied(); + case Result::FTRUNCATE_OK: break; + } + } + + static inline void assert_unlink(Directory_service::Unlink_result r) + { + typedef Directory_service::Unlink_result Result; + switch (r) { + case Result::UNLINK_ERR_NO_ENTRY: throw Lookup_failed(); + case Result::UNLINK_ERR_NO_PERM: throw Permission_denied(); + case Result::UNLINK_OK: break; + } + } + + static inline void assert_stat(Directory_service::Stat_result r) + { + typedef Directory_service::Stat_result Result; + switch (r) { + case Result::STAT_ERR_NO_ENTRY: throw Lookup_failed(); + case Result::STAT_OK: break; + } + } + + static inline void assert_rename(Directory_service::Rename_result r) + { + typedef Directory_service::Rename_result Result; + switch (r) { + case Result::RENAME_ERR_NO_ENTRY: throw Lookup_failed(); + case Result::RENAME_ERR_CROSS_FS: throw Permission_denied(); + case Result::RENAME_ERR_NO_PERM: throw Permission_denied(); + case Result::RENAME_OK: break; + } + } +} + +#endif diff --git a/repos/os/src/server/vfs/main.cc b/repos/os/src/server/vfs/main.cc new file mode 100644 index 000000000..c8d0ae905 --- /dev/null +++ b/repos/os/src/server/vfs/main.cc @@ -0,0 +1,550 @@ +/* + * \brief VFS File_system server + * \author Emery Hemingway + * \date 2015-08-16 + */ + +/* + * Copyright (C) 2015 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +/* Genode includes */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* Local includes */ +#include "assert.h" +#include "node_cache.h" +#include "node_handle_registry.h" + +namespace File_system { + + using namespace Genode; + using namespace Vfs; + + class Session_component; + class Root; + struct Main; + + typedef Genode::Path Subpath; + + Vfs::File_system *root() + { + static Vfs::Dir_file_system _root(config()->xml_node().sub_node("vfs"), + Vfs::global_file_system_factory()); + + return &_root; + } +}; + + +class File_system::Session_component : public Session_rpc_object +{ + private: + + Node_handle_registry _handle_registry; + Subpath const _root_path; + Signal_rpc_member _process_packet_dispatcher; + bool _writable; + + + /****************************** + ** Packet-stream processing ** + ******************************/ + + /** + * Perform packet operation + */ + void _process_packet_op(Packet_descriptor &packet) + { + void * const content = tx_sink()->packet_content(packet); + size_t const length = packet.length(); + seek_off_t const offset = packet.position(); + + if ((!(content && length)) || (packet.length() > packet.size())) { + packet.succeeded(false); + return; + } + + /* resulting length */ + size_t res_length = 0; + + switch (packet.operation()) { + + case Packet_descriptor::READ: { + Node *node = _handle_registry.lookup_read(packet.handle()); + if (!node) return; + Node_lock_guard guard(node); + res_length = node->read((char *)content, length, offset); + break; + } + + case Packet_descriptor::WRITE: { + Node *node = _handle_registry.lookup_write(packet.handle()); + if (!node) return; + Node_lock_guard guard(node); + res_length = node->write((char const *)content, length, offset); + break; + } + } + + if (res_length) { + packet.length(res_length); + packet.succeeded(true); + } + } + + void _process_packet() + { + Packet_descriptor packet = tx_sink()->get_packet(); + + /* assume failure by default */ + packet.succeeded(false); + + _process_packet_op(packet); + + /* + * The 'acknowledge_packet' function cannot block because we + * checked for 'ready_to_ack' in '_process_packets'. + */ + tx_sink()->acknowledge_packet(packet); + } + + /** + * Called by signal dispatcher, executed in the context of the main + * thread (not serialized with the RPC functions) + */ + void _process_packets(unsigned) + { + while (tx_sink()->packet_avail()) { + + /* + * Make sure that the '_process_packet' function does not + * block. + * + * If the acknowledgement queue is full, we defer packet + * processing until the client processed pending + * acknowledgements and thereby emitted a ready-to-ack + * signal. Otherwise, the call of 'acknowledge_packet()' + * in '_process_packet' would infinitely block the context + * of the main thread. The main thread is however needed + * for receiving any subsequent 'ready-to-ack' signals. + */ + if (!tx_sink()->ready_to_ack()) + return; + + _process_packet(); + } + } + + /** + * Check if string represents a valid path (must start with '/') + */ + static void _assert_valid_path(char const *path) { + if (!path || path[0] != '/') throw Lookup_failed(); } + + /** + * Check if string represents a valid name (must not contain '/') + */ + static void _assert_valid_name(char const *name) { + for (char const *p = name; *p; ++p) + if (*p == '/') + throw Invalid_name(); } + + public: + + /** + * Constructor + * \param ep thead entrypoint for session + * \param cache node cache + * \param tx_buf_size shared transmission buffer size + * \param root_path path root of the session + * \param writable whether the session can modify files + */ + Session_component(Server::Entrypoint &ep, + Node_cache &cache, + size_t tx_buf_size, + Subpath const &root_path, + bool writable) + : + Session_rpc_object(env()->ram_session()->alloc(tx_buf_size), ep.rpc_ep()), + _handle_registry(cache), + _root_path(root_path.base()), + _process_packet_dispatcher(ep, *this, &Session_component::_process_packets), + _writable(writable) + { + /* + * Register '_process_packets' dispatch function as signal + * handler for packet-avail and ready-to-ack signals. + */ + _tx.sigh_packet_avail(_process_packet_dispatcher); + _tx.sigh_ready_to_ack(_process_packet_dispatcher); + } + + /** + * Destructor + */ + ~Session_component() + { + Dataspace_capability ds = tx_sink()->dataspace(); + env()->ram_session()->free(static_cap_cast(ds)); + } + + + /*************************** + ** File_system interface ** + ***************************/ + + Dir_handle dir(Path const &path, bool create) override + { + if ((!_writable) && create) + throw Permission_denied(); + + char const *path_str = path.string(); + _assert_valid_path(path_str); + + /* re-root the path */ + Subpath dir_path(path_str+1, _root_path.base()); + path_str = dir_path.base(); + + return _handle_registry.directory(path_str, create); + } + + File_handle file(Dir_handle dir_handle, Name const &name, + Mode fs_mode, bool create) override + { + if ((!_writable) && + (create || (fs_mode != STAT_ONLY && fs_mode != READ_ONLY))) + throw Permission_denied(); + + char const *name_str = name.string(); + _assert_valid_name(name_str); + + Directory *dir = _handle_registry.lookup_and_lock(dir_handle); + Node_lock_guard dir_guard(dir); + + /* re-root the path */ + Subpath subpath(name_str, dir->path()); + char *path_str = subpath.base(); + File_handle handle = _handle_registry.file(path_str, fs_mode, create); + if (create) + dir->mark_as_updated(); + return handle; + } + + Symlink_handle symlink(Dir_handle dir_handle, Name const &name, bool create) override + { + if (create && !_writable) throw Permission_denied(); + + char const *name_str = name.string(); + _assert_valid_name(name_str); + + Directory *dir = _handle_registry.lookup_and_lock(dir_handle); + Node_lock_guard dir_guard(dir); + + /* re-root the path */ + Subpath subpath(name_str, dir->path()); + char *path_str = subpath.base(); + + Symlink_handle handle = _handle_registry.symlink( + path_str, (_writable ? READ_WRITE : READ_ONLY), create); + if (create) + dir->mark_as_updated(); + return handle; + } + + Node_handle node(Path const &path) override + { + char const *path_str = path.string(); + _assert_valid_path(path_str); + + /* re-root the path */ + Subpath sub_path(path_str+1, _root_path.base()); + path_str = sub_path.base(); + + return _handle_registry.node(path_str); + } + + void close(Node_handle handle) { _handle_registry.free(handle); } + + Status status(Node_handle node_handle) + { + Directory_service::Stat vfs_stat; + File_system::Status fs_stat; + Node *node; + try { node = _handle_registry.lookup_and_lock(node_handle); } + catch (Invalid_handle) { return fs_stat; } + Node_lock_guard guard(node); + + char const *path_str = node->path(); + + if (root()->stat(path_str, vfs_stat) != Directory_service::STAT_OK) + return fs_stat; + + fs_stat.inode = vfs_stat.inode; + + switch (vfs_stat.mode & ( + Directory_service::STAT_MODE_DIRECTORY | + Directory_service::STAT_MODE_SYMLINK | + File_system::Status::MODE_FILE)) { + + case Directory_service::STAT_MODE_DIRECTORY: + fs_stat.mode = File_system::Status::MODE_DIRECTORY; + fs_stat.size = root()->num_dirent(path_str) * sizeof(Directory_entry); + return fs_stat; + + case Directory_service::STAT_MODE_SYMLINK: + fs_stat.mode = File_system::Status::MODE_SYMLINK; + break; + + default: /* Directory_service::STAT_MODE_FILE */ + fs_stat.mode = File_system::Status::MODE_FILE; + break; + } + + fs_stat.size = vfs_stat.size; + return fs_stat; + } + + /** + * Set information about an open file or directory + */ + void control(Node_handle, Control) { } + + /** + * Delete file or directory + */ + void unlink(Dir_handle dir_handle, Name const &name) + { + if (!_writable) throw Permission_denied(); + + char const *str = name.string(); + _assert_valid_name(str); + + Directory *dir = _handle_registry.lookup_and_lock(dir_handle); + Node_lock_guard guard(dir); + + Subpath path(str, dir->path()); + str = path.base(); + + /* + * If the file is open in any other session, + * unlinking is not allowed. This is to prevent + * dangling pointers in handle registries. + */ + if (_handle_registry.is_open(str)) + throw Permission_denied(); + + assert_unlink(root()->unlink(str)); + dir->mark_as_updated(); + } + + /** + * Truncate or grow file to specified size + */ + void truncate(File_handle file_handle, file_size_t size) + { + File *file = _handle_registry.lookup_and_lock(file_handle); + Node_lock_guard guard(file); + file->truncate(size); + file->mark_as_updated(); + } + + /** + * Move and rename directory entry + */ + void move(Dir_handle from_dir_handle, Name const &from_name, + Dir_handle to_dir_handle, Name const &to_name) + { + if (!_writable) + throw Permission_denied(); + + char const *from_str = from_name.string(); + char const *to_str = to_name.string(); + + _assert_valid_name(from_str); + _assert_valid_name( to_str); + + if (_handle_registry.refer_to_same_node(from_dir_handle, to_dir_handle)) { + Directory *dir = _handle_registry.lookup_and_lock(from_dir_handle); + Node_lock_guard from_guard(dir); + + from_str = Subpath(from_str, dir->path()).base(); + to_str = Subpath( to_str, dir->path()).base(); + + if (_handle_registry.is_open(to_str)) + throw Permission_denied(); + + assert_rename(root()->rename(from_str, to_str)); + _handle_registry.rename(from_str, to_str); + + dir->mark_as_updated(); + return; + } + + Directory *from_dir = _handle_registry.lookup_and_lock(from_dir_handle); + Node_lock_guard from_guard(from_dir); + + Directory *to_dir = _handle_registry.lookup_and_lock( to_dir_handle); + Node_lock_guard to_guard(to_dir); + + from_str = Subpath(from_str, from_dir->path()).base(); + to_str = Subpath( to_str, to_dir->path()).base(); + if (_handle_registry.is_open(to_str)) + throw Permission_denied(); + + assert_rename(root()->rename(from_str, to_str)); + _handle_registry.rename(from_str, to_str); + + from_dir->mark_as_updated(); + to_dir->mark_as_updated(); + } + + void sigh(Node_handle node_handle, Signal_context_capability sigh) { + _handle_registry.sigh(node_handle, sigh); } + + /** + * Sync the VFS and send any pending signal on the node. + */ + void sync(Node_handle handle) + { + Node *node; + try { + node = _handle_registry.lookup_and_lock(handle); + } catch (Invalid_handle) { + return; + } + Node_lock_guard guard(node); + + root()->sync(node->path()); + node->notify_listeners(); + } +}; + + +class File_system::Root : public Root_component +{ + private: + + Node_cache _cache; + Server::Entrypoint &_ep; + + protected: + + Session_component *_create_session(const char *args) override + { + Subpath session_root; + bool writeable = false; + + Session_label const label(args); + + try { + Session_policy policy(label); + char buf[MAX_PATH_LEN]; + + /* Determine the session root directory. + * Defaults to '/' if not specified by session + * policy or session arguments. + */ + try { + policy.attribute("root").value(buf, MAX_PATH_LEN); + session_root.append(buf); + } catch (Xml_node::Nonexistent_attribute) { } + + Arg_string::find_arg(args, "root").string(buf, MAX_PATH_LEN, "/"); + session_root.append(buf); + + /* Determine if the session is writeable. + * Policy overrides arguments, both default to false. + */ + if (policy.attribute_value("writeable", false)) + writeable = Arg_string::find_arg(args, "writeable").bool_value(false); + + } catch (Session_policy::No_policy_defined) { + PERR("rejecting session request, no matching policy for %s", label.string()); + throw Root::Unavailable(); + } + + size_t ram_quota = Arg_string::find_arg(args, "ram_quota" ).ulong_value(0); + size_t tx_buf_size = Arg_string::find_arg(args, "tx_buf_size").ulong_value(0); + + if (!tx_buf_size) + throw Root::Invalid_args(); + + /* + * Check if donated ram quota suffices for session data, + * and communication buffer. + */ + size_t session_size = sizeof(Session_component) + tx_buf_size; + if (max((size_t)4096, session_size) > ram_quota) { + PERR("insufficient 'ram_quota' from %s, got %zd, need %zd", + label.string(), ram_quota, session_size); + throw Root::Quota_exceeded(); + } + + /* check if the session root exists */ + if (!root()->is_directory(session_root.base())) { + PERR("session root '%s' not found for '%s'", session_root.base(), label.string()); + throw Root::Unavailable(); + } + + Session_component *session = new(md_alloc()) + Session_component(_ep, _cache, tx_buf_size, session_root, writeable); + PLOG("session opened for '%s' at '%s'", label.string(), session_root.base()); + return session; + } + + public: + + /** + * Constructor + * + * \param ep entrypoint + * \param md_alloc meta-data allocator + */ + Root(Server::Entrypoint &ep, Allocator &md_alloc) + : + Root_component(&ep.rpc_ep(), &md_alloc), + _ep(ep) + { } +}; + + +struct File_system::Main +{ + Server::Entrypoint &ep; + + /* + * Initialize root interface + */ + Sliced_heap sliced_heap = { env()->ram_session(), env()->rm_session() }; + + Root fs_root = { ep, sliced_heap }; + + Main(Server::Entrypoint &ep) : ep(ep) + { + env()->parent()->announce(ep.manage(fs_root)); + PLOG("virtual file system server started"); + } +}; + + +/********************** + ** Server framework ** + **********************/ + +char const * Server::name() { return "vfs_ep"; } + +Genode::size_t Server::stack_size() { return 2*1024*sizeof(long); } + +void Server::construct(Server::Entrypoint &ep) { + static File_system::Main inst(ep); } diff --git a/repos/os/src/server/vfs/node_cache.h b/repos/os/src/server/vfs/node_cache.h new file mode 100644 index 000000000..e0dca93a3 --- /dev/null +++ b/repos/os/src/server/vfs/node_cache.h @@ -0,0 +1,386 @@ +/* + * \brief VFS server node cache + * \author Emery Hemingway + * \date 2015-09-02 + */ + +/* + * Copyright (C) 2015 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _VFS__NODE_CACHE_H_ +#define _VFS__NODE_CACHE_H_ + +/* Genode includes */ +#include +#include +#include +#include + +namespace File_system { + + struct Node; + struct Directory; + class File; + struct Symlink; + class Node_cache; + + typedef Avl_string Avl_path_string; + + Vfs::File_system *root(); +} + + +/** + * Reference counted node object that can be inserted + * into an AVL tree. + */ +class File_system::Node : public Node_base, public Avl_node, private Noncopyable +{ + friend class Node_cache; + + private: + + unsigned _ref_count; + char _path[Vfs::MAX_PATH_LEN]; + + protected: + + void incr() { ++_ref_count; } + void decr() { --_ref_count; } + + bool in_use() const { return _ref_count; } + + public: + + Node(char const *path) + : _ref_count(0) { + strncpy(_path, path, sizeof(_path)); } + + char const *path() const { return _path; } + void path(char const *new_path) { + strncpy(_path, new_path, sizeof(_path)); } + + virtual size_t read(char *dst, size_t len, seek_off_t) { return 0; } + virtual size_t write(char const *src, size_t len, seek_off_t) { return 0; } + + /************************ + ** Avl node interface ** + ************************/ + + bool higher(Node *n) { return (strcmp(n->_path, _path) > 0); } + + /** + * Find by path + */ + Node *find_by_path(const char *path) + { + if (strcmp(path, _path) == 0) return this; + + Node *n = Avl_node::child(strcmp(path, _path) > 0); + return n ? n->find_by_path(path) : nullptr; + } +}; + + +struct File_system::Directory : Node +{ + Directory(char const *path, bool create) + : Node(path) + { + if (create) + assert_mkdir(root()->mkdir(path, 0777)); + else if (strcmp("/", path, 2) == 0) + return; + else if (!root()->leaf_path(path)) + throw Lookup_failed(); + else if (!root()->is_directory(path)) + throw Node_already_exists(); + } + + size_t read(char *dst, size_t len, seek_off_t seek_offset) + { + Directory_service::Dirent vfs_dirent; + size_t blocksize = sizeof(File_system::Directory_entry); + + int index = (seek_offset / blocksize); + + size_t remains = len; + + while (remains >= blocksize) { + memset(&vfs_dirent, 0x00, sizeof(vfs_dirent)); + if (root()->dirent(path(), index++, vfs_dirent) + != Vfs::Directory_service::DIRENT_OK) + return len - remains; + + File_system::Directory_entry *fs_dirent = (Directory_entry *)dst; + switch (vfs_dirent.type) { + case Vfs::Directory_service::DIRENT_TYPE_DIRECTORY: + fs_dirent->type = File_system::Directory_entry::TYPE_DIRECTORY; + break; + case Vfs::Directory_service::DIRENT_TYPE_SYMLINK: + fs_dirent->type = File_system::Directory_entry::TYPE_SYMLINK; + break; + case Vfs::Directory_service::DIRENT_TYPE_FILE: + default: + fs_dirent->type = File_system::Directory_entry::TYPE_FILE; + break; + } + strncpy(fs_dirent->name, vfs_dirent.name, MAX_NAME_LEN); + + remains -= blocksize; + dst += blocksize; + } + return len - remains; + } +}; + + +class File_system::File : public Node +{ + private: + + Vfs_handle *_handle; + unsigned _mode; + + public: + + File(char const *path, Mode fs_mode, bool create) + : + Node(path), + _handle(nullptr) + { + switch (fs_mode) { + case STAT_ONLY: + case READ_ONLY: + _mode = Directory_service::OPEN_MODE_RDONLY; break; + case WRITE_ONLY: + case READ_WRITE: + _mode = Directory_service::OPEN_MODE_RDWR; break; + } + + unsigned mode = create ? + _mode | Directory_service::OPEN_MODE_CREATE : _mode; + + assert_open(root()->open(path, mode, &_handle)); + } + + ~File() { destroy(env()->heap(), _handle); } + + void open(Mode fs_mode) + { + if (_mode & Directory_service::OPEN_MODE_RDWR) return; + + unsigned mode; + switch (fs_mode) { + case WRITE_ONLY: + case READ_WRITE: + mode = Directory_service::OPEN_MODE_RDWR; break; + default: + return; + } + if (mode == _mode) return; + + Vfs_handle *new_handle = nullptr; + assert_open(root()->open(path(), mode, &new_handle)); + destroy(env()->heap(), _handle); + _handle = new_handle; + _mode = mode; + } + + void truncate(file_size_t size) { + assert_truncate(_handle->fs().ftruncate(_handle, size)); } + + size_t read(char *dst, size_t len, seek_off_t seek_offset) + { + Vfs::file_size res = 0; + _handle->seek(seek_offset); + _handle->fs().read(_handle, dst, len, res); + return res; + } + + size_t write(char const *src, size_t len, seek_off_t seek_offset) + { + Vfs::file_size res = 0; + _handle->seek(seek_offset); + _handle->fs().write(_handle, src, len, res); + mark_as_updated(); + return res; + } +}; + + +struct File_system::Symlink : Node +{ + Symlink(char const *path, bool create) + : Node(path) + { + if (create) + assert_symlink(root()->symlink("", path)); + else if (!root()->leaf_path(path)) + throw Lookup_failed(); + else { + Vfs::Directory_service::Stat s; + assert_stat(root()->stat(path, s)); + if (!(s.mode & Vfs::Directory_service::STAT_MODE_SYMLINK)) + throw Node_already_exists(); + } + } + + size_t read(char *dst, size_t len, seek_off_t seek_offset) + { + Vfs::file_size res = 0; + root()->readlink(path(), dst, len, res); + return res; + } + + size_t write(char const *src, size_t len, seek_off_t seek_offset) + { + root()->unlink(path()); + size_t n = (root()->symlink(src, path()) == Directory_service::SYMLINK_OK) + ? len : 0; + + if (n) { + mark_as_updated(); + notify_listeners(); + } + return n; + } +}; + + +/** + * This structure deduplicates nodes between sessions, without it + * the signal notifications would not propagate between sessions. + */ +struct File_system::Node_cache : Avl_tree +{ + Node *find(char const *path) { + return first() ? (Node *)first()->find_by_path(path) : nullptr; } + + void free(Node *node) + { + node->decr(); + if (node->in_use()) + return; + + remove(node); + destroy(env()->heap(), node); + } + + void remove_path(char const *path) + { + Node *node = find(path); + if (!node ) return; + + remove(node); + destroy(env()->heap(), node); + } + + void rename(char const *from, char const *to) + { + Node *node = find(to); + if (node) + throw Permission_denied(); + + node = find(from); + if (!node ) return; + + remove(node); + node->path(to); + insert(node); + node->mark_as_updated(); + } + + /** + * Return an existing node or query the VFS + * and alloc a proper node. + */ + Node *node(char const *path) + { + Node *node = find(path); + if (!node) { + Directory_service::Stat stat; + assert_stat(root()->stat(path, stat)); + + switch (stat.mode & ( + Directory_service::STAT_MODE_DIRECTORY | + Directory_service::STAT_MODE_SYMLINK | + File_system::Status::MODE_FILE)) { + + case Directory_service::STAT_MODE_DIRECTORY: + node = new (env()->heap()) Directory(path, false); + break; + + case Directory_service::STAT_MODE_SYMLINK: + node = new (env()->heap()) Symlink(path, false); + break; + + default: /* Directory_service::STAT_MODE_FILE */ + node = new (env()->heap()) File(path, READ_ONLY, false); + break; + } + insert(node); + } + node->incr(); + return node; + } + + Directory *directory(char const *path, bool create) + { + Directory *dir; + Node *node = find(path); + if (node) { + dir = dynamic_cast(node); + if (!dir) + throw Node_already_exists(); + + } else { + dir = new (env()->heap()) Directory(path, create); + insert(dir); + } + dir->incr(); + return dir; + } + + File *file(char const *path, Mode mode, bool create) + { + File *file; + Node *node = find(path); + if (node) { + file = dynamic_cast(node); + if (!file) + throw Node_already_exists(); + + file->open(mode); + + } else { + file = new (env()->heap()) File(path, mode, create); + insert(file); + } + file->incr(); + return file; + } + + Symlink *symlink(char const *path, bool create) + { + Symlink *link; + Node *node = find(path); + if (node) { + link = dynamic_cast(node); + if (!link) + throw Node_already_exists(); + + } else { + link = new (env()->heap()) Symlink(path, create); + insert(link); + } + link->incr(); + return link; + } +}; + +#endif diff --git a/repos/os/src/server/vfs/node_handle_registry.h b/repos/os/src/server/vfs/node_handle_registry.h new file mode 100644 index 000000000..c8236b297 --- /dev/null +++ b/repos/os/src/server/vfs/node_handle_registry.h @@ -0,0 +1,316 @@ +/* + * \brief Facility for managing the session-local node-handle namespace + * \author Norman Feske + * \date 2012-04-11 + * + * This file is derived from os/include/file_system/node_handle_registry.h. + */ + +#ifndef _VFS__NODE_HANDLE_REGISTRY_H_ +#define _VFS__NODE_HANDLE_REGISTRY_H_ + +#include + +namespace File_system { + + class Node; + class Directory; + class File; + class Symlink; + + /** + * Type trait for determining the node type for a given handle type + */ + template struct Node_type; + template<> struct Node_type { typedef Node Type; }; + template<> struct Node_type { typedef Directory Type; }; + template<> struct Node_type { typedef File Type; }; + template<> struct Node_type { typedef Symlink Type; }; + + + /** + * Type trait for determining the handle type for a given node type + */ + template struct Handle_type; + template<> struct Handle_type { typedef Node_handle Type; }; + template<> struct Handle_type { typedef Dir_handle Type; }; + template<> struct Handle_type { typedef File_handle Type; }; + template<> struct Handle_type { typedef Symlink_handle Type; }; + + + class Node_handle_registry + { + private: + + /* maximum number of open nodes per session */ + enum { MAX_NODE_HANDLES = 128U }; + + Lock mutable _lock; + Node *_nodes[MAX_NODE_HANDLES]; + + /** + * Each open node handle can act as a listener to be informed about + * node changes. + */ + Listener _listeners[MAX_NODE_HANDLES]; + + /** + * Mode information is stored here for each open node. + */ + enum Mode _modes[MAX_NODE_HANDLES]; + + /** + * A cache of open nodes shared between sessions. + */ + Node_cache &_cache; + + /** + * Allocate node handle + * + * \throw Out_of_node_handles + */ + int _alloc(Node *node, Mode mode) + { + Lock::Guard guard(_lock); + + for (unsigned i = 0; i < MAX_NODE_HANDLES; i++) + if (!_nodes[i]) { + _nodes[i] = node; + _modes[i] = mode; + return i; + } + + throw Out_of_node_handles(); + } + + bool _in_range(int handle) const + { + return ((handle >= 0) && (handle < MAX_NODE_HANDLES)); + } + + public: + + Node_handle_registry(Node_cache &cache) + : _cache(cache) + { + for (unsigned i = 0; i < MAX_NODE_HANDLES; i++) { + _nodes[i] = 0; + _modes[i] = STAT_ONLY; + } + } + + template + typename Handle_type::Type alloc(NODE_TYPE *node, Mode mode) + { + typedef typename Handle_type::Type Handle; + return Handle(_alloc(node, mode)); + } + + /** + * Request a file from the cache and allocate a handle. + */ + Dir_handle directory(char const *path, bool create) + { + Directory *dir = _cache.directory(path, create); + return alloc(dir, READ_ONLY); + } + + /** + * Request a directory from the cache and allocate a handle. + */ + File_handle file(char const *path, Mode mode, bool create) + { + File *file = _cache.file(path, mode, create); + return alloc(file, mode); + } + + /** + * Request a symlink from the cache and allocate a handle. + */ + Symlink_handle symlink(char const *path, Mode mode, bool create) + { + Symlink *link = _cache.symlink(path, create); + return alloc(link, mode); + } + + /** + * Request a node from the cache and allocate a handle. + */ + Node_handle node(char const *path) + { + Node *node = _cache.node(path); + return alloc(node, STAT_ONLY); + } + + /** + * Release node handle + */ + void free(Node_handle handle) + { + if (!_in_range(handle.value)) + return; + + Lock::Guard guard(_lock); + + Node *node = dynamic_cast(_nodes[handle.value]); + if (!node) { return; } + + node->lock(); + node->notify_listeners(); + + /* + * De-allocate handle + */ + Listener &listener = _listeners[handle.value]; + + if (listener.valid()) + node->remove_listener(&listener); + + _nodes[handle.value] = 0; + listener = Listener(); + + _modes[handle.value] = STAT_ONLY; + + node->unlock(); + _cache.free(node); + } + + /** + * Lookup node using its handle as key + * + * The node returned by this function is in a locked state. + * + * \throw Invalid_handle + */ + template + typename Node_type::Type *lookup_and_lock(HANDLE_TYPE handle) + { + if (!_in_range(handle.value)) + throw Invalid_handle(); + + Lock::Guard guard(_lock); + + typedef typename Node_type::Type Node; + Node *node = dynamic_cast(_nodes[handle.value]); + if (!node) + throw Invalid_handle(); + + node->lock(); + return node; + } + + /** + * Lookup a node for reading + * + * A node in a locked state or a null pointer is returned. + */ + Node *lookup_read(Node_handle handle) + { + if (!_in_range(handle.value)) + return nullptr; + + Lock::Guard guard(_lock); + + switch (_modes[handle.value]) { + case READ_ONLY: + case READ_WRITE: + break; + default: + return nullptr; + } + + Node *node = dynamic_cast(_nodes[handle.value]); + if (node) + node->lock(); + return node; + } + + /** + * Lookup a node for writing + * + * A node in a locked state or a null pointer is returned. + */ + Node *lookup_write(Node_handle handle) + { + if (!_in_range(handle.value)) + return nullptr; + + Lock::Guard guard(_lock); + + switch (_modes[handle.value]) { + case WRITE_ONLY: + case READ_WRITE: + break; + default: + return nullptr; + } + + Node *node = dynamic_cast(_nodes[handle.value]); + if (node) + node->lock(); + return node; + } + + bool refer_to_same_node(Node_handle h1, Node_handle h2) const + { + Lock::Guard guard(_lock); + + if (!(_in_range(h1.value) && _in_range(h2.value))) + throw Invalid_handle(); + + return _nodes[h1.value] == _nodes[h2.value]; + } + + /** + * Register signal handler to be notified of node changes + */ + void sigh(Node_handle handle, Signal_context_capability sigh) + { + if (!_in_range(handle.value)) + throw Invalid_handle(); + + Lock::Guard guard(_lock); + + Node *node = dynamic_cast(_nodes[handle.value]); + if (!node) + throw Invalid_handle(); + + node->lock(); + Node_lock_guard node_lock_guard(node); + + Listener &listener = _listeners[handle.value]; + + /* + * If there was already a handler registered for the node, + * remove the old handler. + */ + if (listener.valid()) + node->remove_listener(&listener); + + /* + * Register new handler + */ + listener = Listener(sigh); + node->add_listener(&listener); + } + + /** + * Remove a path from the cache. + */ + void remove(char const *path) { _cache.remove_path(path); } + + /** + * Rename a node in the cache. + */ + void rename(char const *from, char const *to) { + _cache.rename(from, to); } + + /** + * Is the node open (present in the cache)? + */ + bool is_open(char const *path) { + return _cache.find(path); } + }; +} + +#endif /* _VFS__NODE_HANDLE_REGISTRY_H_ */ diff --git a/repos/os/src/server/vfs/target.mk b/repos/os/src/server/vfs/target.mk new file mode 100644 index 000000000..d5095585b --- /dev/null +++ b/repos/os/src/server/vfs/target.mk @@ -0,0 +1,4 @@ +TARGET = vfs +SRC_CC = main.cc +LIBS = base config server vfs +INC_DIR += $(PRG_DIR) diff --git a/tool/autopilot.list b/tool/autopilot.list index 5c9dce4b4..e14724835 100644 --- a/tool/autopilot.list +++ b/tool/autopilot.list @@ -9,6 +9,8 @@ noux noux_net_netcat libc_ffat libc_vfs +libc_vfs_ram +libc_vfs_fs timed_semaphore signal sub_rm