386 lines
9.0 KiB
C++
386 lines
9.0 KiB
C++
/*
|
|
* \brief I/O channel for files opened via virtual directory service
|
|
* \author Norman Feske
|
|
* \date 2011-02-17
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2011-2017 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 _NOUX__VFS_IO_CHANNEL_H_
|
|
#define _NOUX__VFS_IO_CHANNEL_H_
|
|
|
|
/* Genode includes */
|
|
#include <timer_session/connection.h>
|
|
|
|
/* Noux includes */
|
|
#include <io_channel.h>
|
|
#include <vfs/dir_file_system.h>
|
|
#include <time_info.h>
|
|
|
|
namespace Noux {
|
|
class Vfs_io_waiter;
|
|
struct Vfs_handle_context;
|
|
struct Vfs_io_channel;
|
|
|
|
typedef Registry<Registered_no_delete<Vfs_io_waiter>>
|
|
Vfs_io_waiter_registry;
|
|
}
|
|
|
|
class Noux::Vfs_io_waiter
|
|
{
|
|
private:
|
|
|
|
Genode::Semaphore _sem { };
|
|
|
|
public:
|
|
|
|
void wait_for_io() { _sem.down(); }
|
|
|
|
void wakeup() { _sem.up(); }
|
|
};
|
|
|
|
struct Noux::Vfs_handle_context : Vfs::Io_response_handler
|
|
{
|
|
Vfs_io_waiter vfs_io_waiter { };
|
|
|
|
void read_ready_response() override {
|
|
vfs_io_waiter.wakeup(); }
|
|
|
|
void io_progress_response() override {
|
|
vfs_io_waiter.wakeup(); }
|
|
};
|
|
|
|
struct Noux::Vfs_io_channel : Io_channel
|
|
{
|
|
Signal_handler<Vfs_io_channel> _read_avail_handler;
|
|
|
|
void _handle_read_avail()
|
|
{
|
|
Io_channel::invoke_all_notifiers();
|
|
}
|
|
|
|
Vfs::Vfs_handle &_fh;
|
|
|
|
Vfs_io_waiter_registry &_vfs_io_waiter_registry;
|
|
|
|
Absolute_path _path;
|
|
Absolute_path _leaf_path;
|
|
|
|
bool const _dir = _fh.ds().directory(_leaf_path.base());
|
|
|
|
Time_info const &_time_info;
|
|
Timer::Connection &_timer;
|
|
|
|
void _sync()
|
|
{
|
|
Milliseconds const ms =
|
|
_timer.curr_time().trunc_to_plain_ms();
|
|
uint64_t sec = _time_info.initial_time();
|
|
sec += (ms.value / 1000);
|
|
|
|
Vfs::Timestamp ts { .value = (long long)sec };
|
|
|
|
Registered_no_delete<Vfs_io_waiter>
|
|
vfs_io_waiter(_vfs_io_waiter_registry);
|
|
|
|
if ((_fh.status_flags() & Sysio::OPEN_MODE_ACCMODE) != Sysio::OPEN_MODE_RDONLY) {
|
|
for (;;) {
|
|
if (_fh.fs().update_modification_timestamp(&_fh, ts)) {
|
|
break;
|
|
} else {
|
|
Genode::error("_sync: update_modification_timestamp failed");
|
|
vfs_io_waiter.wait_for_io();
|
|
}
|
|
}
|
|
}
|
|
|
|
while (!_fh.fs().queue_sync(&_fh))
|
|
vfs_io_waiter.wait_for_io();
|
|
|
|
while (_fh.fs().complete_sync(&_fh) == Vfs::File_io_service::SYNC_QUEUED)
|
|
vfs_io_waiter.wait_for_io();
|
|
|
|
/* wake up threads blocking for 'queue_*()' or 'write()' */
|
|
_vfs_io_waiter_registry.for_each([] (Vfs_io_waiter &r) {
|
|
r.wakeup();
|
|
});
|
|
}
|
|
|
|
Vfs_io_channel(char const *path, char const *leaf_path,
|
|
Vfs::Vfs_handle *vfs_handle,
|
|
Vfs_io_waiter_registry &vfs_io_waiter_registry,
|
|
Entrypoint &ep,
|
|
Time_info const &time_info,
|
|
Timer::Connection &timer)
|
|
:
|
|
_read_avail_handler(ep, *this, &Vfs_io_channel::_handle_read_avail),
|
|
_fh(*vfs_handle), _vfs_io_waiter_registry(vfs_io_waiter_registry),
|
|
_path(path), _leaf_path(leaf_path), _time_info(time_info), _timer(timer)
|
|
{
|
|
_fh.fs().register_read_ready_sigh(&_fh, _read_avail_handler);
|
|
}
|
|
|
|
~Vfs_io_channel()
|
|
{
|
|
_sync();
|
|
|
|
_fh.ds().close(&_fh);
|
|
}
|
|
|
|
bool write(Sysio &sysio) override
|
|
{
|
|
if (_dir) {
|
|
sysio.error.write = Vfs::File_io_service::WRITE_ERR_INVALID;
|
|
return false;
|
|
}
|
|
|
|
Vfs::file_size count = sysio.write_in.count;
|
|
Vfs::file_size out_count = 0;
|
|
|
|
Registered_no_delete<Vfs_io_waiter>
|
|
vfs_io_waiter(_vfs_io_waiter_registry);
|
|
|
|
for (;;) {
|
|
try {
|
|
sysio.error.write = _fh.fs().write(&_fh, sysio.write_in.chunk,
|
|
count, out_count);
|
|
break;
|
|
} catch (Vfs::File_io_service::Insufficient_buffer) {
|
|
vfs_io_waiter.wait_for_io();
|
|
}
|
|
}
|
|
|
|
/* wake up threads blocking for 'queue_*()' or 'write()' */
|
|
_vfs_io_waiter_registry.for_each([] (Vfs_io_waiter &r) {
|
|
r.wakeup();
|
|
});
|
|
|
|
if (sysio.error.write != Vfs::File_io_service::WRITE_OK)
|
|
return false;
|
|
|
|
_fh.advance_seek(out_count);
|
|
|
|
sysio.write_out.count = out_count;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool read(Sysio &sysio) override
|
|
{
|
|
if (_dir) {
|
|
sysio.error.read = Vfs::File_io_service::READ_ERR_INVALID;
|
|
return false;
|
|
}
|
|
|
|
size_t count = min(sysio.read_in.count, sizeof(sysio.read_out.chunk));
|
|
|
|
Vfs::file_size out_count = 0;
|
|
|
|
Registered_no_delete<Vfs_io_waiter>
|
|
vfs_io_waiter(_vfs_io_waiter_registry);
|
|
|
|
while (!_fh.fs().queue_read(&_fh, count))
|
|
vfs_io_waiter.wait_for_io();
|
|
|
|
for (;;) {
|
|
|
|
sysio.error.read = _fh.fs().complete_read(&_fh, sysio.read_out.chunk, count, out_count);
|
|
|
|
if (sysio.error.read != Vfs::File_io_service::READ_QUEUED)
|
|
break;
|
|
|
|
vfs_io_waiter.wait_for_io();
|
|
}
|
|
|
|
/* wake up threads blocking for 'queue_*()' or 'write()' */
|
|
_vfs_io_waiter_registry.for_each([] (Vfs_io_waiter &r) {
|
|
r.wakeup();
|
|
});
|
|
|
|
if (sysio.error.read != Vfs::File_io_service::READ_OK)
|
|
return false;
|
|
|
|
sysio.read_out.count = out_count;
|
|
|
|
_fh.advance_seek(out_count);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool fstat(Sysio &sysio) override
|
|
{
|
|
_sync();
|
|
|
|
/*
|
|
* 'sysio.stat_in' is not used in '_fh.ds().stat()',
|
|
* so no 'sysio' member translation is needed here
|
|
*/
|
|
|
|
Vfs::Directory_service::Stat stat;
|
|
sysio.error.stat = _fh.ds().stat(_leaf_path.base(), stat);
|
|
sysio.fstat_out.st = stat;
|
|
|
|
return (sysio.error.stat == Vfs::Directory_service::STAT_OK);
|
|
}
|
|
|
|
bool ftruncate(Sysio &sysio) override
|
|
{
|
|
_sync();
|
|
|
|
sysio.error.ftruncate = _fh.fs().ftruncate(&_fh, sysio.ftruncate_in.length);
|
|
|
|
return (sysio.error.ftruncate == Vfs::File_io_service::FTRUNCATE_OK);
|
|
}
|
|
|
|
bool fcntl(Sysio &sysio) override
|
|
{
|
|
switch (sysio.fcntl_in.cmd) {
|
|
|
|
case Sysio::FCNTL_CMD_GET_FILE_STATUS_FLAGS:
|
|
|
|
sysio.fcntl_out.result = _fh.status_flags();
|
|
return true;
|
|
|
|
case Sysio::FCNTL_CMD_SET_FILE_STATUS_FLAGS:
|
|
_fh.status_flags(sysio.fcntl_in.long_arg);
|
|
return true;
|
|
|
|
default:
|
|
|
|
warning("invalid fcntl command ", (int)sysio.fcntl_in.cmd);
|
|
sysio.error.fcntl = Sysio::FCNTL_ERR_CMD_INVALID;
|
|
return false;
|
|
};
|
|
}
|
|
|
|
/*
|
|
* The 'dirent' function for the root directory only (the
|
|
* 'Dir_file_system::open()' function handles all requests referring
|
|
* to directories). Hence, '_path' is the absolute path of the
|
|
* directory to inspect.
|
|
*/
|
|
bool dirent(Sysio &sysio) override
|
|
{
|
|
/*
|
|
* Return artificial dir entries for "." and ".."
|
|
*/
|
|
unsigned const index = _fh.seek() / sizeof(Sysio::Dirent);
|
|
if (index < 2) {
|
|
sysio.dirent_out.entry.type = Vfs::Directory_service::Dirent_type::DIRECTORY;
|
|
strncpy(sysio.dirent_out.entry.name,
|
|
index ? ".." : ".",
|
|
sizeof(sysio.dirent_out.entry.name));
|
|
|
|
sysio.dirent_out.entry.fileno = 1;
|
|
_fh.advance_seek(sizeof(Sysio::Dirent));
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Delegate remaining dir-entry request to the actual file system.
|
|
* Align index range to zero when calling the directory service.
|
|
*/
|
|
|
|
Vfs::file_size noux_dirent_seek = _fh.seek();
|
|
_fh.seek((index - 2) * sizeof(Vfs::Directory_service::Dirent));
|
|
|
|
Vfs::file_size const count = sizeof(Vfs::Directory_service::Dirent);
|
|
|
|
Registered_no_delete<Vfs_io_waiter>
|
|
vfs_io_waiter(_vfs_io_waiter_registry);
|
|
|
|
while (!_fh.fs().queue_read(&_fh, count))
|
|
vfs_io_waiter.wait_for_io();
|
|
|
|
Vfs::File_io_service::Read_result read_result;
|
|
Vfs::file_size out_count = 0;
|
|
Vfs::Directory_service::Dirent dirent { };
|
|
|
|
for (;;) {
|
|
read_result = _fh.fs().complete_read(&_fh, (char*)&dirent,
|
|
count, out_count);
|
|
|
|
if (read_result != Vfs::File_io_service::READ_QUEUED)
|
|
break;
|
|
|
|
vfs_io_waiter.wait_for_io();
|
|
}
|
|
|
|
/* wake up threads blocking for 'queue_*()' or 'write()' */
|
|
_vfs_io_waiter_registry.for_each([] (Vfs_io_waiter &r) {
|
|
r.wakeup();
|
|
});
|
|
|
|
if ((read_result != Vfs::File_io_service::READ_OK) ||
|
|
(out_count != sizeof(dirent))) {
|
|
dirent = { };
|
|
}
|
|
|
|
_fh.seek(noux_dirent_seek);
|
|
|
|
sysio.dirent_out.entry = dirent;
|
|
|
|
_fh.advance_seek(sizeof(Sysio::Dirent));
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Return size of file that the I/O channel refers to
|
|
*
|
|
* Note that this function overwrites the 'sysio' argument. Do not
|
|
* call it prior saving all input arguments from the original sysio
|
|
* structure.
|
|
*/
|
|
size_t size(Sysio &sysio)
|
|
{
|
|
if (fstat(sysio))
|
|
return sysio.fstat_out.st.size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool ioctl(Sysio &sysio) override
|
|
{
|
|
Vfs::File_system::Ioctl_arg arg = (Vfs::File_system::Ioctl_arg)sysio.ioctl_in.argp;
|
|
|
|
sysio.error.ioctl = _fh.fs().ioctl(&_fh, sysio.ioctl_in.request, arg, sysio.ioctl_out);
|
|
|
|
return (sysio.error.ioctl == Vfs::File_io_service::IOCTL_OK);
|
|
}
|
|
|
|
bool lseek(Sysio &sysio) override
|
|
{
|
|
switch (sysio.lseek_in.whence) {
|
|
case Sysio::LSEEK_SET: _fh.seek(sysio.lseek_in.offset); break;
|
|
case Sysio::LSEEK_CUR: _fh.advance_seek(sysio.lseek_in.offset); break;
|
|
case Sysio::LSEEK_END:
|
|
off_t offset = sysio.lseek_in.offset;
|
|
sysio.fstat_in.fd = sysio.lseek_in.fd;
|
|
_fh.seek(size(sysio) + offset);
|
|
break;
|
|
}
|
|
sysio.lseek_out.offset = _fh.seek();
|
|
return true;
|
|
}
|
|
|
|
bool check_unblock(bool rd, bool wr, bool ex) const override
|
|
{
|
|
return _fh.fs().check_unblock(&_fh, rd, wr, ex);
|
|
}
|
|
|
|
bool path(char *path, size_t len) override
|
|
{
|
|
strncpy(path, _path.base(), len);
|
|
path[len - 1] = '\0';
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
#endif /* _NOUX__VFS_IO_CHANNEL_H_ */
|