remove prototypes for static functions and fix some potential memory leaks
[asterisk/asterisk.git] / build_tools / menuselect.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2005 - 2006, Russell Bryant
5  *
6  * Russell Bryant <russell@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*!
20  * \file
21  *
22  * \author Russell Bryant <russell@digium.com>
23  * 
24  * \brief A menu-driven system for Asterisk module selection
25  */
26
27 #include "asterisk.h"
28
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <unistd.h>
33
34 #include "mxml/mxml.h"
35 #include "menuselect.h"
36
37 #include "asterisk/linkedlists.h"
38
39 #undef MENUSELECT_DEBUG
40
41 /*! The list of categories */
42 struct categories categories = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
43
44 /*!
45    We have to maintain a pointer to the root of the trees generated from reading
46    the build options XML files so that we can free it when we're done.  We don't
47    copy any of the information over from these trees. Our list is just a 
48    convenient mapping to the information contained in these lists with one
49    additional piece of information - whether the build option is enabled or not.
50 */
51 struct tree {
52         /*! the root of the tree */
53         mxml_node_t *root;
54         /*! for linking */
55         AST_LIST_ENTRY(tree) list;
56 };
57
58 /*! The list of trees from makeopts.xml files */
59 static AST_LIST_HEAD_NOLOCK_STATIC(trees, tree);
60
61 static const char * const makeopts_files[] = {
62         "makeopts.xml"
63 };
64
65 static char *output_makeopts = OUTPUT_MAKEOPTS_DEFAULT;
66
67 /*! This is set to 1 if menuselect.makeopts pre-existed the execution of this app */
68 static int existing_config = 0;
69
70 /*! This is set when the --check-deps argument is provided. */
71 static int check_deps = 0;
72
73 /*! Force a clean of the source tree */
74 static int force_clean = 0;
75
76 /*! \brief return a pointer to the first non-whitespace character */
77 static inline char *skip_blanks(char *str)
78 {
79         if (!str)
80                 return NULL;
81
82         while (*str && *str < 33)
83                 str++;
84
85         return str;
86 }
87
88 /*! \brief Add a category to the category list, ensuring that there are no duplicates */
89 static int add_category(struct category *cat)
90 {
91         struct category *tmp;
92
93         AST_LIST_TRAVERSE(&categories, tmp, list) {
94                 if (!strcmp(tmp->name, cat->name)) {
95                         fprintf(stderr, "Category '%s' specified more than once!\n", cat->name);
96                         return -1;
97                 }
98         }
99         AST_LIST_INSERT_TAIL(&categories, cat, list);
100
101         return 0;
102 }
103
104 /*! \brief Add a member to the member list of a category, ensuring that there are no duplicates */
105 static int add_member(struct member *mem, struct category *cat)
106 {
107         struct member *tmp;
108
109         AST_LIST_TRAVERSE(&cat->members, tmp, list) {
110                 if (!strcmp(tmp->name, mem->name)) {
111                         fprintf(stderr, "Member '%s' already exists in category '%s', ignoring.\n", mem->name, cat->name);
112                         return -1;
113                 }
114         }
115         AST_LIST_INSERT_TAIL(&cat->members, mem, list);
116
117         return 0;
118 }
119
120 /*! \brief Free a member structure and all of its members */
121 static void free_member(struct member *mem)
122 {
123         struct depend *dep;
124         struct conflict *cnf;
125
126         while ((dep = AST_LIST_REMOVE_HEAD(&mem->deps, list)))
127                 free(dep);
128         while ((cnf = AST_LIST_REMOVE_HEAD(&mem->conflicts, list)))
129                 free(cnf);
130         free(mem);
131 }
132
133 /*! \brief Parse an input makeopts file */
134 static int parse_makeopts_xml(const char *makeopts_xml)
135 {
136         FILE *f;
137         struct category *cat;
138         struct tree *tree;
139         struct member *mem;
140         struct depend *dep;
141         struct conflict *cnf;
142         mxml_node_t *cur;
143         mxml_node_t *cur2;
144         mxml_node_t *cur3;
145         mxml_node_t *menu;
146         const char *tmp;
147
148         if (!(f = fopen(makeopts_xml, "r"))) {
149                 fprintf(stderr, "Unable to open '%s' for reading!\n", makeopts_xml);
150                 return -1;
151         }
152
153         if (!(tree = calloc(1, sizeof(*tree)))) {
154                 fclose(f);
155                 return -1;
156         }
157
158         if (!(tree->root = mxmlLoadFile(NULL, f, MXML_OPAQUE_CALLBACK))) {
159                 fclose(f);
160                 free(tree);
161                 return -1;
162         }
163
164         AST_LIST_INSERT_HEAD(&trees, tree, list);
165
166         menu = mxmlFindElement(tree->root, tree->root, "menu", NULL, NULL, MXML_DESCEND);
167         for (cur = mxmlFindElement(menu, menu, "category", NULL, NULL, MXML_DESCEND);
168              cur;
169              cur = mxmlFindElement(cur, menu, "category", NULL, NULL, MXML_DESCEND))
170         {
171                 if (!(cat = calloc(1, sizeof(*cat))))
172                         return -1;
173
174                 cat->name = mxmlElementGetAttr(cur, "name");
175                 cat->displayname = mxmlElementGetAttr(cur, "displayname");
176                 if ((tmp = mxmlElementGetAttr(cur, "positive_output")))
177                         cat->positive_output = !strcasecmp(tmp, "yes");
178                 if ((tmp = mxmlElementGetAttr(cur, "force_clean_on_change")))
179                         cat->force_clean_on_change = !strcasecmp(tmp, "yes");
180
181                 if (add_category(cat)) {
182                         free(cat);
183                         continue;
184                 }
185
186                 for (cur2 = mxmlFindElement(cur, cur, "member", NULL, NULL, MXML_DESCEND);
187                      cur2;
188                      cur2 = mxmlFindElement(cur2, cur, "member", NULL, NULL, MXML_DESCEND))
189                 {
190                         if (!(mem = calloc(1, sizeof(*mem))))
191                                 return -1;
192                         
193                         mem->name = mxmlElementGetAttr(cur2, "name");
194                         mem->displayname = mxmlElementGetAttr(cur2, "displayname");
195                 
196                         if (!cat->positive_output)
197                                 mem->enabled = 1;
198         
199                         cur3 = mxmlFindElement(cur2, cur2, "defaultenabled", NULL, NULL, MXML_DESCEND);
200                         if (cur3 && cur3->child)
201                                 mem->defaultenabled = cur3->child->value.opaque;
202                         
203                         for (cur3 = mxmlFindElement(cur2, cur2, "depend", NULL, NULL, MXML_DESCEND);
204                              cur3 && cur3->child;
205                              cur3 = mxmlFindElement(cur3, cur2, "depend", NULL, NULL, MXML_DESCEND))
206                         {
207                                 if (!(dep = calloc(1, sizeof(*dep)))) {
208                                         free_member(mem);
209                                         return -1;
210                                 }
211                                 if (!strlen_zero(cur3->child->value.opaque)) {
212                                         dep->name = cur3->child->value.opaque;
213                                         AST_LIST_INSERT_HEAD(&mem->deps, dep, list);
214                                 } else
215                                         free(dep);
216                         }
217
218                         for (cur3 = mxmlFindElement(cur2, cur2, "conflict", NULL, NULL, MXML_DESCEND);
219                              cur3 && cur3->child;
220                              cur3 = mxmlFindElement(cur3, cur2, "conflict", NULL, NULL, MXML_DESCEND))
221                         {
222                                 if (!(cnf = calloc(1, sizeof(*cnf)))) {
223                                         free_member(mem);
224                                         return -1;
225                                 }
226                                 if (!strlen_zero(cur3->child->value.opaque)) {
227                                         cnf->name = cur3->child->value.opaque;
228                                         AST_LIST_INSERT_HEAD(&mem->conflicts, cnf, list);
229                                 } else
230                                         free(cnf);
231                         }
232
233                         if (add_member(mem, cat))
234                                 free_member(mem);
235                 }
236         }
237
238         fclose(f);
239
240         return 0;
241 }
242
243 /*! \brief Process dependencies against the input dependencies file */
244 static int process_deps(void)
245 {
246         struct category *cat;
247         struct member *mem;
248         struct depend *dep;
249         struct conflict *cnf;
250         FILE *f;
251         struct dep_file {
252                 char name[32];
253                 int met;
254                 AST_LIST_ENTRY(dep_file) list;
255         } *dep_file;
256         AST_LIST_HEAD_NOLOCK_STATIC(deps_file, dep_file);
257         char buf[80];
258         char *p;
259         int res = 0;
260
261         if (!(f = fopen(MENUSELECT_DEPS, "r"))) {
262                 fprintf(stderr, "Unable to open '%s' for reading!  Did you run ./configure ?\n", MENUSELECT_DEPS);
263                 return -1;
264         }
265
266         /* Build a dependency list from the file generated by configure */      
267         while (memset(buf, 0, sizeof(buf)), fgets(buf, sizeof(buf), f)) {
268                 p = buf;
269                 strsep(&p, "=");
270                 if (!p)
271                         continue;
272                 if (!(dep_file = calloc(1, sizeof(*dep_file))))
273                         break;
274                 strncpy(dep_file->name, buf, sizeof(dep_file->name) - 1);
275                 dep_file->met = atoi(p);
276                 AST_LIST_INSERT_TAIL(&deps_file, dep_file, list);
277         }
278
279         fclose(f);
280
281         /* Process dependencies of all modules */
282         AST_LIST_TRAVERSE(&categories, cat, list) {
283                 AST_LIST_TRAVERSE(&cat->members, mem, list) {
284                         AST_LIST_TRAVERSE(&mem->deps, dep, list) {
285                                 mem->depsfailed = 1;
286                                 AST_LIST_TRAVERSE(&deps_file, dep_file, list) {
287                                         if (!strcasecmp(dep_file->name, dep->name)) {
288                                                 if (dep_file->met)
289                                                         mem->depsfailed = 0;
290                                                 break;
291                                         }
292                                 }
293                                 if (mem->depsfailed)
294                                         break; /* This dependency is not met, so we can stop now */
295                         }
296                 }
297         }
298
299         /* Process conflicts of all modules */
300         AST_LIST_TRAVERSE(&categories, cat, list) {
301                 AST_LIST_TRAVERSE(&cat->members, mem, list) {
302                         AST_LIST_TRAVERSE(&mem->conflicts, cnf, list) {
303                                 mem->conflictsfailed = 0;
304                                 AST_LIST_TRAVERSE(&deps_file, dep_file, list) {
305                                         if (!strcasecmp(dep_file->name, cnf->name)) {
306                                                 if (dep_file->met)
307                                                         mem->conflictsfailed = 1;
308                                                 break;
309                                         }
310                                 }
311                                 if (mem->conflictsfailed)
312                                         break; /* This conflict was found, so we can stop now */
313                         }
314                 }
315         }
316
317         /* Free the dependency list we built from the file */
318         while ((dep_file = AST_LIST_REMOVE_HEAD(&deps_file, list)))
319                 free(dep_file);
320
321         return res;
322 }
323
324 /*! \brief Iterate through all of the input makeopts files and call the parse function on them */
325 static int build_member_list(void)
326 {
327         int i;
328         int res = -1;
329
330         for (i = 0; i < (sizeof(makeopts_files) / sizeof(makeopts_files[0])); i++) {
331                 if ((res = parse_makeopts_xml(makeopts_files[i]))) {
332                         fprintf(stderr, "Error parsing '%s'!\n", makeopts_files[i]);
333                         break;
334                 }
335         }
336
337         return res;
338 }
339
340 /*! \brief Given the string representation of a member and category, mark it as present in a given input file */
341 static void mark_as_present(const char *member, const char *category)
342 {
343         struct category *cat;
344         struct member *mem;
345
346         AST_LIST_TRAVERSE(&categories, cat, list) {
347                 if (strcmp(category, cat->name))
348                         continue;
349                 AST_LIST_TRAVERSE(&cat->members, mem, list) {
350                         if (!strcmp(member, mem->name)) {
351                                 mem->enabled = cat->positive_output;
352                                 break;
353                         }
354                 }
355                 if (!mem)
356                         fprintf(stderr, "member '%s' in category '%s' not found, ignoring.\n", member, category);
357                 break;
358         }
359
360         if (!cat)
361                 fprintf(stderr, "category '%s' not found! Can't mark '%s' as disabled.\n", category, member);
362 }
363
364 /*! \brief Toggle a member of a category at the specified index to enabled/disabled */
365 void toggle_enabled(struct category *cat, int index)
366 {
367         struct member *mem;
368         int i = 0;
369
370         AST_LIST_TRAVERSE(&cat->members, mem, list) {
371                 if (i++ == index)
372                         break;
373         }
374
375         if (mem && !(mem->depsfailed || mem->conflictsfailed)) {
376                 mem->enabled = !mem->enabled;
377                 if (cat->force_clean_on_change)
378                         force_clean = 1;
379         }
380 }
381
382 /*! \brief Process a previously failed dependency
383  *
384  * If a module was previously disabled because of a failed dependency
385  * or a conflict, and not because the user selected it to be that way,
386  * then it needs to be re-enabled by default if the problem is no longer present.
387  */
388 static void process_prev_failed_deps(char *buf)
389 {
390         const char *cat_name, *mem_name;
391         struct category *cat;
392         struct member *mem;
393
394         cat_name = strsep(&buf, "=");
395         mem_name = strsep(&buf, "\n");
396
397         if (!cat_name || !mem_name)
398                 return;
399
400         AST_LIST_TRAVERSE(&categories, cat, list) {
401                 if (strcasecmp(cat->name, cat_name))
402                         continue;
403                 AST_LIST_TRAVERSE(&cat->members, mem, list) {
404                         if (strcasecmp(mem->name, mem_name))
405                                 continue;
406
407                         if (!mem->depsfailed && !mem->conflictsfailed)
408                                 mem->enabled = 1;                       
409         
410                         break;
411                 }
412                 break;  
413         }
414
415         if (!cat || !mem)
416                 fprintf(stderr, "Unable to find '%s' in category '%s'\n", mem_name, cat_name);
417 }
418
419 /*! \brief Parse an existing output makeopts file and enable members previously selected */
420 static int parse_existing_config(const char *infile)
421 {
422         FILE *f;
423         char buf[2048];
424         char *category, *parse, *member;
425         int lineno = 0;
426
427         if (!(f = fopen(infile, "r"))) {
428 #ifdef MENUSELECT_DEBUG
429                 /* This isn't really an error, so only print the message in debug mode */
430                 fprintf(stderr, "Unable to open '%s' for reading existing config.\n", infile);
431 #endif  
432                 return -1;
433         }
434
435         while (fgets(buf, sizeof(buf), f)) {
436                 lineno++;
437
438                 if (strlen_zero(buf))
439                         continue;
440
441                 /* skip lines that are not for this tool */
442                 if (strncasecmp(buf, "MENUSELECT_", strlen("MENUSELECT_")))
443                         continue;
444
445                 parse = buf;
446                 parse = skip_blanks(parse);
447                 if (strlen_zero(parse))
448                         continue;
449
450                 /* Grab the category name */    
451                 category = strsep(&parse, "=");
452                 if (!parse) {
453                         fprintf(stderr, "Invalid string in '%s' at line '%d'!\n", output_makeopts, lineno);
454                         continue;
455                 }
456                 
457                 parse = skip_blanks(parse);
458         
459                 if (!strcasecmp(category, "MENUSELECT_DEPSFAILED")) {
460                         process_prev_failed_deps(parse);
461                         continue;
462                 }
463         
464                 while ((member = strsep(&parse, " \n"))) {
465                         member = skip_blanks(member);
466                         if (strlen_zero(member))
467                                 continue;
468                         mark_as_present(member, category);
469                 }
470         }
471
472         fclose(f);
473
474         return 0;
475 }
476
477 /*! \brief Create the output makeopts file that results from the user's selections */
478 static int generate_makeopts_file(void)
479 {
480         FILE *f;
481         struct category *cat;
482         struct member *mem;
483
484         if (!(f = fopen(output_makeopts, "w"))) {
485                 fprintf(stderr, "Unable to open build configuration file (%s) for writing!\n", output_makeopts);
486                 return -1;
487         }
488
489         /* Traverse all categories and members and output them as var/val pairs */
490         AST_LIST_TRAVERSE(&categories, cat, list) {
491                 fprintf(f, "%s=", cat->name);
492                 AST_LIST_TRAVERSE(&cat->members, mem, list) {
493                         if ((!cat->positive_output && (!mem->enabled || mem->depsfailed || mem->conflictsfailed)) ||
494                             (cat->positive_output && mem->enabled && !mem->depsfailed && !mem->conflictsfailed))
495                                 fprintf(f, "%s ", mem->name);
496                 }
497                 fprintf(f, "\n");
498         }
499
500         /* Output which members were disabled because of failed dependencies or conflicts */
501         AST_LIST_TRAVERSE(&categories, cat, list) {
502                 AST_LIST_TRAVERSE(&cat->members, mem, list) {
503                         if (mem->depsfailed || mem->conflictsfailed)
504                                 fprintf(f, "MENUSELECT_DEPSFAILED=%s=%s\n", cat->name, mem->name);
505                 }
506         }
507
508         fclose(f);
509
510         return 0;
511 }
512
513 #ifdef MENUSELECT_DEBUG
514 /*! \brief Print out all of the information contained in our tree */
515 static void dump_member_list(void)
516 {
517         struct category *cat;
518         struct member *mem;
519         struct depend *dep;
520         struct conflict *cnf;
521
522         AST_LIST_TRAVERSE(&categories, cat, list) {
523                 fprintf(stderr, "Category: '%s'\n", cat->name);
524                 AST_LIST_TRAVERSE(&cat->members, mem, list) {
525                         fprintf(stderr, "   ==>> Member: '%s'  (%s)\n", mem->name, mem->enabled ? "Enabled" : "Disabled");
526                         AST_LIST_TRAVERSE(&mem->deps, dep, list)
527                                 fprintf(stderr, "      --> Depends on: '%s'\n", dep->name);
528                         if (!AST_LIST_EMPTY(&mem->deps))
529                                 fprintf(stderr, "      --> Dependencies Met: %s\n", mem->depsfailed ? "No" : "Yes");    
530                         AST_LIST_TRAVERSE(&mem->conflicts, cnf, list)
531                                 fprintf(stderr, "      --> Conflicts with: '%s'\n", cnf->name);
532                         if (!AST_LIST_EMPTY(&mem->conflicts))
533                                 fprintf(stderr, "      --> Conflicts Found: %s\n", mem->conflictsfailed ? "Yes" : "No");
534                 }
535         }
536 }
537 #endif
538
539 /*! \brief Free all categories and their members */
540 static void free_member_list(void)
541 {
542         struct category *cat;
543         struct member *mem;
544         struct depend *dep;
545         struct conflict *cnf;
546
547         while ((cat = AST_LIST_REMOVE_HEAD(&categories, list))) {
548                 while ((mem = AST_LIST_REMOVE_HEAD(&cat->members, list))) {
549                         while ((dep = AST_LIST_REMOVE_HEAD(&mem->deps, list)))
550                                 free(dep);
551                         while ((cnf = AST_LIST_REMOVE_HEAD(&mem->conflicts, list)))
552                                 free(cnf);
553                         free(mem);
554                 }
555                 free(cat);
556         }
557 }
558
559 /*! \brief Free all of the XML trees */
560 static void free_trees(void)
561 {
562         struct tree *tree;
563
564         while ((tree = AST_LIST_REMOVE_HEAD(&trees, list))) {
565                 mxmlDelete(tree->root);
566                 free(tree);
567         }
568 }
569
570 /*! \brief Enable/Disable all members of a category as long as dependencies have been met and no conflicts are found */
571 void set_all(struct category *cat, int val)
572 {
573         struct member *mem;
574
575         AST_LIST_TRAVERSE(&cat->members, mem, list) {
576                 if (!(mem->depsfailed || mem->conflictsfailed))
577                         mem->enabled = val;
578         }
579 }
580
581 int count_categories(void)
582 {
583         struct category *cat;
584         int count = 0;
585
586         AST_LIST_TRAVERSE(&categories, cat, list)
587                 count++;
588
589         return count;           
590 }
591
592 int count_members(struct category *cat)
593 {
594         struct member *mem;
595         int count = 0;
596
597         AST_LIST_TRAVERSE(&cat->members, mem, list)
598                 count++;
599
600         return count;           
601 }
602
603 /*! \brief Make sure an existing menuselect.makeopts disabled everything it should have */
604 static int sanity_check(void)
605 {
606         struct category *cat;
607         struct member *mem;
608
609         AST_LIST_TRAVERSE(&categories, cat, list) {
610                 AST_LIST_TRAVERSE(&cat->members, mem, list) {
611                         if ((mem->depsfailed || mem->conflictsfailed) && mem->enabled) {
612                                 fprintf(stderr, "\n***********************************************************\n"
613                                                 "  The existing menuselect.makeopts file did not specify    \n"
614                                                 "  that '%s' should not be included.  However, either some  \n"
615                                                 "  dependencies for this module were not found or a         \n"
616                                                 "  conflict exists.                                         \n"
617                                                 "                                                           \n"
618                                                 "  Either run 'make menuselect' or remove the existing      \n"
619                                                 "  menuselect.makeopts file to resolve this issue.          \n"
620                                                 "***********************************************************\n\n", mem->name);
621                                 return -1;
622                         }
623                 }
624         }
625         return 0;       /* all good... */
626 }
627
628 /* \brief Set the forced default values if they exist */
629 static void process_defaults(void)
630 {
631         struct category *cat;
632         struct member *mem;
633
634         AST_LIST_TRAVERSE(&categories, cat, list) {
635                 AST_LIST_TRAVERSE(&cat->members, mem, list) {
636                         if (!mem->defaultenabled)
637                                 continue;
638                         
639                         if (!strcasecmp(mem->defaultenabled, "yes"))
640                                 mem->enabled = 1;
641                         else if (!strcasecmp(mem->defaultenabled, "no"))
642                                 mem->enabled = 0;
643                         else
644                                 fprintf(stderr, "Invalid defaultenabled value for '%s' in category '%s'\n", mem->name, cat->name);      
645                 }
646         }
647
648 }
649
650 int main(int argc, char *argv[])
651 {
652         int res = 0;
653         unsigned int x;
654
655         /* Parse the input XML files to build the list of available options */
656         if ((res = build_member_list()))
657                 exit(res);
658         
659         /* Process module dependencies */
660         res = process_deps();
661         
662         /* The --check-deps option is used to ask this application to check to
663          * see if that an existing menuselect.makeopts file contails all of the
664          * modules that have dependencies that have not been met.  If this
665          * is not the case, an informative message will be printed to the
666          * user and the build will fail. */
667         for (x = 1; x < argc; x++) {
668                 if (!strcmp(argv[x], "--check-deps"))
669                         check_deps = 1;
670                 else {
671                         res = parse_existing_config(argv[x]);
672                         if (!res && !strcasecmp(argv[x], OUTPUT_MAKEOPTS_DEFAULT))
673                                 existing_config = 1;
674                         res = 0;
675                 }
676         }
677
678 #ifdef MENUSELECT_DEBUG
679         /* Dump the list produced by parsing the various input files */
680         dump_member_list();
681 #endif
682
683         if (!existing_config)
684                 process_defaults();
685         else if (check_deps)
686                 res = sanity_check();
687
688         /* Run the menu to let the user enable/disable options */
689         if (!check_deps && !res)
690                 res = run_menu();
691
692         /* Write out the menuselect.makeopts file if
693          * 1) menuselect was not executed with --check-deps
694          * 2) menuselect was executed with --check-deps but menuselect.makeopts
695          *    did not already exist.
696          */
697         if ((!check_deps || !existing_config) && !res)
698                 res = generate_makeopts_file();
699         
700         /* free everything we allocated */
701         free_trees();
702         free_member_list();
703
704         /* In some cases, such as modifying the CFLAGS for the build,
705          * a "make clean" needs to be forced.  Removing the .lastclean 
706          * file does this. */
707         if (force_clean)
708                 unlink(".lastclean");
709
710         exit(res);
711 }