blob: 701b0a1df90b7209cc497cb9ff8d742d21baf7f6 [file] [log] [blame]
/**
* @file main.c
* @author Radek Krejci <rkrejci@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
#define _POSIX_C_SOURCE 200809L /* strdup, getline */
#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"
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), 1 if not and -1 on error.\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 *path)
{
(void) path; /* unused */
if (level == LY_LLERR) {
fprintf(stderr, "yangre error: %s\n", msg);
}
}
static const char *module_start = "module yangre {"
"yang-version 1.1;"
"namespace urn:cesnet:libyang:yangre;"
"prefix re;"
"leaf pattern {"
" type string {";
static const char *module_invertmatch = " { modifier invert-match; }";
static const char *module_match = ";";
static const char *module_end = "}}}";
static int
add_pattern(char ***patterns, int **inverts, int *counter, char *pattern)
{
void *reallocated1, *reallocated2;
(*counter)++;
reallocated1 = realloc(*patterns, *counter * sizeof **patterns);
reallocated2 = realloc(*inverts, *counter * sizeof **inverts);
if (!reallocated1 || !reallocated2) {
fprintf(stderr, "yangre error: memory allocation error.\n");
free(reallocated1);
free(reallocated2);
return EXIT_FAILURE;
}
(*patterns) = reallocated1;
(*patterns)[*counter - 1] = strdup(pattern);
(*inverts) = reallocated2;
(*inverts)[*counter - 1] = 0;
return EXIT_SUCCESS;
}
int
main(int argc, char *argv[])
{
LY_ERR match;
int i, opt_index = 0, ret = -1, verbose = 0, blankline = 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}
};
char **patterns = NULL, *str = NULL, *modstr = NULL, *s;
int *invert_match = NULL;
int patterns_count = 0;
struct ly_ctx *ctx = NULL;
const struct lys_module *mod;
FILE *infile = NULL;
size_t len = 0;
ssize_t l;
opterr = 0;
while ((i = getopt_long(argc, argv, "hf:ivVp:", options, &opt_index)) != -1) {
switch (i) {
case 'h':
help();
ret = -2; /* continue to allow printing version and help at once */
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;
}
infile = fopen(optarg, "r");
if (!infile) {
fprintf(stderr, "yangre error: unable to open input file %s (%s).\n", optarg, strerror(errno));
goto cleanup;
}
while ((l = getline(&str, &len, infile)) != -1) {
if (!blankline && (str[0] == '\n')) {
/* blank line */
blankline = 1;
continue;
}
if ((str[0] != '\n') && (str[l - 1] == '\n')) {
/* remove ending newline */
str[l - 1] = '\0';
}
if (blankline) {
/* done - str is now the string to check */
blankline = 0;
break;
/* else read the patterns */
} else if (add_pattern(&patterns, &invert_match, &patterns_count,
(str[0] == ' ') ? &str[1] : str)) {
goto cleanup;
}
if (str[0] == ' ') {
/* set invert-match */
invert_match[patterns_count - 1] = 1;
}
}
if (blankline) {
/* corner case, no input after blankline meaning the pattern to check is empty */
if (str != NULL) {
free(str);
}
str = malloc(sizeof(char));
str[0] = '\0';
}
break;
case 'i':
if (!patterns_count || invert_match[patterns_count - 1]) {
help();
fprintf(stderr, "yangre error: invert-match option must follow some pattern.\n");
goto cleanup;
}
invert_match[patterns_count - 1] = 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, &invert_match, &patterns_count, optarg)) {
goto cleanup;
}
break;
case 'v':
version();
ret = -2; /* continue to allow printing version and help at once */
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 (ret == -2) {
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];
}
for (modstr = (char *)module_start, i = 0; i < patterns_count; i++) {
if (asprintf(&s, "%s pattern %s%s", modstr, patterns[i], invert_match[i] ? module_invertmatch : module_match) == -1) {
fprintf(stderr, "yangre error: memory allocation failed.\n");
goto cleanup;
}
if (modstr != module_start) {
free(modstr);
}
modstr = s;
}
if (asprintf(&s, "%s%s", modstr, module_end) == -1) {
fprintf(stderr, "yangre error: memory allocation failed.\n");
goto cleanup;
}
if (modstr != module_start) {
free(modstr);
}
modstr = s;
if (ly_ctx_new(NULL, 0, &ctx)) {
goto cleanup;
}
ly_set_log_clb(pattern_error, 0);
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) {
for (i = 0; i < patterns_count; i++) {
fprintf(stdout, "pattern %d: %s\n", i + 1, patterns[i]);
fprintf(stdout, "matching %d: %s\n", i + 1, invert_match[i] ? "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_errmsg(ctx));
}
}
if (match == LY_SUCCESS) {
ret = 0;
} else if (match == LY_EVALID) {
ret = 1;
} else {
ret = -1;
}
cleanup:
ly_ctx_destroy(ctx, NULL);
for (i = 0; i < patterns_count; i++) {
free(patterns[i]);
}
free(patterns);
free(invert_match);
free(modstr);
if (infile) {
fclose(infile);
free(str);
}
return ret;
}