root/usr/src/lib/libnwam/common/libnwam_object.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.
 */

#include <assert.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>

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

/*
 * Generic object manipulation functions. Given an object handle and
 * other parameters, create/destroy objects, walk them, walk their
 * properties, modify/retrieve/delete properties, enable/disable them,
 * etc. All object handles are "struct nwam_handle *" objects, sharing
 * the same description based on the object type, name, original name
 * (used in renaming) and associated data representing properties.
 */

nwam_error_t
nwam_handle_create(nwam_object_type_t type, const char *name,
    struct nwam_handle **hpp)
{

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

        if (strnlen(name, NWAM_MAX_NAME_LEN) > NWAM_MAX_NAME_LEN) {
                *hpp = NULL;
                return (NWAM_INVALID_ARG);
        }

        if ((*hpp = calloc(1, sizeof (struct nwam_handle))) == NULL)
                return (NWAM_NO_MEMORY);

        (*hpp)->nwh_object_type = type;
        (void) strlcpy((*hpp)->nwh_name, name, strlen(name) + 1);
        (*hpp)->nwh_committed = B_FALSE;
        (*hpp)->nwh_data = NULL;

        return (NWAM_SUCCESS);
}

/*
 * Read object of specified type from dbname.
 */
nwam_error_t
nwam_read(nwam_object_type_t type, const char *dbname, const char *name,
    uint64_t flags, struct nwam_handle **hpp)
{
        nwam_error_t err;
        char dbname_copy[MAXPATHLEN];

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

        if (dbname != NULL)
                (void) strlcpy(dbname_copy, dbname, sizeof (dbname_copy));

        if ((err = nwam_valid_flags(flags, NWAM_FLAG_BLOCKING)) != NWAM_SUCCESS)
                return (err);
        if ((err = nwam_handle_create(type, name, hpp)) != NWAM_SUCCESS)
                return (err);

        if ((err = nwam_read_object_from_backend
            (dbname != NULL ? dbname_copy : NULL,
            type == NWAM_OBJECT_TYPE_NCP ? NULL : (*hpp)->nwh_name, flags,
            &(*hpp)->nwh_data)) != NWAM_SUCCESS) {
                free(*hpp);
                *hpp = NULL;
                return (err);
        }
        if (type == NWAM_OBJECT_TYPE_NCP && dbname != NULL) {
                char *ncpname;

                /*
                 * dbname_copy may have been changed due to case-insensitive
                 * match against the actual NCP configuration file.
                 */
                if (nwam_ncp_file_to_name(dbname_copy, &ncpname)
                    == NWAM_SUCCESS) {
                        (void) strlcpy((*hpp)->nwh_name, ncpname,
                            sizeof ((*hpp)->nwh_name));
                        free(ncpname);
                }
        }

        (*hpp)->nwh_committed = B_TRUE;

        return (NWAM_SUCCESS);
}

/*
 * Create simply creates the handle - the object-specific function must
 * then fill in property values.
 */
nwam_error_t
nwam_create(nwam_object_type_t type, const char *dbname, const char *name,
    struct nwam_handle **hpp)
{
        struct nwam_handle *hp;

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

        if (nwam_read(type, dbname, name, 0, &hp) == NWAM_SUCCESS) {
                nwam_free(hp);
                return (NWAM_ENTITY_EXISTS);
        }
        /* Create handle */
        return (nwam_handle_create(type, name, hpp));
}

nwam_error_t
nwam_get_name(struct nwam_handle *hp, char **namep)
{
        assert(hp != NULL && namep != NULL);

        if ((*namep = strdup(hp->nwh_name)) == NULL) {
                *namep = NULL;
                return (NWAM_NO_MEMORY);
        }
        return (NWAM_SUCCESS);
}

nwam_error_t
nwam_set_name(struct nwam_handle *hp, const char *name)
{
        assert(hp != NULL && name != NULL);

        if (hp->nwh_committed)
                return (NWAM_ENTITY_READ_ONLY);

        if (strlen(name) >= sizeof (hp->nwh_name))
                return (NWAM_INVALID_ARG);

        (void) strcpy(hp->nwh_name, name);

        return (NWAM_SUCCESS);
}

/* Compare object names c1 and c2 using strcasecmp() */
static int
name_cmp(const void *c1, const void *c2)
{
        nwam_ncu_type_t t1, t2;
        char            *n1, *n2;

        /* If c1 and c2 are typed NCU names, compare names without the types */
        if (nwam_ncu_typed_name_to_name(*(const char **)c1, &t1, &n1)
            == NWAM_SUCCESS &&
            nwam_ncu_typed_name_to_name(*(const char **)c2, &t2, &n2)
            == NWAM_SUCCESS) {
                int ret = strcasecmp(n1, n2);
                free(n1);
                free(n2);

                /* For NCUs with the same name, compare their types */
                if (ret == 0) {
                        if (t1 < t2)
                                ret = -1;
                        else if (t1 > t2)
                                ret = 1;
                }
                return (ret);
        }

        return (strcasecmp(*(const char **)c1, *(const char **)c2));
}

/*
 * Generic walk function takes the standard walk arguments, and in addition
 * takes a selection callback that is object-specific. If this returns
 * 0, the object is a valid selection for the walk and the callback is called.
 * Otherwise, it is skipped.
 */
nwam_error_t
nwam_walk(nwam_object_type_t type, const char *dbname,
    int(*cb)(struct nwam_handle *, void *),
    void *data, uint64_t flags, int *retp,
    int(*selectcb)(struct nwam_handle *, uint64_t, void *))
{
        void *objlist;
        nwam_value_t value;
        char **object_names;
        uint_t i, num_objects = 0;
        struct nwam_handle *hp;
        nwam_error_t err;
        int ret = 0;

        assert(cb != NULL);

        /*
         * To walk a set of objects, call nwam_read_object_from_backend()
         * with a "dbname" argument set to the container db name and
         * the object name set to NULL. This returns an nvlist with one
         * member - the NWAM_OBJECT_NAMES_STRING - and the values it contains
         * represent the names of the objects.  Read each in turn, calling
         * the callback function.
         */
        if ((err = nwam_read_object_from_backend((char *)dbname, NULL, flags,
            &objlist)) != NWAM_SUCCESS) {
                if (err == NWAM_ENTITY_NOT_FOUND) {
                        /*
                         * This indicates the dbname container is not present.
                         * Do not pass back an error in this case, since it is
                         * valid for a container not to exist.
                         */
                        return (NWAM_SUCCESS);
                }
                return (err);
        }

        if ((err = nwam_get_prop_value(objlist, NWAM_OBJECT_NAMES_STRING,
            &value)) != NWAM_SUCCESS) {
                nwam_free_object_list(objlist);
                return (err);
        }
        err = nwam_value_get_string_array(value, &object_names, &num_objects);
        nwam_free_object_list(objlist);
        if (err != NWAM_SUCCESS) {
                nwam_value_free(value);
                return (err);
        }

        /* sort the object names alphabetically */
        qsort(object_names, num_objects, sizeof (char *), name_cmp);

        for (i = 0; i < num_objects; i++) {
                err = nwam_read(type, dbname, object_names[i],
                    flags & NWAM_FLAG_GLOBAL_MASK, &hp);
                /* An object may have disappeared.  If so, skip it. */
                if (err == NWAM_ENTITY_NOT_FOUND)
                        continue;
                if (err != NWAM_SUCCESS) {
                        nwam_value_free(value);
                        return (err);
                }
                if ((selectcb == NULL) || (selectcb(hp, flags, data) == 0)) {
                        ret = cb(hp, data);
                        if (ret != 0) {
                                nwam_free(hp);
                                nwam_value_free(value);
                                if (retp != NULL)
                                        *retp = ret;
                                return (NWAM_WALK_HALTED);
                        }
                }
                nwam_free(hp);
        }
        nwam_value_free(value);

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

void
nwam_free(struct nwam_handle *hp)
{
        if (hp != NULL) {
                if (hp->nwh_data != NULL)
                        nwam_free_object_list(hp->nwh_data);
                free(hp);
        }
}

/*
 * Copy object represented by oldhp to an object newname, all in container
 * dbname.
 */
nwam_error_t
nwam_copy(const char *dbname, struct nwam_handle *oldhp, const char *newname,
    struct nwam_handle **newhpp)
{
        nwam_error_t err;
        struct nwam_handle *hp;

        assert(oldhp != NULL && newname != NULL && newhpp != NULL);

        if (nwam_read(oldhp->nwh_object_type, dbname, newname, 0, &hp)
            == NWAM_SUCCESS) {
                nwam_free(hp);
                return (NWAM_ENTITY_EXISTS);
        }

        if ((err = nwam_handle_create(oldhp->nwh_object_type, newname, newhpp))
            != NWAM_SUCCESS)
                return (err);
        if ((err = nwam_dup_object_list(oldhp->nwh_data,
            &((*newhpp)->nwh_data))) != NWAM_SUCCESS) {
                nwam_free(*newhpp);
                *newhpp = NULL;
                return (err);
        }

        return (NWAM_SUCCESS);
}

/* ARGSUSED3 */
nwam_error_t
nwam_walk_props(struct nwam_handle *hp,
    int (*cb)(const char *, nwam_value_t, void *),
    void *data, uint64_t flags, int *retp)
{
        char *lastpropname = NULL, *propname;
        nwam_value_t value;
        nwam_error_t err;
        int ret = 0;

        assert(hp != NULL && hp->nwh_data != NULL && cb != NULL);

        if ((err = nwam_valid_flags(flags, 0)) != NWAM_SUCCESS)
                return (err);
        while ((err = nwam_next_object_prop(hp->nwh_data, lastpropname,
            &propname, &value)) == NWAM_SUCCESS) {

                ret = cb(propname, value, data);
                if (ret != 0)
                        err = NWAM_WALK_HALTED;

                /* Free value */
                nwam_value_free(value);

                if (err != NWAM_SUCCESS)
                        break;

                lastpropname = propname;
        }

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

/*
 * Note that prior to calling the generic commit function, object-specific
 * validation should be carried out.
 */
nwam_error_t
nwam_commit(const char *dbname, struct nwam_handle *hp, uint64_t flags)
{
        nwam_error_t err;
        uint64_t iflags = flags;
        boolean_t is_ncu;
        struct nwam_handle *testhp;
        nwam_action_t action;

        assert(hp != NULL);

        /*
         * NWAM_FLAG_ENTITY_KNOWN_WLAN is only used for Known WLANs and
         * NWAM_FLAG_ENTITY_ENABLE is used for other objects (during enable
         * and disable).
         */
        if ((err = nwam_valid_flags(flags,
            NWAM_FLAG_BLOCKING | NWAM_FLAG_CREATE |
            (hp->nwh_object_type == NWAM_OBJECT_TYPE_KNOWN_WLAN ?
            NWAM_FLAG_ENTITY_KNOWN_WLAN : NWAM_FLAG_ENTITY_ENABLE)))
            != NWAM_SUCCESS)
                return (err);

        is_ncu = (hp->nwh_object_type == NWAM_OBJECT_TYPE_NCU);

        /*
         * Does object already exist? If not, action is ADD, otherwise REFRESH.
         */
        switch (nwam_read(hp->nwh_object_type, (char *)dbname, hp->nwh_name, 0,
            &testhp)) {
        case NWAM_ENTITY_NOT_FOUND:
                action = NWAM_ACTION_ADD;
                break;
        case NWAM_SUCCESS:
                nwam_free(testhp);
                if (hp->nwh_object_type == NWAM_OBJECT_TYPE_NCP)
                        return (NWAM_ENTITY_EXISTS);
                /* FALLTHRU */
        default:
                action = NWAM_ACTION_REFRESH;
                break;
        }

        err = nwam_update_object_in_backend((char *)dbname,
            hp->nwh_object_type == NWAM_OBJECT_TYPE_NCP ? NULL : hp->nwh_name,
            iflags, hp->nwh_data);
        if (err != NWAM_SUCCESS)
                return (err);

        hp->nwh_committed = B_TRUE;

        /*
         * Tell nwamd to reread this object.  For NCUs, we need to convert
         * the dbname to the NCP name in order to pass it to nwamd.
         */
        if (is_ncu) {
                char *ncpname;

                if (nwam_ncp_file_to_name(dbname, &ncpname) == NWAM_SUCCESS) {
                        (void) nwam_request_action(hp->nwh_object_type,
                            hp->nwh_name, ncpname, action);
                        free(ncpname);
                }
        } else {
                (void) nwam_request_action(hp->nwh_object_type, hp->nwh_name,
                    NULL, action);
        }
        return (NWAM_SUCCESS);
}

static boolean_t
nwam_is_active(struct nwam_handle *hp)
{
        nwam_state_t state;
        nwam_aux_state_t aux;

        return ((nwam_get_state(NULL, hp, &state, &aux) == NWAM_SUCCESS &&
            state == NWAM_STATE_ONLINE));
}

nwam_error_t
nwam_destroy(const char *dbname, struct nwam_handle *hp, uint64_t flags)
{
        nwam_error_t err;
        char *name;
        boolean_t is_ncp, is_ncu;

        assert(hp != NULL);

        /* NWAM_FLAG_ENTITY_KNOWN_WLAN is only used for Known WLANs */
        if ((err = nwam_valid_flags(flags,
            NWAM_FLAG_BLOCKING | NWAM_FLAG_DO_NOT_FREE |
            (hp->nwh_object_type == NWAM_OBJECT_TYPE_KNOWN_WLAN ?
            NWAM_FLAG_ENTITY_KNOWN_WLAN : 0))) != NWAM_SUCCESS)
                return (err);

        is_ncp = hp->nwh_object_type == NWAM_OBJECT_TYPE_NCP;
        is_ncu = hp->nwh_object_type == NWAM_OBJECT_TYPE_NCU;
        name = hp->nwh_name;

        /* Check if object is active */
        if (!is_ncp && !is_ncu && nwam_is_active(hp))
                return (NWAM_ENTITY_IN_USE);

        /* For NCPs, just remove the dbname file, otherwise remove the object */
        err = nwam_remove_object_from_backend((char *)dbname,
            is_ncp ? NULL : name, flags);

        /*
         * Tell nwamd to remove this object.  For NCUs, we need to convert the
         * dbname filename to the NCP name to pass it to nwamd.
         */
        if (is_ncu) {
                char *ncpname;

                if (nwam_ncp_file_to_name(dbname, &ncpname) == NWAM_SUCCESS) {
                        (void) nwam_request_action(hp->nwh_object_type, name,
                            ncpname, NWAM_ACTION_DESTROY);
                        free(ncpname);
                }
        } else {
                (void) nwam_request_action(hp->nwh_object_type, name, NULL,
                    NWAM_ACTION_DESTROY);
        }

        if ((err == NWAM_SUCCESS) && !(flags & NWAM_FLAG_DO_NOT_FREE))
                nwam_free(hp);

        return (err);
}

/*
 * Enable/disable functions assume prior checking of activation mode
 * to ensure an enable/disable action is valid for the object. "parent" in these
 * functions specifies the NCP for NCUs.
 */
nwam_error_t
nwam_enable(const char *parent, struct nwam_handle *hp)
{
        return (nwam_request_action(hp->nwh_object_type, hp->nwh_name,
            parent, NWAM_ACTION_ENABLE));
}

nwam_error_t
nwam_disable(const char *parent, struct nwam_handle *hp)
{
        return (nwam_request_action(hp->nwh_object_type, hp->nwh_name,
            parent, NWAM_ACTION_DISABLE));
}

nwam_error_t
nwam_get_state(const char *parent, struct nwam_handle *hp, nwam_state_t *statep,
    nwam_aux_state_t *auxp)
{
        return (nwam_request_state(hp->nwh_object_type, hp->nwh_name, parent,
            statep, auxp));
}

struct nwam_prop_table_entry *
nwam_get_prop_table_entry(struct nwam_prop_table table, const char *propname)
{
        struct nwam_prop_table_entry *cur = table.entries;
        struct nwam_prop_table_entry *end = cur + table.num_entries;

        assert(propname != NULL);

        for (; cur < end; cur++) {
                if (strcmp(propname, cur->prop_name) == 0)
                        return (cur);
        }
        return (NULL);
}

nwam_error_t
nwam_get_prop_description(struct nwam_prop_table table, const char *propname,
    const char **descriptionp)
{
        struct nwam_prop_table_entry *pte;

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

        if ((pte = nwam_get_prop_table_entry(table, propname)) == NULL) {
                *descriptionp = NULL;
                return (NWAM_INVALID_ARG);
        }

        *descriptionp = dgettext(TEXT_DOMAIN, pte->prop_description);
        return (NWAM_SUCCESS);
}

nwam_error_t
nwam_get_prop_type(struct nwam_prop_table table, const char *propname,
    nwam_value_type_t *typep)
{
        struct nwam_prop_table_entry *pte;

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

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

        *typep = pte->prop_type;

        return (NWAM_SUCCESS);
}

nwam_error_t
nwam_prop_multivalued(struct nwam_prop_table table, const char *propname,
    boolean_t *multip)
{
        struct nwam_prop_table_entry *pte;

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

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

        if (pte->prop_max_numvalues > 1)
                *multip = B_TRUE;
        else
                *multip = B_FALSE;

        return (NWAM_SUCCESS);
}

nwam_error_t
nwam_prop_read_only(struct nwam_prop_table table, const char *propname,
    boolean_t *readp)
{
        struct nwam_prop_table_entry *pte;

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

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

        *readp = (pte->prop_is_readonly && !nwam_uid_is_special());

        return (NWAM_SUCCESS);
}

/*
 * Structure used to pass in prop table and errprop string pointer to internal
 * validate function.
 */
struct validate_internal_arg {
        struct nwam_prop_table table;
        const char **errpropp;
};

/*
 * Callback used by nwam_walk_props() in nwam_validate(), and
 * by nwam_validate_prop() to determine that the number, type and
 * range of values are correct, and that validation function (if present)
 * succeeds.
 */
static int
nwam_validate_prop_internal(const char *propname, nwam_value_t value,
    void *arg)
{
        struct validate_internal_arg *via = arg;
        struct nwam_prop_table table = via->table;
        const char **errpropp = via->errpropp;
        struct nwam_prop_table_entry *pte;
        nwam_error_t err;
        nwam_value_type_t type;
        uint_t numvalues;
        int i;

        if ((err = nwam_value_get_numvalues(value, &numvalues))
            != NWAM_SUCCESS ||
            (err = nwam_value_get_type(value, &type)) != NWAM_SUCCESS) {
                if (errpropp != NULL)
                        *errpropp = propname;
                return (err);
        }
        if ((pte = nwam_get_prop_table_entry(table, propname)) == NULL)
                return (NWAM_INVALID_ARG);

        /* have we get expected number of values? */
        if (numvalues < pte->prop_min_numvalues ||
            numvalues > pte->prop_max_numvalues) {
                if (errpropp != NULL)
                        *errpropp = propname;
                if (numvalues < 1)
                        return (NWAM_ENTITY_NO_VALUE);
                else
                        return (NWAM_ENTITY_INVALID_VALUE);
        }
        /* Ensure type matches */
        if (numvalues > 0) {
                for (i = 0; i < numvalues; i++) {
                        if (pte->prop_type != type) {
                                if (errpropp != NULL)
                                        *errpropp = propname;
                                return (NWAM_ENTITY_TYPE_MISMATCH);

                        }
                }
        }
        /* Call property-specific validation function */
        if (pte->prop_validate != NULL) {
                err = pte->prop_validate(value);
                if (err != NWAM_SUCCESS && errpropp != NULL)
                        *errpropp = propname;
                return (err);
        }

        return (NWAM_SUCCESS);
}

nwam_error_t
nwam_validate_prop(struct nwam_prop_table table, struct nwam_handle *hp,
    const char *propname, nwam_value_t value)
{
        struct validate_internal_arg via;

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

        via.table = table;
        via.errpropp = NULL;

        return ((nwam_error_t)nwam_validate_prop_internal(propname,
            value, &via));
}

nwam_error_t
nwam_validate(struct nwam_prop_table table, struct nwam_handle *hp,
    const char **errpropp)
{
        struct validate_internal_arg via;
        nwam_error_t err1, err2;

        assert(hp != NULL);

        via.table = table;
        via.errpropp = errpropp;

        err1 = nwam_walk_props(hp, nwam_validate_prop_internal, &via,
            0, (int *)&err2);
        if (err1 != NWAM_SUCCESS)
                return (err2);
        return (NWAM_SUCCESS);
}

/*
 * Given the type and class flag representations, return the list of properties
 * that can be set for that type/class combination. Note this list is a complete
 * property list that includes both the required and the optional properties.
 * The type and class flags are only used for NCU objects at present.
 *
 * Caller needs to free prop_list.
 */
nwam_error_t
nwam_get_default_proplist(struct nwam_prop_table table,
    uint64_t type, uint64_t class, const char ***prop_list, uint_t *numvalues)
{
        struct nwam_prop_table_entry *cur = table.entries;
        struct nwam_prop_table_entry *end = cur + table.num_entries;
        int i = 0;
        const char **list = NULL;

        assert(prop_list != NULL && numvalues != NULL);

        /* Construct a list of all properties for required type/class */
        list = calloc(table.num_entries, sizeof (char *));
        if (list == NULL) {
                *prop_list = NULL;
                *numvalues = 0;
                return (NWAM_NO_MEMORY);
        }
        for (; cur < end; cur++) {
                if (((type & cur->prop_type_membership) == 0) ||
                    ((class & cur->prop_class_membership) == 0))
                        continue;
                list[i++] = cur->prop_name;
        }
        *numvalues = i;
        *prop_list = list;
        return (NWAM_SUCCESS);
}