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