root/scripts/kconfig/conf.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
 */

#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/time.h>
#include <errno.h>

#include "internal.h"
#include "lkc.h"

static void conf(struct menu *menu);
static void check_conf(struct menu *menu);

enum input_mode {
        oldaskconfig,
        syncconfig,
        oldconfig,
        allnoconfig,
        allyesconfig,
        allmodconfig,
        alldefconfig,
        randconfig,
        defconfig,
        savedefconfig,
        listnewconfig,
        helpnewconfig,
        olddefconfig,
        yes2modconfig,
        mod2yesconfig,
        mod2noconfig,
};
static enum input_mode input_mode = oldaskconfig;
static int input_mode_opt;
static int indent = 1;
static int tty_stdio;
static int sync_kconfig;
static int conf_cnt;
static char line[PATH_MAX];
static struct menu *rootEntry;

static void print_help(struct menu *menu)
{
        struct gstr help = str_new();

        menu_get_ext_help(menu, &help);

        printf("\n%s\n", str_get(&help));
        str_free(&help);
}

static void strip(char *str)
{
        char *p = str;
        int l;

        while ((isspace(*p)))
                p++;
        l = strlen(p);
        if (p != str)
                memmove(str, p, l + 1);
        if (!l)
                return;
        p = str + l - 1;
        while ((isspace(*p)))
                *p-- = 0;
}

/* Helper function to facilitate fgets() by Jean Sacren. */
static void xfgets(char *str, int size, FILE *in)
{
        if (!fgets(str, size, in))
                fprintf(stderr, "\nError in reading or end of file.\n");

        if (!tty_stdio)
                printf("%s", str);
}

static void set_randconfig_seed(void)
{
        unsigned int seed;
        char *env;
        bool seed_set = false;

        env = getenv("KCONFIG_SEED");
        if (env && *env) {
                char *endp;

                seed = strtol(env, &endp, 0);
                if (*endp == '\0')
                        seed_set = true;
        }

        if (!seed_set) {
                struct timeval now;

                /*
                 * Use microseconds derived seed, compensate for systems where it may
                 * be zero.
                 */
                gettimeofday(&now, NULL);
                seed = (now.tv_sec + 1) * (now.tv_usec + 1);
        }

        printf("KCONFIG_SEED=0x%X\n", seed);
        srand(seed);
}

/**
 * randomize_choice_values - randomize choice block
 *
 * @choice: menu entry for the choice
 */
static void randomize_choice_values(struct menu *choice)
{
        struct menu *menu;
        int x;
        int cnt = 0;

        /*
         * First, count the number of symbols to randomize. If sym_has_value()
         * is true, it was specified by KCONFIG_ALLCONFIG. It needs to be
         * respected.
         */
        menu_for_each_sub_entry(menu, choice) {
                struct symbol *sym = menu->sym;

                if (sym && !sym_has_value(sym))
                        cnt++;
        }

        while (cnt > 0) {
                x = rand() % cnt;

                menu_for_each_sub_entry(menu, choice) {
                        struct symbol *sym = menu->sym;

                        if (sym && !sym_has_value(sym))
                                x--;

                        if (x < 0) {
                                sym->def[S_DEF_USER].tri = yes;
                                sym->flags |= SYMBOL_DEF_USER;
                                /*
                                 * Move the selected item to the _tail_ because
                                 * this needs to have a lower priority than the
                                 * user input from KCONFIG_ALLCONFIG.
                                 */
                                list_move_tail(&sym->choice_link,
                                               &choice->choice_members);

                                break;
                        }
                }
                cnt--;
        }
}

enum conf_def_mode {
        def_default,
        def_yes,
        def_mod,
        def_no,
        def_random
};

static void conf_set_all_new_symbols(enum conf_def_mode mode)
{
        struct menu *menu;
        int cnt;
        /*
         * can't go as the default in switch-case below, otherwise gcc whines
         * about -Wmaybe-uninitialized
         */
        int pby = 50; /* probability of bool     = y */
        int pty = 33; /* probability of tristate = y */
        int ptm = 33; /* probability of tristate = m */

        if (mode == def_random) {
                int n, p[3];
                char *env = getenv("KCONFIG_PROBABILITY");

                n = 0;
                while (env && *env) {
                        char *endp;
                        int tmp = strtol(env, &endp, 10);

                        if (tmp >= 0 && tmp <= 100) {
                                p[n++] = tmp;
                        } else {
                                errno = ERANGE;
                                perror("KCONFIG_PROBABILITY");
                                exit(1);
                        }
                        env = (*endp == ':') ? endp + 1 : endp;
                        if (n >= 3)
                                break;
                }
                switch (n) {
                case 1:
                        pby = p[0];
                        ptm = pby / 2;
                        pty = pby - ptm;
                        break;
                case 2:
                        pty = p[0];
                        ptm = p[1];
                        pby = pty + ptm;
                        break;
                case 3:
                        pby = p[0];
                        pty = p[1];
                        ptm = p[2];
                        break;
                }

                if (pty + ptm > 100) {
                        errno = ERANGE;
                        perror("KCONFIG_PROBABILITY");
                        exit(1);
                }
        }

        menu_for_each_entry(menu) {
                struct symbol *sym = menu->sym;
                tristate val;

                if (!sym || !menu->prompt || sym_has_value(sym) ||
                    (sym->type != S_BOOLEAN && sym->type != S_TRISTATE) ||
                    sym_is_choice_value(sym))
                        continue;

                if (sym_is_choice(sym)) {
                        if (mode == def_random)
                                randomize_choice_values(menu);
                        continue;
                }

                switch (mode) {
                case def_yes:
                        val = yes;
                        break;
                case def_mod:
                        val = mod;
                        break;
                case def_no:
                        val = no;
                        break;
                case def_random:
                        val = no;
                        cnt = rand() % 100;
                        if (sym->type == S_TRISTATE) {
                                if (cnt < pty)
                                        val = yes;
                                else if (cnt < pty + ptm)
                                        val = mod;
                        } else if (cnt < pby) {
                                val = yes;
                        }
                        break;
                default:
                        continue;
                }
                sym->def[S_DEF_USER].tri = val;
                sym->flags |= SYMBOL_DEF_USER;
        }

        sym_clear_all_valid();
}

static void conf_rewrite_tristates(tristate old_val, tristate new_val)
{
        struct symbol *sym;

        for_all_symbols(sym) {
                if (sym_get_type(sym) == S_TRISTATE &&
                    sym->def[S_DEF_USER].tri == old_val)
                        sym->def[S_DEF_USER].tri = new_val;
        }
        sym_clear_all_valid();
}

static int conf_askvalue(struct symbol *sym, const char *def)
{
        if (!sym_has_value(sym))
                printf("(NEW) ");

        line[0] = '\n';
        line[1] = 0;

        if (!sym_is_changeable(sym)) {
                printf("%s\n", def);
                line[0] = '\n';
                line[1] = 0;
                return 0;
        }

        switch (input_mode) {
        case oldconfig:
        case syncconfig:
                if (sym_has_value(sym)) {
                        printf("%s\n", def);
                        return 0;
                }
                /* fall through */
        default:
                fflush(stdout);
                xfgets(line, sizeof(line), stdin);
                break;
        }

        return 1;
}

static int conf_string(struct menu *menu)
{
        struct symbol *sym = menu->sym;
        const char *def;

        while (1) {
                printf("%*s%s ", indent - 1, "", menu->prompt->text);
                printf("(%s) ", sym->name);
                def = sym_get_string_value(sym);
                if (def)
                        printf("[%s] ", def);
                if (!conf_askvalue(sym, def))
                        return 0;
                switch (line[0]) {
                case '\n':
                        break;
                case '?':
                        /* print help */
                        if (line[1] == '\n') {
                                print_help(menu);
                                def = NULL;
                                break;
                        }
                        /* fall through */
                default:
                        line[strlen(line)-1] = 0;
                        def = line;
                }
                if (def && sym_set_string_value(sym, def))
                        return 0;
        }
}

static int conf_sym(struct menu *menu)
{
        struct symbol *sym = menu->sym;
        tristate oldval, newval;

        while (1) {
                printf("%*s%s ", indent - 1, "", menu->prompt->text);
                if (sym->name)
                        printf("(%s) ", sym->name);
                putchar('[');
                oldval = sym_get_tristate_value(sym);
                switch (oldval) {
                case no:
                        putchar('N');
                        break;
                case mod:
                        putchar('M');
                        break;
                case yes:
                        putchar('Y');
                        break;
                }
                if (oldval != no && sym_tristate_within_range(sym, no))
                        printf("/n");
                if (oldval != mod && sym_tristate_within_range(sym, mod))
                        printf("/m");
                if (oldval != yes && sym_tristate_within_range(sym, yes))
                        printf("/y");
                printf("/?] ");
                if (!conf_askvalue(sym, sym_get_string_value(sym)))
                        return 0;
                strip(line);

                switch (line[0]) {
                case 'n':
                case 'N':
                        newval = no;
                        if (!line[1] || !strcmp(&line[1], "o"))
                                break;
                        continue;
                case 'm':
                case 'M':
                        newval = mod;
                        if (!line[1])
                                break;
                        continue;
                case 'y':
                case 'Y':
                        newval = yes;
                        if (!line[1] || !strcmp(&line[1], "es"))
                                break;
                        continue;
                case 0:
                        newval = oldval;
                        break;
                case '?':
                        goto help;
                default:
                        continue;
                }
                if (sym_set_tristate_value(sym, newval))
                        return 0;
help:
                print_help(menu);
        }
}

static void conf_choice(struct menu *menu)
{
        struct symbol *def_sym;
        struct menu *child;
        bool is_new = false;

        while (1) {
                int cnt, def;

                printf("%*s%s\n", indent - 1, "", menu_get_prompt(menu));
                def_sym = sym_calc_choice(menu);
                cnt = def = 0;
                line[0] = 0;
                for (child = menu->list; child; child = child->next) {
                        if (!menu_is_visible(child))
                                continue;
                        if (!child->sym) {
                                printf("%*c %s\n", indent, '*', menu_get_prompt(child));
                                continue;
                        }
                        cnt++;
                        if (child->sym == def_sym) {
                                def = cnt;
                                printf("%*c", indent, '>');
                        } else
                                printf("%*c", indent, ' ');
                        printf(" %d. %s (%s)", cnt, menu_get_prompt(child),
                               child->sym->name);
                        if (!sym_has_value(child->sym)) {
                                is_new = true;
                                printf(" (NEW)");
                        }
                        printf("\n");
                }
                printf("%*schoice", indent - 1, "");
                if (cnt == 1) {
                        printf("[1]: 1\n");
                        goto conf_childs;
                }
                printf("[1-%d?]: ", cnt);
                switch (input_mode) {
                case oldconfig:
                case syncconfig:
                        if (!is_new) {
                                cnt = def;
                                printf("%d\n", cnt);
                                break;
                        }
                        /* fall through */
                case oldaskconfig:
                        fflush(stdout);
                        xfgets(line, sizeof(line), stdin);
                        strip(line);
                        if (line[0] == '?') {
                                print_help(menu);
                                continue;
                        }
                        if (!line[0])
                                cnt = def;
                        else if (isdigit(line[0]))
                                cnt = atoi(line);
                        else
                                continue;
                        break;
                default:
                        break;
                }

        conf_childs:
                for (child = menu->list; child; child = child->next) {
                        if (!child->sym || !menu_is_visible(child))
                                continue;
                        if (!--cnt)
                                break;
                }
                if (!child)
                        continue;
                if (line[0] && line[strlen(line) - 1] == '?') {
                        print_help(child);
                        continue;
                }
                choice_set_value(menu, child->sym);
                return;
        }
}

static void conf(struct menu *menu)
{
        struct symbol *sym;
        struct property *prop;
        struct menu *child;

        if (!menu_is_visible(menu))
                return;

        sym = menu->sym;
        prop = menu->prompt;
        if (prop) {
                const char *prompt;

                switch (prop->type) {
                case P_MENU:
                        /*
                         * Except in oldaskconfig mode, we show only menus that
                         * contain new symbols.
                         */
                        if (input_mode != oldaskconfig && rootEntry != menu) {
                                check_conf(menu);
                                return;
                        }
                        /* fall through */
                case P_COMMENT:
                        prompt = menu_get_prompt(menu);
                        if (prompt)
                                printf("%*c\n%*c %s\n%*c\n",
                                        indent, '*',
                                        indent, '*', prompt,
                                        indent, '*');
                default:
                        ;
                }
        }

        if (!sym)
                goto conf_childs;

        if (sym_is_choice(sym)) {
                conf_choice(menu);
                return;
        }

        switch (sym->type) {
        case S_INT:
        case S_HEX:
        case S_STRING:
                conf_string(menu);
                break;
        default:
                conf_sym(menu);
                break;
        }

conf_childs:
        if (sym)
                indent += 2;
        for (child = menu->list; child; child = child->next)
                conf(child);
        if (sym)
                indent -= 2;
}

static void check_conf(struct menu *menu)
{
        struct symbol *sym;
        struct menu *child;

        if (!menu_is_visible(menu))
                return;

        sym = menu->sym;
        if (sym && !sym_has_value(sym) && sym_is_changeable(sym)) {
                switch (input_mode) {
                case listnewconfig:
                        if (sym->name)
                                print_symbol_for_listconfig(sym);
                        break;
                case helpnewconfig:
                        printf("-----\n");
                        print_help(menu);
                        printf("-----\n");
                        break;
                default:
                        if (!conf_cnt++)
                                printf("*\n* Restart config...\n*\n");
                        rootEntry = menu_get_menu_or_parent_menu(menu);
                        conf(rootEntry);
                        break;
                }
        }

        for (child = menu->list; child; child = child->next)
                check_conf(child);
}

static const struct option long_opts[] = {
        {"help",          no_argument,       NULL,            'h'},
        {"silent",        no_argument,       NULL,            's'},
        {"oldaskconfig",  no_argument,       &input_mode_opt, oldaskconfig},
        {"oldconfig",     no_argument,       &input_mode_opt, oldconfig},
        {"syncconfig",    no_argument,       &input_mode_opt, syncconfig},
        {"defconfig",     required_argument, &input_mode_opt, defconfig},
        {"savedefconfig", required_argument, &input_mode_opt, savedefconfig},
        {"allnoconfig",   no_argument,       &input_mode_opt, allnoconfig},
        {"allyesconfig",  no_argument,       &input_mode_opt, allyesconfig},
        {"allmodconfig",  no_argument,       &input_mode_opt, allmodconfig},
        {"alldefconfig",  no_argument,       &input_mode_opt, alldefconfig},
        {"randconfig",    no_argument,       &input_mode_opt, randconfig},
        {"listnewconfig", no_argument,       &input_mode_opt, listnewconfig},
        {"helpnewconfig", no_argument,       &input_mode_opt, helpnewconfig},
        {"olddefconfig",  no_argument,       &input_mode_opt, olddefconfig},
        {"yes2modconfig", no_argument,       &input_mode_opt, yes2modconfig},
        {"mod2yesconfig", no_argument,       &input_mode_opt, mod2yesconfig},
        {"mod2noconfig",  no_argument,       &input_mode_opt, mod2noconfig},
        {NULL, 0, NULL, 0}
};

static void conf_usage(const char *progname)
{
        printf("Usage: %s [options] kconfig_file\n", progname);
        printf("\n");
        printf("Generic options:\n");
        printf("  -h, --help              Print this message and exit.\n");
        printf("  -s, --silent            Do not print log.\n");
        printf("\n");
        printf("Mode options:\n");
        printf("  --listnewconfig         List new options\n");
        printf("  --helpnewconfig         List new options and help text\n");
        printf("  --oldaskconfig          Start a new configuration using a line-oriented program\n");
        printf("  --oldconfig             Update a configuration using a provided .config as base\n");
        printf("  --syncconfig            Similar to oldconfig but generates configuration in\n"
               "                          include/{generated/,config/}\n");
        printf("  --olddefconfig          Same as oldconfig but sets new symbols to their default value\n");
        printf("  --defconfig <file>      New config with default defined in <file>\n");
        printf("  --savedefconfig <file>  Save the minimal current configuration to <file>\n");
        printf("  --allnoconfig           New config where all options are answered with no\n");
        printf("  --allyesconfig          New config where all options are answered with yes\n");
        printf("  --allmodconfig          New config where all options are answered with mod\n");
        printf("  --alldefconfig          New config with all symbols set to default\n");
        printf("  --randconfig            New config with random answer to all options\n");
        printf("  --yes2modconfig         Change answers from yes to mod if possible\n");
        printf("  --mod2yesconfig         Change answers from mod to yes if possible\n");
        printf("  --mod2noconfig          Change answers from mod to no if possible\n");
        printf("  (If none of the above is given, --oldaskconfig is the default)\n");
        printf("\n");
        printf("Arguments:\n");
        printf("  kconfig_file            Top-level Kconfig file.\n");
}

int main(int ac, char **av)
{
        const char *progname = av[0];
        int opt;
        const char *name, *defconfig_file = NULL /* gcc uninit */;
        int no_conf_write = 0;

        tty_stdio = isatty(0) && isatty(1);

        while ((opt = getopt_long(ac, av, "hs", long_opts, NULL)) != -1) {
                switch (opt) {
                case 'h':
                        conf_usage(progname);
                        exit(1);
                        break;
                case 's':
                        conf_set_message_callback(NULL);
                        break;
                case 0:
                        input_mode = input_mode_opt;
                        switch (input_mode) {
                        case syncconfig:
                                /*
                                 * syncconfig is invoked during the build stage.
                                 * Suppress distracting
                                 *   "configuration written to ..."
                                 */
                                conf_set_message_callback(NULL);
                                sync_kconfig = 1;
                                break;
                        case defconfig:
                        case savedefconfig:
                                defconfig_file = optarg;
                                break;
                        case randconfig:
                                set_randconfig_seed();
                                break;
                        default:
                                break;
                        }
                default:
                        break;
                }
        }
        if (ac == optind) {
                fprintf(stderr, "%s: Kconfig file missing\n", av[0]);
                conf_usage(progname);
                exit(1);
        }
        conf_parse(av[optind]);
        //zconfdump(stdout);

        switch (input_mode) {
        case defconfig:
                if (conf_read(defconfig_file)) {
                        fprintf(stderr,
                                "***\n"
                                  "*** Can't find default configuration \"%s\"!\n"
                                  "***\n",
                                defconfig_file);
                        exit(1);
                }
                break;
        case savedefconfig:
        case syncconfig:
        case oldaskconfig:
        case oldconfig:
        case listnewconfig:
        case helpnewconfig:
        case olddefconfig:
        case yes2modconfig:
        case mod2yesconfig:
        case mod2noconfig:
                conf_read(NULL);
                break;
        case allnoconfig:
        case allyesconfig:
        case allmodconfig:
        case alldefconfig:
        case randconfig:
                name = getenv("KCONFIG_ALLCONFIG");
                if (!name)
                        break;
                if ((strcmp(name, "") != 0) && (strcmp(name, "1") != 0)) {
                        if (conf_read_simple(name, S_DEF_USER)) {
                                fprintf(stderr,
                                        "*** Can't read seed configuration \"%s\"!\n",
                                        name);
                                exit(1);
                        }
                        break;
                }
                switch (input_mode) {
                case allnoconfig:       name = "allno.config"; break;
                case allyesconfig:      name = "allyes.config"; break;
                case allmodconfig:      name = "allmod.config"; break;
                case alldefconfig:      name = "alldef.config"; break;
                case randconfig:        name = "allrandom.config"; break;
                default: break;
                }
                if (conf_read_simple(name, S_DEF_USER) &&
                    conf_read_simple("all.config", S_DEF_USER)) {
                        fprintf(stderr,
                                "*** KCONFIG_ALLCONFIG set, but no \"%s\" or \"all.config\" file found\n",
                                name);
                        exit(1);
                }
                break;
        default:
                break;
        }

        if (conf_errors())
                exit(1);

        if (sync_kconfig) {
                name = getenv("KCONFIG_NOSILENTUPDATE");
                if (name && *name) {
                        if (conf_get_changed()) {
                                fprintf(stderr,
                                        "\n*** The configuration requires explicit update.\n\n");
                                return 1;
                        }
                        no_conf_write = 1;
                }
        }

        switch (input_mode) {
        case allnoconfig:
                conf_set_all_new_symbols(def_no);
                break;
        case allyesconfig:
                conf_set_all_new_symbols(def_yes);
                break;
        case allmodconfig:
                conf_set_all_new_symbols(def_mod);
                break;
        case alldefconfig:
                conf_set_all_new_symbols(def_default);
                break;
        case randconfig:
                conf_set_all_new_symbols(def_random);
                break;
        case defconfig:
                conf_set_all_new_symbols(def_default);
                break;
        case savedefconfig:
                break;
        case yes2modconfig:
                conf_rewrite_tristates(yes, mod);
                break;
        case mod2yesconfig:
                conf_rewrite_tristates(mod, yes);
                break;
        case mod2noconfig:
                conf_rewrite_tristates(mod, no);
                break;
        case oldaskconfig:
                rootEntry = &rootmenu;
                conf(&rootmenu);
                input_mode = oldconfig;
                /* fall through */
        case oldconfig:
        case listnewconfig:
        case helpnewconfig:
        case syncconfig:
                /* Update until a loop caused no more changes */
                do {
                        conf_cnt = 0;
                        check_conf(&rootmenu);
                } while (conf_cnt);
                break;
        case olddefconfig:
        default:
                break;
        }

        if (sym_dep_errors())
                exit(1);

        if (input_mode == savedefconfig) {
                if (conf_write_defconfig(defconfig_file)) {
                        fprintf(stderr, "n*** Error while saving defconfig to: %s\n\n",
                                defconfig_file);
                        return 1;
                }
        } else if (input_mode != listnewconfig && input_mode != helpnewconfig) {
                if (!no_conf_write && conf_write(NULL)) {
                        fprintf(stderr, "\n*** Error during writing of the configuration.\n\n");
                        exit(1);
                }

                /*
                 * Create auto.conf if it does not exist.
                 * This prevents GNU Make 4.1 or older from emitting
                 * "include/config/auto.conf: No such file or directory"
                 * in the top-level Makefile
                 *
                 * syncconfig always creates or updates auto.conf because it is
                 * used during the build.
                 */
                if (conf_write_autoconf(sync_kconfig) && sync_kconfig) {
                        fprintf(stderr,
                                "\n*** Error during sync of the configuration.\n\n");
                        return 1;
                }
        }
        return 0;
}