blob: b512ad8047dc466c980a1248a175bffd7abe6cdf [file] [log] [blame]
Radek Krejci5819f7c2019-05-31 14:53:29 +02001/**
2 * @file main.c
3 * @author Radek Krejci <rkrejci@cesnet.cz>
4 * @brief libyang's YANG Regular Expression tool
5 *
6 * Copyright (c) 2017 CESNET, z.s.p.o.
7 *
8 * This source code is licensed under BSD 3-Clause License (the "License").
9 * You may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * https://opensource.org/licenses/BSD-3-Clause
13 */
14
Christian Hopps32874e12021-05-01 09:43:54 -040015#define _GNU_SOURCE /* asprintf, strdup */
16#include <sys/cdefs.h>
Radek Krejci5819f7c2019-05-31 14:53:29 +020017
18#include <errno.h>
Radek Krejci4001a102020-11-13 15:43:05 +010019#include <getopt.h>
Radek Krejci5819f7c2019-05-31 14:53:29 +020020#include <stdio.h>
21#include <stdlib.h>
Radek Krejci5819f7c2019-05-31 14:53:29 +020022#include <string.h>
Radek Krejci4001a102020-11-13 15:43:05 +010023#include <sys/types.h>
Radek Krejci5819f7c2019-05-31 14:53:29 +020024
Radek Krejci535ea9f2020-05-29 16:01:05 +020025#include "libyang.h"
26
Michal Vasko5aa44c02020-06-29 11:47:02 +020027#include "compat.h"
Radek Krejci4001a102020-11-13 15:43:05 +010028#include "tools/config.h"
Radek Krejci5819f7c2019-05-31 14:53:29 +020029
30void
31help(void)
32{
33 fprintf(stdout, "YANG Regular Expressions processor.\n");
34 fprintf(stdout, "Usage:\n");
35 fprintf(stdout, " yangre [-hv]\n");
36 fprintf(stdout, " yangre [-V] -p <regexp1> [-i] [-p <regexp2> [-i] ...] <string>\n");
37 fprintf(stdout, " yangre [-V] -f <file>\n");
38 fprintf(stdout, "Returns 0 if string matches the pattern(s), 1 if not and -1 on error.\n\n");
39 fprintf(stdout, "Options:\n"
Radek Krejci4001a102020-11-13 15:43:05 +010040 " -h, --help Show this help message and exit.\n"
41 " -v, --version Show version number and exit.\n"
42 " -V, --verbose Print the processing information.\n"
43 " -i, --invert-match Invert-match modifier for the closest preceding\n"
44 " pattern.\n"
45 " -p, --pattern=\"REGEXP\" Regular expression including the quoting,\n"
46 " which is applied the same way as in a YANG module.\n"
47 " -f, --file=\"FILE\" List of patterns and the <string> (separated by an\n"
48 " empty line) are taken from <file>. Invert-match is\n"
49 " indicated by the single space character at the \n"
50 " beginning of the pattern line. YANG quotation around\n"
51 " patterns is still expected, but that avoids issues with\n"
52 " reading quotation by shell. Avoid newline at the end\n"
53 " of the string line to represent empty <string>.");
Radek Krejci5819f7c2019-05-31 14:53:29 +020054 fprintf(stdout, "Examples:\n"
Radek Krejci4001a102020-11-13 15:43:05 +010055 " pattern \"[0-9a-fA-F]*\"; -> yangre -p '\"[0-9a-fA-F]*\"' '1F'\n"
56 " pattern '[a-zA-Z0-9\\-_.]*'; -> yangre -p \"'[a-zA-Z0-9\\-_.]*'\" 'a-b'\n"
57 " pattern [xX][mM][lL].*; -> yangre -p '[xX][mM][lL].*' 'xml-encoding'\n\n");
Radek Krejci5819f7c2019-05-31 14:53:29 +020058 fprintf(stdout, "Note that to pass YANG quoting through your shell, you are supposed to use\n"
Radek Krejci4001a102020-11-13 15:43:05 +010059 "the other quotation around. For not-quoted patterns, use single quotes.\n\n");
Radek Krejci5819f7c2019-05-31 14:53:29 +020060}
61
62void
63version(void)
64{
65 fprintf(stdout, "yangre %s\n", PROJECT_VERSION);
66}
67
68void
69pattern_error(LY_LOG_LEVEL level, const char *msg, const char *path)
70{
71 (void) path; /* unused */
72
Radek Krejcid7857262020-11-06 10:29:22 +010073 if (level == LY_LLERR) {
Radek Krejci5819f7c2019-05-31 14:53:29 +020074 fprintf(stderr, "yangre error: %s\n", msg);
75 }
76}
77
78static const char *module_start = "module yangre {"
Radek Krejci4001a102020-11-13 15:43:05 +010079 "yang-version 1.1;"
80 "namespace urn:cesnet:libyang:yangre;"
81 "prefix re;"
82 "leaf pattern {"
83 " type string {";
Radek Krejci5819f7c2019-05-31 14:53:29 +020084static const char *module_invertmatch = " { modifier invert-match; }";
85static const char *module_match = ";";
86static const char *module_end = "}}}";
87
88static int
89add_pattern(char ***patterns, int **inverts, int *counter, char *pattern)
90{
91 void *reallocated1, *reallocated2;
92
93 (*counter)++;
94 reallocated1 = realloc(*patterns, *counter * sizeof **patterns);
95 reallocated2 = realloc(*inverts, *counter * sizeof **inverts);
96 if (!reallocated1 || !reallocated2) {
97 fprintf(stderr, "yangre error: memory allocation error.\n");
98 free(reallocated1);
99 free(reallocated2);
100 return EXIT_FAILURE;
101 }
102 (*patterns) = reallocated1;
103 (*patterns)[*counter - 1] = strdup(pattern);
104 (*inverts) = reallocated2;
105 (*inverts)[*counter - 1] = 0;
106
107 return EXIT_SUCCESS;
108}
109
110int
Radek Krejci4001a102020-11-13 15:43:05 +0100111main(int argc, char *argv[])
Radek Krejci5819f7c2019-05-31 14:53:29 +0200112{
113 LY_ERR match;
114 int i, opt_index = 0, ret = -1, verbose = 0, blankline = 0;
115 struct option options[] = {
116 {"help", no_argument, NULL, 'h'},
117 {"file", required_argument, NULL, 'f'},
118 {"invert-match", no_argument, NULL, 'i'},
119 {"pattern", required_argument, NULL, 'p'},
120 {"version", no_argument, NULL, 'v'},
121 {"verbose", no_argument, NULL, 'V'},
122 {NULL, 0, NULL, 0}
123 };
124 char **patterns = NULL, *str = NULL, *modstr = NULL, *s;
125 int *invert_match = NULL;
126 int patterns_count = 0;
127 struct ly_ctx *ctx = NULL;
128 const struct lys_module *mod;
129 FILE *infile = NULL;
130 size_t len = 0;
131 ssize_t l;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200132
133 opterr = 0;
134 while ((i = getopt_long(argc, argv, "hf:ivVp:", options, &opt_index)) != -1) {
135 switch (i) {
136 case 'h':
137 help();
138 ret = -2; /* continue to allow printing version and help at once */
139 break;
140 case 'f':
141 if (infile) {
142 help();
143 fprintf(stderr, "yangre error: multiple input files are not supported.\n");
144 goto cleanup;
145 } else if (patterns_count) {
146 help();
147 fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n");
148 goto cleanup;
149 }
150 infile = fopen(optarg, "r");
151 if (!infile) {
152 fprintf(stderr, "yangre error: unable to open input file %s (%s).\n", optarg, strerror(errno));
153 goto cleanup;
154 }
155
Radek Krejci4001a102020-11-13 15:43:05 +0100156 while ((l = getline(&str, &len, infile)) != -1) {
157 if (!blankline && (str[0] == '\n')) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200158 /* blank line */
159 blankline = 1;
160 continue;
161 }
Radek Krejci4001a102020-11-13 15:43:05 +0100162 if ((str[0] != '\n') && (str[l - 1] == '\n')) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200163 /* remove ending newline */
164 str[l - 1] = '\0';
165 }
166 if (blankline) {
167 /* done - str is now the string to check */
168 blankline = 0;
169 break;
170 /* else read the patterns */
171 } else if (add_pattern(&patterns, &invert_match, &patterns_count,
Radek Krejci4001a102020-11-13 15:43:05 +0100172 (str[0] == ' ') ? &str[1] : str)) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200173 goto cleanup;
174 }
175 if (str[0] == ' ') {
176 /* set invert-match */
177 invert_match[patterns_count - 1] = 1;
178 }
179 }
180 if (blankline) {
181 /* corner case, no input after blankline meaning the pattern to check is empty */
182 if (str != NULL) {
183 free(str);
184 }
185 str = malloc(sizeof(char));
186 str[0] = '\0';
187 }
188 break;
189 case 'i':
190 if (!patterns_count || invert_match[patterns_count - 1]) {
191 help();
192 fprintf(stderr, "yangre error: invert-match option must follow some pattern.\n");
193 goto cleanup;
194 }
195 invert_match[patterns_count - 1] = 1;
196 break;
197 case 'p':
198 if (infile) {
199 help();
200 fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n");
201 goto cleanup;
202 }
203 if (add_pattern(&patterns, &invert_match, &patterns_count, optarg)) {
204 goto cleanup;
205 }
206 break;
207 case 'v':
208 version();
209 ret = -2; /* continue to allow printing version and help at once */
210 break;
211 case 'V':
212 verbose = 1;
213 break;
214 default:
215 help();
216 if (optopt) {
217 fprintf(stderr, "yangre error: invalid option: -%c\n", optopt);
218 } else {
219 fprintf(stderr, "yangre error: invalid option: %s\n", argv[optind - 1]);
220 }
221 goto cleanup;
222 }
223 }
224
225 if (ret == -2) {
226 goto cleanup;
227 }
228
229 if (!str) {
230 /* check options compatibility */
231 if (optind >= argc) {
232 help();
233 fprintf(stderr, "yangre error: missing <string> parameter to process.\n");
234 goto cleanup;
235 } else if (!patterns_count) {
236 help();
237 fprintf(stderr, "yangre error: missing pattern parameter to use.\n");
238 goto cleanup;
239 }
240 str = argv[optind];
241 }
242
Radek Krejci4001a102020-11-13 15:43:05 +0100243 for (modstr = (char *)module_start, i = 0; i < patterns_count; i++) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200244 if (asprintf(&s, "%s pattern %s%s", modstr, patterns[i], invert_match[i] ? module_invertmatch : module_match) == -1) {
245 fprintf(stderr, "yangre error: memory allocation failed.\n");
246 goto cleanup;
247 }
248 if (modstr != module_start) {
249 free(modstr);
250 }
251 modstr = s;
252 }
253 if (asprintf(&s, "%s%s", modstr, module_end) == -1) {
254 fprintf(stderr, "yangre error: memory allocation failed.\n");
255 goto cleanup;
256 }
257 if (modstr != module_start) {
258 free(modstr);
259 }
260 modstr = s;
261
262 if (ly_ctx_new(NULL, 0, &ctx)) {
263 goto cleanup;
264 }
265
266 ly_set_log_clb(pattern_error, 0);
Michal Vasko3a41dff2020-07-15 14:30:28 +0200267 if (lys_parse_mem(ctx, modstr, LYS_IN_YANG, &mod) || !mod->compiled || !mod->compiled->data) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200268 goto cleanup;
269 }
270
Radek Krejcid7857262020-11-06 10:29:22 +0100271 /* check the value */
Michal Vaskoaebbce02021-04-06 09:23:37 +0200272 match = lyd_value_validate(ctx, mod->compiled->data, str, strlen(str), NULL, NULL, NULL);
Radek Krejcid7857262020-11-06 10:29:22 +0100273
Radek Krejci5819f7c2019-05-31 14:53:29 +0200274 if (verbose) {
275 for (i = 0; i < patterns_count; i++) {
276 fprintf(stdout, "pattern %d: %s\n", i + 1, patterns[i]);
277 fprintf(stdout, "matching %d: %s\n", i + 1, invert_match[i] ? "inverted" : "regular");
278 }
279 fprintf(stdout, "string : %s\n", str);
280 if (match == LY_SUCCESS) {
281 fprintf(stdout, "result : matching\n");
282 } else if (match == LY_EVALID) {
283 fprintf(stdout, "result : not matching\n");
284 } else {
Radek Krejcid7857262020-11-06 10:29:22 +0100285 fprintf(stdout, "result : error (%s)\n", ly_errmsg(ctx));
Radek Krejci5819f7c2019-05-31 14:53:29 +0200286 }
287 }
288 if (match == LY_SUCCESS) {
289 ret = 0;
290 } else if (match == LY_EVALID) {
291 ret = 1;
292 } else {
293 ret = -1;
294 }
295
296cleanup:
Radek Krejci90ed21e2021-04-12 14:47:46 +0200297 ly_ctx_destroy(ctx);
Radek Krejci5819f7c2019-05-31 14:53:29 +0200298 for (i = 0; i < patterns_count; i++) {
299 free(patterns[i]);
300 }
301 free(patterns);
302 free(invert_match);
303 free(modstr);
304 if (infile) {
305 fclose(infile);
306 free(str);
307 }
Radek Krejci5819f7c2019-05-31 14:53:29 +0200308
309 return ret;
310}