462 lines
13 KiB
C
462 lines
13 KiB
C
|
/**
|
||
|
* @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;
|
||
|
}
|