diff --git a/ports/include/noux_session/sysio.h b/ports/include/noux_session/sysio.h index 1e21d71c5e..4345a1f865 100644 --- a/ports/include/noux_session/sysio.h +++ b/ports/include/noux_session/sysio.h @@ -69,11 +69,15 @@ namespace Noux { OPEN_MODE_CREATE = 0x0800, /* libc O_EXCL */ }; + /** + * These values are the same as in the FreeBSD libc + */ enum { STAT_MODE_SYMLINK = 0120000, STAT_MODE_FILE = 0100000, STAT_MODE_DIRECTORY = 0040000, STAT_MODE_CHARDEV = 0020000, + STAT_MODE_BLOCKDEV = 0060000, }; struct Stat @@ -92,7 +96,7 @@ namespace Noux { struct Ioctl_in { enum Opcode { OP_UNDEFINED, OP_TIOCGWINSZ, OP_TIOCSETAF, - OP_TIOCSETAW, OP_FIONBIO }; + OP_TIOCSETAW, OP_FIONBIO, OP_DIOCGMEDIASIZE }; enum Val { VAL_NULL, VAL_ECHO, VAL_ECHONL }; @@ -112,6 +116,13 @@ namespace Noux { int rows; int columns; } tiocgwinsz; + + /* if request was 'OP_DIOCGMEDIASIZE' */ + struct { + /* disk size rounded up to sector size in bytes*/ + int size; + + } diocgmediasize; }; }; @@ -124,6 +135,7 @@ namespace Noux { DIRENT_TYPE_DIRECTORY, DIRENT_TYPE_FIFO, DIRENT_TYPE_CHARDEV, + DIRENT_TYPE_BLOCKDEV, DIRENT_TYPE_SYMLINK, DIRENT_TYPE_END }; diff --git a/ports/src/lib/libc_noux/plugin.cc b/ports/src/lib/libc_noux/plugin.cc index dc1887c175..f1e22e886a 100644 --- a/ports/src/lib/libc_noux/plugin.cc +++ b/ports/src/lib/libc_noux/plugin.cc @@ -29,6 +29,7 @@ /* libc includes */ #include +#include #include #include #include @@ -41,6 +42,15 @@ #include #include +/** + * There is a off_t typedef clash between sys/socket.h + * and base/stdint.h. We define the macro here to circumvent + * this issue. + */ +#undef DIOCGMEDIASIZE +#define DIOCGMEDIASIZE _IOR('d', 129, int64_t) + + /* libc-internal includes */ #include @@ -1115,6 +1125,14 @@ namespace { break; } + case DIOCGMEDIASIZE: + { + sysio()->ioctl_in.request = Noux::Sysio::Ioctl_in::OP_DIOCGMEDIASIZE; + sysio()->ioctl_in.argp = 0; + + break; + } + default: PWRN("unsupported ioctl (request=0x%x)", request); @@ -1155,6 +1173,12 @@ namespace { case FIONBIO: return 0; + case DIOCGMEDIASIZE: + { + int64_t *disk_size = (int64_t*)argp; + *disk_size = sysio()->ioctl_out.diocgmediasize.size; + return 0; + } default: return -1; @@ -1368,6 +1392,7 @@ namespace { case Noux::Sysio::DIRENT_TYPE_SYMLINK: dirent->d_type = DT_LNK; break; case Noux::Sysio::DIRENT_TYPE_FIFO: dirent->d_type = DT_FIFO; break; case Noux::Sysio::DIRENT_TYPE_CHARDEV: dirent->d_type = DT_CHR; break; + case Noux::Sysio::DIRENT_TYPE_BLOCKDEV: dirent->d_type = DT_BLK; break; case Noux::Sysio::DIRENT_TYPE_END: return 0; } diff --git a/ports/src/noux/block_file_system.h b/ports/src/noux/block_file_system.h new file mode 100644 index 0000000000..f8a8cbda4f --- /dev/null +++ b/ports/src/noux/block_file_system.h @@ -0,0 +1,465 @@ +/* + * \brief Block device file system + * \author Josef Soentgen + * \date 2013-12-20 + */ + +/* + * Copyright (C) 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__BLOCK_FILE_SYSTEM_H_ +#define _NOUX__BLOCK_FILE_SYSTEM_H_ + +/* Genode includes */ +#include +#include +#include +#include +#include +#include + +/* Noux includes */ +#include +#include "file_system.h" + + +namespace Noux { + + class Block_file_system : public File_system + { + private: + + 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; + + char *_block_buffer; + unsigned _block_buffer_count; + + Genode::Allocator_avl _tx_block_alloc; + Block::Connection _block; + size_t _block_size; + Block::sector_t _block_count; + Block::Session::Operations _block_ops; + Block::Session::Tx::Source *_tx_source; + + off_t _seek_offset; + + bool _readable; + bool _writeable; + + + enum { FILENAME_MAX_LEN = 64 }; + char _filename[FILENAME_MAX_LEN]; + + bool _is_root(const char *path) + { + return (strcmp(path, "") == 0) || (strcmp(path, "/") == 0); + } + + bool _is_block_file(const char *path) + { + return (strlen(path) == (strlen(_filename) + 1)) && + (strcmp(&path[1], _filename) == 0); + } + + size_t _block_io(size_t nr, void *buf, size_t sz, bool write, bool bulk = false) + { + Block::Packet_descriptor::Opcode op; + op = write ? Block::Packet_descriptor::WRITE : Block::Packet_descriptor::READ; + + size_t packet_size = bulk ? sz : _block_size; + size_t packet_count = bulk ? (sz / _block_size) : 1; + + /* sanity check */ + if (packet_count > _block_buffer_count) { + packet_size = _block_buffer_count * _block_size; + packet_count = _block_buffer_count; + } + + //if (verbose) + PDBG("%5s: block:%zu size:%zu packets:%zu", + write ? "write" : "read", nr, sz, packet_count); + + Block::Packet_descriptor p(_tx_source->alloc_packet(packet_size), op, + nr, packet_count); + + if (write) + Genode::memcpy(_tx_source->packet_content(p), buf, packet_size); + + _tx_source->submit_packet(p); + p = _tx_source->get_acked_packet(); + + if (!p.succeeded()) { + PERR("Could not read block(s)"); + _tx_source->release_packet(p); + return 0; + } + + if (!write) + Genode::memcpy(buf, _tx_source->packet_content(p), packet_size); + + _tx_source->release_packet(p); + return packet_size; + } + + public: + + Block_file_system(Xml_node config) + : + _label(config), + _block_buffer(0), + _block_buffer_count(1), + _tx_block_alloc(env()->heap()), + _block(&_tx_block_alloc, 128*1024, _label.string), + _tx_source(_block.tx()), + _seek_offset(0), + _readable(false), + _writeable(false) + { + _filename[0] = 0; + try { config.attribute("name").value(_filename, sizeof(_filename)); } + catch (...) { } + + try { config.attribute("block_buffer_count").value(&_block_buffer_count); } + catch (...) { } + + _block.info(&_block_count, &_block_size, &_block_ops); + + _readable = _block_ops.supported(Block::Packet_descriptor::READ); + _writeable = _block_ops.supported(Block::Packet_descriptor::WRITE); + + _block_buffer = new (env()->heap()) char[_block_buffer_count * _block_size]; + + //if (verbose) + PDBG("number of blocks: %llu with block size: %zu (bytes)" + " , readable: %d writeable: %d", + _block_count, _block_size, _readable, _writeable); + } + + ~Block_file_system() + { + destroy(env()->heap(), _block_buffer); + } + + + /********************************* + ** Directory-service interface ** + *********************************/ + + Dataspace_capability dataspace(char const *path) + { + /* not supported */ + return Dataspace_capability(); + } + + void release(char const *path, Dataspace_capability ds_cap) + { + /* not supported */ + } + + bool stat(Sysio *sysio, char const *path) + { + Sysio::Stat st = { 0, 0, 0, 0, 0, 0 }; + + if (_is_root(path)) + st.mode = Sysio::STAT_MODE_DIRECTORY; + else if (_is_block_file(path)) { + st.mode = Sysio::STAT_MODE_BLOCKDEV; + } else { + sysio->error.stat = Sysio::STAT_ERR_NO_ENTRY; + return false; + } + + sysio->stat_out.st = st; + return true; + } + + bool dirent(Sysio *sysio, char const *path, off_t index) + { + if (_is_root(path)) { + if (index == 0) { + sysio->dirent_out.entry.type = Sysio::DIRENT_TYPE_BLOCKDEV; + strncpy(sysio->dirent_out.entry.name, + _filename, + sizeof(sysio->dirent_out.entry.name)); + } else { + sysio->dirent_out.entry.type = Sysio::DIRENT_TYPE_END; + } + + return true; + } + + return false; + } + + size_t num_dirent(char const *path) + { + if (_is_root(path)) + return 1; + else + return 0; + } + + bool is_directory(char const *path) + { + if (_is_root(path)) + return true; + + return false; + } + + char const *leaf_path(char const *path) + { + return path; + } + + Vfs_handle *open(Sysio *sysio, char const *path) + { + if (!_is_block_file(path)) { + sysio->error.open = Sysio::OPEN_ERR_UNACCESSIBLE; + return 0; + } + + return new (env()->heap()) Vfs_handle(this, this, 0); + } + + bool unlink(Sysio *sysio, char const *path) + { + /* not supported */ + return false; + } + + bool readlink(Sysio *sysio, char const *path) + { + /* not supported */ + return false; + } + + bool rename(Sysio *sysio, char const *from_path, char const *to_path) + { + /* not supported */ + return false; + } + + bool mkdir(Sysio *sysio, char const *path) + { + /* not supported */ + return false; + } + + bool symlink(Sysio *sysio, char const *path) + { + /* not supported */ + return false; + } + + /*************************** + ** File_system interface ** + ***************************/ + + char const *name() const { return "block"; } + + + /******************************** + ** File I/O service interface ** + ********************************/ + + bool write(Sysio *sysio, Vfs_handle *vfs_handle) + { + if (!_writeable) { + PERR("block device is not writeable"); + return false; + } + + size_t _seek_offset = vfs_handle->seek(); + size_t count = sysio->write_in.count; + const char *_buf = (const char*) sysio->write_in.chunk; + + size_t written = 0; + while (count > 0) { + size_t displ = 0; + size_t length = 0; + size_t nbytes = 0; + size_t blk_nr = _seek_offset / _block_size; + + displ = _seek_offset % _block_size; + + if ((displ + count) > _block_size) + length = (_block_size - displ); + else + length = count; + + /** + * We take a shortcut and write as much as possible without + * using the block buffer if the offset is aligned on a block + * boundary and the count is a multiple of the block size, + * e.g. 4K writes will be written at once. + * + * XXX this is quite hackish because we have to omit partial + * blocks at the end. + */ + if (displ == 0 && (count % _block_size) >= 0 && !(count < _block_size)) { + size_t bytes_left = count - (count % _block_size); + + nbytes = _block_io(blk_nr, (void*)(_buf + written), + bytes_left, true, true); + if (nbytes == 0) { + PERR("error while reading block:%zu from block device", + blk_nr); + return false; + } + + written += nbytes; + count -= nbytes; + + continue; + } + + /** + * The offset is not aligned on a block boundary. Therefore + * we need to read the block to the block buffer first and + * put the buffer content at the right offset before we can + * write the whole block back. In addition if length is less + * than block size, we also have to read the block first. + */ + if (displ > 0 || length < _block_size) { + PWRN("offset:%u block_size:%zd displacement:%zd length:%zu", + _seek_offset, _block_size, displ, length); + + _block_io(blk_nr, _block_buffer, _block_size, false); + /* rewind seek offset to account for the block read */ + _seek_offset -= _block_size; + } + + Genode::memcpy(_block_buffer + displ, _buf + written, length); + + nbytes = _block_io(blk_nr, _block_buffer, _block_size, true); + if ((unsigned)nbytes != _block_size) { + PERR("error while reading block:%zu from Block_device", + blk_nr); + return false; + } + + written += length; + count -= length; + } + + sysio->write_out.count = written; + + return true; + } + + bool read(Sysio *sysio, Vfs_handle *vfs_handle) + { + if (!_readable) { + PERR("block device is not readable"); + return false; + } + + size_t _seek_offset = vfs_handle->seek(); + size_t count = sysio->read_in.count; + char *_buf = (char*) sysio->read_out.chunk; + + size_t read = 0; + while (count > 0) { + size_t displ = 0; + size_t length = 0; + size_t nbytes = 0; + size_t blk_nr = _seek_offset / _block_size; + + displ = _seek_offset % _block_size; + + if ((displ + count) > _block_size) + length = (_block_size - displ); + else + length = count; + + /** + * We take a shortcut and read the blocks all at once if t he + * offset is aligned on a block boundary and we the count is a + * multiple of the block size, e.g. 4K reads will be read at + * once. + * + * XXX this is quite hackish because we have to omit partial + * blocks at the end. + */ + if (displ == 0 && (count % _block_size) >= 0 && !(count < _block_size)) { + size_t bytes_left = count - (count % _block_size); + + nbytes = _block_io(blk_nr, _buf + read, bytes_left, false, true); + if (nbytes == 0) { + PERR("error while reading block:%zu from block device", + blk_nr); + return false; + } + + read += nbytes; + count -= nbytes; + + continue; + } + + if (displ > 0) + PWRN("offset:%u is not aligned to block_size:%zu" + " displacement:%zu", _seek_offset, _block_size, + displ); + + nbytes = _block_io(blk_nr, _block_buffer, _block_size, false); + if ((unsigned)nbytes != _block_size) { + PERR("error while reading block:%zu from block device", + blk_nr); + return false; + } + + Genode::memcpy(_buf + read, _block_buffer + displ, length); + + read += length; + count -= length; + } + + sysio->read_out.count = read; + + return true; + } + + bool ftruncate(Sysio *sysio, Vfs_handle *vfs_handle) { return true; } + + bool check_unblock(Vfs_handle *vfs_handle, bool rd, bool wr, bool ex) + { + return true; + } + + bool ioctl(Sysio *sysio, Vfs_handle *vfs_handle) + { + switch (sysio->ioctl_in.request) { + case Sysio::Ioctl_in::OP_DIOCGMEDIASIZE: + { + sysio->ioctl_out.diocgmediasize.size = _block_count * _block_size; + return true; + } + default: + PDBG("invalid ioctl request %d", sysio->ioctl_in.request); + return false; + } + + /* never reached */ + return false; + } + }; +} + +#endif /* _NOUX__BLOCK_FILE_SYSTEM_H_ */ diff --git a/ports/src/noux/dir_file_system.h b/ports/src/noux/dir_file_system.h index 84954d67cb..b5b078f97c 100644 --- a/ports/src/noux/dir_file_system.h +++ b/ports/src/noux/dir_file_system.h @@ -27,6 +27,7 @@ #include #include #include +#include namespace Noux { @@ -123,6 +124,11 @@ namespace Noux { continue; } + if (sub_node.has_type("block")) { + _append_file_system(new Block_file_system(sub_node)); + continue; + } + { char type_name[64]; sub_node.type_name(type_name, sizeof(type_name));