genode/repos/os/src/server/iso9660/iso9660.cc

476 lines
10 KiB
C++

/*
* \brief ISO 9660 file system support
* \author Sebastian Sumpf <sebastian.sumpf@genode-labs.com>
* \date 2010-07-26
*/
/*
* Copyright (C) 2010-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 <base/allocator_avl.h>
#include <base/exception.h>
#include <base/log.h>
#include <base/stdint.h>
#include <util/misc_math.h>
#include <util/token.h>
#include "iso9660.h"
using namespace Genode;
namespace Iso {
class Sector;
class Rock_ridge;
class Iso_base;
}
/*
* Sector reads one or more packets from the block interface
*/
class Iso::Sector {
public:
enum {
MAX_SECTORS = 32, /* max. number sectors that can be read in one
transaction */
BLOCK_SIZE = 2048,
};
private:
Block::Session::Tx::Source &_source;
Block::Packet_descriptor _p { };
public:
Sector(Block::Connection &block,
unsigned long blk_nr, unsigned long count)
: _source(*block.tx())
{
try {
_p = Block::Packet_descriptor(
block.alloc_packet(blk_size() * count),
Block::Packet_descriptor::READ,
blk_nr * ((float)blk_size() / BLOCK_SIZE),
count * ((float)blk_size() / BLOCK_SIZE));
_source.submit_packet(_p);
_p = _source.get_acked_packet();
if (!_p.succeeded()) {
Genode::error("Could not read block ", blk_nr);
throw Io_error();
}
} catch (Block::Session::Tx::Source::Packet_alloc_failed) {
Genode::error("packet overrun!");
_p = _source.get_acked_packet();
throw Io_error();
}
}
~Sector() {
_source.release_packet(_p);
}
/**
* Return address of packet content
*/
template <typename T>
T addr() { return reinterpret_cast<T>(_source.packet_content(_p)); }
static size_t blk_size() { return BLOCK_SIZE; }
static unsigned long to_blk(unsigned long bytes) {
return ((bytes + blk_size() - 1) & ~(blk_size() - 1)) / blk_size(); }
};
/**
* Rock ridge extension (see IEEE P1282)
*/
class Iso::Rock_ridge
{
public:
enum {
NM = 0x4d4e, /* POSIX name system use entry (little endian) */
};
private:
uint16_t _signature;
uint8_t _length;
uint8_t _version;
uint8_t _flags;
char _name;
public:
char *name() { return &_name; }
uint8_t length() { return _length - 5; }
static Rock_ridge* scan_name(uint8_t *ptr, uint8_t size)
{
Rock_ridge *rr = (Rock_ridge *)ptr;
while (rr->_length && ((uint8_t *)rr < ptr + size - 4)) {
if (rr->_signature == NM)
return rr;
rr = rr->next();
}
return 0;
}
Rock_ridge *next() { return (Rock_ridge *)((uint8_t *)this + _length); }
};
/*********************
** ISO9660 classes **
*********************/
/**
* Access memory using offsets
*/
class Iso::Iso_base
{
protected:
template <typename T>
T value(int offset) { return *((T *)(this + offset)); }
template <typename T>
T ptr(int offset) { return (T)(this + offset); }
};
/**
* Class representing a directory descriptions (see ECMA 119)
*/
class Directory_record : public Iso::Iso_base
{
enum {
TABLE_LENGTH = 33, /* fixed length of directory record */
ROOT_DIR = 0x0, /* special names of root and parent directories */
PARENT_DIR = 0x1,
DIR_FLAG = 0x2, /* directory flag in attributes */
};
public:
/* total length of this record */
uint8_t record_length() { return value<uint8_t>(0); }
/* starting block of extent */
uint32_t blk_nr() { return value<uint32_t>(2); }
/* length in bytes of extent */
uint32_t data_length() { return value<uint32_t>(10); }
/* attributes */
uint8_t file_flags() { return value<uint8_t>(25); }
/* length of file name */
uint8_t file_name_length() { return value<uint8_t>(32); }
/* retrieve the file name */
void file_name(char *buf)
{
buf[0] = 0;
/* try Rock Ridge name */
Iso::Rock_ridge *rr = Iso::Rock_ridge::scan_name(system_use(),
system_use_size());
if (rr) {
memcpy(buf, rr->name(), rr->length());
buf[rr->length()] = 0;
return;
}
/* retrieve iso name */
char *name = ptr<char*>(33);
/*
* Check for root and parent directory names and modify them
* to '.' and '..' respectively.
*/
if (file_name_length() == 1)
switch (name[0]) {
case PARENT_DIR:
buf[2] = 0;
buf[1] = '.';
buf[0] = '.';
return;
case ROOT_DIR:
buf[1] = 0;
buf[0] = '.';
return;
}
memcpy(buf, name, file_name_length());
buf[file_name_length()] = 0;
}
/* pad byte after file name (if file name length is even, only) */
uint8_t pad_byte() { return !(file_name_length() % 2) ? 1 : 0; }
/* system use area */
uint8_t *system_use()
{
return (uint8_t*)this + TABLE_LENGTH
+ file_name_length() + pad_byte();
}
/* length in bytes of system use area */
uint8_t system_use_size()
{
return record_length() - file_name_length()
- TABLE_LENGTH - pad_byte();
}
/* retrieve next record */
Directory_record *next()
{
Directory_record *_next = this + record_length();
if (_next->record_length())
return _next;
return 0;
}
/* find a directory record with file name matching 'level' */
Directory_record *locate(char const *level)
{
Directory_record *dir = this;
while (dir) {
char name[Iso::LEVEL_LENGTH];
dir->file_name(name);
if (!strcmp(name, level))
return dir;
dir = dir->next();
}
return 0;
}
/* describes this record a directory */
bool directory() { return file_flags() & DIR_FLAG; }
};
/**
* Volume descriptor (see ECMA 119)
*/
class Volume_descriptor : public Iso::Iso_base
{
enum {
/* volume types */
PRIMARY = 0x01, /* type of primary volume descriptor */
TERMINATOR = 0xff, /* type of terminating descriptor */
/* constants */
ROOT_SIZE = 34, /* the root directory record has a fixed length */
};
public:
/* descriptor type */
uint8_t type() { return value<uint8_t>(0); }
/* root directory record */
Directory_record * root_record() { return ptr<Directory_record *>(156); }
/* check for primary descriptor */
bool primary() { return type() == PRIMARY; }
/* check for terminating descriptor */
bool terminator() { return type() == TERMINATOR; }
/* copy the root record */
Directory_record *copy_root_record(Genode::Allocator &alloc)
{
Directory_record *buf;
if (!(alloc.alloc(ROOT_SIZE, &buf)))
throw Insufficient_ram_quota();
memcpy(buf, root_record(), ROOT_SIZE);
return buf;
}
};
/**
* Locate the root-directory record in the primary volume descriptor
*/
static Directory_record *locate_root(Genode::Allocator &alloc,
Block::Connection &block)
{
/* volume descriptors in ISO9660 start at block 16 */
for (unsigned long blk_nr = 16;; blk_nr++) {
Iso::Sector sec(block, blk_nr, 1);
Volume_descriptor *vol = sec.addr<Volume_descriptor *>();
if (vol->primary())
return vol->copy_root_record(alloc);
if (vol->terminator())
return nullptr;
}
}
/**
* Return root directory record
*/
static Directory_record *root_dir(Genode::Allocator &alloc,
Block::Connection &block)
{
Directory_record *root = locate_root(alloc, block);
if (!root) { throw Iso::Non_data_disc(); }
return root;
}
/*******************
** Iso interface **
*******************/
static Directory_record *_root_dir;
Iso::File_info *Iso::file_info(Genode::Allocator &alloc, Block::Connection &block,
char const *path)
{
char level[PATH_LENGTH];
struct Scanner_policy_file
{
static bool identifier_char(char c, unsigned /* i */)
{
return c != '/' && c != 0;
}
};
typedef ::Genode::Token<Scanner_policy_file> Token;
Token t(path);
if (!_root_dir) {
_root_dir = root_dir(alloc, block);
}
Directory_record *dir = _root_dir;
uint32_t blk_nr = 0, data_length = 0;
/* determine block nr and file length on disk, parse directory records */
while (t) {
if (t.type() != Token::IDENT) {
t = t.next();
continue;
}
t.string(level, PATH_LENGTH);
/*
* Save current block number in a variable because successive
* iterations might override the memory location where dir points
* to when a directory entry spans several sectors.
*/
uint32_t current_blk_nr = dir->blk_nr();
/* load extent of directory record and search for level */
for (unsigned long i = 0; i < Sector::to_blk(dir->data_length()); i++) {
Sector sec(block, current_blk_nr + i, 1);
Directory_record *tmp = sec.addr<Directory_record *>()->locate(level);
if (!tmp && i == Sector::to_blk(dir->data_length()) - 1) {
Genode::error("file not found: ", Genode::Cstring(path));
throw File_not_found();
}
if (!tmp) continue;
dir = tmp;
current_blk_nr = dir->blk_nr();
if (!dir->directory()) {
blk_nr = current_blk_nr;
data_length = dir->data_length();
}
break;
}
t = t.next();
}
/* Warning: Don't access 'dir' after this point, since the sector is gone */
if (!blk_nr && !data_length) {
Genode::error("file not found: ", Genode::Cstring(path));
throw File_not_found();
}
return new (alloc) File_info(blk_nr, data_length);
}
unsigned long Iso::read_file(Block::Connection &block, File_info *info,
off_t file_offset, uint32_t length, void *buf_ptr)
{
uint8_t *buf = (uint8_t *)buf_ptr;
if (info->size() <= (size_t)(length + file_offset))
length = info->size() - file_offset - 1;
unsigned long total_blk_count = ((length + (Sector::blk_size() - 1)) &
~((Sector::blk_size()) - 1)) / Sector::blk_size();
unsigned long ret = total_blk_count;
unsigned long blk_count;
unsigned long blk_nr = info->blk_nr() + (file_offset / Sector::blk_size());
while ((blk_count = min<unsigned long>(Sector::MAX_SECTORS, total_blk_count))) {
Sector sec(block, blk_nr, blk_count);
total_blk_count -= blk_count;
blk_nr += blk_count;
unsigned long copy_length = blk_count * Sector::blk_size();
memcpy(buf, sec.addr<void *>(), copy_length);
length -= copy_length;
buf += copy_length;
/* zero out rest of page */
if (!total_blk_count && (blk_count % 2))
memset(buf, 0, Sector::blk_size());
}
return ret * Sector::blk_size();
}