summaryrefslogtreecommitdiffstats
path: root/tools/re
diff options
context:
space:
mode:
Diffstat (limited to 'tools/re')
-rw-r--r--tools/re/CMakeLists.txt20
-rw-r--r--tools/re/main.c461
-rw-r--r--tools/re/yangre.1118
3 files changed, 599 insertions, 0 deletions
diff --git a/tools/re/CMakeLists.txt b/tools/re/CMakeLists.txt
new file mode 100644
index 0000000..7b6de22
--- /dev/null
+++ b/tools/re/CMakeLists.txt
@@ -0,0 +1,20 @@
+# yangre
+
+set(resrc
+ main.c)
+
+set(format_sources
+ ${format_sources}
+ ${CMAKE_CURRENT_SOURCE_DIR}/*.c
+ PARENT_SCOPE)
+
+add_executable(yangre ${resrc} ${compatsrc})
+target_link_libraries(yangre yang)
+install(TARGETS yangre DESTINATION ${CMAKE_INSTALL_BINDIR})
+install(FILES ${PROJECT_SOURCE_DIR}/tools/re/yangre.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
+target_include_directories(yangre BEFORE PRIVATE ${PROJECT_BINARY_DIR})
+
+if(WIN32)
+ target_include_directories(yangre PRIVATE ${GETOPT_INCLUDE_DIR})
+ target_link_libraries(yangre ${GETOPT_LIBRARY})
+endif()
diff --git a/tools/re/main.c b/tools/re/main.c
new file mode 100644
index 0000000..5d1edd5
--- /dev/null
+++ b/tools/re/main.c
@@ -0,0 +1,461 @@
+/**
+ * @file main.c
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Adam Piecek <piecek@cesnet.cz>
+ * @brief libyang's YANG Regular Expression tool
+ *
+ * Copyright (c) 2017 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#define _GNU_SOURCE /* asprintf, strdup */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "libyang.h"
+
+#include "compat.h"
+#include "tools/config.h"
+
+struct yr_pattern {
+ char *expr;
+ ly_bool invert;
+};
+
+void
+help(void)
+{
+ fprintf(stdout, "YANG Regular Expressions processor.\n");
+ fprintf(stdout, "Usage:\n");
+ fprintf(stdout, " yangre [-hv]\n");
+ fprintf(stdout, " yangre [-V] -p <regexp1> [-i] [-p <regexp2> [-i] ...] <string>\n");
+ fprintf(stdout, " yangre [-V] -f <file>\n");
+ fprintf(stdout, "Returns 0 if string matches the pattern(s) or if otherwise successful.\n");
+ fprintf(stdout, "Returns 1 on error.\n");
+ fprintf(stdout, "Returns 2 if string does not match the pattern(s).\n\n");
+ fprintf(stdout, "Options:\n"
+ " -h, --help Show this help message and exit.\n"
+ " -v, --version Show version number and exit.\n"
+ " -V, --verbose Print the processing information.\n"
+ " -i, --invert-match Invert-match modifier for the closest preceding\n"
+ " pattern.\n"
+ " -p, --pattern=\"REGEXP\" Regular expression including the quoting,\n"
+ " which is applied the same way as in a YANG module.\n"
+ " -f, --file=\"FILE\" List of patterns and the <string> (separated by an\n"
+ " empty line) are taken from <file>. Invert-match is\n"
+ " indicated by the single space character at the \n"
+ " beginning of the pattern line. YANG quotation around\n"
+ " patterns is still expected, but that avoids issues with\n"
+ " reading quotation by shell. Avoid newline at the end\n"
+ " of the string line to represent empty <string>.");
+ fprintf(stdout, "Examples:\n"
+ " pattern \"[0-9a-fA-F]*\"; -> yangre -p '\"[0-9a-fA-F]*\"' '1F'\n"
+ " pattern '[a-zA-Z0-9\\-_.]*'; -> yangre -p \"'[a-zA-Z0-9\\-_.]*'\" 'a-b'\n"
+ " pattern [xX][mM][lL].*; -> yangre -p '[xX][mM][lL].*' 'xml-encoding'\n\n");
+ fprintf(stdout, "Note that to pass YANG quoting through your shell, you are supposed to use\n"
+ "the other quotation around. For not-quoted patterns, use single quotes.\n\n");
+}
+
+void
+version(void)
+{
+ fprintf(stdout, "yangre %s\n", PROJECT_VERSION);
+}
+
+void
+pattern_error(LY_LOG_LEVEL level, const char *msg, const char *UNUSED(data_path), const char *UNUSED(schema_path),
+ uint64_t UNUSED(line))
+{
+ if (level == LY_LLERR) {
+ fprintf(stderr, "yangre error: %s\n", msg);
+ }
+}
+
+static int
+add_pattern(struct yr_pattern **patterns, int *counter, char *pattern)
+{
+ void *reallocated;
+ int orig_counter;
+
+ /* Store the original number of items. */
+ orig_counter = *counter;
+
+ /* Reallocate 'patterns' memory with additional space. */
+ reallocated = realloc(*patterns, (orig_counter + 1) * sizeof **patterns);
+ if (!reallocated) {
+ goto error;
+ }
+ (*patterns) = reallocated;
+ /* Allocated memory is now larger. */
+ (*counter)++;
+ /* Copy the pattern and store it to the additonal space. */
+ (*patterns)[orig_counter].expr = strdup(pattern);
+ if (!(*patterns)[orig_counter].expr) {
+ goto error;
+ }
+ (*patterns)[orig_counter].invert = 0;
+
+ return 0;
+
+error:
+ fprintf(stderr, "yangre error: memory allocation error.\n");
+ return 1;
+}
+
+static int
+create_empty_string(char **str)
+{
+ free(*str);
+ *str = malloc(sizeof(char));
+ if (!(*str)) {
+ fprintf(stderr, "yangre error: memory allocation failed.\n");
+ return 1;
+ }
+ (*str)[0] = '\0';
+
+ return 0;
+}
+
+static ly_bool
+file_is_empty(FILE *fp)
+{
+ int c;
+
+ c = fgetc(fp);
+ if (c == EOF) {
+ return 1;
+ } else {
+ ungetc(c, fp);
+ return 0;
+ }
+}
+
+/**
+ * @brief Open the @p filepath, parse patterns and given string-argument.
+ *
+ * @param[in] filepath File to parse. Contains patterns and string.
+ * @param[out] infile The file descriptor of @p filepath.
+ * @param[out] patterns Storage of patterns.
+ * @param[out] patterns_count Number of items in @p patterns.
+ * @param[out] strarg The string-argument to check.
+ * @return 0 on success.
+ */
+static int
+parse_patterns_file(const char *filepath, FILE **infile, struct yr_pattern **patterns, int *patterns_count, char **strarg)
+{
+ int blankline = 0;
+ char *str = NULL;
+ size_t len = 0;
+ ssize_t l;
+
+ *infile = fopen(filepath, "rb");
+ if (!(*infile)) {
+ fprintf(stderr, "yangre error: unable to open input file %s (%s).\n", optarg, strerror(errno));
+ goto error;
+ }
+ if (file_is_empty(*infile)) {
+ if (create_empty_string(strarg)) {
+ goto error;
+ }
+ return 0;
+ }
+
+ while ((l = getline(&str, &len, *infile)) != -1) {
+ if (!blankline && ((str[0] == '\n') || ((str[0] == '\r') && (str[1] == '\n')))) {
+ /* blank line */
+ blankline = 1;
+ continue;
+ }
+ if ((str[0] != '\n') && (str[0] != '\r') && (str[l - 1] == '\n')) {
+ /* remove ending newline */
+ if ((l > 1) && (str[l - 2] == '\r') && (str[l - 1] == '\n')) {
+ str[l - 2] = '\0';
+ } else {
+ str[l - 1] = '\0';
+ }
+ }
+ if (blankline) {
+ /* done - str is now the string to check */
+ blankline = 0;
+ *strarg = str;
+ break;
+ /* else read the patterns */
+ } else if (add_pattern(patterns, patterns_count, (str[0] == ' ') ? &str[1] : str)) {
+ goto error;
+ }
+ if (str[0] == ' ') {
+ /* set invert-match */
+ (*patterns)[*patterns_count - 1].invert = 1;
+ }
+ }
+ if (!str || (blankline && (str[0] != '\0'))) {
+ /* corner case, no input after blankline meaning the pattern to check is empty */
+ if (create_empty_string(&str)) {
+ goto error;
+ }
+ }
+ *strarg = str;
+
+ return 0;
+
+error:
+ free(str);
+ if (*infile) {
+ fclose(*infile);
+ *infile = NULL;
+ }
+ *strarg = NULL;
+
+ return 1;
+}
+
+static char *
+modstr_init(void)
+{
+ const char *module_start = "module yangre {"
+ "yang-version 1.1;"
+ "namespace urn:cesnet:libyang:yangre;"
+ "prefix re;"
+ "leaf pattern {"
+ " type string {";
+
+ return strdup(module_start);
+}
+
+static char *
+modstr_add_pattern(char **modstr, const struct yr_pattern *pattern)
+{
+ char *new;
+ const char *module_invertmatch = " { modifier invert-match; }";
+ const char *module_match = ";";
+
+ if (asprintf(&new, "%s pattern %s%s", *modstr, pattern->expr,
+ pattern->invert ? module_invertmatch : module_match) == -1) {
+ fprintf(stderr, "yangre error: memory allocation failed.\n");
+ return NULL;
+ }
+ free(*modstr);
+ *modstr = NULL;
+
+ return new;
+}
+
+static char *
+modstr_add_ending(char **modstr)
+{
+ char *new;
+ static const char *module_end = "}}}";
+
+ if (asprintf(&new, "%s%s", *modstr, module_end) == -1) {
+ fprintf(stderr, "yangre error: memory allocation failed.\n");
+ return NULL;
+ }
+ free(*modstr);
+ *modstr = NULL;
+
+ return new;
+}
+
+static int
+create_module(struct yr_pattern *patterns, int patterns_count, char **mod)
+{
+ int i;
+ char *new = NULL, *modstr;
+
+ if (!(modstr = modstr_init())) {
+ goto error;
+ }
+
+ for (i = 0; i < patterns_count; i++) {
+ if (!(new = modstr_add_pattern(&modstr, &patterns[i]))) {
+ goto error;
+ }
+ modstr = new;
+ }
+
+ if (!(new = modstr_add_ending(&modstr))) {
+ goto error;
+ }
+
+ *mod = new;
+
+ return 0;
+
+error:
+ *mod = NULL;
+ free(new);
+ free(modstr);
+
+ return 1;
+}
+
+static void
+print_verbose(struct yr_pattern *patterns, int patterns_count, char *str, LY_ERR match)
+{
+ int i;
+
+ for (i = 0; i < patterns_count; i++) {
+ fprintf(stdout, "pattern %d: %s\n", i + 1, patterns[i].expr);
+ fprintf(stdout, "matching %d: %s\n", i + 1, patterns[i].invert ? "inverted" : "regular");
+ }
+ fprintf(stdout, "string : %s\n", str);
+ if (match == LY_SUCCESS) {
+ fprintf(stdout, "result : matching\n");
+ } else if (match == LY_EVALID) {
+ fprintf(stdout, "result : not matching\n");
+ } else {
+ fprintf(stdout, "result : error (%s)\n", ly_last_logmsg());
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ LY_ERR match;
+ int i, opt_index = 0, ret = 1, verbose = 0;
+ struct option options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {"file", required_argument, NULL, 'f'},
+ {"invert-match", no_argument, NULL, 'i'},
+ {"pattern", required_argument, NULL, 'p'},
+ {"version", no_argument, NULL, 'v'},
+ {"verbose", no_argument, NULL, 'V'},
+ {NULL, 0, NULL, 0}
+ };
+ struct yr_pattern *patterns = NULL;
+ char *str = NULL, *modstr = NULL;
+ int patterns_count = 0;
+ struct ly_ctx *ctx = NULL;
+ struct lys_module *mod;
+ FILE *infile = NULL;
+ ly_bool info_printed = 0;
+
+ opterr = 0;
+ while ((i = getopt_long(argc, argv, "hf:ivVp:", options, &opt_index)) != -1) {
+ switch (i) {
+ case 'h':
+ help();
+ info_printed = 1;
+ break;
+ case 'f':
+ if (infile) {
+ help();
+ fprintf(stderr, "yangre error: multiple input files are not supported.\n");
+ goto cleanup;
+ } else if (patterns_count) {
+ help();
+ fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n");
+ goto cleanup;
+ }
+ if (parse_patterns_file(optarg, &infile, &patterns, &patterns_count, &str)) {
+ goto cleanup;
+ }
+ break;
+ case 'i':
+ if (!patterns_count || patterns[patterns_count - 1].invert) {
+ help();
+ fprintf(stderr, "yangre error: invert-match option must follow some pattern.\n");
+ goto cleanup;
+ }
+ patterns[patterns_count - 1].invert = 1;
+ break;
+ case 'p':
+ if (infile) {
+ help();
+ fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n");
+ goto cleanup;
+ }
+ if (add_pattern(&patterns, &patterns_count, optarg)) {
+ goto cleanup;
+ }
+ break;
+ case 'v':
+ version();
+ info_printed = 1;
+ break;
+ case 'V':
+ verbose = 1;
+ break;
+ default:
+ help();
+ if (optopt) {
+ fprintf(stderr, "yangre error: invalid option: -%c\n", optopt);
+ } else {
+ fprintf(stderr, "yangre error: invalid option: %s\n", argv[optind - 1]);
+ }
+ goto cleanup;
+ }
+ }
+
+ if (info_printed) {
+ ret = 0;
+ goto cleanup;
+ }
+
+ if (!str) {
+ /* check options compatibility */
+ if (optind >= argc) {
+ help();
+ fprintf(stderr, "yangre error: missing <string> parameter to process.\n");
+ goto cleanup;
+ } else if (!patterns_count) {
+ help();
+ fprintf(stderr, "yangre error: missing pattern parameter to use.\n");
+ goto cleanup;
+ }
+ str = argv[optind];
+ }
+
+ if (create_module(patterns, patterns_count, &modstr)) {
+ goto cleanup;
+ }
+
+ if (ly_ctx_new(NULL, 0, &ctx)) {
+ goto cleanup;
+ }
+
+ ly_set_log_clb(pattern_error);
+ if (lys_parse_mem(ctx, modstr, LYS_IN_YANG, &mod) || !mod->compiled || !mod->compiled->data) {
+ goto cleanup;
+ }
+
+ /* check the value */
+ match = lyd_value_validate(ctx, mod->compiled->data, str, strlen(str), NULL, NULL, NULL);
+
+ if (verbose) {
+ print_verbose(patterns, patterns_count, str, match);
+ }
+ if (match == LY_SUCCESS) {
+ ret = 0;
+ } else if (match == LY_EVALID) {
+ ret = 2;
+ } else {
+ ret = 1;
+ }
+
+cleanup:
+ ly_ctx_destroy(ctx);
+ for (i = 0; i < patterns_count; i++) {
+ free(patterns[i].expr);
+ }
+ if (patterns_count) {
+ free(patterns);
+ }
+ free(modstr);
+ if (infile) {
+ fclose(infile);
+ free(str);
+ }
+
+ return ret;
+}
diff --git a/tools/re/yangre.1 b/tools/re/yangre.1
new file mode 100644
index 0000000..e7b572b
--- /dev/null
+++ b/tools/re/yangre.1
@@ -0,0 +1,118 @@
+.\" Manpage for yanglint.
+.\" Process this file with
+.\" groff -man -Tascii yangre.1
+.\"
+
+.TH YANGRE 1 "2018-11-09" "libyang"
+.SH NAME
+yangre \- YANG regular expression processor
+.
+.SH SYNOPSIS
+.B yangre
+[\-V] \-p \fIREGEXP\fP [\-i] [\-p \fIREGEXP\fP [\-i]...] \fISTRING\fP
+.br
+.B yangre
+[\-V] \-f \fIFILE\fP
+.
+.SH DESCRIPTION
+\fByangre\fP is a command-line tool to test and evaluate regular expressions
+for use in YANG schemas. Supported regular expressions are defined by the
+W3C's XML-Schema standard.
+
+\fByangre\fP can be used either with regular expressions and a target string
+on the command line or with input from a file. The latter is particularly
+useful to avoid dealing with proper shell escaping of regular expression
+patterns, which can be somewhat tricky.
+.
+.SH GENERAL OPTIONS
+.TP
+.BR "\-h\fR,\fP \-\^\-help"
+.br
+Outputs usage help and exits.
+.TP
+.BR "\-v\fR,\fP \-\^\-version"
+.br
+Outputs the version number and exits.
+.TP
+.BR "\-V\fR,\fP \-\^\-verbose"
+Increases the verbosity level. If not specified, only errors are printed, with
+each appearance it adds: warnings, verbose messages, debug messages (if compiled
+with debug information).
+.SH COMMAND LINE INPUT
+.TP
+.BR "\-p \fIREGEXP\fP\fR,\fP \-\^\-pattern=\fIREGEXP\fP"
+.br
+One or more regular expression patterns to be tested against the input
+string. Supplied expressions are tested in the order they appear on the
+command line. Testing is aborted when an expression does not match (or
+does match, if the \fB-i\fP option is used.)
+.TP
+.BR "\-i\fR,\fP \-\^\-invert-match"
+.br
+Reverse match condition for the previous pattern. If the pattern matches,
+an error is printed and evaluation is aborted.
+.TP
+.BR "\fISTRING\fP"
+.br
+Target text input to match the regular expression(s) against. The same
+text is used for all regular expressions. Note that only the first
+argument is used by \fByangre\fP, if it contains spaces or other shell
+metacharacters they must be properly escaped. Additional arguments are
+silently ignored.
+.SH FILE INPUT
+.TP
+.BR "\-f \fIFILE\fP\fR,\fP \-\^\-file=\fIFILE\fP"
+Read both patterns and target text from the specified input file.
+
+\fIFILE\fP must consist of one or more YANG regular expressions, each on
+their own line, followed by a blank line and one line of target text. No
+preprocessing is done on file input, there are no comment lines and
+whitespace is not stripped. A single space character at the beginning of
+a pattern line inverts the match condition for the pattern on that line.
+Patterns must still be properly quoted as mandated by the YANG standard.
+.SH RETURN VALUES
+.TP
+0
+.I Successful match
+.br
+The target text matched for all patterns.
+.TP
+1
+.I Pattern mismatch
+.br
+One or more patterns did not match the target text. An error message is
+printed to stderr describing which pattern was the first not to match.
+.TP
+255
+.I Other error
+.br
+One or more patterns could not be processed or some other error occurred that
+precluded processing.
+.SH EXAMPLES
+.IP \[bu] 2
+Test a single pattern:
+ yangre -p 'te.*xt' text_text
+.IP \[bu]
+Test multiple patterns:
+ yangre -p '.*pat1' -p 'pat2.*' -p 'notpat' -i pat2testpat1
+.IP \[bu]
+Input from a file:
+ cat > /tmp/patterns <<EOF
+ .*pat1
+ pat2.*
+ notpat
+
+ pat2testpat1
+ EOF
+ yangre -f /tmp/patterns
+
+.SH SEE ALSO
+https://github.com/CESNET/libyang (libyang homepage and Git repository)
+.
+.SH AUTHORS
+Radek Krejci <rkrejci@cesnet.cz>, Michal Vasko <mvasko@cesnet.cz>
+.br
+This man page was written by David Lamparter <equinox@diac24.net>
+.
+.SH COPYRIGHT
+Copyright \(co 2015-2018 CESNET, a.l.e.