/* * \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 #include #include #include #include /* libc plugin includes */ #include #include /* fuse */ #include /* libc includes */ #include #include #include #include #include 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(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(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(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; }