blob: d2b4f5ecda9c47d52fdb7a7d1522789d38a3f292 [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);
183 YLMSG_E("Storing data file information failes.\n");
184 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
401check_request_paths(struct ly_ctx *ctx, struct ly_set *request_paths, struct ly_set *data_inputs)
402{
403 if ((request_paths->count > 1) && (request_paths->count != data_inputs->count)) {
404 YLMSG_E("Number of request paths does not match the number of reply data files (%u:%u).\n",
405 request_paths->count, data_inputs->count);
406 return -1;
407 }
408
409 for (uint32_t u = 0; u < request_paths->count; ++u) {
410 const char *path = (const char *)request_paths->objs[u];
411 const struct lysc_node *action = NULL;
412
413 action = lys_find_path(ctx, NULL, path, 0);
414 if (!action) {
415 YLMSG_E("The request path \"%s\" is not valid.\n", path);
416 return -1;
417 } else if (!(action->nodetype & (LYS_RPC | LYS_ACTION))) {
418 YLMSG_E("The request path \"%s\" does not represent RPC/Action.\n", path);
419 return -1;
420 }
421 }
422
423 return 0;
424}
425
426int
427evaluate_xpath(const struct lyd_node *tree, const char *xpath)
428{
Radek Krejci89757c12020-11-26 12:07:47 +0100429 struct ly_set *set = NULL;
Radek Krejcie9f13b12020-11-09 17:42:04 +0100430
431 if (lyd_find_xpath(tree, xpath, &set)) {
432 return -1;
433 }
434
435 /* print result */
436 printf("XPath \"%s\" evaluation result:\n", xpath);
437 if (!set->count) {
438 printf("\tEmpty\n");
439 } else {
440 for (uint32_t u = 0; u < set->count; ++u) {
441 struct lyd_node *node = (struct lyd_node *)set->objs[u];
442 printf(" %s \"%s\"", lys_nodetype2str(node->schema->nodetype), node->schema->name);
443 if (node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) {
444 printf(" (value: \"%s\")\n", ((struct lyd_node_term *)node)->value.canonical);
445 } else if (node->schema->nodetype == LYS_LIST) {
446 printf(" (");
447 for (struct lyd_node *key = ((struct lyd_node_inner *)node)->child; key && lysc_is_key(key->schema); key = key->next) {
448 printf("%s\"%s\": \"%s\";", (key != ((struct lyd_node_inner *)node)->child) ? " " : "",
449 key->schema->name, ((struct lyd_node_term *)key)->value.canonical);
450 }
451 printf(")\n");
452 }
453 }
454 }
455
456 ly_set_free(set, NULL);
457 return 0;
458}
459
460LY_ERR
461process_data(struct ly_ctx *ctx, uint8_t data_type, uint8_t merge, LYD_FORMAT format, struct ly_out *out,
462 uint32_t options_parse, uint32_t options_validate, uint32_t options_print,
463 struct cmdline_file *operational_f, struct ly_set *inputs, struct ly_set *request_paths, struct ly_set *requests,
464 struct ly_set *xpaths)
465{
Radek Krejci89757c12020-11-26 12:07:47 +0100466 LY_ERR ret = LY_SUCCESS;
Radek Krejcie9f13b12020-11-09 17:42:04 +0100467 struct lyd_node *tree = NULL, *merged_tree = NULL;
Radek Krejci89757c12020-11-26 12:07:47 +0100468 struct lyd_node *operational = NULL;
Radek Krejcie9f13b12020-11-09 17:42:04 +0100469
470 /* additional operational datastore */
471 if (operational_f && operational_f->in) {
472 ret = lyd_parse_data(ctx, operational_f->in, operational_f->format, LYD_PARSE_ONLY, 0, &operational);
473 if (ret) {
474 YLMSG_E("Failed to parse operational datastore file \"%s\".\n", operational_f->path);
475 goto cleanup;
476 }
477 }
478
479 for (uint32_t u = 0; u < inputs->count; ++u) {
480 struct cmdline_file *input_f = (struct cmdline_file *)inputs->objs[u];
481 struct cmdline_file *request_f;
482 switch (data_type) {
483 case 0:
484 ret = lyd_parse_data(ctx, input_f->in, input_f->format, options_parse, options_validate, &tree);
485 break;
486 case LYD_VALIDATE_OP_RPC:
487 ret = lyd_parse_rpc(ctx, input_f->in, input_f->format, &tree, NULL);
488 break;
489 case LYD_VALIDATE_OP_REPLY: {
490 struct lyd_node *request = NULL;
491
492 /* get the request data */
493 if (request_paths->count) {
494 const char *path;
495 if (request_paths->count > 1) {
496 /* one to one */
497 path = (const char *)request_paths->objs[u];
498 } else {
499 /* one to all */
500 path = (const char *)request_paths->objs[0];
501 }
502 ret = lyd_new_path(NULL, ctx, path, NULL, 0, &request);
503 if (ret) {
504 YLMSG_E("Failed to create request data from path \"%s\".\n", path);
505 goto cleanup;
506 }
507 } else {
508 assert(requests->count > u);
509 request_f = (struct cmdline_file *)requests->objs[u];
510
511 ret = lyd_parse_rpc(ctx, request_f->in, request_f->format, &request, NULL);
512 if (ret) {
513 YLMSG_E("Failed to parse input data file \"%s\".\n", input_f->path);
514 goto cleanup;
515 }
516 }
517
518 /* get the reply data */
519 ret = lyd_parse_reply(request, input_f->in, input_f->format, &tree, NULL);
520 lyd_free_all(request);
521
522 break;
523 } /* case PARSE_REPLY */
524 case LYD_VALIDATE_OP_NOTIF:
525 ret = lyd_parse_notif(ctx, input_f->in, input_f->format, &tree, NULL);
526 break;
Radek Krejci89757c12020-11-26 12:07:47 +0100527 default:
528 YLMSG_E("Internal error (%s:%d).\n", __FILE__, __LINE__);
529 goto cleanup;
Radek Krejcie9f13b12020-11-09 17:42:04 +0100530 }
531
532 if (ret) {
533 YLMSG_E("Failed to parse input data file \"%s\".\n", input_f->path);
534 goto cleanup;
535 }
536
537 if (merge) {
538 /* merge the data so far parsed for later validation and print */
539 if (!merged_tree) {
540 merged_tree = tree;
541 } else {
542 ret = lyd_merge_siblings(&merged_tree, tree, LYD_MERGE_DESTRUCT);
543 if (ret) {
544 YLMSG_E("Merging %s with previous data failed.\n", input_f->path);
545 goto cleanup;
546 }
547 }
548 tree = NULL;
549 } else if (format) {
550 lyd_print_all(out, tree, format, options_print);
551 } else if (operational) {
552 /* additional validation of the RPC/Action/reply/Notification with the operational datastore */
553 ret = lyd_validate_op(tree, operational, data_type, NULL);
554 if (ret) {
555 YLMSG_E("Failed to validate input data file \"%s\" with operational datastore \"%s\".\n",
556 input_f->path, operational_f->path);
557 goto cleanup;
558 }
559 }
560 lyd_free_all(tree);
561 tree = NULL;
562 }
563
564 if (merge) {
565 /* validate the merged result */
566 ret = lyd_validate_all(&merged_tree, ctx, LYD_VALIDATE_PRESENT, NULL);
567 if (ret) {
568 YLMSG_E("Merged data are not valid.\n");
569 goto cleanup;
570 }
571
572 if (format) {
573 /* and print it */
574 lyd_print_all(out, merged_tree, format, options_print);
575 }
576
577 for (uint32_t u = 0; u < xpaths->count; ++u) {
578 if (evaluate_xpath(merged_tree, (const char *)xpaths->objs[u])) {
579 goto cleanup;
580 }
581 }
582 }
583
584cleanup:
585 /* cleanup */
586 lyd_free_all(merged_tree);
587 lyd_free_all(tree);
588
589 return ret;
590}