int efi_alloc_and_init(int fd, uint32_t nparts, struct dk_gpt **vtoc) { diskaddr_t capacity; uint_t lbsize; uint_t nblocks; size_t length; struct dk_gpt *vptr; struct uuid uuid; if (read_disk_info(fd, &capacity, &lbsize) != 0) { if (efi_debug) (void) fprintf(stderr, "couldn't read disk information\n"); return (-1); } nblocks = NBLOCKS(nparts, lbsize); if ((nblocks * lbsize) < EFI_MIN_ARRAY_SIZE + lbsize) { /* 16K plus one block for the GPT */ nblocks = EFI_MIN_ARRAY_SIZE / lbsize + 1; } if (nparts > MAX_PARTS) { if (efi_debug) { (void) fprintf(stderr, "the maximum number of partitions supported is %lu\n", MAX_PARTS); } return (-1); } length = sizeof (struct dk_gpt) + sizeof (struct dk_part) * (nparts - 1); if ((*vtoc = calloc(length, 1)) == NULL) return (-1); vptr = *vtoc; vptr->efi_version = EFI_VERSION_CURRENT; vptr->efi_lbasize = lbsize; vptr->efi_nparts = nparts; /* * 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" */ vptr->efi_first_u_lba = nblocks + 1; vptr->efi_last_lba = capacity - 1; vptr->efi_altern_lba = capacity -1; vptr->efi_last_u_lba = vptr->efi_last_lba - nblocks; (void) uuid_generate((uchar_t *)&uuid); UUID_LE_CONVERT(vptr->efi_disk_uguid, uuid); return (0); }
static int efi_read(int fd, struct dk_gpt *vtoc) { int i, j; int label_len; int rval = 0; int md_flag = 0; int vdc_flag = 0; diskaddr_t capacity = 0; uint_t lbsize = 0; struct dk_minfo disk_info; dk_efi_t dk_ioc; efi_gpt_t *efi; efi_gpe_t *efi_parts; struct dk_cinfo dki_info; uint32_t user_length; boolean_t legacy_label = B_FALSE; /* * get the partition number for this file descriptor. */ if ((rval = efi_get_info(fd, &dki_info)) != 0) return rval; if ((strncmp(dki_info.dki_cname, "pseudo", 7) == 0) && (strncmp(dki_info.dki_dname, "md", 3) == 0)) { md_flag++; } else if ((strncmp(dki_info.dki_cname, "vdc", 4) == 0) && (strncmp(dki_info.dki_dname, "vdc", 4) == 0)) { /* * The controller and drive name "vdc" (virtual disk client) * indicates a LDoms virtual disk. */ vdc_flag++; } /* get the LBA size */ if (read_disk_info(fd, &capacity, &lbsize) == -1) { if (efi_debug) { (void) fprintf(stderr, "unable to read disk info: %d", errno); } return (VT_EINVAL); } disk_info.dki_lbsize = lbsize; disk_info.dki_capacity = capacity; if (disk_info.dki_lbsize == 0) { if (efi_debug) { (void) fprintf(stderr, "efi_read: assuming LBA 512 bytes\n"); } disk_info.dki_lbsize = DEV_BSIZE; } /* * Read the EFI GPT to figure out how many partitions we need * to deal with. */ dk_ioc.dki_lba = 1; if (NBLOCKS(vtoc->efi_nparts, disk_info.dki_lbsize) < 34) { label_len = EFI_MIN_ARRAY_SIZE + disk_info.dki_lbsize; } else { label_len = vtoc->efi_nparts * (int) sizeof (efi_gpe_t) + disk_info.dki_lbsize; if (label_len % disk_info.dki_lbsize) { /* pad to physical sector size */ label_len += disk_info.dki_lbsize; label_len &= ~(disk_info.dki_lbsize - 1); } } if (posix_memalign((void **)&dk_ioc.dki_data, disk_info.dki_lbsize, label_len)) return (VT_ERROR); memset(dk_ioc.dki_data, 0, label_len); dk_ioc.dki_length = disk_info.dki_lbsize; user_length = vtoc->efi_nparts; efi = dk_ioc.dki_data; if (md_flag) { dk_ioc.dki_length = label_len; if (efi_ioctl(fd, DKIOCGETEFI, &dk_ioc) == -1) { switch (errno) { case EIO: return (VT_EIO); default: return (VT_ERROR); } } } else if ((rval = check_label(fd, &dk_ioc)) == VT_EINVAL) { /* * 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. */ dk_ioc.dki_lba = disk_info.dki_capacity - 2; dk_ioc.dki_length = disk_info.dki_lbsize; rval = check_label(fd, &dk_ioc); if (rval == VT_EINVAL) { /* * we didn't find legacy backup EFI label, try to * search backup EFI label in the last block. */ dk_ioc.dki_lba = disk_info.dki_capacity - 1; dk_ioc.dki_length = disk_info.dki_lbsize; rval = check_label(fd, &dk_ioc); if (rval == 0) { legacy_label = B_TRUE; if (efi_debug) (void) fprintf(stderr, "efi_read: primary label corrupt; " "using EFI backup label located on" " the last block\n"); } } else { if ((efi_debug) && (rval == 0)) (void) fprintf(stderr, "efi_read: primary label" " corrupt; using legacy EFI backup label " " located on the next to last block\n"); } if (rval == 0) { dk_ioc.dki_lba = LE_64(efi->efi_gpt_PartitionEntryLBA); vtoc->efi_flags |= EFI_GPT_PRIMARY_CORRUPT; vtoc->efi_nparts = LE_32(efi->efi_gpt_NumberOfPartitionEntries); /* * 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 */ dk_ioc.dki_data = (efi_gpt_t *)((char *)dk_ioc.dki_data + disk_info.dki_lbsize); if (legacy_label) dk_ioc.dki_length = disk_info.dki_capacity - 1 - dk_ioc.dki_lba; else dk_ioc.dki_length = disk_info.dki_capacity - 2 - dk_ioc.dki_lba; dk_ioc.dki_length *= disk_info.dki_lbsize; if (dk_ioc.dki_length > ((len_t)label_len - sizeof (*dk_ioc.dki_data))) { rval = VT_EINVAL; } else { /* * read GUID partition entry array */ rval = efi_ioctl(fd, DKIOCGETEFI, &dk_ioc); } } } else if (rval == 0) { dk_ioc.dki_lba = LE_64(efi->efi_gpt_PartitionEntryLBA); /* LINTED */ dk_ioc.dki_data = (efi_gpt_t *)((char *)dk_ioc.dki_data + disk_info.dki_lbsize); dk_ioc.dki_length = label_len - disk_info.dki_lbsize; rval = efi_ioctl(fd, DKIOCGETEFI, &dk_ioc); } else if (vdc_flag && rval == VT_ERROR && errno == EINVAL) { /* * 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. */ dk_ioc.dki_lba = 1; dk_ioc.dki_length = label_len; rval = check_label(fd, &dk_ioc); } if (rval < 0) { free(efi); return (rval); } /* LINTED -- always longlong aligned */ efi_parts = (efi_gpe_t *)(((char *)efi) + disk_info.dki_lbsize); /* * Assemble this into a "dk_gpt" struct for easier * digestibility by applications. */ vtoc->efi_version = LE_32(efi->efi_gpt_Revision); vtoc->efi_nparts = LE_32(efi->efi_gpt_NumberOfPartitionEntries); vtoc->efi_part_size = LE_32(efi->efi_gpt_SizeOfPartitionEntry); vtoc->efi_lbasize = disk_info.dki_lbsize; vtoc->efi_last_lba = disk_info.dki_capacity - 1; vtoc->efi_first_u_lba = LE_64(efi->efi_gpt_FirstUsableLBA); vtoc->efi_last_u_lba = LE_64(efi->efi_gpt_LastUsableLBA); vtoc->efi_altern_lba = LE_64(efi->efi_gpt_AlternateLBA); UUID_LE_CONVERT(vtoc->efi_disk_uguid, efi->efi_gpt_DiskGUID); /* * If the array the user passed in is too small, set the length * to what it needs to be and return */ if (user_length < vtoc->efi_nparts) { return (VT_EINVAL); } for (i = 0; i < vtoc->efi_nparts; i++) { UUID_LE_CONVERT(vtoc->efi_parts[i].p_guid, efi_parts[i].efi_gpe_PartitionTypeGUID); for (j = 0; j < sizeof (conversion_array) / sizeof (struct uuid_to_ptag); j++) { if (bcmp(&vtoc->efi_parts[i].p_guid, &conversion_array[j].uuid, sizeof (struct uuid)) == 0) { vtoc->efi_parts[i].p_tag = j; break; } } if (vtoc->efi_parts[i].p_tag == V_UNASSIGNED) continue; vtoc->efi_parts[i].p_flag = LE_16(efi_parts[i].efi_gpe_Attributes.PartitionAttrs); vtoc->efi_parts[i].p_start = LE_64(efi_parts[i].efi_gpe_StartingLBA); vtoc->efi_parts[i].p_size = LE_64(efi_parts[i].efi_gpe_EndingLBA) - vtoc->efi_parts[i].p_start + 1; for (j = 0; j < EFI_PART_NAME_LEN; j++) { vtoc->efi_parts[i].p_name[j] = (uchar_t)LE_16( efi_parts[i].efi_gpe_PartitionName[j]); } UUID_LE_CONVERT(vtoc->efi_parts[i].p_uguid, efi_parts[i].efi_gpe_UniquePartitionGUID); } free(efi); return (dki_info.dki_partition); }
static int efi_ioctl(int fd, int cmd, dk_efi_t *dk_ioc) { void *data = dk_ioc->dki_data; int error; #if defined(__linux__) || defined(__APPLE__) diskaddr_t capacity; uint_t lbsize; /* * When the IO is not being performed in kernel as an ioctl we need * to know the sector size so we can seek to the proper byte offset. */ if (read_disk_info(fd, &capacity, &lbsize) == -1) { if (efi_debug) fprintf(stderr,"unable to read disk info: %d",errno); errno = EIO; return -1; } switch (cmd) { case DKIOCGETEFI: if (lbsize == 0) { if (efi_debug) (void) fprintf(stderr, "DKIOCGETEFI assuming " "LBA %d bytes\n", DEV_BSIZE); lbsize = DEV_BSIZE; } error = lseek(fd, dk_ioc->dki_lba * lbsize, SEEK_SET); if (error == -1) { if (efi_debug) (void) fprintf(stderr, "DKIOCGETEFI lseek " "error: %d\n", errno); return error; } error = read(fd, data, dk_ioc->dki_length); if (error == -1) { if (efi_debug) (void) fprintf(stderr, "DKIOCGETEFI read " "error: %d\n", errno); return error; } if (error != dk_ioc->dki_length) { if (efi_debug) (void) fprintf(stderr, "DKIOCGETEFI short " "read of %d bytes\n", error); errno = EIO; return -1; } error = 0; break; case DKIOCSETEFI: if (lbsize == 0) { if (efi_debug) (void) fprintf(stderr, "DKIOCSETEFI unknown " "LBA size\n"); errno = EIO; return -1; } error = lseek(fd, dk_ioc->dki_lba * lbsize, SEEK_SET); if (error == -1) { if (efi_debug) (void) fprintf(stderr, "DKIOCSETEFI lseek " "error: %d\n", errno); return error; } error = write(fd, data, dk_ioc->dki_length); if (error == -1) { if (efi_debug) (void) fprintf(stderr, "DKIOCSETEFI write " "error: %d\n", errno); return error; } if (error != dk_ioc->dki_length) { if (efi_debug) (void) fprintf(stderr, "DKIOCSETEFI short " "write of %d bytes\n", error); errno = EIO; return -1; } /* Sync the new EFI table to disk */ error = fsync(fd); if (error == -1) return error; /* Ensure any local disk cache is also flushed */ #if defined (__linux__) if (ioctl(fd, BLKFLSBUF, 0) == -1) return error; #endif error = 0; break; default: if (efi_debug) (void) fprintf(stderr, "unsupported ioctl()\n"); errno = EIO; return -1; } #else dk_ioc->dki_data_64 = (uint64_t)(uintptr_t)data; error = ioctl(fd, cmd, (void *)dk_ioc); dk_ioc->dki_data = data; #endif return (error); }