648 lines
17 KiB
C++
648 lines
17 KiB
C++
/*
|
|
* \brief Adapter from Genode 'File_system' session to Noux file system
|
|
* \author Norman Feske
|
|
* \date 2011-02-17
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2012-2013 Genode Labs GmbH
|
|
*
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
* under the terms of the GNU General Public License version 2.
|
|
*/
|
|
|
|
#ifndef _NOUX__FS_FILE_SYSTEM_H_
|
|
#define _NOUX__FS_FILE_SYSTEM_H_
|
|
|
|
/* Genode includes */
|
|
#include <base/allocator_avl.h>
|
|
#include <base/env.h>
|
|
|
|
/* Noux includes */
|
|
#include <noux_session/sysio.h>
|
|
#include <file_system_session/connection.h>
|
|
|
|
namespace Noux {
|
|
|
|
class Fs_file_system : public File_system
|
|
{
|
|
enum { verbose = false };
|
|
|
|
private:
|
|
|
|
Lock _lock;
|
|
|
|
Allocator_avl _fs_packet_alloc;
|
|
|
|
struct Label
|
|
{
|
|
enum { LABEL_MAX_LEN = 64 };
|
|
char string[LABEL_MAX_LEN];
|
|
|
|
Label(Xml_node config)
|
|
{
|
|
string[0] = 0;
|
|
try { config.attribute("label").value(string, sizeof(string)); }
|
|
catch (...) { }
|
|
}
|
|
} _label;
|
|
|
|
::File_system::Connection _fs;
|
|
|
|
class Fs_vfs_handle : public Vfs_handle
|
|
{
|
|
private:
|
|
|
|
::File_system::File_handle const _handle;
|
|
|
|
public:
|
|
|
|
Fs_vfs_handle(File_system *fs, int status_flags,
|
|
::File_system::File_handle handle)
|
|
: Vfs_handle(fs, fs, status_flags), _handle(handle)
|
|
{ }
|
|
|
|
~Fs_vfs_handle()
|
|
{
|
|
Fs_file_system *fs = static_cast<Fs_file_system *>(ds());
|
|
fs->_fs.close(_handle);
|
|
}
|
|
|
|
::File_system::File_handle file_handle() const { return _handle; }
|
|
};
|
|
|
|
/**
|
|
* Helper for managing the lifetime of temporary open node handles
|
|
*/
|
|
struct Fs_handle_guard
|
|
{
|
|
::File_system::Session &_fs;
|
|
::File_system::Node_handle _handle;
|
|
|
|
Fs_handle_guard(::File_system::Session &fs,
|
|
::File_system::Node_handle handle)
|
|
: _fs(fs), _handle(handle) { }
|
|
|
|
~Fs_handle_guard() { _fs.close(_handle); }
|
|
};
|
|
|
|
size_t _read(::File_system::Node_handle node_handle, void *buf,
|
|
size_t count, size_t seek_offset)
|
|
{
|
|
::File_system::Session::Tx::Source &source = *_fs.tx();
|
|
|
|
size_t const max_packet_size = source.bulk_buffer_size() / 2;
|
|
count = min(max_packet_size, count);
|
|
|
|
::File_system::Packet_descriptor
|
|
packet(source.alloc_packet(count),
|
|
0,
|
|
node_handle,
|
|
::File_system::Packet_descriptor::READ,
|
|
count,
|
|
seek_offset);
|
|
|
|
/* pass packet to server side */
|
|
source.submit_packet(packet);
|
|
source.get_acked_packet();
|
|
|
|
memcpy(buf, source.packet_content(packet), count);
|
|
|
|
/*
|
|
* XXX check if acked packet belongs to request,
|
|
* needed for thread safety
|
|
*/
|
|
|
|
source.release_packet(packet);
|
|
|
|
return count;
|
|
}
|
|
|
|
size_t _write(::File_system::Node_handle node_handle,
|
|
const char *buf, size_t count, size_t seek_offset)
|
|
{
|
|
::File_system::Session::Tx::Source &source = *_fs.tx();
|
|
|
|
size_t const max_packet_size = source.bulk_buffer_size() / 2;
|
|
count = min(max_packet_size, count);
|
|
|
|
::File_system::Packet_descriptor
|
|
packet(source.alloc_packet(count),
|
|
0,
|
|
node_handle,
|
|
::File_system::Packet_descriptor::WRITE,
|
|
count,
|
|
seek_offset);
|
|
|
|
memcpy(source.packet_content(packet), buf, count);
|
|
|
|
/* pass packet to server side */
|
|
source.submit_packet(packet);
|
|
source.get_acked_packet();
|
|
|
|
/*
|
|
* XXX check if acked packet belongs to request,
|
|
* needed for thread safety
|
|
*/
|
|
|
|
source.release_packet(packet);
|
|
|
|
return count;
|
|
}
|
|
|
|
public:
|
|
|
|
/*
|
|
* XXX read label from config
|
|
*/
|
|
Fs_file_system(Xml_node config)
|
|
:
|
|
_fs_packet_alloc(env()->heap()),
|
|
_label(config),
|
|
_fs(_fs_packet_alloc, 128*1024, _label.string)
|
|
{ }
|
|
|
|
|
|
/*********************************
|
|
** Directory-service interface **
|
|
*********************************/
|
|
|
|
Dataspace_capability dataspace(char const *path)
|
|
{
|
|
Lock::Guard guard(_lock);
|
|
|
|
Absolute_path dir_path(path);
|
|
dir_path.strip_last_element();
|
|
|
|
Absolute_path file_name(path);
|
|
file_name.keep_only_last_element();
|
|
|
|
Ram_dataspace_capability ds_cap;
|
|
char *local_addr = 0;
|
|
|
|
try {
|
|
::File_system::Dir_handle dir = _fs.dir(dir_path.base(),
|
|
false);
|
|
Fs_handle_guard dir_guard(_fs, dir);
|
|
|
|
::File_system::File_handle file =
|
|
_fs.file(dir, file_name.base() + 1,
|
|
::File_system::READ_ONLY, false);
|
|
Fs_handle_guard file_guard(_fs, file);
|
|
|
|
::File_system::Status status = _fs.status(file);
|
|
|
|
Ram_dataspace_capability ds_cap =
|
|
env()->ram_session()->alloc(status.size);
|
|
|
|
local_addr = env()->rm_session()->attach(ds_cap);
|
|
|
|
::File_system::Session::Tx::Source &source = *_fs.tx();
|
|
size_t const max_packet_size = source.bulk_buffer_size() / 2;
|
|
|
|
for (size_t seek_offset = 0; seek_offset < status.size;
|
|
seek_offset += max_packet_size) {
|
|
|
|
size_t const count = min(max_packet_size, status.size - seek_offset);
|
|
|
|
::File_system::Packet_descriptor
|
|
packet(source.alloc_packet(count),
|
|
0,
|
|
file,
|
|
::File_system::Packet_descriptor::READ,
|
|
count,
|
|
seek_offset);
|
|
|
|
/* pass packet to server side */
|
|
source.submit_packet(packet);
|
|
source.get_acked_packet();
|
|
|
|
memcpy(local_addr + seek_offset, source.packet_content(packet), count);
|
|
|
|
/*
|
|
* XXX check if acked packet belongs to request,
|
|
* needed for thread safety
|
|
*/
|
|
|
|
source.release_packet(packet);
|
|
}
|
|
|
|
env()->rm_session()->detach(local_addr);
|
|
|
|
return ds_cap;
|
|
} catch(...) {
|
|
env()->rm_session()->detach(local_addr);
|
|
env()->ram_session()->free(ds_cap);
|
|
return Dataspace_capability();
|
|
}
|
|
}
|
|
|
|
void release(char const *path, Dataspace_capability ds_cap)
|
|
{
|
|
env()->ram_session()->free(static_cap_cast<Ram_dataspace>(ds_cap));
|
|
}
|
|
|
|
bool stat(Sysio *sysio, char const *path)
|
|
{
|
|
::File_system::Status status;
|
|
|
|
try {
|
|
::File_system::Node_handle node = _fs.node(path);
|
|
Fs_handle_guard node_guard(_fs, node);
|
|
status = _fs.status(node);
|
|
} catch (...) {
|
|
if (verbose)
|
|
PDBG("stat failed for path '%s'", path);
|
|
return false;
|
|
}
|
|
|
|
memset(&sysio->stat_out.st, 0, sizeof(sysio->stat_out.st));
|
|
|
|
sysio->stat_out.st.size = status.size;
|
|
|
|
sysio->stat_out.st.mode = Sysio::STAT_MODE_FILE | 0777;
|
|
|
|
if (status.is_symlink())
|
|
sysio->stat_out.st.mode = Sysio::STAT_MODE_SYMLINK | 0777;
|
|
|
|
if (status.is_directory())
|
|
sysio->stat_out.st.mode = Sysio::STAT_MODE_DIRECTORY | 0777;
|
|
|
|
sysio->stat_out.st.uid = 0;
|
|
sysio->stat_out.st.gid = 0;
|
|
return true;
|
|
}
|
|
|
|
bool dirent(Sysio *sysio, char const *path, off_t index)
|
|
{
|
|
Lock::Guard guard(_lock);
|
|
|
|
::File_system::Session::Tx::Source &source = *_fs.tx();
|
|
|
|
if (strcmp(path, "") == 0)
|
|
path = "/";
|
|
|
|
::File_system::Dir_handle dir_handle = _fs.dir(path, false);
|
|
Fs_handle_guard dir_guard(_fs, dir_handle);
|
|
|
|
enum { DIRENT_SIZE = sizeof(::File_system::Directory_entry) };
|
|
|
|
::File_system::Packet_descriptor
|
|
packet(source.alloc_packet(DIRENT_SIZE),
|
|
0,
|
|
dir_handle,
|
|
::File_system::Packet_descriptor::READ,
|
|
DIRENT_SIZE,
|
|
index*DIRENT_SIZE);
|
|
|
|
/* pass packet to server side */
|
|
source.submit_packet(packet);
|
|
source.get_acked_packet();
|
|
|
|
/*
|
|
* XXX check if acked packet belongs to request,
|
|
* needed for thread safety
|
|
*/
|
|
|
|
typedef ::File_system::Directory_entry Directory_entry;
|
|
|
|
/* copy-out payload into destination buffer */
|
|
Directory_entry const *entry =
|
|
(Directory_entry *)source.packet_content(packet);
|
|
|
|
/*
|
|
* The default value has no meaning because the switch below
|
|
* assigns a value in each possible branch. But it is needed to
|
|
* keep the compiler happy.
|
|
*/
|
|
Sysio::Dirent_type type = Sysio::DIRENT_TYPE_END;
|
|
|
|
switch (entry->type) {
|
|
case Directory_entry::TYPE_DIRECTORY: type = Sysio::DIRENT_TYPE_DIRECTORY; break;
|
|
case Directory_entry::TYPE_FILE: type = Sysio::DIRENT_TYPE_FILE; break;
|
|
case Directory_entry::TYPE_SYMLINK: type = Sysio::DIRENT_TYPE_SYMLINK; break;
|
|
}
|
|
|
|
sysio->dirent_out.entry.type = type;
|
|
sysio->dirent_out.entry.fileno = index + 1;
|
|
|
|
strncpy(sysio->dirent_out.entry.name, entry->name,
|
|
sizeof(sysio->dirent_out.entry.name));
|
|
|
|
source.release_packet(packet);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool unlink(Sysio *sysio, char const *path)
|
|
{
|
|
Lock::Guard guard(_lock);
|
|
|
|
Absolute_path dir_path(path);
|
|
dir_path.strip_last_element();
|
|
|
|
Absolute_path file_name(path);
|
|
file_name.keep_only_last_element();
|
|
|
|
try {
|
|
::File_system::Dir_handle dir = _fs.dir(dir_path.base(), false);
|
|
Fs_handle_guard dir_guard(_fs, dir);
|
|
|
|
_fs.unlink(dir, file_name.base() + 1);
|
|
|
|
} catch (...) {
|
|
sysio->error.unlink = Sysio::UNLINK_ERR_NO_ENTRY;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool readlink(Sysio *sysio, char const *path)
|
|
{
|
|
/*
|
|
* Canonicalize path (i.e., path must start with '/')
|
|
*/
|
|
Absolute_path abs_path(path);
|
|
abs_path.strip_last_element();
|
|
|
|
Absolute_path symlink_name(path);
|
|
symlink_name.keep_only_last_element();
|
|
|
|
Sysio::Readlink_error error = Sysio::READLINK_ERR_NO_ENTRY;
|
|
try {
|
|
::File_system::Dir_handle dir_handle = _fs.dir(abs_path.base(), false);
|
|
Fs_handle_guard from_dir_guard(_fs, dir_handle);
|
|
|
|
::File_system::Symlink_handle symlink_handle =
|
|
_fs.symlink(dir_handle, symlink_name.base() + 1, false);
|
|
Fs_handle_guard symlink_guard(_fs, symlink_handle);
|
|
|
|
sysio->readlink_out.count = _read(symlink_handle,
|
|
sysio->readlink_out.chunk,
|
|
min(sysio->readlink_in.bufsiz,
|
|
sizeof(sysio->readlink_out.chunk)),
|
|
0);
|
|
|
|
return true;
|
|
} catch (...) { }
|
|
|
|
sysio->error.readlink = error;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool rename(Sysio *sysio, char const *from_path, char const *to_path)
|
|
{
|
|
Absolute_path from_dir_path(from_path);
|
|
from_dir_path.strip_last_element();
|
|
|
|
Absolute_path from_file_name(from_path);
|
|
from_file_name.keep_only_last_element();
|
|
|
|
Absolute_path to_dir_path(to_path);
|
|
to_dir_path.strip_last_element();
|
|
|
|
Absolute_path to_file_name(to_path);
|
|
to_file_name.keep_only_last_element();
|
|
|
|
try {
|
|
::File_system::Dir_handle from_dir = _fs.dir(from_dir_path.base(), false);
|
|
Fs_handle_guard from_dir_guard(_fs, from_dir);
|
|
|
|
::File_system::Dir_handle to_dir = _fs.dir(to_dir_path.base(), false);
|
|
Fs_handle_guard to_dir_guard(_fs, to_dir);
|
|
|
|
_fs.move(from_dir, from_file_name.base() + 1,
|
|
to_dir, to_file_name.base() + 1);
|
|
|
|
} catch (...) {
|
|
sysio->error.unlink = Sysio::UNLINK_ERR_NO_ENTRY;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool mkdir(Sysio *sysio, char const *path)
|
|
{
|
|
/*
|
|
* Canonicalize path (i.e., path must start with '/')
|
|
*/
|
|
Absolute_path abs_path(path);
|
|
|
|
Sysio::Mkdir_error error = Sysio::MKDIR_ERR_NO_PERM;
|
|
try {
|
|
_fs.dir(abs_path.base(), true);
|
|
return true;
|
|
}
|
|
catch (::File_system::Permission_denied) { error = Sysio::MKDIR_ERR_NO_PERM; }
|
|
catch (::File_system::Node_already_exists) { error = Sysio::MKDIR_ERR_EXISTS; }
|
|
catch (::File_system::Lookup_failed) { error = Sysio::MKDIR_ERR_NO_ENTRY; }
|
|
catch (::File_system::Name_too_long) { error = Sysio::MKDIR_ERR_NAME_TOO_LONG; }
|
|
catch (::File_system::No_space) { error = Sysio::MKDIR_ERR_NO_SPACE; }
|
|
|
|
sysio->error.mkdir = error;
|
|
return false;
|
|
}
|
|
|
|
bool symlink(Sysio *sysio, char const *path)
|
|
{
|
|
/*
|
|
* Canonicalize path (i.e., path must start with '/')
|
|
*/
|
|
Absolute_path abs_path(path);
|
|
abs_path.strip_last_element();
|
|
|
|
Absolute_path symlink_name(path);
|
|
symlink_name.keep_only_last_element();
|
|
|
|
Sysio::Symlink_error error = Sysio::SYMLINK_ERR_NO_ENTRY;
|
|
try {
|
|
::File_system::Dir_handle dir_handle = _fs.dir(abs_path.base(), false);
|
|
Fs_handle_guard from_dir_guard(_fs, dir_handle);
|
|
|
|
::File_system::Symlink_handle symlink_handle =
|
|
_fs.symlink(dir_handle, symlink_name.base() + 1, true);
|
|
Fs_handle_guard symlink_guard(_fs, symlink_handle);
|
|
|
|
_write(symlink_handle, sysio->symlink_in.oldpath,
|
|
strlen(sysio->symlink_in.oldpath) + 1, 0);
|
|
return true;
|
|
}
|
|
catch (::File_system::Invalid_handle) { error = Sysio::SYMLINK_ERR_NO_ENTRY; }
|
|
catch (::File_system::Node_already_exists) { error = Sysio::SYMLINK_ERR_EXISTS; }
|
|
catch (::File_system::Invalid_name) { error = Sysio::SYMLINK_ERR_NAME_TOO_LONG; }
|
|
catch (::File_system::Lookup_failed) { error = Sysio::SYMLINK_ERR_NO_ENTRY; }
|
|
|
|
sysio->error.symlink = error;
|
|
|
|
return false;
|
|
}
|
|
|
|
size_t num_dirent(char const *path)
|
|
{
|
|
if (strcmp(path, "") == 0)
|
|
path = "/";
|
|
|
|
/*
|
|
* XXX handle exceptions
|
|
*/
|
|
::File_system::Node_handle node = _fs.node(path);
|
|
Fs_handle_guard node_guard(_fs, node);
|
|
|
|
::File_system::Status status = _fs.status(node);
|
|
|
|
return status.size / sizeof(::File_system::Directory_entry);
|
|
}
|
|
|
|
bool is_directory(char const *path)
|
|
{
|
|
try {
|
|
::File_system::Node_handle node = _fs.node(path);
|
|
Fs_handle_guard node_guard(_fs, node);
|
|
|
|
::File_system::Status status = _fs.status(node);
|
|
|
|
return status.is_directory();
|
|
}
|
|
catch (...) { return false; }
|
|
}
|
|
|
|
char const *leaf_path(char const *path)
|
|
{
|
|
/* check if node at path exists within file system */
|
|
try {
|
|
::File_system::Node_handle node = _fs.node(path);
|
|
_fs.close(node);
|
|
}
|
|
catch (...) {
|
|
return 0; }
|
|
|
|
return path;
|
|
}
|
|
|
|
Vfs_handle *open(Sysio *sysio, char const *path)
|
|
{
|
|
Lock::Guard guard(_lock);
|
|
|
|
Absolute_path dir_path(path);
|
|
dir_path.strip_last_element();
|
|
|
|
Absolute_path file_name(path);
|
|
file_name.keep_only_last_element();
|
|
|
|
::File_system::Mode mode;
|
|
|
|
switch (sysio->open_in.mode & Sysio::OPEN_MODE_ACCMODE) {
|
|
default: mode = ::File_system::STAT_ONLY; break;
|
|
case Sysio::OPEN_MODE_RDONLY: mode = ::File_system::READ_ONLY; break;
|
|
case Sysio::OPEN_MODE_WRONLY: mode = ::File_system::WRITE_ONLY; break;
|
|
case Sysio::OPEN_MODE_RDWR: mode = ::File_system::READ_WRITE; break;
|
|
}
|
|
|
|
bool const create = sysio->open_in.mode & Sysio::OPEN_MODE_CREATE;
|
|
|
|
if (create)
|
|
if (verbose)
|
|
PDBG("creation of file %s requested", file_name.base() + 1);
|
|
|
|
Sysio::Open_error error = Sysio::OPEN_ERR_UNACCESSIBLE;
|
|
|
|
try {
|
|
::File_system::Dir_handle dir = _fs.dir(dir_path.base(), false);
|
|
Fs_handle_guard dir_guard(_fs, dir);
|
|
|
|
::File_system::File_handle file = _fs.file(dir, file_name.base() + 1,
|
|
mode, create);
|
|
|
|
return new (env()->heap()) Fs_vfs_handle(this, sysio->open_in.mode, file);
|
|
}
|
|
catch (::File_system::Permission_denied) {
|
|
error = Sysio::OPEN_ERR_NO_PERM; }
|
|
catch (::File_system::Invalid_handle) {
|
|
error = Sysio::OPEN_ERR_NO_PERM; }
|
|
catch (::File_system::Lookup_failed) {
|
|
error = Sysio::OPEN_ERR_UNACCESSIBLE; }
|
|
catch (::File_system::Node_already_exists) {
|
|
error = Sysio::OPEN_ERR_EXISTS; }
|
|
|
|
sysio->error.open = error;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/***************************
|
|
** File_system interface **
|
|
***************************/
|
|
|
|
static char const *name() { return "fs"; }
|
|
|
|
void sync()
|
|
{
|
|
_fs.sync();
|
|
}
|
|
|
|
|
|
/********************************
|
|
** File I/O service interface **
|
|
********************************/
|
|
|
|
bool write(Sysio *sysio, Vfs_handle *vfs_handle)
|
|
{
|
|
Fs_vfs_handle const *handle = static_cast<Fs_vfs_handle *>(vfs_handle);
|
|
|
|
size_t const count = min(sizeof(sysio->write_in.chunk),
|
|
sysio->write_in.count);
|
|
|
|
sysio->write_out.count = _write(handle->file_handle(),
|
|
sysio->write_in.chunk, count,
|
|
handle->seek());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool read(Sysio *sysio, Vfs_handle *vfs_handle)
|
|
{
|
|
Fs_vfs_handle const *handle = static_cast<Fs_vfs_handle *>(vfs_handle);
|
|
|
|
::File_system::Status status = _fs.status(handle->file_handle());
|
|
size_t const file_size = status.size;
|
|
|
|
size_t const file_bytes_left = file_size >= handle->seek()
|
|
? file_size - handle->seek() : 0;
|
|
|
|
size_t const count = min(file_bytes_left,
|
|
min(sizeof(sysio->read_out.chunk),
|
|
sysio->read_in.count));
|
|
|
|
sysio->read_out.count = _read(handle->file_handle(),
|
|
sysio->read_out.chunk,
|
|
count, handle->seek());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ftruncate(Sysio *sysio, Vfs_handle *vfs_handle)
|
|
{
|
|
Fs_vfs_handle const *handle = static_cast<Fs_vfs_handle *>(vfs_handle);
|
|
|
|
Sysio::Ftruncate_error error;
|
|
|
|
try {
|
|
_fs.truncate(handle->file_handle(), sysio->ftruncate_in.length);
|
|
return true;
|
|
} catch (::File_system::Invalid_handle) {
|
|
/* should not happen */
|
|
error = Sysio::FTRUNCATE_ERR_NO_PERM;
|
|
} catch (::File_system::Permission_denied) {
|
|
error = Sysio::FTRUNCATE_ERR_NO_PERM;
|
|
}
|
|
|
|
sysio->error.ftruncate = error;
|
|
return false;
|
|
}
|
|
|
|
};
|
|
}
|
|
|
|
#endif /* _NOUX__FS_FILE_SYSTEM_H_ */
|