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 is 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 
 388                         /*
 389                          * If dlmgmtd already has a link with this
 390                          * name under the NGZ then we have a problem.
 391                          */
 392                         if (link_by_name(linkp->ll_link, zoneid) != NULL) {
 393                                 err = EEXIST;
 394                                 goto done;
 395                         }
 396 
 397                         /*
 398                          * Remove the current linkp entry from the
 399                          * list because it's under the wrong zoneid.
 400                          * We don't have to update the dlmgmt_id_avl
 401                          * because it compares entries by ll_linkid
 402                          * only.
 403                          */
 404                         if (avl_find(&dlmgmt_name_avl, linkp, NULL) != NULL)
 405                                 avl_remove(&dlmgmt_name_avl, linkp);
 406 
 407                         /*
 408                          * Update the link to reflect the fact that
 409                          * it's on-loan to an NGZ and re-add it to the
 410                          * list.
 411                          */
 412                         linkp->ll_zoneid = zoneid;
 413                         avl_add(&dlmgmt_name_avl, linkp);
 414                         linkp->ll_onloan = B_TRUE;
 415 
 416                         /*
 417                          * When a VNIC is not persistent and loaned to
 418                          * a zone it is considered transient. This is
 419                          * the same logic found in do_create_vnic()
 420                          * and is needed here in the event of a
 421                          * dlmgmtd restart.
 422                          */
 423                         if (linkp->ll_class == DATALINK_CLASS_VNIC &&
 424                             !(linkp->ll_flags & DLMGMT_PERSIST))
 425                                 linkp->ll_trans = B_TRUE;
 426                 }
 427         } else if (linkp->ll_zoneid != GLOBAL_ZONEID) {
 428                 /*
 429                  * In this case the link was not found under any NGZs
 430                  * but according to its ll_zoneid member it is owned
 431                  * by an NGZ. Add the datalink to the appropriate zone
 432                  * datalink list.
 433                  */
 434                 err = zone_add_datalink(linkp->ll_zoneid, linkp->ll_linkid);
 435                 assert(linkp->ll_onloan == B_FALSE);
 436         }
 437 done:
 438         if (err == 0)
 439                 linkp->ll_flags |= DLMGMT_ACTIVE;
 440         return (err);
 441 }
 442 
 443 /*
 444  * Is linkp visible from the caller's zoneid?  It is if the link is in the
 445  * same zone as the caller, or if the caller is in the global zone and the
 446  * link is on loan to a non-global zone.
 447  */
 448 boolean_t
 449 link_is_visible(dlmgmt_link_t *linkp, zoneid_t zoneid)
 450 {
 451         return (linkp->ll_zoneid == zoneid ||
 452             (zoneid == GLOBAL_ZONEID && linkp->ll_onloan));
 453 }
 454 
 455 dlmgmt_link_t *
 456 link_by_id(datalink_id_t linkid, zoneid_t zoneid)
 457 {
 458         dlmgmt_link_t link, *linkp;
 459 
 460         link.ll_linkid = linkid;
 461         if ((linkp = avl_find(&dlmgmt_id_avl, &link, NULL)) == NULL)
 462                 return (NULL);
 463         if (zoneid != GLOBAL_ZONEID && linkp->ll_zoneid != zoneid)
 464                 return (NULL);
 465         return (linkp);
 466 }
 467 
 468 dlmgmt_link_t *
 469 link_by_name(const char *name, zoneid_t zoneid)
 470 {
 471         dlmgmt_link_t   link, *linkp;
 472 
 473         (void) strlcpy(link.ll_link, name, MAXLINKNAMELEN);
 474         link.ll_zoneid = zoneid;
 475         linkp = avl_find(&dlmgmt_name_avl, &link, NULL);
 476         return (linkp);
 477 }
 478 
 479 int
 480 dlmgmt_create_common(const char *name, datalink_class_t class, uint32_t media,
 481     zoneid_t zoneid, uint32_t flags, dlmgmt_link_t **linkpp)
 482 {
 483         dlmgmt_link_t   *linkp = NULL;
 484         avl_index_t     name_where, id_where;
 485         int             err = 0;
 486 
 487         if (!dladm_valid_linkname(name))
 488                 return (EINVAL);
 489         if (dlmgmt_nextlinkid == DATALINK_INVALID_LINKID)
 490                 return (ENOSPC);
 491         if (flags & ~(DLMGMT_ACTIVE | DLMGMT_PERSIST | DLMGMT_TRANSIENT) ||
 492             ((flags & DLMGMT_PERSIST) && (flags & DLMGMT_TRANSIENT)) ||
 493             flags == 0)
 494                 return (EINVAL);
 495 
 496         if ((linkp = calloc(1, sizeof (dlmgmt_link_t))) == NULL) {
 497                 err = ENOMEM;
 498                 goto done;
 499         }
 500 
 501         (void) strlcpy(linkp->ll_link, name, MAXLINKNAMELEN);
 502         linkp->ll_class = class;
 503         linkp->ll_media = media;
 504         linkp->ll_linkid = dlmgmt_nextlinkid;
 505         linkp->ll_zoneid = zoneid;
 506         linkp->ll_gen = 0;
 507 
 508         /*
 509          * While DLMGMT_TRANSIENT starts off as a flag it is converted
 510          * into a link field since it is really a substate of
 511          * DLMGMT_ACTIVE -- it should not survive as a flag beyond
 512          * this point.
 513          */
 514         linkp->ll_trans = (flags & DLMGMT_TRANSIENT) ? B_TRUE : B_FALSE;
 515         flags &= ~DLMGMT_TRANSIENT;
 516 
 517         if (avl_find(&dlmgmt_name_avl, linkp, &name_where) != NULL ||
 518             avl_find(&dlmgmt_id_avl, linkp, &id_where) != NULL) {
 519                 err = EEXIST;
 520                 goto done;
 521         }
 522 
 523         avl_insert(&dlmgmt_name_avl, linkp, name_where);
 524         avl_insert(&dlmgmt_id_avl, linkp, id_where);
 525 
 526         if ((flags & DLMGMT_ACTIVE) && (err = link_activate(linkp)) != 0) {
 527                 avl_remove(&dlmgmt_name_avl, linkp);
 528                 avl_remove(&dlmgmt_id_avl, linkp);
 529                 goto done;
 530         }
 531 
 532         linkp->ll_flags = flags;
 533         dlmgmt_advance(linkp);
 534         *linkpp = linkp;
 535 
 536 done:
 537         if (err != 0)
 538                 free(linkp);
 539         return (err);
 540 }
 541 
 542 int
 543 dlmgmt_destroy_common(dlmgmt_link_t *linkp, uint32_t flags)
 544 {
 545         /*
 546          * After dlmgmt_create_common() the link flags should only
 547          * ever include ACTIVE or PERSIST.
 548          */
 549         assert((linkp->ll_flags & ~(DLMGMT_ACTIVE | DLMGMT_PERSIST)) == 0);
 550 
 551         if ((linkp->ll_flags & flags) == 0) {
 552                 /*
 553                  * The link does not exist in the specified space.
 554                  */
 555                 return (ENOENT);
 556         }
 557 
 558         linkp->ll_flags &= ~flags;
 559         if (flags & DLMGMT_PERSIST) {
 560                 dlmgmt_linkattr_t *next, *attrp;
 561 
 562                 for (attrp = linkp->ll_head; attrp != NULL; attrp = next) {
 563                         next = attrp->lp_next;
 564                         free(attrp->lp_val);
 565                         free(attrp);
 566                 }
 567                 linkp->ll_head = NULL;
 568         }
 569 
 570         if ((flags & DLMGMT_ACTIVE) && linkp->ll_zoneid != GLOBAL_ZONEID) {
 571                 (void) zone_remove_datalink(linkp->ll_zoneid, linkp->ll_linkid);
 572         }
 573 
 574         if (linkp->ll_flags == 0) {
 575                 avl_remove(&dlmgmt_id_avl, linkp);
 576                 avl_remove(&dlmgmt_name_avl, linkp);
 577                 link_destroy(linkp);
 578         }
 579 
 580         return (0);
 581 }
 582 
 583 int
 584 dlmgmt_getattr_common(dlmgmt_linkattr_t **headp, const char *attr,
 585     dlmgmt_getattr_retval_t *retvalp)
 586 {
 587         int                     err;
 588         void                    *attrval;
 589         size_t                  attrsz;
 590         dladm_datatype_t        attrtype;
 591 
 592         err = linkattr_get(headp, attr, &attrval, &attrsz, &attrtype);
 593         if (err != 0)
 594                 return (err);
 595 
 596         assert(attrsz > 0);
 597         if (attrsz > MAXLINKATTRVALLEN)
 598                 return (EINVAL);
 599 
 600         retvalp->lr_type = attrtype;
 601         retvalp->lr_attrsz = attrsz;
 602         bcopy(attrval, retvalp->lr_attrval, attrsz);
 603         return (0);
 604 }
 605 
 606 void
 607 dlmgmt_dlconf_table_lock(boolean_t write)
 608 {
 609         if (write)
 610                 (void) pthread_rwlock_wrlock(&dlmgmt_dlconf_lock);
 611         else
 612                 (void) pthread_rwlock_rdlock(&dlmgmt_dlconf_lock);
 613 }
 614 
 615 void
 616 dlmgmt_dlconf_table_unlock(void)
 617 {
 618         (void) pthread_rwlock_unlock(&dlmgmt_dlconf_lock);
 619 }
 620 
 621 int
 622 dlconf_create(const char *name, datalink_id_t linkid, datalink_class_t class,
 623     uint32_t media, zoneid_t zoneid, dlmgmt_dlconf_t **dlconfpp)
 624 {
 625         dlmgmt_dlconf_t *dlconfp = NULL;
 626         int             err = 0;
 627 
 628         if (dlmgmt_nextconfid == 0) {
 629                 err = ENOSPC;
 630                 goto done;
 631         }
 632 
 633         if ((dlconfp = calloc(1, sizeof (dlmgmt_dlconf_t))) == NULL) {
 634                 err = ENOMEM;
 635                 goto done;
 636         }
 637 
 638         (void) strlcpy(dlconfp->ld_link, name, MAXLINKNAMELEN);
 639         dlconfp->ld_linkid = linkid;
 640         dlconfp->ld_class = class;
 641         dlconfp->ld_media = media;
 642         dlconfp->ld_id = dlmgmt_nextconfid;
 643         dlconfp->ld_zoneid = zoneid;
 644 
 645 done:
 646         *dlconfpp = dlconfp;
 647         return (err);
 648 }
 649 
 650 void
 651 dlconf_destroy(dlmgmt_dlconf_t *dlconfp)
 652 {
 653         dlmgmt_linkattr_t *next, *attrp;
 654 
 655         for (attrp = dlconfp->ld_head; attrp != NULL; attrp = next) {
 656                 next = attrp->lp_next;
 657                 free(attrp->lp_val);
 658                 free(attrp);
 659         }
 660         free(dlconfp);
 661 }
 662 
 663 int
 664 dlmgmt_generate_name(const char *prefix, char *name, size_t size,
 665     zoneid_t zoneid)
 666 {
 667         dlmgmt_prefix_t *lpp, *prev = NULL;
 668         dlmgmt_link_t   link, *linkp;
 669 
 670         /*
 671          * See whether the requested prefix is already in the list.
 672          */
 673         for (lpp = &dlmgmt_prefixlist; lpp != NULL;
 674             prev = lpp, lpp = lpp->lp_next) {
 675                 if (lpp->lp_zoneid == zoneid &&
 676                     strcmp(prefix, lpp->lp_prefix) == 0)
 677                         break;
 678         }
 679 
 680         /*
 681          * Not found.
 682          */
 683         if (lpp == NULL) {
 684                 assert(prev != NULL);
 685 
 686                 /*
 687                  * First add this new prefix into the prefix list.
 688                  */
 689                 if ((lpp = malloc(sizeof (dlmgmt_prefix_t))) == NULL)
 690                         return (ENOMEM);
 691 
 692                 prev->lp_next = lpp;
 693                 lpp->lp_next = NULL;
 694                 lpp->lp_zoneid = zoneid;
 695                 lpp->lp_nextppa = 0;
 696                 (void) strlcpy(lpp->lp_prefix, prefix, MAXLINKNAMELEN);
 697 
 698                 /*
 699                  * Now determine this prefix's nextppa.
 700                  */
 701                 (void) snprintf(link.ll_link, MAXLINKNAMELEN, "%s%d",
 702                     prefix, 0);
 703                 link.ll_zoneid = zoneid;
 704                 if ((linkp = avl_find(&dlmgmt_name_avl, &link, NULL)) != NULL)
 705                         dlmgmt_advance_ppa(linkp);
 706         }
 707 
 708         if (lpp->lp_nextppa == (uint_t)-1)
 709                 return (ENOSPC);
 710 
 711         (void) snprintf(name, size, "%s%d", prefix, lpp->lp_nextppa);
 712         return (0);
 713 }
 714 
 715 /*
 716  * Advance the next available ppa value if the name prefix of the current
 717  * link is in the prefix list.
 718  */
 719 static void
 720 dlmgmt_advance_ppa(dlmgmt_link_t *linkp)
 721 {
 722         dlmgmt_prefix_t *lpp;
 723         char            prefix[MAXLINKNAMELEN];
 724         char            linkname[MAXLINKNAMELEN];
 725         uint_t          start, ppa;
 726 
 727         (void) dlpi_parselink(linkp->ll_link, prefix, &ppa);
 728 
 729         /*
 730          * See whether the requested prefix is already in the list.
 731          */
 732         for (lpp = &dlmgmt_prefixlist; lpp != NULL; lpp = lpp->lp_next) {
 733                 if (lpp->lp_zoneid == linkp->ll_zoneid &&
 734                     strcmp(prefix, lpp->lp_prefix) == 0)
 735                         break;
 736         }
 737 
 738         /*
 739          * If the link name prefix is in the list, advance the
 740          * next available ppa for the <prefix>N name.
 741          */
 742         if (lpp == NULL || lpp->lp_nextppa != ppa)
 743                 return;
 744 
 745         start = lpp->lp_nextppa++;
 746         linkp = AVL_NEXT(&dlmgmt_name_avl, linkp);
 747         while (lpp->lp_nextppa != start) {
 748                 if (lpp->lp_nextppa == (uint_t)-1) {
 749                         /*
 750                          * wrapped around. search from <prefix>1.
 751                          */
 752                         lpp->lp_nextppa = 0;
 753                         (void) snprintf(linkname, MAXLINKNAMELEN,
 754                             "%s%d", lpp->lp_prefix, lpp->lp_nextppa);
 755                         linkp = link_by_name(linkname, lpp->lp_zoneid);
 756                         if (linkp == NULL)
 757                                 return;
 758                 } else {
 759                         if (linkp == NULL)
 760                                 return;
 761                         (void) dlpi_parselink(linkp->ll_link, prefix, &ppa);
 762                         if ((strcmp(prefix, lpp->lp_prefix) != 0) ||
 763                             (ppa != lpp->lp_nextppa)) {
 764                                 return;
 765                         }
 766                 }
 767                 linkp = AVL_NEXT(&dlmgmt_name_avl, linkp);
 768                 lpp->lp_nextppa++;
 769         }
 770         lpp->lp_nextppa = (uint_t)-1;
 771 }
 772 
 773 /*
 774  * Advance to the next available linkid value.
 775  */
 776 static void
 777 dlmgmt_advance_linkid(dlmgmt_link_t *linkp)
 778 {
 779         datalink_id_t   start;
 780 
 781         if (linkp->ll_linkid != dlmgmt_nextlinkid)
 782                 return;
 783 
 784         start = dlmgmt_nextlinkid;
 785         linkp = AVL_NEXT(&dlmgmt_id_avl, linkp);
 786 
 787         do {
 788                 if (dlmgmt_nextlinkid == DATALINK_MAX_LINKID) {
 789                         /*
 790                          * wrapped around. search from 1.
 791                          */
 792                         dlmgmt_nextlinkid = 1;
 793                         if ((linkp = link_by_id(1, GLOBAL_ZONEID)) == NULL)
 794                                 return;
 795                 } else {
 796                         dlmgmt_nextlinkid++;
 797                         if (linkp == NULL)
 798                                 return;
 799                         if (linkp->ll_linkid != dlmgmt_nextlinkid)
 800                                 return;
 801                 }
 802 
 803                 linkp = AVL_NEXT(&dlmgmt_id_avl, linkp);
 804         } while (dlmgmt_nextlinkid != start);
 805 
 806         dlmgmt_nextlinkid = DATALINK_INVALID_LINKID;
 807 }
 808 
 809 /*
 810  * Advance various global values, for example, next linkid value, next ppa for
 811  * various prefix etc.
 812  */
 813 void
 814 dlmgmt_advance(dlmgmt_link_t *linkp)
 815 {
 816         dlmgmt_advance_linkid(linkp);
 817         dlmgmt_advance_ppa(linkp);
 818 }
 819 
 820 /*
 821  * Advance to the next available dlconf id.
 822  */
 823 void
 824 dlmgmt_advance_dlconfid(dlmgmt_dlconf_t *dlconfp)
 825 {
 826         uint_t  start;
 827 
 828         start = dlmgmt_nextconfid++;
 829         dlconfp = AVL_NEXT(&dlmgmt_dlconf_avl, dlconfp);
 830         while (dlmgmt_nextconfid != start) {
 831                 if (dlmgmt_nextconfid == 0) {
 832                         dlmgmt_dlconf_t dlconf;
 833 
 834                         /*
 835                          * wrapped around. search from 1.
 836                          */
 837                         dlconf.ld_id = dlmgmt_nextconfid = 1;
 838                         dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL);
 839                         if (dlconfp == NULL)
 840                                 return;
 841                 } else {
 842                         if ((dlconfp == NULL) ||
 843                             (dlconfp->ld_id != dlmgmt_nextconfid)) {
 844                                 return;
 845                         }
 846                 }
 847                 dlconfp = AVL_NEXT(&dlmgmt_dlconf_avl, dlconfp);
 848                 dlmgmt_nextconfid++;
 849         }
 850         dlmgmt_nextconfid = 0;
 851 }