summaryrefslogtreecommitdiffstats
path: root/tools/re/main.c
blob: 5e3353645f0c1230f5c1a7d76e1331209e5332e9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
/**
 * @file main.c
 * @author Radek Krejci <rkrejci@cesnet.cz>
 * @author Adam Piecek <piecek@cesnet.cz>
 * @brief libyang's YANG Regular Expression tool
 *
 * Copyright (c) 2017 CESNET, z.s.p.o.
 *
 * This source code is licensed under BSD 3-Clause License (the "License").
 * You may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://opensource.org/licenses/BSD-3-Clause
 */

#define _GNU_SOURCE /* asprintf, strdup */

#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

#include "libyang.h"

#include "compat.h"
#include "tools/config.h"

struct yr_pattern {
    char *expr;
    ly_bool invert;
};

void
help(void)
{
    fprintf(stdout, "YANG Regular Expressions processor.\n");
    fprintf(stdout, "Usage:\n");
    fprintf(stdout, "    yangre [-hv]\n");
    fprintf(stdout, "    yangre [-V] -p <regexp1> [-i] [-p <regexp2> [-i] ...] <string>\n");
    fprintf(stdout, "    yangre [-V] -f <file>\n");
    fprintf(stdout, "Returns 0 if string matches the pattern(s) or if otherwise successful.\n");
    fprintf(stdout, "Returns 1 on error.\n");
    fprintf(stdout, "Returns 2 if string does not match the pattern(s).\n\n");
    fprintf(stdout, "Options:\n"
            "  -h, --help              Show this help message and exit.\n"
            "  -v, --version           Show version number and exit.\n"
            "  -V, --verbose           Print the processing information.\n"
            "  -i, --invert-match      Invert-match modifier for the closest preceding\n"
            "                          pattern.\n"
            "  -p, --pattern=\"REGEXP\"  Regular expression including the quoting,\n"
            "                          which is applied the same way as in a YANG module.\n"
            "  -f, --file=\"FILE\"     List of patterns and the <string> (separated by an\n"
            "                          empty line) are taken from <file>. Invert-match is\n"
            "                          indicated by the single space character at the \n"
            "                          beginning of the pattern line. YANG quotation around\n"
            "                          patterns is still expected, but that avoids issues with\n"
            "                          reading quotation by shell. Avoid newline at the end\n"
            "                          of the string line to represent empty <string>.");
    fprintf(stdout, "Examples:\n"
            "  pattern \"[0-9a-fA-F]*\";      -> yangre -p '\"[0-9a-fA-F]*\"' '1F'\n"
            "  pattern '[a-zA-Z0-9\\-_.]*';  -> yangre -p \"'[a-zA-Z0-9\\-_.]*'\" 'a-b'\n"
            "  pattern [xX][mM][lL].*;      -> yangre -p '[xX][mM][lL].*' 'xml-encoding'\n\n");
    fprintf(stdout, "Note that to pass YANG quoting through your shell, you are supposed to use\n"
            "the other quotation around. For not-quoted patterns, use single quotes.\n\n");
}

void
version(void)
{
    fprintf(stdout, "yangre %s\n", PROJECT_VERSION);
}

void
pattern_error(LY_LOG_LEVEL level, const char *msg, const char *path)
{
    (void) path; /* unused */

    if (level == LY_LLERR) {
        fprintf(stderr, "yangre error: %s\n", msg);
    }
}

static int
add_pattern(struct yr_pattern **patterns, int *counter, char *pattern)
{
    void *reallocated;
    int orig_counter;

    /* Store the original number of items. */
    orig_counter = *counter;

    /* Reallocate 'patterns' memory with additional space. */
    reallocated = realloc(*patterns, (orig_counter + 1) * sizeof **patterns);
    if (!reallocated) {
        goto error;
    }
    (*patterns) = reallocated;
    /* Allocated memory is now larger. */
    (*counter)++;
    /* Copy the pattern and store it to the additonal space. */
    (*patterns)[orig_counter].expr = strdup(pattern);
    if (!(*patterns)[orig_counter].expr) {
        goto error;
    }
    (*patterns)[orig_counter].invert = 0;

    return 0;

error:
    fprintf(stderr, "yangre error: memory allocation error.\n");
    return 1;
}

static int
create_empty_string(char **str)
{
    free(*str);
    *str = malloc(sizeof(char));
    if (!(*str)) {
        fprintf(stderr, "yangre error: memory allocation failed.\n");
        return 1;
    }
    (*str)[0] = '\0';

    return 0;
}

static ly_bool
file_is_empty(FILE *fp)
{
    int c;

    c = fgetc(fp);
    if (c == EOF) {
        return 1;
    } else {
        ungetc(c, fp);
        return 0;
    }
}

/**
 * @brief Open the @p filepath, parse patterns and given string-argument.
 *
 * @param[in] filepath File to parse. Contains patterns and string.
 * @param[out] infile The file descriptor of @p filepath.
 * @param[out] patterns Storage of patterns.
 * @param[out] patterns_count Number of items in @p patterns.
 * @param[out] strarg The string-argument to check.
 * @return 0 on success.
 */
static int
parse_patterns_file(const char *filepath, FILE **infile, struct yr_pattern **patterns, int *patterns_count, char **strarg)
{
    int blankline = 0;
    char *str = NULL;
    size_t len = 0;
    ssize_t l;

    *infile = fopen(filepath, "rb");
    if (!(*infile)) {
        fprintf(stderr, "yangre error: unable to open input file %s (%s).\n", optarg, strerror(errno));
        goto error;
    }
    if (file_is_empty(*infile)) {
        if (create_empty_string(strarg)) {
            goto error;
        }
        return 0;
    }

    while ((l = getline(&str, &len, *infile)) != -1) {
        if (!blankline && ((str[0] == '\n') || ((str[0] == '\r') && (str[1] == '\n')))) {
            /* blank line */
            blankline = 1;
            continue;
        }
        if ((str[0] != '\n') && (str[0] != '\r') && (str[l - 1] == '\n')) {
            /* remove ending newline */
            if ((l > 1) && (str[l - 2] == '\r') && (str[l - 1] == '\n')) {
                str[l - 2] = '\0';
            } else {
                str[l - 1] = '\0';
            }
        }
        if (blankline) {
            /* done - str is now the string to check */
            blankline = 0;
            *strarg = str;
            break;
            /* else read the patterns */
        } else if (add_pattern(patterns, patterns_count, (str[0] == ' ') ? &str[1] : str)) {
            goto error;
        }
        if (str[0] == ' ') {
            /* set invert-match */
            (*patterns)[*patterns_count - 1].invert = 1;
        }
    }
    if (!str || (blankline && (str[0] != '\0'))) {
        /* corner case, no input after blankline meaning the pattern to check is empty */
        if (create_empty_string(&str)) {
            goto error;
        }
    }
    *strarg = str;

    return 0;

error:
    free(str);
    if (*infile) {
        fclose(*infile);
        *infile = NULL;
    }
    *strarg = NULL;

    return 1;
}

static char *
modstr_init(void)
{
    const char *module_start = "module yangre {"
            "yang-version 1.1;"
            "namespace urn:cesnet:libyang:yangre;"
            "prefix re;"
            "leaf pattern {"
            "  type string {";

    return strdup(module_start);
}

static char *
modstr_add_pattern(char **modstr, const struct yr_pattern *pattern)
{
    char *new;
    const char *module_invertmatch = " { modifier invert-match; }";
    const char *module_match = ";";

    if (asprintf(&new, "%s pattern %s%s", *modstr, pattern->expr,
            pattern->invert ? module_invertmatch : module_match) == -1) {
        fprintf(stderr, "yangre error: memory allocation failed.\n");
        return NULL;
    }
    free(*modstr);
    *modstr = NULL;

    return new;
}

static char *
modstr_add_ending(char **modstr)
{
    char *new;
    static const char *module_end = "}}}";

    if (asprintf(&new, "%s%s", *modstr, module_end) == -1) {
        fprintf(stderr, "yangre error: memory allocation failed.\n");
        return NULL;
    }
    free(*modstr);
    *modstr = NULL;

    return new;
}

static int
create_module(struct yr_pattern *patterns, int patterns_count, char **mod)
{
    int i;
    char *new = NULL, *modstr;

    if (!(modstr = modstr_init())) {
        goto error;
    }

    for (i = 0; i < patterns_count; i++) {
        if (!(new = modstr_add_pattern(&modstr, &patterns[i]))) {
            goto error;
        }
        modstr = new;
    }

    if (!(new = modstr_add_ending(&modstr))) {
        goto error;
    }

    *mod = new;

    return 0;

error:
    *mod = NULL;
    free(new);
    free(modstr);

    return 1;
}

static void
print_verbose(struct ly_ctx *ctx, struct yr_pattern *patterns, int patterns_count, char *str, LY_ERR match)
{
    int i;

    for (i = 0; i < patterns_count; i++) {
        fprintf(stdout, "pattern  %d: %s\n", i + 1, patterns[i].expr);
        fprintf(stdout, "matching %d: %s\n", i + 1, patterns[i].invert ? "inverted" : "regular");
    }
    fprintf(stdout, "string    : %s\n", str);
    if (match == LY_SUCCESS) {
        fprintf(stdout, "result    : matching\n");
    } else if (match == LY_EVALID) {
        fprintf(stdout, "result    : not matching\n");
    } else {
        fprintf(stdout, "result    : error (%s)\n", ly_errmsg(ctx));
    }
}

int
main(int argc, char *argv[])
{
    LY_ERR match;
    int i, opt_index = 0, ret = 1, verbose = 0;
    struct option options[] = {
        {"help",             no_argument,       NULL, 'h'},
        {"file",             required_argument, NULL, 'f'},
        {"invert-match",     no_argument,       NULL, 'i'},
        {"pattern",          required_argument, NULL, 'p'},
        {"version",          no_argument,       NULL, 'v'},
        {"verbose",          no_argument,       NULL, 'V'},
        {NULL,               0,                 NULL, 0}
    };
    struct yr_pattern *patterns = NULL;
    char *str = NULL, *modstr = NULL;
    int patterns_count = 0;
    struct ly_ctx *ctx = NULL;
    struct lys_module *mod;
    FILE *infile = NULL;
    ly_bool info_printed = 0;

    opterr = 0;
    while ((i = getopt_long(argc, argv, "hf:ivVp:", options, &opt_index)) != -1) {
        switch (i) {
        case 'h':
            help();
            info_printed = 1;
            break;
        case 'f':
            if (infile) {
                help();
                fprintf(stderr, "yangre error: multiple input files are not supported.\n");
                goto cleanup;
            } else if (patterns_count) {
                help();
                fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n");
                goto cleanup;
            }
            if (parse_patterns_file(optarg, &infile, &patterns, &patterns_count, &str)) {
                goto cleanup;
            }
            break;
        case 'i':
            if (!patterns_count || patterns[patterns_count - 1].invert) {
                help();
                fprintf(stderr, "yangre error: invert-match option must follow some pattern.\n");
                goto cleanup;
            }
            patterns[patterns_count - 1].invert = 1;
            break;
        case 'p':
            if (infile) {
                help();
                fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n");
                goto cleanup;
            }
            if (add_pattern(&patterns, &patterns_count, optarg)) {
                goto cleanup;
            }
            break;
        case 'v':
            version();
            info_printed = 1;
            break;
        case 'V':
            verbose = 1;
            break;
        default:
            help();
            if (optopt) {
                fprintf(stderr, "yangre error: invalid option: -%c\n", optopt);
            } else {
                fprintf(stderr, "yangre error: invalid option: %s\n", argv[optind - 1]);
            }
            goto cleanup;
        }
    }

    if (info_printed) {
        ret = 0;
        goto cleanup;
    }

    if (!str) {
        /* check options compatibility */
        if (optind >= argc) {
            help();
            fprintf(stderr, "yangre error: missing <string> parameter to process.\n");
            goto cleanup;
        } else if (!patterns_count) {
            help();
            fprintf(stderr, "yangre error: missing pattern parameter to use.\n");
            goto cleanup;
        }
        str = argv[optind];
    }

    if (create_module(patterns, patterns_count, &modstr)) {
        goto cleanup;
    }

    if (ly_ctx_new(NULL, 0, &ctx)) {
        goto cleanup;
    }

    ly_set_log_clb(pattern_error, 0);
    if (lys_parse_mem(ctx, modstr, LYS_IN_YANG, &mod) || !mod->compiled || !mod->compiled->data) {
        goto cleanup;
    }

    /* check the value */
    match = lyd_value_validate(ctx, mod->compiled->data, str, strlen(str), NULL, NULL, NULL);

    if (verbose) {
        print_verbose(ctx, patterns, patterns_count, str, match);
    }
    if (match == LY_SUCCESS) {
        ret = 0;
    } else if (match == LY_EVALID) {
        ret = 2;
    } else {
        ret = 1;
    }

cleanup:
    ly_ctx_destroy(ctx);
    for (i = 0; i < patterns_count; i++) {
        free(patterns[i].expr);
    }
    if (patterns_count) {
        free(patterns);
    }
    free(modstr);
    if (infile) {
        fclose(infile);
        free(str);
    }

    return ret;
}