noux: add block device file system

Access to a block session is provided by using the block file system.
By specifying the label, each block session request can be routed to
the proper block session provider:

! <fstab>
! 	...
! 	<dir name="dev">
! 		<block name="blkdev0" label="block_session_0" />
! 	</dir>
! 	...
! </fstab>

In addition to this file system, support for the DIOCGMEDIASIZE ioctl
request was added. This request is used by FreeBSD and therefore our
libc to query the size of the block device in bytes.

Fixes #1010.
This commit is contained in:
Josef Söntgen 2013-12-20 16:51:18 +01:00 committed by Norman Feske
parent 25b41e9cff
commit c4c43ffc9d
4 changed files with 509 additions and 1 deletions

View File

@ -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
};

View File

@ -29,6 +29,7 @@
/* libc includes */
#include <errno.h>
#include <sys/disk.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <sys/dirent.h>
@ -41,6 +42,15 @@
#include <pwd.h>
#include <string.h>
/**
* 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 <libc_mem_alloc.h>
@ -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;
}

View File

@ -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 <base/allocator_avl.h>
#include <base/printf.h>
#include <base/lock.h>
#include <base/stdint.h>
#include <block_session/connection.h>
#include <util/string.h>
/* Noux includes */
#include <noux_session/sysio.h>
#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_ */

View File

@ -27,6 +27,7 @@
#include <zero_file_system.h>
#include <stdio_file_system.h>
#include <random_file_system.h>
#include <block_file_system.h>
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));