c12dbfe608
Need interface to extract information about disk abstraction, to read disk or partition size depending on the provided argument and adjust disk size based on information in partition table. The disk handle from disk_open() has d_offset field to point to partition start. So we can use this fact to return either whole disk size or partition size. For this we only need to record partition size we get from disk_open() anyhow. In addition, this will also make it possible to adjust the disk media size based on information from partition table. The problem with disk size is about some BIOS systems reporting bogus disk size for 2+TB disks, but since such disks are using GPT partitioning, and GPT does have information about disk size (alternate LBA + 1), we can use this fact to record disk size based on partition table. This patch does exactly this: implements DIOCGSECTORSIZE and DIOCGMEDIASIZE ioctl, and DIOCGMEDIASIZE will report either disk media size or partition size. Adds ptable_getsize() call to read partition size in bytes from ptable pointer. Updates disk_open() to use ptable_getsize() to update mediasize value. Implements GPT detection function to update ptable size (used by ptable_getsize()) according to alternate lba (which is location of backup copy of GPT header table). Reviewed by: allanjude Approved by: allanjude (mentor) Differential Revision: https://reviews.freebsd.org/D8594
898 lines
23 KiB
C
898 lines
23 KiB
C
/*-
|
|
* Copyright (c) 2012 Andrey V. Elsukov <ae@FreeBSD.org>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <stand.h>
|
|
#include <sys/param.h>
|
|
#include <sys/diskmbr.h>
|
|
#include <sys/disklabel.h>
|
|
#include <sys/endian.h>
|
|
#include <sys/gpt.h>
|
|
#include <sys/stddef.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/vtoc.h>
|
|
|
|
#include <crc32.h>
|
|
#include <part.h>
|
|
#include <uuid.h>
|
|
|
|
#ifdef PART_DEBUG
|
|
#define DEBUG(fmt, args...) printf("%s: " fmt "\n" , __func__ , ## args)
|
|
#else
|
|
#define DEBUG(fmt, args...)
|
|
#endif
|
|
|
|
#ifdef LOADER_GPT_SUPPORT
|
|
#define MAXTBLSZ 64
|
|
static const uuid_t gpt_uuid_unused = GPT_ENT_TYPE_UNUSED;
|
|
static const uuid_t gpt_uuid_ms_basic_data = GPT_ENT_TYPE_MS_BASIC_DATA;
|
|
static const uuid_t gpt_uuid_freebsd_ufs = GPT_ENT_TYPE_FREEBSD_UFS;
|
|
static const uuid_t gpt_uuid_efi = GPT_ENT_TYPE_EFI;
|
|
static const uuid_t gpt_uuid_freebsd = GPT_ENT_TYPE_FREEBSD;
|
|
static const uuid_t gpt_uuid_freebsd_boot = GPT_ENT_TYPE_FREEBSD_BOOT;
|
|
static const uuid_t gpt_uuid_freebsd_nandfs = GPT_ENT_TYPE_FREEBSD_NANDFS;
|
|
static const uuid_t gpt_uuid_freebsd_swap = GPT_ENT_TYPE_FREEBSD_SWAP;
|
|
static const uuid_t gpt_uuid_freebsd_zfs = GPT_ENT_TYPE_FREEBSD_ZFS;
|
|
static const uuid_t gpt_uuid_freebsd_vinum = GPT_ENT_TYPE_FREEBSD_VINUM;
|
|
#endif
|
|
|
|
struct pentry {
|
|
struct ptable_entry part;
|
|
uint64_t flags;
|
|
union {
|
|
uint8_t bsd;
|
|
uint8_t mbr;
|
|
uuid_t gpt;
|
|
uint16_t vtoc8;
|
|
} type;
|
|
STAILQ_ENTRY(pentry) entry;
|
|
};
|
|
|
|
struct ptable {
|
|
enum ptable_type type;
|
|
uint16_t sectorsize;
|
|
uint64_t sectors;
|
|
|
|
STAILQ_HEAD(, pentry) entries;
|
|
};
|
|
|
|
static struct parttypes {
|
|
enum partition_type type;
|
|
const char *desc;
|
|
} ptypes[] = {
|
|
{ PART_UNKNOWN, "Unknown" },
|
|
{ PART_EFI, "EFI" },
|
|
{ PART_FREEBSD, "FreeBSD" },
|
|
{ PART_FREEBSD_BOOT, "FreeBSD boot" },
|
|
{ PART_FREEBSD_NANDFS, "FreeBSD nandfs" },
|
|
{ PART_FREEBSD_UFS, "FreeBSD UFS" },
|
|
{ PART_FREEBSD_ZFS, "FreeBSD ZFS" },
|
|
{ PART_FREEBSD_SWAP, "FreeBSD swap" },
|
|
{ PART_FREEBSD_VINUM, "FreeBSD vinum" },
|
|
{ PART_LINUX, "Linux" },
|
|
{ PART_LINUX_SWAP, "Linux swap" },
|
|
{ PART_DOS, "DOS/Windows" },
|
|
};
|
|
|
|
const char *
|
|
parttype2str(enum partition_type type)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < nitems(ptypes); i++)
|
|
if (ptypes[i].type == type)
|
|
return (ptypes[i].desc);
|
|
return (ptypes[0].desc);
|
|
}
|
|
|
|
#ifdef LOADER_GPT_SUPPORT
|
|
static void
|
|
uuid_letoh(uuid_t *uuid)
|
|
{
|
|
|
|
uuid->time_low = le32toh(uuid->time_low);
|
|
uuid->time_mid = le16toh(uuid->time_mid);
|
|
uuid->time_hi_and_version = le16toh(uuid->time_hi_and_version);
|
|
}
|
|
|
|
static enum partition_type
|
|
gpt_parttype(uuid_t type)
|
|
{
|
|
|
|
if (uuid_equal(&type, &gpt_uuid_efi, NULL))
|
|
return (PART_EFI);
|
|
else if (uuid_equal(&type, &gpt_uuid_ms_basic_data, NULL))
|
|
return (PART_DOS);
|
|
else if (uuid_equal(&type, &gpt_uuid_freebsd_boot, NULL))
|
|
return (PART_FREEBSD_BOOT);
|
|
else if (uuid_equal(&type, &gpt_uuid_freebsd_ufs, NULL))
|
|
return (PART_FREEBSD_UFS);
|
|
else if (uuid_equal(&type, &gpt_uuid_freebsd_zfs, NULL))
|
|
return (PART_FREEBSD_ZFS);
|
|
else if (uuid_equal(&type, &gpt_uuid_freebsd_swap, NULL))
|
|
return (PART_FREEBSD_SWAP);
|
|
else if (uuid_equal(&type, &gpt_uuid_freebsd_vinum, NULL))
|
|
return (PART_FREEBSD_VINUM);
|
|
else if (uuid_equal(&type, &gpt_uuid_freebsd_nandfs, NULL))
|
|
return (PART_FREEBSD_NANDFS);
|
|
else if (uuid_equal(&type, &gpt_uuid_freebsd, NULL))
|
|
return (PART_FREEBSD);
|
|
return (PART_UNKNOWN);
|
|
}
|
|
|
|
static struct gpt_hdr*
|
|
gpt_checkhdr(struct gpt_hdr *hdr, uint64_t lba_self, uint64_t lba_last,
|
|
uint16_t sectorsize)
|
|
{
|
|
uint32_t sz, crc;
|
|
|
|
if (memcmp(hdr->hdr_sig, GPT_HDR_SIG, sizeof(hdr->hdr_sig)) != 0) {
|
|
DEBUG("no GPT signature");
|
|
return (NULL);
|
|
}
|
|
sz = le32toh(hdr->hdr_size);
|
|
if (sz < 92 || sz > sectorsize) {
|
|
DEBUG("invalid GPT header size: %d", sz);
|
|
return (NULL);
|
|
}
|
|
crc = le32toh(hdr->hdr_crc_self);
|
|
hdr->hdr_crc_self = 0;
|
|
if (crc32(hdr, sz) != crc) {
|
|
DEBUG("GPT header's CRC doesn't match");
|
|
return (NULL);
|
|
}
|
|
hdr->hdr_crc_self = crc;
|
|
hdr->hdr_revision = le32toh(hdr->hdr_revision);
|
|
if (hdr->hdr_revision < GPT_HDR_REVISION) {
|
|
DEBUG("unsupported GPT revision %d", hdr->hdr_revision);
|
|
return (NULL);
|
|
}
|
|
hdr->hdr_lba_self = le64toh(hdr->hdr_lba_self);
|
|
if (hdr->hdr_lba_self != lba_self) {
|
|
DEBUG("self LBA doesn't match");
|
|
return (NULL);
|
|
}
|
|
hdr->hdr_lba_alt = le64toh(hdr->hdr_lba_alt);
|
|
if (hdr->hdr_lba_alt == hdr->hdr_lba_self) {
|
|
DEBUG("invalid alternate LBA");
|
|
return (NULL);
|
|
}
|
|
hdr->hdr_entries = le32toh(hdr->hdr_entries);
|
|
hdr->hdr_entsz = le32toh(hdr->hdr_entsz);
|
|
if (hdr->hdr_entries == 0 ||
|
|
hdr->hdr_entsz < sizeof(struct gpt_ent) ||
|
|
sectorsize % hdr->hdr_entsz != 0) {
|
|
DEBUG("invalid entry size or number of entries");
|
|
return (NULL);
|
|
}
|
|
hdr->hdr_lba_start = le64toh(hdr->hdr_lba_start);
|
|
hdr->hdr_lba_end = le64toh(hdr->hdr_lba_end);
|
|
hdr->hdr_lba_table = le64toh(hdr->hdr_lba_table);
|
|
hdr->hdr_crc_table = le32toh(hdr->hdr_crc_table);
|
|
uuid_letoh(&hdr->hdr_uuid);
|
|
return (hdr);
|
|
}
|
|
|
|
static int
|
|
gpt_checktbl(const struct gpt_hdr *hdr, u_char *tbl, size_t size,
|
|
uint64_t lba_last)
|
|
{
|
|
struct gpt_ent *ent;
|
|
uint32_t i, cnt;
|
|
|
|
cnt = size / hdr->hdr_entsz;
|
|
if (hdr->hdr_entries <= cnt) {
|
|
cnt = hdr->hdr_entries;
|
|
/* Check CRC only when buffer size is enough for table. */
|
|
if (hdr->hdr_crc_table !=
|
|
crc32(tbl, hdr->hdr_entries * hdr->hdr_entsz)) {
|
|
DEBUG("GPT table's CRC doesn't match");
|
|
return (-1);
|
|
}
|
|
}
|
|
for (i = 0; i < cnt; i++) {
|
|
ent = (struct gpt_ent *)(tbl + i * hdr->hdr_entsz);
|
|
uuid_letoh(&ent->ent_type);
|
|
if (uuid_equal(&ent->ent_type, &gpt_uuid_unused, NULL))
|
|
continue;
|
|
ent->ent_lba_start = le64toh(ent->ent_lba_start);
|
|
ent->ent_lba_end = le64toh(ent->ent_lba_end);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
static struct ptable*
|
|
ptable_gptread(struct ptable *table, void *dev, diskread_t dread)
|
|
{
|
|
struct pentry *entry;
|
|
struct gpt_hdr *phdr, hdr;
|
|
struct gpt_ent *ent;
|
|
u_char *buf, *tbl;
|
|
uint64_t offset;
|
|
int pri, sec;
|
|
size_t size, i;
|
|
|
|
buf = malloc(table->sectorsize);
|
|
if (buf == NULL)
|
|
return (NULL);
|
|
tbl = malloc(table->sectorsize * MAXTBLSZ);
|
|
if (tbl == NULL) {
|
|
free(buf);
|
|
return (NULL);
|
|
}
|
|
/* Read the primary GPT header. */
|
|
if (dread(dev, buf, 1, 1) != 0) {
|
|
ptable_close(table);
|
|
table = NULL;
|
|
goto out;
|
|
}
|
|
pri = sec = 0;
|
|
/* Check the primary GPT header. */
|
|
phdr = gpt_checkhdr((struct gpt_hdr *)buf, 1, table->sectors - 1,
|
|
table->sectorsize);
|
|
if (phdr != NULL) {
|
|
/* Read the primary GPT table. */
|
|
size = MIN(MAXTBLSZ,
|
|
howmany(phdr->hdr_entries * phdr->hdr_entsz,
|
|
table->sectorsize));
|
|
if (dread(dev, tbl, size, phdr->hdr_lba_table) == 0 &&
|
|
gpt_checktbl(phdr, tbl, size * table->sectorsize,
|
|
table->sectors - 1) == 0) {
|
|
memcpy(&hdr, phdr, sizeof(hdr));
|
|
pri = 1;
|
|
}
|
|
}
|
|
offset = pri ? hdr.hdr_lba_alt: table->sectors - 1;
|
|
/* Read the backup GPT header. */
|
|
if (dread(dev, buf, 1, offset) != 0)
|
|
phdr = NULL;
|
|
else
|
|
phdr = gpt_checkhdr((struct gpt_hdr *)buf, offset,
|
|
table->sectors - 1, table->sectorsize);
|
|
if (phdr != NULL) {
|
|
/*
|
|
* Compare primary and backup headers.
|
|
* If they are equal, then we do not need to read backup
|
|
* table. If they are different, then prefer backup header
|
|
* and try to read backup table.
|
|
*/
|
|
if (pri == 0 ||
|
|
uuid_equal(&hdr.hdr_uuid, &phdr->hdr_uuid, NULL) == 0 ||
|
|
hdr.hdr_revision != phdr->hdr_revision ||
|
|
hdr.hdr_size != phdr->hdr_size ||
|
|
hdr.hdr_lba_start != phdr->hdr_lba_start ||
|
|
hdr.hdr_lba_end != phdr->hdr_lba_end ||
|
|
hdr.hdr_entries != phdr->hdr_entries ||
|
|
hdr.hdr_entsz != phdr->hdr_entsz ||
|
|
hdr.hdr_crc_table != phdr->hdr_crc_table) {
|
|
/* Read the backup GPT table. */
|
|
size = MIN(MAXTBLSZ,
|
|
howmany(phdr->hdr_entries * phdr->hdr_entsz,
|
|
table->sectorsize));
|
|
if (dread(dev, tbl, size, phdr->hdr_lba_table) == 0 &&
|
|
gpt_checktbl(phdr, tbl, size * table->sectorsize,
|
|
table->sectors - 1) == 0) {
|
|
memcpy(&hdr, phdr, sizeof(hdr));
|
|
sec = 1;
|
|
}
|
|
}
|
|
}
|
|
if (pri == 0 && sec == 0) {
|
|
/* Both primary and backup tables are invalid. */
|
|
table->type = PTABLE_NONE;
|
|
goto out;
|
|
}
|
|
DEBUG("GPT detected");
|
|
size = MIN(hdr.hdr_entries * hdr.hdr_entsz,
|
|
MAXTBLSZ * table->sectorsize);
|
|
|
|
/*
|
|
* If the disk's sector count is smaller than the sector count recorded
|
|
* in the disk's GPT table header, set the table->sectors to the value
|
|
* recorded in GPT tables. This is done to work around buggy firmware
|
|
* that returns truncated disk sizes.
|
|
*
|
|
* Note, this is still not a foolproof way to get disk's size. For
|
|
* example, an image file can be truncated when copied to smaller media.
|
|
*/
|
|
if (hdr.hdr_lba_alt + 1 > table->sectors)
|
|
table->sectors = hdr.hdr_lba_alt + 1;
|
|
|
|
for (i = 0; i < size / hdr.hdr_entsz; i++) {
|
|
ent = (struct gpt_ent *)(tbl + i * hdr.hdr_entsz);
|
|
if (uuid_equal(&ent->ent_type, &gpt_uuid_unused, NULL))
|
|
continue;
|
|
|
|
/* Simple sanity checks. */
|
|
if (ent->ent_lba_start < hdr.hdr_lba_start ||
|
|
ent->ent_lba_end > hdr.hdr_lba_end ||
|
|
ent->ent_lba_start > ent->ent_lba_end)
|
|
continue;
|
|
|
|
entry = malloc(sizeof(*entry));
|
|
if (entry == NULL)
|
|
break;
|
|
entry->part.start = ent->ent_lba_start;
|
|
entry->part.end = ent->ent_lba_end;
|
|
entry->part.index = i + 1;
|
|
entry->part.type = gpt_parttype(ent->ent_type);
|
|
entry->flags = le64toh(ent->ent_attr);
|
|
memcpy(&entry->type.gpt, &ent->ent_type, sizeof(uuid_t));
|
|
STAILQ_INSERT_TAIL(&table->entries, entry, entry);
|
|
DEBUG("new GPT partition added");
|
|
}
|
|
out:
|
|
free(buf);
|
|
free(tbl);
|
|
return (table);
|
|
}
|
|
#endif /* LOADER_GPT_SUPPORT */
|
|
|
|
#ifdef LOADER_MBR_SUPPORT
|
|
/* We do not need to support too many EBR partitions in the loader */
|
|
#define MAXEBRENTRIES 8
|
|
static enum partition_type
|
|
mbr_parttype(uint8_t type)
|
|
{
|
|
|
|
switch (type) {
|
|
case DOSPTYP_386BSD:
|
|
return (PART_FREEBSD);
|
|
case DOSPTYP_LINSWP:
|
|
return (PART_LINUX_SWAP);
|
|
case DOSPTYP_LINUX:
|
|
return (PART_LINUX);
|
|
case 0x01:
|
|
case 0x04:
|
|
case 0x06:
|
|
case 0x07:
|
|
case 0x0b:
|
|
case 0x0c:
|
|
case 0x0e:
|
|
return (PART_DOS);
|
|
}
|
|
return (PART_UNKNOWN);
|
|
}
|
|
|
|
static struct ptable*
|
|
ptable_ebrread(struct ptable *table, void *dev, diskread_t dread)
|
|
{
|
|
struct dos_partition *dp;
|
|
struct pentry *e1, *entry;
|
|
uint32_t start, end, offset;
|
|
u_char *buf;
|
|
int i, index;
|
|
|
|
STAILQ_FOREACH(e1, &table->entries, entry) {
|
|
if (e1->type.mbr == DOSPTYP_EXT ||
|
|
e1->type.mbr == DOSPTYP_EXTLBA)
|
|
break;
|
|
}
|
|
if (e1 == NULL)
|
|
return (table);
|
|
index = 5;
|
|
offset = e1->part.start;
|
|
buf = malloc(table->sectorsize);
|
|
if (buf == NULL)
|
|
return (table);
|
|
DEBUG("EBR detected");
|
|
for (i = 0; i < MAXEBRENTRIES; i++) {
|
|
#if 0 /* Some BIOSes return an incorrect number of sectors */
|
|
if (offset >= table->sectors)
|
|
break;
|
|
#endif
|
|
if (dread(dev, buf, 1, offset) != 0)
|
|
break;
|
|
dp = (struct dos_partition *)(buf + DOSPARTOFF);
|
|
if (dp[0].dp_typ == 0)
|
|
break;
|
|
start = le32toh(dp[0].dp_start);
|
|
if (dp[0].dp_typ == DOSPTYP_EXT &&
|
|
dp[1].dp_typ == 0) {
|
|
offset = e1->part.start + start;
|
|
continue;
|
|
}
|
|
end = le32toh(dp[0].dp_size);
|
|
entry = malloc(sizeof(*entry));
|
|
if (entry == NULL)
|
|
break;
|
|
entry->part.start = offset + start;
|
|
entry->part.end = entry->part.start + end - 1;
|
|
entry->part.index = index++;
|
|
entry->part.type = mbr_parttype(dp[0].dp_typ);
|
|
entry->flags = dp[0].dp_flag;
|
|
entry->type.mbr = dp[0].dp_typ;
|
|
STAILQ_INSERT_TAIL(&table->entries, entry, entry);
|
|
DEBUG("new EBR partition added");
|
|
if (dp[1].dp_typ == 0)
|
|
break;
|
|
offset = e1->part.start + le32toh(dp[1].dp_start);
|
|
}
|
|
free(buf);
|
|
return (table);
|
|
}
|
|
#endif /* LOADER_MBR_SUPPORT */
|
|
|
|
static enum partition_type
|
|
bsd_parttype(uint8_t type)
|
|
{
|
|
|
|
switch (type) {
|
|
case FS_NANDFS:
|
|
return (PART_FREEBSD_NANDFS);
|
|
case FS_SWAP:
|
|
return (PART_FREEBSD_SWAP);
|
|
case FS_BSDFFS:
|
|
return (PART_FREEBSD_UFS);
|
|
case FS_VINUM:
|
|
return (PART_FREEBSD_VINUM);
|
|
case FS_ZFS:
|
|
return (PART_FREEBSD_ZFS);
|
|
}
|
|
return (PART_UNKNOWN);
|
|
}
|
|
|
|
static struct ptable*
|
|
ptable_bsdread(struct ptable *table, void *dev, diskread_t dread)
|
|
{
|
|
struct disklabel *dl;
|
|
struct partition *part;
|
|
struct pentry *entry;
|
|
u_char *buf;
|
|
uint32_t raw_offset;
|
|
int i;
|
|
|
|
if (table->sectorsize < sizeof(struct disklabel)) {
|
|
DEBUG("Too small sectorsize");
|
|
return (table);
|
|
}
|
|
buf = malloc(table->sectorsize);
|
|
if (buf == NULL)
|
|
return (table);
|
|
if (dread(dev, buf, 1, 1) != 0) {
|
|
DEBUG("read failed");
|
|
ptable_close(table);
|
|
table = NULL;
|
|
goto out;
|
|
}
|
|
dl = (struct disklabel *)buf;
|
|
if (le32toh(dl->d_magic) != DISKMAGIC &&
|
|
le32toh(dl->d_magic2) != DISKMAGIC)
|
|
goto out;
|
|
if (le32toh(dl->d_secsize) != table->sectorsize) {
|
|
DEBUG("unsupported sector size");
|
|
goto out;
|
|
}
|
|
dl->d_npartitions = le16toh(dl->d_npartitions);
|
|
if (dl->d_npartitions > 20 || dl->d_npartitions < 8) {
|
|
DEBUG("invalid number of partitions");
|
|
goto out;
|
|
}
|
|
DEBUG("BSD detected");
|
|
part = &dl->d_partitions[0];
|
|
raw_offset = le32toh(part[RAW_PART].p_offset);
|
|
for (i = 0; i < dl->d_npartitions; i++, part++) {
|
|
if (i == RAW_PART)
|
|
continue;
|
|
if (part->p_size == 0)
|
|
continue;
|
|
entry = malloc(sizeof(*entry));
|
|
if (entry == NULL)
|
|
break;
|
|
entry->part.start = le32toh(part->p_offset) - raw_offset;
|
|
entry->part.end = entry->part.start +
|
|
le32toh(part->p_size) + 1;
|
|
entry->part.type = bsd_parttype(part->p_fstype);
|
|
entry->part.index = i; /* starts from zero */
|
|
entry->type.bsd = part->p_fstype;
|
|
STAILQ_INSERT_TAIL(&table->entries, entry, entry);
|
|
DEBUG("new BSD partition added");
|
|
}
|
|
table->type = PTABLE_BSD;
|
|
out:
|
|
free(buf);
|
|
return (table);
|
|
}
|
|
|
|
#ifdef LOADER_VTOC8_SUPPORT
|
|
static enum partition_type
|
|
vtoc8_parttype(uint16_t type)
|
|
{
|
|
|
|
switch (type) {
|
|
case VTOC_TAG_FREEBSD_NANDFS:
|
|
return (PART_FREEBSD_NANDFS);
|
|
case VTOC_TAG_FREEBSD_SWAP:
|
|
return (PART_FREEBSD_SWAP);
|
|
case VTOC_TAG_FREEBSD_UFS:
|
|
return (PART_FREEBSD_UFS);
|
|
case VTOC_TAG_FREEBSD_VINUM:
|
|
return (PART_FREEBSD_VINUM);
|
|
case VTOC_TAG_FREEBSD_ZFS:
|
|
return (PART_FREEBSD_ZFS);
|
|
}
|
|
return (PART_UNKNOWN);
|
|
}
|
|
|
|
static struct ptable*
|
|
ptable_vtoc8read(struct ptable *table, void *dev, diskread_t dread)
|
|
{
|
|
struct pentry *entry;
|
|
struct vtoc8 *dl;
|
|
u_char *buf;
|
|
uint16_t sum, heads, sectors;
|
|
int i;
|
|
|
|
if (table->sectorsize != sizeof(struct vtoc8))
|
|
return (table);
|
|
buf = malloc(table->sectorsize);
|
|
if (buf == NULL)
|
|
return (table);
|
|
if (dread(dev, buf, 1, 0) != 0) {
|
|
DEBUG("read failed");
|
|
ptable_close(table);
|
|
table = NULL;
|
|
goto out;
|
|
}
|
|
dl = (struct vtoc8 *)buf;
|
|
/* Check the sum */
|
|
for (i = sum = 0; i < sizeof(struct vtoc8); i += sizeof(sum))
|
|
sum ^= be16dec(buf + i);
|
|
if (sum != 0) {
|
|
DEBUG("incorrect checksum");
|
|
goto out;
|
|
}
|
|
if (be16toh(dl->nparts) != VTOC8_NPARTS) {
|
|
DEBUG("invalid number of entries");
|
|
goto out;
|
|
}
|
|
sectors = be16toh(dl->nsecs);
|
|
heads = be16toh(dl->nheads);
|
|
if (sectors * heads == 0) {
|
|
DEBUG("invalid geometry");
|
|
goto out;
|
|
}
|
|
DEBUG("VTOC8 detected");
|
|
for (i = 0; i < VTOC8_NPARTS; i++) {
|
|
dl->part[i].tag = be16toh(dl->part[i].tag);
|
|
if (i == VTOC_RAW_PART ||
|
|
dl->part[i].tag == VTOC_TAG_UNASSIGNED)
|
|
continue;
|
|
entry = malloc(sizeof(*entry));
|
|
if (entry == NULL)
|
|
break;
|
|
entry->part.start = be32toh(dl->map[i].cyl) * heads * sectors;
|
|
entry->part.end = be32toh(dl->map[i].nblks) +
|
|
entry->part.start - 1;
|
|
entry->part.type = vtoc8_parttype(dl->part[i].tag);
|
|
entry->part.index = i; /* starts from zero */
|
|
entry->type.vtoc8 = dl->part[i].tag;
|
|
STAILQ_INSERT_TAIL(&table->entries, entry, entry);
|
|
DEBUG("new VTOC8 partition added");
|
|
}
|
|
table->type = PTABLE_VTOC8;
|
|
out:
|
|
free(buf);
|
|
return (table);
|
|
|
|
}
|
|
#endif /* LOADER_VTOC8_SUPPORT */
|
|
|
|
struct ptable*
|
|
ptable_open(void *dev, uint64_t sectors, uint16_t sectorsize,
|
|
diskread_t *dread)
|
|
{
|
|
struct dos_partition *dp;
|
|
struct ptable *table;
|
|
u_char *buf;
|
|
int i, count;
|
|
#ifdef LOADER_MBR_SUPPORT
|
|
struct pentry *entry;
|
|
uint32_t start, end;
|
|
int has_ext;
|
|
#endif
|
|
table = NULL;
|
|
buf = malloc(sectorsize);
|
|
if (buf == NULL)
|
|
return (NULL);
|
|
/* First, read the MBR. */
|
|
if (dread(dev, buf, 1, DOSBBSECTOR) != 0) {
|
|
DEBUG("read failed");
|
|
goto out;
|
|
}
|
|
|
|
table = malloc(sizeof(*table));
|
|
if (table == NULL)
|
|
goto out;
|
|
table->sectors = sectors;
|
|
table->sectorsize = sectorsize;
|
|
table->type = PTABLE_NONE;
|
|
STAILQ_INIT(&table->entries);
|
|
|
|
#ifdef LOADER_VTOC8_SUPPORT
|
|
if (be16dec(buf + offsetof(struct vtoc8, magic)) == VTOC_MAGIC) {
|
|
if (ptable_vtoc8read(table, dev, dread) == NULL) {
|
|
/* Read error. */
|
|
table = NULL;
|
|
goto out;
|
|
} else if (table->type == PTABLE_VTOC8)
|
|
goto out;
|
|
}
|
|
#endif
|
|
/* Check the BSD label. */
|
|
if (ptable_bsdread(table, dev, dread) == NULL) { /* Read error. */
|
|
table = NULL;
|
|
goto out;
|
|
} else if (table->type == PTABLE_BSD)
|
|
goto out;
|
|
|
|
#if defined(LOADER_GPT_SUPPORT) || defined(LOADER_MBR_SUPPORT)
|
|
/* Check the MBR magic. */
|
|
if (buf[DOSMAGICOFFSET] != 0x55 ||
|
|
buf[DOSMAGICOFFSET + 1] != 0xaa) {
|
|
DEBUG("magic sequence not found");
|
|
#if defined(LOADER_GPT_SUPPORT)
|
|
/* There is no PMBR, check that we have backup GPT */
|
|
table->type = PTABLE_GPT;
|
|
table = ptable_gptread(table, dev, dread);
|
|
#endif
|
|
goto out;
|
|
}
|
|
/* Check that we have PMBR. Also do some validation. */
|
|
dp = (struct dos_partition *)(buf + DOSPARTOFF);
|
|
for (i = 0, count = 0; i < NDOSPART; i++) {
|
|
if (dp[i].dp_flag != 0 && dp[i].dp_flag != 0x80) {
|
|
DEBUG("invalid partition flag %x", dp[i].dp_flag);
|
|
goto out;
|
|
}
|
|
#ifdef LOADER_GPT_SUPPORT
|
|
if (dp[i].dp_typ == DOSPTYP_PMBR) {
|
|
table->type = PTABLE_GPT;
|
|
DEBUG("PMBR detected");
|
|
}
|
|
#endif
|
|
if (dp[i].dp_typ != 0)
|
|
count++;
|
|
}
|
|
/* Do we have some invalid values? */
|
|
if (table->type == PTABLE_GPT && count > 1) {
|
|
if (dp[1].dp_typ != DOSPTYP_HFS) {
|
|
table->type = PTABLE_NONE;
|
|
DEBUG("Incorrect PMBR, ignore it");
|
|
} else
|
|
DEBUG("Bootcamp detected");
|
|
}
|
|
#ifdef LOADER_GPT_SUPPORT
|
|
if (table->type == PTABLE_GPT) {
|
|
table = ptable_gptread(table, dev, dread);
|
|
goto out;
|
|
}
|
|
#endif
|
|
#ifdef LOADER_MBR_SUPPORT
|
|
/* Read MBR. */
|
|
DEBUG("MBR detected");
|
|
table->type = PTABLE_MBR;
|
|
for (i = has_ext = 0; i < NDOSPART; i++) {
|
|
if (dp[i].dp_typ == 0)
|
|
continue;
|
|
start = le32dec(&(dp[i].dp_start));
|
|
end = le32dec(&(dp[i].dp_size));
|
|
if (start == 0 || end == 0)
|
|
continue;
|
|
#if 0 /* Some BIOSes return an incorrect number of sectors */
|
|
if (start + end - 1 >= sectors)
|
|
continue; /* XXX: ignore */
|
|
#endif
|
|
if (dp[i].dp_typ == DOSPTYP_EXT ||
|
|
dp[i].dp_typ == DOSPTYP_EXTLBA)
|
|
has_ext = 1;
|
|
entry = malloc(sizeof(*entry));
|
|
if (entry == NULL)
|
|
break;
|
|
entry->part.start = start;
|
|
entry->part.end = start + end - 1;
|
|
entry->part.index = i + 1;
|
|
entry->part.type = mbr_parttype(dp[i].dp_typ);
|
|
entry->flags = dp[i].dp_flag;
|
|
entry->type.mbr = dp[i].dp_typ;
|
|
STAILQ_INSERT_TAIL(&table->entries, entry, entry);
|
|
DEBUG("new MBR partition added");
|
|
}
|
|
if (has_ext) {
|
|
table = ptable_ebrread(table, dev, dread);
|
|
/* FALLTHROUGH */
|
|
}
|
|
#endif /* LOADER_MBR_SUPPORT */
|
|
#endif /* LOADER_MBR_SUPPORT || LOADER_GPT_SUPPORT */
|
|
out:
|
|
free(buf);
|
|
return (table);
|
|
}
|
|
|
|
void
|
|
ptable_close(struct ptable *table)
|
|
{
|
|
struct pentry *entry;
|
|
|
|
while (!STAILQ_EMPTY(&table->entries)) {
|
|
entry = STAILQ_FIRST(&table->entries);
|
|
STAILQ_REMOVE_HEAD(&table->entries, entry);
|
|
free(entry);
|
|
}
|
|
free(table);
|
|
}
|
|
|
|
enum ptable_type
|
|
ptable_gettype(const struct ptable *table)
|
|
{
|
|
|
|
return (table->type);
|
|
}
|
|
|
|
int
|
|
ptable_getsize(const struct ptable *table, uint64_t *sizep)
|
|
{
|
|
uint64_t tmp = table->sectors * table->sectorsize;
|
|
|
|
if (tmp < table->sectors)
|
|
return (EOVERFLOW);
|
|
|
|
if (sizep != NULL)
|
|
*sizep = tmp;
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
ptable_getpart(const struct ptable *table, struct ptable_entry *part, int index)
|
|
{
|
|
struct pentry *entry;
|
|
|
|
if (part == NULL || table == NULL)
|
|
return (EINVAL);
|
|
|
|
STAILQ_FOREACH(entry, &table->entries, entry) {
|
|
if (entry->part.index != index)
|
|
continue;
|
|
memcpy(part, &entry->part, sizeof(*part));
|
|
return (0);
|
|
}
|
|
return (ENOENT);
|
|
}
|
|
|
|
/*
|
|
* Search for a slice with the following preferences:
|
|
*
|
|
* 1: Active FreeBSD slice
|
|
* 2: Non-active FreeBSD slice
|
|
* 3: Active Linux slice
|
|
* 4: non-active Linux slice
|
|
* 5: Active FAT/FAT32 slice
|
|
* 6: non-active FAT/FAT32 slice
|
|
*/
|
|
#define PREF_RAWDISK 0
|
|
#define PREF_FBSD_ACT 1
|
|
#define PREF_FBSD 2
|
|
#define PREF_LINUX_ACT 3
|
|
#define PREF_LINUX 4
|
|
#define PREF_DOS_ACT 5
|
|
#define PREF_DOS 6
|
|
#define PREF_NONE 7
|
|
int
|
|
ptable_getbestpart(const struct ptable *table, struct ptable_entry *part)
|
|
{
|
|
struct pentry *entry, *best;
|
|
int pref, preflevel;
|
|
|
|
if (part == NULL || table == NULL)
|
|
return (EINVAL);
|
|
|
|
best = NULL;
|
|
preflevel = pref = PREF_NONE;
|
|
STAILQ_FOREACH(entry, &table->entries, entry) {
|
|
#ifdef LOADER_MBR_SUPPORT
|
|
if (table->type == PTABLE_MBR) {
|
|
switch (entry->type.mbr) {
|
|
case DOSPTYP_386BSD:
|
|
pref = entry->flags & 0x80 ? PREF_FBSD_ACT:
|
|
PREF_FBSD;
|
|
break;
|
|
case DOSPTYP_LINUX:
|
|
pref = entry->flags & 0x80 ? PREF_LINUX_ACT:
|
|
PREF_LINUX;
|
|
break;
|
|
case 0x01: /* DOS/Windows */
|
|
case 0x04:
|
|
case 0x06:
|
|
case 0x0c:
|
|
case 0x0e:
|
|
case DOSPTYP_FAT32:
|
|
pref = entry->flags & 0x80 ? PREF_DOS_ACT:
|
|
PREF_DOS;
|
|
break;
|
|
default:
|
|
pref = PREF_NONE;
|
|
}
|
|
}
|
|
#endif /* LOADER_MBR_SUPPORT */
|
|
#ifdef LOADER_GPT_SUPPORT
|
|
if (table->type == PTABLE_GPT) {
|
|
if (entry->part.type == PART_DOS)
|
|
pref = PREF_DOS;
|
|
else if (entry->part.type == PART_FREEBSD_UFS ||
|
|
entry->part.type == PART_FREEBSD_ZFS)
|
|
pref = PREF_FBSD;
|
|
else
|
|
pref = PREF_NONE;
|
|
}
|
|
#endif /* LOADER_GPT_SUPPORT */
|
|
if (pref < preflevel) {
|
|
preflevel = pref;
|
|
best = entry;
|
|
}
|
|
}
|
|
if (best != NULL) {
|
|
memcpy(part, &best->part, sizeof(*part));
|
|
return (0);
|
|
}
|
|
return (ENOENT);
|
|
}
|
|
|
|
int
|
|
ptable_iterate(const struct ptable *table, void *arg, ptable_iterate_t *iter)
|
|
{
|
|
struct pentry *entry;
|
|
char name[32];
|
|
int ret = 0;
|
|
|
|
name[0] = '\0';
|
|
STAILQ_FOREACH(entry, &table->entries, entry) {
|
|
#ifdef LOADER_MBR_SUPPORT
|
|
if (table->type == PTABLE_MBR)
|
|
sprintf(name, "s%d", entry->part.index);
|
|
else
|
|
#endif
|
|
#ifdef LOADER_GPT_SUPPORT
|
|
if (table->type == PTABLE_GPT)
|
|
sprintf(name, "p%d", entry->part.index);
|
|
else
|
|
#endif
|
|
#ifdef LOADER_VTOC8_SUPPORT
|
|
if (table->type == PTABLE_VTOC8)
|
|
sprintf(name, "%c", (u_char) 'a' +
|
|
entry->part.index);
|
|
else
|
|
#endif
|
|
if (table->type == PTABLE_BSD)
|
|
sprintf(name, "%c", (u_char) 'a' +
|
|
entry->part.index);
|
|
if ((ret = iter(arg, name, &entry->part)) != 0)
|
|
return (ret);
|
|
}
|
|
return (ret);
|
|
}
|