blob: e128649c96fea9bc4d9ea9198ad80d334528c46b [file] [log] [blame]
Radek Krejcied5acc52019-04-25 15:57:04 +02001/**
2 * @file main_ni.c
3 * @author Radek Krejci <rkrejci@cesnet.cz>
4 * @brief libyang's yanglint tool - noninteractive code
5 *
6 * Copyright (c) 2015-2018 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
15#include "config.h"
16
17#include <stdio.h>
18#include <stdlib.h>
19#include <getopt.h>
20#include <errno.h>
21#include <libgen.h>
22#include <sys/stat.h>
23#include <sys/times.h>
24#include <sys/types.h>
25#include <string.h>
26#include <unistd.h>
27
28#include "commands.h"
29#include "libyang.h"
30
31volatile uint8_t verbose = 0;
32
33#if 0
34/* from commands.c */
35int print_list(FILE *out, struct ly_ctx *ctx, LYD_FORMAT outformat);
36#endif
37
38void
39help(int shortout)
40{
41 fprintf(stdout, "Usage:\n");
42 fprintf(stdout, " yanglint [options] [-f { yang | yin | tree | tree-rfc | jsons}] <file>...\n");
43 fprintf(stdout, " Validates the YANG module in <file>, and all its dependencies.\n\n");
44 fprintf(stdout, " yanglint [options] [-f { xml | json }] <schema>... <file>...\n");
45 fprintf(stdout, " Validates the YANG modeled data in <file> according to the <schema>.\n\n");
46 fprintf(stdout, " yanglint\n");
47 fprintf(stdout, " Starts interactive mode with more features.\n\n");
48
49 if (shortout) {
50 return;
51 }
52 fprintf(stdout, "Options:\n"
53 " -h, --help Show this help message and exit.\n"
54 " -v, --version Show version number and exit.\n"
55 " -V, --verbose Show verbose messages, can be used multiple times to\n"
56 " increase verbosity.\n"
57#ifndef NDEBUG
58 " -G GROUPS, --debug=GROUPS\n"
59 " Enable printing of specific debugging message group\n"
60 " (nothing will be printed unless verbosity is set to debug):\n"
61 " <group>[,<group>]* (dict, yang, yin, xpath, diff)\n\n"
62#endif
63 " -p PATH, --path=PATH Search path for schema (YANG/YIN) modules. The option can be used multiple times.\n"
64 " Current working directory and path of the module being added is used implicitly.\n\n"
65 " -D, --disable-searchdir\n"
66 " Do not implicitly search in CWD for schema modules. If specified a second time,\n"
67 " do not even search the module directory (all modules must be explicitly specified).\n\n"
68 " -s, --strict Strict data parsing (do not skip unknown data),\n"
69 " has no effect for schemas.\n\n"
70 " -m, --merge Merge input data files into a single tree and validate at once,\n"
71 " has no effect for the auto, rpc, rpcreply and notif TYPEs.\n\n"
72 " -f FORMAT, --format=FORMAT\n"
73 " Convert to FORMAT. Supported formats: \n"
74 " yang, yin, tree and jsons (JSON) for schemas,\n"
75 " xml, json for data.\n"
76 " -a, --auto Modify the xml output by adding envelopes for autodetection.\n\n"
77 " -i, --allimplemented Make all the imported modules implemented.\n\n"
78 " -l, --list Print info about the loaded schemas in ietf-yang-library format,\n"
79 " the -f option applies here to specify data encoding.\n"
80 " (i - imported module, I - implemented module)\n\n"
81 " -o OUTFILE, --output=OUTFILE\n"
82 " Write the output to OUTFILE instead of stdout.\n\n"
83 " -F FEATURES, --features=FEATURES\n"
84 " Features to support, default all.\n"
85 " <modname>:[<feature>,]*\n\n"
86 " -d MODE, --default=MODE\n"
87 " Print data with default values, according to the MODE\n"
88 " (to print attributes, ietf-netconf-with-defaults model\n"
89 " must be loaded):\n"
90 " all - Add missing default nodes.\n"
91 " all-tagged - Add missing default nodes and mark all the default\n"
92 " nodes with the attribute.\n"
93 " trim - Remove all nodes with a default value.\n"
94 " implicit-tagged - Add missing nodes and mark them with the attribute.\n\n"
95 " -t TYPE, --type=TYPE\n"
96 " Specify data tree type in the input data file:\n"
97 " auto - Resolve data type (one of the following) automatically\n"
98 " (as pyang does) - applicable only on XML input data.\n"
99 " data - Complete datastore with status data (default type).\n"
100 " config - Configuration datastore (without status data).\n"
101 " get - Result of the NETCONF <get> operation.\n"
102 " getconfig - Result of the NETCONF <get-config> operation.\n"
103 " edit - Content of the NETCONF <edit-config> operation.\n"
104 " rpc - Content of the NETCONF <rpc> message, defined as YANG's rpc input statement.\n"
105 " rpcreply - Reply to the RPC. The input data <file>s are expected in pairs - each RPC reply\n"
106 " input data <file> must be followed by the origin RPC input data <file> for the reply.\n"
107 " The same rule of pairing applies also in case of 'auto' TYPE and input data file\n"
108 " containing RPC reply.\n"
109 " notif - Notification instance (content of the <notification> element without <eventTime>.\n\n"
110 " -O FILE, --operational=FILE\n"
111 " - Optional parameter for 'rpc', 'rpcreply' and 'notif' TYPEs, the FILE contains running\n"
112 " configuration datastore and state data (operational datastore) referenced from\n"
113 " the RPC/Notification. The same data apply to all input data <file>s. Note that\n"
114 " the file is validated as 'data' TYPE. Special value '!' can be used as FILE argument\n"
115 " to ignore the external references.\n\n"
116 " -y YANGLIB_PATH - Path to a yang-library data describing the initial context.\n\n"
117 "Tree output specific options:\n"
118 " --tree-help - Print help on tree symbols and exit.\n"
119 " --tree-print-groupings\n"
120 " Print top-level groupings in a separate section.\n"
121 " --tree-print-uses - Print uses nodes instead the resolved grouping nodes.\n"
122 " --tree-no-leafref-target\n"
123 " Do not print target nodes of leafrefs.\n"
124 " --tree-path=SCHEMA_PATH\n"
125 " Print only the specified subtree.\n"
126 " --tree-line-length=LINE_LENGTH\n"
127 " Wrap lines if longer than the specified length (it is not a strict limit, longer lines\n"
128 " can often appear).\n"
129 "\n");
130}
131
132void
133tree_help(void)
134{
135 fprintf(stdout, "Each node is printed as:\n\n");
136 fprintf(stdout, "<status> <flags> <name> <opts> <type> <if-features>\n\n"
137 " <status> is one of:\n"
138 " + for current\n"
139 " x for deprecated\n"
140 " o for obsolete\n\n"
141 " <flags> is one of:\n"
142 " rw for configuration data\n"
143 " ro for status data\n"
144 " -x for RPCs\n"
145 " -n for Notification\n\n"
146 " <name> is the name of the node\n"
147 " (<name>) means that the node is a choice node\n"
148 " :(<name>) means that the node is a case node\n\n"
149 " if the node is augmented into the tree from another module,\n"
150 " it is printed with the module name as <module-name>:<name>.\n\n"
151 " <opts> is one of:\n"
152 " ? for an optional leaf or choice\n"
153 " ! for a presence container\n"
154 " * for a leaf-list or list\n"
155 " [<keys>] for a list's keys\n\n"
156 " <type> is the name of the type for leafs and leaf-lists\n"
157 " If there is a default value defined, it is printed within\n"
158 " angle brackets <default-value>.\n"
159 " If the type is a leafref, the type is printed as -> TARGET`\n\n"
160 " <if-features> is the list of features this node depends on,\n"
161 " printed within curly brackets and a question mark {...}?\n\n");
162}
163
164void
165version(void)
166{
167 fprintf(stdout, "yanglint %s\n", PROJECT_VERSION);
168}
169
170void
171libyang_verbclb(LY_LOG_LEVEL level, const char *msg, const char *path)
172{
173 char *levstr;
174
175 if (level <= verbose) {
176 switch(level) {
177 case LY_LLERR:
178 levstr = "err :";
179 break;
180 case LY_LLWRN:
181 levstr = "warn:";
182 break;
183 case LY_LLVRB:
184 levstr = "verb:";
185 break;
186 default:
187 levstr = "dbg :";
188 break;
189 }
190 if (path) {
191 fprintf(stderr, "%s %s (%s)\n", levstr, msg, path);
192 } else {
193 fprintf(stderr, "%s %s\n", levstr, msg);
194 }
195 }
196}
197
198/*
199 * return:
200 * 0 - error
201 * 1 - schema format
202 * 2 - data format
203 */
204static int
205get_fileformat(const char *filename, LYS_INFORMAT *schema/* TODO , LYD_FORMAT *data */)
206{
207 char *ptr;
208 LYS_INFORMAT informat_s;
209#if 0
210 LYD_FORMAT informat_d;
211#endif
212 /* get the file format */
213 if ((ptr = strrchr(filename, '.')) != NULL) {
214 ++ptr;
215 if (!strcmp(ptr, "yang")) {
216 informat_s = LYS_IN_YANG;
217#if 0
218 informat_d = 0;
219 } else if (!strcmp(ptr, "yin")) {
220 informat_s = LYS_IN_YIN;
221 informat_d = 0;
222 } else if (!strcmp(ptr, "xml")) {
223 informat_s = 0;
224 informat_d = LYD_XML;
225 } else if (!strcmp(ptr, "json")) {
226 informat_s = 0;
227 informat_d = LYD_JSON;
228#endif
229 } else {
230 fprintf(stderr, "yanglint error: input file in an unknown format \"%s\".\n", ptr);
231 return 0;
232 }
233 } else {
234 fprintf(stderr, "yanglint error: input file \"%s\" without file extension - unknown format.\n", filename);
235 return 0;
236 }
237#if 0
238 if (data) {
239 (*data) = informat_d;
240 }
241#endif
242 if (schema) {
243 (*schema) = informat_s;
244 }
245
Radek Krejci77954e82019-04-30 13:51:29 +0200246#if 0
Radek Krejcied5acc52019-04-25 15:57:04 +0200247 if (informat_s) {
248 return 1;
249 } else {
250 return 2;
251 }
Radek Krejci77954e82019-04-30 13:51:29 +0200252#else
253 return 1;
254#endif
Radek Krejcied5acc52019-04-25 15:57:04 +0200255}
256
257int
258main_ni(int argc, char* argv[])
259{
260 int ret = EXIT_FAILURE;
Radek Krejci693262f2019-04-29 15:23:20 +0200261 int opt, opt_index = 0, i, featsize = 0, compiled = 0;
Radek Krejcied5acc52019-04-25 15:57:04 +0200262 struct option options[] = {
263#if 0
264 {"auto", no_argument, NULL, 'a'},
265 {"default", required_argument, NULL, 'd'},
266#endif
267 {"format", required_argument, NULL, 'f'},
268 {"features", required_argument, NULL, 'F'},
269#if 0
270 {"tree-print-groupings", no_argument, NULL, 'g'},
271 {"tree-print-uses", no_argument, NULL, 'u'},
272 {"tree-no-leafref-target", no_argument, NULL, 'n'},
273 {"tree-path", required_argument, NULL, 'P'},
274 {"tree-line-length", required_argument, NULL, 'L'},
275#endif
Radek Krejci693262f2019-04-29 15:23:20 +0200276 {"compiled", no_argument, NULL, 'c'},
Radek Krejcied5acc52019-04-25 15:57:04 +0200277 {"help", no_argument, NULL, 'h'},
278#if 0
279 {"tree-help", no_argument, NULL, 'H'},
280#endif
281 {"allimplemented", no_argument, NULL, 'i'},
282 {"disable-cwd-search", no_argument, NULL, 'D'},
283 {"list", no_argument, NULL, 'l'},
284#if 0
285 {"merge", no_argument, NULL, 'm'},
286#endif
287 {"output", required_argument, NULL, 'o'},
288 {"path", required_argument, NULL, 'p'},
289#if 0
290 {"running", required_argument, NULL, 'r'},
291 {"operational", required_argument, NULL, 'O'},
292 {"strict", no_argument, NULL, 's'},
293 {"type", required_argument, NULL, 't'},
294#endif
295 {"version", no_argument, NULL, 'v'},
296 {"verbose", no_argument, NULL, 'V'},
297#ifndef NDEBUG
298 {"debug", required_argument, NULL, 'G'},
299#endif
300 {NULL, required_argument, NULL, 'y'},
301 {NULL, 0, NULL, 0}
302 };
303 FILE *out = stdout;
304 struct ly_ctx *ctx = NULL;
305 const struct lys_module *mod;
306 LYS_OUTFORMAT outformat_s = 0;
307 LYS_INFORMAT informat_s;
308#if 0
309 LYD_FORMAT informat_d, outformat_d = 0, ylformat = 0;
310#endif
311 struct ly_set *searchpaths = NULL;
312 const char *outtarget_s = NULL;
313 char **feat = NULL, *ptr, *featlist, *dir;
314 struct stat st;
315 uint32_t u;
316 int options_ctx = LY_CTX_NOYANGLIBRARY, list = 0, outoptions_s = 0, outline_length_s = 0;
317#if 0
318 const char *oper_file = NULL, *envelope_s = NULL;
319 char *ylpath = NULL;
320 int options_dflt = 0, options_parser = 0, envelope = 0, autodetection = 0, merge = 0;
321 struct dataitem {
322 const char *filename;
323 struct lyxml_elem *xml;
324 struct lyd_node *tree;
325 struct dataitem *next;
326 LYD_FORMAT format;
327 int type;
328 } *data = NULL, *data_item, *data_prev = NULL;
329 struct lyxml_elem *iter, *elem;
330 struct lyd_node *oper = NULL, *subroot, *next, *node;
331#endif
332 struct ly_set *mods = NULL;
333 void *p;
334 int index = 0;
335
336 opterr = 0;
337#ifndef NDEBUG
Radek Krejci693262f2019-04-29 15:23:20 +0200338 while ((opt = getopt_long(argc, argv, "acd:f:F:gunP:L:hHiDlmo:p:r:O:st:vVG:y:", options, &opt_index)) != -1)
Radek Krejcied5acc52019-04-25 15:57:04 +0200339#else
Radek Krejci693262f2019-04-29 15:23:20 +0200340 while ((opt = getopt_long(argc, argv, "acd:f:F:gunP:L:hHiDlmo:p:r:O:st:vVy:", options, &opt_index)) != -1)
Radek Krejcied5acc52019-04-25 15:57:04 +0200341#endif
342 {
343 switch (opt) {
344#if 0
345 case 'a':
346 envelope = 1;
347 break;
348 case 'd':
349 if (!strcmp(optarg, "all")) {
350 options_dflt = (options_dflt & ~LYP_WD_MASK) | LYP_WD_ALL;
351 } else if (!strcmp(optarg, "all-tagged")) {
352 options_dflt = (options_dflt & ~LYP_WD_MASK) | LYP_WD_ALL_TAG;
353 } else if (!strcmp(optarg, "trim")) {
354 options_dflt = (options_dflt & ~LYP_WD_MASK) | LYP_WD_TRIM;
355 } else if (!strcmp(optarg, "implicit-tagged")) {
356 options_dflt = (options_dflt & ~LYP_WD_MASK) | LYP_WD_IMPL_TAG;
357 } else {
358 fprintf(stderr, "yanglint error: unknown default mode %s\n", optarg);
359 help(1);
360 goto cleanup;
361 }
362 break;
363#endif
Radek Krejci693262f2019-04-29 15:23:20 +0200364 case 'c':
365 compiled = 1;
366 break;
Radek Krejcied5acc52019-04-25 15:57:04 +0200367 case 'f':
368 if (!strcasecmp(optarg, "yang")) {
369 outformat_s = LYS_OUT_YANG;
370#if 0
371 outformat_d = 0;
372 } else if (!strcasecmp(optarg, "tree")) {
373 outformat_s = LYS_OUT_TREE;
374 outformat_d = 0;
375 } else if (!strcasecmp(optarg, "tree-rfc")) {
376 outformat_s = LYS_OUT_TREE;
377 outoptions_s |= LYS_OUTOPT_TREE_RFC;
378 outformat_d = 0;
379 } else if (!strcasecmp(optarg, "yin")) {
380 outformat_s = LYS_OUT_YIN;
381 outformat_d = 0;
382 } else if (!strcasecmp(optarg, "jsons")) {
383 outformat_s = LYS_OUT_JSON;
384 outformat_d = 0;
385 } else if (!strcasecmp(optarg, "xml")) {
386 outformat_s = 0;
387 outformat_d = LYD_XML;
388 } else if (!strcasecmp(optarg, "json")) {
389 outformat_s = 0;
390 outformat_d = LYD_JSON;
391#endif
392 } else {
393 fprintf(stderr, "yanglint error: unknown output format %s\n", optarg);
394 help(1);
395 goto cleanup;
396 }
397 break;
398 case 'F':
399 featsize++;
400 if (!feat) {
401 p = malloc(sizeof *feat);
402 } else {
403 p = realloc(feat, featsize * sizeof *feat);
404 }
405 if (!p) {
406 fprintf(stderr, "yanglint error: Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
407 goto cleanup;
408 }
409 feat = p;
410 feat[featsize - 1] = strdup(optarg);
411 ptr = strchr(feat[featsize - 1], ':');
412 if (!ptr) {
413 fprintf(stderr, "yanglint error: Invalid format of the features specification (%s)", optarg);
414 goto cleanup;
415 }
416 *ptr = '\0';
417
418 break;
419#if 0
420 case 'g':
421 outoptions_s |= LYS_OUTOPT_TREE_GROUPING;
422 break;
423 case 'u':
424 outoptions_s |= LYS_OUTOPT_TREE_USES;
425 break;
426 case 'n':
427 outoptions_s |= LYS_OUTOPT_TREE_NO_LEAFREF;
428 break;
429 case 'P':
430 outtarget_s = optarg;
431 break;
432 case 'L':
433 outline_length_s = atoi(optarg);
434 break;
435#endif
436 case 'h':
437 help(0);
438 ret = EXIT_SUCCESS;
439 goto cleanup;
440#if 0
441 case 'H':
442 tree_help();
443 ret = EXIT_SUCCESS;
444 goto cleanup;
445#endif
446 case 'i':
447 options_ctx |= LY_CTX_ALLIMPLEMENTED;
448 break;
449 case 'D':
450 if (options_ctx & LY_CTX_DISABLE_SEARCHDIRS) {
451 fprintf(stderr, "yanglint error: -D specified too many times.\n");
452 goto cleanup;
453 } else if (options_ctx & LY_CTX_DISABLE_SEARCHDIR_CWD) {
454 options_ctx &= ~LY_CTX_DISABLE_SEARCHDIR_CWD;
455 options_ctx |= LY_CTX_DISABLE_SEARCHDIRS;
456 } else {
457 options_ctx |= LY_CTX_DISABLE_SEARCHDIR_CWD;
458 }
459 break;
460 case 'l':
461 list = 1;
462 break;
463#if 0
464 case 'm':
465 merge = 1;
466 break;
467#endif
468 case 'o':
469 if (out != stdout) {
470 fclose(out);
471 }
472 out = fopen(optarg, "w");
473 if (!out) {
474 fprintf(stderr, "yanglint error: unable open output file %s (%s)\n", optarg, strerror(errno));
475 goto cleanup;
476 }
477 break;
478 case 'p':
479 if (stat(optarg, &st) == -1) {
480 fprintf(stderr, "yanglint error: Unable to use search path (%s) - %s.\n", optarg, strerror(errno));
481 goto cleanup;
482 }
483 if (!S_ISDIR(st.st_mode)) {
484 fprintf(stderr, "yanglint error: Provided search path is not a directory.\n");
485 goto cleanup;
486 }
487 if (!searchpaths) {
488 searchpaths = ly_set_new();
489 }
490 ly_set_add(searchpaths, optarg, 0);
491 break;
492#if 0
493 case 'r':
494 case 'O':
495 if (oper_file || (options_parser & LYD_OPT_NOEXTDEPS)) {
496 fprintf(stderr, "yanglint error: The operational datastore (-O) cannot be set multiple times.\n");
497 goto cleanup;
498 }
499 if (optarg[0] == '!') {
500 /* ignore extenral dependencies to the operational datastore */
501 options_parser |= LYD_OPT_NOEXTDEPS;
502 } else {
503 /* external file with the operational datastore */
504 oper_file = optarg;
505 }
506 break;
507 case 's':
508 options_parser |= LYD_OPT_STRICT;
509 break;
510 case 't':
511 if (!strcmp(optarg, "auto")) {
512 options_parser = (options_parser & ~LYD_OPT_TYPEMASK);
513 autodetection = 1;
514 } else if (!strcmp(optarg, "config")) {
515 options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_CONFIG;
516 } else if (!strcmp(optarg, "get")) {
517 options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_GET;
518 } else if (!strcmp(optarg, "getconfig")) {
519 options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_GETCONFIG;
520 } else if (!strcmp(optarg, "edit")) {
521 options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_EDIT;
522 } else if (!strcmp(optarg, "data")) {
523 options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_DATA_NO_YANGLIB;
524 } else if (!strcmp(optarg, "rpc")) {
525 options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_RPC;
526 } else if (!strcmp(optarg, "rpcreply")) {
527 options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_RPCREPLY;
528 } else if (!strcmp(optarg, "notif")) {
529 options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_NOTIF;
530 } else {
531 fprintf(stderr, "yanglint error: unknown data tree type %s\n", optarg);
532 help(1);
533 goto cleanup;
534 }
535 break;
536#endif
537 case 'v':
538 version();
539 ret = EXIT_SUCCESS;
540 goto cleanup;
541 case 'V':
542 verbose++;
543 break;
544#ifndef NDEBUG
545 case 'G':
546 u = 0;
547 ptr = optarg;
548 while (ptr[0]) {
549 if (!strncmp(ptr, "dict", 4)) {
550 u |= LY_LDGDICT;
551 ptr += 4;
552 } else if (!strncmp(ptr, "yang", 4)) {
553 u |= LY_LDGYANG;
554 ptr += 4;
555 } else if (!strncmp(ptr, "yin", 3)) {
556 u |= LY_LDGYIN;
557 ptr += 3;
558 } else if (!strncmp(ptr, "xpath", 5)) {
559 u |= LY_LDGXPATH;
560 ptr += 5;
561 } else if (!strncmp(ptr, "diff", 4)) {
562 u |= LY_LDGDIFF;
563 ptr += 4;
564 }
565
566 if (ptr[0]) {
567 if (ptr[0] != ',') {
568 fprintf(stderr, "yanglint error: unknown debug group string \"%s\"\n", optarg);
569 goto cleanup;
570 }
571 ++ptr;
572 }
573 }
574 ly_verb_dbg(u);
575 break;
576#endif
577#if 0
578 case 'y':
579 ptr = strrchr(optarg, '.');
580 if (ptr) {
581 ptr++;
582 if (!strcmp(ptr, "xml")) {
583 ylformat = LYD_XML;
584 } else if (!strcmp(ptr, "json")) {
585 ylformat = LYD_JSON;
586 } else {
587 fprintf(stderr, "yanglint error: yang-library file in an unknown format \"%s\".\n", ptr);
588 goto cleanup;
589 }
590 } else {
591 fprintf(stderr, "yanglint error: yang-library file in an unknown format.\n");
592 goto cleanup;
593 }
594 ylpath = optarg;
595 break;
596#endif
597 default:
598 help(1);
599 if (optopt) {
600 fprintf(stderr, "yanglint error: invalid option: -%c\n", optopt);
601 } else {
602 fprintf(stderr, "yanglint error: invalid option: %s\n", argv[optind - 1]);
603 }
604 goto cleanup;
605 }
606 }
607
608 /* check options compatibility */
609 if (!list && optind >= argc) {
610 help(1);
611 fprintf(stderr, "yanglint error: missing <file> to process\n");
612 goto cleanup;
613 }
Radek Krejci693262f2019-04-29 15:23:20 +0200614 if (compiled) {
615 if (!outformat_s) {
616 fprintf(stderr, "yanglint warning: --compiled option takes effect only in case of printing schemas.\n");
617 } else {
618 outformat_s++;
619 }
620 }
Radek Krejcied5acc52019-04-25 15:57:04 +0200621 if (outformat_s && outformat_s != LYS_OUT_TREE && (optind + 1) < argc) {
622 /* we have multiple schemas to be printed as YIN or YANG */
623 fprintf(stderr, "yanglint error: too many schemas to convert and store.\n");
624 goto cleanup;
625 }
626 if (outoptions_s || outtarget_s || outline_length_s) {
627#if 0
628 if (outformat_d || (outformat_s && outformat_s != LYS_OUT_TREE)) {
629 /* we have --tree-print-grouping with other output format than tree */
630 fprintf(stderr,
631 "yanglint warning: --tree options take effect only in case of the tree output format.\n");
632 }
633 }
634 if (merge) {
635 if (autodetection || (options_parser & (LYD_OPT_RPC | LYD_OPT_RPCREPLY | LYD_OPT_NOTIF))) {
636 fprintf(stderr, "yanglint warning: merging not allowed, ignoring option -m.\n");
637 merge = 0;
638 } else {
639 /* first, files will be parsed as trusted to allow missing data, then the data trees will be merged
640 * and the result will be validated */
641 options_parser |= LYD_OPT_TRUSTED;
642 }
643#endif
644 }
645#if 0
646 if (!outformat_d && options_dflt) {
647 /* we have options for printing default nodes, but data output not specified */
648 fprintf(stderr, "yanglint warning: default mode is ignored when not printing data.\n");
649 }
650 if (outformat_s && (options_parser || autodetection)) {
651 /* we have options for printing data tree, but output is schema */
652 fprintf(stderr, "yanglint warning: data parser options are ignored when printing schema.\n");
653 }
654 if (oper_file && (!autodetection && !(options_parser & (LYD_OPT_RPC | LYD_OPT_RPCREPLY | LYD_OPT_NOTIF)))) {
655 fprintf(stderr, "yanglint warning: operational datastore applies only to RPCs or Notifications.\n");
656 /* ignore operational datastore file */
657 oper_file = NULL;
658 }
659 if ((options_parser & LYD_OPT_TYPEMASK) == LYD_OPT_DATA) {
660 /* add option to ignore ietf-yang-library data for implicit data type */
661 options_parser |= LYD_OPT_DATA_NO_YANGLIB;
662 }
663#endif
664
665 /* set callback for printing libyang messages */
666 ly_set_log_clb(libyang_verbclb, 1);
667#if 0
668 /* create libyang context */
669 if (ylpath) {
670 ctx = ly_ctx_new_ylpath(searchpaths ? (const char*)searchpaths->set.g[0] : NULL, ylpath, ylformat, options_ctx);
671 } else {
672#else
673 {
674#endif
675 ly_ctx_new(NULL, options_ctx, &ctx);
676 }
677 if (!ctx) {
678 goto cleanup;
679 }
680
681 /* set searchpaths */
682 if (searchpaths) {
683 for (u = 0; u < searchpaths->count; u++) {
684 ly_ctx_set_searchdir(ctx, (const char*)searchpaths->objs[u]);
685 }
686 index = u + 1;
687 }
688
689 /* derefered setting of verbosity in libyang after context initiation */
690 ly_verb(verbose);
691
692 mods = ly_set_new();
693
694
695 /* divide input files */
696 for (i = 0; i < argc - optind; i++) {
697 /* get the file format */
698 if (!get_fileformat(argv[optind + i], &informat_s/* TODO, &informat_d */)) {
699 goto cleanup;
700 }
701
702 if (informat_s) {
703 /* load/validate schema */
704 if (verbose >= 2) {
705 fprintf(stdout, "Validating %s schema file.\n", argv[optind + i]);
706 }
707 dir = strdup(argv[optind + i]);
708 ly_ctx_set_searchdir(ctx, ptr = dirname(dir));
709 mod = lys_parse_path(ctx, argv[optind + i], informat_s);
710 ly_ctx_unset_searchdir(ctx, index);
711 free(dir);
712 if (!mod) {
713 goto cleanup;
714 }
715 ly_set_add(mods, (void *)mod, 0);
716#if 0
717 } else {
718 if (autodetection && informat_d != LYD_XML) {
719 /* data file content autodetection is possible only for XML input */
720 fprintf(stderr, "yanglint error: data type autodetection is applicable only to XML files.\n");
721 goto cleanup;
722 }
723
724 /* remember data filename and its format */
725 if (!data) {
726 data = data_item = malloc(sizeof *data);
727 } else {
728 for (data_item = data; data_item->next; data_item = data_item->next);
729 data_item->next = malloc(sizeof *data_item);
730 data_item = data_item->next;
731 }
732 data_item->filename = argv[optind + i];
733 data_item->format = informat_d;
734 data_item->type = options_parser & LYD_OPT_TYPEMASK;
735 data_item->tree = NULL;
736 data_item->xml = NULL;
737 data_item->next = NULL;
738#endif
739 }
740 }
741#if 0
742 if (outformat_d && !data && !list) {
743 fprintf(stderr, "yanglint error: no input data file for the specified data output format.\n");
744 goto cleanup;
745 }
746#endif
747
748 /* enable specified features, if not specified, all the module's features are enabled */
749 u = 4; /* skip internal libyang modules */
750 while ((mod = ly_ctx_get_module_iter(ctx, &u))) {
751 for (i = 0; i < featsize; i++) {
752 if (!strcmp(feat[i], mod->name)) {
753 /* parse features spec */
754 featlist = strdup(feat[i] + strlen(feat[i]) + 1);
755 ptr = NULL;
756 while((ptr = strtok(ptr ? NULL : featlist, ","))) {
757 if (verbose >= 2) {
758 fprintf(stdout, "Enabling feature %s in module %s.\n", ptr, mod->name);
759 }
760 if (lys_feature_enable(mod, ptr)) {
761 fprintf(stderr, "Feature %s not defined in module %s.\n", ptr, mod->name);
762 }
763 }
764 free(featlist);
765 break;
766 }
767 }
768 if (i == featsize) {
769 if (verbose >= 2) {
770 fprintf(stdout, "Enabling all features in module %s.\n", mod->name);
771 }
772 lys_feature_enable(mod, "*");
773 }
774 }
775
776 /* convert (print) to FORMAT */
777 if (outformat_s) {
778 if (outformat_s == LYS_OUT_JSON && mods->count > 1) {
779 fputs("[", out);
780 }
781 for (u = 0; u < mods->count; u++) {
782 if (u) {
783 if (outformat_s == LYS_OUT_JSON) {
784 fputs(",\n", out);
785 } else {
786 fputs("\n", out);
787 }
788 }
789 lys_print_file(out, (struct lys_module *)mods->objs[u], outformat_s, outline_length_s, outoptions_s);
790 }
791 if (outformat_s == LYS_OUT_JSON) {
792 if (mods->count > 1) {
793 fputs("]\n", out);
794 } else if (mods->count == 1) {
795 fputs("\n", out);
796 }
797 }
798#if 0
799 } else if (data) {
800 ly_errno = 0;
801
802 /* prepare operational datastore when specified for RPC/Notification */
803 if (oper_file) {
804 /* get the file format */
805 if (!get_fileformat(oper_file, NULL, &informat_d)) {
806 goto cleanup;
807 } else if (!informat_d) {
808 fprintf(stderr, "yanglint error: The operational data are expected in XML or JSON format.\n");
809 goto cleanup;
810 }
811 oper = lyd_parse_path(ctx, oper_file, informat_d, LYD_OPT_DATA_NO_YANGLIB | LYD_OPT_TRUSTED);
812 if (!oper) {
813 fprintf(stderr, "yanglint error: Failed to parse the operational datastore file for RPC/Notification validation.\n");
814 goto cleanup;
815 }
816 }
817
818 for (data_item = data, data_prev = NULL; data_item; data_prev = data_item, data_item = data_item->next) {
819 /* parse data file - via LYD_OPT_TRUSTED postpone validation when all data are loaded and merged */
820 if (autodetection) {
821 /* erase option not covered by LYD_OPT_TYPEMASK, but used according to the type */
822 options_parser &= ~LYD_OPT_DATA_NO_YANGLIB;
823 /* automatically detect data type from the data top level */
824 data_item->xml = lyxml_parse_path(ctx, data_item->filename, 0);
825 if (!data_item->xml) {
826 fprintf(stderr, "yanglint error: parsing XML data for data type autodetection failed.\n");
827 goto cleanup;
828 }
829
830 /* NOTE: namespace is ignored to simplify usage of this feature */
831 if (!strcmp(data_item->xml->name, "data")) {
832 if (verbose >= 2) {
833 fprintf(stdout, "Parsing %s as complete datastore.\n", data_item->filename);
834 }
835 options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_DATA_NO_YANGLIB;
836 data_item->type = LYD_OPT_DATA;
837 } else if (!strcmp(data_item->xml->name, "config")) {
838 if (verbose >= 2) {
839 fprintf(stdout, "Parsing %s as config data.\n", data_item->filename);
840 }
841 options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_CONFIG;
842 data_item->type = LYD_OPT_CONFIG;
843 } else if (!strcmp(data_item->xml->name, "get-reply")) {
844 if (verbose >= 2) {
845 fprintf(stdout, "Parsing %s as <get> reply data.\n", data_item->filename);
846 }
847 options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_GET;
848 data_item->type = LYD_OPT_GET;
849 } else if (!strcmp(data_item->xml->name, "get-config-reply")) {
850 if (verbose >= 2) {
851 fprintf(stdout, "Parsing %s as <get-config> reply data.\n", data_item->filename);
852 }
853 options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_GETCONFIG;
854 data_item->type = LYD_OPT_GETCONFIG;
855 } else if (!strcmp(data_item->xml->name, "edit-config")) {
856 if (verbose >= 2) {
857 fprintf(stdout, "Parsing %s as <edit-config> data.\n", data_item->filename);
858 }
859 options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_EDIT;
860 data_item->type = LYD_OPT_EDIT;
861 } else if (!strcmp(data_item->xml->name, "rpc")) {
862 if (verbose >= 2) {
863 fprintf(stdout, "Parsing %s as <rpc> data.\n", data_item->filename);
864 }
865 options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_RPC;
866 data_item->type = LYD_OPT_RPC;
867 } else if (!strcmp(data_item->xml->name, "rpc-reply")) {
868 if (verbose >= 2) {
869 fprintf(stdout, "Parsing %s as <rpc-reply> data.\n", data_item->filename);
870 }
871
872 data_item->type = LYD_OPT_RPCREPLY;
873 if (!data_item->next || (data_prev && !data_prev->tree)) {
874 fprintf(stderr, "RPC reply (%s) must be paired with the original RPC, see help.\n", data_item->filename);
875 goto cleanup;
876 }
877
878 continue;
879 } else if (!strcmp(data_item->xml->name, "notification")) {
880 if (verbose >= 2) {
881 fprintf(stdout, "Parsing %s as <notification> data.\n", data_item->filename);
882 }
883 options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_NOTIF;
884 data_item->type = LYD_OPT_NOTIF;
885
886 /* ignore eventTime element if present */
887 while (data_item->xml->child && !strcmp(data_item->xml->child->name, "eventTime")) {
888 lyxml_free(ctx, data_item->xml->child);
889 }
890 } else {
891 fprintf(stderr, "yanglint error: invalid top-level element \"%s\" for data type autodetection.\n",
892 data_item->xml->name);
893 goto cleanup;
894 }
895
896 data_item->tree = lyd_parse_xml(ctx, &data_item->xml->child, options_parser, oper);
897 if (data_prev && data_prev->type == LYD_OPT_RPCREPLY) {
898parse_reply:
899 /* check result of the RPC parsing, we are going to do another parsing in this step */
900 if (ly_errno) {
901 goto cleanup;
902 }
903
904 /* check that we really have RPC for the reply */
905 if (data_item->type != LYD_OPT_RPC) {
906 fprintf(stderr, "yanglint error: RPC reply (%s) must be paired with the original RPC, see help.\n", data_prev->filename);
907 goto cleanup;
908 }
909
910 if (data_prev->format == LYD_XML) {
911 /* ignore <ok> and <rpc-error> elements if present */
912 u = 0;
913 LY_TREE_FOR_SAFE(data_prev->xml->child, iter, elem) {
914 if (!strcmp(data_prev->xml->child->name, "ok")) {
915 if (u) {
916 /* rpc-error or ok already present */
917 u = 0x8; /* error flag */
918 } else {
919 u = 0x1 | 0x4; /* <ok> flag with lyxml_free() flag */
920 }
921 } else if (!strcmp(data_prev->xml->child->name, "rpc-error")) {
922 if (u && (u & 0x1)) {
923 /* ok already present, rpc-error can be present multiple times */
924 u = 0x8; /* error flag */
925 } else {
926 u = 0x2 | 0x4; /* <rpc-error> flag with lyxml_free() flag */
927 }
928 }
929
930 if (u == 0x8) {
931 fprintf(stderr, "yanglint error: Invalid RPC reply (%s) content.\n", data_prev->filename);
932 goto cleanup;
933 } else if (u & 0x4) {
934 lyxml_free(ctx, data_prev->xml->child);
935 u &= ~0x4; /* unset lyxml_free() flag */
936 }
937 }
938
939 /* finally, parse RPC reply from the previous step */
940 data_prev->tree = lyd_parse_xml(ctx, &data_prev->xml->child,
941 (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_RPCREPLY, data_item->tree, oper);
942 } else { /* LYD_JSON */
943 data_prev->tree = lyd_parse_path(ctx, data_prev->filename, data_item->format,
944 (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_RPCREPLY, data_item->tree, oper);
945 }
946 }
947 } else if ((options_parser & LYD_OPT_TYPEMASK) == LYD_OPT_RPCREPLY) {
948 if (data_prev && !data_prev->tree) {
949 /* now we should have RPC for the preceding RPC reply */
950 data_item->tree = lyd_parse_path(ctx, data_item->filename, data_item->format,
951 (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_RPC, oper);
952 data_item->type = LYD_OPT_RPC;
953 goto parse_reply;
954 } else {
955 /* now we have RPC reply which will be parsed in next step together with its RPC */
956 if (!data_item->next) {
957 fprintf(stderr, "yanglint error: RPC reply (%s) must be paired with the original RPC, see help.\n", data_item->filename);
958 goto cleanup;
959 }
960 if (data_item->format == LYD_XML) {
961 /* create rpc-reply container to unify handling with autodetection */
962 data_item->xml = calloc(1, sizeof *data_item->xml);
963 if (!data_item->xml) {
964 fprintf(stderr, "yanglint error: Memory allocation failed failed.\n");
965 goto cleanup;
966 }
967 data_item->xml->name = lydict_insert(ctx, "rpc-reply", 9);
968 data_item->xml->prev = data_item->xml;
969 data_item->xml->child = lyxml_parse_path(ctx, data_item->filename, LYXML_PARSE_MULTIROOT | LYXML_PARSE_NOMIXEDCONTENT);
970 if (data_item->xml->child) {
971 data_item->xml->child->parent = data_item->xml;
972 }
973 }
974 continue;
975 }
976 } else {
977 data_item->tree = lyd_parse_path(ctx, data_item->filename, data_item->format, options_parser, oper);
978 }
979 if (ly_errno) {
980 goto cleanup;
981 }
982
983 if (merge && data != data_item) {
984 if (!data->tree) {
985 data->tree = data_item->tree;
986 } else if (data_item->tree) {
987 /* merge results */
988 if (lyd_merge(data->tree, data_item->tree, LYD_OPT_DESTRUCT | LYD_OPT_EXPLICIT)) {
989 fprintf(stderr, "yanglint error: merging multiple data trees failed.\n");
990 goto cleanup;
991 }
992 }
993 data_item->tree = NULL;
994 }
995 }
996
997 if (merge) {
998 /* validate the merged data tree, do not trust the input, invalidate all the data first */
999 LY_TREE_FOR(data->tree, subroot) {
1000 LY_TREE_DFS_BEGIN(subroot, next, node) {
1001 node->validity = LYD_VAL_OK;
1002 switch (node->schema->nodetype) {
1003 case LYS_LEAFLIST:
1004 case LYS_LEAF:
1005 if (((struct lys_node_leaf *)node->schema)->type.base == LY_TYPE_LEAFREF) {
1006 node->validity |= LYD_VAL_LEAFREF;
1007 }
1008 break;
1009 case LYS_LIST:
1010 node->validity |= LYD_VAL_UNIQUE;
1011 /* falls through */
1012 case LYS_CONTAINER:
1013 case LYS_NOTIF:
1014 case LYS_RPC:
1015 case LYS_ACTION:
1016 node->validity |= LYD_VAL_MAND;
1017 break;
1018 default:
1019 break;
1020 }
1021 LY_TREE_DFS_END(subroot, next, node)
1022 }
1023 }
1024 if (lyd_validate(&data->tree, options_parser & ~LYD_OPT_TRUSTED, ctx)) {
1025 goto cleanup;
1026 }
1027 }
1028
1029 /* print only if data output format specified */
1030 if (outformat_d) {
1031 for (data_item = data; data_item; data_item = data_item->next) {
1032 if (!merge && verbose >= 2) {
1033 fprintf(stdout, "File %s:\n", data_item->filename);
1034 }
1035 if (outformat_d == LYD_XML && envelope) {
1036 switch (data_item->type) {
1037 case LYD_OPT_DATA:
1038 envelope_s = "data";
1039 break;
1040 case LYD_OPT_CONFIG:
1041 envelope_s = "config";
1042 break;
1043 case LYD_OPT_GET:
1044 envelope_s = "get-reply";
1045 break;
1046 case LYD_OPT_GETCONFIG:
1047 envelope_s = "get-config-reply";
1048 break;
1049 case LYD_OPT_EDIT:
1050 envelope_s = "edit-config";
1051 break;
1052 case LYD_OPT_RPC:
1053 envelope_s = "rpc";
1054 break;
1055 case LYD_OPT_RPCREPLY:
1056 envelope_s = "rpc-reply";
1057 break;
1058 case LYD_OPT_NOTIF:
1059 envelope_s = "notification";
1060 break;
1061 }
1062 fprintf(out, "<%s>\n", envelope_s);
1063 if (data_item->type == LYD_OPT_RPC && data_item->tree->schema->nodetype != LYS_RPC) {
1064 /* action */
1065 fprintf(out, "<action xmlns=\"urn:ietf:params:xml:ns:yang:1\">\n");
1066 }
1067 }
1068 lyd_print_file(out, (data_item->type == LYD_OPT_RPCREPLY) ? data_item->tree->child : data_item->tree,
1069 outformat_d, LYP_WITHSIBLINGS | LYP_FORMAT | options_dflt);
1070 if (envelope_s) {
1071 if (data_item->type == LYD_OPT_RPC && data_item->tree->schema->nodetype != LYS_RPC) {
1072 fprintf(out, "</action>\n");
1073 }
1074 fprintf(out, "</%s>\n", envelope_s);
1075 }
1076 if (merge) {
1077 /* stop after first item */
1078 break;
1079 }
1080 }
1081 }
1082#endif
1083 }
1084#if 0
1085 if (list) {
1086 print_list(out, ctx, outformat_d);
1087 }
1088#endif
1089
1090 ret = EXIT_SUCCESS;
1091
1092cleanup:
1093 if (out && out != stdout) {
1094 fclose(out);
1095 }
1096 ly_set_free(mods, NULL);
1097 ly_set_free(searchpaths, NULL);
1098 for (i = 0; i < featsize; i++) {
1099 free(feat[i]);
1100 }
1101 free(feat);
1102#if 0
1103 for (; data; data = data_item) {
1104 data_item = data->next;
1105 lyxml_free(ctx, data->xml);
1106 lyd_free_withsiblings(data->tree);
1107 free(data);
1108 }
1109 lyd_free_withsiblings(oper);
1110#endif
1111 ly_ctx_destroy(ctx, NULL);
1112
1113 return ret;
1114}