summaryrefslogtreecommitdiffstats
path: root/tools/re/main.c
blob: 2292b2abc61b1c3610212ffede5c4f61f606552f (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
/**
 * @file main.c
 * @author Radek Krejci <rkrejci@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"

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), 1 if not and -1 on error.\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 const char *module_start = "module yangre {"
        "yang-version 1.1;"
        "namespace urn:cesnet:libyang:yangre;"
        "prefix re;"
        "leaf pattern {"
        "  type string {";
static const char *module_invertmatch = " { modifier invert-match; }";
static const char *module_match = ";";
static const char *module_end = "}}}";

static int
add_pattern(char ***patterns, int **inverts, int *counter, char *pattern)
{
    void *reallocated1, *reallocated2;

    (*counter)++;
    reallocated1 = realloc(*patterns, *counter * sizeof **patterns);
    reallocated2 = realloc(*inverts, *counter * sizeof **inverts);
    if (!reallocated1 || !reallocated2) {
        fprintf(stderr, "yangre error: memory allocation error.\n");
        free(reallocated1);
        free(reallocated2);
        return EXIT_FAILURE;
    }
    (*patterns) = reallocated1;
    (*patterns)[*counter - 1] = strdup(pattern);
    (*inverts) = reallocated2;
    (*inverts)[*counter - 1] = 0;

    return EXIT_SUCCESS;
}

int
main(int argc, char *argv[])
{
    LY_ERR match;
    int i, opt_index = 0, ret = -1, verbose = 0, blankline = 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}
    };
    char **patterns = NULL, *str = NULL, *modstr = NULL, *s;
    int *invert_match = NULL;
    int patterns_count = 0;
    struct ly_ctx *ctx = NULL;
    struct lys_module *mod;
    FILE *infile = NULL;
    size_t len = 0;
    ssize_t l;

    opterr = 0;
    while ((i = getopt_long(argc, argv, "hf:ivVp:", options, &opt_index)) != -1) {
        switch (i) {
        case 'h':
            help();
            ret = -2; /* continue to allow printing version and help at once */
            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;
            }
            infile = fopen(optarg, "rb");
            if (!infile) {
                fprintf(stderr, "yangre error: unable to open input file %s (%s).\n", optarg, strerror(errno));
                goto cleanup;
            }

            while ((l = getline(&str, &len, infile)) != -1) {
                if (!blankline && (str[0] == '\n')) {
                    /* blank line */
                    blankline = 1;
                    continue;
                }
                if ((str[0] != '\n') && (str[l - 1] == '\n')) {
                    /* remove ending newline */
                    str[l - 1] = '\0';
                }
                if (blankline) {
                    /* done - str is now the string to check */
                    blankline = 0;
                    break;
                    /* else read the patterns */
                } else if (add_pattern(&patterns, &invert_match, &patterns_count,
                        (str[0] == ' ') ? &str[1] : str)) {
                    goto cleanup;
                }
                if (str[0] == ' ') {
                    /* set invert-match */
                    invert_match[patterns_count - 1] = 1;
                }
            }
            if (blankline) {
                /* corner case, no input after blankline meaning the pattern to check is empty */
                if (str != NULL) {
                    free(str);
                }
                str = malloc(sizeof(char));
                str[0] = '\0';
            }
            break;
        case 'i':
            if (!patterns_count || invert_match[patterns_count - 1]) {
                help();
                fprintf(stderr, "yangre error: invert-match option must follow some pattern.\n");
                goto cleanup;
            }
            invert_match[patterns_count - 1] = 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, &invert_match, &patterns_count, optarg)) {
                goto cleanup;
            }
            break;
        case 'v':
            version();
            ret = -2; /* continue to allow printing version and help at once */
            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 (ret == -2) {
        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];
    }

    for (modstr = (char *)module_start, i = 0; i < patterns_count; i++) {
        if (asprintf(&s, "%s pattern %s%s", modstr, patterns[i], invert_match[i] ? module_invertmatch : module_match) == -1) {
            fprintf(stderr, "yangre error: memory allocation failed.\n");
            goto cleanup;
        }
        if (modstr != module_start) {
            free(modstr);
        }
        modstr = s;
    }
    if (asprintf(&s, "%s%s", modstr, module_end) == -1) {
        fprintf(stderr, "yangre error: memory allocation failed.\n");
        goto cleanup;
    }
    if (modstr != module_start) {
        free(modstr);
    }
    modstr = s;

    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) {
        for (i = 0; i < patterns_count; i++) {
            fprintf(stdout, "pattern  %d: %s\n", i + 1, patterns[i]);
            fprintf(stdout, "matching %d: %s\n", i + 1, invert_match[i] ? "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));
        }
    }
    if (match == LY_SUCCESS) {
        ret = 0;
    } else if (match == LY_EVALID) {
        ret = 1;
    } else {
        ret = -1;
    }

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

    return ret;
}