gems: add tool to write a GPT to a Block device

This component creates a GPT on a Block device. It supports the common
actions, as in adding, deleting and modifying entries in the GPT, while
considering alignment constraints. If needed it will round the length of
a partition down to meet those constraints. The component will not
perform layout checking, i.e., it does not care about overlapping
partitions. Only when apping a partition it will make sure that the
partition will fit.

Please read _repos/gems/src/app/gpt_write/README_ for more detailed
information on how to use the component and feel free to check out
_repos/gems/run/gpt_write.run_.

Fixes #2814.
This commit is contained in:
Josef Söntgen 2018-05-01 15:29:31 +02:00 committed by Christian Helmuth
parent 3065cceb73
commit ab77f94348
8 changed files with 1838 additions and 0 deletions

View File

@ -0,0 +1,73 @@
#
# Test runs only on Linux
#
assert_spec linux
#
# Build
#
build { core init drivers/timer server/lx_block app/gpt_write }
create_boot_directory
#
# Generate config
#
install_config {
<config>
<parent-provides>
<service name="ROM"/>
<service name="IRQ"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
<service name="PD"/>
<service name="RM"/>
<service name="CPU"/>
<service name="LOG"/>
<service name="TRACE"/>
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<default caps="100"/>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides><service name="Timer"/></provides>
</start>
<start name="lx_block">
<resource name="RAM" quantum="2M"/>
<provides> <service name="Block"/> </provides>
<config file="gpt.img" block_size="512" writeable="yes"/>
</start>
<start name="gpt_write">
<resource name="RAM" quantum="2M"/>
<config verbose="yes" initialize="yes" align="4K">
<actions>
<add entry="1" type="BIOS" label="GRUB BIOS" start="2048" size="1M"/>
<add entry="2" type="EFI" label="EFI System" start="4096" size="16M"/>
<add entry="3" type="Linux" label="GENODE" start="36864" size="128M"/>
<add type="BDP" label="FAT32 Data" size="max"/>
<delete entry="1"/>
<delete label="FAT32 Data"/>
<modify label="GENODE" new_label="GENODE*" new_size="max"/>
</actions>
</config>
</start>
</config>}
#
# Create test file
#
catch { exec dd if=/dev/zero of=bin/gpt.img bs=1M count=256 }
#
# Boot modules
#
build_boot_image {
core ld.lib.so init timer lx_block gpt_write
gpt.img
}
run_genode_until {child "gpt_write" exited with exit value 0.*\n} 10
exec rm -f bin/gpt.img

View File

@ -0,0 +1,99 @@
Brief
=====
This component creates a GPT on a Block device. It supports the common
actions, as in adding, deleting and modifying entries in the GPT, while
considering alignment constraints. If needed it will round the length of
a partition down to meet those constraints. The component will not perform
layout checking, i.e., it does not care about overlapping partitions. Only
when apping a partition it will make sure that the partition will fit.
As a temporary limitation it does not handle Block devices with a logical
block size of 4096 or larger well.
Configuration
=============
The component is instructed to manage the GPT by specifying actions.
The actions are:
* 'add' for adding a new partition. The used entry is set by
the 'entry' attribute, while the 'start' attributes denotes the
starting LBA of the partition. If the 'entry' attribute is missing,
the component operates in append-mode and the next free entry will
be used, while the component selects a proper start LBA and ignores
the 'start' attribute. The mandatory 'label', 'size' and 'type'
attributes must be used to set the corresponding property.
* 'delete' for removing partitions by clearing the corresponding entry.
The entry can by selected by using either the 'entry' or 'label'
attribute.
* 'modify' for changing properties of an existing entry. The
entry can be selected by using either the 'entry' or 'label'
attribute. The 'new_label', 'new_size' and 'new_type' attributes
may be set to change the corresponding property.
The size-related attributes, 'size' and 'new_size', can take as value
a suffixed number (K, M, G) to interpret the value to the base of 2^10,
2^20 or 2^30. As special treatment it is also possible to use 'max' as
value. In this case the component will try to use all free space between
this partition and the next partition or, for that matter, the end of the
addressable space within the GPT.
The type-related attributes, 'type' and 'new_type', take a identifier,
which selects the proper GUID. Valid identifiers are:
* 'EFI' for the EFI system partition type
* 'BIOS' for a BIOS boot partition type (for GRUB to store its core.img)
* 'BDP' for basic data partition type (Windows/DOS file systems)
* 'Linux' for a Linux file system data type (Linux file systems)
The label-related attributes, 'label' and 'new_label', take a ASCII string,
i.e., [A-Za-z- *].
There are config node attributes, in addition to the actions, that instruct
the component to perform additional tasks.
To remove the GPT headers as well as the protective MBR the 'wipe'
attribute can be specified:
! <config wipe="yes"/>
In this case 'gpt_write' will only perform the wiping and than exit,
even if other attributes are set or actions specified.
To update the GPT information, in case the underlying Block device has
changed (for example a generated disk image is copied to a larger USB
stick), the 'update_geometry' attribute may be used to increase the
useable space of the GPT:
! <config update_geometry="yes"/>
Setting the 'initialize' attribute will instruct the component to clear
any existing GPT/PMBR.
! <config initialize="yes"/>
The alignment of partitions can by specified by setting the 'align'
attribute. It defaults to '4096' if not set.
These attributes can be freely mixed, considering the constraint of
using 'wipe'.
The following snippet shows a examplary configuration:
! <config initialize="yes" align="4K">
! <actions>
! <add entry="1" type="BIOS" label="GRUB BIOS" start="2048" size="1M"/>
! <add entry="2" type="EFI" label="EFI System" start="4096" size="16M"/>
! <add entry="3" type="Linux" label="Genode" start="36864" size="32G"/>
! <add type="BDP" label="FAT32 Data" size="1G"/>
!
! <delete entry="1"/>
! <delete label="FAT32 Data"/>
!
! <modify label="Genode" new_label="GENODE*" new_size="max"/>
! </actions>

View File

@ -0,0 +1,453 @@
/*
* \brief GUID Partition table definitions
* \author Josef Soentgen
* \date 2014-09-19
*/
/*
* Copyright (C) 2014-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.
*/
#ifndef _GPT_H_
#define _GPT_H_
/* Genode includes */
#include <base/fixed_stdint.h>
/* local includes */
#include <util.h>
namespace Gpt {
using namespace Genode;
/***************
** Datatypes **
***************/
using Type = Genode::String<32>;
using Label = Genode::String<32>;
/**
* DCE uuid struct
*/
struct Uuid
{
enum { UUID_NODE_LEN = 6 };
uint32_t time_low;
uint16_t time_mid;
uint16_t time_hi_and_version;
uint8_t clock_seq_hi_and_reserved;
uint8_t clock_seq_low;
uint8_t node[UUID_NODE_LEN];
bool valid() const {
return time_low != 0 && time_hi_and_version != 0;
}
} __attribute__((packed));
/**
* GUID parition table header
*/
struct Header
{
char signature[8]; /* identifies GUID Partition Table */
uint32_t revision; /* GPT specification revision */
uint32_t size; /* size of GPT header */
uint32_t crc; /* CRC32 of GPT header */
uint32_t reserved; /* must be zero */
uint64_t lba; /* LBA that contains this header */
uint64_t backup_lba; /* LBA of backup GPT header */
uint64_t part_lba_start; /* first LBA usable for partitions */
uint64_t part_lba_end; /* last LBA usable for partitions */
Uuid guid; /* GUID to identify the disk */
uint64_t gpe_lba; /* first LBA of GPE array */
uint32_t gpe_num; /* number of entries in GPE array */
uint32_t gpe_size; /* size of each GPE */
uint32_t gpe_crc; /* CRC32 of GPE array */
/* the remainder of the struct must be zero */
bool valid(bool primary = true)
{
/* check sig */
if (strcmp(signature, "EFI PART", 8) != 0) {
return false;
}
/* check header crc */
uint32_t const save_crc = crc;
crc = 0;
if (Util::crc32(this, size) != save_crc) {
error("wrong header checksum");
return false;
}
crc = save_crc;
/* check header lba */
if ((primary ? lba : backup_lba) != 1) {
return false;
}
return true;
}
bool entries_valid(void const *entries) const
{
size_t const length = gpe_num * gpe_size;
return Util::crc32(entries, length) == gpe_crc ? true : false;
}
} __attribute__((packed));
/**
* GUID partition entry format
*/
struct Entry
{
enum { NAME_LEN = 36 };
Uuid type; /* partition type GUID */
Uuid guid; /* unique partition GUID */
uint64_t lba_start; /* start of partition */
uint64_t lba_end; /* end of partition */
uint64_t attributes; /* partition attributes */
uint16_t name[NAME_LEN]; /* partition name in UTF-16LE */
bool valid() const { return type.valid(); }
/**
* Extract all valid ASCII characters in the name entry
*/
bool read_name(char *dest, size_t dest_len) const
{
return !!Util::extract_ascii(dest, dest_len, name, NAME_LEN);
}
/**
* Write ASCII to name field
*/
bool write_name(Label const &label)
{
return !!Util::convert_ascii(name, NAME_LEN,
(uint8_t*)label.string(),
label.length() - 1);
}
uint64_t length() const { return lba_end - lba_start + 1; }
} __attribute__((packed));
/*****************
** Definitions **
*****************/
enum { REVISION = 0x00010000u, };
enum {
MIN_ENTRIES = 128u,
MAX_ENTRIES = MIN_ENTRIES,
ENTRIES_SIZE = sizeof(Entry) * MAX_ENTRIES,
PGPT_LBA = 1,
};
/***************
** Functions **
***************/
/**
* Convert type string to UUID
*
* \param type type string
*
* \return UUID in case the type is known, otherwise exception is thrown
*
* \throw Invalid_type
*/
Uuid type_to_uuid(Type const &type)
{
struct {
char const *type;
Uuid uuid;
} _gpt_types[] = {
/* EFI System Partition */
{ "EFI", 0xC12A7328, 0xF81F, 0x11D2, 0xBA, 0x4B, 0x00, 0xA0, 0xC9, 0x3E, 0xC9, 0x3B },
/* BIOS Boot Partition (GRUB) */
{ "BIOS", 0x21686148, 0x6449, 0x6E6F, 0x74, 0x4E, 0x65, 0x65, 0x64, 0x45, 0x46, 0x49 },
/* Basic Data Partition (FAT32, exFAT, NTFS, ...) */
{ "BDP", 0xEBD0A0A2, 0xB9E5, 0x4433, 0x87, 0xC0, 0x68, 0xB6, 0xB7, 0x26, 0x99, 0xC7 },
/* Linux Filesystem Data (for now used by Genode for Ext2 rootfs) */
{ "Linux", 0x0FC63DAF, 0x8483, 0x4772, 0x8E, 0x79, 0x3D, 0x69, 0xD8, 0x47, 0x7D, 0xE4 },
};
size_t const num = sizeof(_gpt_types) / sizeof(_gpt_types[0]);
for (size_t i = 0; i < num; i++) {
if (type == _gpt_types[i].type) {
return _gpt_types[i].uuid;
}
}
struct Invalid_type : Genode::Exception { };
throw Invalid_type();
}
/**
* Generate random UUID (RFC 4122 4.4)
*
* \return random UUID
*/
Uuid generate_uuid()
{
uint8_t buf[sizeof(Uuid)];
Util::get_random(buf, sizeof(buf));
Uuid uuid;
Genode::memcpy(&uuid, buf, sizeof(buf));
uuid.time_hi_and_version =
(uuid.time_hi_and_version & 0x0fff) | 0x4000;
uuid.clock_seq_hi_and_reserved =
(uuid.clock_seq_hi_and_reserved & 0x3f) | 0x80;
return uuid;
}
/**
* Get block gap to next logical entry
*
* \param header pointer to GPT header
* \param entry pointer to current entry
* \param entries pointer to entries
*
* \return the number of free blocks to the next logical entry
*/
Genode::uint64_t gap_length(Gpt::Header const &header,
Gpt::Entry const *entries,
Gpt::Entry const *entry)
{
using namespace Genode;
/* add one block => end == start */
uint64_t const end_lba = entry ? entry->lba_end + 1 : ~0ull;
enum { INVALID_START = ~0ull, };
uint64_t next_start_lba = INVALID_START;
for (uint32_t i = 0; i < header.gpe_num; i++) {
Entry const *e = (entries + i);
if (!e->valid() || e == entry) { continue; }
/*
* Check if the entry starts afterwards and save the
* entry with the smallest distance.
*/
if (e->lba_start >= end_lba) {
next_start_lba = min(next_start_lba, e->lba_start);
}
}
/*
* Use stored next start LBA or paritions end LBA from header,
* if there is no other entry or we are the only one.
*/
return (next_start_lba == INVALID_START ? header.part_lba_end
: next_start_lba) - end_lba;
}
/**
* Find free GPT entry
*
* \param header reference to GPT header
* \param entries pointer to memory containing GPT entries
*
* \return if a free entry is found a pointer to its memory
* is returned, otherwise a null pointer is returned
*/
Entry *find_free(Header const &header, Entry *entries)
{
if (!entries) { return nullptr; }
Entry *result = nullptr;
for (size_t i = 0; i < header.gpe_num; i++) {
Entry *e = (entries + i);
if (e->valid()) { continue; }
result = e;
break;
}
return result;
}
/**
* Get last valid entry
*
* \param header reference to GPT header
* \param entries pointer to memory containing GPT entries
*
*
* \return if a free entry is found a pointer to its memory
* is returned, otherwise a null pointer is returned
*/
Entry const *find_last_valid(Header const &header, Entry const *entries)
{
if (!entries) { return nullptr; }
Entry const *result = nullptr;
for (size_t i = 0; i < header.gpe_num; i++) {
Entry const * const e = (entries + i);
if (e->valid()) { result = e; }
}
return result;
}
/**
* Get next free entry
*/
Entry *find_next_free(Header const &header, Entry *entries,
Entry const *entry)
{
if (!entries || !entry) { return nullptr; }
size_t const num = (entry - entries + 1);
if (num >= header.gpe_num) { return nullptr; }
Entry *result = nullptr;
for (size_t i = num; i < header.gpe_num; i++) {
Entry * const e = (entries + i);
if (e->valid()) { continue; }
result = e;
break;
}
return result;
}
/**
* Lookup GPT entry
*
* \param entries pointer to memory containing GPT entries
* \param num number of GPT entries
* \param label name of the partition in the entry
*
* \return if entry is found a pointer to its memory location
* is returned, otherwise a null pointer is returned
*/
Entry *lookup_entry(Entry *entries, size_t num, Label const &label)
{
if (!entries) { return nullptr; }
Entry *result = nullptr;
char tmp[48];
for (size_t i = 0; i < num; i++) {
Entry *e = (entries + i);
if (!e->valid()) { continue; }
if (!e->read_name(tmp, sizeof(tmp))) { continue; }
if (Genode::strcmp(label.string(), tmp) == 0) {
result = e;
break;
}
}
return result;
}
/**
* Get number of entry
*
* \param entries pointer to memory containing GPT entries
* \param entry pointer to memory containing GPT entry
*
* \return number of entry
*/
uint32_t entry_num(Entry const *entries, Entry const *e)
{
return e - entries + 1;
}
/**
* Get used blocks
*
* \param header reference to GPT header
* \param entries pointer to memory containing GPT entries
*
* \return if a free entry is found a pointer to its memory
* is returned, otherwise a null pointer if returned
*/
uint64_t get_used_blocks(Header const &header, Entry const *entries)
{
if (!entries) { return ~0ull; }
uint64_t result = 0;
for (size_t i = 0; i < header.gpe_num; i++) {
Entry const * const e = (entries + i);
if (!e->valid()) { continue; }
uint64_t const blocks = e->lba_end - e->lba_start;
result += blocks;
}
return result;
}
/**
* Check if given GPT header and entries are valid
*
* \param header reference to header
* \param entries pointer to memory containing GPT entries
* \param primary if true, header is assumed to be the primary
* and otherwise to be the backup header
*
* \return true if header and entries are valid, otherwise false
*/
bool valid(Header &header, Entry const *e, bool primary)
{
return header.valid(primary)
&& header.entries_valid(e) ? true : false;
}
/**
* Update CRC32 fields
*
* \param header reference to header
* \param entries pointer to memory containing GPT entries
*/
void update_crc32(Header &header, void const *entries)
{
size_t const len = header.gpe_size * header.gpe_num;
header.gpe_crc = Util::crc32(entries, len);
header.crc = 0;
header.crc = Util::crc32(&header, header.size);
}
} /* namespace Gpt */
#endif /* _GPT_H_ */

View File

@ -0,0 +1,765 @@
/*
* \brief GPT partitioning tool
* \author Josef Soentgen
* \date 2018-05-01
*/
/*
* Copyright (C) 2018 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/attached_rom_dataspace.h>
#include <base/component.h>
#include <base/heap.h>
/* local includes */
#include <gpt.h>
#include <pmbr.h>
namespace Gpt {
struct Writer;
}
struct Gpt::Writer
{
struct Io_error : Genode::Exception { };
struct Gpt_invalid : Genode::Exception { };
using sector_t = Block::sector_t;
Block::Connection &_block;
Block::Session::Operations _block_ops { };
Block::sector_t _block_count { 0 };
size_t _block_size { 0 };
/*
* Blocks available is a crude approximation that _does not_ take
* alignment or unusable blocks because of the layout into account!
*/
uint64_t _blocks_avail { 0 };
/* track actions */
bool _new_gpt { false };
bool _new_pmbr { false };
bool _new_geometry { false };
/* flags */
bool _verbose { false };
bool _update_geometry { false };
bool _initialize { false };
bool _wipe { false };
bool _force_alignment { false };
Util::Number_of_bytes _entry_alignment { 4096u };
Genode::Xml_node *_config { nullptr };
void _handle_config(Genode::Xml_node config)
{
_verbose = config.attribute_value("verbose", false);
_initialize = config.attribute_value("initialize", false);
_wipe = config.attribute_value("wipe", false);
_force_alignment = config.attribute_value("force_align", false);
_update_geometry = config.attribute_value("update_geometry", false);
{
Util::Size_string align =
config.attribute_value("align", Util::Size_string(4096u));
ascii_to(align.string(), _entry_alignment);
}
bool const commands = config.has_sub_node("commands");
if (_wipe && (_initialize || commands)) {
Genode::warning("will exit after wiping");
}
_config = &config;
}
/************
** Tables **
************/
Protective_mbr::Header _pmbr { };
Gpt::Header _pgpt { };
Gpt::Entry _pgpt_entries[MAX_ENTRIES] { };
Gpt::Header _bgpt { };
Gpt::Entry _bgpt_entries[MAX_ENTRIES] { };
Block::sector_t _old_backup_hdr_lba { 0 };
/**
* Fill in-memory GPT header/entries and valid
*
* \param primary if set to true the primary GPT is checked, if false
* the backup header is checked
*
* \throw Io_error
*/
void _fill_and_check_header(bool primary)
{
using namespace Genode;
Gpt::Header *hdr = primary ? &_pgpt : &_bgpt;
Gpt::Entry *entries = primary ? _pgpt_entries : _bgpt_entries;
memset(hdr, 0, sizeof(Gpt::Header));
memset(entries, 0, ENTRIES_SIZE);
try {
/* header */
{
Block::sector_t const lba = primary
? 1 : _pgpt.backup_lba;
Util::Block_io io(_block, _block_size, lba, 1);
memcpy(hdr, io.addr<Gpt::Header*>(), sizeof(Gpt::Header));
if (!hdr->valid(primary)) {
error(primary ? "primary" : "backup",
" GPT header not valid");
throw Gpt_invalid();
}
}
/* entries */
{
uint32_t const max_entries = hdr->gpe_num > (uint32_t)MAX_ENTRIES
? (uint32_t)MAX_ENTRIES : hdr->gpe_num;
Block::sector_t const lba = hdr->gpe_lba;
size_t const count = max_entries * hdr->gpe_size / _block_size;
Util::Block_io io(_block, _block_size, lba, count);
size_t const len = count * _block_size;
memcpy(entries, io.addr<void const*>(), len);
}
} catch (Util::Block_io::Io_error) {
error("could not read GPT header/entries");
throw Io_error();
}
if (!Gpt::valid(*hdr, entries, primary)) {
error("GPT header and entries not valid");
throw Gpt_invalid();
}
}
/**
* Wipe old backup GPT header from Block device
*/
bool _wipe_old_backup_header()
{
enum { BLOCK_SIZE = 4096u, };
if (_block_size > BLOCK_SIZE) {
Genode::error("block size of ", _block_size, "not supported");
return false;
}
char zeros[BLOCK_SIZE] { };
size_t const blocks = 1 + (ENTRIES_SIZE / _block_size);
Block::sector_t const lba = _old_backup_hdr_lba - blocks;
using namespace Util;
try {
Block_io clear(_block, _block_size, lba, blocks, true, zeros, _block_size);
} catch (Block_io::Io_error) { return false; }
return true;
}
/**
* Wipe all tables from Block device
*
* Note: calling this method actually destroys old data!
*/
bool _wipe_tables()
{
enum { BLOCK_SIZE = 4096u, };
if (_block_size > BLOCK_SIZE) {
Genode::error("block size of ", _block_size, "not supported");
return false;
}
char zeros[BLOCK_SIZE] { };
using namespace Util;
try {
/* PMBR */
Block_io clear_mbr(_block, _block_size, 0, 1, true, zeros, _block_size);
size_t const blocks = 1 + (Gpt::ENTRIES_SIZE / _block_size);
/* PGPT */
for (size_t i = 0; i < blocks; i++) {
Block_io clear_lba(_block, _block_size, 1 + i, 1,
true, zeros, _block_size);
}
/* BGPT */
for (size_t i = 0; i < blocks; i++) {
Block_io clear_lba(_block, _block_size, (_block_count - 1) - i, 1,
true, zeros, _block_size);
}
return true;
} catch (Block_io::Io_error) { return false; }
}
/**
* Setup protective MBR
*
* The first protective partition covers the whole Block device from the
* second block up to the 32bit boundary.
*/
void _setup_pmbr()
{
_pmbr.partitions[0].type = Protective_mbr::TYPE_PROTECTIVE;
_pmbr.partitions[0].lba = 1;
_pmbr.partitions[0].sectors = (uint32_t)(_block_count - 1);
_new_pmbr = true;
}
/**
* Initialize tables
*
* All tables, primary GPT and backup GPT as well as the protective MBR
* will be cleared in memory, a new disk GUID will be generated and the
* default values will be set.
*/
void _initialize_tables()
{
_setup_pmbr();
/* wipe PGPT and BGPT */
Genode::memset(&_pgpt, 0, sizeof(_pgpt));
Genode::memset(_pgpt_entries, 0, sizeof(_pgpt_entries));
Genode::memset(&_bgpt, 0, sizeof(_bgpt));
Genode::memset(_bgpt_entries, 0, sizeof(_bgpt_entries));
size_t const blocks = (size_t)ENTRIES_SIZE / _block_size;
/* setup PGPT, BGPT will be synced later */
Genode::memcpy(_pgpt.signature, "EFI PART", 8);
_pgpt.revision = Gpt::REVISION;
_pgpt.size = sizeof(Gpt::Header);
_pgpt.lba = Gpt::PGPT_LBA;
_pgpt.backup_lba = _block_count - 1;
_pgpt.part_lba_start = 2 + blocks;
_pgpt.part_lba_end = _block_count - (blocks + 2);
_pgpt.guid = Gpt::generate_uuid();
_pgpt.gpe_lba = 2;
_pgpt.gpe_num = Gpt::MAX_ENTRIES;
_pgpt.gpe_size = sizeof(Gpt::Entry);
_blocks_avail = _pgpt.part_lba_end - _pgpt.part_lba_start;
_new_gpt = true;
}
/**
* Synchronize backup header with changes in the primary header
*/
void _sync_backup_header()
{
size_t const len = _pgpt.gpe_num * _pgpt.gpe_size;
Genode::memcpy(_bgpt_entries, _pgpt_entries, len);
Genode::memcpy(&_bgpt, &_pgpt, sizeof(Gpt::Header));
_bgpt.lba = _pgpt.backup_lba;
_bgpt.backup_lba = _pgpt.lba;
_bgpt.gpe_lba = _pgpt.part_lba_end + 1;
}
/**
* Write given header to Block device
*
* \param hdr reference to header
* \param entries pointer to GPT entry array
* \param primary flag to indicate which header to write, primary if true
* and backup when false
*
* \return true when successful, otherwise false
*/
bool _write_header(Gpt::Header const &hdr, Gpt::Entry const *entries, bool primary)
{
using namespace Util;
try {
Block::sector_t const hdr_lba = primary
? 1 : _pgpt.backup_lba;
Block_io hdr_io(_block, _block_size, hdr_lba, 1, true,
&hdr, sizeof(Gpt::Header));
size_t const len = hdr.gpe_num * hdr.gpe_size;
size_t const blocks = len / _block_size;
Block::sector_t const entries_lba = primary
? hdr_lba + 1
: _block_count - (blocks + 1);
Block_io entries_io(_block, _block_size, entries_lba, blocks, true,
entries, len);
} catch (Block_io::Io_error) { return false; }
return true;
}
/**
* Write protective MBR to Block device
*
* \return true when successful, otherwise false
*/
bool _write_pmbr()
{
using namespace Util;
try {
Block_io pmbr(_block, _block_size, 0, 1, true, &_pmbr, sizeof(_pmbr));
} catch (Block_io::Io_error) {
return false;
}
return true;
}
/**
* Commit in-memory changes to Block device
*
* \return true if successful, otherwise false
*/
bool _commit_changes()
{
/* only if in-memory structures changed we want to write */
if (!_new_gpt && !_new_geometry) { return true; }
/* remove stale backup header */
if (_new_geometry) { _wipe_old_backup_header(); }
_sync_backup_header();
Gpt::update_crc32(_pgpt, _pgpt_entries);
Gpt::update_crc32(_bgpt, _bgpt_entries);
return _write_header(_pgpt, _pgpt_entries, true)
&& _write_header(_bgpt, _bgpt_entries, false)
&& (_new_pmbr ? _write_pmbr() : true) ? true : false;
}
/**
* Update geometry information, i.e., fill whole Block device
*/
void _update_geometry_information()
{
if (_pgpt.backup_lba == _block_count - 1) { return; }
_setup_pmbr();
_old_backup_hdr_lba = _pgpt.backup_lba;
size_t const blocks = (size_t)ENTRIES_SIZE / _block_size;
_pgpt.backup_lba = _block_count - 1;
_pgpt.part_lba_end = _block_count - (blocks + 2);
_new_geometry = true;
}
/*************
** Actions **
*************/
enum {
INVALID_ENTRY = ~0u,
INVALID_START = 0,
INVALID_SIZE = ~0ull,
};
/**
* Check if given entry number is in range
*/
uint32_t _check_range(Gpt::Header const &hdr, uint32_t const entry)
{
return (entry != (uint32_t)INVALID_ENTRY
&& (entry == 0 || entry > hdr.gpe_num))
? (uint32_t)INVALID_ENTRY : entry;
}
/**
* Lookup entry by number or label
*
* \return pointer to entry if found, otherwise a nullptr is returned
*/
Gpt::Entry *_lookup_entry(Genode::Xml_node node)
{
Gpt::Label const label = node.attribute_value("label", Gpt::Label());
uint32_t const entry =
_check_range(_pgpt,
node.attribute_value("entry", (uint32_t)INVALID_ENTRY));
if (entry == INVALID_ENTRY && !label.valid()) {
Genode::error("cannot lookup entry, invalid arguments");
return nullptr;
}
if (entry != INVALID_ENTRY && label.valid()) {
Genode::warning("entry and label given, entry number will be used");
}
Gpt::Entry *e = nullptr;
if (entry != INVALID_ENTRY) {
/* we start at 0 */
e = &_pgpt_entries[entry - 1];
}
if (e == nullptr && label.valid()) {
e = Gpt::lookup_entry(_pgpt_entries, _pgpt.gpe_num, label);
}
return e;
}
/**
* Add new GPT entry
*
* \param node action node that contains the arguments
*
* \return true if entry was successfully added, otherwise false
*/
bool _do_add(Genode::Xml_node node)
{
bool const add = node.has_attribute("entry");
Gpt::Label const label = node.attribute_value("label", Gpt::Label());
Gpt::Type const type = node.attribute_value("type", Gpt::Type());
uint64_t const size = Util::convert(node.attribute_value("size",
Util::Size_string()));
if (_verbose) {
Genode::log(add ? "Add" : "Append", " entry '",
label.valid() ? label : "", "' size: ", size);
}
if (!size) {
Genode::error("invalid size");
return false;
}
/* check if partition will fit */
Block::sector_t length = Util::size_to_lba(_block_size, size);
Block::sector_t lba = node.attribute_value("start", (Block::sector_t)INVALID_START);
Gpt::Entry *e = nullptr;
if (add) {
uint32_t const entry = _check_range(_pgpt,
node.attribute_value("entry", (uint32_t)INVALID_ENTRY));
if ( entry == INVALID_ENTRY
|| lba == INVALID_START
|| size == INVALID_SIZE) {
Genode::error("cannot add entry, invalid arguments");
return false;
}
if (length > _blocks_avail) {
Genode::error("not enough sectors left (", _blocks_avail,
") to satisfy request");
return false;
}
if (_pgpt_entries[entry].valid()) {
Genode::error("cannot add already existing entry ", entry);
return false;
}
e = &_pgpt_entries[entry-1];
if (e->valid()) {
Genode::error("cannot add existing entry ", entry);
return false;
}
} else {
/* assume append operation */
Entry const *last = Gpt::find_last_valid(_pgpt, _pgpt_entries);
e = last ? Gpt::find_next_free(_pgpt, _pgpt_entries, last)
: Gpt::find_free(_pgpt, _pgpt_entries);
if (!e) {
Genode::error("cannot append partition, no free entry found");
return false;
}
if (lba != INVALID_START) {
Genode::warning("will ignore start LBA in append mode");
}
lba = last ? last->lba_end + 1 : _pgpt.part_lba_start;
if (lba == INVALID_START) {
Genode::error("cannot find start LBA");
return false;
}
/* limit length to available blocks */
if (length == (~0ull / _block_size)) {
length = Gpt::gap_length(_pgpt, _pgpt_entries, last ? last : nullptr);
}
/* account for alignment */
uint64_t const align = _entry_alignment / _block_size;
if (length < align) {
Genode::error("cannot satisfy alignment constraints");
return false;
}
}
Gpt::Uuid const type_uuid = Gpt::type_to_uuid(type);
e->type = type_uuid;
e->guid = Gpt::generate_uuid();
e->lba_start = Util::align_start(_block_size,
_entry_alignment, lba);
uint64_t const lba_start = e->lba_start;
if (lba_start != lba) {
Genode::warning("start LBA ", lba, " set to ", lba_start,
" due to alignment constraints");
}
e->lba_end = e->lba_start + (length - 1);
if (label.valid()) { e->write_name(label); }
_blocks_avail -= length;
return true;
}
/**
* Delete existing GPT entry
*
* \param node action node that contains the arguments
*
* \return true if entry was successfully added, otherwise false
*/
bool _do_delete(Genode::Xml_node node)
{
Gpt::Entry *e = _lookup_entry(node);
if (!e) { return false; }
if (_verbose) {
char tmp[48];
e->read_name(tmp, sizeof(tmp));
uint32_t const num = Gpt::entry_num(_pgpt_entries, e);
Genode::log("Delete entry ", num, " '", (char const*)tmp, "'");
}
_blocks_avail += e->length();
Genode::memset(e, 0, sizeof(Gpt::Entry));
return true;
}
/**
* Update existing GPT entry
*
* \param node action node that contains the arguments
*
* \return true if entry was successfully added, otherwise false
*/
bool _do_modify(Genode::Xml_node node)
{
using namespace Genode;
Gpt::Entry *e = _lookup_entry(node);
if (!e) {
Genode::error("could not lookup entry");
return false;
}
if (_verbose) {
char tmp[48];
e->read_name(tmp, sizeof(tmp));
uint32_t const num = Gpt::entry_num(_pgpt_entries, e);
Genode::log("Modify entry ", num, " '", (char const*)tmp, "'");
}
uint64_t const new_size = Util::convert(node.attribute_value("new_size",
Util::Size_string()));
if (new_size) {
bool const fill = new_size == ~0ull;
uint64_t length = fill ? Gpt::gap_length(_pgpt, _pgpt_entries, e)
: Util::size_to_lba(_block_size, new_size);
if (length == 0) {
error("cannot modify: ", fill ? "no space left"
: "invalid length");
return false;
}
uint64_t const old_length = e->length();
uint64_t const new_length = length + (fill ? old_length : 0);
uint64_t const expand = new_length > old_length ? new_length - old_length : 0;
if (expand && expand > _blocks_avail) {
Genode::error("cannot modify: new length ", expand, " too large");
return false;
}
if (!expand) { _blocks_avail += (old_length - new_length); }
else { _blocks_avail -= (new_length - old_length); }
/* XXX overlapping check anyone? */
e->lba_end = e->lba_start + new_length - 1;
}
Gpt::Label const new_label = node.attribute_value("new_label", Gpt::Label());
if (new_label.valid()) { e->write_name(new_label); }
Gpt::Type const new_type = node.attribute_value("new_type", Gpt::Type());
if (new_type.valid()) {
try {
Gpt::Uuid type_uuid = Gpt::type_to_uuid(new_type);
memcpy(&e->type, &type_uuid, sizeof(Gpt::Uuid));
} catch (...) {
warning("could not update invalid type");
}
}
/* XXX should we generate a new GUID? */
// e->guid = Gpt::generate_uuid();
return true;
}
/**
* Constructor
*
* \param block reference to underlying Block::Connection
* \param config copy of config Xml_node
*
* \throw Io_error
*/
Writer(Block::Connection &block, Genode::Xml_node config) : _block(block)
{
_block.info(&_block_count, &_block_size, &_block_ops);
if (!_block_ops.supported(Block::Packet_descriptor::WRITE)) {
Genode::error("cannot write to Block session");
throw Io_error();
}
/* initial config read in */
_handle_config(config);
/*
* In case of wiping, end here.
*/
if (_wipe) { return; }
/*
* Read and validate the primary GPT header and its entries first
* and check the backup GPT header afterwards.
*/
if (!_initialize) {
_fill_and_check_header(true);
_fill_and_check_header(false);
if (_update_geometry) {
Genode::log("Update geometry information");
_update_geometry_information();
}
/* set available blocks */
uint64_t const total = _pgpt.part_lba_end - _pgpt.part_lba_start;
_blocks_avail = total - Gpt::get_used_blocks(_pgpt, _pgpt_entries);
}
}
/**
* Execute actions specified in config
*
* \return true if actions were executed successfully, otherwise
* false
*/
bool execute_actions()
{
if (_wipe) { return _wipe_tables(); }
if (_initialize) { _initialize_tables(); }
try {
Genode::Xml_node actions = _config->sub_node("actions");
actions.for_each_sub_node([&] (Genode::Xml_node node) {
bool result = false;
if (node.has_type("add")) {
result = _do_add(node);
} else if (node.has_type("delete")) {
result = _do_delete(node);
} else if (node.has_type("modify")) {
result = _do_modify(node);
} else {
Genode::warning("skipping invalid action");
return;
}
if (!result) { throw -1; }
_new_gpt |= result;
});
} catch (...) { return false; }
/* finally write changes to disk */
return _commit_changes();
}
};
struct Main
{
Genode::Env &_env;
Genode::Heap _heap { _env.ram(), _env.rm() };
Genode::Attached_rom_dataspace _config_rom { _env, "config" };
enum { TX_BUF_SIZE = 128u << 10, };
Genode::Allocator_avl _block_alloc { &_heap };
Block::Connection _block { _env, &_block_alloc, TX_BUF_SIZE };
Genode::Constructible<Gpt::Writer> _writer { };
Main(Genode::Env &env) : _env(env)
{
if (!_config_rom.valid()) {
Genode::error("invalid config");
_env.parent().exit(1);
return;
}
Util::init_random(_heap);
try {
_writer.construct(_block, _config_rom.xml());
} catch (...) {
_env.parent().exit(1);
return;
}
bool const success = _writer->execute_actions();
_env.parent().exit(success ? 0 : 1);
}
};
void Component::construct(Genode::Env &env) { static Main main(env); }

View File

@ -0,0 +1,48 @@
/*
* \brief Protective MBR partition table definitions
* \author Josef Soentgen
* \date 2018-05-03
*/
/*
* Copyright (C) 2018 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.
*/
#ifndef _PMBR_H_
#define _PMBR_H_
/* Genode includes */
#include <base/fixed_stdint.h>
namespace Protective_mbr
{
enum { TYPE_PROTECTIVE = 0xEE, };
/**
* Partition table entry format
*/
struct Partition
{
Genode::uint8_t unused[4] { };
Genode::uint8_t type { };
Genode::uint8_t unused2[3] { };
Genode::uint32_t lba { };
Genode::uint32_t sectors { };
} __attribute__((packed));
/**
* Master boot record header
*/
struct Header
{
Genode::uint8_t unused[446] { };
Partition partitions[4] { };
Genode::uint16_t magic { 0xaa55 };
} __attribute__((packed));
}
#endif /* _PMBR_H_ */

View File

@ -0,0 +1,4 @@
TARGET := gpt_write
LIBS := base jitterentropy
SRC_CC := main.cc util.cc
INC_DIR := $(PRG_DIR)

View File

@ -0,0 +1,227 @@
/*
* \brief Random utility
* \author Josef Soentgen
* \date 2018-05-05
*/
/*
* Copyright (C) 2018 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.h>
/* local includes */
#include <util.h>
/* library includes */
#include <jitterentropy.h>
static struct rand_data *_ec_stir;
/**
* Initialize random back end
*
* \param alloc reference to allocator used internally
*
* \throw Init_random_failed
*/
void Util::init_random(Genode::Allocator &alloc)
{
struct Init_random_failed : Genode::Exception { };
/* initialize private allocator backend */
jitterentropy_init(alloc);
int err = jent_entropy_init();
if (err) {
Genode::error("jitterentropy library could not be initialized!");
throw Init_random_failed();
}
/* use the default behaviour as specified in jitterentropy(3) */
_ec_stir = jent_entropy_collector_alloc(0, 0);
if (!_ec_stir) {
Genode::error("jitterentropy could not allocate entropy collector!");
throw Init_random_failed();
}
}
/**
* Fill buffer with requested number of random bytes
*
* \param dest pointer to destination buffer
* \param len size of the destination buffer
*
* \throw Could_not_harvest_enough_randomness
*/
void Util::get_random(Genode::uint8_t *dest, Genode::size_t len)
{
struct Could_not_harvest_enough_randomness : Genode::Exception { };
if (jent_read_entropy(_ec_stir, (char*)dest, len) < 0) {
throw Could_not_harvest_enough_randomness();
}
}
/**
* Convert size string
*
* \param size reference to size string
*
* \return converted size in bytes
*/
Genode::uint64_t Util::convert(Util::Size_string const &size)
{
if (!size.valid()) { return 0; }
Genode::uint64_t length = 0;
enum { MAX_SIZE = ~0ull, };
if (size == "max") {
length = MAX_SIZE;
} else {
Util::Number_of_bytes bytes;
Util::ascii_to(size.string(), bytes);
length = bytes;
}
return length;
}
/**
* Align LBA at alignment boundary
*
* \param block_size used to calculate number of LBAs
* \param alignment alignment in number of bytes
* \param lba start LBA
*
* \return returns aligned start LBA
*/
Block::sector_t Util::align_start(Genode::size_t block_size,
Genode::size_t alignment,
Block::sector_t lba)
{
struct Invalid_alignment : Genode::Exception { };
if (alignment < block_size || !block_size || !alignment) {
throw Invalid_alignment();
}
Block::sector_t const blocks = alignment / block_size;
return Genode::align_addr(lba, Genode::log2(blocks));
}
/**
* Convert size in bytes to number of LBAs
*/
Block::sector_t Util::size_to_lba(Genode::size_t block_size, Genode::uint64_t size)
{
/* XXX align/round-down etc. */
return size / block_size;
}
/**
* Simple bitwise CRC32 checking
*
* \param buf pointer to buffer containing data
* \param size length of buffer in bytes
*
* \return CRC32 checksum of data
*/
Genode::uint32_t Util::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;
}
/**
* Extract all valid ASCII characters from UTF-16LE buffer
*
* The function operates in a rather crude way and just tries to extract all
* characters < 128, even non-printable ones.
*
* \param dest pointer to destination buffer
* \param dest_len length of the destination buffer in bytes
* \param src pointer to source buffer
* \param dest_len length of the source buffer in 2 bytes
*
* \return length of resulting ASCII string
*/
Genode::size_t Util::extract_ascii(char *dest, size_t dest_len,
uint16_t const *src, size_t src_len)
{
char *p = dest;
size_t j = 0;
size_t i = 0;
for (size_t u = 0; u < src_len && src[u] != 0; u++) {
uint32_t utfchar = src[i++];
if ((utfchar & 0xf800) == 0xd800) {
unsigned int c = src[i];
if ((utfchar & 0x400) != 0 || (c & 0xfc00) != 0xdc00) {
utfchar = 0xfffd;
} else {
i++;
}
}
p[j] = (utfchar < 0x80) ? utfchar : '.';
/* leave space for NUL */
if (++j == dest_len - 1) { break; }
}
p[j] = 0;
return j;
}
/**
* Convert printable ASCII characters to UTF-16LE
*
* The function operates in a rather crude way and will
* truncate the input string if it does not fit.
*
* \param dest pointer to destination buffer
* \param dest_len length of the destination buffer in 16bit words
* \param src pointer to source buffer
* \param dest_len length of the source buffer in 8bit words
*
* \return length of resulting UTF-16 string
*/
Genode::size_t Util::convert_ascii(uint16_t *dest, size_t dest_len,
uint8_t const *src, size_t src_len)
{
Genode::memset(dest, 0, dest_len * sizeof(uint16_t));
if (src_len / sizeof(uint16_t) > dest_len) {
Genode::warning("input too long, will be truncated");
src_len = dest_len;
}
size_t i = 0;
for (; i < src_len; i++) {
uint16_t const utfchar = src[i];
dest[i] = utfchar;
}
return i;
}

View File

@ -0,0 +1,169 @@
/*
* \brief GPT utils
* \author Josef Soentgen
* \date 2018-05-01
*/
/*
* Copyright (C) 2018 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.
*/
#ifndef _UTIL_H_
#define _UTIL_H_
/* Genode includes */
#include <base/fixed_stdint.h>
#include <block_session/connection.h>
#include <util/misc_math.h>
#include <util/string.h>
namespace Util {
using namespace Genode;
void init_random(Genode::Allocator &);
void get_random(uint8_t *dest, size_t len);
using Label = Genode::String<128>;
using Size_string = Genode::String<64>;
uint64_t convert(Size_string const &);
using sector_t = Block::sector_t;
sector_t align_start(size_t, size_t, sector_t);
sector_t size_to_lba(size_t, uint64_t);
struct Block_io;
uint32_t crc32(void const * const, size_t);
size_t extract_ascii(char *, size_t, uint16_t const *, size_t);
size_t convert_ascii(uint16_t *, size_t, uint8_t const *, size_t);
/*
* Wrapper to get suffixed uint64_t values
*/
class Number_of_bytes
{
uint64_t _n;
public:
/**
* Default constructor
*/
Number_of_bytes() : _n(0) { }
/**
* Constructor, to be used implicitly via assignment operator
*/
Number_of_bytes(Genode::uint64_t n) : _n(n) { }
/**
* Convert number of bytes to 'size_t' value
*/
operator Genode::uint64_t() const { return _n; }
void print(Output &output) const
{
using Genode::print;
enum { KB = 1024UL, MB = KB*1024UL, GB = MB*1024UL };
if (_n == 0) print(output, 0);
else if (_n % GB == 0) print(output, _n/GB, "G");
else if (_n % MB == 0) print(output, _n/MB, "M");
else if (_n % KB == 0) print(output, _n/KB, "K");
else print(output, _n);
}
};
inline size_t ascii_to(const char *s, Number_of_bytes &result)
{
unsigned long res = 0;
/* convert numeric part of string */
int i = ascii_to_unsigned(s, res, 0);
/* handle suffixes */
if (i > 0)
switch (s[i]) {
case 'G': res *= 1024;
case 'M': res *= 1024;
case 'K': res *= 1024; i++;
default: break;
}
result = res;
return i;
}
};
/*
* Block_io wraps a Block::Connection for synchronous operations
*/
struct Util::Block_io
{
struct Io_error : Genode::Exception { };
using Packet_descriptor = Block::Packet_descriptor;
Block::Connection &_block;
Packet_descriptor _p;
/**
* Constructor
*
* \param block reference to underlying Block::Connection
* \param block_size logical block size of the Block::Connection
* \param lba LBA to start access from
* \param count number of LBAs to access
* \param write set type of operation, write if true, read
* if false
*
* \throw Io_error
*/
Block_io(Block::Connection &block, size_t block_size,
sector_t lba, size_t count,
bool write = false, void const *data = nullptr, size_t len = 0)
:
_block(block),
_p(_block.tx()->alloc_packet(block_size * count),
write ? Packet_descriptor::WRITE
: Packet_descriptor::READ, lba, count)
{
if (write) {
if (data && len) {
void *p = addr<void*>();
Genode::memcpy(p, data, len);
} else {
Genode::error("invalid data for write");
throw Io_error();
}
}
_block.tx()->submit_packet(_p);
_p = _block.tx()->get_acked_packet();
if (!_p.succeeded()) {
Genode::error("could not ", write ? "write" : "read",
" block-range [", _p.block_number(), ",",
_p.block_number() + count, ")");
_block.tx()->release_packet(_p);
throw Io_error();
}
}
~Block_io() { _block.tx()->release_packet(_p); }
template <typename T> T addr()
{
return reinterpret_cast<T>(_block.tx()->packet_content(_p));
}
};
#endif /* _UTIL_H_ */