Home | History | Annotate | Download | only in io
      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 2009 Sun Microsystems, Inc.  All rights reserved.
     24  * Use is subject to license terms.
     25  */
     26 
     27 /*
     28  * MD Event Generator (MDEG) Module
     29  */
     30 
     31 #include <sys/machsystm.h>
     32 #include <sys/taskq.h>
     33 #include <sys/disp.h>
     34 #include <sys/cmn_err.h>
     35 #include <sys/note.h>
     36 
     37 #include <sys/mdeg.h>
     38 #include <sys/mach_descrip.h>
     39 #include <sys/mdesc.h>
     40 
     41 /*
     42  * A single client registration
     43  */
     44 typedef struct mdeg_clnt {
     45 	boolean_t		valid;		/* structure is in active use */
     46 	mdeg_node_match_t	*nmatch;	/* node match filter */
     47 	mdeg_node_spec_t	*pspec;		/* parent match filter */
     48 	mdeg_cb_t		cb;		/* the client callback */
     49 	caddr_t			cb_arg;		/* argument to the callback */
     50 	uint64_t		magic;		/* sanity checking magic */
     51 	mdeg_handle_t		hdl;		/* handle assigned by MDEG */
     52 } mdeg_clnt_t;
     53 
     54 /*
     55  * Global MDEG data
     56  *
     57  * Locking Strategy:
     58  *
     59  *   mdeg.lock - lock used to synchronize system-wide MD updates. An
     60  *	MD update must be treated as an atomic event. The lock is
     61  *	taken when notification that a new MD is available and held
     62  *	until all clients have been notified.
     63  *
     64  *   mdeg.rwlock - lock used to synchronize access to the table of
     65  *	registered clients. The reader lock must be held when looking
     66  *	up client information in the table. The writer lock must be
     67  *	held when modifying any client information.
     68  */
     69 static struct mdeg {
     70 	taskq_t 	*taskq;		/* for internal processing */
     71 	boolean_t	enabled;	/* enable/disable taskq processing */
     72 	kmutex_t	lock;		/* synchronize MD updates */
     73 	md_t		*md_prev;	/* previous MD */
     74 	md_t		*md_curr;	/* current MD */
     75 	mdeg_clnt_t	*tbl;		/* table of registered clients */
     76 	krwlock_t	rwlock;		/* client table lock */
     77 	uint_t		maxclnts;	/* client table size */
     78 	uint_t		nclnts;		/* current number of clients */
     79 } mdeg;
     80 
     81 /*
     82  * Debugging routines
     83  */
     84 #ifdef DEBUG
     85 uint_t mdeg_debug = 0x0;
     86 
     87 static void mdeg_dump_clnt(mdeg_clnt_t *clnt);
     88 static void mdeg_dump_table(void);
     89 
     90 #define	MDEG_DBG		if (mdeg_debug) printf
     91 #define	MDEG_DUMP_CLNT		mdeg_dump_clnt
     92 #define	MDEG_DUMP_TABLE		mdeg_dump_table
     93 
     94 #else /* DEBUG */
     95 
     96 #define	MDEG_DBG		_NOTE(CONSTCOND) if (0) printf
     97 #define	MDEG_DUMP_CLNT
     98 #define	MDEG_DUMP_TABLE()
     99 
    100 #endif /* DEBUG */
    101 
    102 /*
    103  * Global constants
    104  */
    105 #define	MDEG_MAX_TASKQ_THR	512	/* maximum number of taskq threads */
    106 #define	MDEG_MAX_CLNTS_INIT	64	/* initial client table size */
    107 
    108 #define	MDEG_MAGIC		0x4D4445475F48444Cull	/* 'MDEG_HDL' */
    109 
    110 /*
    111  * A client handle is a 64 bit value with two pieces of
    112  * information encoded in it. The upper 32 bits are the
    113  * index into the table of a particular client structure.
    114  * The lower 32 bits are a counter that is incremented
    115  * each time a client structure is reused.
    116  */
    117 #define	MDEG_IDX_SHIFT			32
    118 #define	MDEG_COUNT_MASK			0xfffffffful
    119 
    120 #define	MDEG_ALLOC_HDL(_idx, _count)	(((uint64_t)_idx << MDEG_IDX_SHIFT) | \
    121 					((uint64_t)(_count + 1) &	      \
    122 					MDEG_COUNT_MASK))
    123 #define	MDEG_HDL2IDX(hdl)		(hdl >> MDEG_IDX_SHIFT)
    124 #define	MDEG_HDL2COUNT(hdl)		(hdl & MDEG_COUNT_MASK)
    125 
    126 static const char trunc_str[] = " ... }";
    127 
    128 /*
    129  * Utility routines
    130  */
    131 static mdeg_clnt_t *mdeg_alloc_clnt(void);
    132 static void mdeg_notify_client(void *);
    133 static mde_cookie_t mdeg_find_start_node(md_t *, mdeg_node_spec_t *);
    134 static boolean_t mdeg_node_spec_match(md_t *, mde_cookie_t, mdeg_node_spec_t *);
    135 static void mdeg_get_diff_results(md_diff_cookie_t, mdeg_result_t *);
    136 
    137 int
    138 mdeg_init(void)
    139 {
    140 	int	tblsz;
    141 
    142 	/*
    143 	 * Grab the current MD
    144 	 */
    145 	if ((mdeg.md_curr = md_get_handle()) == NULL) {
    146 		cmn_err(CE_WARN, "unable to cache snapshot of MD");
    147 		return (-1);
    148 	}
    149 
    150 	/*
    151 	 * Initialize table of registered clients
    152 	 */
    153 	mdeg.maxclnts = MDEG_MAX_CLNTS_INIT;
    154 
    155 	tblsz = mdeg.maxclnts * sizeof (mdeg_clnt_t);
    156 	mdeg.tbl = kmem_zalloc(tblsz, KM_SLEEP);
    157 
    158 	rw_init(&mdeg.rwlock, NULL, RW_DRIVER, NULL);
    159 
    160 	mdeg.nclnts = 0;
    161 
    162 	/*
    163 	 * Initialize global lock
    164 	 */
    165 	mutex_init(&mdeg.lock, NULL, MUTEX_DRIVER, NULL);
    166 
    167 	/*
    168 	 * Initialize the task queue
    169 	 */
    170 	mdeg.taskq = taskq_create("mdeg_taskq", 1, minclsyspri, 1,
    171 	    MDEG_MAX_TASKQ_THR, TASKQ_PREPOPULATE | TASKQ_DYNAMIC);
    172 
    173 	/* ready to begin handling clients */
    174 	mdeg.enabled = B_TRUE;
    175 
    176 	return (0);
    177 }
    178 
    179 void
    180 mdeg_fini(void)
    181 {
    182 	/*
    183 	 * Flip the enabled switch off to make sure that
    184 	 * no events get dispatched while things are being
    185 	 * torn down.
    186 	 */
    187 	mdeg.enabled = B_FALSE;
    188 
    189 	/* destroy the task queue */
    190 	taskq_destroy(mdeg.taskq);
    191 
    192 	/*
    193 	 * Deallocate the table of registered clients
    194 	 */
    195 	kmem_free(mdeg.tbl, mdeg.maxclnts * sizeof (mdeg_clnt_t));
    196 	rw_destroy(&mdeg.rwlock);
    197 
    198 	/*
    199 	 * Free up the cached MDs.
    200 	 */
    201 	if (mdeg.md_curr)
    202 		(void) md_fini_handle(mdeg.md_curr);
    203 
    204 	if (mdeg.md_prev)
    205 		(void) md_fini_handle(mdeg.md_prev);
    206 
    207 	mutex_destroy(&mdeg.lock);
    208 }
    209 
    210 static mdeg_clnt_t *
    211 mdeg_alloc_clnt(void)
    212 {
    213 	mdeg_clnt_t	*clnt;
    214 	int		idx;
    215 	mdeg_clnt_t	*newtbl;
    216 	uint_t		newmaxclnts;
    217 	uint_t		newtblsz;
    218 	uint_t		oldtblsz;
    219 
    220 	ASSERT(RW_WRITE_HELD(&mdeg.rwlock));
    221 
    222 	/* search for an unused slot in the table */
    223 	for (idx = 0; idx < mdeg.maxclnts; idx++) {
    224 		clnt = &mdeg.tbl[idx];
    225 		if (!clnt->valid) {
    226 			break;
    227 		}
    228 	}
    229 
    230 	/* found any empty slot */
    231 	if (idx != mdeg.maxclnts) {
    232 		goto found;
    233 	}
    234 
    235 	/*
    236 	 * There was no free space in the table. Grow
    237 	 * the table to double its current size.
    238 	 */
    239 
    240 	MDEG_DBG("client table full:\n");
    241 	MDEG_DUMP_TABLE();
    242 
    243 	newmaxclnts = mdeg.maxclnts * 2;
    244 	newtblsz = newmaxclnts * sizeof (mdeg_clnt_t);
    245 
    246 	newtbl = kmem_zalloc(newtblsz, KM_SLEEP);
    247 
    248 	/* copy old table data to the new table */
    249 	oldtblsz = mdeg.maxclnts * sizeof (mdeg_clnt_t);
    250 	bcopy(mdeg.tbl, newtbl, oldtblsz);
    251 
    252 	/*
    253 	 * Since the old table was full, the first free entry
    254 	 * will be just past the end of the old table data in
    255 	 * the new table.
    256 	 */
    257 	clnt = &newtbl[mdeg.maxclnts];
    258 
    259 	/* clean up the old table */
    260 	kmem_free(mdeg.tbl, oldtblsz);
    261 	mdeg.tbl = newtbl;
    262 	mdeg.maxclnts = newmaxclnts;
    263 
    264 found:
    265 	ASSERT(clnt->valid == 0);
    266 
    267 	clnt->hdl = MDEG_ALLOC_HDL(idx, MDEG_HDL2COUNT(clnt->hdl));
    268 
    269 	return (clnt);
    270 }
    271 
    272 static mdeg_clnt_t *
    273 mdeg_get_client(mdeg_handle_t hdl)
    274 {
    275 	int		idx;
    276 	mdeg_clnt_t	*clnt;
    277 
    278 	idx = MDEG_HDL2IDX(hdl);
    279 
    280 	/* check if index is out of bounds */
    281 	if ((idx < 0) || (idx >= mdeg.maxclnts)) {
    282 		MDEG_DBG("mdeg_get_client: index out of bounds\n");
    283 		return (NULL);
    284 	}
    285 
    286 	clnt = &mdeg.tbl[idx];
    287 
    288 	/* check for a valid client */
    289 	if (!clnt->valid) {
    290 		MDEG_DBG("mdeg_get_client: client is not valid\n");
    291 		return (NULL);
    292 	}
    293 
    294 	/* make sure the handle is an exact match */
    295 	if (clnt->hdl != hdl) {
    296 		MDEG_DBG("mdeg_get_client: bad handle\n");
    297 		return (NULL);
    298 	}
    299 
    300 	if (clnt->magic != MDEG_MAGIC) {
    301 		MDEG_DBG("mdeg_get_client: bad magic\n");
    302 		return (NULL);
    303 	}
    304 
    305 	return (clnt);
    306 }
    307 
    308 /*
    309  * Send a notification to a client immediately after it registers.
    310  * The result_t is a list of all the nodes that match their specified
    311  * nodes of interest, all returned on the added list. This serves
    312  * as a base of reference to the client. All future MD updates are
    313  * relative to this list.
    314  */
    315 static int
    316 mdeg_notify_client_reg(mdeg_clnt_t *clnt)
    317 {
    318 	md_t			*mdp = NULL;
    319 	mde_str_cookie_t	nname;
    320 	mde_str_cookie_t	aname;
    321 	mde_cookie_t		startnode;
    322 	int			nnodes;
    323 	int			nodechk;
    324 	mde_cookie_t		*listp = NULL;
    325 	mdeg_result_t		*mdeg_res = NULL;
    326 	int			rv = MDEG_SUCCESS;
    327 
    328 	mutex_enter(&mdeg.lock);
    329 
    330 	/*
    331 	 * Handle the special case where the node specification
    332 	 * is NULL. In this case, call the client callback without
    333 	 * any results. All processing is left to the client.
    334 	 */
    335 	if (clnt->pspec == NULL) {
    336 		/* call the client callback */
    337 		(*clnt->cb)(clnt->cb_arg, NULL);
    338 		goto done;
    339 	}
    340 
    341 	if ((mdp = md_get_handle()) == NULL) {
    342 		cmn_err(CE_WARN, "unable to retrieve current MD");
    343 		rv = MDEG_FAILURE;
    344 		goto done;
    345 	}
    346 
    347 	startnode = mdeg_find_start_node(mdp, clnt->pspec);
    348 	if (startnode == MDE_INVAL_ELEM_COOKIE) {
    349 		/* not much we can do */
    350 		cmn_err(CE_WARN, "unable to match node specifier");
    351 		rv = MDEG_FAILURE;
    352 		goto done;
    353 	}
    354 
    355 	/*
    356 	 * Use zalloc to provide correct default values for the
    357 	 * unused removed, match_prev, and match_curr lists.
    358 	 */
    359 	mdeg_res = kmem_zalloc(sizeof (mdeg_result_t), KM_SLEEP);
    360 
    361 	nname = md_find_name(mdp, clnt->nmatch->namep);
    362 	aname = md_find_name(mdp, "fwd");
    363 
    364 	nnodes = md_scan_dag(mdp, startnode, nname, aname, NULL);
    365 
    366 	if (nnodes == 0) {
    367 		MDEG_DBG("mdeg_notify_client_reg: no nodes of interest\n");
    368 		rv = MDEG_SUCCESS;
    369 		goto done;
    370 	} else if (nnodes == -1) {
    371 		MDEG_DBG("error scanning DAG\n");
    372 		rv = MDEG_FAILURE;
    373 		goto done;
    374 	}
    375 
    376 	MDEG_DBG("mdeg_notify_client_reg: %d node%s of interest\n",
    377 	    nnodes, (nnodes == 1) ? "" : "s");
    378 
    379 	/* get the list of nodes of interest */
    380 	listp = kmem_alloc(sizeof (mde_cookie_t) * nnodes, KM_SLEEP);
    381 	nodechk = md_scan_dag(mdp, startnode, nname, aname, listp);
    382 
    383 	ASSERT(nodechk == nnodes);
    384 
    385 	mdeg_res->added.mdp = mdp;
    386 	mdeg_res->added.mdep = listp;
    387 	mdeg_res->added.nelem = nnodes;
    388 
    389 	/* call the client callback */
    390 	(*clnt->cb)(clnt->cb_arg, mdeg_res);
    391 
    392 done:
    393 	mutex_exit(&mdeg.lock);
    394 
    395 	if (mdp)
    396 		(void) md_fini_handle(mdp);
    397 
    398 	if (listp)
    399 		kmem_free(listp, sizeof (mde_cookie_t) * nnodes);
    400 
    401 	if (mdeg_res)
    402 		kmem_free(mdeg_res, sizeof (mdeg_result_t));
    403 
    404 	return (rv);
    405 }
    406 
    407 /*
    408  * Register to receive an event notification when the system
    409  * machine description is updated.
    410  *
    411  * Passing NULL for the node specification parameter is valid
    412  * as long as the match specification is also NULL. In this
    413  * case, the client will receive a notification when the MD
    414  * has been updated, but the callback will not include any
    415  * information. The client is then responsible for obtaining
    416  * its own copy of the system MD and performing any processing
    417  * manually.
    418  */
    419 int
    420 mdeg_register(mdeg_node_spec_t *pspecp, mdeg_node_match_t *nmatchp,
    421     mdeg_cb_t cb, void *cb_arg, mdeg_handle_t *hdlp)
    422 {
    423 	mdeg_clnt_t	*clnt;
    424 
    425 	/*
    426 	 * If the RW lock is held, a client is calling
    427 	 * register from its own callback.
    428 	 */
    429 	if (RW_LOCK_HELD(&mdeg.rwlock)) {
    430 		MDEG_DBG("mdeg_register: rwlock already held\n");
    431 		return (MDEG_FAILURE);
    432 	}
    433 
    434 	/* node spec and node match must both be valid, or both NULL */
    435 	if (((pspecp != NULL) && (nmatchp == NULL)) ||
    436 	    ((pspecp == NULL) && (nmatchp != NULL))) {
    437 		MDEG_DBG("mdeg_register: invalid parameters\n");
    438 		return (MDEG_FAILURE);
    439 	}
    440 
    441 	rw_enter(&mdeg.rwlock, RW_WRITER);
    442 
    443 	clnt = mdeg_alloc_clnt();
    444 
    445 	ASSERT(clnt);
    446 
    447 	/*
    448 	 * Fill in the rest of the data
    449 	 */
    450 	clnt->nmatch = nmatchp;
    451 	clnt->pspec = pspecp;
    452 	clnt->cb = cb;
    453 	clnt->cb_arg = cb_arg;
    454 	clnt->magic = MDEG_MAGIC;
    455 
    456 	/* do this last */
    457 	clnt->valid = B_TRUE;
    458 
    459 	MDEG_DBG("client registered (0x%lx):\n", clnt->hdl);
    460 	MDEG_DUMP_CLNT(clnt);
    461 
    462 	mdeg.nclnts++;
    463 
    464 	if (mdeg_notify_client_reg(clnt) != MDEG_SUCCESS) {
    465 		bzero(clnt, sizeof (mdeg_clnt_t));
    466 		rw_exit(&mdeg.rwlock);
    467 		return (MDEG_FAILURE);
    468 	}
    469 
    470 	rw_exit(&mdeg.rwlock);
    471 
    472 	*hdlp = clnt->hdl;
    473 
    474 	return (MDEG_SUCCESS);
    475 }
    476 
    477 int
    478 mdeg_unregister(mdeg_handle_t hdl)
    479 {
    480 	mdeg_clnt_t	*clnt;
    481 	mdeg_handle_t	mdh;
    482 
    483 	/*
    484 	 * If the RW lock is held, a client is calling
    485 	 * unregister from its own callback.
    486 	 */
    487 	if (RW_LOCK_HELD(&mdeg.rwlock)) {
    488 		MDEG_DBG("mdeg_unregister: rwlock already held\n");
    489 		return (MDEG_FAILURE);
    490 	}
    491 
    492 	/* lookup the client */
    493 	if ((clnt = mdeg_get_client(hdl)) == NULL) {
    494 		return (MDEG_FAILURE);
    495 	}
    496 
    497 	rw_enter(&mdeg.rwlock, RW_WRITER);
    498 
    499 	MDEG_DBG("client unregistered (0x%lx):\n", hdl);
    500 	MDEG_DUMP_CLNT(clnt);
    501 
    502 	/* save the handle to prevent reuse */
    503 	mdh = clnt->hdl;
    504 	bzero(clnt, sizeof (mdeg_clnt_t));
    505 
    506 	clnt->hdl = mdh;
    507 
    508 	mdeg.nclnts--;
    509 
    510 	rw_exit(&mdeg.rwlock);
    511 
    512 	return (MDEG_SUCCESS);
    513 }
    514 
    515 /*
    516  * Simple algorithm for now, grab the global lock and let all
    517  * the clients update themselves in parallel. There is a lot of
    518  * room for improvement here. We could eliminate some scans of
    519  * the DAG by incrementally scanning at lower levels of the DAG
    520  * rather than having each client start its own scan from the root.
    521  */
    522 void
    523 mdeg_notify_clients(void)
    524 {
    525 	md_t		*md_new;
    526 	mdeg_clnt_t	*clnt;
    527 	int		idx;
    528 	int		nclnt;
    529 
    530 	rw_enter(&mdeg.rwlock, RW_READER);
    531 	mutex_enter(&mdeg.lock);
    532 
    533 	/*
    534 	 * Rotate the MDs
    535 	 */
    536 	if ((md_new = md_get_handle()) == NULL) {
    537 		cmn_err(CE_WARN, "unable to retrieve new MD");
    538 		goto done;
    539 	}
    540 
    541 	if (mdeg.md_prev) {
    542 		(void) md_fini_handle(mdeg.md_prev);
    543 	}
    544 
    545 	mdeg.md_prev = mdeg.md_curr;
    546 	mdeg.md_curr = md_new;
    547 
    548 	if (mdeg.nclnts == 0) {
    549 		MDEG_DBG("mdeg_notify_clients: no clients registered\n");
    550 		goto done;
    551 	}
    552 
    553 	/* dispatch the update notification to all clients */
    554 	for (idx = 0, nclnt = 0; idx < mdeg.maxclnts; idx++) {
    555 		clnt = &mdeg.tbl[idx];
    556 
    557 		if (!clnt->valid)
    558 			continue;
    559 
    560 		MDEG_DBG("notifying client 0x%lx (%d/%d)\n", clnt->hdl,
    561 		    ++nclnt, mdeg.nclnts);
    562 
    563 		(void) taskq_dispatch(mdeg.taskq, mdeg_notify_client,
    564 		    (void *)clnt, TQ_SLEEP);
    565 	}
    566 
    567 	taskq_wait(mdeg.taskq);
    568 
    569 done:
    570 	mutex_exit(&mdeg.lock);
    571 	rw_exit(&mdeg.rwlock);
    572 }
    573 
    574 static void
    575 mdeg_notify_client(void *arg)
    576 {
    577 	mdeg_clnt_t		*clnt = (mdeg_clnt_t *)arg;
    578 	md_diff_cookie_t	mdd = MD_INVAL_DIFF_COOKIE;
    579 	mdeg_result_t		mdeg_res;
    580 	mde_cookie_t		md_prev_start;
    581 	mde_cookie_t		md_curr_start;
    582 
    583 	rw_enter(&mdeg.rwlock, RW_READER);
    584 
    585 	if (!mdeg.enabled) {
    586 		/* trying to shutdown */
    587 		MDEG_DBG("mdeg_notify_client: mdeg disabled, aborting\n");
    588 		goto cleanup;
    589 	}
    590 
    591 	/*
    592 	 * Handle the special case where the node specification
    593 	 * is NULL. In this case, call the client callback without
    594 	 * any results. All processing is left to the client.
    595 	 */
    596 	if (clnt->pspec == NULL) {
    597 		/* call the client callback */
    598 		(*clnt->cb)(clnt->cb_arg, NULL);
    599 
    600 		MDEG_DBG("MDEG client callback done\n");
    601 		goto cleanup;
    602 	}
    603 
    604 	/* find our start nodes */
    605 	md_prev_start = mdeg_find_start_node(mdeg.md_prev, clnt->pspec);
    606 	if (md_prev_start == MDE_INVAL_ELEM_COOKIE) {
    607 		goto cleanup;
    608 	}
    609 
    610 	md_curr_start = mdeg_find_start_node(mdeg.md_curr, clnt->pspec);
    611 	if (md_curr_start == MDE_INVAL_ELEM_COOKIE) {
    612 		goto cleanup;
    613 	}
    614 
    615 	/* diff the MDs */
    616 	mdd = md_diff_init(mdeg.md_prev, md_prev_start, mdeg.md_curr,
    617 	    md_curr_start, clnt->nmatch->namep, clnt->nmatch->matchp);
    618 
    619 	if (mdd == MD_INVAL_DIFF_COOKIE) {
    620 		MDEG_DBG("unable to diff MDs\n");
    621 		goto cleanup;
    622 	}
    623 
    624 	/*
    625 	 * Cache the results of the diff
    626 	 */
    627 	mdeg_get_diff_results(mdd, &mdeg_res);
    628 
    629 	/* call the client callback */
    630 	(*clnt->cb)(clnt->cb_arg, &mdeg_res);
    631 
    632 	MDEG_DBG("MDEG client callback done\n");
    633 
    634 cleanup:
    635 	rw_exit(&mdeg.rwlock);
    636 
    637 	if (mdd != MD_INVAL_DIFF_COOKIE)
    638 		(void) md_diff_fini(mdd);
    639 }
    640 
    641 static mde_cookie_t
    642 mdeg_find_start_node(md_t *md, mdeg_node_spec_t *nspec)
    643 {
    644 	mde_cookie_t		*nodesp;
    645 	mde_str_cookie_t	nname;
    646 	mde_str_cookie_t	aname;
    647 	int			nnodes;
    648 	int			idx;
    649 
    650 	if ((md == NULL) || (nspec == NULL))
    651 		return (MDE_INVAL_ELEM_COOKIE);
    652 
    653 	nname = md_find_name(md, nspec->namep);
    654 	aname = md_find_name(md, "fwd");
    655 
    656 	nnodes = md_scan_dag(md, NULL, nname, aname, NULL);
    657 	if (nnodes == 0)
    658 		return (MDE_INVAL_ELEM_COOKIE);
    659 
    660 	nodesp = kmem_alloc(sizeof (mde_cookie_t) * nnodes, KM_SLEEP);
    661 
    662 	(void) md_scan_dag(md, NULL, nname, aname, nodesp);
    663 
    664 	for (idx = 0; idx < nnodes; idx++) {
    665 
    666 		if (mdeg_node_spec_match(md, nodesp[idx], nspec)) {
    667 			mde_cookie_t res = nodesp[idx];
    668 
    669 			kmem_free(nodesp, sizeof (mde_cookie_t) * nnodes);
    670 			return (res);
    671 		}
    672 	}
    673 
    674 	kmem_free(nodesp, sizeof (mde_cookie_t) * nnodes);
    675 	return (MDE_INVAL_ELEM_COOKIE);
    676 }
    677 
    678 static boolean_t
    679 mdeg_node_spec_match(md_t *md, mde_cookie_t node, mdeg_node_spec_t *nspec)
    680 {
    681 	mdeg_prop_spec_t	*prop;
    682 
    683 	ASSERT(md && nspec);
    684 	ASSERT(node != MDE_INVAL_ELEM_COOKIE);
    685 
    686 	prop = nspec->specp;
    687 
    688 	while (prop->type != MDET_LIST_END) {
    689 
    690 		switch (prop->type) {
    691 		case MDET_PROP_VAL: {
    692 			uint64_t val;
    693 
    694 			if (md_get_prop_val(md, node, prop->namep, &val) != 0)
    695 				return (B_FALSE);
    696 
    697 			if (prop->ps_val != val)
    698 				return (B_FALSE);
    699 
    700 			break;
    701 		}
    702 		case MDET_PROP_STR: {
    703 			char	*str;
    704 
    705 			if (md_get_prop_str(md, node, prop->namep, &str) != 0)
    706 				return (B_FALSE);
    707 
    708 			if (strcmp(prop->ps_str, str) != 0)
    709 				return (B_FALSE);
    710 
    711 			break;
    712 		}
    713 
    714 		default:
    715 			return (B_FALSE);
    716 		}
    717 
    718 		prop++;
    719 	}
    720 
    721 	return (B_TRUE);
    722 }
    723 
    724 static void
    725 mdeg_get_diff_results(md_diff_cookie_t mdd, mdeg_result_t *res)
    726 {
    727 	/*
    728 	 * Cache added nodes.
    729 	 */
    730 	res->added.mdp = mdeg.md_curr;
    731 	res->added.nelem = md_diff_added(mdd, &(res->added.mdep));
    732 
    733 	if (res->added.nelem == -1) {
    734 		bzero(&(res->added), sizeof (mdeg_diff_t));
    735 	}
    736 
    737 	/*
    738 	 * Cache removed nodes.
    739 	 */
    740 	res->removed.mdp = mdeg.md_prev;
    741 	res->removed.nelem = md_diff_removed(mdd, &(res->removed.mdep));
    742 
    743 	if (res->removed.nelem == -1) {
    744 		bzero(&(res->removed), sizeof (mdeg_diff_t));
    745 	}
    746 
    747 	/*
    748 	 * Cache matching node pairs.
    749 	 */
    750 	res->match_curr.mdp = mdeg.md_curr;
    751 	res->match_prev.mdp = mdeg.md_prev;
    752 	res->match_curr.nelem = md_diff_matched(mdd, &(res->match_prev.mdep),
    753 	    &(res->match_curr.mdep));
    754 	res->match_prev.nelem = res->match_curr.nelem;
    755 
    756 	if (res->match_prev.nelem == -1) {
    757 		bzero(&(res->match_prev), sizeof (mdeg_diff_t));
    758 		bzero(&(res->match_curr), sizeof (mdeg_diff_t));
    759 	}
    760 }
    761 
    762 #ifdef DEBUG
    763 /*
    764  * Generate a string that represents the node specifier
    765  * structure. Clamp the string length if the specifier
    766  * structure contains too much information.
    767  *
    768  *	General form:
    769  *
    770  *		<nodename>:{<propname>=<propval>,...}
    771  *	e.g.
    772  *		vdevice:{name=vsw,reg=0x0}
    773  */
    774 static void
    775 mdeg_spec_str(mdeg_node_spec_t *spec, char *buf, int len)
    776 {
    777 	mdeg_prop_spec_t	*prop;
    778 	int			offset;
    779 	boolean_t		first = B_TRUE;
    780 	char			*end = buf + len;
    781 
    782 	offset = snprintf(buf, len, "%s:{", spec->namep);
    783 
    784 	buf += offset;
    785 	len -= offset;
    786 	if (len <= 0)
    787 		goto trunc;
    788 
    789 	prop = spec->specp;
    790 
    791 	while (prop->type != MDET_LIST_END) {
    792 
    793 		switch (prop->type) {
    794 		case MDET_PROP_VAL:
    795 			offset = snprintf(buf, len, "%s%s=0x%lx",
    796 			    (first) ? "" : ",", prop->namep, prop->ps_val);
    797 			buf += offset;
    798 			len -= offset;
    799 			if (len <= 0)
    800 				goto trunc;
    801 			break;
    802 
    803 		case MDET_PROP_STR:
    804 			offset = snprintf(buf, len, "%s%s=%s",
    805 			    (first) ? "" : ",", prop->namep, prop->ps_str);
    806 			buf += offset;
    807 			len -= offset;
    808 			if (len <= 0)
    809 				goto trunc;
    810 			break;
    811 
    812 		default:
    813 			(void) snprintf(buf, len, "}");
    814 			return;
    815 		}
    816 
    817 		if (first)
    818 			first = B_FALSE;
    819 		prop++;
    820 	}
    821 
    822 	(void) snprintf(buf, len, "}");
    823 	return;
    824 
    825 trunc:
    826 	/* string too long, truncate it */
    827 	buf = end - (strlen(trunc_str) + 1);
    828 	(void) sprintf(buf, trunc_str);
    829 }
    830 
    831 /*
    832  * Generate a string that represents the match structure.
    833  * Clamp the string length if the match structure contains
    834  * too much information.
    835  *
    836  *	General form:
    837  *
    838  *		<nodename>:{<propname>,...}
    839  *	e.g.
    840  *		nmatch=vport:{reg}
    841  */
    842 static void
    843 mdeg_match_str(mdeg_node_match_t *match, char *buf, int len)
    844 {
    845 	md_prop_match_t	*prop;
    846 	int		offset;
    847 	boolean_t	first = B_TRUE;
    848 	char		*end = buf + len;
    849 
    850 	offset = snprintf(buf, len, "%s:{", match->namep);
    851 
    852 	buf += offset;
    853 	len -= offset;
    854 	if (len <= 0)
    855 		goto trunc;
    856 
    857 	prop = match->matchp;
    858 
    859 	while (prop->type != MDET_LIST_END) {
    860 		offset = snprintf(buf, len, "%s%s", (first) ? "" : ",",
    861 		    prop->namep);
    862 		buf += offset;
    863 		len -= offset;
    864 		if (len <= 0)
    865 			goto trunc;
    866 
    867 		if (first)
    868 			first = B_FALSE;
    869 		prop++;
    870 	}
    871 
    872 	(void) snprintf(buf, len, "}");
    873 	return;
    874 
    875 trunc:
    876 	/* string too long, truncate it */
    877 	buf = end - (strlen(trunc_str) + 1);
    878 	(void) sprintf(buf, trunc_str);
    879 }
    880 
    881 #define	MAX_FIELD_STR	80
    882 
    883 static void
    884 mdeg_dump_clnt(mdeg_clnt_t *clnt)
    885 {
    886 	char	str[MAX_FIELD_STR] = "";
    887 
    888 	if (!clnt->valid) {
    889 		MDEG_DBG("  valid=B_FALSE\n");
    890 		return;
    891 	}
    892 
    893 	if (clnt->pspec) {
    894 		mdeg_spec_str(clnt->pspec, str, MAX_FIELD_STR);
    895 		MDEG_DBG("  pspecp=%s\n", str);
    896 	}
    897 
    898 	if (clnt->nmatch) {
    899 		mdeg_match_str(clnt->nmatch, str, MAX_FIELD_STR);
    900 		MDEG_DBG("  nmatch=%s\n", str);
    901 	}
    902 }
    903 
    904 static void
    905 mdeg_dump_table(void)
    906 {
    907 	int		idx;
    908 	mdeg_clnt_t	*clnt;
    909 
    910 	for (idx = 0; idx < mdeg.maxclnts; idx++) {
    911 		clnt = &(mdeg.tbl[idx]);
    912 
    913 		MDEG_DBG("client %d (0x%lx):\n", idx, clnt->hdl);
    914 		mdeg_dump_clnt(clnt);
    915 	}
    916 }
    917 #endif /* DEBUG */
    918