diff --git a/libports/lib/mk/libc_fs.mk b/libports/lib/mk/libc_fs.mk new file mode 100644 index 0000000000..948352ca99 --- /dev/null +++ b/libports/lib/mk/libc_fs.mk @@ -0,0 +1,6 @@ +SRC_CC = plugin.cc +LIBS += libc + +vpath plugin.cc $(REP_DIR)/src/lib/libc_fs + +SHARED_LIB = yes diff --git a/libports/run/libc_fs.run b/libports/run/libc_fs.run new file mode 100644 index 0000000000..ce26ba2234 --- /dev/null +++ b/libports/run/libc_fs.run @@ -0,0 +1,64 @@ +# +# \brief Test for using the libc_fs plugin with the RAM file system +# \author Norman Feske +# \date 2012-04-11 +# + +# +# Build +# + +build { core init server/ram_fs test/libc_fs } + +create_boot_directory + +# +# Generate config +# + +install_config { + + + + + + + + + + + + + + + + + + + + + + + +} + +# +# Boot modules +# + +build_boot_image { + core init + ld.lib.so libc.lib.so libc_log.lib.so libc_fs.lib.so + ram_fs test-libc_fs +} + +# +# Execute test case +# + +append qemu_args " -m 128 -nographic " +run_genode_until {.*child exited with exit value 0.*} 60 + +puts "\ntest succeeded\n" + +# vi: set ft=tcl : diff --git a/libports/src/lib/libc_fs/plugin.cc b/libports/src/lib/libc_fs/plugin.cc new file mode 100644 index 0000000000..85205bb823 --- /dev/null +++ b/libports/src/lib/libc_fs/plugin.cc @@ -0,0 +1,664 @@ +/* + * \brief Libc plugin for accessing a file-system session + * \author Norman Feske + * \date 2012-04-11 + */ + +/* + * Copyright (C) 2012 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 + +/* libc includes */ +#include +#include +#include +#include +#include +#include + +/* libc plugin interface */ +#include +#include + + +static bool const verbose = false; + + +namespace File_system { struct Packet_ref { }; } + + +namespace { + +enum { PATH_MAX_LEN = 256 }; + + +/** + * Current working directory + */ +struct Cwd +{ + char path[PATH_MAX_LEN]; + + Cwd() { Genode::strncpy(path, "/", sizeof(path)); } +}; + + +static Cwd *cwd() +{ + static Cwd cwd_inst; + return &cwd_inst; +} + + +struct Canonical_path +{ + char str[PATH_MAX_LEN]; + + Canonical_path(char const *pathname) + { + /* + * If pathname is a relative path, prepend the current working + * directory. + * + * XXX we might consider using Noux' 'Path' class here + */ + if (pathname[0] != '/') { + snprintf(str, sizeof(str), "%s/%s", cwd()->path, pathname); + } else { + strncpy(str, pathname, sizeof(str)); + } + } +}; + + +static File_system::Session *file_system() +{ + static Genode::Allocator_avl tx_buffer_alloc(Genode::env()->heap()); + static File_system::Connection fs(tx_buffer_alloc); + return &fs; +} + + +struct Node_handle_guard +{ + File_system::Node_handle handle; + + Node_handle_guard(File_system::Node_handle handle) : handle(handle) { } + + ~Node_handle_guard() { file_system()->close(handle); } +}; + + +class Plugin_context : public Libc::Plugin_context, + public File_system::Packet_ref +{ + private: + + enum Type { TYPE_FILE, TYPE_DIR, TYPE_SYMLINK }; + + Type _type; + + File_system::Node_handle _node_handle; + + /** + * Current file position if manually seeked, or ~0 for append mode + */ + off_t _seek_offset; + + public: + + bool in_flight; + + Plugin_context(File_system::File_handle handle) + : _type(TYPE_FILE), _node_handle(handle), _seek_offset(~0), in_flight(false) { } + + Plugin_context(File_system::Dir_handle handle) + : _type(TYPE_DIR), _node_handle(handle), _seek_offset(~0), in_flight(false) { } + + Plugin_context(File_system::Symlink_handle handle) + : _type(TYPE_SYMLINK), _node_handle(handle), _seek_offset(~0), in_flight(false) { } + + File_system::Node_handle node_handle() const { return _node_handle; } + + /** + * Return true of handle is append mode + */ + bool is_appending() const { return ~0 == _seek_offset; } + + /** + * Set seek offset, switch to non-append mode + */ + void seek_offset(size_t seek_offset) { _seek_offset = seek_offset; } + + /** + * Return seek offset if handle is in non-append mode + */ + off_t seek_offset() const { return _seek_offset; } + + /** + * Advance current seek position by 'incr' number of bytes + * + * This function has no effect if the handle is in append mode. + */ + void advance_seek_offset(size_t incr) + { + if (!is_appending()) + _seek_offset += incr; + } + + void infinite_seek_offset() + { + _seek_offset = ~0; + } + + virtual ~Plugin_context() { } +}; + + +static inline Plugin_context *context(Libc::File_descriptor *fd) +{ + return fd->context ? static_cast(fd->context) : 0; +} + + +static void wait_for_acknowledgement(File_system::Session::Tx::Source &source) +{ + ::File_system::Packet_descriptor packet = source.get_acked_packet(); + PDBG("got acknowledgement for packet of size %zd", packet.size()); + + static_cast(packet.ref())->in_flight = false; + + source.release_packet(packet); +} + + +/** + * Collect pending packet acknowledgements, freeing the space occupied + * by the packet in the bulk buffer + * + * This function should be called prior enqueing new packets into the + * packet stream to free up space in the bulk buffer. + */ +static void collect_acknowledgements(File_system::Session::Tx::Source &source) +{ + while (source.ack_avail()) + wait_for_acknowledgement(source); +} + + +static void obtain_stat_for_node(File_system::Node_handle node_handle, + struct stat *buf) +{ + if (!buf) + return; + + File_system::Status status = file_system()->status(node_handle); + + /* + * Translate status information to 'struct stat' format + */ + memset(buf, 0, sizeof(struct stat)); + buf->st_size = status.size; + + if (status.is_directory()) + buf->st_mode |= S_IFDIR; + else if (status.is_symlink()) + buf->st_mode |= S_IFLNK; + else + buf->st_mode |= S_IFREG; + + struct tm tm; + memset(&tm, 0, sizeof(struct tm)); + + buf->st_mtime = mktime(&tm); + + if (buf->st_mtime == -1) + PERR("mktime() returned -1, the file modification time reported by stat() will be incorrect"); +} + + +class Plugin : public Libc::Plugin +{ + public: + + /** + * Constructor + */ + Plugin() { } + + ~Plugin() { } + + bool supports_chdir(const char *path) + { + if (verbose) + PDBG("path = %s", path); + return true; + } + + bool supports_mkdir(const char *path, mode_t) + { + if (verbose) + PDBG("path = %s", path); + return true; + } + + bool supports_open(const char *pathname, int flags) + { + if (verbose) + PDBG("pathname = %s", pathname); + return true; + } + + bool supports_rename(const char *oldpath, const char *newpath) + { + if (verbose) + PDBG("oldpath = %s, newpath = %s", oldpath, newpath); + return true; + } + + bool supports_stat(const char *path) + { + if (verbose) + PDBG("path = %s", path); + return true; + } + + bool supports_unlink(const char *path) + { + if (verbose) + PDBG("path = %s", path); + return true; + } + + int chdir(const char *path) + { + if (*path != '/') { + PERR("chdir: relative path names not yet supported"); + errno = ENOENT; + return -1; + } + + Genode::strncpy(cwd()->path, path, sizeof(cwd()->path)); + + /* strip trailing slash if needed */ + char *s = cwd()->path; + for (; s[0] && s[1]; s++); + if (s[0] == '/') + s[0] = 0; + + return 0; + } + + int close(Libc::File_descriptor *fd) + { + /* wait for the completion of all operations of the context */ + while (context(fd)->in_flight) { + PDBG("wait_for_acknowledgement"); + wait_for_acknowledgement(*file_system()->tx()); + } + + file_system()->close(context(fd)->node_handle()); + return 0; + } + + int fcntl(Libc::File_descriptor *, int cmd, long arg) + { + PDBG("not implemented"); + /* libc's opendir() fails if fcntl() returns -1, so we return 0 here */ + if (verbose) + PDBG("fcntl() called - not yet implemented"); + return 0; + } + + int fstat(Libc::File_descriptor *fd, struct stat *buf) + { + try { + obtain_stat_for_node(context(fd)->node_handle(), buf); + return 0; + } + catch (...) { + struct Unhandled_exception_in_fstat { }; + throw Unhandled_exception_in_fstat(); + } + return -1; + } + + int fstatfs(Libc::File_descriptor *, struct statfs *buf) + { + PDBG("not implemented"); + /* libc's opendir() fails if _fstatfs() returns -1, so we return 0 here */ + if (verbose) + PDBG("_fstatfs() called - not yet implemented"); + return 0; + } + + int fsync(Libc::File_descriptor *fd) + { + PDBG("not implemented"); + return -1; + } + + ssize_t getdirentries(Libc::File_descriptor *fd, char *buf, + ::size_t nbytes, ::off_t *basep) + { + using namespace File_system; + + if (nbytes < sizeof(struct dirent)) { + PERR("buf too small"); + return -1; + } + + unsigned const curr_offset = *basep; + unsigned const curr_index = curr_offset / sizeof(struct dirent); + + seek_off_t const orig_seek_offset = context(fd)->seek_offset(); + context(fd)->seek_offset(curr_index*sizeof(Directory_entry)); + Directory_entry entry; + size_t num_bytes = read(fd, &entry, sizeof(entry)); + + context(fd)->seek_offset(orig_seek_offset); + + /* detect end of directory entries */ + if (num_bytes == 0) + return 0; + + if (num_bytes != sizeof(entry)) { + PERR("getdirentries retrieved unexpected directory entry size"); + return -1; + } + + struct dirent *dirent = (struct dirent *)buf; + Genode::memset(dirent, 0, sizeof(struct dirent)); + + switch (entry.type) { + case Directory_entry::TYPE_DIRECTORY: dirent->d_type = DT_DIR; break; + case Directory_entry::TYPE_FILE: dirent->d_type = DT_REG; break; + case Directory_entry::TYPE_SYMLINK: dirent->d_type = DT_LNK; break; + } + + dirent->d_fileno = curr_index + 1; + dirent->d_reclen = sizeof(struct dirent); + + Genode::strncpy(dirent->d_name, entry.name, sizeof(dirent->d_name)); + + dirent->d_namlen = Genode::strlen(dirent->d_name); + + *basep += sizeof(struct dirent); + return sizeof(struct dirent); + } + + ::off_t lseek(Libc::File_descriptor *fd, ::off_t offset, int whence) + { + switch (whence) { + + case SEEK_SET: + context(fd)->seek_offset(offset); + return 0; + + case SEEK_CUR: + context(fd)->advance_seek_offset(offset); + return 0; + + case SEEK_END: + if (offset != 0) { + errno = EINVAL; + return -1; + } + context(fd)->infinite_seek_offset(); + return 0; + + default: + errno = EINVAL; + return -1; + } + } + + int mkdir(const char *path, mode_t mode) + { + try { + file_system()->dir(path, true); + return 0; + } + catch (File_system::Permission_denied) { errno = EPERM; } + catch (File_system::Node_already_exists) { errno = EEXIST; } + catch (File_system::Lookup_failed) { errno = ENOENT; } + catch (File_system::Name_too_long) { errno = ENAMETOOLONG; } + catch (File_system::No_space) { errno = ENOSPC; } + + return -1; + } + + Libc::File_descriptor *open(const char *pathname, int flags) + { + Canonical_path path(pathname); + + File_system::Mode mode; + switch (flags & O_ACCMODE) { + case O_RDONLY: mode = File_system::READ_ONLY; break; + case O_WRONLY: mode = File_system::WRITE_ONLY; break; + case O_RDWR: mode = File_system::READ_WRITE; break; + default: mode = File_system::STAT_ONLY; break; + } + + /* + * Probe for an existing directory to open + */ + try { + PDBG("open dir '%s'", path.str); + File_system::Dir_handle const handle = + file_system()->dir(path.str, false); + + Plugin_context *context = new (Genode::env()->heap()) + Plugin_context(handle); + + return Libc::file_descriptor_allocator()->alloc(this, context); + } catch (File_system::Lookup_failed) { } + + /* + * Determine directory path that contains the node to open + */ + unsigned last_slash = 0; + for (unsigned i = 0; path.str[i]; i++) + if (path.str[i] == '/') + last_slash = i; + + char dir_path[256]; + dir_path[0] = 0; + if (last_slash > 0) + Genode::strncpy(dir_path, path.str, + Genode::min(sizeof(dir_path), last_slash + 1)); + + /* + * Determine base name + */ + char const *basename = path.str + last_slash + 1; + + try { + /* + * Open directory that contains the file to be opened/created + */ + File_system::Dir_handle const dir_handle = + file_system()->dir(dir_path, false); + + Node_handle_guard guard(dir_handle); + + /* + * Open or create file + */ + bool const create = (flags & O_CREAT) != 0; + File_system::File_handle const handle = + file_system()->file(dir_handle, basename, mode, create); + + Plugin_context *context = new (Genode::env()->heap()) + Plugin_context(handle); + + return Libc::file_descriptor_allocator()->alloc(this, context); + + } + catch (File_system::Lookup_failed) { + PERR("open(%s) lookup failed", pathname); } + return 0; + } + + int rename(const char *oldpath, const char *newpath) + { + PDBG("not implemented"); + return -1; + } + + ssize_t read(Libc::File_descriptor *fd, void *buf, ::size_t count) + { + File_system::Session::Tx::Source &source = *file_system()->tx(); + + size_t const max_packet_size = source.bulk_buffer_size() / 2; + + size_t remaining_count = count; + + if (context(fd)->seek_offset() == ~0) + context(fd)->seek_offset(0); + + while (remaining_count) { + + collect_acknowledgements(source); + + size_t curr_packet_size = Genode::min(remaining_count, max_packet_size); + + /* + * XXX handle 'Packet_alloc_failed' exception' + */ + File_system::Packet_descriptor + packet(source.alloc_packet(curr_packet_size), + static_cast(context(fd)), + context(fd)->node_handle(), + File_system::Packet_descriptor::READ, + curr_packet_size, + context(fd)->seek_offset()); + + /* mark context as having an operation in flight */ + context(fd)->in_flight = true; + + /* pass packet to server side */ + source.submit_packet(packet); + + do { + packet = source.get_acked_packet(); + static_cast(packet.ref())->in_flight = false; + } while (context(fd)->in_flight); + + context(fd)->in_flight = false; + + /* + * XXX check if acked packet belongs to request, + * needed for thread safety + */ + + size_t read_num_bytes = Genode::min(packet.length(), curr_packet_size); + + /* copy-out payload into destination buffer */ + memcpy(buf, source.packet_content(packet), read_num_bytes); + + source.release_packet(packet); + + /* prepare next iteration */ + context(fd)->advance_seek_offset(read_num_bytes); + buf = (void *)((Genode::addr_t)buf + read_num_bytes); + remaining_count -= read_num_bytes; + + /* + * If we received less bytes than requested, we reached the end + * of the file. + */ + if (read_num_bytes < curr_packet_size) + break; + } + + return count - remaining_count; + } + + int stat(const char *pathname, struct stat *buf) + { + PDBG("stat %s", pathname); + Canonical_path path(pathname); + + try { + File_system::Node_handle const node_handle = + file_system()->node(path.str); + Node_handle_guard guard(node_handle); + + obtain_stat_for_node(node_handle, buf); + return 0; + } + catch (File_system::Lookup_failed) { + PERR("lookup failed"); + errno = ENOENT; + } + return -1; + } + + int unlink(const char *path) + { + return -1; + } + + ssize_t write(Libc::File_descriptor *fd, const void *buf, ::size_t count) + { + File_system::Session::Tx::Source &source = *file_system()->tx(); + + size_t const max_packet_size = source.bulk_buffer_size() / 2; + + size_t remaining_count = count; + + while (remaining_count) { + + collect_acknowledgements(source); + + size_t curr_packet_size = Genode::min(remaining_count, max_packet_size); + + /* + * XXX handle 'Packet_alloc_failed' exception' + */ + File_system::Packet_descriptor + packet(source.alloc_packet(curr_packet_size), + static_cast(context(fd)), + context(fd)->node_handle(), + File_system::Packet_descriptor::WRITE, + curr_packet_size, + context(fd)->seek_offset()); + + /* mark context as having an operation in flight */ + context(fd)->in_flight = true; + + /* copy-in payload into packet */ + memcpy(source.packet_content(packet), buf, curr_packet_size); + + /* pass packet to server side */ + source.submit_packet(packet); + + /* prepare next iteration */ + context(fd)->advance_seek_offset(curr_packet_size); + buf = (void *)((Genode::addr_t)buf + curr_packet_size); + remaining_count -= curr_packet_size; + } + + PDBG("write returns %zd", count); + return count; + } +}; + + +} /* unnamed namespace */ + + +void __attribute__((constructor)) init_libc_fs(void) +{ + PDBG("using the libc_fs plugin"); + static Plugin plugin; +} diff --git a/libports/src/test/libc_fs/target.mk b/libports/src/test/libc_fs/target.mk new file mode 100644 index 0000000000..b831b5595a --- /dev/null +++ b/libports/src/test/libc_fs/target.mk @@ -0,0 +1,6 @@ +TARGET = test-libc_fs +LIBS = cxx env libc libc_log libc_fs +SRC_CC = main.cc + +# we re-use the libc_ffat test +vpath main.cc $(REP_DIR)/src/test/libc_ffat diff --git a/os/include/file_system_session/capability.h b/os/include/file_system_session/capability.h new file mode 100644 index 0000000000..ce721ce198 --- /dev/null +++ b/os/include/file_system_session/capability.h @@ -0,0 +1,22 @@ +/* + * \brief File-system session capability type + * \author Norman Feske + * \date 2012-04-05 + */ + +/* + * Copyright (C) 2012 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 _INCLUDE__FILE_SYSTEM_SESSION__CAPABILITY_H_ +#define _INCLUDE__FILE_SYSTEM_SESSION__CAPABILITY_H_ + +#include +#include + +namespace File_system { typedef Genode::Capability Session_capability; } + +#endif /* _INCLUDE__FILE_SYSTEM_SESSION__CAPABILITY_H_ */ diff --git a/os/include/file_system_session/client.h b/os/include/file_system_session/client.h new file mode 100644 index 0000000000..47df82bbc8 --- /dev/null +++ b/os/include/file_system_session/client.h @@ -0,0 +1,106 @@ +/* + * \brief Client-side file-system session interface + * \author Norman Feske + * \date 2012-04-05 + */ + +/* + * Copyright (C) 2012 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 _INCLUDE__FILE_SYSTEM_SESSION__CLIENT_H_ +#define _INCLUDE__FILE_SYSTEM_SESSION__CLIENT_H_ + +#include +#include +#include + +namespace File_system { + + class Session_client : public Rpc_client + { + private: + + Packet_stream_tx::Client _tx; + + public: + + /** + * Constructor + * + * \param session session capability + * \param tx_buffer_alloc allocator used for managing the + * transmission buffer + */ + Session_client(Session_capability session, + Range_allocator &tx_buffer_alloc) + : + Rpc_client(session), + _tx(call(), &tx_buffer_alloc) + { } + + + /*********************************** + ** File-system session interface ** + ***********************************/ + + Tx::Source *tx() { return _tx.source(); } + + File_handle file(Dir_handle dir, Name const &name, Mode mode, bool create) + { + return call(dir, name, mode, create); + } + + Symlink_handle symlink(Dir_handle dir, Name const &name, bool create) + { + return call(dir, name, create); + } + + Dir_handle dir(Path const &path, bool create) + { + return call(path, create); + } + + Node_handle node(Path const &path) + { + return call(path); + } + + void close(Node_handle node) + { + call(node); + } + + Status status(Node_handle node) + { + return call(node); + } + + void control(Node_handle node, Control control) + { + call(node, control); + } + + void unlink(Dir_handle dir, Name const &name) + { + call(dir, name); + } + + void truncate(File_handle file, file_size_t size) + { + call(file, size); + } + + void move(Dir_handle from_dir, Name const &from_name, + Dir_handle to_dir, Name const &to_name) + { + call(from_dir, from_name, to_dir, to_name); + } + + }; +} + +#endif /* _INCLUDE__FILE_SYSTEM_SESSION__CLIENT_H_ */ diff --git a/os/include/file_system_session/connection.h b/os/include/file_system_session/connection.h new file mode 100644 index 0000000000..4a05c31ad9 --- /dev/null +++ b/os/include/file_system_session/connection.h @@ -0,0 +1,43 @@ +/* + * \brief Connection to file-system service + * \author Norman Feske + * \date 2012-04-05 + */ + +/* + * Copyright (C) 2012 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 _INCLUDE__FILE_SYSTEM_SESSION__CONNECTION_H_ +#define _INCLUDE__FILE_SYSTEM_SESSION__CONNECTION_H_ + +#include +#include +#include + +namespace File_system { + + struct Connection : Genode::Connection, Session_client + { + /** + * Constructor + * + * \param tx_buffer_alloc allocator used for managing the + * transmission buffer + * \param tx_buf_size size of transmission buffer in bytes + */ + Connection(Range_allocator &tx_block_alloc, + size_t tx_buf_size = 128*1024, + const char *label = "") + : + Genode::Connection( + session("ram_quota=%zd, tx_buf_size=%zd, label=\"%s\"", + 3*4096 + tx_buf_size, tx_buf_size, label)), + Session_client(cap(), tx_block_alloc) { } + }; +} + +#endif /* _INCLUDE__FILE_SYSTEM_SESSION__CONNECTION_H_ */ diff --git a/os/include/file_system_session/file_system_session.h b/os/include/file_system_session/file_system_session.h new file mode 100644 index 0000000000..1c191bdbcd --- /dev/null +++ b/os/include/file_system_session/file_system_session.h @@ -0,0 +1,332 @@ +/* + * \brief File-system session interface + * \author Norman Feske + * \date 2012-04-05 + */ + +/* + * Copyright (C) 2012 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 _INCLUDE__FILE_SYSTEM_SESSION__FILE_SYSTEM_SESSION_H_ +#define _INCLUDE__FILE_SYSTEM_SESSION__FILE_SYSTEM_SESSION_H_ + +#include +#include +#include +#include + +namespace File_system { + + using namespace Genode; + + struct Node_handle + { + int value; + + Node_handle() : value(-1) { } + Node_handle(int v) : value(v) { } + }; + + + struct File_handle : Node_handle + { + File_handle() { } + File_handle(int v) : Node_handle(v) { } + }; + + + struct Dir_handle : Node_handle + { + Dir_handle() { } + Dir_handle(int v) : Node_handle(v) { } + }; + + + struct Symlink_handle : Node_handle + { + Symlink_handle() { } + Symlink_handle(int v) : Node_handle(v) { } + }; + + + /** + * Type of client context embedded in each packet descriptor + * + * Using the opaque refererence, the client is able to attribute incoming + * packet acknowledgements to a context that is meaningful for the client. + * It has no meaning at the server side. + */ + struct Packet_ref; + + + typedef uint64_t seek_off_t; + typedef uint64_t file_size_t; + + + class Packet_descriptor : public ::Packet_descriptor + { + public: + + enum Opcode { READ, WRITE }; + + private: + + Node_handle _handle; /* node handle */ + Opcode _op; /* requested operation */ + seek_off_t _position; /* seek offset in bytes */ + size_t _length; /* transaction length in bytes */ + bool _success; /* indicates success of operation */ + Packet_ref *_ref; /* opaque reference used at the client side + for recognizing acknowledgements */ + + public: + + /** + * Constructor + */ + Packet_descriptor(off_t offset = 0, size_t size = 0) + : + ::Packet_descriptor(offset, size), _handle(-1), + _op(READ), _position(0), _length(0), _success(false) { } + + /** + * Constructor + * + * \param position seek offset in bytes (by default, append) + */ + Packet_descriptor(Packet_descriptor p, Packet_ref *ref, + Node_handle handle, Opcode op, size_t length, + seek_off_t position = ~0) + : + ::Packet_descriptor(p.offset(), p.size()), + _handle(handle), _op(op), + _position(position), _length(length), _success(false), + _ref(ref) + { } + + Node_handle handle() const { return _handle; } + Opcode operation() const { return _op; } + seek_off_t position() const { return _position; } + size_t length() const { return _length; } + bool succeeded() const { return _success; } + Packet_ref *ref() const { return _ref; } + + /* + * Accessors called at the server side + */ + void succeeded(bool b) { _success = b ? 1 : 0; } + void length(size_t length) { _length = length; } + }; + + + /** + * Flags as supplied to 'file', 'dir', and 'symlink' calls + */ + enum Mode { STAT_ONLY = 0, READ_ONLY = 1, WRITE_ONLY = 2, READ_WRITE = 3 }; + + enum { MAX_NAME_LEN = 128, MAX_PATH_LEN = 1024 }; + + typedef Rpc_in_buffer Name; + typedef Rpc_in_buffer Path; + + + struct Status + { + enum { + MODE_SYMLINK = 0020000, + MODE_FILE = 0100000, + MODE_DIRECTORY = 0040000, + }; + + /* + * XXX add access time + * XXX add executable bit + */ + + file_size_t size; + unsigned mode; + unsigned long inode; + + bool is_directory() const { return mode & MODE_DIRECTORY; } + bool is_symlink() const { return mode & MODE_SYMLINK; } + }; + + + struct Control { /* to manipulate the executable bit */ }; + + + /** + * Data structure returned when reading from a directory node + */ + struct Directory_entry + { + enum Type { TYPE_FILE, TYPE_DIRECTORY, TYPE_SYMLINK }; + Type type; + char name[MAX_NAME_LEN]; + }; + + + /* + * Exception types + */ + class Exception : public Genode::Exception { }; + class Permission_denied : Exception { }; + class Node_already_exists : Exception { }; + class Lookup_failed : Exception { }; + class Name_too_long : Exception { }; + class No_space : Exception { }; + class Out_of_node_handles : Exception { }; + class Invalid_handle : Exception { }; + class Invalid_name : Exception { }; + class Size_limit_reached : Exception { }; + + + struct Session : public Genode::Session + { + enum { TX_QUEUE_SIZE = 16 }; + + typedef Packet_stream_policy Tx_policy; + + typedef Packet_stream_tx::Channel Tx; + + static const char *service_name() { return "File_system"; } + + virtual ~Session() { } + + /** + * Request client-side packet-stream interface of tx channel + */ + virtual Tx::Source *tx() { return 0; } + + /** + * Open or create file + * + * \throw Invalid_handle directory handle is invalid + * \throw Node_already_exists file cannot be created because a + * node with the same name already exists + * \throw Invalid_name file name contains invalid characters + * \throw Lookup_failed the name refers to a node other than a + * file + */ + virtual File_handle file(Dir_handle, Name const &name, Mode, bool create) = 0; + + /** + * Open or create symlink + */ + virtual Symlink_handle symlink(Dir_handle, Name const &name, bool create) = 0; + + /** + * Open or create directory + * + * \throw Permission_denied + * \throw Node_already_exists directory cannot be created because a + * node with the same name already exists + * \throw Lookup_failed path lookup failed because one element + * of 'path' does not exist + * \throw Name_too_long + * \throw No_space + */ + virtual Dir_handle dir(Path const &path, bool create) = 0; + + /** + * Open existing node + * + * The returned node handle can be used merely as argument for + * 'status'. + */ + virtual Node_handle node(Path const &path) = 0; + + /** + * Close file + */ + virtual void close(Node_handle) = 0; + + /** + * Request information about an open file or directory + */ + virtual Status status(Node_handle) = 0; + + /** + * Set information about an open file or directory + */ + virtual void control(Node_handle, Control) = 0; + + /** + * Delete file or directory + */ + virtual void unlink(Dir_handle, Name const &) = 0; + + /** + * Truncate or grow file to specified size + */ + virtual void truncate(File_handle, file_size_t size) = 0; + + /** + * Move and rename directory entry + */ + virtual void move(Dir_handle, Name const &from, + Dir_handle, Name const &to) = 0; + + + /******************* + ** RPC interface ** + *******************/ + + GENODE_RPC(Rpc_tx_cap, Capability, _tx_cap); + GENODE_RPC_THROW(Rpc_file, File_handle, file, + GENODE_TYPE_LIST(Invalid_handle, Node_already_exists, + Invalid_name, Lookup_failed, + Permission_denied), + Dir_handle, Name const &, Mode, bool); + GENODE_RPC_THROW(Rpc_symlink, Symlink_handle, symlink, + GENODE_TYPE_LIST(Invalid_handle, Node_already_exists, + Invalid_name, Lookup_failed), + Dir_handle, Name const &, bool); + GENODE_RPC_THROW(Rpc_dir, Dir_handle, dir, + GENODE_TYPE_LIST(Permission_denied, Node_already_exists, + Lookup_failed, Name_too_long, No_space), + Path const &, bool); + GENODE_RPC_THROW(Rpc_node, Node_handle, node, + GENODE_TYPE_LIST(Lookup_failed), + Path const &); + GENODE_RPC(Rpc_close, void, close, Node_handle); + GENODE_RPC(Rpc_status, Status, status, Node_handle); + GENODE_RPC(Rpc_control, void, control, Node_handle, Control); + GENODE_RPC_THROW(Rpc_unlink, void, unlink, + GENODE_TYPE_LIST(Permission_denied, Invalid_name, Lookup_failed), + Dir_handle, Name const &); + GENODE_RPC_THROW(Rpc_truncate, void, truncate, + GENODE_TYPE_LIST(Permission_denied, Invalid_handle), + File_handle, file_size_t); + GENODE_RPC_THROW(Rpc_move, void, move, + GENODE_TYPE_LIST(Permission_denied, Invalid_name, Lookup_failed), + Dir_handle, Name const &, Dir_handle, Name const &); + + /* + * Manual type-list definition, needed because the RPC interface + * exceeds the maximum number of type-list elements supported by + * 'Genode::Meta::Type_list<>'. + */ + typedef Meta::Type_tuple + > > > > > > > > > > Rpc_functions; + }; +} + +#endif /* _INCLUDE__FILE_SYSTEM_SESSION__FILE_SYSTEM_SESSION_H_ */ diff --git a/os/include/file_system_session/rpc_object.h b/os/include/file_system_session/rpc_object.h new file mode 100644 index 0000000000..5b6b5a8931 --- /dev/null +++ b/os/include/file_system_session/rpc_object.h @@ -0,0 +1,53 @@ +/* + * \brief Server-side file-system session interface + * \author Norman Feske + * \date 2012-04-05 + */ + +/* + * Copyright (C) 2012 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 _INCLUDE__FILE_SYSTEM_SESSION__SERVER_H_ +#define _INCLUDE__FILE_SYSTEM_SESSION__SERVER_H_ + +#include +#include +#include + +namespace File_system { + + class Session_rpc_object : public Genode::Rpc_object + { + protected: + + Packet_stream_tx::Rpc_object _tx; + + public: + + /** + * Constructor + * + * \param tx_ds dataspace used as communication buffer + * for the tx packet stream + * \param ep entry point used for packet-stream channel + */ + Session_rpc_object(Dataspace_capability tx_ds, Rpc_entrypoint &ep) + : _tx(tx_ds, ep) { } + + /** + * Return capability to packet-stream channel + * + * This function is called by the client via an RPC call at session + * construction time. + */ + Capability _tx_cap() { return _tx.cap(); } + + Tx::Sink *tx_sink() { return _tx.sink(); } + }; +} + +#endif /* _INCLUDE__FILE_SYSTEM_SESSION__SERVER_H_ */ diff --git a/os/run/ram_fs_chunk.run b/os/run/ram_fs_chunk.run new file mode 100644 index 0000000000..8d1d2fe824 --- /dev/null +++ b/os/run/ram_fs_chunk.run @@ -0,0 +1,77 @@ +# +# \brief Unit test for chunk data structure used by RAM fs +# \author Norman Feske +# \date 2012-04-19 +# + +build "core init test/ram_fs_chunk" + +create_boot_directory + +install_config { + + + + + + + + + + + +} + +build_boot_image "core init test-ram_fs_chunk" + +append qemu_args "-nographic -m 64" + +run_genode_until "child exited with exit value 0.*\n" 10 + +grep_output {^\[init -> test-ram_fs_chunk\]} + +compare_output_to { + [init -> test-ram_fs_chunk] --- ram_fs_chunk test --- + [init -> test-ram_fs_chunk] chunk sizes + [init -> test-ram_fs_chunk]  level 0: payload=120 sizeof=36 + [init -> test-ram_fs_chunk]  level 1: payload=24 sizeof=32 + [init -> test-ram_fs_chunk]  level 2: payload=6 sizeof=28 + [init -> test-ram_fs_chunk]  level 3: payload=2 sizeof=16 + [init -> test-ram_fs_chunk] write "five-o-one" at offset 0 -> content (size=10): "five-o-one" + [init -> test-ram_fs_chunk] write "five" at offset 7 -> content (size=11): "five-o-five" + [init -> test-ram_fs_chunk] write "Nuance" at offset 17 -> content (size=23): "five-o-five......Nuance" + [init -> test-ram_fs_chunk] write "YM-2149" at offset 35 -> content (size=42): "five-o-five......Nuance............YM-2149" + [init -> test-ram_fs_chunk] trunc(30) -> content (size=30): "five-o-five......Nuance......." + [init -> test-ram_fs_chunk] trunc(29) -> content (size=24): "five-o-five......Nuance." + [init -> test-ram_fs_chunk] trunc(28) -> content (size=24): "five-o-five......Nuance." + [init -> test-ram_fs_chunk] trunc(27) -> content (size=24): "five-o-five......Nuance." + [init -> test-ram_fs_chunk] trunc(26) -> content (size=24): "five-o-five......Nuance." + [init -> test-ram_fs_chunk] trunc(25) -> content (size=24): "five-o-five......Nuance." + [init -> test-ram_fs_chunk] trunc(24) -> content (size=24): "five-o-five......Nuance." + [init -> test-ram_fs_chunk] trunc(23) -> content (size=23): "five-o-five......Nuance" + [init -> test-ram_fs_chunk] trunc(22) -> content (size=22): "five-o-five......Nuanc" + [init -> test-ram_fs_chunk] trunc(21) -> content (size=21): "five-o-five......Nuan" + [init -> test-ram_fs_chunk] trunc(20) -> content (size=20): "five-o-five......Nua" + [init -> test-ram_fs_chunk] trunc(19) -> content (size=19): "five-o-five......Nu" + [init -> test-ram_fs_chunk] trunc(18) -> content (size=18): "five-o-five......N" + [init -> test-ram_fs_chunk] trunc(17) -> content (size=17): "five-o-five......" + [init -> test-ram_fs_chunk] trunc(16) -> content (size=14): "five-o-five..." + [init -> test-ram_fs_chunk] trunc(15) -> content (size=14): "five-o-five..." + [init -> test-ram_fs_chunk] trunc(14) -> content (size=14): "five-o-five..." + [init -> test-ram_fs_chunk] trunc(13) -> content (size=12): "five-o-five." + [init -> test-ram_fs_chunk] trunc(12) -> content (size=12): "five-o-five." + [init -> test-ram_fs_chunk] trunc(11) -> content (size=11): "five-o-five" + [init -> test-ram_fs_chunk] trunc(10) -> content (size=10): "five-o-fiv" + [init -> test-ram_fs_chunk] trunc(9) -> content (size=9): "five-o-fi" + [init -> test-ram_fs_chunk] trunc(8) -> content (size=8): "five-o-f" + [init -> test-ram_fs_chunk] trunc(7) -> content (size=7): "five-o-" + [init -> test-ram_fs_chunk] trunc(6) -> content (size=6): "five-o" + [init -> test-ram_fs_chunk] trunc(5) -> content (size=5): "five-" + [init -> test-ram_fs_chunk] trunc(4) -> content (size=4): "five" + [init -> test-ram_fs_chunk] trunc(3) -> content (size=3): "fiv" + [init -> test-ram_fs_chunk] trunc(2) -> content (size=2): "fi" + [init -> test-ram_fs_chunk] trunc(1) -> content (size=1): "f" + [init -> test-ram_fs_chunk] allocator: sum=0 +} + + diff --git a/os/src/server/ram_fs/README b/os/src/server/ram_fs/README new file mode 100644 index 0000000000..4a59890446 --- /dev/null +++ b/os/src/server/ram_fs/README @@ -0,0 +1,53 @@ +This directory contains an in-memory file-system implementation. + +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 +ram_fs policy must be defined. + +The following configuration illustates the way of how to express policy. + +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! + +The '' sub node of the '' node provides a way to pre-populate +the file system with directories and files. Note that '' nodes can be +arbitrarily nested. Files can be loaded from the ROM service. By adding the +optional 'at' attribute to a rom node, the file name can be defined +independently from the ROM module name. + +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 ram_fs, refer to the 'libports/run/libc_fs.run' +script. diff --git a/os/src/server/ram_fs/chunk.h b/os/src/server/ram_fs/chunk.h new file mode 100644 index 0000000000..16abc07668 --- /dev/null +++ b/os/src/server/ram_fs/chunk.h @@ -0,0 +1,441 @@ +/* + * \brief Data structure for storing sparse files in RAM + * \author Norman Feske + * \date 2012-04-18 + */ + +/* + * Copyright (C) 2012 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 _CHUNK_H_ +#define _CHUNK_H_ + +/* Genode includes */ +#include +#include +#include +#include + +namespace File_system { + + using namespace Genode; + + + /** + * Common base class of both 'Chunk' and 'Chunk_index' + */ + class Chunk_base : Noncopyable + { + public: + + class Index_out_of_range { }; + + protected: + + seek_off_t const _base_offset; + size_t _num_entries; /* corresponds to last used entry */ + + /** + * Test if specified range lies within the chunk + */ + void assert_valid_range(seek_off_t start, size_t len, + file_size_t chunk_size) const + { + if (is_zero()) return; + + if (start < _base_offset) + throw Index_out_of_range(); + + if (start + len > _base_offset + chunk_size) + throw Index_out_of_range(); + } + + Chunk_base(seek_off_t base_offset) + : _base_offset(base_offset), _num_entries(0) { } + + /** + * Construct zero chunk + * + * A zero chunk is a chunk that cannot be written to. When reading + * from it, it returns zeros. Because there is a single zero chunk + * for each chunk type, the base offset is meaningless. We use a + * base offset of ~0 as marker to identify zero chunks. + */ + Chunk_base() : _base_offset(~0L), _num_entries(0) { } + + public: + + /** + * Return absolute base offset of chunk in bytes + */ + seek_off_t base_offset() const { return _base_offset; } + + /** + * Return true if chunk is a read-only zero chunk + */ + bool is_zero() const { return _base_offset == (seek_off_t)(~0L); } + + /** + * Return true if chunk has no allocated sub chunks + */ + bool empty() const { return _num_entries == 0; } + }; + + + /** + * Chunk of bytes used as leaf in hierarchy of chunk indices + */ + template + class Chunk : public Chunk_base + { + private: + + char _data[CHUNK_SIZE]; + + public: + + enum { SIZE = CHUNK_SIZE }; + + /** + * Construct byte chunk + * + * \param base_offset absolute offset of chunk in bytes + * + * The first argument is unused. Its mere purpose is to make the + * signature of the constructor compatible to the constructor + * of 'Chunk_index'. + */ + Chunk(Allocator &, seek_off_t base_offset) + : + Chunk_base(base_offset) + { + memset(_data, 0, CHUNK_SIZE); + } + + /** + * Construct zero chunk + */ + Chunk() { } + + /** + * Return number of used entries + * + * The returned value corresponds to the index of the last used + * entry + 1. It does not correlate to the number of actually + * allocated entries (there may be ranges of zero blocks). + */ + file_size_t used_size() const { return _num_entries; } + + void write(char const *src, size_t len, seek_off_t seek_offset) + { + assert_valid_range(seek_offset, len, SIZE); + + /* offset relative to this chunk */ + seek_off_t const local_offset = seek_offset - base_offset(); + + memcpy(&_data[local_offset], src, len); + + _num_entries = max(_num_entries, local_offset + len); + } + + void read(char *dst, size_t len, seek_off_t seek_offset) const + { + assert_valid_range(seek_offset, len, SIZE); + + memcpy(dst, &_data[seek_offset - base_offset()], len); + } + + void truncate(file_size_t size) + { + assert_valid_range(size, 0, SIZE); + + /* + * Offset of the first free position (relative to the beginning + * this chunk). + */ + seek_off_t const local_offset = size - base_offset(); + + if (local_offset >= _num_entries) + return; + + memset(&_data[local_offset], 0, _num_entries - local_offset); + + _num_entries = local_offset; + } + }; + + + template + class Chunk_index : public Chunk_base + { + public: + + typedef ENTRY_TYPE Entry; + + enum { ENTRY_SIZE = ENTRY_TYPE::SIZE, + SIZE = ENTRY_SIZE*NUM_ENTRIES }; + + private: + + Allocator &_alloc; + + Entry * _entries[NUM_ENTRIES]; + + /** + * Return instance of a zero sub chunk + */ + static Entry const &_zero_chunk() + { + static Entry zero_chunk; + return zero_chunk; + } + + /** + * Return sub chunk at given index + * + * If there is no sub chunk at the specified index, this function + * transparently allocates one. Hence, the returned sub chunk + * is ready to be written to. + */ + Entry &_entry_for_writing(unsigned index) + { + if (index >= NUM_ENTRIES) + throw Index_out_of_range(); + + if (_entries[index]) + return *_entries[index]; + + seek_off_t entry_offset = base_offset() + index*ENTRY_SIZE; + + _entries[index] = new (&_alloc) Entry(_alloc, entry_offset); + + _num_entries = max(_num_entries, index + 1); + + return *_entries[index]; + } + + /** + * Return sub chunk at given index (for reading only) + * + * This function transparently provides a zero sub chunk for any + * index that is not populated by a real chunk. + */ + Entry const &_entry_for_reading(unsigned index) const + { + if (index >= NUM_ENTRIES) + throw Index_out_of_range(); + + if (_entries[index]) + return *_entries[index]; + + return _zero_chunk(); + } + + /** + * Return index of entry located at specified byte offset + * + * The caller of this function must make sure that the offset + * parameter is within the bounds of the chunk. + */ + unsigned _index_by_offset(seek_off_t offset) const + { + return (offset - base_offset()) / ENTRY_SIZE; + } + + /** + * Apply operation 'func' to a range of entries + */ + template + static void _range_op(THIS &obj, DATA *data, size_t len, + seek_off_t seek_offset, FUNC const &func) + { + /* + * Depending on whether this function is called for reading + * (const function) or writing (non-const function), the + * operand type is const or non-const Entry. The correct type + * is embedded as a trait in the 'FUNC' functor type. + */ + typedef typename FUNC::Entry Const_qualified_entry; + + obj.assert_valid_range(seek_offset, len, SIZE); + + while (len > 0) { + + unsigned const index = obj._index_by_offset(seek_offset); + + Const_qualified_entry &entry = FUNC::lookup(obj, index); + + /* + * Calculate byte offset relative to the chunk + * + * We cannot use 'entry.base_offset()' for this calculation + * because in the const case, the lookup might return a + * zero chunk, which has no defined base offset. Therefore, + * we calculate the base offset via index*ENTRY_SIZE. + */ + seek_off_t const local_seek_offset = + seek_offset - obj.base_offset() - index*ENTRY_SIZE; + + /* available capacity at 'entry' starting at seek offset */ + seek_off_t const capacity = ENTRY_SIZE - local_seek_offset; + seek_off_t const curr_len = min(len, capacity); + + /* apply functor (read or write) to entry */ + func(entry, data, curr_len, seek_offset); + + /* advance to next entry */ + len -= curr_len; + data += curr_len; + seek_offset += curr_len; + } + } + + struct Write_func + { + typedef ENTRY_TYPE Entry; + + static Entry &lookup(Chunk_index &chunk, unsigned i) { + return chunk._entry_for_writing(i); } + + void operator () (Entry &entry, char const *src, size_t len, + seek_off_t seek_offset) const + { + entry.write(src, len, seek_offset); + } + }; + + struct Read_func + { + typedef ENTRY_TYPE const Entry; + + static Entry &lookup(Chunk_index const &chunk, unsigned i) { + return chunk._entry_for_reading(i); } + + void operator () (Entry &entry, char *dst, size_t len, + seek_off_t seek_offset) const + { + if (entry.is_zero()) + memset(dst, 0, len); + else + entry.read(dst, len, seek_offset); + } + }; + + void _init_entries() + { + for (unsigned i = 0; i < NUM_ENTRIES; i++) + _entries[i] = 0; + } + + void _destroy_entry(unsigned i) + { + if (_entries[i] && (i < _num_entries)) { + destroy(&_alloc, _entries[i]); + _entries[i] = 0; + } + } + + public: + + /** + * Constructor + * + * \param alloc allocator to use for allocating sub-chunk + * indices and chunks + * \param base_offset absolute offset of the chunk in bytes + */ + Chunk_index(Allocator &alloc, seek_off_t base_offset) + : Chunk_base(base_offset), _alloc(alloc) { _init_entries(); } + + /** + * Construct zero chunk + */ + Chunk_index() : _alloc(*(Allocator *)0) { } + + /** + * Destructor + */ + ~Chunk_index() + { + for (unsigned i = 0; i < NUM_ENTRIES; i++) + _destroy_entry(i); + } + + /** + * Return size of chunk in bytes + * + * The returned value corresponds to the position after the highest + * offset that was written to. + */ + file_size_t used_size() const + { + if (_num_entries == 0) + return 0; + + /* size of entries that lie completely within the used range */ + file_size_t const size_whole_entries = ENTRY_SIZE*(_num_entries - 1); + + Entry *last_entry = _entries[_num_entries - 1]; + if (!last_entry) + return size_whole_entries; + + return size_whole_entries + last_entry->used_size(); + } + + /** + * Write data to chunk + */ + void write(char const *src, size_t len, seek_off_t seek_offset) + { + _range_op(*this, src, len, seek_offset, Write_func()); + } + + /** + * Read data from chunk + */ + void read(char *dst, size_t len, seek_off_t seek_offset) const + { + _range_op(*this, dst, len, seek_offset, Read_func()); + } + + /** + * Truncate chunk to specified size in bytes + * + * This function can be used to shrink a chunk only. Specifying a + * 'size' larger than 'used_size' has no effect. The value returned + * by 'used_size' refers always to the position of the last byte + * written to the chunk. + */ + void truncate(file_size_t size) + { + unsigned const trunc_index = _index_by_offset(size); + + if (trunc_index >= _num_entries) + return; + + for (unsigned i = trunc_index + 1; i < _num_entries; i++) + _destroy_entry(i); + + /* traverse into sub chunks */ + if (_entries[trunc_index]) + _entries[trunc_index]->truncate(size); + + _num_entries = trunc_index + 1; + + /* + * If the truncated at a chunk boundary, we can release the + * empty trailing chunk at 'trunc_index'. + */ + if (_entries[trunc_index] && _entries[trunc_index]->empty()) { + _destroy_entry(trunc_index); + _num_entries--; + } + } + }; +}; + +#endif /* _CHUNK_H_ */ diff --git a/os/src/server/ram_fs/directory.h b/os/src/server/ram_fs/directory.h new file mode 100644 index 0000000000..7617bcfa52 --- /dev/null +++ b/os/src/server/ram_fs/directory.h @@ -0,0 +1,197 @@ +/* + * \brief File-system directory node + * \author Norman Feske + * \date 2012-04-11 + */ + +#ifndef _DIRECTORY_H_ +#define _DIRECTORY_H_ + +/* local includes */ +#include +#include +#include +#include + +namespace File_system { + + class Directory : public Node + { + private: + + List _entries; + size_t _num_entries; + + public: + + Directory(char const *name) : _num_entries(0) { Node::name(name); } + + bool has_sub_node_unsynchronized(char const *name) const + { + Node *sub_node = _entries.first(); + for (; sub_node; sub_node = sub_node->next()) + if (strcmp(sub_node->name(), name) == 0) + return true; + + return false; + } + + void adopt_unsynchronized(Node *node) + { + /* + * XXX inc ref counter + */ + _entries.insert(node); + _num_entries++; + } + + void discard_unsynchronized(Node *node) + { + _entries.remove(node); + _num_entries--; + } + + Node *lookup_and_lock(char const *path, bool return_parent = false) + { + if (strcmp(path, "") == 0) { + lock(); + return this; + } + + if (!path || path[0] == '/') + throw Lookup_failed(); + + /* find first path delimiter */ + unsigned i = 0; + for (; path[i] && path[i] != '/'; i++); + + /* + * If no path delimiter was found, we are the parent of the + * specified path. + */ + if (path[i] == 0 && return_parent) { + lock(); + return this; + } + + /* + * The offset 'i' corresponds to the end of the first path + * element, which can be either the end of the string or the + * first '/' character. + */ + + /* try to find entry that matches the first path element */ + Node *sub_node = _entries.first(); + for (; sub_node; sub_node = sub_node->next()) + if (strcmp(sub_node->name(), path, i) == 0) + break; + + if (!sub_node) + throw Lookup_failed(); + + if (is_basename(path)) { + + /* + * Because 'path' is a basename that corresponds to an + * existing sub_node, we have found what we were looking + * for. + */ + sub_node->lock(); + return sub_node; + } + + /* + * As 'path' contains one or more path delimiters, traverse + * into the sub directory names after the first path element. + */ + + /* + * We cannot traverse into anything other than a directory. + * + * XXX we might follow symlinks here + */ + Directory *sub_dir = dynamic_cast(sub_node); + if (!sub_dir) + throw Lookup_failed(); + + return sub_dir->lookup_and_lock(path + i + 1); + } + + Directory *lookup_and_lock_dir(char const *path) + { + Node *node = lookup_and_lock(path); + + Directory *dir = dynamic_cast(node); + if (dir) + return dir; + + node->unlock(); + throw Lookup_failed(); + } + + File *lookup_and_lock_file(char const *path) + { + Node *node = lookup_and_lock(path); + + File *file = dynamic_cast(node); + if (file) + return file; + + node->unlock(); + throw Lookup_failed(); + } + + /** + * Lookup parent directory of the specified path + * + * \throw Lookup_failed + */ + Directory *lookup_and_lock_parent(char const *path) + { + return static_cast(lookup_and_lock(path, true)); + } + + 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; + } + + seek_off_t index = seek_offset / sizeof(Directory_entry); + + if (seek_offset % sizeof(Directory_entry)) { + PERR("seek offset not alighed to sizeof(Directory_entry)"); + return 0; + } + + /* find list element */ + Node *node = _entries.first(); + for (unsigned i = 0; i < index && node; node = node->next(), i++); + + /* index out of range */ + if (!node) + return 0; + + Directory_entry *e = (Directory_entry *)(dst); + + if (dynamic_cast(node)) e->type = Directory_entry::TYPE_FILE; + if (dynamic_cast(node)) e->type = Directory_entry::TYPE_DIRECTORY; + if (dynamic_cast(node)) e->type = Directory_entry::TYPE_SYMLINK; + + strncpy(e->name, node->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 { return _num_entries; } + }; +} + +#endif /* _DIRECTORY_H_ */ diff --git a/os/src/server/ram_fs/file.h b/os/src/server/ram_fs/file.h new file mode 100644 index 0000000000..5260919ec3 --- /dev/null +++ b/os/src/server/ram_fs/file.h @@ -0,0 +1,94 @@ +/* + * \brief File node + * \author Norman Feske + * \date 2012-04-11 + */ + +#ifndef _FILE_H_ +#define _FILE_H_ + +/* Genode includes */ +#include + +/* local includes */ +#include +#include + +namespace File_system { + + class File : public Node + { + private: + + typedef Chunk<4096> Chunk_level_3; + typedef Chunk_index<128, Chunk_level_3> Chunk_level_2; + typedef Chunk_index<64, Chunk_level_2> Chunk_level_1; + typedef Chunk_index<64, Chunk_level_1> Chunk_level_0; + + Chunk_level_0 _chunk; + + file_size_t _length; + + public: + + File(Allocator &alloc, char const *name) + : _chunk(alloc, 0), _length(0) { Node::name(name); } + + size_t read(char *dst, size_t len, seek_off_t seek_offset) + { + file_size_t const chunk_used_size = _chunk.used_size(); + + if (seek_offset >= _length) + return 0; + + /* + * Constrain read transaction to available chunk data + * + * Note that 'chunk_used_size' may be lower than '_length' + * because 'Chunk' may have truncated tailing zeros. + */ + if (seek_offset + len >= _length) + len = _length - seek_offset; + + file_size_t read_len = len; + + if (seek_offset + read_len > chunk_used_size) { + if (chunk_used_size >= seek_offset) + read_len = chunk_used_size - seek_offset; + else + read_len = 0; + } + + _chunk.read(dst, read_len, seek_offset); + + /* add zero padding if needed */ + if (read_len < len) + memset(dst + read_len, 0, len - read_len); + + return len; + } + + size_t write(char const *src, size_t len, seek_off_t seek_offset) + { + if (seek_offset == (seek_off_t)(~0)) + seek_offset = _chunk.used_size(); + + if (seek_offset + len >= Chunk_level_0::SIZE) + throw Size_limit_reached(); + + _chunk.write(src, len, (size_t)seek_offset); + + /* + * Keep track of file length. We cannot use 'chunk.used_size()' + * as file length because trailing zeros may by represented + * by zero chunks, which do not contribute to 'used_size()'. + */ + _length = max(_length, seek_offset + len); + return 0; + } + + file_size_t length() const { return _length; } + }; +} + +#endif /* _FILE_H_ */ diff --git a/os/src/server/ram_fs/main.cc b/os/src/server/ram_fs/main.cc new file mode 100644 index 0000000000..ba05f96928 --- /dev/null +++ b/os/src/server/ram_fs/main.cc @@ -0,0 +1,633 @@ +/* + * \brief RAM file system + * \author Norman Feske + * \date 2012-04-11 + */ + +/* + * Copyright (C) 2012 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 +#include + + +/************************************* + ** Helpers for dispatching signals ** + *************************************/ + +namespace Genode { + + struct Signal_dispatcher_base : Signal_context + { + virtual void dispatch(int num) = 0; + }; + + + template + class Signal_dispatcher : private Signal_dispatcher_base, + public Signal_context_capability + { + private: + + T &obj; + void (T::*member) (int); + Signal_receiver &sig_rec; + + public: + + /** + * Constructor + * + * \param sig_rec signal receiver to associate the signal + * handler with + * \param obj,member object and member function to call when + * the signal occurs + */ + Signal_dispatcher(Signal_receiver &sig_rec, + T &obj, void (T::*member)(int)) + : + Signal_context_capability(sig_rec.manage(this)), + obj(obj), member(member), + sig_rec(sig_rec) + { } + + ~Signal_dispatcher() { sig_rec.dissolve(this); } + + void dispatch(int num) { (obj.*member)(num); } + }; +} + + +/************************* + ** File-system service ** + *************************/ + +namespace File_system { + + class Session_component : public Session_rpc_object + { + private: + + Directory &_root; + Node_handle_registry _handle_registry; + bool _writable; + + Signal_dispatcher _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(int) + { + 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 (most 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, Rpc_entrypoint &ep, + Signal_receiver &sig_rec, + Directory &root, bool writable) + : + Session_rpc_object(env()->ram_session()->alloc(tx_buf_size), ep), + _root(root), + _writable(writable), + _process_packet_dispatcher(sig_rec, *this, + &Session_component::_process_packets) + { + /* + * 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 ** + ***************************/ + + 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 (mode != STAT_ONLY && mode != READ_ONLY) + throw Permission_denied(); + + if (create) { + + if (!_writable) + throw Permission_denied(); + + if (dir->has_sub_node_unsynchronized(name.string())) + throw Node_already_exists(); + + try { + File * const file = new (env()->heap()) + File(*env()->heap(), name.string()); + + dir->adopt_unsynchronized(file); + } + catch (Allocator::Out_of_memory) { throw No_space(); } + } + + File *file = dir->lookup_and_lock_file(name.string()); + Node_lock_guard file_guard(*file); + return _handle_registry.alloc(file); + } + + Symlink_handle symlink(Dir_handle, Name const &name, bool create) + { + return Symlink_handle(-1); + } + + Dir_handle dir(Path const &path, bool create) + { + char const *path_str = path.string(); + + _assert_valid_path(path_str); + + /* skip leading '/' */ + path_str++; + + if (create) { + + if (!_writable) + throw Permission_denied(); + + if (!path.is_valid_string()) + throw Name_too_long(); + + Directory *parent = _root.lookup_and_lock_parent(path_str); + + Node_lock_guard guard(*parent); + + char const *name = basename(path_str); + + if (parent->has_sub_node_unsynchronized(name)) + throw Node_already_exists(); + + try { + parent->adopt_unsynchronized(new (env()->heap()) Directory(name)); + } catch (Allocator::Out_of_memory) { + throw No_space(); + } + } + + Directory *dir = _root.lookup_and_lock_dir(path_str); + Node_lock_guard guard(*dir); + return _handle_registry.alloc(dir); + } + + Node_handle node(Path const &path) + { + _assert_valid_path(path.string()); + + Node *node = _root.lookup_and_lock(path.string() + 1); + + Node_lock_guard guard(*node); + return _handle_registry.alloc(node); + } + + void close(Node_handle handle) + { + _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; + } + Symlink *symlink = dynamic_cast(node); + if (symlink) { + s.mode = File_system::Status::MODE_SYMLINK; + return s; + } + return Status(); + } + + void control(Node_handle, Control) { } + + void unlink(Dir_handle dir_handle, Name const &name) + { + if (!valid_name(name.string())) + throw Invalid_name(); + + if (!_writable) + throw Permission_denied(); + + Directory *dir = _handle_registry.lookup_and_lock(dir_handle); + Node_lock_guard dir_guard(*dir); + + Node *node = dir->lookup_and_lock(name.string()); + + dir->discard_unsynchronized(node); + + // XXX implement ref counting, do not destroy node that is + // is still referenced by a node handle + + node->unlock(); + destroy(env()->heap(), node); + } + + void truncate(File_handle, file_size_t size) { } + + 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(); + + if (!valid_name(from_name.string())) + throw Lookup_failed(); + + if (!valid_name(to_name.string())) + throw Invalid_name(); + + Directory *from_dir = _handle_registry.lookup_and_lock(from_dir_handle); + Node_lock_guard from_dir_guard(*from_dir); + + Node *node = from_dir->lookup_and_lock(from_name.string()); + Node_lock_guard node_guard(*node); + node->name(to_name.string()); + + if (!_handle_registry.refer_to_same_node(from_dir_handle, to_dir_handle)) { + Directory *to_dir = _handle_registry.lookup_and_lock(to_dir_handle); + Node_lock_guard to_dir_guard(*to_dir); + + from_dir->discard_unsynchronized(node); + to_dir->adopt_unsynchronized(node); + } + } + }; + + + class Root : public Root_component + { + private: + + Rpc_entrypoint &_channel_ep; + Signal_receiver &_sig_rec; + Directory &_root_dir; + + protected: + + Session_component *_create_session(const char *args) + { + /* + * Determine client-specific policy defined implicitly by + * the client's label. + */ + + Directory *session_root_dir = 0; + bool writeable = false; + + enum { ROOT_MAX_LEN = 256 }; + char root[ROOT_MAX_LEN]; + root[0] = 0; + + try { + Session_policy policy(args); + + /* + * Determine directory that is used as root directory of + * the session. + */ + try { + policy.attribute("root").value(root, sizeof(root)); + if (strcmp("/", root) == 0) { + session_root_dir = &_root_dir; + } else { + + /* + * 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(); + + session_root_dir = _root_dir.lookup_and_lock_dir(root + 1); + session_root_dir->unlock(); + } + } 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, _channel_ep, _sig_rec, + *session_root_dir, writeable); + } + + public: + + /** + * Constructor + * + * \param session_ep session entrypoint + * \param sig_rec signal receiver used for handling the + * data-flow signals of packet streams + * \param md_alloc meta-data allocator + */ + Root(Rpc_entrypoint &session_ep, Allocator &md_alloc, + Signal_receiver &sig_rec, Directory &root_dir) + : + Root_component(&session_ep, &md_alloc), + _channel_ep(session_ep), _sig_rec(sig_rec), _root_dir(root_dir) + { } + }; +}; + + +/** + * Helper for conveniently accessing 'Xml_node' attribute strings + */ +struct Attribute_string +{ + char buf[File_system::MAX_NAME_LEN]; + + /** + * Constructor + * + * \param attr attribute name + * \param fallback if non-null, this is the string used if the attribute + * is not defined. If null, the constructor throws + * an 'Nonexistent_attribute' exception' + * \throw Xml_node::Nonexistent_attribute + */ + Attribute_string(Genode::Xml_node node, char const *attr, char *fallback = 0) + { + try { + node.attribute(attr).value(buf, sizeof(buf)); + } catch (Genode::Xml_node::Nonexistent_attribute) { + + if (fallback) { + Genode::strncpy(buf, fallback, sizeof(buf)); + } else { + char type_name[16]; + node.type_name(type_name, sizeof(type_name)); + PWRN("missing \"%s\" attribute in <%s> node", attr, type_name); + throw Genode::Xml_node::Nonexistent_attribute(); + } + } + } + + operator char * () { return buf; } +}; + + +static void preload_content(Genode::Allocator &alloc, + Genode::Xml_node node, + File_system::Directory &dir) +{ + using namespace File_system; + + for (unsigned i = 0; i < node.num_sub_nodes(); i++) { + Xml_node sub_node = node.sub_node(i); + + /* + * Lookup name attribtue, let 'Nonexistent_attribute' exception fall + * through because this configuration error is considered fatal. + */ + Attribute_string name(sub_node, "name"); + + /* + * Create directory + */ + if (sub_node.has_type("dir")) { + + Directory *sub_dir = new (&alloc) Directory(name); + + /* traverse into the new directory */ + preload_content(alloc, sub_node, *sub_dir); + + dir.adopt_unsynchronized(sub_dir); + } + + /* + * Create file from ROM module + */ + if (sub_node.has_type("rom")) { + + /* read "as" attribute, use "name" as default */ + Attribute_string as(sub_node, "as", name); + + /* read file content from ROM module */ + try { + Attached_rom_dataspace rom(name); + File *file = new (&alloc) File(alloc, as); + file->write(rom.local_addr(), rom.size(), 0); + dir.adopt_unsynchronized(file); + } + catch (Rom_connection::Rom_connection_failed) { + PWRN("failed to open ROM file \"%s\"", (char *)name); } + catch (Rm_session::Attach_failed) { + PWRN("Could not locally attach ROM file \"%s\"", (char *)name); } + } + } +} + + +int main(int, char **) +{ + using namespace File_system; + + enum { STACK_SIZE = 8192 }; + static Cap_connection cap; + static Rpc_entrypoint ep(&cap, STACK_SIZE, "ram_fs_ep"); + static Sliced_heap sliced_heap(env()->ram_session(), env()->rm_session()); + static Signal_receiver sig_rec; + static Directory root_dir(""); + + /* preload RAM file system with content as declared in the config */ + try { + Xml_node content = config()->xml_node().sub_node("content"); + preload_content(*env()->heap(), content, root_dir); } + catch (Xml_node::Nonexistent_sub_node) { } + catch (Config::Invalid) { } + + static File_system::Root root(ep, sliced_heap, sig_rec, root_dir); + + env()->parent()->announce(ep.manage(&root)); + + for (;;) { + Signal s = sig_rec.wait_for_signal(); + static_cast(s.context())->dispatch(s.num()); + } + + return 0; +} diff --git a/os/src/server/ram_fs/node.h b/os/src/server/ram_fs/node.h new file mode 100644 index 0000000000..0a9df7e353 --- /dev/null +++ b/os/src/server/ram_fs/node.h @@ -0,0 +1,74 @@ +/* + * \brief File-system node + * \author Norman Feske + * \date 2012-04-11 + */ + +#ifndef _NODE_H_ +#define _NODE_H_ + +/* Genode includes */ +#include +#include + +namespace File_system { + + class Node : public List::Element + { + public: + + typedef char Name[128]; + + private: + + Lock _lock; + int _ref_count; + Name _name; + unsigned long const _inode; + + /** + * Generate unique inode number + */ + static unsigned long _unique_inode() + { + static unsigned long inode_count; + return ++inode_count; + } + + public: + + Node() : _ref_count(0), _inode(_unique_inode()) { _name[0] = 0; } + + virtual ~Node() { } + + 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; + + }; + + + /** + * 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/ram_fs/node_handle_registry.h b/os/src/server/ram_fs/node_handle_registry.h new file mode 100644 index 0000000000..a08c82dc7c --- /dev/null +++ b/os/src/server/ram_fs/node_handle_registry.h @@ -0,0 +1,133 @@ +/* + * \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]; + + /** + * 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)) + _nodes[handle.value] = 0; + } + + /** + * 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)) + throw Invalid_handle(); + + return _nodes[h1.value] == _nodes[h2.value]; + } + }; +} + +#endif /* _NODE_HANDLE_REGISTRY_H_ */ diff --git a/os/src/server/ram_fs/symlink.h b/os/src/server/ram_fs/symlink.h new file mode 100644 index 0000000000..07dacd3850 --- /dev/null +++ b/os/src/server/ram_fs/symlink.h @@ -0,0 +1,37 @@ +/* + * \brief Symlink file-system node + * \author Norman Feske + * \date 2012-04-11 + */ + +#ifndef _SYMLINK_H_ +#define _SYMLINK_H_ + +/* local includes */ +#include + +namespace File_system { + + class Symlink : public Node + { + private: + + Name _link_to; + + public: + + size_t read(char *dst, size_t len, seek_off_t seek_offset) + { + PDBG("not implemented"); + return 0; + } + + size_t write(char const *src, size_t len, seek_off_t seek_offset) + { + PDBG("not implemented"); + return 0; + } + }; +} + +#endif /* _SYMLINK_H_ */ diff --git a/os/src/server/ram_fs/target.mk b/os/src/server/ram_fs/target.mk new file mode 100644 index 0000000000..06861add60 --- /dev/null +++ b/os/src/server/ram_fs/target.mk @@ -0,0 +1,4 @@ +TARGET = ram_fs +SRC_CC = main.cc +LIBS = cxx env server signal +INC_DIR += $(PRG_DIR) diff --git a/os/src/server/ram_fs/util.h b/os/src/server/ram_fs/util.h new file mode 100644 index 0000000000..17f57fd330 --- /dev/null +++ b/os/src/server/ram_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_ */ diff --git a/os/src/test/ram_fs_chunk/main.cc b/os/src/test/ram_fs_chunk/main.cc new file mode 100644 index 0000000000..2e265e3688 --- /dev/null +++ b/os/src/test/ram_fs_chunk/main.cc @@ -0,0 +1,132 @@ +/* + * \brief Unit test for RAM fs chunk data structure + * \author Norman Feske + * \date 2012-04-19 + */ + +/* Genode includes */ +#include +#include + +/* local 'ram_fs' include */ +#include + +namespace File_system { + typedef Chunk<2> Chunk_level_3; + typedef Chunk_index<3, Chunk_level_3> Chunk_level_2; + typedef Chunk_index<4, Chunk_level_2> Chunk_level_1; + typedef Chunk_index<5, Chunk_level_1> Chunk_level_0; +} + + +namespace Genode { + + struct Allocator_tracer : Allocator + { + size_t _sum; + Allocator &_wrapped; + + Allocator_tracer(Allocator &wrapped) : _sum(0), _wrapped(wrapped) { } + + size_t sum() const { return _sum; } + + bool alloc(size_t size, void **out_addr) + { + _sum += size; + return _wrapped.alloc(size, out_addr); + } + + void free(void *addr, size_t size) + { + _sum -= size; + _wrapped.free(addr, size); + } + + size_t overhead(size_t size) { return 0; } + }; +}; + + +static void dump(File_system::Chunk_level_0 &chunk) +{ + using namespace File_system; + + static char read_buf[Chunk_level_0::SIZE]; + + size_t used_size = chunk.used_size(); + + struct File_size_out_of_bounds { }; + if (used_size > Chunk_level_0::SIZE) + throw File_size_out_of_bounds(); + + chunk.read(read_buf, used_size, 0); + + printf("content (size=%zd): \"", used_size); + for (unsigned i = 0; i < used_size; i++) { + char c = read_buf[i]; + if (c) + printf("%c", c); + else + printf("."); + } + printf("\"\n"); +} + + +static void write(File_system::Chunk_level_0 &chunk, + char const *str, Genode::off_t seek_offset) +{ + using namespace Genode; + printf("write \"%s\" at offset %ld -> ", str, seek_offset); + chunk.write(str, strlen(str), seek_offset); + dump(chunk); +} + + +static void truncate(File_system::Chunk_level_0 &chunk, + File_system::file_size_t size) +{ + using namespace Genode; + printf("trunc(%zd) -> ", (size_t)size); + chunk.truncate(size); + dump(chunk); +} + + +int main(int, char **) +{ + using namespace File_system; + using namespace Genode; + + printf("--- ram_fs_chunk test ---\n"); + + PINF("chunk sizes"); + PINF(" level 0: payload=%zd sizeof=%zd", Chunk_level_0::SIZE, sizeof(Chunk_level_0)); + PINF(" level 1: payload=%zd sizeof=%zd", Chunk_level_1::SIZE, sizeof(Chunk_level_1)); + PINF(" level 2: payload=%zd sizeof=%zd", Chunk_level_2::SIZE, sizeof(Chunk_level_2)); + PINF(" level 3: payload=%zd sizeof=%zd", Chunk_level_3::SIZE, sizeof(Chunk_level_3)); + + static Allocator_tracer alloc(*env()->heap()); + + { + Chunk_level_0 chunk(alloc, 0); + + write(chunk, "five-o-one", 0); + + /* overwrite part of the file */ + write(chunk, "five", 7); + + /* write to position beyond current file length */ + write(chunk, "Nuance", 17); + write(chunk, "YM-2149", 35); + + truncate(chunk, 30); + + for (unsigned i = 29; i > 0; i--) + truncate(chunk, i); + } + + printf("allocator: sum=%zd\n", alloc.sum()); + + return 0; +} diff --git a/os/src/test/ram_fs_chunk/target.mk b/os/src/test/ram_fs_chunk/target.mk new file mode 100644 index 0000000000..81dd60011b --- /dev/null +++ b/os/src/test/ram_fs_chunk/target.mk @@ -0,0 +1,4 @@ +TARGET = test-ram_fs_chunk +SRC_CC = main.cc +INC_DIR += $(REP_DIR)/src/server/ram_fs +LIBS = env