537 lines
16 KiB
C++
537 lines
16 KiB
C++
/*
|
|
* \brief File-system session interface
|
|
* \author Norman Feske
|
|
* \author Emery Hemingway
|
|
* \author Christian Helmuth
|
|
* \date 2012-04-05
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2012-2018 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 _INCLUDE__FILE_SYSTEM_SESSION__FILE_SYSTEM_SESSION_H_
|
|
#define _INCLUDE__FILE_SYSTEM_SESSION__FILE_SYSTEM_SESSION_H_
|
|
|
|
#include <util/string.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 {
|
|
|
|
struct Node
|
|
{
|
|
typedef Genode::Id_space<Node>::Id Id;
|
|
};
|
|
|
|
struct File : Node
|
|
{
|
|
struct Id : Node::Id
|
|
{
|
|
explicit Id(unsigned long value) : Node::Id { value } { };
|
|
};
|
|
};
|
|
|
|
struct Directory : Node
|
|
{
|
|
struct Id : Node::Id
|
|
{
|
|
explicit Id(unsigned long value) : Node::Id { value } { };
|
|
};
|
|
};
|
|
|
|
struct Symlink : Node
|
|
{
|
|
struct Id : Node::Id
|
|
{
|
|
explicit Id(unsigned long value) : Node::Id { value } { };
|
|
};
|
|
};
|
|
|
|
struct Watch : Node
|
|
{
|
|
struct Id : Node::Id
|
|
{
|
|
explicit Id(unsigned long value) : Node::Id { value } { };
|
|
};
|
|
};
|
|
|
|
typedef Node::Id Node_handle;
|
|
typedef File::Id File_handle;
|
|
typedef Directory::Id Dir_handle;
|
|
typedef Symlink::Id Symlink_handle;
|
|
typedef Watch::Id Watch_handle;
|
|
|
|
enum class Node_type {
|
|
DIRECTORY,
|
|
SYMLINK,
|
|
CONTINUOUS_FILE,
|
|
TRANSACTIONAL_FILE
|
|
};
|
|
|
|
struct Node_rwx
|
|
{
|
|
bool readable;
|
|
bool writeable;
|
|
bool executable;
|
|
};
|
|
|
|
using Genode::size_t;
|
|
|
|
typedef Genode::uint64_t seek_off_t;
|
|
typedef Genode::uint64_t file_size_t;
|
|
|
|
struct Timestamp
|
|
{
|
|
/*
|
|
* The INVALID value is used whenever the underlying file system
|
|
* session does not support modification timestamps. The value is
|
|
* chosen such that it is unlikely to occur, instead of simply '0',
|
|
* which would correspond to plausible time (see comment below).
|
|
* This allows for handling this case explicitly. In any case, an
|
|
* invalid timestamp should not be used for doing any calculations.
|
|
*/
|
|
static constexpr Genode::int64_t INVALID = 0x7fffffffffffffffLL;
|
|
|
|
/*
|
|
* The 'value' member contains the modification timestamp in seconds.
|
|
* Value '0' is defined as 1970-01-01T00:00:00Z, where a positive value
|
|
* covers all seconds after this date and a negative one all before.
|
|
*/
|
|
Genode::int64_t value;
|
|
};
|
|
|
|
typedef Genode::Out_of_ram Out_of_ram;
|
|
typedef Genode::Out_of_caps Out_of_caps;
|
|
|
|
class Packet_descriptor;
|
|
|
|
/**
|
|
* 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 };
|
|
|
|
/**
|
|
* File offset constant for reading or writing to the end of a file
|
|
*
|
|
* Clients are unable to reliably append to the end of a file where there
|
|
* may be other writes to the same offset in the queues of other clients.
|
|
* The SEEK_TAIL constant resolves this contention by aligning packet
|
|
* operations with the end of the file at the time the packet is dequeued.
|
|
*
|
|
* SEEK_TAIL behavior with directory and symlink nodes is undefined.
|
|
*/
|
|
enum { SEEK_TAIL = ~0ULL };
|
|
|
|
typedef Genode::Rpc_in_buffer<MAX_NAME_LEN> Name;
|
|
typedef Genode::Rpc_in_buffer<MAX_PATH_LEN> Path;
|
|
|
|
struct Status;
|
|
struct Control;
|
|
struct Directory_entry;
|
|
|
|
/*
|
|
* Exception types
|
|
*/
|
|
class Exception : public Genode::Exception { };
|
|
class Invalid_handle : Exception { };
|
|
class Invalid_name : Exception { };
|
|
class Lookup_failed : Exception { };
|
|
class Name_too_long : Exception { };
|
|
class Node_already_exists : Exception { };
|
|
class No_space : Exception { };
|
|
class Not_empty : Exception { };
|
|
class Permission_denied : Exception { };
|
|
class Unavailable : Exception { };
|
|
|
|
struct Session;
|
|
}
|
|
|
|
|
|
class File_system::Packet_descriptor : public Genode::Packet_descriptor
|
|
{
|
|
public:
|
|
|
|
enum Opcode {
|
|
READ,
|
|
WRITE,
|
|
WRITE_TIMESTAMP,
|
|
CONTENT_CHANGED,
|
|
READ_READY,
|
|
|
|
/**
|
|
* Synchronize file system
|
|
*
|
|
* This is only needed by file systems that maintain an internal
|
|
* cache, which needs to be flushed on certain occasions.
|
|
*/
|
|
SYNC
|
|
};
|
|
|
|
private:
|
|
|
|
Node_handle _handle { 0 }; /* node handle */
|
|
Opcode _op; /* requested operation */
|
|
bool _success; /* indicates success of operation */
|
|
union
|
|
{
|
|
struct
|
|
{
|
|
seek_off_t _position; /* file seek offset in bytes */
|
|
size_t _length; /* transaction length in bytes */
|
|
};
|
|
Timestamp _modification_time; /* seconds since the Unix epoch */
|
|
};
|
|
|
|
public:
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
Packet_descriptor(Genode::off_t buf_offset = 0,
|
|
Genode::size_t buf_size = 0)
|
|
:
|
|
Genode::Packet_descriptor(buf_offset, buf_size),
|
|
_op(READ), _success(false), _position(0), _length(0) { }
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* \param position seek offset in bytes
|
|
*
|
|
* Note, if 'position' is set to 'SEEK_TAIL' read operations will read
|
|
* 'length' bytes from the end of the file while write operations will
|
|
* append length bytes at the end of the file.
|
|
*/
|
|
Packet_descriptor(Packet_descriptor p,
|
|
Node_handle handle, Opcode op, size_t length,
|
|
seek_off_t position = SEEK_TAIL)
|
|
:
|
|
Genode::Packet_descriptor(p.offset(), p.size()),
|
|
_handle(handle), _op(op), _success(false),
|
|
_position(position), _length(length)
|
|
{ }
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* This constructor provided for sending server-side
|
|
* notification packets.
|
|
*/
|
|
Packet_descriptor(Node_handle handle, Opcode op)
|
|
:
|
|
Genode::Packet_descriptor(0, 0),
|
|
_handle(handle), _op(op), _success(true),
|
|
_position(0), _length(0)
|
|
{ }
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
Packet_descriptor(Packet_descriptor p, Node_handle handle, Opcode op, Timestamp const &mtime)
|
|
:
|
|
Genode::Packet_descriptor(p.offset(), p.size()),
|
|
_handle(handle), _op(op), _success(false),
|
|
_modification_time(mtime)
|
|
{ }
|
|
|
|
Node_handle handle() const { return _handle; }
|
|
Opcode operation() const { return _op; }
|
|
seek_off_t position() const { return _op != Opcode::WRITE_TIMESTAMP ? _position : 0; }
|
|
size_t length() const { return _op != Opcode::WRITE_TIMESTAMP ? _length : 0; }
|
|
bool succeeded() const { return _success; }
|
|
|
|
template <typename FN>
|
|
void with_timestamp(FN const &fn) const
|
|
{
|
|
if (_op == Opcode::WRITE_TIMESTAMP)
|
|
fn(_modification_time);
|
|
}
|
|
|
|
/*
|
|
* Accessors called at the server side
|
|
*/
|
|
void succeeded(bool b) { _success = b ? 1 : 0; }
|
|
void length(size_t length) { _length = length; }
|
|
};
|
|
|
|
|
|
struct File_system::Status
|
|
{
|
|
file_size_t size;
|
|
Node_type type;
|
|
Node_rwx rwx;
|
|
unsigned long inode;
|
|
Timestamp modification_time;
|
|
|
|
/**
|
|
* Return true if node is a directory
|
|
*/
|
|
bool directory() const { return type == Node_type::DIRECTORY; }
|
|
|
|
/**
|
|
* Return true if node is a symbolic link
|
|
*/
|
|
bool symlink() const { return type == Node_type::SYMLINK; }
|
|
};
|
|
|
|
|
|
struct File_system::Control { /* to manipulate the executable bit */ };
|
|
|
|
|
|
/**
|
|
* Data structure returned when reading from a directory node
|
|
*/
|
|
struct File_system::Directory_entry
|
|
{
|
|
struct Name
|
|
{
|
|
char buf[MAX_NAME_LEN] { };
|
|
|
|
Name() { };
|
|
Name(char const *name) { Genode::strncpy(buf, name, sizeof(buf)); }
|
|
};
|
|
|
|
unsigned long inode;
|
|
Node_type type;
|
|
Node_rwx rwx;
|
|
Name name;
|
|
|
|
/**
|
|
* Sanitize object received from a file-system server as plain bytes
|
|
*/
|
|
void sanitize()
|
|
{
|
|
/* enforce null termination */
|
|
name.buf[MAX_NAME_LEN - 1] = 0;
|
|
}
|
|
};
|
|
|
|
|
|
struct File_system::Session : public Genode::Session
|
|
{
|
|
enum { TX_QUEUE_SIZE = 16 };
|
|
|
|
typedef Genode::Packet_stream_policy<File_system::Packet_descriptor,
|
|
TX_QUEUE_SIZE, TX_QUEUE_SIZE,
|
|
char> Tx_policy;
|
|
|
|
typedef Packet_stream_tx::Channel<Tx_policy> Tx;
|
|
|
|
/**
|
|
* \noapi
|
|
*/
|
|
static const char *service_name() { return "File_system"; }
|
|
|
|
enum { CAP_QUOTA = 12 };
|
|
|
|
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 Invalid_name file name contains invalid characters
|
|
* \throw Lookup_failed the name refers to a node other than a file
|
|
* \throw Node_already_exists file cannot be created because a node with
|
|
* the same name already exists
|
|
* \throw No_space storage exhausted
|
|
* \throw Out_of_ram server cannot allocate metadata
|
|
* \throw Out_of_caps
|
|
* \throw Permission_denied
|
|
* \throw Unavailable directory vanished
|
|
*/
|
|
virtual File_handle file(Dir_handle, Name const &name, Mode, bool create) = 0;
|
|
|
|
/**
|
|
* Open or create symlink
|
|
*
|
|
* \throw Invalid_handle directory handle is invalid
|
|
* \throw Invalid_name symlink name contains invalid characters
|
|
* \throw Lookup_failed the name refers to a node other than a symlink
|
|
* \throw Node_already_exists symlink cannot be created because a node with
|
|
* the same name already exists
|
|
* \throw No_space storage exhausted
|
|
* \throw Out_of_ram server cannot allocate metadata
|
|
* \throw Out_of_caps
|
|
* \throw Permission_denied
|
|
* \throw Unavailable directory vanished
|
|
*/
|
|
virtual Symlink_handle symlink(Dir_handle, Name const &name, bool create) = 0;
|
|
|
|
/**
|
|
* Open or create directory
|
|
*
|
|
* \throw Lookup_failed path lookup failed because one element
|
|
* of 'path' does not exist
|
|
* \throw Name_too_long 'path' is too long
|
|
* \throw Node_already_exists directory cannot be created because a
|
|
* node with the same name already exists
|
|
* \throw No_space storage exhausted
|
|
* \throw Out_of_ram server cannot allocate metadata
|
|
* \throw Out_of_caps
|
|
* \throw Permission_denied
|
|
*/
|
|
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'.
|
|
*
|
|
* \throw Lookup_failed path lookup failed because one element
|
|
* of 'path' does not exist
|
|
* \throw Out_of_ram server cannot allocate metadata
|
|
* \throw Out_of_caps
|
|
*/
|
|
virtual Node_handle node(Path const &path) = 0;
|
|
|
|
/**
|
|
* Watch a node for for changes.
|
|
*
|
|
* When changes are made to the node at this path a CONTENT_CHANGED
|
|
* packet will be sent from the server to the client.
|
|
*
|
|
* \throw Lookup_failed path lookup failed because one element
|
|
* of 'path' does not exist
|
|
* \throw Out_of_ram server cannot allocate metadata
|
|
* \throw Out_of_caps
|
|
* \throw Unavailable file-system is static or does not support
|
|
* notifications
|
|
*
|
|
* The returned node handle is used to identify notification packets.
|
|
*/
|
|
virtual Watch_handle watch(Path const &path) = 0;
|
|
|
|
/**
|
|
* Close file
|
|
*
|
|
* \throw Invalid_handle node handle is invalid
|
|
*/
|
|
virtual void close(Node_handle) = 0;
|
|
|
|
/**
|
|
* Request information about an open file or directory
|
|
*
|
|
* \throw Invalid_handle node handle is invalid
|
|
* \throw Unavailable node vanished
|
|
*/
|
|
virtual Status status(Node_handle) = 0;
|
|
|
|
/**
|
|
* Set information about an open file or directory
|
|
*
|
|
* \throw Invalid_handle node handle is invalid
|
|
* \throw Unavailable node vanished
|
|
*/
|
|
virtual void control(Node_handle, Control) = 0;
|
|
|
|
/**
|
|
* Delete file or directory
|
|
*
|
|
* \throw Invalid_handle directory handle is invalid
|
|
* \throw Invalid_name 'name' contains invalid characters
|
|
* \throw Lookup_failed lookup of 'name' in 'dir' failed
|
|
* \throw Not_empty argument is a non-empty directory and
|
|
* the backend does not support recursion
|
|
* \throw Permission_denied
|
|
* \throw Unavailable directory vanished
|
|
*/
|
|
virtual void unlink(Dir_handle dir, Name const &name) = 0;
|
|
|
|
/**
|
|
* Truncate or grow file to specified size
|
|
*
|
|
* \throw Invalid_handle node handle is invalid
|
|
* \throw No_space new size exceeds free space
|
|
* \throw Permission_denied node modification not allowed
|
|
* \throw Unavailable node vanished
|
|
*/
|
|
virtual void truncate(File_handle, file_size_t size) = 0;
|
|
|
|
/**
|
|
* Move and rename directory entry
|
|
*
|
|
* \throw Invalid_handle a directory handle is invalid
|
|
* \throw Invalid_name 'to' contains invalid characters
|
|
* \throw Lookup_failed 'from' not found
|
|
* \throw Permission_denied node modification not allowed
|
|
* \throw Unavailable a directory vanished
|
|
*/
|
|
virtual void move(Dir_handle, Name const &from,
|
|
Dir_handle, Name const &to) = 0;
|
|
|
|
|
|
/*******************
|
|
** RPC interface **
|
|
*******************/
|
|
|
|
GENODE_RPC(Rpc_tx_cap, Genode::Capability<Tx>, _tx_cap);
|
|
GENODE_RPC_THROW(Rpc_file, File_handle, file,
|
|
GENODE_TYPE_LIST(Invalid_handle, Invalid_name,
|
|
Lookup_failed, Node_already_exists,
|
|
No_space, Out_of_ram, Out_of_caps,
|
|
Permission_denied, Unavailable),
|
|
Dir_handle, Name const &, Mode, bool);
|
|
GENODE_RPC_THROW(Rpc_symlink, Symlink_handle, symlink,
|
|
GENODE_TYPE_LIST(Invalid_handle, Invalid_name,
|
|
Lookup_failed, Node_already_exists,
|
|
No_space, Out_of_ram, Out_of_caps,
|
|
Permission_denied, Unavailable),
|
|
Dir_handle, Name const &, bool);
|
|
GENODE_RPC_THROW(Rpc_dir, Dir_handle, dir,
|
|
GENODE_TYPE_LIST(Lookup_failed, Name_too_long,
|
|
Node_already_exists, No_space,
|
|
Out_of_ram, Out_of_caps, Permission_denied),
|
|
Path const &, bool);
|
|
GENODE_RPC_THROW(Rpc_node, Node_handle, node,
|
|
GENODE_TYPE_LIST(Lookup_failed, Out_of_ram, Out_of_caps),
|
|
Path const &);
|
|
GENODE_RPC_THROW(Rpc_watch, Watch_handle, watch,
|
|
GENODE_TYPE_LIST(Lookup_failed, Out_of_ram, Out_of_caps, Unavailable),
|
|
Path const &);
|
|
GENODE_RPC_THROW(Rpc_close, void, close,
|
|
GENODE_TYPE_LIST(Invalid_handle),
|
|
Node_handle);
|
|
GENODE_RPC_THROW(Rpc_status, Status, status,
|
|
GENODE_TYPE_LIST(Invalid_handle, Unavailable),
|
|
Node_handle);
|
|
GENODE_RPC_THROW(Rpc_control, void, control,
|
|
GENODE_TYPE_LIST(Invalid_handle, Unavailable),
|
|
Node_handle, Control);
|
|
GENODE_RPC_THROW(Rpc_unlink, void, unlink,
|
|
GENODE_TYPE_LIST(Invalid_handle, Invalid_name,
|
|
Lookup_failed, Not_empty,
|
|
Permission_denied, Unavailable),
|
|
Dir_handle, Name const &);
|
|
GENODE_RPC_THROW(Rpc_truncate, void, truncate,
|
|
GENODE_TYPE_LIST(Invalid_handle, No_space,
|
|
Permission_denied, Unavailable),
|
|
File_handle, file_size_t);
|
|
GENODE_RPC_THROW(Rpc_move, void, move,
|
|
GENODE_TYPE_LIST(Invalid_handle, Invalid_name,
|
|
Lookup_failed, Permission_denied, Unavailable),
|
|
Dir_handle, Name const &, Dir_handle, Name const &);
|
|
|
|
GENODE_RPC_INTERFACE(Rpc_tx_cap,
|
|
Rpc_file, Rpc_symlink, Rpc_dir,
|
|
Rpc_node, Rpc_watch,
|
|
Rpc_close, Rpc_status, Rpc_control, Rpc_unlink,
|
|
Rpc_truncate, Rpc_move);
|
|
};
|
|
|
|
#endif /* _INCLUDE__FILE_SYSTEM_SESSION__FILE_SYSTEM_SESSION_H_ */
|