/* * \brief ISO 9660 file system support * \author Sebastian Sumpf * \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 #include #include #include #include #include #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 T addr() { return reinterpret_cast(_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 T value(int offset) { return *((T *)(this + offset)); } template 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(0); } /* starting block of extent */ uint32_t blk_nr() { return value(2); } /* length in bytes of extent */ uint32_t data_length() { return value(10); } /* attributes */ uint8_t file_flags() { return value(25); } /* length of file name */ uint8_t file_name_length() { return value(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(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(0); } /* root directory record */ Directory_record * root_record() { return ptr(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(); 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 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()->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(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(), 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(); }