freebsd-dev/stand/common/part.c
Benno Rice 48990fce8e Add an ISO9660 "partition table" type to loader.
When booted via isoboot(8) loader will be handed a disk that simply contains
an ISO9660 image. Currently this confuses it greatly. Teach it how to spot
that it's in this situation and that ISO9660 has one "partition" covering
the whole disk.

Reviewed by:	imp
Sponsored by:	iXsystems, Inc.
Differential Revision:	https://reviews.freebsd.org/D14915
2018-04-05 19:45:30 +00:00

946 lines
24 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 <fs/cd9660/iso.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" },
{ PART_ISO9660, "ISO9660" },
};
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, uint8_t *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;
uint8_t *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;
uint8_t *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;
uint8_t *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 */
#define cdb2devb(bno) ((bno) * ISO_DEFAULT_BLOCK_SIZE / table->sectorsize)
static struct ptable *
ptable_iso9660read(struct ptable *table, void *dev, diskread_t dread)
{
uint8_t *buf;
struct iso_primary_descriptor *vd;
struct pentry *entry;
buf = malloc(table->sectorsize);
if (buf == NULL)
return (table);
if (dread(dev, buf, 1, cdb2devb(16)) != 0) {
DEBUG("read failed");
ptable_close(table);
table = NULL;
goto out;
}
vd = (struct iso_primary_descriptor *)buf;
if (bcmp(vd->id, ISO_STANDARD_ID, sizeof vd->id) != 0)
goto out;
entry = malloc(sizeof(*entry));
if (entry == NULL)
goto out;
entry->part.start = 0;
entry->part.end = table->sectors;
entry->part.type = PART_ISO9660;
entry->part.index = 0;
STAILQ_INSERT_TAIL(&table->entries, entry, entry);
table->type = PTABLE_ISO9660;
out:
free(buf);
return (table);
}
struct ptable *
ptable_open(void *dev, uint64_t sectors, uint16_t sectorsize,
diskread_t *dread)
{
struct dos_partition *dp;
struct ptable *table;
uint8_t *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);
if (ptable_iso9660read(table, dev, dread) != NULL) {
if (table->type == PTABLE_ISO9660)
goto out;
}
#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", (uint8_t) 'a' +
entry->part.index);
else
#endif
if (table->type == PTABLE_BSD)
sprintf(name, "%c", (uint8_t) 'a' +
entry->part.index);
if ((ret = iter(arg, name, &entry->part)) != 0)
return (ret);
}
return (ret);
}