#include #include #include #include #include #include #include #include #include #include #include #include #include #include "s1kd_tools.h" #include "stylesheets.h" /* Program name and version information. */ #define PROG_NAME "s1kd-appcheck" #define VERSION "6.4.2" /* Message prefixes. */ #define ERR_PREFIX PROG_NAME ": ERROR: " #define WRN_PREFIX PROG_NAME ": WARNING: " #define INF_PREFIX PROG_NAME ": INFO: " #define SUC_PREFIX PROG_NAME ": SUCCESS: " #define FLD_PREFIX PROG_NAME ": FAILED: " /* Info messages. */ #define I_CHECK_PROD INF_PREFIX "Checking %s for product %s in %s...\n" #define I_CHECK_PROD_LINENO INF_PREFIX "Checking %s for product on line %ld of %s...\n" #define I_CHECK_ALL_START INF_PREFIX "Checking %s for:\n" #define I_CHECK_ALL_PROP INF_PREFIX " %s %s = %s\n" #define I_NESTEDCHECK INF_PREFIX "Checking nested applicability in %s...\n" #define I_PROPCHECK INF_PREFIX "Checking product attribute and condition definitions in %s...\n" #define I_NUM_PRODS INF_PREFIX "Checking %s for %d configurations...\n" /* Error messages. */ #define E_CHECK_FAIL_PROD ERR_PREFIX "%s is invalid for product %s (line %ld of %s)\n" #define E_CHECK_FAIL_PROD_LINENO ERR_PREFIX "%s is invalid for product on line %ld of %s\n" #define E_CHECK_FAIL_ALL_START ERR_PREFIX "%s is invalid when:\n" #define E_CHECK_FAIL_ALL_PROP ERR_PREFIX " %s %s = %s\n" #define E_BAD_LIST ERR_PREFIX "Could not read list: %s\n" #define E_NO_ACT ERR_PREFIX "%s uses computable applicability, but no ACT could be found.\n" #define E_NO_CCT ERR_PREFIX "%s uses conditions, but no CCT could be found.\n" #define E_BAD_OBJECT ERR_PREFIX "Could not read object: %s\n" #define E_PROPCHECK ERR_PREFIX "%s: %s %s is not defined (line %ld)\n" #define E_PROPCHECK_VAL ERR_PREFIX "%s: %s is not a defined value of %s %s (line %ld)\n" #define E_NESTEDCHECK ERR_PREFIX "%s: %s on line %ld is applicable when %s %s = %s, which is not a subset of the applicability of the parent %s on line %ld\n" #define E_NESTEDCHECK_WHOLE ERR_PREFIX "%s: %s on line %ld is applicable when %s %s = %s, which is not a subset of the applicability of the whole object.\n" #define E_NESTEDCHECK_REDUNDANT ERR_PREFIX "%s: %s on line %ld has the same applicability as its parent %s on line %ld (%s)\n" #define E_DUPLICATECHECK ERR_PREFIX "%s: Annotation on line %ld is a duplicate of annotation on line %ld.\n" #define E_MAX_OBJECTS ERR_PREFIX "Out of memory\n" #define E_POPEN ERR_PREFIX "Error executing %s: %s\n" /* Warning messages. */ #define W_MISSING_REF_DM WRN_PREFIX "Could not read referenced object: %s\n" /* Success messages. */ #define S_VALID SUC_PREFIX "%s passed the applicability check.\n" /* Failure messages. */ #define F_INVALID FLD_PREFIX "%s failed the applicability check.\n" /* Exit status codes. */ #define EXIT_BAD_OBJECT 2 #define EXIT_MAX_OBJECTS 3 #define EXIT_POPEN 4 /* Default commands used to filter and validate. */ #define DEFAULT_FILTER "s1kd-instance" #define DEFAULT_VALIDATE "s1kd-validate" #define DEFAULT_BREXCHECK "s1kd-brexcheck" /* Namespace for special elements/attributes. */ #define S1KD_APPCHECK_NS BAD_CAST "urn:s1kd-tools:s1kd-appcheck" /* Progress bar formats. */ #define PROGRESS_OFF 0 #define PROGRESS_CLI 1 #define PROGRESS_ZENITY 2 /* Initial maximum number of CSDB object paths. */ static int OBJECT_MAX = 1; /* List of CSDB object paths. */ static char (*objects)[PATH_MAX]; static int nobjects = 0; /* Search for ACT, CCT, PCT recursively. */ static bool recursive_search = false; /* Assume issue/inwork numbers are omitted. */ static bool no_issue = false; /* Directory to search for ACT, CCT, PCT in. */ static char *search_dir; /* The verbosity of output. */ static enum verbosity { QUIET, NORMAL, VERBOSE, DEBUG } verbosity = NORMAL; /* What type of check to perform. */ enum appcheckmode { CUSTOM, PCT, ALL, STANDALONE }; /* Which filenames to print. */ enum show_filenames { SHOW_NONE, SHOW_INVALID, SHOW_VALID }; /* Applicability properties to ignore when validating objects. */ xmlHashTablePtr ignored_properties = NULL; /* Applicability check options. */ struct appcheckopts { char *useract; char *usercct; char *userpct; char *filter; char *args; xmlNodePtr validators; bool output_tree; enum show_filenames filenames; bool brexcheck; bool add_deps; bool check_props; bool check_nested; bool check_redundant; bool check_duplicate; bool rem_delete; enum appcheckmode mode; }; /* Show usage message. */ static void show_help(void) { puts("Usage: " PROG_NAME " [options] [...]"); puts(""); puts("Options:"); puts(" -A, --act User-specified ACT."); puts(" -a, --all Validate against all property values."); puts(" -b, --brexcheck Validate against BREX."); puts(" -C, --cct User-specified CCT."); puts(" -c, --custom Perform a customized check."); puts(" -D, --duplicate Check for duplicate applicability annotations."); puts(" -d, --dir Search for ACT/CCT/PCT in ."); puts(" -e, --exec Commands used to validate objects."); puts(" -F, --valid-filenames List valid files."); puts(" -f, --filenames List invalid files."); puts(" -h, -?, --help Show help/usage message."); puts(" -i, --ignore Ignore an applicability property when validating."); puts(" -K, --filter Command used to create objects."); puts(" -k, --args Arguments used to create objects."); puts(" -l, --list Treat input as list of CSDB objects."); puts(" -N, --omit-issue Assume issue/inwork numbers are omitted."); puts(" -n, --nested Check nested applicability annotations."); puts(" -o, --output-valid Output valid CSDB objects to stdout."); puts(" -P, --pct User-specified PCT."); puts(" -p, --progress Display a progress bar."); puts(" -q, --quiet Quiet mode."); puts(" -R, --redundant Check for redundant applicability annotations."); puts(" -r, --recursive Search for ACT/CCT/PCT recursively."); puts(" -s, --strict Check that all properties are defined."); puts(" -T, --summary Print a summary of the check."); puts(" -t, --products Validate against product instances."); puts(" -v, --verbose Verbose output."); puts(" -x, --xml Output XML report."); puts(" -~, --dependencies Check CCT dependencies."); puts(" -^, --remove-deleted Validate with elements marked as \"delete\" removed."); puts(" --version Show version information."); puts(" --zenity-progress Print progress information in the zenity --progress format."); puts(" ... CSDB object(s) to check."); LIBXML2_PARSE_LONGOPT_HELP } /* Show version information. */ static void show_version(void) { printf("%s (s1kd-tools) %s\n", PROG_NAME, VERSION); printf("Using libxml %s and libxslt %s\n", xmlParserVersion, xsltEngineVersion); } /* Return the first node matching an XPath expression. */ static xmlNodePtr first_xpath_node(xmlDocPtr doc, xmlNodePtr node, const xmlChar *path) { xmlXPathContextPtr ctx; xmlXPathObjectPtr obj; xmlNodePtr first; if (doc) { ctx = xmlXPathNewContext(doc); } else { ctx = xmlXPathNewContext(node->doc); } ctx->node = node; obj = xmlXPathEvalExpression(BAD_CAST path, ctx); if (xmlXPathNodeSetIsEmpty(obj->nodesetval)) { first = NULL; } else { first = obj->nodesetval->nodeTab[0]; } xmlXPathFreeObject(obj); xmlXPathFreeContext(ctx); return first; } /* Return the value of the first node matching an XPath expression. */ static xmlChar *first_xpath_value(xmlDocPtr doc, xmlNodePtr node, const xmlChar *path) { return xmlNodeGetContent(first_xpath_node(doc, node, path)); } /* Find a data module filename in the current directory based on the dmRefIdent * element. */ static bool find_dmod_fname(char *dst, xmlNodePtr dmRefIdent) { char *model_ident_code; char *system_diff_code; char *system_code; char *sub_system_code; char *sub_sub_system_code; char *assy_code; char *disassy_code; char *disassy_code_variant; char *info_code; char *info_code_variant; char *item_location_code; char *learn_code; char *learn_event_code; char code[64]; xmlNodePtr dmCode, issueInfo, language; dmCode = first_xpath_node(NULL, dmRefIdent, BAD_CAST "dmCode|avee"); issueInfo = first_xpath_node(NULL, dmRefIdent, BAD_CAST "issueInfo|issno"); language = first_xpath_node(NULL, dmRefIdent, BAD_CAST "language"); model_ident_code = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "modelic|@modelIdentCode"); system_diff_code = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "sdc|@systemDiffCode"); system_code = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "chapnum|@systemCode"); sub_system_code = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "section|@subSystemCode"); sub_sub_system_code = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "subsect|@subSubSystemCode"); assy_code = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "subject|@assyCode"); disassy_code = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "discode|@disassyCode"); disassy_code_variant = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "discodev|@disassyCodeVariant"); info_code = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "incode|@infoCode"); info_code_variant = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "incodev|@infoCodeVariant"); item_location_code = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "itemloc|@itemLocationCode"); learn_code = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "@learnCode"); learn_event_code = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "@learnEventCode"); snprintf(code, 64, "DMC-%s-%s-%s-%s%s-%s-%s%s-%s%s-%s", model_ident_code, system_diff_code, system_code, sub_system_code, sub_sub_system_code, assy_code, disassy_code, disassy_code_variant, info_code, info_code_variant, item_location_code); xmlFree(model_ident_code); xmlFree(system_diff_code); xmlFree(system_code); xmlFree(sub_system_code); xmlFree(sub_sub_system_code); xmlFree(assy_code); xmlFree(disassy_code); xmlFree(disassy_code_variant); xmlFree(info_code); xmlFree(info_code_variant); xmlFree(item_location_code); if (learn_code) { char learn[8]; snprintf(learn, 8, "-%s%s", learn_code, learn_event_code); strcat(code, learn); } xmlFree(learn_code); xmlFree(learn_event_code); if (!no_issue) { if (issueInfo) { char *issue_number; char *in_work; char iss[8]; issue_number = (char *) first_xpath_value(NULL, issueInfo, BAD_CAST "@issno|@issueNumber"); in_work = (char *) first_xpath_value(NULL, issueInfo, BAD_CAST "@inwork|@inWork"); snprintf(iss, 8, "_%s-%s", issue_number, in_work ? in_work : "00"); strcat(code, iss); xmlFree(issue_number); xmlFree(in_work); } else if (language) { strcat(code, "_\?\?\?-\?\?"); } } if (language) { char *language_iso_code; char *country_iso_code; char lang[8]; language_iso_code = (char *) first_xpath_value(NULL, language, BAD_CAST "@language|@languageIsoCode"); country_iso_code = (char *) first_xpath_value(NULL, language, BAD_CAST "@country|@countryIsoCode"); snprintf(lang, 8, "_%s-%s", language_iso_code, country_iso_code); strcat(code, lang); xmlFree(language_iso_code); xmlFree(country_iso_code); } /* Look for DM in the directory hierarchy. */ if (find_csdb_object(dst, search_dir, code, is_dm, recursive_search)) { return true; } /* Look for DM in the list of objects to check. */ if (find_csdb_object_in_list(dst, objects, nobjects, code)) { return true; } fprintf(stderr, W_MISSING_REF_DM, code); return false; } /* Find the filename of a referenced ACT data module. */ static bool find_act_fname(char *dst, const char *useract, xmlDocPtr doc) { if (useract) { strcpy(dst, useract); return true; } else if (doc) { xmlNodePtr actref; actref = first_xpath_node(doc, NULL, BAD_CAST "//applicCrossRefTableRef/dmRef/dmRefIdent|//actref/refdm"); return actref && find_dmod_fname(dst, actref); } return false; } /* Find the filename of a referenced CCT data module. */ static bool find_cct_fname(char *dst, const char *usercct, xmlDocPtr act) { if (usercct) { strcpy(dst, usercct); return true; } else if (act) { xmlNodePtr cctref; cctref = first_xpath_node(act, NULL, BAD_CAST "//condCrossRefTableRef/dmRef/dmRefIdent|//cctref/refdm"); return cctref && find_dmod_fname(dst, cctref); } return false; } /* Find the filename of a referenced PCT data module via the ACT. */ static bool find_pct_fname(char *dst, const char *userpct, xmlDocPtr act) { if (userpct) { strcpy(dst, userpct); return true; } else if (act) { xmlNodePtr pctref; pctref = first_xpath_node(act, NULL, BAD_CAST "//productCrossRefTableRef/dmRef/dmRefIdent|//pctref/refdm"); return pctref && find_dmod_fname(dst, pctref); } return false; } /* Add a nested applic error to the report. */ static xmlNodePtr add_nested_error(xmlNodePtr report, xmlNodePtr node, xmlNodePtr parent, const xmlChar *id, const xmlChar *type, const xmlChar *val, const char *path) { xmlNodePtr und; long int cline, pline; xmlChar line_s[16], *xpath; cline = xmlGetLineNo(node); pline = xmlGetLineNo(parent); if (verbosity >= NORMAL) { if (parent) { fprintf(stderr, E_NESTEDCHECK, path, (char *) node->name, cline, (char *) type, (char *) id, (char *) val, (char *) parent->name, pline); } else { fprintf(stderr, E_NESTEDCHECK_WHOLE, path, (char *) node->name, cline, (char *) type, (char *) id, (char *) val); } } und = xmlNewChild(report, NULL, BAD_CAST "nestedApplicError", NULL); xmlSetProp(und, BAD_CAST "applicPropertyIdent", id); xmlSetProp(und, BAD_CAST "applicPropertyType", type); xmlSetProp(und, BAD_CAST "applicPropertyValue", val); xmlStrPrintf(line_s, 16, "%ld", cline); xmlSetProp(und, BAD_CAST "line", line_s); xpath = xpath_of(node); xmlSetProp(und, BAD_CAST "xpath", xpath); xmlFree(xpath); if (parent) { xmlStrPrintf(line_s, 16, "%ld", pline); xmlSetProp(und, BAD_CAST "parentLine", line_s); xpath = xpath_of(parent); xmlSetProp(und, BAD_CAST "parentXpath", xpath); xmlFree(xpath); } return und; } /* Add a redundant applicability error to the report. */ static xmlNodePtr add_redundant_error(xmlNodePtr report, xmlNodePtr node, xmlNodePtr parent, const xmlChar *id, const char *path) { xmlNodePtr und; long int cline, pline; xmlChar line_s[16], *xpath; cline = xmlGetLineNo(node); pline = xmlGetLineNo(parent); if (verbosity >= NORMAL) { fprintf(stderr, E_NESTEDCHECK_REDUNDANT, path, (char *) node->name, cline, (char *) parent->name, pline, (char *) id); } und = xmlNewChild(report, NULL, BAD_CAST "redundantApplicError", NULL); xmlStrPrintf(line_s, 16, "%ld", cline); xmlSetProp(und, BAD_CAST "line", line_s); xpath = xpath_of(node); xmlSetProp(und, BAD_CAST "xpath", xpath); xmlFree(xpath); if (parent) { xmlStrPrintf(line_s, 16, "%ld", pline); xmlSetProp(und, BAD_CAST "parentLine", line_s); xpath = xpath_of(parent); xmlSetProp(und, BAD_CAST "parentXpath", xpath); xmlFree(xpath); } return und; } /* Check that an assertion in a nested applicability annotation is a subset of * its parent. */ static int check_nested_applic_assert(xmlNodePtr node, xmlNodePtr parent, xmlNodePtr assert, xmlNodePtr parent_app, const char *path, xmlNodePtr report) { xmlNodePtr defs, defs_assert; int err; xmlChar *id = xpath_first_value(NULL, assert, BAD_CAST "@applicPropertyIdent|@actidref"); xmlChar *type = xpath_first_value(NULL, assert, BAD_CAST "@applicPropertyType|@actreftype"); xmlChar *vals = xpath_first_value(NULL, assert, BAD_CAST "@applicPropertyValues|@actvalues"); xmlNodePtr parent_app_node; defs = xmlNewNode(NULL, BAD_CAST "applic"); defs_assert = xmlNewChild(defs, NULL, BAD_CAST "assert", NULL); xmlSetProp(defs_assert, BAD_CAST "applicPropertyIdent", id); xmlSetProp(defs_assert, BAD_CAST "applicPropertyType", type); xmlSetProp(defs_assert, BAD_CAST "applicPropertyValues", vals); parent_app_node = xpath_first_node(parent_app->doc, parent_app, BAD_CAST "assert|evaluate"); err = parent_app_node && !eval_applic(defs, xpath_first_node(parent_app->doc, parent_app, BAD_CAST "assert|evaluate"), true); xmlFreeNode(defs); if (err) { add_nested_error(report, node, parent, id, type, vals, path); } xmlFree(id); xmlFree(type); xmlFree(vals); return err; } /* Check that an applicability annotation is a subset of a given parent annotation. */ static int check_nested_applic_props(xmlDocPtr doc, const char *path, xmlNodePtr node, xmlNodePtr parent, xmlNodePtr app, xmlNodePtr parent_app, xmlNodePtr report) { xmlXPathContextPtr ctx; xmlXPathObjectPtr obj; int err = 0; ctx = xmlXPathNewContext(doc); ctx->node = app; obj = xmlXPathEvalExpression(BAD_CAST ".//assert", ctx); if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) { int i; for (i = 0; i < obj->nodesetval->nodeNr; ++i) { err += check_nested_applic_assert(node, parent, obj->nodesetval->nodeTab[i], parent_app, path, report); } } xmlXPathFreeObject(obj); xmlXPathFreeContext(ctx); return err; } /* Check for redundant applicability. * * FIXME: This is a very rudimentary implementation that only checks if the * EXACT annotation is repeated on child elements. In the future, this * should also check if logic between parent and child annotations is * redundant. */ static int check_redundant_applic(const char *path, xmlNodePtr node, const xmlChar *id, xmlNodePtr parent, const xmlChar *parent_id, xmlNodePtr report) { if (xmlStrcmp(id, parent_id) == 0) { add_redundant_error(report, node, parent, id, path); return 1; } return 0; } /* Check that an applicability annotation is a subset of any parent annotation. */ static int check_nested_applic(xmlDocPtr doc, xmlNodePtr node, const char *path, struct appcheckopts *opts, xmlNodePtr report) { xmlNodePtr app, parent_app, parent; xmlChar *id, *xpath; int n, err = 0; /* Get annotation of the current node. */ id = first_xpath_value(doc, node, BAD_CAST "@applicRefId|@refapplic"); n = xmlStrlen(id) + 17; xpath = malloc(n * sizeof(xmlChar)); xmlStrPrintf(xpath, n, "//applic[@id='%s']", id); app = first_xpath_node(doc, NULL, xpath); xmlFree(xpath); /* Check against the applicability of each parent. */ parent = node->parent; while (parent && parent->type == XML_ELEMENT_NODE) { xmlChar *parent_id; parent_id = first_xpath_value(doc, parent, BAD_CAST "@applicRefId|@refapplic"); if (parent_id != NULL) { /* Get annotation of the parent node. */ n = xmlStrlen(parent_id) + 100; xpath = malloc(n * sizeof(xmlChar)); xmlStrPrintf(xpath, n, "//applic[@id='%s']", parent_id); parent_app = first_xpath_node(doc, NULL, xpath); xmlFree(xpath); /* Check for incompatible annotations. */ if (opts->check_nested && check_nested_applic_props(doc, path, node, parent, app, parent_app, report) != 0) { err = 1; } /* Check for redundant annotations. */ if (opts->check_redundant && check_redundant_applic(path, node, id, parent, parent_id, report) != 0) { err = 1; } } xmlFree(parent_id); parent = parent->parent; } xmlFree(id); /* Check against the whole object applicability. */ if ((parent_app = first_xpath_node(doc, node, BAD_CAST "//applic"))) { /* Check for incompatible annotations. */ if (opts->check_nested && check_nested_applic_props(doc, path, node, NULL, app, parent_app, report) != 0) { err = 1; } } return err != 0; } /* Check that all applicability annotations are subsets of their parent annotations. */ static int check_nested_applics(xmlDocPtr doc, const char *path, struct appcheckopts *opts, xmlNodePtr report) { xmlXPathContextPtr ctx; xmlXPathObjectPtr obj; int err = 0; if (verbosity >= DEBUG) { fprintf(stderr, I_NESTEDCHECK, path); } ctx = xmlXPathNewContext(doc); obj = xmlXPathEvalExpression(BAD_CAST "//*[@applicRefId or @refapplic]", ctx); if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) { int i; for (i = 0; i < obj->nodesetval->nodeNr; ++i) { err += check_nested_applic(doc, obj->nodesetval->nodeTab[i], path, opts, report); } } xmlXPathFreeObject(obj); xmlXPathFreeContext(ctx); return err; } /* Add an undefined property node to the report. */ static xmlNodePtr add_undef_node(xmlNodePtr report, xmlNodePtr assert, const xmlChar *id, const xmlChar *type, const xmlChar *val, long int line) { xmlNodePtr und; xmlChar line_s[16], *xpath; und = xmlNewChild(report, NULL, BAD_CAST "undefined", NULL); xmlSetProp(und, BAD_CAST "applicPropertyIdent", id); xmlSetProp(und, BAD_CAST "applicPropertyType", type); if (val) { xmlSetProp(und, BAD_CAST "applicPropertyValue", val); } xmlStrPrintf(line_s, 16, "%ld", line); xmlSetProp(und, BAD_CAST "line", line_s); xpath = xpath_of(assert); xmlSetProp(und, BAD_CAST "xpath", xpath); xmlFree(xpath); return und; } /* Check whether a property value is defined in the ACT/CCT. */ static int check_val_against_prop(xmlNodePtr assert, const xmlChar *id, const xmlChar *type, const xmlChar *val, xmlNodePtr prop, const char *path, xmlNodePtr report) { xmlXPathContextPtr ctx; xmlXPathObjectPtr obj; bool match; xmlChar *pattern; pattern = first_xpath_value(NULL, prop, BAD_CAST "@valuePattern|@pattern"); if (pattern) { match = match_pattern(val, pattern); xmlFree(pattern); } else { match = false; ctx = xmlXPathNewContext(prop->doc); ctx->node = prop; obj = xmlXPathEvalExpression(BAD_CAST "enumeration|enum", ctx); if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) { int i; for (i = 0; !match && i < obj->nodesetval->nodeNr; ++i) { xmlChar *vals, *v = NULL; char *end = NULL; vals = first_xpath_value(NULL, obj->nodesetval->nodeTab[i], BAD_CAST "@applicPropertyValues|@actvalues"); while ((v = BAD_CAST strtok_r(v ? NULL : (char *) vals, "|", &end))) { if (is_in_range((char *) val, (char *) v)) { match = true; break; } } xmlFree(vals); } } xmlXPathFreeObject(obj); xmlXPathFreeContext(ctx); } if (match) { return 0; } else { long int line; line = xmlGetLineNo(assert); if (verbosity >= NORMAL) { fprintf(stderr, E_PROPCHECK_VAL, path, (char *) val, (char *) type, (char *) id, line); } add_undef_node(report, assert, id, type, val, line); } return 1; } /* Check whether a property is defined in the ACT/CCT. */ static int check_prop_against_ct(xmlNodePtr assert, xmlDocPtr act, xmlDocPtr cct, const char *path, xmlNodePtr report) { xmlChar *id, *type, *vals, *xpath = NULL; int n, err = 0; xmlNodePtr prop; if (!(id = first_xpath_value(NULL, assert, BAD_CAST "@applicPropertyIdent|@actidref"))) { return 0; } if (!(type = first_xpath_value(NULL, assert, BAD_CAST "@applicPropertyType|@actreftype"))) { return 0; } if (!(vals = first_xpath_value(NULL, assert, BAD_CAST "@applicPropertyValues|@applicPropertyValue|@actvalues|@actvalue"))) { return 0; } if (xmlStrcmp(type, BAD_CAST "condition") == 0 && cct) { /* For conditions, first get the condition itself. */ n = xmlStrlen(id) + 29; xpath = malloc(n * sizeof(xmlChar)); xmlStrPrintf(xpath, n, "(//cond|//condition)[@id='%s']", id); prop = first_xpath_node(cct, NULL, xpath); /* Then get the condition type. */ if (prop) { xmlChar *condtype; condtype = first_xpath_value(cct, prop, BAD_CAST "@condTypeRefId|@condtyperef"); if (condtype) { xmlFree(xpath); n = xmlStrlen(condtype) + 37; xpath = malloc(n * sizeof(xmlChar)); xmlStrPrintf(xpath, n, "(//condType|//conditiontype)[@id='%s']", condtype); prop = first_xpath_node(cct, NULL, xpath); } xmlFree(condtype); } } else if (xmlStrcmp(type, BAD_CAST "prodattr") == 0 && act) { n = xmlStrlen(id) + 40; xpath = malloc(n * sizeof(xmlChar)); xmlStrPrintf(xpath, n, "(//productAttribute|//prodattr)[@id='%s']", id); prop = first_xpath_node(act, NULL, xpath); } else { prop = NULL; } xmlFree(xpath); if (prop) { xmlChar *v = NULL; char *end = NULL; while ((v = BAD_CAST strtok_r(v ? NULL : (char *) vals, "|~", &end))) { err += check_val_against_prop(assert, id, type, v, prop, path, report); } } else { long int line; line = xmlGetLineNo(assert); if (verbosity >= NORMAL) { fprintf(stderr, E_PROPCHECK, path, (char *) type, (char *) id, line); } add_undef_node(report, assert, id, type, NULL, line); ++err; } xmlFree(id); xmlFree(type); xmlFree(vals); return err; } /* Check whether all properties in an object are defined in the ACT/CCT. */ static int check_props_against_cts(xmlDocPtr doc, const char *path, xmlDocPtr act, xmlDocPtr cct, xmlNodePtr report) { xmlXPathContextPtr ctx; xmlXPathObjectPtr obj; int err = 0; if (verbosity >= DEBUG) { fprintf(stderr, I_PROPCHECK, path); } ctx = xmlXPathNewContext(doc); obj = xmlXPathEvalExpression(BAD_CAST "//assert", ctx); if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) { int i; for (i = 0; i < obj->nodesetval->nodeNr; ++i) { err += check_prop_against_ct(obj->nodesetval->nodeTab[i], act, cct, path, report); } } xmlXPathFreeObject(obj); xmlXPathFreeContext(ctx); return err; } /* Add an assignment to a set of assertions. */ static void add_assign(xmlNodePtr asserts, xmlNodePtr assert) { xmlChar *i, *t, *v; xmlNodePtr new; i = first_xpath_value(NULL, assert, BAD_CAST "@applicPropertyIdent|@actidref"); t = first_xpath_value(NULL, assert, BAD_CAST "@applicPropertyType|@actreftype"); v = first_xpath_value(NULL, assert, BAD_CAST "@applicPropertyValue|@actvalue"); new = xmlNewNode(NULL, BAD_CAST "assign"); xmlSetProp(new, BAD_CAST "applicPropertyIdent", i); xmlSetProp(new, BAD_CAST "applicPropertyType", t); xmlSetProp(new, BAD_CAST "applicPropertyValue", v); xmlAddChild(asserts, new); xmlFree(i); xmlFree(t); xmlFree(v); } /* Extract the assignments in a PCT instance. */ static void extract_assigns(xmlNodePtr asserts, xmlNodePtr product) { xmlXPathContextPtr ctx; xmlXPathObjectPtr obj; ctx = xmlXPathNewContext(product->doc); ctx->node = product; obj = xmlXPathEvalExpression(BAD_CAST ".//assign", ctx); if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) { int i; for (i = 0; i < obj->nodesetval->nodeNr; ++i) { add_assign(asserts, obj->nodesetval->nodeTab[i]); } } xmlXPathFreeObject(obj); xmlXPathFreeContext(ctx); } /* Check if an object is valid for a set of assertions. */ static int check_assigns(xmlDocPtr doc, const char *path, xmlNodePtr asserts, xmlNodePtr product, const xmlChar *id, const char *pctfname, struct appcheckopts *opts) { xmlNodePtr cur; int err = 0, e = 0; char filter_cmd[1024] = ""; char cmd[4096]; FILE *p; if (verbosity >= DEBUG) { if (opts->mode >= ALL) { fprintf(stderr, I_CHECK_ALL_START, path); } else if (id) { fprintf(stderr, I_CHECK_PROD, path, id, pctfname); } else { fprintf(stderr, I_CHECK_PROD_LINENO, path, xmlGetLineNo(product), pctfname); } } if (opts->filter) { strncpy(filter_cmd, opts->filter, 1023); } else { strncpy(filter_cmd, DEFAULT_FILTER, 1023); } if (opts->args) { strcat(filter_cmd, " "); strncat(filter_cmd, opts->args, 1023 - strlen(filter_cmd)); } else { strcat(filter_cmd, " -w"); } for (cur = asserts->children; cur; cur = cur->next) { char *i, *t, *v; char *c; i = (char *) first_xpath_value(NULL, cur, BAD_CAST "@applicPropertyIdent"); t = (char *) first_xpath_value(NULL, cur, BAD_CAST "@applicPropertyType"); v = (char *) first_xpath_value(NULL, cur, BAD_CAST "@applicPropertyValue"); if (opts->mode >= ALL && verbosity >= DEBUG) { fprintf(stderr, I_CHECK_ALL_PROP, t, i, v); } c = malloc(strlen(i) + strlen(t) + strlen(v) + 9); sprintf(c, " -s \"%s:%s=%s\"", i, t, v); strcat(filter_cmd, c); free(c); xmlFree(i); xmlFree(t); xmlFree(v); } /* Custom validators. */ if (opts->validators) { for (cur = opts->validators->children; cur; cur = cur->next) { xmlChar *c; strcpy(cmd, filter_cmd); strcat(cmd, "|"); c = xmlNodeGetContent(cur); strncat(cmd, (char *) c, 4095 - strlen(cmd)); p = popen(cmd, "w"); if (p == NULL) { fprintf(stderr, E_POPEN, cmd, strerror(errno)); exit(EXIT_POPEN); } xmlDocDump(p, doc); e += pclose(p); xmlFree(c); } /* Default validators. */ } else { strcpy(cmd, filter_cmd); /* Schema validation */ strcat(cmd, "|" DEFAULT_VALIDATE " -e"); switch (verbosity) { case QUIET: case NORMAL: strcat(cmd, " -q"); break; case VERBOSE: break; case DEBUG: strcat(cmd, " -v"); break; } p = popen(cmd, "w"); if (p == NULL) { fprintf(stderr, E_POPEN, cmd, strerror(errno)); exit(EXIT_POPEN); } xmlDocDump(p, doc); e += pclose(p); /* BREX validation */ if (opts->brexcheck) { strcpy(cmd, filter_cmd); strcat(cmd, "|" DEFAULT_BREXCHECK " -cel"); strcat(cmd, " -d '"); strcat(cmd, search_dir); strcat(cmd, "'"); if (recursive_search) { strcat(cmd, " -r"); } switch (verbosity) { case QUIET: case NORMAL: strcat(cmd, " -q"); break; case VERBOSE: break; case DEBUG: strcat(cmd, " -v"); break; } p = popen(cmd, "w"); if (p == NULL) { fprintf(stderr, E_POPEN, cmd, strerror(errno)); exit(EXIT_POPEN); } xmlDocDump(p, doc); e += pclose(p); } } if (e) { if (verbosity >= NORMAL) { if (opts->mode >= ALL) { fprintf(stderr, E_CHECK_FAIL_ALL_START, path); for (cur = asserts->children; cur; cur = cur->next) { char *i, *t, *v; i = (char *) first_xpath_value(NULL, cur, BAD_CAST "@applicPropertyIdent"); t = (char *) first_xpath_value(NULL, cur, BAD_CAST "@applicPropertyType"); v = (char *) first_xpath_value(NULL, cur, BAD_CAST "@applicPropertyValue"); fprintf(stderr, E_CHECK_FAIL_ALL_PROP, t, i, v); xmlFree(i); xmlFree(t); xmlFree(v); } } else if (id) { fprintf(stderr, E_CHECK_FAIL_PROD, path, id, xmlGetLineNo(product), pctfname); } else { fprintf(stderr, E_CHECK_FAIL_PROD_LINENO, path, xmlGetLineNo(product), pctfname); } } xmlSetProp(asserts, BAD_CAST "valid", BAD_CAST "no"); ++err; } else { xmlSetProp(asserts, BAD_CAST "valid", BAD_CAST "yes"); } return err ? 1 : 0; } /* Extract assertions from ACT/CCT enumerations. */ static void extract_enumvals(xmlNodePtr asserts, xmlNodePtr prop, const xmlChar *id, bool cct) { xmlXPathContextPtr ctx; xmlXPathObjectPtr obj; ctx = xmlXPathNewContext(prop->doc); ctx->node = prop; obj = xmlXPathEvalExpression(BAD_CAST ".//enumeration/@applicPropertyValues|.//enum/@actvalues", ctx); if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) { int i; for (i = 0; i < obj->nodesetval->nodeNr; ++i) { xmlChar *v; xmlChar *c = NULL; v = xmlNodeGetContent(obj->nodesetval->nodeTab[i]); while ((c = BAD_CAST strtok(c ? NULL : (char *) v, "|~"))) { xmlNodePtr assert; assert = xmlNewNode(NULL, BAD_CAST "assign"); xmlSetProp(assert, BAD_CAST "applicPropertyIdent", id); xmlSetProp(assert, BAD_CAST "applicPropertyType", BAD_CAST (cct ? "condition" : "prodattr")); xmlSetProp(assert, BAD_CAST "applicPropertyValue", c); xmlAddChild(asserts, assert); } xmlFree(v); } } xmlXPathFreeObject(obj); xmlXPathFreeContext(ctx); } /* General XSLT transformation with embedded stylesheet, preserving the DTD. */ static void transform_doc(xmlDocPtr doc, unsigned char *xml, unsigned int len, const char **params) { xmlDocPtr styledoc, res, src; xsltStylesheetPtr style; xmlNodePtr old; styledoc = read_xml_mem((const char *) xml, len); style = xsltParseStylesheetDoc(styledoc); src = xmlCopyDoc(doc, 1); res = xsltApplyStylesheet(style, src, params); xmlFreeDoc(src); old = xmlDocSetRootElement(doc, xmlCopyNode(xmlDocGetRootElement(res), 1)); xmlFreeNode(old); xmlFreeDoc(res); xsltFreeStylesheet(style); } /* Add a node containing the path of an object to the report. */ static xmlNodePtr add_object_node(xmlNodePtr parent, const char *name, const char *path) { xmlNodePtr node; node = xmlNewChild(parent, NULL, BAD_CAST name, NULL); xmlSetProp(node, BAD_CAST "path", BAD_CAST path); return node; } /* Add a string to each annotation that uniquely identifies it. */ static void add_unique_applic_strings(xmlDocPtr doc) { transform_doc(doc, duplicate_xsl, duplicate_xsl_len, NULL); } /* Remove the unique identifier strings from annotations. */ static void rem_unique_applic_strings(xmlDocPtr doc) { transform_doc(doc, clean_duplicate_xsl, clean_duplicate_xsl_len, NULL); } /* Add a duplicate applicability error to the report. */ static xmlNodePtr add_duplicate_error(xmlNodePtr report, xmlNodePtr node1, xmlNodePtr node2, const char *path) { xmlNodePtr error; long int line1, line2; xmlChar line_s[16], *xpath; line1 = xmlGetLineNo(node1); line2 = xmlGetLineNo(node2); if (verbosity >= NORMAL) { fprintf(stderr, E_DUPLICATECHECK, path, line2, line1); } error = xmlNewChild(report, NULL, BAD_CAST "duplicateApplicError", NULL); xmlStrPrintf(line_s, 16, "%ld", line2); xmlSetProp(error, BAD_CAST "line", line_s); xpath = xpath_of(node2); xmlSetProp(error, BAD_CAST "xpath", xpath); xmlFree(xpath); xmlStrPrintf(line_s, 16, "%ld", line1); xmlSetProp(error, BAD_CAST "duplicateOfLine", line_s); xpath = xpath_of(node1); xmlSetProp(error, BAD_CAST "duplicateOfXPath", xpath); xmlFree(xpath); return error; } static bool same_annotation(xmlNodePtr app1, xmlNodePtr app2) { xmlDocPtr d1, d2; xmlChar *s1, *s2; bool same; /* Compare c14n representation of XML to * determine if the annotations are duplicates. */ d1 = xmlNewDoc(BAD_CAST "1.0"); d2 = xmlNewDoc(BAD_CAST "1.0"); xmlDocSetRootElement(d1, xmlCopyNode(app1, 1)); xmlDocSetRootElement(d2, xmlCopyNode(app2, 1)); xmlC14NDocDumpMemory(d1, NULL, XML_C14N_1_0, NULL, 0, &s1); xmlC14NDocDumpMemory(d2, NULL, XML_C14N_1_0, NULL, 0, &s2); same = xmlStrcmp(s1, s2) == 0; xmlFree(s1); xmlFree(s2); xmlFreeDoc(d1); xmlFreeDoc(d2); return same; } /* Check for duplicate annotations. */ static int check_duplicate_applic(xmlDocPtr doc, const char *path, struct appcheckopts *opts, xmlNodePtr report) { xmlXPathContextPtr ctx; xmlXPathObjectPtr obj; int err = 0; add_unique_applic_strings(doc); ctx = xmlXPathNewContext(doc); xmlXPathRegisterNs(ctx, BAD_CAST "s1kd-appcheck", S1KD_APPCHECK_NS); obj = xmlXPathEval(BAD_CAST "//applic/s1kd-appcheck:annotation", ctx); if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) { int i; for (i = 0; i < obj->nodesetval->nodeNr; ++i) { int j; for (j = i + 1; j < obj->nodesetval->nodeNr; ++j) { if (same_annotation(obj->nodesetval->nodeTab[i], obj->nodesetval->nodeTab[j])) { add_duplicate_error(report, obj->nodesetval->nodeTab[i]->parent, obj->nodesetval->nodeTab[j]->parent, path); err = 1; } } } } xmlXPathFreeObject(obj); xmlXPathFreeContext(ctx); rem_unique_applic_strings(doc); return err; } static int custom_check(xmlDocPtr doc, const char *path, struct appcheckopts *opts, xmlNodePtr report) { xmlDocPtr act = NULL; xmlDocPtr cct = NULL; int err = 0; if (opts->add_deps || opts->check_props) { char actfname[PATH_MAX]; char cctfname[PATH_MAX]; if (find_act_fname(actfname, opts->useract, doc)) { if ((act = read_xml_doc(actfname))) { add_object_node(report, "act", actfname); } } if (find_cct_fname(cctfname, opts->usercct, act)) { if ((cct = read_xml_doc(cctfname))) { add_object_node(report, "cct", cctfname); if (opts->add_deps) { add_cct_depends(doc, cct, NULL); } } } if (opts->check_props) { err += check_props_against_cts(doc, path, act, cct, report); } } if (opts->check_duplicate) { err += check_duplicate_applic(doc, path, opts, report); } if (opts->check_nested || opts->check_redundant) { err += check_nested_applics(doc, path, opts, report); } xmlFreeDoc(cct); xmlFreeDoc(act); return err; } /* Check that an object is valid for all defined product instances. */ static int check_prods(xmlDocPtr doc, const char *path, xmlDocPtr all, xmlDocPtr act, struct appcheckopts *opts, xmlNodePtr report) { char pctfname[PATH_MAX]; int err = 0; xmlDocPtr pct; xmlXPathContextPtr ctx; xmlXPathObjectPtr obj; if (all) { pct = all; } else { if (!find_pct_fname(pctfname, opts->userpct, act)) { return 0; } pct = read_xml_doc(pctfname); add_object_node(report, "pct", pctfname); } ctx = xmlXPathNewContext(pct); obj = xmlXPathEvalExpression(BAD_CAST "//product", ctx); if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) { int i; if (verbosity >= DEBUG) { fprintf(stderr, I_NUM_PRODS, path, obj->nodesetval->nodeNr); } for (i = 0; i < obj->nodesetval->nodeNr; ++i) { xmlNodePtr asserts; xmlChar *id; asserts = xmlNewNode(NULL, BAD_CAST "asserts"); if (all) { id = NULL; } else { xmlChar line_s[16], *xpath; id = xmlGetProp(obj->nodesetval->nodeTab[i], BAD_CAST "id"); if (id) { xmlSetProp(asserts, BAD_CAST "product", id); } xmlStrPrintf(line_s, 16, "%ld", xmlGetLineNo(obj->nodesetval->nodeTab[i])); xmlSetProp(asserts, BAD_CAST "line", line_s); xpath = xpath_of(obj->nodesetval->nodeTab[i]); xmlSetProp(asserts, BAD_CAST "xpath", xpath); xmlFree(xpath); } extract_assigns(asserts, obj->nodesetval->nodeTab[i]); err += check_assigns(doc, path, asserts, obj->nodesetval->nodeTab[i], id, pctfname, opts); xmlAddChild(report, xmlCopyNode(asserts, 1)); xmlFreeNode(asserts); xmlFree(id); } } xmlXPathFreeObject(obj); xmlXPathFreeContext(ctx); if (!all) { xmlFreeDoc(pct); } return err; } /* Add an assertion from an object to a set of assertions. */ static void add_assert(xmlNodePtr asserts, xmlNodePtr assert) { xmlChar *i, *t, *v, *c = NULL; i = first_xpath_value(NULL, assert, BAD_CAST "@applicPropertyIdent|@actidref"); t = first_xpath_value(NULL, assert, BAD_CAST "@applicPropertyType|@actreftype"); v = first_xpath_value(NULL, assert, BAD_CAST "@applicPropertyValues|@actvalues"); while ((c = BAD_CAST strtok(c ? NULL : (char *) v, "|~"))) { xmlNodePtr cur; bool exists = false; for (cur = asserts->children; cur && !exists; cur = cur->next) { xmlChar *ci, *ct, *cv; ci = first_xpath_value(NULL, cur, BAD_CAST "@applicPropertyIdent"); ct = first_xpath_value(NULL, cur, BAD_CAST "@applicPropertyType"); cv = first_xpath_value(NULL, cur, BAD_CAST "@applicPropertyValue"); exists = xmlStrcmp(i, ci) == 0 && xmlStrcmp(t, ct) == 0 && xmlStrcmp(c, cv) == 0; xmlFree(ci); xmlFree(ct); xmlFree(cv); } if (!exists) { xmlNodePtr new; new = xmlNewNode(NULL, BAD_CAST "assign"); xmlSetProp(new, BAD_CAST "applicPropertyIdent", i); xmlSetProp(new, BAD_CAST "applicPropertyType", t); xmlSetProp(new, BAD_CAST "applicPropertyValue", c); xmlAddChild(asserts, new); } } xmlFree(i); xmlFree(t); xmlFree(v); } /* Find a property in a set of properties. */ static xmlNodePtr set_has_prop(xmlNodePtr set, const xmlChar *name) { xmlNodePtr cur, assert = NULL; for (cur = set->children; cur && !assert; cur = cur->next) { xmlChar *i; i = first_xpath_value(NULL, cur->children, BAD_CAST "@applicPropertyIdent|@actidref"); if (xmlStrcmp(i, name) == 0) { assert = cur; } xmlFree(i); } return assert; } /* Check the applicability within an object without using an ACT, CCT or PCT. */ static int check_object_props(xmlDocPtr doc, const char *path, struct appcheckopts *opts, xmlNodePtr report) { xmlDocPtr psdoc; xmlNodePtr propsets; xmlXPathContextPtr ctx; xmlXPathObjectPtr obj; int err = 0; psdoc = xmlNewDoc(BAD_CAST "1.0"); propsets = xmlNewNode(NULL, BAD_CAST "propsets"); xmlDocSetRootElement(psdoc, propsets); /* Add CCT dependencies so they are counted as part of the object's applicability. */ if (opts->add_deps || opts->check_props) { char actfname[PATH_MAX]; char cctfname[PATH_MAX]; xmlDocPtr act = NULL; xmlDocPtr cct = NULL; if (find_act_fname(actfname, opts->useract, doc)) { if ((act = read_xml_doc(actfname))) { add_object_node(report, "act", actfname); } } if (find_cct_fname(cctfname, opts->usercct, act)) { if ((cct = read_xml_doc(cctfname))) { add_object_node(report, "cct", cctfname); if (opts->add_deps) { add_cct_depends(doc, cct, NULL); } } } if (opts->check_props) { err += check_props_against_cts(doc, path, act, cct, report); } xmlFreeDoc(cct); xmlFreeDoc(act); } if (opts->check_duplicate) { err += check_duplicate_applic(doc, path, opts, report); } if (opts->check_nested || opts->check_redundant) { err += check_nested_applics(doc, path, opts, report); } ctx = xmlXPathNewContext(doc); obj = xmlXPathEvalExpression(BAD_CAST "//assert", ctx); if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) { int i; for (i = 0; i < obj->nodesetval->nodeNr; ++i) { xmlNodePtr asserts; xmlChar *name; name = first_xpath_value(doc, obj->nodesetval->nodeTab[i], BAD_CAST "@applicPropertyIdent|@actidref"); if (!(asserts = set_has_prop(propsets, name))) { asserts = xmlNewChild(propsets, NULL, BAD_CAST "asserts", NULL); } xmlFree(name); add_assert(asserts, obj->nodesetval->nodeTab[i]); } } transform_doc(psdoc, combos_xsl, combos_xsl_len, NULL); err += check_prods(doc, path, psdoc, NULL, opts, report); xmlXPathFreeObject(obj); xmlXPathFreeContext(ctx); xmlFreeDoc(psdoc); return err; } /* Check if a property has been added to the ignord properties table. */ static bool prop_is_ignored(const xmlChar *id, const xmlChar *type) { xmlChar *specifier; if (ignored_properties == NULL) { return false; } specifier = xmlStrdup(id); specifier = xmlStrcat(specifier, BAD_CAST ":"); specifier = xmlStrcat(specifier, type); bool is_ignored = xmlHashLookup(ignored_properties, specifier) != NULL; xmlFree(specifier); return is_ignored; } /* Determine whether a property is used in the inline annotations of an object. */ static bool prop_is_used(const xmlChar *id, const xmlChar *type, xmlDocPtr doc) { xmlChar *xpath; int n; xmlNodePtr node; if (prop_is_ignored(id, type)) { return false; } n = xmlStrlen(id) * 2 + xmlStrlen(type) * 2 + 126; xpath = malloc(n * sizeof(xmlChar)); xmlStrPrintf(xpath, n, "(//content|//inlineapplics)//assert[(@applicPropertyIdent='%s' or @actidref='%s') and (@applicPropertyType='%s' or @actreftype='%s')]", id, id, type, type); node = first_xpath_node(doc, NULL, xpath); xmlFree(xpath); return node != NULL; } /* Determine whether an object uses any conditions. */ static bool has_conds(xmlDocPtr doc) { return first_xpath_node(doc, NULL, BAD_CAST "//assert[@applicPropertyType='condition' or @actreftype='condition']"); } /* Check all possible combinations of applicability property values which may * affect the object. */ static int check_all_props(xmlDocPtr doc, const char *path, xmlDocPtr act, struct appcheckopts *opts, xmlNodePtr report) { xmlXPathContextPtr ctx; xmlXPathObjectPtr obj; int err = 0; char cctfname[PATH_MAX]; xmlDocPtr cct = NULL; int i; xmlDocPtr psdoc; xmlNodePtr propsets; psdoc = xmlNewDoc(BAD_CAST "1.0"); propsets = xmlNewNode(NULL, BAD_CAST "propsets"); xmlDocSetRootElement(psdoc, propsets); if (find_cct_fname(cctfname, opts->usercct, act)) { if ((cct = read_xml_doc(cctfname))) { add_object_node(report, "cct", cctfname); if (opts->add_deps) { add_cct_depends(doc, cct, NULL); } } } if (opts->check_props) { err += check_props_against_cts(doc, path, act, cct, report); } if (opts->check_duplicate) { err += check_duplicate_applic(doc, path, opts, report); } if (opts->check_nested || opts->check_redundant) { err += check_nested_applics(doc, path, opts, report); } ctx = xmlXPathNewContext(act); obj = xmlXPathEvalExpression(BAD_CAST "//productAttribute|//prodattr", ctx); if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) { for (i = 0; i < obj->nodesetval->nodeNr; ++i) { xmlChar *id; xmlNodePtr asserts; id = xmlGetProp(obj->nodesetval->nodeTab[i], BAD_CAST "id"); if (prop_is_used(id, BAD_CAST "prodattr", doc)) { asserts = xmlNewNode(NULL, BAD_CAST "asserts"); extract_enumvals(asserts, obj->nodesetval->nodeTab[i], id, false); xmlAddChild(propsets, asserts); } xmlFree(id); } } xmlXPathFreeObject(obj); xmlXPathFreeContext(ctx); if (cct) { ctx = xmlXPathNewContext(cct); obj = xmlXPathEvalExpression(BAD_CAST "//cond|//condition", ctx); if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) { for (i = 0; i < obj->nodesetval->nodeNr; ++i) { xmlChar *id; xmlNodePtr type; xmlNodePtr asserts; id = xmlGetProp(obj->nodesetval->nodeTab[i], BAD_CAST "id"); if (prop_is_used(id, BAD_CAST "condition", doc)) { xmlChar *typerefid; xmlChar *xpath; int n; asserts = xmlNewNode(NULL, BAD_CAST "asserts"); typerefid = first_xpath_value(cct, obj->nodesetval->nodeTab[i], BAD_CAST "@condTypeRefId|@condtyperef"); n = xmlStrlen(typerefid) + 12; xpath = malloc(n * sizeof(xmlChar)); xmlStrPrintf(xpath, n, "//*[@id='%s']", typerefid); xmlFree(typerefid); type = first_xpath_node(cct, NULL, xpath); xmlFree(xpath); extract_enumvals(asserts, type, id, true); xmlAddChild(propsets, asserts); } xmlFree(id); } } xmlXPathFreeObject(obj); xmlXPathFreeContext(ctx); xmlFreeDoc(cct); } else if (has_conds(doc)) { fprintf(stderr, E_NO_CCT, path); xmlNewChild(report, NULL, BAD_CAST "cctNotFound", NULL); ++err; } transform_doc(psdoc, combos_xsl, combos_xsl_len, NULL); err += check_prods(doc, path, psdoc, NULL, opts, report); xmlFreeDoc(psdoc); return err; } /* Check product instances read from the PCT. */ static int check_pct_instances(xmlDocPtr doc, const char *path, xmlDocPtr act, struct appcheckopts *opts, xmlNodePtr report) { int err = 0; /* Add CCT dependencies. */ if (opts->add_deps || opts->check_props) { char cctfname[PATH_MAX]; xmlDocPtr cct = NULL; /* The ACT may or may not have already been read. */ if (act) { if (find_cct_fname(cctfname, opts->usercct, act)) { if ((cct = read_xml_doc(cctfname))) { add_object_node(report, "cct", cctfname); if (opts->add_deps) { add_cct_depends(doc, cct, NULL); } } } if (opts->check_props) { err += check_props_against_cts(doc, path, act, cct, report); } xmlFreeDoc(cct); } else { char actfname[PATH_MAX]; if (find_act_fname(actfname, opts->useract, doc)) { if ((act = read_xml_doc(actfname))) { add_object_node(report, "act", actfname); } } if (find_cct_fname(cctfname, opts->usercct, act)) { if ((cct = read_xml_doc(cctfname))) { add_object_node(report, "cct", cctfname); if (opts->add_deps) { add_cct_depends(doc, cct, NULL); } } } if (opts->check_props) { err += check_props_against_cts(doc, path, act, cct, report); } xmlFreeDoc(cct); xmlFreeDoc(act); act = NULL; } } if (opts->check_duplicate) { err += check_duplicate_applic(doc, path, opts, report); } if (opts->check_nested || opts->check_redundant) { err += check_nested_applics(doc, path, opts, report); } err += check_prods(doc, path, NULL, act, opts, report); return err; } /* Determine whether an object uses any computable applicability. */ static bool has_applic(xmlDocPtr doc) { return first_xpath_node(doc, NULL, BAD_CAST "//assert") != NULL; } /* Check the applicability in an object. */ static int check_applic_file(const char *path, struct appcheckopts *opts, xmlNodePtr report) { xmlDocPtr doc; int err = 0; char actfname[PATH_MAX]; xmlNodePtr report_node = NULL; xmlDocPtr validtree = NULL; if (!(doc = read_xml_doc(path))) { if (verbosity > QUIET) { fprintf(stderr, E_BAD_OBJECT, path); } exit(EXIT_BAD_OBJECT); } /* Make a copy of the XML tree before performing extra * processing on it. */ if (opts->output_tree) { validtree = xmlCopyDoc(doc, 1); } /* Remove elements marked as "delete". */ if (opts->rem_delete) { rem_delete_elems(doc); } if (report) { report_node = add_object_node(report, "object", path); } /* Add the type of check to the report. */ switch (opts->mode) { case CUSTOM: xmlSetProp(report, BAD_CAST "type", BAD_CAST "custom"); break; case PCT: xmlSetProp(report, BAD_CAST "type", BAD_CAST "pct"); break; case ALL: xmlSetProp(report, BAD_CAST "type", BAD_CAST "all"); break; case STANDALONE: xmlSetProp(report, BAD_CAST "type", BAD_CAST "standalone"); break; } if (opts->check_props) { xmlSetProp(report, BAD_CAST "strict", BAD_CAST "yes"); } else { xmlSetProp(report, BAD_CAST "strict", BAD_CAST "no"); } if (opts->check_nested) { xmlSetProp(report, BAD_CAST "checkNestedApplic", BAD_CAST "yes"); } else { xmlSetProp(report, BAD_CAST "checkNestedApplic", BAD_CAST "no"); } if (opts->check_redundant) { xmlSetProp(report, BAD_CAST "checkRedundantApplic", BAD_CAST "yes"); } else { xmlSetProp(report, BAD_CAST "checkRedundantApplic", BAD_CAST "no"); } if (opts->check_duplicate) { xmlSetProp(report, BAD_CAST "checkDuplicateApplic", BAD_CAST "yes"); } else { xmlSetProp(report, BAD_CAST "checkDuplicateApplic", BAD_CAST "no"); } if (opts->mode == CUSTOM) { err += custom_check(doc, path, opts, report_node); } else if (opts->mode == STANDALONE) { err += check_object_props(doc, path, opts, report_node); } else if (opts->mode == PCT && opts->userpct) { err += check_pct_instances(doc, path, NULL, opts, report_node); } else if (find_act_fname(actfname, opts->useract, doc)) { xmlDocPtr act; add_object_node(report_node, "act", actfname); act = read_xml_doc(actfname); if (opts->mode == ALL) { err += check_all_props(doc, path, act, opts, report_node); } else { err += check_pct_instances(doc, path, act, opts, report_node); } xmlFreeDoc(act); } else if (has_applic(doc)) { fprintf(stderr, E_NO_ACT, path); xmlNewChild(report_node, NULL, BAD_CAST "actNotFound", NULL); ++err; } if (err) { xmlSetProp(report_node, BAD_CAST "valid", BAD_CAST "no"); if (opts->filenames == SHOW_INVALID) { puts(path); } } else { xmlSetProp(report_node, BAD_CAST "valid", BAD_CAST "yes"); if (opts->filenames == SHOW_VALID) { puts(path); } } if (opts->output_tree) { if (err == 0) { save_xml_doc(validtree, "-"); } xmlFreeDoc(validtree); } if (verbosity >= VERBOSE) { fprintf(stderr, err ? F_INVALID : S_VALID, path); } xmlFreeDoc(doc); return err; } /* Add a CSDB object path to check. */ static void add_object(const char *path) { if (nobjects == OBJECT_MAX) { if (!(objects = realloc(objects, (OBJECT_MAX *= 2) * PATH_MAX))) { if (verbosity > QUIET) { fprintf(stderr, E_MAX_OBJECTS); } exit(EXIT_MAX_OBJECTS); } } strcpy(objects[nobjects++], path); } /* Add a list of CSDB object paths to check. */ static void add_object_list(const char *fname) { FILE *f; char path[PATH_MAX]; if (fname) { if (!(f = fopen(fname, "r"))) { if (verbosity >= NORMAL) { fprintf(stderr, E_BAD_LIST, fname); } return; } } else { f = stdin; } while (fgets(path, PATH_MAX, f)) { strtok(path, "\t\r\n"); add_object(path); } if (fname) { fclose(f); } } /* Show a summary of the check. */ static void print_stats(xmlDocPtr doc) { xmlDocPtr styledoc; xsltStylesheetPtr style; xmlDocPtr res; styledoc = read_xml_mem((const char *) stats_xsl, stats_xsl_len); style = xsltParseStylesheetDoc(styledoc); res = xsltApplyStylesheet(style, doc, NULL); fprintf(stderr, "%s", (char *) res->children->content); xmlFreeDoc(res); xsltFreeStylesheet(style); } /* Add a property to the list of ones to ignore while validating. */ static void add_ignored_property(char *specifier) { if (ignored_properties == NULL) { ignored_properties = xmlHashCreate(0); } xmlHashAddEntry(ignored_properties, BAD_CAST specifier, ignored_properties); } int main(int argc, char **argv) { int i; const char *sopts = "A:abC:cDd:e:Ffi:NnK:k:loP:pqRrsTtvx~h?"; struct option lopts[] = { {"version" , no_argument , 0, 0}, {"help" , no_argument , 0, 'h'}, {"act" , required_argument, 0, 'A'}, {"all" , no_argument , 0, 'a'}, {"brexcheck" , no_argument , 0, 'b'}, {"cct" , required_argument, 0, 'C'}, {"custom" , no_argument , 0, 'c'}, {"duplicate" , no_argument , 0, 'D'}, {"dir" , required_argument, 0, 'd'}, {"exec" , required_argument, 0, 'e'}, {"valid-filenames", no_argument , 0, 'F'}, {"filenames" , no_argument , 0, 'f'}, {"ignore" , required_argument, 0, 'i'}, {"filter" , required_argument, 0, 'K'}, {"args" , required_argument, 0, 'k'}, {"list" , no_argument , 0, 'l'}, {"omit-issue" , no_argument , 0, 'N'}, {"nested" , no_argument , 0, 'n'}, {"output-valid" , no_argument , 0, 'o'}, {"pct" , required_argument, 0, 'P'}, {"progress" , required_argument, 0, 'p'}, {"quiet" , no_argument , 0, 'q'}, {"redundant" , no_argument , 0, 'R'}, {"recursive" , no_argument , 0, 'r'}, {"strict" , no_argument , 0, 's'}, {"summary" , no_argument , 0, 'T'}, {"products" , no_argument , 0, 't'}, {"verbose" , no_argument , 0, 'v'}, {"xml" , no_argument , 0, 'x'}, {"dependencies" , no_argument , 0, '~'}, {"remove-deleted" , no_argument , 0, '^'}, {"zenity-progress", no_argument , 0, 0}, LIBXML2_PARSE_LONGOPT_DEFS {0, 0, 0, 0} }; int loptind = 0; bool islist = false; bool xmlout = false; bool show_stats = false; int show_progress = PROGRESS_OFF; struct appcheckopts opts = { /* useract */ NULL, /* usercct */ NULL, /* userpct */ NULL, /* args */ NULL, /* filter */ NULL, /* validators */ NULL, /* output_tree */ false, /* filenames */ SHOW_NONE, /* brexcheck */ false, /* add_deps */ false, /* check_props */ false, /* check_nested */ false, /* check_redundant */ false, /* check_duplicate */ false, /* rem_delete */ false, /* mode */ STANDALONE }; int err = 0; xmlDocPtr report; xmlNodePtr appcheck; exsltRegisterAll(); objects = malloc(OBJECT_MAX * PATH_MAX); search_dir = strdup("."); report = xmlNewDoc(BAD_CAST "1.0"); appcheck = xmlNewNode(NULL, BAD_CAST "appCheck"); xmlDocSetRootElement(report, appcheck); while ((i = getopt_long(argc, argv, sopts, lopts, &loptind)) != -1) { switch (i) { case 0: if (strcmp(lopts[loptind].name, "version") == 0) { show_version(); goto cleanup; } else if (strcmp(lopts[loptind].name, "zenity-progress") == 0) { show_progress = PROGRESS_ZENITY; } LIBXML2_PARSE_LONGOPT_HANDLE(lopts, loptind, optarg) break; case 'A': opts.useract = strdup(optarg); break; case 'a': opts.mode = ALL; break; case 'b': opts.brexcheck = true; break; case 'C': opts.usercct = strdup(optarg); break; case 'c': opts.mode = CUSTOM; break; case 'D': opts.check_duplicate = true; break; case 'd': free(search_dir); search_dir = strdup(optarg); break; case 'e': if (!opts.validators) { opts.validators = xmlNewNode(NULL, BAD_CAST "validators"); } xmlNewChild(opts.validators, NULL, BAD_CAST "cmd", BAD_CAST optarg); break; case 'F': opts.filenames = SHOW_VALID; break; case 'f': opts.filenames = SHOW_INVALID; break; case 'i': add_ignored_property(optarg); break; case 'K': opts.filter = strdup(optarg); break; case 'k': opts.args = strdup(optarg); break; case 'l': islist = true; break; case 'N': no_issue = true; break; case 'n': opts.check_nested = true; break; case 'o': opts.output_tree = true; break; case 'P': opts.userpct = strdup(optarg); break; case 'p': show_progress = PROGRESS_CLI; break; case 'R': opts.check_redundant = true; break; case 'r': recursive_search = true; break; case 's': opts.check_props = true; break; case 'T': show_stats = true; break; case 't': opts.mode = PCT; break; case 'q': --verbosity; break; case 'v': ++verbosity; break; case 'x': xmlout = true; break; case '~': opts.add_deps = true; break; case '^': opts.rem_delete = true; break; case 'h': case '?': show_help(); goto cleanup; } } if (optind < argc) { for (i = optind; i < argc; ++i) { if (islist) { add_object_list(argv[i]); } else { add_object(argv[i]); } } } else if (islist) { add_object_list(NULL); } else { add_object("-"); } for (i = 0; i < nobjects; ++i) { err += check_applic_file(objects[i], &opts, appcheck); switch (show_progress) { case PROGRESS_OFF: break; case PROGRESS_CLI: print_progress_bar(i, nobjects); break; case PROGRESS_ZENITY: print_zenity_progress("Performing applicability check...", i, nobjects); break; } } if (nobjects) { switch (show_progress) { case PROGRESS_OFF: break; case PROGRESS_CLI: print_progress_bar(i, nobjects); break; case PROGRESS_ZENITY: print_zenity_progress("Applicability check complete", i, nobjects); break; } } if (xmlout) { save_xml_doc(report, "-"); } if (show_stats) { print_stats(report); } cleanup: xmlFreeDoc(report); free(opts.userpct); free(opts.useract); free(opts.usercct); free(opts.filter); free(opts.args); xmlFreeNode(opts.validators); free(search_dir); free(objects); if (ignored_properties != NULL) xmlHashFree(ignored_properties, NULL); xsltCleanupGlobals(); xmlCleanupParser(); return err ? EXIT_FAILURE : EXIT_SUCCESS; }