From a2826174070aa7d5ba8952f6c6cf5bca659fba1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20S=C3=B6ntgen?= Date: Mon, 9 Dec 2013 16:30:07 +0100 Subject: [PATCH] libports: initial fuse_fs implementation The fuse_fs server provides access to a FUSE based file system by using a File_system_session. Fixes #1058. --- libports/src/server/fuse_fs/README | 26 + libports/src/server/fuse_fs/directory.h | 277 +++++++++ libports/src/server/fuse_fs/exfat/target.mk | 17 + libports/src/server/fuse_fs/ext2/target.mk | 30 + libports/src/server/fuse_fs/file.h | 152 +++++ libports/src/server/fuse_fs/fuse_fs_main.cc | 570 ++++++++++++++++++ libports/src/server/fuse_fs/mode_util.h | 35 ++ libports/src/server/fuse_fs/node.h | 130 ++++ .../src/server/fuse_fs/node_handle_registry.h | 198 ++++++ libports/src/server/fuse_fs/ntfs-3g/target.mk | 21 + libports/src/server/fuse_fs/symlink.h | 83 +++ libports/src/server/fuse_fs/util.h | 126 ++++ 12 files changed, 1665 insertions(+) create mode 100644 libports/src/server/fuse_fs/README create mode 100644 libports/src/server/fuse_fs/directory.h create mode 100644 libports/src/server/fuse_fs/exfat/target.mk create mode 100644 libports/src/server/fuse_fs/ext2/target.mk create mode 100644 libports/src/server/fuse_fs/file.h create mode 100644 libports/src/server/fuse_fs/fuse_fs_main.cc create mode 100644 libports/src/server/fuse_fs/mode_util.h create mode 100644 libports/src/server/fuse_fs/node.h create mode 100644 libports/src/server/fuse_fs/node_handle_registry.h create mode 100644 libports/src/server/fuse_fs/ntfs-3g/target.mk create mode 100644 libports/src/server/fuse_fs/symlink.h create mode 100644 libports/src/server/fuse_fs/util.h diff --git a/libports/src/server/fuse_fs/README b/libports/src/server/fuse_fs/README new file mode 100644 index 0000000000..db68b4c5fd --- /dev/null +++ b/libports/src/server/fuse_fs/README @@ -0,0 +1,26 @@ +The fuse_fs server provides access to a FUSE based file system by using a +File_system_session. + +The File_system_session component implementation is independent from each +FUSE based file system. fuse_fs only calls the FUSE operation in question +directly. These operations are provided by the FUSE file system and Genode's +libfuse library makes sure, that each operation is executeable, e.g. by using +a dummy function in case it is not provided by the FUSE file system. +Therefore, to utilize a FUSE file system, the FUSE file system is linked +against libfuse as well as the File_system_session component. For each +fuse_fs server there is a binary (.e.g. 'os/src/server/fuse_fs/ext2'). + +Note: write-support is supported but considered to be experimantal at this +point and for now using it is NOT recommended. + + +To use the ext2_fuse_fs server in noux the following config snippet may be +used: + +! +! +! +! +! +! +! diff --git a/libports/src/server/fuse_fs/directory.h b/libports/src/server/fuse_fs/directory.h new file mode 100644 index 0000000000..6b79f76776 --- /dev/null +++ b/libports/src/server/fuse_fs/directory.h @@ -0,0 +1,277 @@ +/* + * \brief File-system directory node + * \author Norman Feske + * \author Christian Helmuth + * \author Josef Soentgen + * \date 2013-11-11 + */ + +#ifndef _DIRECTORY_H_ +#define _DIRECTORY_H_ + +/* Genode includes */ +#include +#include + +/* libc includes */ +#include +#include +#include +#include + +/* local includes */ +#include +#include +#include +#include +#include + +#include +#include + + +namespace File_system { + class Directory; +} + + +class File_system::Directory : public Node +{ + private: + + typedef Genode::Path Path; + + struct fuse_file_info _file_info; + Path _path; + Allocator &_alloc; + + /** + * Check if the given path points to a directory + */ + bool _is_dir(char const *path) + { + struct stat s; + if (Fuse::fuse()->op.getattr(path, &s) != 0 || ! S_ISDIR(s.st_mode)) + return false; + + return true; + } + + void _open_path(char const *path, bool create) + { + int res; + int tries = 0; + do { + /* first try to open path */ + res = Fuse::fuse()->op.opendir(path, &_file_info); + if (res == 0) { + break; + } + + if (create && !tries) { + res = Fuse::fuse()->op.mkdir(path, 0755); + switch (res) { + case 0: break; + default: + PERR("could not create '%s'", path); + throw Lookup_failed(); + } + + tries++; + continue; + } + + if (res < 0) { + int err = -res; + switch (err) { + case EACCES: + PERR("op.mkdir() permission denied"); + throw Permission_denied(); + case EEXIST: + throw Node_already_exists(); + case EIO: + PERR("op.mkdir() I/O error occurred"); + throw Lookup_failed(); + case ENOENT: + throw Lookup_failed(); + case ENOTDIR: + throw Lookup_failed(); + case ENOSPC: + PERR("op.mkdir() error while expanding directory"); + throw Lookup_failed(); + case EROFS: + throw Permission_denied(); + default: + PERR("op.mkdir() returned unexpected error code: %d", res); + throw Lookup_failed(); + } + } + } while (true); + } + + size_t _num_entries() + { + char buf[4096]; + + Genode::memset(buf, 0, sizeof (buf)); + + struct fuse_dirhandle dh = { + .filler = Fuse::fuse()->filler, + .buf = buf, + .size = sizeof (buf), + .offset = 0, + }; + + int res = Fuse::fuse()->op.readdir(_path.base(), &dh, + Fuse::fuse()->filler, 0, + &_file_info); + + if (res != 0) + return 0; + + return dh.offset / sizeof (struct dirent); + } + + public: + + Directory(Allocator &alloc, char const *path, bool create) + : + Node(path), + _path(path), + _alloc(alloc) + { + if (!create && !_is_dir(path)) + throw Lookup_failed(); + + _open_path(path, create); + } + + virtual ~Directory() + { + Fuse::fuse()->op.release(_path.base(), &_file_info); + } + + Node *node(char const *path) + { + Path node_path(path, _path.base()); + + struct stat s; + int res = Fuse::fuse()->op.getattr(node_path.base(), &s); + if (res != 0) + 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(this, path, STAT_ONLY); + else if (S_ISLNK(s.st_mode)) + node = new (&_alloc) Symlink(this, path, false); + else + throw Lookup_failed(); + + node->lock(); + return node; + } + + struct fuse_file_info *file_info() { return &_file_info; } + + Status status() + { + struct stat s; + int res = Fuse::fuse()->op.getattr(_path.base(), &s); + if (res != 0) + return Status(); + + Status status; + status.inode = s.st_ino ? s.st_ino : 1; + status.size = _num_entries() * sizeof(Directory_entry); + status.mode = File_system::Status::MODE_DIRECTORY; + + return status; + } + + 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); + + char buf[4096]; + + Genode::memset(buf, 0, sizeof (buf)); + struct dirent *de = (struct dirent *)buf; + + struct fuse_dirhandle dh = { + .filler = Fuse::fuse()->filler, + .buf = buf, + .size = sizeof (buf), + .offset = 0, + }; + + int res = Fuse::fuse()->op.readdir(_path.base(), &dh, + Fuse::fuse()->filler, 0, + &_file_info); + if (res != 0) + return 0; + + if (index > (seek_off_t)(dh.offset / sizeof (struct dirent))) + return 0; + + struct dirent *dent = de + index; + 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; + /** + * There are FUSE file system implementations that do not fill-out + * d_type when calling readdir(). We mark these entries by setting + * their type to DT_UNKNOWN in our libfuse implementation. Afterwards + * we call getattr() on each entry that, hopefully, will yield proper + * results. + */ + case DT_UNKNOWN: + { + Genode::Path<4096> path(dent->d_name, _path.base()); + struct stat sbuf; + res = Fuse::fuse()->op.getattr(path.base(), &sbuf); + if (res == 0) { + switch (IFTODT(sbuf.st_mode)) { + case DT_REG: e->type = Directory_entry::TYPE_FILE; break; + case DT_DIR: e->type = Directory_entry::TYPE_DIRECTORY; break; + } + /* break outer switch */ + 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; + } +}; + +#endif /* _DIRECTORY_H_ */ diff --git a/libports/src/server/fuse_fs/exfat/target.mk b/libports/src/server/fuse_fs/exfat/target.mk new file mode 100644 index 0000000000..2612cb8895 --- /dev/null +++ b/libports/src/server/fuse_fs/exfat/target.mk @@ -0,0 +1,17 @@ +include $(REP_DIR)/ports/exfat.inc +EXFAT_DIR = $(REP_DIR)/contrib/$(EXFAT) + +TARGET = exfat_fuse_fs + +SRC_C = $(notdir $(EXFAT_DIR)/fuse/main.c) +SRC_CC = fuse_fs_main.cc \ + init.cc + +LIBS = base config server libc libc_log libc_block libfuse libexfat +INC_DIR += $(PRG_DIR)/.. + +CC_OPT += -Wno-unused-function + +vpath %.c $(EXFAT_DIR)/fuse +vpath fuse_fs_main.cc $(PRG_DIR)/.. +vpath init.cc $(PRG_DIR)/../../../lib/exfat diff --git a/libports/src/server/fuse_fs/ext2/target.mk b/libports/src/server/fuse_fs/ext2/target.mk new file mode 100644 index 0000000000..72b6933c90 --- /dev/null +++ b/libports/src/server/fuse_fs/ext2/target.mk @@ -0,0 +1,30 @@ +include $(REP_DIR)/ports/fuse-ext2.inc +FUSE_EXT2_DIR = $(REP_DIR)/contrib/$(FUSE_EXT2)/fuse-ext2 + +TARGET = ext2_fuse_fs + +FILTER_OUT = fuse-ext2.probe.c fuse-ext2.wait.c +SRC_C = $(filter-out $(FILTER_OUT), $(notdir $(wildcard $(FUSE_EXT2_DIR)/*.c))) + + +SRC_CC = fuse_fs_main.cc \ + init.cc + +LIBS = base config server libc libc_log libc_block libfuse libext2fs + +CC_OPT += -DHAVE_CONFIG_H -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 + +CC_OPT += -Wno-unused-function -Wno-unused-variable \ + -Wno-unused-but-set-variable -Wno-cpp \ + -Wno-implicit-function-declaration \ + -Wno-maybe-uninitialized + + +INC_DIR += $(REP_DIR)/src/lib/fuse-ext2 \ + $(FUSE_EXT2_DIR) + +INC_DIR += $(PRG_DIR)/.. + +vpath %.c $(FUSE_EXT2_DIR) +vpath fuse_fs_main.cc $(PRG_DIR)/.. +vpath init.cc $(PRG_DIR)/../../../lib/fuse-ext2 diff --git a/libports/src/server/fuse_fs/file.h b/libports/src/server/fuse_fs/file.h new file mode 100644 index 0000000000..b8aea3a5cd --- /dev/null +++ b/libports/src/server/fuse_fs/file.h @@ -0,0 +1,152 @@ +/* + * \brief File node + * \author Norman Feske + * \author Christian Helmuth + * \author Josef Soentgen + * \date 2013-11-26 + */ + +#ifndef _FILE_H_ +#define _FILE_H_ + +/* local includes */ +#include +#include + +#include +#include + +namespace File_system { + class File; +} + + +class File_system::File : public Node +{ + private: + + Node *_parent; + + typedef Genode::Path Path; + Path _path; + + struct fuse_file_info _file_info; + + void _open_path(char const *path, Mode mode, bool create, bool trunc) + { + int res; + int tries = 0; + do { + /* first try to open pathname */ + res = Fuse::fuse()->op.open(path, &_file_info); + if (res == 0) { + break; + } + + /* try to create pathname if open failed and create is true */ + if (create && !tries) { + mode_t mode = S_IFREG | 0644; + int res = Fuse::fuse()->op.mknod(path, mode, 0); + switch (res) { + case 0: + break; + default: + PERR("could not create '%s'", path); + throw Lookup_failed(); + } + + tries++; + continue; + } + + if (res < 0) { + throw Lookup_failed(); + } + } + while (true); + + if (trunc) { + res = Fuse::fuse()->op.ftruncate(path, 0, &_file_info); + + if (res != 0) { + Fuse::fuse()->op.release(path, &_file_info); + throw Lookup_failed(); + } + } + } + + size_t _length() + { + struct stat s; + int res = Fuse::fuse()->op.getattr(_path.base(), &s); + if (res != 0) + return 0; + + return s.st_size; + } + + public: + + File(Node *parent, char const *name, Mode mode, + bool create = false, bool trunc = false) + : + Node(name), + _parent(parent), + _path(name, _parent->name()) + { + _open_path(_path.base(), mode, create, trunc); + } + + ~File() + { + Fuse::fuse()->op.release(_path.base(), &_file_info); + } + + struct fuse_file_info *file_info() { return &_file_info; } + + Status status() + { + struct stat s; + int res = Fuse::fuse()->op.getattr(_path.base(), &s); + if (res != 0) + return Status(); + + Status status; + status.inode = s.st_ino ? s.st_ino : 1; + status.size = s.st_size; + status.mode = File_system::Status::MODE_FILE; + return status; + } + + size_t read(char *dst, size_t len, seek_off_t seek_offset) + { + /* append mode, use actual length as offset */ + if (seek_offset == ~0ULL) + seek_offset = _length(); + + int ret = Fuse::fuse()->op.read(_path.base(), dst, len, + seek_offset, &_file_info); + return ret < 0 ? 0 : ret; + } + + size_t write(char const *src, size_t len, seek_off_t seek_offset) + { + /* append mode, use actual length as offset */ + if (seek_offset == ~0ULL) + seek_offset = _length(); + + int ret = Fuse::fuse()->op.write(_path.base(), src, len, + seek_offset, &_file_info); + return ret < 0 ? 0 : ret; + } + + void truncate(file_size_t size) + { + int res = Fuse::fuse()->op.ftruncate(_path.base(), size, + &_file_info); + if (res == 0) + mark_as_updated(); + } +}; + +#endif /* _FILE_H_ */ diff --git a/libports/src/server/fuse_fs/fuse_fs_main.cc b/libports/src/server/fuse_fs/fuse_fs_main.cc new file mode 100644 index 0000000000..5446afeb46 --- /dev/null +++ b/libports/src/server/fuse_fs/fuse_fs_main.cc @@ -0,0 +1,570 @@ +/* + * \brief FUSE file system + * \author Josef Soentgen + * \date 2013-11-27 + */ + +/* + * Copyright (C) 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 + +/* libc includes */ +#include + +/* local includes */ +#include +#include +#include + + +static bool const verbose = false; +#define PDBGV(...) if (verbose) PDBG(__VA_ARGS__) + + +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 _writeable; + + 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: + /* session is read-only */ + if (!_writeable) + break; + + 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 writeable, + 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)), + _writeable(writeable), + _process_packet_dispatcher(_ep, *this, &Session_component::_process_packets) + { + _tx.sigh_packet_avail(_process_packet_dispatcher); + _tx.sigh_ready_to_ack(_process_packet_dispatcher); + } + + /** + * Destructor + */ + ~Session_component() + { + Fuse::sync_fs(); + + 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_filename(name.string())) + throw Invalid_name(); + + Directory *dir = _handle_registry.lookup_and_lock(dir_handle); + Node_lock_guard dir_guard(*dir); + + PDBGV("dir: '%s' name: '%s' %s", dir->name(), name.string(), + create ? "create" : ""); + + if (create && !_writeable) + throw Permission_denied(); + + File *file = new (&_md_alloc) File(dir, 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) + { + if (! Fuse::support_symlinks()) { + PERR("FUSE file system does not support symlinks"); + return Symlink_handle(); + } + + if (!valid_filename(name.string())) + throw Invalid_name(); + + Directory *dir = _handle_registry.lookup_and_lock(dir_handle); + Node_lock_guard dir_guard(*dir); + + PDBGV("dir: '%s' name: '%s'", dir->name(), name.string()); + + if (create && !_writeable) + throw Permission_denied(); + + Symlink *symlink = new (&_md_alloc) Symlink(dir, name.string(), create); + Node_lock_guard symlink_guard(*symlink); + + return _handle_registry.alloc(symlink); + } + + Dir_handle dir(Path const &path, bool create) + { + char const *path_str = path.string(); + + _assert_valid_path(path_str); + + PDBGV("path: '%s'", path_str); + + if (create && !_writeable) + throw Permission_denied(); + + if (!path.is_valid_string()) + throw Name_too_long(); + + Directory *dir_node = new (&_md_alloc) Directory(_md_alloc, path_str, create); + + Node_lock_guard guard(*dir_node); + return _handle_registry.alloc(dir_node); + } + + Node_handle node(Path const &path) + { + char const *path_str = path.string(); + + _assert_valid_path(path_str); + + /** + * FIXME this leads to '/' as parent and 'the rest' as name, + * which fortunatly is in this case not a problem. + */ + PDBGV("path_str: '%s'", path_str); + Node *node = _root.node(path_str + 1); + + Node_lock_guard guard(*node); + return _handle_registry.alloc(node); + } + + void close(Node_handle handle) + { + Node *node; + + try { + node = _handle_registry.lookup_and_lock(handle); + /** + * We need to call unlock() here because the handle registry + * calls lock() itself on the node. + */ + node->unlock(); + } catch (Invalid_handle) { + PERR("close() called with invalid handle"); + return; + } + + PDBGV("node: %p name: '%s'", node, node->name()); + + _handle_registry.free(handle); + destroy(&_md_alloc, node); + } + + Status status(Node_handle node_handle) + { + Node *node = _handle_registry.lookup_and_lock(node_handle); + Node_lock_guard guard(*node); + + File *file = dynamic_cast(node); + if (file) + return file->status(); + + Directory *dir = dynamic_cast(node); + if (dir) + return dir->status(); + + Symlink *symlink = dynamic_cast(node); + if (symlink) + return symlink->status(); + + return Status(); + } + + void control(Node_handle, Control) + { + PERR("%s not implemented", __func__); + } + + void unlink(Dir_handle dir_handle, Name const &name) + { + if (!_writeable) + throw Permission_denied(); + + Directory *dir = _handle_registry.lookup_and_lock(dir_handle); + Node_lock_guard dir_guard(*dir); + + PDBGV("dir: '%s' name: '%s'", dir->name(), name.string()); + + Absolute_path absolute_path(_root.name()); + + try { + absolute_path.append(dir->name()); + absolute_path.append("/"); + absolute_path.append(name.string()); + } catch (Path_base::Path_too_long) { + throw Invalid_name(); + } + + /* XXX remove direct use of FUSE operations */ + int res = Fuse::fuse()->op.unlink(absolute_path.base()); + + if (res != 0) { + PERR("fuse()->op.unlink() returned unexpected error code: %d", res); + return; + } + } + + void truncate(File_handle file_handle, file_size_t size) + { + if (!_writeable) + throw Permission_denied(); + + File *file; + try { file = _handle_registry.lookup_and_lock(file_handle); } + catch (Invalid_handle) { throw Lookup_failed(); } + + Node_lock_guard file_guard(*file); + file->truncate(size); + } + + void move(Dir_handle from_dir_handle, Name const &from_name, + Dir_handle to_dir_handle, Name const &to_name) + { + if (!_writeable) + throw Permission_denied(); + + Directory *from_dir, *to_dir; + try { from_dir = _handle_registry.lookup_and_lock(from_dir_handle); } + catch (Invalid_handle) { throw Lookup_failed(); } + + try { to_dir = _handle_registry.lookup_and_lock(to_dir_handle); } + catch (Invalid_handle) { + from_dir->unlock(); + throw Lookup_failed(); + } + + Node_lock_guard from_dir_guard(*from_dir); + Node_lock_guard to_dir_guard(*to_dir); + + PDBGV("from_dir: '%s' from_name: '%s', to_dir: '%s' to_name: '%s'", + from_dir->name(), from_name.string(), to_dir->name(), to_name.string()); + + Absolute_path absolute_from_path(_root.name()); + Absolute_path absolute_to_path(_root.name()); + + try { + absolute_from_path.append(from_dir->name()); + absolute_from_path.append("/"); + absolute_from_path.append(from_name.string()); + absolute_to_path.append(to_dir->name()); + absolute_to_path.append("/"); + absolute_to_path.append(to_name.string()); + } catch (Path_base::Path_too_long) { + throw Invalid_name(); + } + + PDBGV("from_path = %s", absolute_from_path.base()); + PDBGV("to_path = %s", absolute_to_path.base()); + + /* XXX remove direct use of FUSE operations */ + int res = Fuse::fuse()->op.rename(absolute_to_path.base(), + absolute_from_path.base()); + + if (res != 0) { + PERR("fuse()->op.rename() returned unexpected error code: %d", res); + return; + } + } + + void sigh(Node_handle node_handle, Signal_context_capability sigh) + { + _handle_registry.sigh(node_handle, sigh); + } + + void sync() + { + Fuse::sync_fs(); + } +}; + + +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; + } 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"); + PWRN("WARNING: write support in fuse_fs is considered experimental, data-loss may occur."); + } 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) + { + if (!Fuse::init_fs()) { + PERR("FUSE fs initialization failed"); + return; + } + + env()->parent()->announce(ep.manage(fs_root)); + } + + ~Main() + { + if (Fuse::initialized()) { + Fuse::deinit_fs(); + } + } +}; + + +/********************** + ** Server framework ** + **********************/ + +char const * Server::name() { return "fuse_fs_ep"; } +/** + * The large stack is needed because FUSE file system may call + * libc functions that require a large stack, e.g. timezone + * related functions. + */ +Genode::size_t Server::stack_size() { return 8192 * sizeof(long); } +void Server::construct(Server::Entrypoint &ep) { static File_system::Main inst(ep); } diff --git a/libports/src/server/fuse_fs/mode_util.h b/libports/src/server/fuse_fs/mode_util.h new file mode 100644 index 0000000000..169a51c92c --- /dev/null +++ b/libports/src/server/fuse_fs/mode_util.h @@ -0,0 +1,35 @@ +/* + * \brief Mode utilities + * \author Josef Soentgen + * \date 2013-11-26 + */ + +#ifndef _MODE_UTIL_H_ +#define _MODE_UTIL_H_ + +/* Genode includes */ +#include + +/* libc includes */ +#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 /* _MODE_UTIL_H_ */ diff --git a/libports/src/server/fuse_fs/node.h b/libports/src/server/fuse_fs/node.h new file mode 100644 index 0000000000..5a4643de45 --- /dev/null +++ b/libports/src/server/fuse_fs/node.h @@ -0,0 +1,130 @@ +/* + * \brief File-system node + * \author Norman Feske + * \author Christian Helmuth + * \author Josef Soentgen + * \date 2013-11-11 + */ + +#ifndef _NODE_H_ +#define _NODE_H_ + +/* Genode includes */ +#include +#include +#include +#include + + +namespace File_system { + + typedef Genode::Path Absolute_path; + + 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 + { + protected: + + unsigned long _inode; + Absolute_path _name; + + private: + + Lock _lock; + List _listeners; + + public: + + Node(char const *name) : _name(name) { } + + virtual ~Node() + { + /* propagate event to listeners */ + mark_as_updated(); + notify_listeners(); + + while (_listeners.first()) + _listeners.remove(_listeners.first()); + } + + char const *name() const { return _name.base(); } + + 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/libports/src/server/fuse_fs/node_handle_registry.h b/libports/src/server/fuse_fs/node_handle_registry.h new file mode 100644 index 0000000000..a334e423f8 --- /dev/null +++ b/libports/src/server/fuse_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/libports/src/server/fuse_fs/ntfs-3g/target.mk b/libports/src/server/fuse_fs/ntfs-3g/target.mk new file mode 100644 index 0000000000..f70f6980d0 --- /dev/null +++ b/libports/src/server/fuse_fs/ntfs-3g/target.mk @@ -0,0 +1,21 @@ +include $(REP_DIR)/ports/ntfs-3g.inc +NTFS_3G_DIR = $(REP_DIR)/contrib/$(NTFS_3G) + +TARGET = ntfs-3g_fuse_fs + +SRC_C = ntfs-3g.c ntfs-3g_common.c +SRC_CC = fuse_fs_main.cc \ + init.cc + +LIBS = base config server libc libc_log libc_block libfuse libntfs-3g + +CC_OPT = -DHAVE_TIMESPEC -DHAVE_CONFIG_H -DRECORD_LOCKING_NOT_IMPLEMENTED + +INC_DIR += $(PRG_DIR)/.. + +INC_DIR += $(REP_DIR)/src/lib/ntfs-3g \ + $(REP_DIR)/contrib/$(NTFS_3G)/src + +vpath %.c $(NTFS_3G_DIR)/src +vpath fuse_fs_main.cc $(PRG_DIR)/.. +vpath %.cc $(REP_DIR)/src/lib/ntfs-3g diff --git a/libports/src/server/fuse_fs/symlink.h b/libports/src/server/fuse_fs/symlink.h new file mode 100644 index 0000000000..1901c0d3b7 --- /dev/null +++ b/libports/src/server/fuse_fs/symlink.h @@ -0,0 +1,83 @@ +/* + * \brief Symlink file-system node + * \author Norman Feske + * \author Christian Helmuth + * \author Josef Soentgen + * \date 2013-11-26 + */ + +#ifndef _SYMLINK_H_ +#define _SYMLINK_H_ + +/* local includes */ +#include + + +namespace File_system { + class Symlink; +} + + +class File_system::Symlink : public Node +{ + private: + + Node *_parent; + typedef Genode::Path Path; + Path _path; + + size_t _length() const + { + struct stat s; + int res = Fuse::fuse()->op.getattr(_path.base(), &s); + if (res != 0) + return 0; + + return s.st_size; + } + + public: + + Symlink(Node *parent, char const *name, bool create = false) + : + Node(name), + _parent(parent), + _path(name, parent->name()) + { } + + Status status() + { + struct stat s; + int res = Fuse::fuse()->op.getattr(_path.base(), &s); + if (res != 0) + return Status(); + + Status status; + status.inode = s.st_ino ? s.st_ino : 1; + status.size = s.st_size; + status.mode = File_system::Status::MODE_FILE; + return status; + } + + size_t read(char *dst, size_t len, seek_off_t seek_offset) + { + int res = Fuse::fuse()->op.readlink(_path.base(), dst, len); + if (res != 0) + return 0; + + return Genode::strlen(dst); + } + + size_t write(char const *src, size_t len, seek_off_t seek_offset) + { + int res = Fuse::fuse()->op.symlink(src, _path.base()); + if (res != 0) + return 0; + + return len; + } + + file_size_t length() const { return _length(); } +}; + +#endif /* _SYMLINK_H_ */ diff --git a/libports/src/server/fuse_fs/util.h b/libports/src/server/fuse_fs/util.h new file mode 100644 index 0000000000..f7988b10e3 --- /dev/null +++ b/libports/src/server/fuse_fs/util.h @@ -0,0 +1,126 @@ +/* + * \brief Utilities + * \author Norman Feske + * \author Christian Prochaska + * \date 2012-04-11 + */ + +#ifndef _UTIL_H_ +#define _UTIL_H_ + +/* Genode includes */ +#include + + +/** + * 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 null-terminated string 'substr' occurs in null-terminated + * string 'str' + */ +static bool string_contains(char const *str, char const *substr) +{ + using namespace Genode; + + size_t str_len = strlen(str); + size_t substr_len = strlen(substr); + + if (str_len < substr_len) + return false; + + for (size_t i = 0; i <= (str_len - substr_len); i++) + if (strcmp(&str[i], substr, substr_len) == 0) + return true; + + return false; +} + + +/** + * Return true if 'str' is a valid file name + */ +static inline bool valid_filename(char const *str) +{ + if (!str) return false; + + /* must have at least one character */ + if (str[0] == 0) return false; + + /* must not contain '/' or '\' or ':' */ + if (string_contains(str, '/') || + string_contains(str, '\\') || + string_contains(str, ':')) + return false; + + return true; +} + +/** + * Return true if 'str' is a valid path + */ +static inline bool valid_path(char const *str) +{ + if (!str) return false; + + /* must start with '/' */ + if (str[0] != '/') + return false; + + /* must not contain '\' or ':' */ + if (string_contains(str, '\\') || + string_contains(str, ':')) + return false; + + /* must not contain "/../" */ + if (string_contains(str, "/../")) return false; + + return true; +} + +/** + * Return true if 'str' is "/" + */ +static inline bool is_root(const char *str) +{ + return (Genode::strcmp(str, "/") == 0); +} + +#endif /* _UTIL_H_ */