root/usr/src/lib/libzfs_jni/common/libzfs_jni_diskmgt.c
/*
 * 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;
}