/** * @file main.c * @author Radek Krejci * @author Adam Piecek * @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 #include #include #include #include #include #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 [-i] [-p [-i] ...] \n"); fprintf(stdout, " yangre [-V] -f \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 (separated by an\n" " empty line) are taken from . 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 ."); 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 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; }