rdwr_efi.c revision e088753c56867594fc7d2221ad9ac39a9298883d
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2012 Nexenta Systems, Inc. All rights reserved.
* Copyright 2014 Toomas Soome <tsoome@me.com>
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <strings.h>
#include <unistd.h>
#include <libintl.h>
#include <sys/efi_partition.h>
#include <sys/byteorder.h>
static struct uuid_to_ptag {
} conversion_array[] = {
{ EFI_UNUSED },
{ EFI_BOOT },
{ EFI_ROOT },
{ EFI_SWAP },
{ EFI_USR },
{ EFI_BACKUP },
{ 0 }, /* STAND is never used */
{ EFI_VAR },
{ EFI_HOME },
{ EFI_ALTSCTR },
{ 0 }, /* CACHE (cachefs) is never used */
{ EFI_RESERVED },
{ EFI_SYSTEM },
{ EFI_LEGACY_MBR },
{ EFI_SYMC_PUB },
{ EFI_SYMC_CDS },
{ EFI_MSFT_RESV },
{ EFI_DELL_BASIC },
{ EFI_DELL_RAID },
{ EFI_DELL_SWAP },
{ EFI_DELL_LVM },
{ EFI_DELL_RESV },
{ EFI_AAPL_HFS },
{ EFI_AAPL_UFS },
{ EFI_BIOS_BOOT },
{ EFI_FREEBSD_BOOT },
{ EFI_FREEBSD_SWAP },
{ EFI_FREEBSD_UFS },
{ EFI_FREEBSD_VINUM },
{ EFI_FREEBSD_ZFS }
};
/*
* Default vtoc information for non-SVr4 partitions
*/
{ V_ROOT, 0 }, /* a - 0 */
{ V_UNASSIGNED, 0 }, /* d - 3 */
{ V_UNASSIGNED, 0 }, /* e - 4 */
{ V_UNASSIGNED, 0 }, /* f - 5 */
{ V_USR, 0 }, /* g - 6 */
{ V_UNASSIGNED, 0 }, /* h - 7 */
#if defined(_SUNOS_VTOC_16)
{ V_ALTSCTR, 0 }, /* j - 9 */
#else
#endif /* defined(i386) */
{ V_UNASSIGNED, 0 }, /* k - 10 */
{ V_UNASSIGNED, 0 }, /* l - 11 */
{ V_UNASSIGNED, 0 }, /* m - 12 */
{ V_UNASSIGNED, 0 }, /* n - 13 */
{ V_UNASSIGNED, 0 }, /* o - 14 */
{ V_UNASSIGNED, 0 }, /* p - 15 */
#endif /* defined(_SUNOS_VTOC_16) */
};
#ifdef DEBUG
int efi_debug = 1;
#else
int efi_debug = 0;
#endif
extern unsigned int efi_crc32(const unsigned char *, unsigned int);
static int
{
return (errno);
return (0);
}
/*
* the number of blocks the EFI label takes up (round up to nearest
* block)
*/
((l) - 1)) / (l)))
/* number of partitions -- limited by what we can malloc */
sizeof (struct dk_part))
int
{
if (efi_debug)
"couldn't read disk information\n");
return (-1);
}
/* 16K plus one block for the GPT */
}
if (efi_debug) {
"the maximum number of partitions supported is %lu\n",
}
return (-1);
}
return (-1);
/*
* add one block here for the PMBR; on disks with a 512 byte
* block size and 128 or fewer partitions, efi_first_u_lba
* should work out to "34"
*/
return (0);
}
/*
* Read EFI - return partition number upon success.
*/
int
{
int rval;
int length;
/* figure out the number of entries that would fit into 16K */
return (VT_ERROR);
void *tmp;
(int) sizeof (struct dk_part) *
return (VT_ERROR);
} else {
}
}
if (rval < 0) {
if (efi_debug) {
"read of EFI table failed, rval=%d\n", rval);
}
}
return (rval);
}
static int
{
int error;
return (error);
}
static int
{
switch (errno) {
case EIO:
return (VT_EIO);
default:
return (VT_ERROR);
}
}
if (efi_debug)
"Bad EFI signature: 0x%llx != 0x%llx\n",
(long long)efi->efi_gpt_Signature,
(long long)LE_64(EFI_SIGNATURE));
return (VT_EINVAL);
}
/*
* check CRC of the header; the size of the header should
* never be larger than one block
*/
efi->efi_gpt_HeaderCRC32 = 0;
if (efi_debug)
"Bad EFI CRC: 0x%x != 0x%x\n",
crc,
sizeof (struct efi_gpt))));
return (VT_EINVAL);
}
return (0);
}
static int
{
int i, j;
int label_len;
int rval = 0;
int md_flag = 0;
int vdc_flag = 0;
/*
* get the partition number for this file descriptor.
*/
if (efi_debug) {
}
switch (errno) {
case EIO:
return (VT_EIO);
case EINVAL:
return (VT_EINVAL);
default:
return (VT_ERROR);
}
}
md_flag++;
/*
* The controller and drive name "vdc" (virtual disk client)
* indicates a LDoms virtual disk.
*/
vdc_flag++;
}
/* get the LBA size */
if (efi_debug) {
"assuming LBA 512 bytes %d\n",
errno);
}
}
if (disk_info.dki_lbsize == 0) {
if (efi_debug) {
"efi_read: assuming LBA 512 bytes\n");
}
}
/*
* Read the EFI GPT to figure out how many partitions we need
* to deal with.
*/
} else {
/* pad to physical sector size */
}
}
return (VT_ERROR);
if (md_flag) {
switch (errno) {
case EIO:
return (VT_EIO);
default:
return (VT_ERROR);
}
}
/*
* No valid label here; try the alternate. Note that here
* we just read GPT header and save it into dk_ioc.data,
* Later, we will read GUID partition entry array if we
* can get valid GPT header.
*/
/*
* This is a workaround for legacy systems. In the past, the
* last sector of SCSI disk was invisible on x86 platform. At
* that time, backup label was saved on the next to the last
* sector. It is possible for users to move a disk from previous
* solaris system to present system. Here, we attempt to search
* legacy backup EFI label first.
*/
/*
* we didn't find legacy backup EFI label, try to
* search backup EFI label in the last block.
*/
if (rval == 0) {
if (efi_debug)
"efi_read: primary label corrupt; "
"using EFI backup label located on"
" the last block\n");
}
} else {
" corrupt; using legacy EFI backup label "
" located on the next to last block\n");
}
if (rval == 0) {
vtoc->efi_nparts =
/*
* Partition tables are between backup GPT header
* table and ParitionEntryLBA (the starting LBA of
* the GUID partition entries array). Now that we
* already got valid GPT header and saved it in
* dk_ioc.dki_data, we try to get GUID partition
* entry array here.
*/
/* LINTED */
+ disk_info.dki_lbsize);
if (legacy_label)
else
if (dk_ioc.dki_length >
} else {
/*
* read GUID partition entry array
*/
}
}
} else if (rval == 0) {
/* LINTED */
+ disk_info.dki_lbsize);
/*
* When the device is a LDoms virtual disk, the DKIOCGETEFI
* ioctl can fail with EINVAL if the virtual disk backend
* is a ZFS volume serviced by a domain running an old version
* of Solaris. This is because the DKIOCGETEFI ioctl was
* initially incorrectly implemented for a ZFS volume and it
* expected the GPT and GPE to be retrieved with a single ioctl.
* So we try to read the GPT and the GPE using that old style
* ioctl.
*/
}
if (rval < 0) {
return (rval);
}
/* LINTED -- always longlong aligned */
/*
* Assemble this into a "dk_gpt" struct for easier
* digestibility by applications.
*/
/*
* If the array the user passed in is too small, set the length
* to what it needs to be and return
*/
return (VT_EINVAL);
}
for (i = 0; i < vtoc->efi_nparts; i++) {
for (j = 0;
j < sizeof (conversion_array)
/ sizeof (struct uuid_to_ptag); j++) {
&conversion_array[j].uuid,
sizeof (struct uuid)) == 0) {
break;
}
}
continue;
for (j = 0; j < EFI_PART_NAME_LEN; j++) {
efi_parts[i].efi_gpe_PartitionName[j]);
}
}
return (dki_info.dki_partition);
}
/* writes a "protective" MBR */
static int
{
int len;
/*
* Preserve any boot code and disk signature if the first block is
* already an MBR.
*/
/* LINTED -- always longlong aligned */
} else {
}
}
/* bootable or not */
*cp++ = 0;
/* beginning CHS; 0xffffff if not representable */
*cp++ = 0xff;
*cp++ = 0xff;
*cp++ = 0xff;
/* OS type */
/* ending CHS; 0xffffff if not representable */
*cp++ = 0xff;
*cp++ = 0xff;
*cp++ = 0xff;
/* starting LBA: 1 (little endian format) by EFI definition */
*cp++ = 0x01;
*cp++ = 0x00;
*cp++ = 0x00;
*cp++ = 0x00;
/* ending LBA: last block on the disk (little endian format) */
if (size_in_lba < 0xffffffff) {
} else {
*cp++ = 0xff;
*cp++ = 0xff;
*cp++ = 0xff;
*cp++ = 0xff;
}
/* LINTED -- always longlong aligned */
switch (errno) {
case EIO:
return (VT_EIO);
case EINVAL:
return (VT_EINVAL);
default:
return (VT_ERROR);
}
}
return (0);
}
/* make sure the user specified something reasonable */
static int
{
int resv_part = -1;
int i, j;
/*
* Sanity-check the input (make sure no partitions overlap)
*/
for (i = 0; i < vtoc->efi_nparts; i++) {
/* It can't be unassigned and have an actual size */
if (efi_debug) {
"partition %d is \"unassigned\" but has a size of %llu",
i,
}
return (VT_EINVAL);
}
continue;
/* we have encountered an unknown uuid */
}
if (resv_part != -1) {
if (efi_debug) {
"found duplicate reserved partition at %d\n",
i);
}
return (VT_EINVAL);
}
resv_part = i;
}
if (efi_debug) {
"Partition %d starts at %llu. ",
i,
"It must be between %llu and %llu.\n",
}
return (VT_EINVAL);
}
vtoc->efi_first_u_lba) ||
if (efi_debug) {
"Partition %d ends at %llu. ",
i,
"It must be between %llu and %llu.\n",
}
return (VT_EINVAL);
}
for (j = 0; j < vtoc->efi_nparts; j++) {
if (efi_debug) {
"Partition %d overlaps partition %d.",
i, j);
}
return (VT_EINVAL);
}
}
}
}
/* just a warning for now */
"no reserved partition found\n");
}
return (0);
}
/*
* add all the unallocated space to the current label
*/
int
efi_use_whole_disk(int fd)
{
int rval;
int i;
uint_t phy_last_slice = 0;
diskaddr_t pl_start = 0;
if (rval < 0) {
return (rval);
}
/* find the last physically non-zero partition */
phy_last_slice = i;
}
}
/*
* If alter_lba is 1, we are using the backup label.
* Since we can locate the backup label by disk capacity,
* there must be no unallocated space.
*/
>= efi_label->efi_last_lba)) {
if (efi_debug) {
"efi_use_whole_disk: requested space not found\n");
}
return (VT_ENOSPC);
}
/*
* If there is space between the last physically non-zero partition
* and the reserved partition, just add the unallocated space to this
* area. Otherwise, the unallocated space is added to the last
* physically non-zero partition.
*/
}
/*
* Move the reserved partition. There is currently no data in
* here except fabricated devids (which get generated via
* efi_write()). So there is no need to copy data.
*/
if (rval < 0) {
if (efi_debug) {
"efi_use_whole_disk:fail to write label, rval=%d\n",
rval);
}
return (rval);
}
return (0);
}
/*
* write EFI label and backup label
*/
int
{
int i, j;
int md_flag = 0;
int nblocks;
if (efi_debug)
switch (errno) {
case EIO:
return (VT_EIO);
case EINVAL:
return (VT_EINVAL);
default:
return (VT_ERROR);
}
}
/* check if we are dealing wih a metadevice */
md_flag = 1;
}
if (check_input(vtoc)) {
/*
* not valid; if it's a metadevice just pass it down
* because SVM will do its own checking
*/
if (md_flag == 0) {
return (VT_EINVAL);
}
}
} else {
vtoc->efi_lbasize) *
}
/*
* the number of blocks occupied by GUID partition entry array
*/
/*
* Backup GPT header is located on the block after GUID
* partition entry array. Here, we calculate the address
* for backup GPT header.
*/
return (VT_ERROR);
/* stuff user's input into EFI struct */
efi->efi_gpt_Reserved1 = 0;
/* LINTED -- always longlong aligned */
for (i = 0; i < vtoc->efi_nparts; i++) {
for (j = 0;
j < sizeof (conversion_array) /
sizeof (struct uuid_to_ptag); j++) {
conversion_array[j].uuid);
break;
}
}
if (j == sizeof (conversion_array) /
sizeof (struct uuid_to_ptag)) {
/*
* If we didn't have a matching uuid match, bail here.
* Don't write a label with unknown uuid.
*/
if (efi_debug) {
"Unknown uuid for p_tag %d\n",
}
return (VT_EINVAL);
}
for (j = 0; j < EFI_PART_NAME_LEN; j++) {
efi_parts[i].efi_gpe_PartitionName[j] =
}
(void) uuid_generate((uchar_t *)
}
sizeof (uuid_t));
}
switch (errno) {
case EIO:
return (VT_EIO);
case EINVAL:
return (VT_EINVAL);
default:
return (VT_ERROR);
}
}
/* if it's a metadevice we're done */
if (md_flag) {
return (0);
}
/* write backup partition array */
/* LINTED */
vtoc->efi_lbasize);
/*
* we wrote the primary label okay, so don't fail
*/
if (efi_debug) {
"write of backup partitions to block %llu "
"failed, errno %d\n",
errno);
}
}
/*
* now swap MyLBA and AlternateLBA fields and write backup
* partition table header
*/
/* LINTED */
vtoc->efi_lbasize);
efi->efi_gpt_HeaderCRC32 = 0;
sizeof (struct efi_gpt)));
if (efi_debug) {
"write of backup header to block %llu failed, "
"errno %d\n",
errno);
}
}
/* write the PMBR */
return (0);
}
void
{
}
/*
* Input: File descriptor
* Output: 1 if disk has an EFI label, or > 2TB with no VTOC or legacy MBR.
* Otherwise 0.
*/
int
{
return (1);
return (1);
}
}
return (0);
}
void
{
int resv_part = -1;
int i, j;
int overlap = 0;
/*
* make sure no partitions overlap
*/
for (i = 0; i < vtoc->efi_nparts; i++) {
/* It can't be unassigned and have an actual size */
"partition %d is \"unassigned\" but has a size "
}
continue;
}
if (resv_part != -1) {
"found duplicate reserved partition at "
"%d\n", i);
}
resv_part = i;
"Warning: reserved partition size must "
"be %d sectors\n", EFI_MIN_RESV_SIZE);
}
"Partition %d starts at %llu\n",
i,
"It must be between %llu and %llu.\n",
}
vtoc->efi_first_u_lba) ||
"Partition %d ends at %llu\n",
i,
"It must be between %llu and %llu.\n",
}
for (j = 0; j < vtoc->efi_nparts; j++) {
if (!overlap) {
"label error: EFI Labels do not "
"support overlapping partitions\n");
}
"Partition %d overlaps partition "
"%d.\n", i, j);
overlap = 1;
}
}
}
}
/* make sure there is a reserved partition */
if (resv_part == -1) {
"no reserved partition found\n");
}
}
/*
* We need to get information necessary to construct a *new* efi
* label type
*/
int
{
int i;
/*
* Now build the default partition table
*/
if (efi_debug) {
}
return (-1);
}
}
/*
* Make constants first
* and variable partitions later
*/
/* root partition - s0 128 MB */
/* partition - s1 128 MB */
/* partition -s2 is NOT the Backup disk */
/* partition -s6 /usr partition - HOG */
- (1024 * 16);
/* efi reserved partition - s9 16K */
return (0);
}