genode/repos/libports/src/lib/libc/vfs_plugin.cc

1770 lines
41 KiB
C++

/*
* \brief Libc plugin for using a process-local virtual file system
* \author Norman Feske
* \author Christian Helmuth
* \author Emery Hemingway
* \date 2014-04-09
*/
/*
* Copyright (C) 2014-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.
*/
/* Genode includes */
#include <base/env.h>
#include <base/log.h>
#include <vfs/dir_file_system.h>
/* libc includes */
#include <errno.h>
#include <dirent.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <termios.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/disk.h>
#include <dlfcn.h>
/* libc plugin interface */
#include <libc-plugin/plugin.h>
/* libc-internal includes */
#include <internal/vfs_plugin.h>
#include <internal/mem_alloc.h>
#include <internal/errno.h>
#include <internal/init.h>
#include <internal/legacy.h>
#include <internal/suspend.h>
static Libc::Suspend *_suspend_ptr;
void Libc::init_vfs_plugin(Suspend &suspend)
{
_suspend_ptr = &suspend;
}
static void suspend(Libc::Suspend_functor &check)
{
struct Missing_call_of_init_vfs_plugin : Genode::Exception { };
if (!_suspend_ptr)
throw Missing_call_of_init_vfs_plugin();
_suspend_ptr->suspend(check);
};
static Genode::Lock &vfs_lock()
{
static Genode::Lock _vfs_lock;
return _vfs_lock;
}
#define VFS_THREAD_SAFE(code) ({ \
Genode::Lock::Guard g(vfs_lock()); \
code; \
})
static Vfs::Vfs_handle *vfs_handle(Libc::File_descriptor *fd)
{
return reinterpret_cast<Vfs::Vfs_handle *>(fd->context);
}
static Libc::Plugin_context *vfs_context(Vfs::Vfs_handle *vfs_handle)
{
return reinterpret_cast<Libc::Plugin_context *>(vfs_handle);
}
/**
* Utility to convert VFS stat struct to the libc stat struct
*
* Code shared between 'stat' and 'fstat'.
*/
static void vfs_stat_to_libc_stat_struct(Vfs::Directory_service::Stat const &src,
struct stat *dst)
{
enum { FS_BLOCK_SIZE = 4096 };
unsigned const readable_bits = S_IRUSR,
writeable_bits = S_IWUSR,
executable_bits = S_IXUSR;
auto type = [] (Vfs::Node_type type)
{
switch (type) {
case Vfs::Node_type::DIRECTORY: return S_IFDIR;
case Vfs::Node_type::CONTINUOUS_FILE: return S_IFREG;
case Vfs::Node_type::TRANSACTIONAL_FILE: return S_IFSOCK;
case Vfs::Node_type::SYMLINK: return S_IFLNK;
}
return 0;
};
*dst = { };
dst->st_uid = 0;
dst->st_gid = 0;
dst->st_mode = (src.rwx.readable ? readable_bits : 0)
| (src.rwx.writeable ? writeable_bits : 0)
| (src.rwx.executable ? executable_bits : 0)
| type(src.type);
dst->st_size = src.size;
dst->st_blksize = FS_BLOCK_SIZE;
dst->st_blocks = (dst->st_size + FS_BLOCK_SIZE - 1) / FS_BLOCK_SIZE;
dst->st_ino = src.inode;
dst->st_dev = src.device;
long long mtime = src.modification_time.value;
dst->st_mtime = mtime != Vfs::Timestamp::INVALID ? mtime : 0;
dst->st_nlink = 1;
}
static Genode::Xml_node *_config_node;
char const *libc_resolv_path;
namespace Libc {
Xml_node config() __attribute__((weak));
Xml_node config()
{
if (!_config_node) {
error("libc config not initialized - aborting");
exit(1);
}
return *_config_node;
}
class Config_attr
{
private:
typedef String<Vfs::MAX_PATH_LEN> Value;
Value const _value;
public:
Config_attr(char const *attr_name, char const *default_value)
:
_value(config().attribute_value(attr_name,
Value(default_value)))
{ }
char const *string() const { return _value.string(); }
};
char const *config_pipe() __attribute__((weak));
char const *config_pipe()
{
static Config_attr attr("pipe", "");
return attr.string();
}
char const *config_rng() __attribute__((weak));
char const *config_rng()
{
static Config_attr rng("rng", "");
return rng.string();
}
char const *config_socket() __attribute__((weak));
char const *config_socket()
{
static Config_attr socket("socket", "");
return socket.string();
}
char const *config_nameserver_file() __attribute__((weak));
char const *config_nameserver_file()
{
static Config_attr ns_file("nameserver_file",
"/socket/nameserver");
return ns_file.string();
}
void libc_config_init(Xml_node node)
{
static Xml_node config = node;
_config_node = &config;
libc_resolv_path = config_nameserver_file();
}
void notify_read_ready(Vfs::Vfs_handle *handle)
{
/*
* If this call fails, the VFS plugin is expected to call the IO
* handler when the notification request can be processed. The
* libc IO handler will then call 'notify_read_ready()' again
* via 'select_notify()'.
*/
VFS_THREAD_SAFE(handle->fs().notify_read_ready(handle));
}
bool read_ready(File_descriptor *fd)
{
Vfs::Vfs_handle *handle = vfs_handle(fd);
if (!handle) return false;
notify_read_ready(handle);
return VFS_THREAD_SAFE(handle->fs().read_ready(handle));
}
}
template <typename FN>
void Libc::Vfs_plugin::_with_info(File_descriptor &fd, FN const &fn)
{
if (!_root_dir.constructed())
return;
Absolute_path path = ioctl_dir(fd);
path.append_element("info");
try {
Lock::Guard g(vfs_lock());
File_content const content(_alloc, *_root_dir, path.string(),
File_content::Limit{4096U});
content.xml([&] (Xml_node node) {
fn(node); });
} catch (...) { }
}
int Libc::Vfs_plugin::access(const char *path, int amode)
{
if (VFS_THREAD_SAFE(_root_fs.leaf_path(path)))
return 0;
errno = ENOENT;
return -1;
}
Libc::File_descriptor *Libc::Vfs_plugin::open(char const *path, int flags,
int libc_fd)
{
if (VFS_THREAD_SAFE(_root_fs.directory(path))) {
if (((flags & O_ACCMODE) != O_RDONLY)) {
errno = EISDIR;
return nullptr;
}
flags |= O_DIRECTORY;
Vfs::Vfs_handle *handle = 0;
typedef Vfs::Directory_service::Opendir_result Opendir_result;
switch (VFS_THREAD_SAFE(_root_fs.opendir(path, false, &handle, _alloc))) {
case Opendir_result::OPENDIR_OK: break;
case Opendir_result::OPENDIR_ERR_LOOKUP_FAILED: errno = ENOENT; return nullptr;
case Opendir_result::OPENDIR_ERR_NAME_TOO_LONG: errno = ENAMETOOLONG; return nullptr;
case Opendir_result::OPENDIR_ERR_NODE_ALREADY_EXISTS: errno = EEXIST; return nullptr;
case Opendir_result::OPENDIR_ERR_NO_SPACE: errno = ENOSPC; return nullptr;
case Opendir_result::OPENDIR_ERR_OUT_OF_RAM:
case Opendir_result::OPENDIR_ERR_OUT_OF_CAPS:
case Opendir_result::OPENDIR_ERR_PERMISSION_DENIED: errno = EPERM; return nullptr;
}
/* the directory was successfully opened */
File_descriptor *fd =
file_descriptor_allocator()->alloc(this, vfs_context(handle), libc_fd);
/* FIXME error cleanup code leaks resources! */
if (!fd) {
VFS_THREAD_SAFE(handle->close());
errno = EMFILE;
return nullptr;
}
handle->handler(&_response_handler);
fd->flags = flags & O_ACCMODE;
return fd;
}
if (flags & O_DIRECTORY) {
errno = ENOTDIR;
return nullptr;
}
typedef Vfs::Directory_service::Open_result Result;
Vfs::Vfs_handle *handle = 0;
while (handle == nullptr) {
switch (VFS_THREAD_SAFE(_root_fs.open(path, flags, &handle, _alloc))) {
case Result::OPEN_OK:
break;
case Result::OPEN_ERR_UNACCESSIBLE:
{
if (!(flags & O_CREAT)) {
if (flags & O_NOFOLLOW) {
errno = ELOOP;
return 0;
}
errno = ENOENT;
return 0;
}
/* O_CREAT is set, so try to create the file */
switch (VFS_THREAD_SAFE(_root_fs.open(path, flags | O_EXCL, &handle, _alloc))) {
case Result::OPEN_OK:
break;
case Result::OPEN_ERR_EXISTS:
/* file has been created by someone else in the meantime */
if (flags & O_NOFOLLOW) {
errno = ELOOP;
return 0;
}
errno = EEXIST;
return 0;
case Result::OPEN_ERR_NO_PERM: errno = EPERM; return 0;
case Result::OPEN_ERR_UNACCESSIBLE: errno = ENOENT; return 0;
case Result::OPEN_ERR_NAME_TOO_LONG: errno = ENAMETOOLONG; return 0;
case Result::OPEN_ERR_NO_SPACE: errno = ENOSPC; return 0;
case Result::OPEN_ERR_OUT_OF_RAM: errno = ENOSPC; return 0;
case Result::OPEN_ERR_OUT_OF_CAPS: errno = ENOSPC; return 0;
}
}
break;
case Result::OPEN_ERR_NO_PERM: errno = EPERM; return 0;
case Result::OPEN_ERR_EXISTS: errno = EEXIST; return 0;
case Result::OPEN_ERR_NAME_TOO_LONG: errno = ENAMETOOLONG; return 0;
case Result::OPEN_ERR_NO_SPACE: errno = ENOSPC; return 0;
case Result::OPEN_ERR_OUT_OF_RAM: errno = ENOSPC; return 0;
case Result::OPEN_ERR_OUT_OF_CAPS: errno = ENOSPC; return 0;
}
}
/* the file was successfully opened */
File_descriptor *fd =
file_descriptor_allocator()->alloc(this, vfs_context(handle), libc_fd);
/* FIXME error cleanup code leaks resources! */
if (!fd) {
VFS_THREAD_SAFE(handle->close());
errno = EMFILE;
return nullptr;
}
handle->handler(&_response_handler);
fd->flags = flags & (O_ACCMODE|O_NONBLOCK|O_APPEND);
if ((flags & O_TRUNC) && (ftruncate(fd, 0) == -1)) {
VFS_THREAD_SAFE(handle->close());
errno = EINVAL; /* XXX which error code fits best ? */
return nullptr;
}
return fd;
}
void Libc::Vfs_plugin::_vfs_write_mtime(Vfs::Vfs_handle &handle)
{
struct timespec ts;
if (_update_mtime == Update_mtime::NO)
return;
/* XXX using clock_gettime directly is probably not the best idea */
if (clock_gettime(CLOCK_REALTIME, &ts) < 0) {
ts.tv_sec = 0;
}
Vfs::Timestamp time { .value = (long long)ts.tv_sec };
struct Check : Libc::Suspend_functor
{
bool retry { false };
Vfs::Vfs_handle &vfs_handle;
Vfs::Timestamp &time;
Check(Vfs::Vfs_handle &vfs_handle, Vfs::Timestamp &time)
: vfs_handle(vfs_handle), time(time) { }
bool suspend() override
{
retry = !vfs_handle.fs().update_modification_timestamp(&vfs_handle, time);
return retry;
}
} check(handle, time);
do {
_suspend_ptr->suspend(check);
} while (check.retry);
}
int Libc::Vfs_plugin::_vfs_sync(Vfs::Vfs_handle &vfs_handle)
{
_vfs_write_mtime(vfs_handle);
typedef Vfs::File_io_service::Sync_result Result;
Result result = Result::SYNC_QUEUED;
{
struct Check : Suspend_functor
{
bool retry { false };
Vfs::Vfs_handle &vfs_handle;
Check(Vfs::Vfs_handle &vfs_handle)
: vfs_handle(vfs_handle) { }
bool suspend() override
{
retry = !VFS_THREAD_SAFE(vfs_handle.fs().queue_sync(&vfs_handle));
return retry;
}
} check(vfs_handle);
/*
* Cannot call suspend() immediately, because the Libc kernel
* might not be running yet.
*/
if (!VFS_THREAD_SAFE(vfs_handle.fs().queue_sync(&vfs_handle))) {
do {
suspend(check);
} while (check.retry);
}
}
{
struct Check : Suspend_functor
{
bool retry { false };
Vfs::Vfs_handle &vfs_handle;
Result &result;
Check(Vfs::Vfs_handle &vfs_handle, Result &result)
: vfs_handle(vfs_handle), result(result) { }
bool suspend() override
{
result = VFS_THREAD_SAFE(vfs_handle.fs().complete_sync(&vfs_handle));
retry = result == Vfs::File_io_service::SYNC_QUEUED;
return retry;
}
} check(vfs_handle, result);
/*
* Cannot call suspend() immediately, because the Libc kernel
* might not be running yet.
*/
result = VFS_THREAD_SAFE(vfs_handle.fs().complete_sync(&vfs_handle));
if (result == Result::SYNC_QUEUED) {
do {
suspend(check);
} while (check.retry);
}
}
return result == Result::SYNC_OK ? 0 : Errno(EIO);
}
int Libc::Vfs_plugin::close(File_descriptor *fd)
{
Vfs::Vfs_handle *handle = vfs_handle(fd);
if ((fd->modified) || (fd->flags & O_CREAT)) {
_vfs_sync(*handle);
}
VFS_THREAD_SAFE(handle->close());
file_descriptor_allocator()->free(fd);
return 0;
}
int Libc::Vfs_plugin::dup2(File_descriptor *fd,
File_descriptor *new_fd)
{
Vfs::Vfs_handle *handle = nullptr;
typedef Vfs::Directory_service::Open_result Result;
if (VFS_THREAD_SAFE(_root_fs.open(fd->fd_path, fd->flags, &handle, _alloc))
!= Result::OPEN_OK) {
warning("dup2 failed for path ", fd->fd_path);
return Errno(EBADF);
}
handle->seek(vfs_handle(fd)->seek());
handle->handler(&_response_handler);
new_fd->context = vfs_context(handle);
new_fd->flags = fd->flags;
new_fd->path(fd->fd_path);
return new_fd->libc_fd;
}
Libc::File_descriptor *Libc::Vfs_plugin::dup(File_descriptor *fd)
{
Vfs::Vfs_handle *handle = nullptr;
typedef Vfs::Directory_service::Open_result Result;
if (VFS_THREAD_SAFE(_root_fs.open(fd->fd_path, fd->flags, &handle, _alloc))
!= Result::OPEN_OK) {
warning("dup failed for path ", fd->fd_path);
errno = EBADF;
return nullptr;
}
handle->seek(vfs_handle(fd)->seek());
handle->handler(&_response_handler);
File_descriptor * const new_fd =
file_descriptor_allocator()->alloc(this, vfs_context(handle));
new_fd->flags = fd->flags;
new_fd->path(fd->fd_path);
return new_fd;
}
int Libc::Vfs_plugin::fstat(File_descriptor *fd, struct stat *buf)
{
Vfs::Vfs_handle *handle = vfs_handle(fd);
if (fd->modified) {
_vfs_sync(*handle);
fd->modified = false;
}
int const result = stat(fd->fd_path, buf);
/*
* The libc expects stdout to be a character device.
* If 'st_mode' is set to 'S_IFREG', 'printf' does not work.
*/
if (fd->libc_fd == 1) {
buf->st_mode &= ~S_IFMT;
buf->st_mode |= S_IFCHR;
}
return result;
}
int Libc::Vfs_plugin::fstatfs(File_descriptor *fd, struct statfs *buf)
{
if (!fd || !buf)
return Errno(EFAULT);
Genode::memset(buf, 0, sizeof(*buf));
buf->f_flags = MNT_UNION;
return 0;
}
int Libc::Vfs_plugin::mkdir(const char *path, mode_t mode)
{
Vfs::Vfs_handle *dir_handle { 0 };
typedef Vfs::Directory_service::Opendir_result Opendir_result;
switch (VFS_THREAD_SAFE(_root_fs.opendir(path, true, &dir_handle, _alloc))) {
case Opendir_result::OPENDIR_OK:
VFS_THREAD_SAFE(dir_handle->close());
break;
case Opendir_result::OPENDIR_ERR_LOOKUP_FAILED:
return Errno(ENOENT);
case Opendir_result::OPENDIR_ERR_NAME_TOO_LONG:
return Errno(ENAMETOOLONG);
case Opendir_result::OPENDIR_ERR_NODE_ALREADY_EXISTS:
return Errno(EEXIST);
case Opendir_result::OPENDIR_ERR_NO_SPACE:
return Errno(ENOSPC);
case Opendir_result::OPENDIR_ERR_OUT_OF_RAM:
case Opendir_result::OPENDIR_ERR_OUT_OF_CAPS:
case Opendir_result::OPENDIR_ERR_PERMISSION_DENIED:
return Errno(EPERM);
}
return 0;
}
int Libc::Vfs_plugin::stat(char const *path, struct stat *buf)
{
if (!path or !buf) {
errno = EFAULT;
return -1;
}
typedef Vfs::Directory_service::Stat_result Result;
Vfs::Directory_service::Stat stat;
switch (VFS_THREAD_SAFE(_root_fs.stat(path, stat))) {
case Result::STAT_ERR_NO_ENTRY: errno = ENOENT; return -1;
case Result::STAT_ERR_NO_PERM: errno = EACCES; return -1;
case Result::STAT_OK: break;
}
vfs_stat_to_libc_stat_struct(stat, buf);
return 0;
}
ssize_t Libc::Vfs_plugin::write(File_descriptor *fd, const void *buf,
::size_t count)
{
typedef Vfs::File_io_service::Write_result Result;
if ((fd->flags & O_ACCMODE) == O_RDONLY) {
return Errno(EBADF);
}
Vfs::Vfs_handle *handle = vfs_handle(fd);
Vfs::file_size out_count = 0;
Result out_result = Result::WRITE_OK;
if (fd->flags & O_NONBLOCK) {
try {
out_result = VFS_THREAD_SAFE(handle->fs().write(handle, (char const *)buf, count, out_count));
Plugin::resume_all();
} catch (Vfs::File_io_service::Insufficient_buffer) { }
} else {
Vfs::file_size const initial_seek { handle->seek() };
struct Check : Suspend_functor
{
bool retry { false };
Vfs::File_system &_root_fs;
char const * const _fd_path;
Vfs::Vfs_handle *_handle;
void const *_buf;
::size_t _count;
::off_t _offset = 0;
Vfs::file_size &_out_count;
Result &_out_result;
unsigned _iteration = 0;
bool _fd_refers_to_continuous_file()
{
if (!_fd_path) {
warning("Vfs_plugin: _fd_refers_to_continuous_file: missing fd_path");
return false;
}
typedef Vfs::Directory_service::Stat_result Result;
Vfs::Directory_service::Stat stat { };
if (VFS_THREAD_SAFE(_root_fs.stat(_fd_path, stat)) != Result::STAT_OK)
return false;
return stat.type == Vfs::Node_type::CONTINUOUS_FILE;
};
Check(Vfs::File_system &root_fs, char const *fd_path,
Vfs::Vfs_handle *handle, void const *buf,
::size_t count, Vfs::file_size &out_count,
Result &out_result)
:
_root_fs(root_fs), _fd_path(fd_path), _handle(handle), _buf(buf),
_count(count), _out_count(out_count), _out_result(out_result)
{ }
bool suspend() override
{
for (;;) {
/* number of bytes written in one iteration */
Vfs::file_size partial_out_count = 0;
try {
char const * const src = (char const *)_buf + _offset;
_out_result = VFS_THREAD_SAFE(_handle->fs().write(_handle, src,
_count, partial_out_count));
} catch (Vfs::File_io_service::Insufficient_buffer) {
retry = true;
return retry;
}
if (_out_result != Result::WRITE_OK) {
retry = false;
return retry;
}
/* increment byte count reported to caller */
_out_count += partial_out_count;
bool const write_complete = (partial_out_count == _count);
if (write_complete) {
retry = false;
return retry;
}
/*
* If the write has not consumed all bytes, set up
* another partial write iteration with the remaining
* bytes as 'count'.
*
* The costly 'fd_refers_to_continuous_file' is called
* for the first iteration only.
*/
bool const continuous_file = (_iteration > 0 || _fd_refers_to_continuous_file());
if (!continuous_file) {
warning("partial write on transactional file");
_out_result = Result::WRITE_ERR_IO;
retry = false;
return retry;
}
_iteration++;
bool const stalled = (partial_out_count == 0);
if (stalled) {
retry = true;
return retry;
}
/* issue new write operation for remaining bytes */
_count -= partial_out_count;
_offset += partial_out_count;
_handle->advance_seek(partial_out_count);
}
}
} check(_root_fs, fd->fd_path, handle, buf, count, out_count, out_result);
do {
suspend(check);
} while (check.retry);
/* XXX reset seek pointer after loop (will be advanced below by out_count) */
handle->seek(initial_seek);
}
Plugin::resume_all();
switch (out_result) {
case Result::WRITE_ERR_AGAIN: return Errno(EAGAIN);
case Result::WRITE_ERR_WOULD_BLOCK: return Errno(EWOULDBLOCK);
case Result::WRITE_ERR_INVALID: return Errno(EINVAL);
case Result::WRITE_ERR_IO: return Errno(EIO);
case Result::WRITE_ERR_INTERRUPT: return Errno(EINTR);
case Result::WRITE_OK: break;
}
VFS_THREAD_SAFE(handle->advance_seek(out_count));
fd->modified = true;
return out_count;
}
ssize_t Libc::Vfs_plugin::read(File_descriptor *fd, void *buf,
::size_t count)
{
dispatch_pending_io_signals();
if ((fd->flags & O_ACCMODE) == O_WRONLY) {
return Errno(EBADF);
}
typedef Vfs::File_io_service::Read_result Result;
Vfs::Vfs_handle *handle = vfs_handle(fd);
if (fd->flags & O_DIRECTORY)
return Errno(EISDIR);
if (fd->flags & O_NONBLOCK && !read_ready(fd))
return Errno(EAGAIN);
{
struct Check : Suspend_functor
{
bool retry { false };
Vfs::Vfs_handle *handle;
::size_t count;
Check(Vfs::Vfs_handle *handle, ::size_t count)
: handle(handle), count(count) { }
bool suspend() override
{
retry = !VFS_THREAD_SAFE(handle->fs().queue_read(handle, count));
return retry;
}
} check ( handle, count);
do {
suspend(check);
} while (check.retry);
}
Vfs::file_size out_count = 0;
Result out_result;
{
struct Check : Suspend_functor
{
bool retry { false };
Vfs::Vfs_handle *handle;
void *buf;
::size_t count;
Vfs::file_size &out_count;
Result &out_result;
Check(Vfs::Vfs_handle *handle, void *buf, ::size_t count,
Vfs::file_size &out_count, Result &out_result)
: handle(handle), buf(buf), count(count), out_count(out_count),
out_result(out_result)
{ }
bool suspend() override
{
out_result = VFS_THREAD_SAFE(handle->fs().complete_read(handle, (char *)buf,
count, out_count));
/* suspend me if read is still queued */
retry = (out_result == Result::READ_QUEUED);
return retry;
}
} check ( handle, buf, count, out_count, out_result);
do {
suspend(check);
} while (check.retry);
}
Plugin::resume_all();
switch (out_result) {
case Result::READ_ERR_AGAIN: return Errno(EAGAIN);
case Result::READ_ERR_WOULD_BLOCK: return Errno(EWOULDBLOCK);
case Result::READ_ERR_INVALID: return Errno(EINVAL);
case Result::READ_ERR_IO: return Errno(EIO);
case Result::READ_ERR_INTERRUPT: return Errno(EINTR);
case Result::READ_OK: break;
case Result::READ_QUEUED: /* handled above, so never reached */ break;
}
VFS_THREAD_SAFE(handle->advance_seek(out_count));
return out_count;
}
ssize_t Libc::Vfs_plugin::getdirentries(File_descriptor *fd, char *buf,
::size_t nbytes, ::off_t *basep)
{
if (nbytes < sizeof(struct dirent)) {
error("getdirentries: buffer too small");
return -1;
}
typedef Vfs::File_io_service::Read_result Result;
Vfs::Vfs_handle *handle = vfs_handle(fd);
typedef Vfs::Directory_service::Dirent Dirent;
Dirent dirent_out;
{
struct Check : Suspend_functor
{
bool retry { false };
Vfs::Vfs_handle *handle;
Check(Vfs::Vfs_handle *handle)
: handle(handle) { }
bool suspend() override
{
retry = !VFS_THREAD_SAFE(handle->fs().queue_read(handle, sizeof(Dirent)));
return retry;
}
} check(handle);
do {
suspend(check);
} while (check.retry);
}
Result out_result;
Vfs::file_size out_count;
{
struct Check : Suspend_functor
{
bool retry { false };
Vfs::Vfs_handle *handle;
Dirent &dirent_out;
Vfs::file_size &out_count;
Result &out_result;
Check(Vfs::Vfs_handle *handle, Dirent &dirent_out,
Vfs::file_size &out_count, Result &out_result)
: handle(handle), dirent_out(dirent_out), out_count(out_count),
out_result(out_result) { }
bool suspend() override
{
out_result = VFS_THREAD_SAFE(handle->fs().complete_read(handle,
(char*)&dirent_out,
sizeof(Dirent),
out_count));
dirent_out.sanitize();
/* suspend me if read is still queued */
retry = (out_result == Result::READ_QUEUED);
return retry;
}
} check(handle, dirent_out, out_count, out_result);
do {
suspend(check);
} while (check.retry);
}
Plugin::resume_all();
if ((out_result != Result::READ_OK) ||
(out_count < sizeof(Dirent))) {
return 0;
}
using Dirent_type = Vfs::Directory_service::Dirent_type;
if (dirent_out.type == Dirent_type::END)
return 0;
/*
* Convert dirent structure from VFS to libc
*/
auto dirent_type = [] (Dirent_type type)
{
switch (type) {
case Dirent_type::DIRECTORY: return DT_DIR;
case Dirent_type::CONTINUOUS_FILE: return DT_REG;
case Dirent_type::TRANSACTIONAL_FILE: return DT_SOCK;
case Dirent_type::SYMLINK: return DT_LNK;
case Dirent_type::END: return DT_UNKNOWN;
}
return DT_UNKNOWN;
};
dirent &dirent = *(struct dirent *)buf;
dirent = { };
dirent.d_type = dirent_type(dirent_out.type);
dirent.d_fileno = dirent_out.fileno;
dirent.d_reclen = sizeof(struct dirent);
Genode::strncpy(dirent.d_name, dirent_out.name.buf, sizeof(dirent.d_name));
dirent.d_namlen = Genode::strlen(dirent.d_name);
/*
* Keep track of VFS seek pointer and user-supplied basep.
*/
VFS_THREAD_SAFE(handle->advance_seek(sizeof(Vfs::Directory_service::Dirent)));
*basep += sizeof(struct dirent);
return sizeof(struct dirent);
}
int Libc::Vfs_plugin::ioctl(File_descriptor *fd, int request, char *argp)
{
bool handled = false;
if (request == TIOCGWINSZ) {
if (!argp)
return Errno(EINVAL);
_with_info(*fd, [&] (Xml_node info) {
if (info.type() == "terminal") {
::winsize *winsize = (::winsize *)argp;
winsize->ws_row = info.attribute_value("rows", 25U);
winsize->ws_col = info.attribute_value("columns", 80U);
handled = true;
}
});
} else if (request == TIOCGETA) {
::termios *termios = (::termios *)argp;
termios->c_iflag = 0;
termios->c_oflag = 0;
termios->c_cflag = 0;
/*
* Set 'ECHO' flag, needed by libreadline. Otherwise, echoing
* user input doesn't work in bash.
*/
termios->c_lflag = ECHO;
::memset(termios->c_cc, _POSIX_VDISABLE, sizeof(termios->c_cc));
termios->c_ispeed = 0;
termios->c_ospeed = 0;
handled = true;
}
if (handled)
return 0;
return _legacy_ioctl(fd, request, argp);
}
/**
* Fallback for ioctl operations targeting the deprecated VFS ioctl interface
*
* XXX Remove this method once all ioctl operations are supported via
* regular VFS file accesses.
*/
int Libc::Vfs_plugin::_legacy_ioctl(File_descriptor *fd, int request, char *argp)
{
using ::off_t;
/*
* Marshal ioctl arguments
*/
typedef Vfs::File_io_service::Ioctl_opcode Opcode;
Opcode opcode = Opcode::IOCTL_OP_UNDEFINED;
Vfs::File_io_service::Ioctl_arg arg = 0;
switch (request) {
case TIOCGWINSZ:
{
if (!argp) {
errno = EINVAL;
return -1;
}
opcode = Opcode::IOCTL_OP_TIOCGWINSZ;
break;
}
case TIOCSETAF:
{
opcode = Opcode::IOCTL_OP_TIOCSETAF;
::termios *termios = (::termios *)argp;
/*
* For now only enabling/disabling of ECHO is supported
*/
if (termios->c_lflag & (ECHO | ECHONL)) {
arg = (Vfs::File_io_service::IOCTL_VAL_ECHO |
Vfs::File_io_service::IOCTL_VAL_ECHONL);
}
else {
arg = Vfs::File_io_service::IOCTL_VAL_NULL;
}
break;
}
case TIOCSETAW:
{
opcode = Opcode::IOCTL_OP_TIOCSETAW;
arg = argp ? *(int*)argp : 0;
break;
}
case FIONBIO:
{
opcode = Opcode::IOCTL_OP_FIONBIO;
arg = argp ? *(int*)argp : 0;
break;
}
case DIOCGMEDIASIZE:
{
if (!argp) {
errno = EINVAL;
return -1;
}
opcode = Opcode::IOCTL_OP_DIOCGMEDIASIZE;
arg = 0;
break;
}
default:
warning("unsupported ioctl (request=", Hex(request), ")");
break;
}
if (opcode == Opcode::IOCTL_OP_UNDEFINED) {
errno = ENOTTY;
return -1;
}
typedef Vfs::File_io_service::Ioctl_result Result;
Vfs::File_io_service::Ioctl_out out;
Genode::memset(&out, 0, sizeof(out));
Vfs::Vfs_handle *handle = vfs_handle(fd);
switch (VFS_THREAD_SAFE(handle->fs().ioctl(handle, opcode, arg, out))) {
case Result::IOCTL_ERR_INVALID: errno = EINVAL; return -1;
case Result::IOCTL_ERR_NOTTY: errno = ENOTTY; return -1;
case Result::IOCTL_OK: break;
}
/*
* Unmarshal ioctl results
*/
switch (request) {
case TIOCGWINSZ:
{
::winsize *winsize = (::winsize *)argp;
winsize->ws_row = out.tiocgwinsz.rows;
winsize->ws_col = out.tiocgwinsz.columns;
return 0;
}
case TIOCSETAF:
case TIOCSETAW:
return 0;
case FIONBIO:
return 0;
case DIOCGMEDIASIZE:
{
/* resolve ambiguity with libc type */
using Genode::int64_t;
int64_t *disk_size = (int64_t*)argp;
*disk_size = out.diocgmediasize.size;
return 0;
}
default:
break;
}
return -1;
}
::off_t Libc::Vfs_plugin::lseek(File_descriptor *fd, ::off_t offset, int whence)
{
Vfs::Vfs_handle *handle = vfs_handle(fd);
switch (whence) {
case SEEK_SET: handle->seek(offset); break;
case SEEK_CUR: handle->advance_seek(offset); break;
case SEEK_END:
{
struct stat stat;
::memset(&stat, 0, sizeof(stat));
fstat(fd, &stat);
handle->seek(stat.st_size + offset);
}
break;
}
return handle->seek();
}
int Libc::Vfs_plugin::ftruncate(File_descriptor *fd, ::off_t length)
{
Vfs::Vfs_handle *handle = vfs_handle(fd);
if (fd->modified) {
_vfs_sync(*handle);
fd->modified = false;
}
typedef Vfs::File_io_service::Ftruncate_result Result;
switch (VFS_THREAD_SAFE(handle->fs().ftruncate(handle, length))) {
case Result::FTRUNCATE_ERR_NO_PERM: errno = EPERM; return -1;
case Result::FTRUNCATE_ERR_INTERRUPT: errno = EINTR; return -1;
case Result::FTRUNCATE_ERR_NO_SPACE: errno = ENOSPC; return -1;
case Result::FTRUNCATE_OK: break;
}
return 0;
}
int Libc::Vfs_plugin::fcntl(File_descriptor *fd, int cmd, long arg)
{
switch (cmd) {
case F_DUPFD_CLOEXEC:
case F_DUPFD:
{
/*
* Allocate free file descriptor locally.
*/
File_descriptor *new_fd =
file_descriptor_allocator()->alloc(this, 0);
if (!new_fd) return Errno(EMFILE);
/*
* Use new allocated number as name of file descriptor
* duplicate.
*/
if (Vfs_plugin::dup2(fd, new_fd) == -1) {
error("Plugin::fcntl: dup2 unexpectedly failed");
return Errno(EINVAL);
}
return new_fd->libc_fd;
}
case F_GETFD: return fd->cloexec ? FD_CLOEXEC : 0;
case F_SETFD: fd->cloexec = arg == FD_CLOEXEC; return 0;
case F_GETFL: return fd->flags;
case F_SETFL: {
/* only the specified flags may be changed */
long const mask = (O_NONBLOCK | O_APPEND | O_ASYNC | O_FSYNC);
fd->flags = (fd->flags & ~mask) | (arg & mask);
} return 0;
default:
break;
}
error("fcntl(): command ", cmd, " not supported - vfs");
return Errno(EINVAL);
}
int Libc::Vfs_plugin::fsync(File_descriptor *fd)
{
Vfs::Vfs_handle *handle = vfs_handle(fd);
if (!fd->modified) { return 0; }
/*
* XXX checking the return value of _vfs_sync amd returning -1
* in the EIO case will break the lighttpd or rather the socket_fs
*/
fd->modified = !!_vfs_sync(*handle);
return 0;
}
int Libc::Vfs_plugin::symlink(const char *oldpath, const char *newpath)
{
typedef Vfs::Directory_service::Openlink_result Openlink_result;
Vfs::Vfs_handle *handle { 0 };
Openlink_result openlink_result =
VFS_THREAD_SAFE(_root_fs.openlink(newpath, true, &handle, _alloc));
switch (openlink_result) {
case Openlink_result::OPENLINK_OK:
break;
case Openlink_result::OPENLINK_ERR_LOOKUP_FAILED:
return Errno(ENOENT);
case Openlink_result::OPENLINK_ERR_NAME_TOO_LONG:
return Errno(ENAMETOOLONG);
case Openlink_result::OPENLINK_ERR_NODE_ALREADY_EXISTS:
return Errno(EEXIST);
case Openlink_result::OPENLINK_ERR_NO_SPACE:
return Errno(ENOSPC);
case Openlink_result::OPENLINK_ERR_OUT_OF_RAM:
return Errno(ENOSPC);
case Openlink_result::OPENLINK_ERR_OUT_OF_CAPS:
return Errno(ENOSPC);
case Vfs::Directory_service::OPENLINK_ERR_PERMISSION_DENIED:
return Errno(EPERM);
}
Vfs::file_size count = ::strlen(oldpath) + 1;
Vfs::file_size out_count = 0;
handle->handler(&_response_handler);
struct Check : Suspend_functor
{
bool retry { false };
Vfs::Vfs_handle *handle;
void const *buf;
::size_t count;
Vfs::file_size &out_count;
Check(Vfs::Vfs_handle *handle, void const *buf,
::size_t count, Vfs::file_size &out_count)
: handle(handle), buf(buf), count(count), out_count(out_count)
{ }
bool suspend() override
{
try {
VFS_THREAD_SAFE(handle->fs().write(handle, (char const *)buf,
count, out_count));
retry = false;
} catch (Vfs::File_io_service::Insufficient_buffer) {
retry = true;
}
return retry;
}
} check ( handle, oldpath, count, out_count);
do {
suspend(check);
} while (check.retry);
Plugin::resume_all();
_vfs_sync(*handle);
VFS_THREAD_SAFE(handle->close());
if (out_count != count)
return Errno(ENAMETOOLONG);
return 0;
}
ssize_t Libc::Vfs_plugin::readlink(const char *path, char *buf, ::size_t buf_size)
{
Vfs::Vfs_handle *symlink_handle { 0 };
Vfs::Directory_service::Openlink_result openlink_result =
VFS_THREAD_SAFE(_root_fs.openlink(path, false, &symlink_handle, _alloc));
switch (openlink_result) {
case Vfs::Directory_service::OPENLINK_OK:
break;
case Vfs::Directory_service::OPENLINK_ERR_LOOKUP_FAILED:
return Errno(ENOENT);
case Vfs::Directory_service::OPENLINK_ERR_NAME_TOO_LONG:
/* should not happen */
return Errno(ENAMETOOLONG);
case Vfs::Directory_service::OPENLINK_ERR_NODE_ALREADY_EXISTS:
case Vfs::Directory_service::OPENLINK_ERR_NO_SPACE:
case Vfs::Directory_service::OPENLINK_ERR_OUT_OF_RAM:
case Vfs::Directory_service::OPENLINK_ERR_OUT_OF_CAPS:
case Vfs::Directory_service::OPENLINK_ERR_PERMISSION_DENIED:
return Errno(EACCES);
}
symlink_handle->handler(&_response_handler);
{
struct Check : Suspend_functor
{
bool retry { false };
Vfs::Vfs_handle *symlink_handle;
::size_t const buf_size;
Check(Vfs::Vfs_handle *symlink_handle,
::size_t const buf_size)
: symlink_handle(symlink_handle), buf_size(buf_size) { }
bool suspend() override
{
retry =
!VFS_THREAD_SAFE(symlink_handle->fs().queue_read(symlink_handle, buf_size));
return retry;
}
} check(symlink_handle, buf_size);
do {
suspend(check);
} while (check.retry);
}
typedef Vfs::File_io_service::Read_result Result;
Result out_result;
Vfs::file_size out_len = 0;
{
struct Check : Suspend_functor
{
bool retry { false };
Vfs::Vfs_handle *symlink_handle;
char *buf;
::size_t const buf_size;
Vfs::file_size &out_len;
Result &out_result;
Check(Vfs::Vfs_handle *symlink_handle,
char *buf,
::size_t const buf_size,
Vfs::file_size &out_len,
Result &out_result)
: symlink_handle(symlink_handle), buf(buf), buf_size(buf_size),
out_len(out_len), out_result(out_result) { }
bool suspend() override
{
out_result = VFS_THREAD_SAFE(symlink_handle->fs().complete_read(symlink_handle, buf, buf_size, out_len));
/* suspend me if read is still queued */
retry = (out_result == Result::READ_QUEUED);
return retry;
}
} check(symlink_handle, buf, buf_size, out_len, out_result);
do {
suspend(check);
} while (check.retry);
}
Plugin::resume_all();
switch (out_result) {
case Result::READ_ERR_AGAIN: return Errno(EAGAIN);
case Result::READ_ERR_WOULD_BLOCK: return Errno(EWOULDBLOCK);
case Result::READ_ERR_INVALID: return Errno(EINVAL);
case Result::READ_ERR_IO: return Errno(EIO);
case Result::READ_ERR_INTERRUPT: return Errno(EINTR);
case Result::READ_OK: break;
case Result::READ_QUEUED: /* handled above, so never reached */ break;
};
VFS_THREAD_SAFE(symlink_handle->close());
return out_len;
}
int Libc::Vfs_plugin::rmdir(char const *path)
{
return unlink(path);
}
int Libc::Vfs_plugin::unlink(char const *path)
{
typedef Vfs::Directory_service::Unlink_result Result;
switch (VFS_THREAD_SAFE(_root_fs.unlink(path))) {
case Result::UNLINK_ERR_NO_ENTRY: errno = ENOENT; return -1;
case Result::UNLINK_ERR_NO_PERM: errno = EPERM; return -1;
case Result::UNLINK_ERR_NOT_EMPTY: errno = ENOTEMPTY; return -1;
case Result::UNLINK_OK: break;
}
return 0;
}
int Libc::Vfs_plugin::rename(char const *from_path, char const *to_path)
{
typedef Vfs::Directory_service::Rename_result Result;
if (VFS_THREAD_SAFE(_root_fs.leaf_path(to_path))) {
if (VFS_THREAD_SAFE(_root_fs.directory(to_path))) {
if (!VFS_THREAD_SAFE(_root_fs.directory(from_path))) {
errno = EISDIR; return -1;
}
if (VFS_THREAD_SAFE(_root_fs.num_dirent(to_path))) {
errno = ENOTEMPTY; return -1;
}
} else {
if (VFS_THREAD_SAFE(_root_fs.directory(from_path))) {
errno = ENOTDIR; return -1;
}
}
}
switch (VFS_THREAD_SAFE(_root_fs.rename(from_path, to_path))) {
case Result::RENAME_ERR_NO_ENTRY: errno = ENOENT; return -1;
case Result::RENAME_ERR_CROSS_FS: errno = EXDEV; return -1;
case Result::RENAME_ERR_NO_PERM: errno = EPERM; return -1;
case Result::RENAME_OK: break;
}
return 0;
}
void *Libc::Vfs_plugin::mmap(void *addr_in, ::size_t length, int prot, int flags,
File_descriptor *fd, ::off_t offset)
{
if (prot != PROT_READ && !(prot == (PROT_READ | PROT_WRITE) && flags == MAP_PRIVATE)) {
error("mmap for prot=", Hex(prot), " not supported");
errno = EACCES;
return (void *)-1;
}
if (addr_in != 0) {
error("mmap for predefined address not supported");
errno = EINVAL;
return (void *)-1;
}
/*
* XXX attempt to obtain memory mapping via
* 'Vfs::Directory_service::dataspace'.
*/
void *addr = mem_alloc()->alloc(length, PAGE_SHIFT);
if (addr == (void *)-1) {
errno = ENOMEM;
return (void *)-1;
}
/* copy variables for complete read */
size_t read_remain = length;
size_t read_offset = offset;
char *read_addr = (char *)addr;
while (read_remain > 0) {
ssize_t length_read = ::pread(fd->libc_fd, read_addr, read_remain, read_offset);
if (length_read < 0) { /* error */
error("mmap could not obtain file content");
::munmap(addr, length);
errno = EACCES;
return (void *)-1;
} else if (length_read == 0) /* EOF */
break; /* done (length can legally be greater than the file length) */
read_remain -= length_read;
read_offset += length_read;
read_addr += length_read;
}
return addr;
}
int Libc::Vfs_plugin::munmap(void *addr, ::size_t)
{
mem_alloc()->free(addr);
return 0;
}
int Libc::Vfs_plugin::pipe(Libc::File_descriptor *pipefdo[2])
{
Absolute_path base_path(Libc::config_pipe());
if (base_path == "") {
error(__func__, ": pipe fs not mounted");
return Errno(EACCES);
}
Libc::File_descriptor *meta_fd { nullptr };
{
Absolute_path new_path = base_path;
new_path.append("/new");
meta_fd = open(new_path.base(), O_RDONLY, Libc::ANY_FD);
if (!meta_fd) {
Genode::error("failed to create pipe at ", new_path);
return Errno(EACCES);
}
meta_fd->path(new_path.string());
char buf[32] { };
int const n = read(meta_fd, buf, sizeof(buf)-1);
if (n < 1) {
error("failed to read pipe at ", new_path);
close(meta_fd);
return Errno(EACCES);
}
buf[n] = '\0';
base_path.append("/");
base_path.append(buf);
}
auto open_pipe_fd = [&] (auto path_suffix, auto flags)
{
Absolute_path path = base_path;
path.append(path_suffix);
File_descriptor *fd = open(path.base(), flags, Libc::ANY_FD);
if (!fd)
error("failed to open pipe end at ", path);
else
fd->path(path.string());
return fd;
};
pipefdo[0] = open_pipe_fd("/out", O_RDONLY);
pipefdo[1] = open_pipe_fd("/in", O_WRONLY);
close(meta_fd);
if (!pipefdo[0] || !pipefdo[1])
return Errno(EACCES);
return 0;
}
bool Libc::Vfs_plugin::poll(File_descriptor &fd, struct pollfd &pfd)
{
if (fd.plugin != this) return false;
Vfs::Vfs_handle *handle = vfs_handle(&fd);
if (!handle) {
pfd.revents |= POLLNVAL;
return true;
}
enum {
POLLIN_MASK = POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI,
POLLOUT_MASK = POLLOUT | POLLWRNORM | POLLWRBAND,
};
bool res { false };
if ((pfd.events & POLLIN_MASK)
&& VFS_THREAD_SAFE(handle->fs().read_ready(handle)))
{
pfd.revents |= pfd.events & POLLIN_MASK;
res = true;
}
if ((pfd.events & POLLOUT_MASK) /* XXX always writeable */)
{
pfd.revents |= pfd.events & POLLOUT_MASK;
res = true;
}
return res;
}
bool Libc::Vfs_plugin::supports_select(int nfds,
fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
struct timeval *timeout)
{
/* return true if any file descriptor (which is set) belongs to the VFS */
for (int fd = 0; fd < nfds; ++fd) {
if (FD_ISSET(fd, readfds) || FD_ISSET(fd, writefds) || FD_ISSET(fd, exceptfds)) {
File_descriptor *fdo =
file_descriptor_allocator()->find_by_libc_fd(fd);
if (fdo && (fdo->plugin == this))
return true;
}
}
return false;
}
int Libc::Vfs_plugin::select(int nfds,
fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
struct timeval *timeout)
{
int nready = 0;
fd_set const in_readfds = *readfds;
fd_set const in_writefds = *writefds;
/* XXX exceptfds not supported */
/* clear fd sets */
FD_ZERO(readfds);
FD_ZERO(writefds);
FD_ZERO(exceptfds);
for (int fd = 0; fd < nfds; ++fd) {
File_descriptor *fdo =
file_descriptor_allocator()->find_by_libc_fd(fd);
/* handle only fds that belong to this plugin */
if (!fdo || (fdo->plugin != this))
continue;
Vfs::Vfs_handle *handle = vfs_handle(fdo);
if (!handle) continue;
if (FD_ISSET(fd, &in_readfds)) {
if (VFS_THREAD_SAFE(handle->fs().read_ready(handle))) {
FD_SET(fd, readfds);
++nready;
} else {
notify_read_ready(handle);
}
}
if (FD_ISSET(fd, &in_writefds)) {
if (true /* XXX always writeable */) {
FD_SET(fd, writefds);
++nready;
}
}
/* XXX exceptfds not supported */
}
return nready;
}