blob: 796a45096b3fba82a8a2c68ed7594a88dcd4c483 [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
76 /* get the file format */
77 if (get_format(filepath, format_schema, format_data)) {
78 return -1;
79 }
80
81 if (ly_in_new_filepath(filepath, 0, in)) {
82 YLMSG_E("Unable to process input file.\n");
83 return -1;
84 }
85
86 return 0;
87}
88
89void
90free_features(void *flist)
91{
92 struct schema_features *rec = (struct schema_features *)flist;
93
94 if (rec) {
95 free(rec->module);
96 if (rec->features) {
97 for (uint32_t u = 0; rec->features[u]; ++u) {
98 free(rec->features[u]);
99 }
100 free(rec->features);
101 }
102 free(rec);
103 }
104}
105
106void
107get_features(struct ly_set *fset, const char *module, const char ***features)
108{
109 /* get features list for this module */
110 for (uint32_t u = 0; u < fset->count; ++u) {
111 struct schema_features *sf = (struct schema_features *)fset->objs[u];
112 if (!strcmp(module, sf->module)) {
113 *features = (const char **)sf->features;
114 return;
115 }
116 }
117}
118
119int
120parse_features(const char *fstring, struct ly_set *fset)
121{
122 struct schema_features *rec;
123 char *p;
124
125 rec = calloc(1, sizeof *rec);
126 if (!rec) {
127 YLMSG_E("Unable to allocate features information record (%s).\n", strerror(errno));
128 return -1;
129 }
130 if (ly_set_add(fset, rec, 1, NULL)) {
131 YLMSG_E("Unable to store features information (%s).\n", strerror(errno));
132 free(rec);
133 return -1;
134 }
135
136 /* fill the record */
137 p = strchr(fstring, ':');
138 if (!p) {
139 YLMSG_E("Invalid format of the features specification (%s)", fstring);
140 return -1;
141 }
142 rec->module = strndup(fstring, p - fstring);
143
144 /* start count on 2 to include terminating NULL byte */
145 for (int count = 2; p; ++count) {
146 size_t len = 0;
147 char *token = p + 1;
148 p = strchr(token, ',');
149 if (!p) {
150 /* the last item, if any */
151 len = strlen(token);
152 } else {
153 len = p - token;
154 }
155 if (len) {
156 char **fp = realloc(rec->features, count * sizeof *rec->features);
157 if (!fp) {
158 YLMSG_E("Unable to store features list information (%s).\n", strerror(errno));
159 return -1;
160 }
161 rec->features = fp;
162 rec->features[count - 1] = NULL; /* terminating NULL-byte */
163 fp = &rec->features[count - 2]; /* array item to set */
164 (*fp) = strndup(token, len);
165 }
166 }
167
168 return 0;
169}
170
171struct cmdline_file *
172fill_cmdline_file(struct ly_set *set, struct ly_in *in, const char *path, LYD_FORMAT format)
173{
174 struct cmdline_file *rec;
175
176 rec = malloc(sizeof *rec);
177 if (!rec) {
178 YLMSG_E("Allocating memory for data file information failed.\n");
179 return NULL;
180 }
181 if (set && ly_set_add(set, rec, 1, NULL)) {
182 free(rec);
Michal Vasko7edebb42021-01-25 14:18:46 +0100183 YLMSG_E("Storing data file information failed.\n");
Radek Krejcie9f13b12020-11-09 17:42:04 +0100184 return NULL;
185 }
186 rec->in = in;
187 rec->path = path;
188 rec->format = format;
189
190 return rec;
191}
192
193void
194free_cmdline_file(void *cmdline_file)
195{
196 struct cmdline_file *rec = (struct cmdline_file *)cmdline_file;
197
198 if (rec) {
199 ly_in_free(rec->in, 1);
200 free(rec);
201 }
202}
203
204void
205free_cmdline(char *argv[])
206{
207 if (argv) {
208 free(argv[0]);
209 free(argv);
210 }
211}
212
213int
214parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[])
215{
216 int count;
217 char **vector;
218 char *ptr;
219 char qmark = 0;
220
221 assert(cmdline);
222 assert(argc_p);
223 assert(argv_p);
224
225 /* init */
226 optind = 0; /* reinitialize getopt() */
227 count = 1;
228 vector = malloc((count + 1) * sizeof *vector);
229 vector[0] = strdup(cmdline);
230
231 /* command name */
232 strtok(vector[0], " ");
233
234 /* arguments */
235 while ((ptr = strtok(NULL, " "))) {
236 size_t len;
237 void *r;
238
239 len = strlen(ptr);
240
241 if (qmark) {
242 /* still in quotated text */
243 /* remove NULL termination of the previous token since it is not a token,
244 * but a part of the quotation string */
245 ptr[-1] = ' ';
246
247 if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) {
248 /* end of quotation */
249 qmark = 0;
250 /* shorten the argument by the terminating quotation mark */
251 ptr[len - 1] = '\0';
252 }
253 continue;
254 }
255
256 /* another token in cmdline */
257 ++count;
258 r = realloc(vector, (count + 1) * sizeof *vector);
259 if (!r) {
260 YLMSG_E("Memory allocation failed (%s:%d, %s),", __FILE__, __LINE__, strerror(errno));
261 free(vector);
262 return -1;
263 }
264 vector = r;
265 vector[count - 1] = ptr;
266
267 if ((ptr[0] == '"') || (ptr[0] == '\'')) {
268 /* remember the quotation mark to identify end of quotation */
269 qmark = ptr[0];
270
271 /* move the remembered argument after the quotation mark */
272 ++vector[count - 1];
273
274 /* check if the quotation is terminated within this token */
275 if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) {
276 /* end of quotation */
277 qmark = 0;
278 /* shorten the argument by the terminating quotation mark */
279 ptr[len - 1] = '\0';
280 }
281 }
282 }
283 vector[count] = NULL;
284
285 *argc_p = count;
286 *argv_p = vector;
287
288 return 0;
289}
290
291int
292get_format(const char *filename, LYS_INFORMAT *schema, LYD_FORMAT *data)
293{
294 char *ptr;
295 LYS_INFORMAT informat_s;
296 LYD_FORMAT informat_d;
297
298 /* get the file format */
299 if ((ptr = strrchr(filename, '.')) != NULL) {
300 ++ptr;
301 if (!strcmp(ptr, "yang")) {
302 informat_s = LYS_IN_YANG;
303 informat_d = 0;
304 } else if (!strcmp(ptr, "yin")) {
305 informat_s = LYS_IN_YIN;
306 informat_d = 0;
307 } else if (!strcmp(ptr, "xml")) {
308 informat_s = 0;
309 informat_d = LYD_XML;
310 } else if (!strcmp(ptr, "json")) {
311 informat_s = 0;
312 informat_d = LYD_JSON;
313 } else {
314 YLMSG_E("Input file \"%s\" in an unknown format \"%s\".\n", filename, ptr);
315 return 0;
316 }
317 } else {
318 YLMSG_E("Input file \"%s\" without file extension - unknown format.\n", filename);
319 return 1;
320 }
321
322 if (informat_d) {
323 if (!data) {
324 YLMSG_E("Input file \"%s\" not expected to contain data instances (unexpected format).\n", filename);
325 return 2;
326 }
327 (*data) = informat_d;
328 } else if (informat_s) {
329 if (!schema) {
330 YLMSG_E("Input file \"%s\" not expected to contain schema definition (unexpected format).\n", filename);
331 return 3;
332 }
333 (*schema) = informat_s;
334 }
335
336 return 0;
337}
338
339int
340print_list(struct ly_out *out, struct ly_ctx *ctx, LYD_FORMAT outformat)
341{
342 struct lyd_node *ylib;
343 uint32_t idx = 0, has_modules = 0;
344 const struct lys_module *mod;
345
346 if (outformat != LYD_UNKNOWN) {
347 if (ly_ctx_get_yanglib_data(ctx, &ylib)) {
348 YLMSG_E("Getting context info (ietf-yang-library data) failed.\n");
349 return 1;
350 }
351
352 lyd_print_all(out, ylib, outformat, 0);
353 lyd_free_all(ylib);
354 return 0;
355 }
356
357 /* iterate schemas in context and provide just the basic info */
358 ly_print(out, "List of the loaded models:\n");
359 while ((mod = ly_ctx_get_module_iter(ctx, &idx))) {
360 has_modules++;
361
362 /* conformance print */
363 if (mod->implemented) {
364 ly_print(out, " I");
365 } else {
366 ly_print(out, " i");
367 }
368
369 /* module print */
370 ly_print(out, " %s", mod->name);
371 if (mod->revision) {
372 ly_print(out, "@%s", mod->revision);
373 }
374
375 /* submodules print */
376 if (mod->parsed && mod->parsed->includes) {
377 uint64_t u = 0;
378 ly_print(out, " (");
379 LY_ARRAY_FOR(mod->parsed->includes, u) {
380 ly_print(out, "%s%s", !u ? "" : ",", mod->parsed->includes[u].name);
381 if (mod->parsed->includes[u].rev[0]) {
382 ly_print(out, "@%s", mod->parsed->includes[u].rev);
383 }
384 }
385 ly_print(out, ")");
386 }
387
388 /* finish the line */
389 ly_print(out, "\n");
390 }
391
392 if (!has_modules) {
393 ly_print(out, "\t(none)\n");
394 }
395
396 ly_print_flush(out);
397 return 0;
398}
399
400int
Radek Krejcie9f13b12020-11-09 17:42:04 +0100401evaluate_xpath(const struct lyd_node *tree, const char *xpath)
402{
Radek Krejci89757c12020-11-26 12:07:47 +0100403 struct ly_set *set = NULL;
Radek Krejcie9f13b12020-11-09 17:42:04 +0100404
405 if (lyd_find_xpath(tree, xpath, &set)) {
406 return -1;
407 }
408
409 /* print result */
410 printf("XPath \"%s\" evaluation result:\n", xpath);
411 if (!set->count) {
412 printf("\tEmpty\n");
413 } else {
414 for (uint32_t u = 0; u < set->count; ++u) {
415 struct lyd_node *node = (struct lyd_node *)set->objs[u];
416 printf(" %s \"%s\"", lys_nodetype2str(node->schema->nodetype), node->schema->name);
417 if (node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) {
418 printf(" (value: \"%s\")\n", ((struct lyd_node_term *)node)->value.canonical);
419 } else if (node->schema->nodetype == LYS_LIST) {
420 printf(" (");
421 for (struct lyd_node *key = ((struct lyd_node_inner *)node)->child; key && lysc_is_key(key->schema); key = key->next) {
422 printf("%s\"%s\": \"%s\";", (key != ((struct lyd_node_inner *)node)->child) ? " " : "",
423 key->schema->name, ((struct lyd_node_term *)key)->value.canonical);
424 }
425 printf(")\n");
426 }
427 }
428 }
429
430 ly_set_free(set, NULL);
431 return 0;
432}
433
434LY_ERR
435process_data(struct ly_ctx *ctx, uint8_t data_type, uint8_t merge, LYD_FORMAT format, struct ly_out *out,
436 uint32_t options_parse, uint32_t options_validate, uint32_t options_print,
Radek Krejci6784a4e2020-12-09 14:23:05 +0100437 struct cmdline_file *operational_f, struct ly_set *inputs, struct ly_set *xpaths)
Radek Krejcie9f13b12020-11-09 17:42:04 +0100438{
Radek Krejci89757c12020-11-26 12:07:47 +0100439 LY_ERR ret = LY_SUCCESS;
Radek Krejcie9f13b12020-11-09 17:42:04 +0100440 struct lyd_node *tree = NULL, *merged_tree = NULL;
Radek Krejci89757c12020-11-26 12:07:47 +0100441 struct lyd_node *operational = NULL;
Radek Krejcie9f13b12020-11-09 17:42:04 +0100442
443 /* additional operational datastore */
444 if (operational_f && operational_f->in) {
445 ret = lyd_parse_data(ctx, operational_f->in, operational_f->format, LYD_PARSE_ONLY, 0, &operational);
446 if (ret) {
447 YLMSG_E("Failed to parse operational datastore file \"%s\".\n", operational_f->path);
448 goto cleanup;
449 }
450 }
451
452 for (uint32_t u = 0; u < inputs->count; ++u) {
453 struct cmdline_file *input_f = (struct cmdline_file *)inputs->objs[u];
Radek Krejcie9f13b12020-11-09 17:42:04 +0100454 switch (data_type) {
455 case 0:
456 ret = lyd_parse_data(ctx, input_f->in, input_f->format, options_parse, options_validate, &tree);
457 break;
458 case LYD_VALIDATE_OP_RPC:
459 ret = lyd_parse_rpc(ctx, input_f->in, input_f->format, &tree, NULL);
460 break;
Radek Krejci6784a4e2020-12-09 14:23:05 +0100461 case LYD_VALIDATE_OP_REPLY:
462 ret = lyd_parse_reply(ctx, input_f->in, input_f->format, &tree, NULL);
Radek Krejcie9f13b12020-11-09 17:42:04 +0100463 break;
Radek Krejcie9f13b12020-11-09 17:42:04 +0100464 case LYD_VALIDATE_OP_NOTIF:
465 ret = lyd_parse_notif(ctx, input_f->in, input_f->format, &tree, NULL);
466 break;
Radek Krejci89757c12020-11-26 12:07:47 +0100467 default:
468 YLMSG_E("Internal error (%s:%d).\n", __FILE__, __LINE__);
469 goto cleanup;
Radek Krejcie9f13b12020-11-09 17:42:04 +0100470 }
471
472 if (ret) {
473 YLMSG_E("Failed to parse input data file \"%s\".\n", input_f->path);
474 goto cleanup;
475 }
476
477 if (merge) {
478 /* merge the data so far parsed for later validation and print */
479 if (!merged_tree) {
480 merged_tree = tree;
481 } else {
482 ret = lyd_merge_siblings(&merged_tree, tree, LYD_MERGE_DESTRUCT);
483 if (ret) {
484 YLMSG_E("Merging %s with previous data failed.\n", input_f->path);
485 goto cleanup;
486 }
487 }
488 tree = NULL;
489 } else if (format) {
490 lyd_print_all(out, tree, format, options_print);
491 } else if (operational) {
492 /* additional validation of the RPC/Action/reply/Notification with the operational datastore */
493 ret = lyd_validate_op(tree, operational, data_type, NULL);
494 if (ret) {
495 YLMSG_E("Failed to validate input data file \"%s\" with operational datastore \"%s\".\n",
496 input_f->path, operational_f->path);
497 goto cleanup;
498 }
499 }
500 lyd_free_all(tree);
501 tree = NULL;
502 }
503
504 if (merge) {
505 /* validate the merged result */
506 ret = lyd_validate_all(&merged_tree, ctx, LYD_VALIDATE_PRESENT, NULL);
507 if (ret) {
508 YLMSG_E("Merged data are not valid.\n");
509 goto cleanup;
510 }
511
512 if (format) {
513 /* and print it */
514 lyd_print_all(out, merged_tree, format, options_print);
515 }
516
517 for (uint32_t u = 0; u < xpaths->count; ++u) {
518 if (evaluate_xpath(merged_tree, (const char *)xpaths->objs[u])) {
519 goto cleanup;
520 }
521 }
522 }
523
524cleanup:
525 /* cleanup */
526 lyd_free_all(merged_tree);
527 lyd_free_all(tree);
528
529 return ret;
530}