I hereby relicense all my work on Sortix under the ISC license as below. All Sortix contributions by other people are already under this license, are not substantial enough to be copyrightable, or have been removed. All imported code from other projects is compatible with this license. All GPL licensed code from other projects had previously been removed. Copyright 2011-2016 Jonas 'Sortie' Termansen and contributors. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
321 lines
11 KiB
C
321 lines
11 KiB
C
/*
|
|
* Copyright (c) 2015 Jonas 'Sortie' Termansen.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*
|
|
* gpt.c
|
|
* GUID Partition Table.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <endian.h>
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <wchar.h>
|
|
|
|
#include <mount/blockdevice.h>
|
|
#include <mount/gpt.h>
|
|
#include <mount/harddisk.h>
|
|
#include <mount/partition.h>
|
|
|
|
#include "util.h"
|
|
|
|
void gpt_decode(struct gpt* v)
|
|
{
|
|
// signature is endian agnostic.
|
|
v->revision = le32toh(v->revision);
|
|
v->header_size = le32toh(v->header_size);
|
|
v->header_crc32 = le32toh(v->header_crc32);
|
|
v->reserved0 = le32toh(v->reserved0);
|
|
v->my_lba = le64toh(v->my_lba);
|
|
v->alternate_lba = le64toh(v->alternate_lba);
|
|
v->first_usable_lba = le64toh(v->first_usable_lba);
|
|
v->last_usable_lba = le64toh(v->last_usable_lba);
|
|
// disk_guid is endian agnostic.
|
|
v->partition_entry_lba = le64toh(v->partition_entry_lba);
|
|
v->number_of_partition_entries = le32toh(v->number_of_partition_entries);
|
|
v->size_of_partition_entry = le32toh(v->size_of_partition_entry);
|
|
v->partition_entry_array_crc32 = le32toh(v->partition_entry_array_crc32);
|
|
// reserved bytes are endian agnostic.
|
|
}
|
|
|
|
void gpt_encode(struct gpt* v)
|
|
{
|
|
// signature is endian agnostic.
|
|
v->revision = htole32(v->revision);
|
|
v->header_size = htole32(v->header_size);
|
|
v->header_crc32 = htole32(v->header_crc32);
|
|
v->reserved0 = htole32(v->reserved0);
|
|
v->my_lba = htole64(v->my_lba);
|
|
v->alternate_lba = htole64(v->alternate_lba);
|
|
v->first_usable_lba = htole64(v->first_usable_lba);
|
|
v->last_usable_lba = htole64(v->last_usable_lba);
|
|
// disk_guid is endian agnostic.
|
|
v->partition_entry_lba = htole64(v->partition_entry_lba);
|
|
v->number_of_partition_entries = htole32(v->number_of_partition_entries);
|
|
v->size_of_partition_entry = htole32(v->size_of_partition_entry);
|
|
v->partition_entry_array_crc32 = htole32(v->partition_entry_array_crc32);
|
|
// reserved bytes are endian agnostic.
|
|
}
|
|
|
|
void gpt_partition_decode(struct gpt_partition* v)
|
|
{
|
|
// partition_type_guid is endian agnostic.
|
|
// unique_partition_guid is endian agnostic.
|
|
v->starting_lba = le64toh(v->starting_lba);
|
|
v->ending_lba = le64toh(v->ending_lba);
|
|
v->attributes = le64toh(v->attributes);
|
|
for ( size_t i = 0; i < GPT_PARTITION_NAME_LENGTH; i++ )
|
|
v->partition_name[i] = le16toh(v->partition_name[i]);
|
|
}
|
|
|
|
void gpt_partition_encode(struct gpt_partition* v)
|
|
{
|
|
// partition_type_guid is endian agnostic.
|
|
// unique_partition_guid is endian agnostic.
|
|
v->starting_lba = htole64(v->starting_lba);
|
|
v->ending_lba = htole64(v->ending_lba);
|
|
v->attributes = htole64(v->attributes);
|
|
for ( size_t i = 0; i < GPT_PARTITION_NAME_LENGTH; i++ )
|
|
v->partition_name[i] = htole16(v->partition_name[i]);
|
|
}
|
|
|
|
char* gpt_decode_utf16(uint16_t* string, size_t length)
|
|
{
|
|
mbstate_t ps;
|
|
memset(&ps, 0, sizeof(ps));
|
|
|
|
char* result = NULL;
|
|
size_t result_used = 0;
|
|
size_t result_length = 0;
|
|
|
|
for ( size_t i = 0; true; i++ )
|
|
{
|
|
wchar_t wc;
|
|
if ( length <= i )
|
|
wc = L'\0';
|
|
else if ( 0xDC00 <= string[i] && string[i] <= 0xDFFF )
|
|
return free(result), errno = EILSEQ, (char*) NULL;
|
|
else if ( 0xD800 <= string[i] && string[i] <= 0xDBFF )
|
|
{
|
|
uint16_t high = string[i] - 0xD800;
|
|
if ( i + 1 == length )
|
|
return free(result), errno = EILSEQ, (char*) NULL;
|
|
if ( !(0xDC00 <= string[i+1] && string[i] <= 0xDFFF) )
|
|
return free(result), errno = EILSEQ, (char*) NULL;
|
|
uint16_t low = string[++i] - 0xDC00;
|
|
wc = (((wchar_t) high << 10) | ((wchar_t) low << 0)) + 0x10000;
|
|
}
|
|
else
|
|
wc = (wchar_t) string[i++];
|
|
char mb[MB_CUR_MAX];
|
|
size_t amount = wcrtomb(mb, wc, &ps);
|
|
if ( amount == (size_t) -1 )
|
|
return free(result), (char*) NULL;
|
|
for ( size_t n = 0; n < amount; n++ )
|
|
{
|
|
if ( result_used == result_length )
|
|
{
|
|
// TODO: Potential overflow.
|
|
size_t new_length = result_length ? 2 * result_length : length;
|
|
char* new_result = (char*) realloc(result, new_length);
|
|
if ( !new_result )
|
|
return free(result), (char*) NULL;
|
|
result = new_result;
|
|
result_length = new_length;
|
|
}
|
|
result[result_used++] = mb[n];
|
|
}
|
|
if ( wc == L'\0' )
|
|
{
|
|
char* new_result = (char*) realloc(result, result_used);
|
|
if ( new_result )
|
|
result = new_result;
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
void gpt_partition_table_release(struct gpt_partition_table* pt)
|
|
{
|
|
if ( !pt )
|
|
return;
|
|
free(pt->rpt);
|
|
free(pt);
|
|
}
|
|
|
|
static bool gpt_check_power_of_two(uintmax_t value,
|
|
uintmax_t low,
|
|
uintmax_t high)
|
|
{
|
|
for ( uintmax_t cmp = low; cmp <= high; cmp *= 2 )
|
|
if ( value == cmp )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
enum partition_error
|
|
blockdevice_get_partition_table_gpt(struct partition_table** pt_ptr,
|
|
struct blockdevice* bdev)
|
|
{
|
|
*pt_ptr = NULL;
|
|
blksize_t logical_block_size = blockdevice_logical_block_size(bdev);
|
|
if ( !blockdevice_check_reasonable_block_size(logical_block_size) )
|
|
return errno = EINVAL, PARTITION_ERROR_ERRNO;
|
|
off_t device_size = blockdevice_size(bdev);
|
|
const char* device_path = bdev->p ? bdev->p->path : bdev->hd->path;
|
|
|
|
unsigned char* gpt_block = (unsigned char*) malloc(logical_block_size);
|
|
if ( !gpt_block )
|
|
return PARTITION_ERROR_ERRNO;
|
|
if ( blockdevice_preadall(bdev, gpt_block, logical_block_size,
|
|
1 * logical_block_size) != (size_t) logical_block_size )
|
|
return free(gpt_block), PARTITION_ERROR_ERRNO;
|
|
|
|
struct gpt gpt;
|
|
memcpy(&gpt, gpt_block, sizeof(gpt));
|
|
gpt_decode(&gpt);
|
|
|
|
if ( memcmp(gpt.signature, "EFI PART", 8) != 0 )
|
|
return PARTITION_ERROR_INVALID;
|
|
static_assert( 92 == sizeof(gpt) - sizeof(gpt.reserved1),
|
|
"92 == sizeof(gpt) - sizeof(gpt.reserved1)");
|
|
if ( gpt.header_size < 92 )
|
|
return free(gpt_block), PARTITION_ERROR_INVALID;
|
|
|
|
struct partition_table* pt = CALLOC_TYPE(struct partition_table);
|
|
if ( !pt )
|
|
return free(gpt_block), PARTITION_ERROR_ERRNO;
|
|
*pt_ptr = pt;
|
|
pt->type = PARTITION_TABLE_TYPE_GPT;
|
|
size_t pt__partitions_length = 0;
|
|
|
|
if ( logical_block_size < gpt.header_size )
|
|
return free(gpt_block), PARTITION_ERROR_HEADER_TOO_LARGE;
|
|
|
|
struct gpt_partition_table* gptpt = CALLOC_TYPE(struct gpt_partition_table);
|
|
if ( !gptpt )
|
|
return free(gpt_block), PARTITION_ERROR_ERRNO;
|
|
pt->raw_partition_table = gptpt;
|
|
memcpy(&gptpt->gpt, gpt_block, sizeof(gptpt->gpt));
|
|
|
|
struct gpt* check_gpt = (struct gpt*) gpt_block;
|
|
check_gpt->header_crc32 = 0;
|
|
uint32_t checksum = gpt_crc32(gpt_block, gpt.header_size);
|
|
free(gpt_block);
|
|
if ( checksum != gpt.header_crc32 )
|
|
return PARTITION_ERROR_CHECKSUM;
|
|
|
|
memcpy(pt->gpt_disk_guid, gpt.disk_guid, 16);
|
|
|
|
if ( !gpt_check_power_of_two(gpt.size_of_partition_entry,
|
|
sizeof(struct gpt_partition), 65536) )
|
|
return PARTITION_ERROR_INVALID;
|
|
if ( gpt.my_lba != 1 )
|
|
return PARTITION_ERROR_INVALID;
|
|
|
|
if ( gpt.my_lba >= gpt.first_usable_lba )
|
|
return PARTITION_ERROR_INVALID;
|
|
// TODO: Ensure nothing collides with anything else.
|
|
if ( gpt.partition_entry_lba <= 1 )
|
|
return PARTITION_ERROR_INVALID;
|
|
if ( gpt.last_usable_lba < gpt.first_usable_lba )
|
|
return PARTITION_ERROR_INVALID;
|
|
|
|
// TODO: Potential overflow.
|
|
pt->usable_start = (off_t) gpt.first_usable_lba * (off_t) logical_block_size;
|
|
pt->usable_end = ((off_t) gpt.last_usable_lba + 1) * (off_t) logical_block_size;
|
|
if ( device_size < pt->usable_end )
|
|
return PARTITION_ERROR_INVALID;
|
|
|
|
// TODO: Potential overflow.
|
|
size_t rpt_size = (size_t) gpt.size_of_partition_entry *
|
|
(size_t) gpt.number_of_partition_entries;
|
|
off_t rpt_off = (off_t) gpt.partition_entry_lba * (off_t) logical_block_size;
|
|
off_t rpt_end = rpt_off + rpt_off;
|
|
if ( pt->usable_start < rpt_end )
|
|
return PARTITION_ERROR_INVALID;
|
|
if ( !(gptpt->rpt = (unsigned char*) malloc(rpt_size)) )
|
|
return PARTITION_ERROR_ERRNO;
|
|
unsigned char* rpt = gptpt->rpt;
|
|
if ( blockdevice_preadall(bdev, rpt, rpt_size, rpt_off) != rpt_size )
|
|
return PARTITION_ERROR_ERRNO;
|
|
if ( gpt.partition_entry_array_crc32 != gpt_crc32(rpt, rpt_size) )
|
|
return PARTITION_ERROR_CHECKSUM;
|
|
|
|
for ( uint32_t i = 0; i < gpt.number_of_partition_entries; i++ )
|
|
{
|
|
size_t pentry_off = (size_t) i * (size_t) gpt.size_of_partition_entry;
|
|
struct gpt_partition pentry;
|
|
memcpy(&pentry, rpt + pentry_off, sizeof(pentry));
|
|
gpt_partition_decode(&pentry);
|
|
if ( memiszero(pentry.partition_type_guid, 16) )
|
|
continue;
|
|
if ( pentry.ending_lba < pentry.starting_lba )
|
|
return PARTITION_ERROR_END_BEFORE_START;
|
|
// TODO: Potential overflow.
|
|
uint64_t lba_count = (pentry.ending_lba - pentry.starting_lba) + 1;
|
|
off_t start = (off_t) pentry.starting_lba * (off_t) logical_block_size;
|
|
if ( start < pt->usable_start )
|
|
return PARTITION_ERROR_BEFORE_USABLE;
|
|
off_t length = (off_t) lba_count * (off_t) logical_block_size;
|
|
if ( pt->usable_end < start || pt->usable_end - start < length )
|
|
return PARTITION_ERROR_BEYOND_USABLE;
|
|
struct partition* p = CALLOC_TYPE(struct partition);
|
|
if ( !p )
|
|
return PARTITION_ERROR_ERRNO;
|
|
memset(&p->bdev, 0, sizeof(p->bdev));
|
|
p->bdev.p = p;
|
|
p->parent_bdev = bdev;
|
|
p->index = 1 + i;
|
|
p->start = start;
|
|
p->length = length;
|
|
p->type = PARTITION_TYPE_PRIMARY;
|
|
p->table_type = PARTITION_TABLE_TYPE_GPT;
|
|
memcpy(p->gpt_type_guid, pentry.partition_type_guid, 16);
|
|
memcpy(p->gpt_unique_guid, pentry.unique_partition_guid, 16);
|
|
p->gpt_attributes = pentry.attributes;
|
|
if ( !array_add((void***) &pt->partitions,
|
|
&pt->partitions_count,
|
|
&pt__partitions_length,
|
|
p) )
|
|
return free(p), PARTITION_ERROR_ERRNO;
|
|
if ( !(p->gpt_name = gpt_decode_utf16(pentry.partition_name,
|
|
GPT_PARTITION_NAME_LENGTH)) )
|
|
return PARTITION_ERROR_ERRNO;
|
|
if ( device_path &&
|
|
asprintf(&p->path, "%sp%u", device_path, p->index) < 0 )
|
|
return PARTITION_ERROR_ERRNO;
|
|
}
|
|
|
|
qsort(pt->partitions, pt->partitions_count, sizeof(pt->partitions[0]),
|
|
partition_compare_start_indirect);
|
|
for ( size_t i = 1; i < pt->partitions_count; i++ )
|
|
{
|
|
struct partition* a = pt->partitions[i-1];
|
|
struct partition* b = pt->partitions[i];
|
|
if ( partition_check_overlap(a, b->start, b->length) )
|
|
return PARTITION_ERROR_OVERLAP;
|
|
}
|
|
qsort(pt->partitions, pt->partitions_count, sizeof(pt->partitions[0]),
|
|
partition_compare_index_indirect);
|
|
|
|
return PARTITION_ERROR_NONE;
|
|
}
|