diff --git a/tools/s1kd-appcheck/README.md b/tools/s1kd-appcheck/README.md
index 185b2164..6e6b0428 100644
--- a/tools/s1kd-appcheck/README.md
+++ b/tools/s1kd-appcheck/README.md
@@ -65,6 +65,10 @@ the actual validation.
used to determine if the object is valid (with a non-zero exit
status indicating it is invalid). This overrides the default
commands (s1kd-validate, and s1kd-brexcheck if -b is specified).
+
+ When the -X (--xml-with-errors) option is used, if these commands
+ output an XML document with any elements named `error`, those
+ elements will be copied into the XML report output by this tool.
- \-F, --valid-filenames
Print the filenames of valid objects.
@@ -141,8 +145,14 @@ the actual validation.
- \-v, --verbose
Verbose output. Specify multiple times to increase the verbosity.
+ - \-X, --xml-with-errors
+ Output an XML report of the check, including full details of errors
+ produced by the validation tools (such as s1kd-validate and
+ s1kd-brexcheck) for each set of assertions tested.
+
- \-x, --xml
- Print an XML report of the check.
+ Output a simplified XML report of the check, only including whether
+ sets of assertions passed or failed.
- \-\~, --dependencies
Check with CCT dependency tests added to assertions which use the
diff --git a/tools/s1kd-appcheck/doc/DMC-S1KDTOOLS-A-11-00-00-00A-040A-D_EN-CA.XML b/tools/s1kd-appcheck/doc/DMC-S1KDTOOLS-A-11-00-00-00A-040A-D_EN-CA.XML
index 95aae552..9fa7929d 100644
--- a/tools/s1kd-appcheck/doc/DMC-S1KDTOOLS-A-11-00-00-00A-040A-D_EN-CA.XML
+++ b/tools/s1kd-appcheck/doc/DMC-S1KDTOOLS-A-11-00-00-00A-040A-D_EN-CA.XML
@@ -6,10 +6,10 @@
-
+
-
+
s1kd-appcheck(1) | s1kd-tools
@@ -53,6 +53,9 @@
Add --unstrict-nested option.
+
+ Add --xml-with-errors option.
+
@@ -123,6 +126,7 @@
-e, --exec <cmd>
The commands used to validate objects. Multiple commands can be used by specifying this option multiple times. The objects will be passed to each command on stdin, and the exit status of the command will be used to determine if the object is valid (with a non-zero exit status indicating it is invalid). This overrides the default commands (s1kd-validate, and s1kd-brexcheck if -b is specified).
+ When the -X (--xml-with-errors) option is used, if these commands output an XML document with any elements named error, those elements will be copied into the XML report output by this tool.
@@ -245,10 +249,16 @@
Verbose output. Specify multiple times to increase the verbosity.
+
+ -X, --xml-with-errors
+
+ Output an XML report of the check, including full details of errors produced by the validation tools (such as s1kd-validate and s1kd-brexcheck) for each set of assertions tested.
+
+
-x, --xml
- Print an XML report of the check.
+ Output a simplified XML report of the check, only including whether sets of assertions passed or failed.
diff --git a/tools/s1kd-appcheck/doc/s1kd-appcheck.1 b/tools/s1kd-appcheck/doc/s1kd-appcheck.1
index 9107b258..4907c35a 100644
--- a/tools/s1kd-appcheck/doc/s1kd-appcheck.1
+++ b/tools/s1kd-appcheck/doc/s1kd-appcheck.1
@@ -1,6 +1,6 @@
.\" Automatically generated by Pandoc 2.0.6
.\"
-.TH "s1kd\-appcheck" "1" "2024\-08\-14" "" "s1kd\-tools"
+.TH "s1kd\-appcheck" "1" "2024\-12\-23" "" "s1kd\-tools"
.hy
.SH NAME
.PP
@@ -89,6 +89,10 @@ non\-zero exit status indicating it is invalid).
This overrides the default commands (s1kd\-validate, and s1kd\-brexcheck
if \-b is specified).
.RS
+.PP
+When the \-X (\-\-xml\-with\-errors) option is used, if these commands
+output an XML document with any elements named \f[C]error\f[], those
+elements will be copied into the XML report output by this tool.
.RE
.TP
.B \-F, \-\-valid\-filenames
@@ -209,8 +213,16 @@ Specify multiple times to increase the verbosity.
.RS
.RE
.TP
+.B \-X, \-\-xml\-with\-errors
+Output an XML report of the check, including full details of errors
+produced by the validation tools (such as s1kd\-validate and
+s1kd\-brexcheck) for each set of assertions tested.
+.RS
+.RE
+.TP
.B \-x, \-\-xml
-Print an XML report of the check.
+Output a simplified XML report of the check, only including whether sets
+of assertions passed or failed.
.RS
.RE
.TP
diff --git a/tools/s1kd-appcheck/s1kd-appcheck.c b/tools/s1kd-appcheck/s1kd-appcheck.c
index 2a96beff..89b573f6 100644
--- a/tools/s1kd-appcheck/s1kd-appcheck.c
+++ b/tools/s1kd-appcheck/s1kd-appcheck.c
@@ -1,3 +1,5 @@
+#define _GNU_SOURCE
+
#include
#include
#include
@@ -5,11 +7,14 @@
#include
#include
#include
+#include
+#include
#include
#include
#include
#include
#include
+#include
#include
#include
#include "s1kd_tools.h"
@@ -17,7 +22,7 @@
/* Program name and version information. */
#define PROG_NAME "s1kd-appcheck"
-#define VERSION "6.7.2"
+#define VERSION "6.8.0"
/* Message prefixes. */
#define ERR_PREFIX PROG_NAME ": ERROR: "
@@ -35,6 +40,7 @@
#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"
#define I_THREADS INF_PREFIX "Creating %d threads...\n"
+#define I_EXEC INF_PREFIX "Executing: %s\n"
/* Error messages. */
#define E_CHECK_FAIL_PROD ERR_PREFIX "%s is invalid for product %s (line %ld of %s)\n"
@@ -52,7 +58,7 @@
#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"
+#define E_PIPE ERR_PREFIX "Error executing %s: %s\n"
/* Warning messages. */
#define W_MISSING_REF_DM WRN_PREFIX "Could not read referenced object: %s\n"
@@ -66,7 +72,7 @@
/* Exit status codes. */
#define EXIT_BAD_OBJECT 2
#define EXIT_MAX_OBJECTS 3
-#define EXIT_POPEN 4
+#define EXIT_PIPE 4
/* Default commands used to filter and validate. */
#define DEFAULT_FILTER "s1kd-instance"
@@ -133,6 +139,7 @@ struct appcheckopts {
bool check_duplicate;
bool rem_delete;
enum appcheckmode mode;
+ bool include_errors;
};
/* Struct to pass data to object_thread. */
@@ -203,7 +210,8 @@ static void show_help(void)
puts(" -t, --products Validate against product instances.");
puts(" -u, --unstrict-nested Perform a nested check in unstrict mode.");
puts(" -v, --verbose Verbose output.");
- puts(" -x, --xml Output XML report.");
+ puts(" -X, --xml-with-errors Output an XML report, including all details on errors.");
+ puts(" -x, --xml Output a simpler XML report.");
puts(" -~, --dependencies Check CCT dependencies.");
puts(" -^, --remove-deleted Validate with elements marked as \"delete\" removed.");
puts(" -#, --threads Number of threads to use. x * y threads are created in total.");
@@ -1022,14 +1030,157 @@ static void extract_assigns(xmlNodePtr asserts, xmlNodePtr product)
xmlXPathFreeContext(ctx);
}
+/* Send an XML document to an external command's stdin, and return the exit status code. */
+static int execute_command_on_doc(xmlDocPtr doc, const char *cmd)
+{
+ FILE *p;
+ p = popen(cmd, "w");
+ if (p == NULL) {
+ fprintf(stderr, E_PIPE, cmd, strerror(errno));
+ }
+ xmlDocDump(p, doc);
+ return pclose(p);
+}
+
+/* Send an XML document to an external command's stdin, and read an XML document from the command's stdout. */
+static int execute_command_on_doc_and_parse_output(xmlDocPtr doc, const char *cmd, xmlDocPtr *out)
+{
+ /* Pipe to write to the stdin of the command. */
+ int stdin_pipe[2];
+
+ /* Pipe to read the stdout from the command. */
+ int stdout_pipe[2];
+
+ /* The PID of the child process. */
+ int pid;
+
+ /* Create a pipe for stdin. Abort on error. */
+ if (pipe2(stdin_pipe, O_CLOEXEC) < 0) {
+ goto pipe_error;
+ }
+
+ /* Create a pipe for stdout. Abort on error. */
+ if (pipe2(stdout_pipe, O_CLOEXEC) < 0) {
+ goto pipe_error;
+ }
+
+ /* Create a child process to execute the command. */
+ pid = fork();
+
+ /* If there is an error forking, abort. */
+ if (pid == -1) {
+ goto pipe_error;
+ }
+
+ /* The child process executes the given command with stdin and stdout directed to the created pipes. */
+ if (pid == 0) {
+ /* Close the ends of the pipes we don't use in the child process. */
+ if (close(stdin_pipe[1]) == -1 || close(stdout_pipe[0]) == -1) {
+ goto child_pipe_error;
+ }
+
+ /* Direct stdin and stdout to the created pipes, and close the dup'd pipe ends. */
+ if (stdin_pipe[0] != STDIN_FILENO) {
+ if (dup2(stdin_pipe[0], STDIN_FILENO) == -1 || close(stdin_pipe[0]) == -1) {
+ goto child_pipe_error;
+ }
+ }
+ if (stdout_pipe[1] != STDOUT_FILENO) {
+ if (dup2(stdout_pipe[1], STDOUT_FILENO) == -1 || close(stdout_pipe[1]) == -1) {
+ goto child_pipe_error;
+ }
+ }
+
+ /* Print an info message with the command in DEBUG verbosity. */
+ if (verbosity >= DEBUG) {
+ fprintf(stderr, I_EXEC, cmd);
+ }
+
+ /* Replace the child process with the execution of the command. */
+ execl(_PATH_BSHELL, "sh", "-c", cmd, (char *) NULL);
+
+ /* If for some reason exec fails, abort. */
+child_pipe_error:
+ fprintf(stderr, E_PIPE, cmd, strerror(errno));
+ _exit(EXIT_FAILURE);
+ }
+
+ /* Parent process continues here. */
+
+ /* Close the ends of the pipes we don't use in the parent process. */
+ if (close(stdin_pipe[0]) == -1 || close(stdout_pipe[1]) == -1) {
+ goto pipe_error;
+ }
+
+ /* Write the XML document to the stdin of the child process. */
+ xmlSaveCtxtPtr ctxt = xmlSaveToFd(stdin_pipe[1], NULL, 0);
+ xmlSaveDoc(ctxt, doc);
+ xmlSaveClose(ctxt);
+
+ /* Close stdin of the child process. */
+ if (close(stdin_pipe[1]) == -1) {
+ goto pipe_error;
+ }
+
+ /* Read the stdout of the child process into a new XML document. */
+ *out = xmlReadFd(stdout_pipe[0], NULL, NULL, XML_PARSE_NOERROR);
+
+ /* Close the stdout of the child process. */
+ if (close(stdout_pipe[0]) == -1) {
+ goto pipe_error;
+ }
+
+ /* Wait for the child process to finish to get its exit status. */
+ int status;
+ pid_t wait_pid;
+ do {
+ wait_pid = waitpid(pid, &status, 0);
+ } while (wait_pid == -1 && errno == EINTR);
+
+ /* If the child process returned normally, return the exit status. Otherwise, abort. */
+ if (WIFEXITED(status)) {
+ return WEXITSTATUS(status);
+ }
+
+ /* Abort if there are any errors creating/executing the child process. */
+pipe_error:
+ fprintf(stderr, E_PIPE, cmd, strerror(errno));
+ exit(EXIT_PIPE);
+}
+
+/* Add error elements from reports to the collection of errors. */
+static void add_errors_from_report(const xmlNodePtr errors, const xmlDocPtr report, const xmlChar *cmd)
+{
+ xmlNodePtr cmd_node;
+ xmlXPathContextPtr ctx;
+ xmlXPathObjectPtr obj;
+
+ cmd_node = xmlNewChild(errors, NULL, BAD_CAST "validator", NULL);
+ xmlSetProp(cmd_node, BAD_CAST "command", cmd);
+
+ ctx = xmlXPathNewContext(report);
+ obj = xmlXPathEvalExpression(BAD_CAST "//error", ctx);
+
+ if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
+ int i;
+
+ for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
+ xmlAddChild(cmd_node, xmlCopyNode(obj->nodesetval->nodeTab[i], 1));
+ }
+ }
+
+ 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;
+ xmlDocPtr filtered_doc = NULL;
+ xmlNodePtr errors = NULL;
if (verbosity >= DEBUG) {
if (opts->mode >= ALL) {
@@ -1076,36 +1227,50 @@ static int check_assigns(xmlDocPtr doc, const char *path, xmlNodePtr asserts, xm
xmlFree(v);
}
+ execute_command_on_doc_and_parse_output(doc, filter_cmd, &filtered_doc);
+
+ /* If the filtered doc is empty, the doc was not applicable to the given assigns. */
+ if (filtered_doc == NULL) {
+ return 0;
+ }
+
+ if (opts->include_errors) {
+ errors = xmlNewNode(NULL, BAD_CAST "errors");
+ }
+
/* Custom validators. */
if (opts->validators) {
for (cur = opts->validators->children; cur; cur = cur->next) {
- xmlChar *c;
+ xmlChar *cmd;
- strcpy(cmd, filter_cmd);
- strcat(cmd, "|");
+ cmd = xmlNodeGetContent(cur);
- c = xmlNodeGetContent(cur);
+ if (opts->include_errors) {
+ xmlDocPtr report = NULL;
- strncat(cmd, (char *) c, 4095 - strlen(cmd));
+ e += execute_command_on_doc_and_parse_output(filtered_doc, (char *) cmd, &report);
- p = popen(cmd, "w");
+ if (report) {
+ add_errors_from_report(errors, report, cmd);
+ }
- if (p == NULL) {
- fprintf(stderr, E_POPEN, cmd, strerror(errno));
- exit(EXIT_POPEN);
+ xmlFreeDoc(report);
+ } else {
+ e += execute_command_on_doc(filtered_doc, (char *) cmd);
}
- xmlDocDump(p, doc);
- e += pclose(p);
-
- xmlFree(c);
+ xmlFree(cmd);
}
/* Default validators. */
} else {
- strcpy(cmd, filter_cmd);
+ char cmd[4096];
/* Schema validation */
- strcat(cmd, "|" DEFAULT_VALIDATE " -e");
+ if (opts->include_errors) {
+ strcpy(cmd, DEFAULT_VALIDATE " -x");
+ } else {
+ strcpy(cmd, DEFAULT_VALIDATE);
+ }
switch (verbosity) {
case QUIET:
@@ -1119,20 +1284,27 @@ static int check_assigns(xmlDocPtr doc, const char *path, xmlNodePtr asserts, xm
break;
}
- p = popen(cmd, "w");
+ if (opts->include_errors) {
+ xmlDocPtr validate_report = NULL;
- if (p == NULL) {
- fprintf(stderr, E_POPEN, cmd, strerror(errno));
- exit(EXIT_POPEN);
- }
+ e += execute_command_on_doc_and_parse_output(filtered_doc, cmd, &validate_report);
- xmlDocDump(p, doc);
- e += pclose(p);
+ if (validate_report) {
+ add_errors_from_report(errors, validate_report, BAD_CAST cmd);
+ }
+
+ xmlFreeDoc(validate_report);
+ } else {
+ e += execute_command_on_doc(filtered_doc, cmd);
+ }
/* BREX validation */
if (opts->brexcheck) {
- strcpy(cmd, filter_cmd);
- strcat(cmd, "|" DEFAULT_BREXCHECK " -cel");
+ if (opts->include_errors) {
+ strcpy(cmd, DEFAULT_BREXCHECK " -clx");
+ } else {
+ strcpy(cmd, DEFAULT_BREXCHECK " -cl");
+ }
strcat(cmd, " -d '");
strcat(cmd, search_dir);
@@ -1154,18 +1326,24 @@ static int check_assigns(xmlDocPtr doc, const char *path, xmlNodePtr asserts, xm
break;
}
- p = popen(cmd, "w");
+ if (opts->include_errors) {
+ xmlDocPtr brexcheck_report = NULL;
- if (p == NULL) {
- fprintf(stderr, E_POPEN, cmd, strerror(errno));
- exit(EXIT_POPEN);
- }
+ e += execute_command_on_doc_and_parse_output(filtered_doc, cmd, &brexcheck_report);
+
+ if (brexcheck_report) {
+ add_errors_from_report(errors, brexcheck_report, BAD_CAST cmd);
+ }
- xmlDocDump(p, doc);
- e += pclose(p);
+ xmlFreeDoc(brexcheck_report);
+ } else {
+ e += execute_command_on_doc(filtered_doc, cmd);
+ }
}
}
+ xmlFreeDoc(filtered_doc);
+
if (e) {
if (verbosity >= NORMAL) {
if (opts->mode >= ALL) {
@@ -1191,11 +1369,20 @@ static int check_assigns(xmlDocPtr doc, const char *path, xmlNodePtr asserts, xm
}
xmlSetProp(asserts, BAD_CAST "valid", BAD_CAST "no");
+
+ if (opts->include_errors) {
+ xmlAddChild(asserts, xmlCopyNode(errors, 1));
+ }
+
++err;
} else {
xmlSetProp(asserts, BAD_CAST "valid", BAD_CAST "yes");
}
+ if (opts->include_errors) {
+ xmlFreeNode(errors);
+ }
+
return err ? 1 : 0;
}
@@ -2205,7 +2392,7 @@ int main(int argc, char **argv)
{
int i;
- const char *sopts = "A:abC:cDd:e:Ffi:NnK:k:loP:pqRrsTtuvx~#:h?";
+ const char *sopts = "A:abC:cDd:e:Ffi:NnK:k:loP:pqRrsTtuvXx~#:h?";
struct option lopts[] = {
{"version" , no_argument , 0, 0},
{"help" , no_argument , 0, 'h'},
@@ -2236,6 +2423,7 @@ int main(int argc, char **argv)
{"products" , no_argument , 0, 't'},
{"unstrict-nested", no_argument , 0, 'u'},
{"verbose" , no_argument , 0, 'v'},
+ {"xml-with-errors", no_argument , 0, 'X'},
{"xml" , no_argument , 0, 'x'},
{"dependencies" , no_argument , 0, '~'},
{"threads" , required_argument, 0, '#'},
@@ -2268,7 +2456,8 @@ int main(int argc, char **argv)
/* check_redundant */ false,
/* check_duplicate */ false,
/* rem_delete */ false,
- /* mode */ STANDALONE
+ /* mode */ STANDALONE,
+ /* include_errors */ false
};
int err = 0;
@@ -2383,6 +2572,8 @@ int main(int argc, char **argv)
case 'v':
++verbosity;
break;
+ case 'X':
+ opts.include_errors = true;
case 'x':
xmlout = true;
break;