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  */
  25 
  26 /*
  27  * Main door handler functions used by dlmgmtd to process the different door
  28  * call requests. Door call requests can come from the user-land applications,
  29  * or from the kernel.
  30  *
  31  * Note on zones handling:
  32  *
  33  * There are two zoneid's associated with a link.  One is the zoneid of the
  34  * zone in which the link was created (ll_zoneid in the dlmgmt_link_t), and
  35  * the other is the zoneid of the zone where the link is currently assigned
  36  * (the "zone" link property).  The two can be different if a datalink is
  37  * created in the global zone and subsequently assigned to a non-global zone
  38  * via zonecfg or via explicitly setting the "zone" link property.
  39  *
  40  * Door clients can see links that were created in their zone, and links that
  41  * are currently assigned to their zone.  Door clients in a zone can only
  42  * modify links that were created in their zone.
  43  *
  44  * The datalink ID space is global, while each zone has its own datalink name
  45  * space.  This allows each zone to have complete freedom over the names that
  46  * they assign to links created within the zone.
  47  */
  48 
  49 #include <assert.h>
  50 #include <alloca.h>
  51 #include <errno.h>
  52 #include <priv_utils.h>
  53 #include <stdlib.h>
  54 #include <strings.h>
  55 #include <syslog.h>
  56 #include <sys/sysevent/eventdefs.h>
  57 #include <zone.h>
  58 #include <libsysevent.h>
  59 #include <libdlmgmt.h>
  60 #include <librcm.h>
  61 #include <sys/types.h>
  62 #include <sys/stat.h>
  63 #include <fcntl.h>
  64 #include <unistd.h>
  65 #include "dlmgmt_impl.h"
  66 
  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         /*
 453          * Hold the reader lock to access the link
 454          */
 455         dlmgmt_table_lock(B_FALSE);
 456 
 457         if ((linkp = link_by_name(getlinkid->ld_link, zoneid)) == NULL) {
 458                 /*
 459                  * The link does not exist in this zone.
 460                  */
 461                 err = ENOENT;
 462                 goto done;
 463         }
 464 
 465         retvalp->lr_linkid = linkp->ll_linkid;
 466         retvalp->lr_flags = linkp->ll_flags;
 467         retvalp->lr_class = linkp->ll_class;
 468         retvalp->lr_media = linkp->ll_media;
 469 
 470 done:
 471         dlmgmt_table_unlock();
 472         retvalp->lr_err = err;
 473 }
 474 
 475 /* ARGSUSED */
 476 static void
 477 dlmgmt_getnext(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 478     ucred_t *cred)
 479 {
 480         dlmgmt_door_getnext_t   *getnext = argp;
 481         dlmgmt_getnext_retval_t *retvalp = retp;
 482         dlmgmt_link_t           link, *linkp;
 483         avl_index_t             where;
 484         int                     err = 0;
 485 
 486         /*
 487          * Hold the reader lock to access the link
 488          */
 489         dlmgmt_table_lock(B_FALSE);
 490 
 491         link.ll_linkid = (getnext->ld_linkid + 1);
 492         if ((linkp = avl_find(&dlmgmt_id_avl, &link, &where)) == NULL)
 493                 linkp = avl_nearest(&dlmgmt_id_avl, where, AVL_AFTER);
 494 
 495         for (; linkp != NULL; linkp = AVL_NEXT(&dlmgmt_id_avl, linkp)) {
 496                 if (!link_is_visible(linkp, zoneid))
 497                         continue;
 498                 if ((linkp->ll_class & getnext->ld_class) &&
 499                     (linkp->ll_flags & getnext->ld_flags) &&
 500                     DATALINK_MEDIA_ACCEPTED(getnext->ld_dmedia,
 501                     linkp->ll_media))
 502                         break;
 503         }
 504 
 505         if (linkp == NULL) {
 506                 err = ENOENT;
 507         } else {
 508                 retvalp->lr_linkid = linkp->ll_linkid;
 509                 retvalp->lr_class = linkp->ll_class;
 510                 retvalp->lr_media = linkp->ll_media;
 511                 retvalp->lr_flags = linkp->ll_flags;
 512         }
 513 
 514         dlmgmt_table_unlock();
 515         retvalp->lr_err = err;
 516 }
 517 
 518 /* ARGSUSED */
 519 static void
 520 dlmgmt_upcall_getattr(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 521     ucred_t *cred)
 522 {
 523         dlmgmt_upcall_arg_getattr_t     *getattr = argp;
 524         dlmgmt_getattr_retval_t         *retvalp = retp;
 525         dlmgmt_link_t                   *linkp;
 526 
 527         /*
 528          * Hold the reader lock to access the link
 529          */
 530         dlmgmt_table_lock(B_FALSE);
 531         if ((linkp = link_by_id(getattr->ld_linkid, zoneid)) == NULL) {
 532                 retvalp->lr_err = ENOENT;
 533         } else {
 534                 retvalp->lr_err = dlmgmt_getattr_common(&linkp->ll_head,
 535                     getattr->ld_attr, retvalp);
 536         }
 537         dlmgmt_table_unlock();
 538 }
 539 
 540 /* ARGSUSED */
 541 static void
 542 dlmgmt_createid(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 543     ucred_t *cred)
 544 {
 545         dlmgmt_door_createid_t  *createid = argp;
 546         dlmgmt_createid_retval_t *retvalp = retp;
 547         dlmgmt_link_t           *linkp;
 548         datalink_id_t           linkid = DATALINK_INVALID_LINKID;
 549         char                    link[MAXLINKNAMELEN];
 550         int                     err;
 551 
 552         /*
 553          * Hold the writer lock to update the dlconf table.
 554          */
 555         dlmgmt_table_lock(B_TRUE);
 556 
 557         if ((err = dlmgmt_checkprivs(createid->ld_class, cred)) != 0)
 558                 goto done;
 559 
 560         if (createid->ld_prefix) {
 561                 err = dlmgmt_generate_name(createid->ld_link, link,
 562                     MAXLINKNAMELEN, zoneid);
 563                 if (err != 0)
 564                         goto done;
 565 
 566                 err = dlmgmt_create_common(link, createid->ld_class,
 567                     createid->ld_media, zoneid, createid->ld_flags, &linkp);
 568         } else {
 569                 err = dlmgmt_create_common(createid->ld_link,
 570                     createid->ld_class, createid->ld_media, zoneid,
 571                     createid->ld_flags, &linkp);
 572         }
 573 
 574         if (err == 0) {
 575                 /*
 576                  * Keep the active mapping.
 577                  */
 578                 linkid = linkp->ll_linkid;
 579                 if (createid->ld_flags & DLMGMT_ACTIVE) {
 580                         (void) dlmgmt_write_db_entry(linkp->ll_link, linkp,
 581                             DLMGMT_ACTIVE);
 582                 }
 583         }
 584 
 585 done:
 586         dlmgmt_table_unlock();
 587         retvalp->lr_linkid = linkid;
 588         retvalp->lr_err = err;
 589 }
 590 
 591 /* ARGSUSED */
 592 static void
 593 dlmgmt_destroyid(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 594     ucred_t *cred)
 595 {
 596         dlmgmt_door_destroyid_t *destroyid = argp;
 597         dlmgmt_destroyid_retval_t *retvalp = retp;
 598         datalink_id_t           linkid = destroyid->ld_linkid;
 599         uint32_t                flags = destroyid->ld_flags;
 600         dlmgmt_link_t           *linkp = NULL;
 601         int                     err = 0;
 602 
 603         /*
 604          * Hold the writer lock to update the link table.
 605          */
 606         dlmgmt_table_lock(B_TRUE);
 607         if ((linkp = link_by_id(linkid, zoneid)) == NULL) {
 608                 err = ENOENT;
 609                 goto done;
 610         }
 611 
 612         if ((err = dlmgmt_checkprivs(linkp->ll_class, cred)) != 0)
 613                 goto done;
 614 
 615         /*
 616          * Delete the active mapping.
 617          */
 618         if (flags & DLMGMT_ACTIVE)
 619                 err = dlmgmt_delete_db_entry(linkp, DLMGMT_ACTIVE);
 620         if (err == 0)
 621                 err = dlmgmt_destroy_common(linkp, flags);
 622 done:
 623         dlmgmt_table_unlock();
 624         retvalp->lr_err = err;
 625 }
 626 
 627 /*
 628  * Remap a linkid to a given link name, i.e., rename an existing link1
 629  * (ld_linkid) to a non-existent link2 (ld_link): rename link1's name to
 630  * the given link name.
 631  */
 632 /* ARGSUSED */
 633 static void
 634 dlmgmt_remapid(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 635     ucred_t *cred)
 636 {
 637         dlmgmt_door_remapid_t   *remapid = argp;
 638         dlmgmt_remapid_retval_t *retvalp = retp;
 639         dlmgmt_link_t           *linkp;
 640         char                    oldname[MAXLINKNAMELEN];
 641         boolean_t               renamed = B_FALSE;
 642         int                     err = 0;
 643 
 644         if (!dladm_valid_linkname(remapid->ld_link)) {
 645                 retvalp->lr_err = EINVAL;
 646                 return;
 647         }
 648 
 649         /*
 650          * Hold the writer lock to update the link table.
 651          */
 652         dlmgmt_table_lock(B_TRUE);
 653         if ((linkp = link_by_id(remapid->ld_linkid, zoneid)) == NULL) {
 654                 err = ENOENT;
 655                 goto done;
 656         }
 657 
 658         if ((err = dlmgmt_checkprivs(linkp->ll_class, cred)) != 0)
 659                 goto done;
 660 
 661         if (linkp->ll_tomb == B_TRUE) {
 662                 err = EBUSY;
 663                 goto done;
 664         }
 665 
 666 
 667         if (link_by_name(remapid->ld_link, linkp->ll_zoneid) != NULL) {
 668                 err = EEXIST;
 669                 goto done;
 670         }
 671 
 672         (void) strlcpy(oldname, linkp->ll_link, MAXLINKNAMELEN);
 673         avl_remove(&dlmgmt_name_avl, linkp);
 674         (void) strlcpy(linkp->ll_link, remapid->ld_link, MAXLINKNAMELEN);
 675         avl_add(&dlmgmt_name_avl, linkp);
 676         renamed = B_TRUE;
 677 
 678         if (linkp->ll_flags & DLMGMT_ACTIVE) {
 679                 err = dlmgmt_write_db_entry(oldname, linkp, DLMGMT_ACTIVE);
 680                 if (err != 0)
 681                         goto done;
 682         }
 683         if (linkp->ll_flags & DLMGMT_PERSIST) {
 684                 err = dlmgmt_write_db_entry(oldname, linkp, DLMGMT_PERSIST);
 685                 if (err != 0) {
 686                         if (linkp->ll_flags & DLMGMT_ACTIVE) {
 687                                 (void) dlmgmt_write_db_entry(remapid->ld_link,
 688                                     linkp, DLMGMT_ACTIVE);
 689                         }
 690                         goto done;
 691                 }
 692         }
 693 
 694         dlmgmt_advance(linkp);
 695         linkp->ll_gen++;
 696 done:
 697         if (err != 0 && renamed) {
 698                 avl_remove(&dlmgmt_name_avl, linkp);
 699                 (void) strlcpy(linkp->ll_link, oldname, MAXLINKNAMELEN);
 700                 avl_add(&dlmgmt_name_avl, linkp);
 701         }
 702         dlmgmt_table_unlock();
 703         retvalp->lr_err = err;
 704 }
 705 
 706 /* ARGSUSED */
 707 static void
 708 dlmgmt_upid(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 709     ucred_t *cred)
 710 {
 711         dlmgmt_door_upid_t      *upid = argp;
 712         dlmgmt_upid_retval_t    *retvalp = retp;
 713         dlmgmt_link_t           *linkp;
 714         int                     err = 0;
 715 
 716         /*
 717          * Hold the writer lock to update the link table.
 718          */
 719         dlmgmt_table_lock(B_TRUE);
 720         if ((linkp = link_by_id(upid->ld_linkid, zoneid)) == NULL) {
 721                 err = ENOENT;
 722                 goto done;
 723         }
 724 
 725         if ((err = dlmgmt_checkprivs(linkp->ll_class, cred)) != 0)
 726                 goto done;
 727 
 728         if (linkp->ll_tomb == B_TRUE) {
 729                 err = EBUSY;
 730                 goto done;
 731         }
 732 
 733         if (linkp->ll_flags & DLMGMT_ACTIVE) {
 734                 err = EINVAL;
 735                 goto done;
 736         }
 737 
 738         if ((err = link_activate(linkp)) == 0) {
 739                 (void) dlmgmt_write_db_entry(linkp->ll_link, linkp,
 740                     DLMGMT_ACTIVE);
 741         }
 742 done:
 743         dlmgmt_table_unlock();
 744         retvalp->lr_err = err;
 745 }
 746 
 747 /* ARGSUSED */
 748 static void
 749 dlmgmt_createconf(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 750     ucred_t *cred)
 751 {
 752         dlmgmt_door_createconf_t *createconf = argp;
 753         dlmgmt_createconf_retval_t *retvalp = retp;
 754         dlmgmt_dlconf_t         *dlconfp;
 755         int                     err;
 756 
 757         /*
 758          * Hold the writer lock to update the dlconf table.
 759          */
 760         dlmgmt_dlconf_table_lock(B_TRUE);
 761 
 762         if ((err = dlmgmt_checkprivs(createconf->ld_class, cred)) != 0)
 763                 goto done;
 764 
 765         err = dlconf_create(createconf->ld_link, createconf->ld_linkid,
 766             createconf->ld_class, createconf->ld_media, zoneid, &dlconfp);
 767         if (err == 0) {
 768                 avl_add(&dlmgmt_dlconf_avl, dlconfp);
 769                 dlmgmt_advance_dlconfid(dlconfp);
 770                 retvalp->lr_confid = dlconfp->ld_id;
 771         }
 772 done:
 773         dlmgmt_dlconf_table_unlock();
 774         retvalp->lr_err = err;
 775 }
 776 
 777 /* ARGSUSED */
 778 static void
 779 dlmgmt_setattr(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 780     ucred_t *cred)
 781 {
 782         dlmgmt_door_setattr_t   *setattr = argp;
 783         dlmgmt_setattr_retval_t *retvalp = retp;
 784         dlmgmt_dlconf_t         dlconf, *dlconfp;
 785         int                     err = 0;
 786 
 787         /*
 788          * Hold the writer lock to update the dlconf table.
 789          */
 790         dlmgmt_dlconf_table_lock(B_TRUE);
 791 
 792         dlconf.ld_id = setattr->ld_confid;
 793         dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL);
 794         if (dlconfp == NULL || zoneid != dlconfp->ld_zoneid) {
 795                 err = ENOENT;
 796                 goto done;
 797         }
 798 
 799         if ((err = dlmgmt_checkprivs(dlconfp->ld_class, cred)) != 0)
 800                 goto done;
 801 
 802         err = linkattr_set(&(dlconfp->ld_head), setattr->ld_attr,
 803             &setattr->ld_attrval, setattr->ld_attrsz, setattr->ld_type);
 804 
 805 done:
 806         dlmgmt_dlconf_table_unlock();
 807         retvalp->lr_err = err;
 808 }
 809 
 810 /* ARGSUSED */
 811 static void
 812 dlmgmt_unsetconfattr(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 813     ucred_t *cred)
 814 {
 815         dlmgmt_door_unsetattr_t *unsetattr = argp;
 816         dlmgmt_unsetattr_retval_t *retvalp = retp;
 817         dlmgmt_dlconf_t         dlconf, *dlconfp;
 818         int                     err = 0;
 819 
 820         /*
 821          * Hold the writer lock to update the dlconf table.
 822          */
 823         dlmgmt_dlconf_table_lock(B_TRUE);
 824 
 825         dlconf.ld_id = unsetattr->ld_confid;
 826         dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL);
 827         if (dlconfp == NULL || zoneid != dlconfp->ld_zoneid) {
 828                 err = ENOENT;
 829                 goto done;
 830         }
 831 
 832         if ((err = dlmgmt_checkprivs(dlconfp->ld_class, cred)) != 0)
 833                 goto done;
 834 
 835         linkattr_unset(&(dlconfp->ld_head), unsetattr->ld_attr);
 836 
 837 done:
 838         dlmgmt_dlconf_table_unlock();
 839         retvalp->lr_err = err;
 840 }
 841 
 842 /*
 843  * Note that dlmgmt_openconf() returns a conf ID of a conf AVL tree entry,
 844  * which is managed by dlmgmtd.  The ID is used to find the conf entry when
 845  * dlmgmt_write_conf() is called.  The conf entry contains an ld_gen value
 846  * (which is the generation number - ll_gen) of the dlmgmt_link_t at the time
 847  * of dlmgmt_openconf(), and ll_gen changes every time the dlmgmt_link_t
 848  * changes its attributes.  Therefore, dlmgmt_write_conf() can compare ld_gen
 849  * in the conf entry against the latest dlmgmt_link_t ll_gen value to see if
 850  * anything has changed between the dlmgmt_openconf() and dlmgmt_writeconf()
 851  * calls.  If so, EAGAIN is returned.  This mechanism can ensures atomicity
 852  * across the pair of dladm_read_conf() and dladm_write_conf() calls.
 853  */
 854 /* ARGSUSED */
 855 static void
 856 dlmgmt_writeconf(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 857     ucred_t *cred)
 858 {
 859         dlmgmt_door_writeconf_t *writeconf = argp;
 860         dlmgmt_writeconf_retval_t *retvalp = retp;
 861         dlmgmt_dlconf_t         dlconf, *dlconfp;
 862         dlmgmt_link_t           *linkp;
 863         dlmgmt_linkattr_t       *attrp, *next;
 864         int                     err = 0;
 865 
 866         /*
 867          * Hold the lock to access the dlconf table.
 868          */
 869         dlmgmt_dlconf_table_lock(B_TRUE);
 870 
 871         dlconf.ld_id = writeconf->ld_confid;
 872         dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL);
 873         if (dlconfp == NULL || zoneid != dlconfp->ld_zoneid) {
 874                 err = ENOENT;
 875                 goto done;
 876         }
 877 
 878         if ((err = dlmgmt_checkprivs(dlconfp->ld_class, cred)) != 0)
 879                 goto done;
 880 
 881         /*
 882          * Hold the writer lock to update the link table.
 883          */
 884         dlmgmt_table_lock(B_TRUE);
 885         linkp = link_by_id(dlconfp->ld_linkid, zoneid);
 886         if ((linkp == NULL) || (linkp->ll_class != dlconfp->ld_class) ||
 887             (linkp->ll_media != dlconfp->ld_media) ||
 888             (strcmp(linkp->ll_link, dlconfp->ld_link) != 0)) {
 889                 /*
 890                  * The link does not exist.
 891                  */
 892                 dlmgmt_table_unlock();
 893                 err = ENOENT;
 894                 goto done;
 895         }
 896 
 897         if (linkp->ll_gen != dlconfp->ld_gen) {
 898                 /*
 899                  * Something has changed the link configuration; try again.
 900                  */
 901                 dlmgmt_table_unlock();
 902                 err = EAGAIN;
 903                 goto done;
 904         }
 905 
 906         /*
 907          * Delete the old attribute list.
 908          */
 909         for (attrp = linkp->ll_head; attrp != NULL; attrp = next) {
 910                 next = attrp->lp_next;
 911                 free(attrp->lp_val);
 912                 free(attrp);
 913         }
 914         linkp->ll_head = NULL;
 915 
 916         /*
 917          * Set the new attribute.
 918          */
 919         for (attrp = dlconfp->ld_head; attrp != NULL; attrp = attrp->lp_next) {
 920                 if ((err = linkattr_set(&(linkp->ll_head), attrp->lp_name,
 921                     attrp->lp_val, attrp->lp_sz, attrp->lp_type)) != 0) {
 922                         dlmgmt_table_unlock();
 923                         goto done;
 924                 }
 925         }
 926 
 927         linkp->ll_gen++;
 928         err = dlmgmt_write_db_entry(linkp->ll_link, linkp, DLMGMT_PERSIST);
 929         dlmgmt_table_unlock();
 930 done:
 931         dlmgmt_dlconf_table_unlock();
 932         retvalp->lr_err = err;
 933 }
 934 
 935 /* ARGSUSED */
 936 static void
 937 dlmgmt_removeconf(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 938     ucred_t *cred)
 939 {
 940         dlmgmt_door_removeconf_t        *removeconf = argp;
 941         dlmgmt_removeconf_retval_t      *retvalp = retp;
 942         dlmgmt_link_t                   *linkp;
 943         int                             err;
 944 
 945         dlmgmt_table_lock(B_TRUE);
 946         if ((linkp = link_by_id(removeconf->ld_linkid, zoneid)) == NULL) {
 947                 err = ENOENT;
 948                 goto done;
 949         }
 950         if (zoneid != GLOBAL_ZONEID && linkp->ll_onloan) {
 951                 /*
 952                  * A non-global zone cannot remove the persistent
 953                  * configuration of a link that is on loan from the global
 954                  * zone.
 955                  */
 956                 err = EACCES;
 957                 goto done;
 958         }
 959         if ((err = dlmgmt_checkprivs(linkp->ll_class, cred)) != 0)
 960                 goto done;
 961 
 962         err = dlmgmt_delete_db_entry(linkp, DLMGMT_PERSIST);
 963 done:
 964         dlmgmt_table_unlock();
 965         retvalp->lr_err = err;
 966 }
 967 
 968 /* ARGSUSED */
 969 static void
 970 dlmgmt_destroyconf(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
 971     ucred_t *cred)
 972 {
 973         dlmgmt_door_destroyconf_t       *destroyconf = argp;
 974         dlmgmt_destroyconf_retval_t     *retvalp = retp;
 975         dlmgmt_dlconf_t                 dlconf, *dlconfp;
 976         int                             err = 0;
 977 
 978         /*
 979          * Hold the writer lock to update the dlconf table.
 980          */
 981         dlmgmt_dlconf_table_lock(B_TRUE);
 982 
 983         dlconf.ld_id = destroyconf->ld_confid;
 984         dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL);
 985         if (dlconfp == NULL || zoneid != dlconfp->ld_zoneid) {
 986                 err = ENOENT;
 987                 goto done;
 988         }
 989 
 990         if ((err = dlmgmt_checkprivs(dlconfp->ld_class, cred)) != 0)
 991                 goto done;
 992 
 993         avl_remove(&dlmgmt_dlconf_avl, dlconfp);
 994         dlconf_destroy(dlconfp);
 995 
 996 done:
 997         dlmgmt_dlconf_table_unlock();
 998         retvalp->lr_err = err;
 999 }
1000 
1001 /*
1002  * dlmgmt_openconf() returns a handle of the current configuration, which
1003  * is then used to update the configuration by dlmgmt_writeconf(). Therefore,
1004  * it requires privileges.
1005  *
1006  * Further, please see the comments above dladm_write_conf() to see how
1007  * ld_gen is used to ensure atomicity across the {dlmgmt_openconf(),
1008  * dlmgmt_writeconf()} pair.
1009  */
1010 /* ARGSUSED */
1011 static void
1012 dlmgmt_openconf(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
1013     ucred_t *cred)
1014 {
1015         dlmgmt_door_openconf_t  *openconf = argp;
1016         dlmgmt_openconf_retval_t *retvalp = retp;
1017         dlmgmt_link_t           *linkp;
1018         datalink_id_t           linkid = openconf->ld_linkid;
1019         dlmgmt_dlconf_t         *dlconfp;
1020         dlmgmt_linkattr_t       *attrp;
1021         int                     err = 0;
1022 
1023         /*
1024          * Hold the writer lock to update the dlconf table.
1025          */
1026         dlmgmt_dlconf_table_lock(B_TRUE);
1027 
1028         /*
1029          * Hold the reader lock to access the link
1030          */
1031         dlmgmt_table_lock(B_FALSE);
1032         linkp = link_by_id(linkid, zoneid);
1033         if ((linkp == NULL) || !(linkp->ll_flags & DLMGMT_PERSIST)) {
1034                 /* The persistent link configuration does not exist. */
1035                 err = ENOENT;
1036                 goto done;
1037         }
1038         if (linkp->ll_onloan && zoneid != GLOBAL_ZONEID) {
1039                 /*
1040                  * The caller is in a non-global zone and the persistent
1041                  * configuration belongs to the global zone.
1042                  */
1043                 err = EACCES;
1044                 goto done;
1045         }
1046 
1047         if ((err = dlmgmt_checkprivs(linkp->ll_class, cred)) != 0)
1048                 goto done;
1049 
1050         if ((err = dlconf_create(linkp->ll_link, linkp->ll_linkid,
1051             linkp->ll_class, linkp->ll_media, zoneid, &dlconfp)) != 0)
1052                 goto done;
1053 
1054         for (attrp = linkp->ll_head; attrp != NULL; attrp = attrp->lp_next) {
1055                 if ((err = linkattr_set(&(dlconfp->ld_head), attrp->lp_name,
1056                     attrp->lp_val, attrp->lp_sz, attrp->lp_type)) != 0) {
1057                         dlconf_destroy(dlconfp);
1058                         goto done;
1059                 }
1060         }
1061         dlconfp->ld_gen = linkp->ll_gen;
1062         avl_add(&dlmgmt_dlconf_avl, dlconfp);
1063         dlmgmt_advance_dlconfid(dlconfp);
1064 
1065         retvalp->lr_confid = dlconfp->ld_id;
1066 done:
1067         dlmgmt_table_unlock();
1068         dlmgmt_dlconf_table_unlock();
1069         retvalp->lr_err = err;
1070 }
1071 
1072 /*
1073  * dlmgmt_getconfsnapshot() returns a read-only snapshot of all the
1074  * configuration, and requires no privileges.
1075  *
1076  * If the given size cannot hold all the configuration, set the size
1077  * that is needed, and return ENOSPC.
1078  */
1079 /* ARGSUSED */
1080 static void
1081 dlmgmt_getconfsnapshot(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
1082     ucred_t *cred)
1083 {
1084         dlmgmt_door_getconfsnapshot_t   *snapshot = argp;
1085         dlmgmt_getconfsnapshot_retval_t *retvalp = retp;
1086         dlmgmt_link_t                   *linkp;
1087         datalink_id_t                   linkid = snapshot->ld_linkid;
1088         dlmgmt_linkattr_t               *attrp;
1089         char                            *buf;
1090         size_t                          nvlsz;
1091         nvlist_t                        *nvl = NULL;
1092         int                             err = 0;
1093 
1094         assert(*sz >= sizeof (dlmgmt_getconfsnapshot_retval_t));
1095 
1096         /*
1097          * Hold the reader lock to access the link
1098          */
1099         dlmgmt_table_lock(B_FALSE);
1100         linkp = link_by_id(linkid, zoneid);
1101         if ((linkp == NULL) || !(linkp->ll_flags & DLMGMT_PERSIST)) {
1102                 /* The persistent link configuration does not exist. */
1103                 err = ENOENT;
1104                 goto done;
1105         }
1106         if (linkp->ll_onloan && zoneid != GLOBAL_ZONEID) {
1107                 /*
1108                  * The caller is in a non-global zone and the persistent
1109                  * configuration belongs to the global zone.
1110                  */
1111                 err = EACCES;
1112                 goto done;
1113         }
1114 
1115         err = nvlist_alloc(&nvl, NV_UNIQUE_NAME_TYPE, 0);
1116         if (err != 0)
1117                 goto done;
1118 
1119         for (attrp = linkp->ll_head; attrp != NULL; attrp = attrp->lp_next) {
1120                 if ((err = nvlist_add_byte_array(nvl, attrp->lp_name,
1121                     attrp->lp_val, attrp->lp_sz)) != 0) {
1122                         goto done;
1123                 }
1124         }
1125 
1126         if ((err = nvlist_size(nvl, &nvlsz, NV_ENCODE_NATIVE)) != 0)
1127                 goto done;
1128 
1129         if (nvlsz + sizeof (dlmgmt_getconfsnapshot_retval_t) > *sz) {
1130                 *sz = nvlsz + sizeof (dlmgmt_getconfsnapshot_retval_t);
1131                 err = ENOSPC;
1132                 goto done;
1133         }
1134 
1135         /*
1136          * pack the the nvlist into the return value.
1137          */
1138         *sz = nvlsz + sizeof (dlmgmt_getconfsnapshot_retval_t);
1139         retvalp->lr_nvlsz = nvlsz;
1140         buf = (char *)retvalp + sizeof (dlmgmt_getconfsnapshot_retval_t);
1141         err = nvlist_pack(nvl, &buf, &nvlsz, NV_ENCODE_NATIVE, 0);
1142 
1143 done:
1144         dlmgmt_table_unlock();
1145         nvlist_free(nvl);
1146         retvalp->lr_err = err;
1147 }
1148 
1149 /* ARGSUSED */
1150 static void
1151 dlmgmt_getattr(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
1152     ucred_t *cred)
1153 {
1154         dlmgmt_door_getattr_t   *getattr = argp;
1155         dlmgmt_getattr_retval_t *retvalp = retp;
1156         dlmgmt_dlconf_t         dlconf, *dlconfp;
1157         int                     err;
1158 
1159         /*
1160          * Hold the read lock to access the dlconf table.
1161          */
1162         dlmgmt_dlconf_table_lock(B_FALSE);
1163 
1164         dlconf.ld_id = getattr->ld_confid;
1165         if ((dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL)) == NULL ||
1166             zoneid != dlconfp->ld_zoneid) {
1167                 retvalp->lr_err = ENOENT;
1168         } else {
1169                 if ((err = dlmgmt_checkprivs(dlconfp->ld_class, cred)) != 0) {
1170                         retvalp->lr_err = err;
1171                 } else {
1172                         retvalp->lr_err = dlmgmt_getattr_common(
1173                             &dlconfp->ld_head, getattr->ld_attr, retvalp);
1174                 }
1175         }
1176 
1177         dlmgmt_dlconf_table_unlock();
1178 }
1179 
1180 /* ARGSUSED */
1181 static void
1182 dlmgmt_upcall_linkprop_init(void *argp, void *retp, size_t *sz,
1183     zoneid_t zoneid, ucred_t *cred)
1184 {
1185         dlmgmt_door_linkprop_init_t     *lip = argp;
1186         dlmgmt_linkprop_init_retval_t   *retvalp = retp;
1187         dlmgmt_link_t                   *linkp;
1188         int                             err;
1189 
1190         dlmgmt_table_lock(B_FALSE);
1191         if ((linkp = link_by_id(lip->ld_linkid, zoneid)) == NULL)
1192                 err = ENOENT;
1193         else
1194                 err = dlmgmt_checkprivs(linkp->ll_class, cred);
1195         dlmgmt_table_unlock();
1196 
1197         if (err == 0) {
1198                 dladm_status_t  s;
1199                 char            buf[DLADM_STRSIZE];
1200 
1201                 s = dladm_init_linkprop(dld_handle, lip->ld_linkid, B_TRUE);
1202                 if (s != DLADM_STATUS_OK) {
1203                         dlmgmt_log(LOG_WARNING,
1204                             "linkprop initialization failed on link %d: %s",
1205                             lip->ld_linkid, dladm_status2str(s, buf));
1206                         err = EINVAL;
1207                 }
1208         }
1209         retvalp->lr_err = err;
1210 }
1211 
1212 /* ARGSUSED */
1213 static void
1214 dlmgmt_setzoneid(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
1215     ucred_t *cred)
1216 {
1217         dlmgmt_door_setzoneid_t *setzoneid = argp;
1218         dlmgmt_setzoneid_retval_t *retvalp = retp;
1219         dlmgmt_link_t           *linkp;
1220         datalink_id_t           linkid = setzoneid->ld_linkid;
1221         zoneid_t                oldzoneid, newzoneid;
1222         int                     err = 0;
1223 
1224         dlmgmt_table_lock(B_TRUE);
1225 
1226         /* We currently only allow changing zoneid's from the global zone. */
1227         if (zoneid != GLOBAL_ZONEID) {
1228                 err = EACCES;
1229                 goto done;
1230         }
1231 
1232         if ((linkp = link_by_id(linkid, zoneid)) == NULL) {
1233                 err = ENOENT;
1234                 goto done;
1235         }
1236 
1237         if ((err = dlmgmt_checkprivs(linkp->ll_class, cred)) != 0)
1238                 goto done;
1239 
1240         if (linkp->ll_tomb == B_TRUE) {
1241                 err = EBUSY;
1242                 goto done;
1243         }
1244 
1245         /* We can only assign an active link to a zone. */
1246         if (!(linkp->ll_flags & DLMGMT_ACTIVE)) {
1247                 err = EINVAL;
1248                 goto done;
1249         }
1250 
1251         oldzoneid = linkp->ll_zoneid;
1252         newzoneid = setzoneid->ld_zoneid;
1253 
1254         if (oldzoneid == newzoneid)
1255                 goto done;
1256 
1257         /*
1258          * Before we remove the link from its current zone, make sure that
1259          * there isn't a link with the same name in the destination zone.
1260          */
1261         if (zoneid != GLOBAL_ZONEID &&
1262             link_by_name(linkp->ll_link, newzoneid) != NULL) {
1263                 err = EEXIST;
1264                 goto done;
1265         }
1266 
1267         if (oldzoneid != GLOBAL_ZONEID) {
1268                 if (zone_remove_datalink(oldzoneid, linkid) != 0) {
1269                         err = errno;
1270                         dlmgmt_log(LOG_WARNING, "unable to remove link %d from "
1271                             "zone %d: %s", linkid, oldzoneid, strerror(err));
1272                         goto done;
1273                 }
1274                 avl_remove(&dlmgmt_loan_avl, linkp);
1275                 linkp->ll_onloan = B_FALSE;
1276         }
1277         if (newzoneid != GLOBAL_ZONEID) {
1278                 if (zone_add_datalink(newzoneid, linkid) != 0) {
1279                         err = errno;
1280                         dlmgmt_log(LOG_WARNING, "unable to add link %d to zone "
1281                             "%d: %s", linkid, newzoneid, strerror(err));
1282                         (void) zone_add_datalink(oldzoneid, linkid);
1283                         goto done;
1284                 }
1285                 avl_add(&dlmgmt_loan_avl, linkp);
1286                 linkp->ll_onloan = B_TRUE;
1287         }
1288 
1289         avl_remove(&dlmgmt_name_avl, linkp);
1290         linkp->ll_zoneid = newzoneid;
1291         avl_add(&dlmgmt_name_avl, linkp);
1292 
1293 done:
1294         dlmgmt_table_unlock();
1295         retvalp->lr_err = err;
1296 }
1297 
1298 /* ARGSUSED */
1299 static void
1300 dlmgmt_zoneboot(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
1301     ucred_t *cred)
1302 {
1303         int                     err;
1304         dlmgmt_door_zoneboot_t  *zoneboot = argp;
1305         dlmgmt_zoneboot_retval_t *retvalp = retp;
1306 
1307         dlmgmt_table_lock(B_TRUE);
1308 
1309         if ((err = dlmgmt_checkprivs(0, cred)) != 0)
1310                 goto done;
1311 
1312         if (zoneid != GLOBAL_ZONEID) {
1313                 err = EACCES;
1314                 goto done;
1315         }
1316         if (zoneboot->ld_zoneid == GLOBAL_ZONEID) {
1317                 err = EINVAL;
1318                 goto done;
1319         }
1320 
1321         if ((err = dlmgmt_elevate_privileges()) == 0) {
1322                 err = dlmgmt_zone_init(zoneboot->ld_zoneid);
1323                 (void) dlmgmt_drop_privileges();
1324         }
1325 done:
1326         dlmgmt_table_unlock();
1327         retvalp->lr_err = err;
1328 }
1329 
1330 /* ARGSUSED */
1331 static void
1332 dlmgmt_zonehalt(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
1333     ucred_t *cred)
1334 {
1335         int                     err = 0;
1336         dlmgmt_door_zonehalt_t  *zonehalt = argp;
1337         dlmgmt_zonehalt_retval_t *retvalp = retp;
1338         static char my_pid[10];
1339 
1340         if (my_pid[0] == NULL)
1341                 (void) snprintf(my_pid, sizeof (my_pid), "%d\n", getpid());
1342 
1343         if ((err = dlmgmt_checkprivs(0, cred)) == 0) {
1344                 if (zoneid != GLOBAL_ZONEID) {
1345                         err = EACCES;
1346                 } else if (zonehalt->ld_zoneid == GLOBAL_ZONEID) {
1347                         err = EINVAL;
1348                 } else {
1349                         /*
1350                          * dls and mac don't honor the locking rules defined in
1351                          * mac. In order to try and make that case less likely
1352                          * to happen, we try to serialize some of the zone
1353                          * activity here between dlmgmtd and the brands on
1354                          * /etc/dladm/zone.lck
1355                          */
1356                         int fd;
1357 
1358                         while ((fd = open(ZONE_LOCK, O_WRONLY |
1359                             O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)) < 0)
1360                         (void) sleep(1);
1361                         (void) write(fd, my_pid, sizeof (my_pid));
1362                         (void) close(fd);
1363 
1364                         dlmgmt_table_lock(B_TRUE);
1365                         dlmgmt_db_fini(zonehalt->ld_zoneid);
1366                         dlmgmt_table_unlock();
1367 
1368                         (void) unlink(ZONE_LOCK);
1369                 }
1370         }
1371         retvalp->lr_err = err;
1372 }
1373 
1374 static dlmgmt_door_info_t i_dlmgmt_door_info_tbl[] = {
1375         { DLMGMT_CMD_DLS_CREATE, sizeof (dlmgmt_upcall_arg_create_t),
1376             sizeof (dlmgmt_create_retval_t), dlmgmt_upcall_create },
1377         { DLMGMT_CMD_DLS_GETATTR, sizeof (dlmgmt_upcall_arg_getattr_t),
1378             sizeof (dlmgmt_getattr_retval_t), dlmgmt_upcall_getattr },
1379         { DLMGMT_CMD_DLS_DESTROY, sizeof (dlmgmt_upcall_arg_destroy_t),
1380             sizeof (dlmgmt_destroy_retval_t), dlmgmt_upcall_destroy },
1381         { DLMGMT_CMD_GETNAME, sizeof (dlmgmt_door_getname_t),
1382             sizeof (dlmgmt_getname_retval_t), dlmgmt_getname },
1383         { DLMGMT_CMD_GETLINKID, sizeof (dlmgmt_door_getlinkid_t),
1384             sizeof (dlmgmt_getlinkid_retval_t), dlmgmt_getlinkid },
1385         { DLMGMT_CMD_GETNEXT, sizeof (dlmgmt_door_getnext_t),
1386             sizeof (dlmgmt_getnext_retval_t), dlmgmt_getnext },
1387         { DLMGMT_CMD_DLS_UPDATE, sizeof (dlmgmt_upcall_arg_update_t),
1388             sizeof (dlmgmt_update_retval_t), dlmgmt_upcall_update },
1389         { DLMGMT_CMD_CREATE_LINKID, sizeof (dlmgmt_door_createid_t),
1390             sizeof (dlmgmt_createid_retval_t), dlmgmt_createid },
1391         { DLMGMT_CMD_DESTROY_LINKID, sizeof (dlmgmt_door_destroyid_t),
1392             sizeof (dlmgmt_destroyid_retval_t), dlmgmt_destroyid },
1393         { DLMGMT_CMD_REMAP_LINKID, sizeof (dlmgmt_door_remapid_t),
1394             sizeof (dlmgmt_remapid_retval_t), dlmgmt_remapid },
1395         { DLMGMT_CMD_CREATECONF, sizeof (dlmgmt_door_createconf_t),
1396             sizeof (dlmgmt_createconf_retval_t), dlmgmt_createconf },
1397         { DLMGMT_CMD_OPENCONF, sizeof (dlmgmt_door_openconf_t),
1398             sizeof (dlmgmt_openconf_retval_t), dlmgmt_openconf },
1399         { DLMGMT_CMD_WRITECONF, sizeof (dlmgmt_door_writeconf_t),
1400             sizeof (dlmgmt_writeconf_retval_t), dlmgmt_writeconf },
1401         { DLMGMT_CMD_UP_LINKID, sizeof (dlmgmt_door_upid_t),
1402             sizeof (dlmgmt_upid_retval_t), dlmgmt_upid },
1403         { DLMGMT_CMD_SETATTR, sizeof (dlmgmt_door_setattr_t),
1404             sizeof (dlmgmt_setattr_retval_t), dlmgmt_setattr },
1405         { DLMGMT_CMD_UNSETATTR, sizeof (dlmgmt_door_unsetattr_t),
1406             sizeof (dlmgmt_unsetattr_retval_t), dlmgmt_unsetconfattr },
1407         { DLMGMT_CMD_REMOVECONF, sizeof (dlmgmt_door_removeconf_t),
1408             sizeof (dlmgmt_removeconf_retval_t), dlmgmt_removeconf },
1409         { DLMGMT_CMD_DESTROYCONF, sizeof (dlmgmt_door_destroyconf_t),
1410             sizeof (dlmgmt_destroyconf_retval_t), dlmgmt_destroyconf },
1411         { DLMGMT_CMD_GETATTR, sizeof (dlmgmt_door_getattr_t),
1412             sizeof (dlmgmt_getattr_retval_t), dlmgmt_getattr },
1413         { DLMGMT_CMD_GETCONFSNAPSHOT, sizeof (dlmgmt_door_getconfsnapshot_t),
1414             sizeof (dlmgmt_getconfsnapshot_retval_t), dlmgmt_getconfsnapshot },
1415         { DLMGMT_CMD_LINKPROP_INIT, sizeof (dlmgmt_door_linkprop_init_t),
1416             sizeof (dlmgmt_linkprop_init_retval_t),
1417             dlmgmt_upcall_linkprop_init },
1418         { DLMGMT_CMD_SETZONEID, sizeof (dlmgmt_door_setzoneid_t),
1419             sizeof (dlmgmt_setzoneid_retval_t), dlmgmt_setzoneid },
1420         { DLMGMT_CMD_ZONEBOOT, sizeof (dlmgmt_door_zoneboot_t),
1421             sizeof (dlmgmt_zoneboot_retval_t), dlmgmt_zoneboot },
1422         { DLMGMT_CMD_ZONEHALT, sizeof (dlmgmt_door_zonehalt_t),
1423             sizeof (dlmgmt_zonehalt_retval_t), dlmgmt_zonehalt },
1424         { 0, 0, 0, NULL }
1425 };
1426 
1427 static dlmgmt_door_info_t *
1428 dlmgmt_getcmdinfo(int cmd)
1429 {
1430         dlmgmt_door_info_t      *infop = i_dlmgmt_door_info_tbl;
1431 
1432         while (infop->di_handler != NULL) {
1433                 if (infop->di_cmd == cmd)
1434                         break;
1435                 infop++;
1436         }
1437         return (infop);
1438 }
1439 
1440 /* ARGSUSED */
1441 void
1442 dlmgmt_handler(void *cookie, char *argp, size_t argsz, door_desc_t *dp,
1443     uint_t n_desc)
1444 {
1445         dlmgmt_door_arg_t       *door_arg = (dlmgmt_door_arg_t *)(void *)argp;
1446         dlmgmt_door_info_t      *infop = NULL;
1447         dlmgmt_retval_t         retval;
1448         ucred_t                 *cred = NULL;
1449         zoneid_t                zoneid;
1450         void                    *retvalp = NULL;
1451         size_t                  sz, acksz;
1452         int                     err = 0;
1453 
1454         infop = dlmgmt_getcmdinfo(door_arg->ld_cmd);
1455         if (infop == NULL || argsz != infop->di_reqsz) {
1456                 err = EINVAL;
1457                 goto done;
1458         }
1459 
1460         if (door_ucred(&cred) != 0 || (zoneid = ucred_getzoneid(cred)) == -1) {
1461                 err = errno;
1462                 goto done;
1463         }
1464 
1465         /*
1466          * Note that malloc() cannot be used here because door_return
1467          * never returns, and memory allocated by malloc() would get leaked.
1468          * Use alloca() instead.
1469          */
1470         acksz = infop->di_acksz;
1471 
1472 again:
1473         retvalp = alloca(acksz);
1474         sz = acksz;
1475         infop->di_handler(argp, retvalp, &acksz, zoneid, cred);
1476         if (acksz > sz) {
1477                 /*
1478                  * If the specified buffer size is not big enough to hold the
1479                  * return value, reallocate the buffer and try to get the
1480                  * result one more time.
1481                  */
1482                 assert(((dlmgmt_retval_t *)retvalp)->lr_err == ENOSPC);
1483                 goto again;
1484         }
1485 
1486 done:
1487         if (cred != NULL)
1488                 ucred_free(cred);
1489         if (err == 0) {
1490                 (void) door_return(retvalp, acksz, NULL, 0);
1491         } else {
1492                 retval.lr_err = err;
1493                 (void) door_return((char *)&retval, sizeof (retval), NULL, 0);
1494         }
1495 }