mirror of
https://github.com/freebsd/freebsd-src
synced 2024-09-06 17:18:32 +00:00
![Marius Strobl](/assets/img/avatar_default.png)
The removal of the sparc64 support in February 2020 obsoleted the VTOC8 partitioning scheme as no other FreeBSD platform makes use of it. Moreover, the code is bitrotting as nothing defines e. g. LOADER_VTOC8_SUPPORT any more and, thus, should go now, too. With this change, the following commits are reverted as far as VTOC8 is concerned and parts haven't already previously been deleted along with prior sparc64 removals:094fcb157d
a7d366e958
ba8d50d08b
The alignment exampled9711c28ef
added to the VTOC8 section of gpart.8 is folded into the MBR one. This should finally conclude the deorbit of sparc64-specific bits. We had joy, we had fun we ran Unix on a Sun. But that source and the song of FreeBSD have all gone. Credits to Michael Bueker for the original "Unix on a Sun" and Rod McKuen for the "Seasons in the Sun" lyrics.
846 lines
22 KiB
C
846 lines
22 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 <fs/cd9660/iso.h>
|
|
|
|
#include <zlib.h>
|
|
#include <part.h>
|
|
#include <uuid.h>
|
|
|
|
#ifdef PART_DEBUG
|
|
#define DPRINTF(fmt, args...) printf("%s: " fmt "\n", __func__, ## args)
|
|
#else
|
|
#define DPRINTF(fmt, args...) ((void)0)
|
|
#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_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;
|
|
static const uuid_t gpt_uuid_apple_apfs = GPT_ENT_TYPE_APPLE_APFS;
|
|
#endif
|
|
|
|
struct pentry {
|
|
struct ptable_entry part;
|
|
uint64_t flags;
|
|
union {
|
|
uint8_t bsd;
|
|
uint8_t mbr;
|
|
uuid_t gpt;
|
|
} 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_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" },
|
|
{ PART_APFS, "APFS" },
|
|
};
|
|
|
|
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, NULL))
|
|
return (PART_FREEBSD);
|
|
else if (uuid_equal(&type, &gpt_uuid_apple_apfs, NULL))
|
|
return (PART_APFS);
|
|
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) {
|
|
DPRINTF("no GPT signature");
|
|
return (NULL);
|
|
}
|
|
sz = le32toh(hdr->hdr_size);
|
|
if (sz < 92 || sz > sectorsize) {
|
|
DPRINTF("invalid GPT header size: %d", sz);
|
|
return (NULL);
|
|
}
|
|
crc = le32toh(hdr->hdr_crc_self);
|
|
hdr->hdr_crc_self = crc32(0, Z_NULL, 0);
|
|
if (crc32(hdr->hdr_crc_self, (const Bytef *)hdr, sz) != crc) {
|
|
DPRINTF("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) {
|
|
DPRINTF("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) {
|
|
DPRINTF("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) {
|
|
DPRINTF("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) {
|
|
DPRINTF("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(0, tbl, hdr->hdr_entries * hdr->hdr_entsz)) {
|
|
DPRINTF("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;
|
|
}
|
|
DPRINTF("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.
|
|
*/
|
|
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);
|
|
DPRINTF("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);
|
|
DPRINTF("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);
|
|
DPRINTF("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_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)) {
|
|
DPRINTF("Too small sectorsize");
|
|
return (table);
|
|
}
|
|
buf = malloc(table->sectorsize);
|
|
if (buf == NULL)
|
|
return (table);
|
|
if (dread(dev, buf, 1, 1) != 0) {
|
|
DPRINTF("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) {
|
|
DPRINTF("unsupported sector size");
|
|
goto out;
|
|
}
|
|
dl->d_npartitions = le16toh(dl->d_npartitions);
|
|
if (dl->d_npartitions > 20 || dl->d_npartitions < 8) {
|
|
DPRINTF("invalid number of partitions");
|
|
goto out;
|
|
}
|
|
DPRINTF("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);
|
|
DPRINTF("new BSD partition added");
|
|
}
|
|
table->type = PTABLE_BSD;
|
|
out:
|
|
free(buf);
|
|
return (table);
|
|
}
|
|
|
|
#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) {
|
|
DPRINTF("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;
|
|
#ifdef LOADER_MBR_SUPPORT
|
|
struct pentry *entry;
|
|
uint32_t start, end;
|
|
int has_ext;
|
|
#endif
|
|
table = NULL;
|
|
dp = NULL;
|
|
buf = malloc(sectorsize);
|
|
if (buf == NULL)
|
|
return (NULL);
|
|
/* First, read the MBR. */
|
|
if (dread(dev, buf, 1, DOSBBSECTOR) != 0) {
|
|
DPRINTF("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) {
|
|
/* Read error. */
|
|
table = NULL;
|
|
goto out;
|
|
} else if (table->type == PTABLE_ISO9660)
|
|
goto out;
|
|
|
|
/* 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) {
|
|
DPRINTF("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 = malloc(NDOSPART * sizeof(struct dos_partition));
|
|
if (dp == NULL)
|
|
goto out;
|
|
bcopy(buf + DOSPARTOFF, dp, NDOSPART * sizeof(struct dos_partition));
|
|
|
|
/*
|
|
* In mac we can have PMBR partition in hybrid MBR;
|
|
* that is, MBR partition which has DOSPTYP_PMBR entry defined as
|
|
* start sector 1. After DOSPTYP_PMBR, there may be other partitions.
|
|
* UEFI compliant PMBR has no other partitions.
|
|
*/
|
|
for (int i = 0; i < NDOSPART; i++) {
|
|
if (dp[i].dp_flag != 0 && dp[i].dp_flag != 0x80) {
|
|
DPRINTF("invalid partition flag %x", dp[i].dp_flag);
|
|
goto out;
|
|
}
|
|
#ifdef LOADER_GPT_SUPPORT
|
|
if (dp[i].dp_typ == DOSPTYP_PMBR && dp[i].dp_start == 1) {
|
|
table->type = PTABLE_GPT;
|
|
DPRINTF("PMBR detected");
|
|
}
|
|
#endif
|
|
}
|
|
#ifdef LOADER_GPT_SUPPORT
|
|
if (table->type == PTABLE_GPT) {
|
|
table = ptable_gptread(table, dev, dread);
|
|
goto out;
|
|
}
|
|
#endif
|
|
#ifdef LOADER_MBR_SUPPORT
|
|
/* Read MBR. */
|
|
DPRINTF("MBR detected");
|
|
table->type = PTABLE_MBR;
|
|
for (int 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);
|
|
DPRINTF("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(dp);
|
|
free(buf);
|
|
return (table);
|
|
}
|
|
|
|
void
|
|
ptable_close(struct ptable *table)
|
|
{
|
|
struct pentry *entry;
|
|
|
|
if (table == NULL)
|
|
return;
|
|
|
|
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
|
|
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);
|
|
}
|