summaryrefslogtreecommitdiff
path: root/usr/src/lib/libfdisk/common/libfdisk.c
diff options
context:
space:
mode:
authorSheshadri Vasudevan <Sheshadri.Vasudevan@Sun.COM>2009-11-09 11:54:00 +0530
committerSheshadri Vasudevan <Sheshadri.Vasudevan@Sun.COM>2009-11-09 11:54:00 +0530
commitbb9b6b3f59b8820022416cea99b49c50fef6e391 (patch)
tree5cfc62e3fbe3ccccd18fad13dbded011cf0367ad /usr/src/lib/libfdisk/common/libfdisk.c
parent4ba8a21421a6934b2b13d3e3403a479686e5b79f (diff)
downloadillumos-joyent-bb9b6b3f59b8820022416cea99b49c50fef6e391.tar.gz
6879987 libfdisk should produce a 64 bit library
--HG-- rename : usr/src/lib/libfdisk/i386/libfdisk.c => usr/src/lib/libfdisk/common/libfdisk.c rename : usr/src/lib/libfdisk/i386/libfdisk.h => usr/src/lib/libfdisk/common/libfdisk.h rename : usr/src/lib/libfdisk/i386/llib-lfdisk => usr/src/lib/libfdisk/common/llib-lfdisk rename : usr/src/lib/libfdisk/i386/mapfile-vers => usr/src/lib/libfdisk/common/mapfile-vers
Diffstat (limited to 'usr/src/lib/libfdisk/common/libfdisk.c')
-rw-r--r--usr/src/lib/libfdisk/common/libfdisk.c1366
1 files changed, 1366 insertions, 0 deletions
diff --git a/usr/src/lib/libfdisk/common/libfdisk.c b/usr/src/lib/libfdisk/common/libfdisk.c
new file mode 100644
index 0000000000..db6916679b
--- /dev/null
+++ b/usr/src/lib/libfdisk/common/libfdisk.c
@@ -0,0 +1,1366 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/systeminfo.h>
+#include <sys/efi_partition.h>
+#include <sys/byteorder.h>
+
+#include <sys/vtoc.h>
+#include <sys/tty.h>
+#include <sys/dktp/fdisk.h>
+#include <sys/dkio.h>
+#include <sys/mnttab.h>
+#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;
+
+ 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) {
+ return (rval);
+ }
+ }
+
+ temp->op_flag = opflag;
+
+ if ((rval = fdisk_init_disk_geom(temp)) != FDISK_SUCCESS) {
+ return (rval);
+ }
+
+ *epp = temp;
+
+ 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.
+ */
+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);
+ }
+ bcopy(&buf[FDISK_PART_TABLE_START], epp->mtable, cpcnt);
+
+ /*LINTED*/
+ if (LE_16((*(uint16_t *)&buf[510])) != MBB_MAGIC) {
+ return (FDISK_EBADMAGIC);
+ }
+
+ 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]);
+}