/* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 2015 by Delphix. All rights reserved. */ #include "libzfs_jni_diskmgt.h" #include "libzfs_jni_util.h" #include <strings.h> #include <libzfs.h> #include <sys/mnttab.h> /* * Function prototypes */ static char *get_device_name(dm_descriptor_t device, int *error); static dmgt_disk_t *get_disk(dm_descriptor_t disk, int *error); static char **get_disk_aliases(dm_descriptor_t disk, char *name, int *error); static int get_disk_online(dm_descriptor_t disk, int *error); static void remove_slice_from_list(dmgt_slice_t **slices, int index); static dmgt_slice_t **get_disk_slices(dm_descriptor_t media, const char *name, uint32_t blocksize, int *error); static dmgt_slice_t **get_disk_usable_slices(dm_descriptor_t media, const char *name, uint32_t blocksize, int *in_use, int *error); static void get_disk_size(dm_descriptor_t media, char *name, uint64_t *size, uint32_t *blocksize, int *error); static void get_slice_use(dm_descriptor_t slice, char *name, char **used_name, char **used_by, int *error); static dmgt_slice_t *get_slice( dm_descriptor_t slice, uint32_t blocksize, int *error); static void handle_error(const char *format, ...); static int slice_in_use(dmgt_slice_t *slice, int *error); static int slice_too_small(dmgt_slice_t *slice); /* * Static data */ static void (*error_func)(const char *, va_list); /* * Static functions */ static char * get_device_name(dm_descriptor_t device, int *error) { char *dup = NULL; char *name; *error = 0; name = dm_get_name(device, error); if (*error) { handle_error("could not determine name of device"); } else { dup = strdup(name); if (dup == NULL) { handle_error("out of memory"); *error = -1; } dm_free_name(name); } return (dup); } /* * Gets a dmgt_disk_t for the given disk dm_descriptor_t. * * Results: * * 1. Success: error is set to 0 and a dmgt_disk_t is returned * * 2. Failure: error is set to -1 and NULL is returned */ static dmgt_disk_t * get_disk(dm_descriptor_t disk, int *error) { dmgt_disk_t *dp; *error = 0; dp = (dmgt_disk_t *)calloc(1, sizeof (dmgt_disk_t)); if (dp == NULL) { handle_error("out of memory"); *error = -1; } else { /* Get name */ dp->name = get_device_name(disk, error); if (!*error) { /* Get aliases */ dp->aliases = get_disk_aliases(disk, dp->name, error); if (!*error) { /* Get media */ dm_descriptor_t *media = dm_get_associated_descriptors(disk, DM_MEDIA, error); if (*error != 0 || media == NULL || *media == 0) { handle_error( "could not get media from disk %s", dp->name); *error = -1; } else { /* Get size */ get_disk_size(media[0], dp->name, &(dp->size), &(dp->blocksize), error); if (!*error) { /* Get free slices */ dp->slices = get_disk_usable_slices( media[0], dp->name, dp->blocksize, &(dp->in_use), error); } dm_free_descriptors(media); } } } } if (*error) { /* Normalize error */ *error = -1; if (dp != NULL) { dmgt_free_disk(dp); dp = NULL; } } return (dp); } static char ** get_disk_aliases(dm_descriptor_t disk, char *name, int *error) { char **names = NULL; dm_descriptor_t *aliases; *error = 0; aliases = dm_get_associated_descriptors(disk, DM_ALIAS, error); if (*error || aliases == NULL) { *error = -1; handle_error("could not get aliases for disk %s", name); } else { int j; /* Count aliases */ for (j = 0; aliases[j] != 0; j++) ; names = (char **)calloc(j + 1, sizeof (char *)); if (names == NULL) { *error = -1; handle_error("out of memory"); } else { /* For each alias... */ for (j = 0; *error == 0 && aliases[j] != 0; j++) { dm_descriptor_t alias = aliases[j]; char *aname = dm_get_name(alias, error); if (*error) { handle_error("could not get alias %d " "for disk %s", (j + 1), name); } else { names[j] = strdup(aname); if (names[j] == NULL) { *error = -1; handle_error("out of memory"); } dm_free_name(aname); } } } dm_free_descriptors(aliases); } if (*error && names != NULL) { /* Free previously-allocated names */ zjni_free_array((void **)names, free); } return (names); } static int get_disk_online(dm_descriptor_t disk, int *error) { uint32_t status = 0; nvlist_t *attrs; *error = 0; attrs = dm_get_attributes(disk, error); if (*error) { handle_error("could not get disk attributes for disk"); } else { /* Try to get the status */ nvpair_t *match = zjni_nvlist_walk_nvpair( attrs, DM_STATUS, DATA_TYPE_UINT32, NULL); if (match == NULL || nvpair_value_uint32(match, &status)) { handle_error("could not get status of disk"); *error = 1; } nvlist_free(attrs); } return (status != 0); } /* * Gets the slices for the given disk. * * Results: * * 1. Success: error is set to 0 and slices are returned * * 2. Failure: error is set to -1 and NULL is returned */ static dmgt_slice_t ** get_disk_slices(dm_descriptor_t media, const char *name, uint32_t blocksize, int *error) { dm_descriptor_t *slices; dmgt_slice_t **sap = NULL; *error = 0; slices = dm_get_associated_descriptors(media, DM_SLICE, error); if (*error != 0) { handle_error("could not get slices of disk %s", name); } else { int j; int nslices = 0; /* For each slice... */ for (j = 0; *error == 0 && slices != NULL && slices[j] != 0; j++) { /* Get slice */ dmgt_slice_t *slice = get_slice(slices[j], blocksize, error); if (!*error) { dmgt_slice_t **mem = (dmgt_slice_t **)realloc(sap, (nslices + 2) * sizeof (dmgt_slice_t *)); if (mem == NULL) { handle_error("out of memory"); *error = -1; } else { sap = mem; /* NULL-terminated array */ sap[nslices] = slice; sap[nslices + 1] = NULL; nslices++; } } } dm_free_descriptors(slices); } if (*error) { /* Normalize error */ *error = -1; if (sap != NULL) { zjni_free_array((void **)sap, (zjni_free_f)dmgt_free_slice); sap = NULL; } } return (sap); } static void remove_slice_from_list(dmgt_slice_t **slices, int index) { int i; for (i = index; slices[i] != NULL; i++) { slices[i] = slices[i + 1]; } } static int slices_overlap(dmgt_slice_t *slice1, dmgt_slice_t *slice2) { uint64_t start1 = slice1->start; uint64_t end1 = start1 + slice1->size - 1; uint64_t start2 = slice2->start; uint64_t end2 = start2 + slice2->size - 1; int overlap = (start2 <= end1 && start1 <= end2); #ifdef DEBUG if (overlap) { (void) fprintf(stderr, "can't use %s: overlaps with %s\n", slice2->name, slice1->name); (void) fprintf(stderr, " 1: start: %llu - %llu\n", (unsigned long long)start1, (unsigned long long)end1); (void) fprintf(stderr, " 2: start: %llu - %llu\n", (unsigned long long)start2, (unsigned long long)end2); } #endif return (overlap); } /* * Gets the slices for the given disk. * * Results: * * 1. Success: error is set to 0 and slices are returned * * 2. Failure: error is set to -1 and NULL is returned */ static dmgt_slice_t ** get_disk_usable_slices(dm_descriptor_t media, const char *name, uint32_t blocksize, int *in_use, int *error) { dmgt_slice_t **slices = get_disk_slices(media, name, blocksize, error); if (*error) { slices = NULL; } *in_use = 0; if (slices != NULL) { int i, nslices; for (nslices = 0; slices[nslices] != NULL; nslices++) ; /* Prune slices based on use */ for (i = nslices - 1; i >= 0; i--) { dmgt_slice_t *slice = slices[i]; int s_in_use; /* * Slice at this index could be NULL if * removed in earlier iteration */ if (slice == NULL) { continue; } s_in_use = slice_in_use(slice, error); if (*error) { break; } if (s_in_use) { int j; remove_slice_from_list(slices, i); /* Disk is in use */ *in_use = 1; /* * Remove any slice that overlaps with this * in-use slice */ for (j = nslices - 1; j >= 0; j--) { dmgt_slice_t *slice2 = slices[j]; if (slice2 != NULL && slices_overlap(slice, slice2)) { remove_slice_from_list(slices, j); dmgt_free_slice(slice2); } } dmgt_free_slice(slice); } else if (slice_too_small(slice)) { remove_slice_from_list(slices, i); dmgt_free_slice(slice); } } } if (*error) { /* Normalize error */ *error = -1; if (slices != NULL) { zjni_free_array((void **)slices, (zjni_free_f)dmgt_free_slice); slices = NULL; } } return (slices); } static void get_disk_size(dm_descriptor_t media, char *name, uint64_t *size, uint32_t *blocksize, int *error) { nvlist_t *attrs; *size = 0; *error = 0; attrs = dm_get_attributes(media, error); if (*error) { handle_error("could not get media attributes from disk: %s", name); } else { /* Try to get the number of accessible blocks */ nvpair_t *match = zjni_nvlist_walk_nvpair( attrs, DM_NACCESSIBLE, DATA_TYPE_UINT64, NULL); if (match == NULL || nvpair_value_uint64(match, size)) { /* Disk is probably not labeled, get raw size instead */ match = zjni_nvlist_walk_nvpair( attrs, DM_SIZE, DATA_TYPE_UINT64, NULL); if (match == NULL || nvpair_value_uint64(match, size)) { handle_error("could not get size of disk: %s", name); *error = 1; } } if (*error == 0) { match = zjni_nvlist_walk_nvpair( attrs, DM_BLOCKSIZE, DATA_TYPE_UINT32, NULL); if (match == NULL || nvpair_value_uint32(match, blocksize)) { handle_error("could not get " "block size of disk: %s", name); *error = 1; } else { *size *= *blocksize; } } nvlist_free(attrs); } } static void get_slice_use(dm_descriptor_t slice, char *name, char **used_name, char **used_by, int *error) { /* Get slice use statistics */ nvlist_t *stats = dm_get_stats(slice, DM_SLICE_STAT_USE, error); if (*error != 0) { handle_error("could not get stats of slice %s", name); } else { *used_name = NULL; *used_by = NULL; if (stats != NULL) { char *tmp; nvpair_t *match; /* Get the type of usage for this slice */ match = zjni_nvlist_walk_nvpair( stats, DM_USED_BY, DATA_TYPE_STRING, NULL); if (match != NULL && nvpair_value_string(match, &tmp) == 0) { *used_name = strdup(tmp); if (*used_name == NULL) { *error = -1; handle_error("out of memory"); } else { /* Get the object using this slice */ match = zjni_nvlist_walk_nvpair(stats, DM_USED_NAME, DATA_TYPE_STRING, NULL); if (match != NULL && nvpair_value_string(match, &tmp) == 0) { *used_by = strdup(tmp); if (*used_by == NULL) { *error = -1; handle_error( "out of memory"); } } } } nvlist_free(stats); } } } static dmgt_slice_t * get_slice(dm_descriptor_t slice, uint32_t blocksize, int *error) { dmgt_slice_t *sp; *error = 0; sp = (dmgt_slice_t *)calloc(1, sizeof (dmgt_slice_t)); if (sp == NULL) { *error = -1; handle_error("out of memory"); } else { /* Get name */ sp->name = get_device_name(slice, error); if (!*error) { nvlist_t *attrs = dm_get_attributes(slice, error); if (*error) { handle_error("could not get " "attributes from slice: %s", sp->name); } else { /* Get the size in blocks */ nvpair_t *match = zjni_nvlist_walk_nvpair( attrs, DM_SIZE, DATA_TYPE_UINT64, NULL); uint64_t size_blocks; sp->size = 0; if (match == NULL || nvpair_value_uint64(match, &size_blocks)) { handle_error("could not get " "size of slice: %s", sp->name); *error = 1; } else { uint64_t start_blocks; /* Convert to bytes */ sp->size = blocksize * size_blocks; /* Get the starting block */ match = zjni_nvlist_walk_nvpair( attrs, DM_START, DATA_TYPE_UINT64, NULL); if (match == NULL || nvpair_value_uint64(match, &start_blocks)) { handle_error( "could not get " "start block of slice: %s", sp->name); *error = 1; } else { /* Convert to bytes */ sp->start = blocksize * start_blocks; /* Set slice use */ get_slice_use(slice, sp->name, &(sp->used_name), &(sp->used_by), error); } } } } } if (*error && sp != NULL) { dmgt_free_slice(sp); } return (sp); } static void handle_error(const char *format, ...) { va_list ap; va_start(ap, format); if (error_func != NULL) { error_func(format, ap); } va_end(ap); } /* Should go away once 6285992 is fixed */ static int slice_too_small(dmgt_slice_t *slice) { /* Check size */ if (slice->size < SPA_MINDEVSIZE) { #ifdef DEBUG (void) fprintf(stderr, "can't use %s: slice too small: %llu\n", slice->name, (unsigned long long)slice->size); #endif return (1); } return (0); } static int slice_in_use(dmgt_slice_t *slice, int *error) { char *msg = NULL; int in_use; /* Determine whether this slice could be passed to "zpool -f" */ in_use = dm_inuse(slice->name, &msg, DM_WHO_ZPOOL_FORCE, error); if (*error) { handle_error("%s: could not determine usage", slice->name); } #ifdef DEBUG if (in_use) { (void) fprintf(stderr, "can't use %s: used name: %s: used by: %s\n message: %s\n", slice->name, slice->used_name, slice->used_by, msg); } #endif if (msg != NULL) { free(msg); } return (in_use); } /* * Extern functions */ /* * Iterates through each available disk on the system. For each free * dmgt_disk_t *, runs the given function with the dmgt_disk_t * as * the first arg and the given void * as the second arg. */ int dmgt_avail_disk_iter(dmgt_disk_iter_f func, void *data) { int error = 0; int filter[] = { DM_DT_FIXED, -1 }; /* Search for fixed disks */ dm_descriptor_t *disks = dm_get_descriptors(DM_DRIVE, filter, &error); if (error) { handle_error("unable to communicate with libdiskmgt"); } else { int i; /* For each disk... */ for (i = 0; disks != NULL && disks[i] != 0; i++) { dm_descriptor_t disk = (dm_descriptor_t)disks[i]; int online; /* Reset error flag for each disk */ error = 0; /* Is this disk online? */ online = get_disk_online(disk, &error); if (!error && online) { /* Get a dmgt_disk_t for this dm_descriptor_t */ dmgt_disk_t *dp = get_disk(disk, &error); if (!error) { /* * If this disk or any of its * slices is usable... */ if (!dp->in_use || zjni_count_elements( (void **)dp->slices) != 0) { /* Run the given function */ if (func(dp, data)) { error = -1; } dmgt_free_disk(dp); #ifdef DEBUG } else { (void) fprintf(stderr, "disk " "has no available slices: " "%s\n", dp->name); #endif } } } } dm_free_descriptors(disks); } return (error); } void dmgt_free_disk(dmgt_disk_t *disk) { if (disk != NULL) { free(disk->name); zjni_free_array((void **)disk->aliases, free); zjni_free_array((void **)disk->slices, (zjni_free_f)dmgt_free_slice); free(disk); } } void dmgt_free_slice(dmgt_slice_t *slice) { if (slice != NULL) { free(slice->name); free(slice->used_name); free(slice->used_by); free(slice); } } /* * For clients that need to capture error output. */ void dmgt_set_error_handler(void (*func)(const char *, va_list)) { error_func = func; }