initial revision
[ucgine.git] / tools / cfzy / libconfizery / cfzy_conftree.c
1 /*
2  * Copyright (c) 2013, Olivier MATZ <zer0@droids-corp.org>
3  * All rights reserved.
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  *     * Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *     * Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *     * Neither the name of the University of California, Berkeley nor the
13  *       names of its contributors may be used to endorse or promote products
14  *       derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include <stddef.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <errno.h>
32
33 #include "cfzy_log.h"
34 #include "cfzy_list.h"
35 #include "cfzy_htable.h"
36 #include "cfzy_confnode.h"
37 #include "cfzy_conftree.h"
38 #include "cfzy_conftree_parser.h"
39
40 #define LOG(level, fmt, args...)                                \
41         CFZY_LOG("conftree", level, fmt, ##args)
42
43 /* Parse a configuration tree and add all the nodes in the
44  * hashtable. Return the number of nodes or -1 on error. */
45 static int conftree_fill_htable(struct cfzy_conftree *conftree,
46                                 struct cfzy_confnode *n)
47 {
48         struct cfzy_confnode *c;
49         int ret, count;
50
51         if (n->name != NULL) {
52                 if (cfzy_htable_lookup(conftree->nodes_htable, n->name) != NULL) {
53                         LOG(ERR, "duplicate symbol <%s>\n", n->name);
54                         return -1;
55                 }
56
57                 if (cfzy_htable_add(conftree->nodes_htable, n->name, n) < 0)
58                         return -1;
59         }
60         count = 1; /* count the nodes recursively */
61
62         TAILQ_FOREACH(c, &n->children, child_next) {
63                 ret = conftree_fill_htable(conftree, c);
64                 if (ret < 0)
65                         return -1;
66                 count += ret;
67         }
68
69         return count;
70 }
71
72 /* Parse a configuration tree and add all the nodes in the hashtable */
73 static int conftree_generate_deplist(struct cfzy_confnode *n)
74 {
75         struct cfzy_confnode *c;
76
77         n->node_deps = cfzy_confnode_get_deplist(n);
78         if (n->node_deps == NULL)
79                 return -1;
80
81         TAILQ_FOREACH(c, &n->children, child_next) {
82                 if (conftree_generate_deplist(c) < 0)
83                         return -1;
84         }
85
86         return 0;
87 }
88
89 /* Parse a configuration tree and add all the nodes in the
90  * hashtable. Return the number of nodes in the prio_list. */
91 static int conftree_fill_prio_list(struct cfzy_conftree *conftree,
92                                    struct cfzy_confnode *n)
93 {
94         struct cfzy_list_elt *e;
95         struct cfzy_confnode *c;
96         int ok = 1;
97         int count = 0, ret;
98
99         if (n->in_prio_list == 1) {
100                 count = 1;
101         }
102         else {
103                 /* if the node is not in prio_list, check if all
104                  * prerequisites are in prio list, and add in the
105                  * list */
106                 TAILQ_FOREACH(e, n->node_deps, next) {
107                         c = e->ptr;
108                         if (c->in_prio_list == 0) {
109                                 ok = 0;
110                                 break;
111                         }
112                 }
113                 if (ok == 1) {
114                         TAILQ_INSERT_TAIL(&conftree->prio_list, n, prio_next);
115                         count = 1;
116                         n->in_prio_list = 1;
117                 }
118         }
119
120         /* process children */
121         TAILQ_FOREACH(c, &n->children, child_next) {
122                 ret = conftree_fill_prio_list(conftree, c);
123                 if (ret < 0)
124                         return -1;
125                 count += ret;
126         }
127
128         return count;
129 }
130
131 /* create a new configuration tree from filename */
132 struct cfzy_conftree *cfzy_conftree_new(const char *filename)
133 {
134         struct cfzy_conftree *conftree;
135         int node_count, prio_count, prev;
136
137         LOG(INFO, "Open conftree <%s>\n", filename);
138
139         conftree = malloc(sizeof(*conftree));
140         if (conftree == NULL)
141                 return NULL;
142         memset(conftree, 0, sizeof(*conftree));
143
144         conftree->path = strdup(filename);
145         if (conftree->path == NULL) {
146                 cfzy_conftree_free(conftree);
147                 return NULL;
148         }
149
150         conftree->root = cfzy_confnode_root_new(conftree);
151         if (conftree->root == NULL) {
152                 cfzy_conftree_free(conftree);
153                 return NULL;
154         }
155
156         /* parse the configuration tree file */
157         if (cfzy_conftree_parse(filename, conftree) < 0) {
158                 LOG(ERR, "cannot parse configuration tree\n");
159                 cfzy_conftree_free(conftree);
160                 return NULL;
161         }
162
163         conftree->nodes_htable = cfzy_htable_alloc();
164         if (conftree->nodes_htable == NULL) {
165                 LOG(ERR, "cannot allocate conftree htable\n");
166                 cfzy_conftree_free(conftree);
167                 return NULL;
168         }
169
170         /* fill a htable indexed by node name */
171         node_count = conftree_fill_htable(conftree, conftree->root);
172         if (node_count < 0) {
173                 LOG(ERR, "cannot fill conftree htable\n");
174                 cfzy_conftree_free(conftree);
175                 return NULL;
176         }
177         conftree->count = node_count;
178
179         /* for each node, generate the list of prerequisite nodes */
180         if (conftree_generate_deplist(conftree->root) < 0) {
181                 LOG(ERR, "cannot generate dep list\n");
182                 cfzy_conftree_free(conftree);
183                 return NULL;
184         }
185
186         /* generate a list ordered by node priority: if Node1 depends
187          * on Node2, then Node1 is located after Node2 in the list. */
188         TAILQ_INIT(&conftree->prio_list);
189         prev = 0;
190         while (1) {
191                 prio_count = conftree_fill_prio_list(conftree, conftree->root);
192                 if (prio_count < 0) {
193                         LOG(ERR, "cannot generate prio list\n");
194                         cfzy_conftree_free(conftree);
195                         return NULL;
196                 }
197                 if (prio_count == node_count)
198                         break;
199                 if (prio_count == prev) {
200                         LOG(ERR, "circular dependency in conf tree\n");
201                         cfzy_conftree_free(conftree);
202                         return NULL;
203                 }
204                 prev = prio_count;
205         }
206
207         return conftree;
208 }
209
210 /* free a configuration tree */
211 void cfzy_conftree_free(struct cfzy_conftree *conftree)
212 {
213         if (conftree->path != NULL)
214                 free(conftree->path);
215         if (conftree->nodes_htable != NULL)
216                 cfzy_htable_free(conftree->nodes_htable, NULL);
217         if (conftree->root != NULL)
218                 cfzy_confnode_free(conftree->root);
219         free(conftree);
220 }
221
222 int cfzy_conftree_dump(const struct cfzy_conftree *conftree,
223                         const char *filename)
224 {
225         FILE *f;
226
227         f = fopen(filename, "w");
228         if (f == NULL) {
229                 LOG(ERR, "Cannot open <%s>: %s\n", filename, strerror(errno));
230                 return -1;
231         }
232
233         if (cfzy_confnode_dump(conftree->root, f,
234                                CFZY_DUMP_MODE_FULL_RECURSIVE) < 0) {
235                 LOG(ERR, "Cannot dump confnode tree\n");
236                 fclose(f);
237                 return -1;
238         }
239
240         fclose(f);
241         return 0;
242 }
243
244 int cfzy_conftree_evaluate(struct cfzy_conftree *conftree)
245 {
246         struct cfzy_confnode *n;
247
248         TAILQ_FOREACH(n, &conftree->prio_list, prio_next) {
249                 if (cfzy_confnode_evaluate(n) < 0) {
250                         LOG(ERR, "Cannot evaluate conftree\n");
251                         return -1;
252                 }
253                 LOG(DEBUG, "eval %s -> %s\n", n->name, n->effective_value);
254         }
255
256         return 0;
257 }
258
259 /* same than strcmp() but allow string to be NULL */
260 static int strcmp2(const char *s1, const char *s2)
261 {
262         if (s1 == NULL && s2 == NULL)
263                 return 0;
264         if (s1 == NULL)
265                 return -1;
266         if (s2 == NULL)
267                 return 1;
268         return strcmp(s1, s2);
269 }
270
271 static int __filter(struct cfzy_confnode *n, struct cfzy_list_head *node_list,
272                     int flags)
273 {
274         int add_it = 0;
275         struct cfzy_confnode *c;
276
277         if ((flags & CFZY_FILTER_F_USR_UNSET) && n->user_value == NULL)
278                 add_it = 1;
279         else if ((flags & CFZY_FILTER_F_EFF_UNSET) && n->effective_value == NULL)
280                 add_it = 1;
281         else if ((flags & CFZY_FILTER_F_USR_CHANGED) &&
282                  strcmp2(n->user_value, n->old_user_value))
283                 add_it = 1;
284         else if ((flags & CFZY_FILTER_F_EFF_CHANGED) &&
285                  strcmp2(n->effective_value, n->old_effective_value))
286                 add_it = 1;
287
288         if (add_it == 1) {
289                 if (cfzy_list_add_tail(node_list, n) < 0) {
290                         LOG(ERR, "cannot add node in list\n");
291                         return -1;
292                 }
293         }
294
295         /* children nodes */
296         TAILQ_FOREACH(c, &n->children, child_next) {
297                 if (__filter(c, node_list, flags) < 0)
298                         return -1;
299         }
300
301         return 0;
302 }
303
304 /* Get a list of nodes matching conditions (for instance the nodes
305  * that have no user value) */
306 struct cfzy_list_head *
307 cfzy_conftree_filter(struct cfzy_conftree *conftree, int flags)
308 {
309         struct cfzy_list_head *node_list = NULL;
310
311         node_list = cfzy_list_alloc();
312         if (node_list == NULL) {
313                 LOG(ERR, "cannot allocate node list\n");
314                 return NULL;
315         }
316
317         if (__filter(conftree->root, node_list, flags) < 0) {
318                 LOG(ERR, "cannot filter nodes\n");
319                 cfzy_list_free(node_list, NULL);
320                 return NULL;
321         }
322
323         return node_list;
324 }
325
326 static int __save_values(struct cfzy_confnode *n)
327 {
328         struct cfzy_confnode *c;
329
330         /* duplicate user value */
331         if (n->old_user_value != NULL) {
332                 free(n->old_user_value);
333                 n->old_user_value = NULL;
334         }
335         if (n->user_value != NULL) {
336                 n->old_user_value = strdup(n->user_value);
337                 if (n->old_user_value == NULL) {
338                         LOG(ERR, "cannot duplicate value\n");
339                         return -1;
340                 }
341         }
342
343         /* duplicate effective value */
344         if (n->old_effective_value != NULL) {
345                 free(n->old_effective_value);
346                 n->old_effective_value = NULL;
347         }
348         if (n->effective_value != NULL) {
349                 n->old_effective_value = strdup(n->effective_value);
350                 if (n->old_effective_value == NULL) {
351                         LOG(ERR, "cannot duplicate value\n");
352                         return -1;
353                 }
354         }
355
356         /* do the same on children */
357         TAILQ_FOREACH(c, &n->children, child_next) {
358                 if (__save_values(c) < 0)
359                         return -1;
360         }
361
362         return 0;
363 }
364
365 int cfzy_conftree_save_values(struct cfzy_conftree *conftree)
366 {
367         return __save_values(conftree->root);
368 }