diff options
author | Sheshadri Vasudevan <Sheshadri.Vasudevan@Sun.COM> | 2009-11-09 11:54:00 +0530 |
---|---|---|
committer | Sheshadri Vasudevan <Sheshadri.Vasudevan@Sun.COM> | 2009-11-09 11:54:00 +0530 |
commit | bb9b6b3f59b8820022416cea99b49c50fef6e391 (patch) | |
tree | 5cfc62e3fbe3ccccd18fad13dbded011cf0367ad /usr/src/lib/libfdisk/common/libfdisk.c | |
parent | 4ba8a21421a6934b2b13d3e3403a479686e5b79f (diff) | |
download | illumos-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.c | 1366 |
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]); +} |