It seems that CD-ROMs with MBRs count number of 512 bytes rather than number of 2048-byte sectors.
266 lines
8.6 KiB
C
266 lines
8.6 KiB
C
/*
|
|
* Copyright (c) 2015, 2018 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.
|
|
*
|
|
* mbr.c
|
|
* Master Boot Record.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <endian.h>
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <mount/blockdevice.h>
|
|
#include <mount/harddisk.h>
|
|
#include <mount/mbr.h>
|
|
#include <mount/partition.h>
|
|
|
|
#include "util.h"
|
|
|
|
void mbr_partition_decode(struct mbr_partition* v)
|
|
{
|
|
// flags is endian agnostic.
|
|
// start_head is endian agnostic.
|
|
v->start_sector_cylinder = le16toh(v->start_sector_cylinder);
|
|
v->end_sector_cylinder = le16toh(v->end_sector_cylinder);
|
|
// system_id is endian agnostic.
|
|
// end_head is endian agnostic.
|
|
v->start_sector = le32toh(v->start_sector);
|
|
v->total_sectors = le32toh(v->total_sectors);
|
|
}
|
|
|
|
void mbr_partition_encode(struct mbr_partition* v)
|
|
{
|
|
v->start_sector_cylinder = htole16(v->start_sector_cylinder);
|
|
v->end_sector_cylinder = htole16(v->end_sector_cylinder);
|
|
// flags is endian agnostic.
|
|
// start_head is endian agnostic.
|
|
v->start_sector = htole32(v->start_sector);
|
|
v->total_sectors = htole32(v->total_sectors);
|
|
// system_id is endian agnostic.
|
|
// end_head is endian agnostic.
|
|
}
|
|
|
|
void mbr_partition_table_release(struct mbr_partition_table* pt)
|
|
{
|
|
if ( !pt )
|
|
return;
|
|
free(pt->ebr_chain);
|
|
free(pt);
|
|
}
|
|
|
|
static bool mbr_is_extended_partition(const struct mbr_partition* partition)
|
|
{
|
|
return partition->system_id == 0x05 || // CHS addressing.
|
|
partition->system_id == 0x0F; // LBA addressing.
|
|
}
|
|
|
|
bool mbr_is_partition_used(const struct mbr_partition* partition)
|
|
{
|
|
if ( memiszero(partition, sizeof(*partition)) )
|
|
return false;
|
|
if ( !partition->system_id ||
|
|
!partition->total_sectors )
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
enum partition_error
|
|
blockdevice_get_partition_table_mbr(struct partition_table** pt_ptr,
|
|
struct blockdevice* bdev)
|
|
{
|
|
*pt_ptr = NULL;
|
|
blksize_t logical_block_size = 512;
|
|
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;
|
|
|
|
struct mbr mbr;
|
|
if ( blockdevice_preadall(bdev, &mbr, sizeof(mbr), 0) != sizeof(mbr) )
|
|
return PARTITION_ERROR_ERRNO;
|
|
|
|
if ( mbr.signature[0] != 0x55 && mbr.signature[1] != 0xAA )
|
|
return errno = EINVAL, PARTITION_ERROR_ERRNO;
|
|
|
|
struct partition_table* pt = CALLOC_TYPE(struct partition_table);
|
|
if ( !pt )
|
|
return PARTITION_ERROR_ERRNO;
|
|
*pt_ptr = pt;
|
|
pt->type = PARTITION_TABLE_TYPE_MBR;
|
|
size_t pt__partitions_length = 0;
|
|
pt->usable_start = logical_block_size;
|
|
off_t last_sector = device_size / logical_block_size;
|
|
if ( UINT32_MAX + 1LL < last_sector )
|
|
last_sector = UINT32_MAX + 1LL;
|
|
pt->usable_end = last_sector * logical_block_size;
|
|
|
|
struct mbr_partition_table* mbrpt = CALLOC_TYPE(struct mbr_partition_table);
|
|
if ( !mbrpt )
|
|
return PARTITION_ERROR_ERRNO;
|
|
pt->raw_partition_table = mbrpt;
|
|
memcpy(&mbrpt->mbr, &mbr, sizeof(mbr));
|
|
|
|
unsigned int extended_partition_count = 0;
|
|
for ( unsigned int i = 0; i < 4; i++ )
|
|
{
|
|
struct mbr_partition pmbr;
|
|
memcpy(&pmbr, mbr.partitions[i], sizeof(pmbr));
|
|
mbr_partition_decode(&pmbr);
|
|
if ( mbr_is_partition_used(&pmbr) && mbr_is_extended_partition(&pmbr) )
|
|
extended_partition_count++;
|
|
}
|
|
|
|
if ( 2 <= extended_partition_count ) // Violates assumptions.
|
|
return PARTITION_ERROR_TOO_EXTENDED;
|
|
|
|
for ( unsigned int i = 0; i < 4; i++ )
|
|
{
|
|
struct mbr_partition pmbr;
|
|
memcpy(&pmbr, mbr.partitions[i], sizeof(pmbr));
|
|
mbr_partition_decode(&pmbr);
|
|
if ( !mbr_is_partition_used(&pmbr) )
|
|
continue;
|
|
// TODO: Potential overflow.
|
|
if ( pmbr.start_sector == 0 )
|
|
return PARTITION_ERROR_BEFORE_USABLE;
|
|
off_t start = (off_t) pmbr.start_sector * (off_t) logical_block_size;
|
|
off_t length = (off_t) pmbr.total_sectors * (off_t) logical_block_size;
|
|
if ( device_size < start || device_size - start < length )
|
|
return PARTITION_ERROR_BEYOND_DEVICE;
|
|
for ( size_t j = 0; j < pt->partitions_count; j++ )
|
|
if ( partition_check_overlap(pt->partitions[j], start, length) )
|
|
return PARTITION_ERROR_OVERLAP;
|
|
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;
|
|
if ( mbr_is_extended_partition(&pmbr) )
|
|
{
|
|
p->extended_start = start;
|
|
p->extended_length = length;
|
|
p->type = PARTITION_TYPE_EXTENDED;
|
|
}
|
|
p->table_type = PARTITION_TABLE_TYPE_MBR;
|
|
p->mbr_system_id = pmbr.system_id;
|
|
if ( !array_add((void***) &pt->partitions,
|
|
&pt->partitions_count,
|
|
&pt__partitions_length,
|
|
p) )
|
|
return free(p), PARTITION_ERROR_ERRNO;
|
|
if ( device_path &&
|
|
asprintf(&p->path, "%sp%u", device_path, p->index) < 0 )
|
|
return PARTITION_ERROR_ERRNO;
|
|
}
|
|
|
|
for ( unsigned int i = 0; i < 4; i++ )
|
|
{
|
|
struct mbr_partition pextmbr;
|
|
memcpy(&pextmbr, mbr.partitions[i], sizeof(pextmbr));
|
|
mbr_partition_decode(&pextmbr);
|
|
if ( !mbr_is_partition_used(&pextmbr) )
|
|
continue;
|
|
if ( !mbr_is_extended_partition(&pextmbr) )
|
|
continue;
|
|
// TODO: Potential overflow.
|
|
off_t pext_start = (off_t) pextmbr.start_sector * (off_t) logical_block_size;
|
|
off_t pext_length = (off_t) pextmbr.total_sectors * (off_t) logical_block_size;
|
|
off_t ebr_rel = 0;
|
|
unsigned int j = 0;
|
|
while ( true )
|
|
{
|
|
struct mbr ebr;
|
|
if ( pext_length <= ebr_rel )
|
|
return PARTITION_ERROR_BEYOND_EXTENDED;
|
|
off_t ebr_off = pext_start + ebr_rel;
|
|
if ( blockdevice_preadall(bdev, &ebr, sizeof(ebr), ebr_off) != sizeof(ebr) )
|
|
return PARTITION_ERROR_ERRNO;
|
|
if ( ebr.signature[0] != 0x55 && ebr.signature[1] != 0xAA )
|
|
return PARTITION_ERROR_BAD_EXTENDED;
|
|
size_t new_chain_count = mbrpt->ebr_chain_count + 1;
|
|
size_t chain_size = sizeof(struct mbr_ebr_link) * new_chain_count;
|
|
struct mbr_ebr_link* new_chain =
|
|
(struct mbr_ebr_link*) realloc(mbrpt->ebr_chain, chain_size);
|
|
if ( !new_chain )
|
|
return PARTITION_ERROR_ERRNO;
|
|
mbrpt->ebr_chain = new_chain;
|
|
mbrpt->ebr_chain_count = new_chain_count;
|
|
memcpy(&mbrpt->ebr_chain[j].ebr, &ebr, sizeof(ebr));
|
|
mbrpt->ebr_chain[j].offset = ebr_off;
|
|
struct mbr_partition pmbr;
|
|
memcpy(&pmbr, ebr.partitions[0], sizeof(pmbr));
|
|
mbr_partition_decode(&pmbr);
|
|
if ( mbr_is_partition_used(&pmbr) )
|
|
{
|
|
// TODO: Potential overflow.
|
|
off_t start = (off_t) pmbr.start_sector * (off_t) logical_block_size;
|
|
off_t length = (off_t) pmbr.total_sectors * (off_t) logical_block_size;
|
|
if ( pext_length - ebr_rel < start )
|
|
return PARTITION_ERROR_BEYOND_EXTENDED;
|
|
off_t max_length = (pext_length - ebr_rel) - start;
|
|
if ( max_length < length )
|
|
return PARTITION_ERROR_BEYOND_EXTENDED;
|
|
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 = 5 + j;
|
|
p->start = ebr_off + start;
|
|
p->length = length;
|
|
p->type = PARTITION_TYPE_LOGICAL;
|
|
p->table_type = PARTITION_TABLE_TYPE_MBR;
|
|
p->mbr_system_id = pmbr.system_id;
|
|
if ( !array_add((void***) &pt->partitions,
|
|
&pt->partitions_count,
|
|
&pt__partitions_length,
|
|
p) )
|
|
return free(p), PARTITION_ERROR_ERRNO;
|
|
if ( device_path &&
|
|
asprintf(&p->path, "%sp%u", device_path, p->index) < 0 )
|
|
return PARTITION_ERROR_ERRNO;
|
|
}
|
|
j++;
|
|
struct mbr_partition next;
|
|
memcpy(&next, ebr.partitions[1], sizeof(next));
|
|
mbr_partition_decode(&next);
|
|
// TODO: Potential overflow.
|
|
off_t next_rel = (off_t) next.start_sector * (off_t) logical_block_size;
|
|
if ( !next_rel )
|
|
break;
|
|
if ( next_rel <= ebr_rel ) // Violates assumptions.
|
|
return PARTITION_ERROR_NONLINEAR_EXTENDED;
|
|
if ( pext_length <= next_rel )
|
|
return PARTITION_ERROR_BEYOND_EXTENDED;
|
|
ebr_rel = next_rel;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return PARTITION_ERROR_NONE;
|
|
}
|