xref: /illumos-gate/usr/src/cmd/dlmgmtd/dlmgmt_door.c (revision eae72b5b)
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 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * Main door handler functions used by dlmgmtd to process the different door
29  * call requests. Door call requests can come from the user-land applications,
30  * or from the kernel.
31  */
32 
33 #include <assert.h>
34 #include <alloca.h>
35 #include <errno.h>
36 #include <priv_utils.h>
37 #include <stdlib.h>
38 #include <strings.h>
39 #include <syslog.h>
40 #include <sys/sysevent/eventdefs.h>
41 #include <libsysevent.h>
42 #include <libdlmgmt.h>
43 #include <librcm.h>
44 #include "dlmgmt_impl.h"
45 
46 typedef void dlmgmt_door_handler_t(void *, void *);
47 
48 typedef struct dlmgmt_door_info_s {
49 	uint_t			di_cmd;
50 	boolean_t		di_set;
51 	size_t			di_reqsz;
52 	size_t			di_acksz;
53 	dlmgmt_door_handler_t	*di_handler;
54 } dlmgmt_door_info_t;
55 
56 
57 static dlmgmt_link_t *
58 dlmgmt_getlink_by_dev(char *devname)
59 {
60 	dlmgmt_link_t *linkp = avl_first(&dlmgmt_id_avl);
61 
62 	for (; linkp != NULL; linkp = AVL_NEXT(&dlmgmt_id_avl, linkp)) {
63 		if ((linkp->ll_class == DATALINK_CLASS_PHYS) &&
64 		    linkattr_equal(&(linkp->ll_head), FDEVNAME, devname,
65 		    strlen(devname) + 1)) {
66 			return (linkp);
67 		}
68 	}
69 	return (NULL);
70 }
71 
72 /*
73  * Post the EC_DATALINK sysevent for the given linkid. This sysevent will
74  * be consumed by the datalink sysevent module.
75  */
76 static void
77 dlmgmt_post_sysevent(const char *subclass, datalink_id_t linkid)
78 {
79 	nvlist_t	*nvl = NULL;
80 	sysevent_id_t	eid;
81 	int		err;
82 
83 	if (((err = nvlist_alloc(&nvl, NV_UNIQUE_NAME_TYPE, 0)) != 0) ||
84 	    ((err = nvlist_add_uint64(nvl, RCM_NV_LINKID, linkid)) != 0)) {
85 		goto done;
86 	}
87 
88 	if (sysevent_post_event(EC_DATALINK, (char *)subclass, SUNW_VENDOR,
89 	    (char *)progname, nvl, &eid) == -1) {
90 		err = errno;
91 	}
92 
93 done:
94 	if (err != 0) {
95 		dlmgmt_log(LOG_WARNING, "dlmgmt_post_sysevent(%d) failed: %s",
96 		    linkid, strerror(err));
97 	}
98 	nvlist_free(nvl);
99 }
100 
101 static void
102 dlmgmt_upcall_create(void *argp, void *retp)
103 {
104 	dlmgmt_upcall_arg_create_t *create = argp;
105 	dlmgmt_create_retval_t	*retvalp = retp;
106 	datalink_class_t	class;
107 	uint32_t		media;
108 	dlmgmt_link_t		*linkp;
109 	char			link[MAXLINKNAMELEN];
110 	uint32_t		flags;
111 	int			err;
112 	boolean_t		created = B_FALSE;
113 
114 	/*
115 	 * Determine whether this link is persistent. Note that this request
116 	 * is coming from kernel so this link must be active.
117 	 */
118 	flags = DLMGMT_ACTIVE | (create->ld_persist ? DLMGMT_PERSIST : 0);
119 
120 	class = create->ld_class;
121 	media = create->ld_media;
122 
123 	/*
124 	 * Hold the writer lock to update the link table.
125 	 */
126 	dlmgmt_table_lock(B_TRUE);
127 
128 	/*
129 	 * Check to see whether this is the reattachment of an existing
130 	 * physical link. If so, return its linkid.
131 	 */
132 	if ((class == DATALINK_CLASS_PHYS) &&
133 	    (linkp = dlmgmt_getlink_by_dev(create->ld_devname)) != NULL) {
134 		err = linkattr_set(&(linkp->ll_head), FPHYMAJ,
135 		    &create->ld_phymaj, sizeof (uint64_t), DLADM_TYPE_UINT64);
136 		if (err != 0)
137 			goto done;
138 
139 		err = linkattr_set(&(linkp->ll_head), FPHYINST,
140 		    &create->ld_phyinst, sizeof (uint64_t), DLADM_TYPE_UINT64);
141 		if (err != 0)
142 			goto done;
143 
144 		linkp->ll_flags |= flags;
145 		linkp->ll_gen++;
146 		goto done;
147 	}
148 
149 	if ((err = dlmgmt_create_common(create->ld_devname, class, media,
150 	    flags, &linkp)) == EEXIST) {
151 		/*
152 		 * The link name already exists. Return error if this is a
153 		 * non-physical link (in that case, the link name must be
154 		 * the same as the given name).
155 		 */
156 		if (class != DATALINK_CLASS_PHYS)
157 			goto done;
158 
159 		/*
160 		 * The physical link's name already exists, request
161 		 * a suggested link name: net<nextppa>
162 		 */
163 		err = dlmgmt_generate_name("net", link, MAXLINKNAMELEN);
164 		if (err != 0)
165 			goto done;
166 
167 		err = dlmgmt_create_common(link, class, media, flags, &linkp);
168 	}
169 
170 	if (err != 0)
171 		goto done;
172 
173 	created = B_TRUE;
174 
175 	/*
176 	 * This is a new link.  Only need to persist link attributes for
177 	 * physical links.
178 	 */
179 	if (class == DATALINK_CLASS_PHYS &&
180 	    (((err = linkattr_set(&linkp->ll_head, FDEVNAME, create->ld_devname,
181 	    strlen(create->ld_devname) + 1, DLADM_TYPE_STR)) != 0) ||
182 	    ((err = linkattr_set(&linkp->ll_head, FPHYMAJ, &create->ld_phymaj,
183 	    sizeof (uint64_t), DLADM_TYPE_UINT64)) != 0) ||
184 	    ((err = linkattr_set(&linkp->ll_head, FPHYINST, &create->ld_phyinst,
185 	    sizeof (uint64_t), DLADM_TYPE_UINT64)) != 0))) {
186 		(void) dlmgmt_destroy_common(linkp, flags);
187 		goto done;
188 	}
189 
190 done:
191 	if ((err == 0) && ((err = dlmgmt_write_db_entry(linkp->ll_linkid,
192 	    linkp->ll_flags)) != 0) && created) {
193 		(void) dlmgmt_destroy_common(linkp, flags);
194 	}
195 
196 	if (err == 0)
197 		retvalp->lr_linkid = linkp->ll_linkid;
198 
199 	dlmgmt_table_unlock();
200 
201 	if (err == 0) {
202 		/*
203 		 * Post the ESC_DATALINK_PHYS_ADD sysevent. This sysevent
204 		 * is consumed by the datalink sysevent module which in
205 		 * turn generates the RCM_RESOURCE_LINK_NEW RCM event.
206 		 */
207 		dlmgmt_post_sysevent(ESC_DATALINK_PHYS_ADD, retvalp->lr_linkid);
208 	}
209 
210 	retvalp->lr_err = err;
211 }
212 
213 static void
214 dlmgmt_upcall_update(void *argp, void *retp)
215 {
216 	dlmgmt_upcall_arg_update_t	*update = argp;
217 	dlmgmt_update_retval_t		*retvalp = retp;
218 	uint32_t			media = update->ld_media;
219 	dlmgmt_link_t			*linkp;
220 	int				err = 0;
221 
222 	/*
223 	 * Hold the writer lock to update the link table.
224 	 */
225 	dlmgmt_table_lock(B_TRUE);
226 
227 	/*
228 	 * Check to see whether this is the reattachment of an existing
229 	 * physical link. If so, return its linkid.
230 	 */
231 	if ((linkp = dlmgmt_getlink_by_dev(update->ld_devname)) == NULL) {
232 		err = ENOENT;
233 		goto done;
234 	}
235 
236 	retvalp->lr_linkid = linkp->ll_linkid;
237 	retvalp->lr_media = media;
238 	if (linkp->ll_media != media && linkp->ll_media != DL_OTHER) {
239 		/*
240 		 * Assume a DL_ETHER link ce0, a DL_WIFI link ath0
241 		 * 1. # dladm rename-link ce0 net0
242 		 * 2. DR out ce0. net0 is down.
243 		 * 3. use rename-link to have the ath0 device inherit
244 		 *    the configuration from net0
245 		 *    # dladm rename-link ath0 net0
246 		 * 4. DR in ath0.
247 		 * As ath0 and ce0 do not have the same media type, ath0
248 		 * cannot inherit the configuration of net0.
249 		 */
250 		err = EEXIST;
251 
252 		/*
253 		 * Return the media type of the existing link to indicate the
254 		 * reason for the name conflict.
255 		 */
256 		retvalp->lr_media = linkp->ll_media;
257 		goto done;
258 	}
259 
260 	if (update->ld_novanity &&
261 	    (strcmp(update->ld_devname, linkp->ll_link) != 0)) {
262 		/*
263 		 * Return an error if this is a physical link that does not
264 		 * support vanity naming, but the link name is not the same
265 		 * as the given device name.
266 		 */
267 		err = EEXIST;
268 		goto done;
269 	}
270 
271 	linkp->ll_media = media;
272 	linkp->ll_gen++;
273 
274 	(void) dlmgmt_write_db_entry(linkp->ll_linkid, linkp->ll_flags);
275 
276 done:
277 	dlmgmt_table_unlock();
278 	retvalp->lr_err = err;
279 }
280 
281 static void
282 dlmgmt_upcall_destroy(void *argp, void *retp)
283 {
284 	dlmgmt_upcall_arg_destroy_t	*destroy = argp;
285 	dlmgmt_destroy_retval_t		*retvalp = retp;
286 	datalink_id_t			linkid = destroy->ld_linkid;
287 	dlmgmt_link_t			*linkp = NULL;
288 	uint32_t			flags, dflags = 0;
289 	int				err = 0;
290 
291 	flags = DLMGMT_ACTIVE | (destroy->ld_persist ? DLMGMT_PERSIST : 0);
292 
293 	/*
294 	 * Hold the writer lock to update the link table.
295 	 */
296 	dlmgmt_table_lock(B_TRUE);
297 
298 	if ((linkp = link_by_id(linkid)) == NULL) {
299 		err = ENOENT;
300 		goto done;
301 	}
302 
303 	if (((linkp->ll_flags & flags) & DLMGMT_ACTIVE) &&
304 	    ((err = dlmgmt_delete_db_entry(linkid, DLMGMT_ACTIVE)) != 0)) {
305 		dflags = DLMGMT_ACTIVE;
306 		goto done;
307 	}
308 
309 	if (((linkp->ll_flags & flags) & DLMGMT_PERSIST) &&
310 	    ((err = dlmgmt_delete_db_entry(linkid, DLMGMT_PERSIST)) != 0)) {
311 		if (dflags != 0)
312 			(void) dlmgmt_write_db_entry(linkp->ll_linkid, dflags);
313 		dflags |= DLMGMT_PERSIST;
314 		goto done;
315 	}
316 
317 	if ((err = dlmgmt_destroy_common(linkp, flags)) != 0 && dflags != 0)
318 		(void) dlmgmt_write_db_entry(linkp->ll_linkid, dflags);
319 
320 done:
321 	dlmgmt_table_unlock();
322 	retvalp->lr_err = err;
323 }
324 
325 static void
326 dlmgmt_getname(void *argp, void *retp)
327 {
328 	dlmgmt_door_getname_t	*getname = argp;
329 	dlmgmt_getname_retval_t	*retvalp = retp;
330 	dlmgmt_link_t		*linkp;
331 	int			err = 0;
332 
333 	/*
334 	 * Hold the reader lock to access the link
335 	 */
336 	dlmgmt_table_lock(B_FALSE);
337 	if ((linkp = link_by_id(getname->ld_linkid)) == NULL) {
338 		/*
339 		 * The link does not exists.
340 		 */
341 		err = ENOENT;
342 		goto done;
343 	}
344 
345 	if (strlcpy(retvalp->lr_link, linkp->ll_link, MAXLINKNAMELEN) >=
346 	    MAXLINKNAMELEN) {
347 		err = ENOSPC;
348 		goto done;
349 	}
350 	retvalp->lr_flags = linkp->ll_flags;
351 	retvalp->lr_class = linkp->ll_class;
352 	retvalp->lr_media = linkp->ll_media;
353 
354 done:
355 	dlmgmt_table_unlock();
356 	retvalp->lr_err = err;
357 }
358 
359 static void
360 dlmgmt_getlinkid(void *argp, void *retp)
361 {
362 	dlmgmt_door_getlinkid_t	*getlinkid = argp;
363 	dlmgmt_getlinkid_retval_t *retvalp = retp;
364 	dlmgmt_link_t		*linkp;
365 	int			err = 0;
366 
367 	/*
368 	 * Hold the reader lock to access the link
369 	 */
370 	dlmgmt_table_lock(B_FALSE);
371 	if ((linkp = link_by_name(getlinkid->ld_link)) == NULL) {
372 		/*
373 		 * The link does not exists.
374 		 */
375 		err = ENOENT;
376 		goto done;
377 	}
378 
379 	retvalp->lr_linkid = linkp->ll_linkid;
380 	retvalp->lr_flags = linkp->ll_flags;
381 	retvalp->lr_class = linkp->ll_class;
382 	retvalp->lr_media = linkp->ll_media;
383 
384 done:
385 	dlmgmt_table_unlock();
386 	retvalp->lr_err = err;
387 }
388 
389 static void
390 dlmgmt_getnext(void *argp, void *retp)
391 {
392 	dlmgmt_door_getnext_t	*getnext = argp;
393 	dlmgmt_getnext_retval_t	*retvalp = retp;
394 	dlmgmt_link_t		link, *linkp;
395 	datalink_id_t		linkid = getnext->ld_linkid;
396 	avl_index_t		where;
397 	int			err = 0;
398 
399 	/*
400 	 * Hold the reader lock to access the link
401 	 */
402 	dlmgmt_table_lock(B_FALSE);
403 
404 	link.ll_linkid = (linkid + 1);
405 	linkp = avl_find(&dlmgmt_id_avl, &link, &where);
406 	if (linkp == NULL)
407 		linkp = avl_nearest(&dlmgmt_id_avl, where, AVL_AFTER);
408 
409 	for (; linkp != NULL; linkp = AVL_NEXT(&dlmgmt_id_avl, linkp)) {
410 		if ((linkp->ll_class & getnext->ld_class) &&
411 		    (linkp->ll_flags & getnext->ld_flags) &&
412 		    DATALINK_MEDIA_ACCEPTED(getnext->ld_dmedia,
413 		    linkp->ll_media))
414 			break;
415 	}
416 
417 	if (linkp == NULL) {
418 		err = ENOENT;
419 	} else {
420 		retvalp->lr_linkid = linkp->ll_linkid;
421 		retvalp->lr_class = linkp->ll_class;
422 		retvalp->lr_media = linkp->ll_media;
423 		retvalp->lr_flags = linkp->ll_flags;
424 	}
425 
426 	dlmgmt_table_unlock();
427 	retvalp->lr_err = err;
428 }
429 
430 static void
431 dlmgmt_upcall_getattr(void *argp, void *retp)
432 {
433 	dlmgmt_upcall_arg_getattr_t	*getattr = argp;
434 	dlmgmt_getattr_retval_t		*retvalp = retp;
435 	dlmgmt_link_t			*linkp;
436 
437 	/*
438 	 * Hold the reader lock to access the link
439 	 */
440 	dlmgmt_table_lock(B_FALSE);
441 	if ((linkp = link_by_id(getattr->ld_linkid)) == NULL) {
442 		/*
443 		 * The link does not exist.
444 		 */
445 		retvalp->lr_err = ENOENT;
446 		goto done;
447 	}
448 
449 	dlmgmt_getattr_common(&linkp->ll_head, getattr->ld_attr, retvalp);
450 
451 done:
452 	dlmgmt_table_unlock();
453 }
454 
455 static void
456 dlmgmt_createid(void *argp, void *retp)
457 {
458 	dlmgmt_door_createid_t	*createid = argp;
459 	dlmgmt_createid_retval_t *retvalp = retp;
460 	dlmgmt_link_t		*linkp;
461 	datalink_id_t		linkid = DATALINK_INVALID_LINKID;
462 	char			link[MAXLINKNAMELEN];
463 	int			err;
464 
465 	/*
466 	 * Hold the writer lock to update the dlconf table.
467 	 */
468 	dlmgmt_table_lock(B_TRUE);
469 
470 	if (createid->ld_prefix) {
471 		err = dlmgmt_generate_name(createid->ld_link, link,
472 		    MAXLINKNAMELEN);
473 		if (err != 0)
474 			goto done;
475 
476 		err = dlmgmt_create_common(link, createid->ld_class,
477 		    createid->ld_media, createid->ld_flags, &linkp);
478 	} else {
479 		err = dlmgmt_create_common(createid->ld_link,
480 		    createid->ld_class, createid->ld_media, createid->ld_flags,
481 		    &linkp);
482 	}
483 
484 	if (err == 0) {
485 		/*
486 		 * Keep the active mapping.
487 		 */
488 		linkid = linkp->ll_linkid;
489 		if (createid->ld_flags & DLMGMT_ACTIVE)
490 			(void) dlmgmt_write_db_entry(linkid, DLMGMT_ACTIVE);
491 	}
492 
493 done:
494 	dlmgmt_table_unlock();
495 	retvalp->lr_linkid = linkid;
496 	retvalp->lr_err = err;
497 }
498 
499 static void
500 dlmgmt_destroyid(void *argp, void *retp)
501 {
502 	dlmgmt_door_destroyid_t	*destroyid = argp;
503 	dlmgmt_destroyid_retval_t *retvalp = retp;
504 	datalink_id_t		linkid = destroyid->ld_linkid;
505 	uint32_t		flags = destroyid->ld_flags;
506 	dlmgmt_link_t		*linkp = NULL;
507 	int			err = 0;
508 
509 	/*
510 	 * Hold the writer lock to update the link table.
511 	 */
512 	dlmgmt_table_lock(B_TRUE);
513 	if ((linkp = link_by_id(linkid)) == NULL) {
514 		err = ENOENT;
515 		goto done;
516 	}
517 
518 	if ((err = dlmgmt_destroy_common(linkp, flags)) != 0)
519 		goto done;
520 
521 	/*
522 	 * Delete the active mapping.
523 	 */
524 	if (flags & DLMGMT_ACTIVE)
525 		(void) dlmgmt_delete_db_entry(linkid, DLMGMT_ACTIVE);
526 
527 done:
528 	dlmgmt_table_unlock();
529 	retvalp->lr_err = err;
530 }
531 
532 /*
533  * Remap a linkid to a given link name, i.e., rename an existing link1
534  * (ld_linkid) to a non-existent link2 (ld_link): rename link1's name to
535  * the given link name.
536  */
537 static void
538 dlmgmt_remapid(void *argp, void *retp)
539 {
540 	dlmgmt_door_remapid_t	*remapid = argp;
541 	dlmgmt_remapid_retval_t	*retvalp = retp;
542 	datalink_id_t		linkid1 = remapid->ld_linkid;
543 	dlmgmt_link_t		link, *linkp1, *tmp;
544 	avl_index_t		where;
545 	int			err = 0;
546 
547 	if (!dladm_valid_linkname(remapid->ld_link)) {
548 		retvalp->lr_err = EINVAL;
549 		return;
550 	}
551 
552 	/*
553 	 * Hold the writer lock to update the link table.
554 	 */
555 	dlmgmt_table_lock(B_TRUE);
556 	if ((linkp1 = link_by_id(linkid1)) == NULL) {
557 		err = ENOENT;
558 		goto done;
559 	}
560 
561 	if (link_by_name(remapid->ld_link) != NULL) {
562 		err = EEXIST;
563 		goto done;
564 	}
565 
566 	avl_remove(&dlmgmt_name_avl, linkp1);
567 	(void) strlcpy(link.ll_link, remapid->ld_link, MAXLINKNAMELEN);
568 	tmp = avl_find(&dlmgmt_name_avl, &link, &where);
569 	assert(tmp == NULL);
570 	(void) strlcpy(linkp1->ll_link, remapid->ld_link, MAXLINKNAMELEN);
571 	avl_insert(&dlmgmt_name_avl, linkp1, where);
572 	dlmgmt_advance(linkp1);
573 
574 	/*
575 	 * If we renamed a temporary link, update the temporary repository.
576 	 */
577 	if (linkp1->ll_flags & DLMGMT_ACTIVE)
578 		(void) dlmgmt_write_db_entry(linkid1, DLMGMT_ACTIVE);
579 done:
580 	dlmgmt_table_unlock();
581 	retvalp->lr_err = err;
582 }
583 
584 static void
585 dlmgmt_upid(void *argp, void *retp)
586 {
587 	dlmgmt_door_upid_t	*upid = argp;
588 	dlmgmt_upid_retval_t	*retvalp = retp;
589 	dlmgmt_link_t		*linkp;
590 	int			err = 0;
591 
592 	/*
593 	 * Hold the writer lock to update the link table.
594 	 */
595 	dlmgmt_table_lock(B_TRUE);
596 	if ((linkp = link_by_id(upid->ld_linkid)) == NULL) {
597 		err = ENOENT;
598 		goto done;
599 	}
600 
601 	if (linkp->ll_flags & DLMGMT_ACTIVE) {
602 		err = EINVAL;
603 		goto done;
604 	}
605 
606 	linkp->ll_flags |= DLMGMT_ACTIVE;
607 	(void) dlmgmt_write_db_entry(linkp->ll_linkid, DLMGMT_ACTIVE);
608 done:
609 	dlmgmt_table_unlock();
610 	retvalp->lr_err = err;
611 }
612 
613 static void
614 dlmgmt_createconf(void *argp, void *retp)
615 {
616 	dlmgmt_door_createconf_t *createconf = argp;
617 	dlmgmt_createconf_retval_t *retvalp = retp;
618 	dlmgmt_dlconf_t		dlconf, *dlconfp, *tmp;
619 	avl_index_t		where;
620 	int			err;
621 
622 	/*
623 	 * Hold the writer lock to update the dlconf table.
624 	 */
625 	dlmgmt_dlconf_table_lock(B_TRUE);
626 
627 	if ((err = dlconf_create(createconf->ld_link, createconf->ld_linkid,
628 	    createconf->ld_class, createconf->ld_media, &dlconfp)) != 0) {
629 		goto done;
630 	}
631 
632 	dlconf.ld_id = dlconfp->ld_id;
633 	tmp = avl_find(&dlmgmt_dlconf_avl, &dlconf, &where);
634 	assert(tmp == NULL);
635 	avl_insert(&dlmgmt_dlconf_avl, dlconfp, where);
636 	dlmgmt_advance_dlconfid(dlconfp);
637 
638 	retvalp->lr_conf = (dladm_conf_t)dlconfp->ld_id;
639 done:
640 	dlmgmt_dlconf_table_unlock();
641 	retvalp->lr_err = err;
642 }
643 
644 static void
645 dlmgmt_setattr(void *argp, void *retp)
646 {
647 	dlmgmt_door_setattr_t	*setattr = argp;
648 	dlmgmt_setattr_retval_t	*retvalp = retp;
649 	dlmgmt_dlconf_t		dlconf, *dlconfp;
650 	int			err = 0;
651 
652 	/*
653 	 * Hold the writer lock to update the dlconf table.
654 	 */
655 	dlmgmt_dlconf_table_lock(B_TRUE);
656 
657 	dlconf.ld_id = (int)setattr->ld_conf;
658 	dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL);
659 	if (dlconfp == NULL) {
660 		err = ENOENT;
661 		goto done;
662 	}
663 
664 	err = linkattr_set(&(dlconfp->ld_head), setattr->ld_attr,
665 	    &setattr->ld_attrval, setattr->ld_attrsz, setattr->ld_type);
666 
667 done:
668 	dlmgmt_dlconf_table_unlock();
669 	retvalp->lr_err = err;
670 }
671 
672 static void
673 dlmgmt_unsetconfattr(void *argp, void *retp)
674 {
675 	dlmgmt_door_unsetattr_t	*unsetattr = argp;
676 	dlmgmt_unsetattr_retval_t *retvalp = retp;
677 	dlmgmt_dlconf_t		dlconf, *dlconfp;
678 	int			err = 0;
679 
680 	/*
681 	 * Hold the writer lock to update the dlconf table.
682 	 */
683 	dlmgmt_dlconf_table_lock(B_TRUE);
684 
685 	dlconf.ld_id = (int)unsetattr->ld_conf;
686 	dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL);
687 	if (dlconfp == NULL) {
688 		err = ENOENT;
689 		goto done;
690 	}
691 
692 	err = linkattr_unset(&(dlconfp->ld_head), unsetattr->ld_attr);
693 
694 done:
695 	dlmgmt_dlconf_table_unlock();
696 	retvalp->lr_err = err;
697 }
698 
699 /*
700  * Note that dlmgmt_readconf() returns a conf ID of a conf AVL tree entry,
701  * which is managed by dlmgmtd.  The ID is used to find the conf entry when
702  * dlmgmt_write_conf() is called.  The conf entry contains an ld_gen value
703  * (which is the generation number - ll_gen) of the dlmgmt_link_t at the time
704  * of dlmgmt_readconf(), and ll_gen changes every time the dlmgmt_link_t
705  * changes its attributes.  Therefore, dlmgmt_write_conf() can compare ld_gen
706  * in the conf entry against the latest dlmgmt_link_t ll_gen value to see if
707  * anything has changed between the dlmgmt_read_conf() and dlmgmt_writeconf()
708  * calls.  If so, EAGAIN is returned.  This mechanism can ensures atomicity
709  * across the pair of dladm_read_conf() and dladm_write_conf() calls.
710  */
711 static void
712 dlmgmt_writeconf(void *argp, void *retp)
713 {
714 	dlmgmt_door_writeconf_t	*writeconf = argp;
715 	dlmgmt_writeconf_retval_t *retvalp = retp;
716 	dlmgmt_dlconf_t		dlconf, *dlconfp;
717 	dlmgmt_link_t		*linkp;
718 	dlmgmt_linkattr_t	*attrp, *next;
719 	int			err = 0;
720 
721 	/*
722 	 * Hold the read lock to access the dlconf table.
723 	 */
724 	dlmgmt_dlconf_table_lock(B_TRUE);
725 
726 	dlconf.ld_id = (int)writeconf->ld_conf;
727 	dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL);
728 	if (dlconfp == NULL) {
729 		err = ENOENT;
730 		goto done;
731 	}
732 
733 	/*
734 	 * Hold the writer lock to update the link table.
735 	 */
736 	dlmgmt_table_lock(B_TRUE);
737 	linkp = link_by_id(dlconfp->ld_linkid);
738 	if ((linkp == NULL) || (linkp->ll_class != dlconfp->ld_class) ||
739 	    (linkp->ll_media != dlconfp->ld_media) ||
740 	    (strcmp(linkp->ll_link, dlconfp->ld_link) != 0)) {
741 		/*
742 		 * The link does not exist.
743 		 */
744 		dlmgmt_table_unlock();
745 		err = ENOENT;
746 		goto done;
747 	}
748 
749 	if (linkp->ll_gen != dlconfp->ld_gen) {
750 		/*
751 		 * Something has changed the link configuration; try again.
752 		 */
753 		dlmgmt_table_unlock();
754 		err = EAGAIN;
755 		goto done;
756 	}
757 
758 	/*
759 	 * Delete the old attribute list.
760 	 */
761 	for (attrp = linkp->ll_head; attrp != NULL; attrp = next) {
762 		next = attrp->lp_next;
763 		free(attrp->lp_val);
764 		free(attrp);
765 	}
766 	linkp->ll_head = NULL;
767 
768 	/*
769 	 * Set the new attribute.
770 	 */
771 	for (attrp = dlconfp->ld_head; attrp != NULL; attrp = attrp->lp_next) {
772 		if ((err = linkattr_set(&(linkp->ll_head), attrp->lp_name,
773 		    attrp->lp_val, attrp->lp_sz, attrp->lp_type)) != 0) {
774 			dlmgmt_table_unlock();
775 			goto done;
776 		}
777 	}
778 
779 	linkp->ll_gen++;
780 	err = dlmgmt_write_db_entry(linkp->ll_linkid, DLMGMT_PERSIST);
781 	dlmgmt_table_unlock();
782 done:
783 	dlmgmt_dlconf_table_unlock();
784 	retvalp->lr_err = err;
785 }
786 
787 static void
788 dlmgmt_removeconf(void *argp, void *retp)
789 {
790 	dlmgmt_door_removeconf_t 	*removeconf = argp;
791 	dlmgmt_removeconf_retval_t	*retvalp = retp;
792 	int				err;
793 
794 	dlmgmt_table_lock(B_TRUE);
795 	err = dlmgmt_delete_db_entry(removeconf->ld_linkid, DLMGMT_PERSIST);
796 	dlmgmt_table_unlock();
797 	retvalp->lr_err = err;
798 }
799 
800 static void
801 dlmgmt_destroyconf(void *argp, void *retp)
802 {
803 	dlmgmt_door_destroyconf_t	*destroyconf = argp;
804 	dlmgmt_destroyconf_retval_t	*retvalp = retp;
805 	dlmgmt_dlconf_t			dlconf, *dlconfp;
806 	int				err = 0;
807 
808 	/*
809 	 * Hold the writer lock to update the dlconf table.
810 	 */
811 	dlmgmt_dlconf_table_lock(B_TRUE);
812 
813 	dlconf.ld_id = (int)destroyconf->ld_conf;
814 	dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL);
815 	if (dlconfp == NULL) {
816 		err = ENOENT;
817 		goto done;
818 	}
819 
820 	avl_remove(&dlmgmt_dlconf_avl, dlconfp);
821 	dlconf_destroy(dlconfp);
822 
823 done:
824 	dlmgmt_dlconf_table_unlock();
825 	retvalp->lr_err = err;
826 }
827 
828 /*
829  * See the comments above dladm_write_conf() to see how ld_gen is used to
830  * ensure atomicity across the {dlmgmt_readconf(), dlmgmt_writeconf()} pair.
831  */
832 static void
833 dlmgmt_readconf(void *argp, void *retp)
834 {
835 	dlmgmt_door_readconf_t	*readconf = argp;
836 	dlmgmt_readconf_retval_t *retvalp = retp;
837 	dlmgmt_link_t 		*linkp;
838 	datalink_id_t		linkid = readconf->ld_linkid;
839 	dlmgmt_dlconf_t		*dlconfp, *tmp, dlconf;
840 	dlmgmt_linkattr_t	*attrp;
841 	avl_index_t		where;
842 	int			err = 0;
843 
844 	/*
845 	 * Hold the writer lock to update the dlconf table.
846 	 */
847 	dlmgmt_dlconf_table_lock(B_TRUE);
848 
849 	/*
850 	 * Hold the reader lock to access the link
851 	 */
852 	dlmgmt_table_lock(B_FALSE);
853 	linkp = link_by_id(linkid);
854 	if ((linkp == NULL) || !(linkp->ll_flags & DLMGMT_PERSIST)) {
855 		/*
856 		 * The persistent link configuration does not exists.
857 		 */
858 		err = ENOENT;
859 		goto done;
860 	}
861 
862 	if ((err = dlconf_create(linkp->ll_link, linkp->ll_linkid,
863 	    linkp->ll_class, linkp->ll_media, &dlconfp)) != 0) {
864 		goto done;
865 	}
866 
867 	for (attrp = linkp->ll_head; attrp != NULL; attrp = attrp->lp_next) {
868 		if ((err = linkattr_set(&(dlconfp->ld_head), attrp->lp_name,
869 		    attrp->lp_val, attrp->lp_sz, attrp->lp_type)) != 0) {
870 			dlconf_destroy(dlconfp);
871 			goto done;
872 		}
873 	}
874 	dlconfp->ld_gen = linkp->ll_gen;
875 
876 	dlconf.ld_id = dlconfp->ld_id;
877 	tmp = avl_find(&dlmgmt_dlconf_avl, &dlconf, &where);
878 	assert(tmp == NULL);
879 	avl_insert(&dlmgmt_dlconf_avl, dlconfp, where);
880 	dlmgmt_advance_dlconfid(dlconfp);
881 
882 	retvalp->lr_conf = (dladm_conf_t)dlconfp->ld_id;
883 done:
884 	dlmgmt_table_unlock();
885 	dlmgmt_dlconf_table_unlock();
886 	retvalp->lr_err = err;
887 }
888 
889 /*
890  * Note: the caller must free *retvalpp in case of success.
891  */
892 static void
893 dlmgmt_getattr(void *argp, void *retp)
894 {
895 	dlmgmt_door_getattr_t	*getattr = argp;
896 	dlmgmt_getattr_retval_t	*retvalp = retp;
897 	dlmgmt_dlconf_t		dlconf, *dlconfp;
898 
899 	/*
900 	 * Hold the read lock to access the dlconf table.
901 	 */
902 	dlmgmt_dlconf_table_lock(B_FALSE);
903 
904 	dlconf.ld_id = (int)getattr->ld_conf;
905 	dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL);
906 	if (dlconfp == NULL) {
907 		retvalp->lr_err = ENOENT;
908 		goto done;
909 	}
910 
911 	dlmgmt_getattr_common(&dlconfp->ld_head, getattr->ld_attr, retvalp);
912 
913 done:
914 	dlmgmt_dlconf_table_unlock();
915 }
916 
917 static void
918 dlmgmt_upcall_linkprop_init(void *argp, void *retp)
919 {
920 	dlmgmt_door_linkprop_init_t	*lip = argp;
921 	dlmgmt_linkprop_init_retval_t	*retvalp = retp;
922 	dlmgmt_link_t			*linkp;
923 	boolean_t			do_linkprop = B_FALSE;
924 
925 	/*
926 	 * Ignore wifi links until wifi property ioctls are converted
927 	 * to generic property ioctls. This avoids deadlocks due to
928 	 * wifi property ioctls using their own /dev/net device,
929 	 * not the DLD control device.
930 	 */
931 	dlmgmt_table_lock(B_FALSE);
932 	if ((linkp = link_by_id(lip->ld_linkid)) == NULL)
933 		retvalp->lr_err = ENOENT;
934 	else if (linkp->ll_media == DL_WIFI)
935 		retvalp->lr_err = 0;
936 	else
937 		do_linkprop = B_TRUE;
938 	dlmgmt_table_unlock();
939 
940 	if (do_linkprop)
941 		retvalp->lr_err = dladm_init_linkprop(lip->ld_linkid, B_TRUE);
942 }
943 
944 static dlmgmt_door_info_t i_dlmgmt_door_info_tbl[] = {
945 	{ DLMGMT_CMD_DLS_CREATE, B_TRUE, sizeof (dlmgmt_upcall_arg_create_t),
946 	    sizeof (dlmgmt_create_retval_t), dlmgmt_upcall_create },
947 	{ DLMGMT_CMD_DLS_GETATTR, B_FALSE, sizeof (dlmgmt_upcall_arg_getattr_t),
948 	    sizeof (dlmgmt_getattr_retval_t), dlmgmt_upcall_getattr },
949 	{ DLMGMT_CMD_DLS_DESTROY, B_TRUE, sizeof (dlmgmt_upcall_arg_destroy_t),
950 	    sizeof (dlmgmt_destroy_retval_t), dlmgmt_upcall_destroy },
951 	{ DLMGMT_CMD_GETNAME, B_FALSE, sizeof (dlmgmt_door_getname_t),
952 	    sizeof (dlmgmt_getname_retval_t), dlmgmt_getname },
953 	{ DLMGMT_CMD_GETLINKID,	B_FALSE, sizeof (dlmgmt_door_getlinkid_t),
954 	    sizeof (dlmgmt_getlinkid_retval_t), dlmgmt_getlinkid },
955 	{ DLMGMT_CMD_GETNEXT, B_FALSE, sizeof (dlmgmt_door_getnext_t),
956 	    sizeof (dlmgmt_getnext_retval_t), dlmgmt_getnext },
957 	{ DLMGMT_CMD_DLS_UPDATE, B_TRUE, sizeof (dlmgmt_upcall_arg_update_t),
958 	    sizeof (dlmgmt_update_retval_t), dlmgmt_upcall_update },
959 	{ DLMGMT_CMD_CREATE_LINKID, B_TRUE, sizeof (dlmgmt_door_createid_t),
960 	    sizeof (dlmgmt_createid_retval_t), dlmgmt_createid },
961 	{ DLMGMT_CMD_DESTROY_LINKID, B_TRUE, sizeof (dlmgmt_door_destroyid_t),
962 	    sizeof (dlmgmt_destroyid_retval_t), dlmgmt_destroyid },
963 	{ DLMGMT_CMD_REMAP_LINKID, B_TRUE, sizeof (dlmgmt_door_remapid_t),
964 	    sizeof (dlmgmt_remapid_retval_t), dlmgmt_remapid },
965 	{ DLMGMT_CMD_CREATECONF, B_TRUE, sizeof (dlmgmt_door_createconf_t),
966 	    sizeof (dlmgmt_createconf_retval_t), dlmgmt_createconf },
967 	{ DLMGMT_CMD_READCONF, B_FALSE, sizeof (dlmgmt_door_readconf_t),
968 	    sizeof (dlmgmt_readconf_retval_t), dlmgmt_readconf },
969 	{ DLMGMT_CMD_WRITECONF, B_TRUE, sizeof (dlmgmt_door_writeconf_t),
970 	    sizeof (dlmgmt_writeconf_retval_t), dlmgmt_writeconf },
971 	{ DLMGMT_CMD_UP_LINKID, B_TRUE, sizeof (dlmgmt_door_upid_t),
972 	    sizeof (dlmgmt_upid_retval_t), dlmgmt_upid },
973 	{ DLMGMT_CMD_SETATTR, B_TRUE, sizeof (dlmgmt_door_setattr_t),
974 	    sizeof (dlmgmt_setattr_retval_t), dlmgmt_setattr },
975 	{ DLMGMT_CMD_UNSETATTR, B_TRUE, sizeof (dlmgmt_door_unsetattr_t),
976 	    sizeof (dlmgmt_unsetattr_retval_t), dlmgmt_unsetconfattr },
977 	{ DLMGMT_CMD_REMOVECONF, B_TRUE, sizeof (dlmgmt_door_removeconf_t),
978 	    sizeof (dlmgmt_removeconf_retval_t), dlmgmt_removeconf },
979 	{ DLMGMT_CMD_DESTROYCONF, B_TRUE, sizeof (dlmgmt_door_destroyconf_t),
980 	    sizeof (dlmgmt_destroyconf_retval_t), dlmgmt_destroyconf },
981 	{ DLMGMT_CMD_GETATTR, B_FALSE, sizeof (dlmgmt_door_getattr_t),
982 	    sizeof (dlmgmt_getattr_retval_t), dlmgmt_getattr },
983 	{ DLMGMT_CMD_LINKPROP_INIT, B_TRUE,
984 	    sizeof (dlmgmt_door_linkprop_init_t),
985 	    sizeof (dlmgmt_linkprop_init_retval_t),
986 	    dlmgmt_upcall_linkprop_init }
987 };
988 
989 #define	DLMGMT_INFO_TABLE_SIZE	(sizeof (i_dlmgmt_door_info_tbl) /	\
990     sizeof (i_dlmgmt_door_info_tbl[0]))
991 
992 /* ARGSUSED */
993 void
994 dlmgmt_handler(void *cookie, char *argp, size_t argsz, door_desc_t *dp,
995     uint_t n_desc)
996 {
997 	dlmgmt_door_info_t	*infop = NULL;
998 	dlmgmt_retval_t		retval;
999 	void			*retvalp;
1000 	int			err = 0;
1001 	int			i;
1002 
1003 	for (i = 0; i < DLMGMT_INFO_TABLE_SIZE; i++) {
1004 		if (i_dlmgmt_door_info_tbl[i].di_cmd ==
1005 		    ((dlmgmt_door_arg_t *)(void *)argp)->ld_cmd) {
1006 			infop = i_dlmgmt_door_info_tbl + i;
1007 			break;
1008 		}
1009 	}
1010 
1011 	if (infop == NULL || argsz != infop->di_reqsz) {
1012 		err = EINVAL;
1013 		goto fail;
1014 	}
1015 
1016 	if (infop->di_set) {
1017 		ucred_t	*cred = NULL;
1018 		const priv_set_t *eset;
1019 
1020 		if (door_ucred(&cred) != 0) {
1021 			err = errno;
1022 			goto fail;
1023 		}
1024 
1025 		eset = ucred_getprivset(cred, PRIV_EFFECTIVE);
1026 		if (eset == NULL || !priv_ismember(eset, PRIV_SYS_DL_CONFIG))
1027 			err = EACCES;
1028 		ucred_free(cred);
1029 		if (err != 0)
1030 			goto fail;
1031 	}
1032 
1033 	/*
1034 	 * We cannot use malloc() here because door_return never returns, and
1035 	 * memory allocated by malloc() would get leaked. Use alloca() instead.
1036 	 */
1037 	retvalp = alloca(infop->di_acksz);
1038 	infop->di_handler(argp, retvalp);
1039 	(void) door_return(retvalp, infop->di_acksz, NULL, 0);
1040 	return;
1041 
1042 fail:
1043 	retval.lr_err = err;
1044 	(void) door_return((char *)&retval, sizeof (retval), NULL, 0);
1045 }
1046