blob: 7a7ec517a1a3ec3067813f28747af3d41dc71eef [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 */
Radek Krejci5819f7c2019-05-31 14:53:29 +020016
aPieceke29b1642023-06-30 10:28:14 +020017#include <assert.h>
Radek Krejci5819f7c2019-05-31 14:53:29 +020018#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");
aPiecekbfdf83b2023-06-29 13:55:09 +020038 fprintf(stdout, "Returns 0 if string matches the pattern(s) or if otherwise successful.\n");
39 fprintf(stdout, "Returns 1 on error.\n");
40 fprintf(stdout, "Returns 2 if string does not match the pattern(s).\n\n");
Radek Krejci5819f7c2019-05-31 14:53:29 +020041 fprintf(stdout, "Options:\n"
Radek Krejci4001a102020-11-13 15:43:05 +010042 " -h, --help Show this help message and exit.\n"
43 " -v, --version Show version number and exit.\n"
44 " -V, --verbose Print the processing information.\n"
45 " -i, --invert-match Invert-match modifier for the closest preceding\n"
46 " pattern.\n"
47 " -p, --pattern=\"REGEXP\" Regular expression including the quoting,\n"
48 " which is applied the same way as in a YANG module.\n"
49 " -f, --file=\"FILE\" List of patterns and the <string> (separated by an\n"
50 " empty line) are taken from <file>. Invert-match is\n"
51 " indicated by the single space character at the \n"
52 " beginning of the pattern line. YANG quotation around\n"
53 " patterns is still expected, but that avoids issues with\n"
54 " reading quotation by shell. Avoid newline at the end\n"
55 " of the string line to represent empty <string>.");
Radek Krejci5819f7c2019-05-31 14:53:29 +020056 fprintf(stdout, "Examples:\n"
Radek Krejci4001a102020-11-13 15:43:05 +010057 " pattern \"[0-9a-fA-F]*\"; -> yangre -p '\"[0-9a-fA-F]*\"' '1F'\n"
58 " pattern '[a-zA-Z0-9\\-_.]*'; -> yangre -p \"'[a-zA-Z0-9\\-_.]*'\" 'a-b'\n"
59 " pattern [xX][mM][lL].*; -> yangre -p '[xX][mM][lL].*' 'xml-encoding'\n\n");
Radek Krejci5819f7c2019-05-31 14:53:29 +020060 fprintf(stdout, "Note that to pass YANG quoting through your shell, you are supposed to use\n"
Radek Krejci4001a102020-11-13 15:43:05 +010061 "the other quotation around. For not-quoted patterns, use single quotes.\n\n");
Radek Krejci5819f7c2019-05-31 14:53:29 +020062}
63
64void
65version(void)
66{
67 fprintf(stdout, "yangre %s\n", PROJECT_VERSION);
68}
69
70void
71pattern_error(LY_LOG_LEVEL level, const char *msg, const char *path)
72{
73 (void) path; /* unused */
74
Radek Krejcid7857262020-11-06 10:29:22 +010075 if (level == LY_LLERR) {
Radek Krejci5819f7c2019-05-31 14:53:29 +020076 fprintf(stderr, "yangre error: %s\n", msg);
77 }
78}
79
Radek Krejci5819f7c2019-05-31 14:53:29 +020080static int
81add_pattern(char ***patterns, int **inverts, int *counter, char *pattern)
82{
83 void *reallocated1, *reallocated2;
aPiecekb2b51362023-06-22 09:14:59 +020084 int orig_counter;
Radek Krejci5819f7c2019-05-31 14:53:29 +020085
aPiecekb2b51362023-06-22 09:14:59 +020086 /* Store the original number of items. */
87 orig_counter = *counter;
88
89 /* Reallocate 'patterns' memory with additional space. */
90 reallocated1 = realloc(*patterns, (orig_counter + 1) * sizeof **patterns);
91 if (!reallocated1) {
92 goto error;
Radek Krejci5819f7c2019-05-31 14:53:29 +020093 }
94 (*patterns) = reallocated1;
aPiecekb2b51362023-06-22 09:14:59 +020095 /* Allocated memory is now larger. */
96 (*counter)++;
97 /* Copy the pattern and store it to the additonal space. */
98 (*patterns)[orig_counter] = strdup(pattern);
99 if (!(*patterns)[orig_counter]) {
100 goto error;
101 }
102
103 /* Reallocate 'inverts' memory with additional space. */
104 reallocated2 = realloc(*inverts, (orig_counter + 1) * sizeof **inverts);
105 if (!reallocated2) {
106 goto error;
107 }
Radek Krejci5819f7c2019-05-31 14:53:29 +0200108 (*inverts) = reallocated2;
aPiecekb2b51362023-06-22 09:14:59 +0200109 (*inverts)[orig_counter] = 0;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200110
aPiecekbfdf83b2023-06-29 13:55:09 +0200111 return 0;
aPiecekb2b51362023-06-22 09:14:59 +0200112
113error:
114 fprintf(stderr, "yangre error: memory allocation error.\n");
aPiecekbfdf83b2023-06-29 13:55:09 +0200115 return 1;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200116}
117
aPieceke29b1642023-06-30 10:28:14 +0200118/**
119 * @brief Open the @p filepath, parse patterns and given string-argument.
120 *
121 * @param[in] filepath File to parse. Contains patterns and string.
122 * @param[out] infile The file descriptor of @p filepath.
123 * @param[out] patterns Storage of patterns.
124 * @param[out] invert_match Array of flags indicates which patterns are inverted.
125 * @param[out] patterns_count Number of items in @p patterns (and @p invert_match too).
126 * @param[out] strarg The string-argument to check.
127 * @return 0 on success.
128 */
129static int
130parse_patterns_file(const char *filepath, FILE **infile, char ***patterns, int **invert_match, int *patterns_count,
131 char **strarg)
132{
133 int blankline = 0;
134 char *str = NULL;
135 size_t len = 0;
136 ssize_t l;
137
138 *infile = fopen(filepath, "rb");
139 if (!(*infile)) {
140 fprintf(stderr, "yangre error: unable to open input file %s (%s).\n", optarg, strerror(errno));
141 goto error;
142 }
143
144 while ((l = getline(&str, &len, *infile)) != -1) {
145 if (!blankline && (str[0] == '\n')) {
146 /* blank line */
147 blankline = 1;
148 continue;
149 }
150 if ((str[0] != '\n') && (str[l - 1] == '\n')) {
151 /* remove ending newline */
152 str[l - 1] = '\0';
153 }
154 if (blankline) {
155 /* done - str is now the string to check */
156 blankline = 0;
157 *strarg = str;
158 break;
159 /* else read the patterns */
160 } else if (add_pattern(patterns, invert_match, patterns_count,
161 (str[0] == ' ') ? &str[1] : str)) {
162 goto error;
163 }
164 if (str[0] == ' ') {
165 /* set invert-match */
166 (*invert_match)[*patterns_count - 1] = 1;
167 }
168 }
169 assert(str);
170 if (blankline && (str[0] != '\0')) {
171 /* corner case, no input after blankline meaning the pattern to check is empty */
172 free(str);
173 str = malloc(sizeof(char));
174 if (!str) {
175 fprintf(stderr, "yangre error: memory allocation failed.\n");
176 goto error;
177 }
178 str[0] = '\0';
179 }
180 *strarg = str;
181
182 return 0;
183
184error:
185 free(str);
186 if (*infile) {
187 fclose(*infile);
188 *infile = NULL;
189 }
190 *strarg = NULL;
191
192 return 1;
193}
194
aPiecekc5cf3712023-06-30 11:27:33 +0200195static char *
196modstr_init(void)
197{
198 const char *module_start = "module yangre {"
199 "yang-version 1.1;"
200 "namespace urn:cesnet:libyang:yangre;"
201 "prefix re;"
202 "leaf pattern {"
203 " type string {";
204
205 return strdup(module_start);
206}
207
208static char *
209modstr_add_pattern(char **modstr, const char *pattern, int invert_match)
210{
211 char *new;
212 const char *module_invertmatch = " { modifier invert-match; }";
213 const char *module_match = ";";
214
215 if (asprintf(&new, "%s pattern %s%s", *modstr, pattern, invert_match ? module_invertmatch : module_match) == -1) {
216 fprintf(stderr, "yangre error: memory allocation failed.\n");
217 return NULL;
218 }
219 free(*modstr);
220 *modstr = NULL;
221
222 return new;
223}
224
225static char *
226modstr_add_ending(char **modstr)
227{
228 char *new;
229 static const char *module_end = "}}}";
230
231 if (asprintf(&new, "%s%s", *modstr, module_end) == -1) {
232 fprintf(stderr, "yangre error: memory allocation failed.\n");
233 return NULL;
234 }
235 free(*modstr);
236 *modstr = NULL;
237
238 return new;
239}
240
241static int
242create_module(char **patterns, int *invert_match, int patterns_count, char **mod)
243{
244 int i;
245 char *new = NULL, *modstr;
246
247 if (!(modstr = modstr_init())) {
248 goto error;
249 }
250
251 for (i = 0; i < patterns_count; i++) {
252 if (!(new = modstr_add_pattern(&modstr, patterns[i], invert_match[i]))) {
253 goto error;
254 }
255 modstr = new;
256 }
257
258 if (!(new = modstr_add_ending(&modstr))) {
259 goto error;
260 }
261
262 *mod = new;
263
264 return 0;
265
266error:
267 *mod = NULL;
268 free(new);
269 free(modstr);
270
271 return 1;
272}
273
aPiecek44ffabc2023-06-30 11:32:11 +0200274static void
275print_verbose(struct ly_ctx *ctx, char **patterns, int *invert_match, int patterns_count, char *str, LY_ERR match)
276{
277 int i;
278
279 for (i = 0; i < patterns_count; i++) {
280 fprintf(stdout, "pattern %d: %s\n", i + 1, patterns[i]);
281 fprintf(stdout, "matching %d: %s\n", i + 1, invert_match[i] ? "inverted" : "regular");
282 }
283 fprintf(stdout, "string : %s\n", str);
284 if (match == LY_SUCCESS) {
285 fprintf(stdout, "result : matching\n");
286 } else if (match == LY_EVALID) {
287 fprintf(stdout, "result : not matching\n");
288 } else {
289 fprintf(stdout, "result : error (%s)\n", ly_errmsg(ctx));
290 }
291}
292
Radek Krejci5819f7c2019-05-31 14:53:29 +0200293int
Radek Krejci4001a102020-11-13 15:43:05 +0100294main(int argc, char *argv[])
Radek Krejci5819f7c2019-05-31 14:53:29 +0200295{
296 LY_ERR match;
aPieceke29b1642023-06-30 10:28:14 +0200297 int i, opt_index = 0, ret = 1, verbose = 0;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200298 struct option options[] = {
299 {"help", no_argument, NULL, 'h'},
300 {"file", required_argument, NULL, 'f'},
301 {"invert-match", no_argument, NULL, 'i'},
302 {"pattern", required_argument, NULL, 'p'},
303 {"version", no_argument, NULL, 'v'},
304 {"verbose", no_argument, NULL, 'V'},
305 {NULL, 0, NULL, 0}
306 };
aPiecekc5cf3712023-06-30 11:27:33 +0200307 char **patterns = NULL, *str = NULL, *modstr = NULL;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200308 int *invert_match = NULL;
309 int patterns_count = 0;
310 struct ly_ctx *ctx = NULL;
Michal Vasko4de7d072021-07-09 09:13:18 +0200311 struct lys_module *mod;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200312 FILE *infile = NULL;
aPiecekbfdf83b2023-06-29 13:55:09 +0200313 ly_bool info_printed = 0;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200314
315 opterr = 0;
316 while ((i = getopt_long(argc, argv, "hf:ivVp:", options, &opt_index)) != -1) {
317 switch (i) {
318 case 'h':
319 help();
aPiecekbfdf83b2023-06-29 13:55:09 +0200320 info_printed = 1;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200321 break;
322 case 'f':
323 if (infile) {
324 help();
325 fprintf(stderr, "yangre error: multiple input files are not supported.\n");
326 goto cleanup;
327 } else if (patterns_count) {
328 help();
329 fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n");
330 goto cleanup;
331 }
aPieceke29b1642023-06-30 10:28:14 +0200332 if (parse_patterns_file(optarg, &infile, &patterns, &invert_match, &patterns_count, &str)) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200333 goto cleanup;
334 }
Radek Krejci5819f7c2019-05-31 14:53:29 +0200335 break;
336 case 'i':
337 if (!patterns_count || invert_match[patterns_count - 1]) {
338 help();
339 fprintf(stderr, "yangre error: invert-match option must follow some pattern.\n");
340 goto cleanup;
341 }
342 invert_match[patterns_count - 1] = 1;
343 break;
344 case 'p':
345 if (infile) {
346 help();
347 fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n");
348 goto cleanup;
349 }
350 if (add_pattern(&patterns, &invert_match, &patterns_count, optarg)) {
351 goto cleanup;
352 }
353 break;
354 case 'v':
355 version();
aPiecekbfdf83b2023-06-29 13:55:09 +0200356 info_printed = 1;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200357 break;
358 case 'V':
359 verbose = 1;
360 break;
361 default:
362 help();
363 if (optopt) {
364 fprintf(stderr, "yangre error: invalid option: -%c\n", optopt);
365 } else {
366 fprintf(stderr, "yangre error: invalid option: %s\n", argv[optind - 1]);
367 }
368 goto cleanup;
369 }
370 }
371
aPiecekbfdf83b2023-06-29 13:55:09 +0200372 if (info_printed) {
373 ret = 0;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200374 goto cleanup;
375 }
376
377 if (!str) {
378 /* check options compatibility */
379 if (optind >= argc) {
380 help();
381 fprintf(stderr, "yangre error: missing <string> parameter to process.\n");
382 goto cleanup;
383 } else if (!patterns_count) {
384 help();
385 fprintf(stderr, "yangre error: missing pattern parameter to use.\n");
386 goto cleanup;
387 }
388 str = argv[optind];
389 }
390
aPiecekc5cf3712023-06-30 11:27:33 +0200391 if (create_module(patterns, invert_match, patterns_count, &modstr)) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200392 goto cleanup;
393 }
Radek Krejci5819f7c2019-05-31 14:53:29 +0200394
395 if (ly_ctx_new(NULL, 0, &ctx)) {
396 goto cleanup;
397 }
398
399 ly_set_log_clb(pattern_error, 0);
Michal Vasko3a41dff2020-07-15 14:30:28 +0200400 if (lys_parse_mem(ctx, modstr, LYS_IN_YANG, &mod) || !mod->compiled || !mod->compiled->data) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200401 goto cleanup;
402 }
403
Radek Krejcid7857262020-11-06 10:29:22 +0100404 /* check the value */
Michal Vaskoaebbce02021-04-06 09:23:37 +0200405 match = lyd_value_validate(ctx, mod->compiled->data, str, strlen(str), NULL, NULL, NULL);
Radek Krejcid7857262020-11-06 10:29:22 +0100406
Radek Krejci5819f7c2019-05-31 14:53:29 +0200407 if (verbose) {
aPiecek44ffabc2023-06-30 11:32:11 +0200408 print_verbose(ctx, patterns, invert_match, patterns_count, str, match);
Radek Krejci5819f7c2019-05-31 14:53:29 +0200409 }
410 if (match == LY_SUCCESS) {
411 ret = 0;
412 } else if (match == LY_EVALID) {
aPiecekbfdf83b2023-06-29 13:55:09 +0200413 ret = 2;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200414 } else {
aPiecekbfdf83b2023-06-29 13:55:09 +0200415 ret = 1;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200416 }
417
418cleanup:
Radek Krejci90ed21e2021-04-12 14:47:46 +0200419 ly_ctx_destroy(ctx);
Radek Krejci5819f7c2019-05-31 14:53:29 +0200420 for (i = 0; i < patterns_count; i++) {
421 free(patterns[i]);
422 }
423 free(patterns);
424 free(invert_match);
aPiecekc5cf3712023-06-30 11:27:33 +0200425 free(modstr);
Radek Krejci5819f7c2019-05-31 14:53:29 +0200426 if (infile) {
427 fclose(infile);
428 free(str);
429 }
Radek Krejci5819f7c2019-05-31 14:53:29 +0200430
431 return ret;
432}