/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #define _LAYOUT_DISCOVERY_C #include "volume_dlist.h" #include "volume_error.h" #include "volume_nvpair.h" #include "volume_output.h" #include "layout_device_cache.h" #include "layout_device_util.h" #include "layout_dlist_util.h" #include "layout_discovery.h" #include "layout_request.h" #include "layout_slice.h" #include "layout_svm_util.h" /* * lists of device dm_descriptor_t handles discovered during * the initial system probe. Lists are populated by * discover_known_devices. * * "bad" slices are those that are known to libdiskmgt but * cannot be accessed. An example would be a slice that has * disappeared due to disk re-slicing: libdiskmgt may have a * cached handle for it, but the slice no longer exists. * * "bad" disks are thoese that are known to libdiskmgt but * cannot be accessed. An example would be a disk that has * failed or has gone offline: libdiskmgt may have a cached * handle for it, but the disk does not respond. */ static dlist_t *_bad_slices = NULL; static dlist_t *_bad_disks = NULL; static dlist_t *_known_slices = NULL; static dlist_t *_known_disks = NULL; static dlist_t *_known_hbas = NULL; /* * helper functions for building known device lists, used by * discover_known_devices. */ static int generate_known_slices(dlist_t *disks, dlist_t **known, dlist_t **bad); static int generate_known_disks(dlist_t **known, dlist_t **bad); static int generate_known_hbas(dlist_t *disks, dlist_t **known); static int generate_known_hba_name( dm_descriptor_t hba, dm_descriptor_t alias, dm_descriptor_t disk); static void print_known_devices(); static void print_device_list(dlist_t *devices); /* * lists of device dm_descriptor_t handles that are usable by layout. * These devices must still pass the user specified available/unavailable * filter before they're actually considered available. * * Lists are populated by discover_usable_devices. */ static dlist_t *_usable_slices = NULL; static dlist_t *_usable_disks = NULL; static dlist_t *_usable_hbas = NULL; /* * private flag that remembers if any HBA is known to support MPXIO */ static boolean_t _mpxio_enabled = B_FALSE; /* * The slice_class struct is used to group slices by usage class. */ typedef struct { char *usage; /* usage description */ dlist_t *sliceinfo; /* list with info about each slice with usage */ } slice_class_t; #define USE_DISKSET "diskset" static int check_slice_usage( char *dsname, dm_descriptor_t slice, dm_descriptor_t disk, boolean_t *avail, dlist_t **bad, dlist_t **classes); static int check_svm_slice_usage( char *dsname, dm_descriptor_t slice, dm_descriptor_t disk, boolean_t *avail, dlist_t **classes); static int save_slice_classification( char *dsname, dm_descriptor_t slice, dm_descriptor_t disk, char *usage, char *usage_detail, dlist_t **classes); static int generate_usable_disks_and_slices_in_local_set( dlist_t **classes, dlist_t **bad_disks, dlist_t **usable_disks, dlist_t **usable_slices); static int generate_usable_disks_and_slices_in_named_set( char *dsname, dlist_t **classes, dlist_t **bad_slices, dlist_t **usable_disks, dlist_t **usable_slices); static int create_usable_slices( dm_descriptor_t disk, dlist_t *used, dlist_t *unused, dlist_t **usable); static int add_new_usable( dm_descriptor_t disk, uint64_t stblk, uint64_t nblks, dlist_t **next_unused, dlist_t **usable); static int update_slice_attributes( dm_descriptor_t slice, uint64_t stblk, uint64_t nblks, uint64_t nbytes); static int generate_usable_hbas( dlist_t *disks, dlist_t **usable); static void print_usable_devices(); static void print_unusable_devices( dlist_t *badslices, dlist_t *baddisks, dlist_t *usedslices); static char *get_slice_usage_msg( char *usage); /* * virtual slices... */ static int generate_virtual_slices( dlist_t *avail_disks_local_set, dlist_t **usable); /* * multipathed disks have aliases, as do slices on those disks. * these need to be tracked since the user may specify them. * A multi-pathed disk is one connected to the system thru * more than one physical HBA, each connection gets a distinct * name in the device tree and they're all more or less equivalent. * No indication as to how many possible physical connections a * disk may have, so we pick an arbitrary number of aliases to * support. There is nothing significant about this number, * it just controls the number of alias slots that get allocated. */ #define MAX_ALIASES 8 /* * attribute name for layout private information stored in * device nvpair attribute lists. */ static char *ATTR_DEVICE_ALIASES = "layout_device_aliases"; static int compare_start_blocks( void *desc1, void *desc2); static int compare_desc_display_names( void *desc1, void *desc2); /* * FUNCTION: is_mpxio_enabled() * * RETURNS: boolean_t - B_TRUE - if MPXIO appears enabled for the system * B_FALSE - otherwise * * PURPOSE: returns the value of _mpxio_enabled which is set to B_TRUE * during system configuration discovery if any of the knwon * HBAs advertises itself as a "multiplex" controller. */ boolean_t is_mpxio_enabled() { return (_mpxio_enabled); } /* * FUNCTION: discover_known_devices() * * SIDEEFFECT: populates the module private lists of known devices * (_known_slices, _known_disks, _known_hbas). * * All known devices will also have had their CTD * short names inferred and stored. * * RETURNS: int - 0 on success * !0 otherwise * * PURPOSE: Load physical devices discovered thru libdiskmgt. */ int discover_known_devices() { int error = 0; oprintf(OUTPUT_TERSE, gettext("\nScanning system physical " "device configuration...\n")); /* initialize layout_device_cache */ ((error = create_device_caches()) != 0) || (error = generate_known_disks(&_known_disks, &_bad_disks)) || (error = generate_known_slices(_known_disks, &_known_slices, &_bad_slices)) || (error = generate_known_hbas(_known_disks, &_known_hbas)); if (error == 0) { print_known_devices(); } return (error); } /* * FUNCTION: release_known_devices() * * RETURNS: int - 0 on success * !0 otherwise * * PURPOSE: Unloads all state currently held for known * physical devices. */ int release_known_devices( char *diskset) { /* these lists are module private */ dlist_free_items(_bad_slices, NULL); dlist_free_items(_bad_disks, NULL); dlist_free_items(_known_slices, NULL); dlist_free_items(_known_disks, NULL); dlist_free_items(_known_hbas, NULL); _bad_slices = NULL; _bad_disks = NULL; _known_slices = NULL; _known_disks = NULL; _known_hbas = NULL; /* clean up state kept in layout_device_cache */ release_device_caches(); return (0); } /* * FUNCTION: discover_usable_devices(char *diskset) * * INPUT: diskset - a char * diskset name. * * SIDEEFFECT: Traverses the lists of known devices and populates the * module private lists of usable devices (_usable_slices, * _usable_disks, _usable_hbas), as well as the module * private list of used slices. * * RETURNS: int - 0 on success * !0 otherwise * * PURPOSE: Process the known devices and determine which of them are * usable for generating volumes in the specified diskset. * * The specified diskset's name cannot be NULL or 0 length. */ int discover_usable_devices( char *diskset) { int error = 0; dlist_t *used_classes = NULL; dlist_t *iter = NULL; if (diskset == NULL || diskset[0] == '\0') { volume_set_error( gettext("a diskset name must be specified in " "the request\n")); return (-1); } oprintf(OUTPUT_TERSE, gettext("\nDetermining usable physical devices " "for disk set \"%s\"...\n"), diskset); error = generate_usable_disks_and_slices_in_local_set( &used_classes, &_bad_slices, &_usable_disks, &_usable_slices); if (error == 0) { error = generate_usable_disks_and_slices_in_named_set( diskset, &used_classes, &_bad_slices, &_usable_disks, &_usable_slices); if (error == 0) { error = generate_usable_hbas(_usable_disks, &_usable_hbas); if (error == 0) { print_usable_devices(); print_unusable_devices( _bad_slices, _bad_disks, used_classes); } } } /* * free slice classification usage and lists, items are char* * the used_classes structure is only filled in if verbose * output was requested. */ for (iter = used_classes; iter != NULL; iter = iter->next) { slice_class_t *class = (slice_class_t *)iter->obj; free(class->usage); dlist_free_items(class->sliceinfo, free); } dlist_free_items(used_classes, free); return (error); } /* * FUNCTION: release_usable_devices() * * RETURNS: int - 0 on success * !0 otherwise * * PURPOSE: Unloads all state currently held for usable * physical devices. */ int release_usable_devices() { /* list items are shared with _known_XXX lists */ dlist_free_items(_usable_slices, NULL); dlist_free_items(_usable_disks, NULL); dlist_free_items(_usable_hbas, NULL); _usable_slices = NULL; _usable_disks = NULL; _usable_hbas = NULL; /* clean up state kept in layout_device_util */ release_virtual_slices(); return (0); } /* * FUNCTION: get_known_slices(dlist_t **list) * get_known_disks(dlist_t **list) * get_known_hbas(dlist_t **list) * * OUTPUT: list - a dlist_t pointer to hold the returned list of * devices. * * RETURNS: int - 0 on success * !0 otherwise * * PURPOSE: Public accessors for the module private lists of * available devices. */ int get_known_slices( dlist_t **list) { *list = _known_slices; return (0); } int get_known_disks( dlist_t **list) { *list = _known_disks; return (0); } int get_known_hbas( dlist_t **list) { *list = _known_hbas; return (0); } /* make fully qualified DID device name */ static char * make_fully_qualified_did_device_name( char *device) { static char buf[MAXPATHLEN]; if (device != NULL && strrchr(device, '/') == NULL) { (void) snprintf(buf, MAXPATHLEN-1, "%s/%s", "/dev/did/dsk", device); return (buf); } return (device); } /* * FUNCTION: generate_known_disks(dlist_t **known, * dlist_t **bad) * * INPUT: NONE * * OUTPUT: known - populated list of known disks * bad - populated list of known bad disks * * RETURNS: int - 0 on success * !0 otherwise * * PURPOSE: Does the system configuration discovery to determine * what disks are known to be attached to the system. * * Determines the CTD name for each disk and saves it. */ static int generate_known_disks( dlist_t **known, dlist_t **bad) { int i; int error = 0; dm_descriptor_t *ddp; ddp = dm_get_descriptors(DM_DRIVE, NULL, &error); (void) add_descriptors_to_free(ddp); *known = NULL; if (error != 0) { volume_set_error( gettext("Error discovering system hardware configuration,\n" "unable to communicate with libdiskmgt or diskmgtd.\n")); return (-1); } if ((ddp == NULL) || (ddp[0] == NULL)) { volume_set_error(gettext("there are no known disks\n")); return (-1); } /* iterate all returned disks and add them to the known list */ for (i = 0; (ddp[i] != NULL) && (error == 0); i++) { dm_descriptor_t disk = (dm_descriptor_t)ddp[i]; dlist_t *aliases = NULL; uint32_t mtype = DM_MT_UNKNOWN; uint32_t dtype = DM_DT_UNKNOWN; boolean_t bad_disk = B_FALSE; boolean_t online = B_TRUE; #if defined(i386) /* on X86, disks must have a solaris FDISK partition */ boolean_t solpart = B_FALSE; #endif /* defined(i386) */ if (((error = disk_get_is_online(disk, &online)) == 0 && online == B_FALSE) || error == ENODEV) { /* if the disk is offline, report it as bad */ bad_disk = B_TRUE; error = 0; } else if (error == 0 && (((error = disk_get_media_type(disk, &mtype)) != 0) || ((error = disk_get_drive_type(disk, &dtype)) != 0)) && error == ENODEV) { /* * if any disk attribute access fails with ENODEV * report it as bad */ bad_disk = B_TRUE; error = 0; } else { /* * Determine whether disk is fixed by checking its * drive type. If drive type is unknown, check media * type. */ int isfixed = (dtype == DM_DT_FIXED || (dtype == DM_DT_UNKNOWN && mtype == DM_MT_FIXED)); if (!isfixed) { continue; /* ignore non-fixed disks */ } #if defined(i386) if (((error = disk_get_has_solaris_partition(disk, &solpart)) != 0) || (solpart != B_TRUE)) { /* X86 drive has no solaris partition, report as bad */ oprintf(OUTPUT_DEBUG, gettext("%s has no solaris FDISK partition.\n")); bad_disk = B_TRUE; } #endif /* defined(i386) */ } if (bad_disk) { /* remember bad disks and continue */ if (dlist_contains(*bad, (void *)disk, compare_descriptor_names) != B_TRUE) { dlist_t *item = dlist_new_item((void *)disk); if (item == NULL) { error = ENOMEM; } else { *bad = dlist_append(item, *bad, AT_TAIL); } } continue; } /* get disk name and multipath aliases */ if ((error = disk_get_aliases(disk, &aliases)) == 0) { dlist_t *iter; boolean_t disk_name_set = B_FALSE; for (iter = aliases; (iter != NULL) && (error == 0); iter = iter->next) { dm_descriptor_t ap = (uintptr_t)iter->obj; char *alias; if ((error = get_name(ap, &alias)) == 0) { /* save first alias as display name */ if (disk_name_set != B_TRUE) { /* make sure DID disk alias is fully qualified */ if (is_did_disk_name(alias) == B_TRUE) { char *qual_name = make_fully_qualified_did_device_name(alias); set_display_name(disk, qual_name); oprintf(OUTPUT_DEBUG, gettext("DID disk name: %s\n"), qual_name); } else { set_display_name(disk, alias); oprintf(OUTPUT_DEBUG, gettext("disk name: %s\n"), alias); } disk_name_set = B_TRUE; } else { /* save others as aliases */ set_alias(disk, alias); oprintf(OUTPUT_DEBUG, gettext(" alias: %s\n"), alias); } } } dlist_free_items(aliases, NULL); } if (error == 0) { dlist_t *item = dlist_new_item((void *)disk); if (item == NULL) { error = ENOMEM; } else { *known = dlist_insert_ordered(item, *known, ASCENDING, compare_desc_display_names); } } } if (ddp != NULL) { free(ddp); } return (error); } /* * FUNCTION: generate_known_slices(dlist_t *disks, * dlist_t **known, dlist_t **bad) * * OUTPUT: disks - a pointer to a list of known disks * known - a pointer to a dlist_t list to hold the known slices * bad - a pointer to a dlist_t to hold the bad slices * * RETURNS: int - 0 on success * !0 otherwise. * * PURPOSE: Examines input list of known disks and determines the slices * attached to each. * * Some slices returned from libdiskmgt may not really exist, * this is detected when trying to get more information about * the slice -- ENODEV is returned. Any such slices will be * added to the bad slice list. */ static int generate_known_slices( dlist_t *disks, dlist_t **known, dlist_t **bad) { dlist_t *iter; int error = 0; /* iterate list of disks and add their slices to the known list */ for (iter = disks; (iter != NULL) && (error == 0); iter = iter->next) { dm_descriptor_t disk = (uintptr_t)iter->obj; dlist_t *slices = NULL; dlist_t *iter1; char *dname = NULL; boolean_t disk_ctd_alias_derived = B_FALSE; if (((error = disk_get_slices(disk, &slices)) != 0) || ((error = get_display_name(disk, &dname)) != 0)) { continue; } for (iter1 = slices; (iter1 != NULL) && (error == 0); iter1 = iter1->next) { dm_descriptor_t slice = (uintptr_t)iter1->obj; uint32_t index = 0; nvlist_t *attrs = NULL; char *sname = NULL; if (((error = get_name(slice, &sname)) != 0) || ((error = slice_get_index(slice, &index)) != 0) || ((error = get_cached_attributes(slice, &attrs)) != 0)) { if (error == ENODEV) { /* bad slice, remember it and continue */ dlist_t *item = dlist_new_item((void *)slice); if (item == NULL) { error = ENOMEM; } else { *bad = dlist_insert_ordered( item, *bad, ASCENDING, compare_descriptor_names); error = 0; } } continue; } if ((error == 0) && (is_did_slice_name(sname) == B_TRUE) && (disk_ctd_alias_derived == B_FALSE)) { /* BEGIN CSTYLED */ /* * If the slice name is a DID name, get the local CTD * name for slice, extract the disk name and add it as * an alias for the disk. * * This is the only way to derive the CTD alias for the * disk when DID is enabled. * * The disk_ctd_alias_derived flag ensure the disk's * CTD alias is only set once. * * The slice's CTD aliases are then derived from the * disk's CTD alias in the normal, non-DID name processing * which happens below. */ /* END CSTYLED */ char *local = NULL; if ((error = nvlist_lookup_string(attrs, DM_LOCALNAME, &local)) != 0) { if (error == ENOENT) { /* no local name -> no DID */ error = 0; } } else { char *localdisk = NULL; char *diskonly = NULL; if ((error = extract_diskname(local, &localdisk)) == 0) { if ((diskonly = strrchr(localdisk, '/')) != NULL) { ++diskonly; } else { diskonly = localdisk; } oprintf(OUTPUT_DEBUG, gettext(" set DID disk CTD alias: %s\n"), diskonly); error = set_alias(disk, diskonly); free(localdisk); disk_ctd_alias_derived = B_TRUE; } } } /* derive slice display name from disk's display name */ if (error == 0) { if ((error = make_slicename_for_diskname_and_index( dname, index, &sname)) == 0) { error = set_display_name(slice, sname); } } /* set slice aliases using disk aliases */ if (error == 0) { dlist_t *aliases = NULL; if ((error = get_aliases(disk, &aliases)) == 0) { dlist_t *iter2 = aliases; for (; (iter2 != NULL) && (error == 0); iter2 = iter2->next) { char *dalias = (char *)iter2->obj; char *salias = NULL; if ((error = make_slicename_for_diskname_and_index( dalias, index, &salias)) == 0) { error = set_alias(slice, salias); free(salias); } } dlist_free_items(aliases, free); } } if (error == 0) { dlist_t *item = dlist_new_item((void *)slice); if (item == NULL) { error = ENOMEM; } else { *known = dlist_insert_ordered( item, *known, ASCENDING, compare_desc_display_names); } } } dlist_free_items(slices, NULL); } return (error); } /* * FUNCTION: generate_known_hbas(dlist_t *disks, dlist_t **known) * * INPUT: diskset - a char * diskset name. * * OUTPUT: populates the list of known HBAs. * * RETURNS: int - 0 on success * !0 otherwise * * PURPOSE: Examines known disk list and derives the list of known HBAs. * * Determines the CTD name for an HBA and saves it. */ static int generate_known_hbas( dlist_t *disks, dlist_t **known) { dlist_t *iter; int error = 0; /* * for each known disk follow its HBA connections and * assemble the list of known HBAs. */ for (iter = disks; (iter != NULL) && (error == 0); iter = iter->next) { dm_descriptor_t disk = (uintptr_t)iter->obj; dlist_t *hbas = NULL; dlist_t *iter2 = NULL; dlist_t *iter3 = NULL; dlist_t *aliases = NULL; char *dname = NULL; ((error = get_display_name(disk, &dname)) != 0) || (error = disk_get_aliases(disk, &aliases)) || (error = disk_get_hbas(disk, &hbas)); if (error == 0) { if ((hbas == NULL) || (dlist_length(hbas) == 0)) { oprintf(OUTPUT_DEBUG, gettext("Disk %s has no HBA/Controller?!\n"), dname); error = -1; dlist_free_items(hbas, NULL); dlist_free_items(aliases, NULL); continue; } for (iter2 = hbas, iter3 = aliases; iter2 != NULL && iter3 != NULL; iter2 = iter2->next, iter3 = iter3->next) { dm_descriptor_t hba = (uintptr_t)iter2->obj; dm_descriptor_t alias = (uintptr_t)iter3->obj; dlist_t *item = NULL; /* scan list of known HBAs and see if known */ if (dlist_contains(*known, (void*)hba, compare_descriptor_names) == B_TRUE) { /* known HBA */ continue; } /* see if HBA supports MPXIO */ if ((error == 0) && (_mpxio_enabled != B_TRUE)) { hba_is_multiplex(hba, &_mpxio_enabled); } /* generate a CTD name for HBA */ error = generate_known_hba_name(hba, alias, disk); if (error == 0) { /* add to known HBA list */ if ((item = dlist_new_item((void *)hba)) == NULL) { error = ENOMEM; } else { *known = dlist_insert_ordered(item, *known, ASCENDING, compare_desc_display_names); } } } } dlist_free_items(aliases, NULL); dlist_free_items(hbas, NULL); } return (error); } /* * FUNCTION: generate_known_hba_name(dm_descriptor_t hba, * dm_descriptor_t alias, char *diskname) * * INPUT: hba - a dm_descriptor_t HBA handle. * alias - a dm_descriptor_t disk alias handle. * diskname - a char * disk name * * RETURNS: int - 0 on success * !0 otherwise * * PURPOSE: Sets the CTD name for the input HBA. * * The CTD name for the HBA is generated from the input * disk alias (ex: cXdXtXsX) or from the disk name if * the input alias is a DID name (ex: dX). */ static int generate_known_hba_name( dm_descriptor_t hba, dm_descriptor_t alias, dm_descriptor_t disk) { char *hbaname = NULL; char *aliasname = NULL; int error = 0; ((error = get_name(alias, &aliasname)) != 0) || (error = extract_hbaname(aliasname, &hbaname)); if (error != 0) { free(hbaname); return (error); } /* see if the input alias is a DID name... */ if (is_did_disk_name(aliasname) == B_TRUE) { /* look for a non-DID name in disk's aliases */ dlist_t *aliases = NULL; error = get_aliases(disk, &aliases); for (; (error == 0) && (aliases != NULL); aliases = aliases->next) { aliasname = (char *)aliases->obj; if (is_did_disk_name(aliasname) != B_TRUE) { /* this is the "local" CTD name generated by */ /* generate_known_disks() above */ error = extract_hbaname(aliasname, &hbaname); if ((error == 0) && (hbaname != NULL)) { set_display_name(hba, hbaname); break; } } } dlist_free_items(aliases, free); } else { /* use whatever was derived from the alias name */ set_display_name(hba, hbaname); } return (error); } /* * FUNCTION: print_known_devices() * * PURPOSE: Print out the known devices. * * Iterates the lists of known slices, disks and HBAs * and prints out their CTD and device names. */ static void print_known_devices( char *diskset) { int i = 0; struct { char *msg; dlist_t *list; } devs[3]; devs[0].msg = gettext("HBA/Controllers"); devs[0].list = _known_hbas; devs[1].msg = gettext("disks"); devs[1].list = _known_disks; devs[2].msg = gettext("slices"); devs[2].list = _known_slices; for (i = 0; i < 3; i++) { oprintf(OUTPUT_VERBOSE, gettext("\n These %s are known:\n\n"), devs[i].msg); print_device_list(devs[i].list); } } /* * FUNCTION: get_usable_slices(dlist_t **list) * * OUTPUT: list - a dlist_t pointer to hold the returned list of * devices. * * RETURNS: int - 0 on success * !0 otherwise * * PURPOSE: Public accessors the the modules private lists of * available devices. * * The functions are keyed by diskset name in the event * objects in different disksets are loaded concurrently. */ int get_usable_slices( dlist_t **list) { *list = _usable_slices; return (0); } int get_usable_disks( dlist_t **list) { *list = _usable_disks; return (0); } int get_usable_hbas( dlist_t **list) { *list = _usable_hbas; return (0); } /* * FUNCTION: generate_usable_disks_and_slices_in_local_set(dlist_t **classes, * dlist_t **bad_disks, dlist_t **usable_disks, * dlist_t **usable_slices) * * OUTPUT: used_classes - a pointer to a list of slice_class_t structs * updated with known slices that have detected uses * added to the correct class'e list of slices. * bad_disks - a pointer to a list of bad/unusable disks updated * with any bad disks that were detected * useable_disks - a pointer to a list of usable disks * useable_slices - a pointer to a list of usable slices * * RETURNS: int - 0 on success * !0 otherwise. * * PURPOSE: Scans the disks in the local set to determine which are * usable during layout processing. * * Determines which are usable by layout using usages detected * by libdiskmgt. */ static int generate_usable_disks_and_slices_in_local_set( dlist_t **classes, dlist_t **bad_slices, dlist_t **usable_disks, dlist_t **usable_slices) { char *dsname = MD_LOCAL_NAME; dlist_t *disks; dlist_t *iter; int error; /* Get disks in local set */ error = get_disks_in_diskset(dsname, &disks); if (error != 0) { return (error); } /* For each disk in this set... */ for (iter = disks; iter != NULL && error == 0; iter = iter->next) { dm_descriptor_t disk = (uintptr_t)iter->obj; dlist_t *slices; /* Get slices on this disk */ error = disk_get_slices(disk, &slices); if (error == 0) { dlist_t *iter2; /* * Assume disk is available until a bad or unavailable * slice is found */ boolean_t avail = B_TRUE; boolean_t bad_disk = B_FALSE; /* For each slice on this disk... */ for (iter2 = slices; iter2 != NULL && error == 0 && avail == B_TRUE && bad_disk == B_FALSE; iter2 = iter2->next) { dm_descriptor_t slice = (uintptr_t)iter2->obj; dlist_t *bad_slices_on_this_disk = NULL; /* Is this slice available? */ error = check_slice_usage(dsname, slice, disk, &avail, &bad_slices_on_this_disk, classes); /* Is the slice bad (inaccessible)? */ if (error != 0 && bad_slices_on_this_disk != NULL) { bad_disk = B_TRUE; *bad_slices = dlist_append_list( *bad_slices, bad_slices_on_this_disk); } } /* Is the disk available? */ if (error == 0 && bad_disk == B_FALSE && avail == B_TRUE) { error = dlist_append_object( (void *)disk, usable_disks, AT_TAIL); } dlist_free_items(slices, NULL); } } dlist_free_items(disks, NULL); if (error == 0) { /* BEGIN CSTYLED */ /* * Now reslice usable disks in the local set to * simulate the slices they'll have when they're added * to the named disk set, and add these resulting * virtual slices to the list of available slices. */ /* END CSTYLED */ error = generate_virtual_slices(*usable_disks, usable_slices); } return (error); } /* * FUNCTION: generate_virtual_slices(dlist_t *unused, dlist_t **usable) * * INPUT: slice_classes - a list of unused slice dm_descriptor_t handles. * * OUTPUT: usable - pointer to the list of usable slices, updated * with any created virtual slices. * * RETURNS: int - 0 on success * !0 otherwise. * * PURPOSE: Helper which creates virtual slices for each disk which * could be added to a diskset if necessary... * * Search the input list of slice classes for the entry * containing slices known to be available for use by layout. * * Iterate the list of unused slices and determine the set * of unique disks. * * For each unique disk, create virtual slice descriptors to * represent those that will exist if/when the disk is added * to the diskset. * * Add theese virtual slices to the list of usable slices. */ static int generate_virtual_slices( dlist_t *avail_disks_local_set, dlist_t **usable) { dlist_t *iter = NULL; int error = 0; /* generate virtual slices */ error = create_virtual_slices(avail_disks_local_set); if (error == 0) { get_virtual_slices(&iter); for (; (iter != NULL) && (error == 0); iter = iter->next) { dlist_t *item = dlist_new_item((void *) iter->obj); if (item == NULL) { error = ENOMEM; } else { *usable = dlist_insert_ordered(item, *usable, ASCENDING, compare_desc_display_names); } } } return (error); } /* * FUNCTION: generate_usable_disks_and_slices_in_named_set(char *dsname, * dlist_t **classes, dlist_t **bad_slices, * dlist_t **usable_slices, dlist_t **usable_disks) * * INPUT: dsname - a char * diskset name. * * OUTPUT: classes - pointer to a list of slice_class_t structs, * updated to include slices in the disk set with * known uses. * bad_slices - pointer to a list of bad/unusable slices, * updated to include slices in the disk set that * are inaccessible or no longer existent. * usable_slices - pointer to a list of usable slices in the * disk set. * usable_disks - pointer to a list of usable disks in the * disk set. * * RETURNS: int - 0 on success * !0 otherwise. * * PURPOSE: 1. determine the disks in the named disk set * 2. determine the used slices on the disks * 3. determine the unused slices on the disks * 4. look for unused space on the disks and collect it * into an existing unused slice, or create a new * virtual slice. */ static int generate_usable_disks_and_slices_in_named_set( char *dsname, dlist_t **classes, dlist_t **bad_slices, dlist_t **usable_disks, dlist_t **usable_slices) { dlist_t *disks = NULL; dlist_t *iter = NULL; int error = 0; error = get_disks_in_diskset(dsname, &disks); if (error != 0) { return (error); } /* For each disk... */ for (iter = disks; iter != NULL && error == 0; iter = iter->next) { dm_descriptor_t disk = (uintptr_t)iter->obj; dlist_t *iter2; dlist_t *slices = NULL; dlist_t *bad_slices_on_this_disk = NULL; dlist_t *used_slices_on_this_disk = NULL; dlist_t *unused_slices_on_this_disk = NULL; boolean_t bad_disk = B_FALSE; error = disk_get_slices(disk, &slices); if (error != 0) { break; } /* Determine the used, unused, and bad slices on the disk */ /* For each slice... */ for (iter2 = slices; iter2 != NULL && error == 0 && bad_disk == B_FALSE; iter2 = iter2->next) { dm_descriptor_t slice = (uintptr_t)iter2->obj; boolean_t rsvd = B_FALSE; boolean_t avail = B_FALSE; /* Get slice usage */ if (((error = is_reserved_slice(slice, &rsvd)) == 0) && ((error = check_slice_usage(dsname, slice, disk, &avail, &bad_slices_on_this_disk, classes)) == 0)) { /* Is the slice bad (inaccessible)? */ if (bad_slices_on_this_disk != NULL) { *bad_slices = dlist_append_list( *bad_slices, bad_slices_on_this_disk); /* * Since one slice on this disk is bad, don't * use any slices on this disk */ bad_disk = B_TRUE; } else { dlist_t *item = dlist_new_item((void *)slice); if (item == NULL) { error = ENOMEM; } else { /* Add slice to used/unused list as appropriate */ if (avail == B_TRUE && rsvd == B_FALSE) { unused_slices_on_this_disk = dlist_append( item, unused_slices_on_this_disk, AT_TAIL); } else { used_slices_on_this_disk = dlist_insert_ordered(item, used_slices_on_this_disk, ASCENDING, compare_start_blocks); } } } } } /* Done iterating slices */ if (error == 0 && bad_disk == B_FALSE) { /* For each unused slice... */ for (iter2 = unused_slices_on_this_disk; iter2 != NULL && error == 0; iter2 = iter2->next) { dm_descriptor_t slice = (uintptr_t)iter2->obj; error = update_slice_attributes(slice, 0, 0, 0); /* Only do this once */ if (error == 0 && iter2 == unused_slices_on_this_disk) { error = add_modified_disk(NULL, disk); } } if (error == 0) { /* Create usable slices from the used/unused slice lists */ error = create_usable_slices(disk, used_slices_on_this_disk, unused_slices_on_this_disk, usable_slices); if (error == 0) { error = dlist_append_object( (void *)disk, usable_disks, AT_TAIL); } } } dlist_free_items(slices, NULL); dlist_free_items(used_slices_on_this_disk, NULL); dlist_free_items(unused_slices_on_this_disk, NULL); } return (error); } /* * FUNCTION: create_usable_slices(dm_descriptor_t disk, dlist_t *used, * dlist_t *unused, dlist_t **usable); * * INPUT: disk - a dm_descriptor_t disk handle * used - pointer to a list of pvt_t structs * representing existing used slices * on the input disk. * unused - pointer to a list of pvt_t structs * representing existing unused slices * on the input disk. * * OUTPUT: usable - pointer to a list of pvts representing slices * which can be used for new volume layouts. * * Slices in this list have any available space on the * disk collected into the fewest, lowest indexed slices * possible. * * RETURNS: int - 0 on success * !0 otherwise. * * PURPOSE: helper for generate_usable_slices_and_disks_in_diskset() which * turns any detected free space on the input disk into one or * more slices. */ static int create_usable_slices( dm_descriptor_t disk, dlist_t *used, dlist_t *unused, dlist_t **usable) { dlist_t *iter; int error = 0; boolean_t first = B_TRUE; dlist_t *next_unused = unused; char *dname = NULL; uint64_t disk_firstblk = 0; uint64_t disk_nblks = 0; uint64_t disk_endblk = 0; oprintf(OUTPUT_DEBUG, gettext("\n create_usable_slices for disk\n")); /* get necessary info about disk: */ error = get_display_name(disk, &dname); if (error != 0) { return (error); } /* disk start block is first usable block */ error = disk_get_start_block(disk, &disk_firstblk); if (error != 0) { return (error); } /* disk size determines last usable disk block */ error = disk_get_size_in_blocks(disk, &disk_nblks); if (error != 0) { return (error); } disk_endblk = disk_firstblk + disk_nblks - 1; /* search for gaps before, between and after used slices */ for (iter = used; iter != NULL && error == 0; iter = iter->next) { dm_descriptor_t cur = (uintptr_t)iter->obj; uint64_t cur_stblk = 0; uint64_t cur_nblks = 0; uint64_t cur_endblk = 0; uint32_t cur_index = 0; uint64_t new_stblk = 0; uint64_t new_endblk = 0; char *sname = NULL; (void) get_display_name(cur, &sname); if (((error = slice_get_index(cur, &cur_index)) != 0) || ((error = slice_get_start_block(cur, &cur_stblk)) != 0) || ((error = slice_get_size_in_blocks(cur, &cur_nblks)) != 0)) { continue; } cur_endblk = cur_stblk + cur_nblks - 1; oprintf(OUTPUT_DEBUG, gettext(" used slice %d (%10llu to %10llu)\n"), cur_index, cur_stblk, cur_endblk); if (first == B_TRUE) { /* first slice: make sure it starts at disk_firstblk */ first = B_FALSE; if (cur_stblk != disk_firstblk) { /* close gap at beginning of disk */ new_stblk = disk_firstblk; new_endblk = cur_stblk - 1; oprintf(OUTPUT_DEBUG, gettext(" unused space before first " "used slice\n")); } } if (iter->next != NULL) { /* check for gap between slices */ dm_descriptor_t next = (uintptr_t)iter->next->obj; uint64_t next_stblk = 0; uint32_t next_index = 0; if (((error = slice_get_start_block(next, &next_stblk)) == 0) && ((error = slice_get_index(next, &next_index)) == 0)) { if (cur_endblk != next_stblk - 1) { /* close gap between slices */ new_stblk = cur_endblk + 1; new_endblk = next_stblk - 1; oprintf(OUTPUT_DEBUG, gettext(" unused space between slices " "%d and %d\n"), cur_index, next_index); } } } else { /* last slice: make sure it includes last block on disk */ if (cur_endblk != disk_endblk) { /* close gap at end of disk */ new_stblk = cur_endblk + 1; new_endblk = disk_endblk; oprintf(OUTPUT_DEBUG, gettext(" unused space after last slice " "cur_endblk: %llu disk_endblk: %llu\n"), cur_endblk, disk_endblk); } } if ((error == 0) && (new_endblk != 0)) { error = add_new_usable(disk, new_stblk, new_endblk - new_stblk + 1, &next_unused, usable); } } if (error != 0) { dlist_free_items(*usable, free); *usable = NULL; } return (error); } /* * FUNCTION: add_new_usable(dm_descriptor_t disk, uint64_t stblk, * uint64_t nblks, dlist_t **next_unused, * dlist_t **usable); * * INPUT: disk - a dm_descriptor_t disk handle * stblk - start block of the usable space * nblks - number of usable blocks * next_unused - pointer to the next unused slice * * OUTPUT: next_unused - updated pointer to the next unused slice * usable - possibly updated pointer to a list of slices on * the disk with usable space * * RETURNS: int - 0 on success * !0 otherwise. * * PURPOSE: helper for create_usable_slices() which turns free space * on the input disk into a usable slice. * * If possible an existing unused slice will be recycled * into a usable slice. If there are none, a new virtual * slice will be created. */ static int add_new_usable( dm_descriptor_t disk, uint64_t stblk, uint64_t nblks, dlist_t **next_unused, dlist_t **usable) { dm_descriptor_t new_usable = 0; int error = 0; /* try to use an existing unused slice for the usable slice */ if (*next_unused != NULL) { new_usable = (uintptr_t)((*next_unused)->obj); *next_unused = (*next_unused)->next; oprintf(OUTPUT_DEBUG, gettext("\trecyling used slice into usable slice " "start: %llu, end: %llu\n"), stblk, stblk + nblks + 1); } if (new_usable == NULL) { /* no unused slices, try to make a new virtual slice */ uint32_t index = UINT32_MAX; error = disk_get_available_slice_index(disk, &index); if ((error == 0) && (index != UINT32_MAX)) { char *dname = NULL; error = get_display_name(disk, &dname); if (error == 0) { char buf[MAXNAMELEN]; (void) snprintf(buf, MAXNAMELEN-1, "%ss%d", dname, index); error = add_virtual_slice(buf, index, 0, 0, disk); if (error == 0) { /* retrieve the virtual slice */ error = slice_get_by_name(buf, &new_usable); } } } } if ((error == 0) && (new_usable != (dm_descriptor_t)0)) { /* BEGIN CSTYLED */ /* * have an unused slice, update its attributes to reflect * the usable space it represents */ /* END CSTYLED */ uint64_t disk_blksz = 0; error = disk_get_blocksize(disk, &disk_blksz); if (error == 0) { error = update_slice_attributes(new_usable, stblk, nblks, nblks * disk_blksz); if (error == 0) { error = dlist_append_object( (void *)new_usable, usable, AT_TAIL); } } } return (error); } /* * FUNCTION: update_slice_attributes(dm_descriptor_t slice, uint64_t stblk, * uint64_t nblks, uint64_t nbytes) * * INPUT: slice - a dm_descriptor_t slice handle * stblk - start block of the usable space * nblks - size of slice in blocks * nbytes - size of slice in bytes * * SIDEEFFECT: adds a modification record for the slice. * * RETURNS: int - 0 on success * !0 otherwise. * * PURPOSE: utility which updates several slice attributes in one call. */ static int update_slice_attributes( dm_descriptor_t slice, uint64_t stblk, uint64_t nblks, uint64_t nbytes) { char *sname = NULL; uint32_t index = 0; int error = 0; if ((error = get_display_name(slice, &sname)) == 0) { if ((error = slice_get_index(slice, &index)) == 0) { if ((error = slice_set_start_block(slice, stblk)) == 0) { if ((error = slice_set_size_in_blocks(slice, nblks)) == 0) { if (nblks == 0) { error = add_slice_to_remove(sname, index); } else { error = assemble_modified_slice((dm_descriptor_t)0, sname, index, stblk, nblks, nbytes, NULL); } } } } } return (error); } /* * FUNCTION: generate_usable_hbas(dlist_t *slices, * dlist_t **usable) * * INPUT: disks - a list of usable disks. * * OUTPUT: usable - a populated list of usable HBAs. * * RETURNS: int - 0 on success * !0 otherwise * * PURPOSE: Examines usable disk list and derives the list of usable HBAs. * */ static int generate_usable_hbas( dlist_t *disks, dlist_t **usable) { dlist_t *iter; int error = 0; /* * for each usable disk, follow its HBA connections and * add them to the list of usable HBAs. */ for (iter = disks; (iter != NULL) && (error == 0); iter = iter->next) { dm_descriptor_t dp = NULL; dlist_t *hbas = NULL; dlist_t *iter2 = NULL; dp = (uintptr_t)iter->obj; error = disk_get_hbas(dp, &hbas); if (error == 0) { for (iter2 = hbas; (iter2 != NULL) && (error == 0); iter2 = iter2->next) { dm_descriptor_t hba = (uintptr_t)iter2->obj; dlist_t *item = NULL; /* scan list of usable HBAs and see if known */ if (dlist_contains(*usable, (void*)hba, compare_descriptor_names) == B_TRUE) { /* known HBA, continue to next HBA/alias */ continue; } /* add this HBA to the usable list */ if ((item = dlist_new_item((void *)hba)) == NULL) { error = ENOMEM; } else { *usable = dlist_insert_ordered(item, *usable, ASCENDING, compare_desc_display_names); } } } dlist_free_items(hbas, NULL); } return (error); } /* * FUNCTION: check_slice_usage(char *dsname, dm_descriptor_t slice, * dm_descriptor_t disk, boolean_t *avail, * dlist_t **bad, dlist_t **classes) * * INPUT: dsname - a char * diskset name. * slice - a dm_descriptor_t handle for a known slices. * disk - a dm_descriptor_t handle the slice's disk. * * OUTPUT: avail - a boolean_t to hold the slice's availability. * bad - pointer to a list of bad/unusable slices, * possibly updated if the input slice * was determined to be inaccessible. * classes - pointer to a list of slice_class_t structs, * possibly updated to include the input slice * if it has a known use. * * RETURNS: int - 0 on success * !0 otherwise. * * PURPOSE: Handles the details of * determining usage and/or availability of a single slice. * * Queries the device library for the input slice's detectable * usage status. * * If the slice has a detected usage, its name is added to * the appropriate slice_class_t list in the input list of * slice classes, this is only done if verbose output was * requested. */ static int check_slice_usage( char *dsname, dm_descriptor_t slice, dm_descriptor_t disk, boolean_t *avail, dlist_t **bad, dlist_t **classes) { boolean_t online = B_FALSE; boolean_t used = B_FALSE; nvlist_t *stats = NULL; char *name = NULL; char *used_by = NULL; char *use_detail = NULL; int error = 0; *avail = B_FALSE; if (((error = get_display_name(slice, &name)) != 0) || (error = disk_get_is_online(disk, &online))) { return (error); } /* * if the disk is known to be offline, skip getting status * for the slice since it will just fail and return ENODEV. */ if (online != B_TRUE) { error = ENODEV; } else { stats = dm_get_stats(slice, DM_SLICE_STAT_USE, &error); } if (error != 0) { if (error == ENODEV) { dlist_t *item = dlist_new_item((void *)slice); oprintf(OUTPUT_TERSE, gettext("Warning: unable to get slice information " "for %s, it will not be used.\n"), name); if (item == NULL) { error = ENOMEM; } else { error = 0; *bad = dlist_insert_ordered(item, *bad, ASCENDING, compare_desc_display_names); } } else { oprintf(OUTPUT_TERSE, gettext("check_slice_usage: dm_get_stats for " "%s failed %d\n"), name, error); } return (error); } /* * check if/how the slice is currently being used, * device library provides this info in the nvpair_t list: * * stat_type is DM_SLICE_STAT_USE * used_by: string (mount, svm, lu, vxvm, fs) * used_name: string * */ if (stats != NULL) { error = get_string(stats, DM_USED_BY, &used_by); if (error != 0) { if (error == ENOENT) { used_by = NULL; error = 0; } else { oprintf(OUTPUT_TERSE, gettext("check_slice_usage: dm_get_stats.%s for " "%s failed %d\n"), DM_USED_BY, name, error); } } if (error == 0) { error = get_string(stats, DM_USED_NAME, &use_detail); if (error != 0) { if (error == ENOENT) { use_detail = NULL; error = 0; } else { oprintf(OUTPUT_TERSE, gettext("check_slice_usage: " "dm_get_stats.%s for " "%s failed %d\n"), DM_USED_NAME, name, error); } } } } if ((error == 0) && (used_by != NULL) && (used_by[0] != '\0')) { /* was detected usage SVM? */ if (string_case_compare(used_by, DM_USE_SVM) == 0) { /* check use_detail, it is in the form diskset:name */ if (strncmp("diskset:", use_detail, 8) == 0) { /* check disk set name */ char *str = strrchr(use_detail, ':'); if ((str != NULL) && (string_case_compare(str+1, dsname) == 0)) { /* slice in the right diskset */ error = check_svm_slice_usage( dsname, slice, disk, &used, classes); } else { /* slice in other diskset */ save_slice_classification( dsname, slice, disk, used_by, use_detail, classes); used = B_TRUE; } } else { /* slice is volume component */ save_slice_classification( dsname, slice, disk, used_by, use_detail, classes); used = B_TRUE; } } else { /* save usage */ save_slice_classification( dsname, slice, disk, used_by, use_detail, classes); used = B_TRUE; } } nvlist_free(stats); if (error == 0) { if (used == B_TRUE) { *avail = B_FALSE; } else { *avail = B_TRUE; } } return (error); } /* * FUNCTION: check_svm_slice_usage(char *dsname, dm_descriptor_t slice, * dm_descriptor_t disk, boolean_t *used, * dlist_t **classes) * * INPUT: dsname - a char * diskset name. * slice - a dm_descriptor_t handle for a known slices. * disk - a dm_descriptor_t handle the slice's disk. * * OUTPUT: used - a boolean_t to hold the slice usage status. * classes - pointer to a list of slice_class_t possibly updated * with the input slice's SVM specific usage * classification. * * RETURNS: int - 0 on success * !0 otherwise. * * PURPOSE: Handles the finer details of * a single slice is being used in the context of SVM. * * Currently, one thing is checked: * * 1. determine if the slice is reserved for metadb replicas. * The convention for disks in disksets is that a single slice * (index 6 or 7) is set aside for metadb replicas. * * If this condition does not hold, the slice is considered * available for use by layout and 'used' is set to B_FALSE. */ static int check_svm_slice_usage( char *dsname, dm_descriptor_t slice, dm_descriptor_t disk, boolean_t *used, dlist_t **classes) { boolean_t is_replica = B_FALSE; uint32_t index = 0; char *diskname = NULL; int error = 0; ((error = slice_get_index(slice, &index)) != 0) || (error = get_display_name(disk, &diskname)) || (error = is_reserved_replica_slice_index( dsname, diskname, index, &is_replica)); if (error == 0) { if (is_replica == B_TRUE) { /* is replica slice -> used */ save_slice_classification(dsname, slice, disk, DM_USE_SVM, gettext("reserved for metadb replicas"), classes); *used = B_TRUE; } else { *used = B_FALSE; } } return (error); } /* * FUNCTION: save_slice_classification(char *dsname, dm_descriptor_t slice, * dm_descriptor_t disk, char *used_by, char *usage_detail, * dlist_t **classes) * * INPUT: dsname - a char * disk set name * slice - a dm_descriptor_t slice handle. * disk - a dm_descriptor_t handle for the slice's disk. * used_by - a char * usage classification. * usage_detail - a char * usage description for the slice. * * OUTPUT: classes - a list of slice_class_t updated to hold a usage * entry for the input slicexs. * * SIDEEFFECT: adds the input slice to the list of known, used slices. * * RETURNS: int - 0 on success * !0 otherwise. * * PURPOSE: Adds an entry to the * appropriate slice_class_t list of slices. If there is * not an appropriate slice_class_t entry in the input list * of classes, one is added. * * As a performance optimization the slice usage classification * information is only saved if verbose output was requested by * the user. */ static int save_slice_classification( char *dsname, dm_descriptor_t slice, dm_descriptor_t disk, char *usage, char *usage_detail, dlist_t **classes) { int error = 0; error = add_used_slice(slice); if ((error == 0) && (get_max_verbosity() >= OUTPUT_VERBOSE)) { dlist_t *iter; dlist_t *item; slice_class_t *class = NULL; /* locate class struct matching 'usage' */ for (iter = *classes; iter != NULL; iter = iter->next) { class = (slice_class_t *)iter->obj; if (string_case_compare(usage, class->usage) == 0) { break; } } if (iter == NULL) { /* add a new class to the list of classes */ class = (slice_class_t *)calloc(1, sizeof (slice_class_t)); if (class == NULL) { error = ENOMEM; } else { class->usage = strdup(usage); if (class->usage == NULL) { free(class); class = NULL; error = ENOMEM; } else { item = dlist_new_item((void *)class); if (item == NULL) { free(class->usage); free(class); class = NULL; error = ENOMEM; } else { *classes = dlist_append(item, *classes, AT_TAIL); } } } } if ((error == 0) && (class != NULL)) { char buf[BUFSIZ]; char *dup = NULL; char *slicename = NULL; (void) get_display_name(slice, &slicename); (void) snprintf(buf, BUFSIZ-1, " %s: %s", slicename, usage_detail); if ((dup = strdup(buf)) == NULL) { error = ENOMEM; } else { if ((item = dlist_new_item((void *)dup)) == NULL) { free(dup); error = ENOMEM; } else { class->sliceinfo = dlist_insert_ordered( item, class->sliceinfo, ASCENDING, compare_strings); } } } } return (error); } /* * FUNCTION: print_usable_devices() * * PURPOSE: Print out the devices determined to be available for * use by layout. * * Iterates the lists of usable slices, disks and HBAs * and prints out their CTD and device names. */ static void print_usable_devices() { int i = 0; struct { char *msg; dlist_t *list; } devs[3]; devs[0].msg = gettext("HBA/Controllers"); devs[0].list = _usable_hbas; devs[1].msg = gettext("disks"); devs[1].list = _usable_disks; devs[2].msg = gettext("slices"); devs[2].list = _usable_slices; for (i = 0; i < 3; i++) { oprintf(OUTPUT_VERBOSE, gettext("\n These %s are usable:\n\n"), devs[i].msg); print_device_list(devs[i].list); } } /* * FUNCTION: print_device_list(dlist_t *devices) * * INPUT: devices - a list of device descriptor handles * * PURPOSE: A helper for the print_XXX_devices() routines which iterates * the input list and prints out each device name, CTD name and * alias(es). */ static void print_device_list( dlist_t *devices) { dlist_t *iter = NULL; for (iter = devices; iter != NULL; iter = iter->next) { dm_descriptor_t device = ((uintptr_t)iter->obj); char *name = NULL; char *ctd = NULL; dlist_t *aliases = NULL; (void) get_display_name(device, &ctd); (void) get_name(device, &name); oprintf(OUTPUT_VERBOSE, " %-25s %s\n", (ctd != NULL ? ctd : ""), name); (void) get_aliases(device, &aliases); for (; aliases != NULL; aliases = aliases->next) { oprintf(OUTPUT_VERBOSE, gettext(" (alias: %s)\n"), (char *)aliases->obj); } dlist_free_items(aliases, free); } } /* * FUNCTION: print_unusable_devices( * dlist_t *bad_slices, dlist_t *bad_disks, * dlist_t *used_classes) * * INPUT: used_classes - a list of slice_class_t structs * * PURPOSE: Print out the devices determined to be unavailable for * use by layout. * * Iterates the input list of slice classifications and prints * out a description of the class and the slices so classified. * * Also iterates the lists of bad slices and disks (those that * libdiskmgt returned descriptors for but cannot be accessed) * and notes them as unusable. */ static void print_unusable_devices( dlist_t *bad_slices, dlist_t *bad_disks, dlist_t *used_classes) { dlist_t *iter = NULL; dlist_t *slices = NULL; char *preamble; struct { char *msg; dlist_t *list; } devs[2]; /* report bad disks and slices */ devs[0].msg = gettext("disks"); devs[0].list = bad_disks; devs[1].msg = gettext("slices"); devs[1].list = bad_slices; if (bad_disks != NULL) { oprintf(OUTPUT_VERBOSE, #if defined(sparc) gettext("\n These disks are not usable, they may " "may be offline or cannot be accessed:\n\n")); #elif defined(i386) gettext("\n These disks are not usable, they may " "may be offline,\n missing a Solaris FDISK " "partition or cannot be accessed:\n\n")); #endif print_device_list(bad_disks); } if (bad_slices != NULL) { oprintf(OUTPUT_VERBOSE, gettext( "\n These slices, and subsequently the disks on which they\n" "reside, are not usable, they cannot be accessed:\n\n")); print_device_list(bad_slices); } /* report used slices and usages */ preamble = gettext("\n These slices are not usable, %s:\n\n"); for (iter = used_classes; iter != NULL; iter = iter->next) { slice_class_t *class = (slice_class_t *)iter->obj; if (class->sliceinfo != NULL) { oprintf(OUTPUT_VERBOSE, preamble, get_slice_usage_msg(class->usage)); slices = class->sliceinfo; for (; slices != NULL; slices = slices->next) { oprintf(OUTPUT_VERBOSE, " %s\n", (char *)slices->obj); } } } } /* * FUNCTION: char * get_slice_usage_msg(char *usage) * * INPUT: usage - char * string representing a slice usage classification * * OUTPUT: char * "friendly" usage message * * PURPOSE: the input usage string comes from libdiskmgt and is very terse. * * Convert it into a friendlier usage description suitable for user * consumption. */ static char * get_slice_usage_msg( char *usage) { char *str = NULL; if (string_case_compare(usage, DM_USE_MOUNT) == 0) { str = gettext("they have mounted filesystems"); } else if (string_case_compare(usage, DM_USE_FS) == 0) { str = gettext("they appear to have unmounted filesystems"); } else if (string_case_compare(usage, DM_USE_SVM) == 0) { str = gettext("they are utilized by SVM"); } else if (string_case_compare(usage, DM_USE_VXVM) == 0) { str = gettext("they are utilized by VxVm"); } else if (string_case_compare(usage, DM_USE_LU) == 0) { str = gettext("they are utilized by LiveUpgrade"); } else if (string_case_compare(usage, DM_USE_DUMP) == 0) { str = gettext("they are reserved as dump devices"); } else if (string_case_compare(usage, USE_DISKSET) == 0) { str = gettext("they have disk set issues"); } else { /* libdiskmgt has detected a usage unknown to layout */ str = usage; } return (str); } /* * FUNCTION: set_alias(dm_descriptor_t desc, char *alias) * * INPUT: desc - a dm_descriptor_t handle. * alias - a char * alias for the device represented * by the descriptor. * * RETURNS: int - 0 on success * !0 otherwise * * PURPOSE: Adds the specified alias to the known aliases for the * device associated with the input descriptor. */ int set_alias( dm_descriptor_t desc, char *alias) { nvlist_t *attrs = NULL; char **old_aliases = NULL; char **new_aliases = NULL; uint_t nelem = 0; int error = 0; int i = 0; if ((error = get_cached_attributes(desc, &attrs)) != 0) { return (error); } if ((error = get_string_array( attrs, ATTR_DEVICE_ALIASES, &old_aliases, &nelem)) != 0) { if (error != ENOENT) { return (error); } /* no aliases yet */ error = 0; } /* add new alias */ new_aliases = (char **)calloc(MAX_ALIASES, sizeof (char *)); if (new_aliases != NULL) { for (i = 0; i < nelem && i < MAX_ALIASES; i++) { char *dup = strdup(old_aliases[i]); if (dup != NULL) { new_aliases[i] = dup; } else { error = ENOMEM; } } if (error == 0) { if (i == MAX_ALIASES) { volume_set_error( gettext("Maximum number of device aliases " "(8) reached\n"), MAX_ALIASES); error = -1; } else { new_aliases[i] = alias; error = set_string_array(attrs, ATTR_DEVICE_ALIASES, new_aliases, i + 1); } } free(new_aliases); } if (error == 0) { /* cache descriptor under this alias */ error = add_cached_descriptor(alias, desc); } return (error); } /* * FUNCTION: get_aliases(dm_descriptor_t desc, dlist_t **list) * * INPUT: desc - a dm_descriptor_t handle. * * OUTPUT: list - a dlist_t list pointing to the list of * aliases associated with the device * represented by the descriptor. * * RETURNS: int - 0 on success * !0 otherwise * * PURPOSE: Retrieves aliases for the input descriptor and * appends them to the input list. * * The list of returned items must be freed by calling * dlist_free_items(list, free) */ int get_aliases( dm_descriptor_t desc, dlist_t **list) { nvlist_t *attrs = NULL; char **aliases = NULL; uint_t nelem = 0; int error = 0; int i; if ((error = get_cached_attributes(desc, &attrs)) != 0) { return (error); } if ((error = get_string_array( attrs, ATTR_DEVICE_ALIASES, &aliases, &nelem)) != 0) { if (error == ENOENT) { /* no aliases */ return (0); } } for (i = 0; i < nelem; i++) { dlist_t *item; char *dup; if ((dup = strdup(aliases[i])) == NULL) { error = ENOMEM; break; } if ((item = dlist_new_item(dup)) == NULL) { free(dup); error = ENOMEM; break; } *list = dlist_append(item, *list, AT_TAIL); } return (error); } /* * FUNCTION: compare_start_blocks( * void *obj1, void *obj2) * * INPUT: desc1 - opaque pointer to a dm_descriptor_t * desc2 - opaque pointer to a dm_descriptor_t * * RETURNS: int - <0 - if desc1.stblk < desc2.stblk * 0 - if desc1.stblk == desc2.stblk * >0 - if desc1.stblk > desc.stblk * * PURPOSE: dlist_t helper which compares the start blocks of * the two input dm_descriptor_t slice handles. */ static int compare_start_blocks( void *desc1, void *desc2) { uint64_t stblk1 = 0; uint64_t stblk2 = 0; assert(desc1 != (dm_descriptor_t)0); assert(desc2 != (dm_descriptor_t)0); (void) slice_get_start_block((uintptr_t)desc1, &stblk1); (void) slice_get_start_block((uintptr_t)desc2, &stblk2); return (stblk1 - stblk2); } /* * FUNCTION: compare_desc_display_names( * void *desc1, void *desc2) * * INPUT: desc1 - opaque pointer to a dm_descriptor_t * desc2 - opaque pointer to a dm_descriptor_t * * RETURNS: int - <0 - if desc1.name < desc2.name * 0 - if desc1.name == desc2.name * >0 - if desc1.name > desc.name * * PURPOSE: dlist_t helper which compares the CTD names of the * two input dm_descriptor_t objects. */ static int compare_desc_display_names( void *desc1, void *desc2) { char *name1 = NULL; char *name2 = NULL; assert(desc1 != (dm_descriptor_t)0); assert(desc2 != (dm_descriptor_t)0); (void) get_display_name((uintptr_t)desc1, &name1); (void) get_display_name((uintptr_t)desc2, &name2); return (string_case_compare(name1, name2)); }