genode/libports/src/lib/libc_fs/plugin.cc

825 lines
20 KiB
C++

/*
* \brief Libc plugin for accessing a file-system session
* \author Norman Feske
* \date 2012-04-11
*/
/*
* Copyright (C) 2012-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/env.h>
#include <base/printf.h>
#include <file_system_session/connection.h>
#include <os/path.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 <sys/mman.h>
/* libc plugin interface */
#include <libc-plugin/plugin.h>
#include <libc-plugin/fd_alloc.h>
/* libc-internal includes */
#include <libc_mem_alloc.h>
static bool const verbose = false;
namespace File_system { struct Packet_ref { }; }
namespace {
enum { PATH_MAX_LEN = 256 };
typedef Genode::Path<PATH_MAX_LEN> Canonical_path;
static File_system::Session *file_system()
{
static Genode::Allocator_avl tx_buffer_alloc(Genode::env()->heap());
static File_system::Connection fs(tx_buffer_alloc);
return &fs;
}
struct Node_handle_guard
{
File_system::Node_handle handle;
Node_handle_guard(File_system::Node_handle handle) : handle(handle) { }
~Node_handle_guard() { file_system()->close(handle); }
};
class Plugin_context : public Libc::Plugin_context,
public File_system::Packet_ref
{
private:
enum Type { TYPE_FILE, TYPE_DIR, TYPE_SYMLINK };
Type _type;
File_system::Node_handle _node_handle;
int _fd_flags;
int _status_flags;
/**
* Current file position if manually seeked, or ~0 for append mode
*/
off_t _seek_offset;
public:
Plugin_context(File_system::File_handle handle)
: _type(TYPE_FILE), _node_handle(handle), _fd_flags(0),
_status_flags(0), _seek_offset(~0) { }
Plugin_context(File_system::Dir_handle handle)
: _type(TYPE_DIR), _node_handle(handle), _fd_flags(0),
_status_flags(0), _seek_offset(0){ }
Plugin_context(File_system::Symlink_handle handle)
: _type(TYPE_SYMLINK), _node_handle(handle), _fd_flags(0),
_status_flags(0), _seek_offset(~0) { }
File_system::Node_handle node_handle() const { return _node_handle; }
/**
* Set/get file descriptor flags
*/
void fd_flags(int flags) { _fd_flags = flags; }
int fd_flags() { return _fd_flags; }
/**
* Set/get file status status flags
*/
void status_flags(int flags) { _status_flags = flags; }
int status_flags() { return _status_flags; }
/**
* Return true of handle is append mode
*/
bool is_appending() const { return ~0 == _seek_offset; }
/**
* Set seek offset, switch to non-append mode
*/
void seek_offset(size_t seek_offset) { _seek_offset = seek_offset; }
/**
* Return seek offset if handle is in non-append mode
*/
off_t seek_offset() const { return _seek_offset; }
/**
* Advance current seek position by 'incr' number of bytes
*
* This function has no effect if the handle is in append mode.
*/
void advance_seek_offset(size_t incr)
{
if (!is_appending())
_seek_offset += incr;
}
void infinite_seek_offset()
{
_seek_offset = ~0;
}
virtual ~Plugin_context() { }
};
static inline Plugin_context *context(Libc::File_descriptor *fd)
{
return fd->context ? static_cast<Plugin_context *>(fd->context) : 0;
}
static size_t const session_max_packet_size(File_system::Session::Tx::Source &source)
{
using Policy = File_system::Session::Tx_policy;
return source.bulk_buffer_size() - (sizeof(Policy::Ack_queue)
+ sizeof(Policy::Submit_queue));
}
static void obtain_stat_for_node(File_system::Node_handle node_handle,
struct stat *buf)
{
if (!buf)
return;
File_system::Status status = file_system()->status(node_handle);
/*
* Translate status information to 'struct stat' format
*/
memset(buf, 0, sizeof(struct stat));
buf->st_size = status.size;
if (status.is_directory())
buf->st_mode |= S_IFDIR;
else if (status.is_symlink())
buf->st_mode |= S_IFLNK;
else
buf->st_mode |= S_IFREG;
struct tm tm;
memset(&tm, 0, sizeof(struct tm));
buf->st_mtime = mktime(&tm);
if (buf->st_mtime == -1 && verbose)
PERR("mktime() returned -1, the file modification time reported by stat() will be incorrect");
}
class Plugin : public Libc::Plugin
{
private:
Genode::Lock _rw_lock;
::off_t _file_size(Libc::File_descriptor *fd)
{
struct stat stat_buf;
if (fstat(fd, &stat_buf) == -1)
return -1;
return stat_buf.st_size;
}
public:
/**
* Constructor
*
* Use priority 1 to override libc_vfs.
*/
Plugin() : Libc::Plugin(1) { }
~Plugin() { }
bool supports_mkdir(const char *path, mode_t)
{
if (verbose)
PDBG("path = %s", path);
return true;
}
bool supports_open(const char *pathname, int flags)
{
if (verbose)
PDBG("pathname = %s", pathname);
return true;
}
bool supports_readlink(const char *path, char *, size_t)
{
if (verbose)
PDBG("path = %s", path);
return true;
}
bool supports_rename(const char *oldpath, const char *newpath)
{
if (verbose)
PDBG("oldpath = %s, newpath = %s", oldpath, newpath);
return true;
}
bool supports_stat(const char *path)
{
if (verbose)
PDBG("path = %s", path);
return true;
}
bool supports_symlink(const char *oldpath, const char *newpath)
{
if (verbose)
PDBG("oldpath = %s, newpath = %s", oldpath, newpath);
return true;
}
bool supports_unlink(const char *path)
{
if (verbose)
PDBG("path = %s", path);
return true;
}
bool supports_mmap() { return true; }
int close(Libc::File_descriptor *fd)
{
file_system()->close(context(fd)->node_handle());
Genode::destroy(Genode::env()->heap(), context(fd));
Libc::file_descriptor_allocator()->free(fd);
return 0;
}
int fcntl(Libc::File_descriptor *fd, int cmd, long arg)
{
switch (cmd) {
case F_GETFD: return context(fd)->fd_flags();
case F_SETFD: context(fd)->fd_flags(arg); return 0;
case F_GETFL: return context(fd)->status_flags();
default: PERR("fcntl(): command %d not supported", cmd); return -1;
}
}
int fstat(Libc::File_descriptor *fd, struct stat *buf)
{
try {
obtain_stat_for_node(context(fd)->node_handle(), buf);
return 0;
}
catch (...) {
struct Unhandled_exception_in_fstat { };
throw Unhandled_exception_in_fstat();
}
return -1;
}
int fstatfs(Libc::File_descriptor *, struct statfs *buf)
{
/* libc's opendir() fails if _fstatfs() returns -1, so we return 0 here */
if (verbose)
PDBG("_fstatfs() called - not yet implemented");
return 0;
}
int fsync(Libc::File_descriptor *fd)
{
if (verbose)
PDBG("not implemented");
return -1;
}
int ftruncate(Libc::File_descriptor *fd, ::off_t length)
{
File_system::Node_handle node_handle = context(fd)->node_handle();
File_system::File_handle &file_handle =
static_cast<File_system::File_handle&>(node_handle);
try {
file_system()->truncate(file_handle, length);
} catch (File_system::Invalid_handle) {
errno = EINVAL;
return -1;
} catch (File_system::Permission_denied) {
errno = EPERM;
return -1;
}
return 0;
}
/*
* *basep does not get initialized by the libc and is therefore
* useless for determining a specific directory index. This
* function uses the plugin-internal seek offset instead.
*/
ssize_t getdirentries(Libc::File_descriptor *fd, char *buf,
::size_t nbytes, ::off_t *basep)
{
using namespace File_system;
if (nbytes < sizeof(struct dirent)) {
PERR("buf too small");
return -1;
}
Directory_entry entry;
size_t num_bytes = read(fd, &entry, sizeof(entry));
/* detect end of directory entries */
if (num_bytes == 0)
return 0;
if (num_bytes != sizeof(entry)) {
PERR("getdirentries retrieved unexpected directory entry size");
return -1;
}
struct dirent *dirent = (struct dirent *)buf;
Genode::memset(dirent, 0, sizeof(struct dirent));
switch (entry.type) {
case Directory_entry::TYPE_DIRECTORY: dirent->d_type = DT_DIR; break;
case Directory_entry::TYPE_FILE: dirent->d_type = DT_REG; break;
case Directory_entry::TYPE_SYMLINK: dirent->d_type = DT_LNK; break;
}
dirent->d_fileno = 1 + (context(fd)->seek_offset() / sizeof(struct dirent));
dirent->d_reclen = sizeof(struct dirent);
Genode::strncpy(dirent->d_name, entry.name, sizeof(dirent->d_name));
dirent->d_namlen = Genode::strlen(dirent->d_name);
*basep += sizeof(struct dirent);
return sizeof(struct dirent);
}
::off_t lseek(Libc::File_descriptor *fd, ::off_t offset, int whence)
{
switch (whence) {
case SEEK_SET:
context(fd)->seek_offset(offset);
return offset;
case SEEK_CUR:
context(fd)->advance_seek_offset(offset);
if (context(fd)->is_appending())
return _file_size(fd);
return context(fd)->seek_offset();
case SEEK_END:
if (offset != 0) {
errno = EINVAL;
return -1;
}
context(fd)->infinite_seek_offset();
return _file_size(fd);
default:
errno = EINVAL;
return -1;
}
}
int mkdir(const char *path, mode_t mode)
{
Canonical_path canonical_path(path);
try {
File_system::Dir_handle const handle =
file_system()->dir(canonical_path.base(), true);
file_system()->close(handle);
return 0;
}
catch (File_system::Permission_denied) { errno = EPERM; }
catch (File_system::Node_already_exists) { errno = EEXIST; }
catch (File_system::Lookup_failed) { errno = ENOENT; }
catch (File_system::Name_too_long) { errno = ENAMETOOLONG; }
catch (File_system::No_space) { errno = ENOSPC; }
return -1;
}
Libc::File_descriptor *open(const char *pathname, int flags)
{
Canonical_path path(pathname);
File_system::Mode mode;
switch (flags & O_ACCMODE) {
case O_RDONLY: mode = File_system::READ_ONLY; break;
case O_WRONLY: mode = File_system::WRITE_ONLY; break;
case O_RDWR: mode = File_system::READ_WRITE; break;
default: mode = File_system::STAT_ONLY; break;
}
/*
* Probe for an existing directory to open
*/
try {
if (verbose)
PDBG("open dir '%s'", path.base());
File_system::Dir_handle const handle =
file_system()->dir(path.base(), false);
Plugin_context *context = new (Genode::env()->heap())
Plugin_context(handle);
return Libc::file_descriptor_allocator()->alloc(this, context);
} catch (File_system::Lookup_failed) { }
/*
* Determine directory path that contains the node to open
*/
Canonical_path dir_path(pathname);
dir_path.strip_last_element();
Canonical_path basename(pathname);
basename.keep_only_last_element();
try {
/*
* Open directory that contains the file to be opened/created
*/
File_system::Dir_handle const dir_handle =
file_system()->dir(dir_path.base(), false);
Node_handle_guard guard(dir_handle);
File_system::File_handle handle;
/*
* Open or create file
*/
bool const create = (flags & O_CREAT) != 0;
bool opened = false;
while (!opened) {
try {
handle = file_system()->file(dir_handle, basename.base() + 1, mode, create);
opened = true;
} catch (File_system::Node_already_exists) {
if (flags & O_EXCL) {
errno = EEXIST;
return 0;
}
/* try to open the existing file */
try {
handle = file_system()->file(dir_handle, basename.base() + 1, mode, false);
opened = true;
} catch (File_system::Lookup_failed) {
/* the file got deleted in the meantime */
}
}
}
Plugin_context *context = new (Genode::env()->heap())
Plugin_context(handle);
/*
* Prevent returning the file size as current seek offset
* for files that are opened read only because this
* behaviour is only useful for appending write operations
* to files.
*
* XXX Improve handling of files opened in append mode.
*/
if ((flags & O_ACCMODE) == O_RDONLY)
context->seek_offset(0);
context->status_flags(flags);
Libc::File_descriptor *fd = Libc::file_descriptor_allocator()->alloc(this, context);
if ((flags & O_TRUNC) && (ftruncate(fd, 0) == -1)) {
Libc::file_descriptor_allocator()->free(fd);
destroy(Genode::env()->heap(), context);
return 0;
}
return fd;
}
catch (File_system::Lookup_failed) {
PERR("open(%s) lookup failed", pathname); }
return 0;
}
int rename(const char *oldpath, const char *newpath)
{
if (verbose)
PDBG("not implemented");
return -1;
}
ssize_t read(Libc::File_descriptor *fd, void *buf, ::size_t count)
{
Genode::Lock::Guard guard(_rw_lock);
File_system::Session::Tx::Source &source = *file_system()->tx();
size_t const max_packet_size = session_max_packet_size(source);
size_t remaining_count = count;
if (context(fd)->seek_offset() == ~0)
context(fd)->seek_offset(0);
while (remaining_count) {
size_t curr_packet_size = Genode::min(remaining_count, max_packet_size);
File_system::Packet_descriptor packet(
source.alloc_packet(curr_packet_size),
static_cast<File_system::Packet_ref *>(context(fd)),
context(fd)->node_handle(),
File_system::Packet_descriptor::READ,
curr_packet_size,
context(fd)->seek_offset());
/* pass packet to server side */
source.submit_packet(packet);
packet = source.get_acked_packet();
/*
* XXX check if acked packet belongs to request,
* needed for thread safety
*/
size_t read_num_bytes = Genode::min(packet.length(), curr_packet_size);
/* copy-out payload into destination buffer */
memcpy(buf, source.packet_content(packet), read_num_bytes);
source.release_packet(packet);
/* prepare next iteration */
context(fd)->advance_seek_offset(read_num_bytes);
buf = (void *)((Genode::addr_t)buf + read_num_bytes);
remaining_count -= read_num_bytes;
/*
* If we received less bytes than requested, we reached the end
* of the file.
*/
if (read_num_bytes < curr_packet_size)
break;
}
return count - remaining_count;
}
ssize_t readlink(const char *path, char *buf, size_t bufsiz)
{
if (verbose)
PDBG("path = %s, bufsiz = %zu", path, bufsiz);
Canonical_path abs_path(path);
abs_path.strip_last_element();
Canonical_path symlink_name(path);
symlink_name.keep_only_last_element();
try {
::File_system::Dir_handle dir_handle = file_system()->dir(abs_path.base(), false);
::File_system::Symlink_handle symlink_handle =
file_system()->symlink(dir_handle, symlink_name.base() + 1, false);
if (symlink_handle.value == -1) {
errno = ENOSYS;
return -1;
}
Plugin_context *context = new (Genode::env()->heap())
Plugin_context(symlink_handle);
Libc::File_descriptor *fd = Libc::file_descriptor_allocator()->alloc(this, context);
ssize_t result = read(fd, buf, bufsiz);
if (verbose)
PDBG("result = %zd, buf = %s", result, buf[result] ?
"<not zero terminated>" : buf);
close(fd);
return result;
} catch (...) { }
errno = ENOENT;
return -1;
}
int stat(const char *pathname, struct stat *buf)
{
if (verbose)
PDBG("stat %s", pathname);
Canonical_path path(pathname);
try {
File_system::Node_handle const node_handle =
file_system()->node(path.base());
Node_handle_guard guard(node_handle);
obtain_stat_for_node(node_handle, buf);
return 0;
}
catch (File_system::Lookup_failed) {
PERR("stat(%s): lookup failed", pathname);
errno = ENOENT;
}
return -1;
}
int symlink(const char *oldpath, const char *newpath)
{
Canonical_path abs_path(newpath);
abs_path.strip_last_element();
Canonical_path symlink_name(newpath);
symlink_name.keep_only_last_element();
try {
/*
* Open directory that contains the file to be opened/created
*/
File_system::Dir_handle const dir_handle =
file_system()->dir(abs_path.base(), false);
Node_handle_guard guard(dir_handle);
File_system::Symlink_handle symlink_handle =
file_system()->symlink(dir_handle, symlink_name.base() + 1, true);
if (symlink_handle.value == -1) {
errno = ENOSYS;
return -1;
}
Plugin_context *context = new (Genode::env()->heap())
Plugin_context(symlink_handle);
Libc::File_descriptor *fd =
Libc::file_descriptor_allocator()->alloc(this, context);
ssize_t res = write(fd, oldpath, strlen(oldpath) + 1);
Libc::file_descriptor_allocator()->free(fd);
destroy(Genode::env()->heap(), context);
if (res == -1) {
errno = EIO;
return -1;
}
return 0;
}
catch (File_system::Lookup_failed) {
PERR("symlink(%s) lookup failed", newpath); }
errno = ENOENT;
return -1;
}
int unlink(const char *path)
{
Canonical_path dir_path(path);
dir_path.strip_last_element();
Canonical_path basename(path);
basename.keep_only_last_element();
try {
/*
* Open directory that contains the file to be opened/created
*/
File_system::Dir_handle const dir_handle =
file_system()->dir(dir_path.base(), false);
Node_handle_guard guard(dir_handle);
file_system()->unlink(dir_handle, basename.base() + 1);
} catch (...) {
PERR("unlink(%s) failed", path);
return -1;
}
return 0;
}
ssize_t write(Libc::File_descriptor *fd, const void *buf, ::size_t count)
{
Genode::Lock::Guard guard(_rw_lock);
File_system::Session::Tx::Source &source = *file_system()->tx();
size_t const max_packet_size = session_max_packet_size(source);
size_t remaining_count = count;
while (remaining_count) {
size_t curr_packet_size = Genode::min(remaining_count, max_packet_size);
File_system::Packet_descriptor
packet(source.alloc_packet(curr_packet_size),
static_cast<File_system::Packet_ref *>(context(fd)),
context(fd)->node_handle(),
File_system::Packet_descriptor::WRITE,
curr_packet_size,
context(fd)->seek_offset());
/* copy-in payload into packet */
memcpy(source.packet_content(packet), buf, curr_packet_size);
/* pass packet to server side */
source.submit_packet(packet);
packet = source.get_acked_packet();
/* prepare next iteration */
context(fd)->advance_seek_offset(curr_packet_size);
buf = (void *)((Genode::addr_t)buf + curr_packet_size);
remaining_count -= curr_packet_size;
source.release_packet(packet);
}
if (verbose)
PDBG("write returns %zd", count);
return count;
}
void *mmap(void *addr_in, ::size_t length, int prot, int flags,
Libc::File_descriptor *fd, ::off_t offset)
{
if (prot != PROT_READ) {
PERR("mmap for prot=%x not supported", prot);
errno = EACCES;
return (void *)-1;
}
if (addr_in != 0) {
PERR("mmap for predefined address not supported");
errno = EINVAL;
return (void *)-1;
}
void *addr = Libc::mem_alloc()->alloc(length, PAGE_SHIFT);
if (addr == (void *)-1) {
errno = ENOMEM;
return (void *)-1;
}
if (::pread(fd->libc_fd, addr, length, offset) < 0) {
PERR("mmap could not obtain file content");
::munmap(addr, length);
errno = EACCES;
return (void *)-1;
}
return addr;
}
int munmap(void *addr, ::size_t)
{
Libc::mem_alloc()->free(addr);
return 0;
}
};
} /* unnamed namespace */
void __attribute__((constructor)) init_libc_fs(void)
{
PDBG("using the libc_fs plugin");
static Plugin plugin;
}