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

715 lines
16 KiB
C++

/*
* \brief Libc plugin for accessing a file-system session
* \author Norman Feske
* \date 2012-04-11
*/
/*
* Copyright (C) 2012 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>
/* libc includes */
#include <errno.h>
#include <dirent.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* libc plugin interface */
#include <libc-plugin/plugin.h>
#include <libc-plugin/fd_alloc.h>
static bool const verbose = false;
namespace File_system { struct Packet_ref { }; }
namespace {
enum { PATH_MAX_LEN = 256 };
/**
* Current working directory
*/
struct Cwd
{
char path[PATH_MAX_LEN];
Cwd() { Genode::strncpy(path, "/", sizeof(path)); }
};
static Cwd *cwd()
{
static Cwd cwd_inst;
return &cwd_inst;
}
struct Canonical_path
{
char str[PATH_MAX_LEN];
Canonical_path(char const *pathname)
{
/*
* If pathname is a relative path, prepend the current working
* directory.
*
* XXX we might consider using Noux' 'Path' class here
*/
if (pathname[0] != '/') {
snprintf(str, sizeof(str), "%s/%s", cwd()->path, pathname);
} else {
strncpy(str, pathname, sizeof(str));
}
}
};
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;
/**
* Current file position if manually seeked, or ~0 for append mode
*/
off_t _seek_offset;
public:
bool in_flight;
Plugin_context(File_system::File_handle handle)
: _type(TYPE_FILE), _node_handle(handle), _seek_offset(~0), in_flight(false) { }
Plugin_context(File_system::Dir_handle handle)
: _type(TYPE_DIR), _node_handle(handle), _seek_offset(0), in_flight(false) { }
Plugin_context(File_system::Symlink_handle handle)
: _type(TYPE_SYMLINK), _node_handle(handle), _seek_offset(~0), in_flight(false) { }
File_system::Node_handle node_handle() const { return _node_handle; }
/**
* 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 void wait_for_acknowledgement(File_system::Session::Tx::Source &source)
{
::File_system::Packet_descriptor packet = source.get_acked_packet();
PDBG("got acknowledgement for packet of size %zd", packet.size());
static_cast<Plugin_context *>(packet.ref())->in_flight = false;
source.release_packet(packet);
}
/**
* Collect pending packet acknowledgements, freeing the space occupied
* by the packet in the bulk buffer
*
* This function should be called prior enqueing new packets into the
* packet stream to free up space in the bulk buffer.
*/
static void collect_acknowledgements(File_system::Session::Tx::Source &source)
{
while (source.ack_avail())
wait_for_acknowledgement(source);
}
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)
PERR("mktime() returned -1, the file modification time reported by stat() will be incorrect");
}
class Plugin : public Libc::Plugin
{
private:
::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
*/
Plugin() { }
~Plugin() { }
bool supports_chdir(const char *path)
{
if (verbose)
PDBG("path = %s", path);
return true;
}
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_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_unlink(const char *path)
{
if (verbose)
PDBG("path = %s", path);
return true;
}
int chdir(const char *path)
{
if (*path != '/') {
PERR("chdir: relative path names not yet supported");
errno = ENOENT;
return -1;
}
Genode::strncpy(cwd()->path, path, sizeof(cwd()->path));
/* strip trailing slash if needed */
char *s = cwd()->path;
for (; s[0] && s[1]; s++);
if (s[0] == '/')
s[0] = 0;
return 0;
}
int close(Libc::File_descriptor *fd)
{
/* wait for the completion of all operations of the context */
while (context(fd)->in_flight) {
PDBG("wait_for_acknowledgement");
wait_for_acknowledgement(*file_system()->tx());
}
file_system()->close(context(fd)->node_handle());
return 0;
}
int fcntl(Libc::File_descriptor *, int cmd, long arg)
{
PDBG("not implemented");
/* libc's opendir() fails if fcntl() returns -1, so we return 0 here */
if (verbose)
PDBG("fcntl() called - not yet implemented");
return 0;
}
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)
{
PDBG("not implemented");
/* 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)
{
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.str, 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 {
PDBG("open dir '%s'", path.str);
File_system::Dir_handle const handle =
file_system()->dir(path.str, 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
*/
unsigned last_slash = 0;
for (unsigned i = 0; path.str[i]; i++)
if (path.str[i] == '/')
last_slash = i;
char dir_path[256] = "/";
if (last_slash > 0)
Genode::strncpy(dir_path, path.str,
Genode::min(sizeof(dir_path), last_slash + 1));
/*
* Determine base name
*/
char const *basename = path.str + last_slash + 1;
try {
/*
* Open directory that contains the file to be opened/created
*/
File_system::Dir_handle const dir_handle =
file_system()->dir(dir_path, 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, mode, create);
opened = true;
} catch (File_system::Node_already_exists) {
if (flags & O_EXCL)
throw File_system::Node_already_exists();
/* try to open the existing file */
try {
handle = file_system()->file(dir_handle, basename, 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);
return Libc::file_descriptor_allocator()->alloc(this, context);
}
catch (File_system::Lookup_failed) {
PERR("open(%s) lookup failed", pathname); }
return 0;
}
int rename(const char *oldpath, const char *newpath)
{
PDBG("not implemented");
return -1;
}
ssize_t read(Libc::File_descriptor *fd, void *buf, ::size_t count)
{
File_system::Session::Tx::Source &source = *file_system()->tx();
size_t const max_packet_size = source.bulk_buffer_size() / 2;
size_t remaining_count = count;
if (context(fd)->seek_offset() == ~0)
context(fd)->seek_offset(0);
while (remaining_count) {
collect_acknowledgements(source);
size_t curr_packet_size = Genode::min(remaining_count, max_packet_size);
/*
* XXX handle 'Packet_alloc_failed' exception'
*/
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());
/* mark context as having an operation in flight */
context(fd)->in_flight = true;
/* pass packet to server side */
source.submit_packet(packet);
do {
packet = source.get_acked_packet();
static_cast<Plugin_context *>(packet.ref())->in_flight = false;
} while (context(fd)->in_flight);
context(fd)->in_flight = false;
/*
* 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;
}
int stat(const char *pathname, struct stat *buf)
{
PDBG("stat %s", pathname);
Canonical_path path(pathname);
try {
File_system::Node_handle const node_handle =
file_system()->node(path.str);
Node_handle_guard guard(node_handle);
obtain_stat_for_node(node_handle, buf);
return 0;
}
catch (File_system::Lookup_failed) {
PERR("lookup failed");
errno = ENOENT;
}
return -1;
}
int unlink(const char *path)
{
return -1;
}
ssize_t write(Libc::File_descriptor *fd, const void *buf, ::size_t count)
{
File_system::Session::Tx::Source &source = *file_system()->tx();
size_t const max_packet_size = source.bulk_buffer_size() / 2;
size_t remaining_count = count;
while (remaining_count) {
collect_acknowledgements(source);
size_t curr_packet_size = Genode::min(remaining_count, max_packet_size);
/*
* XXX handle 'Packet_alloc_failed' exception'
*/
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());
/* mark context as having an operation in flight */
context(fd)->in_flight = true;
/* copy-in payload into packet */
memcpy(source.packet_content(packet), buf, curr_packet_size);
/* pass packet to server side */
source.submit_packet(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;
}
PDBG("write returns %zd", count);
return count;
}
};
} /* unnamed namespace */
void __attribute__((constructor)) init_libc_fs(void)
{
PDBG("using the libc_fs plugin");
static Plugin plugin;
}