File-system interface, ram_fs, libc-fs

This patch introduces the file-system-session interface, provides an
implementation of this interface in the form of an in-memory file
system, and enables the libc to use the new file-system facility.

The new interface resides in 'os/include/file_system_session/'. It
uses synchronous RPC calls for functions referring to directory
and meta-data handling. For transferring payload from/to files, the
packet-stream interface is used. I envision that the asynchronous design
of the packet-stream interface fits well will the block-session
interface. Compared to Unix-like file-system APIs, Genode's file-system
session interface is much simpler. In particular, it does not support
per-file permissions. On Genode, we facilitate binding policy (such as
write-permission) is sessions rather than individual file objects.

As a reference implementation of the new interface, there is the
new 'ram_fs' service at 'os/src/server/ram_fs'. It stores sparse
files in memory. At the startup, 'ram_fs' is able to populate the
file-system content with directories and ROM modules as specified
in its configuration.

To enable libc-using programs to access the new file-system interface,
there is the new libc plugin at 'libports/src/lib/libc-fs'. Using this
plugin, files stored on a native Genode file system can be accessed
using the traditional POSIX file API.

To see how the three parts described above fit together, the test
case at 'libports/run/libc_fs' can be taken as reference. It reuses
the original 'libc_ffat' test to exercise several file operations
on a RAM file-system using the libc API.

:Known limitations:

The current state should be regarded as work in progress. In particular
the error handling is not complete yet. Not all of the session functions
return the proper exceptions in the event of an error. I plan to
successively refine the interface while advancing the file-system
implementations. Also the support for truncating files and symlink
handling are not yet implemented.

Furthermore, there is much room for optimization, in particular for the
handling of directory entries. Currently, we communicate only one dir
entry at a time, which is bad when traversing large trees. However, I
decided to focus on functionality first and defer optimizations (such as
batching dir entries) to a later stage.

The current implementation does not handle file modification times at
all, which may be a severe limitation for tools that depend on this
information such as GNU make. Support for time will be added after we
have revisited Genode's timer-session interface (issue #1).

Fixes #54
Fixes #171
This commit is contained in:
Norman Feske 2012-04-11 15:46:33 +02:00
parent f0fcf084d7
commit ae1d0c04ae
22 changed files with 3238 additions and 0 deletions

View File

@ -0,0 +1,6 @@
SRC_CC = plugin.cc
LIBS += libc
vpath plugin.cc $(REP_DIR)/src/lib/libc_fs
SHARED_LIB = yes

64
libports/run/libc_fs.run Normal file
View File

@ -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 {
<config>
<parent-provides>
<service name="ROM"/>
<service name="RAM"/>
<service name="CAP"/>
<service name="PD"/>
<service name="RM"/>
<service name="CPU"/>
<service name="LOG"/>
<service name="SIGNAL"/>
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<start name="ram_fs">
<resource name="RAM" quantum="4M"/>
<provides> <service name="File_system"/> </provides>
<config> <policy label="" root="/" writeable="yes" /> </config>
</start>
<start name="test-libc_fs">
<resource name="RAM" quantum="2M"/>
</start>
</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 :

View File

@ -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 <base/env.h>
#include <base/printf.h>
#include <file_system_session/connection.h>
/* libc includes */
#include <errno.h>
#include <dirent.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* libc plugin interface */
#include <libc-plugin/plugin.h>
#include <libc-plugin/fd_alloc.h>
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<Plugin_context *>(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<Plugin_context *>(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<File_system::Packet_ref *>(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<Plugin_context *>(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<File_system::Packet_ref *>(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;
}

View File

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

View File

@ -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 <base/capability.h>
#include <file_system_session/file_system_session.h>
namespace File_system { typedef Genode::Capability<Session> Session_capability; }
#endif /* _INCLUDE__FILE_SYSTEM_SESSION__CAPABILITY_H_ */

View File

@ -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 <base/rpc_client.h>
#include <file_system_session/capability.h>
#include <packet_stream_tx/client.h>
namespace File_system {
class Session_client : public Rpc_client<Session>
{
private:
Packet_stream_tx::Client<Tx> _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>(session),
_tx(call<Rpc_tx_cap>(), &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<Rpc_file>(dir, name, mode, create);
}
Symlink_handle symlink(Dir_handle dir, Name const &name, bool create)
{
return call<Rpc_symlink>(dir, name, create);
}
Dir_handle dir(Path const &path, bool create)
{
return call<Rpc_dir>(path, create);
}
Node_handle node(Path const &path)
{
return call<Rpc_node>(path);
}
void close(Node_handle node)
{
call<Rpc_close>(node);
}
Status status(Node_handle node)
{
return call<Rpc_status>(node);
}
void control(Node_handle node, Control control)
{
call<Rpc_control>(node, control);
}
void unlink(Dir_handle dir, Name const &name)
{
call<Rpc_unlink>(dir, name);
}
void truncate(File_handle file, file_size_t size)
{
call<Rpc_truncate>(file, size);
}
void move(Dir_handle from_dir, Name const &from_name,
Dir_handle to_dir, Name const &to_name)
{
call<Rpc_move>(from_dir, from_name, to_dir, to_name);
}
};
}
#endif /* _INCLUDE__FILE_SYSTEM_SESSION__CLIENT_H_ */

View File

@ -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 <file_system_session/client.h>
#include <base/connection.h>
#include <base/allocator.h>
namespace File_system {
struct Connection : Genode::Connection<Session>, 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>(
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_ */

View File

@ -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 <base/exception.h>
#include <os/packet_stream.h>
#include <packet_stream_tx/packet_stream_tx.h>
#include <session/session.h>
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<MAX_NAME_LEN> Name;
typedef Rpc_in_buffer<MAX_PATH_LEN> 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<File_system::Packet_descriptor,
TX_QUEUE_SIZE, TX_QUEUE_SIZE,
char> Tx_policy;
typedef Packet_stream_tx::Channel<Tx_policy> 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>, _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_tx_cap,
Meta::Type_tuple<Rpc_file,
Meta::Type_tuple<Rpc_symlink,
Meta::Type_tuple<Rpc_dir,
Meta::Type_tuple<Rpc_node,
Meta::Type_tuple<Rpc_close,
Meta::Type_tuple<Rpc_status,
Meta::Type_tuple<Rpc_control,
Meta::Type_tuple<Rpc_unlink,
Meta::Type_tuple<Rpc_truncate,
Meta::Type_tuple<Rpc_move,
Meta::Empty>
> > > > > > > > > > Rpc_functions;
};
}
#endif /* _INCLUDE__FILE_SYSTEM_SESSION__FILE_SYSTEM_SESSION_H_ */

View File

@ -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 <file_system_session/file_system_session.h>
#include <packet_stream_tx/rpc_object.h>
#include <base/rpc_server.h>
namespace File_system {
class Session_rpc_object : public Genode::Rpc_object<Session, Session_rpc_object>
{
protected:
Packet_stream_tx::Rpc_object<Tx> _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> _tx_cap() { return _tx.cap(); }
Tx::Sink *tx_sink() { return _tx.sink(); }
};
}
#endif /* _INCLUDE__FILE_SYSTEM_SESSION__SERVER_H_ */

77
os/run/ram_fs_chunk.run Normal file
View File

@ -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 {
<config>
<parent-provides>
<service name="LOG"/>
</parent-provides>
<default-route>
<any-service> <parent/> </any-service>
</default-route>
<start name="test-ram_fs_chunk">
<resource name="RAM" quantum="1M"/>
</start>
</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
}

View File

@ -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.
! <config>
! <!-- preload RAM file system with some ROM images -->
! <content>
! <dir name="tmp">
! <rom name="init" as="blubb" />
! </dir>
! <dir name="home">
! <dir name="user">
! <!-- just a place holder -->
! <rom name="timer" />
! </dir>
! </dir>
! </content>
! <!-- constrain sessions according to their labels -->
! <policy label="noux -> root" root="/" />
! <policy label="noux -> home" root="/home/user" writeable="yes" />
! <policy label="noux -> tmp" root="/tmp" writeable="yes" />
! </config>
The '<content>' sub node of the '<config>' node provides a way to pre-populate
the file system with directories and files. Note that '<dir>' 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 '<policy>'
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.

View File

@ -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 <util/noncopyable.h>
#include <base/allocator.h>
#include <util/string.h>
#include <file_system_session/file_system_session.h>
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 <unsigned CHUNK_SIZE>
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 <unsigned NUM_ENTRIES, typename ENTRY_TYPE>
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 <typename THIS, typename DATA, typename FUNC>
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_ */

View File

@ -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 <node.h>
#include <util.h>
#include <file.h>
#include <symlink.h>
namespace File_system {
class Directory : public Node
{
private:
List<Node> _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<Directory *>(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<Directory *>(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<File *>(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<Directory *>(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<File *>(node)) e->type = Directory_entry::TYPE_FILE;
if (dynamic_cast<Directory *>(node)) e->type = Directory_entry::TYPE_DIRECTORY;
if (dynamic_cast<Symlink *>(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_ */

View File

@ -0,0 +1,94 @@
/*
* \brief File node
* \author Norman Feske
* \date 2012-04-11
*/
#ifndef _FILE_H_
#define _FILE_H_
/* Genode includes */
#include <base/allocator.h>
/* local includes */
#include <node.h>
#include <chunk.h>
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_ */

View File

@ -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 <file_system_session/rpc_object.h>
#include <root/component.h>
#include <cap_session/connection.h>
#include <base/sleep.h>
#include <os/attached_rom_dataspace.h>
#include <os/config.h>
#include <os/session_policy.h>
#include <util/xml_node.h>
/* local includes */
#include <directory.h>
#include <node_handle_registry.h>
/*************************************
** Helpers for dispatching signals **
*************************************/
namespace Genode {
struct Signal_dispatcher_base : Signal_context
{
virtual void dispatch(int num) = 0;
};
template <typename T>
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<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:
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<Ram_dataspace>(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<File *>(node);
if (file) {
s.size = file->length();
s.mode = File_system::Status::MODE_FILE;
return s;
}
Directory *dir = dynamic_cast<Directory *>(node);
if (dir) {
s.size = dir->num_entries()*sizeof(Directory_entry);
s.mode = File_system::Status::MODE_DIRECTORY;
return s;
}
Symlink *symlink = dynamic_cast<Symlink *>(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<Session_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_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<char>(), 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<Signal_dispatcher_base *>(s.context())->dispatch(s.num());
}
return 0;
}

View File

@ -0,0 +1,74 @@
/*
* \brief File-system node
* \author Norman Feske
* \date 2012-04-11
*/
#ifndef _NODE_H_
#define _NODE_H_
/* Genode includes */
#include <util/list.h>
#include <base/lock.h>
namespace File_system {
class Node : public List<Node>::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_ */

View File

@ -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<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];
/**
* 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))
_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 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))
throw Invalid_handle();
return _nodes[h1.value] == _nodes[h2.value];
}
};
}
#endif /* _NODE_HANDLE_REGISTRY_H_ */

View File

@ -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 <node.h>
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_ */

View File

@ -0,0 +1,4 @@
TARGET = ram_fs
SRC_CC = main.cc
LIBS = cxx env server signal
INC_DIR += $(PRG_DIR)

View File

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

View File

@ -0,0 +1,132 @@
/*
* \brief Unit test for RAM fs chunk data structure
* \author Norman Feske
* \date 2012-04-19
*/
/* Genode includes */
#include <base/env.h>
#include <base/printf.h>
/* local 'ram_fs' include */
#include <chunk.h>
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;
}

View File

@ -0,0 +1,4 @@
TARGET = test-ram_fs_chunk
SRC_CC = main.cc
INC_DIR += $(REP_DIR)/src/server/ram_fs
LIBS = env