libc: support for ioctls via ioctl directory

This patch introduces a new scheme of handling ioctl operations that
maps ioctls to pseudo-file accesses, similar to how the libc maps socket
calls to socket-fs operations.

A device file can be accompanied with a (hidden) directory that is named
after the device file and hosts pseudo files for triggering the various
device operations. For example, for accessing a terminal, the directory
structure looks like this:

  /dev/terminal
  /dev/.terminal/info

The 'info' file contains device information in XML format. The type of
the XML node corresponds to the device type. E.g., If the libc receives
a 'TIOCGWINSZ' ioctl for /dev/terminal, it reads the content of
/dev/.terminal/info to obtain the terminal-size information. In this
case, the 'info' file looks as follows:

  <terminal rows="25" columns="80/>

Following this scheme, VFS plugins can support ioctl operations by
providing an ioctl directory in addition to the actual device file.

Internally, the mechanism uses the 'os/vfs.h' API to access pseudo
files. Hence, we need to propagate the Vfs::Env to 'vfs_plugin.cc' to
create an instance of a 'Directory' for the root for the VFS.

Issue #3519
This commit is contained in:
Norman Feske 2019-10-09 15:51:18 +02:00 committed by Christian Helmuth
parent 07a40d028a
commit 7ac32ea60c
4 changed files with 151 additions and 50 deletions

View File

@ -68,6 +68,9 @@ class Libc::Env_implementation : public Libc::Env, public Config_accessor
: _env(env), _vfs_env(_env, alloc, _vfs_config()) { }
Vfs::Env &vfs_env() { return _vfs_env; }
/*************************
** Libc::Env interface **
*************************/

View File

@ -98,9 +98,10 @@ struct Libc::Kernel final : Vfs::Io_response_handler,
bool const _update_mtime = _libc_env.libc_config().attribute_value("update_mtime", true);
Vfs_plugin _vfs { _libc_env, _heap, *this,
Vfs_plugin _vfs { _libc_env, _libc_env.vfs_env(), _heap, *this,
_update_mtime ? Vfs_plugin::Update_mtime::YES
: Vfs_plugin::Update_mtime::NO };
: Vfs_plugin::Update_mtime::NO,
_libc_env.config() };
bool const _cloned = _libc_env.libc_config().attribute_value("cloned", false);
pid_t const _pid = _libc_env.libc_config().attribute_value("pid", 0U);

View File

@ -18,6 +18,7 @@
/* Genode includes */
#include <libc/component.h>
#include <os/vfs.h>
#include <vfs/file_system.h>
/* libc includes */
@ -41,12 +42,32 @@ class Libc::Vfs_plugin : public Plugin
enum class Update_mtime { NO, YES };
/**
* Return path to pseudo files used for ioctl operations of a given FD
*/
static Absolute_path ioctl_dir(File_descriptor const &fd)
{
Absolute_path path(fd.fd_path);
/*
* The pseudo files used for ioctl operations reside in a (hidden)
* directory named after the device path and prefixed with '.'.
*/
String<64> const ioctl_dir_name(".", path.last_element());
path.strip_last_element();
path.append_element(ioctl_dir_name.string());
return path;
}
private:
Genode::Allocator &_alloc;
Vfs::File_system &_root_dir;
Vfs::Io_response_handler &_response_handler;
Update_mtime const _update_mtime;
Genode::Allocator &_alloc;
Vfs::File_system &_root_fs;
Constructible<Genode::Directory> _root_dir { };
Vfs::Io_response_handler &_response_handler;
Update_mtime const _update_mtime;
/**
* Sync a handle and propagate errors
@ -58,21 +79,47 @@ class Libc::Vfs_plugin : public Plugin
*/
void _vfs_write_mtime(Vfs::Vfs_handle&);
int _legacy_ioctl(File_descriptor *, int , char *);
/**
* Call functor 'fn' with ioctl info for the given file descriptor 'fd'
*
* The functor is called with an 'Xml_node' of the ioctl information
* as argument.
*
* If no ioctl info is available, 'fn' is not called.
*/
template <typename FN>
void _with_info(File_descriptor &fd, FN const &fn);
public:
Vfs_plugin(Libc::Env &env,
Vfs::Env &vfs_env,
Genode::Allocator &alloc,
Vfs::Io_response_handler &handler,
Update_mtime update_mtime)
Update_mtime update_mtime,
Xml_node config)
:
_alloc(alloc), _root_dir(env.vfs()),
_alloc(alloc),
_root_fs(env.vfs()),
_response_handler(handler),
_update_mtime(update_mtime)
{ }
{
if (config.has_sub_node("libc"))
_root_dir.construct(vfs_env);
}
~Vfs_plugin() final { }
bool root_dir_has_dirents() const { return _root_dir.num_dirent("/") > 0; }
template <typename FN>
void with_root_dir(FN const &fn)
{
if (_root_dir.constructed())
fn(*_root_dir);
}
bool root_dir_has_dirents() const { return _root_fs.num_dirent("/") > 0; }
bool supports_access(const char *, int) override { return true; }
bool supports_mkdir(const char *, mode_t) override { return true; }

View File

@ -216,12 +216,34 @@ namespace Libc {
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_dir.leaf_path(path)))
if (VFS_THREAD_SAFE(_root_fs.leaf_path(path)))
return 0;
errno = ENOENT;
@ -232,7 +254,7 @@ int Libc::Vfs_plugin::access(const char *path, int amode)
Libc::File_descriptor *Libc::Vfs_plugin::open(char const *path, int flags,
int libc_fd)
{
if (VFS_THREAD_SAFE(_root_dir.directory(path))) {
if (VFS_THREAD_SAFE(_root_fs.directory(path))) {
if (((flags & O_ACCMODE) != O_RDONLY)) {
errno = EISDIR;
@ -245,7 +267,7 @@ Libc::File_descriptor *Libc::Vfs_plugin::open(char const *path, int flags,
typedef Vfs::Directory_service::Opendir_result Opendir_result;
switch (VFS_THREAD_SAFE(_root_dir.opendir(path, false, &handle, _alloc))) {
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;
@ -286,7 +308,7 @@ Libc::File_descriptor *Libc::Vfs_plugin::open(char const *path, int flags,
while (handle == nullptr) {
switch (VFS_THREAD_SAFE(_root_dir.open(path, flags, &handle, _alloc))) {
switch (VFS_THREAD_SAFE(_root_fs.open(path, flags, &handle, _alloc))) {
case Result::OPEN_OK:
break;
@ -303,7 +325,7 @@ Libc::File_descriptor *Libc::Vfs_plugin::open(char const *path, int flags,
}
/* O_CREAT is set, so try to create the file */
switch (VFS_THREAD_SAFE(_root_dir.open(path, flags | O_EXCL, &handle, _alloc))) {
switch (VFS_THREAD_SAFE(_root_fs.open(path, flags | O_EXCL, &handle, _alloc))) {
case Result::OPEN_OK:
break;
@ -491,7 +513,7 @@ int Libc::Vfs_plugin::dup2(File_descriptor *fd,
typedef Vfs::Directory_service::Open_result Result;
if (VFS_THREAD_SAFE(_root_dir.open(fd->fd_path, fd->flags, &handle, _alloc))
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);
@ -515,7 +537,7 @@ Libc::File_descriptor *Libc::Vfs_plugin::dup(File_descriptor *fd)
typedef Vfs::Directory_service::Open_result Result;
if (VFS_THREAD_SAFE(_root_dir.open(fd->fd_path, fd->flags, &handle, _alloc))
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);
@ -576,7 +598,7 @@ int Libc::Vfs_plugin::mkdir(const char *path, mode_t mode)
typedef Vfs::Directory_service::Opendir_result Opendir_result;
switch (VFS_THREAD_SAFE(_root_dir.opendir(path, true, &dir_handle, _alloc))) {
switch (VFS_THREAD_SAFE(_root_fs.opendir(path, true, &dir_handle, _alloc))) {
case Opendir_result::OPENDIR_OK:
VFS_THREAD_SAFE(dir_handle->close());
break;
@ -609,7 +631,7 @@ int Libc::Vfs_plugin::stat(char const *path, struct stat *buf)
Vfs::Directory_service::Stat stat;
switch (VFS_THREAD_SAFE(_root_dir.stat(path, 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;
@ -925,6 +947,55 @@ ssize_t Libc::Vfs_plugin::getdirentries(File_descriptor *fd, char *buf,
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;
@ -951,27 +1022,6 @@ int Libc::Vfs_plugin::ioctl(File_descriptor *fd, int request, char *argp)
break;
}
case 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;
return 0;
}
break;
case TIOCSETAF:
{
opcode = Opcode::IOCTL_OP_TIOCSETAF;
@ -1183,7 +1233,7 @@ int Libc::Vfs_plugin::symlink(const char *oldpath, const char *newpath)
Vfs::Vfs_handle *handle { 0 };
Openlink_result openlink_result =
VFS_THREAD_SAFE(_root_dir.openlink(newpath, true, &handle, _alloc));
VFS_THREAD_SAFE(_root_fs.openlink(newpath, true, &handle, _alloc));
switch (openlink_result) {
case Openlink_result::OPENLINK_OK:
@ -1258,7 +1308,7 @@ ssize_t Libc::Vfs_plugin::readlink(const char *path, char *buf, ::size_t buf_siz
Vfs::Vfs_handle *symlink_handle { 0 };
Vfs::Directory_service::Openlink_result openlink_result =
VFS_THREAD_SAFE(_root_dir.openlink(path, false, &symlink_handle, _alloc));
VFS_THREAD_SAFE(_root_fs.openlink(path, false, &symlink_handle, _alloc));
switch (openlink_result) {
case Vfs::Directory_service::OPENLINK_OK:
@ -1373,7 +1423,7 @@ int Libc::Vfs_plugin::unlink(char const *path)
{
typedef Vfs::Directory_service::Unlink_result Result;
switch (VFS_THREAD_SAFE(_root_dir.unlink(path))) {
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;
@ -1387,25 +1437,25 @@ 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_dir.leaf_path(to_path))) {
if (VFS_THREAD_SAFE(_root_fs.leaf_path(to_path))) {
if (VFS_THREAD_SAFE(_root_dir.directory(to_path))) {
if (!VFS_THREAD_SAFE(_root_dir.directory(from_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_dir.num_dirent(to_path))) {
if (VFS_THREAD_SAFE(_root_fs.num_dirent(to_path))) {
errno = ENOTEMPTY; return -1;
}
} else {
if (VFS_THREAD_SAFE(_root_dir.directory(from_path))) {
if (VFS_THREAD_SAFE(_root_fs.directory(from_path))) {
errno = ENOTDIR; return -1;
}
}
}
switch (VFS_THREAD_SAFE(_root_dir.rename(from_path, to_path))) {
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;