blob: 5e3353645f0c1230f5c1a7d76e1331209e5332e9 [file] [log] [blame]
Radek Krejci5819f7c2019-05-31 14:53:29 +02001/**
2 * @file main.c
3 * @author Radek Krejci <rkrejci@cesnet.cz>
aPiecek834d73b2023-06-30 14:33:02 +02004 * @author Adam Piecek <piecek@cesnet.cz>
Radek Krejci5819f7c2019-05-31 14:53:29 +02005 * @brief libyang's YANG Regular Expression tool
6 *
7 * Copyright (c) 2017 CESNET, z.s.p.o.
8 *
9 * This source code is licensed under BSD 3-Clause License (the "License").
10 * You may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
12 *
13 * https://opensource.org/licenses/BSD-3-Clause
14 */
15
Christian Hopps32874e12021-05-01 09:43:54 -040016#define _GNU_SOURCE /* asprintf, strdup */
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
aPiecek834d73b2023-06-30 14:33:02 +020030struct yr_pattern {
31 char *expr;
32 ly_bool invert;
33};
34
Radek Krejci5819f7c2019-05-31 14:53:29 +020035void
36help(void)
37{
38 fprintf(stdout, "YANG Regular Expressions processor.\n");
39 fprintf(stdout, "Usage:\n");
40 fprintf(stdout, " yangre [-hv]\n");
41 fprintf(stdout, " yangre [-V] -p <regexp1> [-i] [-p <regexp2> [-i] ...] <string>\n");
42 fprintf(stdout, " yangre [-V] -f <file>\n");
aPiecekbfdf83b2023-06-29 13:55:09 +020043 fprintf(stdout, "Returns 0 if string matches the pattern(s) or if otherwise successful.\n");
44 fprintf(stdout, "Returns 1 on error.\n");
45 fprintf(stdout, "Returns 2 if string does not match the pattern(s).\n\n");
Radek Krejci5819f7c2019-05-31 14:53:29 +020046 fprintf(stdout, "Options:\n"
Radek Krejci4001a102020-11-13 15:43:05 +010047 " -h, --help Show this help message and exit.\n"
48 " -v, --version Show version number and exit.\n"
49 " -V, --verbose Print the processing information.\n"
50 " -i, --invert-match Invert-match modifier for the closest preceding\n"
51 " pattern.\n"
52 " -p, --pattern=\"REGEXP\" Regular expression including the quoting,\n"
53 " which is applied the same way as in a YANG module.\n"
54 " -f, --file=\"FILE\" List of patterns and the <string> (separated by an\n"
55 " empty line) are taken from <file>. Invert-match is\n"
56 " indicated by the single space character at the \n"
57 " beginning of the pattern line. YANG quotation around\n"
58 " patterns is still expected, but that avoids issues with\n"
59 " reading quotation by shell. Avoid newline at the end\n"
60 " of the string line to represent empty <string>.");
Radek Krejci5819f7c2019-05-31 14:53:29 +020061 fprintf(stdout, "Examples:\n"
Radek Krejci4001a102020-11-13 15:43:05 +010062 " pattern \"[0-9a-fA-F]*\"; -> yangre -p '\"[0-9a-fA-F]*\"' '1F'\n"
63 " pattern '[a-zA-Z0-9\\-_.]*'; -> yangre -p \"'[a-zA-Z0-9\\-_.]*'\" 'a-b'\n"
64 " pattern [xX][mM][lL].*; -> yangre -p '[xX][mM][lL].*' 'xml-encoding'\n\n");
Radek Krejci5819f7c2019-05-31 14:53:29 +020065 fprintf(stdout, "Note that to pass YANG quoting through your shell, you are supposed to use\n"
Radek Krejci4001a102020-11-13 15:43:05 +010066 "the other quotation around. For not-quoted patterns, use single quotes.\n\n");
Radek Krejci5819f7c2019-05-31 14:53:29 +020067}
68
69void
70version(void)
71{
72 fprintf(stdout, "yangre %s\n", PROJECT_VERSION);
73}
74
75void
76pattern_error(LY_LOG_LEVEL level, const char *msg, const char *path)
77{
78 (void) path; /* unused */
79
Radek Krejcid7857262020-11-06 10:29:22 +010080 if (level == LY_LLERR) {
Radek Krejci5819f7c2019-05-31 14:53:29 +020081 fprintf(stderr, "yangre error: %s\n", msg);
82 }
83}
84
Radek Krejci5819f7c2019-05-31 14:53:29 +020085static int
aPiecek834d73b2023-06-30 14:33:02 +020086add_pattern(struct yr_pattern **patterns, int *counter, char *pattern)
Radek Krejci5819f7c2019-05-31 14:53:29 +020087{
aPiecek834d73b2023-06-30 14:33:02 +020088 void *reallocated;
aPiecekb2b51362023-06-22 09:14:59 +020089 int orig_counter;
Radek Krejci5819f7c2019-05-31 14:53:29 +020090
aPiecekb2b51362023-06-22 09:14:59 +020091 /* Store the original number of items. */
92 orig_counter = *counter;
93
94 /* Reallocate 'patterns' memory with additional space. */
aPiecek834d73b2023-06-30 14:33:02 +020095 reallocated = realloc(*patterns, (orig_counter + 1) * sizeof **patterns);
96 if (!reallocated) {
aPiecekb2b51362023-06-22 09:14:59 +020097 goto error;
Radek Krejci5819f7c2019-05-31 14:53:29 +020098 }
aPiecek834d73b2023-06-30 14:33:02 +020099 (*patterns) = reallocated;
aPiecekb2b51362023-06-22 09:14:59 +0200100 /* Allocated memory is now larger. */
101 (*counter)++;
102 /* Copy the pattern and store it to the additonal space. */
aPiecek834d73b2023-06-30 14:33:02 +0200103 (*patterns)[orig_counter].expr = strdup(pattern);
104 if (!(*patterns)[orig_counter].expr) {
aPiecekb2b51362023-06-22 09:14:59 +0200105 goto error;
106 }
aPiecek834d73b2023-06-30 14:33:02 +0200107 (*patterns)[orig_counter].invert = 0;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200108
aPiecekbfdf83b2023-06-29 13:55:09 +0200109 return 0;
aPiecekb2b51362023-06-22 09:14:59 +0200110
111error:
112 fprintf(stderr, "yangre error: memory allocation error.\n");
aPiecekbfdf83b2023-06-29 13:55:09 +0200113 return 1;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200114}
115
aPiecekac2c2822023-07-04 10:15:56 +0200116static int
117create_empty_string(char **str)
118{
119 free(*str);
120 *str = malloc(sizeof(char));
121 if (!(*str)) {
122 fprintf(stderr, "yangre error: memory allocation failed.\n");
123 return 1;
124 }
125 (*str)[0] = '\0';
126
127 return 0;
128}
129
130static ly_bool
131file_is_empty(FILE *fp)
132{
133 int c;
134
135 c = fgetc(fp);
136 if (c == EOF) {
137 return 1;
138 } else {
139 ungetc(c, fp);
140 return 0;
141 }
142}
143
aPieceke29b1642023-06-30 10:28:14 +0200144/**
145 * @brief Open the @p filepath, parse patterns and given string-argument.
146 *
147 * @param[in] filepath File to parse. Contains patterns and string.
148 * @param[out] infile The file descriptor of @p filepath.
149 * @param[out] patterns Storage of patterns.
aPiecek834d73b2023-06-30 14:33:02 +0200150 * @param[out] patterns_count Number of items in @p patterns.
aPieceke29b1642023-06-30 10:28:14 +0200151 * @param[out] strarg The string-argument to check.
152 * @return 0 on success.
153 */
154static int
aPiecek834d73b2023-06-30 14:33:02 +0200155parse_patterns_file(const char *filepath, FILE **infile, struct yr_pattern **patterns, int *patterns_count, char **strarg)
aPieceke29b1642023-06-30 10:28:14 +0200156{
157 int blankline = 0;
158 char *str = NULL;
159 size_t len = 0;
160 ssize_t l;
161
162 *infile = fopen(filepath, "rb");
163 if (!(*infile)) {
164 fprintf(stderr, "yangre error: unable to open input file %s (%s).\n", optarg, strerror(errno));
165 goto error;
166 }
aPiecekac2c2822023-07-04 10:15:56 +0200167 if (file_is_empty(*infile)) {
168 if (create_empty_string(strarg)) {
169 goto error;
170 }
171 return 0;
172 }
aPieceke29b1642023-06-30 10:28:14 +0200173
174 while ((l = getline(&str, &len, *infile)) != -1) {
lePicif71fd7d2023-07-04 08:07:01 +0200175 if (!blankline && ((str[0] == '\n') || ((str[0] == '\r') && (str[1] == '\n')))) {
aPieceke29b1642023-06-30 10:28:14 +0200176 /* blank line */
177 blankline = 1;
178 continue;
179 }
lePicif71fd7d2023-07-04 08:07:01 +0200180 if ((str[0] != '\n') && (str[0] != '\r') && (str[l - 1] == '\n')) {
aPieceke29b1642023-06-30 10:28:14 +0200181 /* remove ending newline */
lePicif71fd7d2023-07-04 08:07:01 +0200182 if ((l > 1) && (str[l - 2] == '\r') && (str[l - 1] == '\n')) {
183 str[l - 2] = '\0';
184 } else {
185 str[l - 1] = '\0';
186 }
aPieceke29b1642023-06-30 10:28:14 +0200187 }
188 if (blankline) {
189 /* done - str is now the string to check */
190 blankline = 0;
191 *strarg = str;
192 break;
193 /* else read the patterns */
aPiecek834d73b2023-06-30 14:33:02 +0200194 } else if (add_pattern(patterns, patterns_count, (str[0] == ' ') ? &str[1] : str)) {
aPieceke29b1642023-06-30 10:28:14 +0200195 goto error;
196 }
197 if (str[0] == ' ') {
198 /* set invert-match */
aPiecek834d73b2023-06-30 14:33:02 +0200199 (*patterns)[*patterns_count - 1].invert = 1;
aPieceke29b1642023-06-30 10:28:14 +0200200 }
201 }
lePicif71fd7d2023-07-04 08:07:01 +0200202 if (!str || (blankline && (str[0] != '\0'))) {
aPieceke29b1642023-06-30 10:28:14 +0200203 /* corner case, no input after blankline meaning the pattern to check is empty */
aPiecekac2c2822023-07-04 10:15:56 +0200204 if (create_empty_string(&str)) {
aPieceke29b1642023-06-30 10:28:14 +0200205 goto error;
206 }
aPieceke29b1642023-06-30 10:28:14 +0200207 }
208 *strarg = str;
209
210 return 0;
211
212error:
213 free(str);
214 if (*infile) {
215 fclose(*infile);
216 *infile = NULL;
217 }
218 *strarg = NULL;
219
220 return 1;
221}
222
aPiecekc5cf3712023-06-30 11:27:33 +0200223static char *
224modstr_init(void)
225{
226 const char *module_start = "module yangre {"
227 "yang-version 1.1;"
228 "namespace urn:cesnet:libyang:yangre;"
229 "prefix re;"
230 "leaf pattern {"
231 " type string {";
232
233 return strdup(module_start);
234}
235
236static char *
aPiecek834d73b2023-06-30 14:33:02 +0200237modstr_add_pattern(char **modstr, const struct yr_pattern *pattern)
aPiecekc5cf3712023-06-30 11:27:33 +0200238{
239 char *new;
240 const char *module_invertmatch = " { modifier invert-match; }";
241 const char *module_match = ";";
242
aPiecek834d73b2023-06-30 14:33:02 +0200243 if (asprintf(&new, "%s pattern %s%s", *modstr, pattern->expr,
244 pattern->invert ? module_invertmatch : module_match) == -1) {
aPiecekc5cf3712023-06-30 11:27:33 +0200245 fprintf(stderr, "yangre error: memory allocation failed.\n");
246 return NULL;
247 }
248 free(*modstr);
249 *modstr = NULL;
250
251 return new;
252}
253
254static char *
255modstr_add_ending(char **modstr)
256{
257 char *new;
258 static const char *module_end = "}}}";
259
260 if (asprintf(&new, "%s%s", *modstr, module_end) == -1) {
261 fprintf(stderr, "yangre error: memory allocation failed.\n");
262 return NULL;
263 }
264 free(*modstr);
265 *modstr = NULL;
266
267 return new;
268}
269
270static int
aPiecek834d73b2023-06-30 14:33:02 +0200271create_module(struct yr_pattern *patterns, int patterns_count, char **mod)
aPiecekc5cf3712023-06-30 11:27:33 +0200272{
273 int i;
274 char *new = NULL, *modstr;
275
276 if (!(modstr = modstr_init())) {
277 goto error;
278 }
279
280 for (i = 0; i < patterns_count; i++) {
aPiecek834d73b2023-06-30 14:33:02 +0200281 if (!(new = modstr_add_pattern(&modstr, &patterns[i]))) {
aPiecekc5cf3712023-06-30 11:27:33 +0200282 goto error;
283 }
284 modstr = new;
285 }
286
287 if (!(new = modstr_add_ending(&modstr))) {
288 goto error;
289 }
290
291 *mod = new;
292
293 return 0;
294
295error:
296 *mod = NULL;
297 free(new);
298 free(modstr);
299
300 return 1;
301}
302
aPiecek44ffabc2023-06-30 11:32:11 +0200303static void
aPiecek834d73b2023-06-30 14:33:02 +0200304print_verbose(struct ly_ctx *ctx, struct yr_pattern *patterns, int patterns_count, char *str, LY_ERR match)
aPiecek44ffabc2023-06-30 11:32:11 +0200305{
306 int i;
307
308 for (i = 0; i < patterns_count; i++) {
aPiecek834d73b2023-06-30 14:33:02 +0200309 fprintf(stdout, "pattern %d: %s\n", i + 1, patterns[i].expr);
310 fprintf(stdout, "matching %d: %s\n", i + 1, patterns[i].invert ? "inverted" : "regular");
aPiecek44ffabc2023-06-30 11:32:11 +0200311 }
312 fprintf(stdout, "string : %s\n", str);
313 if (match == LY_SUCCESS) {
314 fprintf(stdout, "result : matching\n");
315 } else if (match == LY_EVALID) {
316 fprintf(stdout, "result : not matching\n");
317 } else {
318 fprintf(stdout, "result : error (%s)\n", ly_errmsg(ctx));
319 }
320}
321
Radek Krejci5819f7c2019-05-31 14:53:29 +0200322int
Radek Krejci4001a102020-11-13 15:43:05 +0100323main(int argc, char *argv[])
Radek Krejci5819f7c2019-05-31 14:53:29 +0200324{
325 LY_ERR match;
aPieceke29b1642023-06-30 10:28:14 +0200326 int i, opt_index = 0, ret = 1, verbose = 0;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200327 struct option options[] = {
328 {"help", no_argument, NULL, 'h'},
329 {"file", required_argument, NULL, 'f'},
330 {"invert-match", no_argument, NULL, 'i'},
331 {"pattern", required_argument, NULL, 'p'},
332 {"version", no_argument, NULL, 'v'},
333 {"verbose", no_argument, NULL, 'V'},
334 {NULL, 0, NULL, 0}
335 };
aPiecek834d73b2023-06-30 14:33:02 +0200336 struct yr_pattern *patterns = NULL;
337 char *str = NULL, *modstr = NULL;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200338 int patterns_count = 0;
339 struct ly_ctx *ctx = NULL;
Michal Vasko4de7d072021-07-09 09:13:18 +0200340 struct lys_module *mod;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200341 FILE *infile = NULL;
aPiecekbfdf83b2023-06-29 13:55:09 +0200342 ly_bool info_printed = 0;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200343
344 opterr = 0;
345 while ((i = getopt_long(argc, argv, "hf:ivVp:", options, &opt_index)) != -1) {
346 switch (i) {
347 case 'h':
348 help();
aPiecekbfdf83b2023-06-29 13:55:09 +0200349 info_printed = 1;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200350 break;
351 case 'f':
352 if (infile) {
353 help();
354 fprintf(stderr, "yangre error: multiple input files are not supported.\n");
355 goto cleanup;
356 } else if (patterns_count) {
357 help();
358 fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n");
359 goto cleanup;
360 }
aPiecek834d73b2023-06-30 14:33:02 +0200361 if (parse_patterns_file(optarg, &infile, &patterns, &patterns_count, &str)) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200362 goto cleanup;
363 }
Radek Krejci5819f7c2019-05-31 14:53:29 +0200364 break;
365 case 'i':
aPiecek834d73b2023-06-30 14:33:02 +0200366 if (!patterns_count || patterns[patterns_count - 1].invert) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200367 help();
368 fprintf(stderr, "yangre error: invert-match option must follow some pattern.\n");
369 goto cleanup;
370 }
aPiecek834d73b2023-06-30 14:33:02 +0200371 patterns[patterns_count - 1].invert = 1;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200372 break;
373 case 'p':
374 if (infile) {
375 help();
376 fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n");
377 goto cleanup;
378 }
aPiecek834d73b2023-06-30 14:33:02 +0200379 if (add_pattern(&patterns, &patterns_count, optarg)) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200380 goto cleanup;
381 }
382 break;
383 case 'v':
384 version();
aPiecekbfdf83b2023-06-29 13:55:09 +0200385 info_printed = 1;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200386 break;
387 case 'V':
388 verbose = 1;
389 break;
390 default:
391 help();
392 if (optopt) {
393 fprintf(stderr, "yangre error: invalid option: -%c\n", optopt);
394 } else {
395 fprintf(stderr, "yangre error: invalid option: %s\n", argv[optind - 1]);
396 }
397 goto cleanup;
398 }
399 }
400
aPiecekbfdf83b2023-06-29 13:55:09 +0200401 if (info_printed) {
402 ret = 0;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200403 goto cleanup;
404 }
405
406 if (!str) {
407 /* check options compatibility */
408 if (optind >= argc) {
409 help();
410 fprintf(stderr, "yangre error: missing <string> parameter to process.\n");
411 goto cleanup;
412 } else if (!patterns_count) {
413 help();
414 fprintf(stderr, "yangre error: missing pattern parameter to use.\n");
415 goto cleanup;
416 }
417 str = argv[optind];
418 }
419
aPiecek834d73b2023-06-30 14:33:02 +0200420 if (create_module(patterns, patterns_count, &modstr)) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200421 goto cleanup;
422 }
Radek Krejci5819f7c2019-05-31 14:53:29 +0200423
424 if (ly_ctx_new(NULL, 0, &ctx)) {
425 goto cleanup;
426 }
427
428 ly_set_log_clb(pattern_error, 0);
Michal Vasko3a41dff2020-07-15 14:30:28 +0200429 if (lys_parse_mem(ctx, modstr, LYS_IN_YANG, &mod) || !mod->compiled || !mod->compiled->data) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200430 goto cleanup;
431 }
432
Radek Krejcid7857262020-11-06 10:29:22 +0100433 /* check the value */
Michal Vaskoaebbce02021-04-06 09:23:37 +0200434 match = lyd_value_validate(ctx, mod->compiled->data, str, strlen(str), NULL, NULL, NULL);
Radek Krejcid7857262020-11-06 10:29:22 +0100435
Radek Krejci5819f7c2019-05-31 14:53:29 +0200436 if (verbose) {
aPiecek834d73b2023-06-30 14:33:02 +0200437 print_verbose(ctx, patterns, patterns_count, str, match);
Radek Krejci5819f7c2019-05-31 14:53:29 +0200438 }
439 if (match == LY_SUCCESS) {
440 ret = 0;
441 } else if (match == LY_EVALID) {
aPiecekbfdf83b2023-06-29 13:55:09 +0200442 ret = 2;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200443 } else {
aPiecekbfdf83b2023-06-29 13:55:09 +0200444 ret = 1;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200445 }
446
447cleanup:
Radek Krejci90ed21e2021-04-12 14:47:46 +0200448 ly_ctx_destroy(ctx);
Radek Krejci5819f7c2019-05-31 14:53:29 +0200449 for (i = 0; i < patterns_count; i++) {
aPiecek834d73b2023-06-30 14:33:02 +0200450 free(patterns[i].expr);
Radek Krejci5819f7c2019-05-31 14:53:29 +0200451 }
aPiecek834d73b2023-06-30 14:33:02 +0200452 if (patterns_count) {
453 free(patterns);
454 }
aPiecekc5cf3712023-06-30 11:27:33 +0200455 free(modstr);
Radek Krejci5819f7c2019-05-31 14:53:29 +0200456 if (infile) {
457 fclose(infile);
458 free(str);
459 }
Radek Krejci5819f7c2019-05-31 14:53:29 +0200460
461 return ret;
462}