/* * \brief Adapter from Genode 'File_system' session to Noux file system * \author Norman Feske * \date 2011-02-17 */ /* * 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. */ #ifndef _NOUX__FS_FILE_SYSTEM_H_ #define _NOUX__FS_FILE_SYSTEM_H_ /* Genode includes */ #include #include /* Noux includes */ #include #include namespace Noux { class Fs_file_system : public File_system { enum { verbose = false }; private: Lock _lock; Allocator_avl _fs_packet_alloc; struct Label { enum { LABEL_MAX_LEN = 64 }; char string[LABEL_MAX_LEN]; Label(Xml_node config) { string[0] = 0; try { config.attribute("label").value(string, sizeof(string)); } catch (...) { } } } _label; ::File_system::Connection _fs; class Fs_vfs_handle : public Vfs_handle { private: ::File_system::File_handle const _handle; public: Fs_vfs_handle(File_system *fs, int status_flags, ::File_system::File_handle handle) : Vfs_handle(fs, fs, status_flags), _handle(handle) { } ~Fs_vfs_handle() { Fs_file_system *fs = static_cast(ds()); fs->_fs.close(_handle); } ::File_system::File_handle file_handle() const { return _handle; } }; /** * Helper for managing the lifetime of temporary open node handles */ struct Fs_handle_guard { ::File_system::Session &_fs; ::File_system::Node_handle _handle; Fs_handle_guard(::File_system::Session &fs, ::File_system::Node_handle handle) : _fs(fs), _handle(handle) { } ~Fs_handle_guard() { _fs.close(_handle); } }; size_t _read(::File_system::Node_handle node_handle, void *buf, size_t count, size_t seek_offset) { ::File_system::Session::Tx::Source &source = *_fs.tx(); size_t const max_packet_size = source.bulk_buffer_size() / 2; count = min(max_packet_size, count); ::File_system::Packet_descriptor packet(source.alloc_packet(count), 0, node_handle, ::File_system::Packet_descriptor::READ, count, seek_offset); /* pass packet to server side */ source.submit_packet(packet); source.get_acked_packet(); memcpy(buf, source.packet_content(packet), count); /* * XXX check if acked packet belongs to request, * needed for thread safety */ source.release_packet(packet); return count; } size_t _write(::File_system::Node_handle node_handle, const char *buf, size_t count, size_t seek_offset) { ::File_system::Session::Tx::Source &source = *_fs.tx(); size_t const max_packet_size = source.bulk_buffer_size() / 2; count = min(max_packet_size, count); ::File_system::Packet_descriptor packet(source.alloc_packet(count), 0, node_handle, ::File_system::Packet_descriptor::WRITE, count, seek_offset); memcpy(source.packet_content(packet), buf, count); /* pass packet to server side */ source.submit_packet(packet); source.get_acked_packet(); /* * XXX check if acked packet belongs to request, * needed for thread safety */ source.release_packet(packet); return count; } public: /* * XXX read label from config */ Fs_file_system(Xml_node config) : _fs_packet_alloc(env()->heap()), _label(config), _fs(_fs_packet_alloc, 128*1024, _label.string) { } /********************************* ** Directory-service interface ** *********************************/ Dataspace_capability dataspace(char const *path) { Lock::Guard guard(_lock); Absolute_path dir_path(path); dir_path.strip_last_element(); Absolute_path file_name(path); file_name.keep_only_last_element(); Ram_dataspace_capability ds_cap; char *local_addr = 0; try { ::File_system::Dir_handle dir = _fs.dir(dir_path.base(), false); Fs_handle_guard dir_guard(_fs, dir); ::File_system::File_handle file = _fs.file(dir, file_name.base() + 1, ::File_system::READ_ONLY, false); Fs_handle_guard file_guard(_fs, file); ::File_system::Status status = _fs.status(file); Ram_dataspace_capability ds_cap = env()->ram_session()->alloc(status.size); local_addr = env()->rm_session()->attach(ds_cap); ::File_system::Session::Tx::Source &source = *_fs.tx(); size_t const max_packet_size = source.bulk_buffer_size() / 2; for (size_t seek_offset = 0; seek_offset < status.size; seek_offset += max_packet_size) { size_t const count = min(max_packet_size, status.size - seek_offset); ::File_system::Packet_descriptor packet(source.alloc_packet(count), 0, file, ::File_system::Packet_descriptor::READ, count, seek_offset); /* pass packet to server side */ source.submit_packet(packet); source.get_acked_packet(); memcpy(local_addr + seek_offset, source.packet_content(packet), count); /* * XXX check if acked packet belongs to request, * needed for thread safety */ source.release_packet(packet); } env()->rm_session()->detach(local_addr); return ds_cap; } catch(...) { env()->rm_session()->detach(local_addr); env()->ram_session()->free(ds_cap); return Dataspace_capability(); } } void release(char const *path, Dataspace_capability ds_cap) { env()->ram_session()->free(static_cap_cast(ds_cap)); } bool stat(Sysio *sysio, char const *path) { ::File_system::Status status; try { ::File_system::Node_handle node = _fs.node(path); Fs_handle_guard node_guard(_fs, node); status = _fs.status(node); } catch (...) { if (verbose) PDBG("stat failed for path '%s'", path); return false; } memset(&sysio->stat_out.st, 0, sizeof(sysio->stat_out.st)); sysio->stat_out.st.size = status.size; sysio->stat_out.st.mode = Sysio::STAT_MODE_FILE | 0777; if (status.is_symlink()) sysio->stat_out.st.mode = Sysio::STAT_MODE_SYMLINK | 0777; if (status.is_directory()) sysio->stat_out.st.mode = Sysio::STAT_MODE_DIRECTORY | 0777; sysio->stat_out.st.uid = 0; sysio->stat_out.st.gid = 0; return true; } bool dirent(Sysio *sysio, char const *path, off_t index) { Lock::Guard guard(_lock); ::File_system::Session::Tx::Source &source = *_fs.tx(); if (strcmp(path, "") == 0) path = "/"; ::File_system::Dir_handle dir_handle = _fs.dir(path, false); Fs_handle_guard dir_guard(_fs, dir_handle); enum { DIRENT_SIZE = sizeof(::File_system::Directory_entry) }; ::File_system::Packet_descriptor packet(source.alloc_packet(DIRENT_SIZE), 0, dir_handle, ::File_system::Packet_descriptor::READ, DIRENT_SIZE, index*DIRENT_SIZE); /* pass packet to server side */ source.submit_packet(packet); source.get_acked_packet(); /* * XXX check if acked packet belongs to request, * needed for thread safety */ typedef ::File_system::Directory_entry Directory_entry; /* copy-out payload into destination buffer */ Directory_entry const *entry = (Directory_entry *)source.packet_content(packet); /* * The default value has no meaning because the switch below * assigns a value in each possible branch. But it is needed to * keep the compiler happy. */ Sysio::Dirent_type type = Sysio::DIRENT_TYPE_END; switch (entry->type) { case Directory_entry::TYPE_DIRECTORY: type = Sysio::DIRENT_TYPE_DIRECTORY; break; case Directory_entry::TYPE_FILE: type = Sysio::DIRENT_TYPE_FILE; break; case Directory_entry::TYPE_SYMLINK: type = Sysio::DIRENT_TYPE_SYMLINK; break; } sysio->dirent_out.entry.type = type; sysio->dirent_out.entry.fileno = index + 1; strncpy(sysio->dirent_out.entry.name, entry->name, sizeof(sysio->dirent_out.entry.name)); source.release_packet(packet); return true; } bool unlink(Sysio *sysio, char const *path) { Lock::Guard guard(_lock); Absolute_path dir_path(path); dir_path.strip_last_element(); Absolute_path file_name(path); file_name.keep_only_last_element(); try { ::File_system::Dir_handle dir = _fs.dir(dir_path.base(), false); Fs_handle_guard dir_guard(_fs, dir); _fs.unlink(dir, file_name.base() + 1); } catch (...) { sysio->error.unlink = Sysio::UNLINK_ERR_NO_ENTRY; return false; } return true; } bool readlink(Sysio *sysio, char const *path) { /* * Canonicalize path (i.e., path must start with '/') */ Absolute_path abs_path(path); abs_path.strip_last_element(); Absolute_path symlink_name(path); symlink_name.keep_only_last_element(); Sysio::Readlink_error error = Sysio::READLINK_ERR_NO_ENTRY; try { ::File_system::Dir_handle dir_handle = _fs.dir(abs_path.base(), false); Fs_handle_guard from_dir_guard(_fs, dir_handle); ::File_system::Symlink_handle symlink_handle = _fs.symlink(dir_handle, symlink_name.base() + 1, false); Fs_handle_guard symlink_guard(_fs, symlink_handle); sysio->readlink_out.count = _read(symlink_handle, sysio->readlink_out.chunk, min(sysio->readlink_in.bufsiz, sizeof(sysio->readlink_out.chunk)), 0); return true; } catch (...) { } sysio->error.readlink = error; return false; } bool rename(Sysio *sysio, char const *from_path, char const *to_path) { Absolute_path from_dir_path(from_path); from_dir_path.strip_last_element(); Absolute_path from_file_name(from_path); from_file_name.keep_only_last_element(); Absolute_path to_dir_path(to_path); to_dir_path.strip_last_element(); Absolute_path to_file_name(to_path); to_file_name.keep_only_last_element(); try { ::File_system::Dir_handle from_dir = _fs.dir(from_dir_path.base(), false); Fs_handle_guard from_dir_guard(_fs, from_dir); ::File_system::Dir_handle to_dir = _fs.dir(to_dir_path.base(), false); Fs_handle_guard to_dir_guard(_fs, to_dir); _fs.move(from_dir, from_file_name.base() + 1, to_dir, to_file_name.base() + 1); } catch (...) { sysio->error.unlink = Sysio::UNLINK_ERR_NO_ENTRY; return false; } return true; } bool mkdir(Sysio *sysio, char const *path) { /* * Canonicalize path (i.e., path must start with '/') */ Absolute_path abs_path(path); Sysio::Mkdir_error error = Sysio::MKDIR_ERR_NO_PERM; try { _fs.dir(abs_path.base(), true); return true; } catch (::File_system::Permission_denied) { error = Sysio::MKDIR_ERR_NO_PERM; } catch (::File_system::Node_already_exists) { error = Sysio::MKDIR_ERR_EXISTS; } catch (::File_system::Lookup_failed) { error = Sysio::MKDIR_ERR_NO_ENTRY; } catch (::File_system::Name_too_long) { error = Sysio::MKDIR_ERR_NAME_TOO_LONG; } catch (::File_system::No_space) { error = Sysio::MKDIR_ERR_NO_SPACE; } sysio->error.mkdir = error; return false; } bool symlink(Sysio *sysio, char const *path) { /* * Canonicalize path (i.e., path must start with '/') */ Absolute_path abs_path(path); abs_path.strip_last_element(); Absolute_path symlink_name(path); symlink_name.keep_only_last_element(); Sysio::Symlink_error error = Sysio::SYMLINK_ERR_NO_ENTRY; try { ::File_system::Dir_handle dir_handle = _fs.dir(abs_path.base(), false); Fs_handle_guard from_dir_guard(_fs, dir_handle); ::File_system::Symlink_handle symlink_handle = _fs.symlink(dir_handle, symlink_name.base() + 1, true); Fs_handle_guard symlink_guard(_fs, symlink_handle); _write(symlink_handle, sysio->symlink_in.oldpath, strlen(sysio->symlink_in.oldpath) + 1, 0); return true; } catch (::File_system::Invalid_handle) { error = Sysio::SYMLINK_ERR_NO_ENTRY; } catch (::File_system::Node_already_exists) { error = Sysio::SYMLINK_ERR_EXISTS; } catch (::File_system::Invalid_name) { error = Sysio::SYMLINK_ERR_NAME_TOO_LONG; } catch (::File_system::Lookup_failed) { error = Sysio::SYMLINK_ERR_NO_ENTRY; } sysio->error.symlink = error; return false; } size_t num_dirent(char const *path) { if (strcmp(path, "") == 0) path = "/"; /* * XXX handle exceptions */ ::File_system::Node_handle node = _fs.node(path); Fs_handle_guard node_guard(_fs, node); ::File_system::Status status = _fs.status(node); return status.size / sizeof(::File_system::Directory_entry); } bool is_directory(char const *path) { try { ::File_system::Node_handle node = _fs.node(path); Fs_handle_guard node_guard(_fs, node); ::File_system::Status status = _fs.status(node); return status.is_directory(); } catch (...) { return false; } } char const *leaf_path(char const *path) { /* check if node at path exists within file system */ try { ::File_system::Node_handle node = _fs.node(path); _fs.close(node); } catch (...) { return 0; } return path; } Vfs_handle *open(Sysio *sysio, char const *path) { Lock::Guard guard(_lock); Absolute_path dir_path(path); dir_path.strip_last_element(); Absolute_path file_name(path); file_name.keep_only_last_element(); ::File_system::Mode mode; switch (sysio->open_in.mode & Sysio::OPEN_MODE_ACCMODE) { default: mode = ::File_system::STAT_ONLY; break; case Sysio::OPEN_MODE_RDONLY: mode = ::File_system::READ_ONLY; break; case Sysio::OPEN_MODE_WRONLY: mode = ::File_system::WRITE_ONLY; break; case Sysio::OPEN_MODE_RDWR: mode = ::File_system::READ_WRITE; break; } bool const create = sysio->open_in.mode & Sysio::OPEN_MODE_CREATE; if (create) if (verbose) PDBG("creation of file %s requested", file_name.base() + 1); Sysio::Open_error error = Sysio::OPEN_ERR_UNACCESSIBLE; try { ::File_system::Dir_handle dir = _fs.dir(dir_path.base(), false); Fs_handle_guard dir_guard(_fs, dir); ::File_system::File_handle file = _fs.file(dir, file_name.base() + 1, mode, create); return new (env()->heap()) Fs_vfs_handle(this, sysio->open_in.mode, file); } catch (::File_system::Permission_denied) { error = Sysio::OPEN_ERR_NO_PERM; } catch (::File_system::Invalid_handle) { error = Sysio::OPEN_ERR_NO_PERM; } catch (::File_system::Lookup_failed) { error = Sysio::OPEN_ERR_UNACCESSIBLE; } catch (::File_system::Node_already_exists) { error = Sysio::OPEN_ERR_EXISTS; } sysio->error.open = error; return 0; } /*************************** ** File_system interface ** ***************************/ char const *name() const { return "fs"; } /******************************** ** File I/O service interface ** ********************************/ bool write(Sysio *sysio, Vfs_handle *vfs_handle) { Fs_vfs_handle const *handle = static_cast(vfs_handle); size_t const count = min(sizeof(sysio->write_in.chunk), sysio->write_in.count); sysio->write_out.count = _write(handle->file_handle(), sysio->write_in.chunk, count, handle->seek()); return true; } bool read(Sysio *sysio, Vfs_handle *vfs_handle) { Fs_vfs_handle const *handle = static_cast(vfs_handle); ::File_system::Status status = _fs.status(handle->file_handle()); size_t const file_size = status.size; size_t const file_bytes_left = file_size >= handle->seek() ? file_size - handle->seek() : 0; size_t const count = min(file_bytes_left, min(sizeof(sysio->read_out.chunk), sysio->read_in.count)); sysio->read_out.count = _read(handle->file_handle(), sysio->read_out.chunk, count, handle->seek()); return true; } bool ftruncate(Sysio *sysio, Vfs_handle *vfs_handle) { Fs_vfs_handle const *handle = static_cast(vfs_handle); Sysio::Ftruncate_error error; try { _fs.truncate(handle->file_handle(), sysio->ftruncate_in.length); return true; } catch (::File_system::Invalid_handle) { /* should not happen */ error = Sysio::FTRUNCATE_ERR_NO_PERM; } catch (::File_system::Permission_denied) { error = Sysio::FTRUNCATE_ERR_NO_PERM; } sysio->error.ftruncate = error; return false; } }; } #endif /* _NOUX__FS_FILE_SYSTEM_H_ */