76592e272df166199933bed99b0ee811aaa852c8
[protos/libecoli.git] / libecoli_yaml / ecoli_yaml.c
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright 2018, Olivier MATZ <zer0@droids-corp.org>
3  */
4
5 #include <stdlib.h>
6 #include <stdio.h>
7 #include <string.h>
8 #include <stdint.h>
9 #include <errno.h>
10 #include <limits.h>
11 #include <assert.h>
12
13 #include <yaml.h>
14
15 #include <ecoli_malloc.h>
16 #include <ecoli_keyval.h>
17 #include <ecoli_node.h>
18 #include <ecoli_config.h>
19 #include <ecoli_yaml.h>
20
21 /* associate a yaml node to a ecoli node */
22 struct pair {
23         const yaml_node_t *ynode;
24         struct ec_node *enode;
25 };
26
27 /* store the associations yaml_node <-> ec_node */
28 struct enode_table {
29         struct pair *pair;
30         size_t len;
31 };
32
33 static struct ec_node *
34 parse_ec_node(struct enode_table *table, const yaml_document_t *document,
35         const yaml_node_t *ynode);
36
37 static struct ec_config *
38 parse_ec_config_list(struct enode_table *table,
39                 const struct ec_config_schema *schema,
40                 const yaml_document_t *document, const yaml_node_t *ynode);
41
42 static struct ec_config *
43 parse_ec_config_dict(struct enode_table *table,
44                 const struct ec_config_schema *schema,
45                 const yaml_document_t *document, const yaml_node_t *ynode);
46
47 /* XXX to utils.c ? */
48 static int
49 parse_llint(const char *str, int64_t *val)
50 {
51         char *endptr;
52         int save_errno = errno;
53
54         errno = 0;
55         *val = strtoll(str, &endptr, 0);
56
57         if ((errno == ERANGE && (*val == LLONG_MAX || *val == LLONG_MIN)) ||
58                         (errno != 0 && *val == 0))
59                 return -1;
60
61         if (*endptr != 0) {
62                 errno = EINVAL;
63                 return -1;
64         }
65
66         errno = save_errno;
67         return 0;
68 }
69
70 static int
71 parse_ullint(const char *str, uint64_t *val)
72 {
73         char *endptr;
74         int save_errno = errno;
75
76         /* since a negative input is silently converted to a positive
77          * one by strtoull(), first check that it is positive */
78         if (strchr(str, '-'))
79                 return -1;
80
81         errno = 0;
82         *val = strtoull(str, &endptr, 0);
83
84         if ((errno == ERANGE && *val == ULLONG_MAX) ||
85                         (errno != 0 && *val == 0))
86                 return -1;
87
88         if (*endptr != 0)
89                 return -1;
90
91         errno = save_errno;
92         return 0;
93 }
94
95 static int
96 parse_bool(const char *str, bool *val)
97 {
98         if (!strcasecmp(str, "true")) {
99                 *val = true;
100                 return 0;
101         } else if (!strcasecmp(str, "false")) {
102                 *val = false;
103                 return 0;
104         }
105         errno = EINVAL;
106         return -1;
107 }
108
109 static int
110 add_in_table(struct enode_table *table,
111         const yaml_node_t *ynode, struct ec_node *enode)
112 {
113         struct pair *pair = NULL;
114
115         pair = realloc(table->pair, (table->len + 1) * sizeof(*pair));
116         if (pair == NULL)
117                 return -1;
118
119         ec_node_clone(enode);
120         pair[table->len].ynode = ynode;
121         pair[table->len].enode = enode;
122         table->pair = pair;
123         table->len++;
124
125         return 0;
126 }
127
128 static void
129 free_table(struct enode_table *table)
130 {
131         size_t i;
132
133         for (i = 0; i < table->len; i++)
134                 ec_node_free(table->pair[i].enode);
135         free(table->pair);
136 }
137
138 static struct ec_config *
139 parse_ec_config(struct enode_table *table,
140                 const struct ec_config_schema *schema_elt,
141                 const yaml_document_t *document, const yaml_node_t *ynode)
142 {
143         const struct ec_config_schema *subschema;
144         struct ec_config *config = NULL;
145         struct ec_node *enode = NULL;
146         enum ec_config_type type;
147         const char *value_str;
148         uint64_t u64;
149         int64_t i64;
150         bool boolean;
151
152         type = ec_config_schema_type(schema_elt);
153
154         switch (type) {
155         case EC_CONFIG_TYPE_BOOL:
156                 if (ynode->type != YAML_SCALAR_NODE) {
157                         fprintf(stderr, "Boolean should be scalar\n");
158                         goto fail;
159                 }
160                 value_str = (const char *)ynode->data.scalar.value;
161                 if (parse_bool(value_str, &boolean)  < 0) {
162                         fprintf(stderr, "Failed to parse boolean\n");
163                         goto fail;
164                 }
165                 config = ec_config_bool(boolean);
166                 if (config == NULL) {
167                         fprintf(stderr, "Failed to create config\n");
168                         goto fail;
169                 }
170                 break;
171         case EC_CONFIG_TYPE_INT64:
172                 if (ynode->type != YAML_SCALAR_NODE) {
173                         fprintf(stderr, "Int64 should be scalar\n");
174                         goto fail;
175                 }
176                 value_str = (const char *)ynode->data.scalar.value;
177                 if (parse_llint(value_str, &i64)  < 0) {
178                         fprintf(stderr, "Failed to parse i64\n");
179                         goto fail;
180                 }
181                 config = ec_config_i64(i64);
182                 if (config == NULL) {
183                         fprintf(stderr, "Failed to create config\n");
184                         goto fail;
185                 }
186                 break;
187         case EC_CONFIG_TYPE_UINT64:
188                 if (ynode->type != YAML_SCALAR_NODE) {
189                         fprintf(stderr, "Uint64 should be scalar\n");
190                         goto fail;
191                 }
192                 value_str = (const char *)ynode->data.scalar.value;
193                 if (parse_ullint(value_str, &u64)  < 0) {
194                         fprintf(stderr, "Failed to parse u64\n");
195                         goto fail;
196                 }
197                 config = ec_config_u64(u64);
198                 if (config == NULL) {
199                         fprintf(stderr, "Failed to create config\n");
200                         goto fail;
201                 }
202                 break;
203         case EC_CONFIG_TYPE_STRING:
204                 if (ynode->type != YAML_SCALAR_NODE) {
205                         fprintf(stderr, "String should be scalar\n");
206                         goto fail;
207                 }
208                 value_str = (const char *)ynode->data.scalar.value;
209                 config = ec_config_string(value_str);
210                 if (config == NULL) {
211                         fprintf(stderr, "Failed to create config\n");
212                         goto fail;
213                 }
214                 break;
215         case EC_CONFIG_TYPE_NODE:
216                 enode = parse_ec_node(table, document, ynode);
217                 if (enode == NULL)
218                         goto fail;
219                 config = ec_config_node(enode);
220                 if (config == NULL) {
221                         fprintf(stderr, "Failed to create config\n");
222                         goto fail;
223                 }
224                 break;
225         case EC_CONFIG_TYPE_LIST:
226                 subschema = ec_config_schema_sub(schema_elt);
227                 if (subschema == NULL) {
228                         fprintf(stderr, "List has no subschema\n");
229                         goto fail;
230                 }
231                 config = parse_ec_config_list(table, subschema, document, ynode);
232                 if (config == NULL)
233                         goto fail;
234                 break;
235         case EC_CONFIG_TYPE_DICT:
236                 subschema = ec_config_schema_sub(schema_elt);
237                 if (subschema == NULL) {
238                         fprintf(stderr, "Dict has no subschema\n");
239                         goto fail;
240                 }
241                 config = parse_ec_config_dict(table, subschema, document, ynode);
242                 if (config == NULL)
243                         goto fail;
244                 break;
245         default:
246                 fprintf(stderr, "Invalid config type %d\n", type);
247                 goto fail;
248         }
249
250         return config;
251
252 fail:
253         ec_node_free(enode);
254         ec_config_free(config);
255         return NULL;
256 }
257
258 static struct ec_config *
259 parse_ec_config_list(struct enode_table *table,
260                 const struct ec_config_schema *schema,
261                 const yaml_document_t *document, const yaml_node_t *ynode)
262 {
263         struct ec_config *config = NULL, *subconfig = NULL;
264         const yaml_node_item_t *item;
265         const yaml_node_t *value;
266
267         if (ynode->type != YAML_SEQUENCE_NODE) {
268                 fprintf(stderr, "Ecoli list config should be a yaml sequence\n");
269                 goto fail;
270         }
271
272         config = ec_config_list();
273         if (config == NULL) {
274                 fprintf(stderr, "Failed to allocate config\n");
275                 goto fail;
276         }
277
278         for (item = ynode->data.sequence.items.start;
279              item < ynode->data.sequence.items.top; item++) {
280                 value = document->nodes.start + (*item) - 1; // XXX -1 ?
281                 subconfig = parse_ec_config(table, schema, document, value);
282                 if (subconfig == NULL)
283                         goto fail;
284                 if (ec_config_list_add(config, subconfig) < 0) {
285                         fprintf(stderr, "Failed to add list entry\n");
286                         goto fail;
287                 }
288         }
289
290         return config;
291
292 fail:
293         ec_config_free(config);
294         return NULL;
295 }
296
297 static struct ec_config *
298 parse_ec_config_dict(struct enode_table *table,
299                 const struct ec_config_schema *schema,
300                 const yaml_document_t *document, const yaml_node_t *ynode)
301 {
302         const struct ec_config_schema *schema_elt;
303         struct ec_config *config = NULL, *subconfig = NULL;
304         const yaml_node_t *key, *value;
305         const yaml_node_pair_t *pair;
306         const char *key_str;
307
308         if (ynode->type != YAML_MAPPING_NODE) {
309                 fprintf(stderr, "Ecoli config should be a yaml mapping node\n");
310                 goto fail;
311         }
312
313         config = ec_config_dict();
314         if (config == NULL) {
315                 fprintf(stderr, "Failed to allocate config\n");
316                 goto fail;
317         }
318
319         for (pair = ynode->data.mapping.pairs.start;
320              pair < ynode->data.mapping.pairs.top; pair++) {
321                 key = document->nodes.start + pair->key - 1; // XXX -1 ?
322                 value = document->nodes.start + pair->value - 1;
323                 key_str = (const char *)key->data.scalar.value;
324
325                 if (ec_config_key_is_reserved(key_str))
326                         continue;
327                 schema_elt = ec_config_schema_lookup(schema, key_str);
328                 if (schema_elt == NULL) {
329                         fprintf(stderr, "No such config %s\n", key_str);
330                         goto fail;
331                 }
332                 subconfig = parse_ec_config(table, schema_elt, document, value);
333                 if (subconfig == NULL)
334                         goto fail;
335                 if (ec_config_dict_set(config, key_str, subconfig) < 0) {
336                         fprintf(stderr, "Failed to set dict entry\n");
337                         goto fail;
338                 }
339         }
340
341         return config;
342
343 fail:
344         ec_config_free(config);
345         return NULL;
346 }
347
348 static struct ec_node *
349 parse_ec_node(struct enode_table *table,
350         const yaml_document_t *document, const yaml_node_t *ynode)
351 {
352         const struct ec_config_schema *schema;
353         const struct ec_node_type *type = NULL;
354         const char *id = NULL;
355         char *help = NULL;
356         struct ec_config *config = NULL;
357         const yaml_node_t *attrs = NULL;
358         const yaml_node_t *key, *value;
359         const yaml_node_pair_t *pair;
360         const char *key_str, *value_str;
361         struct ec_node *enode = NULL;
362
363         if (ynode->type != YAML_MAPPING_NODE) {
364                 fprintf(stderr, "Ecoli node should be a yaml mapping node\n");
365                 goto fail;
366         }
367
368         for (pair = ynode->data.mapping.pairs.start;
369              pair < ynode->data.mapping.pairs.top; pair++) {
370                 key = document->nodes.start + pair->key - 1; // XXX -1 ?
371                 value = document->nodes.start + pair->value - 1;
372                 key_str = (const char *)key->data.scalar.value;
373                 value_str = (const char *)value->data.scalar.value;
374
375                 if (!strcmp(key_str, "type")) {
376                         if (type != NULL) {
377                                 fprintf(stderr, "Duplicate type\n");
378                                 goto fail;
379                         }
380                         if (value->type != YAML_SCALAR_NODE) {
381                                 fprintf(stderr, "Type must be a string\n");
382                                 goto fail;
383                         }
384                         type = ec_node_type_lookup(value_str);
385                         if (type == NULL) {
386                                 fprintf(stderr, "Cannot find type %s\n",
387                                         value_str);
388                                 goto fail;
389                         }
390                 } else if (!strcmp(key_str, "attrs")) {
391                         if (attrs != NULL) {
392                                 fprintf(stderr, "Duplicate attrs\n");
393                                 goto fail;
394                         }
395                         if (value->type != YAML_MAPPING_NODE) {
396                                 fprintf(stderr, "Attrs must be a maping\n");
397                                 goto fail;
398                         }
399                         attrs = value;
400                 } else if (!strcmp(key_str, "id")) {
401                         if (id != NULL) {
402                                 fprintf(stderr, "Duplicate id\n");
403                                 goto fail;
404                         }
405                         if (value->type != YAML_SCALAR_NODE) {
406                                 fprintf(stderr, "Id must be a scalar\n");
407                                 goto fail;
408                         }
409                         id = value_str;
410                 } else if (!strcmp(key_str, "help")) {
411                         if (help != NULL) {
412                                 fprintf(stderr, "Duplicate help\n");
413                                 goto fail;
414                         }
415                         if (value->type != YAML_SCALAR_NODE) {
416                                 fprintf(stderr, "Help must be a scalar\n");
417                                 goto fail;
418                         }
419                         help = ec_strdup(value_str);
420                         if (help == NULL) {
421                                 fprintf(stderr, "Failed to allocate help\n");
422                                 goto fail;
423                         }
424                 }
425         }
426
427         /* create the ecoli node */
428         if (id == NULL)
429                 id = EC_NO_ID;
430         enode = ec_node_from_type(type, id);
431         if (enode == NULL) {
432                 fprintf(stderr, "Cannot create ecoli node\n");
433                 goto fail;
434         }
435         if (add_in_table(table, ynode, enode) < 0) {
436                 fprintf(stderr, "Cannot add node in table\n");
437                 goto fail;
438         }
439
440         /* create its config */
441         schema = ec_node_type_schema(type);
442         if (schema == NULL) {
443                 fprintf(stderr, "No configuration schema for type %s\n",
444                         ec_node_type_name(type));
445                 goto fail;
446         }
447
448         config = parse_ec_config_dict(table, schema, document, ynode);
449         if (config == NULL)
450                 goto fail;
451
452         if (ec_node_set_config(enode, config) < 0) {
453                 fprintf(stderr, "Failed to set config\n");
454                 goto fail;
455         }
456
457         if (help != NULL) {
458                 if (ec_keyval_set(ec_node_attrs(enode), "help", help,
459                                         ec_free_func) < 0) {
460                         fprintf(stderr, "Failed to set help\n");
461                         help = NULL;
462                         goto fail;
463                 }
464                 help = NULL;
465         }
466
467         /* add attributes (all as string) */
468         //XXX
469
470         return enode;
471
472 fail:
473         ec_node_free(enode);
474         ec_config_free(config);
475         ec_free(help);
476
477         return NULL;
478 }
479
480 static struct ec_node *
481 parse_document(struct enode_table *table,
482         const yaml_document_t *document)
483 {
484         yaml_node_t *node;
485
486         node = document->nodes.start;
487         return parse_ec_node(table, document, node);
488 }
489
490 struct ec_node *
491 ec_yaml_import(const char *filename)
492 {
493         FILE *file;
494         yaml_parser_t parser;
495         yaml_document_t document;
496         struct ec_node *root = NULL;
497         struct enode_table table;
498
499         memset(&table, 0, sizeof(table));
500
501         file = fopen(filename, "rb");
502         if (file == NULL) {
503                 fprintf(stderr, "Failed to open file %s\n", filename);
504                 goto fail_no_doc;
505         }
506
507         if (yaml_parser_initialize(&parser) == 0) {
508                 fprintf(stderr, "Failed to initialize yaml parser\n");
509                 goto fail_no_doc;
510         }
511
512         yaml_parser_set_input_file(&parser, file);
513
514         if (yaml_parser_load(&parser, &document) == 0) {
515                 fprintf(stderr, "Failed to load yaml document\n");
516                 goto fail_no_doc;
517         }
518
519         if (yaml_document_get_root_node(&document) == NULL) {
520                 fprintf(stderr, "Incomplete document\n"); //XXX check err
521                 goto fail;
522         }
523
524         root = parse_document(&table, &document);
525         if (root == NULL) {
526                 fprintf(stderr, "Failed to parse document\n");
527                 goto fail;
528         }
529
530         yaml_document_delete(&document);
531         yaml_parser_delete(&parser);
532         fclose(file);
533         free_table(&table);
534
535         return root;
536
537 fail:
538         yaml_document_delete(&document);
539 fail_no_doc:
540         yaml_parser_delete(&parser);
541         if (file != NULL)
542                 fclose(file);
543         free_table(&table);
544         ec_node_free(root);
545
546         return NULL;
547 }