add meson support
[protos/libecoli.git] / src / 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                 enode = NULL;
221                 if (config == NULL) {
222                         fprintf(stderr, "Failed to create config\n");
223                         goto fail;
224                 }
225                 break;
226         case EC_CONFIG_TYPE_LIST:
227                 subschema = ec_config_schema_sub(schema_elt);
228                 if (subschema == NULL) {
229                         fprintf(stderr, "List has no subschema\n");
230                         goto fail;
231                 }
232                 config = parse_ec_config_list(table, subschema, document, ynode);
233                 if (config == NULL)
234                         goto fail;
235                 break;
236         case EC_CONFIG_TYPE_DICT:
237                 subschema = ec_config_schema_sub(schema_elt);
238                 if (subschema == NULL) {
239                         fprintf(stderr, "Dict has no subschema\n");
240                         goto fail;
241                 }
242                 config = parse_ec_config_dict(table, subschema, document, ynode);
243                 if (config == NULL)
244                         goto fail;
245                 break;
246         default:
247                 fprintf(stderr, "Invalid config type %d\n", type);
248                 goto fail;
249         }
250
251         return config;
252
253 fail:
254         ec_node_free(enode);
255         ec_config_free(config);
256         return NULL;
257 }
258
259 static struct ec_config *
260 parse_ec_config_list(struct enode_table *table,
261                 const struct ec_config_schema *schema,
262                 const yaml_document_t *document, const yaml_node_t *ynode)
263 {
264         struct ec_config *config = NULL, *subconfig = NULL;
265         const yaml_node_item_t *item;
266         const yaml_node_t *value;
267
268         if (ynode->type != YAML_SEQUENCE_NODE) {
269                 fprintf(stderr, "Ecoli list config should be a yaml sequence\n");
270                 goto fail;
271         }
272
273         config = ec_config_list();
274         if (config == NULL) {
275                 fprintf(stderr, "Failed to allocate config\n");
276                 goto fail;
277         }
278
279         for (item = ynode->data.sequence.items.start;
280              item < ynode->data.sequence.items.top; item++) {
281                 value = document->nodes.start + (*item) - 1; // XXX -1 ?
282                 subconfig = parse_ec_config(table, schema, document, value);
283                 if (subconfig == NULL)
284                         goto fail;
285                 if (ec_config_list_add(config, subconfig) < 0) {
286                         fprintf(stderr, "Failed to add list entry\n");
287                         goto fail;
288                 }
289         }
290
291         return config;
292
293 fail:
294         ec_config_free(config);
295         return NULL;
296 }
297
298 static struct ec_config *
299 parse_ec_config_dict(struct enode_table *table,
300                 const struct ec_config_schema *schema,
301                 const yaml_document_t *document, const yaml_node_t *ynode)
302 {
303         const struct ec_config_schema *schema_elt;
304         struct ec_config *config = NULL, *subconfig = NULL;
305         const yaml_node_t *key, *value;
306         const yaml_node_pair_t *pair;
307         const char *key_str;
308
309         if (ynode->type != YAML_MAPPING_NODE) {
310                 fprintf(stderr, "Ecoli config should be a yaml mapping node\n");
311                 goto fail;
312         }
313
314         config = ec_config_dict();
315         if (config == NULL) {
316                 fprintf(stderr, "Failed to allocate config\n");
317                 goto fail;
318         }
319
320         for (pair = ynode->data.mapping.pairs.start;
321              pair < ynode->data.mapping.pairs.top; pair++) {
322                 key = document->nodes.start + pair->key - 1; // XXX -1 ?
323                 value = document->nodes.start + pair->value - 1;
324                 key_str = (const char *)key->data.scalar.value;
325
326                 if (ec_config_key_is_reserved(key_str))
327                         continue;
328                 schema_elt = ec_config_schema_lookup(schema, key_str);
329                 if (schema_elt == NULL) {
330                         fprintf(stderr, "No such config %s\n", key_str);
331                         goto fail;
332                 }
333                 subconfig = parse_ec_config(table, schema_elt, document, value);
334                 if (subconfig == NULL)
335                         goto fail;
336                 if (ec_config_dict_set(config, key_str, subconfig) < 0) {
337                         fprintf(stderr, "Failed to set dict entry\n");
338                         goto fail;
339                 }
340         }
341
342         return config;
343
344 fail:
345         ec_config_free(config);
346         return NULL;
347 }
348
349 static struct ec_node *
350 parse_ec_node(struct enode_table *table,
351         const yaml_document_t *document, const yaml_node_t *ynode)
352 {
353         const struct ec_config_schema *schema;
354         const struct ec_node_type *type = NULL;
355         const char *id = NULL;
356         char *help = NULL;
357         struct ec_config *config = NULL;
358         const yaml_node_t *attrs = NULL;
359         const yaml_node_t *key, *value;
360         const yaml_node_pair_t *pair;
361         const char *key_str, *value_str;
362         struct ec_node *enode = NULL;
363
364         if (ynode->type != YAML_MAPPING_NODE) {
365                 fprintf(stderr, "Ecoli node should be a yaml mapping node\n");
366                 goto fail;
367         }
368
369         for (pair = ynode->data.mapping.pairs.start;
370              pair < ynode->data.mapping.pairs.top; pair++) {
371                 key = document->nodes.start + pair->key - 1; // XXX -1 ?
372                 value = document->nodes.start + pair->value - 1;
373                 key_str = (const char *)key->data.scalar.value;
374                 value_str = (const char *)value->data.scalar.value;
375
376                 if (!strcmp(key_str, "type")) {
377                         if (type != NULL) {
378                                 fprintf(stderr, "Duplicate type\n");
379                                 goto fail;
380                         }
381                         if (value->type != YAML_SCALAR_NODE) {
382                                 fprintf(stderr, "Type must be a string\n");
383                                 goto fail;
384                         }
385                         type = ec_node_type_lookup(value_str);
386                         if (type == NULL) {
387                                 fprintf(stderr, "Cannot find type %s\n",
388                                         value_str);
389                                 goto fail;
390                         }
391                 } else if (!strcmp(key_str, "attrs")) {
392                         if (attrs != NULL) {
393                                 fprintf(stderr, "Duplicate attrs\n");
394                                 goto fail;
395                         }
396                         if (value->type != YAML_MAPPING_NODE) {
397                                 fprintf(stderr, "Attrs must be a maping\n");
398                                 goto fail;
399                         }
400                         attrs = value;
401                 } else if (!strcmp(key_str, "id")) {
402                         if (id != NULL) {
403                                 fprintf(stderr, "Duplicate id\n");
404                                 goto fail;
405                         }
406                         if (value->type != YAML_SCALAR_NODE) {
407                                 fprintf(stderr, "Id must be a scalar\n");
408                                 goto fail;
409                         }
410                         id = value_str;
411                 } else if (!strcmp(key_str, "help")) {
412                         if (help != NULL) {
413                                 fprintf(stderr, "Duplicate help\n");
414                                 goto fail;
415                         }
416                         if (value->type != YAML_SCALAR_NODE) {
417                                 fprintf(stderr, "Help must be a scalar\n");
418                                 goto fail;
419                         }
420                         help = ec_strdup(value_str);
421                         if (help == NULL) {
422                                 fprintf(stderr, "Failed to allocate help\n");
423                                 goto fail;
424                         }
425                 }
426         }
427
428         /* create the ecoli node */
429         if (id == NULL)
430                 id = EC_NO_ID;
431         enode = ec_node_from_type(type, id);
432         if (enode == NULL) {
433                 fprintf(stderr, "Cannot create ecoli node\n");
434                 goto fail;
435         }
436         if (add_in_table(table, ynode, enode) < 0) {
437                 fprintf(stderr, "Cannot add node in table\n");
438                 goto fail;
439         }
440
441         /* create its config */
442         schema = ec_node_type_schema(type);
443         if (schema == NULL) {
444                 fprintf(stderr, "No configuration schema for type %s\n",
445                         ec_node_type_name(type));
446                 goto fail;
447         }
448
449         config = parse_ec_config_dict(table, schema, document, ynode);
450         if (config == NULL)
451                 goto fail;
452
453         if (ec_node_set_config(enode, config) < 0) {
454                 config = NULL; /* freed */
455                 fprintf(stderr, "Failed to set config\n");
456                 goto fail;
457         }
458         config = NULL; /* freed */
459
460         if (help != NULL) {
461                 if (ec_keyval_set(ec_node_attrs(enode), "help", help,
462                                         ec_free_func) < 0) {
463                         fprintf(stderr, "Failed to set help\n");
464                         help = NULL;
465                         goto fail;
466                 }
467                 help = NULL;
468         }
469
470         /* add attributes (all as string) */
471         //XXX
472
473         return enode;
474
475 fail:
476         ec_node_free(enode);
477         ec_config_free(config);
478         ec_free(help);
479
480         return NULL;
481 }
482
483 static struct ec_node *
484 parse_document(struct enode_table *table,
485         const yaml_document_t *document)
486 {
487         yaml_node_t *node;
488
489         node = document->nodes.start;
490         return parse_ec_node(table, document, node);
491 }
492
493 struct ec_node *
494 ec_yaml_import(const char *filename)
495 {
496         FILE *file;
497         yaml_parser_t parser;
498         yaml_document_t document;
499         struct ec_node *root = NULL;
500         struct enode_table table;
501
502         memset(&table, 0, sizeof(table));
503
504         file = fopen(filename, "rb");
505         if (file == NULL) {
506                 fprintf(stderr, "Failed to open file %s\n", filename);
507                 goto fail_no_doc;
508         }
509
510         if (yaml_parser_initialize(&parser) == 0) {
511                 fprintf(stderr, "Failed to initialize yaml parser\n");
512                 goto fail_no_doc;
513         }
514
515         yaml_parser_set_input_file(&parser, file);
516
517         if (yaml_parser_load(&parser, &document) == 0) {
518                 fprintf(stderr, "Failed to load yaml document\n");
519                 goto fail_no_doc;
520         }
521
522         if (yaml_document_get_root_node(&document) == NULL) {
523                 fprintf(stderr, "Incomplete document\n"); //XXX check err
524                 goto fail;
525         }
526
527         root = parse_document(&table, &document);
528         if (root == NULL) {
529                 fprintf(stderr, "Failed to parse document\n");
530                 goto fail;
531         }
532
533         yaml_document_delete(&document);
534         yaml_parser_delete(&parser);
535         fclose(file);
536         free_table(&table);
537
538         return root;
539
540 fail:
541         yaml_document_delete(&document);
542 fail_no_doc:
543         yaml_parser_delete(&parser);
544         if (file != NULL)
545                 fclose(file);
546         free_table(&table);
547         ec_node_free(root);
548
549         return NULL;
550 }