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; 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 (ioctl(fd, DKIOCINFO, (caddr_t)&dki_info) == -1) { if (efi_debug) { (void) fprintf(stderr, "DKIOCINFO errno 0x%x\n", errno); } switch (errno) { case EIO: return (VT_EIO); case EINVAL: return (VT_EINVAL); default: return (VT_ERROR); } } 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 (ioctl(fd, DKIOCGMEDIAINFO, (caddr_t)&disk_info) == -1) { if (efi_debug) { (void) fprintf(stderr, "assuming LBA 512 bytes %d\n", errno); } disk_info.dki_lbsize = DEV_BSIZE; } 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 ((dk_ioc.dki_data = calloc(label_len, 1)) == NULL) return (VT_ERROR); 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); }
/*ARGSUSED*/ int zvol_ioctl(dev_t dev, int cmd, intptr_t arg, int flag, cred_t *cr, int *rvalp) { zvol_state_t *zv; struct dk_cinfo dkc; struct dk_minfo dkm; dk_efi_t efi; efi_gpt_t gpt; efi_gpe_t gpe; struct uuid uuid = EFI_RESERVED; uint32_t crc; int error = 0; mutex_enter(&zvol_state_lock); zv = ddi_get_soft_state(zvol_state, getminor(dev)); if (zv == NULL) { mutex_exit(&zvol_state_lock); return (ENXIO); } switch (cmd) { case DKIOCINFO: bzero(&dkc, sizeof (dkc)); (void) strcpy(dkc.dki_cname, "zvol"); (void) strcpy(dkc.dki_dname, "zvol"); dkc.dki_ctype = DKC_UNKNOWN; dkc.dki_maxtransfer = 1 << (SPA_MAXBLOCKSHIFT - zv->zv_min_bs); mutex_exit(&zvol_state_lock); if (ddi_copyout(&dkc, (void *)arg, sizeof (dkc), flag)) error = EFAULT; return (error); case DKIOCGMEDIAINFO: bzero(&dkm, sizeof (dkm)); dkm.dki_lbsize = 1U << zv->zv_min_bs; dkm.dki_capacity = zv->zv_volsize >> zv->zv_min_bs; dkm.dki_media_type = DK_UNKNOWN; mutex_exit(&zvol_state_lock); if (ddi_copyout(&dkm, (void *)arg, sizeof (dkm), flag)) error = EFAULT; return (error); case DKIOCGETEFI: if (ddi_copyin((void *)arg, &efi, sizeof (dk_efi_t), flag)) { mutex_exit(&zvol_state_lock); return (EFAULT); } bzero(&gpt, sizeof (gpt)); bzero(&gpe, sizeof (gpe)); efi.dki_data = (void *)(uintptr_t)efi.dki_data_64; if (efi.dki_length < sizeof (gpt) + sizeof (gpe)) { mutex_exit(&zvol_state_lock); return (EINVAL); } efi.dki_length = sizeof (gpt) + sizeof (gpe); gpt.efi_gpt_Signature = LE_64(EFI_SIGNATURE); gpt.efi_gpt_Revision = LE_32(EFI_VERSION_CURRENT); gpt.efi_gpt_HeaderSize = LE_32(sizeof (gpt)); gpt.efi_gpt_FirstUsableLBA = LE_64(0ULL); gpt.efi_gpt_LastUsableLBA = LE_64((zv->zv_volsize >> zv->zv_min_bs) - 1); gpt.efi_gpt_NumberOfPartitionEntries = LE_32(1); gpt.efi_gpt_SizeOfPartitionEntry = LE_32(sizeof (gpe)); UUID_LE_CONVERT(gpe.efi_gpe_PartitionTypeGUID, uuid); gpe.efi_gpe_StartingLBA = gpt.efi_gpt_FirstUsableLBA; gpe.efi_gpe_EndingLBA = gpt.efi_gpt_LastUsableLBA; CRC32(crc, &gpe, sizeof (gpe), -1U, crc32_table); gpt.efi_gpt_PartitionEntryArrayCRC32 = LE_32(~crc); CRC32(crc, &gpt, sizeof (gpt), -1U, crc32_table); gpt.efi_gpt_HeaderCRC32 = LE_32(~crc); mutex_exit(&zvol_state_lock); if (ddi_copyout(&gpt, efi.dki_data, sizeof (gpt), flag) || ddi_copyout(&gpe, efi.dki_data + 1, sizeof (gpe), flag)) error = EFAULT; return (error); default: error = ENOTSUP; break; } mutex_exit(&zvol_state_lock); return (error); }
static int efi_read(int fd, struct dk_gpt *vtoc) { int i, j; int label_len; int rval = 0; int md_flag = 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; /* * get the partition number for this file descriptor. */ if (ioctl(fd, DKIOCINFO, (caddr_t)&dki_info) == -1) { if (efi_debug) (void) fprintf(stderr, "DKIOCINFO errno 0x%x\n", errno); switch (errno) { case EIO: return (VT_EIO); case EINVAL: return (VT_EINVAL); default: return (VT_ERROR); } } if ((strncmp(dki_info.dki_cname, "pseudo", 7) == 0) && (strncmp(dki_info.dki_dname, "md", 3) == 0)) { md_flag++; } /* get the LBA size */ if (ioctl(fd, DKIOCGMEDIAINFO, (caddr_t)&disk_info) == -1) { if (efi_debug) { (void) fprintf(stderr, "assuming LBA 512 bytes %d\n", errno); } disk_info.dki_lbsize = DEV_BSIZE; } 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 ((dk_ioc.dki_data = calloc(label_len, 1)) == NULL) return (VT_ERROR); dk_ioc.dki_length = label_len; user_length = vtoc->efi_nparts; efi = dk_ioc.dki_data; if (md_flag) { 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 */ 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) { /* * 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. */ dk_ioc.dki_lba = disk_info.dki_capacity - 2; dk_ioc.dki_length = disk_info.dki_lbsize; rval = check_label(fd, &dk_ioc); if (efi_debug && (rval == 0)) { (void) fprintf(stderr, "efi_read: primary label corrupt; " "using legacy EFI backup label\n"); } } if (rval == 0) { if (efi_debug) { (void) fprintf(stderr, "efi_read: primary label corrupt; " "using backup\n"); } 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); /* * partitions are between last usable LBA and * backup partition header */ dk_ioc.dki_data++; dk_ioc.dki_length = disk_info.dki_capacity - dk_ioc.dki_lba - 1; dk_ioc.dki_length *= disk_info.dki_lbsize; if (dk_ioc.dki_length > (len_t)label_len) { rval = VT_EINVAL; } else { rval = efi_ioctl(fd, DKIOCGETEFI, &dk_ioc); } } } if (rval < 0) { free(efi); return (rval); } /* partitions start in the next block */ /* 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); 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); }