..
/
download
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <stdbool.h>
#include <errno.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#include <libxml/c14n.h>
#include <libxml/hash.h>
#include <libxslt/transform.h>
#include <libexslt/exslt.h>
#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] [<object>...]");
puts("");
puts("Options:");
puts(" -A, --act <file> User-specified ACT.");
puts(" -a, --all Validate against all property values.");
puts(" -b, --brexcheck Validate against BREX.");
puts(" -C, --cct <file> User-specified CCT.");
puts(" -c, --custom Perform a customized check.");
puts(" -D, --duplicate Check for duplicate applicability annotations.");
puts(" -d, --dir <dir> Search for ACT/CCT/PCT in <dir>.");
puts(" -e, --exec <cmd> 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 <id:type> Ignore an applicability property when validating.");
puts(" -K, --filter <cmd> Command used to create objects.");
puts(" -k, --args <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 <file> 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(" <object>... 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;
}
gopher://khzae.net/0/s1000d/s1kd-tools/src/tools/s1kd-appcheck/s1kd-appcheck.c