blob: d3194103d369b50eea4e00f020883d01777a7bd5 [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 Vaskofc5a1d12021-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{
Radek Krejci8143bb42021-04-07 11:43:50 +0200111 static const char *all_features[] = {"*", NULL};
112
Radek Krejcie9f13b12020-11-09 17:42:04 +0100113 /* get features list for this module */
114 for (uint32_t u = 0; u < fset->count; ++u) {
115 struct schema_features *sf = (struct schema_features *)fset->objs[u];
Michal Vaskofc5a1d12021-11-22 10:00:27 +0100116 if (!strcmp(module, sf->mod_name)) {
Radek Krejci8143bb42021-04-07 11:43:50 +0200117 /* matched module - explicitly set features */
Radek Krejcie9f13b12020-11-09 17:42:04 +0100118 *features = (const char **)sf->features;
119 return;
120 }
121 }
Radek Krejci8143bb42021-04-07 11:43:50 +0200122
123 /* features not set, enable all features by default */
124 *features = all_features;
Radek Krejcie9f13b12020-11-09 17:42:04 +0100125}
126
127int
128parse_features(const char *fstring, struct ly_set *fset)
129{
130 struct schema_features *rec;
131 char *p;
132
133 rec = calloc(1, sizeof *rec);
134 if (!rec) {
135 YLMSG_E("Unable to allocate features information record (%s).\n", strerror(errno));
136 return -1;
137 }
138 if (ly_set_add(fset, rec, 1, NULL)) {
139 YLMSG_E("Unable to store features information (%s).\n", strerror(errno));
140 free(rec);
141 return -1;
142 }
143
144 /* fill the record */
145 p = strchr(fstring, ':');
146 if (!p) {
147 YLMSG_E("Invalid format of the features specification (%s)", fstring);
148 return -1;
149 }
Michal Vaskofc5a1d12021-11-22 10:00:27 +0100150 rec->mod_name = strndup(fstring, p - fstring);
Radek Krejcie9f13b12020-11-09 17:42:04 +0100151
152 /* start count on 2 to include terminating NULL byte */
153 for (int count = 2; p; ++count) {
154 size_t len = 0;
155 char *token = p + 1;
156 p = strchr(token, ',');
157 if (!p) {
158 /* the last item, if any */
159 len = strlen(token);
160 } else {
161 len = p - token;
162 }
163 if (len) {
164 char **fp = realloc(rec->features, count * sizeof *rec->features);
165 if (!fp) {
166 YLMSG_E("Unable to store features list information (%s).\n", strerror(errno));
167 return -1;
168 }
169 rec->features = fp;
170 rec->features[count - 1] = NULL; /* terminating NULL-byte */
171 fp = &rec->features[count - 2]; /* array item to set */
172 (*fp) = strndup(token, len);
173 }
174 }
175
176 return 0;
177}
178
179struct cmdline_file *
180fill_cmdline_file(struct ly_set *set, struct ly_in *in, const char *path, LYD_FORMAT format)
181{
182 struct cmdline_file *rec;
183
184 rec = malloc(sizeof *rec);
185 if (!rec) {
186 YLMSG_E("Allocating memory for data file information failed.\n");
187 return NULL;
188 }
189 if (set && ly_set_add(set, rec, 1, NULL)) {
190 free(rec);
Michal Vasko7edebb42021-01-25 14:18:46 +0100191 YLMSG_E("Storing data file information failed.\n");
Radek Krejcie9f13b12020-11-09 17:42:04 +0100192 return NULL;
193 }
194 rec->in = in;
195 rec->path = path;
196 rec->format = format;
197
198 return rec;
199}
200
201void
202free_cmdline_file(void *cmdline_file)
203{
204 struct cmdline_file *rec = (struct cmdline_file *)cmdline_file;
205
206 if (rec) {
207 ly_in_free(rec->in, 1);
208 free(rec);
209 }
210}
211
212void
213free_cmdline(char *argv[])
214{
215 if (argv) {
216 free(argv[0]);
217 free(argv);
218 }
219}
220
221int
222parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[])
223{
224 int count;
225 char **vector;
226 char *ptr;
227 char qmark = 0;
228
229 assert(cmdline);
230 assert(argc_p);
231 assert(argv_p);
232
233 /* init */
234 optind = 0; /* reinitialize getopt() */
235 count = 1;
236 vector = malloc((count + 1) * sizeof *vector);
237 vector[0] = strdup(cmdline);
238
239 /* command name */
240 strtok(vector[0], " ");
241
242 /* arguments */
243 while ((ptr = strtok(NULL, " "))) {
244 size_t len;
245 void *r;
246
247 len = strlen(ptr);
248
249 if (qmark) {
250 /* still in quotated text */
251 /* remove NULL termination of the previous token since it is not a token,
252 * but a part of the quotation string */
253 ptr[-1] = ' ';
254
255 if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) {
256 /* end of quotation */
257 qmark = 0;
258 /* shorten the argument by the terminating quotation mark */
259 ptr[len - 1] = '\0';
260 }
261 continue;
262 }
263
264 /* another token in cmdline */
265 ++count;
266 r = realloc(vector, (count + 1) * sizeof *vector);
267 if (!r) {
268 YLMSG_E("Memory allocation failed (%s:%d, %s),", __FILE__, __LINE__, strerror(errno));
269 free(vector);
270 return -1;
271 }
272 vector = r;
273 vector[count - 1] = ptr;
274
275 if ((ptr[0] == '"') || (ptr[0] == '\'')) {
276 /* remember the quotation mark to identify end of quotation */
277 qmark = ptr[0];
278
279 /* move the remembered argument after the quotation mark */
280 ++vector[count - 1];
281
282 /* check if the quotation is terminated within this token */
283 if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) {
284 /* end of quotation */
285 qmark = 0;
286 /* shorten the argument by the terminating quotation mark */
287 ptr[len - 1] = '\0';
288 }
289 }
290 }
291 vector[count] = NULL;
292
293 *argc_p = count;
294 *argv_p = vector;
295
296 return 0;
297}
298
299int
300get_format(const char *filename, LYS_INFORMAT *schema, LYD_FORMAT *data)
301{
302 char *ptr;
303 LYS_INFORMAT informat_s;
304 LYD_FORMAT informat_d;
305
306 /* get the file format */
307 if ((ptr = strrchr(filename, '.')) != NULL) {
308 ++ptr;
309 if (!strcmp(ptr, "yang")) {
310 informat_s = LYS_IN_YANG;
311 informat_d = 0;
312 } else if (!strcmp(ptr, "yin")) {
313 informat_s = LYS_IN_YIN;
314 informat_d = 0;
315 } else if (!strcmp(ptr, "xml")) {
316 informat_s = 0;
317 informat_d = LYD_XML;
318 } else if (!strcmp(ptr, "json")) {
319 informat_s = 0;
320 informat_d = LYD_JSON;
Michal Vaskod3b10542021-02-03 11:31:16 +0100321 } else if (!strcmp(ptr, "lyb")) {
322 informat_s = 0;
323 informat_d = LYD_LYB;
Radek Krejcie9f13b12020-11-09 17:42:04 +0100324 } else {
325 YLMSG_E("Input file \"%s\" in an unknown format \"%s\".\n", filename, ptr);
326 return 0;
327 }
328 } else {
329 YLMSG_E("Input file \"%s\" without file extension - unknown format.\n", filename);
330 return 1;
331 }
332
333 if (informat_d) {
334 if (!data) {
335 YLMSG_E("Input file \"%s\" not expected to contain data instances (unexpected format).\n", filename);
336 return 2;
337 }
338 (*data) = informat_d;
339 } else if (informat_s) {
340 if (!schema) {
341 YLMSG_E("Input file \"%s\" not expected to contain schema definition (unexpected format).\n", filename);
342 return 3;
343 }
344 (*schema) = informat_s;
345 }
346
347 return 0;
348}
349
350int
351print_list(struct ly_out *out, struct ly_ctx *ctx, LYD_FORMAT outformat)
352{
353 struct lyd_node *ylib;
354 uint32_t idx = 0, has_modules = 0;
355 const struct lys_module *mod;
356
357 if (outformat != LYD_UNKNOWN) {
Michal Vasko794ab4b2021-03-31 09:42:19 +0200358 if (ly_ctx_get_yanglib_data(ctx, &ylib, "%u", ly_ctx_get_change_count(ctx))) {
Michal Vaskoc431a0a2021-01-25 14:31:58 +0100359 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 +0100360 return 1;
361 }
362
363 lyd_print_all(out, ylib, outformat, 0);
364 lyd_free_all(ylib);
365 return 0;
366 }
367
368 /* iterate schemas in context and provide just the basic info */
369 ly_print(out, "List of the loaded models:\n");
370 while ((mod = ly_ctx_get_module_iter(ctx, &idx))) {
371 has_modules++;
372
373 /* conformance print */
374 if (mod->implemented) {
375 ly_print(out, " I");
376 } else {
377 ly_print(out, " i");
378 }
379
380 /* module print */
381 ly_print(out, " %s", mod->name);
382 if (mod->revision) {
383 ly_print(out, "@%s", mod->revision);
384 }
385
386 /* submodules print */
387 if (mod->parsed && mod->parsed->includes) {
388 uint64_t u = 0;
389 ly_print(out, " (");
390 LY_ARRAY_FOR(mod->parsed->includes, u) {
391 ly_print(out, "%s%s", !u ? "" : ",", mod->parsed->includes[u].name);
392 if (mod->parsed->includes[u].rev[0]) {
393 ly_print(out, "@%s", mod->parsed->includes[u].rev);
394 }
395 }
396 ly_print(out, ")");
397 }
398
399 /* finish the line */
400 ly_print(out, "\n");
401 }
402
403 if (!has_modules) {
404 ly_print(out, "\t(none)\n");
405 }
406
407 ly_print_flush(out);
408 return 0;
409}
410
411int
Radek Krejcie9f13b12020-11-09 17:42:04 +0100412evaluate_xpath(const struct lyd_node *tree, const char *xpath)
413{
Radek Krejci89757c12020-11-26 12:07:47 +0100414 struct ly_set *set = NULL;
Radek Krejcie9f13b12020-11-09 17:42:04 +0100415
416 if (lyd_find_xpath(tree, xpath, &set)) {
417 return -1;
418 }
419
420 /* print result */
421 printf("XPath \"%s\" evaluation result:\n", xpath);
422 if (!set->count) {
423 printf("\tEmpty\n");
424 } else {
425 for (uint32_t u = 0; u < set->count; ++u) {
426 struct lyd_node *node = (struct lyd_node *)set->objs[u];
427 printf(" %s \"%s\"", lys_nodetype2str(node->schema->nodetype), node->schema->name);
428 if (node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) {
Radek Krejci6d5ba0c2021-04-26 07:49:59 +0200429 printf(" (value: \"%s\")\n", lyd_get_value(node));
Radek Krejcie9f13b12020-11-09 17:42:04 +0100430 } else if (node->schema->nodetype == LYS_LIST) {
431 printf(" (");
432 for (struct lyd_node *key = ((struct lyd_node_inner *)node)->child; key && lysc_is_key(key->schema); key = key->next) {
433 printf("%s\"%s\": \"%s\";", (key != ((struct lyd_node_inner *)node)->child) ? " " : "",
Radek Krejci6d5ba0c2021-04-26 07:49:59 +0200434 key->schema->name, lyd_get_value(key));
Radek Krejcie9f13b12020-11-09 17:42:04 +0100435 }
436 printf(")\n");
437 }
438 }
439 }
440
441 ly_set_free(set, NULL);
442 return 0;
443}
444
445LY_ERR
Michal Vaskoe0665742021-02-11 11:08:44 +0100446process_data(struct ly_ctx *ctx, enum lyd_type data_type, uint8_t merge, LYD_FORMAT format, struct ly_out *out,
Radek Krejcie9f13b12020-11-09 17:42:04 +0100447 uint32_t options_parse, uint32_t options_validate, uint32_t options_print,
Radek Krejci6784a4e2020-12-09 14:23:05 +0100448 struct cmdline_file *operational_f, struct ly_set *inputs, struct ly_set *xpaths)
Radek Krejcie9f13b12020-11-09 17:42:04 +0100449{
Radek Krejci89757c12020-11-26 12:07:47 +0100450 LY_ERR ret = LY_SUCCESS;
Radek Krejcie9f13b12020-11-09 17:42:04 +0100451 struct lyd_node *tree = NULL, *merged_tree = NULL;
Radek Krejci89757c12020-11-26 12:07:47 +0100452 struct lyd_node *operational = NULL;
Radek Krejcie9f13b12020-11-09 17:42:04 +0100453
454 /* additional operational datastore */
455 if (operational_f && operational_f->in) {
Michal Vaskoe0665742021-02-11 11:08:44 +0100456 ret = lyd_parse_data(ctx, NULL, operational_f->in, operational_f->format, LYD_PARSE_ONLY, 0, &operational);
Radek Krejcie9f13b12020-11-09 17:42:04 +0100457 if (ret) {
458 YLMSG_E("Failed to parse operational datastore file \"%s\".\n", operational_f->path);
459 goto cleanup;
460 }
461 }
462
463 for (uint32_t u = 0; u < inputs->count; ++u) {
464 struct cmdline_file *input_f = (struct cmdline_file *)inputs->objs[u];
Radek Krejcie9f13b12020-11-09 17:42:04 +0100465 switch (data_type) {
Michal Vasko1e4c68e2021-02-18 15:03:01 +0100466 case LYD_TYPE_DATA_YANG:
Michal Vaskoe0665742021-02-11 11:08:44 +0100467 ret = lyd_parse_data(ctx, NULL, input_f->in, input_f->format, options_parse, options_validate, &tree);
Radek Krejcie9f13b12020-11-09 17:42:04 +0100468 break;
Michal Vasko1e4c68e2021-02-18 15:03:01 +0100469 case LYD_TYPE_RPC_YANG:
470 case LYD_TYPE_REPLY_YANG:
471 case LYD_TYPE_NOTIF_YANG:
Michal Vaskoe0665742021-02-11 11:08:44 +0100472 ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, data_type, &tree, NULL);
Radek Krejcie9f13b12020-11-09 17:42:04 +0100473 break;
Radek Krejci89757c12020-11-26 12:07:47 +0100474 default:
475 YLMSG_E("Internal error (%s:%d).\n", __FILE__, __LINE__);
476 goto cleanup;
Radek Krejcie9f13b12020-11-09 17:42:04 +0100477 }
478
479 if (ret) {
480 YLMSG_E("Failed to parse input data file \"%s\".\n", input_f->path);
481 goto cleanup;
482 }
483
484 if (merge) {
485 /* merge the data so far parsed for later validation and print */
486 if (!merged_tree) {
487 merged_tree = tree;
488 } else {
489 ret = lyd_merge_siblings(&merged_tree, tree, LYD_MERGE_DESTRUCT);
490 if (ret) {
491 YLMSG_E("Merging %s with previous data failed.\n", input_f->path);
492 goto cleanup;
493 }
494 }
495 tree = NULL;
496 } else if (format) {
497 lyd_print_all(out, tree, format, options_print);
498 } else if (operational) {
499 /* additional validation of the RPC/Action/reply/Notification with the operational datastore */
500 ret = lyd_validate_op(tree, operational, data_type, NULL);
501 if (ret) {
502 YLMSG_E("Failed to validate input data file \"%s\" with operational datastore \"%s\".\n",
503 input_f->path, operational_f->path);
504 goto cleanup;
505 }
506 }
507 lyd_free_all(tree);
508 tree = NULL;
509 }
510
511 if (merge) {
512 /* validate the merged result */
513 ret = lyd_validate_all(&merged_tree, ctx, LYD_VALIDATE_PRESENT, NULL);
514 if (ret) {
515 YLMSG_E("Merged data are not valid.\n");
516 goto cleanup;
517 }
518
519 if (format) {
520 /* and print it */
521 lyd_print_all(out, merged_tree, format, options_print);
522 }
523
524 for (uint32_t u = 0; u < xpaths->count; ++u) {
525 if (evaluate_xpath(merged_tree, (const char *)xpaths->objs[u])) {
526 goto cleanup;
527 }
528 }
529 }
530
531cleanup:
532 /* cleanup */
533 lyd_free_all(merged_tree);
534 lyd_free_all(tree);
535
536 return ret;
537}