blob: bc4d57250cd960eb0a1788171e1154480fd18118 [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
16
17#include "common.h"
18
19#include <assert.h>
20#include <errno.h>
21#include <getopt.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include <sys/stat.h>
26
27#include "compat.h"
28#include "libyang.h"
29
30int
31parse_schema_path(const char *path, char **dir, char **module)
32{
33 char *p;
34
35 assert(dir);
36 assert(module);
37
38 /* split the path to dirname and basename for further work */
39 *dir = strdup(path);
40 *module = strrchr(*dir, '/');
41 if (!(*module)) {
42 *module = *dir;
43 *dir = strdup("./");
44 } else {
45 *module[0] = '\0'; /* break the dir */
46 *module = strdup((*module) + 1);
47 }
48 /* get the pure module name without suffix or revision part of the filename */
49 if ((p = strchr(*module, '@'))) {
50 /* revision */
51 *p = '\0';
52 } else if ((p = strrchr(*module, '.'))) {
53 /* fileformat suffix */
54 *p = '\0';
55 }
56
57 return 0;
58}
59
60int
61get_input(const char *filepath, LYS_INFORMAT *format_schema, LYD_FORMAT *format_data, struct ly_in **in)
62{
63 struct stat st;
64
65 /* check that the filepath exists and is a regular file */
66 if (stat(filepath, &st) == -1) {
67 YLMSG_E("Unable to use input filepath (%s) - %s.\n", filepath, strerror(errno));
68 return -1;
69 }
70 if (!S_ISREG(st.st_mode)) {
71 YLMSG_E("Provided input file (%s) is not a regular file.\n", filepath);
72 return -1;
73 }
74
75 /* get the file format */
76 if (get_format(filepath, format_schema, format_data)) {
77 return -1;
78 }
79
80 if (ly_in_new_filepath(filepath, 0, in)) {
81 YLMSG_E("Unable to process input file.\n");
82 return -1;
83 }
84
85 return 0;
86}
87
88void
89free_features(void *flist)
90{
91 struct schema_features *rec = (struct schema_features *)flist;
92
93 if (rec) {
94 free(rec->module);
95 if (rec->features) {
96 for (uint32_t u = 0; rec->features[u]; ++u) {
97 free(rec->features[u]);
98 }
99 free(rec->features);
100 }
101 free(rec);
102 }
103}
104
105void
106get_features(struct ly_set *fset, const char *module, const char ***features)
107{
108 /* get features list for this module */
109 for (uint32_t u = 0; u < fset->count; ++u) {
110 struct schema_features *sf = (struct schema_features *)fset->objs[u];
111 if (!strcmp(module, sf->module)) {
112 *features = (const char **)sf->features;
113 return;
114 }
115 }
116}
117
118int
119parse_features(const char *fstring, struct ly_set *fset)
120{
121 struct schema_features *rec;
122 char *p;
123
124 rec = calloc(1, sizeof *rec);
125 if (!rec) {
126 YLMSG_E("Unable to allocate features information record (%s).\n", strerror(errno));
127 return -1;
128 }
129 if (ly_set_add(fset, rec, 1, NULL)) {
130 YLMSG_E("Unable to store features information (%s).\n", strerror(errno));
131 free(rec);
132 return -1;
133 }
134
135 /* fill the record */
136 p = strchr(fstring, ':');
137 if (!p) {
138 YLMSG_E("Invalid format of the features specification (%s)", fstring);
139 return -1;
140 }
141 rec->module = strndup(fstring, p - fstring);
142
143 /* start count on 2 to include terminating NULL byte */
144 for (int count = 2; p; ++count) {
145 size_t len = 0;
146 char *token = p + 1;
147 p = strchr(token, ',');
148 if (!p) {
149 /* the last item, if any */
150 len = strlen(token);
151 } else {
152 len = p - token;
153 }
154 if (len) {
155 char **fp = realloc(rec->features, count * sizeof *rec->features);
156 if (!fp) {
157 YLMSG_E("Unable to store features list information (%s).\n", strerror(errno));
158 return -1;
159 }
160 rec->features = fp;
161 rec->features[count - 1] = NULL; /* terminating NULL-byte */
162 fp = &rec->features[count - 2]; /* array item to set */
163 (*fp) = strndup(token, len);
164 }
165 }
166
167 return 0;
168}
169
170struct cmdline_file *
171fill_cmdline_file(struct ly_set *set, struct ly_in *in, const char *path, LYD_FORMAT format)
172{
173 struct cmdline_file *rec;
174
175 rec = malloc(sizeof *rec);
176 if (!rec) {
177 YLMSG_E("Allocating memory for data file information failed.\n");
178 return NULL;
179 }
180 if (set && ly_set_add(set, rec, 1, NULL)) {
181 free(rec);
182 YLMSG_E("Storing data file information failes.\n");
183 return NULL;
184 }
185 rec->in = in;
186 rec->path = path;
187 rec->format = format;
188
189 return rec;
190}
191
192void
193free_cmdline_file(void *cmdline_file)
194{
195 struct cmdline_file *rec = (struct cmdline_file *)cmdline_file;
196
197 if (rec) {
198 ly_in_free(rec->in, 1);
199 free(rec);
200 }
201}
202
203void
204free_cmdline(char *argv[])
205{
206 if (argv) {
207 free(argv[0]);
208 free(argv);
209 }
210}
211
212int
213parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[])
214{
215 int count;
216 char **vector;
217 char *ptr;
218 char qmark = 0;
219
220 assert(cmdline);
221 assert(argc_p);
222 assert(argv_p);
223
224 /* init */
225 optind = 0; /* reinitialize getopt() */
226 count = 1;
227 vector = malloc((count + 1) * sizeof *vector);
228 vector[0] = strdup(cmdline);
229
230 /* command name */
231 strtok(vector[0], " ");
232
233 /* arguments */
234 while ((ptr = strtok(NULL, " "))) {
235 size_t len;
236 void *r;
237
238 len = strlen(ptr);
239
240 if (qmark) {
241 /* still in quotated text */
242 /* remove NULL termination of the previous token since it is not a token,
243 * but a part of the quotation string */
244 ptr[-1] = ' ';
245
246 if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) {
247 /* end of quotation */
248 qmark = 0;
249 /* shorten the argument by the terminating quotation mark */
250 ptr[len - 1] = '\0';
251 }
252 continue;
253 }
254
255 /* another token in cmdline */
256 ++count;
257 r = realloc(vector, (count + 1) * sizeof *vector);
258 if (!r) {
259 YLMSG_E("Memory allocation failed (%s:%d, %s),", __FILE__, __LINE__, strerror(errno));
260 free(vector);
261 return -1;
262 }
263 vector = r;
264 vector[count - 1] = ptr;
265
266 if ((ptr[0] == '"') || (ptr[0] == '\'')) {
267 /* remember the quotation mark to identify end of quotation */
268 qmark = ptr[0];
269
270 /* move the remembered argument after the quotation mark */
271 ++vector[count - 1];
272
273 /* check if the quotation is terminated within this token */
274 if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) {
275 /* end of quotation */
276 qmark = 0;
277 /* shorten the argument by the terminating quotation mark */
278 ptr[len - 1] = '\0';
279 }
280 }
281 }
282 vector[count] = NULL;
283
284 *argc_p = count;
285 *argv_p = vector;
286
287 return 0;
288}
289
290int
291get_format(const char *filename, LYS_INFORMAT *schema, LYD_FORMAT *data)
292{
293 char *ptr;
294 LYS_INFORMAT informat_s;
295 LYD_FORMAT informat_d;
296
297 /* get the file format */
298 if ((ptr = strrchr(filename, '.')) != NULL) {
299 ++ptr;
300 if (!strcmp(ptr, "yang")) {
301 informat_s = LYS_IN_YANG;
302 informat_d = 0;
303 } else if (!strcmp(ptr, "yin")) {
304 informat_s = LYS_IN_YIN;
305 informat_d = 0;
306 } else if (!strcmp(ptr, "xml")) {
307 informat_s = 0;
308 informat_d = LYD_XML;
309 } else if (!strcmp(ptr, "json")) {
310 informat_s = 0;
311 informat_d = LYD_JSON;
312 } else {
313 YLMSG_E("Input file \"%s\" in an unknown format \"%s\".\n", filename, ptr);
314 return 0;
315 }
316 } else {
317 YLMSG_E("Input file \"%s\" without file extension - unknown format.\n", filename);
318 return 1;
319 }
320
321 if (informat_d) {
322 if (!data) {
323 YLMSG_E("Input file \"%s\" not expected to contain data instances (unexpected format).\n", filename);
324 return 2;
325 }
326 (*data) = informat_d;
327 } else if (informat_s) {
328 if (!schema) {
329 YLMSG_E("Input file \"%s\" not expected to contain schema definition (unexpected format).\n", filename);
330 return 3;
331 }
332 (*schema) = informat_s;
333 }
334
335 return 0;
336}
337
338int
339print_list(struct ly_out *out, struct ly_ctx *ctx, LYD_FORMAT outformat)
340{
341 struct lyd_node *ylib;
342 uint32_t idx = 0, has_modules = 0;
343 const struct lys_module *mod;
344
345 if (outformat != LYD_UNKNOWN) {
346 if (ly_ctx_get_yanglib_data(ctx, &ylib)) {
347 YLMSG_E("Getting context info (ietf-yang-library data) failed.\n");
348 return 1;
349 }
350
351 lyd_print_all(out, ylib, outformat, 0);
352 lyd_free_all(ylib);
353 return 0;
354 }
355
356 /* iterate schemas in context and provide just the basic info */
357 ly_print(out, "List of the loaded models:\n");
358 while ((mod = ly_ctx_get_module_iter(ctx, &idx))) {
359 has_modules++;
360
361 /* conformance print */
362 if (mod->implemented) {
363 ly_print(out, " I");
364 } else {
365 ly_print(out, " i");
366 }
367
368 /* module print */
369 ly_print(out, " %s", mod->name);
370 if (mod->revision) {
371 ly_print(out, "@%s", mod->revision);
372 }
373
374 /* submodules print */
375 if (mod->parsed && mod->parsed->includes) {
376 uint64_t u = 0;
377 ly_print(out, " (");
378 LY_ARRAY_FOR(mod->parsed->includes, u) {
379 ly_print(out, "%s%s", !u ? "" : ",", mod->parsed->includes[u].name);
380 if (mod->parsed->includes[u].rev[0]) {
381 ly_print(out, "@%s", mod->parsed->includes[u].rev);
382 }
383 }
384 ly_print(out, ")");
385 }
386
387 /* finish the line */
388 ly_print(out, "\n");
389 }
390
391 if (!has_modules) {
392 ly_print(out, "\t(none)\n");
393 }
394
395 ly_print_flush(out);
396 return 0;
397}
398
399int
400check_request_paths(struct ly_ctx *ctx, struct ly_set *request_paths, struct ly_set *data_inputs)
401{
402 if ((request_paths->count > 1) && (request_paths->count != data_inputs->count)) {
403 YLMSG_E("Number of request paths does not match the number of reply data files (%u:%u).\n",
404 request_paths->count, data_inputs->count);
405 return -1;
406 }
407
408 for (uint32_t u = 0; u < request_paths->count; ++u) {
409 const char *path = (const char *)request_paths->objs[u];
410 const struct lysc_node *action = NULL;
411
412 action = lys_find_path(ctx, NULL, path, 0);
413 if (!action) {
414 YLMSG_E("The request path \"%s\" is not valid.\n", path);
415 return -1;
416 } else if (!(action->nodetype & (LYS_RPC | LYS_ACTION))) {
417 YLMSG_E("The request path \"%s\" does not represent RPC/Action.\n", path);
418 return -1;
419 }
420 }
421
422 return 0;
423}
424
425int
426evaluate_xpath(const struct lyd_node *tree, const char *xpath)
427{
428 struct ly_set *set;
429
430 if (lyd_find_xpath(tree, xpath, &set)) {
431 return -1;
432 }
433
434 /* print result */
435 printf("XPath \"%s\" evaluation result:\n", xpath);
436 if (!set->count) {
437 printf("\tEmpty\n");
438 } else {
439 for (uint32_t u = 0; u < set->count; ++u) {
440 struct lyd_node *node = (struct lyd_node *)set->objs[u];
441 printf(" %s \"%s\"", lys_nodetype2str(node->schema->nodetype), node->schema->name);
442 if (node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) {
443 printf(" (value: \"%s\")\n", ((struct lyd_node_term *)node)->value.canonical);
444 } else if (node->schema->nodetype == LYS_LIST) {
445 printf(" (");
446 for (struct lyd_node *key = ((struct lyd_node_inner *)node)->child; key && lysc_is_key(key->schema); key = key->next) {
447 printf("%s\"%s\": \"%s\";", (key != ((struct lyd_node_inner *)node)->child) ? " " : "",
448 key->schema->name, ((struct lyd_node_term *)key)->value.canonical);
449 }
450 printf(")\n");
451 }
452 }
453 }
454
455 ly_set_free(set, NULL);
456 return 0;
457}
458
459LY_ERR
460process_data(struct ly_ctx *ctx, uint8_t data_type, uint8_t merge, LYD_FORMAT format, struct ly_out *out,
461 uint32_t options_parse, uint32_t options_validate, uint32_t options_print,
462 struct cmdline_file *operational_f, struct ly_set *inputs, struct ly_set *request_paths, struct ly_set *requests,
463 struct ly_set *xpaths)
464{
465 LY_ERR ret;
466 struct lyd_node *tree = NULL, *merged_tree = NULL;
467 struct lyd_node *operational;
468
469 /* additional operational datastore */
470 if (operational_f && operational_f->in) {
471 ret = lyd_parse_data(ctx, operational_f->in, operational_f->format, LYD_PARSE_ONLY, 0, &operational);
472 if (ret) {
473 YLMSG_E("Failed to parse operational datastore file \"%s\".\n", operational_f->path);
474 goto cleanup;
475 }
476 }
477
478 for (uint32_t u = 0; u < inputs->count; ++u) {
479 struct cmdline_file *input_f = (struct cmdline_file *)inputs->objs[u];
480 struct cmdline_file *request_f;
481 switch (data_type) {
482 case 0:
483 ret = lyd_parse_data(ctx, input_f->in, input_f->format, options_parse, options_validate, &tree);
484 break;
485 case LYD_VALIDATE_OP_RPC:
486 ret = lyd_parse_rpc(ctx, input_f->in, input_f->format, &tree, NULL);
487 break;
488 case LYD_VALIDATE_OP_REPLY: {
489 struct lyd_node *request = NULL;
490
491 /* get the request data */
492 if (request_paths->count) {
493 const char *path;
494 if (request_paths->count > 1) {
495 /* one to one */
496 path = (const char *)request_paths->objs[u];
497 } else {
498 /* one to all */
499 path = (const char *)request_paths->objs[0];
500 }
501 ret = lyd_new_path(NULL, ctx, path, NULL, 0, &request);
502 if (ret) {
503 YLMSG_E("Failed to create request data from path \"%s\".\n", path);
504 goto cleanup;
505 }
506 } else {
507 assert(requests->count > u);
508 request_f = (struct cmdline_file *)requests->objs[u];
509
510 ret = lyd_parse_rpc(ctx, request_f->in, request_f->format, &request, NULL);
511 if (ret) {
512 YLMSG_E("Failed to parse input data file \"%s\".\n", input_f->path);
513 goto cleanup;
514 }
515 }
516
517 /* get the reply data */
518 ret = lyd_parse_reply(request, input_f->in, input_f->format, &tree, NULL);
519 lyd_free_all(request);
520
521 break;
522 } /* case PARSE_REPLY */
523 case LYD_VALIDATE_OP_NOTIF:
524 ret = lyd_parse_notif(ctx, input_f->in, input_f->format, &tree, NULL);
525 break;
526 }
527
528 if (ret) {
529 YLMSG_E("Failed to parse input data file \"%s\".\n", input_f->path);
530 goto cleanup;
531 }
532
533 if (merge) {
534 /* merge the data so far parsed for later validation and print */
535 if (!merged_tree) {
536 merged_tree = tree;
537 } else {
538 ret = lyd_merge_siblings(&merged_tree, tree, LYD_MERGE_DESTRUCT);
539 if (ret) {
540 YLMSG_E("Merging %s with previous data failed.\n", input_f->path);
541 goto cleanup;
542 }
543 }
544 tree = NULL;
545 } else if (format) {
546 lyd_print_all(out, tree, format, options_print);
547 } else if (operational) {
548 /* additional validation of the RPC/Action/reply/Notification with the operational datastore */
549 ret = lyd_validate_op(tree, operational, data_type, NULL);
550 if (ret) {
551 YLMSG_E("Failed to validate input data file \"%s\" with operational datastore \"%s\".\n",
552 input_f->path, operational_f->path);
553 goto cleanup;
554 }
555 }
556 lyd_free_all(tree);
557 tree = NULL;
558 }
559
560 if (merge) {
561 /* validate the merged result */
562 ret = lyd_validate_all(&merged_tree, ctx, LYD_VALIDATE_PRESENT, NULL);
563 if (ret) {
564 YLMSG_E("Merged data are not valid.\n");
565 goto cleanup;
566 }
567
568 if (format) {
569 /* and print it */
570 lyd_print_all(out, merged_tree, format, options_print);
571 }
572
573 for (uint32_t u = 0; u < xpaths->count; ++u) {
574 if (evaluate_xpath(merged_tree, (const char *)xpaths->objs[u])) {
575 goto cleanup;
576 }
577 }
578 }
579
580cleanup:
581 /* cleanup */
582 lyd_free_all(merged_tree);
583 lyd_free_all(tree);
584
585 return ret;
586}