diff options
Diffstat (limited to 'tools/re/main.c')
-rw-r--r-- | tools/re/main.c | 461 |
1 files changed, 461 insertions, 0 deletions
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; +} |