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