/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #ifdef _KERNEL #include #else /* _KERNEL */ #include #include #endif /* _KERNEL */ #include #include #include #define MDD_FREE_CHECK(mdp, ptr, sz) \ do { \ if (ptr) mdp->freep(ptr, sz); \ _NOTE(CONSTCOND) } while (0) #define MD_DIFF_MAGIC 0x4D445F4449464621ull /* 'MD_DIFF!' */ #define MD_DIFF_NOMATCH (-1) #define MD_DIFF_MATCH (1) typedef struct { mde_cookie_t *mdep; uint_t nelem; } md_diff_t; typedef struct { uint64_t mdd_magic; md_diff_t added; md_diff_t removed; md_diff_t match1; md_diff_t match2; void *(*allocp)(size_t); void (*freep)(void *, size_t); } md_diff_impl_t; /* * Internal utility functions */ static int mdd_scan_for_nodes(md_t *mdp, mde_cookie_t start, char *compnodep, int *countp, mde_cookie_t **nodespp); static boolean_t mdd_any_dup_nodes(md_impl_t *mdp, md_prop_match_t *pmp, int count, mde_cookie_t *nodesp); static int mdd_node_list_match(md_impl_t *md1, md_impl_t *md2, md_element_t *match_nodep, mde_cookie_t *match_listp, uint8_t *match_seenp, int start, int end, md_prop_match_t *match_elemsp); static int mdd_node_compare(md_impl_t *mdap, md_impl_t *mdbp, md_element_t *nodeap, md_element_t *nodebp, md_prop_match_t *match_elemsp); /* * Given two DAGs and information about how to uniquely identify * the nodes of interest, determine which nodes have been added * to the second MD, removed from the first MD, or exist in both * MDs. This information is recorded and can be accessed using the * opaque cookie returned to the caller. */ md_diff_cookie_t md_diff_init(md_t *md1p, mde_cookie_t start1, md_t *md2p, mde_cookie_t start2, char *compnodep, md_prop_match_t *match_fieldsp) { int idx; md_impl_t *md1 = (md_impl_t *)md1p; md_impl_t *md2 = (md_impl_t *)md2p; mde_cookie_t *md1nodesp = NULL; mde_cookie_t *md2nodesp = NULL; int md1count = 0; int md2count = 0; uint8_t *seenp = NULL; /* variables used to gather results */ md_diff_impl_t *diff_res; mde_cookie_t *mde_add_scr; mde_cookie_t *mde_rem_scr; mde_cookie_t *mde_match1_scr; mde_cookie_t *mde_match2_scr; int nadd = 0; int nrem = 0; int nmatch = 0; /* sanity check params */ if ((md1p == NULL) || (md2p == NULL)) return (MD_INVAL_DIFF_COOKIE); if ((start1 == MDE_INVAL_ELEM_COOKIE) || (start2 == MDE_INVAL_ELEM_COOKIE)) return (MD_INVAL_DIFF_COOKIE); if ((compnodep == NULL) || (match_fieldsp == NULL)) return (MD_INVAL_DIFF_COOKIE); /* * Prepare an array of the matching nodes from the first MD. */ if (mdd_scan_for_nodes(md1p, start1, compnodep, &md1count, &md1nodesp) == -1) return (MD_INVAL_DIFF_COOKIE); /* sanity check that all nodes are unique */ if (md1nodesp && mdd_any_dup_nodes(md1, match_fieldsp, md1count, md1nodesp)) { MDD_FREE_CHECK(md1, md1nodesp, sizeof (mde_cookie_t) * md1count); return (MD_INVAL_DIFF_COOKIE); } /* * Prepare an array of the matching nodes from the second MD. */ if (mdd_scan_for_nodes(md2p, start2, compnodep, &md2count, &md2nodesp) == -1) return (MD_INVAL_DIFF_COOKIE); /* sanity check that all nodes are unique */ if (md2nodesp && mdd_any_dup_nodes(md2, match_fieldsp, md2count, md2nodesp)) { MDD_FREE_CHECK(md1, md1nodesp, sizeof (mde_cookie_t) * md1count); MDD_FREE_CHECK(md2, md2nodesp, sizeof (mde_cookie_t) * md2count); return (MD_INVAL_DIFF_COOKIE); } /* setup our result structure */ diff_res = md1->allocp(sizeof (md_diff_impl_t)); bzero(diff_res, sizeof (md_diff_impl_t)); diff_res->allocp = md1->allocp; diff_res->freep = md1->freep; diff_res->mdd_magic = MD_DIFF_MAGIC; /* * Special cases for empty lists */ if ((md1count == 0) && (md2count != 0)) { /* all the nodes found were added */ diff_res->added.mdep = md2nodesp; diff_res->added.nelem = md2count; return ((mde_cookie_t)diff_res); } if ((md1count != 0) && (md2count == 0)) { /* all the nodes found were removed */ diff_res->removed.mdep = md1nodesp; diff_res->removed.nelem = md1count; return ((mde_cookie_t)diff_res); } if ((md1count == 0) && (md2count == 0)) /* no nodes found */ return ((mde_cookie_t)diff_res); /* * Both lists have some elements. Allocate some scratch * buffers to sort them into our three categories, added, * removed, and matched pairs. */ mde_add_scr = diff_res->allocp(sizeof (mde_cookie_t) * md2count); mde_rem_scr = diff_res->allocp(sizeof (mde_cookie_t) * md1count); mde_match1_scr = diff_res->allocp(sizeof (mde_cookie_t) * md1count); mde_match2_scr = diff_res->allocp(sizeof (mde_cookie_t) * md2count); /* array of seen flags only needed for md2 */ seenp = (uint8_t *)diff_res->allocp(sizeof (uint8_t) * md2count); bzero(seenp, sizeof (uint8_t) * md2count); /* * Make a pass through the md1 node array. Make note of * any nodes not in the md2 array, indicating that they * have been removed. Also keep track of the nodes that * are present in both arrays for the matched pair results. */ for (idx = 0; idx < md1count; idx++) { md_element_t *elem = &(md1->mdep[md1nodesp[idx]]); int match = mdd_node_list_match(md1, md2, elem, md2nodesp, seenp, 0, md2count - 1, match_fieldsp); if (match == MD_DIFF_NOMATCH) /* record deleted node */ mde_rem_scr[nrem++] = md1nodesp[idx]; else { /* record matched node pair */ mde_match1_scr[nmatch] = md1nodesp[idx]; mde_match2_scr[nmatch] = md2nodesp[match]; nmatch++; /* mark that this match has been recorded */ seenp[match] = 1; } } /* * Make a pass through the md2 array. Any nodes that have * not been marked as seen have been added. */ for (idx = 0; idx < md2count; idx++) { if (!seenp[idx]) /* record added node */ mde_add_scr[nadd++] = md2nodesp[idx]; } /* fill in the added node list */ if (nadd) { int addsz = sizeof (mde_cookie_t) * nadd; diff_res->added.mdep = (mde_cookie_t *)diff_res->allocp(addsz); bcopy(mde_add_scr, diff_res->added.mdep, addsz); diff_res->added.nelem = nadd; } /* fill in the removed node list */ if (nrem) { int remsz = sizeof (mde_cookie_t) * nrem; diff_res->removed.mdep = (mde_cookie_t *)diff_res->allocp(remsz); bcopy(mde_rem_scr, diff_res->removed.mdep, remsz); diff_res->removed.nelem = nrem; } /* fill in the matching node lists */ if (nmatch) { int matchsz = sizeof (mde_cookie_t) * nmatch; diff_res->match1.mdep = (mde_cookie_t *)diff_res->allocp(matchsz); diff_res->match2.mdep = (mde_cookie_t *)diff_res->allocp(matchsz); bcopy(mde_match1_scr, diff_res->match1.mdep, matchsz); bcopy(mde_match2_scr, diff_res->match2.mdep, matchsz); diff_res->match1.nelem = nmatch; diff_res->match2.nelem = nmatch; } /* clean up */ md1->freep(md1nodesp, sizeof (mde_cookie_t) * md1count); md2->freep(md2nodesp, sizeof (mde_cookie_t) * md2count); diff_res->freep(mde_add_scr, sizeof (mde_cookie_t) * md2count); diff_res->freep(mde_rem_scr, sizeof (mde_cookie_t) * md1count); diff_res->freep(mde_match1_scr, sizeof (mde_cookie_t) * md1count); diff_res->freep(mde_match2_scr, sizeof (mde_cookie_t) * md2count); diff_res->freep(seenp, sizeof (uint8_t) * md2count); return ((md_diff_cookie_t)diff_res); } /* * Returns an array of the nodes added to the second MD in a * previous md_diff_init() call. Returns the number of elements * in the returned array. If the value is zero, the pointer * passed back will be NULL. */ int md_diff_added(md_diff_cookie_t mdd, mde_cookie_t **mde_addedp) { md_diff_impl_t *mddp = (md_diff_impl_t *)mdd; if ((mddp == NULL) || (mddp->mdd_magic != MD_DIFF_MAGIC)) return (-1); *mde_addedp = mddp->added.mdep; return (mddp->added.nelem); } /* * Returns an array of the nodes removed from the first MD in a * previous md_diff_init() call. Returns the number of elements * in the returned array. If the value is zero, the pointer * passed back will be NULL. */ int md_diff_removed(md_diff_cookie_t mdd, mde_cookie_t **mde_removedp) { md_diff_impl_t *mddp = (md_diff_impl_t *)mdd; if ((mddp == NULL) || (mddp->mdd_magic != MD_DIFF_MAGIC)) return (-1); *mde_removedp = mddp->removed.mdep; return (mddp->removed.nelem); } /* * Returns a pair of parallel arrays that contain nodes that were * considered matching based on the match criteria passed in to * a previous md_diff_init() call. Returns the number of elements * in the arrays. If the value is zero, both pointers passed back * will be NULL. */ int md_diff_matched(md_diff_cookie_t mdd, mde_cookie_t **mde_match1p, mde_cookie_t **mde_match2p) { md_diff_impl_t *mddp = (md_diff_impl_t *)mdd; if ((mddp == NULL) || (mddp->mdd_magic != MD_DIFF_MAGIC)) return (-1); *mde_match1p = mddp->match1.mdep; *mde_match2p = mddp->match2.mdep; return (mddp->match1.nelem); } /* * Deallocate any storage used to store results of a previous * md_diff_init() call. Returns 0 on success and -1 on failure. */ int md_diff_fini(md_diff_cookie_t mdd) { md_diff_impl_t *mddp = (md_diff_impl_t *)mdd; if ((mddp == NULL) || (mddp->mdd_magic != MD_DIFF_MAGIC)) return (-1); mddp->mdd_magic = 0; MDD_FREE_CHECK(mddp, mddp->added.mdep, mddp->added.nelem * sizeof (mde_cookie_t)); MDD_FREE_CHECK(mddp, mddp->removed.mdep, mddp->removed.nelem * sizeof (mde_cookie_t)); MDD_FREE_CHECK(mddp, mddp->match1.mdep, mddp->match1.nelem * sizeof (mde_cookie_t)); MDD_FREE_CHECK(mddp, mddp->match2.mdep, mddp->match2.nelem * sizeof (mde_cookie_t)); mddp->freep(mddp, sizeof (md_diff_impl_t)); return (0); } /* * Walk the "fwd" DAG in an MD and return an array of nodes that are * of the specified type. The start param is used to start the walk * from an arbitrary location in the DAG. Returns an array of nodes * as well as a count of the number of nodes in the array. If the * count is zero, the node pointer will be passed back as NULL. * * Returns: 0 success; -1 failure */ static int mdd_scan_for_nodes(md_t *mdp, mde_cookie_t start, char *compnodep, int *countp, mde_cookie_t **nodespp) { mde_str_cookie_t cname; mde_str_cookie_t aname; md_impl_t *mdip = (md_impl_t *)mdp; if (mdip == NULL) return (-1); cname = md_find_name(mdp, compnodep); aname = md_find_name(mdp, "fwd"); /* get the number of nodes of interest in the DAG */ *countp = md_scan_dag(mdp, start, cname, aname, NULL); if (*countp == 0) { *nodespp = NULL; return (0); } /* allocate the storage */ *nodespp = mdip->allocp(sizeof (mde_cookie_t) * (*countp)); /* populate our array with the matching nodes */ (void) md_scan_dag(mdp, start, cname, aname, *nodespp); return (0); } /* * Walk an array of nodes and check if there are any duplicate * nodes. A duplicate is determined based on the specified match * criteria. Returns B_TRUE if there are any duplicates and B_FALSE * otherwise. */ static boolean_t mdd_any_dup_nodes(md_impl_t *mdp, md_prop_match_t *pmp, int count, mde_cookie_t *nodesp) { int idx; int match; md_element_t *elem; ASSERT(count > 0 || nodesp == NULL); for (idx = 0; idx < count; idx++) { elem = &(mdp->mdep[nodesp[idx]]); match = mdd_node_list_match(mdp, mdp, elem, nodesp, NULL, idx + 1, count - 1, pmp); if (match != MD_DIFF_NOMATCH) return (B_TRUE); } return (B_FALSE); } /* * Given a node and a array of nodes, compare the node to all elements * in the specified start-end range of the array. If the node matches * one of the nodes in the array, return the index of that node. Otherwise * return MD_DIFF_NOMATCH. * * The optional seen array parameter can be used to optimize repeated * calls to this function. If the seen array indicates that an element * has already been matched, the full comparison is not necessary. */ static int mdd_node_list_match(md_impl_t *md1, md_impl_t *md2, md_element_t *match_nodep, mde_cookie_t *match_listp, uint8_t *match_seenp, int start, int end, md_prop_match_t *match_elemsp) { int match; int idx; md_element_t *elem; for (idx = start; idx <= end; idx++) { if ((match_seenp != NULL) && (match_seenp[idx])) continue; elem = &(md2->mdep[match_listp[idx]]); match = mdd_node_compare(md1, md2, match_nodep, elem, match_elemsp); if (match == MD_DIFF_MATCH) return (idx); } return (MD_DIFF_NOMATCH); } /* * Given two nodes and a list of properties, compare the nodes. * A match is concluded if both nodes have all of the specified * properties and all the values of those properties are the * same. Returns MD_DIFF_NOMATCH if the nodes do not match and * MD_DIFF_MATCH otherwise. */ static int mdd_node_compare(md_impl_t *mdap, md_impl_t *mdbp, md_element_t *nodeap, md_element_t *nodebp, md_prop_match_t *match_elemsp) { md_element_t *ap; md_element_t *bp; boolean_t nodea_interest; boolean_t nodeb_interest; int idx; /* make sure we are starting at the beginning of the nodes */ if ((MDE_TAG(nodeap) != MDET_NODE) || (MDE_TAG(nodebp) != MDET_NODE)) return (MD_DIFF_NOMATCH); for (idx = 0; match_elemsp[idx].type != MDET_LIST_END; idx++) { int type; nodea_interest = B_FALSE; nodeb_interest = B_FALSE; type = match_elemsp[idx].type; /* * Check node A for the property of interest */ for (ap = nodeap; MDE_TAG(ap) != MDET_NODE_END; ap++) { char *elemname; if (MDE_TAG(ap) != type) continue; elemname = mdap->namep + MDE_NAME(ap); if (strcmp(elemname, match_elemsp[idx].namep) == 0) { /* found the property of interest */ nodea_interest = B_TRUE; break; } } /* node A is not of interest */ if (!nodea_interest) return (MD_DIFF_NOMATCH); /* * Check node B for the property of interest */ for (bp = nodebp; MDE_TAG(bp) != MDET_NODE_END; bp++) { char *elemname; if (MDE_TAG(bp) != type) continue; elemname = mdbp->namep + MDE_NAME(bp); if (strcmp(elemname, match_elemsp[idx].namep) == 0) { nodeb_interest = B_TRUE; break; } } /* node B is not of interest */ if (!nodeb_interest) return (MD_DIFF_NOMATCH); /* * Both nodes have the property of interest. The * nodes are not a match unless the value of that * property match */ switch (type) { case MDET_PROP_VAL: if (MDE_PROP_VALUE(ap) != MDE_PROP_VALUE(bp)) return (MD_DIFF_NOMATCH); break; case MDET_PROP_STR: { char *stra = (char *)(mdap->datap + MDE_PROP_DATA_OFFSET(ap)); char *strb = (char *)(mdbp->datap + MDE_PROP_DATA_OFFSET(bp)); if (strcmp(stra, strb) != 0) return (MD_DIFF_NOMATCH); break; } case MDET_PROP_DAT: { caddr_t dataa; caddr_t datab; if (MDE_PROP_DATA_LEN(ap) != MDE_PROP_DATA_LEN(bp)) return (MD_DIFF_NOMATCH); dataa = (caddr_t)(mdap->datap + MDE_PROP_DATA_OFFSET(ap)); datab = (caddr_t)(mdbp->datap + MDE_PROP_DATA_OFFSET(bp)); if (memcmp(dataa, datab, MDE_PROP_DATA_LEN(ap)) != 0) return (MD_DIFF_NOMATCH); break; } default: /* unsupported prop type */ return (MD_DIFF_NOMATCH); } } /* * All the specified properties exist in both * nodes and have the same value. The two nodes * match. */ return (MD_DIFF_MATCH); }