/* * 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 * or http://www.opensolaris.org/os/licensing. * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libfdisk.h" #define DEFAULT_PATH_PREFIX "/dev/rdsk/" static void fdisk_free_ld_nodes(ext_part_t *epp); static void fdisk_ext_place_in_sorted_list(ext_part_t *epp, logical_drive_t *newld); static void fdisk_ext_remove_from_sorted_list(ext_part_t *epp, logical_drive_t *delld); static int fdisk_ext_overlapping_parts(ext_part_t *epp, uint32_t begsec, uint32_t endsec); static int fdisk_read_extpart(ext_part_t *epp); static void fdisk_set_CHS_values(ext_part_t *epp, struct ipart *part); static int fdisk_init_master_part_table(ext_part_t *epp); static struct ipart *fdisk_alloc_part_table(); static int fdisk_read_master_part_table(ext_part_t *epp); static int fdisk_init_disk_geom(ext_part_t *epp) { struct dk_geom disk_geom; struct dk_minfo disk_info; int no_virtgeom_ioctl = 0, no_physgeom_ioctl = 0; /* Get disk's HBA (virtual) geometry */ errno = 0; if (ioctl(epp->dev_fd, DKIOCG_VIRTGEOM, &disk_geom)) { if (errno == ENOTTY) { no_virtgeom_ioctl = 1; } else if (errno == EINVAL) { /* * This means that the ioctl exists, but * is invalid for this disk, meaning the * disk doesn't have an HBA geometry * (like, say, it's larger than 8GB). */ epp->disk_geom.virt_cyl = epp->disk_geom.virt_heads = epp->disk_geom.virt_sec = 0; } else { return (FDISK_ENOVGEOM); } } else { /* save virtual geometry values obtained by ioctl */ epp->disk_geom.virt_cyl = disk_geom.dkg_ncyl; epp->disk_geom.virt_heads = disk_geom.dkg_nhead; epp->disk_geom.virt_sec = disk_geom.dkg_nsect; } errno = 0; if (ioctl(epp->dev_fd, DKIOCG_PHYGEOM, &disk_geom)) { if (errno == ENOTTY) { no_physgeom_ioctl = 1; } else { return (FDISK_ENOPGEOM); } } /* * Call DKIOCGGEOM if the ioctls for physical and virtual * geometry fail. Get both from this generic call. */ if (no_virtgeom_ioctl && no_physgeom_ioctl) { errno = 0; if (ioctl(epp->dev_fd, DKIOCGGEOM, &disk_geom)) { return (FDISK_ENOLGEOM); } } epp->disk_geom.phys_cyl = disk_geom.dkg_ncyl; epp->disk_geom.phys_heads = disk_geom.dkg_nhead; epp->disk_geom.phys_sec = disk_geom.dkg_nsect; epp->disk_geom.alt_cyl = disk_geom.dkg_acyl; /* * If DKIOCGMEDIAINFO ioctl succeeds, set the dki_lbsize as the * size of the sector, else default to 512 */ if (ioctl(epp->dev_fd, DKIOCGMEDIAINFO, (caddr_t)&disk_info) < 0) { /* ioctl failed, falling back to default value of 512 bytes */ epp->disk_geom.sectsize = 512; } else { epp->disk_geom.sectsize = ((disk_info.dki_lbsize) ? disk_info.dki_lbsize : 512); } /* * if hba geometry was not set by DKIOC_VIRTGEOM * or we got an invalid hba geometry * then set hba geometry based on max values */ if (no_virtgeom_ioctl || disk_geom.dkg_ncyl == 0 || disk_geom.dkg_nhead == 0 || disk_geom.dkg_nsect == 0 || disk_geom.dkg_ncyl > MAX_CYL || disk_geom.dkg_nhead > MAX_HEAD || disk_geom.dkg_nsect > MAX_SECT) { epp->disk_geom.virt_sec = MAX_SECT; epp->disk_geom.virt_heads = MAX_HEAD + 1; epp->disk_geom.virt_cyl = (epp->disk_geom.phys_cyl * epp->disk_geom.phys_heads * epp->disk_geom.phys_sec) / (epp->disk_geom.virt_sec * epp->disk_geom.virt_heads); } return (FDISK_SUCCESS); } /* * Initialise important members of the ext_part_t structure and * other data structures vital to functionality of libfdisk */ int libfdisk_init(ext_part_t **epp, char *devstr, struct ipart *parttab, int opflag) { ext_part_t *temp; struct stat sbuf; int rval = FDISK_SUCCESS; int found_bad_magic = 0; if ((temp = calloc(1, sizeof (ext_part_t))) == NULL) { return (ENOMEM); } (void) strncpy(temp->device_name, devstr, sizeof (temp->device_name)); /* Try to stat the node as provided */ if (stat(temp->device_name, &sbuf) != 0) { /* Prefix /dev/rdsk/ and stat again */ (void) snprintf(temp->device_name, sizeof (temp->device_name), "%s%s", DEFAULT_PATH_PREFIX, devstr); if (stat(temp->device_name, &sbuf) != 0) { /* * In case of an EFI labeled disk, the device name * could be cN[tN]dN. There is no pN. So we add "p0" * at the end if we do not find it and stat again. */ if (strrchr(temp->device_name, 'p') == NULL) { (void) strcat(temp->device_name, "p0"); } if (stat(temp->device_name, &sbuf) != 0) { /* Failed all options, give up */ free(temp); return (EINVAL); } } } /* Make sure the device is a raw device */ if ((sbuf.st_mode & S_IFMT) != S_IFCHR) { return (EINVAL); } temp->ld_head = NULL; temp->sorted_ld_head = NULL; if ((temp->dev_fd = open(temp->device_name, O_RDWR, 0666)) < 0) { free(temp); return (EINVAL); } if ((temp->mtable = parttab) == NULL) { if ((rval = fdisk_init_master_part_table(temp)) != FDISK_SUCCESS) { /* * When we have no fdisk magic 0xAA55 on the disk, * we return FDISK_EBADMAGIC after successfully * obtaining the disk geometry. */ if (rval != FDISK_EBADMAGIC) return (rval); else found_bad_magic = 1; } } temp->op_flag = opflag; if ((rval = fdisk_init_disk_geom(temp)) != FDISK_SUCCESS) { return (rval); } *epp = temp; if (found_bad_magic != 0) { return (FDISK_EBADMAGIC); } if (opflag & FDISK_READ_DISK) { rval = fdisk_read_extpart(*epp); } return (rval); } int libfdisk_reset(ext_part_t *epp) { int rval = FDISK_SUCCESS; fdisk_free_ld_nodes(epp); epp->first_ebr_is_null = 1; epp->corrupt_logical_drives = 0; epp->logical_drive_count = 0; epp->invalid_bb_sig[0] = 0; if (epp->op_flag & FDISK_READ_DISK) { rval = fdisk_read_extpart(epp); } return (rval); } void libfdisk_fini(ext_part_t **epp) { fdisk_free_ld_nodes(*epp); (void) close((*epp)->dev_fd); free(*epp); *epp = NULL; } int fdisk_is_linux_swap(ext_part_t *epp, uint32_t part_start, uint64_t *lsm_offset) { int i; int rval = -1; off_t seek_offset; uint32_t linux_pg_size; char *buf, *linux_swap_magic; int sec_sz = fdisk_get_disk_geom(epp, PHYSGEOM, SSIZE); off_t label_offset; /* * Known linux kernel page sizes * The linux swap magic is found as the last 10 bytes of a disk chunk * at the beginning of the linux swap partition whose size is that of * kernel page size. */ uint32_t linux_pg_size_arr[] = {4096, }; if ((buf = calloc(1, sec_sz)) == NULL) { return (ENOMEM); } /* * Check if there is a sane Solaris VTOC * If there is a valid vtoc, no need to lookup * for the linux swap signature. */ label_offset = (part_start + DK_LABEL_LOC) * sec_sz; if ((rval = lseek(epp->dev_fd, label_offset, SEEK_SET)) < 0) goto done; if ((rval = read(epp->dev_fd, buf, sec_sz)) < sec_sz) { rval = EIO; goto done; } if ((((struct dk_label *)buf)->dkl_magic == DKL_MAGIC) && (((struct dk_label *)buf)->dkl_vtoc.v_sanity == VTOC_SANE)) { rval = -1; goto done; } /* No valid vtoc, so check for linux swap signature */ linux_swap_magic = buf + sec_sz - LINUX_SWAP_MAGIC_LENGTH; for (i = 0; i < sizeof (linux_pg_size_arr)/sizeof (uint32_t); i++) { linux_pg_size = linux_pg_size_arr[i]; seek_offset = linux_pg_size/sec_sz - 1; seek_offset += part_start; seek_offset *= sec_sz; if ((rval = lseek(epp->dev_fd, seek_offset, SEEK_SET)) < 0) { break; } if ((rval = read(epp->dev_fd, buf, sec_sz)) < sec_sz) { rval = EIO; break; } if ((strncmp(linux_swap_magic, "SWAP-SPACE", LINUX_SWAP_MAGIC_LENGTH) == 0) || (strncmp(linux_swap_magic, "SWAPSPACE2", LINUX_SWAP_MAGIC_LENGTH) == 0)) { /* Found a linux swap */ rval = 0; if (lsm_offset != NULL) *lsm_offset = (uint64_t)seek_offset; break; } } done: free(buf); return (rval); } int fdisk_get_solaris_part(ext_part_t *epp, int *pnum, uint32_t *begsec, uint32_t *numsec) { logical_drive_t *temp = fdisk_get_ld_head(epp); uint32_t part_start; int pno; int rval = -1; for (pno = 5; temp != NULL; temp = temp->next, pno++) { if (fdisk_is_solaris_part(LE_8(temp->parts[0].systid))) { part_start = temp->abs_secnum + temp->logdrive_offset; if ((temp->parts[0].systid == SUNIXOS) && (fdisk_is_linux_swap(epp, part_start, NULL) == 0)) { continue; } *pnum = pno; *begsec = part_start; *numsec = temp->numsect; rval = FDISK_SUCCESS; } } return (rval); } int fdisk_get_part_info(ext_part_t *epp, int pnum, uchar_t *sysid, uint32_t *begsec, uint32_t *numsec) { logical_drive_t *temp = fdisk_get_ld_head(epp); int pno; if ((pnum < 5) || (pnum >= MAX_EXT_PARTS + 5)) { return (EINVAL); } for (pno = 5; (pno < pnum) && (temp != NULL); temp = temp->next, pno++) ; if (temp == NULL) { return (EINVAL); } *sysid = LE_8(temp->parts[0].systid); *begsec = temp->abs_secnum + temp->logdrive_offset; *numsec = temp->numsect; return (FDISK_SUCCESS); } /* * Allocate a node of type logical_drive_t and return the pointer to it */ static logical_drive_t * fdisk_alloc_ld_node() { logical_drive_t *temp; if ((temp = calloc(1, sizeof (logical_drive_t))) == NULL) { return (NULL); } temp->next = NULL; return (temp); } /* * Free all the logical_drive_t's allocated during the run */ static void fdisk_free_ld_nodes(ext_part_t *epp) { logical_drive_t *temp; for (temp = epp->ld_head; temp != NULL; ) { temp = epp->ld_head -> next; free(epp->ld_head); epp->ld_head = temp; } epp->ld_head = NULL; epp->sorted_ld_head = NULL; } /* * Find the first free sector within the extended partition */ int fdisk_ext_find_first_free_sec(ext_part_t *epp, uint32_t *first_free_sec) { logical_drive_t *temp; uint32_t last_free_sec; *first_free_sec = epp->ext_beg_sec; if (epp->ld_head == NULL) { return (FDISK_SUCCESS); } /* * When the first logical drive is out of order, we need to adjust * first_free_sec accordingly. In this case, the first extended * partition sector is not free even though the actual logical drive * does not occupy space from the beginning of the extended partition. * The next free sector would be the second sector of the extended * partition. */ if (epp->ld_head->abs_secnum > epp->ext_beg_sec + MAX_LOGDRIVE_OFFSET) { (*first_free_sec)++; } while (*first_free_sec <= epp->ext_end_sec) { for (temp = epp->sorted_ld_head; temp != NULL; temp = temp->sorted_next) { if (temp->abs_secnum == *first_free_sec) { *first_free_sec = temp->abs_secnum + temp->logdrive_offset + temp->numsect; } } last_free_sec = fdisk_ext_find_last_free_sec(epp, *first_free_sec); if ((last_free_sec - *first_free_sec) < MAX_LOGDRIVE_OFFSET) { /* * Minimum size of a partition assumed to be atleast one * sector. */ *first_free_sec = last_free_sec + 1; continue; } break; } if (*first_free_sec > epp->ext_end_sec) { return (FDISK_EOOBOUND); } return (FDISK_SUCCESS); } /* * Find the last free sector within the extended partition given, a beginning * sector (so that the range - "begsec to last_free_sec" is contiguous) */ uint32_t fdisk_ext_find_last_free_sec(ext_part_t *epp, uint32_t begsec) { logical_drive_t *temp; uint32_t last_free_sec; last_free_sec = epp->ext_end_sec; for (temp = epp->sorted_ld_head; temp != NULL; temp = temp->sorted_next) { if (temp->abs_secnum > begsec) { last_free_sec = temp->abs_secnum - 1; break; } } return (last_free_sec); } /* * Place the given ext_part_t structure in a sorted list, sorted in the * ascending order of their beginning sectors. */ static void fdisk_ext_place_in_sorted_list(ext_part_t *epp, logical_drive_t *newld) { logical_drive_t *pre, *cur; if (newld->abs_secnum < epp->sorted_ld_head->abs_secnum) { newld->sorted_next = epp->sorted_ld_head; epp->sorted_ld_head = newld; return; } pre = cur = epp->sorted_ld_head; for (; cur != NULL; pre = cur, cur = cur->sorted_next) { if (newld->abs_secnum < cur->abs_secnum) { break; } } newld->sorted_next = cur; pre->sorted_next = newld; } static void fdisk_ext_remove_from_sorted_list(ext_part_t *epp, logical_drive_t *delld) { logical_drive_t *pre, *cur; if (delld == epp->sorted_ld_head) { epp->sorted_ld_head = delld->sorted_next; return; } pre = cur = epp->sorted_ld_head; for (; cur != NULL; pre = cur, cur = cur->sorted_next) { if (cur->abs_secnum == delld->abs_secnum) { /* Found */ break; } } pre->sorted_next = cur->sorted_next; } static int fdisk_ext_overlapping_parts(ext_part_t *epp, uint32_t begsec, uint32_t endsec) { logical_drive_t *temp; uint32_t firstsec, lastsec, last_free_sec; for (temp = epp->ld_head; temp != NULL; temp = temp->next) { firstsec = temp->abs_secnum; lastsec = firstsec + temp->logdrive_offset + temp->numsect - 1; if ((begsec >= firstsec) && (begsec <= lastsec)) { return (1); } } /* * Find the maximum possible end sector value * given a beginning sector value */ last_free_sec = fdisk_ext_find_last_free_sec(epp, begsec); if (endsec > last_free_sec) { return (1); } return (0); } /* * Check if the logical drive boundaries are sane */ int fdisk_validate_logical_drive(ext_part_t *epp, uint32_t begsec, uint32_t offset, uint32_t numsec) { uint32_t endsec; endsec = begsec + offset + numsec - 1; if (begsec < epp->ext_beg_sec || begsec > epp->ext_end_sec || endsec < epp->ext_beg_sec || endsec > epp->ext_end_sec || endsec < begsec || fdisk_ext_overlapping_parts(epp, begsec, endsec)) { return (1); } return (0); } /* * Procedure to walk through the extended partitions and build a Singly * Linked List out of the data. */ static int fdisk_read_extpart(ext_part_t *epp) { struct ipart *fdp, *ext_fdp; int i = 0, j = 0, ext_part_found = 0, lpart = 5; off_t secnum, offset; logical_drive_t *temp, *ep_ptr; unsigned char *ext_buf; int sectsize = epp->disk_geom.sectsize; if ((ext_buf = (uchar_t *)malloc(sectsize)) == NULL) { return (ENOMEM); } fdp = epp->mtable; for (i = 0; (i < FD_NUMPART) && (!ext_part_found); i++, fdp++) { if (fdisk_is_dos_extended(LE_8(fdp->systid))) { ext_part_found = 1; secnum = LE_32(fdp->relsect); offset = secnum * sectsize; epp->ext_beg_sec = secnum; epp->ext_end_sec = secnum + LE_32(fdp->numsect) - 1; epp->ext_beg_cyl = FDISK_SECT_TO_CYL(epp, epp->ext_beg_sec); epp->ext_end_cyl = FDISK_SECT_TO_CYL(epp, epp->ext_end_sec); /*LINTED*/ while (B_TRUE) { if (lseek(epp->dev_fd, offset, SEEK_SET) < 0) { return (EIO); } if (read(epp->dev_fd, ext_buf, sectsize) < sectsize) { return (EIO); } /*LINTED*/ ext_fdp = (struct ipart *) (&ext_buf[FDISK_PART_TABLE_START]); if ((LE_32(ext_fdp->relsect) == 0) && (epp->logical_drive_count == 0)) { /* No logical drives defined */ epp->first_ebr_is_null = 0; return (FDISK_ENOLOGDRIVE); } temp = fdisk_alloc_ld_node(); temp->abs_secnum = secnum; temp->logdrive_offset = LE_32(ext_fdp->relsect); temp ->numsect = LE_32(ext_fdp->numsect); if (epp->ld_head == NULL) { /* adding first logical drive */ if (temp->logdrive_offset > MAX_LOGDRIVE_OFFSET) { /* out of order */ temp->abs_secnum += temp->logdrive_offset; temp->logdrive_offset = 0; } } temp->begcyl = FDISK_SECT_TO_CYL(epp, temp->abs_secnum); temp->endcyl = FDISK_SECT_TO_CYL(epp, temp->abs_secnum + temp->logdrive_offset + temp->numsect - 1); /* * Check for sanity of logical drives */ if (fdisk_validate_logical_drive(epp, temp->abs_secnum, temp->logdrive_offset, temp->numsect)) { epp->corrupt_logical_drives = 1; free(temp); return (FDISK_EBADLOGDRIVE); } temp->parts[0] = *ext_fdp; ext_fdp++; temp->parts[1] = *ext_fdp; if (epp->ld_head == NULL) { epp->ld_head = temp; epp->sorted_ld_head = temp; ep_ptr = temp; epp->logical_drive_count = 1; } else { ep_ptr->next = temp; ep_ptr = temp; fdisk_ext_place_in_sorted_list(epp, temp); epp->logical_drive_count++; } /*LINTED*/ if (LE_16((*(uint16_t *)&ext_buf[510])) != MBB_MAGIC) { epp->invalid_bb_sig[j++] = lpart; temp->modified = FDISK_MINOR_WRITE; } if (LE_32(ext_fdp->relsect) == 0) break; else { secnum = LE_32(fdp->relsect) + LE_32(ext_fdp->relsect); offset = secnum * sectsize; } lpart++; } } } return (FDISK_SUCCESS); } static int fdisk_init_master_part_table(ext_part_t *epp) { int rval; if ((epp->mtable = fdisk_alloc_part_table()) == NULL) { return (ENOMEM); } rval = fdisk_read_master_part_table(epp); if (rval) { return (rval); } return (FDISK_SUCCESS); } static struct ipart * fdisk_alloc_part_table() { int size = sizeof (struct ipart); struct ipart *table; if ((table = calloc(4, size)) == NULL) { return (NULL); } return (table); } /* * Reads the master fdisk partition table from the device assuming that it has * a valid table. * MBR is supposed to be of 512 bytes no matter what the device block size is. */ static int fdisk_read_master_part_table(ext_part_t *epp) { uchar_t buf[512]; int sectsize = 512; int size = sizeof (struct ipart); int cpcnt = FD_NUMPART * size; if (lseek(epp->dev_fd, 0, SEEK_SET) < 0) { return (EIO); } if (read(epp->dev_fd, buf, sectsize) < sectsize) { return (EIO); } /*LINTED*/ if (LE_16((*(uint16_t *)&buf[510])) != MBB_MAGIC) { bzero(epp->mtable, cpcnt); return (FDISK_EBADMAGIC); } bcopy(&buf[FDISK_PART_TABLE_START], epp->mtable, cpcnt); return (FDISK_SUCCESS); } int fdisk_ext_part_exists(ext_part_t *epp) { int i; struct ipart *part_table = epp->mtable; if (part_table == NULL) { /* No extended partition found */ return (0); } for (i = 0; i < FD_NUMPART; i++) { if (fdisk_is_dos_extended(LE_8(part_table[i].systid))) { break; } } if (i == FD_NUMPART) { /* No extended partition found */ return (0); } return (1); } int fdisk_ext_validate_part_start(ext_part_t *epp, uint32_t begcyl, uint32_t *begsec) { logical_drive_t *temp; uint32_t first_free_sec; uint32_t first_free_cyl; int rval; rval = fdisk_ext_find_first_free_sec(epp, &first_free_sec); if (rval != FDISK_SUCCESS) { return (rval); } first_free_cyl = FDISK_SECT_TO_CYL(epp, first_free_sec); if (begcyl == first_free_cyl) { *begsec = first_free_sec; return (FDISK_SUCCESS); } /* Check if the cylinder number is beyond the extended partition */ if ((begcyl < epp->ext_beg_cyl) || (begcyl > epp->ext_end_cyl)) { return (FDISK_EOOBOUND); } for (temp = epp->ld_head; temp != NULL; temp = temp->next) { if ((begcyl >= temp->begcyl) && (begcyl <= temp->endcyl)) { return (FDISK_EOVERLAP); } } *begsec = FDISK_CYL_TO_SECT(epp, begcyl); return (FDISK_SUCCESS); } void fdisk_change_logical_drive_id(ext_part_t *epp, int pno, uchar_t partid) { logical_drive_t *temp; int i; i = FD_NUMPART + 1; for (temp = epp->ld_head; i < pno; temp = temp->next, i++) ; temp->parts[0].systid = LE_8(partid); temp->modified = FDISK_MAJOR_WRITE; } /* * A couple of special scenarios : * 1. Since the first logical drive's EBR is always at the beginning of the * extended partition, any specification that starts the first logical drive * out of order will need to address the following issue : * If the beginning of the drive is not coinciding with the beginning of the * extended partition and : * a) The start is within MAX_LOGDRIVE_OFFSET, the offset changes from the * default of 63 to less than 63. * logdrive_offset is updated to keep track of the space between * the beginning of the logical drive and extended partition. abs_secnum * points to the beginning of the extended partition. * b) The start is greater than MAX_LOGDRIVE_OFFSET, the offset changes from * the default of 63 to greater than 63. * logdrive_offset is set to 0. abs_secnum points to the beginning of the * logical drive, which is at an offset from the extended partition. */ void fdisk_add_logical_drive(ext_part_t *epp, uint32_t begsec, uint32_t endsec, uchar_t partid) { logical_drive_t *temp, *pre, *cur; struct ipart *part; temp = fdisk_alloc_ld_node(); temp->abs_secnum = begsec; temp->logdrive_offset = MAX_LOGDRIVE_OFFSET; temp->numsect = endsec - begsec + 1 - MAX_LOGDRIVE_OFFSET; temp->begcyl = FDISK_SECT_TO_CYL(epp, begsec); temp->endcyl = FDISK_SECT_TO_CYL(epp, endsec); temp->modified = FDISK_MAJOR_WRITE; part = &temp->parts[0]; part->bootid = 0; part->systid = LE_8(partid); part->relsect = MAX_LOGDRIVE_OFFSET; part->numsect = LE_32(temp->numsect); fdisk_set_CHS_values(epp, part); if (epp->ld_head == NULL) { epp->corrupt_logical_drives = 0; if (begsec != epp->ext_beg_sec) { part->relsect = LE_32(begsec - epp->ext_beg_sec); temp->numsect = endsec - begsec + 1; part->numsect = LE_32(temp->numsect); if (LE_32(part->relsect) > MAX_LOGDRIVE_OFFSET) { temp->logdrive_offset = 0; } else { temp->abs_secnum = epp->ext_beg_sec; temp->logdrive_offset = LE_32(part->relsect); } } epp->first_ebr_is_null = 0; epp->ld_head = temp; epp->sorted_ld_head = temp; epp->logical_drive_count = 1; return; } if (temp->abs_secnum == epp->ext_beg_sec) { part->relsect = LE_32(LE_32(part->relsect) - 1); temp->logdrive_offset--; temp->abs_secnum++; } for (pre = cur = epp->ld_head; cur != NULL; pre = cur, cur = cur->next) ; part = &pre->parts[1]; part->bootid = 0; part->systid = LE_8(EXTDOS); part->relsect = LE_32(temp->abs_secnum - epp->ext_beg_sec); part->numsect = LE_32(temp->numsect + temp->logdrive_offset); fdisk_set_CHS_values(epp, part); pre->next = temp; pre->modified = FDISK_MAJOR_WRITE; epp->logical_drive_count++; fdisk_ext_place_in_sorted_list(epp, temp); } /* * There are 2 cases that need to be handled. * 1. Deleting the first extended partition : * The peculiarity of this case is that the offset of the first extended * partition is always indicated by the entry in the master boot record. * (MBR). This never changes, unless the extended partition itself is * deleted. Hence, the location of the first EBR is fixed. * It is only the logical drive which is deleted. This first EBR now gives * information of the next logical drive and the info about the subsequent * extended partition. Hence the "relsect" of the first EBR is modified to * point to the next logical drive. * * 2. Deleting an intermediate extended partition. * This is quite normal and follows the semantics of a normal linked list * delete operation. The node being deleted has the information about the * logical drive that it houses and the location and the size of the next * extended partition. This informationis transferred to the node previous * to the node being deleted. * */ void fdisk_delete_logical_drive(ext_part_t *epp, int pno) { logical_drive_t *pre, *cur; int i; i = FD_NUMPART + 1; pre = cur = epp->ld_head; for (; i < pno; i++) { pre = cur; cur = cur->next; } if (cur == epp->ld_head) { /* Deleting the first logical drive */ if (cur->next == NULL) { /* Deleting the only logical drive left */ free(cur); epp->ld_head = NULL; epp->sorted_ld_head = NULL; epp->logical_drive_count = 0; epp->first_ebr_is_null = 1; } else { pre = epp->ld_head; cur = pre->next; cur->parts[0].relsect = LE_32(LE_32(cur->parts[0].relsect) + LE_32(pre->parts[1].relsect)); /* Corner case when partitions are out of order */ if ((pre->abs_secnum != epp->ext_beg_sec) && (cur->abs_secnum == epp->ext_beg_sec + 1)) { cur->logdrive_offset++; cur->abs_secnum = epp->ext_beg_sec; } else { cur->abs_secnum = LE_32(cur->parts[0].relsect) + epp->ext_beg_sec; cur->logdrive_offset = 0; } fdisk_ext_remove_from_sorted_list(epp, pre); epp->ld_head = cur; epp->ld_head->modified = FDISK_MAJOR_WRITE; epp->logical_drive_count--; free(pre); } } else { pre->parts[1] = cur->parts[1]; pre->next = cur->next; fdisk_ext_remove_from_sorted_list(epp, cur); pre->modified = FDISK_MAJOR_WRITE; free(cur); epp->logical_drive_count--; } } static void fdisk_set_CHS_values(ext_part_t *epp, struct ipart *part) { uint32_t lba, cy, hd, sc; uint32_t sectors = epp->disk_geom.virt_sec; uint32_t heads = epp->disk_geom.virt_heads; lba = LE_32(part->relsect) + epp->ext_beg_sec; if (lba >= heads * sectors * MAX_CYL) { /* * the lba address cannot be expressed in CHS value * so store the maximum CHS field values in the CHS fields. */ cy = MAX_CYL + 1; hd = MAX_HEAD; sc = MAX_SECT; } else { cy = lba / sectors / heads; hd = lba / sectors % heads; sc = lba % sectors + 1; } part->begcyl = cy & 0xff; part->beghead = (uchar_t)hd; part->begsect = (uchar_t)(((cy >> 2) & 0xc0) | sc); /* * This code is identical to the code above * except that it works on ending CHS values */ lba += LE_32(part->numsect - 1); if (lba >= heads * sectors * MAX_CYL) { cy = MAX_CYL + 1; hd = MAX_HEAD; sc = MAX_SECT; } else { cy = lba / sectors / heads; hd = lba / sectors % heads; sc = lba % sectors + 1; } part->endcyl = cy & 0xff; part->endhead = (uchar_t)hd; part->endsect = (uchar_t)(((cy >> 2) & 0xc0) | sc); } static int read_modify_write_ebr(ext_part_t *epp, unsigned char *ebr_buf, struct ipart *ebr_tab, uint32_t sec_offset) { off_t seek_offset; int sectsize = epp->disk_geom.sectsize; seek_offset = (off_t)sec_offset * sectsize; if (lseek(epp->dev_fd, seek_offset, SEEK_SET) < 0) { return (EIO); } if (read(epp->dev_fd, ebr_buf, sectsize) < sectsize) { return (EIO); } bzero(&ebr_buf[FDISK_PART_TABLE_START], 4 * sizeof (struct ipart)); if (ebr_tab != NULL) { bcopy(ebr_tab, &ebr_buf[FDISK_PART_TABLE_START], 2 * sizeof (struct ipart)); } ebr_buf[510] = 0x55; ebr_buf[511] = 0xAA; if (lseek(epp->dev_fd, seek_offset, SEEK_SET) < 0) { return (EIO); } if (write(epp->dev_fd, ebr_buf, sectsize) < sectsize) { return (EIO); } return (0); } /* * XXX - ZFS mounts not detected. Needs to come in as a feature. * Currently only /etc/mnttab entries are being checked */ int fdisk_mounted_logical_drives(ext_part_t *epp) { char *part_str, *canonp; char compare_pdev_str[PATH_MAX]; char compare_sdev_str[PATH_MAX]; FILE *fp; struct mnttab mt; int part; int look_for_mounted_slices = 0; uint32_t begsec, numsec; /* * Do not check for mounted logical drives for * devices other than /dev/rdsk/ */ if (strstr(epp->device_name, DEFAULT_PATH_PREFIX) == NULL) { return (0); } if ((fp = fopen(MNTTAB, "r")) == NULL) { return (ENOENT); } canonp = epp->device_name + strlen(DEFAULT_PATH_PREFIX); (void) snprintf(compare_pdev_str, PATH_MAX, "%s%s", "/dev/dsk/", canonp); part_str = strrchr(compare_pdev_str, 'p'); *(part_str + 1) = '\0'; (void) strcpy(compare_sdev_str, compare_pdev_str); part_str = strrchr(compare_sdev_str, 'p'); *part_str = 's'; if (fdisk_get_solaris_part(epp, &part, &begsec, &numsec) == FDISK_SUCCESS) { if (part > FD_NUMPART) { /* * Solaris partition is on a logical drive. Look for * mounted slices. */ look_for_mounted_slices = 1; } } while (getmntent(fp, &mt) == 0) { if (strstr(mt.mnt_special, compare_pdev_str) == NULL) { if (strstr(mt.mnt_special, compare_sdev_str) == NULL) { continue; } else { if (look_for_mounted_slices) { return (FDISK_EMOUNTED); } } } /* * Get the partition number that is mounted, which would be * found just beyond the last 'p' in the device string. * For example, in /dev/dsk/c0t0d0p12, partition number 12 * is just beyond the last 'p'. */ part_str = strrchr(mt.mnt_special, 'p'); if (part_str != NULL) { part_str++; part = atoi(part_str); /* Extended partition numbers start from 5 */ if (part >= 5) { return (FDISK_EMOUNTED); } } } return (0); } int fdisk_commit_ext_part(ext_part_t *epp) { logical_drive_t *temp; int wflag = 0; /* write flag */ int rval; int sectsize = epp->disk_geom.sectsize; unsigned char *ebr_buf; int ld_count; uint32_t abs_secnum; int check_mounts = 0; if ((ebr_buf = (unsigned char *)malloc(sectsize)) == NULL) { return (ENOMEM); } if (epp->first_ebr_is_null) { /* * Indicator that the extended partition as a whole was * modifies (either created or deleted. Must check for mounts * and must commit */ check_mounts = 1; } /* * Pass1 through the logical drives to make sure that commit of minor * written block dont get held up due to mounts. */ for (temp = epp->ld_head; temp != NULL; temp = temp->next) { if (temp == epp->ld_head) { abs_secnum = epp->ext_beg_sec; } else { abs_secnum = temp->abs_secnum; } if (temp->modified == FDISK_MINOR_WRITE) { rval = read_modify_write_ebr(epp, ebr_buf, temp->parts, abs_secnum); if (rval) { goto error; } temp->modified = 0; } else if (temp->modified == FDISK_MAJOR_WRITE) { check_mounts = 1; } } if (!check_mounts) { goto skip_check_mounts; } if ((rval = fdisk_mounted_logical_drives(epp)) != 0) { /* One/more extended partitions are mounted */ if (ebr_buf) { free(ebr_buf); } return (rval); } skip_check_mounts: if (epp->first_ebr_is_null) { rval = read_modify_write_ebr(epp, ebr_buf, NULL, epp->ext_beg_sec); if (rval) { goto error; } wflag = 1; ld_count = 0; } else { if (epp->logical_drive_count == 0) { /* * Can hit this case when there is just an extended * partition with no logical drives, and the user * committed without making any changes * We dont have anything to commit. Return success */ if (ebr_buf) { free(ebr_buf); } return (FDISK_SUCCESS); } /* * Make sure that the first EBR is written with the first * logical drive's data, which might not be the first in disk * order. */ for (temp = epp->ld_head, ld_count = 0; temp != NULL; temp = temp->next, ld_count++) { if (ld_count == 0) { abs_secnum = epp->ext_beg_sec; } else { abs_secnum = temp->abs_secnum; } if (temp->modified) { rval = read_modify_write_ebr(epp, ebr_buf, temp->parts, abs_secnum); if (rval) { if (ld_count) { /* * There was atleast one * write to the disk before * this failure. Make sure that * the kernel is notified. * Issue the ioctl. */ break; } goto error; } if ((!wflag) && (temp->modified == FDISK_MAJOR_WRITE)) { wflag = 1; } } } if (wflag == 0) { /* No changes made */ rval = FDISK_SUCCESS; goto error; } } /* Issue ioctl to the driver to update extended partition info */ rval = ioctl(epp->dev_fd, DKIOCSETEXTPART); /* * Certain devices ex:lofi do not support DKIOCSETEXTPART. * Extended partitions are still created on these devices. */ if (errno == ENOTTY) rval = FDISK_SUCCESS; error: if (ebr_buf) { free(ebr_buf); } return (rval); } int fdisk_init_ext_part(ext_part_t *epp, uint32_t rsect, uint32_t nsect) { epp->first_ebr_is_null = 1; epp->corrupt_logical_drives = 0; epp->logical_drive_count = 0; epp->ext_beg_sec = rsect; epp->ext_end_sec = rsect + nsect - 1; epp->ext_beg_cyl = FDISK_SECT_TO_CYL(epp, epp->ext_beg_sec); epp->ext_end_cyl = FDISK_SECT_TO_CYL(epp, epp->ext_end_sec); epp->invalid_bb_sig[0] = 0; return (0); } int fdisk_delete_ext_part(ext_part_t *epp) { epp->first_ebr_is_null = 1; /* Clear the logical drive information */ fdisk_free_ld_nodes(epp); epp->logical_drive_count = 0; epp->corrupt_logical_drives = 0; epp->invalid_bb_sig[0] = 0; return (0); } int fdisk_get_disk_geom(ext_part_t *epp, int type, int what) { switch (type) { case PHYSGEOM: switch (what) { case NCYL: return ((int)epp->disk_geom.phys_cyl); case NHEADS: return ((int)epp->disk_geom.phys_heads); case NSECTPT: return ((int)epp->disk_geom.phys_sec); case SSIZE: return ((int)epp->disk_geom.sectsize); case ACYL: return ((int)epp->disk_geom.alt_cyl); default: return (EINVAL); } case VIRTGEOM: switch (what) { case NCYL: return ((int)epp->disk_geom.virt_cyl); case NHEADS: return ((int)epp->disk_geom.virt_heads); case NSECTPT: return ((int)epp->disk_geom.virt_sec); case SSIZE: return ((int)epp->disk_geom.sectsize); case ACYL: return ((int)epp->disk_geom.alt_cyl); default: return (EINVAL); } default: return (EINVAL); } } int fdisk_invalid_bb_sig(ext_part_t *epp, uchar_t **bbsig_arr) { *bbsig_arr = &(epp->invalid_bb_sig[0]); return (epp->invalid_bb_sig[0]); }