blob: 12b5fdfae99701f6e8870bea14710f25f4389bb7 [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
17#include <errno.h>
Radek Krejci4001a102020-11-13 15:43:05 +010018#include <getopt.h>
Radek Krejci5819f7c2019-05-31 14:53:29 +020019#include <stdio.h>
20#include <stdlib.h>
Radek Krejci5819f7c2019-05-31 14:53:29 +020021#include <string.h>
Radek Krejci4001a102020-11-13 15:43:05 +010022#include <sys/types.h>
Radek Krejci5819f7c2019-05-31 14:53:29 +020023
Radek Krejci535ea9f2020-05-29 16:01:05 +020024#include "libyang.h"
25
Michal Vasko5aa44c02020-06-29 11:47:02 +020026#include "compat.h"
Radek Krejci4001a102020-11-13 15:43:05 +010027#include "tools/config.h"
Radek Krejci5819f7c2019-05-31 14:53:29 +020028
29void
30help(void)
31{
32 fprintf(stdout, "YANG Regular Expressions processor.\n");
33 fprintf(stdout, "Usage:\n");
34 fprintf(stdout, " yangre [-hv]\n");
35 fprintf(stdout, " yangre [-V] -p <regexp1> [-i] [-p <regexp2> [-i] ...] <string>\n");
36 fprintf(stdout, " yangre [-V] -f <file>\n");
37 fprintf(stdout, "Returns 0 if string matches the pattern(s), 1 if not and -1 on error.\n\n");
38 fprintf(stdout, "Options:\n"
Radek Krejci4001a102020-11-13 15:43:05 +010039 " -h, --help Show this help message and exit.\n"
40 " -v, --version Show version number and exit.\n"
41 " -V, --verbose Print the processing information.\n"
42 " -i, --invert-match Invert-match modifier for the closest preceding\n"
43 " pattern.\n"
44 " -p, --pattern=\"REGEXP\" Regular expression including the quoting,\n"
45 " which is applied the same way as in a YANG module.\n"
46 " -f, --file=\"FILE\" List of patterns and the <string> (separated by an\n"
47 " empty line) are taken from <file>. Invert-match is\n"
48 " indicated by the single space character at the \n"
49 " beginning of the pattern line. YANG quotation around\n"
50 " patterns is still expected, but that avoids issues with\n"
51 " reading quotation by shell. Avoid newline at the end\n"
52 " of the string line to represent empty <string>.");
Radek Krejci5819f7c2019-05-31 14:53:29 +020053 fprintf(stdout, "Examples:\n"
Radek Krejci4001a102020-11-13 15:43:05 +010054 " pattern \"[0-9a-fA-F]*\"; -> yangre -p '\"[0-9a-fA-F]*\"' '1F'\n"
55 " pattern '[a-zA-Z0-9\\-_.]*'; -> yangre -p \"'[a-zA-Z0-9\\-_.]*'\" 'a-b'\n"
56 " pattern [xX][mM][lL].*; -> yangre -p '[xX][mM][lL].*' 'xml-encoding'\n\n");
Radek Krejci5819f7c2019-05-31 14:53:29 +020057 fprintf(stdout, "Note that to pass YANG quoting through your shell, you are supposed to use\n"
Radek Krejci4001a102020-11-13 15:43:05 +010058 "the other quotation around. For not-quoted patterns, use single quotes.\n\n");
Radek Krejci5819f7c2019-05-31 14:53:29 +020059}
60
61void
62version(void)
63{
64 fprintf(stdout, "yangre %s\n", PROJECT_VERSION);
65}
66
67void
68pattern_error(LY_LOG_LEVEL level, const char *msg, const char *path)
69{
70 (void) path; /* unused */
71
Radek Krejcid7857262020-11-06 10:29:22 +010072 if (level == LY_LLERR) {
Radek Krejci5819f7c2019-05-31 14:53:29 +020073 fprintf(stderr, "yangre error: %s\n", msg);
74 }
75}
76
77static const char *module_start = "module yangre {"
Radek Krejci4001a102020-11-13 15:43:05 +010078 "yang-version 1.1;"
79 "namespace urn:cesnet:libyang:yangre;"
80 "prefix re;"
81 "leaf pattern {"
82 " type string {";
Radek Krejci5819f7c2019-05-31 14:53:29 +020083static const char *module_invertmatch = " { modifier invert-match; }";
84static const char *module_match = ";";
85static const char *module_end = "}}}";
86
87static int
88add_pattern(char ***patterns, int **inverts, int *counter, char *pattern)
89{
90 void *reallocated1, *reallocated2;
aPiecekb2b51362023-06-22 09:14:59 +020091 int orig_counter;
Radek Krejci5819f7c2019-05-31 14:53:29 +020092
aPiecekb2b51362023-06-22 09:14:59 +020093 /* Store the original number of items. */
94 orig_counter = *counter;
95
96 /* Reallocate 'patterns' memory with additional space. */
97 reallocated1 = realloc(*patterns, (orig_counter + 1) * sizeof **patterns);
98 if (!reallocated1) {
99 goto error;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200100 }
101 (*patterns) = reallocated1;
aPiecekb2b51362023-06-22 09:14:59 +0200102 /* Allocated memory is now larger. */
103 (*counter)++;
104 /* Copy the pattern and store it to the additonal space. */
105 (*patterns)[orig_counter] = strdup(pattern);
106 if (!(*patterns)[orig_counter]) {
107 goto error;
108 }
109
110 /* Reallocate 'inverts' memory with additional space. */
111 reallocated2 = realloc(*inverts, (orig_counter + 1) * sizeof **inverts);
112 if (!reallocated2) {
113 goto error;
114 }
Radek Krejci5819f7c2019-05-31 14:53:29 +0200115 (*inverts) = reallocated2;
aPiecekb2b51362023-06-22 09:14:59 +0200116 (*inverts)[orig_counter] = 0;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200117
118 return EXIT_SUCCESS;
aPiecekb2b51362023-06-22 09:14:59 +0200119
120error:
121 fprintf(stderr, "yangre error: memory allocation error.\n");
122 return EXIT_FAILURE;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200123}
124
125int
Radek Krejci4001a102020-11-13 15:43:05 +0100126main(int argc, char *argv[])
Radek Krejci5819f7c2019-05-31 14:53:29 +0200127{
128 LY_ERR match;
129 int i, opt_index = 0, ret = -1, verbose = 0, blankline = 0;
130 struct option options[] = {
131 {"help", no_argument, NULL, 'h'},
132 {"file", required_argument, NULL, 'f'},
133 {"invert-match", no_argument, NULL, 'i'},
134 {"pattern", required_argument, NULL, 'p'},
135 {"version", no_argument, NULL, 'v'},
136 {"verbose", no_argument, NULL, 'V'},
137 {NULL, 0, NULL, 0}
138 };
139 char **patterns = NULL, *str = NULL, *modstr = NULL, *s;
140 int *invert_match = NULL;
141 int patterns_count = 0;
142 struct ly_ctx *ctx = NULL;
Michal Vasko4de7d072021-07-09 09:13:18 +0200143 struct lys_module *mod;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200144 FILE *infile = NULL;
145 size_t len = 0;
146 ssize_t l;
Radek Krejci5819f7c2019-05-31 14:53:29 +0200147
148 opterr = 0;
149 while ((i = getopt_long(argc, argv, "hf:ivVp:", options, &opt_index)) != -1) {
150 switch (i) {
151 case 'h':
152 help();
153 ret = -2; /* continue to allow printing version and help at once */
154 break;
155 case 'f':
156 if (infile) {
157 help();
158 fprintf(stderr, "yangre error: multiple input files are not supported.\n");
159 goto cleanup;
160 } else if (patterns_count) {
161 help();
162 fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n");
163 goto cleanup;
164 }
Jan Kundrátb1aa77f2021-12-13 15:16:47 +0100165 infile = fopen(optarg, "rb");
Radek Krejci5819f7c2019-05-31 14:53:29 +0200166 if (!infile) {
167 fprintf(stderr, "yangre error: unable to open input file %s (%s).\n", optarg, strerror(errno));
168 goto cleanup;
169 }
170
Radek Krejci4001a102020-11-13 15:43:05 +0100171 while ((l = getline(&str, &len, infile)) != -1) {
172 if (!blankline && (str[0] == '\n')) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200173 /* blank line */
174 blankline = 1;
175 continue;
176 }
Radek Krejci4001a102020-11-13 15:43:05 +0100177 if ((str[0] != '\n') && (str[l - 1] == '\n')) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200178 /* remove ending newline */
179 str[l - 1] = '\0';
180 }
181 if (blankline) {
182 /* done - str is now the string to check */
183 blankline = 0;
184 break;
185 /* else read the patterns */
186 } else if (add_pattern(&patterns, &invert_match, &patterns_count,
Radek Krejci4001a102020-11-13 15:43:05 +0100187 (str[0] == ' ') ? &str[1] : str)) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200188 goto cleanup;
189 }
190 if (str[0] == ' ') {
191 /* set invert-match */
192 invert_match[patterns_count - 1] = 1;
193 }
194 }
195 if (blankline) {
196 /* corner case, no input after blankline meaning the pattern to check is empty */
197 if (str != NULL) {
198 free(str);
199 }
200 str = malloc(sizeof(char));
201 str[0] = '\0';
202 }
203 break;
204 case 'i':
205 if (!patterns_count || invert_match[patterns_count - 1]) {
206 help();
207 fprintf(stderr, "yangre error: invert-match option must follow some pattern.\n");
208 goto cleanup;
209 }
210 invert_match[patterns_count - 1] = 1;
211 break;
212 case 'p':
213 if (infile) {
214 help();
215 fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n");
216 goto cleanup;
217 }
218 if (add_pattern(&patterns, &invert_match, &patterns_count, optarg)) {
219 goto cleanup;
220 }
221 break;
222 case 'v':
223 version();
224 ret = -2; /* continue to allow printing version and help at once */
225 break;
226 case 'V':
227 verbose = 1;
228 break;
229 default:
230 help();
231 if (optopt) {
232 fprintf(stderr, "yangre error: invalid option: -%c\n", optopt);
233 } else {
234 fprintf(stderr, "yangre error: invalid option: %s\n", argv[optind - 1]);
235 }
236 goto cleanup;
237 }
238 }
239
240 if (ret == -2) {
241 goto cleanup;
242 }
243
244 if (!str) {
245 /* check options compatibility */
246 if (optind >= argc) {
247 help();
248 fprintf(stderr, "yangre error: missing <string> parameter to process.\n");
249 goto cleanup;
250 } else if (!patterns_count) {
251 help();
252 fprintf(stderr, "yangre error: missing pattern parameter to use.\n");
253 goto cleanup;
254 }
255 str = argv[optind];
256 }
257
Radek Krejci4001a102020-11-13 15:43:05 +0100258 for (modstr = (char *)module_start, i = 0; i < patterns_count; i++) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200259 if (asprintf(&s, "%s pattern %s%s", modstr, patterns[i], invert_match[i] ? module_invertmatch : module_match) == -1) {
260 fprintf(stderr, "yangre error: memory allocation failed.\n");
261 goto cleanup;
262 }
263 if (modstr != module_start) {
264 free(modstr);
265 }
266 modstr = s;
267 }
268 if (asprintf(&s, "%s%s", modstr, module_end) == -1) {
269 fprintf(stderr, "yangre error: memory allocation failed.\n");
270 goto cleanup;
271 }
272 if (modstr != module_start) {
273 free(modstr);
274 }
275 modstr = s;
276
277 if (ly_ctx_new(NULL, 0, &ctx)) {
278 goto cleanup;
279 }
280
281 ly_set_log_clb(pattern_error, 0);
Michal Vasko3a41dff2020-07-15 14:30:28 +0200282 if (lys_parse_mem(ctx, modstr, LYS_IN_YANG, &mod) || !mod->compiled || !mod->compiled->data) {
Radek Krejci5819f7c2019-05-31 14:53:29 +0200283 goto cleanup;
284 }
285
Radek Krejcid7857262020-11-06 10:29:22 +0100286 /* check the value */
Michal Vaskoaebbce02021-04-06 09:23:37 +0200287 match = lyd_value_validate(ctx, mod->compiled->data, str, strlen(str), NULL, NULL, NULL);
Radek Krejcid7857262020-11-06 10:29:22 +0100288
Radek Krejci5819f7c2019-05-31 14:53:29 +0200289 if (verbose) {
290 for (i = 0; i < patterns_count; i++) {
291 fprintf(stdout, "pattern %d: %s\n", i + 1, patterns[i]);
292 fprintf(stdout, "matching %d: %s\n", i + 1, invert_match[i] ? "inverted" : "regular");
293 }
294 fprintf(stdout, "string : %s\n", str);
295 if (match == LY_SUCCESS) {
296 fprintf(stdout, "result : matching\n");
297 } else if (match == LY_EVALID) {
298 fprintf(stdout, "result : not matching\n");
299 } else {
Radek Krejcid7857262020-11-06 10:29:22 +0100300 fprintf(stdout, "result : error (%s)\n", ly_errmsg(ctx));
Radek Krejci5819f7c2019-05-31 14:53:29 +0200301 }
302 }
303 if (match == LY_SUCCESS) {
304 ret = 0;
305 } else if (match == LY_EVALID) {
306 ret = 1;
307 } else {
308 ret = -1;
309 }
310
311cleanup:
Radek Krejci90ed21e2021-04-12 14:47:46 +0200312 ly_ctx_destroy(ctx);
Radek Krejci5819f7c2019-05-31 14:53:29 +0200313 for (i = 0; i < patterns_count; i++) {
314 free(patterns[i]);
315 }
316 free(patterns);
317 free(invert_match);
aPiecekb0356f42023-06-22 10:13:38 +0200318 if (modstr != module_start) {
319 free(modstr);
320 }
Radek Krejci5819f7c2019-05-31 14:53:29 +0200321 if (infile) {
322 fclose(infile);
323 free(str);
324 }
Radek Krejci5819f7c2019-05-31 14:53:29 +0200325
326 return ret;
327}