dlmgmt_util.c revision 327151705b7439cb7ab35c370f682cac7ef9523a
/*
* 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.
*/
/*
* 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 becomes active and it belongs to a non-global zone, it is also added
* to that zone.
*/
int
link_activate(dlmgmt_link_t *linkp)
{
int err = 0;
zoneid_t zoneid;
if (zone_check_datalink(&zoneid, linkp->ll_linkid) == 0) {
/*
* This link was already added to a non-global zone. This can
* happen if dlmgmtd is restarted.
*/
if (zoneid != linkp->ll_zoneid) {
if (link_by_name(linkp->ll_link, zoneid) != NULL) {
err = EEXIST;
goto done;
}
avl_remove(&dlmgmt_name_avl, linkp);
linkp->ll_zoneid = zoneid;
avl_add(&dlmgmt_name_avl, linkp);
avl_add(&dlmgmt_loan_avl, linkp);
linkp->ll_onloan = B_TRUE;
}
} else if (linkp->ll_zoneid != GLOBAL_ZONEID) {
err = zone_add_datalink(linkp->ll_zoneid, linkp->ll_linkid);
}
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;
linkp = avl_find(&dlmgmt_id_avl, &link, NULL);
if (zoneid != GLOBAL_ZONEID && linkp->ll_zoneid != zoneid)
linkp = 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 ((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;
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)
{
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;
}