blob: 5d1edd5738d215d02b0e0692db67b4fe3e148ad2 [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
Michal Vasko7a266772024-01-23 11:02:38 +010076pattern_error(LY_LOG_LEVEL level, const char *msg, const char *UNUSED(data_path), const char *UNUSED(schema_path),
77 uint64_t UNUSED(line))
Radek Krejci5819f7c2019-05-31 14:53:29 +020078{
Radek Krejcid7857262020-11-06 10:29:22 +010079 if (level == LY_LLERR) {
Radek Krejci5819f7c2019-05-31 14:53:29 +020080 fprintf(stderr, "yangre error: %s\n", msg);
81 }
82}
83
Radek Krejci5819f7c2019-05-31 14:53:29 +020084static int
aPiecek834d73b2023-06-30 14:33:02 +020085add_pattern(struct yr_pattern **patterns, int *counter, char *pattern)
Radek Krejci5819f7c2019-05-31 14:53:29 +020086{
aPiecek834d73b2023-06-30 14:33:02 +020087 void *reallocated;
aPiecekb2b51362023-06-22 09:14:59 +020088 int orig_counter;
Radek Krejci5819f7c2019-05-31 14:53:29 +020089
aPiecekb2b51362023-06-22 09:14:59 +020090 /* Store the original number of items. */
91 orig_counter = *counter;
92
93 /* Reallocate 'patterns' memory with additional space. */
aPiecek834d73b2023-06-30 14:33:02 +020094 reallocated = realloc(*patterns, (orig_counter + 1) * sizeof **patterns);
95 if (!reallocated) {
aPiecekb2b51362023-06-22 09:14:59 +020096 goto error;
Radek Krejci5819f7c2019-05-31 14:53:29 +020097 }
aPiecek834d73b2023-06-30 14:33:02 +020098 (*patterns) = reallocated;
aPiecekb2b51362023-06-22 09:14:59 +020099 /* Allocated memory is now larger. */
100 (*counter)++;
101 /* Copy the pattern and store it to the additonal space. */
aPiecek834d73b2023-06-30 14:33:02 +0200102 (*patterns)[orig_counter].expr = strdup(pattern);
103 if (!(*patterns)[orig_counter].expr) {
aPiecekb2b51362023-06-22 09:14:59 +0200104 goto error;
105 }
aPiecek834d73b2023-06-30 14:33:02 +0200106 (*patterns)[orig_counter].invert = 0;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200107
aPiecekbfdf83b2023-06-29 13:55:09 +0200108 return 0;
aPiecekb2b51362023-06-22 09:14:59 +0200109
110error:
111 fprintf(stderr, "yangre error: memory allocation error.\n");
aPiecekbfdf83b2023-06-29 13:55:09 +0200112 return 1;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200113}
114
aPiecekac2c2822023-07-04 10:15:56 +0200115static int
116create_empty_string(char **str)
117{
118 free(*str);
119 *str = malloc(sizeof(char));
120 if (!(*str)) {
121 fprintf(stderr, "yangre error: memory allocation failed.\n");
122 return 1;
123 }
124 (*str)[0] = '\0';
125
126 return 0;
127}
128
129static ly_bool
130file_is_empty(FILE *fp)
131{
132 int c;
133
134 c = fgetc(fp);
135 if (c == EOF) {
136 return 1;
137 } else {
138 ungetc(c, fp);
139 return 0;
140 }
141}
142
aPieceke29b1642023-06-30 10:28:14 +0200143/**
144 * @brief Open the @p filepath, parse patterns and given string-argument.
145 *
146 * @param[in] filepath File to parse. Contains patterns and string.
147 * @param[out] infile The file descriptor of @p filepath.
148 * @param[out] patterns Storage of patterns.
aPiecek834d73b2023-06-30 14:33:02 +0200149 * @param[out] patterns_count Number of items in @p patterns.
aPieceke29b1642023-06-30 10:28:14 +0200150 * @param[out] strarg The string-argument to check.
151 * @return 0 on success.
152 */
153static int
aPiecek834d73b2023-06-30 14:33:02 +0200154parse_patterns_file(const char *filepath, FILE **infile, struct yr_pattern **patterns, int *patterns_count, char **strarg)
aPieceke29b1642023-06-30 10:28:14 +0200155{
156 int blankline = 0;
157 char *str = NULL;
158 size_t len = 0;
159 ssize_t l;
160
161 *infile = fopen(filepath, "rb");
162 if (!(*infile)) {
163 fprintf(stderr, "yangre error: unable to open input file %s (%s).\n", optarg, strerror(errno));
164 goto error;
165 }
aPiecekac2c2822023-07-04 10:15:56 +0200166 if (file_is_empty(*infile)) {
167 if (create_empty_string(strarg)) {
168 goto error;
169 }
170 return 0;
171 }
aPieceke29b1642023-06-30 10:28:14 +0200172
173 while ((l = getline(&str, &len, *infile)) != -1) {
lePicif71fd7d2023-07-04 08:07:01 +0200174 if (!blankline && ((str[0] == '\n') || ((str[0] == '\r') && (str[1] == '\n')))) {
aPieceke29b1642023-06-30 10:28:14 +0200175 /* blank line */
176 blankline = 1;
177 continue;
178 }
lePicif71fd7d2023-07-04 08:07:01 +0200179 if ((str[0] != '\n') && (str[0] != '\r') && (str[l - 1] == '\n')) {
aPieceke29b1642023-06-30 10:28:14 +0200180 /* remove ending newline */
lePicif71fd7d2023-07-04 08:07:01 +0200181 if ((l > 1) && (str[l - 2] == '\r') && (str[l - 1] == '\n')) {
182 str[l - 2] = '\0';
183 } else {
184 str[l - 1] = '\0';
185 }
aPieceke29b1642023-06-30 10:28:14 +0200186 }
187 if (blankline) {
188 /* done - str is now the string to check */
189 blankline = 0;
190 *strarg = str;
191 break;
192 /* else read the patterns */
aPiecek834d73b2023-06-30 14:33:02 +0200193 } else if (add_pattern(patterns, patterns_count, (str[0] == ' ') ? &str[1] : str)) {
aPieceke29b1642023-06-30 10:28:14 +0200194 goto error;
195 }
196 if (str[0] == ' ') {
197 /* set invert-match */
aPiecek834d73b2023-06-30 14:33:02 +0200198 (*patterns)[*patterns_count - 1].invert = 1;
aPieceke29b1642023-06-30 10:28:14 +0200199 }
200 }
lePicif71fd7d2023-07-04 08:07:01 +0200201 if (!str || (blankline && (str[0] != '\0'))) {
aPieceke29b1642023-06-30 10:28:14 +0200202 /* corner case, no input after blankline meaning the pattern to check is empty */
aPiecekac2c2822023-07-04 10:15:56 +0200203 if (create_empty_string(&str)) {
aPieceke29b1642023-06-30 10:28:14 +0200204 goto error;
205 }
aPieceke29b1642023-06-30 10:28:14 +0200206 }
207 *strarg = str;
208
209 return 0;
210
211error:
212 free(str);
213 if (*infile) {
214 fclose(*infile);
215 *infile = NULL;
216 }
217 *strarg = NULL;
218
219 return 1;
220}
221
aPiecekc5cf3712023-06-30 11:27:33 +0200222static char *
223modstr_init(void)
224{
225 const char *module_start = "module yangre {"
226 "yang-version 1.1;"
227 "namespace urn:cesnet:libyang:yangre;"
228 "prefix re;"
229 "leaf pattern {"
230 " type string {";
231
232 return strdup(module_start);
233}
234
235static char *
aPiecek834d73b2023-06-30 14:33:02 +0200236modstr_add_pattern(char **modstr, const struct yr_pattern *pattern)
aPiecekc5cf3712023-06-30 11:27:33 +0200237{
238 char *new;
239 const char *module_invertmatch = " { modifier invert-match; }";
240 const char *module_match = ";";
241
aPiecek834d73b2023-06-30 14:33:02 +0200242 if (asprintf(&new, "%s pattern %s%s", *modstr, pattern->expr,
243 pattern->invert ? module_invertmatch : module_match) == -1) {
aPiecekc5cf3712023-06-30 11:27:33 +0200244 fprintf(stderr, "yangre error: memory allocation failed.\n");
245 return NULL;
246 }
247 free(*modstr);
248 *modstr = NULL;
249
250 return new;
251}
252
253static char *
254modstr_add_ending(char **modstr)
255{
256 char *new;
257 static const char *module_end = "}}}";
258
259 if (asprintf(&new, "%s%s", *modstr, module_end) == -1) {
260 fprintf(stderr, "yangre error: memory allocation failed.\n");
261 return NULL;
262 }
263 free(*modstr);
264 *modstr = NULL;
265
266 return new;
267}
268
269static int
aPiecek834d73b2023-06-30 14:33:02 +0200270create_module(struct yr_pattern *patterns, int patterns_count, char **mod)
aPiecekc5cf3712023-06-30 11:27:33 +0200271{
272 int i;
273 char *new = NULL, *modstr;
274
275 if (!(modstr = modstr_init())) {
276 goto error;
277 }
278
279 for (i = 0; i < patterns_count; i++) {
aPiecek834d73b2023-06-30 14:33:02 +0200280 if (!(new = modstr_add_pattern(&modstr, &patterns[i]))) {
aPiecekc5cf3712023-06-30 11:27:33 +0200281 goto error;
282 }
283 modstr = new;
284 }
285
286 if (!(new = modstr_add_ending(&modstr))) {
287 goto error;
288 }
289
290 *mod = new;
291
292 return 0;
293
294error:
295 *mod = NULL;
296 free(new);
297 free(modstr);
298
299 return 1;
300}
301
aPiecek44ffabc2023-06-30 11:32:11 +0200302static void
Michal Vasko7a266772024-01-23 11:02:38 +0100303print_verbose(struct yr_pattern *patterns, int patterns_count, char *str, LY_ERR match)
aPiecek44ffabc2023-06-30 11:32:11 +0200304{
305 int i;
306
307 for (i = 0; i < patterns_count; i++) {
aPiecek834d73b2023-06-30 14:33:02 +0200308 fprintf(stdout, "pattern %d: %s\n", i + 1, patterns[i].expr);
309 fprintf(stdout, "matching %d: %s\n", i + 1, patterns[i].invert ? "inverted" : "regular");
aPiecek44ffabc2023-06-30 11:32:11 +0200310 }
311 fprintf(stdout, "string : %s\n", str);
312 if (match == LY_SUCCESS) {
313 fprintf(stdout, "result : matching\n");
314 } else if (match == LY_EVALID) {
315 fprintf(stdout, "result : not matching\n");
316 } else {
Michal Vasko7a266772024-01-23 11:02:38 +0100317 fprintf(stdout, "result : error (%s)\n", ly_last_logmsg());
aPiecek44ffabc2023-06-30 11:32:11 +0200318 }
319}
320
Radek Krejci5819f7c2019-05-31 14:53:29 +0200321int
Radek Krejci4001a102020-11-13 15:43:05 +0100322main(int argc, char *argv[])
Radek Krejci5819f7c2019-05-31 14:53:29 +0200323{
324 LY_ERR match;
aPieceke29b1642023-06-30 10:28:14 +0200325 int i, opt_index = 0, ret = 1, verbose = 0;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200326 struct option options[] = {
327 {"help", no_argument, NULL, 'h'},
328 {"file", required_argument, NULL, 'f'},
329 {"invert-match", no_argument, NULL, 'i'},
330 {"pattern", required_argument, NULL, 'p'},
331 {"version", no_argument, NULL, 'v'},
332 {"verbose", no_argument, NULL, 'V'},
333 {NULL, 0, NULL, 0}
334 };
aPiecek834d73b2023-06-30 14:33:02 +0200335 struct yr_pattern *patterns = NULL;
336 char *str = NULL, *modstr = NULL;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200337 int patterns_count = 0;
338 struct ly_ctx *ctx = NULL;
Michal Vasko4de7d072021-07-09 09:13:18 +0200339 struct lys_module *mod;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200340 FILE *infile = NULL;
aPiecekbfdf83b2023-06-29 13:55:09 +0200341 ly_bool info_printed = 0;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200342
343 opterr = 0;
344 while ((i = getopt_long(argc, argv, "hf:ivVp:", options, &opt_index)) != -1) {
345 switch (i) {
346 case 'h':
347 help();
aPiecekbfdf83b2023-06-29 13:55:09 +0200348 info_printed = 1;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200349 break;
350 case 'f':
351 if (infile) {
352 help();
353 fprintf(stderr, "yangre error: multiple input files are not supported.\n");
354 goto cleanup;
355 } else if (patterns_count) {
356 help();
357 fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n");
358 goto cleanup;
359 }
aPiecek834d73b2023-06-30 14:33:02 +0200360 if (parse_patterns_file(optarg, &infile, &patterns, &patterns_count, &str)) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200361 goto cleanup;
362 }
Radek Krejci5819f7c2019-05-31 14:53:29 +0200363 break;
364 case 'i':
aPiecek834d73b2023-06-30 14:33:02 +0200365 if (!patterns_count || patterns[patterns_count - 1].invert) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200366 help();
367 fprintf(stderr, "yangre error: invert-match option must follow some pattern.\n");
368 goto cleanup;
369 }
aPiecek834d73b2023-06-30 14:33:02 +0200370 patterns[patterns_count - 1].invert = 1;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200371 break;
372 case 'p':
373 if (infile) {
374 help();
375 fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n");
376 goto cleanup;
377 }
aPiecek834d73b2023-06-30 14:33:02 +0200378 if (add_pattern(&patterns, &patterns_count, optarg)) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200379 goto cleanup;
380 }
381 break;
382 case 'v':
383 version();
aPiecekbfdf83b2023-06-29 13:55:09 +0200384 info_printed = 1;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200385 break;
386 case 'V':
387 verbose = 1;
388 break;
389 default:
390 help();
391 if (optopt) {
392 fprintf(stderr, "yangre error: invalid option: -%c\n", optopt);
393 } else {
394 fprintf(stderr, "yangre error: invalid option: %s\n", argv[optind - 1]);
395 }
396 goto cleanup;
397 }
398 }
399
aPiecekbfdf83b2023-06-29 13:55:09 +0200400 if (info_printed) {
401 ret = 0;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200402 goto cleanup;
403 }
404
405 if (!str) {
406 /* check options compatibility */
407 if (optind >= argc) {
408 help();
409 fprintf(stderr, "yangre error: missing <string> parameter to process.\n");
410 goto cleanup;
411 } else if (!patterns_count) {
412 help();
413 fprintf(stderr, "yangre error: missing pattern parameter to use.\n");
414 goto cleanup;
415 }
416 str = argv[optind];
417 }
418
aPiecek834d73b2023-06-30 14:33:02 +0200419 if (create_module(patterns, patterns_count, &modstr)) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200420 goto cleanup;
421 }
Radek Krejci5819f7c2019-05-31 14:53:29 +0200422
423 if (ly_ctx_new(NULL, 0, &ctx)) {
424 goto cleanup;
425 }
426
Michal Vasko7a266772024-01-23 11:02:38 +0100427 ly_set_log_clb(pattern_error);
Michal Vasko3a41dff2020-07-15 14:30:28 +0200428 if (lys_parse_mem(ctx, modstr, LYS_IN_YANG, &mod) || !mod->compiled || !mod->compiled->data) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200429 goto cleanup;
430 }
431
Radek Krejcid7857262020-11-06 10:29:22 +0100432 /* check the value */
Michal Vaskoaebbce02021-04-06 09:23:37 +0200433 match = lyd_value_validate(ctx, mod->compiled->data, str, strlen(str), NULL, NULL, NULL);
Radek Krejcid7857262020-11-06 10:29:22 +0100434
Radek Krejci5819f7c2019-05-31 14:53:29 +0200435 if (verbose) {
Michal Vasko7a266772024-01-23 11:02:38 +0100436 print_verbose(patterns, patterns_count, str, match);
Radek Krejci5819f7c2019-05-31 14:53:29 +0200437 }
438 if (match == LY_SUCCESS) {
439 ret = 0;
440 } else if (match == LY_EVALID) {
aPiecekbfdf83b2023-06-29 13:55:09 +0200441 ret = 2;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200442 } else {
aPiecekbfdf83b2023-06-29 13:55:09 +0200443 ret = 1;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200444 }
445
446cleanup:
Radek Krejci90ed21e2021-04-12 14:47:46 +0200447 ly_ctx_destroy(ctx);
Radek Krejci5819f7c2019-05-31 14:53:29 +0200448 for (i = 0; i < patterns_count; i++) {
aPiecek834d73b2023-06-30 14:33:02 +0200449 free(patterns[i].expr);
Radek Krejci5819f7c2019-05-31 14:53:29 +0200450 }
aPiecek834d73b2023-06-30 14:33:02 +0200451 if (patterns_count) {
452 free(patterns);
453 }
aPiecekc5cf3712023-06-30 11:27:33 +0200454 free(modstr);
Radek Krejci5819f7c2019-05-31 14:53:29 +0200455 if (infile) {
456 fclose(infile);
457 free(str);
458 }
Radek Krejci5819f7c2019-05-31 14:53:29 +0200459
460 return ret;
461}