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.
This commit is contained in:
Josef Söntgen 2013-12-09 16:30:07 +01:00 committed by Christian Helmuth
parent 829961b0ad
commit a282617407
12 changed files with 1665 additions and 0 deletions

View File

@ -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:
! <start name="ext2_fuse_fs">
! <resource name="RAM" quantum="8M"/>
! <provides> <service name="File_system"/> </provides>
! <config>
! <policy label="noux -> fuse" root="/" writeable="no" />
! </config>
! </start>

View File

@ -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 <os/path.h>
#include <util/string.h>
/* libc includes */
#include <sys/dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
/* local includes */
#include <file.h>
#include <mode_util.h>
#include <node.h>
#include <util.h>
#include <symlink.h>
#include <fuse.h>
#include <fuse_private.h>
namespace File_system {
class Directory;
}
class File_system::Directory : public Node
{
private:
typedef Genode::Path<MAX_PATH_LEN> 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_ */

View File

@ -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

View File

@ -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

View File

@ -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 <mode_util.h>
#include <node.h>
#include <fuse.h>
#include <fuse_private.h>
namespace File_system {
class File;
}
class File_system::File : public Node
{
private:
Node *_parent;
typedef Genode::Path<MAX_PATH_LEN> 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_ */

View File

@ -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 <file_system_session/rpc_object.h>
#include <os/attached_rom_dataspace.h>
#include <os/config.h>
#include <os/server.h>
#include <os/session_policy.h>
#include <root/component.h>
#include <util/xml_node.h>
/* libc includes */
#include <errno.h>
/* local includes */
#include <directory.h>
#include <node_handle_registry.h>
#include <util.h>
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<Session_component> _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<Ram_dataspace>(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<File *>(node);
if (file)
return file->status();
Directory *dir = dynamic_cast<Directory *>(node);
if (dir)
return dir->status();
Symlink *symlink = dynamic_cast<Symlink *>(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<Session_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<Session_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); }

View File

@ -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 <file_system_session/file_system_session.h>
/* libc includes */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
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_ */

View File

@ -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 <util/list.h>
#include <base/lock.h>
#include <base/signal.h>
#include <os/path.h>
namespace File_system {
typedef Genode::Path<MAX_PATH_LEN> Absolute_path;
class Listener : public List<Listener>::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<Node>::Element
{
protected:
unsigned long _inode;
Absolute_path _name;
private:
Lock _lock;
List<Listener> _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_ */

View File

@ -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<typename T> struct Node_type;
template<> struct Node_type<Node_handle> { typedef Node Type; };
template<> struct Node_type<Dir_handle> { typedef Directory Type; };
template<> struct Node_type<File_handle> { typedef File Type; };
template<> struct Node_type<Symlink_handle> { typedef Symlink Type; };
/**
* Type trait for determining the handle type for a given node type
*/
template<typename T> struct Handle_type;
template<> struct Handle_type<Node> { typedef Node_handle Type; };
template<> struct Handle_type<Directory> { typedef Dir_handle Type; };
template<> struct Handle_type<File> { typedef File_handle Type; };
template<> struct Handle_type<Symlink> { 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 NODE_TYPE>
typename Handle_type<NODE_TYPE>::Type alloc(NODE_TYPE *node)
{
typedef typename Handle_type<NODE_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<Node *>(_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 HANDLE_TYPE>
typename Node_type<HANDLE_TYPE>::Type *lookup_and_lock(HANDLE_TYPE handle)
{
Lock::Guard guard(_lock);
if (!_in_range(handle.value))
throw Invalid_handle();
typedef typename Node_type<HANDLE_TYPE>::Type Node;
Node *node = dynamic_cast<Node *>(_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<Node *>(_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_ */

View File

@ -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

View File

@ -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 <node.h>
namespace File_system {
class Symlink;
}
class File_system::Symlink : public Node
{
private:
Node *_parent;
typedef Genode::Path<MAX_PATH_LEN> 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_ */

View File

@ -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 <util/string.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 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_ */