/* * \brief Libc plugin for using a process-local virtual file system * \author Norman Feske * \author Christian Helmuth * \author Emery Hemingway * \date 2014-04-09 */ /* * Copyright (C) 2014-2017 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. */ /* Genode includes */ #include #include #include /* libc includes */ #include #include #include #include #include #include #include #include #include #include #include #include /* libc plugin interface */ #include #include /* libc-internal includes */ #include "libc_mem_alloc.h" #include "libc_errno.h" #include "task.h" static Vfs::Vfs_handle *vfs_handle(Libc::File_descriptor *fd) { return reinterpret_cast(fd->context); } static Libc::Plugin_context *vfs_context(Vfs::Vfs_handle *vfs_handle) { return reinterpret_cast(vfs_handle); } /** * Utility to convert VFS stat struct to the libc stat struct * * Code shared between 'stat' and 'fstat'. */ static void vfs_stat_to_libc_stat_struct(Vfs::Directory_service::Stat const &src, struct stat *dst) { enum { FS_BLOCK_SIZE = 1024 }; Genode::memset(dst, 0, sizeof(*dst)); dst->st_uid = src.uid; dst->st_gid = src.gid; dst->st_mode = src.mode; dst->st_size = src.size; dst->st_blksize = FS_BLOCK_SIZE; dst->st_blocks = (dst->st_size + FS_BLOCK_SIZE - 1) / FS_BLOCK_SIZE; dst->st_ino = src.inode; dst->st_dev = src.device; } static Genode::Xml_node *_config_node; namespace Libc { void libc_config_init(Genode::Xml_node node) { static Genode::Xml_node config = node; _config_node = &config; } Genode::Xml_node config() __attribute__((weak)); Genode::Xml_node config() { if (!_config_node) { error("libc config not initialized - aborting"); exit(1); } return *_config_node; } class Config_attr { private: char _buf[Vfs::MAX_PATH_LEN]; public: Config_attr(char const *attr_name, char const *default_value) { Genode::strncpy(_buf, default_value, sizeof(_buf)); try { Libc::config().attribute(attr_name).value(_buf, sizeof(_buf)); } catch (...) { } } char const *string() const { return _buf; } }; char const *config_rtc() __attribute__((weak)); char const *config_rtc() { static Config_attr rtc("rtc", ""); return rtc.string(); } char const *config_socket() __attribute__((weak)); char const *config_socket() { static Config_attr socket("socket", ""); return socket.string(); } void notify_read_ready(Vfs::Vfs_handle *handle) { struct Check : Libc::Suspend_functor { Vfs::Vfs_handle *handle; Check(Vfs::Vfs_handle *handle) : handle(handle) { } bool suspend() override { return !handle->fs().notify_read_ready(handle); } } check(handle); while (!handle->fs().notify_read_ready(handle)) Libc::suspend(check); } bool read_ready(Libc::File_descriptor *fd) { Vfs::Vfs_handle *handle = vfs_handle(fd); if (!handle) return false; notify_read_ready(handle); return handle->fs().read_ready(handle); } } int Libc::Vfs_plugin::access(const char *path, int amode) { if (_root_dir.leaf_path(path)) return 0; errno = ENOENT; return -1; } Libc::File_descriptor *Libc::Vfs_plugin::open(char const *path, int flags, int libc_fd) { if (_root_dir.directory(path)) { if (((flags & O_ACCMODE) != O_RDONLY)) { errno = EINVAL; return nullptr; } Vfs::Vfs_handle *handle = 0; typedef Vfs::Directory_service::Opendir_result Opendir_result; switch (_root_dir.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; case Opendir_result::OPENDIR_ERR_NODE_ALREADY_EXISTS: errno = EEXIST; return nullptr; case Opendir_result::OPENDIR_ERR_NO_SPACE: errno = ENOSPC; return nullptr; case Opendir_result::OPENDIR_ERR_OUT_OF_RAM: case Opendir_result::OPENDIR_ERR_OUT_OF_CAPS: case Opendir_result::OPENDIR_ERR_PERMISSION_DENIED: errno = EPERM; return nullptr; } /* the directory was successfully opened */ Libc::File_descriptor *fd = Libc::file_descriptor_allocator()->alloc(this, vfs_context(handle), libc_fd); /* FIXME error cleanup code leaks resources! */ if (!fd) { errno = EMFILE; return nullptr; } fd->flags = flags & O_ACCMODE; return fd; } typedef Vfs::Directory_service::Open_result Result; Vfs::Vfs_handle *handle = 0; while (handle == 0) { switch (_root_dir.open(path, flags, &handle, _alloc)) { case Result::OPEN_OK: break; case Result::OPEN_ERR_UNACCESSIBLE: { if (!(flags & O_CREAT)) { if (flags & O_NOFOLLOW) { errno = ELOOP; return 0; } errno = ENOENT; return 0; } /* O_CREAT is set, so try to create the file */ switch (_root_dir.open(path, flags | O_EXCL, &handle, _alloc)) { case Result::OPEN_OK: break; case Result::OPEN_ERR_EXISTS: /* file has been created by someone else in the meantime */ if (flags & O_NOFOLLOW) { errno = ELOOP; return 0; } errno = EEXIST; return 0; case Result::OPEN_ERR_NO_PERM: errno = EPERM; return 0; case Result::OPEN_ERR_UNACCESSIBLE: errno = ENOENT; return 0; case Result::OPEN_ERR_NAME_TOO_LONG: errno = ENAMETOOLONG; return 0; case Result::OPEN_ERR_NO_SPACE: errno = ENOSPC; return 0; case Result::OPEN_ERR_OUT_OF_RAM: errno = ENOSPC; return 0; case Result::OPEN_ERR_OUT_OF_CAPS: errno = ENOSPC; return 0; } } break; case Result::OPEN_ERR_NO_PERM: errno = EPERM; return 0; case Result::OPEN_ERR_EXISTS: errno = EEXIST; return 0; case Result::OPEN_ERR_NAME_TOO_LONG: errno = ENAMETOOLONG; return 0; case Result::OPEN_ERR_NO_SPACE: errno = ENOSPC; return 0; case Result::OPEN_ERR_OUT_OF_RAM: errno = ENOSPC; return 0; case Result::OPEN_ERR_OUT_OF_CAPS: errno = ENOSPC; return 0; } } /* the file was successfully opened */ Libc::File_descriptor *fd = Libc::file_descriptor_allocator()->alloc(this, vfs_context(handle), libc_fd); /* FIXME error cleanup code leaks resources! */ if (!fd) { errno = EMFILE; return nullptr; } fd->flags = flags & (O_ACCMODE|O_NONBLOCK|O_APPEND); if ((flags & O_TRUNC) && (ftruncate(fd, 0) == -1)) { errno = EINVAL; /* XXX which error code fits best ? */ return nullptr; } return fd; } int Libc::Vfs_plugin::close(Libc::File_descriptor *fd) { Vfs::Vfs_handle *handle = vfs_handle(fd); _vfs_sync(handle); handle->ds().close(handle); Libc::file_descriptor_allocator()->free(fd); return 0; } int Libc::Vfs_plugin::dup2(Libc::File_descriptor *fd, Libc::File_descriptor *new_fd) { new_fd->context = fd->context; return new_fd->libc_fd; } int Libc::Vfs_plugin::fstat(Libc::File_descriptor *fd, struct stat *buf) { return stat(fd->fd_path, buf); } int Libc::Vfs_plugin::fstatfs(Libc::File_descriptor *fd, struct statfs *buf) { if (!fd || !buf) return Libc::Errno(EFAULT); Genode::memset(buf, 0, sizeof(*buf)); buf->f_flags = MNT_UNION; return 0; } int Libc::Vfs_plugin::mkdir(const char *path, mode_t mode) { Vfs::Vfs_handle *dir_handle { 0 }; typedef Vfs::Directory_service::Opendir_result Opendir_result; switch (_root_dir.opendir(path, true, &dir_handle, _alloc)) { case Opendir_result::OPENDIR_OK: dir_handle->ds().close(dir_handle); break; case Opendir_result::OPENDIR_ERR_LOOKUP_FAILED: return Errno(ENOENT); case Opendir_result::OPENDIR_ERR_NAME_TOO_LONG: return Errno(ENAMETOOLONG); case Opendir_result::OPENDIR_ERR_NODE_ALREADY_EXISTS: return Errno(EEXIST); case Opendir_result::OPENDIR_ERR_NO_SPACE: return Errno(ENOSPC); case Opendir_result::OPENDIR_ERR_OUT_OF_RAM: case Opendir_result::OPENDIR_ERR_OUT_OF_CAPS: case Opendir_result::OPENDIR_ERR_PERMISSION_DENIED: return Errno(EPERM); } return 0; } int Libc::Vfs_plugin::stat(char const *path, struct stat *buf) { if (!path or !buf) { errno = EFAULT; return -1; } typedef Vfs::Directory_service::Stat_result Result; Vfs::Directory_service::Stat stat; switch (_root_dir.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; } vfs_stat_to_libc_stat_struct(stat, buf); return 0; } ssize_t Libc::Vfs_plugin::write(Libc::File_descriptor *fd, const void *buf, ::size_t count) { typedef Vfs::File_io_service::Write_result Result; Vfs::Vfs_handle *handle = vfs_handle(fd); Vfs::file_size out_count = 0; Result out_result = Result::WRITE_OK; if (fd->flags & O_NONBLOCK) { try { out_result = handle->fs().write(handle, (char const *)buf, count, out_count); } catch (Vfs::File_io_service::Insufficient_buffer) { } } else { struct Check : Libc::Suspend_functor { bool retry { false }; Vfs::Vfs_handle *handle; void const *buf; ::size_t count; Vfs::file_size &out_count; Result &out_result; Check(Vfs::Vfs_handle *handle, void const *buf, ::size_t count, Vfs::file_size &out_count, Result &out_result) : handle(handle), buf(buf), count(count), out_count(out_count), out_result(out_result) { } bool suspend() override { try { out_result = handle->fs().write(handle, (char const *)buf, count, out_count); retry = false; } catch (Vfs::File_io_service::Insufficient_buffer) { retry = true; } return retry; } } check(handle, buf, count, out_count, out_result); do { Libc::suspend(check); } while (check.retry); } switch (out_result) { case Result::WRITE_ERR_AGAIN: return Errno(EAGAIN); case Result::WRITE_ERR_WOULD_BLOCK: return Errno(EWOULDBLOCK); case Result::WRITE_ERR_INVALID: return Errno(EINVAL); case Result::WRITE_ERR_IO: return Errno(EIO); case Result::WRITE_ERR_INTERRUPT: return Errno(EINTR); case Result::WRITE_OK: break; } handle->advance_seek(out_count); return out_count; } ssize_t Libc::Vfs_plugin::read(Libc::File_descriptor *fd, void *buf, ::size_t count) { Libc::dispatch_pending_io_signals(); typedef Vfs::File_io_service::Read_result Result; Vfs::Vfs_handle *handle = vfs_handle(fd); if (fd->flags & O_NONBLOCK && !Libc::read_ready(fd)) return Errno(EAGAIN); { struct Check : Libc::Suspend_functor { bool retry { false }; Vfs::Vfs_handle *handle; ::size_t count; Check(Vfs::Vfs_handle *handle, ::size_t count) : handle(handle), count(count) { } bool suspend() override { retry = !handle->fs().queue_read(handle, count); return retry; } } check ( handle, count); do { Libc::suspend(check); } while (check.retry); } Vfs::file_size out_count = 0; Result out_result; { struct Check : Libc::Suspend_functor { bool retry { false }; Vfs::Vfs_handle *handle; void *buf; ::size_t count; Vfs::file_size &out_count; Result &out_result; Check(Vfs::Vfs_handle *handle, void *buf, ::size_t count, Vfs::file_size &out_count, Result &out_result) : handle(handle), buf(buf), count(count), out_count(out_count), out_result(out_result) { } bool suspend() override { out_result = handle->fs().complete_read(handle, (char *)buf, count, out_count); /* suspend me if read is still queued */ retry = (out_result == Result::READ_QUEUED); return retry; } } check ( handle, buf, count, out_count, out_result); do { Libc::suspend(check); } while (check.retry); } switch (out_result) { case Result::READ_ERR_AGAIN: return Errno(EAGAIN); case Result::READ_ERR_WOULD_BLOCK: return Errno(EWOULDBLOCK); case Result::READ_ERR_INVALID: return Errno(EINVAL); case Result::READ_ERR_IO: return Errno(EIO); case Result::READ_ERR_INTERRUPT: return Errno(EINTR); case Result::READ_OK: break; case Result::READ_QUEUED: /* handled above, so never reached */ break; } handle->advance_seek(out_count); return out_count; } ssize_t Libc::Vfs_plugin::getdirentries(Libc::File_descriptor *fd, char *buf, ::size_t nbytes, ::off_t *basep) { if (nbytes < sizeof(struct dirent)) { Genode::error("getdirentries: buffer too small"); return -1; } typedef Vfs::File_io_service::Read_result Result; Vfs::Vfs_handle *handle = vfs_handle(fd); typedef Vfs::Directory_service::Dirent Dirent; Dirent dirent_out; { struct Check : Libc::Suspend_functor { bool retry { false }; Vfs::Vfs_handle *handle; Check(Vfs::Vfs_handle *handle) : handle(handle) { } bool suspend() override { retry = !handle->fs().queue_read(handle, sizeof(Dirent)); return retry; } } check(handle); do { Libc::suspend(check); } while (check.retry); } Result out_result; Vfs::file_size out_count; { struct Check : Libc::Suspend_functor { bool retry { false }; Vfs::Vfs_handle *handle; Dirent &dirent_out; Vfs::file_size &out_count; Result &out_result; Check(Vfs::Vfs_handle *handle, Dirent &dirent_out, Vfs::file_size &out_count, Result &out_result) : handle(handle), dirent_out(dirent_out), out_count(out_count), out_result(out_result) { } bool suspend() override { out_result = handle->fs().complete_read(handle, (char*)&dirent_out, sizeof(Dirent), out_count); /* suspend me if read is still queued */ retry = (out_result == Result::READ_QUEUED); return retry; } } check(handle, dirent_out, out_count, out_result); do { Libc::suspend(check); } while (check.retry); } if ((out_result != Result::READ_OK) || (out_count < sizeof(Dirent))) { return 0; } /* * Convert dirent structure from VFS to libc */ struct dirent *dirent = (struct dirent *)buf; Genode::memset(dirent, 0, sizeof(struct dirent)); switch (dirent_out.type) { case Vfs::Directory_service::DIRENT_TYPE_DIRECTORY: dirent->d_type = DT_DIR; break; case Vfs::Directory_service::DIRENT_TYPE_FILE: dirent->d_type = DT_REG; break; case Vfs::Directory_service::DIRENT_TYPE_SYMLINK: dirent->d_type = DT_LNK; break; case Vfs::Directory_service::DIRENT_TYPE_FIFO: dirent->d_type = DT_FIFO; break; case Vfs::Directory_service::DIRENT_TYPE_CHARDEV: dirent->d_type = DT_CHR; break; case Vfs::Directory_service::DIRENT_TYPE_BLOCKDEV: dirent->d_type = DT_BLK; break; case Vfs::Directory_service::DIRENT_TYPE_END: return 0; } dirent->d_fileno = dirent_out.fileno; dirent->d_reclen = sizeof(struct dirent); Genode::strncpy(dirent->d_name, dirent_out.name, sizeof(dirent->d_name)); dirent->d_namlen = Genode::strlen(dirent->d_name); /* * Keep track of VFS seek pointer and user-supplied basep. */ handle->advance_seek(sizeof(Vfs::Directory_service::Dirent)); *basep += sizeof(struct dirent); return sizeof(struct dirent); } int Libc::Vfs_plugin::ioctl(Libc::File_descriptor *fd, int request, char *argp) { using ::off_t; /* * Marshal ioctl arguments */ typedef Vfs::File_io_service::Ioctl_opcode Opcode; Opcode opcode = Opcode::IOCTL_OP_UNDEFINED; Vfs::File_io_service::Ioctl_arg arg = 0; switch (request) { case TIOCGWINSZ: opcode = Opcode::IOCTL_OP_TIOCGWINSZ; 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; ::termios *termios = (::termios *)argp; /* * For now only enabling/disabling of ECHO is supported */ if (termios->c_lflag & (ECHO | ECHONL)) { arg = (Vfs::File_io_service::IOCTL_VAL_ECHO | Vfs::File_io_service::IOCTL_VAL_ECHONL); } else { arg = Vfs::File_io_service::IOCTL_VAL_NULL; } break; } case TIOCSETAW: { opcode = Opcode::IOCTL_OP_TIOCSETAW; arg = argp ? *(int*)argp : 0; break; } case FIONBIO: { opcode = Opcode::IOCTL_OP_FIONBIO; arg = argp ? *(int*)argp : 0; break; } case DIOCGMEDIASIZE: { if (!argp) { errno = EINVAL; return -1; } opcode = Opcode::IOCTL_OP_DIOCGMEDIASIZE; arg = 0; break; } default: Genode::warning("unsupported ioctl (request=", Genode::Hex(request), ")"); break; } if (opcode == Opcode::IOCTL_OP_UNDEFINED) { errno = ENOTTY; return -1; } typedef Vfs::File_io_service::Ioctl_result Result; Vfs::File_io_service::Ioctl_out out; Genode::memset(&out, 0, sizeof(out)); Vfs::Vfs_handle *handle = vfs_handle(fd); switch (handle->fs().ioctl(handle, opcode, arg, out)) { case Result::IOCTL_ERR_INVALID: errno = EINVAL; return -1; case Result::IOCTL_ERR_NOTTY: errno = ENOTTY; return -1; case Result::IOCTL_OK: break; } /* * Unmarshal ioctl results */ switch (request) { case TIOCGWINSZ: { ::winsize *winsize = (::winsize *)arg; winsize->ws_row = out.tiocgwinsz.rows; winsize->ws_col = out.tiocgwinsz.columns; return 0; } case TIOCSETAF: case TIOCSETAW: return 0; case FIONBIO: return 0; case DIOCGMEDIASIZE: { /* resolve ambiguity with libc type */ using Genode::int64_t; int64_t *disk_size = (int64_t*)argp; *disk_size = out.diocgmediasize.size; return 0; } default: break; } return -1; } ::off_t Libc::Vfs_plugin::lseek(Libc::File_descriptor *fd, ::off_t offset, int whence) { Vfs::Vfs_handle *handle = vfs_handle(fd); switch (whence) { case SEEK_SET: handle->seek(offset); break; case SEEK_CUR: handle->advance_seek(offset); break; case SEEK_END: { struct stat stat; ::memset(&stat, 0, sizeof(stat)); fstat(fd, &stat); handle->seek(stat.st_size + offset); } break; } return handle->seek(); } int Libc::Vfs_plugin::ftruncate(Libc::File_descriptor *fd, ::off_t length) { Vfs::Vfs_handle *handle = vfs_handle(fd); typedef Vfs::File_io_service::Ftruncate_result Result; switch (handle->fs().ftruncate(handle, length)) { case Result::FTRUNCATE_ERR_NO_PERM: errno = EPERM; return -1; case Result::FTRUNCATE_ERR_INTERRUPT: errno = EINTR; return -1; case Result::FTRUNCATE_ERR_NO_SPACE: errno = ENOSPC; return -1; case Result::FTRUNCATE_OK: break; } return 0; } int Libc::Vfs_plugin::fcntl(Libc::File_descriptor *fd, int cmd, long arg) { switch (cmd) { case F_DUPFD: { /* * Allocate free file descriptor locally. */ Libc::File_descriptor *new_fd = Libc::file_descriptor_allocator()->alloc(this, 0); if (!new_fd) return Errno(EMFILE); new_fd->path(fd->fd_path); /* * Use new allocated number as name of file descriptor * duplicate. */ if (dup2(fd, new_fd) == -1) { Genode::error("Plugin::fcntl: dup2 unexpectedly failed"); return Errno(EINVAL); } return new_fd->libc_fd; } case F_GETFD: return fd->cloexec ? FD_CLOEXEC : 0; case F_SETFD: fd->cloexec = arg == FD_CLOEXEC; return 0; case F_GETFL: return fd->flags; case F_SETFL: fd->flags = arg; return 0; default: break; } Genode::error("fcntl(): command ", cmd, " not supported - vfs"); return Errno(EINVAL); } int Libc::Vfs_plugin::fsync(Libc::File_descriptor *fd) { Vfs::Vfs_handle *handle = vfs_handle(fd); _vfs_sync(handle); return 0; } int Libc::Vfs_plugin::symlink(const char *oldpath, const char *newpath) { typedef Vfs::Directory_service::Openlink_result Openlink_result; Vfs::Vfs_handle *handle { 0 }; Openlink_result openlink_result = _root_dir.openlink(newpath, true, &handle, _alloc); switch (openlink_result) { case Openlink_result::OPENLINK_OK: break; case Openlink_result::OPENLINK_ERR_LOOKUP_FAILED: return Errno(ENOENT); case Openlink_result::OPENLINK_ERR_NAME_TOO_LONG: return Errno(ENAMETOOLONG); case Openlink_result::OPENLINK_ERR_NODE_ALREADY_EXISTS: return Errno(EEXIST); case Openlink_result::OPENLINK_ERR_NO_SPACE: return Errno(ENOSPC); case Openlink_result::OPENLINK_ERR_OUT_OF_RAM: return Errno(ENOSPC); case Openlink_result::OPENLINK_ERR_OUT_OF_CAPS: return Errno(ENOSPC); case Vfs::Directory_service::OPENLINK_ERR_PERMISSION_DENIED: return Errno(EPERM); } Vfs::file_size count = ::strlen(oldpath) + 1; Vfs::file_size out_count = 0; struct Check : Libc::Suspend_functor { bool retry { false }; Vfs::Vfs_handle *handle; void const *buf; ::size_t count; Vfs::file_size &out_count; Check(Vfs::Vfs_handle *handle, void const *buf, ::size_t count, Vfs::file_size &out_count) : handle(handle), buf(buf), count(count), out_count(out_count) { } bool suspend() override { try { handle->fs().write(handle, (char const *)buf, count, out_count); retry = false; } catch (Vfs::File_io_service::Insufficient_buffer) { retry = true; } return retry; } } check ( handle, oldpath, count, out_count); do { Libc::suspend(check); } while (check.retry); _vfs_sync(handle); handle->ds().close(handle); if (out_count != count) return Errno(ENAMETOOLONG); return 0; } ssize_t Libc::Vfs_plugin::readlink(const char *path, char *buf, ::size_t buf_size) { Vfs::Vfs_handle *symlink_handle { 0 }; Vfs::Directory_service::Openlink_result openlink_result = _root_dir.openlink(path, false, &symlink_handle, _alloc); switch (openlink_result) { case Vfs::Directory_service::OPENLINK_OK: break; case Vfs::Directory_service::OPENLINK_ERR_LOOKUP_FAILED: return Errno(ENOENT); case Vfs::Directory_service::OPENLINK_ERR_NAME_TOO_LONG: /* should not happen */ return Errno(ENAMETOOLONG); case Vfs::Directory_service::OPENLINK_ERR_NODE_ALREADY_EXISTS: case Vfs::Directory_service::OPENLINK_ERR_NO_SPACE: case Vfs::Directory_service::OPENLINK_ERR_OUT_OF_RAM: case Vfs::Directory_service::OPENLINK_ERR_OUT_OF_CAPS: case Vfs::Directory_service::OPENLINK_ERR_PERMISSION_DENIED: return Errno(EACCES); } { struct Check : Libc::Suspend_functor { bool retry { false }; Vfs::Vfs_handle *symlink_handle; ::size_t const buf_size; Check(Vfs::Vfs_handle *symlink_handle, ::size_t const buf_size) : symlink_handle(symlink_handle), buf_size(buf_size) { } bool suspend() override { retry = !symlink_handle->fs().queue_read(symlink_handle, buf_size); return retry; } } check(symlink_handle, buf_size); do { Libc::suspend(check); } while (check.retry); } typedef Vfs::File_io_service::Read_result Result; Result out_result; Vfs::file_size out_len = 0; { struct Check : Libc::Suspend_functor { bool retry { false }; Vfs::Vfs_handle *symlink_handle; char *buf; ::size_t const buf_size; Vfs::file_size &out_len; Result &out_result; Check(Vfs::Vfs_handle *symlink_handle, char *buf, ::size_t const buf_size, Vfs::file_size &out_len, Result &out_result) : symlink_handle(symlink_handle), buf(buf), buf_size(buf_size), out_len(out_len), out_result(out_result) { } bool suspend() override { out_result = symlink_handle->fs().complete_read(symlink_handle, buf, buf_size, out_len); /* suspend me if read is still queued */ retry = (out_result == Result::READ_QUEUED); return retry; } } check(symlink_handle, buf, buf_size, out_len, out_result); do { Libc::suspend(check); } while (check.retry); } switch (out_result) { case Result::READ_ERR_AGAIN: return Errno(EAGAIN); case Result::READ_ERR_WOULD_BLOCK: return Errno(EWOULDBLOCK); case Result::READ_ERR_INVALID: return Errno(EINVAL); case Result::READ_ERR_IO: return Errno(EIO); case Result::READ_ERR_INTERRUPT: return Errno(EINTR); case Result::READ_OK: break; case Result::READ_QUEUED: /* handled above, so never reached */ break; }; symlink_handle->ds().close(symlink_handle); return out_len; } int Libc::Vfs_plugin::rmdir(char const *path) { return unlink(path); } int Libc::Vfs_plugin::unlink(char const *path) { typedef Vfs::Directory_service::Unlink_result Result; switch (_root_dir.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; case Result::UNLINK_OK: break; } return 0; } int Libc::Vfs_plugin::rename(char const *from_path, char const *to_path) { typedef Vfs::Directory_service::Rename_result Result; if (_root_dir.leaf_path(to_path)) { if (_root_dir.directory(to_path)) { if (!_root_dir.directory(from_path)) { errno = EISDIR; return -1; } if (_root_dir.num_dirent(to_path)) { errno = ENOTEMPTY; return -1; } } else { if (_root_dir.directory(from_path)) { errno = ENOTDIR; return -1; } } } switch (_root_dir.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; case Result::RENAME_OK: break; } return 0; } void *Libc::Vfs_plugin::mmap(void *addr_in, ::size_t length, int prot, int flags, Libc::File_descriptor *fd, ::off_t offset) { if (prot != PROT_READ) { Genode::error("mmap for prot=", Genode::Hex(prot), " not supported"); errno = EACCES; return (void *)-1; } if (addr_in != 0) { Genode::error("mmap for predefined address not supported"); errno = EINVAL; return (void *)-1; } /* * XXX attempt to obtain memory mapping via * 'Vfs::Directory_service::dataspace'. */ 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) { Genode::error("mmap could not obtain file content"); ::munmap(addr, length); errno = EACCES; return (void *)-1; } return addr; } int Libc::Vfs_plugin::munmap(void *addr, ::size_t) { Libc::mem_alloc()->free(addr); return 0; } bool Libc::Vfs_plugin::supports_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) { /* return true if any file descriptor (which is set) belongs to the VFS */ for (int fd = 0; fd < nfds; ++fd) { if (FD_ISSET(fd, readfds) || FD_ISSET(fd, writefds) || FD_ISSET(fd, exceptfds)) { Libc::File_descriptor *fdo = Libc::file_descriptor_allocator()->find_by_libc_fd(fd); if (fdo && (fdo->plugin == this)) return true; } } return false; } int Libc::Vfs_plugin::select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) { int nready = 0; fd_set const in_readfds = *readfds; fd_set const in_writefds = *writefds; /* XXX exceptfds not supported */ /* clear fd sets */ FD_ZERO(readfds); FD_ZERO(writefds); FD_ZERO(exceptfds); for (int fd = 0; fd < nfds; ++fd) { Libc::File_descriptor *fdo = Libc::file_descriptor_allocator()->find_by_libc_fd(fd); /* handle only fds that belong to this plugin */ if (!fdo || (fdo->plugin != this)) continue; Vfs::Vfs_handle *handle = vfs_handle(fdo); if (!handle) continue; if (FD_ISSET(fd, &in_readfds)) { if (handle->fs().read_ready(handle)) { FD_SET(fd, readfds); ++nready; } else { Libc::notify_read_ready(handle); } } if (FD_ISSET(fd, &in_writefds)) { if (true /* XXX always writeable */) { FD_SET(fd, writefds); ++nready; } } /* XXX exceptfds not supported */ } return nready; }