part_blk: add GPT support

The GUID partition table (GPT) is primarily used by systems using
(U)EFI and is a replacement for the legacy MBR. For now, the current
implementation is able to address up to 128 GUID partition entries
(GPE).

To enable the GPT support in 'part_blk' it has to be configured
accrodingly:

! <start name="part_blk">
! [...]
!   <config use_gpt="yes">
! [...]
! </start>

If 'part_blk' is not able to find a valid GPT header it falls back
to using the MBR.

Current limitations:

Since no endian conversion takes place it only works on LE platforms
and of all characters in the UTF-16 encoded name field of an entry
only the ones included in the ASCII encoding are printed. It also
ignores all GPE attributes.

Issue #1429.
This commit is contained in:
Josef Söntgen 2014-09-20 22:12:26 +02:00 committed by Christian Helmuth
parent 2694b5f9c6
commit 9b7e0ce0a5
6 changed files with 515 additions and 150 deletions

View File

@ -14,6 +14,10 @@ extended boot records (EBRs) are parsed and offered as separate block sessions
to the front-end clients. The four primary partitions will receive partition
numbers '1' to '4' whereas the first logical partition will be assigned to '5'.
The partition server also understands the GUID partition table (GPT). If the
config attribute 'use_gpt' is set to 'yes' it will first try to parse any
existing GPT. In case there is no GPT it will fall back to parsing the MBR.
In order to route a client to the right partition, the server parses its
configuration section looking for 'policy' tags.

View File

@ -18,7 +18,7 @@
#include <root/component.h>
#include <block_session/rpc_object.h>
#include "partition_table.h"
#include "gpt.h"
namespace Block {
@ -195,8 +195,9 @@ class Block::Root :
{
private:
Rpc_entrypoint &_ep;
Signal_receiver &_receiver;
Rpc_entrypoint &_ep;
Signal_receiver &_receiver;
Block::Partition_table &_table;
long _partition_num(const char *session_label)
{
@ -265,7 +266,7 @@ class Block::Root :
throw Root::Invalid_args();
}
if (!Partition_table::table().partition(num)) {
if (!_table.partition(num)) {
PERR("Partition %ld unavailable", num);
throw Root::Unavailable();
}
@ -274,18 +275,19 @@ class Block::Root :
ds_cap = Genode::env()->ram_session()->alloc(tx_buf_size);
return new (md_alloc())
Session_component(ds_cap,
Partition_table::table().partition(num),
_table.partition(num),
_ep, _receiver);
}
public:
Root(Rpc_entrypoint *session_ep, Allocator *md_alloc,
Signal_receiver &receiver)
Signal_receiver &receiver, Block::Partition_table& table)
:
Root_component(session_ep, md_alloc),
_ep(*session_ep),
_receiver(receiver)
_receiver(receiver),
_table(table)
{ }
};

View File

@ -0,0 +1,284 @@
/*
* \brief GUID Partition table definitions
* \author Josef Soentgen
* \date 2014-09-19
*/
/*
* Copyright (C) 2014 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 _PART_BLK__GPT_H_
#define _PART_BLK__GPT_H_
#include <base/env.h>
#include <base/printf.h>
#include <block_session/client.h>
#include "driver.h"
#include "partition_table.h"
namespace {
static bool const verbose = false;
#define PLOGV(...) do { if (verbose) PLOG(__VA_ARGS__); } while (0)
/* simple bitwise CRC32 checking */
static inline Genode::uint32_t crc32(void const * const buf, Genode::size_t size)
{
Genode::uint8_t const *p = static_cast<Genode::uint8_t const*>(buf);
Genode::uint32_t crc = ~0U;
while (size--) {
crc ^= *p++;
for (Genode::uint32_t j = 0; j < 8; j++)
crc = (-Genode::int32_t(crc & 1) & 0xedb88320) ^ (crc >> 1);
}
return crc ^ ~0U;
}
} /* anonymous namespace */
class Gpt : public Block::Partition_table
{
private:
enum { MAX_PARTITIONS = 128 };
/* contains pointers to valid partitions or 0 */
Block::Partition *_part_list[MAX_PARTITIONS] { 0 };
typedef Block::Partition_table::Sector Sector;
/**
* DCE uuid struct
*/
struct Uuid
{
enum { UUID_NODE_LEN = 6 };
Genode::uint32_t time_low;
Genode::uint16_t time_mid;
Genode::uint16_t time_hi_and_version;
Genode::uint8_t clock_seq_hi_and_reserved;
Genode::uint8_t clock_seq_low;
Genode::uint8_t node[UUID_NODE_LEN];
char const *to_string()
{
static char buffer[37 + 1];
Genode::snprintf(buffer, sizeof(buffer),
"%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",
time_low, time_mid, time_hi_and_version,
clock_seq_hi_and_reserved, clock_seq_low,
node[0], node[1], node[2], node[3], node[4], node[5]);
return buffer;
}
} __attribute__((packed));
/**
* GUID parition table header
*/
struct Gpt_hdr
{
enum { HEADER_LBA = 1 };
char _sig[8]; /* identifies GUID Partition Table */
Genode::uint32_t _revision; /* GPT specification revision */
Genode::uint32_t _hdr_size; /* size of GPT header */
Genode::uint32_t _hdr_crc; /* CRC32 of GPT header */
Genode::uint32_t _reserved; /* must be zero */
Genode::uint64_t _hdr_lba; /* LBA that contains this header */
Genode::uint64_t _backup_hdr_lba; /* LBA of backup GPT header */
Genode::uint64_t _part_lba_start; /* first LBA usable for partitions */
Genode::uint64_t _part_lba_end; /* last LBA usable for partitions */
Uuid _guid; /* GUID to identify the disk */
Genode::uint64_t _gpe_lba; /* first LBA of GPE array */
Genode::uint32_t _entries; /* number of entries in GPE array */
Genode::uint32_t _entry_size; /* size of each GPE */
Genode::uint32_t _gpe_crc; /* CRC32 of GPE array */
void dump_hdr(bool check_primary)
{
PLOGV("GPT %s header:", check_primary ? "primary" : "backup");
PLOGV(" rev: %u", _revision);
PLOGV(" size: %u", _hdr_size);
PLOGV(" crc: %x", _hdr_crc);
PLOGV(" reserved: %u", _reserved);
PLOGV(" hdr lba: %llu", _hdr_lba);
PLOGV(" bak lba: %llu", _backup_hdr_lba);
PLOGV(" part start lba: %llu", _part_lba_start);
PLOGV(" part end lba: %llu", _part_lba_end);
PLOGV(" guid: %s", _guid.to_string());
PLOGV(" gpe lba: %llu", _gpe_lba);
PLOGV(" entries: %u", _entries);
PLOGV(" entry size: %u", _entry_size);
PLOGV(" gpe crc: %x", _gpe_crc);
}
bool is_valid(bool check_primary = true)
{
dump_hdr(check_primary);
/* check sig */
if (Genode::strcmp(_sig, "EFI PART", 8) != 0)
return false;
/* check header crc */
Genode::uint32_t crc = _hdr_crc;
_hdr_crc = 0;
if (crc32(this, _hdr_size) != crc) {
PERR("Wrong GPT header checksum");
return false;
}
/* check header lba */
if (check_primary)
if (_hdr_lba != HEADER_LBA)
return false;
/* check GPT entry array */
Genode::size_t length = _entries * _entry_size;
Sector gpe(_gpe_lba, length / Block::Driver::driver().blk_size());
if (crc32(gpe.addr<void *>(), length) != _gpe_crc)
return false;
if (check_primary) {
/* check backup gpt header */
Sector backup_hdr(_backup_hdr_lba, 1);
if (!backup_hdr.addr<Gpt_hdr*>()->is_valid(false)) {
PWRN("Backup GPT header is corrupted");
}
}
return true;
}
/* the remainder of the LBA must be zero */
} __attribute__((packed));
/**
* GUID partition entry format
*/
struct Gpt_entry
{
enum { NAME_LEN = 36 };
Uuid _type; /* partition type GUID */
Uuid _guid; /* unique partition GUID */
Genode::uint64_t _lba_start; /* start of partition */
Genode::uint64_t _lba_end; /* end of partition */
Genode::uint64_t _attr; /* partition attributes */
Genode::uint16_t _name[NAME_LEN]; /* partition name in UNICODE-16 */
bool is_valid()
{
if (_type.time_low == 0x00000000)
return false;
return true;
}
/**
* Extract all valid ASCII characters in the name entry
*/
char const *name()
{
static char buffer[NAME_LEN + 1];
char *p = buffer;
Genode::size_t i = 0;
for (Genode::size_t u = 0; u < NAME_LEN && _name[u] != 0; u++) {
Genode::uint32_t utfchar = _name[i++];
if ((utfchar & 0xf800) == 0xd800) {
unsigned int c = _name[i];
if ((utfchar & 0x400) != 0 || (c & 0xfc00) != 0xdc00)
utfchar = 0xfffd;
else
i++;
}
*p++ = (utfchar < 0x80) ? utfchar : '.';
}
*p++ = 0;
return buffer;
}
} __attribute__((packed));
/**
* Parse the GPT header
*/
void _parse_gpt(Gpt_hdr *gpt)
{
if (!(gpt->is_valid()))
throw Genode::Exception();
Sector entry_array(gpt->_gpe_lba,
gpt->_entries * gpt->_entry_size / Block::Driver::driver().blk_size());
Gpt_entry *entries = entry_array.addr<Gpt_entry *>();
for (int i = 0; i < MAX_PARTITIONS; i++) {
Gpt_entry *e = (entries + i);
if (!e->is_valid())
continue;
Genode::uint64_t start = e->_lba_start;
Genode::uint64_t length = e->_lba_end - e->_lba_start + 1; /* [...) */
_part_list[i] = new (Genode::env()->heap()) Block::Partition(start, length);
PINF("Partition %d: LBA %llu (%llu blocks) type: '%s' name: '%s'",
i + 1, start, length, e->_type.to_string(), e->name());
}
}
Gpt()
{
Sector s(Gpt_hdr::HEADER_LBA, 1);
_parse_gpt(s.addr<Gpt_hdr *>());
/*
* we read all partition information,
* now it's safe to turn in asynchronous mode
*/
Block::Driver::driver().work_asynchronously();
}
public:
Block::Partition *partition(int num) {
return (num <= MAX_PARTITIONS && num > 0) ? _part_list[num-1] : 0; }
bool avail()
{
for (unsigned num = 0; num < MAX_PARTITIONS; num++)
if (_part_list[num])
return true;
return false;
}
static Gpt& table()
{
static Gpt table;
return table;
}
};
#endif /* _PART_BLK__GUID_PARTITION_TABLE_H_ */

View File

@ -2,11 +2,12 @@
* \brief Front end of the partition server
* \author Sebastian Sumpf
* \author Stefan Kalkowski
* \author Josef Soentgen
* \date 2011-05-30
*/
/*
* Copyright (C) 2011-2013 Genode Labs GmbH
* Copyright (C) 2011-2014 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.
@ -16,9 +17,10 @@
#include <cap_session/connection.h>
#include <os/config.h>
#include "driver.h"
#include "partition_table.h"
#include "component.h"
#include "driver.h"
#include "gpt.h"
#include "mbr.h"
static Genode::Signal_receiver receiver;
@ -33,19 +35,48 @@ void Block::Driver::_ready_to_submit(unsigned) {
Block::Session_component::wake_up(); }
static bool _use_gpt()
{
try {
return Genode::config()->xml_node().attribute("use_gpt").has_value("yes");
} catch(...) { }
return false;
}
int main()
{
using namespace Genode;
if (!Block::Partition_table::table().avail()) {
PERR("No valid partition table found");
return 1;
bool valid_mbr = false;
bool valid_gpt = false;
bool use_gpt = _use_gpt();
if (use_gpt)
try { valid_gpt = Gpt::table().avail(); } catch (...) { }
/* fall back to MBR */
if (!valid_gpt) {
try { valid_mbr = Mbr_partition_table::table().avail(); }
catch (Mbr_partition_table::Protective_mbr_found) {
if (!use_gpt)
PERR("Aborting: found protective MBR but GPT usage was not requested.");
return 1;
}
}
Block::Partition_table *partition_table = 0;
if (valid_gpt)
partition_table = &Gpt::table();
if (valid_mbr)
partition_table = &Mbr_partition_table::table();
enum { STACK_SIZE = 1024 * sizeof(Genode::size_t) };
static Cap_connection cap;
static Rpc_entrypoint ep(&cap, STACK_SIZE, "part_ep");
static Block::Root block_root(&ep, env()->heap(), receiver);
static Block::Root block_root(&ep, env()->heap(), receiver,
*partition_table);
env()->parent()->announce(ep.manage(&block_root));

View File

@ -0,0 +1,174 @@
/*
* \brief MBR partition table definitions
* \author Sebastian Sumpf
* \author Stefan Kalkowski
* \author Josef Soentgen
* \date 2013-12-04
*/
/*
* Copyright (C) 2013-2014 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 _PART_BLK__MBR_H_
#define _PART_BLK__MBR_H_
#include <base/env.h>
#include <base/printf.h>
#include <block_session/client.h>
#include "driver.h"
#include "partition_table.h"
struct Mbr_partition_table : public Block::Partition_table
{
public:
class Protective_mbr_found { };
private:
typedef Block::Partition_table::Sector Sector;
/**
* Partition table entry format
*/
struct Partition_record
{
enum { INVALID = 0, EXTENTED = 0x5, PROTECTIVE = 0xee, };
Genode::uint8_t _unused[4];
Genode::uint8_t _type; /* partition type */
Genode::uint8_t _unused2[3];
Genode::uint32_t _lba; /* logical block address */
Genode::uint32_t _sectors; /* number of sectors */
bool is_valid() { return _type != INVALID; }
bool is_extented() { return _type == EXTENTED; }
bool is_protective() { return _type == PROTECTIVE; }
} __attribute__((packed));
/**
* Master/Extented boot record format
*/
struct Mbr
{
Genode::uint8_t _unused[446];
Partition_record _records[4];
Genode::uint16_t _magic;
bool is_valid()
{
/* magic number of partition table */
enum { MAGIC = 0xaa55 };
return _magic == MAGIC;
}
} __attribute__((packed));
enum { MAX_PARTITIONS = 32 };
Block::Partition *_part_list[MAX_PARTITIONS]; /* contains pointers to valid
partitions or 0 */
void _parse_extented(Partition_record *record)
{
Partition_record *r = record;
unsigned lba = r->_lba;
/* first logical partition number */
int nr = 5;
do {
Sector s(lba, 1);
Mbr *ebr = s.addr<Mbr *>();
if (!(ebr->is_valid()))
return;
/* The first record is the actual logical partition. The lba of this
* partition is relative to the lba of the current EBR */
Partition_record *logical = &(ebr->_records[0]);
if (logical->is_valid() && nr < MAX_PARTITIONS) {
_part_list[nr++] = new (Genode::env()->heap())
Block::Partition(logical->_lba + lba, logical->_sectors);
PINF("Partition %d: LBA %u (%u blocks) type %x", nr - 1,
logical->_lba + lba, logical->_sectors, logical->_type);
}
/*
* the second record points to the next EBR
* (relative form this EBR)
*/
r = &(ebr->_records[1]);
lba += ebr->_records[1]._lba;
} while (r->is_valid());
}
void _parse_mbr(Mbr *mbr)
{
/* no partition table, use whole disc as partition 0 */
if (!(mbr->is_valid()))
_part_list[0] = new(Genode::env()->heap())
Block::Partition(0, Block::Driver::driver().blk_cnt() - 1);
for (int i = 0; i < 4; i++) {
Partition_record *r = &(mbr->_records[i]);
if (!r->is_valid())
continue;
PINF("Partition %d: LBA %u (%u blocks) type: %x",
i + 1, r->_lba, r->_sectors, r->_type);
if (r->is_protective())
throw Protective_mbr_found();
if (r->is_extented()) {
_parse_extented(r);
continue;
}
_part_list[i + 1] = new(Genode::env()->heap())
Block::Partition(r->_lba, r->_sectors);
}
}
Mbr_partition_table()
{
Sector s(0, 1);
_parse_mbr(s.addr<Mbr *>());
/*
* we read all partition information,
* now it's safe to turn in asynchronous mode
*/
Block::Driver::driver().work_asynchronously();
}
public:
Block::Partition *partition(int num) {
return (num < MAX_PARTITIONS) ? _part_list[num] : 0; }
bool avail()
{
for (unsigned num = 0; num < MAX_PARTITIONS; num++)
if (_part_list[num])
return true;
return false;
}
static Mbr_partition_table& table()
{
static Mbr_partition_table table;
return table;
}
};
#endif /* _PART_BLK__MBR_H_ */

View File

@ -29,18 +29,16 @@ namespace Block {
struct Block::Partition
{
Genode::uint32_t lba; /* logical block address on device */
Genode::uint32_t sectors; /* number of sectors in patitions */
Genode::uint64_t lba; /* logical block address on device */
Genode::uint64_t sectors; /* number of sectors in patitions */
Partition(Genode::uint32_t l, Genode::uint32_t s)
Partition(Genode::uint64_t l, Genode::uint64_t s)
: lba(l), sectors(s) { }
};
class Block::Partition_table
struct Block::Partition_table
{
private:
class Sector
{
private:
@ -70,137 +68,9 @@ class Block::Partition_table
return reinterpret_cast<T>(_session.tx()->packet_content(_p)); }
};
virtual Partition *partition(int num) = 0;
/**
* Partition table entry format
*/
struct Partition_record
{
enum { INVALID = 0, EXTENTED = 0x5 };
Genode::uint8_t _unused[4];
Genode::uint8_t _type; /* partition type */
Genode::uint8_t _unused2[3];
Genode::uint32_t _lba; /* logical block address */
Genode::uint32_t _sectors; /* number of sectors */
bool is_valid() { return _type != INVALID; }
bool is_extented() { return _type == EXTENTED; }
} __attribute__((packed));
/**
* Master/Extented boot record format
*/
struct Mbr
{
Genode::uint8_t _unused[446];
Partition_record _records[4];
Genode::uint16_t _magic;
bool is_valid()
{
/* magic number of partition table */
enum { MAGIC = 0xaa55 };
return _magic == MAGIC;
}
} __attribute__((packed));
enum { MAX_PARTITIONS = 32 };
Partition *_part_list[MAX_PARTITIONS]; /* contains pointers to valid
partitions or 0 */
void _parse_extented(Partition_record *record)
{
Partition_record *r = record;
unsigned lba = r->_lba;
/* first logical partition number */
int nr = 5;
do {
Sector s(lba, 1);
Mbr *ebr = s.addr<Mbr *>();
if (!(ebr->is_valid()))
return;
/* The first record is the actual logical partition. The lba of this
* partition is relative to the lba of the current EBR */
Partition_record *logical = &(ebr->_records[0]);
if (logical->is_valid() && nr < MAX_PARTITIONS) {
_part_list[nr++] = new (Genode::env()->heap())
Partition(logical->_lba + lba, logical->_sectors);
PINF("Partition %d: LBA %u (%u blocks) type %x", nr - 1,
logical->_lba + lba, logical->_sectors, logical->_type);
}
/*
* the second record points to the next EBR
* (relative form this EBR)
*/
r = &(ebr->_records[1]);
lba += ebr->_records[1]._lba;
} while (r->is_valid());
}
void _parse_mbr(Mbr *mbr)
{
/* no partition table, use whole disc as partition 0 */
if (!(mbr->is_valid()))
_part_list[0] = new(Genode::env()->heap())
Partition(0, Driver::driver().blk_cnt() - 1);
for (int i = 0; i < 4; i++) {
Partition_record *r = &(mbr->_records[i]);
if (r->is_valid())
PINF("Partition %d: LBA %u (%u blocks) type: %x",
i + 1, r->_lba, r->_sectors, r->_type);
else
continue;
if (r->is_extented())
_parse_extented(r);
else
_part_list[i + 1] = new(Genode::env()->heap())
Partition(r->_lba, r->_sectors);
}
}
Partition_table()
{
Sector s(0, 1);
_parse_mbr(s.addr<Mbr *>());
/*
* we read all partition information,
* now it's safe to turn in asynchronous mode
*/
Driver::driver().work_asynchronously();
}
public:
Partition *partition(int num) {
return (num < MAX_PARTITIONS) ? _part_list[num] : 0; }
bool avail()
{
for (unsigned num = 0; num < MAX_PARTITIONS; num++)
if (_part_list[num])
return true;
return false;
}
static Partition_table& table()
{
static Partition_table table;
return table;
}
virtual bool avail() = 0;
};
#endif /* _PART_BLK__PARTITION_TABLE_H_ */