mirror of
https://github.com/genodelabs/genode.git
synced 2025-04-09 04:15:52 +00:00
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:
parent
3065cceb73
commit
ab77f94348
73
repos/gems/run/gpt_write.run
Normal file
73
repos/gems/run/gpt_write.run
Normal 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
|
99
repos/gems/src/app/gpt_write/README
Normal file
99
repos/gems/src/app/gpt_write/README
Normal 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>
|
||||
|
453
repos/gems/src/app/gpt_write/gpt.h
Normal file
453
repos/gems/src/app/gpt_write/gpt.h
Normal 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_ */
|
765
repos/gems/src/app/gpt_write/main.cc
Normal file
765
repos/gems/src/app/gpt_write/main.cc
Normal 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); }
|
48
repos/gems/src/app/gpt_write/pmbr.h
Normal file
48
repos/gems/src/app/gpt_write/pmbr.h
Normal 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_ */
|
4
repos/gems/src/app/gpt_write/target.mk
Normal file
4
repos/gems/src/app/gpt_write/target.mk
Normal file
@ -0,0 +1,4 @@
|
||||
TARGET := gpt_write
|
||||
LIBS := base jitterentropy
|
||||
SRC_CC := main.cc util.cc
|
||||
INC_DIR := $(PRG_DIR)
|
227
repos/gems/src/app/gpt_write/util.cc
Normal file
227
repos/gems/src/app/gpt_write/util.cc
Normal 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;
|
||||
}
|
169
repos/gems/src/app/gpt_write/util.h
Normal file
169
repos/gems/src/app/gpt_write/util.h
Normal 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_ */
|
Loading…
x
Reference in New Issue
Block a user