xref: /openbmc/linux/scripts/kconfig/confdata.c (revision 64c70b1c)
1 /*
2  * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
3  * Released under the terms of the GNU GPL v2.0.
4  */
5 
6 #include <sys/stat.h>
7 #include <ctype.h>
8 #include <fcntl.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <time.h>
13 #include <unistd.h>
14 
15 #define LKC_DIRECT_LINK
16 #include "lkc.h"
17 
18 static void conf_warning(const char *fmt, ...)
19 	__attribute__ ((format (printf, 1, 2)));
20 
21 static const char *conf_filename;
22 static int conf_lineno, conf_warnings, conf_unsaved;
23 
24 const char conf_defname[] = "arch/$ARCH/defconfig";
25 
26 static void conf_warning(const char *fmt, ...)
27 {
28 	va_list ap;
29 	va_start(ap, fmt);
30 	fprintf(stderr, "%s:%d:warning: ", conf_filename, conf_lineno);
31 	vfprintf(stderr, fmt, ap);
32 	fprintf(stderr, "\n");
33 	va_end(ap);
34 	conf_warnings++;
35 }
36 
37 const char *conf_get_configname(void)
38 {
39 	char *name = getenv("KCONFIG_CONFIG");
40 
41 	return name ? name : ".config";
42 }
43 
44 static char *conf_expand_value(const char *in)
45 {
46 	struct symbol *sym;
47 	const char *src;
48 	static char res_value[SYMBOL_MAXLENGTH];
49 	char *dst, name[SYMBOL_MAXLENGTH];
50 
51 	res_value[0] = 0;
52 	dst = name;
53 	while ((src = strchr(in, '$'))) {
54 		strncat(res_value, in, src - in);
55 		src++;
56 		dst = name;
57 		while (isalnum(*src) || *src == '_')
58 			*dst++ = *src++;
59 		*dst = 0;
60 		sym = sym_lookup(name, 0);
61 		sym_calc_value(sym);
62 		strcat(res_value, sym_get_string_value(sym));
63 		in = src;
64 	}
65 	strcat(res_value, in);
66 
67 	return res_value;
68 }
69 
70 char *conf_get_default_confname(void)
71 {
72 	struct stat buf;
73 	static char fullname[PATH_MAX+1];
74 	char *env, *name;
75 
76 	name = conf_expand_value(conf_defname);
77 	env = getenv(SRCTREE);
78 	if (env) {
79 		sprintf(fullname, "%s/%s", env, name);
80 		if (!stat(fullname, &buf))
81 			return fullname;
82 	}
83 	return name;
84 }
85 
86 int conf_read_simple(const char *name, int def)
87 {
88 	FILE *in = NULL;
89 	char line[1024];
90 	char *p, *p2;
91 	struct symbol *sym;
92 	int i, def_flags;
93 
94 	if (name) {
95 		in = zconf_fopen(name);
96 	} else {
97 		struct property *prop;
98 
99 		name = conf_get_configname();
100 		in = zconf_fopen(name);
101 		if (in)
102 			goto load;
103 		sym_add_change_count(1);
104 		if (!sym_defconfig_list)
105 			return 1;
106 
107 		for_all_defaults(sym_defconfig_list, prop) {
108 			if (expr_calc_value(prop->visible.expr) == no ||
109 			    prop->expr->type != E_SYMBOL)
110 				continue;
111 			name = conf_expand_value(prop->expr->left.sym->name);
112 			in = zconf_fopen(name);
113 			if (in) {
114 				printf(_("#\n"
115 					 "# using defaults found in %s\n"
116 					 "#\n"), name);
117 				goto load;
118 			}
119 		}
120 	}
121 	if (!in)
122 		return 1;
123 
124 load:
125 	conf_filename = name;
126 	conf_lineno = 0;
127 	conf_warnings = 0;
128 	conf_unsaved = 0;
129 
130 	def_flags = SYMBOL_DEF << def;
131 	for_all_symbols(i, sym) {
132 		sym->flags |= SYMBOL_CHANGED;
133 		sym->flags &= ~(def_flags|SYMBOL_VALID);
134 		if (sym_is_choice(sym))
135 			sym->flags |= def_flags;
136 		switch (sym->type) {
137 		case S_INT:
138 		case S_HEX:
139 		case S_STRING:
140 			if (sym->def[def].val)
141 				free(sym->def[def].val);
142 		default:
143 			sym->def[def].val = NULL;
144 			sym->def[def].tri = no;
145 		}
146 	}
147 
148 	while (fgets(line, sizeof(line), in)) {
149 		conf_lineno++;
150 		sym = NULL;
151 		switch (line[0]) {
152 		case '#':
153 			if (memcmp(line + 2, "CONFIG_", 7))
154 				continue;
155 			p = strchr(line + 9, ' ');
156 			if (!p)
157 				continue;
158 			*p++ = 0;
159 			if (strncmp(p, "is not set", 10))
160 				continue;
161 			if (def == S_DEF_USER) {
162 				sym = sym_find(line + 9);
163 				if (!sym) {
164 					conf_warning("trying to assign nonexistent symbol %s", line + 9);
165 					break;
166 				}
167 			} else {
168 				sym = sym_lookup(line + 9, 0);
169 				if (sym->type == S_UNKNOWN)
170 					sym->type = S_BOOLEAN;
171 			}
172 			if (sym->flags & def_flags) {
173 				conf_warning("trying to reassign symbol %s", sym->name);
174 				break;
175 			}
176 			switch (sym->type) {
177 			case S_BOOLEAN:
178 			case S_TRISTATE:
179 				sym->def[def].tri = no;
180 				sym->flags |= def_flags;
181 				break;
182 			default:
183 				;
184 			}
185 			break;
186 		case 'C':
187 			if (memcmp(line, "CONFIG_", 7)) {
188 				conf_warning("unexpected data");
189 				continue;
190 			}
191 			p = strchr(line + 7, '=');
192 			if (!p)
193 				continue;
194 			*p++ = 0;
195 			p2 = strchr(p, '\n');
196 			if (p2) {
197 				*p2-- = 0;
198 				if (*p2 == '\r')
199 					*p2 = 0;
200 			}
201 			if (def == S_DEF_USER) {
202 				sym = sym_find(line + 7);
203 				if (!sym) {
204 					conf_warning("trying to assign nonexistent symbol %s", line + 7);
205 					break;
206 				}
207 			} else {
208 				sym = sym_lookup(line + 7, 0);
209 				if (sym->type == S_UNKNOWN)
210 					sym->type = S_OTHER;
211 			}
212 			if (sym->flags & def_flags) {
213 				conf_warning("trying to reassign symbol %s", sym->name);
214 				break;
215 			}
216 			switch (sym->type) {
217 			case S_TRISTATE:
218 				if (p[0] == 'm') {
219 					sym->def[def].tri = mod;
220 					sym->flags |= def_flags;
221 					break;
222 				}
223 			case S_BOOLEAN:
224 				if (p[0] == 'y') {
225 					sym->def[def].tri = yes;
226 					sym->flags |= def_flags;
227 					break;
228 				}
229 				if (p[0] == 'n') {
230 					sym->def[def].tri = no;
231 					sym->flags |= def_flags;
232 					break;
233 				}
234 				conf_warning("symbol value '%s' invalid for %s", p, sym->name);
235 				break;
236 			case S_OTHER:
237 				if (*p != '"') {
238 					for (p2 = p; *p2 && !isspace(*p2); p2++)
239 						;
240 					sym->type = S_STRING;
241 					goto done;
242 				}
243 			case S_STRING:
244 				if (*p++ != '"')
245 					break;
246 				for (p2 = p; (p2 = strpbrk(p2, "\"\\")); p2++) {
247 					if (*p2 == '"') {
248 						*p2 = 0;
249 						break;
250 					}
251 					memmove(p2, p2 + 1, strlen(p2));
252 				}
253 				if (!p2) {
254 					conf_warning("invalid string found");
255 					continue;
256 				}
257 			case S_INT:
258 			case S_HEX:
259 			done:
260 				if (sym_string_valid(sym, p)) {
261 					sym->def[def].val = strdup(p);
262 					sym->flags |= def_flags;
263 				} else {
264 					conf_warning("symbol value '%s' invalid for %s", p, sym->name);
265 					continue;
266 				}
267 				break;
268 			default:
269 				;
270 			}
271 			break;
272 		case '\r':
273 		case '\n':
274 			break;
275 		default:
276 			conf_warning("unexpected data");
277 			continue;
278 		}
279 		if (sym && sym_is_choice_value(sym)) {
280 			struct symbol *cs = prop_get_symbol(sym_get_choice_prop(sym));
281 			switch (sym->def[def].tri) {
282 			case no:
283 				break;
284 			case mod:
285 				if (cs->def[def].tri == yes) {
286 					conf_warning("%s creates inconsistent choice state", sym->name);
287 					cs->flags &= ~def_flags;
288 				}
289 				break;
290 			case yes:
291 				if (cs->def[def].tri != no) {
292 					conf_warning("%s creates inconsistent choice state", sym->name);
293 					cs->flags &= ~def_flags;
294 				} else
295 					cs->def[def].val = sym;
296 				break;
297 			}
298 			cs->def[def].tri = E_OR(cs->def[def].tri, sym->def[def].tri);
299 		}
300 	}
301 	fclose(in);
302 
303 	if (modules_sym)
304 		sym_calc_value(modules_sym);
305 	return 0;
306 }
307 
308 int conf_read(const char *name)
309 {
310 	struct symbol *sym;
311 	struct property *prop;
312 	struct expr *e;
313 	int i, flags;
314 
315 	sym_set_change_count(0);
316 
317 	if (conf_read_simple(name, S_DEF_USER))
318 		return 1;
319 
320 	for_all_symbols(i, sym) {
321 		sym_calc_value(sym);
322 		if (sym_is_choice(sym) || (sym->flags & SYMBOL_AUTO))
323 			goto sym_ok;
324 		if (sym_has_value(sym) && (sym->flags & SYMBOL_WRITE)) {
325 			/* check that calculated value agrees with saved value */
326 			switch (sym->type) {
327 			case S_BOOLEAN:
328 			case S_TRISTATE:
329 				if (sym->def[S_DEF_USER].tri != sym_get_tristate_value(sym))
330 					break;
331 				if (!sym_is_choice(sym))
332 					goto sym_ok;
333 			default:
334 				if (!strcmp(sym->curr.val, sym->def[S_DEF_USER].val))
335 					goto sym_ok;
336 				break;
337 			}
338 		} else if (!sym_has_value(sym) && !(sym->flags & SYMBOL_WRITE))
339 			/* no previous value and not saved */
340 			goto sym_ok;
341 		conf_unsaved++;
342 		/* maybe print value in verbose mode... */
343 	sym_ok:
344 		if (sym_has_value(sym) && !sym_is_choice_value(sym)) {
345 			if (sym->visible == no)
346 				sym->flags &= ~SYMBOL_DEF_USER;
347 			switch (sym->type) {
348 			case S_STRING:
349 			case S_INT:
350 			case S_HEX:
351 				if (!sym_string_within_range(sym, sym->def[S_DEF_USER].val))
352 					sym->flags &= ~(SYMBOL_VALID|SYMBOL_DEF_USER);
353 			default:
354 				break;
355 			}
356 		}
357 		if (!sym_is_choice(sym))
358 			continue;
359 		prop = sym_get_choice_prop(sym);
360 		flags = sym->flags;
361 		for (e = prop->expr; e; e = e->left.expr)
362 			if (e->right.sym->visible != no)
363 				flags &= e->right.sym->flags;
364 		sym->flags &= flags | ~SYMBOL_DEF_USER;
365 	}
366 
367 	sym_add_change_count(conf_warnings || conf_unsaved);
368 
369 	return 0;
370 }
371 
372 int conf_write(const char *name)
373 {
374 	FILE *out;
375 	struct symbol *sym;
376 	struct menu *menu;
377 	const char *basename;
378 	char dirname[128], tmpname[128], newname[128];
379 	int type, l;
380 	const char *str;
381 	time_t now;
382 	int use_timestamp = 1;
383 	char *env;
384 
385 	dirname[0] = 0;
386 	if (name && name[0]) {
387 		struct stat st;
388 		char *slash;
389 
390 		if (!stat(name, &st) && S_ISDIR(st.st_mode)) {
391 			strcpy(dirname, name);
392 			strcat(dirname, "/");
393 			basename = conf_get_configname();
394 		} else if ((slash = strrchr(name, '/'))) {
395 			int size = slash - name + 1;
396 			memcpy(dirname, name, size);
397 			dirname[size] = 0;
398 			if (slash[1])
399 				basename = slash + 1;
400 			else
401 				basename = conf_get_configname();
402 		} else
403 			basename = name;
404 	} else
405 		basename = conf_get_configname();
406 
407 	sprintf(newname, "%s%s", dirname, basename);
408 	env = getenv("KCONFIG_OVERWRITECONFIG");
409 	if (!env || !*env) {
410 		sprintf(tmpname, "%s.tmpconfig.%d", dirname, (int)getpid());
411 		out = fopen(tmpname, "w");
412 	} else {
413 		*tmpname = 0;
414 		out = fopen(newname, "w");
415 	}
416 	if (!out)
417 		return 1;
418 
419 	sym = sym_lookup("KERNELVERSION", 0);
420 	sym_calc_value(sym);
421 	time(&now);
422 	env = getenv("KCONFIG_NOTIMESTAMP");
423 	if (env && *env)
424 		use_timestamp = 0;
425 
426 	fprintf(out, _("#\n"
427 		       "# Automatically generated make config: don't edit\n"
428 		       "# Linux kernel version: %s\n"
429 		       "%s%s"
430 		       "#\n"),
431 		     sym_get_string_value(sym),
432 		     use_timestamp ? "# " : "",
433 		     use_timestamp ? ctime(&now) : "");
434 
435 	if (!conf_get_changed())
436 		sym_clear_all_valid();
437 
438 	menu = rootmenu.list;
439 	while (menu) {
440 		sym = menu->sym;
441 		if (!sym) {
442 			if (!menu_is_visible(menu))
443 				goto next;
444 			str = menu_get_prompt(menu);
445 			fprintf(out, "\n"
446 				     "#\n"
447 				     "# %s\n"
448 				     "#\n", str);
449 		} else if (!(sym->flags & SYMBOL_CHOICE)) {
450 			sym_calc_value(sym);
451 			if (!(sym->flags & SYMBOL_WRITE))
452 				goto next;
453 			sym->flags &= ~SYMBOL_WRITE;
454 			type = sym->type;
455 			if (type == S_TRISTATE) {
456 				sym_calc_value(modules_sym);
457 				if (modules_sym->curr.tri == no)
458 					type = S_BOOLEAN;
459 			}
460 			switch (type) {
461 			case S_BOOLEAN:
462 			case S_TRISTATE:
463 				switch (sym_get_tristate_value(sym)) {
464 				case no:
465 					fprintf(out, "# CONFIG_%s is not set\n", sym->name);
466 					break;
467 				case mod:
468 					fprintf(out, "CONFIG_%s=m\n", sym->name);
469 					break;
470 				case yes:
471 					fprintf(out, "CONFIG_%s=y\n", sym->name);
472 					break;
473 				}
474 				break;
475 			case S_STRING:
476 				str = sym_get_string_value(sym);
477 				fprintf(out, "CONFIG_%s=\"", sym->name);
478 				while (1) {
479 					l = strcspn(str, "\"\\");
480 					if (l) {
481 						fwrite(str, l, 1, out);
482 						str += l;
483 					}
484 					if (!*str)
485 						break;
486 					fprintf(out, "\\%c", *str++);
487 				}
488 				fputs("\"\n", out);
489 				break;
490 			case S_HEX:
491 				str = sym_get_string_value(sym);
492 				if (str[0] != '0' || (str[1] != 'x' && str[1] != 'X')) {
493 					fprintf(out, "CONFIG_%s=%s\n", sym->name, str);
494 					break;
495 				}
496 			case S_INT:
497 				str = sym_get_string_value(sym);
498 				fprintf(out, "CONFIG_%s=%s\n", sym->name, str);
499 				break;
500 			}
501 		}
502 
503 	next:
504 		if (menu->list) {
505 			menu = menu->list;
506 			continue;
507 		}
508 		if (menu->next)
509 			menu = menu->next;
510 		else while ((menu = menu->parent)) {
511 			if (menu->next) {
512 				menu = menu->next;
513 				break;
514 			}
515 		}
516 	}
517 	fclose(out);
518 
519 	if (*tmpname) {
520 		strcat(dirname, basename);
521 		strcat(dirname, ".old");
522 		rename(newname, dirname);
523 		if (rename(tmpname, newname))
524 			return 1;
525 	}
526 
527 	printf(_("#\n"
528 		 "# configuration written to %s\n"
529 		 "#\n"), newname);
530 
531 	sym_set_change_count(0);
532 
533 	return 0;
534 }
535 
536 int conf_split_config(void)
537 {
538 	char *name, path[128];
539 	char *s, *d, c;
540 	struct symbol *sym;
541 	struct stat sb;
542 	int res, i, fd;
543 
544 	name = getenv("KCONFIG_AUTOCONFIG");
545 	if (!name)
546 		name = "include/config/auto.conf";
547 	conf_read_simple(name, S_DEF_AUTO);
548 
549 	if (chdir("include/config"))
550 		return 1;
551 
552 	res = 0;
553 	for_all_symbols(i, sym) {
554 		sym_calc_value(sym);
555 		if ((sym->flags & SYMBOL_AUTO) || !sym->name)
556 			continue;
557 		if (sym->flags & SYMBOL_WRITE) {
558 			if (sym->flags & SYMBOL_DEF_AUTO) {
559 				/*
560 				 * symbol has old and new value,
561 				 * so compare them...
562 				 */
563 				switch (sym->type) {
564 				case S_BOOLEAN:
565 				case S_TRISTATE:
566 					if (sym_get_tristate_value(sym) ==
567 					    sym->def[S_DEF_AUTO].tri)
568 						continue;
569 					break;
570 				case S_STRING:
571 				case S_HEX:
572 				case S_INT:
573 					if (!strcmp(sym_get_string_value(sym),
574 						    sym->def[S_DEF_AUTO].val))
575 						continue;
576 					break;
577 				default:
578 					break;
579 				}
580 			} else {
581 				/*
582 				 * If there is no old value, only 'no' (unset)
583 				 * is allowed as new value.
584 				 */
585 				switch (sym->type) {
586 				case S_BOOLEAN:
587 				case S_TRISTATE:
588 					if (sym_get_tristate_value(sym) == no)
589 						continue;
590 					break;
591 				default:
592 					break;
593 				}
594 			}
595 		} else if (!(sym->flags & SYMBOL_DEF_AUTO))
596 			/* There is neither an old nor a new value. */
597 			continue;
598 		/* else
599 		 *	There is an old value, but no new value ('no' (unset)
600 		 *	isn't saved in auto.conf, so the old value is always
601 		 *	different from 'no').
602 		 */
603 
604 		/* Replace all '_' and append ".h" */
605 		s = sym->name;
606 		d = path;
607 		while ((c = *s++)) {
608 			c = tolower(c);
609 			*d++ = (c == '_') ? '/' : c;
610 		}
611 		strcpy(d, ".h");
612 
613 		/* Assume directory path already exists. */
614 		fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
615 		if (fd == -1) {
616 			if (errno != ENOENT) {
617 				res = 1;
618 				break;
619 			}
620 			/*
621 			 * Create directory components,
622 			 * unless they exist already.
623 			 */
624 			d = path;
625 			while ((d = strchr(d, '/'))) {
626 				*d = 0;
627 				if (stat(path, &sb) && mkdir(path, 0755)) {
628 					res = 1;
629 					goto out;
630 				}
631 				*d++ = '/';
632 			}
633 			/* Try it again. */
634 			fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
635 			if (fd == -1) {
636 				res = 1;
637 				break;
638 			}
639 		}
640 		close(fd);
641 	}
642 out:
643 	if (chdir("../.."))
644 		return 1;
645 
646 	return res;
647 }
648 
649 int conf_write_autoconf(void)
650 {
651 	struct symbol *sym;
652 	const char *str;
653 	char *name;
654 	FILE *out, *out_h;
655 	time_t now;
656 	int i, l;
657 
658 	sym_clear_all_valid();
659 
660 	file_write_dep("include/config/auto.conf.cmd");
661 
662 	if (conf_split_config())
663 		return 1;
664 
665 	out = fopen(".tmpconfig", "w");
666 	if (!out)
667 		return 1;
668 
669 	out_h = fopen(".tmpconfig.h", "w");
670 	if (!out_h) {
671 		fclose(out);
672 		return 1;
673 	}
674 
675 	sym = sym_lookup("KERNELVERSION", 0);
676 	sym_calc_value(sym);
677 	time(&now);
678 	fprintf(out, "#\n"
679 		     "# Automatically generated make config: don't edit\n"
680 		     "# Linux kernel version: %s\n"
681 		     "# %s"
682 		     "#\n",
683 		     sym_get_string_value(sym), ctime(&now));
684 	fprintf(out_h, "/*\n"
685 		       " * Automatically generated C config: don't edit\n"
686 		       " * Linux kernel version: %s\n"
687 		       " * %s"
688 		       " */\n"
689 		       "#define AUTOCONF_INCLUDED\n",
690 		       sym_get_string_value(sym), ctime(&now));
691 
692 	for_all_symbols(i, sym) {
693 		sym_calc_value(sym);
694 		if (!(sym->flags & SYMBOL_WRITE) || !sym->name)
695 			continue;
696 		switch (sym->type) {
697 		case S_BOOLEAN:
698 		case S_TRISTATE:
699 			switch (sym_get_tristate_value(sym)) {
700 			case no:
701 				break;
702 			case mod:
703 				fprintf(out, "CONFIG_%s=m\n", sym->name);
704 				fprintf(out_h, "#define CONFIG_%s_MODULE 1\n", sym->name);
705 				break;
706 			case yes:
707 				fprintf(out, "CONFIG_%s=y\n", sym->name);
708 				fprintf(out_h, "#define CONFIG_%s 1\n", sym->name);
709 				break;
710 			}
711 			break;
712 		case S_STRING:
713 			str = sym_get_string_value(sym);
714 			fprintf(out, "CONFIG_%s=\"", sym->name);
715 			fprintf(out_h, "#define CONFIG_%s \"", sym->name);
716 			while (1) {
717 				l = strcspn(str, "\"\\");
718 				if (l) {
719 					fwrite(str, l, 1, out);
720 					fwrite(str, l, 1, out_h);
721 					str += l;
722 				}
723 				if (!*str)
724 					break;
725 				fprintf(out, "\\%c", *str);
726 				fprintf(out_h, "\\%c", *str);
727 				str++;
728 			}
729 			fputs("\"\n", out);
730 			fputs("\"\n", out_h);
731 			break;
732 		case S_HEX:
733 			str = sym_get_string_value(sym);
734 			if (str[0] != '0' || (str[1] != 'x' && str[1] != 'X')) {
735 				fprintf(out, "CONFIG_%s=%s\n", sym->name, str);
736 				fprintf(out_h, "#define CONFIG_%s 0x%s\n", sym->name, str);
737 				break;
738 			}
739 		case S_INT:
740 			str = sym_get_string_value(sym);
741 			fprintf(out, "CONFIG_%s=%s\n", sym->name, str);
742 			fprintf(out_h, "#define CONFIG_%s %s\n", sym->name, str);
743 			break;
744 		default:
745 			break;
746 		}
747 	}
748 	fclose(out);
749 	fclose(out_h);
750 
751 	name = getenv("KCONFIG_AUTOHEADER");
752 	if (!name)
753 		name = "include/linux/autoconf.h";
754 	if (rename(".tmpconfig.h", name))
755 		return 1;
756 	name = getenv("KCONFIG_AUTOCONFIG");
757 	if (!name)
758 		name = "include/config/auto.conf";
759 	/*
760 	 * This must be the last step, kbuild has a dependency on auto.conf
761 	 * and this marks the successful completion of the previous steps.
762 	 */
763 	if (rename(".tmpconfig", name))
764 		return 1;
765 
766 	return 0;
767 }
768 
769 static int sym_change_count;
770 static void (*conf_changed_callback)(void);
771 
772 void sym_set_change_count(int count)
773 {
774 	int _sym_change_count = sym_change_count;
775 	sym_change_count = count;
776 	if (conf_changed_callback &&
777 	    (bool)_sym_change_count != (bool)count)
778 		conf_changed_callback();
779 }
780 
781 void sym_add_change_count(int count)
782 {
783 	sym_set_change_count(count + sym_change_count);
784 }
785 
786 bool conf_get_changed(void)
787 {
788 	return sym_change_count;
789 }
790 
791 void conf_set_changed_callback(void (*fn)(void))
792 {
793 	conf_changed_callback = fn;
794 }
795