1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 
  22 /*
  23  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
  24  * Copyright 2017 Joyent, Inc.
  25  */
  26 
  27 /*
  28  * Utility functions used by the dlmgmtd daemon.
  29  */
  30 
  31 #include <assert.h>
  32 #include <pthread.h>
  33 #include <stddef.h>
  34 #include <stdlib.h>
  35 #include <stdio.h>
  36 #include <errno.h>
  37 #include <strings.h>
  38 #include <string.h>
  39 #include <syslog.h>
  40 #include <stdarg.h>
  41 #include <zone.h>
  42 #include <errno.h>
  43 #include <libdlpi.h>
  44 #include "dlmgmt_impl.h"
  45 
  46 /*
  47  * There are three datalink AVL tables.  The dlmgmt_name_avl tree contains all
  48  * datalinks and is keyed by zoneid and link name.  The dlmgmt_id_avl also
  49  * contains all datalinks, and it is keyed by link ID.
  50  */
  51 avl_tree_t      dlmgmt_name_avl;
  52 avl_tree_t      dlmgmt_id_avl;
  53 
  54 avl_tree_t      dlmgmt_dlconf_avl;
  55 
  56 static pthread_rwlock_t dlmgmt_avl_lock = PTHREAD_RWLOCK_INITIALIZER;
  57 static pthread_mutex_t  dlmgmt_avl_mutex = PTHREAD_MUTEX_INITIALIZER;
  58 static pthread_cond_t   dlmgmt_avl_cv = PTHREAD_COND_INITIALIZER;
  59 static pthread_rwlock_t dlmgmt_dlconf_lock = PTHREAD_RWLOCK_INITIALIZER;
  60 
  61 typedef struct dlmgmt_prefix {
  62         struct dlmgmt_prefix    *lp_next;
  63         char                    lp_prefix[MAXLINKNAMELEN];
  64         zoneid_t                lp_zoneid;
  65         uint_t                  lp_nextppa;
  66 } dlmgmt_prefix_t;
  67 static dlmgmt_prefix_t  dlmgmt_prefixlist;
  68 
  69 datalink_id_t           dlmgmt_nextlinkid;
  70 static datalink_id_t    dlmgmt_nextconfid = 1;
  71 
  72 static void             dlmgmt_advance_linkid(dlmgmt_link_t *);
  73 static void             dlmgmt_advance_ppa(dlmgmt_link_t *);
  74 
  75 void
  76 dlmgmt_log(int pri, const char *fmt, ...)
  77 {
  78         va_list alist;
  79 
  80         va_start(alist, fmt);
  81         if (debug) {
  82                 (void) vfprintf(stderr, fmt, alist);
  83                 (void) fputc('\n', stderr);
  84         } else {
  85                 vsyslog(pri, fmt, alist);
  86         }
  87         va_end(alist);
  88 }
  89 
  90 static int
  91 cmp_link_by_name(const void *v1, const void *v2)
  92 {
  93         const dlmgmt_link_t *link1 = v1;
  94         const dlmgmt_link_t *link2 = v2;
  95         int cmp;
  96 
  97         cmp = strcmp(link1->ll_link, link2->ll_link);
  98         return ((cmp == 0) ? 0 : ((cmp < 0) ? -1 : 1));
  99 }
 100 
 101 /*
 102  * Note that the zoneid associated with a link is effectively part of its
 103  * name.  This is essentially what results in having each zone have disjoint
 104  * datalink namespaces.
 105  */
 106 static int
 107 cmp_link_by_zname(const void *v1, const void *v2)
 108 {
 109         const dlmgmt_link_t *link1 = v1;
 110         const dlmgmt_link_t *link2 = v2;
 111 
 112         if (link1->ll_zoneid < link2->ll_zoneid)
 113                 return (-1);
 114         if (link1->ll_zoneid > link2->ll_zoneid)
 115                 return (1);
 116         return (cmp_link_by_name(link1, link2));
 117 }
 118 
 119 static int
 120 cmp_link_by_id(const void *v1, const void *v2)
 121 {
 122         const dlmgmt_link_t *link1 = v1;
 123         const dlmgmt_link_t *link2 = v2;
 124 
 125         if ((uint64_t)(link1->ll_linkid) == (uint64_t)(link2->ll_linkid))
 126                 return (0);
 127         else if ((uint64_t)(link1->ll_linkid) < (uint64_t)(link2->ll_linkid))
 128                 return (-1);
 129         else
 130                 return (1);
 131 }
 132 
 133 static int
 134 cmp_dlconf_by_id(const void *v1, const void *v2)
 135 {
 136         const dlmgmt_dlconf_t *dlconfp1 = v1;
 137         const dlmgmt_dlconf_t *dlconfp2 = v2;
 138 
 139         if (dlconfp1->ld_id == dlconfp2->ld_id)
 140                 return (0);
 141         else if (dlconfp1->ld_id < dlconfp2->ld_id)
 142                 return (-1);
 143         else
 144                 return (1);
 145 }
 146 
 147 void
 148 dlmgmt_linktable_init(void)
 149 {
 150         /*
 151          * Initialize the prefix list. First add the "net" prefix for the
 152          * global zone to the list.
 153          */
 154         dlmgmt_prefixlist.lp_next = NULL;
 155         dlmgmt_prefixlist.lp_zoneid = GLOBAL_ZONEID;
 156         dlmgmt_prefixlist.lp_nextppa = 0;
 157         (void) strlcpy(dlmgmt_prefixlist.lp_prefix, "net", MAXLINKNAMELEN);
 158 
 159         avl_create(&dlmgmt_name_avl, cmp_link_by_zname, sizeof (dlmgmt_link_t),
 160             offsetof(dlmgmt_link_t, ll_name_node));
 161         avl_create(&dlmgmt_id_avl, cmp_link_by_id, sizeof (dlmgmt_link_t),
 162             offsetof(dlmgmt_link_t, ll_id_node));
 163         avl_create(&dlmgmt_dlconf_avl, cmp_dlconf_by_id,
 164             sizeof (dlmgmt_dlconf_t), offsetof(dlmgmt_dlconf_t, ld_node));
 165         dlmgmt_nextlinkid = 1;
 166 }
 167 
 168 void
 169 dlmgmt_linktable_fini(void)
 170 {
 171         dlmgmt_prefix_t *lpp, *next;
 172 
 173         for (lpp = dlmgmt_prefixlist.lp_next; lpp != NULL; lpp = next) {
 174                 next = lpp->lp_next;
 175                 free(lpp);
 176         }
 177 
 178         avl_destroy(&dlmgmt_dlconf_avl);
 179         avl_destroy(&dlmgmt_name_avl);
 180         avl_destroy(&dlmgmt_id_avl);
 181 }
 182 
 183 static void
 184 linkattr_add(dlmgmt_linkattr_t **headp, dlmgmt_linkattr_t *attrp)
 185 {
 186         if (*headp == NULL) {
 187                 *headp = attrp;
 188         } else {
 189                 (*headp)->lp_prev = attrp;
 190                 attrp->lp_next = *headp;
 191                 *headp = attrp;
 192         }
 193 }
 194 
 195 static void
 196 linkattr_rm(dlmgmt_linkattr_t **headp, dlmgmt_linkattr_t *attrp)
 197 {
 198         dlmgmt_linkattr_t *next, *prev;
 199 
 200         next = attrp->lp_next;
 201         prev = attrp->lp_prev;
 202         if (next != NULL)
 203                 next->lp_prev = prev;
 204         if (prev != NULL)
 205                 prev->lp_next = next;
 206         else
 207                 *headp = next;
 208 }
 209 
 210 dlmgmt_linkattr_t *
 211 linkattr_find(dlmgmt_linkattr_t *headp, const char *attr)
 212 {
 213         dlmgmt_linkattr_t *attrp;
 214 
 215         for (attrp = headp; attrp != NULL; attrp = attrp->lp_next) {
 216                 if (strcmp(attrp->lp_name, attr) == 0)
 217                         break;
 218         }
 219         return (attrp);
 220 }
 221 
 222 int
 223 linkattr_set(dlmgmt_linkattr_t **headp, const char *attr, void *attrval,
 224     size_t attrsz, dladm_datatype_t type)
 225 {
 226         dlmgmt_linkattr_t       *attrp;
 227         void                    *newval;
 228         boolean_t               new;
 229 
 230         attrp = linkattr_find(*headp, attr);
 231         if (attrp != NULL) {
 232                 /*
 233                  * It is already set.  If the value changed, update it.
 234                  */
 235                 if (linkattr_equal(headp, attr, attrval, attrsz))
 236                         return (0);
 237                 new = B_FALSE;
 238         } else {
 239                 /*
 240                  * It is not set yet, allocate the linkattr and prepend to the
 241                  * list.
 242                  */
 243                 if ((attrp = calloc(1, sizeof (dlmgmt_linkattr_t))) == NULL)
 244                         return (ENOMEM);
 245 
 246                 (void) strlcpy(attrp->lp_name, attr, MAXLINKATTRLEN);
 247                 new = B_TRUE;
 248         }
 249         if ((newval = calloc(1, attrsz)) == NULL) {
 250                 if (new)
 251                         free(attrp);
 252                 return (ENOMEM);
 253         }
 254 
 255         if (!new)
 256                 free(attrp->lp_val);
 257         attrp->lp_val = newval;
 258         bcopy(attrval, attrp->lp_val, attrsz);
 259         attrp->lp_sz = attrsz;
 260         attrp->lp_type = type;
 261         attrp->lp_linkprop = dladm_attr_is_linkprop(attr);
 262         if (new)
 263                 linkattr_add(headp, attrp);
 264         return (0);
 265 }
 266 
 267 void
 268 linkattr_unset(dlmgmt_linkattr_t **headp, const char *attr)
 269 {
 270         dlmgmt_linkattr_t *attrp;
 271 
 272         if ((attrp = linkattr_find(*headp, attr)) != NULL) {
 273                 linkattr_rm(headp, attrp);
 274                 free(attrp->lp_val);
 275                 free(attrp);
 276         }
 277 }
 278 
 279 int
 280 linkattr_get(dlmgmt_linkattr_t **headp, const char *attr, void **attrvalp,
 281     size_t *attrszp, dladm_datatype_t *typep)
 282 {
 283         dlmgmt_linkattr_t *attrp;
 284 
 285         if ((attrp = linkattr_find(*headp, attr)) == NULL)
 286                 return (ENOENT);
 287 
 288         *attrvalp = attrp->lp_val;
 289         *attrszp = attrp->lp_sz;
 290         if (typep != NULL)
 291                 *typep = attrp->lp_type;
 292         return (0);
 293 }
 294 
 295 boolean_t
 296 linkattr_equal(dlmgmt_linkattr_t **headp, const char *attr, void *attrval,
 297     size_t attrsz)
 298 {
 299         void    *saved_attrval;
 300         size_t  saved_attrsz;
 301 
 302         if (linkattr_get(headp, attr, &saved_attrval, &saved_attrsz, NULL) != 0)
 303                 return (B_FALSE);
 304 
 305         return ((saved_attrsz == attrsz) &&
 306             (memcmp(saved_attrval, attrval, attrsz) == 0));
 307 }
 308 
 309 void
 310 linkattr_destroy(dlmgmt_link_t *linkp)
 311 {
 312         dlmgmt_linkattr_t *next, *attrp;
 313 
 314         for (attrp = linkp->ll_head; attrp != NULL; attrp = next) {
 315                 next = attrp->lp_next;
 316                 free(attrp->lp_val);
 317                 free(attrp);
 318         }
 319 }
 320 
 321 static int
 322 dlmgmt_table_readwritelock(boolean_t write)
 323 {
 324         if (write)
 325                 return (pthread_rwlock_trywrlock(&dlmgmt_avl_lock));
 326         else
 327                 return (pthread_rwlock_tryrdlock(&dlmgmt_avl_lock));
 328 }
 329 
 330 void
 331 dlmgmt_table_lock(boolean_t write)
 332 {
 333         (void) pthread_mutex_lock(&dlmgmt_avl_mutex);
 334         while (dlmgmt_table_readwritelock(write) == EBUSY)
 335                 (void) pthread_cond_wait(&dlmgmt_avl_cv, &dlmgmt_avl_mutex);
 336 
 337         (void) pthread_mutex_unlock(&dlmgmt_avl_mutex);
 338 }
 339 
 340 void
 341 dlmgmt_table_unlock(void)
 342 {
 343         (void) pthread_rwlock_unlock(&dlmgmt_avl_lock);
 344         (void) pthread_mutex_lock(&dlmgmt_avl_mutex);
 345         (void) pthread_cond_broadcast(&dlmgmt_avl_cv);
 346         (void) pthread_mutex_unlock(&dlmgmt_avl_mutex);
 347 }
 348 
 349 void
 350 link_destroy(dlmgmt_link_t *linkp)
 351 {
 352         linkattr_destroy(linkp);
 353         free(linkp);
 354 }
 355 
 356 /*
 357  * Set the DLMGMT_ACTIVE flag on the link to note that it is active.
 358  * When a link is active and owned by an NGZ then it is added to
 359  * that zone's datalink list.
 360  */
 361 int
 362 link_activate(dlmgmt_link_t *linkp)
 363 {
 364         int             err = 0;
 365         zoneid_t        zoneid = ALL_ZONES;
 366 
 367         /*
 368          * If zone_check_datalink() returns 0 it means we found the
 369          * link in one of the NGZ's datalink lists. Otherwise the link
 370          * is under the GZ.
 371          */
 372         if (zone_check_datalink(&zoneid, linkp->ll_linkid) == 0) {
 373                 /*
 374                  * This is a bit subtle. If the following expression
 375                  * is true then the link was found in one of the NGZ's
 376                  * datalink lists but the link structure has it under
 377                  * the GZ. This means that the link is supposed to be
 378                  * loaned out to an NGZ but the dlmgmtd state is out
 379                  * of sync -- possibly due to the process restarting.
 380                  * In this case we need to sync the dlmgmtd state by
 381                  * marking it as on-loan to the NGZ it's currently
 382                  * under.
 383                  */
 384                 if (zoneid != linkp->ll_zoneid) {
 385                         assert(linkp->ll_zoneid == 0);
 386                         assert(linkp->ll_onloan == B_FALSE);
 387                         assert(linkp->ll_transient == 0);
 388 
 389                         /*
 390                          * If dlmgmtd already has a link with this
 391                          * name under the NGZ then we have a problem.
 392                          */
 393                         if (link_by_name(linkp->ll_link, zoneid) != NULL) {
 394                                 err = EEXIST;
 395                                 goto done;
 396                         }
 397 
 398                         /*
 399                          * Remove the current linkp entry from the
 400                          * list because it's under the wrong zoneid.
 401                          * We don't have to update the dlmgmt_id_avl
 402                          * because it compares entries by ll_linkid
 403                          * only.
 404                          */
 405                         if (avl_find(&dlmgmt_name_avl, linkp, NULL) != NULL)
 406                                 avl_remove(&dlmgmt_name_avl, linkp);
 407 
 408                         /*
 409                          * Update the link to reflect the fact that
 410                          * it's on-loan to an NGZ and re-add it to the
 411                          * list.
 412                          */
 413                         linkp->ll_zoneid = zoneid;
 414                         avl_add(&dlmgmt_name_avl, linkp);
 415                         linkp->ll_onloan = B_TRUE;
 416 
 417                         /*
 418                          * When a VNIC is not persistent and loaned to
 419                          * a zone it is considered transient. This is
 420                          * the same logic found in do_create_vnic()
 421                          * and is needed here in the event of a
 422                          * dlmgmtd restart.
 423                          */
 424                         if (linkp->ll_class == DATALINK_CLASS_VNIC &&
 425                             !(linkp->ll_flags & DLMGMT_PERSIST))
 426                                 linkp->ll_transient = B_TRUE;
 427                 }
 428         } else if (linkp->ll_zoneid != GLOBAL_ZONEID) {
 429                 /*
 430                  * In this case the link was not found under any NGZ
 431                  * but according to its ll_zoneid member it is owned
 432                  * by an NGZ. Add the datalink to the appropriate zone
 433                  * datalink list.
 434                  */
 435                 err = zone_add_datalink(linkp->ll_zoneid, linkp->ll_linkid);
 436                 assert(linkp->ll_onloan == B_FALSE);
 437         }
 438 done:
 439         if (err == 0)
 440                 linkp->ll_flags |= DLMGMT_ACTIVE;
 441         return (err);
 442 }
 443 
 444 /*
 445  * Is linkp visible from the caller's zoneid?  It is if the link is in the
 446  * same zone as the caller, or if the caller is in the global zone and the
 447  * link is on loan to a non-global zone.
 448  */
 449 boolean_t
 450 link_is_visible(dlmgmt_link_t *linkp, zoneid_t zoneid)
 451 {
 452         return (linkp->ll_zoneid == zoneid ||
 453             (zoneid == GLOBAL_ZONEID && linkp->ll_onloan));
 454 }
 455 
 456 dlmgmt_link_t *
 457 link_by_id(datalink_id_t linkid, zoneid_t zoneid)
 458 {
 459         dlmgmt_link_t link, *linkp;
 460 
 461         link.ll_linkid = linkid;
 462         if ((linkp = avl_find(&dlmgmt_id_avl, &link, NULL)) == NULL)
 463                 return (NULL);
 464         if (zoneid != GLOBAL_ZONEID && linkp->ll_zoneid != zoneid)
 465                 return (NULL);
 466         return (linkp);
 467 }
 468 
 469 dlmgmt_link_t *
 470 link_by_name(const char *name, zoneid_t zoneid)
 471 {
 472         dlmgmt_link_t   link, *linkp;
 473 
 474         (void) strlcpy(link.ll_link, name, MAXLINKNAMELEN);
 475         link.ll_zoneid = zoneid;
 476         linkp = avl_find(&dlmgmt_name_avl, &link, NULL);
 477         return (linkp);
 478 }
 479 
 480 int
 481 dlmgmt_create_common(const char *name, datalink_class_t class, uint32_t media,
 482     zoneid_t zoneid, uint32_t flags, dlmgmt_link_t **linkpp)
 483 {
 484         dlmgmt_link_t   *linkp = NULL;
 485         avl_index_t     name_where, id_where;
 486         int             err = 0;
 487 
 488         if (!dladm_valid_linkname(name))
 489                 return (EINVAL);
 490         if (dlmgmt_nextlinkid == DATALINK_INVALID_LINKID)
 491                 return (ENOSPC);
 492         if (flags & ~(DLMGMT_ACTIVE | DLMGMT_PERSIST | DLMGMT_TRANSIENT) ||
 493             ((flags & DLMGMT_PERSIST) && (flags & DLMGMT_TRANSIENT)) ||
 494             flags == 0) {
 495                 return (EINVAL);
 496         }
 497 
 498         if ((linkp = calloc(1, sizeof (dlmgmt_link_t))) == NULL) {
 499                 err = ENOMEM;
 500                 goto done;
 501         }
 502 
 503         (void) strlcpy(linkp->ll_link, name, MAXLINKNAMELEN);
 504         linkp->ll_class = class;
 505         linkp->ll_media = media;
 506         linkp->ll_linkid = dlmgmt_nextlinkid;
 507         linkp->ll_zoneid = zoneid;
 508         linkp->ll_gen = 0;
 509 
 510         /*
 511          * While DLMGMT_TRANSIENT starts off as a flag it is converted
 512          * into a link field since it is really a substate of
 513          * DLMGMT_ACTIVE -- it should not survive as a flag beyond
 514          * this point.
 515          */
 516         linkp->ll_transient = (flags & DLMGMT_TRANSIENT) ? B_TRUE : B_FALSE;
 517         flags &= ~DLMGMT_TRANSIENT;
 518 
 519         if (avl_find(&dlmgmt_name_avl, linkp, &name_where) != NULL ||
 520             avl_find(&dlmgmt_id_avl, linkp, &id_where) != NULL) {
 521                 err = EEXIST;
 522                 goto done;
 523         }
 524 
 525         avl_insert(&dlmgmt_name_avl, linkp, name_where);
 526         avl_insert(&dlmgmt_id_avl, linkp, id_where);
 527 
 528         if ((flags & DLMGMT_ACTIVE) && (err = link_activate(linkp)) != 0) {
 529                 avl_remove(&dlmgmt_name_avl, linkp);
 530                 avl_remove(&dlmgmt_id_avl, linkp);
 531                 goto done;
 532         }
 533 
 534         linkp->ll_flags = flags;
 535         dlmgmt_advance(linkp);
 536         *linkpp = linkp;
 537 
 538 done:
 539         if (err != 0)
 540                 free(linkp);
 541         return (err);
 542 }
 543 
 544 int
 545 dlmgmt_destroy_common(dlmgmt_link_t *linkp, uint32_t flags)
 546 {
 547         /*
 548          * After dlmgmt_create_common() the link flags should only
 549          * ever include ACTIVE or PERSIST.
 550          */
 551         assert((linkp->ll_flags & ~(DLMGMT_ACTIVE | DLMGMT_PERSIST)) == 0);
 552 
 553         if ((linkp->ll_flags & flags) == 0) {
 554                 /*
 555                  * The link does not exist in the specified space.
 556                  */
 557                 return (ENOENT);
 558         }
 559 
 560         linkp->ll_flags &= ~flags;
 561         if (flags & DLMGMT_PERSIST) {
 562                 dlmgmt_linkattr_t *next, *attrp;
 563 
 564                 for (attrp = linkp->ll_head; attrp != NULL; attrp = next) {
 565                         next = attrp->lp_next;
 566                         free(attrp->lp_val);
 567                         free(attrp);
 568                 }
 569                 linkp->ll_head = NULL;
 570         }
 571 
 572         if ((flags & DLMGMT_ACTIVE) && linkp->ll_zoneid != GLOBAL_ZONEID) {
 573                 (void) zone_remove_datalink(linkp->ll_zoneid, linkp->ll_linkid);
 574         }
 575 
 576         if (linkp->ll_flags == 0) {
 577                 avl_remove(&dlmgmt_id_avl, linkp);
 578                 avl_remove(&dlmgmt_name_avl, linkp);
 579                 link_destroy(linkp);
 580         }
 581 
 582         return (0);
 583 }
 584 
 585 int
 586 dlmgmt_getattr_common(dlmgmt_linkattr_t **headp, const char *attr,
 587     dlmgmt_getattr_retval_t *retvalp)
 588 {
 589         int                     err;
 590         void                    *attrval;
 591         size_t                  attrsz;
 592         dladm_datatype_t        attrtype;
 593 
 594         err = linkattr_get(headp, attr, &attrval, &attrsz, &attrtype);
 595         if (err != 0)
 596                 return (err);
 597 
 598         assert(attrsz > 0);
 599         if (attrsz > MAXLINKATTRVALLEN)
 600                 return (EINVAL);
 601 
 602         retvalp->lr_type = attrtype;
 603         retvalp->lr_attrsz = attrsz;
 604         bcopy(attrval, retvalp->lr_attrval, attrsz);
 605         return (0);
 606 }
 607 
 608 void
 609 dlmgmt_dlconf_table_lock(boolean_t write)
 610 {
 611         if (write)
 612                 (void) pthread_rwlock_wrlock(&dlmgmt_dlconf_lock);
 613         else
 614                 (void) pthread_rwlock_rdlock(&dlmgmt_dlconf_lock);
 615 }
 616 
 617 void
 618 dlmgmt_dlconf_table_unlock(void)
 619 {
 620         (void) pthread_rwlock_unlock(&dlmgmt_dlconf_lock);
 621 }
 622 
 623 int
 624 dlconf_create(const char *name, datalink_id_t linkid, datalink_class_t class,
 625     uint32_t media, zoneid_t zoneid, dlmgmt_dlconf_t **dlconfpp)
 626 {
 627         dlmgmt_dlconf_t *dlconfp = NULL;
 628         int             err = 0;
 629 
 630         if (dlmgmt_nextconfid == 0) {
 631                 err = ENOSPC;
 632                 goto done;
 633         }
 634 
 635         if ((dlconfp = calloc(1, sizeof (dlmgmt_dlconf_t))) == NULL) {
 636                 err = ENOMEM;
 637                 goto done;
 638         }
 639 
 640         (void) strlcpy(dlconfp->ld_link, name, MAXLINKNAMELEN);
 641         dlconfp->ld_linkid = linkid;
 642         dlconfp->ld_class = class;
 643         dlconfp->ld_media = media;
 644         dlconfp->ld_id = dlmgmt_nextconfid;
 645         dlconfp->ld_zoneid = zoneid;
 646 
 647 done:
 648         *dlconfpp = dlconfp;
 649         return (err);
 650 }
 651 
 652 void
 653 dlconf_destroy(dlmgmt_dlconf_t *dlconfp)
 654 {
 655         dlmgmt_linkattr_t *next, *attrp;
 656 
 657         for (attrp = dlconfp->ld_head; attrp != NULL; attrp = next) {
 658                 next = attrp->lp_next;
 659                 free(attrp->lp_val);
 660                 free(attrp);
 661         }
 662         free(dlconfp);
 663 }
 664 
 665 int
 666 dlmgmt_generate_name(const char *prefix, char *name, size_t size,
 667     zoneid_t zoneid)
 668 {
 669         dlmgmt_prefix_t *lpp, *prev = NULL;
 670         dlmgmt_link_t   link, *linkp;
 671 
 672         /*
 673          * See whether the requested prefix is already in the list.
 674          */
 675         for (lpp = &dlmgmt_prefixlist; lpp != NULL;
 676             prev = lpp, lpp = lpp->lp_next) {
 677                 if (lpp->lp_zoneid == zoneid &&
 678                     strcmp(prefix, lpp->lp_prefix) == 0)
 679                         break;
 680         }
 681 
 682         /*
 683          * Not found.
 684          */
 685         if (lpp == NULL) {
 686                 assert(prev != NULL);
 687 
 688                 /*
 689                  * First add this new prefix into the prefix list.
 690                  */
 691                 if ((lpp = malloc(sizeof (dlmgmt_prefix_t))) == NULL)
 692                         return (ENOMEM);
 693 
 694                 prev->lp_next = lpp;
 695                 lpp->lp_next = NULL;
 696                 lpp->lp_zoneid = zoneid;
 697                 lpp->lp_nextppa = 0;
 698                 (void) strlcpy(lpp->lp_prefix, prefix, MAXLINKNAMELEN);
 699 
 700                 /*
 701                  * Now determine this prefix's nextppa.
 702                  */
 703                 (void) snprintf(link.ll_link, MAXLINKNAMELEN, "%s%d",
 704                     prefix, 0);
 705                 link.ll_zoneid = zoneid;
 706                 if ((linkp = avl_find(&dlmgmt_name_avl, &link, NULL)) != NULL)
 707                         dlmgmt_advance_ppa(linkp);
 708         }
 709 
 710         if (lpp->lp_nextppa == (uint_t)-1)
 711                 return (ENOSPC);
 712 
 713         (void) snprintf(name, size, "%s%d", prefix, lpp->lp_nextppa);
 714         return (0);
 715 }
 716 
 717 /*
 718  * Advance the next available ppa value if the name prefix of the current
 719  * link is in the prefix list.
 720  */
 721 static void
 722 dlmgmt_advance_ppa(dlmgmt_link_t *linkp)
 723 {
 724         dlmgmt_prefix_t *lpp;
 725         char            prefix[MAXLINKNAMELEN];
 726         char            linkname[MAXLINKNAMELEN];
 727         uint_t          start, ppa;
 728 
 729         (void) dlpi_parselink(linkp->ll_link, prefix, &ppa);
 730 
 731         /*
 732          * See whether the requested prefix is already in the list.
 733          */
 734         for (lpp = &dlmgmt_prefixlist; lpp != NULL; lpp = lpp->lp_next) {
 735                 if (lpp->lp_zoneid == linkp->ll_zoneid &&
 736                     strcmp(prefix, lpp->lp_prefix) == 0)
 737                         break;
 738         }
 739 
 740         /*
 741          * If the link name prefix is in the list, advance the
 742          * next available ppa for the <prefix>N name.
 743          */
 744         if (lpp == NULL || lpp->lp_nextppa != ppa)
 745                 return;
 746 
 747         start = lpp->lp_nextppa++;
 748         linkp = AVL_NEXT(&dlmgmt_name_avl, linkp);
 749         while (lpp->lp_nextppa != start) {
 750                 if (lpp->lp_nextppa == (uint_t)-1) {
 751                         /*
 752                          * wrapped around. search from <prefix>1.
 753                          */
 754                         lpp->lp_nextppa = 0;
 755                         (void) snprintf(linkname, MAXLINKNAMELEN,
 756                             "%s%d", lpp->lp_prefix, lpp->lp_nextppa);
 757                         linkp = link_by_name(linkname, lpp->lp_zoneid);
 758                         if (linkp == NULL)
 759                                 return;
 760                 } else {
 761                         if (linkp == NULL)
 762                                 return;
 763                         (void) dlpi_parselink(linkp->ll_link, prefix, &ppa);
 764                         if ((strcmp(prefix, lpp->lp_prefix) != 0) ||
 765                             (ppa != lpp->lp_nextppa)) {
 766                                 return;
 767                         }
 768                 }
 769                 linkp = AVL_NEXT(&dlmgmt_name_avl, linkp);
 770                 lpp->lp_nextppa++;
 771         }
 772         lpp->lp_nextppa = (uint_t)-1;
 773 }
 774 
 775 /*
 776  * Advance to the next available linkid value.
 777  */
 778 static void
 779 dlmgmt_advance_linkid(dlmgmt_link_t *linkp)
 780 {
 781         datalink_id_t   start;
 782 
 783         if (linkp->ll_linkid != dlmgmt_nextlinkid)
 784                 return;
 785 
 786         start = dlmgmt_nextlinkid;
 787         linkp = AVL_NEXT(&dlmgmt_id_avl, linkp);
 788 
 789         do {
 790                 if (dlmgmt_nextlinkid == DATALINK_MAX_LINKID) {
 791                         /*
 792                          * wrapped around. search from 1.
 793                          */
 794                         dlmgmt_nextlinkid = 1;
 795                         if ((linkp = link_by_id(1, GLOBAL_ZONEID)) == NULL)
 796                                 return;
 797                 } else {
 798                         dlmgmt_nextlinkid++;
 799                         if (linkp == NULL)
 800                                 return;
 801                         if (linkp->ll_linkid != dlmgmt_nextlinkid)
 802                                 return;
 803                 }
 804 
 805                 linkp = AVL_NEXT(&dlmgmt_id_avl, linkp);
 806         } while (dlmgmt_nextlinkid != start);
 807 
 808         dlmgmt_nextlinkid = DATALINK_INVALID_LINKID;
 809 }
 810 
 811 /*
 812  * Advance various global values, for example, next linkid value, next ppa for
 813  * various prefix etc.
 814  */
 815 void
 816 dlmgmt_advance(dlmgmt_link_t *linkp)
 817 {
 818         dlmgmt_advance_linkid(linkp);
 819         dlmgmt_advance_ppa(linkp);
 820 }
 821 
 822 /*
 823  * Advance to the next available dlconf id.
 824  */
 825 void
 826 dlmgmt_advance_dlconfid(dlmgmt_dlconf_t *dlconfp)
 827 {
 828         uint_t  start;
 829 
 830         start = dlmgmt_nextconfid++;
 831         dlconfp = AVL_NEXT(&dlmgmt_dlconf_avl, dlconfp);
 832         while (dlmgmt_nextconfid != start) {
 833                 if (dlmgmt_nextconfid == 0) {
 834                         dlmgmt_dlconf_t dlconf;
 835 
 836                         /*
 837                          * wrapped around. search from 1.
 838                          */
 839                         dlconf.ld_id = dlmgmt_nextconfid = 1;
 840                         dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL);
 841                         if (dlconfp == NULL)
 842                                 return;
 843                 } else {
 844                         if ((dlconfp == NULL) ||
 845                             (dlconfp->ld_id != dlmgmt_nextconfid)) {
 846                                 return;
 847                         }
 848                 }
 849                 dlconfp = AVL_NEXT(&dlmgmt_dlconf_avl, dlconfp);
 850                 dlmgmt_nextconfid++;
 851         }
 852         dlmgmt_nextconfid = 0;
 853 }