blob: 88194436ddc93c79f6453abf02a5e4746889b60c [file] [log] [blame]
Radek Krejcie9f13b12020-11-09 17:42:04 +01001/**
2 * @file common.c
3 * @author Radek Krejci <rkrejci@cesnet.cz>
4 * @brief libyang's yanglint tool - common functions for both interactive and non-interactive mode.
5 *
6 * Copyright (c) 2020 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#define _GNU_SOURCE
Radek Krejcif8dc59a2020-11-25 13:47:44 +010016#define _POSIX_C_SOURCE 200809L /* strdup, strndup */
Radek Krejcie9f13b12020-11-09 17:42:04 +010017
18#include "common.h"
19
20#include <assert.h>
21#include <errno.h>
22#include <getopt.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26#include <sys/stat.h>
27
28#include "compat.h"
29#include "libyang.h"
30
31int
32parse_schema_path(const char *path, char **dir, char **module)
33{
34 char *p;
35
36 assert(dir);
37 assert(module);
38
39 /* split the path to dirname and basename for further work */
40 *dir = strdup(path);
41 *module = strrchr(*dir, '/');
42 if (!(*module)) {
43 *module = *dir;
44 *dir = strdup("./");
45 } else {
46 *module[0] = '\0'; /* break the dir */
47 *module = strdup((*module) + 1);
48 }
49 /* get the pure module name without suffix or revision part of the filename */
50 if ((p = strchr(*module, '@'))) {
51 /* revision */
52 *p = '\0';
53 } else if ((p = strrchr(*module, '.'))) {
54 /* fileformat suffix */
55 *p = '\0';
56 }
57
58 return 0;
59}
60
61int
62get_input(const char *filepath, LYS_INFORMAT *format_schema, LYD_FORMAT *format_data, struct ly_in **in)
63{
64 struct stat st;
65
66 /* check that the filepath exists and is a regular file */
67 if (stat(filepath, &st) == -1) {
68 YLMSG_E("Unable to use input filepath (%s) - %s.\n", filepath, strerror(errno));
69 return -1;
70 }
71 if (!S_ISREG(st.st_mode)) {
72 YLMSG_E("Provided input file (%s) is not a regular file.\n", filepath);
73 return -1;
74 }
75
Michal Vaskod3b10542021-02-03 11:31:16 +010076 if ((format_schema && !*format_schema) || (format_data && !*format_data)) {
77 /* get the file format */
78 if (get_format(filepath, format_schema, format_data)) {
79 return -1;
80 }
Radek Krejcie9f13b12020-11-09 17:42:04 +010081 }
82
83 if (ly_in_new_filepath(filepath, 0, in)) {
84 YLMSG_E("Unable to process input file.\n");
85 return -1;
86 }
87
88 return 0;
89}
90
91void
92free_features(void *flist)
93{
94 struct schema_features *rec = (struct schema_features *)flist;
95
96 if (rec) {
Michal Vaskoa9a98612021-11-22 10:00:27 +010097 free(rec->mod_name);
Radek Krejcie9f13b12020-11-09 17:42:04 +010098 if (rec->features) {
99 for (uint32_t u = 0; rec->features[u]; ++u) {
100 free(rec->features[u]);
101 }
102 free(rec->features);
103 }
104 free(rec);
105 }
106}
107
108void
109get_features(struct ly_set *fset, const char *module, const char ***features)
110{
111 /* get features list for this module */
112 for (uint32_t u = 0; u < fset->count; ++u) {
113 struct schema_features *sf = (struct schema_features *)fset->objs[u];
Michal Vaskoa9a98612021-11-22 10:00:27 +0100114 if (!strcmp(module, sf->mod_name)) {
Radek Krejci8143bb42021-04-07 11:43:50 +0200115 /* matched module - explicitly set features */
Radek Krejcie9f13b12020-11-09 17:42:04 +0100116 *features = (const char **)sf->features;
Michal Vasko686d8fc2021-11-22 10:03:23 +0100117 sf->applied = 1;
Radek Krejcie9f13b12020-11-09 17:42:04 +0100118 return;
119 }
120 }
Radek Krejci8143bb42021-04-07 11:43:50 +0200121
Michal Vasko59740872021-11-22 10:01:34 +0100122 /* features not set so disable all */
123 *features = NULL;
Radek Krejcie9f13b12020-11-09 17:42:04 +0100124}
125
126int
127parse_features(const char *fstring, struct ly_set *fset)
128{
129 struct schema_features *rec;
130 char *p;
131
132 rec = calloc(1, sizeof *rec);
133 if (!rec) {
134 YLMSG_E("Unable to allocate features information record (%s).\n", strerror(errno));
135 return -1;
136 }
137 if (ly_set_add(fset, rec, 1, NULL)) {
138 YLMSG_E("Unable to store features information (%s).\n", strerror(errno));
139 free(rec);
140 return -1;
141 }
142
143 /* fill the record */
144 p = strchr(fstring, ':');
145 if (!p) {
Michal Vasko9a5f3dd2021-11-23 08:59:53 +0100146 YLMSG_E("Invalid format of the features specification (%s).\n", fstring);
Radek Krejcie9f13b12020-11-09 17:42:04 +0100147 return -1;
148 }
Michal Vaskoa9a98612021-11-22 10:00:27 +0100149 rec->mod_name = strndup(fstring, p - fstring);
Radek Krejcie9f13b12020-11-09 17:42:04 +0100150
151 /* start count on 2 to include terminating NULL byte */
152 for (int count = 2; p; ++count) {
153 size_t len = 0;
154 char *token = p + 1;
155 p = strchr(token, ',');
156 if (!p) {
157 /* the last item, if any */
158 len = strlen(token);
159 } else {
160 len = p - token;
161 }
162 if (len) {
163 char **fp = realloc(rec->features, count * sizeof *rec->features);
164 if (!fp) {
165 YLMSG_E("Unable to store features list information (%s).\n", strerror(errno));
166 return -1;
167 }
168 rec->features = fp;
169 rec->features[count - 1] = NULL; /* terminating NULL-byte */
170 fp = &rec->features[count - 2]; /* array item to set */
171 (*fp) = strndup(token, len);
172 }
173 }
174
175 return 0;
176}
177
178struct cmdline_file *
179fill_cmdline_file(struct ly_set *set, struct ly_in *in, const char *path, LYD_FORMAT format)
180{
181 struct cmdline_file *rec;
182
183 rec = malloc(sizeof *rec);
184 if (!rec) {
185 YLMSG_E("Allocating memory for data file information failed.\n");
186 return NULL;
187 }
188 if (set && ly_set_add(set, rec, 1, NULL)) {
189 free(rec);
Michal Vasko7edebb42021-01-25 14:18:46 +0100190 YLMSG_E("Storing data file information failed.\n");
Radek Krejcie9f13b12020-11-09 17:42:04 +0100191 return NULL;
192 }
193 rec->in = in;
194 rec->path = path;
195 rec->format = format;
196
197 return rec;
198}
199
200void
201free_cmdline_file(void *cmdline_file)
202{
203 struct cmdline_file *rec = (struct cmdline_file *)cmdline_file;
204
205 if (rec) {
206 ly_in_free(rec->in, 1);
207 free(rec);
208 }
209}
210
211void
212free_cmdline(char *argv[])
213{
214 if (argv) {
215 free(argv[0]);
216 free(argv);
217 }
218}
219
220int
221parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[])
222{
223 int count;
224 char **vector;
225 char *ptr;
226 char qmark = 0;
227
228 assert(cmdline);
229 assert(argc_p);
230 assert(argv_p);
231
232 /* init */
233 optind = 0; /* reinitialize getopt() */
234 count = 1;
235 vector = malloc((count + 1) * sizeof *vector);
236 vector[0] = strdup(cmdline);
237
238 /* command name */
239 strtok(vector[0], " ");
240
241 /* arguments */
242 while ((ptr = strtok(NULL, " "))) {
243 size_t len;
244 void *r;
245
246 len = strlen(ptr);
247
248 if (qmark) {
249 /* still in quotated text */
250 /* remove NULL termination of the previous token since it is not a token,
251 * but a part of the quotation string */
252 ptr[-1] = ' ';
253
254 if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) {
255 /* end of quotation */
256 qmark = 0;
257 /* shorten the argument by the terminating quotation mark */
258 ptr[len - 1] = '\0';
259 }
260 continue;
261 }
262
263 /* another token in cmdline */
264 ++count;
265 r = realloc(vector, (count + 1) * sizeof *vector);
266 if (!r) {
Michal Vasko9a5f3dd2021-11-23 08:59:53 +0100267 YLMSG_E("Memory allocation failed (%s:%d, %s).\n", __FILE__, __LINE__, strerror(errno));
Radek Krejcie9f13b12020-11-09 17:42:04 +0100268 free(vector);
269 return -1;
270 }
271 vector = r;
272 vector[count - 1] = ptr;
273
274 if ((ptr[0] == '"') || (ptr[0] == '\'')) {
275 /* remember the quotation mark to identify end of quotation */
276 qmark = ptr[0];
277
278 /* move the remembered argument after the quotation mark */
279 ++vector[count - 1];
280
281 /* check if the quotation is terminated within this token */
282 if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) {
283 /* end of quotation */
284 qmark = 0;
285 /* shorten the argument by the terminating quotation mark */
286 ptr[len - 1] = '\0';
287 }
288 }
289 }
290 vector[count] = NULL;
291
292 *argc_p = count;
293 *argv_p = vector;
294
295 return 0;
296}
297
298int
299get_format(const char *filename, LYS_INFORMAT *schema, LYD_FORMAT *data)
300{
301 char *ptr;
302 LYS_INFORMAT informat_s;
303 LYD_FORMAT informat_d;
304
305 /* get the file format */
306 if ((ptr = strrchr(filename, '.')) != NULL) {
307 ++ptr;
308 if (!strcmp(ptr, "yang")) {
309 informat_s = LYS_IN_YANG;
310 informat_d = 0;
311 } else if (!strcmp(ptr, "yin")) {
312 informat_s = LYS_IN_YIN;
313 informat_d = 0;
314 } else if (!strcmp(ptr, "xml")) {
315 informat_s = 0;
316 informat_d = LYD_XML;
317 } else if (!strcmp(ptr, "json")) {
318 informat_s = 0;
319 informat_d = LYD_JSON;
Michal Vaskod3b10542021-02-03 11:31:16 +0100320 } else if (!strcmp(ptr, "lyb")) {
321 informat_s = 0;
322 informat_d = LYD_LYB;
Radek Krejcie9f13b12020-11-09 17:42:04 +0100323 } else {
324 YLMSG_E("Input file \"%s\" in an unknown format \"%s\".\n", filename, ptr);
325 return 0;
326 }
327 } else {
328 YLMSG_E("Input file \"%s\" without file extension - unknown format.\n", filename);
329 return 1;
330 }
331
332 if (informat_d) {
333 if (!data) {
334 YLMSG_E("Input file \"%s\" not expected to contain data instances (unexpected format).\n", filename);
335 return 2;
336 }
337 (*data) = informat_d;
338 } else if (informat_s) {
339 if (!schema) {
340 YLMSG_E("Input file \"%s\" not expected to contain schema definition (unexpected format).\n", filename);
341 return 3;
342 }
343 (*schema) = informat_s;
344 }
345
346 return 0;
347}
348
349int
350print_list(struct ly_out *out, struct ly_ctx *ctx, LYD_FORMAT outformat)
351{
352 struct lyd_node *ylib;
353 uint32_t idx = 0, has_modules = 0;
354 const struct lys_module *mod;
355
356 if (outformat != LYD_UNKNOWN) {
Michal Vasko794ab4b2021-03-31 09:42:19 +0200357 if (ly_ctx_get_yanglib_data(ctx, &ylib, "%u", ly_ctx_get_change_count(ctx))) {
Michal Vaskoc431a0a2021-01-25 14:31:58 +0100358 YLMSG_E("Getting context info (ietf-yang-library data) failed. If the YANG module is missing or not implemented, use an option to add it internally.\n");
Radek Krejcie9f13b12020-11-09 17:42:04 +0100359 return 1;
360 }
361
362 lyd_print_all(out, ylib, outformat, 0);
363 lyd_free_all(ylib);
364 return 0;
365 }
366
367 /* iterate schemas in context and provide just the basic info */
368 ly_print(out, "List of the loaded models:\n");
369 while ((mod = ly_ctx_get_module_iter(ctx, &idx))) {
370 has_modules++;
371
372 /* conformance print */
373 if (mod->implemented) {
374 ly_print(out, " I");
375 } else {
376 ly_print(out, " i");
377 }
378
379 /* module print */
380 ly_print(out, " %s", mod->name);
381 if (mod->revision) {
382 ly_print(out, "@%s", mod->revision);
383 }
384
385 /* submodules print */
386 if (mod->parsed && mod->parsed->includes) {
387 uint64_t u = 0;
388 ly_print(out, " (");
389 LY_ARRAY_FOR(mod->parsed->includes, u) {
390 ly_print(out, "%s%s", !u ? "" : ",", mod->parsed->includes[u].name);
391 if (mod->parsed->includes[u].rev[0]) {
392 ly_print(out, "@%s", mod->parsed->includes[u].rev);
393 }
394 }
395 ly_print(out, ")");
396 }
397
398 /* finish the line */
399 ly_print(out, "\n");
400 }
401
402 if (!has_modules) {
403 ly_print(out, "\t(none)\n");
404 }
405
406 ly_print_flush(out);
407 return 0;
408}
409
410int
Radek Krejcie9f13b12020-11-09 17:42:04 +0100411evaluate_xpath(const struct lyd_node *tree, const char *xpath)
412{
Radek Krejci89757c12020-11-26 12:07:47 +0100413 struct ly_set *set = NULL;
Radek Krejcie9f13b12020-11-09 17:42:04 +0100414
415 if (lyd_find_xpath(tree, xpath, &set)) {
416 return -1;
417 }
418
419 /* print result */
420 printf("XPath \"%s\" evaluation result:\n", xpath);
421 if (!set->count) {
422 printf("\tEmpty\n");
423 } else {
424 for (uint32_t u = 0; u < set->count; ++u) {
425 struct lyd_node *node = (struct lyd_node *)set->objs[u];
426 printf(" %s \"%s\"", lys_nodetype2str(node->schema->nodetype), node->schema->name);
427 if (node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) {
Radek Krejci6d5ba0c2021-04-26 07:49:59 +0200428 printf(" (value: \"%s\")\n", lyd_get_value(node));
Radek Krejcie9f13b12020-11-09 17:42:04 +0100429 } else if (node->schema->nodetype == LYS_LIST) {
430 printf(" (");
431 for (struct lyd_node *key = ((struct lyd_node_inner *)node)->child; key && lysc_is_key(key->schema); key = key->next) {
432 printf("%s\"%s\": \"%s\";", (key != ((struct lyd_node_inner *)node)->child) ? " " : "",
Radek Krejci6d5ba0c2021-04-26 07:49:59 +0200433 key->schema->name, lyd_get_value(key));
Radek Krejcie9f13b12020-11-09 17:42:04 +0100434 }
435 printf(")\n");
436 }
437 }
438 }
439
440 ly_set_free(set, NULL);
441 return 0;
442}
443
444LY_ERR
Michal Vaskoe0665742021-02-11 11:08:44 +0100445process_data(struct ly_ctx *ctx, enum lyd_type data_type, uint8_t merge, LYD_FORMAT format, struct ly_out *out,
Michal Vasko3f08fb92022-04-21 09:52:35 +0200446 uint32_t options_parse, uint32_t options_validate, uint32_t options_print, struct cmdline_file *operational_f,
447 struct cmdline_file *rpc_f, struct ly_set *inputs, struct ly_set *xpaths)
Radek Krejcie9f13b12020-11-09 17:42:04 +0100448{
Radek Krejci89757c12020-11-26 12:07:47 +0100449 LY_ERR ret = LY_SUCCESS;
Michal Vasko3f08fb92022-04-21 09:52:35 +0200450 struct lyd_node *tree = NULL, *envp = NULL, *merged_tree = NULL, *oper_tree = NULL;
Radek Krejcie9f13b12020-11-09 17:42:04 +0100451
452 /* additional operational datastore */
453 if (operational_f && operational_f->in) {
Michal Vasko3f08fb92022-04-21 09:52:35 +0200454 ret = lyd_parse_data(ctx, NULL, operational_f->in, operational_f->format, LYD_PARSE_ONLY, 0, &oper_tree);
Radek Krejcie9f13b12020-11-09 17:42:04 +0100455 if (ret) {
456 YLMSG_E("Failed to parse operational datastore file \"%s\".\n", operational_f->path);
457 goto cleanup;
458 }
459 }
460
461 for (uint32_t u = 0; u < inputs->count; ++u) {
462 struct cmdline_file *input_f = (struct cmdline_file *)inputs->objs[u];
Radek Krejcie9f13b12020-11-09 17:42:04 +0100463 switch (data_type) {
Michal Vasko1e4c68e2021-02-18 15:03:01 +0100464 case LYD_TYPE_DATA_YANG:
Michal Vaskoe0665742021-02-11 11:08:44 +0100465 ret = lyd_parse_data(ctx, NULL, input_f->in, input_f->format, options_parse, options_validate, &tree);
Radek Krejcie9f13b12020-11-09 17:42:04 +0100466 break;
Michal Vasko1e4c68e2021-02-18 15:03:01 +0100467 case LYD_TYPE_RPC_YANG:
468 case LYD_TYPE_REPLY_YANG:
469 case LYD_TYPE_NOTIF_YANG:
Michal Vaskoe0665742021-02-11 11:08:44 +0100470 ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, data_type, &tree, NULL);
Radek Krejcie9f13b12020-11-09 17:42:04 +0100471 break;
Michal Vasko3f08fb92022-04-21 09:52:35 +0200472 case LYD_TYPE_RPC_NETCONF:
473 case LYD_TYPE_NOTIF_NETCONF:
474 ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, data_type, &envp, &tree);
475 break;
476 case LYD_TYPE_REPLY_NETCONF:
477 /* parse source RPC operation */
478 assert(rpc_f && rpc_f->in);
479 ret = lyd_parse_op(ctx, NULL, rpc_f->in, rpc_f->format, LYD_TYPE_RPC_NETCONF, &envp, &tree);
480 if (ret) {
481 YLMSG_E("Failed to parse source NETCONF RPC operation file \"%s\".\n", rpc_f->path);
482 goto cleanup;
483 }
484
485 /* free input */
486 lyd_free_siblings(lyd_child(tree));
487
488 /* we do not care */
489 lyd_free_all(envp);
490 envp = NULL;
491
492 ret = lyd_parse_op(ctx, tree, input_f->in, input_f->format, data_type, &envp, NULL);
493 break;
Radek Krejci89757c12020-11-26 12:07:47 +0100494 default:
495 YLMSG_E("Internal error (%s:%d).\n", __FILE__, __LINE__);
496 goto cleanup;
Radek Krejcie9f13b12020-11-09 17:42:04 +0100497 }
498
499 if (ret) {
500 YLMSG_E("Failed to parse input data file \"%s\".\n", input_f->path);
501 goto cleanup;
502 }
503
504 if (merge) {
505 /* merge the data so far parsed for later validation and print */
506 if (!merged_tree) {
507 merged_tree = tree;
508 } else {
509 ret = lyd_merge_siblings(&merged_tree, tree, LYD_MERGE_DESTRUCT);
510 if (ret) {
511 YLMSG_E("Merging %s with previous data failed.\n", input_f->path);
512 goto cleanup;
513 }
514 }
515 tree = NULL;
516 } else if (format) {
Michal Vasko3f08fb92022-04-21 09:52:35 +0200517 /* print */
518 switch (data_type) {
519 case LYD_TYPE_DATA_YANG:
520 lyd_print_all(out, tree, format, options_print);
521 break;
522 case LYD_TYPE_RPC_YANG:
523 case LYD_TYPE_REPLY_YANG:
524 case LYD_TYPE_NOTIF_YANG:
525 case LYD_TYPE_RPC_NETCONF:
526 case LYD_TYPE_NOTIF_NETCONF:
527 lyd_print_tree(out, tree, format, options_print);
528 break;
529 case LYD_TYPE_REPLY_NETCONF:
530 /* just the output */
531 lyd_print_tree(out, lyd_child(tree), format, options_print);
532 break;
533 default:
534 assert(0);
535 }
536 } else if (oper_tree) {
Radek Krejcie9f13b12020-11-09 17:42:04 +0100537 /* additional validation of the RPC/Action/reply/Notification with the operational datastore */
Michal Vasko3f08fb92022-04-21 09:52:35 +0200538 ret = lyd_validate_op(tree, oper_tree, data_type, NULL);
Radek Krejcie9f13b12020-11-09 17:42:04 +0100539 if (ret) {
540 YLMSG_E("Failed to validate input data file \"%s\" with operational datastore \"%s\".\n",
541 input_f->path, operational_f->path);
542 goto cleanup;
543 }
544 }
Michal Vasko3f08fb92022-04-21 09:52:35 +0200545
546 /* next iter */
Radek Krejcie9f13b12020-11-09 17:42:04 +0100547 lyd_free_all(tree);
548 tree = NULL;
Michal Vasko3f08fb92022-04-21 09:52:35 +0200549 lyd_free_all(envp);
550 envp = NULL;
Radek Krejcie9f13b12020-11-09 17:42:04 +0100551 }
552
553 if (merge) {
554 /* validate the merged result */
555 ret = lyd_validate_all(&merged_tree, ctx, LYD_VALIDATE_PRESENT, NULL);
556 if (ret) {
557 YLMSG_E("Merged data are not valid.\n");
558 goto cleanup;
559 }
560
561 if (format) {
562 /* and print it */
563 lyd_print_all(out, merged_tree, format, options_print);
564 }
565
Michal Vaskof8e71f72022-03-29 12:12:58 +0200566 for (uint32_t u = 0; xpaths && (u < xpaths->count); ++u) {
Radek Krejcie9f13b12020-11-09 17:42:04 +0100567 if (evaluate_xpath(merged_tree, (const char *)xpaths->objs[u])) {
568 goto cleanup;
569 }
570 }
571 }
572
573cleanup:
Radek Krejcie9f13b12020-11-09 17:42:04 +0100574 lyd_free_all(tree);
Michal Vasko3f08fb92022-04-21 09:52:35 +0200575 lyd_free_all(envp);
Michal Vaskoe1f495b2022-04-20 08:32:41 +0200576 lyd_free_all(merged_tree);
Michal Vasko3f08fb92022-04-21 09:52:35 +0200577 lyd_free_all(oper_tree);
Radek Krejcie9f13b12020-11-09 17:42:04 +0100578 return ret;
579}