blob: 918b1bad82ba485a2ec363bf031a383ac01dcf31 [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");
Radek Krejcid8c0f5e2019-11-17 12:18:34 +080042 fprintf(stdout, " yanglint [options] [-f { yang | yin | tree | tree-rfc | info}] <file>...\n");
Radek Krejcied5acc52019-04-25 15:57:04 +020043 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"
Radek Krejcid8c0f5e2019-11-17 12:18:34 +080074 " yang, yin, tree and info for schemas,\n"
Radek Krejcied5acc52019-04-25 15:57:04 +020075 " 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"
Radek Krejcie1f6d5a2019-11-17 14:03:04 +0800128 " can often appear).\n\n"
129 "Info output specific options:\n"
130 " -P INFOPATH, --info-path=INFOPATH\n"
131 " - Schema path with full module names used as node's prefixes, the path identify the root\n"
132 " node of the subtree to print information about.\n"
133 " --single-node - Print information about a single node instead of a subtree."
Radek Krejcied5acc52019-04-25 15:57:04 +0200134 "\n");
135}
136
137void
138tree_help(void)
139{
140 fprintf(stdout, "Each node is printed as:\n\n");
141 fprintf(stdout, "<status> <flags> <name> <opts> <type> <if-features>\n\n"
142 " <status> is one of:\n"
143 " + for current\n"
144 " x for deprecated\n"
145 " o for obsolete\n\n"
146 " <flags> is one of:\n"
147 " rw for configuration data\n"
148 " ro for status data\n"
149 " -x for RPCs\n"
150 " -n for Notification\n\n"
151 " <name> is the name of the node\n"
152 " (<name>) means that the node is a choice node\n"
153 " :(<name>) means that the node is a case node\n\n"
154 " if the node is augmented into the tree from another module,\n"
155 " it is printed with the module name as <module-name>:<name>.\n\n"
156 " <opts> is one of:\n"
157 " ? for an optional leaf or choice\n"
158 " ! for a presence container\n"
159 " * for a leaf-list or list\n"
160 " [<keys>] for a list's keys\n\n"
161 " <type> is the name of the type for leafs and leaf-lists\n"
162 " If there is a default value defined, it is printed within\n"
163 " angle brackets <default-value>.\n"
164 " If the type is a leafref, the type is printed as -> TARGET`\n\n"
165 " <if-features> is the list of features this node depends on,\n"
166 " printed within curly brackets and a question mark {...}?\n\n");
167}
168
169void
170version(void)
171{
172 fprintf(stdout, "yanglint %s\n", PROJECT_VERSION);
173}
174
175void
176libyang_verbclb(LY_LOG_LEVEL level, const char *msg, const char *path)
177{
178 char *levstr;
179
180 if (level <= verbose) {
181 switch(level) {
182 case LY_LLERR:
183 levstr = "err :";
184 break;
185 case LY_LLWRN:
186 levstr = "warn:";
187 break;
188 case LY_LLVRB:
189 levstr = "verb:";
190 break;
191 default:
192 levstr = "dbg :";
193 break;
194 }
195 if (path) {
196 fprintf(stderr, "%s %s (%s)\n", levstr, msg, path);
197 } else {
198 fprintf(stderr, "%s %s\n", levstr, msg);
199 }
200 }
201}
202
203/*
204 * return:
205 * 0 - error
206 * 1 - schema format
207 * 2 - data format
208 */
209static int
Radek Krejcie7b95092019-05-15 11:03:07 +0200210get_fileformat(const char *filename, LYS_INFORMAT *schema, LYD_FORMAT *data)
Radek Krejcied5acc52019-04-25 15:57:04 +0200211{
212 char *ptr;
213 LYS_INFORMAT informat_s;
Radek Krejcied5acc52019-04-25 15:57:04 +0200214 LYD_FORMAT informat_d;
Radek Krejcie7b95092019-05-15 11:03:07 +0200215
Radek Krejcied5acc52019-04-25 15:57:04 +0200216 /* get the file format */
217 if ((ptr = strrchr(filename, '.')) != NULL) {
218 ++ptr;
219 if (!strcmp(ptr, "yang")) {
220 informat_s = LYS_IN_YANG;
Radek Krejcied5acc52019-04-25 15:57:04 +0200221 informat_d = 0;
222 } else if (!strcmp(ptr, "yin")) {
223 informat_s = LYS_IN_YIN;
224 informat_d = 0;
225 } else if (!strcmp(ptr, "xml")) {
226 informat_s = 0;
227 informat_d = LYD_XML;
Radek Krejcie7b95092019-05-15 11:03:07 +0200228#if 0
Radek Krejcied5acc52019-04-25 15:57:04 +0200229 } else if (!strcmp(ptr, "json")) {
230 informat_s = 0;
231 informat_d = LYD_JSON;
232#endif
233 } else {
234 fprintf(stderr, "yanglint error: input file in an unknown format \"%s\".\n", ptr);
235 return 0;
236 }
237 } else {
238 fprintf(stderr, "yanglint error: input file \"%s\" without file extension - unknown format.\n", filename);
239 return 0;
240 }
Radek Krejcie7b95092019-05-15 11:03:07 +0200241
Radek Krejcied5acc52019-04-25 15:57:04 +0200242 if (data) {
243 (*data) = informat_d;
244 }
Radek Krejcie7b95092019-05-15 11:03:07 +0200245
Radek Krejcied5acc52019-04-25 15:57:04 +0200246 if (schema) {
247 (*schema) = informat_s;
248 }
249
250 if (informat_s) {
251 return 1;
252 } else {
253 return 2;
254 }
255}
256
257int
258main_ni(int argc, char* argv[])
259{
260 int ret = EXIT_FAILURE;
Radek Krejcid8c0f5e2019-11-17 12:18:34 +0800261 int opt, opt_index = 0, i, featsize = 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
276 {"help", no_argument, NULL, 'h'},
277#if 0
278 {"tree-help", no_argument, NULL, 'H'},
279#endif
280 {"allimplemented", no_argument, NULL, 'i'},
281 {"disable-cwd-search", no_argument, NULL, 'D'},
282 {"list", no_argument, NULL, 'l'},
283#if 0
284 {"merge", no_argument, NULL, 'm'},
285#endif
286 {"output", required_argument, NULL, 'o'},
287 {"path", required_argument, NULL, 'p'},
Radek Krejcie1f6d5a2019-11-17 14:03:04 +0800288 {"info-path", required_argument, NULL, 'P'},
Radek Krejcied5acc52019-04-25 15:57:04 +0200289#if 0
290 {"running", required_argument, NULL, 'r'},
291 {"operational", required_argument, NULL, 'O'},
Radek Krejcie7b95092019-05-15 11:03:07 +0200292#endif
Radek Krejcie1f6d5a2019-11-17 14:03:04 +0800293 {"single-node", no_argument, NULL, 'q'},
Radek Krejcied5acc52019-04-25 15:57:04 +0200294 {"strict", no_argument, NULL, 's'},
295 {"type", required_argument, NULL, 't'},
Radek Krejcied5acc52019-04-25 15:57:04 +0200296 {"version", no_argument, NULL, 'v'},
297 {"verbose", no_argument, NULL, 'V'},
298#ifndef NDEBUG
299 {"debug", required_argument, NULL, 'G'},
300#endif
301 {NULL, required_argument, NULL, 'y'},
302 {NULL, 0, NULL, 0}
303 };
304 FILE *out = stdout;
305 struct ly_ctx *ctx = NULL;
306 const struct lys_module *mod;
307 LYS_OUTFORMAT outformat_s = 0;
308 LYS_INFORMAT informat_s;
Radek Krejcie7b95092019-05-15 11:03:07 +0200309 LYD_FORMAT informat_d, outformat_d = 0;
Radek Krejcied5acc52019-04-25 15:57:04 +0200310#if 0
Radek Krejcie7b95092019-05-15 11:03:07 +0200311 LYD_FORMAT ylformat = 0;
Radek Krejcied5acc52019-04-25 15:57:04 +0200312#endif
313 struct ly_set *searchpaths = NULL;
314 const char *outtarget_s = NULL;
315 char **feat = NULL, *ptr, *featlist, *dir;
316 struct stat st;
317 uint32_t u;
318 int options_ctx = LY_CTX_NOYANGLIBRARY, list = 0, outoptions_s = 0, outline_length_s = 0;
Radek Krejcie7b95092019-05-15 11:03:07 +0200319 int autodetection = 0, options_parser = 0, merge = 0;
320 const char *oper_file = NULL;
Radek Krejcied5acc52019-04-25 15:57:04 +0200321#if 0
Radek Krejcie7b95092019-05-15 11:03:07 +0200322 const char *envelope_s = NULL;
Radek Krejcied5acc52019-04-25 15:57:04 +0200323 char *ylpath = NULL;
Radek Krejcie7b95092019-05-15 11:03:07 +0200324 int options_dflt = 0, envelope = 0;
325 struct lyxml_elem *iter, *elem;
326 struct *subroot, *next, *node;
327#endif
Michal Vaskof03ed032020-03-04 13:31:44 +0100328 struct lyd_node *tree = NULL;
Radek Krejcied5acc52019-04-25 15:57:04 +0200329 struct dataitem {
330 const char *filename;
Radek Krejcied5acc52019-04-25 15:57:04 +0200331 struct lyd_node *tree;
332 struct dataitem *next;
333 LYD_FORMAT format;
Michal Vaskoa3881362020-01-21 15:57:35 +0100334 int flags;
Radek Krejcied5acc52019-04-25 15:57:04 +0200335 } *data = NULL, *data_item, *data_prev = NULL;
Radek Krejcied5acc52019-04-25 15:57:04 +0200336 struct ly_set *mods = NULL;
337 void *p;
338 int index = 0;
339
340 opterr = 0;
341#ifndef NDEBUG
Radek Krejci693262f2019-04-29 15:23:20 +0200342 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 +0200343#else
Radek Krejci693262f2019-04-29 15:23:20 +0200344 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 +0200345#endif
346 {
347 switch (opt) {
348#if 0
349 case 'a':
350 envelope = 1;
351 break;
352 case 'd':
353 if (!strcmp(optarg, "all")) {
354 options_dflt = (options_dflt & ~LYP_WD_MASK) | LYP_WD_ALL;
355 } else if (!strcmp(optarg, "all-tagged")) {
356 options_dflt = (options_dflt & ~LYP_WD_MASK) | LYP_WD_ALL_TAG;
357 } else if (!strcmp(optarg, "trim")) {
358 options_dflt = (options_dflt & ~LYP_WD_MASK) | LYP_WD_TRIM;
359 } else if (!strcmp(optarg, "implicit-tagged")) {
360 options_dflt = (options_dflt & ~LYP_WD_MASK) | LYP_WD_IMPL_TAG;
361 } else {
362 fprintf(stderr, "yanglint error: unknown default mode %s\n", optarg);
363 help(1);
364 goto cleanup;
365 }
366 break;
367#endif
368 case 'f':
369 if (!strcasecmp(optarg, "yang")) {
370 outformat_s = LYS_OUT_YANG;
Radek Krejcied5acc52019-04-25 15:57:04 +0200371 outformat_d = 0;
Radek Krejcie7b95092019-05-15 11:03:07 +0200372#if 0
Radek Krejcied5acc52019-04-25 15:57:04 +0200373 } else if (!strcasecmp(optarg, "tree")) {
374 outformat_s = LYS_OUT_TREE;
375 outformat_d = 0;
376 } else if (!strcasecmp(optarg, "tree-rfc")) {
377 outformat_s = LYS_OUT_TREE;
378 outoptions_s |= LYS_OUTOPT_TREE_RFC;
379 outformat_d = 0;
FredGand944bdc2019-11-05 21:57:07 +0800380#endif
Radek Krejcied5acc52019-04-25 15:57:04 +0200381 } else if (!strcasecmp(optarg, "yin")) {
382 outformat_s = LYS_OUT_YIN;
383 outformat_d = 0;
Radek Krejcid8c0f5e2019-11-17 12:18:34 +0800384 } else if (!strcasecmp(optarg, "info")) {
385 outformat_s = LYS_OUT_YANG_COMPILED;
Radek Krejcied5acc52019-04-25 15:57:04 +0200386 outformat_d = 0;
387 } else if (!strcasecmp(optarg, "xml")) {
388 outformat_s = 0;
389 outformat_d = LYD_XML;
Radek Krejcie7b95092019-05-15 11:03:07 +0200390#if 0
Radek Krejcied5acc52019-04-25 15:57:04 +0200391 } else if (!strcasecmp(optarg, "json")) {
392 outformat_s = 0;
393 outformat_d = LYD_JSON;
394#endif
395 } else {
396 fprintf(stderr, "yanglint error: unknown output format %s\n", optarg);
397 help(1);
398 goto cleanup;
399 }
400 break;
401 case 'F':
402 featsize++;
403 if (!feat) {
404 p = malloc(sizeof *feat);
405 } else {
406 p = realloc(feat, featsize * sizeof *feat);
407 }
408 if (!p) {
409 fprintf(stderr, "yanglint error: Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno));
410 goto cleanup;
411 }
412 feat = p;
413 feat[featsize - 1] = strdup(optarg);
414 ptr = strchr(feat[featsize - 1], ':');
415 if (!ptr) {
416 fprintf(stderr, "yanglint error: Invalid format of the features specification (%s)", optarg);
417 goto cleanup;
418 }
419 *ptr = '\0';
420
421 break;
422#if 0
423 case 'g':
424 outoptions_s |= LYS_OUTOPT_TREE_GROUPING;
425 break;
426 case 'u':
427 outoptions_s |= LYS_OUTOPT_TREE_USES;
428 break;
429 case 'n':
430 outoptions_s |= LYS_OUTOPT_TREE_NO_LEAFREF;
431 break;
Radek Krejcie1f6d5a2019-11-17 14:03:04 +0800432#endif
Radek Krejcied5acc52019-04-25 15:57:04 +0200433 case 'P':
434 outtarget_s = optarg;
435 break;
Radek Krejcie1f6d5a2019-11-17 14:03:04 +0800436#if 0
Radek Krejcied5acc52019-04-25 15:57:04 +0200437 case 'L':
438 outline_length_s = atoi(optarg);
439 break;
440#endif
441 case 'h':
442 help(0);
443 ret = EXIT_SUCCESS;
444 goto cleanup;
445#if 0
446 case 'H':
447 tree_help();
448 ret = EXIT_SUCCESS;
449 goto cleanup;
450#endif
451 case 'i':
452 options_ctx |= LY_CTX_ALLIMPLEMENTED;
453 break;
454 case 'D':
455 if (options_ctx & LY_CTX_DISABLE_SEARCHDIRS) {
456 fprintf(stderr, "yanglint error: -D specified too many times.\n");
457 goto cleanup;
458 } else if (options_ctx & LY_CTX_DISABLE_SEARCHDIR_CWD) {
459 options_ctx &= ~LY_CTX_DISABLE_SEARCHDIR_CWD;
460 options_ctx |= LY_CTX_DISABLE_SEARCHDIRS;
461 } else {
462 options_ctx |= LY_CTX_DISABLE_SEARCHDIR_CWD;
463 }
464 break;
465 case 'l':
466 list = 1;
467 break;
468#if 0
469 case 'm':
470 merge = 1;
471 break;
472#endif
473 case 'o':
474 if (out != stdout) {
475 fclose(out);
476 }
477 out = fopen(optarg, "w");
478 if (!out) {
479 fprintf(stderr, "yanglint error: unable open output file %s (%s)\n", optarg, strerror(errno));
480 goto cleanup;
481 }
482 break;
483 case 'p':
484 if (stat(optarg, &st) == -1) {
485 fprintf(stderr, "yanglint error: Unable to use search path (%s) - %s.\n", optarg, strerror(errno));
486 goto cleanup;
487 }
488 if (!S_ISDIR(st.st_mode)) {
489 fprintf(stderr, "yanglint error: Provided search path is not a directory.\n");
490 goto cleanup;
491 }
492 if (!searchpaths) {
493 searchpaths = ly_set_new();
494 }
495 ly_set_add(searchpaths, optarg, 0);
496 break;
497#if 0
498 case 'r':
499 case 'O':
500 if (oper_file || (options_parser & LYD_OPT_NOEXTDEPS)) {
501 fprintf(stderr, "yanglint error: The operational datastore (-O) cannot be set multiple times.\n");
502 goto cleanup;
503 }
504 if (optarg[0] == '!') {
505 /* ignore extenral dependencies to the operational datastore */
506 options_parser |= LYD_OPT_NOEXTDEPS;
507 } else {
508 /* external file with the operational datastore */
509 oper_file = optarg;
510 }
511 break;
Radek Krejcie7b95092019-05-15 11:03:07 +0200512#endif
Radek Krejcied5acc52019-04-25 15:57:04 +0200513 case 's':
514 options_parser |= LYD_OPT_STRICT;
515 break;
516 case 't':
517 if (!strcmp(optarg, "auto")) {
Radek Krejcied5acc52019-04-25 15:57:04 +0200518 autodetection = 1;
Michal Vaskob36053d2020-03-26 15:49:30 +0100519 /*} else if (!strcmp(optarg, "config")) {
Michal Vaskoa3881362020-01-21 15:57:35 +0100520 options_parser |= LYD_OPT_CONFIG;
Radek Krejcied5acc52019-04-25 15:57:04 +0200521 } else if (!strcmp(optarg, "get")) {
Michal Vaskoa3881362020-01-21 15:57:35 +0100522 options_parser |= LYD_OPT_GET;
Radek Krejcied5acc52019-04-25 15:57:04 +0200523 } else if (!strcmp(optarg, "getconfig")) {
Michal Vaskoa3881362020-01-21 15:57:35 +0100524 options_parser |= LYD_OPT_GETCONFIG;
Michal Vaskob36053d2020-03-26 15:49:30 +0100525 } else if (!strcmp(optarg, "edit")) {
Michal Vasko9f96a052020-03-10 09:41:45 +0100526 options_parser |= LYD_OPT_EDIT;*/
Radek Krejcied5acc52019-04-25 15:57:04 +0200527 } else if (!strcmp(optarg, "data")) {
Michal Vaskoa3881362020-01-21 15:57:35 +0100528 /* no options */
Radek Krejcied5acc52019-04-25 15:57:04 +0200529 } else {
530 fprintf(stderr, "yanglint error: unknown data tree type %s\n", optarg);
531 help(1);
532 goto cleanup;
533 }
534 break;
Radek Krejcied5acc52019-04-25 15:57:04 +0200535 case 'v':
536 version();
537 ret = EXIT_SUCCESS;
538 goto cleanup;
539 case 'V':
540 verbose++;
541 break;
542#ifndef NDEBUG
543 case 'G':
544 u = 0;
545 ptr = optarg;
546 while (ptr[0]) {
547 if (!strncmp(ptr, "dict", 4)) {
548 u |= LY_LDGDICT;
549 ptr += 4;
550 } else if (!strncmp(ptr, "yang", 4)) {
551 u |= LY_LDGYANG;
552 ptr += 4;
553 } else if (!strncmp(ptr, "yin", 3)) {
554 u |= LY_LDGYIN;
555 ptr += 3;
556 } else if (!strncmp(ptr, "xpath", 5)) {
557 u |= LY_LDGXPATH;
558 ptr += 5;
559 } else if (!strncmp(ptr, "diff", 4)) {
560 u |= LY_LDGDIFF;
561 ptr += 4;
562 }
563
564 if (ptr[0]) {
565 if (ptr[0] != ',') {
566 fprintf(stderr, "yanglint error: unknown debug group string \"%s\"\n", optarg);
567 goto cleanup;
568 }
569 ++ptr;
570 }
571 }
572 ly_verb_dbg(u);
573 break;
574#endif
575#if 0
576 case 'y':
577 ptr = strrchr(optarg, '.');
578 if (ptr) {
579 ptr++;
580 if (!strcmp(ptr, "xml")) {
581 ylformat = LYD_XML;
582 } else if (!strcmp(ptr, "json")) {
583 ylformat = LYD_JSON;
584 } else {
585 fprintf(stderr, "yanglint error: yang-library file in an unknown format \"%s\".\n", ptr);
586 goto cleanup;
587 }
588 } else {
589 fprintf(stderr, "yanglint error: yang-library file in an unknown format.\n");
590 goto cleanup;
591 }
592 ylpath = optarg;
593 break;
594#endif
595 default:
596 help(1);
597 if (optopt) {
598 fprintf(stderr, "yanglint error: invalid option: -%c\n", optopt);
599 } else {
600 fprintf(stderr, "yanglint error: invalid option: %s\n", argv[optind - 1]);
601 }
602 goto cleanup;
603 }
604 }
605
606 /* check options compatibility */
607 if (!list && optind >= argc) {
608 help(1);
609 fprintf(stderr, "yanglint error: missing <file> to process\n");
610 goto cleanup;
611 }
Radek Krejcied5acc52019-04-25 15:57:04 +0200612 if (outformat_s && outformat_s != LYS_OUT_TREE && (optind + 1) < argc) {
613 /* we have multiple schemas to be printed as YIN or YANG */
614 fprintf(stderr, "yanglint error: too many schemas to convert and store.\n");
615 goto cleanup;
616 }
617 if (outoptions_s || outtarget_s || outline_length_s) {
618#if 0
619 if (outformat_d || (outformat_s && outformat_s != LYS_OUT_TREE)) {
620 /* we have --tree-print-grouping with other output format than tree */
621 fprintf(stderr,
622 "yanglint warning: --tree options take effect only in case of the tree output format.\n");
623 }
624 }
625 if (merge) {
626 if (autodetection || (options_parser & (LYD_OPT_RPC | LYD_OPT_RPCREPLY | LYD_OPT_NOTIF))) {
627 fprintf(stderr, "yanglint warning: merging not allowed, ignoring option -m.\n");
628 merge = 0;
629 } else {
630 /* first, files will be parsed as trusted to allow missing data, then the data trees will be merged
631 * and the result will be validated */
632 options_parser |= LYD_OPT_TRUSTED;
633 }
634#endif
635 }
636#if 0
637 if (!outformat_d && options_dflt) {
638 /* we have options for printing default nodes, but data output not specified */
639 fprintf(stderr, "yanglint warning: default mode is ignored when not printing data.\n");
640 }
641 if (outformat_s && (options_parser || autodetection)) {
642 /* we have options for printing data tree, but output is schema */
643 fprintf(stderr, "yanglint warning: data parser options are ignored when printing schema.\n");
644 }
645 if (oper_file && (!autodetection && !(options_parser & (LYD_OPT_RPC | LYD_OPT_RPCREPLY | LYD_OPT_NOTIF)))) {
646 fprintf(stderr, "yanglint warning: operational datastore applies only to RPCs or Notifications.\n");
647 /* ignore operational datastore file */
648 oper_file = NULL;
649 }
650 if ((options_parser & LYD_OPT_TYPEMASK) == LYD_OPT_DATA) {
651 /* add option to ignore ietf-yang-library data for implicit data type */
652 options_parser |= LYD_OPT_DATA_NO_YANGLIB;
653 }
Michal Vaskoa3881362020-01-21 15:57:35 +0100654#endif
Radek Krejcied5acc52019-04-25 15:57:04 +0200655
656 /* set callback for printing libyang messages */
657 ly_set_log_clb(libyang_verbclb, 1);
658#if 0
659 /* create libyang context */
660 if (ylpath) {
661 ctx = ly_ctx_new_ylpath(searchpaths ? (const char*)searchpaths->set.g[0] : NULL, ylpath, ylformat, options_ctx);
662 } else {
663#else
664 {
665#endif
666 ly_ctx_new(NULL, options_ctx, &ctx);
667 }
668 if (!ctx) {
669 goto cleanup;
670 }
671
672 /* set searchpaths */
673 if (searchpaths) {
674 for (u = 0; u < searchpaths->count; u++) {
675 ly_ctx_set_searchdir(ctx, (const char*)searchpaths->objs[u]);
676 }
677 index = u + 1;
678 }
679
680 /* derefered setting of verbosity in libyang after context initiation */
681 ly_verb(verbose);
682
683 mods = ly_set_new();
684
685
686 /* divide input files */
687 for (i = 0; i < argc - optind; i++) {
688 /* get the file format */
Radek Krejcie7b95092019-05-15 11:03:07 +0200689 if (!get_fileformat(argv[optind + i], &informat_s, &informat_d)) {
Radek Krejcied5acc52019-04-25 15:57:04 +0200690 goto cleanup;
691 }
692
693 if (informat_s) {
694 /* load/validate schema */
695 if (verbose >= 2) {
696 fprintf(stdout, "Validating %s schema file.\n", argv[optind + i]);
697 }
698 dir = strdup(argv[optind + i]);
699 ly_ctx_set_searchdir(ctx, ptr = dirname(dir));
700 mod = lys_parse_path(ctx, argv[optind + i], informat_s);
701 ly_ctx_unset_searchdir(ctx, index);
702 free(dir);
703 if (!mod) {
704 goto cleanup;
705 }
706 ly_set_add(mods, (void *)mod, 0);
Radek Krejcied5acc52019-04-25 15:57:04 +0200707 } else {
708 if (autodetection && informat_d != LYD_XML) {
709 /* data file content autodetection is possible only for XML input */
710 fprintf(stderr, "yanglint error: data type autodetection is applicable only to XML files.\n");
711 goto cleanup;
712 }
713
714 /* remember data filename and its format */
715 if (!data) {
716 data = data_item = malloc(sizeof *data);
717 } else {
718 for (data_item = data; data_item->next; data_item = data_item->next);
719 data_item->next = malloc(sizeof *data_item);
720 data_item = data_item->next;
721 }
722 data_item->filename = argv[optind + i];
723 data_item->format = informat_d;
Michal Vaskoa3881362020-01-21 15:57:35 +0100724 data_item->flags = options_parser;
Radek Krejcied5acc52019-04-25 15:57:04 +0200725 data_item->tree = NULL;
Radek Krejcied5acc52019-04-25 15:57:04 +0200726 data_item->next = NULL;
Radek Krejcied5acc52019-04-25 15:57:04 +0200727 }
728 }
Radek Krejcied5acc52019-04-25 15:57:04 +0200729 if (outformat_d && !data && !list) {
730 fprintf(stderr, "yanglint error: no input data file for the specified data output format.\n");
731 goto cleanup;
732 }
Radek Krejcied5acc52019-04-25 15:57:04 +0200733
734 /* enable specified features, if not specified, all the module's features are enabled */
735 u = 4; /* skip internal libyang modules */
736 while ((mod = ly_ctx_get_module_iter(ctx, &u))) {
737 for (i = 0; i < featsize; i++) {
738 if (!strcmp(feat[i], mod->name)) {
739 /* parse features spec */
740 featlist = strdup(feat[i] + strlen(feat[i]) + 1);
741 ptr = NULL;
742 while((ptr = strtok(ptr ? NULL : featlist, ","))) {
743 if (verbose >= 2) {
744 fprintf(stdout, "Enabling feature %s in module %s.\n", ptr, mod->name);
745 }
746 if (lys_feature_enable(mod, ptr)) {
747 fprintf(stderr, "Feature %s not defined in module %s.\n", ptr, mod->name);
748 }
749 }
750 free(featlist);
751 break;
752 }
753 }
754 if (i == featsize) {
755 if (verbose >= 2) {
756 fprintf(stdout, "Enabling all features in module %s.\n", mod->name);
757 }
758 lys_feature_enable(mod, "*");
759 }
760 }
761
762 /* convert (print) to FORMAT */
763 if (outformat_s) {
Radek Krejcie1f6d5a2019-11-17 14:03:04 +0800764 if (outtarget_s) {
765 lys_node_print_file(out, ctx, NULL, outformat_s, outtarget_s, outline_length_s, outoptions_s);
766 } else {
767 for (u = 0; u < mods->count; u++) {
768 if (u) {
769 fputs("\n", out);
770 }
771 lys_print_file(out, (struct lys_module *)mods->objs[u], outformat_s, outline_length_s, outoptions_s);
Radek Krejcied5acc52019-04-25 15:57:04 +0200772 }
773 }
Radek Krejcied5acc52019-04-25 15:57:04 +0200774 } else if (data) {
Radek Krejcied5acc52019-04-25 15:57:04 +0200775
776 /* prepare operational datastore when specified for RPC/Notification */
777 if (oper_file) {
778 /* get the file format */
779 if (!get_fileformat(oper_file, NULL, &informat_d)) {
780 goto cleanup;
781 } else if (!informat_d) {
782 fprintf(stderr, "yanglint error: The operational data are expected in XML or JSON format.\n");
783 goto cleanup;
784 }
Michal Vaskof03ed032020-03-04 13:31:44 +0100785 tree = lyd_parse_path(ctx, oper_file, informat_d, LYD_OPT_PARSE_ONLY);
786 if (!tree) {
Radek Krejcied5acc52019-04-25 15:57:04 +0200787 fprintf(stderr, "yanglint error: Failed to parse the operational datastore file for RPC/Notification validation.\n");
788 goto cleanup;
789 }
790 }
791
792 for (data_item = data, data_prev = NULL; data_item; data_prev = data_item, data_item = data_item->next) {
793 /* parse data file - via LYD_OPT_TRUSTED postpone validation when all data are loaded and merged */
Radek Krejcie7b95092019-05-15 11:03:07 +0200794#if 0
Radek Krejcied5acc52019-04-25 15:57:04 +0200795 if (autodetection) {
796 /* erase option not covered by LYD_OPT_TYPEMASK, but used according to the type */
797 options_parser &= ~LYD_OPT_DATA_NO_YANGLIB;
798 /* automatically detect data type from the data top level */
799 data_item->xml = lyxml_parse_path(ctx, data_item->filename, 0);
800 if (!data_item->xml) {
801 fprintf(stderr, "yanglint error: parsing XML data for data type autodetection failed.\n");
802 goto cleanup;
803 }
804
805 /* NOTE: namespace is ignored to simplify usage of this feature */
806 if (!strcmp(data_item->xml->name, "data")) {
807 if (verbose >= 2) {
808 fprintf(stdout, "Parsing %s as complete datastore.\n", data_item->filename);
809 }
810 options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_DATA_NO_YANGLIB;
811 data_item->type = LYD_OPT_DATA;
812 } else if (!strcmp(data_item->xml->name, "config")) {
813 if (verbose >= 2) {
814 fprintf(stdout, "Parsing %s as config data.\n", data_item->filename);
815 }
816 options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_CONFIG;
817 data_item->type = LYD_OPT_CONFIG;
818 } else if (!strcmp(data_item->xml->name, "get-reply")) {
819 if (verbose >= 2) {
820 fprintf(stdout, "Parsing %s as <get> reply data.\n", data_item->filename);
821 }
822 options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_GET;
823 data_item->type = LYD_OPT_GET;
824 } else if (!strcmp(data_item->xml->name, "get-config-reply")) {
825 if (verbose >= 2) {
826 fprintf(stdout, "Parsing %s as <get-config> reply data.\n", data_item->filename);
827 }
828 options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_GETCONFIG;
829 data_item->type = LYD_OPT_GETCONFIG;
830 } else if (!strcmp(data_item->xml->name, "edit-config")) {
831 if (verbose >= 2) {
832 fprintf(stdout, "Parsing %s as <edit-config> data.\n", data_item->filename);
833 }
834 options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_EDIT;
835 data_item->type = LYD_OPT_EDIT;
836 } else if (!strcmp(data_item->xml->name, "rpc")) {
837 if (verbose >= 2) {
838 fprintf(stdout, "Parsing %s as <rpc> data.\n", data_item->filename);
839 }
840 options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_RPC;
841 data_item->type = LYD_OPT_RPC;
842 } else if (!strcmp(data_item->xml->name, "rpc-reply")) {
843 if (verbose >= 2) {
844 fprintf(stdout, "Parsing %s as <rpc-reply> data.\n", data_item->filename);
845 }
846
847 data_item->type = LYD_OPT_RPCREPLY;
848 if (!data_item->next || (data_prev && !data_prev->tree)) {
849 fprintf(stderr, "RPC reply (%s) must be paired with the original RPC, see help.\n", data_item->filename);
850 goto cleanup;
851 }
852
853 continue;
854 } else if (!strcmp(data_item->xml->name, "notification")) {
855 if (verbose >= 2) {
856 fprintf(stdout, "Parsing %s as <notification> data.\n", data_item->filename);
857 }
858 options_parser = (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_NOTIF;
859 data_item->type = LYD_OPT_NOTIF;
860
861 /* ignore eventTime element if present */
862 while (data_item->xml->child && !strcmp(data_item->xml->child->name, "eventTime")) {
863 lyxml_free(ctx, data_item->xml->child);
864 }
865 } else {
866 fprintf(stderr, "yanglint error: invalid top-level element \"%s\" for data type autodetection.\n",
867 data_item->xml->name);
868 goto cleanup;
869 }
870
871 data_item->tree = lyd_parse_xml(ctx, &data_item->xml->child, options_parser, oper);
872 if (data_prev && data_prev->type == LYD_OPT_RPCREPLY) {
873parse_reply:
874 /* check result of the RPC parsing, we are going to do another parsing in this step */
875 if (ly_errno) {
876 goto cleanup;
877 }
878
879 /* check that we really have RPC for the reply */
880 if (data_item->type != LYD_OPT_RPC) {
881 fprintf(stderr, "yanglint error: RPC reply (%s) must be paired with the original RPC, see help.\n", data_prev->filename);
882 goto cleanup;
883 }
884
885 if (data_prev->format == LYD_XML) {
886 /* ignore <ok> and <rpc-error> elements if present */
887 u = 0;
888 LY_TREE_FOR_SAFE(data_prev->xml->child, iter, elem) {
889 if (!strcmp(data_prev->xml->child->name, "ok")) {
890 if (u) {
891 /* rpc-error or ok already present */
892 u = 0x8; /* error flag */
893 } else {
894 u = 0x1 | 0x4; /* <ok> flag with lyxml_free() flag */
895 }
896 } else if (!strcmp(data_prev->xml->child->name, "rpc-error")) {
897 if (u && (u & 0x1)) {
898 /* ok already present, rpc-error can be present multiple times */
899 u = 0x8; /* error flag */
900 } else {
901 u = 0x2 | 0x4; /* <rpc-error> flag with lyxml_free() flag */
902 }
903 }
904
905 if (u == 0x8) {
906 fprintf(stderr, "yanglint error: Invalid RPC reply (%s) content.\n", data_prev->filename);
907 goto cleanup;
908 } else if (u & 0x4) {
909 lyxml_free(ctx, data_prev->xml->child);
910 u &= ~0x4; /* unset lyxml_free() flag */
911 }
912 }
913
914 /* finally, parse RPC reply from the previous step */
915 data_prev->tree = lyd_parse_xml(ctx, &data_prev->xml->child,
916 (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_RPCREPLY, data_item->tree, oper);
917 } else { /* LYD_JSON */
918 data_prev->tree = lyd_parse_path(ctx, data_prev->filename, data_item->format,
919 (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_RPCREPLY, data_item->tree, oper);
920 }
921 }
922 } else if ((options_parser & LYD_OPT_TYPEMASK) == LYD_OPT_RPCREPLY) {
923 if (data_prev && !data_prev->tree) {
924 /* now we should have RPC for the preceding RPC reply */
925 data_item->tree = lyd_parse_path(ctx, data_item->filename, data_item->format,
926 (options_parser & ~LYD_OPT_TYPEMASK) | LYD_OPT_RPC, oper);
927 data_item->type = LYD_OPT_RPC;
928 goto parse_reply;
929 } else {
930 /* now we have RPC reply which will be parsed in next step together with its RPC */
931 if (!data_item->next) {
932 fprintf(stderr, "yanglint error: RPC reply (%s) must be paired with the original RPC, see help.\n", data_item->filename);
933 goto cleanup;
934 }
935 if (data_item->format == LYD_XML) {
936 /* create rpc-reply container to unify handling with autodetection */
937 data_item->xml = calloc(1, sizeof *data_item->xml);
938 if (!data_item->xml) {
939 fprintf(stderr, "yanglint error: Memory allocation failed failed.\n");
940 goto cleanup;
941 }
942 data_item->xml->name = lydict_insert(ctx, "rpc-reply", 9);
943 data_item->xml->prev = data_item->xml;
944 data_item->xml->child = lyxml_parse_path(ctx, data_item->filename, LYXML_PARSE_MULTIROOT | LYXML_PARSE_NOMIXEDCONTENT);
945 if (data_item->xml->child) {
946 data_item->xml->child->parent = data_item->xml;
947 }
948 }
949 continue;
950 }
951 } else {
Radek Krejcie7b95092019-05-15 11:03:07 +0200952#else
953 {
954#endif
Michal Vaskoa3881362020-01-21 15:57:35 +0100955 data_item->tree = lyd_parse_path(ctx, data_item->filename, data_item->format, options_parser);
Radek Krejcied5acc52019-04-25 15:57:04 +0200956 }
Radek Krejcie7b95092019-05-15 11:03:07 +0200957 if (ly_err_first(ctx)) {
Radek Krejcied5acc52019-04-25 15:57:04 +0200958 goto cleanup;
959 }
Radek Krejcie7b95092019-05-15 11:03:07 +0200960#if 0
Radek Krejcied5acc52019-04-25 15:57:04 +0200961 if (merge && data != data_item) {
962 if (!data->tree) {
963 data->tree = data_item->tree;
964 } else if (data_item->tree) {
965 /* merge results */
966 if (lyd_merge(data->tree, data_item->tree, LYD_OPT_DESTRUCT | LYD_OPT_EXPLICIT)) {
967 fprintf(stderr, "yanglint error: merging multiple data trees failed.\n");
968 goto cleanup;
969 }
970 }
971 data_item->tree = NULL;
972 }
Radek Krejcie7b95092019-05-15 11:03:07 +0200973#endif
Radek Krejcied5acc52019-04-25 15:57:04 +0200974 }
Radek Krejcie7b95092019-05-15 11:03:07 +0200975#if 0
Radek Krejcied5acc52019-04-25 15:57:04 +0200976 if (merge) {
977 /* validate the merged data tree, do not trust the input, invalidate all the data first */
978 LY_TREE_FOR(data->tree, subroot) {
979 LY_TREE_DFS_BEGIN(subroot, next, node) {
980 node->validity = LYD_VAL_OK;
981 switch (node->schema->nodetype) {
982 case LYS_LEAFLIST:
983 case LYS_LEAF:
984 if (((struct lys_node_leaf *)node->schema)->type.base == LY_TYPE_LEAFREF) {
985 node->validity |= LYD_VAL_LEAFREF;
986 }
987 break;
988 case LYS_LIST:
989 node->validity |= LYD_VAL_UNIQUE;
990 /* falls through */
991 case LYS_CONTAINER:
992 case LYS_NOTIF:
993 case LYS_RPC:
994 case LYS_ACTION:
995 node->validity |= LYD_VAL_MAND;
996 break;
997 default:
998 break;
999 }
1000 LY_TREE_DFS_END(subroot, next, node)
1001 }
1002 }
1003 if (lyd_validate(&data->tree, options_parser & ~LYD_OPT_TRUSTED, ctx)) {
1004 goto cleanup;
1005 }
1006 }
Radek Krejcie7b95092019-05-15 11:03:07 +02001007#endif
Radek Krejcied5acc52019-04-25 15:57:04 +02001008 /* print only if data output format specified */
1009 if (outformat_d) {
1010 for (data_item = data; data_item; data_item = data_item->next) {
1011 if (!merge && verbose >= 2) {
1012 fprintf(stdout, "File %s:\n", data_item->filename);
1013 }
Radek Krejcie7b95092019-05-15 11:03:07 +02001014#if 0
Radek Krejcied5acc52019-04-25 15:57:04 +02001015 if (outformat_d == LYD_XML && envelope) {
1016 switch (data_item->type) {
1017 case LYD_OPT_DATA:
1018 envelope_s = "data";
1019 break;
1020 case LYD_OPT_CONFIG:
1021 envelope_s = "config";
1022 break;
1023 case LYD_OPT_GET:
1024 envelope_s = "get-reply";
1025 break;
1026 case LYD_OPT_GETCONFIG:
1027 envelope_s = "get-config-reply";
1028 break;
1029 case LYD_OPT_EDIT:
1030 envelope_s = "edit-config";
1031 break;
1032 case LYD_OPT_RPC:
1033 envelope_s = "rpc";
1034 break;
1035 case LYD_OPT_RPCREPLY:
1036 envelope_s = "rpc-reply";
1037 break;
1038 case LYD_OPT_NOTIF:
1039 envelope_s = "notification";
1040 break;
1041 }
1042 fprintf(out, "<%s>\n", envelope_s);
1043 if (data_item->type == LYD_OPT_RPC && data_item->tree->schema->nodetype != LYS_RPC) {
1044 /* action */
1045 fprintf(out, "<action xmlns=\"urn:ietf:params:xml:ns:yang:1\">\n");
1046 }
1047 }
Radek Krejcie7b95092019-05-15 11:03:07 +02001048#endif
Michal Vaskoa3881362020-01-21 15:57:35 +01001049 lyd_print_file(out, data_item->tree, outformat_d, LYDP_WITHSIBLINGS | LYDP_FORMAT /* TODO defaults | options_dflt */);
Radek Krejcie7b95092019-05-15 11:03:07 +02001050#if 0
Radek Krejcied5acc52019-04-25 15:57:04 +02001051 if (envelope_s) {
1052 if (data_item->type == LYD_OPT_RPC && data_item->tree->schema->nodetype != LYS_RPC) {
1053 fprintf(out, "</action>\n");
1054 }
1055 fprintf(out, "</%s>\n", envelope_s);
1056 }
1057 if (merge) {
1058 /* stop after first item */
1059 break;
1060 }
Radek Krejcie7b95092019-05-15 11:03:07 +02001061#endif
Radek Krejcied5acc52019-04-25 15:57:04 +02001062 }
1063 }
Radek Krejcied5acc52019-04-25 15:57:04 +02001064 }
1065#if 0
1066 if (list) {
1067 print_list(out, ctx, outformat_d);
1068 }
1069#endif
1070
1071 ret = EXIT_SUCCESS;
1072
1073cleanup:
1074 if (out && out != stdout) {
1075 fclose(out);
1076 }
1077 ly_set_free(mods, NULL);
1078 ly_set_free(searchpaths, NULL);
1079 for (i = 0; i < featsize; i++) {
1080 free(feat[i]);
1081 }
1082 free(feat);
Radek Krejcied5acc52019-04-25 15:57:04 +02001083 for (; data; data = data_item) {
1084 data_item = data->next;
Radek Krejcie7b95092019-05-15 11:03:07 +02001085 lyd_free_all(data->tree);
Radek Krejcied5acc52019-04-25 15:57:04 +02001086 free(data);
1087 }
Radek Krejcied5acc52019-04-25 15:57:04 +02001088 ly_ctx_destroy(ctx, NULL);
1089
1090 return ret;
1091}