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