root/usr/src/cmd/dlmgmtd/dlmgmt_util.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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2017 Joyent, Inc.
 */

/*
 * Utility functions used by the dlmgmtd daemon.
 */

#include <assert.h>
#include <pthread.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <strings.h>
#include <string.h>
#include <syslog.h>
#include <stdarg.h>
#include <zone.h>
#include <errno.h>
#include <libdlpi.h>
#include "dlmgmt_impl.h"

/*
 * There are three datalink AVL tables.  The dlmgmt_name_avl tree contains all
 * datalinks and is keyed by zoneid and link name.  The dlmgmt_id_avl also
 * contains all datalinks, and it is keyed by link ID.  The dlmgmt_loan_avl is
 * keyed by link name, and contains the set of global-zone links that are
 * currently on loan to non-global zones.
 */
avl_tree_t      dlmgmt_name_avl;
avl_tree_t      dlmgmt_id_avl;
avl_tree_t      dlmgmt_loan_avl;

avl_tree_t      dlmgmt_dlconf_avl;

static pthread_rwlock_t dlmgmt_avl_lock = PTHREAD_RWLOCK_INITIALIZER;
static pthread_mutex_t  dlmgmt_avl_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t   dlmgmt_avl_cv = PTHREAD_COND_INITIALIZER;
static pthread_rwlock_t dlmgmt_dlconf_lock = PTHREAD_RWLOCK_INITIALIZER;

typedef struct dlmgmt_prefix {
        struct dlmgmt_prefix    *lp_next;
        char                    lp_prefix[MAXLINKNAMELEN];
        zoneid_t                lp_zoneid;
        uint_t                  lp_nextppa;
} dlmgmt_prefix_t;
static dlmgmt_prefix_t  dlmgmt_prefixlist;

datalink_id_t           dlmgmt_nextlinkid;
static datalink_id_t    dlmgmt_nextconfid = 1;

static void             dlmgmt_advance_linkid(dlmgmt_link_t *);
static void             dlmgmt_advance_ppa(dlmgmt_link_t *);

void
dlmgmt_log(int pri, const char *fmt, ...)
{
        va_list alist;

        va_start(alist, fmt);
        if (debug) {
                (void) vfprintf(stderr, fmt, alist);
                (void) fputc('\n', stderr);
        } else {
                vsyslog(pri, fmt, alist);
        }
        va_end(alist);
}

static int
cmp_link_by_name(const void *v1, const void *v2)
{
        const dlmgmt_link_t *link1 = v1;
        const dlmgmt_link_t *link2 = v2;
        int cmp;

        cmp = strcmp(link1->ll_link, link2->ll_link);
        return ((cmp == 0) ? 0 : ((cmp < 0) ? -1 : 1));
}

/*
 * Note that the zoneid associated with a link is effectively part of its
 * name.  This is essentially what results in having each zone have disjoint
 * datalink namespaces.
 */
static int
cmp_link_by_zname(const void *v1, const void *v2)
{
        const dlmgmt_link_t *link1 = v1;
        const dlmgmt_link_t *link2 = v2;

        if (link1->ll_zoneid < link2->ll_zoneid)
                return (-1);
        if (link1->ll_zoneid > link2->ll_zoneid)
                return (1);
        return (cmp_link_by_name(link1, link2));
}

static int
cmp_link_by_id(const void *v1, const void *v2)
{
        const dlmgmt_link_t *link1 = v1;
        const dlmgmt_link_t *link2 = v2;

        if ((uint64_t)(link1->ll_linkid) == (uint64_t)(link2->ll_linkid))
                return (0);
        else if ((uint64_t)(link1->ll_linkid) < (uint64_t)(link2->ll_linkid))
                return (-1);
        else
                return (1);
}

static int
cmp_dlconf_by_id(const void *v1, const void *v2)
{
        const dlmgmt_dlconf_t *dlconfp1 = v1;
        const dlmgmt_dlconf_t *dlconfp2 = v2;

        if (dlconfp1->ld_id == dlconfp2->ld_id)
                return (0);
        else if (dlconfp1->ld_id < dlconfp2->ld_id)
                return (-1);
        else
                return (1);
}

void
dlmgmt_linktable_init(void)
{
        /*
         * Initialize the prefix list. First add the "net" prefix for the
         * global zone to the list.
         */
        dlmgmt_prefixlist.lp_next = NULL;
        dlmgmt_prefixlist.lp_zoneid = GLOBAL_ZONEID;
        dlmgmt_prefixlist.lp_nextppa = 0;
        (void) strlcpy(dlmgmt_prefixlist.lp_prefix, "net", MAXLINKNAMELEN);

        avl_create(&dlmgmt_name_avl, cmp_link_by_zname, sizeof (dlmgmt_link_t),
            offsetof(dlmgmt_link_t, ll_name_node));
        avl_create(&dlmgmt_id_avl, cmp_link_by_id, sizeof (dlmgmt_link_t),
            offsetof(dlmgmt_link_t, ll_id_node));
        avl_create(&dlmgmt_loan_avl, cmp_link_by_name, sizeof (dlmgmt_link_t),
            offsetof(dlmgmt_link_t, ll_loan_node));
        avl_create(&dlmgmt_dlconf_avl, cmp_dlconf_by_id,
            sizeof (dlmgmt_dlconf_t), offsetof(dlmgmt_dlconf_t, ld_node));
        dlmgmt_nextlinkid = 1;
}

void
dlmgmt_linktable_fini(void)
{
        dlmgmt_prefix_t *lpp, *next;

        for (lpp = dlmgmt_prefixlist.lp_next; lpp != NULL; lpp = next) {
                next = lpp->lp_next;
                free(lpp);
        }

        avl_destroy(&dlmgmt_dlconf_avl);
        avl_destroy(&dlmgmt_name_avl);
        avl_destroy(&dlmgmt_loan_avl);
        avl_destroy(&dlmgmt_id_avl);
}

static void
linkattr_add(dlmgmt_linkattr_t **headp, dlmgmt_linkattr_t *attrp)
{
        if (*headp == NULL) {
                *headp = attrp;
        } else {
                (*headp)->lp_prev = attrp;
                attrp->lp_next = *headp;
                *headp = attrp;
        }
}

static void
linkattr_rm(dlmgmt_linkattr_t **headp, dlmgmt_linkattr_t *attrp)
{
        dlmgmt_linkattr_t *next, *prev;

        next = attrp->lp_next;
        prev = attrp->lp_prev;
        if (next != NULL)
                next->lp_prev = prev;
        if (prev != NULL)
                prev->lp_next = next;
        else
                *headp = next;
}

dlmgmt_linkattr_t *
linkattr_find(dlmgmt_linkattr_t *headp, const char *attr)
{
        dlmgmt_linkattr_t *attrp;

        for (attrp = headp; attrp != NULL; attrp = attrp->lp_next) {
                if (strcmp(attrp->lp_name, attr) == 0)
                        break;
        }
        return (attrp);
}

int
linkattr_set(dlmgmt_linkattr_t **headp, const char *attr, void *attrval,
    size_t attrsz, dladm_datatype_t type)
{
        dlmgmt_linkattr_t       *attrp;
        void                    *newval;
        boolean_t               new;

        attrp = linkattr_find(*headp, attr);
        if (attrp != NULL) {
                /*
                 * It is already set.  If the value changed, update it.
                 */
                if (linkattr_equal(headp, attr, attrval, attrsz))
                        return (0);
                new = B_FALSE;
        } else {
                /*
                 * It is not set yet, allocate the linkattr and prepend to the
                 * list.
                 */
                if ((attrp = calloc(1, sizeof (dlmgmt_linkattr_t))) == NULL)
                        return (ENOMEM);

                (void) strlcpy(attrp->lp_name, attr, MAXLINKATTRLEN);
                new = B_TRUE;
        }
        if ((newval = calloc(1, attrsz)) == NULL) {
                if (new)
                        free(attrp);
                return (ENOMEM);
        }

        if (!new)
                free(attrp->lp_val);
        attrp->lp_val = newval;
        bcopy(attrval, attrp->lp_val, attrsz);
        attrp->lp_sz = attrsz;
        attrp->lp_type = type;
        attrp->lp_linkprop = dladm_attr_is_linkprop(attr);
        if (new)
                linkattr_add(headp, attrp);
        return (0);
}

void
linkattr_unset(dlmgmt_linkattr_t **headp, const char *attr)
{
        dlmgmt_linkattr_t *attrp;

        if ((attrp = linkattr_find(*headp, attr)) != NULL) {
                linkattr_rm(headp, attrp);
                free(attrp->lp_val);
                free(attrp);
        }
}

int
linkattr_get(dlmgmt_linkattr_t **headp, const char *attr, void **attrvalp,
    size_t *attrszp, dladm_datatype_t *typep)
{
        dlmgmt_linkattr_t *attrp;

        if ((attrp = linkattr_find(*headp, attr)) == NULL)
                return (ENOENT);

        *attrvalp = attrp->lp_val;
        *attrszp = attrp->lp_sz;
        if (typep != NULL)
                *typep = attrp->lp_type;
        return (0);
}

boolean_t
linkattr_equal(dlmgmt_linkattr_t **headp, const char *attr, void *attrval,
    size_t attrsz)
{
        void    *saved_attrval;
        size_t  saved_attrsz;

        if (linkattr_get(headp, attr, &saved_attrval, &saved_attrsz, NULL) != 0)
                return (B_FALSE);

        return ((saved_attrsz == attrsz) &&
            (memcmp(saved_attrval, attrval, attrsz) == 0));
}

void
linkattr_destroy(dlmgmt_link_t *linkp)
{
        dlmgmt_linkattr_t *next, *attrp;

        for (attrp = linkp->ll_head; attrp != NULL; attrp = next) {
                next = attrp->lp_next;
                free(attrp->lp_val);
                free(attrp);
        }
}

static int
dlmgmt_table_readwritelock(boolean_t write)
{
        if (write)
                return (pthread_rwlock_trywrlock(&dlmgmt_avl_lock));
        else
                return (pthread_rwlock_tryrdlock(&dlmgmt_avl_lock));
}

void
dlmgmt_table_lock(boolean_t write)
{
        (void) pthread_mutex_lock(&dlmgmt_avl_mutex);
        while (dlmgmt_table_readwritelock(write) == EBUSY)
                (void) pthread_cond_wait(&dlmgmt_avl_cv, &dlmgmt_avl_mutex);

        (void) pthread_mutex_unlock(&dlmgmt_avl_mutex);
}

void
dlmgmt_table_unlock(void)
{
        (void) pthread_rwlock_unlock(&dlmgmt_avl_lock);
        (void) pthread_mutex_lock(&dlmgmt_avl_mutex);
        (void) pthread_cond_broadcast(&dlmgmt_avl_cv);
        (void) pthread_mutex_unlock(&dlmgmt_avl_mutex);
}

void
link_destroy(dlmgmt_link_t *linkp)
{
        linkattr_destroy(linkp);
        free(linkp);
}

/*
 * Set the DLMGMT_ACTIVE flag on the link to note that it is active.
 * When a link is active and owned by an NGZ then it is added to
 * that zone's datalink list.
 */
int
link_activate(dlmgmt_link_t *linkp)
{
        int             err = 0;
        zoneid_t        zoneid = ALL_ZONES;

        /*
         * If zone_check_datalink() returns 0 it means we found the
         * link in one of the NGZ's datalink lists. Otherwise the link
         * is under the GZ.
         */
        if (zone_check_datalink(&zoneid, linkp->ll_linkid) == 0) {
                /*
                 * This is a bit subtle. If the following expression
                 * is true then the link was found in one of the NGZ's
                 * datalink lists but the link structure has it under
                 * the GZ. This means that the link is supposed to be
                 * loaned out to an NGZ but the dlmgmtd state is out
                 * of sync -- possibly due to the process restarting.
                 * In this case we need to sync the dlmgmtd state by
                 * marking it as on-loan to the NGZ it's currently
                 * under.
                 */
                if (zoneid != linkp->ll_zoneid) {
                        assert(linkp->ll_zoneid == 0);
                        assert(linkp->ll_onloan == B_FALSE);
                        assert(linkp->ll_transient == 0);

                        /*
                         * If dlmgmtd already has a link with this
                         * name under the NGZ then we have a problem.
                         */
                        if (link_by_name(linkp->ll_link, zoneid) != NULL) {
                                err = EEXIST;
                                goto done;
                        }

                        /*
                         * Remove the current linkp entry from the
                         * list because it's under the wrong zoneid.
                         * We don't have to update the dlmgmt_id_avl
                         * because it compares entries by ll_linkid
                         * only.
                         */
                        if (avl_find(&dlmgmt_name_avl, linkp, NULL) != NULL)
                                avl_remove(&dlmgmt_name_avl, linkp);

                        /*
                         * Update the link to reflect the fact that
                         * it's on-loan to an NGZ and re-add it to the
                         * list.
                         */
                        linkp->ll_zoneid = zoneid;
                        avl_add(&dlmgmt_name_avl, linkp);

                        /*
                         * Since the link was found to be in a zone but
                         * recorded as a GZ link in the link structure, and
                         * we've now updated that, also mark it as on-loan to
                         * the NGZ.
                         */
                        avl_add(&dlmgmt_loan_avl, linkp);
                        linkp->ll_onloan = B_TRUE;
                }
        } else if (linkp->ll_zoneid != GLOBAL_ZONEID) {
                /*
                 * In this case the link was not found under any NGZ
                 * but according to its ll_zoneid member it is owned
                 * by an NGZ. Add the datalink to the appropriate zone
                 * datalink list.
                 */
                err = zone_add_datalink(linkp->ll_zoneid, linkp->ll_linkid);
                assert(linkp->ll_onloan == B_FALSE);
        }
done:
        if (err == 0)
                linkp->ll_flags |= DLMGMT_ACTIVE;
        return (err);
}

/*
 * Is linkp visible from the caller's zoneid?  It is if the link is in the
 * same zone as the caller, or if the caller is in the global zone and the
 * link is on loan to a non-global zone.
 */
boolean_t
link_is_visible(dlmgmt_link_t *linkp, zoneid_t zoneid)
{
        return (linkp->ll_zoneid == zoneid ||
            (zoneid == GLOBAL_ZONEID && linkp->ll_onloan));
}

dlmgmt_link_t *
link_by_id(datalink_id_t linkid, zoneid_t zoneid)
{
        dlmgmt_link_t link, *linkp;

        link.ll_linkid = linkid;
        if ((linkp = avl_find(&dlmgmt_id_avl, &link, NULL)) == NULL)
                return (NULL);
        if (zoneid != GLOBAL_ZONEID && linkp->ll_zoneid != zoneid)
                return (NULL);
        return (linkp);
}

dlmgmt_link_t *
link_by_name(const char *name, zoneid_t zoneid)
{
        dlmgmt_link_t   link, *linkp;

        (void) strlcpy(link.ll_link, name, MAXLINKNAMELEN);
        link.ll_zoneid = zoneid;
        linkp = avl_find(&dlmgmt_name_avl, &link, NULL);
        if (linkp == NULL && zoneid == GLOBAL_ZONEID) {
                /* The link could be on loan to a non-global zone? */
                linkp = avl_find(&dlmgmt_loan_avl, &link, NULL);
        }
        return (linkp);
}

int
dlmgmt_create_common(const char *name, datalink_class_t class, uint32_t media,
    zoneid_t zoneid, uint32_t flags, dlmgmt_link_t **linkpp)
{
        dlmgmt_link_t   *linkp = NULL;
        avl_index_t     name_where, id_where;
        int             err = 0;

        if (!dladm_valid_linkname(name))
                return (EINVAL);
        if (dlmgmt_nextlinkid == DATALINK_INVALID_LINKID)
                return (ENOSPC);
        if (flags & ~(DLMGMT_ACTIVE | DLMGMT_PERSIST | DLMGMT_TRANSIENT) ||
            ((flags & DLMGMT_PERSIST) && (flags & DLMGMT_TRANSIENT)) ||
            flags == 0) {
                return (EINVAL);
        }

        if ((linkp = calloc(1, sizeof (dlmgmt_link_t))) == NULL) {
                err = ENOMEM;
                goto done;
        }

        (void) strlcpy(linkp->ll_link, name, MAXLINKNAMELEN);
        linkp->ll_class = class;
        linkp->ll_media = media;
        linkp->ll_linkid = dlmgmt_nextlinkid;
        linkp->ll_zoneid = zoneid;
        linkp->ll_gen = 0;

        /*
         * While DLMGMT_TRANSIENT starts off as a flag it is converted
         * into a link field since it is really a substate of
         * DLMGMT_ACTIVE -- it should not survive as a flag beyond
         * this point.
         */
        linkp->ll_transient = (flags & DLMGMT_TRANSIENT) ? B_TRUE : B_FALSE;
        flags &= ~DLMGMT_TRANSIENT;

        if (avl_find(&dlmgmt_name_avl, linkp, &name_where) != NULL ||
            avl_find(&dlmgmt_id_avl, linkp, &id_where) != NULL) {
                err = EEXIST;
                goto done;
        }

        avl_insert(&dlmgmt_name_avl, linkp, name_where);
        avl_insert(&dlmgmt_id_avl, linkp, id_where);

        if ((flags & DLMGMT_ACTIVE) && (err = link_activate(linkp)) != 0) {
                avl_remove(&dlmgmt_name_avl, linkp);
                avl_remove(&dlmgmt_id_avl, linkp);
                goto done;
        }

        linkp->ll_flags = flags;
        dlmgmt_advance(linkp);
        *linkpp = linkp;

done:
        if (err != 0)
                free(linkp);
        return (err);
}

int
dlmgmt_destroy_common(dlmgmt_link_t *linkp, uint32_t flags)
{
        /*
         * After dlmgmt_create_common() the link flags should only
         * ever include ACTIVE or PERSIST.
         */
        assert((linkp->ll_flags & ~(DLMGMT_ACTIVE | DLMGMT_PERSIST)) == 0);

        if ((linkp->ll_flags & flags) == 0) {
                /*
                 * The link does not exist in the specified space.
                 */
                return (ENOENT);
        }

        linkp->ll_flags &= ~flags;
        if (flags & DLMGMT_PERSIST) {
                dlmgmt_linkattr_t *next, *attrp;

                for (attrp = linkp->ll_head; attrp != NULL; attrp = next) {
                        next = attrp->lp_next;
                        free(attrp->lp_val);
                        free(attrp);
                }
                linkp->ll_head = NULL;
        }

        if ((flags & DLMGMT_ACTIVE) && linkp->ll_zoneid != GLOBAL_ZONEID) {
                (void) zone_remove_datalink(linkp->ll_zoneid, linkp->ll_linkid);
                if (linkp->ll_onloan)
                        avl_remove(&dlmgmt_loan_avl, linkp);
        }

        if (linkp->ll_flags == 0) {
                avl_remove(&dlmgmt_id_avl, linkp);
                avl_remove(&dlmgmt_name_avl, linkp);
                link_destroy(linkp);
        }

        return (0);
}

int
dlmgmt_getattr_common(dlmgmt_linkattr_t **headp, const char *attr,
    dlmgmt_getattr_retval_t *retvalp)
{
        int                     err;
        void                    *attrval;
        size_t                  attrsz;
        dladm_datatype_t        attrtype;

        err = linkattr_get(headp, attr, &attrval, &attrsz, &attrtype);
        if (err != 0)
                return (err);

        assert(attrsz > 0);
        if (attrsz > MAXLINKATTRVALLEN)
                return (EINVAL);

        retvalp->lr_type = attrtype;
        retvalp->lr_attrsz = attrsz;
        bcopy(attrval, retvalp->lr_attrval, attrsz);
        return (0);
}

void
dlmgmt_dlconf_table_lock(boolean_t write)
{
        if (write)
                (void) pthread_rwlock_wrlock(&dlmgmt_dlconf_lock);
        else
                (void) pthread_rwlock_rdlock(&dlmgmt_dlconf_lock);
}

void
dlmgmt_dlconf_table_unlock(void)
{
        (void) pthread_rwlock_unlock(&dlmgmt_dlconf_lock);
}

int
dlconf_create(const char *name, datalink_id_t linkid, datalink_class_t class,
    uint32_t media, zoneid_t zoneid, dlmgmt_dlconf_t **dlconfpp)
{
        dlmgmt_dlconf_t *dlconfp = NULL;
        int             err = 0;

        if (dlmgmt_nextconfid == 0) {
                err = ENOSPC;
                goto done;
        }

        if ((dlconfp = calloc(1, sizeof (dlmgmt_dlconf_t))) == NULL) {
                err = ENOMEM;
                goto done;
        }

        (void) strlcpy(dlconfp->ld_link, name, MAXLINKNAMELEN);
        dlconfp->ld_linkid = linkid;
        dlconfp->ld_class = class;
        dlconfp->ld_media = media;
        dlconfp->ld_id = dlmgmt_nextconfid;
        dlconfp->ld_zoneid = zoneid;

done:
        *dlconfpp = dlconfp;
        return (err);
}

void
dlconf_destroy(dlmgmt_dlconf_t *dlconfp)
{
        dlmgmt_linkattr_t *next, *attrp;

        for (attrp = dlconfp->ld_head; attrp != NULL; attrp = next) {
                next = attrp->lp_next;
                free(attrp->lp_val);
                free(attrp);
        }
        free(dlconfp);
}

int
dlmgmt_generate_name(const char *prefix, char *name, size_t size,
    zoneid_t zoneid)
{
        dlmgmt_prefix_t *lpp, *prev = NULL;
        dlmgmt_link_t   link, *linkp;

        /*
         * See whether the requested prefix is already in the list.
         */
        for (lpp = &dlmgmt_prefixlist; lpp != NULL;
            prev = lpp, lpp = lpp->lp_next) {
                if (lpp->lp_zoneid == zoneid &&
                    strcmp(prefix, lpp->lp_prefix) == 0)
                        break;
        }

        /*
         * Not found.
         */
        if (lpp == NULL) {
                assert(prev != NULL);

                /*
                 * First add this new prefix into the prefix list.
                 */
                if ((lpp = malloc(sizeof (dlmgmt_prefix_t))) == NULL)
                        return (ENOMEM);

                prev->lp_next = lpp;
                lpp->lp_next = NULL;
                lpp->lp_zoneid = zoneid;
                lpp->lp_nextppa = 0;
                (void) strlcpy(lpp->lp_prefix, prefix, MAXLINKNAMELEN);

                /*
                 * Now determine this prefix's nextppa.
                 */
                (void) snprintf(link.ll_link, MAXLINKNAMELEN, "%s%d",
                    prefix, 0);
                link.ll_zoneid = zoneid;
                if ((linkp = avl_find(&dlmgmt_name_avl, &link, NULL)) != NULL)
                        dlmgmt_advance_ppa(linkp);
        }

        if (lpp->lp_nextppa == (uint_t)-1)
                return (ENOSPC);

        (void) snprintf(name, size, "%s%d", prefix, lpp->lp_nextppa);
        return (0);
}

/*
 * Advance the next available ppa value if the name prefix of the current
 * link is in the prefix list.
 */
static void
dlmgmt_advance_ppa(dlmgmt_link_t *linkp)
{
        dlmgmt_prefix_t *lpp;
        char            prefix[MAXLINKNAMELEN];
        char            linkname[MAXLINKNAMELEN];
        uint_t          start, ppa;

        (void) dlpi_parselink(linkp->ll_link, prefix, &ppa);

        /*
         * See whether the requested prefix is already in the list.
         */
        for (lpp = &dlmgmt_prefixlist; lpp != NULL; lpp = lpp->lp_next) {
                if (lpp->lp_zoneid == linkp->ll_zoneid &&
                    strcmp(prefix, lpp->lp_prefix) == 0)
                        break;
        }

        /*
         * If the link name prefix is in the list, advance the
         * next available ppa for the <prefix>N name.
         */
        if (lpp == NULL || lpp->lp_nextppa != ppa)
                return;

        start = lpp->lp_nextppa++;
        linkp = AVL_NEXT(&dlmgmt_name_avl, linkp);
        while (lpp->lp_nextppa != start) {
                if (lpp->lp_nextppa == (uint_t)-1) {
                        /*
                         * wrapped around. search from <prefix>1.
                         */
                        lpp->lp_nextppa = 0;
                        (void) snprintf(linkname, MAXLINKNAMELEN,
                            "%s%d", lpp->lp_prefix, lpp->lp_nextppa);
                        linkp = link_by_name(linkname, lpp->lp_zoneid);
                        if (linkp == NULL)
                                return;
                } else {
                        if (linkp == NULL)
                                return;
                        (void) dlpi_parselink(linkp->ll_link, prefix, &ppa);
                        if ((strcmp(prefix, lpp->lp_prefix) != 0) ||
                            (ppa != lpp->lp_nextppa)) {
                                return;
                        }
                }
                linkp = AVL_NEXT(&dlmgmt_name_avl, linkp);
                lpp->lp_nextppa++;
        }
        lpp->lp_nextppa = (uint_t)-1;
}

/*
 * Advance to the next available linkid value.
 */
static void
dlmgmt_advance_linkid(dlmgmt_link_t *linkp)
{
        datalink_id_t   start;

        if (linkp->ll_linkid != dlmgmt_nextlinkid)
                return;

        start = dlmgmt_nextlinkid;
        linkp = AVL_NEXT(&dlmgmt_id_avl, linkp);

        do {
                if (dlmgmt_nextlinkid == DATALINK_MAX_LINKID) {
                        /*
                         * wrapped around. search from 1.
                         */
                        dlmgmt_nextlinkid = 1;
                        if ((linkp = link_by_id(1, GLOBAL_ZONEID)) == NULL)
                                return;
                } else {
                        dlmgmt_nextlinkid++;
                        if (linkp == NULL)
                                return;
                        if (linkp->ll_linkid != dlmgmt_nextlinkid)
                                return;
                }

                linkp = AVL_NEXT(&dlmgmt_id_avl, linkp);
        } while (dlmgmt_nextlinkid != start);

        dlmgmt_nextlinkid = DATALINK_INVALID_LINKID;
}

/*
 * Advance various global values, for example, next linkid value, next ppa for
 * various prefix etc.
 */
void
dlmgmt_advance(dlmgmt_link_t *linkp)
{
        dlmgmt_advance_linkid(linkp);
        dlmgmt_advance_ppa(linkp);
}

/*
 * Advance to the next available dlconf id.
 */
void
dlmgmt_advance_dlconfid(dlmgmt_dlconf_t *dlconfp)
{
        uint_t  start;

        start = dlmgmt_nextconfid++;
        dlconfp = AVL_NEXT(&dlmgmt_dlconf_avl, dlconfp);
        while (dlmgmt_nextconfid != start) {
                if (dlmgmt_nextconfid == 0) {
                        dlmgmt_dlconf_t dlconf;

                        /*
                         * wrapped around. search from 1.
                         */
                        dlconf.ld_id = dlmgmt_nextconfid = 1;
                        dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL);
                        if (dlconfp == NULL)
                                return;
                } else {
                        if ((dlconfp == NULL) ||
                            (dlconfp->ld_id != dlmgmt_nextconfid)) {
                                return;
                        }
                }
                dlconfp = AVL_NEXT(&dlmgmt_dlconf_avl, dlconfp);
                dlmgmt_nextconfid++;
        }
        dlmgmt_nextconfid = 0;
}