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