/*
* 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 (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
*/
#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;
int found_bad_magic = 0;
if ((temp = calloc(1, sizeof (ext_part_t))) == NULL) {
*epp = NULL;
return (ENOMEM);
}
bzero(temp, sizeof (ext_part_t));
(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 */
rval = EINVAL;
goto fail;
}
}
}
/* Make sure the device is a raw device */
if ((sbuf.st_mode & S_IFMT) != S_IFCHR) {
rval = EINVAL;
goto fail;
}
temp->ld_head = NULL;
temp->sorted_ld_head = NULL;
if ((temp->dev_fd = open(temp->device_name, O_RDWR, 0666)) < 0) {
rval = EINVAL;
goto fail;
}
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)
goto fail;
else
found_bad_magic = 1;
} else {
opflag |= _PRIV_FLAG_NEED_MTABLE_FREE;
}
}
temp->op_flag = opflag;
if ((rval = fdisk_init_disk_geom(temp)) != FDISK_SUCCESS) {
goto fail;
}
*epp = temp;
if (found_bad_magic != 0) {
return (FDISK_EBADMAGIC);
}
if (opflag & FDISK_READ_DISK) {
rval = fdisk_read_extpart(*epp);
}
return (rval);
fail:
*epp = NULL;
free(temp);
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)
{
if (*epp == NULL)
return;
fdisk_free_ld_nodes(*epp);
(void) close((*epp)->dev_fd);
if ((*epp)->op_flag & _PRIV_FLAG_NEED_MTABLE_FREE)
free((*epp)->mtable);
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 (lseek(epp->dev_fd, label_offset, SEEK_SET) < 0) {
rval = EIO;
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 (lseek(epp->dev_fd, seek_offset, SEEK_SET) < 0) {
rval = EIO;
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_efi_fs_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_efi_fs_extended(LE_8(temp->parts[0].systid))) {
part_start = temp->abs_secnum + temp->logdrive_offset;
*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);
}
static int
fdisk_validate_existing_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) {
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) {
free(ext_buf);
return (EIO);
}
if (read(epp->dev_fd, ext_buf, sectsize) <
sectsize) {
free(ext_buf);
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;
free(ext_buf);
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);
free(ext_buf);
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++;
}
}
}
free(ext_buf);
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) {
free(epp->mtable);
epp->mtable = NULL;
return (rval);
}
return (FDISK_SUCCESS);
}
static struct ipart *
fdisk_alloc_part_table()
{
return (calloc(4, sizeof (struct ipart)));
}
/*
* 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;
}
/*
* fdisk_change_logical_drive_size
*
* To change the size of a Logical Drive, its EBR needs to specify
* its new size (A), and the "previous" Logical Drive in an Extended
* Partition's list of LD's needs to also know the new size (B).
*
* Note that the length of the target LD has a different
* interpretation between the target LD and its "previous" LD.
*
* Values after the size-change has been made:
*
* +-----------------+----------------------+xxxxxxxxxxxxxxxxxxxxx
* | | partition's | x
* | logdrive_offset | current | additional space x
* | | sectors | x
* +-----------------+----------------------+xxxxxxxxxxxxxxxxxxxxx
*
* +---------------- tgt.numsect -------------+
* +------------ tgt.parts[0].numsect -------+ (A)
*
* +---------------------- prev.parts[1].numsect --------------+ (B)
*
* Inputs:
* epp - Extended Partition's state
* pno - Number of Partition to modify ("5" is the first Logical
* Drive within an ExtPart (eg, "FD_NUMPART + 1"); in the
* filesystem, this LD is represented as the "p5" device).
* An ExtPart can have at most MAX_EXT_PARTS LD's.
* numsec - new size, in sectors
*/
int
fdisk_change_logical_drive_size(ext_part_t *epp, int pno, uint32_t numsec)
{
logical_drive_t *tgt;
logical_drive_t *cur;
logical_drive_t *prev;
int i;
i = FD_NUMPART + 1;
if (pno < i || pno >= i + epp->logical_drive_count) {
/* then partition number is out of range. */
return (EINVAL);
}
/* Find Logical Drive to modify. */
for (tgt = epp->ld_head; i < pno; tgt = tgt->next, i++)
;
/* Range-check the Logical Drive, using the new partition size. */
if (fdisk_validate_existing_logical_drive(epp, tgt->abs_secnum,
tgt->logdrive_offset, numsec)) {
return (EIO);
}
/*
* Adjust the part of the Logical Drive's EBR that determines the LD's
* partition length. Update its associated geometry and mark the LD
* so that it will be committed to disk.
*/
tgt->numsect = numsec;
tgt->parts[0].numsect = LE_32(numsec);
fdisk_set_CHS_values(epp, &tgt->parts[0]);
tgt->modified = FDISK_MAJOR_WRITE;
/*
* Find the previous element in the *unsorted* linked list of LD's
* and adjust its EBR so that it also knows of the new partition size.
*/
/* Find element that references 'tgt' in the unsorted linked list. */
for (prev = NULL, cur = epp->ld_head; cur != NULL && cur != tgt;
prev = cur, cur = cur->next)
;
/*
* If the Logical Drive being modified is not the first element in list,
* update the EBR of its previous element.
*/
if (prev != NULL) {
struct ipart *part;
part = &prev->parts[1];
part->numsect = LE_32(tgt->numsect + tgt->logdrive_offset);
fdisk_set_CHS_values(epp, part);
prev->modified = FDISK_MAJOR_WRITE;
}
return (FDISK_SUCCESS);
}
/*
* 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]);
}