From eda44d61544f515d5f9e2a8220ab23f18f178611 Mon Sep 17 00:00:00 2001 From: Christian Helmuth Date: Fri, 8 Nov 2013 16:44:44 +0100 Subject: [PATCH] base-linux: hybrid file-system server The current implementation is missing support for symbolic links. Fixes #944. --- base-linux/run/lx_fs.run | 79 ++++ os/src/server/lx_fs/README | 44 +++ os/src/server/lx_fs/directory.h | 188 +++++++++ os/src/server/lx_fs/file.h | 138 +++++++ os/src/server/lx_fs/lx_util.h | 38 ++ os/src/server/lx_fs/main.cc | 437 +++++++++++++++++++++ os/src/server/lx_fs/node.h | 134 +++++++ os/src/server/lx_fs/node_handle_registry.h | 198 ++++++++++ os/src/server/lx_fs/symlink.h | 50 +++ os/src/server/lx_fs/target.mk | 6 + os/src/server/lx_fs/util.h | 63 +++ 11 files changed, 1375 insertions(+) create mode 100644 base-linux/run/lx_fs.run create mode 100644 os/src/server/lx_fs/README create mode 100644 os/src/server/lx_fs/directory.h create mode 100644 os/src/server/lx_fs/file.h create mode 100644 os/src/server/lx_fs/lx_util.h create mode 100644 os/src/server/lx_fs/main.cc create mode 100644 os/src/server/lx_fs/node.h create mode 100644 os/src/server/lx_fs/node_handle_registry.h create mode 100644 os/src/server/lx_fs/symlink.h create mode 100644 os/src/server/lx_fs/target.mk create mode 100644 os/src/server/lx_fs/util.h diff --git a/base-linux/run/lx_fs.run b/base-linux/run/lx_fs.run new file mode 100644 index 000000000..5e2f470d9 --- /dev/null +++ b/base-linux/run/lx_fs.run @@ -0,0 +1,79 @@ +# +# \brief Test for using the libc_fs plugin with the Linux file system +# \author Norman Feske +# \author Christian Helmuth +# \date 2013-11-07 +# + +assert_spec linux + +# +# Build +# + +build { core init server/lx_fs test/libc_fs } + +create_boot_directory + +# +# Generate config +# + +install_config { + + + + + + + + + + + + + + + + + + + + + + + +} + +# +# Create test-directory structure +# + +exec mkdir -p bin/libc_fs + +# +# Boot modules +# + +build_boot_image { + core init + ld.lib.so libc.lib.so libc_log.lib.so libc_fs.lib.so + lx_fs test-libc_fs + libc_fs +} + +# +# Execute test case +# + +run_genode_until {.*child exited with exit value 0.*} 60 + +puts "\ntest succeeded\n" + +# +# Cleanup test-directory structure +# + +exec rm -r bin/libc_fs + +# vi: set ft=tcl : diff --git a/os/src/server/lx_fs/README b/os/src/server/lx_fs/README new file mode 100644 index 000000000..7cce6be14 --- /dev/null +++ b/os/src/server/lx_fs/README @@ -0,0 +1,44 @@ +This directory contains an Genode file-system service to Linux host fs +wrapper. + +Configuration +~~~~~~~~~~~~~ + +Access to the file system can be tailored for each session depending on the +session's label. By default, no permissions are granted to any session. +To selectively permit access to (a part of) the file system, at least one +policy must be defined. + +The following configuration illustates the way of how to express policy. + +! +! +! +! +! +! + +Session-specific access-control policy is expressed via one or more '' +nodes. At session-creation time, each policy node is matched against the label +of the new session. If the label of a policy node matches, the defined policy +is applied. If multiple policies match, the one with the longest 'label' +attribute (the most specific one) is selected. + +A policy node may contain the following attributes. The mandatory 'root' +attribute defines the viewport of the session onto the file system. The +optional 'writeable' attribute grants the permission to modify the file system. + + +Example +~~~~~~~ + +To illustrate the use of lx_fs, refer to the 'base-linux/run/lx_fs.run' +script. + + +Notes +~~~~~ + +If the Linux file system experiences changes from other processes +'inotify' may help to keep the servers cache up-to-date. This is not +implemented yet. diff --git a/os/src/server/lx_fs/directory.h b/os/src/server/lx_fs/directory.h new file mode 100644 index 000000000..a3b9a60e0 --- /dev/null +++ b/os/src/server/lx_fs/directory.h @@ -0,0 +1,188 @@ +/* + * \brief File-system directory node + * \author Norman Feske + * \author Christian Helmuth + * \date 2013-11-11 + */ + +#ifndef _DIRECTORY_H_ +#define _DIRECTORY_H_ + +/* Genode include */ +#include + +/* local includes */ +#include +#include +#include + +#include + + +namespace File_system { + class Directory; +} + + +class File_system::Directory : public Node +{ + private: + + typedef Genode::Path Path; + + DIR *_fd; + Path _path; + Allocator &_alloc; + + unsigned long _inode(char const *path, bool create) + { + int ret; + + if (create) { + mode_t ugo = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + ret = mkdir(path, ugo); + if (ret == -1) + throw No_space(); + } + + struct stat s; + + ret = lstat(path, &s); + if (ret == -1) + throw Lookup_failed(); + + return s.st_ino; + } + + DIR *_open(char const *path) + { + DIR *fd = opendir(path); + if (!fd) + throw Lookup_failed(); + + return fd; + } + + public: + + Directory(Allocator &alloc, char const *path, bool create) + : + Node(_inode(path, create)), + _fd(_open(path)), + _path(path, "./"), + _alloc(alloc) + { + Node::name(basename(path)); + } + + virtual ~Directory() + { + closedir(_fd); + } + + /* FIXME returned file node must be locked */ + File * file(char const *name, Mode mode, bool create) + { + File *file = new (&_alloc) File(dirfd(_fd), name, mode, create); + + file->lock(); + return file; + } + + /* FIXME returned directory node must be locked */ + Directory * subdir(char const *path, bool create) + { + Path dir_path(path, _path.base()); + + Directory *dir = new (&_alloc) Directory(_alloc, dir_path.base(), create); + + dir->lock(); + return dir; + } + + Node * node(char const *path) + { + Path node_path(path, _path.base()); + + /* + * XXX Currently, symlinks are transparently dereferenced by the + * use of stat(). For symlink detection we would need lstat() + * and implement special handling of the root, which may be a + * link! + */ + + struct stat s; + int ret = stat(node_path.base(), &s); + if (ret == -1) + throw Lookup_failed(); + + Node *node = 0; + + if (S_ISDIR(s.st_mode)) + node = new (&_alloc) Directory(_alloc, node_path.base(), false); + else if (S_ISREG(s.st_mode)) + node = new (&_alloc) File(node_path.base(), STAT_ONLY); + else + throw Lookup_failed(); + + node->lock(); + return node; + } + + size_t read(char *dst, size_t len, seek_off_t seek_offset) + { + if (len < sizeof(Directory_entry)) { + PERR("read buffer too small for directory entry"); + return 0; + } + + if (seek_offset % sizeof(Directory_entry)) { + PERR("seek offset not aligned to sizeof(Directory_entry)"); + return 0; + } + + seek_off_t index = seek_offset / sizeof(Directory_entry); + + /* seek to index and read entry */ + struct dirent *dent; + rewinddir(_fd); + for (unsigned i = 0; i <= index; ++i) { + dent = readdir(_fd); + } + + if (!dent) + return 0; + + Directory_entry *e = (Directory_entry *)(dst); + + switch (dent->d_type) { + case DT_REG: e->type = Directory_entry::TYPE_FILE; break; + case DT_DIR: e->type = Directory_entry::TYPE_DIRECTORY; break; + case DT_LNK: e->type = Directory_entry::TYPE_SYMLINK; break; + default: + return 0; + } + + strncpy(e->name, dent->d_name, sizeof(e->name)); + + return sizeof(Directory_entry); + } + + size_t write(char const *src, size_t len, seek_off_t seek_offset) + { + /* writing to directory nodes is not supported */ + return 0; + } + + size_t num_entries() const + { + unsigned num = 0; + + rewinddir(_fd); + while (readdir(_fd)) ++num; + + return num; + } +}; + +#endif /* _DIRECTORY_H_ */ diff --git a/os/src/server/lx_fs/file.h b/os/src/server/lx_fs/file.h new file mode 100644 index 000000000..f886041bf --- /dev/null +++ b/os/src/server/lx_fs/file.h @@ -0,0 +1,138 @@ +/* + * \brief File node + * \author Norman Feske + * \author Christian Helmuth + * \date 2013-11-11 + */ + +#ifndef _FILE_H_ +#define _FILE_H_ + +/* local includes */ +#include +#include + + +namespace File_system { + class File; +} + + +class File_system::File : public Node +{ + private: + + int _fd; + + unsigned long _inode(int dir, char const *name, bool create) + { + int ret; + + if (create) { + mode_t ugo = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + ret = mknodat(dir, name, S_IFREG | ugo, 0); + if (ret == -1 && errno != EEXIST) + throw No_space(); + } + + struct stat s; + + ret = fstatat(dir, name, &s, 0); + if (ret == -1) + throw Lookup_failed(); + + return s.st_ino; + } + + unsigned long _inode_path(char const *path) + { + int ret; + struct stat s; + + ret = stat(path, &s); + if (ret == -1) + throw Lookup_failed(); + + return s.st_ino; + } + + int _open(int dir, char const *name, Mode mode) + { + int fd = openat(dir, name, access_mode(mode)); + if (fd == -1) + throw Lookup_failed(); + + return fd; + } + + int _open_path(char const *path, Mode mode) + { + int fd = open(path, access_mode(mode)); + if (fd == -1) + throw Lookup_failed(); + + return fd; + } + + public: + + File(int dir, + char const *name, + Mode mode, + bool create) + : + Node(_inode(dir, name, create)), + _fd(_open(dir, name, mode)) + { + Node::name(name); + } + + File(char const *path, Mode mode) + : + Node(_inode_path(path)), + _fd(_open_path(path, mode)) + { + Node::name(basename(path)); + } + + size_t read(char *dst, size_t len, seek_off_t seek_offset) + { + int ret = pread(_fd, dst, len, seek_offset); + + return ret == -1 ? 0 : ret; + } + + size_t write(char const *src, size_t len, seek_off_t seek_offset) + { + /* should we append? */ + if (seek_offset == ~0ULL) { + off_t off = lseek(_fd, 0, SEEK_END); + if (off == -1) + return 0; + seek_offset = off; + } + + int ret = pwrite(_fd, src, len, seek_offset); + + return ret == -1 ? 0 : ret; + } + + file_size_t length() const + { + struct stat s; + + if (fstat(_fd, &s) < 0) + return 0; + + return s.st_size; + } + + void truncate(file_size_t size) + { + ftruncate(_fd, size); + + mark_as_updated(); + } +}; + +#endif /* _FILE_H_ */ diff --git a/os/src/server/lx_fs/lx_util.h b/os/src/server/lx_fs/lx_util.h new file mode 100644 index 000000000..cc988b78a --- /dev/null +++ b/os/src/server/lx_fs/lx_util.h @@ -0,0 +1,38 @@ +/* + * \brief Linux utilities + * \author Christian Helmuth + * \date 2013-11-11 + */ + +#ifndef _LX_UTIL_H_ +#define _LX_UTIL_H_ + +/* Genode includes */ +#include + +/* Linux includes */ +#include +#include +#include +#include +#include + + +namespace File_system { + int access_mode(File_system::Mode const &mode); +} + + +int File_system::access_mode(File_system::Mode const &mode) +{ + switch (mode) { + case STAT_ONLY: + case READ_ONLY: return O_RDONLY; + case WRITE_ONLY: return O_WRONLY; + case READ_WRITE: return O_RDWR; + } + + return O_RDONLY; +} + +#endif diff --git a/os/src/server/lx_fs/main.cc b/os/src/server/lx_fs/main.cc new file mode 100644 index 000000000..6096286c1 --- /dev/null +++ b/os/src/server/lx_fs/main.cc @@ -0,0 +1,437 @@ +/* + * \brief RAM file system + * \author Norman Feske + * \date 2012-04-11 + */ + +/* + * Copyright (C) 2012-2013 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 + +/* local includes */ +#include +#include + + +namespace File_system { + struct Main; + struct Session_component; + struct Root; +} + + +class File_system::Session_component : public Session_rpc_object +{ + private: + + Server::Entrypoint &_ep; + Allocator &_md_alloc; + Directory &_root; + Node_handle_registry _handle_registry; + bool _writable; + + Signal_rpc_member _process_packet_dispatcher; + + + /****************************** + ** Packet-stream processing ** + ******************************/ + + /** + * Perform packet operation + * + * \return true on success, false on failure + */ + void _process_packet_op(Packet_descriptor &packet, Node &node) + { + void * const content = tx_sink()->packet_content(packet); + size_t const length = packet.length(); + seek_off_t const offset = packet.position(); + + if (!content || (packet.length() > packet.size())) { + packet.succeeded(false); + return; + } + + /* resulting length */ + size_t res_length = 0; + + switch (packet.operation()) { + + case Packet_descriptor::READ: + res_length = node.read((char *)content, length, offset); + break; + + case Packet_descriptor::WRITE: + res_length = node.write((char const *)content, length, offset); + break; + } + + packet.length(res_length); + packet.succeeded(res_length > 0); + } + + void _process_packet() + { + Packet_descriptor packet = tx_sink()->get_packet(); + + /* assume failure by default */ + packet.succeeded(false); + + try { + Node *node = _handle_registry.lookup_and_lock(packet.handle()); + Node_lock_guard guard(*node); + + _process_packet_op(packet, *node); + } + catch (Invalid_handle) { PERR("Invalid_handle"); } + catch (Size_limit_reached) { PERR("Size_limit_reached"); } + + /* + * 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] != '/') { + PWRN("malformed path '%s'", path); + throw Lookup_failed(); + } + } + + public: + + /** + * Constructor + */ + Session_component(size_t tx_buf_size, + Server::Entrypoint &ep, + char const *root_dir, + bool writable, + Allocator &md_alloc) + : + Session_rpc_object(env()->ram_session()->alloc(tx_buf_size), ep.rpc_ep()), + _ep(ep), + _md_alloc(md_alloc), + _root(*new (&_md_alloc) Directory(_md_alloc, root_dir, false)), + _writable(writable), + _process_packet_dispatcher(*this, &Session_component::_process_packets) + { + /* + * Register '_process_packets' dispatch function as signal + * handler for packet-avail and ready-to-ack signals. + */ + Signal_context_capability sigh(_ep.manage(_process_packet_dispatcher)); + + _tx.sigh_packet_avail(sigh); + _tx.sigh_ready_to_ack(sigh); + } + + /** + * Destructor + */ + ~Session_component() + { + _ep.dissolve(_process_packet_dispatcher); + Dataspace_capability ds = tx_sink()->dataspace(); + env()->ram_session()->free(static_cap_cast(ds)); + destroy(&_md_alloc, &_root); + } + + + /*************************** + ** File_system interface ** + ***************************/ + + File_handle file(Dir_handle dir_handle, Name const &name, Mode mode, bool create) + { + if (!valid_name(name.string())) + throw Invalid_name(); + + Directory *dir = _handle_registry.lookup_and_lock(dir_handle); + Node_lock_guard dir_guard(*dir); + + if (!_writable) + if (create || (mode != STAT_ONLY && mode != READ_ONLY)) + throw Permission_denied(); + + File *file = dir->file(name.string(), mode, create); + + Node_lock_guard file_guard(*file); + return _handle_registry.alloc(file); + } + + Symlink_handle symlink(Dir_handle dir_handle, Name const &name, bool create) + { + PERR("%s not implemented", __func__); + return Symlink_handle(); + } + + Dir_handle dir(Path const &path, bool create) + { + char const *path_str = path.string(); + + _assert_valid_path(path_str); + + /* skip leading '/' */ + path_str++; + + if (!_writable && create) + throw Permission_denied(); + + if (!path.is_valid_string()) + throw Name_too_long(); + + Directory *dir = _root.subdir(path_str, create); + Node_lock_guard guard(*dir); + return _handle_registry.alloc(dir); + } + + Node_handle node(Path const &path) + { + char const *path_str = path.string(); + + _assert_valid_path(path_str); + + Node *node = _root.node(path_str + 1); + + Node_lock_guard guard(*node); + return _handle_registry.alloc(node); + } + + void close(Node_handle handle) + { + /* FIXME when to destruct node? */ + _handle_registry.free(handle); + } + + Status status(Node_handle node_handle) + { + Node *node = _handle_registry.lookup_and_lock(node_handle); + Node_lock_guard guard(*node); + + Status s; + s.inode = node->inode(); + s.size = 0; + s.mode = 0; + + File *file = dynamic_cast(node); + if (file) { + s.size = file->length(); + s.mode = File_system::Status::MODE_FILE; + return s; + } + + Directory *dir = dynamic_cast(node); + if (dir) { + s.size = dir->num_entries()*sizeof(Directory_entry); + s.mode = File_system::Status::MODE_DIRECTORY; + return s; + } + + PERR("%s for symlinks not implemented", __func__); + + return Status(); + } + + void control(Node_handle, Control) + { + PERR("%s not implemented", __func__); + } + + void unlink(Dir_handle, Name const &) + { + PERR("%s not implemented", __func__); + } + + void truncate(File_handle file_handle, file_size_t size) + { + if (!_writable) + throw Permission_denied(); + + File *file = _handle_registry.lookup_and_lock(file_handle); + Node_lock_guard file_guard(*file); + file->truncate(size); + } + + void move(Dir_handle, Name const &, Dir_handle, Name const &) + { + PERR("%s not implemented", __func__); + } + + void sigh(Node_handle node_handle, Signal_context_capability sigh) + { + _handle_registry.sigh(node_handle, sigh); + } +}; + + +class File_system::Root : public Root_component +{ + private: + + Server::Entrypoint &_ep; + + protected: + + Session_component *_create_session(const char *args) + { + /* + * Determine client-specific policy defined implicitly by + * the client's label. + */ + + char const *root_dir = "."; + bool writeable = false; + + enum { ROOT_MAX_LEN = 256 }; + char root[ROOT_MAX_LEN]; + root[0] = 0; + + try { + Session_label label(args); + Session_policy policy(label); + + /* + * Determine directory that is used as root directory of + * the session. + */ + try { + policy.attribute("root").value(root, sizeof(root)); + + /* + * Make sure the root path is specified with a + * leading path delimiter. For performing the + * lookup, we skip the first character. + */ + if (root[0] != '/') + throw Lookup_failed(); + + root_dir = root + 1; + } catch (Xml_node::Nonexistent_attribute) { + PERR("Missing \"root\" attribute in policy definition"); + throw Root::Unavailable(); + } catch (Lookup_failed) { + PERR("Session root directory \"%s\" does not exist", root); + throw Root::Unavailable(); + } + + /* + * Determine if write access is permitted for the session. + */ + try { + writeable = policy.attribute("writeable").has_value("yes"); + } catch (Xml_node::Nonexistent_attribute) { } + + } catch (Session_policy::No_policy_defined) { + PERR("Invalid session request, no matching policy"); + 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); + + /* + * 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', got %zd, need %zd", + ram_quota, session_size); + throw Root::Quota_exceeded(); + } + return new (md_alloc()) + Session_component(tx_buf_size, _ep, root_dir, writeable, *md_alloc()); + } + + public: + + /** + * Constructor + * + * \param ep entrypoint + * \param sig_rec signal receiver used for handling the + * data-flow signals of packet streams + * \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)); + } +}; + + +/********************** + ** Server framework ** + **********************/ + +char const * Server::name() { return "lx_fs_ep"; } +Genode::size_t Server::stack_size() { return 2048 * sizeof(long); } +void Server::construct(Server::Entrypoint &ep) { static File_system::Main inst(ep); } diff --git a/os/src/server/lx_fs/node.h b/os/src/server/lx_fs/node.h new file mode 100644 index 000000000..e2fa2e8a7 --- /dev/null +++ b/os/src/server/lx_fs/node.h @@ -0,0 +1,134 @@ +/* + * \brief File-system node + * \author Norman Feske + * \author Christian Helmuth + * \date 2013-11-11 + */ + +#ifndef _NODE_H_ +#define _NODE_H_ + +/* Genode includes */ +#include +#include +#include + + +namespace File_system { + + class Listener : public List::Element + { + private: + + Lock _lock; + Signal_context_capability _sigh; + bool _marked_as_updated; + + public: + + Listener() : _marked_as_updated(false) { } + + Listener(Signal_context_capability sigh) + : _sigh(sigh), _marked_as_updated(false) { } + + void notify() + { + Lock::Guard guard(_lock); + + if (_marked_as_updated && _sigh.valid()) + Signal_transmitter(_sigh).submit(); + + _marked_as_updated = false; + } + + void mark_as_updated() + { + Lock::Guard guard(_lock); + + _marked_as_updated = true; + } + + bool valid() const { return _sigh.valid(); } + }; + + + class Node : public List::Element + { + public: + + typedef char Name[128]; + + private: + + Lock _lock; + Name _name; + unsigned long const _inode; + + List _listeners; + + public: + + Node(unsigned long inode) : _inode(inode) { _name[0] = 0; } + + virtual ~Node() + { + /* propagate event to listeners */ + mark_as_updated(); + notify_listeners(); + + while (_listeners.first()) + _listeners.remove(_listeners.first()); + } + + unsigned long inode() const { return _inode; } + char const *name() const { return _name; } + + /** + * Assign name + */ + void name(char const *name) { strncpy(_name, name, sizeof(_name)); } + + void lock() { _lock.lock(); } + void unlock() { _lock.unlock(); } + + virtual size_t read(char *dst, size_t len, seek_off_t) = 0; + virtual size_t write(char const *src, size_t len, seek_off_t) = 0; + + void add_listener(Listener *listener) + { + _listeners.insert(listener); + } + + void remove_listener(Listener *listener) + { + _listeners.remove(listener); + } + + void notify_listeners() + { + for (Listener *curr = _listeners.first(); curr; curr = curr->next()) + curr->notify(); + } + + void mark_as_updated() + { + for (Listener *curr = _listeners.first(); curr; curr = curr->next()) + curr->mark_as_updated(); + } + }; + + + /** + * Guard used for properly releasing node locks + */ + struct Node_lock_guard + { + Node &node; + + Node_lock_guard(Node &node) : node(node) { } + + ~Node_lock_guard() { node.unlock(); } + }; +} + +#endif /* _NODE_H_ */ diff --git a/os/src/server/lx_fs/node_handle_registry.h b/os/src/server/lx_fs/node_handle_registry.h new file mode 100644 index 000000000..a334e423f --- /dev/null +++ b/os/src/server/lx_fs/node_handle_registry.h @@ -0,0 +1,198 @@ +/* + * \brief Facility for managing the session-local node-handle namespace + * \author Norman Feske + * \date 2012-04-11 + */ + +#ifndef _NODE_HANDLE_REGISTRY_H_ +#define _NODE_HANDLE_REGISTRY_H_ + +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]; + + /** + * Allocate node handle + * + * \throw Out_of_node_handles + */ + int _alloc(Node *node) + { + Lock::Guard guard(_lock); + + for (unsigned i = 0; i < MAX_NODE_HANDLES; i++) + if (!_nodes[i]) { + _nodes[i] = node; + return i; + } + + throw Out_of_node_handles(); + } + + bool _in_range(int handle) const + { + return ((handle >= 0) && (handle < MAX_NODE_HANDLES)); + } + + public: + + Node_handle_registry() + { + for (unsigned i = 0; i < MAX_NODE_HANDLES; i++) + _nodes[i] = 0; + } + + template + typename Handle_type::Type alloc(NODE_TYPE *node) + { + typedef typename Handle_type::Type Handle; + return Handle(_alloc(node)); + } + + /** + * Release node handle + */ + void free(Node_handle handle) + { + Lock::Guard guard(_lock); + + if (!_in_range(handle.value)) + return; + + /* + * Notify listeners about the changed file. + */ + 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(); + + node->unlock(); + } + + /** + * 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) + { + Lock::Guard guard(_lock); + + if (!_in_range(handle.value)) + throw Invalid_handle(); + + typedef typename Node_type::Type Node; + Node *node = dynamic_cast(_nodes[handle.value]); + if (!node) + throw Invalid_handle(); + + 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)) { + PDBG("refer_to_same_node -> Invalid_handle"); + 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) + { + Lock::Guard guard(_lock); + + if (!_in_range(handle.value)) + throw Invalid_handle(); + + Node *node = dynamic_cast(_nodes[handle.value]); + if (!node) { + PDBG("Invalid_handle"); + 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); + } + }; +} + +#endif /* _NODE_HANDLE_REGISTRY_H_ */ diff --git a/os/src/server/lx_fs/symlink.h b/os/src/server/lx_fs/symlink.h new file mode 100644 index 000000000..40bc2bf52 --- /dev/null +++ b/os/src/server/lx_fs/symlink.h @@ -0,0 +1,50 @@ +/* + * \brief Symlink file-system node + * \author Norman Feske + * \author Christian Helmuth + * \date 2013-11-11 + * + * FIXME unfinished + */ + +#ifndef _SYMLINK_H_ +#define _SYMLINK_H_ + +/* local includes */ +#include +#include + + +namespace File_system { + class Symlink; +} + + +class File_system::Symlink : public Node +{ + private: + + char _link_to[MAX_PATH_LEN]; + + public: + + Symlink(char const *name) { Node::name(name); } + + size_t read(char *dst, size_t len, seek_off_t seek_offset) + { + size_t count = min(len, sizeof(_link_to) + 1); + Genode::strncpy(dst, _link_to, count); + return count; + } + + size_t write(char const *src, size_t len, seek_off_t seek_offset) + { + size_t count = min(len, sizeof(_link_to) + 1); + Genode::strncpy(_link_to, src, count); + return count; + } + + file_size_t length() const { return strlen(_link_to) + 1; } +}; + +#endif /* _SYMLINK_H_ */ diff --git a/os/src/server/lx_fs/target.mk b/os/src/server/lx_fs/target.mk new file mode 100644 index 000000000..0e2675425 --- /dev/null +++ b/os/src/server/lx_fs/target.mk @@ -0,0 +1,6 @@ +TARGET = lx_fs +REQUIRES = linux +SRC_CC = main.cc +LIBS = base config server lx_hybrid + +INC_DIR += $(PRG_DIR) /usr/include diff --git a/os/src/server/lx_fs/util.h b/os/src/server/lx_fs/util.h new file mode 100644 index 000000000..17f57fd33 --- /dev/null +++ b/os/src/server/lx_fs/util.h @@ -0,0 +1,63 @@ +/* + * \brief Utilities + * \author Norman Feske + * \date 2012-04-11 + */ + +#ifndef _UTIL_H_ +#define _UTIL_H_ + +/** + * Return base-name portion of null-terminated path string + */ +static inline char const *basename(char const *path) +{ + char const *start = path; + + for (; *path; path++) + if (*path == '/') + start = path + 1; + + return start; +} + + +/** + * Return true if specified path is a base name (contains no path delimiters) + */ +static inline bool is_basename(char const *path) +{ + for (; *path; path++) + if (*path == '/') + return false; + + return true; +} + + +/** + * Return true if character 'c' occurs in null-terminated string 'str' + */ +static inline bool string_contains(char const *str, char c) +{ + for (; *str; str++) + if (*str == c) + return true; + return false; +} + + +/** + * Return true if 'str' is a valid node name + */ +static inline bool valid_name(char const *str) +{ + if (string_contains(str, '/')) return false; + + /* must have at least one character */ + if (str[0] == 0) return false; + + return true; +} + +#endif /* _UTIL_H_ */