root/usr/src/lib/libnwam/common/libnwam_ncp.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 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright (c) 2016, Chris Fraire <cfraire@me.com>.
 */

#include <assert.h>
#include <ctype.h>
#include <libgen.h>
#include <netdb.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <libdladm.h>
#include <libipadm.h>

#include "libnwam_impl.h"
#include <libnwam_priv.h>
#include <libnwam.h>

/*
 * Functions to support creating, modifying, destroying, querying the
 * state of and changing the state of NCP (Network Configuration Profiles)
 * and the NCUs (Network Configuration Units) that are contained in those
 * NCP objects.  An NCP is simply a container for a set of NCUs which represent
 * the datalink and interface configuration preferences for the system.
 * An NCP can consist a set of prioritized link NCUs, e.g. wired links preferred
 * over wireless, a set of manually enabled/diasbled NCUs, or a combination
 * of both. Interface NCUs inherit activation from their underlying links,
 * so if wired is preferred over wireless and a cable is plugged in,
 * the wired link NCU will be active, as will the IP interface NCU above it.
 */

/*
 * The NCU property table is used to mapping property types to property name
 * strings, their associated value types etc. The table is used for validation
 * purposes, and for commit()ing and read()ing NCUs.
 */

static nwam_error_t valid_type(nwam_value_t);
static nwam_error_t valid_class(nwam_value_t);
static nwam_error_t valid_ncp(nwam_value_t);
static nwam_error_t valid_priority_mode(nwam_value_t);
static nwam_error_t valid_ncu_activation_mode(nwam_value_t);
static nwam_error_t valid_link_autopush(nwam_value_t);
static nwam_error_t valid_link_mtu(nwam_value_t);
static nwam_error_t valid_ip_version(nwam_value_t);
static nwam_error_t valid_addrsrc_v4(nwam_value_t);
static nwam_error_t valid_addrsrc_v6(nwam_value_t);
static nwam_error_t valid_reqhost(nwam_value_t);

struct nwam_prop_table_entry ncu_prop_table_entries[] = {
        {NWAM_NCU_PROP_TYPE, NWAM_VALUE_TYPE_UINT64, B_FALSE, 1, 1, valid_type,
            "specifies the NCU type - valid values are \'datalink\' and \'ip\'",
            NWAM_FLAG_NCU_TYPE_ALL, NWAM_FLAG_NCU_CLASS_ALL},
        {NWAM_NCU_PROP_CLASS, NWAM_VALUE_TYPE_UINT64, B_FALSE, 1, 1,
            valid_class,
            "specifies the NCU class - valid values are "
            "\'phys\' and \'ip\'",
            NWAM_FLAG_NCU_TYPE_ALL, NWAM_FLAG_NCU_CLASS_ALL},
        {NWAM_NCU_PROP_PARENT_NCP, NWAM_VALUE_TYPE_STRING, B_FALSE, 1, 1,
            valid_ncp,
            "specifies the parent NCP name",
            NWAM_FLAG_NCU_TYPE_ALL, NWAM_FLAG_NCU_CLASS_ALL},
        {NWAM_NCU_PROP_ACTIVATION_MODE, NWAM_VALUE_TYPE_UINT64, B_FALSE, 1, 1,
            valid_ncu_activation_mode,
            "specifies the NCU activation mode - valid values are:\n"
            "\'prioritized\' and \'manual\'",
            NWAM_FLAG_NCU_TYPE_LINK, NWAM_FLAG_NCU_CLASS_ALL_LINK},
        {NWAM_NCU_PROP_ENABLED, NWAM_VALUE_TYPE_BOOLEAN, B_TRUE, 0, 1,
            nwam_valid_boolean,
            "specifies if manual NCU is to be enabled",
            NWAM_FLAG_NCU_TYPE_ALL, NWAM_FLAG_NCU_CLASS_ALL},
        {NWAM_NCU_PROP_PRIORITY_GROUP, NWAM_VALUE_TYPE_UINT64, B_FALSE, 0, 1,
            nwam_valid_uint64,
            "specifies the priority grouping of NCUs - lower values are "
            "prioritized, negative values are invalid",
            NWAM_FLAG_NCU_TYPE_LINK, NWAM_FLAG_NCU_CLASS_ALL_LINK},
        {NWAM_NCU_PROP_PRIORITY_MODE, NWAM_VALUE_TYPE_UINT64, B_FALSE, 0, 1,
            valid_priority_mode,
            "specifies the mode of prioritization - valid values are:\n"
            "\'exclusive\', \'shared\' and \'all\'",
            NWAM_FLAG_NCU_TYPE_LINK, NWAM_FLAG_NCU_CLASS_ALL_LINK},
        {NWAM_NCU_PROP_LINK_MAC_ADDR, NWAM_VALUE_TYPE_STRING, B_FALSE, 0, 1,
            nwam_valid_mac_addr,
            "specifies MAC address of form aa:bb:cc:dd:ee:ff for the link",
            NWAM_FLAG_NCU_TYPE_LINK, NWAM_FLAG_NCU_CLASS_ALL_LINK},
        {NWAM_NCU_PROP_LINK_AUTOPUSH, NWAM_VALUE_TYPE_STRING, B_FALSE, 0,
            NWAM_MAX_NUM_VALUES, valid_link_autopush,
            "specifies modules to autopush on link",
            NWAM_FLAG_NCU_TYPE_LINK, NWAM_FLAG_NCU_CLASS_ALL_LINK},
        {NWAM_NCU_PROP_LINK_MTU, NWAM_VALUE_TYPE_UINT64, B_FALSE, 0, 1,
            valid_link_mtu,
            "specifies MTU for link",
            NWAM_FLAG_NCU_TYPE_LINK, NWAM_FLAG_NCU_CLASS_ALL_LINK},
        {NWAM_NCU_PROP_IP_VERSION, NWAM_VALUE_TYPE_UINT64, B_FALSE, 0,
            NWAM_MAX_NUM_VALUES, valid_ip_version,
            "specifies IP versions for IP NCU - valid values are:\n"
            "\'ipv4\' and \'ipv6\'",
            NWAM_FLAG_NCU_TYPE_INTERFACE, NWAM_FLAG_NCU_CLASS_ALL_INTERFACE},
        {NWAM_NCU_PROP_IPV4_ADDRSRC, NWAM_VALUE_TYPE_UINT64, B_FALSE, 0,
            NWAM_MAX_NUM_VALUES, valid_addrsrc_v4,
            "specifies IPv4 address source(s) - valid values are:\n"
            "\'dhcp\' and \'static\'",
            NWAM_FLAG_NCU_TYPE_INTERFACE, NWAM_FLAG_NCU_CLASS_ALL_INTERFACE},
        {NWAM_NCU_PROP_IPV4_ADDR, NWAM_VALUE_TYPE_STRING, B_FALSE, 0,
            NWAM_MAX_NUM_VALUES, nwam_valid_host_v4,
            "specifies static IPv4 host address(es)",
            NWAM_FLAG_NCU_TYPE_INTERFACE, NWAM_FLAG_NCU_CLASS_ALL_INTERFACE},
        {NWAM_NCU_PROP_IPV4_DEFAULT_ROUTE, NWAM_VALUE_TYPE_STRING, B_FALSE, 0,
            1, nwam_valid_route_v4,
            "specifies per-interface default IPv4 route",
            NWAM_FLAG_NCU_TYPE_INTERFACE, NWAM_FLAG_NCU_CLASS_ALL_INTERFACE},
        {NWAM_NCU_PROP_IPV6_ADDRSRC, NWAM_VALUE_TYPE_UINT64, B_FALSE, 0,
            NWAM_MAX_NUM_VALUES, valid_addrsrc_v6,
            "specifies IPv6 address source(s) - valid values are:\n"
            "\'dhcp\', \'autoconf\' and \'static\'.\n"
            "\'dhcp\' and \'autoconf\' are mandatory values.",
            NWAM_FLAG_NCU_TYPE_INTERFACE, NWAM_FLAG_NCU_CLASS_ALL_INTERFACE},
        {NWAM_NCU_PROP_IPV6_ADDR, NWAM_VALUE_TYPE_STRING, B_FALSE, 0,
            NWAM_MAX_NUM_VALUES, nwam_valid_host_v6,
            "specifies static IPv6 host address(es)",
            NWAM_FLAG_NCU_TYPE_INTERFACE, NWAM_FLAG_NCU_CLASS_ALL_INTERFACE},
        {NWAM_NCU_PROP_IPV6_DEFAULT_ROUTE, NWAM_VALUE_TYPE_STRING, B_FALSE, 0,
            1, nwam_valid_route_v6,
            "specifies per-interface default IPv6 route",
            NWAM_FLAG_NCU_TYPE_INTERFACE, NWAM_FLAG_NCU_CLASS_ALL_INTERFACE},
        {NWAM_NCU_PROP_IP_PRIMARY, NWAM_VALUE_TYPE_BOOLEAN, B_FALSE, 0,
            1, nwam_valid_boolean,
            "specifies the status of an interface as primary for the delivery"
            " of client-wide configuration data",
            NWAM_FLAG_NCU_TYPE_INTERFACE, NWAM_FLAG_NCU_CLASS_ALL_INTERFACE},
        {NWAM_NCU_PROP_IP_REQHOST, NWAM_VALUE_TYPE_STRING, B_FALSE, 0,
            1, valid_reqhost,
            "specifies a requested hostname for the interface",
            NWAM_FLAG_NCU_TYPE_INTERFACE, NWAM_FLAG_NCU_CLASS_ALL_INTERFACE},
};

#define NWAM_NUM_NCU_PROPS      (sizeof (ncu_prop_table_entries) / \
                                sizeof (*ncu_prop_table_entries))

struct nwam_prop_table ncu_prop_table =
        { NWAM_NUM_NCU_PROPS, ncu_prop_table_entries };

nwam_error_t
nwam_ncp_get_name(nwam_ncp_handle_t ncph, char **namep)
{
        return (nwam_get_name(ncph, namep));
}

static nwam_error_t
nwam_ncp_name_to_file(const char *name, char **filename)
{
        assert(name != NULL && filename != NULL);

        if ((*filename = malloc(MAXPATHLEN)) == NULL)
                return (NWAM_NO_MEMORY);

        (void) snprintf(*filename, MAXPATHLEN, "%s%s%s%s", NWAM_CONF_DIR,
            NWAM_NCP_CONF_FILE_PRE, name, NWAM_NCP_CONF_FILE_SUF);

        return (NWAM_SUCCESS);
}

/* ARGSUSED1 */
nwam_error_t
nwam_ncp_create(const char *name, uint64_t flags, nwam_ncp_handle_t *ncphp)
{
        nwam_error_t err;
        char *ncpfile;

        if ((err = nwam_handle_create(NWAM_OBJECT_TYPE_NCP, name, ncphp))
            != NWAM_SUCCESS)
                return (err);

        /* Create empty container for NCUs */
        if ((err = nwam_ncp_name_to_file(name, &ncpfile))
            != NWAM_SUCCESS) {
                nwam_free(*ncphp);
                *ncphp = NULL;
                return (err);
        }

        if ((err = nwam_commit(ncpfile, *ncphp, flags)) != NWAM_SUCCESS) {
                nwam_free(*ncphp);
                *ncphp = NULL;
        }

        free(ncpfile);

        return (err);
}

/* Used by libnwam_files.c */
nwam_error_t
nwam_ncp_file_to_name(const char *path, char **name)
{
        char path_copy[MAXPATHLEN];
        char *filename, *suffix;

        assert(path != NULL && name != NULL);

        /* Make a copy as basename(3c) may modify string */
        (void) strlcpy(path_copy, path, MAXPATHLEN);

        if ((*name = malloc(NWAM_MAX_NAME_LEN)) == NULL)
                return (NWAM_NO_MEMORY);

        if ((filename = basename(path_copy)) == NULL) {
                free(*name);
                return (NWAM_ENTITY_INVALID);
        }

        /* Ensure filename begins/ends with right prefix/suffix */
        if (sscanf(filename, NWAM_NCP_CONF_FILE_PRE "%256[^\n]s", *name) < 1) {
                free(*name);
                return (NWAM_ENTITY_INVALID);
        }
        suffix = *name + strlen(*name) - strlen(NWAM_NCP_CONF_FILE_SUF);
        if (strstr(*name, NWAM_NCP_CONF_FILE_SUF) != suffix) {
                free(*name);
                return (NWAM_ENTITY_INVALID);
        }
        suffix[0] = '\0';

        return (NWAM_SUCCESS);
}

/* ARGSUSED1 */
nwam_error_t
nwam_ncp_read(const char *name, uint64_t flags, nwam_ncp_handle_t *ncphp)
{
        char *filename;
        nwam_error_t err;

        assert(name != NULL && ncphp != NULL);

        /* try to read the associated ncp configuration */
        if ((err = nwam_ncp_name_to_file(name, &filename)) != NWAM_SUCCESS) {
                *ncphp = NULL;
                return (err);
        }

        err = nwam_read(NWAM_OBJECT_TYPE_NCP, filename, name, flags, ncphp);
        free(filename);
        return (err);
}

static nwam_error_t
nwam_ncu_get_parent_ncp_name(nwam_ncu_handle_t ncuh, char **parentnamep)
{
        nwam_value_t parentval = NULL;
        char *parentname;
        nwam_error_t err;

        if ((err = nwam_ncu_get_prop_value(ncuh, NWAM_NCU_PROP_PARENT_NCP,
            &parentval)) != NWAM_SUCCESS ||
            (err = nwam_value_get_string(parentval, &parentname))
            != NWAM_SUCCESS ||
            (*parentnamep = strdup(parentname)) == NULL) {
                if (parentval != NULL)
                        nwam_value_free(parentval);
                *parentnamep = NULL;
                return (err);
        }
        nwam_value_free(parentval);

        return (NWAM_SUCCESS);
}

static int
nwam_ncp_copy_callback(nwam_ncu_handle_t oldncuh, void *arg)
{
        nwam_error_t err;
        nwam_ncu_handle_t newncuh = NULL;
        char *oldparent;
        char *oldfilename = NULL, *newfilename = NULL;
        nwam_ncp_handle_t newncph = (nwam_ncp_handle_t)arg;
        nwam_value_t newparentval;

        /* Get filenames for the new and old NCU's */
        if ((err = nwam_ncu_get_parent_ncp_name(oldncuh, &oldparent))
            != NWAM_SUCCESS)
                return (err);
        err = nwam_ncp_name_to_file(oldparent, &oldfilename);
        free(oldparent);
        if (err != NWAM_SUCCESS)
                return (err);
        if ((err = nwam_ncp_name_to_file(newncph->nwh_name, &newfilename))
            != NWAM_SUCCESS)
                goto fail;

        /* new NCU name (and typedname) is the same as the old name */
        if ((err = nwam_handle_create(NWAM_OBJECT_TYPE_NCU, oldncuh->nwh_name,
            &newncuh)) != NWAM_SUCCESS)
                goto fail;
        /* Duplicate the old NCU's data */
        if ((err = nwam_dup_object_list(oldncuh->nwh_data,
            &(newncuh->nwh_data))) != NWAM_SUCCESS)
                goto fail;

        /* Update the parent property for the new NCU */
        if ((err = nwam_value_create_string(newncph->nwh_name, &newparentval))
            != NWAM_SUCCESS)
                goto fail;
        err = nwam_set_prop_value(newncuh->nwh_data, NWAM_NCU_PROP_PARENT_NCP,
            newparentval);
        nwam_value_free(newparentval);
        if (err != NWAM_SUCCESS)
                goto fail;

        /* Save the new NCU */
        err = nwam_commit(newfilename, newncuh, 0);

fail:
        free(oldfilename);
        free(newfilename);
        nwam_ncu_free(newncuh);
        return (err);
}

nwam_error_t
nwam_ncp_copy(nwam_ncp_handle_t oldncph, const char *newname,
    nwam_ncp_handle_t *newncphp)
{
        nwam_ncp_handle_t ncph;
        nwam_error_t err;
        int cb_ret;

        assert(oldncph != NULL && newname != NULL && newncphp != NULL);

        /* check if newname NCP already exists */
        if (nwam_ncp_read(newname, 0,  &ncph) == NWAM_SUCCESS) {
                nwam_ncp_free(ncph);
                *newncphp = NULL;
                return (NWAM_ENTITY_EXISTS);
        }

        /* create new handle */
        if ((err = nwam_ncp_create(newname, 0, newncphp)) != NWAM_SUCCESS)
                return (err);

        err = nwam_ncp_walk_ncus(oldncph, nwam_ncp_copy_callback, *newncphp,
            NWAM_FLAG_NCU_TYPE_CLASS_ALL, &cb_ret);
        if (err != NWAM_SUCCESS) {
                /* remove the NCP even if any NCU's had already been copied */
                (void) nwam_ncp_destroy(*newncphp, 0);
                *newncphp = NULL;
                if (err == NWAM_WALK_HALTED)
                        return (cb_ret);
                else
                        return (err);
        }

        return (NWAM_SUCCESS);
}

/*
 * Convert type to flag
 */
static uint64_t
nwam_ncu_type_to_flag(nwam_ncu_type_t type)
{
        switch (type) {
        case NWAM_NCU_TYPE_LINK:
                return (NWAM_FLAG_NCU_TYPE_LINK);
        case NWAM_NCU_TYPE_INTERFACE:
                return (NWAM_FLAG_NCU_TYPE_INTERFACE);
        case NWAM_NCU_TYPE_ANY:
                return (NWAM_FLAG_NCU_TYPE_ALL);
        default:
                return (0);
        }
}

/*
 * Convert class to flag
 */
uint64_t
nwam_ncu_class_to_flag(nwam_ncu_class_t class)
{
        switch (class) {
        case NWAM_NCU_CLASS_PHYS:
                return (NWAM_FLAG_NCU_CLASS_PHYS);
        case NWAM_NCU_CLASS_IP:
                return (NWAM_FLAG_NCU_CLASS_IP);
        case NWAM_NCU_CLASS_ANY:
                return (NWAM_FLAG_NCU_CLASS_ALL);
        default:
                return (0);
        }
}

/*
 * Infer NCU type from NCU class
 */
nwam_ncu_type_t
nwam_ncu_class_to_type(nwam_ncu_class_t class)
{
        switch (class) {
        case NWAM_NCU_CLASS_PHYS:
                return (NWAM_NCU_TYPE_LINK);
        case NWAM_NCU_CLASS_IP:
                return (NWAM_NCU_TYPE_INTERFACE);
        case NWAM_NCU_CLASS_ANY:
                return (NWAM_NCU_TYPE_ANY);
        default:
                return (NWAM_NCU_TYPE_UNKNOWN);
        }
}

/*
 * Make ncp active, deactivating any other active ncp.
 */
nwam_error_t
nwam_ncp_enable(nwam_ncp_handle_t ncph)
{
        nwam_error_t err;
        char *name;

        assert(ncph != NULL);

        err = nwam_enable(NULL, ncph);

        if (err == NWAM_ERROR_BIND) {
                /*
                 * nwamd is not running, set active_ncp property so when
                 * nwamd is next started, this NCP will be used.
                 */
                if ((err = nwam_ncp_get_name(ncph, &name)) != NWAM_SUCCESS)
                        return (err);

                err = nwam_set_smf_string_property(NWAM_FMRI, NWAM_PG,
                    NWAM_PROP_ACTIVE_NCP, name);
                free(name);
        }

        return (err);
}

/* Compare NCP names c1 and c2 using strcasecmp() */
static int
ncpname_cmp(const void *c1, const void *c2)
{
        return (strcasecmp(*(const char **)c1, *(const char **)c2));
}

/* ARGSUSED1 */
nwam_error_t
nwam_walk_ncps(int (*cb)(nwam_ncp_handle_t, void *), void *data,
    uint64_t flags, int *retp)
{
        char *ncpname, **ncpfiles;
        nwam_ncp_handle_t ncph;
        nwam_error_t err;
        nwam_value_t value;
        void *objlist;
        uint_t i, num_ncpfiles;
        int ret = 0;

        assert(cb != NULL);

        if ((err = nwam_valid_flags(flags, NWAM_FLAG_BLOCKING)) != NWAM_SUCCESS)
                return (err);
        /*
         * To get list of NCP files, call nwam_read_object_from_backend()
         * with "parent" argument set to NULL. We get back an object list
         * consisting of string arrays for each object type - NCP, ENM
         * and location. We retrieve the NCP list, which corresponds to
         * the set of NCP backend parent objects (these are files at present).
         */
        if ((err = nwam_read_object_from_backend(NULL, NULL, flags,
            &objlist)) != NWAM_SUCCESS)
                return (err);

        if ((err = nwam_get_prop_value(objlist, NWAM_NCP_OBJECT_STRING, &value))
            != NWAM_SUCCESS) {
                nwam_free_object_list(objlist);
                return (err);
        }
        if ((err = nwam_value_get_string_array(value, &ncpfiles,
            &num_ncpfiles)) != NWAM_SUCCESS) {
                nwam_value_free(value);
                nwam_free_object_list(objlist);
                return (err);
        }

        /* sort the NCP names alphabetically */
        qsort(ncpfiles, num_ncpfiles, sizeof (char *), ncpname_cmp);

        for (i = 0; i < num_ncpfiles; i++) {
                if (nwam_ncp_file_to_name(ncpfiles[i], &ncpname)
                    != NWAM_SUCCESS)
                        continue;
                if ((err = nwam_handle_create(NWAM_OBJECT_TYPE_NCP, ncpname,
                    &ncph)) != NWAM_SUCCESS) {
                        free(ncpname);
                        break;
                }
                ret = cb(ncph, data);
                free(ncph);
                free(ncpname);
                if (ret != 0) {
                        err = NWAM_WALK_HALTED;
                        break;
                }
        }
        nwam_value_free(value);
        nwam_free_object_list(objlist);

        if (retp != NULL)
                *retp = ret;
        return (err);
}

/*
 * Checks if NCP is read-only.  Only NWAM_NCP_NAME_AUTOMATIC is read-only
 * for all but the netadm user (which nwamd runs as).
 */
nwam_error_t
nwam_ncp_get_read_only(nwam_ncp_handle_t ncph, boolean_t *readp)
{
        nwam_error_t err;
        char *name;

        assert(ncph != NULL && readp != NULL);

        if ((err = nwam_ncp_get_name(ncph, &name)) != NWAM_SUCCESS)
                return (err);

        if (NWAM_NCP_AUTOMATIC(name))
                *readp = !nwam_uid_is_special();
        else
                *readp = B_FALSE;

        free(name);
        return (NWAM_SUCCESS);
}

/* Checks if NCU is writable depending on its parent */
nwam_error_t
nwam_ncu_get_read_only(nwam_ncu_handle_t ncuh, boolean_t *readp)
{
        nwam_error_t err;
        nwam_ncp_handle_t ncph;

        assert(ncuh != NULL && readp != NULL);

        if ((err = nwam_ncu_get_ncp(ncuh, &ncph)) != NWAM_SUCCESS)
                return (err);

        err = nwam_ncp_get_read_only(ncph, readp);
        nwam_ncp_free(ncph);
        return (err);
}

/* Returns true if the NCP is active */
static boolean_t
nwam_ncp_is_active(nwam_ncp_handle_t ncph)
{
        char *active_ncp, *name;
        boolean_t ret;

        assert(ncph != NULL);

        /*
         * Determine which NCP is active via the nwamd/active_ncp property
         * value.  This allows us to determine which NCP is active even
         * if nwamd is not running.
         */
        if (nwam_ncp_get_name(ncph, &name) != NWAM_SUCCESS ||
            nwam_get_smf_string_property(NWAM_FMRI, NWAM_PG,
            NWAM_PROP_ACTIVE_NCP, &active_ncp) != NWAM_SUCCESS)
                return (B_FALSE);

        ret = (strcmp(name, active_ncp) == 0);

        free(active_ncp);
        free(name);

        return (ret);
}

nwam_error_t
nwam_ncp_destroy(nwam_ncp_handle_t ncph, uint64_t flags)
{
        char *filename;
        nwam_error_t err;
        boolean_t read_only;

        assert(ncph != NULL);

        if ((err = nwam_ncp_get_read_only(ncph, &read_only)) != NWAM_SUCCESS)
                return (err);
        if (read_only)
                return (NWAM_ENTITY_NOT_DESTROYABLE);

        if (nwam_ncp_is_active(ncph))
                return (NWAM_ENTITY_IN_USE);

        if ((err = nwam_ncp_name_to_file(ncph->nwh_name, &filename))
            != NWAM_SUCCESS)
                return (err);

        err = nwam_destroy(filename, ncph, flags);
        free(filename);

        return (NWAM_SUCCESS);
}

static nwam_error_t
nwam_ncu_internal_name_to_name(const char *internalname,
    nwam_ncu_type_t *typep, char **namep)
{
        char *prefixstr;

        assert(internalname != NULL && namep != NULL);

        if (strncasecmp(internalname, NWAM_NCU_LINK_NAME_PRE,
            strlen(NWAM_NCU_LINK_NAME_PRE)) == 0) {
                prefixstr = NWAM_NCU_LINK_NAME_PRE;
                *typep = NWAM_NCU_TYPE_LINK;
        } else if (strncasecmp(internalname, NWAM_NCU_INTERFACE_NAME_PRE,
            strlen(NWAM_NCU_INTERFACE_NAME_PRE)) == 0) {
                prefixstr = NWAM_NCU_INTERFACE_NAME_PRE;
                *typep = NWAM_NCU_TYPE_INTERFACE;
        } else {
                return (NWAM_INVALID_ARG);
        }

        *namep = strdup(internalname + strlen(prefixstr));
        if (*namep == NULL)
                return (NWAM_NO_MEMORY);
        return (NWAM_SUCCESS);
}

/* ARGSUSED2 */
static int
ncu_selectcb(struct nwam_handle *hp, uint64_t flags, void *data)
{
        nwam_ncu_handle_t ncuh = hp;
        nwam_value_t typeval = NULL, classval = NULL;
        uint64_t type, class, matchflags, walkfilter;

        if (nwam_ncu_get_prop_value(ncuh, NWAM_NCU_PROP_TYPE, &typeval)
            != NWAM_SUCCESS ||
            nwam_ncu_get_prop_value(ncuh, NWAM_NCU_PROP_CLASS, &classval)
            != NWAM_SUCCESS) {
                if (typeval != NULL)
                        nwam_value_free(typeval);
                return (NWAM_INVALID_ARG);
        }
        if (nwam_value_get_uint64(typeval, &type) != NWAM_SUCCESS ||
            nwam_value_get_uint64(classval, &class) != NWAM_SUCCESS) {
                nwam_value_free(typeval);
                nwam_value_free(classval);
                return (NWAM_INVALID_ARG);
        }

        matchflags = nwam_ncu_type_to_flag(type) |
            nwam_ncu_class_to_flag(class);
        nwam_value_free(typeval);
        nwam_value_free(classval);

        if ((walkfilter = (flags & NWAM_WALK_FILTER_MASK)) == 0)
                walkfilter = NWAM_FLAG_NCU_TYPE_CLASS_ALL;

        if (matchflags & walkfilter)
                return (NWAM_SUCCESS);
        return (NWAM_INVALID_ARG);
}

nwam_error_t
nwam_ncp_walk_ncus(nwam_ncp_handle_t ncph,
    int(*cb)(nwam_ncu_handle_t, void *), void *data, uint64_t flags, int *retp)
{
        char *ncpfile;
        nwam_error_t err;

        assert(ncph != NULL && cb != NULL);

        if ((err = nwam_valid_flags(flags,
            NWAM_FLAG_NCU_TYPE_CLASS_ALL | NWAM_FLAG_BLOCKING)) != NWAM_SUCCESS)
                return (err);

        if ((err = nwam_ncp_name_to_file(ncph->nwh_name, &ncpfile))
            != NWAM_SUCCESS)
                return (err);

        err = nwam_walk(NWAM_OBJECT_TYPE_NCU, ncpfile, cb, data, flags,
            retp, ncu_selectcb);
        free(ncpfile);

        return (err);
}

void
nwam_ncp_free(nwam_ncp_handle_t ncph)
{
        nwam_free(ncph);
}

/*
 * Are ncu type and class compatible?
 */
static boolean_t
nwam_ncu_type_class_compatible(nwam_ncu_type_t type, nwam_ncu_class_t class)
{
        switch (type) {
        case NWAM_NCU_TYPE_LINK:
                return (class == NWAM_NCU_CLASS_PHYS);
        case NWAM_NCU_TYPE_INTERFACE:
                return (class == NWAM_NCU_CLASS_IP);
        default:
                return (B_FALSE);
        }
}

/* Name to validate may be internal name. If so, convert it before validating */
static boolean_t
valid_ncu_name(const char *name)
{
        char *n;
        boolean_t ret;
        nwam_ncu_type_t type;

        if (nwam_ncu_internal_name_to_name(name, &type, &n) == NWAM_SUCCESS) {

                ret = dladm_valid_linkname(n);
                free(n);
        } else {
                ret = dladm_valid_linkname(name);
        }

        return (ret);
}

nwam_error_t
nwam_ncu_create(nwam_ncp_handle_t ncph, const char *name,
    nwam_ncu_type_t type, nwam_ncu_class_t class, nwam_ncu_handle_t *ncuhp)
{
        nwam_ncu_handle_t ncuh;
        nwam_value_t typeval = NULL, classval = NULL, parentval = NULL;
        nwam_value_t enabledval = NULL;
        nwam_error_t err;
        boolean_t read_only;
        char *typedname;

        assert(ncph != NULL && name != NULL && ncuhp != NULL);

        if (!valid_ncu_name(name))
                return (NWAM_INVALID_ARG);

        if ((err = nwam_ncp_get_read_only(ncph, &read_only)) != NWAM_SUCCESS)
                return (err);
        if (read_only)
                return (NWAM_ENTITY_READ_ONLY);

        if (nwam_ncu_read(ncph, name, type, 0, &ncuh) == NWAM_SUCCESS) {
                nwam_ncu_free(ncuh);
                return (NWAM_ENTITY_EXISTS);
        }

        if (!valid_ncu_name(name) ||
            !nwam_ncu_type_class_compatible(type, class))
                return (NWAM_INVALID_ARG);

        if ((err = nwam_ncu_name_to_typed_name(name, type, &typedname))
            != NWAM_SUCCESS)
                return (err);

        /* Create handle */
        if ((err = nwam_handle_create(NWAM_OBJECT_TYPE_NCU, typedname, ncuhp))
            != NWAM_SUCCESS)
                return (err);
        free(typedname);

        /*
         * Create new object list for NCU.  The new NCU is initialized with
         * the appropriate type and class.
         */
        if ((err = nwam_alloc_object_list(&(*ncuhp)->nwh_data)) != NWAM_SUCCESS)
                goto finish;

        if ((err = nwam_value_create_uint64(type, &typeval))
            != NWAM_SUCCESS ||
            (err = nwam_value_create_uint64(class, &classval))
            != NWAM_SUCCESS ||
            (err = nwam_value_create_string(ncph->nwh_name, &parentval))
            != NWAM_SUCCESS ||
            (err = nwam_value_create_boolean(B_TRUE, &enabledval))
            != NWAM_SUCCESS) {
                goto finish;
        }
        if ((err = nwam_set_prop_value((*ncuhp)->nwh_data, NWAM_NCU_PROP_TYPE,
            typeval)) != NWAM_SUCCESS ||
            (err = nwam_set_prop_value((*ncuhp)->nwh_data, NWAM_NCU_PROP_CLASS,
            classval)) != NWAM_SUCCESS ||
            (err = nwam_set_prop_value((*ncuhp)->nwh_data,
            NWAM_NCU_PROP_PARENT_NCP, parentval)) != NWAM_SUCCESS ||
            (err = nwam_set_prop_value((*ncuhp)->nwh_data,
            NWAM_NCU_PROP_ENABLED, enabledval)) != NWAM_SUCCESS) {
                goto finish;
        }

        /* Set default IP, datalink properties */
        if (type == NWAM_NCU_TYPE_INTERFACE && class == NWAM_NCU_CLASS_IP) {

                uint64_t ver[] = { IPV4_VERSION, IPV6_VERSION };
                uint64_t v6src[] = { NWAM_ADDRSRC_DHCP, NWAM_ADDRSRC_AUTOCONF };
                uint_t vercnt = 2, v6srccnt = 2;
                nwam_value_t ipver = NULL, v4addrsrc = NULL, v6addrsrc = NULL;

                if ((err = nwam_value_create_uint64_array(ver, vercnt, &ipver))
                    != NWAM_SUCCESS ||
                    (err = nwam_value_create_uint64(NWAM_ADDRSRC_DHCP,
                    &v4addrsrc)) != NWAM_SUCCESS ||
                    (err = nwam_value_create_uint64_array(v6src, v6srccnt,
                    &v6addrsrc)) != NWAM_SUCCESS) {
                        nwam_value_free(ipver);
                        nwam_value_free(v4addrsrc);
                        goto finish;
                }
                if ((err = nwam_set_prop_value((*ncuhp)->nwh_data,
                    NWAM_NCU_PROP_IP_VERSION, ipver)) == NWAM_SUCCESS &&
                    (err = nwam_set_prop_value((*ncuhp)->nwh_data,
                    NWAM_NCU_PROP_IPV4_ADDRSRC, v4addrsrc)) == NWAM_SUCCESS) {
                        err = nwam_set_prop_value((*ncuhp)->nwh_data,
                            NWAM_NCU_PROP_IPV6_ADDRSRC, v6addrsrc);
                }
                nwam_value_free(ipver);
                nwam_value_free(v4addrsrc);
                nwam_value_free(v6addrsrc);
        } else {
                nwam_value_t actval = NULL;
                if ((err = nwam_value_create_uint64(NWAM_ACTIVATION_MODE_MANUAL,
                    &actval)) != NWAM_SUCCESS)
                        goto finish;
                err = nwam_set_prop_value((*ncuhp)->nwh_data,
                    NWAM_NCU_PROP_ACTIVATION_MODE, actval);
                nwam_value_free(actval);
        }

finish:
        nwam_value_free(typeval);
        nwam_value_free(classval);
        nwam_value_free(parentval);
        nwam_value_free(enabledval);
        if (err != NWAM_SUCCESS) {
                nwam_ncu_free(*ncuhp);
                *ncuhp = NULL;
        }
        return (err);
}

nwam_error_t
nwam_ncu_read(nwam_ncp_handle_t ncph, const char *name,
    nwam_ncu_type_t type, uint64_t flags, nwam_ncu_handle_t *ncuhp)
{
        char *ncpfile, *typedname;
        nwam_error_t err, err_ip, err_link;
        nwam_ncu_handle_t ncuh_ip, ncuh_link;

        assert(ncph != NULL && name != NULL && ncuhp != NULL);

        if ((err = nwam_ncp_name_to_file(ncph->nwh_name, &ncpfile))
            != NWAM_SUCCESS)
                return (err);

        if (type == NWAM_NCU_TYPE_ANY) {

                free(ncpfile);

                /*
                 * If we get to this point, we have discovered that no
                 * NCU type is discernable from name or type arguments.
                 * Either exactly one NCU called name must exist of either
                 * type, or the operation should fail.
                 */
                err_ip = nwam_ncu_read(ncph, name, NWAM_NCU_TYPE_INTERFACE,
                    flags, &ncuh_ip);
                err_link = nwam_ncu_read(ncph, name, NWAM_NCU_TYPE_LINK,
                    flags, &ncuh_link);

                *ncuhp = NULL;

                if (err_ip == NWAM_SUCCESS && err_link == NWAM_SUCCESS) {
                        nwam_ncu_free(ncuh_ip);
                        nwam_ncu_free(ncuh_link);
                        err = NWAM_ENTITY_MULTIPLE_VALUES;
                } else if (err_ip != NWAM_SUCCESS && err_link != NWAM_SUCCESS) {
                        err = NWAM_ENTITY_NOT_FOUND;
                } else {
                        if (err_ip == NWAM_SUCCESS) {
                                *ncuhp = ncuh_ip;
                        } else {
                                *ncuhp = ncuh_link;
                        }
                        err = NWAM_SUCCESS;
                }

                return (err);
        }
        if ((err = nwam_ncu_name_to_typed_name(name, type, &typedname)) !=
            NWAM_SUCCESS) {
                free(ncpfile);
                return (err);
        }
        err = nwam_read(NWAM_OBJECT_TYPE_NCU, ncpfile, typedname, flags, ncuhp);

        free(typedname);
        free(ncpfile);

        return (err);
}

nwam_error_t
nwam_ncu_get_name(nwam_ncu_handle_t ncuh, char **namep)
{
        nwam_ncu_type_t type;

        assert(ncuh != NULL && namep != NULL);

        return (nwam_ncu_internal_name_to_name(ncuh->nwh_name, &type, namep));
}

nwam_error_t
nwam_ncu_name_to_typed_name(const char *name, nwam_ncu_type_t type,
    char **typednamep)
{
        char *prefixstr;
        size_t typednamesz;

        assert(name != NULL && typednamep != NULL);

        switch (type) {
        case NWAM_NCU_TYPE_INTERFACE:
                prefixstr = NWAM_NCU_INTERFACE_NAME_PRE;
                break;
        case NWAM_NCU_TYPE_LINK:
                prefixstr = NWAM_NCU_LINK_NAME_PRE;
                break;
        default:
                return (NWAM_INVALID_ARG);
        }
        typednamesz = strlen(name) + strlen(prefixstr) + 1;
        if ((*typednamep = malloc(typednamesz)) == NULL)
                return (NWAM_NO_MEMORY);

        /* Name may be already qualified by type */
        if (strncasecmp(prefixstr, name, strlen(prefixstr)) == 0) {
                (void) snprintf(*typednamep, typednamesz, "%s", name);
        } else {
                (void) snprintf(*typednamep, typednamesz, "%s%s",
                    prefixstr, name);
        }

        return (NWAM_SUCCESS);
}

nwam_error_t
nwam_ncu_typed_name_to_name(const char *typed_name, nwam_ncu_type_t *typep,
    char **name)
{
        return (nwam_ncu_internal_name_to_name(typed_name, typep, name));
}

void
nwam_ncu_free(nwam_ncu_handle_t ncuh)
{
        nwam_free(ncuh);
}

nwam_error_t
nwam_ncu_copy(nwam_ncu_handle_t oldncuh, const char *newname,
    nwam_ncu_handle_t *newncuhp)
{
        nwam_ncp_handle_t ncph;
        nwam_ncu_handle_t ncuh;
        nwam_error_t err;
        nwam_value_t typeval;
        uint64_t type;
        char *typednewname;

        assert(oldncuh != NULL && newname != NULL && newncuhp != NULL);

        if (nwam_ncu_get_prop_value(oldncuh, NWAM_NCU_PROP_TYPE,
            &typeval) != NWAM_SUCCESS) {
                return (NWAM_INVALID_ARG);
        }
        if (nwam_value_get_uint64(typeval, &type) != NWAM_SUCCESS) {
                nwam_value_free(typeval);
                return (NWAM_INVALID_ARG);
        }
        nwam_value_free(typeval);

        /* check if newname NCU already exists */
        if ((err = nwam_ncu_get_ncp(oldncuh, &ncph)) != NWAM_SUCCESS)
                return (err);
        if (nwam_ncu_read(ncph, newname, type, 0, &ncuh) == NWAM_SUCCESS) {
                nwam_ncu_free(ncuh);
                nwam_ncp_free(ncph);
                return (NWAM_ENTITY_EXISTS);
        }
        nwam_ncp_free(ncph);

        if ((err = nwam_ncu_name_to_typed_name(newname, type, &typednewname))
            != NWAM_SUCCESS)
                return (err);

        err = nwam_handle_create(NWAM_OBJECT_TYPE_NCU, typednewname, newncuhp);
        free(typednewname);
        if (err != NWAM_SUCCESS)
                return (err);
        if ((err = nwam_dup_object_list(oldncuh->nwh_data,
            &((*newncuhp)->nwh_data))) != NWAM_SUCCESS) {
                free(*newncuhp);
                *newncuhp = NULL;
                return (err);
        }

        return (NWAM_SUCCESS);
}

nwam_error_t
nwam_ncu_delete_prop(nwam_ncu_handle_t ncuh, const char *propname)
{
        boolean_t ro_ncu, ro_prop;
        nwam_error_t err;
        void *olddata;

        assert(ncuh != NULL && propname != NULL);

        if ((err = nwam_ncu_get_read_only(ncuh, &ro_ncu)) != NWAM_SUCCESS ||
            (err = nwam_ncu_prop_read_only(propname, &ro_prop)) != NWAM_SUCCESS)
                return (err);
        if (ro_ncu || ro_prop)
                return (NWAM_ENTITY_READ_ONLY);

        /*
         * Duplicate data, remove property and validate. If validation
         * fails, revert to data duplicated prior to remove.
         */
        if ((err = nwam_dup_object_list(ncuh->nwh_data, &olddata))
            != NWAM_SUCCESS)
                return (err);
        if ((err = nwam_delete_prop(ncuh->nwh_data, propname))
            != NWAM_SUCCESS) {
                nwam_free_object_list(ncuh->nwh_data);
                ncuh->nwh_data = olddata;
                return (err);
        }
        if ((err = nwam_ncu_validate(ncuh, NULL)) != NWAM_SUCCESS) {
                nwam_free_object_list(ncuh->nwh_data);
                ncuh->nwh_data = olddata;
                return (err);
        }
        nwam_free_object_list(olddata);

        return (NWAM_SUCCESS);
}

nwam_error_t
nwam_ncu_set_prop_value(nwam_ncu_handle_t ncuh, const char *propname,
    nwam_value_t value)
{
        boolean_t ro_ncu, ro_prop;
        nwam_error_t err;
        nwam_ncp_handle_t ncph;

        assert(ncuh != NULL && propname != NULL && value != NULL);

        if ((err = nwam_ncu_get_read_only(ncuh, &ro_ncu)) != NWAM_SUCCESS ||
            (err = nwam_ncu_prop_read_only(propname, &ro_prop)) != NWAM_SUCCESS)
                return (err);
        if (ro_ncu || ro_prop)
                return (NWAM_ENTITY_READ_ONLY);

        err = nwam_ncu_get_ncp(ncuh, &ncph);
        if (err != NWAM_SUCCESS && err != NWAM_INVALID_ARG) {
                /*
                 * If "parent" property doesn't exist, NWAM_INVALID_ARG
                 * is returned.  Allow the setting to continue.
                 */
                return (err);
        }
        nwam_ncp_free(ncph);

        /* Need to ensure property, type and value are valid */
        if ((err = nwam_ncu_validate_prop(ncuh, propname, value))
            != NWAM_SUCCESS)
                return (err);

        return (nwam_set_prop_value(ncuh->nwh_data, propname, value));
}

nwam_error_t
nwam_ncu_get_prop_value(nwam_ncu_handle_t ncuh, const char *propname,
    nwam_value_t *valuep)
{
        assert(ncuh != NULL && propname != NULL && valuep != NULL);

        return (nwam_get_prop_value(ncuh->nwh_data, propname, valuep));
}

nwam_error_t
nwam_ncu_walk_props(nwam_ncu_handle_t ncuh,
    int (*cb)(const char *, nwam_value_t, void *),
    void *data, uint64_t flags, int *retp)
{
        return (nwam_walk_props(ncuh, cb, data, flags, retp));
}

nwam_error_t
nwam_ncu_get_ncp(nwam_ncu_handle_t ncuh, nwam_ncp_handle_t *ncphp)
{
        nwam_error_t err;
        char *parentname = NULL;

        if ((err = nwam_ncu_get_parent_ncp_name(ncuh, &parentname))
            != NWAM_SUCCESS ||
            (err = nwam_handle_create(NWAM_OBJECT_TYPE_NCP, parentname, ncphp))
            != NWAM_SUCCESS) {
                if (parentname != NULL)
                        free(parentname);
                return (err);
        }
        free(parentname);

        return (NWAM_SUCCESS);
}

nwam_error_t
nwam_ncu_commit(nwam_ncu_handle_t ncuh, uint64_t flags)
{
        nwam_error_t err;
        boolean_t read_only;
        char *ncpfile, *ncpname;

        assert(ncuh != NULL && ncuh->nwh_data != NULL);

        if ((err = nwam_ncu_get_read_only(ncuh, &read_only)) != NWAM_SUCCESS)
                return (err);
        if (read_only)
                return (NWAM_ENTITY_READ_ONLY);

        if ((err = nwam_ncu_validate(ncuh, NULL)) != NWAM_SUCCESS ||
            (err = nwam_ncu_get_parent_ncp_name(ncuh, &ncpname))
            != NWAM_SUCCESS)
                return (err);

        if ((err = nwam_ncp_name_to_file(ncpname, &ncpfile)) != NWAM_SUCCESS) {
                free(ncpname);
                return (err);
        }

        err = nwam_commit(ncpfile, ncuh, flags);

        free(ncpname);
        free(ncpfile);

        return (err);
}
/* Get the NCU type */
nwam_error_t
nwam_ncu_get_ncu_type(nwam_ncu_handle_t ncuh, nwam_ncu_type_t *typep)
{
        nwam_error_t err;
        nwam_value_t typeval;
        uint64_t type;

        if ((err = nwam_ncu_get_prop_value(ncuh, NWAM_NCU_PROP_TYPE, &typeval))
            != NWAM_SUCCESS)
                return (err);
        err = nwam_value_get_uint64(typeval, &type);
        nwam_value_free(typeval);
        if (err != NWAM_SUCCESS)
                return (err);

        *typep = type;
        return (NWAM_SUCCESS);
}

/* Get the NCU class */
nwam_error_t
nwam_ncu_get_ncu_class(nwam_ncu_handle_t ncuh, nwam_ncu_class_t *classp)
{
        nwam_error_t err;
        nwam_value_t classval;
        uint64_t class;

        if ((err = nwam_ncu_get_prop_value(ncuh, NWAM_NCU_PROP_CLASS,
            &classval)) != NWAM_SUCCESS)
                return (err);
        err = nwam_value_get_uint64(classval, &class);
        nwam_value_free(classval);
        if (err != NWAM_SUCCESS)
                return (err);

        *classp = class;
        return (NWAM_SUCCESS);
}

/*
 * Determine if the NCU has manual activation-mode or not.
 */
nwam_error_t
nwam_ncu_is_manual(nwam_ncu_handle_t ncuh, boolean_t *manualp)
{
        nwam_error_t err;
        nwam_value_t actval;
        uint64_t activation;

        assert(ncuh != NULL);

        if ((err = nwam_ncu_get_prop_value(ncuh, NWAM_NCU_PROP_ACTIVATION_MODE,
            &actval)) != NWAM_SUCCESS)
                return (err);
        err = nwam_value_get_uint64(actval, &activation);
        nwam_value_free(actval);
        if (err != NWAM_SUCCESS)
                return (err);

        if (activation == NWAM_ACTIVATION_MODE_MANUAL)
                *manualp = B_TRUE;
        else
                *manualp = B_FALSE;
        return (NWAM_SUCCESS);
}

/* Determine if NCU is enabled or not */
static nwam_error_t
nwam_ncu_is_enabled(nwam_ncu_handle_t ncuh, boolean_t *enabledp)
{
        nwam_error_t err;
        nwam_value_t enabledval;

        assert(ncuh != NULL);

        if ((err = nwam_ncu_get_prop_value(ncuh, NWAM_NCU_PROP_ENABLED,
            &enabledval)) != NWAM_SUCCESS)
                return (err);
        err = nwam_value_get_boolean(enabledval, enabledp);
        nwam_value_free(enabledval);
        return (err);
}

/* Update the enabled property */
static nwam_error_t
nwam_ncu_update_enabled(nwam_ncu_handle_t ncuh, boolean_t enabled)
{
        nwam_error_t err;
        nwam_value_t enabledval;

        if ((err = nwam_value_create_boolean(enabled, &enabledval))
            != NWAM_SUCCESS)
                return (err);
        err = nwam_set_prop_value(ncuh->nwh_data, NWAM_NCU_PROP_ENABLED,
            enabledval);
        nwam_value_free(enabledval);
        if (err != NWAM_SUCCESS)
                return (err);
        return (nwam_ncu_commit(ncuh, NWAM_FLAG_ENTITY_ENABLE));
}

/*
 * Make ncu active; fails if the NCU's parent NCP is not active.
 */
nwam_error_t
nwam_ncu_enable(nwam_ncu_handle_t ncuh)
{
        char *ncpname = NULL;
        nwam_error_t err;
        nwam_ncu_type_t type;
        boolean_t read_only, enabled, manual;

        assert(ncuh != NULL);

        /* Don't allow NCUs of Automatic NCP to be enabled */
        if ((err = nwam_ncu_get_read_only(ncuh, &read_only)) != NWAM_SUCCESS)
                return (err);
        if (read_only)
                return (NWAM_ENTITY_NOT_MANUAL);

        /* Link NCUs with manual activation-mode or IP NCUs can be enabled */
        if ((err = nwam_ncu_get_ncu_type(ncuh, &type)) != NWAM_SUCCESS)
                return (err);

        if (type == NWAM_NCU_TYPE_LINK) {
                if ((err = nwam_ncu_is_manual(ncuh, &manual)) != NWAM_SUCCESS)
                        return (err);
                if (!manual)
                        return (NWAM_ENTITY_NOT_MANUAL);
        }

        /* Make sure NCU is not enabled */
        if ((err = nwam_ncu_is_enabled(ncuh, &enabled)) != NWAM_SUCCESS ||
            (err = nwam_ncu_get_parent_ncp_name(ncuh, &ncpname))
            != NWAM_SUCCESS)
                return (err);

        if (enabled) {
                free(ncpname);
                return (NWAM_SUCCESS);
        }

        if ((err = nwam_ncu_update_enabled(ncuh, B_TRUE)) != NWAM_SUCCESS) {
                free(ncpname);
                return (err);
        }

        err = nwam_enable(ncpname, ncuh);
        free(ncpname);

        /* nwamd may not be running, that's okay. */
        if (err == NWAM_ERROR_BIND)
                return (NWAM_SUCCESS);
        else
                return (err);
}

/*
 * Disable ncu; fails if the NCU's parent NCP is not active, or if the
 * NCU is not currently active.
 */
nwam_error_t
nwam_ncu_disable(nwam_ncu_handle_t ncuh)
{
        char *ncpname = NULL;
        nwam_error_t err;
        nwam_ncu_type_t type;
        boolean_t read_only, enabled, manual;

        assert(ncuh != NULL);

        /* Don't allow NCUs of Automatic NCP to be disabled */
        if ((err = nwam_ncu_get_read_only(ncuh, &read_only)) != NWAM_SUCCESS)
                return (err);
        if (read_only)
                return (NWAM_ENTITY_NOT_MANUAL);

        /* Link NCUs with manual activation-mode or IP NCUs can be disabled */
        if ((err = nwam_ncu_get_ncu_type(ncuh, &type)) != NWAM_SUCCESS)
                return (err);

        if (type == NWAM_NCU_TYPE_LINK) {
                if ((err = nwam_ncu_is_manual(ncuh, &manual)) != NWAM_SUCCESS)
                        return (err);
                if (!manual)
                        return (NWAM_ENTITY_NOT_MANUAL);
        }

        /* Make sure NCU is enabled */
        if ((err = nwam_ncu_is_enabled(ncuh, &enabled)) != NWAM_SUCCESS ||
            (err = nwam_ncu_get_parent_ncp_name(ncuh, &ncpname))
            != NWAM_SUCCESS)
                return (err);

        if (!enabled) {
                free(ncpname);
                return (NWAM_SUCCESS);
        }

        if ((err = nwam_ncu_update_enabled(ncuh, B_FALSE)) != NWAM_SUCCESS) {
                free(ncpname);
                return (err);
        }

        err = nwam_disable(ncpname, ncuh);
        free(ncpname);

        /* nwamd may not be running, that's okay. */
        if (err == NWAM_ERROR_BIND)
                return (NWAM_SUCCESS);
        else
                return (err);
}

nwam_error_t
nwam_ncu_destroy(nwam_ncu_handle_t ncuh, uint64_t flags)
{
        char *ncpname, *ncpfile;
        boolean_t read_only;
        nwam_error_t err;

        assert(ncuh != NULL);

        if ((err = nwam_ncu_get_read_only(ncuh, &read_only)) != NWAM_SUCCESS)
                return (err);
        if (read_only)
                return (NWAM_ENTITY_NOT_DESTROYABLE);

        if ((err = nwam_ncu_get_parent_ncp_name(ncuh, &ncpname))
            != NWAM_SUCCESS)
                return (err);
        if ((err = nwam_ncp_name_to_file(ncpname, &ncpfile))
            != NWAM_SUCCESS) {
                free(ncpname);
                return (err);
        }

        err = nwam_destroy(ncpfile, ncuh, flags);

        free(ncpname);
        free(ncpfile);

        return (err);
}

nwam_error_t
nwam_ncu_get_prop_description(const char *propname, const char **descriptionp)
{
        return (nwam_get_prop_description(ncu_prop_table, propname,
            descriptionp));
}

/* Get expected property data type */
nwam_error_t
nwam_ncu_get_prop_type(const char *propname, nwam_value_type_t *typep)
{
        return (nwam_get_prop_type(ncu_prop_table, propname, typep));
}

nwam_error_t
nwam_ncu_prop_read_only(const char *propname, boolean_t *readp)
{
        if ((*readp = NWAM_NCU_PROP_SETONCE(propname)) == B_TRUE)
                return (NWAM_SUCCESS);

        return (nwam_prop_read_only(ncu_prop_table, propname, readp));
}

nwam_error_t
nwam_ncu_prop_multivalued(const char *propname, boolean_t *multip)
{
        return (nwam_prop_multivalued(ncu_prop_table, propname, multip));
}

/*
 * Ensure that the properties in the ncu, determined by that ncu's
 * type and class, belong there.
 */
static nwam_error_t
nwam_ncu_validate_prop_membership(nwam_ncu_handle_t ncuh, const char *propname)
{
        struct nwam_prop_table_entry *pte;
        nwam_value_t typeval, classval;
        uint64_t type, class;
        uint64_t typeflags = 0, classflags = 0;

        /* Get type/class from ncu */
        if (nwam_ncu_get_prop_value(ncuh, NWAM_NCU_PROP_TYPE, &typeval)
            != NWAM_SUCCESS)
                return (NWAM_ENTITY_INVALID);
        if (nwam_value_get_uint64(typeval, &type) != NWAM_SUCCESS) {
                nwam_value_free(typeval);
                return (NWAM_ENTITY_INVALID);
        }
        typeflags = nwam_ncu_type_to_flag((nwam_ncu_type_t)type);
        nwam_value_free(typeval);

        if (nwam_ncu_get_prop_value(ncuh, NWAM_NCU_PROP_CLASS, &classval)
            != NWAM_SUCCESS)
                return (NWAM_ENTITY_INVALID);
        if (nwam_value_get_uint64(classval, &class) != NWAM_SUCCESS) {
                nwam_value_free(classval);
                return (NWAM_ENTITY_INVALID);
        }
        classflags = nwam_ncu_class_to_flag((nwam_ncu_class_t)class);
        nwam_value_free(classval);

        if ((pte = nwam_get_prop_table_entry(ncu_prop_table, propname)) == NULL)
                return (NWAM_INVALID_ARG);

        if (typeflags & pte->prop_type_membership &&
            classflags & pte->prop_class_membership) {
                return (NWAM_SUCCESS);
        } else {
                return (NWAM_ENTITY_INVALID_MEMBER);
        }
}

/* Validate property's ncu membership and type, number and range of values */
nwam_error_t
nwam_ncu_validate_prop(nwam_ncu_handle_t ncuh, const char *propname,
    nwam_value_t value)
{
        nwam_error_t err;

        assert(ncuh != NULL && propname != NULL);

        /* First, determine if this property is valid for this ncu */
        if ((err = nwam_ncu_validate_prop_membership(ncuh, propname))
            != NWAM_SUCCESS)
                return (err);

        return (nwam_validate_prop(ncu_prop_table, ncuh, propname, value));
}

/* Property-specific value validation functions follow */

static nwam_error_t
valid_type(nwam_value_t value)
{
        uint64_t type;

        if (nwam_value_get_uint64(value, &type) != NWAM_SUCCESS ||
            type > NWAM_NCU_TYPE_INTERFACE)
                return (NWAM_ENTITY_INVALID_VALUE);
        return (NWAM_SUCCESS);
}

static nwam_error_t
valid_class(nwam_value_t value)
{
        uint64_t class;

        if (nwam_value_get_uint64(value, &class) != NWAM_SUCCESS ||
            class > NWAM_NCU_CLASS_IP)
                return (NWAM_ENTITY_INVALID_VALUE);
        return (NWAM_SUCCESS);
}

static nwam_error_t
valid_ncp(nwam_value_t value)
{
        char *ncp;

        if (nwam_value_get_string(value, &ncp) != NWAM_SUCCESS)
                return (NWAM_ENTITY_INVALID_VALUE);
        return (NWAM_SUCCESS);
}

static nwam_error_t
valid_priority_mode(nwam_value_t value)
{
        uint64_t priority_mode;

        if (nwam_value_get_uint64(value, &priority_mode) != NWAM_SUCCESS ||
            priority_mode > NWAM_PRIORITY_MODE_ALL)
                return (NWAM_ENTITY_INVALID_VALUE);
        return (NWAM_SUCCESS);
}

static nwam_error_t
valid_ncu_activation_mode(nwam_value_t value)
{
        uint64_t activation_mode;

        if (nwam_value_get_uint64(value, &activation_mode) != NWAM_SUCCESS)
                return (NWAM_ENTITY_INVALID_VALUE);

        switch (activation_mode) {
        case NWAM_ACTIVATION_MODE_MANUAL:
        case NWAM_ACTIVATION_MODE_PRIORITIZED:
                return (NWAM_SUCCESS);
        }
        return (NWAM_ENTITY_INVALID_VALUE);
}

/* ARGSUSED0 */
static nwam_error_t
valid_link_autopush(nwam_value_t value)
{
        return (NWAM_SUCCESS);
}

static nwam_error_t
valid_ip_version(nwam_value_t value)
{
        uint64_t *versions;
        uint_t i, numvalues;

        if (nwam_value_get_uint64_array(value, &versions, &numvalues)
            != NWAM_SUCCESS)
                return (NWAM_ENTITY_INVALID_VALUE);

        for (i = 0; i < numvalues; i++) {
                if (versions[i] != IPV4_VERSION &&
                    versions[i] != IPV6_VERSION)
                return (NWAM_ENTITY_INVALID_VALUE);
        }
        return (NWAM_SUCCESS);
}

static nwam_error_t
valid_addrsrc_v4(nwam_value_t value)
{
        uint64_t *addrsrc;
        uint_t i, numvalues;

        if (nwam_value_get_uint64_array(value, &addrsrc, &numvalues)
            != NWAM_SUCCESS)
                return (NWAM_ENTITY_INVALID_VALUE);

        for (i = 0; i < numvalues; i++) {
                if (addrsrc[i] != NWAM_ADDRSRC_DHCP &&
                    addrsrc[i] != NWAM_ADDRSRC_STATIC)
                        return (NWAM_ENTITY_INVALID_VALUE);
        }
        return (NWAM_SUCCESS);
}

static nwam_error_t
valid_addrsrc_v6(nwam_value_t value)
{
        uint64_t *addrsrc;
        uint_t i, numvalues;
        boolean_t dhcp_found = B_FALSE, autoconf_found = B_FALSE;

        if (nwam_value_get_uint64_array(value, &addrsrc, &numvalues)
            != NWAM_SUCCESS)
                return (NWAM_ENTITY_INVALID_VALUE);

        for (i = 0; i < numvalues; i++) {
                if (addrsrc[i] != NWAM_ADDRSRC_DHCP &&
                    addrsrc[i] != NWAM_ADDRSRC_STATIC &&
                    addrsrc[i] != NWAM_ADDRSRC_AUTOCONF)
                        return (NWAM_ENTITY_INVALID_VALUE);
                if (addrsrc[i] == NWAM_ADDRSRC_DHCP)
                        dhcp_found = B_TRUE;
                if (addrsrc[i] == NWAM_ADDRSRC_AUTOCONF)
                        autoconf_found = B_TRUE;
        }
        /*
         * DHCP and AUTOCONF need to be specified as v6 address sources
         * since there is no way to switch them off in NWAM at present.
         */
        if (dhcp_found && autoconf_found)
                return (NWAM_SUCCESS);
        else
                return (NWAM_ENTITY_INVALID_VALUE);
}

static nwam_error_t
valid_reqhost(nwam_value_t value)
{
        char *hostname;

        if (nwam_value_get_string(value, &hostname) != NWAM_SUCCESS)
                return (NWAM_ENTITY_INVALID_VALUE);
        return (ipadm_is_valid_hostname(hostname) ? NWAM_SUCCESS
            : NWAM_ENTITY_INVALID_VALUE);
}

/* ARGSUSED0 */
static nwam_error_t
valid_link_mtu(nwam_value_t value)
{
        return (NWAM_SUCCESS);
}

nwam_error_t
nwam_ncu_validate(nwam_ncu_handle_t ncuh, const char **errpropp)
{
        return (nwam_validate(ncu_prop_table, ncuh, errpropp));
}

/*
 * Given the ncu type and ncu class, return the list of properties that needs
 * to be set. Note this list is a complete property list that includes both
 * the required ones and the optional ones. Caller needs to free prop_list.
 */
nwam_error_t
nwam_ncu_get_default_proplist(nwam_ncu_type_t type, nwam_ncu_class_t class,
    const char ***prop_list, uint_t *numvalues)
{
        uint64_t typeflags = nwam_ncu_type_to_flag(type);
        uint64_t classflags = nwam_ncu_class_to_flag(class);

        return (nwam_get_default_proplist(ncu_prop_table, typeflags,
            classflags, prop_list, numvalues));
}

nwam_error_t
nwam_ncp_get_state(nwam_ncp_handle_t ncph, nwam_state_t *statep,
    nwam_aux_state_t *auxp)
{
        return (nwam_get_state(ncph->nwh_name, ncph, statep, auxp));
}

nwam_error_t
nwam_ncu_get_state(nwam_ncu_handle_t ncuh, nwam_state_t *statep,
    nwam_aux_state_t *auxp)
{
        nwam_ncp_handle_t ncph;
        char *ncpname;
        nwam_error_t err;

        assert(ncuh != NULL);

        if ((err = nwam_ncu_get_ncp(ncuh, &ncph)) != NWAM_SUCCESS)
                return (err);
        if (!nwam_ncp_is_active(ncph)) {
                nwam_ncp_free(ncph);
                return (NWAM_ENTITY_INVALID);
        }
        nwam_ncp_free(ncph);

        if ((err = nwam_ncu_get_parent_ncp_name(ncuh, &ncpname))
            != NWAM_SUCCESS)
                return (err);

        err = nwam_request_state(NWAM_OBJECT_TYPE_NCU, ncuh->nwh_name, ncpname,
            statep, auxp);
        free(ncpname);
        return (err);
}

nwam_error_t
nwam_ncp_get_active_priority_group(int64_t *priorityp)
{
        return (nwam_request_active_priority_group(priorityp));
}