1770 lines
41 KiB
C++
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::copy_cstring(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;
|
|
}
|