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 (c) 2011, Joyent Inc. All rights reserved.
  25  */
  26 
  27 /*
  28  * Main door handler functions used by dlmgmtd to process the different door
  29  * call requests. Door call requests can come from the user-land applications,
  30  * or from the kernel.
  31  *
  32  * Note on zones handling:
  33  *
  34  * There are two zoneid's associated with a link.  One is the zoneid of the
  35  * zone in which the link was created (ll_zoneid in the dlmgmt_link_t), and
  36  * the other is the zoneid of the zone where the link is currently assigned
  37  * (the "zone" link property).  The two can be different if a datalink is
  38  * created in the global zone and subsequently assigned to a non-global zone
  39  * via zonecfg or via explicitly setting the "zone" link property.
  40  *
  41  * Door clients can see links that were created in their zone, and links that
  42  * are currently assigned to their zone.  Door clients in a zone can only
  43  * modify links that were created in their zone.
  44  *
  45  * The datalink ID space is global, while each zone has its own datalink name
  46  * space.  This allows each zone to have complete freedom over the names that
  47  * they assign to links created within the zone.
  48  */
  49 
  50 #include <assert.h>
  51 #include <alloca.h>
  52 #include <errno.h>
  53 #include <priv_utils.h>
  54 #include <stdlib.h>
  55 #include <strings.h>
  56 #include <syslog.h>
  57 #include <sys/sysevent/eventdefs.h>
  58 #include <zone.h>
  59 #include <libsysevent.h>
  60 #include <libdlmgmt.h>
  61 #include <librcm.h>
  62 #include <sys/types.h>
  63 #include <sys/stat.h>
  64 #include <fcntl.h>
  65 #include <unistd.h>
  66 #include "dlmgmt_impl.h"
  67 
  68 typedef void dlmgmt_door_handler_t(void *, void *, size_t *, zoneid_t,
  69     ucred_t *);
  70 
  71 typedef struct dlmgmt_door_info_s {
  72         uint_t                  di_cmd;
  73         size_t                  di_reqsz;
  74         size_t                  di_acksz;
  75         dlmgmt_door_handler_t   *di_handler;
  76 } dlmgmt_door_info_t;
  77 
  78 /*
  79  * Check if the caller has the required privileges to operate on a link of the
  80  * given class.
  81  */
  82 static int
  83 dlmgmt_checkprivs(datalink_class_t class, ucred_t *cred)
  84 {
  85         const priv_set_t *eset;
  86 
  87         eset = ucred_getprivset(cred, PRIV_EFFECTIVE);
  88         if (eset != NULL && ((class == DATALINK_CLASS_IPTUN &&
  89             priv_ismember(eset, PRIV_SYS_IPTUN_CONFIG)) ||
  90             priv_ismember(eset, PRIV_SYS_DL_CONFIG) ||
  91             priv_ismember(eset, PRIV_SYS_NET_CONFIG)))
  92                 return (0);
  93         return (EACCES);
  94 }
  95 
  96 static dlmgmt_link_t *
  97 dlmgmt_getlink_by_dev(char *devname, zoneid_t zoneid)
  98 {
  99         dlmgmt_link_t *linkp = avl_first(&dlmgmt_id_avl);
 100 
 101         for (; linkp != NULL; linkp = AVL_NEXT(&dlmgmt_id_avl, linkp)) {
 102                 if (link_is_visible(linkp, zoneid) &&
 103                     (linkp->ll_class == DATALINK_CLASS_PHYS) &&
 104                     linkattr_equal(&(linkp->ll_head), FDEVNAME, devname,
 105                     strlen(devname) + 1)) {
 106                         return (linkp);
 107                 }
 108         }
 109         return (NULL);
 110 }
 111 
 112 /*
 113  * Post the EC_DATALINK sysevent for the given linkid. This sysevent will
 114  * be consumed by the datalink sysevent module.
 115  */
 116 static void
 117 dlmgmt_post_sysevent(const char *subclass, datalink_id_t linkid,
 118     boolean_t reconfigured)
 119 {
 120         nvlist_t        *nvl = NULL;
 121         sysevent_id_t   eid;
 122         int             err;
 123 
 124         if (((err = nvlist_alloc(&nvl, NV_UNIQUE_NAME_TYPE, 0)) != 0) ||
 125             ((err = nvlist_add_uint64(nvl, RCM_NV_LINKID, linkid)) != 0) ||
 126             ((err = nvlist_add_boolean_value(nvl, RCM_NV_RECONFIGURED,
 127             reconfigured)) != 0)) {
 128                 goto done;
 129         }
 130 
 131         if (sysevent_post_event(EC_DATALINK, (char *)subclass, SUNW_VENDOR,
 132             (char *)progname, nvl, &eid) == -1) {
 133                 err = errno;
 134         }
 135 
 136 done:
 137         if (err != 0) {
 138                 dlmgmt_log(LOG_WARNING, "dlmgmt_post_sysevent(%d) failed: %s",
 139                     linkid, strerror(err));
 140         }
 141         nvlist_free(nvl);
 142 }
 143 
 144 /* ARGSUSED */
 145 static void
 146 dlmgmt_upcall_create(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 147     ucred_t *cred)
 148 {
 149         dlmgmt_upcall_arg_create_t *create = argp;
 150         dlmgmt_create_retval_t  *retvalp = retp;
 151         datalink_class_t        class;
 152         uint32_t                media;
 153         dlmgmt_link_t           *linkp;
 154         char                    link[MAXLINKNAMELEN];
 155         uint32_t                flags;
 156         int                     err = 0;
 157         boolean_t               created = B_FALSE;
 158         boolean_t               reconfigured = B_FALSE;
 159 
 160         /*
 161          * Determine whether this link is persistent. Note that this request
 162          * is coming from kernel so this link must be active.
 163          */
 164         flags = DLMGMT_ACTIVE | (create->ld_persist ? DLMGMT_PERSIST : 0);
 165 
 166         class = create->ld_class;
 167         media = create->ld_media;
 168 
 169         /*
 170          * Hold the writer lock to update the link table.
 171          */
 172         dlmgmt_table_lock(B_TRUE);
 173 
 174         if ((err = dlmgmt_checkprivs(class, cred)) != 0)
 175                 goto done;
 176 
 177         /*
 178          * Check to see whether this is the reattachment of an existing
 179          * physical link. If so, return its linkid.
 180          */
 181         if ((class == DATALINK_CLASS_PHYS) && (linkp =
 182             dlmgmt_getlink_by_dev(create->ld_devname, zoneid)) != NULL) {
 183                 if (linkattr_equal(&(linkp->ll_head), FPHYMAJ,
 184                     &create->ld_phymaj, sizeof (uint64_t)) &&
 185                     linkattr_equal(&(linkp->ll_head), FPHYINST,
 186                     &create->ld_phyinst, sizeof (uint64_t)) &&
 187                     (linkp->ll_flags & flags) == flags) {
 188                         /*
 189                          * If nothing has been changed, directly return.
 190                          */
 191                         goto noupdate;
 192                 }
 193 
 194                 err = linkattr_set(&(linkp->ll_head), FPHYMAJ,
 195                     &create->ld_phymaj, sizeof (uint64_t), DLADM_TYPE_UINT64);
 196                 if (err != 0)
 197                         goto done;
 198 
 199                 err = linkattr_set(&(linkp->ll_head), FPHYINST,
 200                     &create->ld_phyinst, sizeof (uint64_t), DLADM_TYPE_UINT64);
 201                 if (err != 0)
 202                         goto done;
 203 
 204                 /*
 205                  * This is a device that is dynamic reconfigured.
 206                  */
 207                 if ((linkp->ll_flags & DLMGMT_ACTIVE) == 0)
 208                         reconfigured = B_TRUE;
 209 
 210                 if ((err = link_activate(linkp)) != 0)
 211                         goto done;
 212                 linkp->ll_flags |= flags;
 213                 linkp->ll_gen++;
 214 
 215                 goto done;
 216         }
 217 
 218         if ((err = dlmgmt_create_common(create->ld_devname, class, media,
 219             zoneid, flags, &linkp)) == EEXIST) {
 220                 /*
 221                  * The link name already exists. Return error if this is a
 222                  * non-physical link (in that case, the link name must be
 223                  * the same as the given name).
 224                  */
 225                 if (class != DATALINK_CLASS_PHYS)
 226                         goto done;
 227 
 228                 /*
 229                  * The physical link's name already exists, request
 230                  * a suggested link name: net<nextppa>
 231                  */
 232                 err = dlmgmt_generate_name("net", link, MAXLINKNAMELEN, zoneid);
 233                 if (err != 0)
 234                         goto done;
 235 
 236                 err = dlmgmt_create_common(link, class, media, zoneid, flags,
 237                     &linkp);
 238         }
 239 
 240         if (err != 0)
 241                 goto done;
 242 
 243         created = B_TRUE;
 244 
 245         /*
 246          * This is a new link.  Only need to persist link attributes for
 247          * physical links.
 248          */
 249         if (class == DATALINK_CLASS_PHYS &&
 250             (((err = linkattr_set(&linkp->ll_head, FDEVNAME, create->ld_devname,
 251             strlen(create->ld_devname) + 1, DLADM_TYPE_STR)) != 0) ||
 252             ((err = linkattr_set(&linkp->ll_head, FPHYMAJ, &create->ld_phymaj,
 253             sizeof (uint64_t), DLADM_TYPE_UINT64)) != 0) ||
 254             ((err = linkattr_set(&linkp->ll_head, FPHYINST, &create->ld_phyinst,
 255             sizeof (uint64_t), DLADM_TYPE_UINT64)) != 0))) {
 256                 (void) dlmgmt_destroy_common(linkp, flags);
 257         }
 258 
 259 done:
 260         if ((err == 0) && ((err = dlmgmt_write_db_entry(linkp->ll_link, linkp,
 261             linkp->ll_flags)) != 0) && created) {
 262                 (void) dlmgmt_destroy_common(linkp, flags);
 263         }
 264 
 265 noupdate:
 266         if (err == 0)
 267                 retvalp->lr_linkid = linkp->ll_linkid;
 268 
 269         dlmgmt_table_unlock();
 270 
 271         if ((err == 0) && (class == DATALINK_CLASS_PHYS)) {
 272                 /*
 273                  * Post the ESC_DATALINK_PHYS_ADD sysevent. This sysevent
 274                  * is consumed by the datalink sysevent module which in
 275                  * turn generates the RCM_RESOURCE_LINK_NEW RCM event.
 276                  */
 277                 dlmgmt_post_sysevent(ESC_DATALINK_PHYS_ADD,
 278                     retvalp->lr_linkid, reconfigured);
 279         }
 280 
 281         retvalp->lr_err = err;
 282 }
 283 
 284 /* ARGSUSED */
 285 static void
 286 dlmgmt_upcall_update(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 287     ucred_t *cred)
 288 {
 289         dlmgmt_upcall_arg_update_t      *update = argp;
 290         dlmgmt_update_retval_t          *retvalp = retp;
 291         uint32_t                        media = update->ld_media;
 292         dlmgmt_link_t                   *linkp;
 293         int                             err = 0;
 294 
 295         /*
 296          * Hold the writer lock to update the link table.
 297          */
 298         dlmgmt_table_lock(B_TRUE);
 299 
 300         /*
 301          * Check to see whether this is the reattachment of an existing
 302          * physical link. If so, return its linkid.
 303          */
 304         if ((linkp = dlmgmt_getlink_by_dev(update->ld_devname, zoneid)) ==
 305             NULL) {
 306                 err = ENOENT;
 307                 goto done;
 308         }
 309 
 310         if ((err = dlmgmt_checkprivs(linkp->ll_class, cred)) != 0)
 311                 goto done;
 312 
 313         retvalp->lr_linkid = linkp->ll_linkid;
 314         retvalp->lr_media = media;
 315         if (linkp->ll_media != media && linkp->ll_media != DL_OTHER) {
 316                 /*
 317                  * Assume a DL_ETHER link ce0, a DL_WIFI link ath0
 318                  * 1. # dladm rename-link ce0 net0
 319                  * 2. DR out ce0. net0 is down.
 320                  * 3. use rename-link to have the ath0 device inherit
 321                  *    the configuration from net0
 322                  *    # dladm rename-link ath0 net0
 323                  * 4. DR in ath0.
 324                  * As ath0 and ce0 do not have the same media type, ath0
 325                  * cannot inherit the configuration of net0.
 326                  */
 327                 err = EEXIST;
 328 
 329                 /*
 330                  * Return the media type of the existing link to indicate the
 331                  * reason for the name conflict.
 332                  */
 333                 retvalp->lr_media = linkp->ll_media;
 334                 goto done;
 335         }
 336 
 337         if (update->ld_novanity &&
 338             (strcmp(update->ld_devname, linkp->ll_link) != 0)) {
 339                 /*
 340                  * Return an error if this is a physical link that does not
 341                  * support vanity naming, but the link name is not the same
 342                  * as the given device name.
 343                  */
 344                 err = EEXIST;
 345                 goto done;
 346         }
 347 
 348         if (linkp->ll_media != media) {
 349                 linkp->ll_media = media;
 350                 linkp->ll_gen++;
 351                 (void) dlmgmt_write_db_entry(linkp->ll_link, linkp,
 352                     linkp->ll_flags);
 353         }
 354 
 355 done:
 356         dlmgmt_table_unlock();
 357         retvalp->lr_err = err;
 358 }
 359 
 360 /* ARGSUSED */
 361 static void
 362 dlmgmt_upcall_destroy(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 363     ucred_t *cred)
 364 {
 365         dlmgmt_upcall_arg_destroy_t     *destroy = argp;
 366         dlmgmt_destroy_retval_t         *retvalp = retp;
 367         datalink_id_t                   linkid = destroy->ld_linkid;
 368         dlmgmt_link_t                   *linkp = NULL;
 369         uint32_t                        flags, dflags = 0;
 370         int                             err = 0;
 371 
 372         flags = DLMGMT_ACTIVE | (destroy->ld_persist ? DLMGMT_PERSIST : 0);
 373 
 374         /*
 375          * Hold the writer lock to update the link table.
 376          */
 377         dlmgmt_table_lock(B_TRUE);
 378 
 379         if ((linkp = link_by_id(linkid, zoneid)) == NULL) {
 380                 err = ENOENT;
 381                 goto done;
 382         }
 383 
 384         if ((err = dlmgmt_checkprivs(linkp->ll_class, cred)) != 0)
 385                 goto done;
 386 
 387         if (linkp->ll_tomb == B_TRUE) {
 388                 err = EINPROGRESS;
 389                 goto done;
 390         }
 391 
 392         if (((linkp->ll_flags & flags) & DLMGMT_ACTIVE) != 0) {
 393                 if ((err = dlmgmt_delete_db_entry(linkp, DLMGMT_ACTIVE)) != 0)
 394                         goto done;
 395                 dflags |= DLMGMT_ACTIVE;
 396         }
 397 
 398         if (((linkp->ll_flags & flags) & DLMGMT_PERSIST) != 0) {
 399                 if ((err = dlmgmt_delete_db_entry(linkp, DLMGMT_PERSIST)) != 0)
 400                         goto done;
 401                 dflags |= DLMGMT_PERSIST;
 402         }
 403 
 404         err = dlmgmt_destroy_common(linkp, flags);
 405 done:
 406         if (err != 0 && dflags != 0)
 407                 (void) dlmgmt_write_db_entry(linkp->ll_link, linkp, dflags);
 408 
 409         dlmgmt_table_unlock();
 410         retvalp->lr_err = err;
 411 }
 412 
 413 /* ARGSUSED */
 414 static void
 415 dlmgmt_getname(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 416     ucred_t *cred)
 417 {
 418         dlmgmt_door_getname_t   *getname = argp;
 419         dlmgmt_getname_retval_t *retvalp = retp;
 420         dlmgmt_link_t           *linkp;
 421         int                     err = 0;
 422 
 423         /*
 424          * Hold the reader lock to access the link
 425          */
 426         dlmgmt_table_lock(B_FALSE);
 427         if ((linkp = link_by_id(getname->ld_linkid, zoneid)) == NULL) {
 428                 err = ENOENT;
 429         } else if (strlcpy(retvalp->lr_link, linkp->ll_link, MAXLINKNAMELEN) >=
 430             MAXLINKNAMELEN) {
 431                 err = ENOSPC;
 432         } else {
 433                 retvalp->lr_flags = linkp->ll_flags;
 434                 retvalp->lr_class = linkp->ll_class;
 435                 retvalp->lr_media = linkp->ll_media;
 436         }
 437 
 438         dlmgmt_table_unlock();
 439         retvalp->lr_err = err;
 440 }
 441 
 442 /* ARGSUSED */
 443 static void
 444 dlmgmt_getlinkid(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 445     ucred_t *cred)
 446 {
 447         dlmgmt_door_getlinkid_t *getlinkid = argp;
 448         dlmgmt_getlinkid_retval_t *retvalp = retp;
 449         dlmgmt_link_t           *linkp;
 450         int                     err = 0;
 451 
 452         /* Enable the global zone to lookup links it has given away. */
 453         if (zoneid == GLOBAL_ZONEID && getlinkid->ld_zoneid != -1)
 454                 zoneid = getlinkid->ld_zoneid;
 455 
 456         /*
 457          * Hold the reader lock to access the link
 458          */
 459         dlmgmt_table_lock(B_FALSE);
 460 
 461         if ((linkp = link_by_name(getlinkid->ld_link, zoneid)) == NULL) {
 462                 /*
 463                  * The link does not exist in this zone.
 464                  */
 465                 err = ENOENT;
 466                 goto done;
 467         }
 468 
 469         retvalp->lr_linkid = linkp->ll_linkid;
 470         retvalp->lr_flags = linkp->ll_flags;
 471         retvalp->lr_class = linkp->ll_class;
 472         retvalp->lr_media = linkp->ll_media;
 473 
 474 done:
 475         dlmgmt_table_unlock();
 476         retvalp->lr_err = err;
 477 }
 478 
 479 /* ARGSUSED */
 480 static void
 481 dlmgmt_getnext(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 482     ucred_t *cred)
 483 {
 484         dlmgmt_door_getnext_t   *getnext = argp;
 485         dlmgmt_getnext_retval_t *retvalp = retp;
 486         dlmgmt_link_t           link, *linkp;
 487         avl_index_t             where;
 488         int                     err = 0;
 489 
 490         /*
 491          * Hold the reader lock to access the link
 492          */
 493         dlmgmt_table_lock(B_FALSE);
 494 
 495         link.ll_linkid = (getnext->ld_linkid + 1);
 496         if ((linkp = avl_find(&dlmgmt_id_avl, &link, &where)) == NULL)
 497                 linkp = avl_nearest(&dlmgmt_id_avl, where, AVL_AFTER);
 498 
 499         for (; linkp != NULL; linkp = AVL_NEXT(&dlmgmt_id_avl, linkp)) {
 500                 if (!link_is_visible(linkp, zoneid))
 501                         continue;
 502                 if ((linkp->ll_class & getnext->ld_class) &&
 503                     (linkp->ll_flags & getnext->ld_flags) &&
 504                     DATALINK_MEDIA_ACCEPTED(getnext->ld_dmedia,
 505                     linkp->ll_media))
 506                         break;
 507         }
 508 
 509         if (linkp == NULL) {
 510                 err = ENOENT;
 511         } else {
 512                 retvalp->lr_linkid = linkp->ll_linkid;
 513                 retvalp->lr_class = linkp->ll_class;
 514                 retvalp->lr_media = linkp->ll_media;
 515                 retvalp->lr_flags = linkp->ll_flags;
 516         }
 517 
 518         dlmgmt_table_unlock();
 519         retvalp->lr_err = err;
 520 }
 521 
 522 /* ARGSUSED */
 523 static void
 524 dlmgmt_upcall_getattr(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 525     ucred_t *cred)
 526 {
 527         dlmgmt_upcall_arg_getattr_t     *getattr = argp;
 528         dlmgmt_getattr_retval_t         *retvalp = retp;
 529         dlmgmt_link_t                   *linkp;
 530 
 531         /*
 532          * Hold the reader lock to access the link
 533          */
 534         dlmgmt_table_lock(B_FALSE);
 535         if ((linkp = link_by_id(getattr->ld_linkid, zoneid)) == NULL) {
 536                 retvalp->lr_err = ENOENT;
 537         } else {
 538                 retvalp->lr_err = dlmgmt_getattr_common(&linkp->ll_head,
 539                     getattr->ld_attr, retvalp);
 540         }
 541         dlmgmt_table_unlock();
 542 }
 543 
 544 /* ARGSUSED */
 545 static void
 546 dlmgmt_createid(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 547     ucred_t *cred)
 548 {
 549         dlmgmt_door_createid_t  *createid = argp;
 550         dlmgmt_createid_retval_t *retvalp = retp;
 551         dlmgmt_link_t           *linkp;
 552         datalink_id_t           linkid = DATALINK_INVALID_LINKID;
 553         char                    link[MAXLINKNAMELEN];
 554         int                     err;
 555 
 556         /*
 557          * Hold the writer lock to update the dlconf table.
 558          */
 559         dlmgmt_table_lock(B_TRUE);
 560 
 561         if ((err = dlmgmt_checkprivs(createid->ld_class, cred)) != 0)
 562                 goto done;
 563 
 564         if (createid->ld_prefix) {
 565                 err = dlmgmt_generate_name(createid->ld_link, link,
 566                     MAXLINKNAMELEN, zoneid);
 567                 if (err != 0)
 568                         goto done;
 569 
 570                 err = dlmgmt_create_common(link, createid->ld_class,
 571                     createid->ld_media, zoneid, createid->ld_flags, &linkp);
 572         } else {
 573                 err = dlmgmt_create_common(createid->ld_link,
 574                     createid->ld_class, createid->ld_media, zoneid,
 575                     createid->ld_flags, &linkp);
 576         }
 577 
 578         if (err == 0) {
 579                 /*
 580                  * Keep the active mapping.
 581                  */
 582                 linkid = linkp->ll_linkid;
 583                 if (createid->ld_flags & DLMGMT_ACTIVE) {
 584                         (void) dlmgmt_write_db_entry(linkp->ll_link, linkp,
 585                             DLMGMT_ACTIVE);
 586                 }
 587         }
 588 
 589 done:
 590         dlmgmt_table_unlock();
 591         retvalp->lr_linkid = linkid;
 592         retvalp->lr_err = err;
 593 }
 594 
 595 /* ARGSUSED */
 596 static void
 597 dlmgmt_destroyid(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 598     ucred_t *cred)
 599 {
 600         dlmgmt_door_destroyid_t *destroyid = argp;
 601         dlmgmt_destroyid_retval_t *retvalp = retp;
 602         datalink_id_t           linkid = destroyid->ld_linkid;
 603         uint32_t                flags = destroyid->ld_flags;
 604         dlmgmt_link_t           *linkp = NULL;
 605         int                     err = 0;
 606 
 607         /*
 608          * Hold the writer lock to update the link table.
 609          */
 610         dlmgmt_table_lock(B_TRUE);
 611         if ((linkp = link_by_id(linkid, zoneid)) == NULL) {
 612                 err = ENOENT;
 613                 goto done;
 614         }
 615 
 616         if ((err = dlmgmt_checkprivs(linkp->ll_class, cred)) != 0)
 617                 goto done;
 618 
 619         /*
 620          * Delete the active mapping.
 621          */
 622         if (flags & DLMGMT_ACTIVE)
 623                 err = dlmgmt_delete_db_entry(linkp, DLMGMT_ACTIVE);
 624         if (err == 0)
 625                 err = dlmgmt_destroy_common(linkp, flags);
 626 done:
 627         dlmgmt_table_unlock();
 628         retvalp->lr_err = err;
 629 }
 630 
 631 /*
 632  * Remap a linkid to a given link name, i.e., rename an existing link1
 633  * (ld_linkid) to a non-existent link2 (ld_link): rename link1's name to
 634  * the given link name.
 635  */
 636 /* ARGSUSED */
 637 static void
 638 dlmgmt_remapid(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 639     ucred_t *cred)
 640 {
 641         dlmgmt_door_remapid_t   *remapid = argp;
 642         dlmgmt_remapid_retval_t *retvalp = retp;
 643         dlmgmt_link_t           *linkp;
 644         char                    oldname[MAXLINKNAMELEN];
 645         boolean_t               renamed = B_FALSE;
 646         int                     err = 0;
 647 
 648         if (!dladm_valid_linkname(remapid->ld_link)) {
 649                 retvalp->lr_err = EINVAL;
 650                 return;
 651         }
 652 
 653         /*
 654          * Hold the writer lock to update the link table.
 655          */
 656         dlmgmt_table_lock(B_TRUE);
 657         if ((linkp = link_by_id(remapid->ld_linkid, zoneid)) == NULL) {
 658                 err = ENOENT;
 659                 goto done;
 660         }
 661 
 662         if ((err = dlmgmt_checkprivs(linkp->ll_class, cred)) != 0)
 663                 goto done;
 664 
 665         if (linkp->ll_tomb == B_TRUE) {
 666                 err = EBUSY;
 667                 goto done;
 668         }
 669 
 670 
 671         if (link_by_name(remapid->ld_link, linkp->ll_zoneid) != NULL) {
 672                 err = EEXIST;
 673                 goto done;
 674         }
 675 
 676         (void) strlcpy(oldname, linkp->ll_link, MAXLINKNAMELEN);
 677         avl_remove(&dlmgmt_name_avl, linkp);
 678         (void) strlcpy(linkp->ll_link, remapid->ld_link, MAXLINKNAMELEN);
 679         avl_add(&dlmgmt_name_avl, linkp);
 680         renamed = B_TRUE;
 681 
 682         if (linkp->ll_flags & DLMGMT_ACTIVE) {
 683                 err = dlmgmt_write_db_entry(oldname, linkp, DLMGMT_ACTIVE);
 684                 if (err != 0)
 685                         goto done;
 686         }
 687         if (linkp->ll_flags & DLMGMT_PERSIST) {
 688                 err = dlmgmt_write_db_entry(oldname, linkp, DLMGMT_PERSIST);
 689                 if (err != 0) {
 690                         if (linkp->ll_flags & DLMGMT_ACTIVE) {
 691                                 (void) dlmgmt_write_db_entry(remapid->ld_link,
 692                                     linkp, DLMGMT_ACTIVE);
 693                         }
 694                         goto done;
 695                 }
 696         }
 697 
 698         dlmgmt_advance(linkp);
 699         linkp->ll_gen++;
 700 done:
 701         if (err != 0 && renamed) {
 702                 avl_remove(&dlmgmt_name_avl, linkp);
 703                 (void) strlcpy(linkp->ll_link, oldname, MAXLINKNAMELEN);
 704                 avl_add(&dlmgmt_name_avl, linkp);
 705         }
 706         dlmgmt_table_unlock();
 707         retvalp->lr_err = err;
 708 }
 709 
 710 /* ARGSUSED */
 711 static void
 712 dlmgmt_upid(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 713     ucred_t *cred)
 714 {
 715         dlmgmt_door_upid_t      *upid = argp;
 716         dlmgmt_upid_retval_t    *retvalp = retp;
 717         dlmgmt_link_t           *linkp;
 718         int                     err = 0;
 719 
 720         /*
 721          * Hold the writer lock to update the link table.
 722          */
 723         dlmgmt_table_lock(B_TRUE);
 724         if ((linkp = link_by_id(upid->ld_linkid, zoneid)) == NULL) {
 725                 err = ENOENT;
 726                 goto done;
 727         }
 728 
 729         if ((err = dlmgmt_checkprivs(linkp->ll_class, cred)) != 0)
 730                 goto done;
 731 
 732         if (linkp->ll_tomb == B_TRUE) {
 733                 err = EBUSY;
 734                 goto done;
 735         }
 736 
 737         if (linkp->ll_flags & DLMGMT_ACTIVE) {
 738                 err = EINVAL;
 739                 goto done;
 740         }
 741 
 742         if ((err = link_activate(linkp)) == 0) {
 743                 (void) dlmgmt_write_db_entry(linkp->ll_link, linkp,
 744                     DLMGMT_ACTIVE);
 745         }
 746 done:
 747         dlmgmt_table_unlock();
 748         retvalp->lr_err = err;
 749 }
 750 
 751 /* ARGSUSED */
 752 static void
 753 dlmgmt_createconf(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 754     ucred_t *cred)
 755 {
 756         dlmgmt_door_createconf_t *createconf = argp;
 757         dlmgmt_createconf_retval_t *retvalp = retp;
 758         dlmgmt_dlconf_t         *dlconfp;
 759         int                     err;
 760 
 761         /*
 762          * Hold the writer lock to update the dlconf table.
 763          */
 764         dlmgmt_dlconf_table_lock(B_TRUE);
 765 
 766         if ((err = dlmgmt_checkprivs(createconf->ld_class, cred)) != 0)
 767                 goto done;
 768 
 769         err = dlconf_create(createconf->ld_link, createconf->ld_linkid,
 770             createconf->ld_class, createconf->ld_media, zoneid, &dlconfp);
 771         if (err == 0) {
 772                 avl_add(&dlmgmt_dlconf_avl, dlconfp);
 773                 dlmgmt_advance_dlconfid(dlconfp);
 774                 retvalp->lr_confid = dlconfp->ld_id;
 775         }
 776 done:
 777         dlmgmt_dlconf_table_unlock();
 778         retvalp->lr_err = err;
 779 }
 780 
 781 /* ARGSUSED */
 782 static void
 783 dlmgmt_setattr(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 784     ucred_t *cred)
 785 {
 786         dlmgmt_door_setattr_t   *setattr = argp;
 787         dlmgmt_setattr_retval_t *retvalp = retp;
 788         dlmgmt_dlconf_t         dlconf, *dlconfp;
 789         int                     err = 0;
 790 
 791         /*
 792          * Hold the writer lock to update the dlconf table.
 793          */
 794         dlmgmt_dlconf_table_lock(B_TRUE);
 795 
 796         dlconf.ld_id = setattr->ld_confid;
 797         dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL);
 798         if (dlconfp == NULL || zoneid != dlconfp->ld_zoneid) {
 799                 err = ENOENT;
 800                 goto done;
 801         }
 802 
 803         if ((err = dlmgmt_checkprivs(dlconfp->ld_class, cred)) != 0)
 804                 goto done;
 805 
 806         err = linkattr_set(&(dlconfp->ld_head), setattr->ld_attr,
 807             &setattr->ld_attrval, setattr->ld_attrsz, setattr->ld_type);
 808 
 809 done:
 810         dlmgmt_dlconf_table_unlock();
 811         retvalp->lr_err = err;
 812 }
 813 
 814 /* ARGSUSED */
 815 static void
 816 dlmgmt_unsetconfattr(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 817     ucred_t *cred)
 818 {
 819         dlmgmt_door_unsetattr_t *unsetattr = argp;
 820         dlmgmt_unsetattr_retval_t *retvalp = retp;
 821         dlmgmt_dlconf_t         dlconf, *dlconfp;
 822         int                     err = 0;
 823 
 824         /*
 825          * Hold the writer lock to update the dlconf table.
 826          */
 827         dlmgmt_dlconf_table_lock(B_TRUE);
 828 
 829         dlconf.ld_id = unsetattr->ld_confid;
 830         dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL);
 831         if (dlconfp == NULL || zoneid != dlconfp->ld_zoneid) {
 832                 err = ENOENT;
 833                 goto done;
 834         }
 835 
 836         if ((err = dlmgmt_checkprivs(dlconfp->ld_class, cred)) != 0)
 837                 goto done;
 838 
 839         linkattr_unset(&(dlconfp->ld_head), unsetattr->ld_attr);
 840 
 841 done:
 842         dlmgmt_dlconf_table_unlock();
 843         retvalp->lr_err = err;
 844 }
 845 
 846 /*
 847  * Note that dlmgmt_openconf() returns a conf ID of a conf AVL tree entry,
 848  * which is managed by dlmgmtd.  The ID is used to find the conf entry when
 849  * dlmgmt_write_conf() is called.  The conf entry contains an ld_gen value
 850  * (which is the generation number - ll_gen) of the dlmgmt_link_t at the time
 851  * of dlmgmt_openconf(), and ll_gen changes every time the dlmgmt_link_t
 852  * changes its attributes.  Therefore, dlmgmt_write_conf() can compare ld_gen
 853  * in the conf entry against the latest dlmgmt_link_t ll_gen value to see if
 854  * anything has changed between the dlmgmt_openconf() and dlmgmt_writeconf()
 855  * calls.  If so, EAGAIN is returned.  This mechanism can ensures atomicity
 856  * across the pair of dladm_read_conf() and dladm_write_conf() calls.
 857  */
 858 /* ARGSUSED */
 859 static void
 860 dlmgmt_writeconf(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 861     ucred_t *cred)
 862 {
 863         dlmgmt_door_writeconf_t *writeconf = argp;
 864         dlmgmt_writeconf_retval_t *retvalp = retp;
 865         dlmgmt_dlconf_t         dlconf, *dlconfp;
 866         dlmgmt_link_t           *linkp;
 867         dlmgmt_linkattr_t       *attrp, *next;
 868         int                     err = 0;
 869 
 870         /*
 871          * Hold the lock to access the dlconf table.
 872          */
 873         dlmgmt_dlconf_table_lock(B_TRUE);
 874 
 875         dlconf.ld_id = writeconf->ld_confid;
 876         dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL);
 877         if (dlconfp == NULL || zoneid != dlconfp->ld_zoneid) {
 878                 err = ENOENT;
 879                 goto done;
 880         }
 881 
 882         if ((err = dlmgmt_checkprivs(dlconfp->ld_class, cred)) != 0)
 883                 goto done;
 884 
 885         /*
 886          * Hold the writer lock to update the link table.
 887          */
 888         dlmgmt_table_lock(B_TRUE);
 889         linkp = link_by_id(dlconfp->ld_linkid, zoneid);
 890         if ((linkp == NULL) || (linkp->ll_class != dlconfp->ld_class) ||
 891             (linkp->ll_media != dlconfp->ld_media) ||
 892             (strcmp(linkp->ll_link, dlconfp->ld_link) != 0)) {
 893                 /*
 894                  * The link does not exist.
 895                  */
 896                 dlmgmt_table_unlock();
 897                 err = ENOENT;
 898                 goto done;
 899         }
 900 
 901         if (linkp->ll_gen != dlconfp->ld_gen) {
 902                 /*
 903                  * Something has changed the link configuration; try again.
 904                  */
 905                 dlmgmt_table_unlock();
 906                 err = EAGAIN;
 907                 goto done;
 908         }
 909 
 910         /*
 911          * Delete the old attribute list.
 912          */
 913         for (attrp = linkp->ll_head; attrp != NULL; attrp = next) {
 914                 next = attrp->lp_next;
 915                 free(attrp->lp_val);
 916                 free(attrp);
 917         }
 918         linkp->ll_head = NULL;
 919 
 920         /*
 921          * Set the new attribute.
 922          */
 923         for (attrp = dlconfp->ld_head; attrp != NULL; attrp = attrp->lp_next) {
 924                 if ((err = linkattr_set(&(linkp->ll_head), attrp->lp_name,
 925                     attrp->lp_val, attrp->lp_sz, attrp->lp_type)) != 0) {
 926                         dlmgmt_table_unlock();
 927                         goto done;
 928                 }
 929         }
 930 
 931         linkp->ll_gen++;
 932         err = dlmgmt_write_db_entry(linkp->ll_link, linkp, DLMGMT_PERSIST);
 933         dlmgmt_table_unlock();
 934 done:
 935         dlmgmt_dlconf_table_unlock();
 936         retvalp->lr_err = err;
 937 }
 938 
 939 /* ARGSUSED */
 940 static void
 941 dlmgmt_removeconf(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 942     ucred_t *cred)
 943 {
 944         dlmgmt_door_removeconf_t        *removeconf = argp;
 945         dlmgmt_removeconf_retval_t      *retvalp = retp;
 946         dlmgmt_link_t                   *linkp;
 947         int                             err;
 948 
 949         dlmgmt_table_lock(B_TRUE);
 950         if ((linkp = link_by_id(removeconf->ld_linkid, zoneid)) == NULL) {
 951                 err = ENOENT;
 952                 goto done;
 953         }
 954         if (zoneid != GLOBAL_ZONEID && linkp->ll_onloan) {
 955                 /*
 956                  * A non-global zone cannot remove the persistent
 957                  * configuration of a link that is on loan from the global
 958                  * zone.
 959                  */
 960                 err = EACCES;
 961                 goto done;
 962         }
 963         if ((err = dlmgmt_checkprivs(linkp->ll_class, cred)) != 0)
 964                 goto done;
 965 
 966         err = dlmgmt_delete_db_entry(linkp, DLMGMT_PERSIST);
 967 done:
 968         dlmgmt_table_unlock();
 969         retvalp->lr_err = err;
 970 }
 971 
 972 /* ARGSUSED */
 973 static void
 974 dlmgmt_destroyconf(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 975     ucred_t *cred)
 976 {
 977         dlmgmt_door_destroyconf_t       *destroyconf = argp;
 978         dlmgmt_destroyconf_retval_t     *retvalp = retp;
 979         dlmgmt_dlconf_t                 dlconf, *dlconfp;
 980         int                             err = 0;
 981 
 982         /*
 983          * Hold the writer lock to update the dlconf table.
 984          */
 985         dlmgmt_dlconf_table_lock(B_TRUE);
 986 
 987         dlconf.ld_id = destroyconf->ld_confid;
 988         dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL);
 989         if (dlconfp == NULL || zoneid != dlconfp->ld_zoneid) {
 990                 err = ENOENT;
 991                 goto done;
 992         }
 993 
 994         if ((err = dlmgmt_checkprivs(dlconfp->ld_class, cred)) != 0)
 995                 goto done;
 996 
 997         avl_remove(&dlmgmt_dlconf_avl, dlconfp);
 998         dlconf_destroy(dlconfp);
 999 
1000 done:
1001         dlmgmt_dlconf_table_unlock();
1002         retvalp->lr_err = err;
1003 }
1004 
1005 /*
1006  * dlmgmt_openconf() returns a handle of the current configuration, which
1007  * is then used to update the configuration by dlmgmt_writeconf(). Therefore,
1008  * it requires privileges.
1009  *
1010  * Further, please see the comments above dladm_write_conf() to see how
1011  * ld_gen is used to ensure atomicity across the {dlmgmt_openconf(),
1012  * dlmgmt_writeconf()} pair.
1013  */
1014 /* ARGSUSED */
1015 static void
1016 dlmgmt_openconf(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
1017     ucred_t *cred)
1018 {
1019         dlmgmt_door_openconf_t  *openconf = argp;
1020         dlmgmt_openconf_retval_t *retvalp = retp;
1021         dlmgmt_link_t           *linkp;
1022         datalink_id_t           linkid = openconf->ld_linkid;
1023         dlmgmt_dlconf_t         *dlconfp;
1024         dlmgmt_linkattr_t       *attrp;
1025         int                     err = 0;
1026 
1027         /*
1028          * Hold the writer lock to update the dlconf table.
1029          */
1030         dlmgmt_dlconf_table_lock(B_TRUE);
1031 
1032         /*
1033          * Hold the reader lock to access the link
1034          */
1035         dlmgmt_table_lock(B_FALSE);
1036         linkp = link_by_id(linkid, zoneid);
1037         if ((linkp == NULL) || !(linkp->ll_flags & DLMGMT_PERSIST)) {
1038                 /* The persistent link configuration does not exist. */
1039                 err = ENOENT;
1040                 goto done;
1041         }
1042         if (linkp->ll_onloan && zoneid != GLOBAL_ZONEID) {
1043                 /*
1044                  * The caller is in a non-global zone and the persistent
1045                  * configuration belongs to the global zone.
1046                  */
1047                 err = EACCES;
1048                 goto done;
1049         }
1050 
1051         if ((err = dlmgmt_checkprivs(linkp->ll_class, cred)) != 0)
1052                 goto done;
1053 
1054         if ((err = dlconf_create(linkp->ll_link, linkp->ll_linkid,
1055             linkp->ll_class, linkp->ll_media, zoneid, &dlconfp)) != 0)
1056                 goto done;
1057 
1058         for (attrp = linkp->ll_head; attrp != NULL; attrp = attrp->lp_next) {
1059                 if ((err = linkattr_set(&(dlconfp->ld_head), attrp->lp_name,
1060                     attrp->lp_val, attrp->lp_sz, attrp->lp_type)) != 0) {
1061                         dlconf_destroy(dlconfp);
1062                         goto done;
1063                 }
1064         }
1065         dlconfp->ld_gen = linkp->ll_gen;
1066         avl_add(&dlmgmt_dlconf_avl, dlconfp);
1067         dlmgmt_advance_dlconfid(dlconfp);
1068 
1069         retvalp->lr_confid = dlconfp->ld_id;
1070 done:
1071         dlmgmt_table_unlock();
1072         dlmgmt_dlconf_table_unlock();
1073         retvalp->lr_err = err;
1074 }
1075 
1076 /*
1077  * dlmgmt_getconfsnapshot() returns a read-only snapshot of all the
1078  * configuration, and requires no privileges.
1079  *
1080  * If the given size cannot hold all the configuration, set the size
1081  * that is needed, and return ENOSPC.
1082  */
1083 /* ARGSUSED */
1084 static void
1085 dlmgmt_getconfsnapshot(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
1086     ucred_t *cred)
1087 {
1088         dlmgmt_door_getconfsnapshot_t   *snapshot = argp;
1089         dlmgmt_getconfsnapshot_retval_t *retvalp = retp;
1090         dlmgmt_link_t                   *linkp;
1091         datalink_id_t                   linkid = snapshot->ld_linkid;
1092         dlmgmt_linkattr_t               *attrp;
1093         char                            *buf;
1094         size_t                          nvlsz;
1095         nvlist_t                        *nvl = NULL;
1096         int                             err = 0;
1097 
1098         assert(*sz >= sizeof (dlmgmt_getconfsnapshot_retval_t));
1099 
1100         /*
1101          * Hold the reader lock to access the link
1102          */
1103         dlmgmt_table_lock(B_FALSE);
1104         linkp = link_by_id(linkid, zoneid);
1105         if ((linkp == NULL) || !(linkp->ll_flags & DLMGMT_PERSIST)) {
1106                 /* The persistent link configuration does not exist. */
1107                 err = ENOENT;
1108                 goto done;
1109         }
1110         if (linkp->ll_onloan && zoneid != GLOBAL_ZONEID) {
1111                 /*
1112                  * The caller is in a non-global zone and the persistent
1113                  * configuration belongs to the global zone.
1114                  */
1115                 err = EACCES;
1116                 goto done;
1117         }
1118 
1119         err = nvlist_alloc(&nvl, NV_UNIQUE_NAME_TYPE, 0);
1120         if (err != 0)
1121                 goto done;
1122 
1123         for (attrp = linkp->ll_head; attrp != NULL; attrp = attrp->lp_next) {
1124                 if ((err = nvlist_add_byte_array(nvl, attrp->lp_name,
1125                     attrp->lp_val, attrp->lp_sz)) != 0) {
1126                         goto done;
1127                 }
1128         }
1129 
1130         if ((err = nvlist_size(nvl, &nvlsz, NV_ENCODE_NATIVE)) != 0)
1131                 goto done;
1132 
1133         if (nvlsz + sizeof (dlmgmt_getconfsnapshot_retval_t) > *sz) {
1134                 *sz = nvlsz + sizeof (dlmgmt_getconfsnapshot_retval_t);
1135                 err = ENOSPC;
1136                 goto done;
1137         }
1138 
1139         /*
1140          * pack the the nvlist into the return value.
1141          */
1142         *sz = nvlsz + sizeof (dlmgmt_getconfsnapshot_retval_t);
1143         retvalp->lr_nvlsz = nvlsz;
1144         buf = (char *)retvalp + sizeof (dlmgmt_getconfsnapshot_retval_t);
1145         err = nvlist_pack(nvl, &buf, &nvlsz, NV_ENCODE_NATIVE, 0);
1146 
1147 done:
1148         dlmgmt_table_unlock();
1149         nvlist_free(nvl);
1150         retvalp->lr_err = err;
1151 }
1152 
1153 /* ARGSUSED */
1154 static void
1155 dlmgmt_getattr(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
1156     ucred_t *cred)
1157 {
1158         dlmgmt_door_getattr_t   *getattr = argp;
1159         dlmgmt_getattr_retval_t *retvalp = retp;
1160         dlmgmt_dlconf_t         dlconf, *dlconfp;
1161         int                     err;
1162 
1163         /*
1164          * Hold the read lock to access the dlconf table.
1165          */
1166         dlmgmt_dlconf_table_lock(B_FALSE);
1167 
1168         dlconf.ld_id = getattr->ld_confid;
1169         if ((dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL)) == NULL ||
1170             zoneid != dlconfp->ld_zoneid) {
1171                 retvalp->lr_err = ENOENT;
1172         } else {
1173                 if ((err = dlmgmt_checkprivs(dlconfp->ld_class, cred)) != 0) {
1174                         retvalp->lr_err = err;
1175                 } else {
1176                         retvalp->lr_err = dlmgmt_getattr_common(
1177                             &dlconfp->ld_head, getattr->ld_attr, retvalp);
1178                 }
1179         }
1180 
1181         dlmgmt_dlconf_table_unlock();
1182 }
1183 
1184 /* ARGSUSED */
1185 static void
1186 dlmgmt_upcall_linkprop_init(void *argp, void *retp, size_t *sz,
1187     zoneid_t zoneid, ucred_t *cred)
1188 {
1189         dlmgmt_door_linkprop_init_t     *lip = argp;
1190         dlmgmt_linkprop_init_retval_t   *retvalp = retp;
1191         dlmgmt_link_t                   *linkp;
1192         int                             err;
1193 
1194         dlmgmt_table_lock(B_FALSE);
1195         if ((linkp = link_by_id(lip->ld_linkid, zoneid)) == NULL)
1196                 err = ENOENT;
1197         else
1198                 err = dlmgmt_checkprivs(linkp->ll_class, cred);
1199         dlmgmt_table_unlock();
1200 
1201         if (err == 0) {
1202                 dladm_status_t  s;
1203                 char            buf[DLADM_STRSIZE];
1204 
1205                 s = dladm_init_linkprop(dld_handle, lip->ld_linkid, B_TRUE);
1206                 if (s != DLADM_STATUS_OK) {
1207                         dlmgmt_log(LOG_WARNING,
1208                             "linkprop initialization failed on link %d: %s",
1209                             lip->ld_linkid, dladm_status2str(s, buf));
1210                         err = EINVAL;
1211                 }
1212         }
1213         retvalp->lr_err = err;
1214 }
1215 
1216 /* ARGSUSED */
1217 static void
1218 dlmgmt_setzoneid(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
1219     ucred_t *cred)
1220 {
1221         dlmgmt_door_setzoneid_t *setzoneid = argp;
1222         dlmgmt_setzoneid_retval_t *retvalp = retp;
1223         dlmgmt_link_t           *linkp;
1224         datalink_id_t           linkid = setzoneid->ld_linkid;
1225         zoneid_t                oldzoneid, newzoneid;
1226         int                     err = 0;
1227 
1228         dlmgmt_table_lock(B_TRUE);
1229 
1230         /* We currently only allow changing zoneid's from the global zone. */
1231         if (zoneid != GLOBAL_ZONEID) {
1232                 err = EACCES;
1233                 goto done;
1234         }
1235 
1236         if ((linkp = link_by_id(linkid, zoneid)) == NULL) {
1237                 err = ENOENT;
1238                 goto done;
1239         }
1240 
1241         if ((err = dlmgmt_checkprivs(linkp->ll_class, cred)) != 0)
1242                 goto done;
1243 
1244         if (linkp->ll_tomb == B_TRUE) {
1245                 err = EBUSY;
1246                 goto done;
1247         }
1248 
1249         /* We can only assign an active link to a zone. */
1250         if (!(linkp->ll_flags & DLMGMT_ACTIVE)) {
1251                 err = EINVAL;
1252                 goto done;
1253         }
1254 
1255         oldzoneid = linkp->ll_zoneid;
1256         newzoneid = setzoneid->ld_zoneid;
1257 
1258         if (oldzoneid == newzoneid)
1259                 goto done;
1260 
1261         /*
1262          * Before we remove the link from its current zone, make sure that
1263          * there isn't a link with the same name in the destination zone.
1264          */
1265         if (zoneid != GLOBAL_ZONEID &&
1266             link_by_name(linkp->ll_link, newzoneid) != NULL) {
1267                 err = EEXIST;
1268                 goto done;
1269         }
1270 
1271         if (oldzoneid != GLOBAL_ZONEID) {
1272                 if (zone_remove_datalink(oldzoneid, linkid) != 0) {
1273                         err = errno;
1274                         dlmgmt_log(LOG_WARNING, "unable to remove link %d from "
1275                             "zone %d: %s", linkid, oldzoneid, strerror(err));
1276                         goto done;
1277                 }
1278 
1279                 if (newzoneid == GLOBAL_ZONEID && linkp->ll_onloan) {
1280                         /*
1281                          * We can only reassign a loaned VNIC back to the
1282                          * global zone when the zone is shutting down, since
1283                          * otherwise the VNIC is in use by the zone and will be
1284                          * busy.  Leave the VNIC assigned to the zone so we can
1285                          * still see it and delete it when dlmgmt_zonehalt()
1286                          * runs.
1287                          */
1288                         goto done;
1289                 }
1290 
1291                 linkp->ll_onloan = B_FALSE;
1292         }
1293         if (newzoneid != GLOBAL_ZONEID) {
1294                 if (zone_add_datalink(newzoneid, linkid) != 0) {
1295                         err = errno;
1296                         dlmgmt_log(LOG_WARNING, "unable to add link %d to zone "
1297                             "%d: %s", linkid, newzoneid, strerror(err));
1298                         (void) zone_add_datalink(oldzoneid, linkid);
1299                         goto done;
1300                 }
1301                 linkp->ll_onloan = B_TRUE;
1302         }
1303 
1304         avl_remove(&dlmgmt_name_avl, linkp);
1305         linkp->ll_zoneid = newzoneid;
1306         avl_add(&dlmgmt_name_avl, linkp);
1307 
1308 done:
1309         dlmgmt_table_unlock();
1310         retvalp->lr_err = err;
1311 }
1312 
1313 /* ARGSUSED */
1314 static void
1315 dlmgmt_zoneboot(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
1316     ucred_t *cred)
1317 {
1318         int                     err;
1319         dlmgmt_door_zoneboot_t  *zoneboot = argp;
1320         dlmgmt_zoneboot_retval_t *retvalp = retp;
1321 
1322         dlmgmt_table_lock(B_TRUE);
1323 
1324         if ((err = dlmgmt_checkprivs(0, cred)) != 0)
1325                 goto done;
1326 
1327         if (zoneid != GLOBAL_ZONEID) {
1328                 err = EACCES;
1329                 goto done;
1330         }
1331         if (zoneboot->ld_zoneid == GLOBAL_ZONEID) {
1332                 err = EINVAL;
1333                 goto done;
1334         }
1335 
1336         if ((err = dlmgmt_elevate_privileges()) == 0) {
1337                 err = dlmgmt_zone_init(zoneboot->ld_zoneid);
1338                 (void) dlmgmt_drop_privileges();
1339         }
1340 done:
1341         dlmgmt_table_unlock();
1342         retvalp->lr_err = err;
1343 }
1344 
1345 /* ARGSUSED */
1346 static void
1347 dlmgmt_zonehalt(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
1348     ucred_t *cred)
1349 {
1350         int                     err = 0;
1351         dlmgmt_door_zonehalt_t  *zonehalt = argp;
1352         dlmgmt_zonehalt_retval_t *retvalp = retp;
1353         static char my_pid[10];
1354 
1355         if (my_pid[0] == NULL)
1356                 (void) snprintf(my_pid, sizeof (my_pid), "%d\n", getpid());
1357 
1358         if ((err = dlmgmt_checkprivs(0, cred)) == 0) {
1359                 if (zoneid != GLOBAL_ZONEID) {
1360                         err = EACCES;
1361                 } else if (zonehalt->ld_zoneid == GLOBAL_ZONEID) {
1362                         err = EINVAL;
1363                 } else {
1364                         /*
1365                          * dls and mac don't honor the locking rules defined in
1366                          * mac. In order to try and make that case less likely
1367                          * to happen, we try to serialize some of the zone
1368                          * activity here between dlmgmtd and the brands on
1369                          * /etc/dladm/zone.lck
1370                          */
1371                         int fd;
1372 
1373                         while ((fd = open(ZONE_LOCK, O_WRONLY |
1374                             O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)) < 0)
1375                         (void) sleep(1);
1376                         (void) write(fd, my_pid, sizeof (my_pid));
1377                         (void) close(fd);
1378 
1379                         dlmgmt_table_lock(B_TRUE);
1380                         dlmgmt_db_fini(zonehalt->ld_zoneid);
1381                         dlmgmt_table_unlock();
1382 
1383                         (void) unlink(ZONE_LOCK);
1384                 }
1385         }
1386         retvalp->lr_err = err;
1387 }
1388 
1389 static dlmgmt_door_info_t i_dlmgmt_door_info_tbl[] = {
1390         { DLMGMT_CMD_DLS_CREATE, sizeof (dlmgmt_upcall_arg_create_t),
1391             sizeof (dlmgmt_create_retval_t), dlmgmt_upcall_create },
1392         { DLMGMT_CMD_DLS_GETATTR, sizeof (dlmgmt_upcall_arg_getattr_t),
1393             sizeof (dlmgmt_getattr_retval_t), dlmgmt_upcall_getattr },
1394         { DLMGMT_CMD_DLS_DESTROY, sizeof (dlmgmt_upcall_arg_destroy_t),
1395             sizeof (dlmgmt_destroy_retval_t), dlmgmt_upcall_destroy },
1396         { DLMGMT_CMD_GETNAME, sizeof (dlmgmt_door_getname_t),
1397             sizeof (dlmgmt_getname_retval_t), dlmgmt_getname },
1398         { DLMGMT_CMD_GETLINKID, sizeof (dlmgmt_door_getlinkid_t),
1399             sizeof (dlmgmt_getlinkid_retval_t), dlmgmt_getlinkid },
1400         { DLMGMT_CMD_GETNEXT, sizeof (dlmgmt_door_getnext_t),
1401             sizeof (dlmgmt_getnext_retval_t), dlmgmt_getnext },
1402         { DLMGMT_CMD_DLS_UPDATE, sizeof (dlmgmt_upcall_arg_update_t),
1403             sizeof (dlmgmt_update_retval_t), dlmgmt_upcall_update },
1404         { DLMGMT_CMD_CREATE_LINKID, sizeof (dlmgmt_door_createid_t),
1405             sizeof (dlmgmt_createid_retval_t), dlmgmt_createid },
1406         { DLMGMT_CMD_DESTROY_LINKID, sizeof (dlmgmt_door_destroyid_t),
1407             sizeof (dlmgmt_destroyid_retval_t), dlmgmt_destroyid },
1408         { DLMGMT_CMD_REMAP_LINKID, sizeof (dlmgmt_door_remapid_t),
1409             sizeof (dlmgmt_remapid_retval_t), dlmgmt_remapid },
1410         { DLMGMT_CMD_CREATECONF, sizeof (dlmgmt_door_createconf_t),
1411             sizeof (dlmgmt_createconf_retval_t), dlmgmt_createconf },
1412         { DLMGMT_CMD_OPENCONF, sizeof (dlmgmt_door_openconf_t),
1413             sizeof (dlmgmt_openconf_retval_t), dlmgmt_openconf },
1414         { DLMGMT_CMD_WRITECONF, sizeof (dlmgmt_door_writeconf_t),
1415             sizeof (dlmgmt_writeconf_retval_t), dlmgmt_writeconf },
1416         { DLMGMT_CMD_UP_LINKID, sizeof (dlmgmt_door_upid_t),
1417             sizeof (dlmgmt_upid_retval_t), dlmgmt_upid },
1418         { DLMGMT_CMD_SETATTR, sizeof (dlmgmt_door_setattr_t),
1419             sizeof (dlmgmt_setattr_retval_t), dlmgmt_setattr },
1420         { DLMGMT_CMD_UNSETATTR, sizeof (dlmgmt_door_unsetattr_t),
1421             sizeof (dlmgmt_unsetattr_retval_t), dlmgmt_unsetconfattr },
1422         { DLMGMT_CMD_REMOVECONF, sizeof (dlmgmt_door_removeconf_t),
1423             sizeof (dlmgmt_removeconf_retval_t), dlmgmt_removeconf },
1424         { DLMGMT_CMD_DESTROYCONF, sizeof (dlmgmt_door_destroyconf_t),
1425             sizeof (dlmgmt_destroyconf_retval_t), dlmgmt_destroyconf },
1426         { DLMGMT_CMD_GETATTR, sizeof (dlmgmt_door_getattr_t),
1427             sizeof (dlmgmt_getattr_retval_t), dlmgmt_getattr },
1428         { DLMGMT_CMD_GETCONFSNAPSHOT, sizeof (dlmgmt_door_getconfsnapshot_t),
1429             sizeof (dlmgmt_getconfsnapshot_retval_t), dlmgmt_getconfsnapshot },
1430         { DLMGMT_CMD_LINKPROP_INIT, sizeof (dlmgmt_door_linkprop_init_t),
1431             sizeof (dlmgmt_linkprop_init_retval_t),
1432             dlmgmt_upcall_linkprop_init },
1433         { DLMGMT_CMD_SETZONEID, sizeof (dlmgmt_door_setzoneid_t),
1434             sizeof (dlmgmt_setzoneid_retval_t), dlmgmt_setzoneid },
1435         { DLMGMT_CMD_ZONEBOOT, sizeof (dlmgmt_door_zoneboot_t),
1436             sizeof (dlmgmt_zoneboot_retval_t), dlmgmt_zoneboot },
1437         { DLMGMT_CMD_ZONEHALT, sizeof (dlmgmt_door_zonehalt_t),
1438             sizeof (dlmgmt_zonehalt_retval_t), dlmgmt_zonehalt },
1439         { 0, 0, 0, NULL }
1440 };
1441 
1442 static dlmgmt_door_info_t *
1443 dlmgmt_getcmdinfo(int cmd)
1444 {
1445         dlmgmt_door_info_t      *infop = i_dlmgmt_door_info_tbl;
1446 
1447         while (infop->di_handler != NULL) {
1448                 if (infop->di_cmd == cmd)
1449                         break;
1450                 infop++;
1451         }
1452         return (infop);
1453 }
1454 
1455 /* ARGSUSED */
1456 void
1457 dlmgmt_handler(void *cookie, char *argp, size_t argsz, door_desc_t *dp,
1458     uint_t n_desc)
1459 {
1460         dlmgmt_door_arg_t       *door_arg = (dlmgmt_door_arg_t *)(void *)argp;
1461         dlmgmt_door_info_t      *infop = NULL;
1462         dlmgmt_retval_t         retval;
1463         ucred_t                 *cred = NULL;
1464         zoneid_t                zoneid;
1465         void                    *retvalp = NULL;
1466         size_t                  sz, acksz;
1467         int                     err = 0;
1468 
1469         infop = dlmgmt_getcmdinfo(door_arg->ld_cmd);
1470         if (infop == NULL || argsz != infop->di_reqsz) {
1471                 err = EINVAL;
1472                 goto done;
1473         }
1474 
1475         if (door_ucred(&cred) != 0 || (zoneid = ucred_getzoneid(cred)) == -1) {
1476                 err = errno;
1477                 goto done;
1478         }
1479 
1480         /*
1481          * Note that malloc() cannot be used here because door_return
1482          * never returns, and memory allocated by malloc() would get leaked.
1483          * Use alloca() instead.
1484          */
1485         acksz = infop->di_acksz;
1486 
1487 again:
1488         retvalp = alloca(acksz);
1489         sz = acksz;
1490         infop->di_handler(argp, retvalp, &acksz, zoneid, cred);
1491         if (acksz > sz) {
1492                 /*
1493                  * If the specified buffer size is not big enough to hold the
1494                  * return value, reallocate the buffer and try to get the
1495                  * result one more time.
1496                  */
1497                 assert(((dlmgmt_retval_t *)retvalp)->lr_err == ENOSPC);
1498                 goto again;
1499         }
1500 
1501 done:
1502         if (cred != NULL)
1503                 ucred_free(cred);
1504         if (err == 0) {
1505                 (void) door_return(retvalp, acksz, NULL, 0);
1506         } else {
1507                 retval.lr_err = err;
1508                 (void) door_return((char *)&retval, sizeof (retval), NULL, 0);
1509         }
1510 }