diff --git a/lib/libefi/rdwr_efi_windows.c b/lib/libefi/rdwr_efi_windows.c index fe379c28e92c..be8ac28a3cab 100644 --- a/lib/libefi/rdwr_efi_windows.c +++ b/lib/libefi/rdwr_efi_windows.c @@ -445,7 +445,7 @@ efi_alloc_and_init(int fd, uint32_t nparts, struct dk_gpt **vtoc) * Read EFI - return partition number upon success. */ int -efi_alloc_and_read(int fd, struct dk_gpt **vtoc) +efi_alloc_and_read_flags(int fd, struct dk_gpt **vtoc, uint_t flags) { int rval; uint32_t nparts; @@ -459,6 +459,7 @@ efi_alloc_and_read(int fd, struct dk_gpt **vtoc) return (VT_ERROR); (*vtoc)->efi_nparts = nparts; + (*vtoc)->efi_flags = flags; rval = efi_read(fd, *vtoc); if ((rval == VT_EINVAL) && (*vtoc)->efi_nparts > nparts) { @@ -489,6 +490,28 @@ efi_alloc_and_read(int fd, struct dk_gpt **vtoc) return (rval); } +int +efi_alloc_and_read(int fd, struct dk_gpt **vtoc) +{ + return (efi_alloc_and_read_flags(fd, vtoc, 0)); +} + +static uint32_t +quick_crc32(const void *data, size_t len) +{ + const uint8_t *p = (const uint8_t *)data; + uint32_t crc = 0xFFFFFFFF; + + while (len--) { + crc ^= *p++; + for (int k = 0; k < 8; k++) { + crc = (crc >> 1) ^ (0xEDB88320 & -(crc & 1)); + } + } + + return (~crc); +} + static int efi_ioctl(int fd, int cmd, dk_efi_t *dk_ioc) { @@ -773,7 +796,8 @@ efi_read(int fd, struct dk_gpt *vtoc) return (VT_ERROR); } } - } else if ((rval = check_label(fd, &dk_ioc)) == VT_EINVAL) { + } else if ((rval = check_label(fd, &dk_ioc)) == VT_EINVAL || + (vtoc->efi_flags & EFI_GPT_PRIMARY_SKIP)) { /* * No valid label here; try the alternate. Note that here * we just read GPT header and save it into dk_ioc.data, @@ -1216,6 +1240,209 @@ efi_use_whole_disk(int fd) return (0); } +static int +verify_label(int fd, uint_t lbsize, uint32_t original_checksum) +{ + int error; + unsigned char *data; + uint32_t chksum_sector_1; + + data = (unsigned char *) malloc(lbsize); + if (!data) { + fprintf(stderr, "%s: unable to allocate memory\n", __func__); + return (VT_ERROR); + } + + error = lseek(fd, 1 * lbsize, SEEK_SET); + error = read(fd, data, lbsize); + + chksum_sector_1 = quick_crc32(data, lbsize); + + free(data); + + if (chksum_sector_1 != original_checksum) { + fprintf(stderr, "%s: checksum mismatch\r\n", __func__); + fprintf(stderr, +"It appears something in Windows overwrote the OpenZFS partition.\r\n" +"Manual intervention might be required.\r\n"); + return (VT_EINVAL); + } + + return (0); +} + +/* + * write EFI label and backup label using Windows API + * Currently not used, but perhaps one day. + */ +int +efi_writeX(int fd, struct dk_gpt *vtoc) +{ + dk_efi_t dk_ioc; + efi_gpt_t *efi; + efi_gpe_t *efi_parts; + int i, j; + struct dk_cinfo dki_info; + int rval; + int md_flag = 0; + int nblocks; + diskaddr_t lba_backup_gpt_hdr; + DRIVE_LAYOUT_INFORMATION_EX *dl = NULL; + size_t layoutSize; + UINT32 nparts; + DWORD bytesReturned; + BOOL ok; + + if ((rval = efi_get_info(fd, &dki_info)) != 0) + return (rval); + + /* check if we are dealing wih a metadevice */ + if ((strncmp(dki_info.dki_cname, "pseudo", 7) == 0) && + (strncmp(dki_info.dki_dname, "md", 3) == 0)) { + md_flag = 1; + } +#if 0 + 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); + } + } +#endif + dk_ioc.dki_lba = 1; + if (NBLOCKS(vtoc->efi_nparts, vtoc->efi_lbasize) < 34) { + dk_ioc.dki_length = EFI_MIN_ARRAY_SIZE + vtoc->efi_lbasize; + } else { + dk_ioc.dki_length = (len_t)NBLOCKS(vtoc->efi_nparts, + vtoc->efi_lbasize) * + vtoc->efi_lbasize; + } + + /* + * the number of blocks occupied by GUID partition entry array + */ + nblocks = dk_ioc.dki_length / vtoc->efi_lbasize - 1; + + /* 2) Number of GPT entries in the in-core vtoc */ + nparts = vtoc->efi_nparts; + + /* 3) Allocate DRIVE_LAYOUT_INFORMATION_EX header + nparts entries */ + layoutSize = sizeof (DRIVE_LAYOUT_INFORMATION_EX) + + nparts * sizeof (PARTITION_INFORMATION_EX); + + dl = calloc(1, layoutSize); + if (!dl) { + fprintf(stderr, "efi_write: out of memory\n"); + return (-1); + } + + dl->PartitionStyle = PARTITION_STYLE_GPT; + +#if 0 + // If PartitionCount is 0, it wipes the label + ok = DeviceIoControl( + fd, + IOCTL_DISK_SET_DRIVE_LAYOUT_EX, + dl, + (DWORD)layoutSize, + NULL, 0, + &bytesReturned, + NULL); + + fprintf(stderr, "Wiping label said %d : %d\r\n", ok, bytesReturned); +#endif + + /* 4) Fill in the GPT header fields */ + dl->PartitionStyle = PARTITION_STYLE_GPT; + dl->PartitionCount = nparts; + dl->Gpt.MaxPartitionCount = nparts; + + dl->Gpt.StartingUsableOffset.QuadPart = + (LONGLONG)vtoc->efi_first_u_lba * vtoc->efi_lbasize; + dl->Gpt.UsableLength.QuadPart = + (LONGLONG)(vtoc->efi_last_u_lba - vtoc->efi_first_u_lba + 1) + * vtoc->efi_lbasize; + + memcpy(&dl->Gpt.DiskId, + &vtoc->efi_disk_uguid, + sizeof (dl->Gpt.DiskId)); + /* Note: HeaderLength, etc., are filled by disk.sys */ + + /* 5) Copy each dk_part into the PARTITION_INFORMATION_EX array */ + for (i = 0; i < nparts; i++) { + struct dk_part *kp = &vtoc->efi_parts[i]; + PARTITION_INFORMATION_EX *pi = &dl->PartitionEntry[i]; + + for (j = 0; + j < sizeof (conversion_array) / + sizeof (struct uuid_to_ptag); j++) { + + if (kp->p_tag == j) { + struct uuid *dest = &pi->Gpt.PartitionType; + UUID_LE_CONVERT( + *dest, + conversion_array[j].uuid); + break; + } + } + + pi->StartingOffset.QuadPart = + (LONGLONG)kp->p_start * vtoc->efi_lbasize; + pi->PartitionLength.QuadPart = (LONGLONG)(kp->p_size) + * vtoc->efi_lbasize; + + if (kp->p_tag != V_UNASSIGNED && + uuid_is_null((uchar_t *)&kp->p_uguid)) { + (void) uuid_generate((uchar_t *)&kp->p_uguid); + } + + + pi->PartitionStyle = PARTITION_STYLE_GPT; + pi->PartitionNumber = i + 1; + pi->RewritePartition = TRUE; + + memcpy(&pi->Gpt.PartitionId, &kp->p_uguid, sizeof (GUID)); + pi->Gpt.Attributes = kp->p_flag; + + ZeroMemory(pi->Gpt.Name, sizeof (pi->Gpt.Name)); + // Convert up to 35 characters (leaving room for NUL) + MultiByteToWideChar( + CP_UTF8, // source is UTF-8 + 0, + kp->p_name, -1, // input and NUL + pi->Gpt.Name, // output buffer + sizeof (pi->Gpt.Name) / sizeof (WCHAR)); + + + } + + /* 6) Atomically commit MBR + GPT + entries + backup via Disk.sys */ + ok = DeviceIoControl( + fd, + IOCTL_DISK_SET_DRIVE_LAYOUT_EX, + dl, + (DWORD)layoutSize, + NULL, 0, + &bytesReturned, + NULL); + + free(dl); + + if (!ok) { + DWORD err = GetLastError(); + fprintf(stderr, + "efi_write: IOCTL_DISK_SET_DRIVE_LAYOUT_EX failed: %lu\n", + err); + return (-1); + } + + /* Success: protective MBR, primary and backup GPT written */ + return (0); +} + /* * write EFI label and backup label @@ -1256,7 +1483,7 @@ efi_write(int fd, struct dk_gpt *vtoc) if (NBLOCKS(vtoc->efi_nparts, vtoc->efi_lbasize) < 34) { dk_ioc.dki_length = EFI_MIN_ARRAY_SIZE + vtoc->efi_lbasize; } else { - dk_ioc.dki_length = NBLOCKS(vtoc->efi_nparts, + dk_ioc.dki_length = (len_t)NBLOCKS(vtoc->efi_nparts, vtoc->efi_lbasize) * vtoc->efi_lbasize; } @@ -1277,6 +1504,10 @@ efi_write(int fd, struct dk_gpt *vtoc) return (VT_ERROR); memset(dk_ioc.dki_data, 0, dk_ioc.dki_length); + DWORD bytesReturned; + + dk_ioc.dki_lba = 1; + efi = dk_ioc.dki_data; /* stuff user's input into EFI struct */ @@ -1343,10 +1574,11 @@ efi_write(int fd, struct dk_gpt *vtoc) (void) uuid_generate((uchar_t *) &vtoc->efi_parts[i].p_uguid); } - bcopy(&vtoc->efi_parts[i].p_uguid, - &efi_parts[i].efi_gpe_UniquePartitionGUID, + memcpy(&efi_parts[i].efi_gpe_UniquePartitionGUID, + &vtoc->efi_parts[i].p_uguid, sizeof (uuid_t)); } + efi->efi_gpt_PartitionEntryArrayCRC32 = LE_32(efi_crc32((unsigned char *)efi_parts, vtoc->efi_nparts * (int)sizeof (struct efi_gpe))); @@ -1354,6 +1586,12 @@ efi_write(int fd, struct dk_gpt *vtoc) LE_32(efi_crc32((unsigned char *)efi, LE_32(efi->efi_gpt_HeaderSize))); + // We will purposely corrupt the Signature now, so that + // Windows will stop looking at the disk. Then fix it + // once we have told Windows we changed it. + // "EFI Part" -> "_FI Part" + efi->efi_gpt_Signature = LE_64(EFI_SIGNATURE); + if (efi_ioctl(fd, DKIOCSETEFI, &dk_ioc) == -1) { posix_memalign_free(dk_ioc.dki_data); switch (errno) { @@ -1371,6 +1609,25 @@ efi_write(int fd, struct dk_gpt *vtoc) return (0); } + + // Tell Windows we have changed partitions + fprintf(stderr, "%s telling Windows about it...\r\n", __func__); + DeviceIoControl( + fd, + IOCTL_DISK_UPDATE_PROPERTIES, + NULL, 0, + NULL, 0, + &bytesReturned, + NULL); + + // Wait a bit + sleep(2); + + + // Remember the chksum of sector 1 + uint32_t chksum_sector_1 = 0; + chksum_sector_1 = quick_crc32(dk_ioc.dki_data, vtoc->efi_lbasize); + /* write backup partition array */ dk_ioc.dki_lba = vtoc->efi_last_u_lba + 1; dk_ioc.dki_length -= vtoc->efi_lbasize; @@ -1416,11 +1673,16 @@ efi_write(int fd, struct dk_gpt *vtoc) errno); } } + /* write the PMBR */ (void) write_pmbr(fd, vtoc); + + // Verify it landed on disk ok + rval = verify_label(fd, vtoc->efi_lbasize, chksum_sector_1); + posix_memalign_free(dk_ioc.dki_data); - return (0); + return (rval); } void @@ -1547,56 +1809,35 @@ efi_err_check(struct dk_gpt *vtoc) } } -/* - * We need to get information necessary to construct a *new* efi - * label type - */ -int -efi_auto_sense(int fd, struct dk_gpt **vtoc) +int repair_vtoc(int fd, struct dk_gpt* vtoc) { - - int i; - - /* - * Now build the default partition table - */ - if (efi_alloc_and_init(fd, EFI_NUMPAR, vtoc) != 0) { - if (efi_debug) { - (void) fprintf(stderr, "efi_alloc_and_init failed.\n"); - } - return (-1); - } - - for (i = 0; i < MIN((*vtoc)->efi_nparts, V_NUMPAR); i++) { - (*vtoc)->efi_parts[i].p_tag = default_vtoc_map[i].p_tag; - (*vtoc)->efi_parts[i].p_flag = default_vtoc_map[i].p_flag; - (*vtoc)->efi_parts[i].p_start = 0; - (*vtoc)->efi_parts[i].p_size = 0; - } - /* - * Make constants first - * and variable partitions later - */ - - /* root partition - s0 128 MB */ - (*vtoc)->efi_parts[0].p_start = 34; - (*vtoc)->efi_parts[0].p_size = 262144; - - /* partition - s1 128 MB */ - (*vtoc)->efi_parts[1].p_start = 262178; - (*vtoc)->efi_parts[1].p_size = 262144; - - /* partition -s2 is NOT the Backup disk */ - (*vtoc)->efi_parts[2].p_tag = V_UNASSIGNED; - - /* partition -s6 /usr partition - HOG */ - (*vtoc)->efi_parts[6].p_start = 524322; - (*vtoc)->efi_parts[6].p_size = (*vtoc)->efi_last_u_lba - 524322 - - (1024 * 16); - - /* efi reserved partition - s9 16K */ - (*vtoc)->efi_parts[8].p_start = (*vtoc)->efi_last_u_lba - (1024 * 16); - (*vtoc)->efi_parts[8].p_size = (1024 * 16); - (*vtoc)->efi_parts[8].p_tag = V_RESERVED; - return (0); + diskaddr_t capacity = 0; + uint_t lbsize = 0; + read_disk_info(fd, &capacity, &lbsize); + + uint64_t disk_last_lba = capacity - 1; + uint64_t nblocks = NBLOCKS(EFI_NUMPAR, lbsize); // always 128 for ZFS fix + + if ((nblocks * lbsize) < EFI_MIN_ARRAY_SIZE + lbsize) { + nblocks = EFI_MIN_ARRAY_SIZE / lbsize + 1; + } + + // ---- PATCH HEADERS ONLY ---- + vtoc->efi_lbasize = lbsize; + vtoc->efi_last_lba = disk_last_lba; + vtoc->efi_altern_lba = disk_last_lba; + vtoc->efi_last_u_lba = disk_last_lba - nblocks; + vtoc->efi_nparts = 128; + + fprintf(stderr, "**[repair_vtoc] capacity (blocks): %llu\n", + (unsigned long long)capacity); + fprintf(stderr, "**[repair_vtoc] lbsize : %u\n", lbsize); + fprintf(stderr, "**[repair_vtoc] disk_last_lba : %llu\n", + (unsigned long long)disk_last_lba); + fprintf(stderr, "**[repair_vtoc] nblocks (parttbl): %llu\n", + (unsigned long long)nblocks); + fprintf(stderr, "**[repair_vtoc] efi_last_u_lba : %llu\n", + (unsigned long long)vtoc->efi_last_u_lba); + fprintf(stderr, "**[repair_vtoc] efi_nparts : %u\n", + vtoc->efi_nparts); } diff --git a/lib/libzutil/os/windows/zutil_import_os.c b/lib/libzutil/os/windows/zutil_import_os.c index 5ccac08d742f..2236e8a0772f 100644 --- a/lib/libzutil/os/windows/zutil_import_os.c +++ b/lib/libzutil/os/windows/zutil_import_os.c @@ -960,74 +960,116 @@ zpool_find_import_blkid(libpc_handle_t *hdl, pthread_mutex_t *lock, // 0x400 EFI partition, s0 as ZFS // 0x8410 "version" "name" "testpool" ZFS label if (disk != INVALID_HANDLE_VALUE) { - fprintf(stderr, "asking libefi to read label\n"); - TraceEvent(TRACE_INFO,"asking libefi to read label"); + + + int primary_num_partitions = 0; + + // Read Primary, and Backup labels + for (int backup = 0; backup <= 1; backup++) { + + fprintf(stderr, "asking libefi to read %s label\n", + backup ? "backup" : "primary"); + fflush(stderr); + int error; + struct dk_gpt *vtoc; + + error = efi_alloc_and_read_flags(HTOI(disk), + &vtoc, + backup ? EFI_GPT_PRIMARY_SKIP : 0); + if (error >= 0) { + fprintf(stderr, + "EFI read OK, max partitions %d\n", + vtoc->efi_nparts); fflush(stderr); - int error; - struct dk_gpt *vtoc; - error = efi_alloc_and_read(disk, &vtoc); - if (error >= 0) { - fprintf(stderr, - "EFI read OK, max partitions %d\n", - vtoc->efi_nparts); - TraceEvent(TRACE_INFO,"EFI read OK, max partitions %d", vtoc->efi_nparts); - fflush(stderr); - for (int i = 0; i < vtoc->efi_nparts; i++) { - if (vtoc->efi_parts[i].p_start == 0 && - vtoc->efi_parts[i].p_size == 0) - continue; + if (!backup) + primary_num_partitions = vtoc->efi_nparts; + + for (int i = 0; i < vtoc->efi_nparts; i++) { + + if (vtoc->efi_parts[i].p_start == 0 && + vtoc->efi_parts[i].p_size == 0) + continue; fprintf(stderr, - " part %d: offset %llx: len %llx: " - "tag: %x name: '%s'\n", + " part %d: offset %llx: len %llx:" + " tag: %x name: '%s'\n", i, vtoc->efi_parts[i].p_start, vtoc->efi_parts[i].p_size, vtoc->efi_parts[i].p_tag, vtoc->efi_parts[i].p_name); - TraceEvent(TRACE_INFO,"part %d: offset %llx: len %llx: " - "tag: %x name: '%s' ", i, vtoc->efi_parts[i].p_start, - vtoc->efi_parts[i].p_size, - vtoc->efi_parts[i].p_tag, - vtoc->efi_parts[i].p_name); fflush(stderr); if (vtoc->efi_parts[i].p_start != 0 && vtoc->efi_parts[i].p_size != 0) { - // Lets invent a naming scheme with start, - // and len in it. + // Lets invent a naming scheme with start, + // and len in it. - slice = zutil_alloc(hdl, - sizeof (rdsk_node_t)); + slice = zutil_alloc(hdl, + sizeof (rdsk_node_t)); - error = asprintf(&slice->rn_name, "#%llu#%llu#%s", - vtoc->efi_parts[i].p_start * vtoc->efi_lbasize, - vtoc->efi_parts[i].p_size * vtoc->efi_lbasize, - deviceInterfaceDetailData->DevicePath); - if (error == -1) { - free(slice); - continue; - } + error = asprintf(&slice->rn_name, + "#%llu#%llu#%s", + vtoc->efi_parts[i].p_start * + vtoc->efi_lbasize, + vtoc->efi_parts[i].p_size * + vtoc->efi_lbasize, + deviceInterfaceDetailData->DevicePath); - slice->rn_vdev_guid = 0; - slice->rn_lock = lock; - slice->rn_avl = *slice_cache; - slice->rn_hdl = hdl; - slice->rn_labelpaths = B_FALSE; - slice->rn_order = IMPORT_ORDER_SCAN_OFFSET + i; + if (error == -1) { + free(slice); + continue; + } - pthread_mutex_lock(lock); - if (avl_find(*slice_cache, slice, &where)) { - free(slice->rn_name); - free(slice); - } else { - avl_insert(*slice_cache, slice, where); - } - pthread_mutex_unlock(lock); - } - } + slice->rn_vdev_guid = 0; + slice->rn_lock = lock; + slice->rn_avl = *slice_cache; + slice->rn_hdl = hdl; + slice->rn_labelpaths = B_TRUE; + slice->rn_order = + IMPORT_ORDER_SCAN_OFFSET + i; + + pthread_mutex_lock(lock); + if (avl_find(*slice_cache, slice, &where)) { + free(slice->rn_name); + free(slice); + } else { + avl_insert(*slice_cache, slice, where); + } + pthread_mutex_unlock(lock); + } // if !empty partition + } // for partitions + + fprintf(stderr, + "backup %d, efi_nparts %u, and primarynum %u\r\n", + backup, vtoc->efi_nparts, primary_num_partitions); + if (backup && vtoc && vtoc->efi_nparts == 9) { + fprintf(stderr, + "Windows corrupted Primary EFI/GPT " + "label detected\r\n"); + TraceEvent(TRACE_INFO, "Windows corrupted Primary EFI/GPT label detected\r\n"); + fflush(stderr); + // vtoc->efi_nparts = 128; + // efi_write(disk, vtoc); + CloseHandle(disk); + int status = restore_primary_gpt_from_backup(vtoc, deviceInterfaceDetailData->DevicePath); + if (status < 0) + { + fprintf(stderr,"Reconstruction of corrupted Primary EFI/GPT label FAILED\r\n"); + TraceEvent(TRACE_INFO, "Reconstruction of corrupted Primary EFI/GPT label FAILED\r\n"); + } + else + { + fprintf(stderr, "Reconstruction of corrupted Primary EFI/GPT label SUCCESSFUL\r\n"); + TraceEvent(TRACE_INFO, "Reconstruction of corrupted Primary EFI/GPT label SUCCESSFUL\r\n"); + } } + efi_free(vtoc); + } // if !error + } // for + CloseHandle(disk); + } else { // Unable to open handle fprintf(stderr, "Unable to open disk, are we Administrator? " @@ -1086,6 +1128,44 @@ zpool_find_import_blkid(libpc_handle_t *hdl, pthread_mutex_t *lock, return (0); } +static int +restore_primary_gpt_from_backup( + struct dk_gpt* vtoc, + const char* path) +{ + vtoc->efi_nparts = EFI_NUMPAR; + int fd, error; + + fprintf(stderr, "%s: trying to offline disk path\r\n", __func__); + TraceEvent(TRACE_INFO, "trying to offline disk path\r\n"); + OfflineDisk(path); + + if ((fd = open(path, O_RDWR | O_DIRECT)) < 0) { + fprintf(stderr, "%s: Failed to open disk path [%s]\r\n", __func__, path); + TraceEvent(TRACE_INFO, "Failed to open disk path %s\r\n", path); + return -1; + } + fprintf(stderr, "%s: disk open successful [%s]\r\n", __func__, path); + repair_vtoc(fd, vtoc); + + int rval = efi_write(fd, vtoc); + (void)fsync(fd); + fprintf(stderr, "%s:rewritting the partition completed, status= %d\r\n",__func__, rval); + TraceEvent(TRACE_INFO, "rewritting the partition completed, status= %d\r\n", rval); + dump_label(fd); + + fprintf(stderr, "%s: trying to online disk\r\n", __func__); + TraceEvent(TRACE_INFO, "trying to online disk\r\n"); + rval = OnlineDisk(path); + if (FAILED(rval)) + { + fprintf(stderr, "%s: failed %d (0x%x)\r\n", __func__, rval, rval); + TraceEvent(TRACE_INFO, "Online disk failed = %d\r\n", rval); + } + + (void)close(fd); + return 0; +} /* * Linux persistent device strings for vdev labels