1078 lines
27 KiB
C++
1078 lines
27 KiB
C++
/*
|
|
* \brief Internal nodes of VFS server
|
|
* \author Emery Hemingway
|
|
* \author Christian Helmuth
|
|
* \author Norman Feske
|
|
* \date 2016-03-29
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2016-2019 Genode Labs GmbH
|
|
*
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
* under the terms of the GNU Affero General Public License version 3.
|
|
*/
|
|
|
|
#ifndef _VFS__NODE_H_
|
|
#define _VFS__NODE_H_
|
|
|
|
/* Genode includes */
|
|
#include <vfs/file_system.h>
|
|
#include <os/path.h>
|
|
#include <base/id_space.h>
|
|
|
|
/* Local includes */
|
|
#include "assert.h"
|
|
|
|
namespace Vfs_server {
|
|
|
|
using namespace File_system;
|
|
using namespace Vfs;
|
|
|
|
typedef Vfs::File_io_service::Write_result Write_result;
|
|
typedef Vfs::File_io_service::Read_result Read_result;
|
|
typedef Vfs::File_io_service::Sync_result Sync_result;
|
|
|
|
typedef ::File_system::Session::Tx::Sink Packet_stream;
|
|
|
|
class Node;
|
|
class Io_node;
|
|
class Watch_node;
|
|
class Directory;
|
|
class File;
|
|
class Symlink;
|
|
|
|
typedef Genode::Id_space<Node> Node_space;
|
|
typedef Genode::Fifo<Node> Node_queue;
|
|
|
|
/* Vfs::MAX_PATH is shorter than File_system::MAX_PATH */
|
|
enum { MAX_PATH_LEN = Vfs::MAX_PATH_LEN };
|
|
|
|
typedef Genode::Path<MAX_PATH_LEN> Path;
|
|
|
|
typedef Genode::Allocator::Out_of_memory Out_of_memory;
|
|
|
|
struct Payload_ptr { char *ptr; };
|
|
|
|
/**
|
|
* 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 Io_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; };
|
|
template<> struct Node_type<Watch_handle> { typedef Watch_node Type; };
|
|
|
|
/**
|
|
* Type trait for determining the handle type for a given node type
|
|
*/
|
|
template<typename T> struct Handle_type;
|
|
template<> struct Handle_type<Io_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; };
|
|
template<> struct Handle_type<Watch> { typedef Watch_handle Type; };
|
|
|
|
/*
|
|
* Note that the file objects are created at the
|
|
* VFS in the local node constructors, this is to
|
|
* ensure that in the case of file creation, the
|
|
* Out_of_ram exception is thrown before the VFS is
|
|
* modified.
|
|
*/
|
|
}
|
|
|
|
|
|
class Vfs_server::Node : Node_space::Element, Node_queue::Element
|
|
{
|
|
private:
|
|
|
|
/*
|
|
* Noncopyable
|
|
*/
|
|
Node(Node const &);
|
|
Node &operator = (Node const &);
|
|
|
|
Path const _path;
|
|
|
|
protected:
|
|
|
|
/**
|
|
* Packet descriptor to be added to the acknowledgement queue
|
|
*
|
|
* The '_acked_packet' is reset by 'submit_job' and assigned
|
|
* to a valid descriptor by 'try_execute_job'. The validity of the
|
|
* packet descriptor is tracked by '_acked_packet_valid'.
|
|
*/
|
|
Packet_descriptor _acked_packet { };
|
|
|
|
bool _submit_accepted = false;
|
|
|
|
bool _acked_packet_valid = false;
|
|
|
|
bool _packet_in_progress = false;
|
|
|
|
enum class Read_ready_state { DONT_CARE, REQUESTED, READY };
|
|
|
|
Read_ready_state _read_ready_state { Read_ready_state::DONT_CARE };
|
|
|
|
public:
|
|
|
|
friend Node_queue;
|
|
using Node_queue::Element::enqueued;
|
|
|
|
Node(Node_space &space, char const *node_path)
|
|
:
|
|
Node_space::Element(*this, space), _path(node_path)
|
|
{ }
|
|
|
|
virtual ~Node() { }
|
|
|
|
using Node_space::Element::id;
|
|
|
|
char const *path() const { return _path.base(); }
|
|
|
|
enum class Submit_result { DENIED, ACCEPTED, STALLED };
|
|
|
|
bool job_in_progress() const { return _packet_in_progress; }
|
|
|
|
/**
|
|
* Return true if node is ready to accept 'submit_job'
|
|
*
|
|
* Each node can deal with only one job at a time, except for file
|
|
* nodes, which accept a job in addition to an already submitted
|
|
* READ_READY request (which leaves '_packet_in_progress' untouched).
|
|
*/
|
|
bool job_acceptable() const { return !job_in_progress(); }
|
|
|
|
/**
|
|
* Submit job to node
|
|
*
|
|
* When called, the node is expected to be idle (neither queued in
|
|
* the active-nodes queue nor the finished-nodes queue).
|
|
*/
|
|
virtual Submit_result submit_job(Packet_descriptor, Payload_ptr)
|
|
{
|
|
return Submit_result::DENIED;
|
|
}
|
|
|
|
/**
|
|
* Execute submitted job
|
|
*
|
|
* This function must not be called if 'job_in_progress()' is false.
|
|
*/
|
|
virtual void execute_job()
|
|
{
|
|
Genode::warning("Node::execute_job unexpectedly called");
|
|
}
|
|
|
|
/**
|
|
* Return true if the node has at least one acknowledgement ready
|
|
*/
|
|
bool acknowledgement_pending() const
|
|
{
|
|
return (_read_ready_state == Read_ready_state::READY)
|
|
|| _acked_packet_valid;
|
|
}
|
|
|
|
bool active() const
|
|
{
|
|
return acknowledgement_pending()
|
|
|| job_in_progress()
|
|
|| (_read_ready_state == Read_ready_state::REQUESTED);
|
|
}
|
|
|
|
/**
|
|
* Return and consume one pending acknowledgement
|
|
*/
|
|
Packet_descriptor dequeue_acknowledgement()
|
|
{
|
|
if (_read_ready_state == Read_ready_state::READY) {
|
|
_read_ready_state = Read_ready_state::DONT_CARE;
|
|
|
|
Packet_descriptor ack_read_ready(Packet_descriptor(),
|
|
Node_handle { id().value },
|
|
Packet_descriptor::READ_READY,
|
|
0, 0);
|
|
ack_read_ready.succeeded(true);
|
|
return ack_read_ready;
|
|
}
|
|
|
|
if (_acked_packet_valid) {
|
|
_acked_packet_valid = false;
|
|
return _acked_packet;
|
|
}
|
|
|
|
Genode::warning("dequeue_acknowledgement called with no pending ack");
|
|
return Packet_descriptor();
|
|
}
|
|
|
|
/**
|
|
* Return true if node was written to
|
|
*/
|
|
virtual bool modified() const { return false; }
|
|
|
|
/**
|
|
* Print for debugging
|
|
*/
|
|
void print(Genode::Output &out) const
|
|
{
|
|
Genode::print(out, _path.string(), " (id=", id(), ")");
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Super-class for nodes that process read/write packets
|
|
*/
|
|
class Vfs_server::Io_node : public Vfs_server::Node,
|
|
public Vfs::Io_response_handler
|
|
{
|
|
private:
|
|
|
|
/*
|
|
* Noncopyable
|
|
*/
|
|
Io_node(Io_node const &);
|
|
Io_node &operator = (Io_node const &);
|
|
|
|
Mode const _mode;
|
|
|
|
protected:
|
|
|
|
Payload_ptr _payload_ptr { };
|
|
|
|
bool _modified = false;
|
|
|
|
Vfs::Vfs_handle &_handle;
|
|
|
|
void _import_job(Packet_descriptor packet, Payload_ptr payload_ptr)
|
|
{
|
|
/*
|
|
* Accept a READ_READY request without occupying '_packet'.
|
|
* This way, another request can follow a READ_READY request
|
|
* without blocking on the completion of READ_READY.
|
|
*/
|
|
if (packet.operation() == Packet_descriptor::READ_READY) {
|
|
_read_ready_state = Read_ready_state::REQUESTED;
|
|
return;
|
|
}
|
|
|
|
if (job_in_progress())
|
|
Genode::error("job unexpectedly submitted to busy node");
|
|
|
|
_packet = packet;
|
|
_payload_ptr = payload_ptr;
|
|
_acked_packet_valid = false;
|
|
_acked_packet = Packet_descriptor { };
|
|
}
|
|
|
|
void _acknowledge_as_success(size_t count)
|
|
{
|
|
/*
|
|
* Keep '_packet' and '_payload_ptr' intact to allow for the
|
|
* conversion of directory entries in 'Directory::execute_job'.
|
|
*/
|
|
|
|
_packet_in_progress = false;
|
|
_acked_packet_valid = true;
|
|
_acked_packet = _packet;
|
|
|
|
_acked_packet.length(count);
|
|
_acked_packet.succeeded(true);
|
|
}
|
|
|
|
void _acknowledge_as_failure()
|
|
{
|
|
_packet = Packet_descriptor();
|
|
_payload_ptr = Payload_ptr { nullptr };
|
|
_packet_in_progress = false;
|
|
_acked_packet_valid = true;
|
|
_acked_packet = _packet;
|
|
|
|
_acked_packet.succeeded(false);
|
|
}
|
|
|
|
/**
|
|
* Current job of this node, assigned by 'submit_job'
|
|
*/
|
|
Packet_descriptor _packet { };
|
|
|
|
protected:
|
|
|
|
Submit_result _submit_read_at(file_offset seek_offset)
|
|
{
|
|
if (!(_mode & READ_ONLY))
|
|
return Submit_result::DENIED;
|
|
|
|
_handle.seek(seek_offset);
|
|
|
|
bool const queuing_succeeded =
|
|
_handle.fs().queue_read(&_handle, _packet.length());
|
|
|
|
if (queuing_succeeded)
|
|
_packet_in_progress = true;
|
|
|
|
return queuing_succeeded ? Submit_result::ACCEPTED
|
|
: Submit_result::STALLED;
|
|
}
|
|
|
|
Submit_result _submit_write_at(file_offset seek_offset)
|
|
{
|
|
if (!(_mode & WRITE_ONLY))
|
|
return Submit_result::DENIED;
|
|
|
|
_handle.seek(seek_offset);
|
|
|
|
_packet_in_progress = true;
|
|
return Submit_result::ACCEPTED;
|
|
}
|
|
|
|
Submit_result _submit_sync()
|
|
{
|
|
bool const queuing_succeeded = _handle.fs().queue_sync(&_handle);
|
|
|
|
if (queuing_succeeded)
|
|
_packet_in_progress = true;
|
|
|
|
return queuing_succeeded ? Submit_result::ACCEPTED
|
|
: Submit_result::STALLED;
|
|
}
|
|
|
|
Submit_result _submit_read_ready()
|
|
{
|
|
_read_ready_state = Read_ready_state::REQUESTED;
|
|
|
|
if (_handle.fs().read_ready(&_handle)) {
|
|
/* if the handle is ready, send a packet back immediately */
|
|
read_ready_response();
|
|
} else {
|
|
/* register to send READ_READY acknowledgement later */
|
|
_handle.fs().notify_read_ready(&_handle);
|
|
}
|
|
return Submit_result::ACCEPTED;
|
|
}
|
|
|
|
Submit_result _submit_content_changed()
|
|
{
|
|
Genode::warning("client unexpectedly submitted CONTENT_CHANGED packet");
|
|
return Submit_result::DENIED;
|
|
}
|
|
|
|
Submit_result _submit_write_timestamp()
|
|
{
|
|
if (!(_mode & WRITE_ONLY))
|
|
return Submit_result::DENIED;
|
|
|
|
_packet_in_progress = true;
|
|
return Submit_result::ACCEPTED;
|
|
}
|
|
|
|
void _execute_read()
|
|
{
|
|
file_size out_count = 0;
|
|
|
|
switch (_handle.fs().complete_read(&_handle, _payload_ptr.ptr,
|
|
_packet.length(), out_count)) {
|
|
case Read_result::READ_OK:
|
|
_acknowledge_as_success(out_count);
|
|
break;
|
|
|
|
case Read_result::READ_ERR_IO:
|
|
case Read_result::READ_ERR_INVALID:
|
|
_acknowledge_as_failure();
|
|
break;
|
|
|
|
case Read_result::READ_ERR_WOULD_BLOCK:
|
|
case Read_result::READ_ERR_AGAIN:
|
|
case Read_result::READ_ERR_INTERRUPT:
|
|
case Read_result::READ_QUEUED:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Try to execute write operation at the VFS
|
|
*
|
|
* \return number of consumed bytes
|
|
*/
|
|
file_size _execute_write(char const *src_ptr, size_t length)
|
|
{
|
|
file_size out_count = 0;
|
|
try {
|
|
switch (_handle.fs().write(&_handle, src_ptr, length, out_count)) {
|
|
case Write_result::WRITE_ERR_AGAIN:
|
|
case Write_result::WRITE_ERR_WOULD_BLOCK:
|
|
break;
|
|
|
|
case Write_result::WRITE_ERR_INVALID:
|
|
case Write_result::WRITE_ERR_IO:
|
|
case Write_result::WRITE_ERR_INTERRUPT:
|
|
_acknowledge_as_failure();
|
|
break;
|
|
|
|
case Write_result::WRITE_OK:
|
|
break;
|
|
}
|
|
}
|
|
catch (Vfs::File_io_service::Insufficient_buffer) { /* re-execute */ }
|
|
|
|
_modified = true;
|
|
|
|
return out_count;
|
|
}
|
|
|
|
void _execute_sync()
|
|
{
|
|
switch (_handle.fs().complete_sync(&_handle)) {
|
|
|
|
case Sync_result::SYNC_OK:
|
|
_acknowledge_as_success(0);
|
|
break;
|
|
|
|
case Sync_result::SYNC_ERR_INVALID:
|
|
_acknowledge_as_failure();
|
|
break;
|
|
|
|
case Sync_result::SYNC_QUEUED:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void _execute_write_timestamp()
|
|
{
|
|
try {
|
|
_packet.with_timestamp([&] (::File_system::Timestamp const time) {
|
|
Vfs::Timestamp ts { .value = time.value };
|
|
_handle.fs().update_modification_timestamp(&_handle, ts);
|
|
});
|
|
_acknowledge_as_success(0);
|
|
}
|
|
catch (Vfs::File_io_service::Insufficient_buffer) { }
|
|
|
|
_modified = true;
|
|
}
|
|
|
|
public:
|
|
|
|
Io_node(Node_space &space,
|
|
char const *path,
|
|
Mode mode,
|
|
Vfs_handle &handle)
|
|
:
|
|
Node(space, path), _mode(mode), _handle(handle)
|
|
{
|
|
_handle.handler(this); // XXX remove?
|
|
}
|
|
|
|
virtual ~Io_node()
|
|
{
|
|
_handle.handler(nullptr);
|
|
_handle.close();
|
|
}
|
|
|
|
using Node_space::Element::id;
|
|
|
|
Mode mode() const { return _mode; }
|
|
|
|
|
|
/********************************
|
|
** Vfs_server::Node interface **
|
|
********************************/
|
|
|
|
void execute_job() override { }
|
|
|
|
bool modified() const override { return _modified; }
|
|
|
|
|
|
/****************************************
|
|
** Vfs::Io_response_handler interface **
|
|
****************************************/
|
|
|
|
/**
|
|
* Called by the VFS plugin of this handle
|
|
*/
|
|
void read_ready_response() override
|
|
{
|
|
if (_read_ready_state == Read_ready_state::REQUESTED)
|
|
_read_ready_state = Read_ready_state::READY;
|
|
}
|
|
|
|
/**
|
|
* Called by the VFS plugin of this handle
|
|
*/
|
|
void io_progress_response() override { }
|
|
};
|
|
|
|
|
|
class Vfs_server::Watch_node final : public Vfs_server::Node,
|
|
public Vfs::Watch_response_handler
|
|
{
|
|
public:
|
|
|
|
struct Watch_node_response_handler : Genode::Interface
|
|
{
|
|
virtual void handle_watch_node_response(Watch_node &) = 0;
|
|
};
|
|
|
|
private:
|
|
|
|
/*
|
|
* Noncopyable
|
|
*/
|
|
Watch_node(Watch_node const &);
|
|
Watch_node &operator = (Watch_node const &);
|
|
|
|
Vfs::Vfs_watch_handle &_watch_handle;
|
|
|
|
Watch_node_response_handler &_watch_node_response_handler;
|
|
|
|
public:
|
|
|
|
Watch_node(Node_space &space,
|
|
char const *path,
|
|
Vfs::Vfs_watch_handle &handle,
|
|
Watch_node_response_handler &watch_node_response_handler)
|
|
:
|
|
Node(space, path),
|
|
_watch_handle(handle),
|
|
_watch_node_response_handler(watch_node_response_handler)
|
|
{
|
|
_watch_handle.handler(this);
|
|
}
|
|
|
|
~Watch_node()
|
|
{
|
|
_watch_handle.close();
|
|
}
|
|
|
|
|
|
/*******************************************
|
|
** Vfs::Watch_response_handler interface **
|
|
*******************************************/
|
|
|
|
void watch_response() override
|
|
{
|
|
_acked_packet = Packet_descriptor(Packet_descriptor(),
|
|
Node_handle { id().value },
|
|
Packet_descriptor::CONTENT_CHANGED,
|
|
0, 0);
|
|
_acked_packet.succeeded(true);
|
|
_acked_packet_valid = true;
|
|
_watch_node_response_handler.handle_watch_node_response(*this);
|
|
}
|
|
|
|
|
|
/********************************
|
|
** Vfs_server::Node interface **
|
|
********************************/
|
|
|
|
Submit_result submit_job(Packet_descriptor, Payload_ptr) override
|
|
{
|
|
/*
|
|
* This can only happen if a client misbehaves by submitting
|
|
* work to a watch handle.
|
|
*/
|
|
Genode::warning("job unexpectedly submitted to watch handle");
|
|
|
|
/* don't reset '_acked_packet' as defined in the constructor */
|
|
|
|
return Submit_result::DENIED;
|
|
}
|
|
};
|
|
|
|
|
|
struct Vfs_server::Symlink : Io_node
|
|
{
|
|
private:
|
|
|
|
typedef Genode::String<MAX_PATH_LEN + 1> Write_buffer;
|
|
|
|
Write_buffer _write_buffer { };
|
|
|
|
bool _partial_operation() const
|
|
{
|
|
/* partial read or write is not supported */
|
|
if (_packet.position() != 0) {
|
|
Genode::warning("attempt for partial operation on a symlink");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool _max_path_length_exceeded() const
|
|
{
|
|
return _packet.length() >= MAX_PATH_LEN;
|
|
}
|
|
|
|
static Vfs_handle &_open(Vfs::File_system &vfs, Genode::Allocator &alloc,
|
|
char const *path, bool create)
|
|
{
|
|
Vfs_handle *h = nullptr;
|
|
assert_openlink(vfs.openlink(path, create, &h, alloc));
|
|
return *h;
|
|
}
|
|
|
|
public:
|
|
|
|
Symlink(Node_space &space,
|
|
Vfs::File_system &vfs,
|
|
Genode::Allocator &alloc,
|
|
char const *path,
|
|
Mode mode,
|
|
bool create)
|
|
:
|
|
Io_node(space, path, mode, _open(vfs, alloc, path, create))
|
|
{ }
|
|
|
|
Submit_result submit_job(Packet_descriptor packet, Payload_ptr payload_ptr) override
|
|
{
|
|
_import_job(packet, payload_ptr);
|
|
|
|
switch (packet.operation()) {
|
|
|
|
case Packet_descriptor::READ:
|
|
|
|
if (_partial_operation())
|
|
return Submit_result::DENIED;
|
|
|
|
return _submit_read_at(0);
|
|
|
|
case Packet_descriptor::WRITE:
|
|
{
|
|
if (_partial_operation() || _max_path_length_exceeded())
|
|
return Submit_result::DENIED;
|
|
|
|
/* accessed by 'execute_job' */
|
|
_write_buffer = Write_buffer(Genode::Cstring(_payload_ptr.ptr,
|
|
packet.length()));
|
|
return _submit_write_at(0);
|
|
}
|
|
|
|
case Packet_descriptor::SYNC: return _submit_sync();
|
|
case Packet_descriptor::READ_READY: return _submit_read_ready();
|
|
case Packet_descriptor::CONTENT_CHANGED: return _submit_content_changed();
|
|
case Packet_descriptor::WRITE_TIMESTAMP: return _submit_write_timestamp();
|
|
}
|
|
|
|
Genode::warning("invalid operation ", (int)_packet.operation(), " "
|
|
"requested from symlink node");
|
|
|
|
return Submit_result::DENIED;
|
|
}
|
|
|
|
void execute_job() override
|
|
{
|
|
switch (_packet.operation()) {
|
|
|
|
/*
|
|
* Write symlink content from '_write_buffer' instead of the
|
|
* '_payload_ptr'. In contrast to '_payload_ptr', which points
|
|
* to shared memory, the null-termination of the content of
|
|
* '_write_buffer' does not depend on the goodwill of the client.
|
|
*/
|
|
case Packet_descriptor::WRITE:
|
|
{
|
|
size_t const count = _write_buffer.length();
|
|
|
|
if (_execute_write(_write_buffer.string(), count) == count)
|
|
_acknowledge_as_success(count);
|
|
else
|
|
_acknowledge_as_failure();
|
|
break;
|
|
}
|
|
|
|
/* generic */
|
|
case Packet_descriptor::READ: _execute_read(); break;
|
|
case Packet_descriptor::SYNC: _execute_sync(); break;
|
|
case Packet_descriptor::WRITE_TIMESTAMP: _execute_write_timestamp(); break;
|
|
|
|
/* never executed */
|
|
case Packet_descriptor::READ_READY:
|
|
case Packet_descriptor::CONTENT_CHANGED:
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
class Vfs_server::File : public Io_node
|
|
{
|
|
private:
|
|
|
|
/*
|
|
* Noncopyable
|
|
*/
|
|
File(File const &);
|
|
File &operator = (File const &);
|
|
|
|
char const * const _leaf_path = nullptr; /* offset pointer to Node::_path */
|
|
|
|
typedef Directory_service::Stat Stat;
|
|
|
|
template <typename FN>
|
|
void _with_stat(FN const &fn)
|
|
{
|
|
typedef Directory_service::Stat_result Result;
|
|
|
|
Vfs::Directory_service::Stat stat { };
|
|
if (_handle.ds().stat(_leaf_path, stat) == Result::STAT_OK)
|
|
fn(stat);
|
|
}
|
|
|
|
seek_off_t _seek_pos()
|
|
{
|
|
seek_off_t seek_pos = _packet.position();
|
|
|
|
if (seek_pos == (seek_off_t)SEEK_TAIL)
|
|
_with_stat([&] (Stat const &stat) {
|
|
seek_pos = stat.size; });
|
|
|
|
return seek_pos;
|
|
}
|
|
|
|
enum class Write_type { UNKNOWN, CONTINUOUS, TRANSACTIONAL };
|
|
|
|
Write_type _write_type = Write_type::UNKNOWN;
|
|
|
|
/**
|
|
* Number of bytes consumed by VFS write
|
|
*
|
|
* Used for the incremental write to continuous files.
|
|
*/
|
|
seek_off_t _write_pos = 0;
|
|
|
|
bool _watch_read_ready = false;
|
|
|
|
protected:
|
|
|
|
static Vfs_handle &_open(Vfs::File_system &vfs, Genode::Allocator &alloc,
|
|
char const *path, Mode mode, bool create)
|
|
{
|
|
Vfs_handle *h = nullptr;
|
|
unsigned vfs_mode = (mode-1) |
|
|
(create ? Vfs::Directory_service::OPEN_MODE_CREATE : 0);
|
|
|
|
assert_open(vfs.open(path, vfs_mode, &h, alloc));
|
|
return *h;
|
|
}
|
|
|
|
public:
|
|
|
|
File(Node_space &space,
|
|
Vfs::File_system &vfs,
|
|
Genode::Allocator &alloc,
|
|
char const *path,
|
|
Mode mode,
|
|
bool create)
|
|
:
|
|
Io_node(space, path, mode, _open(vfs, alloc, path, mode, create)),
|
|
_leaf_path(vfs.leaf_path(Node::path()))
|
|
{ }
|
|
|
|
void truncate(file_size_t size)
|
|
{
|
|
assert_truncate(_handle.fs().ftruncate(&_handle, size));
|
|
}
|
|
|
|
Submit_result submit_job(Packet_descriptor packet, Payload_ptr payload_ptr) override
|
|
{
|
|
_import_job(packet, payload_ptr);
|
|
|
|
_write_type = Write_type::UNKNOWN;
|
|
_write_pos = 0;
|
|
|
|
switch (packet.operation()) {
|
|
|
|
case Packet_descriptor::READ: return _submit_read_at(_seek_pos());
|
|
case Packet_descriptor::WRITE: return _submit_write_at(_seek_pos());
|
|
case Packet_descriptor::SYNC: return _submit_sync();
|
|
case Packet_descriptor::READ_READY: return _submit_read_ready();
|
|
case Packet_descriptor::CONTENT_CHANGED: return _submit_content_changed();
|
|
case Packet_descriptor::WRITE_TIMESTAMP: return _submit_write_timestamp();
|
|
}
|
|
|
|
Genode::warning("invalid operation ", (int)_packet.operation(), " "
|
|
"requested from file node");
|
|
|
|
return Submit_result::DENIED;
|
|
}
|
|
|
|
void execute_job() override
|
|
{
|
|
switch (_packet.operation()) {
|
|
|
|
case Packet_descriptor::WRITE:
|
|
{
|
|
size_t const count = _packet.length() - _write_pos;
|
|
char const * const src_ptr = _payload_ptr.ptr + _write_pos;
|
|
size_t const consumed = _execute_write(src_ptr, count);
|
|
|
|
if (consumed == count) {
|
|
_acknowledge_as_success(count);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* The write request was only partially successful.
|
|
* Continue writing if the file is continuous.
|
|
* Return an error if the file is transactional.
|
|
*/
|
|
|
|
/* determine write type once via 'stat' */
|
|
if (_write_type == Write_type::UNKNOWN) {
|
|
_write_type = Write_type::TRANSACTIONAL;
|
|
|
|
_with_stat([&] (Stat const &stat) {
|
|
if (stat.type == Vfs::Node_type::CONTINUOUS_FILE)
|
|
_write_type = Write_type::CONTINUOUS; });
|
|
}
|
|
|
|
if (_write_type == Write_type::TRANSACTIONAL) {
|
|
_acknowledge_as_failure();
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Keep executing the write operation for the remaining bytes.
|
|
* The seek offset used for subsequent VFS write operations
|
|
* is incremented automatically by the VFS handle.
|
|
*/
|
|
_write_pos += consumed;
|
|
break;
|
|
}
|
|
|
|
/* generic */
|
|
case Packet_descriptor::READ: _execute_read(); break;
|
|
case Packet_descriptor::SYNC: _execute_sync(); break;
|
|
case Packet_descriptor::WRITE_TIMESTAMP: _execute_write_timestamp(); break;
|
|
|
|
/* never executed */
|
|
case Packet_descriptor::READ_READY:
|
|
case Packet_descriptor::CONTENT_CHANGED:
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
struct Vfs_server::Directory : Io_node
|
|
{
|
|
public:
|
|
|
|
enum class Session_writeable { READ_ONLY, WRITEABLE };
|
|
|
|
private:
|
|
|
|
Session_writeable const _writeable;
|
|
|
|
typedef Directory_service::Dirent Vfs_dirent;
|
|
typedef ::File_system::Directory_entry Fs_dirent;
|
|
|
|
bool _position_and_length_aligned_with_dirent_size()
|
|
{
|
|
if (_packet.length() < sizeof(Directory_entry))
|
|
return false;
|
|
|
|
if (_packet.length() % sizeof(::File_system::Directory_entry))
|
|
return false;
|
|
|
|
if (_packet.position() % sizeof(::File_system::Directory_entry))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static Fs_dirent _convert_dirent(Vfs_dirent from, Session_writeable writeable)
|
|
{
|
|
from.sanitize();
|
|
|
|
auto fs_dirent_type = [&] (Vfs::Directory_service::Dirent_type type)
|
|
{
|
|
using From = Vfs::Directory_service::Dirent_type;
|
|
using To = ::File_system::Node_type;
|
|
|
|
/*
|
|
* This should never be taken because 'END' is checked as a
|
|
* precondition prior the call to of this function.
|
|
*/
|
|
To const default_result = To::CONTINUOUS_FILE;
|
|
|
|
switch (type) {
|
|
case From::END: return default_result;
|
|
case From::DIRECTORY: return To::DIRECTORY;
|
|
case From::SYMLINK: return To::SYMLINK;
|
|
case From::CONTINUOUS_FILE: return To::CONTINUOUS_FILE;
|
|
case From::TRANSACTIONAL_FILE: return To::TRANSACTIONAL_FILE;
|
|
}
|
|
return default_result;
|
|
};
|
|
|
|
return {
|
|
.inode = from.fileno,
|
|
.type = fs_dirent_type(from.type),
|
|
.rwx = {
|
|
.readable = from.rwx.readable,
|
|
.writeable = (writeable == Session_writeable::WRITEABLE)
|
|
? from.rwx.writeable : false,
|
|
.executable = from.rwx.executable },
|
|
.name = { from.name.buf }
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Convert VFS directory entry to FS directory entry in place in the
|
|
* payload buffer
|
|
*
|
|
* \return size of converted data in bytes
|
|
*/
|
|
file_size _convert_vfs_dirents_to_fs_dirents()
|
|
{
|
|
static_assert(sizeof(Vfs_dirent) == sizeof(Fs_dirent));
|
|
|
|
file_offset const step = sizeof(Fs_dirent);
|
|
file_offset const length = _packet.length();
|
|
|
|
file_size converted_length = 0;
|
|
|
|
for (file_offset offset = 0; offset + step <= length; offset += step) {
|
|
|
|
char * const ptr = _payload_ptr.ptr + offset;
|
|
|
|
Vfs_dirent &vfs_dirent = *(Vfs_dirent *)(ptr);
|
|
Fs_dirent &fs_dirent = *(Fs_dirent *)(ptr);
|
|
|
|
if (vfs_dirent.type == Vfs::Directory_service::Dirent_type::END)
|
|
break;
|
|
|
|
fs_dirent = _convert_dirent(vfs_dirent, _writeable);
|
|
|
|
converted_length += step;
|
|
}
|
|
|
|
return converted_length;
|
|
}
|
|
|
|
static Vfs_handle &_open(Vfs::File_system &vfs, Genode::Allocator &alloc,
|
|
char const *path, bool create)
|
|
{
|
|
Vfs_handle *h = nullptr;
|
|
assert_opendir(vfs.opendir(path, create, &h, alloc));
|
|
return *h;
|
|
}
|
|
|
|
public:
|
|
|
|
Directory(Node_space &space,
|
|
Vfs::File_system &vfs,
|
|
Genode::Allocator &alloc,
|
|
char const *path,
|
|
bool create,
|
|
Session_writeable writeable)
|
|
:
|
|
Io_node(space, path, READ_ONLY, _open(vfs, alloc, path, create)),
|
|
_writeable(writeable)
|
|
{ }
|
|
|
|
/**
|
|
* Open a file handle at this directory
|
|
*/
|
|
Node_space::Id file(Node_space &space,
|
|
Vfs::File_system &vfs,
|
|
Genode::Allocator &alloc,
|
|
char const *path,
|
|
Mode mode,
|
|
bool create)
|
|
{
|
|
File &file = *new (alloc)
|
|
File(space, vfs, alloc,
|
|
Path(path, Node::path()).base(), mode, create);
|
|
|
|
return file.id();
|
|
}
|
|
|
|
/**
|
|
* Open a symlink handle at this directory
|
|
*/
|
|
Node_space::Id symlink(Node_space &space,
|
|
Vfs::File_system &vfs,
|
|
Genode::Allocator &alloc,
|
|
char const *path,
|
|
Mode mode,
|
|
bool create)
|
|
{
|
|
Symlink &link = *new (alloc)
|
|
Symlink(space, vfs, alloc,
|
|
Path(path, Node::path()).base(), mode, create);
|
|
|
|
return link.id();
|
|
}
|
|
|
|
|
|
/********************************
|
|
** Vfs_server::Node interface **
|
|
********************************/
|
|
|
|
Submit_result submit_job(Packet_descriptor packet, Payload_ptr payload_ptr) override
|
|
{
|
|
_import_job(packet, payload_ptr);
|
|
|
|
switch (packet.operation()) {
|
|
|
|
case Packet_descriptor::READ:
|
|
|
|
if (!_position_and_length_aligned_with_dirent_size())
|
|
return Submit_result::DENIED;
|
|
|
|
return _submit_read_at(_packet.position());
|
|
|
|
case Packet_descriptor::WRITE:
|
|
return Submit_result::DENIED;
|
|
|
|
case Packet_descriptor::SYNC: return _submit_sync();
|
|
case Packet_descriptor::READ_READY: return _submit_read_ready();
|
|
case Packet_descriptor::CONTENT_CHANGED: return _submit_content_changed();
|
|
case Packet_descriptor::WRITE_TIMESTAMP: return _submit_write_timestamp();
|
|
}
|
|
|
|
Genode::warning("invalid operation ", (int)_packet.operation(), " "
|
|
"requested from directory node");
|
|
return Submit_result::DENIED;
|
|
}
|
|
|
|
void execute_job() override
|
|
{
|
|
switch (_packet.operation()) {
|
|
|
|
case Packet_descriptor::READ:
|
|
_execute_read();
|
|
|
|
if (_acked_packet_valid) {
|
|
file_size const length = _convert_vfs_dirents_to_fs_dirents();
|
|
|
|
/*
|
|
* Overwrite the acknowledgement assigned by
|
|
* '_execute_read' with an acknowledgement featuring the
|
|
* converted length. This way, the client reads only the
|
|
* number of bytes until the end of the directory.
|
|
*/
|
|
_acknowledge_as_success(length);
|
|
}
|
|
break;
|
|
|
|
/* generic */
|
|
case Packet_descriptor::SYNC: _execute_sync(); break;
|
|
case Packet_descriptor::WRITE_TIMESTAMP: _execute_write_timestamp(); break;
|
|
|
|
/* never executed */
|
|
case Packet_descriptor::WRITE:
|
|
case Packet_descriptor::READ_READY:
|
|
case Packet_descriptor::CONTENT_CHANGED:
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
#endif /* _VFS__NODE_H_ */
|