diff options
Diffstat (limited to 'tools/re')
-rw-r--r-- | tools/re/CMakeLists.txt | 20 | ||||
-rw-r--r-- | tools/re/main.c | 461 | ||||
-rw-r--r-- | tools/re/yangre.1 | 118 |
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. |