580 lines
12 KiB
C++
580 lines
12 KiB
C++
/*
|
|
* \brief Libc libfuse plugin
|
|
* \author Josef Soentgen
|
|
* \date 2013-11-07
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2013 Genode Labs GmbH
|
|
*
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
* under the terms of the GNU General Public License version 2.
|
|
*/
|
|
|
|
/* Genode includes */
|
|
#include <base/allocator_avl.h>
|
|
#include <base/env.h>
|
|
#include <base/printf.h>
|
|
#include <util/string.h>
|
|
#include <os/path.h>
|
|
|
|
/* libc plugin includes */
|
|
#include <libc-plugin/plugin.h>
|
|
#include <libc-plugin/fd_alloc.h>
|
|
|
|
/* fuse */
|
|
#include <fuse_private.h>
|
|
|
|
/* libc includes */
|
|
#include <sys/statvfs.h>
|
|
#include <sys/dirent.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
|
|
using namespace Genode;
|
|
|
|
void *operator new (size_t, void *ptr) { return ptr; }
|
|
|
|
#define PDBGV(...) if (verbose) PDBG(__VA_ARGS__)
|
|
static bool const verbose = false;
|
|
|
|
/* a little helper to prevent code duplication */
|
|
static inline int check_result(int res)
|
|
{
|
|
if (res < 0) {
|
|
/**
|
|
* FUSE file systems always return -errno as result
|
|
* if something went wrong.
|
|
*/
|
|
errno = -res;
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/****************************
|
|
** override libc defaults **
|
|
****************************/
|
|
|
|
/*
|
|
extern "C" int access(const char *pathname, int mode)
|
|
{
|
|
PDBGV("pathname: %s", pathname);
|
|
return check_result(Fuse::fuse()->op.access(pathname, mode));
|
|
}
|
|
*/
|
|
|
|
extern "C" int chmod(const char *path, mode_t mode)
|
|
{
|
|
PDBGV("path: %s", path);
|
|
return check_result(Fuse::fuse()->op.chmod(path, mode));
|
|
}
|
|
|
|
|
|
extern "C" int chown(const char *path, uid_t uid, gid_t gid)
|
|
{
|
|
PDBGV("path: %s", path);
|
|
return check_result(Fuse::fuse()->op.chown(path, uid, gid));
|
|
}
|
|
|
|
|
|
extern "C" int link(const char *oldpath, const char *newpath)
|
|
{
|
|
PDBGV("oldpath: %s", oldpath);
|
|
return check_result(Fuse::fuse()->op.link(oldpath, newpath));
|
|
}
|
|
|
|
|
|
/************
|
|
** Plugin **
|
|
************/
|
|
|
|
namespace {
|
|
|
|
struct Plugin_context : Libc::Plugin_context
|
|
{
|
|
String<4096> path;
|
|
int flags;
|
|
int fd_flags;
|
|
struct fuse_file_info file_info;
|
|
|
|
::off_t offset;
|
|
|
|
Plugin_context(const char *p, int f)
|
|
:
|
|
path(p), flags(f), offset(0)
|
|
{
|
|
Genode::memset(&file_info, 0, sizeof (struct fuse_file_info));
|
|
}
|
|
|
|
~Plugin_context() { }
|
|
};
|
|
|
|
static inline Plugin_context *context(Libc::File_descriptor *fd)
|
|
{
|
|
return static_cast<Plugin_context *>(fd->context);
|
|
}
|
|
|
|
class Plugin : public Libc::Plugin
|
|
{
|
|
private:
|
|
|
|
enum { PLUGIN_PRIORITY = 1 };
|
|
|
|
public:
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
Plugin() : Libc::Plugin(PLUGIN_PRIORITY)
|
|
{
|
|
if (!Fuse::init_fs()) {
|
|
PERR("FUSE fs initialization failed");
|
|
return;
|
|
}
|
|
}
|
|
|
|
~Plugin()
|
|
{
|
|
if (Fuse::initialized())
|
|
Fuse::deinit_fs();
|
|
}
|
|
|
|
bool supports_mkdir(const char *path, mode_t mode)
|
|
{
|
|
PDBGV("path: %s", path);
|
|
if (Fuse::initialized() == 0)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool supports_open(const char *pathname, int flags)
|
|
{
|
|
PDBGV("pathname: %s", pathname);
|
|
|
|
if (Genode::strcmp(pathname, "/dev/blkdev") == 0)
|
|
return false;
|
|
|
|
if (Fuse::initialized() == 0)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool supports_readlink(const char *path, char *buf, ::size_t bufsiz)
|
|
{
|
|
PDBGV("path: %s", path);
|
|
if (Fuse::initialized() == 0)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool supports_rmdir(const char *path)
|
|
{
|
|
PDBGV("path: %s", path);
|
|
if (Fuse::fuse() == 0)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool supports_stat(const char *path)
|
|
{
|
|
PDBGV("path: %s", path);
|
|
if (Fuse::initialized() == 0)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool supports_symlink(const char *oldpath, const char *newpath)
|
|
{
|
|
PDBGV("path: %s", oldpath);
|
|
if (Fuse::fuse() == 0)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool supports_unlink(const char *path)
|
|
{
|
|
PDBGV("path: %s", path);
|
|
if (Fuse::fuse() == 0)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
|
|
int close(Libc::File_descriptor *fd)
|
|
{
|
|
Plugin_context *ctx = context(fd);
|
|
|
|
PDBGV("path: %s", ctx->path.string());
|
|
|
|
Fuse::fuse()->op.release(ctx->path.string(), &ctx->file_info);
|
|
|
|
destroy(env()->heap(), ctx);
|
|
Libc::file_descriptor_allocator()->free(fd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fcntl(Libc::File_descriptor *fd, int cmd, long arg)
|
|
{
|
|
Plugin_context *ctx = context(fd);
|
|
|
|
switch (cmd) {
|
|
case F_GETFD:
|
|
return ctx->fd_flags;
|
|
case F_GETFL:
|
|
return ctx->flags;
|
|
case F_SETFD:
|
|
ctx->fd_flags = arg;
|
|
return 0;
|
|
default:
|
|
PDBG("cmd %d not supported", cmd);
|
|
return -1;
|
|
}
|
|
|
|
return -1; /* never reached */
|
|
}
|
|
|
|
int fstat(Libc::File_descriptor *fd, struct stat *buf)
|
|
{
|
|
Plugin_context *ctx = context(fd);
|
|
|
|
PDBGV("path: %s", ctx->path.string());
|
|
|
|
Genode::memset(buf, 0, sizeof (struct stat));
|
|
|
|
int res = Fuse::fuse()->op.getattr(ctx->path.string(), buf);
|
|
if (res != 0) {
|
|
errno = -res;
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fstatfs(Libc::File_descriptor *fd, struct statfs *buf)
|
|
{
|
|
Plugin_context *ctx = context(fd);
|
|
|
|
PDBGV("path: %s", ctx->path.string());
|
|
|
|
struct statvfs vfs;
|
|
|
|
int res = Fuse::fuse()->op.statfs(ctx->path.string(), &vfs);
|
|
if (res != 0) {
|
|
errno = -res;
|
|
return -1;
|
|
}
|
|
|
|
Genode::memset(buf, 0, sizeof (struct statfs));
|
|
|
|
buf->f_bsize = vfs.f_bsize;
|
|
//buf->f_frsize = vfs.f_frsize;
|
|
buf->f_blocks = vfs.f_blocks;
|
|
buf->f_bavail = vfs.f_bavail;
|
|
buf->f_bfree = vfs.f_bfree;
|
|
buf->f_namemax = vfs.f_namemax;
|
|
buf->f_files = vfs.f_files;
|
|
//buf->f_favail = vfs.f_favail;
|
|
buf->f_ffree = vfs.f_ffree;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ftruncate(Libc::File_descriptor *fd, ::off_t length)
|
|
{
|
|
Plugin_context *ctx = context(fd);
|
|
|
|
PDBGV("path: %s", ctx->path.string());
|
|
|
|
int res = Fuse::fuse()->op.ftruncate(ctx->path.string(), length,
|
|
&ctx->file_info);
|
|
if (res != 0) {
|
|
errno = -res;
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
::ssize_t getdirentries(Libc::File_descriptor *fd, char *buf, ::size_t nbytes,
|
|
::off_t *basep)
|
|
{
|
|
Plugin_context *ctx = context(fd);
|
|
PDBGV("path: %s", ctx->path.string());
|
|
|
|
if (nbytes < sizeof (struct dirent)) {
|
|
PERR("buf too small");
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
struct dirent *de = (struct dirent *)buf;
|
|
::memset(de, 0, sizeof (struct dirent));
|
|
|
|
struct fuse_dirhandle dh = {
|
|
.filler = Fuse::fuse()->filler,
|
|
.buf = buf,
|
|
.size = nbytes,
|
|
.offset = 0,
|
|
};
|
|
|
|
int res = Fuse::fuse()->op.readdir(ctx->path.string(), &dh,
|
|
Fuse::fuse()->filler, 0,
|
|
&ctx->file_info);
|
|
if (res != 0) {
|
|
errno = -res;
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* We have to stat(2) each entry because there are FUSE file
|
|
* systems which do not provide a valid struct stat entry in
|
|
* its readdir() implementation because only d_ino and d_name
|
|
* are specified by POSIX.
|
|
*/
|
|
::off_t offset = 0;
|
|
while (offset < dh.offset) {
|
|
struct dirent *entry = (struct dirent*)(buf + offset);
|
|
|
|
/* try to query the type of the file if the type is unknown */
|
|
if (entry->d_type == DT_UNKNOWN) {
|
|
Genode::Path<4096> path(entry->d_name, ctx->path.string());
|
|
struct stat sbuf;
|
|
res = Fuse::fuse()->op.getattr(path.base(), &sbuf);
|
|
if (res == 0) {
|
|
entry->d_type = IFTODT(sbuf.st_mode);
|
|
entry->d_fileno = sbuf.st_ino ? sbuf.st_ino : 1;
|
|
}
|
|
}
|
|
|
|
offset += sizeof (struct dirent);
|
|
}
|
|
|
|
/**
|
|
* To prevent the libc from being stuck in an endless loop we
|
|
* append an empty entry. This is a rather hacky solution but
|
|
* for now it suffice.
|
|
*/
|
|
dh.offset += sizeof (struct dirent);
|
|
((struct dirent*)(buf + dh.offset))->d_reclen = 0;
|
|
|
|
return dh.offset;
|
|
}
|
|
|
|
::off_t lseek(Libc::File_descriptor *fd, ::off_t offset, int whence)
|
|
{
|
|
Plugin_context *ctx = context(fd);
|
|
PDBGV("path: %s", ctx->path.string());
|
|
|
|
switch (whence) {
|
|
case SEEK_SET:
|
|
ctx->offset = offset;
|
|
return ctx->offset;
|
|
|
|
case SEEK_CUR:
|
|
ctx->offset += offset;
|
|
return ctx->offset;
|
|
|
|
case SEEK_END:
|
|
if (offset != 0) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
ctx->offset = ~0L;
|
|
|
|
return (Fuse::fuse()->block_size * Fuse::fuse()->block_count);
|
|
|
|
default:
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int mkdir(const char *pathname, mode_t mode)
|
|
{
|
|
PDBGV("pathname: %s", pathname);
|
|
int res = Fuse::fuse()->op.mkdir(pathname, mode);
|
|
|
|
return check_result(res);
|
|
}
|
|
|
|
Libc::File_descriptor *open(const char *pathname, int flags)
|
|
{
|
|
/* XXX evaluate flags */
|
|
PDBGV("pathname: %s", pathname);
|
|
|
|
Plugin_context *context = new (Genode::env()->heap())
|
|
Plugin_context(pathname, flags);
|
|
|
|
int res;
|
|
int tries = 0;
|
|
do {
|
|
/* first try to open pathname */
|
|
res = Fuse::fuse()->op.open(pathname, &context->file_info);
|
|
if (res == 0) {
|
|
break;
|
|
}
|
|
|
|
/* try to create pathname if open failed and O_CREAT flag was specified */
|
|
if (flags & O_CREAT && !tries) {
|
|
mode_t mode = S_IFREG | 0644;
|
|
int res = Fuse::fuse()->op.mknod(pathname, mode, 0);
|
|
switch (res) {
|
|
case 0:
|
|
break;
|
|
default:
|
|
PERR("could not create '%s'", pathname);
|
|
destroy(env()->heap(), context);
|
|
return 0;
|
|
}
|
|
|
|
tries++;
|
|
continue;
|
|
}
|
|
|
|
if (res < 0) {
|
|
errno = -res;
|
|
destroy(env()->heap(), context);
|
|
return 0;
|
|
}
|
|
}
|
|
while (true);
|
|
|
|
if (flags & O_TRUNC) {
|
|
res = Fuse::fuse()->op.ftruncate(pathname, 0,
|
|
&context->file_info);
|
|
if (res != 0) {
|
|
errno = -res;
|
|
Fuse::fuse()->op.release(context->path.string(),
|
|
&context->file_info);
|
|
destroy(env()->heap(), context);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
context->file_info.flags = flags;
|
|
|
|
return Libc::file_descriptor_allocator()->alloc(this, context);
|
|
}
|
|
|
|
ssize_t read(Libc::File_descriptor *fd, void *buf, ::size_t count)
|
|
{
|
|
Plugin_context *ctx = context(fd);
|
|
PDBGV("path: %s", ctx->path.string());
|
|
|
|
int res = Fuse::fuse()->op.read(ctx->path.string(),
|
|
reinterpret_cast<char*>(buf),
|
|
count, ctx->offset, &ctx->file_info);
|
|
|
|
if (check_result(res))
|
|
return -1;
|
|
|
|
ctx->offset += res;
|
|
|
|
return res;
|
|
}
|
|
|
|
ssize_t readlink(const char *path, char *buf, ::size_t bufsiz)
|
|
{
|
|
PDBGV("path: %s", path);
|
|
|
|
int res = Fuse::fuse()->op.readlink(path, buf, bufsiz);
|
|
if (res < 0) {
|
|
errno = -res;
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* We have to trust each FUSE file system to append a
|
|
* null byte to buf, which is required according to
|
|
* FUSEs documentation.
|
|
*/
|
|
return Genode::strlen(buf);
|
|
}
|
|
|
|
int rename(const char *oldpath, const char *newpath)
|
|
{
|
|
PDBGV("oldpath: %s newpath: %s", oldpath, newpath);
|
|
int res = Fuse::fuse()->op.rename(oldpath, newpath);
|
|
|
|
return check_result(res);
|
|
}
|
|
|
|
int rmdir(const char *path)
|
|
{
|
|
PDBGV("path: %s", path);
|
|
int res = Fuse::fuse()->op.rmdir(path);
|
|
|
|
return check_result(res);
|
|
}
|
|
|
|
int stat(const char *path, struct stat *buf)
|
|
{
|
|
PDBGV("path: %s", path);
|
|
Genode::memset(buf, 0, sizeof (buf));
|
|
|
|
int res = Fuse::fuse()->op.getattr(path, buf);
|
|
|
|
return check_result(res);
|
|
}
|
|
|
|
int symlink(const char *oldpath, const char *newpath)
|
|
{
|
|
PDBGV("oldpath: %s newpath: %s", oldpath, newpath);
|
|
int res = Fuse::fuse()->op.symlink(oldpath, newpath);
|
|
|
|
return check_result(res);
|
|
}
|
|
|
|
int unlink(const char *path)
|
|
{
|
|
PDBGV("path: %s", path);
|
|
int res = Fuse::fuse()->op.unlink(path);
|
|
|
|
return check_result(res);
|
|
}
|
|
|
|
ssize_t write(Libc::File_descriptor *fd, const void *buf, ::size_t count)
|
|
{
|
|
Plugin_context *ctx = context(fd);
|
|
|
|
PDBGV("path: %s", ctx->path.string());
|
|
int res = Fuse::fuse()->op.write(ctx->path.string(),
|
|
reinterpret_cast<const char*>(buf),
|
|
count, ctx->offset, &ctx->file_info);
|
|
|
|
if (check_result(res))
|
|
return -1;
|
|
|
|
ctx->offset += res;
|
|
|
|
return res;
|
|
}
|
|
|
|
};
|
|
|
|
} /* unnamed namespace */
|
|
|
|
|
|
void __attribute__((constructor)) init_libc_fuse(void)
|
|
{
|
|
/*
|
|
* During the initialization of the plugin, we already require the VFS.
|
|
* Hence, we need to make sure to initialize the VFS before doing our
|
|
* own initialization.
|
|
*/
|
|
extern void init_libc_vfs(void);
|
|
init_libc_vfs();
|
|
|
|
PDBGV("using the libc_fuse plugin");
|
|
static Plugin plugin;
|
|
}
|