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