blob: 5d91f273abb898ab0b9c0da7a8d0bed048d2fa39 [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
aPieceke29b1642023-06-30 10:28:14 +0200116/**
117 * @brief Open the @p filepath, parse patterns and given string-argument.
118 *
119 * @param[in] filepath File to parse. Contains patterns and string.
120 * @param[out] infile The file descriptor of @p filepath.
121 * @param[out] patterns Storage of patterns.
aPiecek834d73b2023-06-30 14:33:02 +0200122 * @param[out] patterns_count Number of items in @p patterns.
aPieceke29b1642023-06-30 10:28:14 +0200123 * @param[out] strarg The string-argument to check.
124 * @return 0 on success.
125 */
126static int
aPiecek834d73b2023-06-30 14:33:02 +0200127parse_patterns_file(const char *filepath, FILE **infile, struct yr_pattern **patterns, int *patterns_count, char **strarg)
aPieceke29b1642023-06-30 10:28:14 +0200128{
129 int blankline = 0;
130 char *str = NULL;
131 size_t len = 0;
132 ssize_t l;
133
134 *infile = fopen(filepath, "rb");
135 if (!(*infile)) {
136 fprintf(stderr, "yangre error: unable to open input file %s (%s).\n", optarg, strerror(errno));
137 goto error;
138 }
139
140 while ((l = getline(&str, &len, *infile)) != -1) {
lePicif71fd7d2023-07-04 08:07:01 +0200141 if (!blankline && ((str[0] == '\n') || ((str[0] == '\r') && (str[1] == '\n')))) {
aPieceke29b1642023-06-30 10:28:14 +0200142 /* blank line */
143 blankline = 1;
144 continue;
145 }
lePicif71fd7d2023-07-04 08:07:01 +0200146 if ((str[0] != '\n') && (str[0] != '\r') && (str[l - 1] == '\n')) {
aPieceke29b1642023-06-30 10:28:14 +0200147 /* remove ending newline */
lePicif71fd7d2023-07-04 08:07:01 +0200148 if ((l > 1) && (str[l - 2] == '\r') && (str[l - 1] == '\n')) {
149 str[l - 2] = '\0';
150 } else {
151 str[l - 1] = '\0';
152 }
aPieceke29b1642023-06-30 10:28:14 +0200153 }
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 */
aPiecek834d73b2023-06-30 14:33:02 +0200160 } else if (add_pattern(patterns, patterns_count, (str[0] == ' ') ? &str[1] : str)) {
aPieceke29b1642023-06-30 10:28:14 +0200161 goto error;
162 }
163 if (str[0] == ' ') {
164 /* set invert-match */
aPiecek834d73b2023-06-30 14:33:02 +0200165 (*patterns)[*patterns_count - 1].invert = 1;
aPieceke29b1642023-06-30 10:28:14 +0200166 }
167 }
lePicif71fd7d2023-07-04 08:07:01 +0200168 if (!str || (blankline && (str[0] != '\0'))) {
aPieceke29b1642023-06-30 10:28:14 +0200169 /* corner case, no input after blankline meaning the pattern to check is empty */
170 free(str);
171 str = malloc(sizeof(char));
172 if (!str) {
173 fprintf(stderr, "yangre error: memory allocation failed.\n");
174 goto error;
175 }
176 str[0] = '\0';
177 }
178 *strarg = str;
179
180 return 0;
181
182error:
183 free(str);
184 if (*infile) {
185 fclose(*infile);
186 *infile = NULL;
187 }
188 *strarg = NULL;
189
190 return 1;
191}
192
aPiecekc5cf3712023-06-30 11:27:33 +0200193static char *
194modstr_init(void)
195{
196 const char *module_start = "module yangre {"
197 "yang-version 1.1;"
198 "namespace urn:cesnet:libyang:yangre;"
199 "prefix re;"
200 "leaf pattern {"
201 " type string {";
202
203 return strdup(module_start);
204}
205
206static char *
aPiecek834d73b2023-06-30 14:33:02 +0200207modstr_add_pattern(char **modstr, const struct yr_pattern *pattern)
aPiecekc5cf3712023-06-30 11:27:33 +0200208{
209 char *new;
210 const char *module_invertmatch = " { modifier invert-match; }";
211 const char *module_match = ";";
212
aPiecek834d73b2023-06-30 14:33:02 +0200213 if (asprintf(&new, "%s pattern %s%s", *modstr, pattern->expr,
214 pattern->invert ? module_invertmatch : module_match) == -1) {
aPiecekc5cf3712023-06-30 11:27:33 +0200215 fprintf(stderr, "yangre error: memory allocation failed.\n");
216 return NULL;
217 }
218 free(*modstr);
219 *modstr = NULL;
220
221 return new;
222}
223
224static char *
225modstr_add_ending(char **modstr)
226{
227 char *new;
228 static const char *module_end = "}}}";
229
230 if (asprintf(&new, "%s%s", *modstr, module_end) == -1) {
231 fprintf(stderr, "yangre error: memory allocation failed.\n");
232 return NULL;
233 }
234 free(*modstr);
235 *modstr = NULL;
236
237 return new;
238}
239
240static int
aPiecek834d73b2023-06-30 14:33:02 +0200241create_module(struct yr_pattern *patterns, int patterns_count, char **mod)
aPiecekc5cf3712023-06-30 11:27:33 +0200242{
243 int i;
244 char *new = NULL, *modstr;
245
246 if (!(modstr = modstr_init())) {
247 goto error;
248 }
249
250 for (i = 0; i < patterns_count; i++) {
aPiecek834d73b2023-06-30 14:33:02 +0200251 if (!(new = modstr_add_pattern(&modstr, &patterns[i]))) {
aPiecekc5cf3712023-06-30 11:27:33 +0200252 goto error;
253 }
254 modstr = new;
255 }
256
257 if (!(new = modstr_add_ending(&modstr))) {
258 goto error;
259 }
260
261 *mod = new;
262
263 return 0;
264
265error:
266 *mod = NULL;
267 free(new);
268 free(modstr);
269
270 return 1;
271}
272
aPiecek44ffabc2023-06-30 11:32:11 +0200273static void
aPiecek834d73b2023-06-30 14:33:02 +0200274print_verbose(struct ly_ctx *ctx, struct yr_pattern *patterns, int patterns_count, char *str, LY_ERR match)
aPiecek44ffabc2023-06-30 11:32:11 +0200275{
276 int i;
277
278 for (i = 0; i < patterns_count; i++) {
aPiecek834d73b2023-06-30 14:33:02 +0200279 fprintf(stdout, "pattern %d: %s\n", i + 1, patterns[i].expr);
280 fprintf(stdout, "matching %d: %s\n", i + 1, patterns[i].invert ? "inverted" : "regular");
aPiecek44ffabc2023-06-30 11:32:11 +0200281 }
282 fprintf(stdout, "string : %s\n", str);
283 if (match == LY_SUCCESS) {
284 fprintf(stdout, "result : matching\n");
285 } else if (match == LY_EVALID) {
286 fprintf(stdout, "result : not matching\n");
287 } else {
288 fprintf(stdout, "result : error (%s)\n", ly_errmsg(ctx));
289 }
290}
291
Radek Krejci5819f7c2019-05-31 14:53:29 +0200292int
Radek Krejci4001a102020-11-13 15:43:05 +0100293main(int argc, char *argv[])
Radek Krejci5819f7c2019-05-31 14:53:29 +0200294{
295 LY_ERR match;
aPieceke29b1642023-06-30 10:28:14 +0200296 int i, opt_index = 0, ret = 1, verbose = 0;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200297 struct option options[] = {
298 {"help", no_argument, NULL, 'h'},
299 {"file", required_argument, NULL, 'f'},
300 {"invert-match", no_argument, NULL, 'i'},
301 {"pattern", required_argument, NULL, 'p'},
302 {"version", no_argument, NULL, 'v'},
303 {"verbose", no_argument, NULL, 'V'},
304 {NULL, 0, NULL, 0}
305 };
aPiecek834d73b2023-06-30 14:33:02 +0200306 struct yr_pattern *patterns = NULL;
307 char *str = NULL, *modstr = NULL;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200308 int patterns_count = 0;
309 struct ly_ctx *ctx = NULL;
Michal Vasko4de7d072021-07-09 09:13:18 +0200310 struct lys_module *mod;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200311 FILE *infile = NULL;
aPiecekbfdf83b2023-06-29 13:55:09 +0200312 ly_bool info_printed = 0;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200313
314 opterr = 0;
315 while ((i = getopt_long(argc, argv, "hf:ivVp:", options, &opt_index)) != -1) {
316 switch (i) {
317 case 'h':
318 help();
aPiecekbfdf83b2023-06-29 13:55:09 +0200319 info_printed = 1;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200320 break;
321 case 'f':
322 if (infile) {
323 help();
324 fprintf(stderr, "yangre error: multiple input files are not supported.\n");
325 goto cleanup;
326 } else if (patterns_count) {
327 help();
328 fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n");
329 goto cleanup;
330 }
aPiecek834d73b2023-06-30 14:33:02 +0200331 if (parse_patterns_file(optarg, &infile, &patterns, &patterns_count, &str)) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200332 goto cleanup;
333 }
Radek Krejci5819f7c2019-05-31 14:53:29 +0200334 break;
335 case 'i':
aPiecek834d73b2023-06-30 14:33:02 +0200336 if (!patterns_count || patterns[patterns_count - 1].invert) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200337 help();
338 fprintf(stderr, "yangre error: invert-match option must follow some pattern.\n");
339 goto cleanup;
340 }
aPiecek834d73b2023-06-30 14:33:02 +0200341 patterns[patterns_count - 1].invert = 1;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200342 break;
343 case 'p':
344 if (infile) {
345 help();
346 fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n");
347 goto cleanup;
348 }
aPiecek834d73b2023-06-30 14:33:02 +0200349 if (add_pattern(&patterns, &patterns_count, optarg)) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200350 goto cleanup;
351 }
352 break;
353 case 'v':
354 version();
aPiecekbfdf83b2023-06-29 13:55:09 +0200355 info_printed = 1;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200356 break;
357 case 'V':
358 verbose = 1;
359 break;
360 default:
361 help();
362 if (optopt) {
363 fprintf(stderr, "yangre error: invalid option: -%c\n", optopt);
364 } else {
365 fprintf(stderr, "yangre error: invalid option: %s\n", argv[optind - 1]);
366 }
367 goto cleanup;
368 }
369 }
370
aPiecekbfdf83b2023-06-29 13:55:09 +0200371 if (info_printed) {
372 ret = 0;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200373 goto cleanup;
374 }
375
376 if (!str) {
377 /* check options compatibility */
378 if (optind >= argc) {
379 help();
380 fprintf(stderr, "yangre error: missing <string> parameter to process.\n");
381 goto cleanup;
382 } else if (!patterns_count) {
383 help();
384 fprintf(stderr, "yangre error: missing pattern parameter to use.\n");
385 goto cleanup;
386 }
387 str = argv[optind];
388 }
389
aPiecek834d73b2023-06-30 14:33:02 +0200390 if (create_module(patterns, patterns_count, &modstr)) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200391 goto cleanup;
392 }
Radek Krejci5819f7c2019-05-31 14:53:29 +0200393
394 if (ly_ctx_new(NULL, 0, &ctx)) {
395 goto cleanup;
396 }
397
398 ly_set_log_clb(pattern_error, 0);
Michal Vasko3a41dff2020-07-15 14:30:28 +0200399 if (lys_parse_mem(ctx, modstr, LYS_IN_YANG, &mod) || !mod->compiled || !mod->compiled->data) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200400 goto cleanup;
401 }
402
Radek Krejcid7857262020-11-06 10:29:22 +0100403 /* check the value */
Michal Vaskoaebbce02021-04-06 09:23:37 +0200404 match = lyd_value_validate(ctx, mod->compiled->data, str, strlen(str), NULL, NULL, NULL);
Radek Krejcid7857262020-11-06 10:29:22 +0100405
Radek Krejci5819f7c2019-05-31 14:53:29 +0200406 if (verbose) {
aPiecek834d73b2023-06-30 14:33:02 +0200407 print_verbose(ctx, patterns, patterns_count, str, match);
Radek Krejci5819f7c2019-05-31 14:53:29 +0200408 }
409 if (match == LY_SUCCESS) {
410 ret = 0;
411 } else if (match == LY_EVALID) {
aPiecekbfdf83b2023-06-29 13:55:09 +0200412 ret = 2;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200413 } else {
aPiecekbfdf83b2023-06-29 13:55:09 +0200414 ret = 1;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200415 }
416
417cleanup:
Radek Krejci90ed21e2021-04-12 14:47:46 +0200418 ly_ctx_destroy(ctx);
Radek Krejci5819f7c2019-05-31 14:53:29 +0200419 for (i = 0; i < patterns_count; i++) {
aPiecek834d73b2023-06-30 14:33:02 +0200420 free(patterns[i].expr);
Radek Krejci5819f7c2019-05-31 14:53:29 +0200421 }
aPiecek834d73b2023-06-30 14:33:02 +0200422 if (patterns_count) {
423 free(patterns);
424 }
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}