/**************************************************************************** * tools/initialconfig.c * * Copyright (C) 2017 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #define _GNU_SOURCE 1 #include #include #include #include #include #include #include #include /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #define MAX_LINE 512 #define MAX_ARCHITECTURES 32 #define MAX_MCUS 64 #define MAX_BOARDS 128 /**************************************************************************** * Private Types ****************************************************************************/ typedef int (*direntcb_t)(const char *dirpath, struct dirent *entry, void *arg); /**************************************************************************** * Private Data ****************************************************************************/ #ifdef CONFIG_WINDOWS_NATIVE static char g_delim = '\\'; /* Delimiter to use when forming paths */ #else static char g_delim = '/'; /* Delimiter to use when forming paths */ #endif static const char g_archdir[] = "arch"; /* Architecture directory */ static const char g_configdir[] = "boards"; /* Board configuration directory */ static char *g_arch[MAX_ARCHITECTURES]; /* List of architecture names */ static int g_narch; /* Number of architecture names */ static char *g_selected_arch; /* Selected architecture name */ static char *g_selected_family; /* Selected architecture family name */ static char *g_mcu[MAX_MCUS]; /* List of MCU names */ static int g_nmcu; /* Number of MCU names */ static char *g_selected_mcu; /* Selected MCU name */ static char *g_board[MAX_BOARDS]; /* List of board names */ static int g_nboard; /* Number of board names */ static char *g_selected_board; /* Selected board name */ static char g_line[MAX_LINE + 1]; /* Line read from config file */ /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: skip_space * * Description: * Skip over any spaces * ****************************************************************************/ static char *skip_space(char *ptr) { while (*ptr && isspace((int)*ptr)) ptr++; return ptr; } /**************************************************************************** * Name: find_name_end * * Description: * Find the end of a variable string * ****************************************************************************/ static char *find_name_end(char *ptr) { while (*ptr && (isalnum((int)*ptr) || *ptr == '_')) ptr++; return ptr; } /**************************************************************************** * Name: find_value_end * * Description: * Find the end of a value string * ****************************************************************************/ static char *find_value_end(char *ptr) { while (*ptr && !isspace((int)*ptr)) { if (*ptr == '"') { do ptr++; while (*ptr && *ptr != '"'); if (*ptr) ptr++; } else { do ptr++; while (*ptr && !isspace((int)*ptr) && *ptr != '"'); } } return ptr; } /**************************************************************************** * Name: read_line * * Description: * Read the next line from the configuration file * ****************************************************************************/ static char *read_line(FILE *stream) { char *ptr; for (; ; ) { g_line[MAX_LINE] = '\0'; if (!fgets(g_line, MAX_LINE, stream)) { return NULL; } else { ptr = skip_space(g_line); if (*ptr && *ptr != '#' && *ptr != '\n') { return ptr; } } } } /**************************************************************************** * Name: parse_line * * Description: * Parse the line from the configuration file into a variable name * string and a value string. * ****************************************************************************/ static void parse_line(char *ptr, char **varname, char **varval) { /* Skip over any leading spaces */ ptr = skip_space(ptr); /* The first no-space is the beginning of the variable name */ *varname = skip_space(ptr); *varval = NULL; /* Parse to the end of the variable name */ ptr = find_name_end(ptr); /* An equal sign is expected next, perhaps after some white space */ if (*ptr && *ptr != '=') { /* Some else follows the variable name. Terminate the variable * name and skip over any spaces. */ *ptr = '\0'; ptr = skip_space(ptr + 1); } /* Verify that the equal sign is present */ if (*ptr == '=') { /* Make sure that the variable name is terminated (this was already * done if the name was followed by white space. */ *ptr = '\0'; /* The variable value should follow =, perhaps separated by some * white space. */ ptr = skip_space(ptr + 1); if (*ptr) { /* Yes.. a variable follows. Save the pointer to the start * of the variable string. */ *varval = ptr; /* Find the end of the variable string and make sure that it * is terminated. */ ptr = find_value_end(ptr); *ptr = '\0'; } } } /**************************************************************************** * Name: find_variable * * Description: * Return true if the selected variable exists. Also return the value of * the variable. * ****************************************************************************/ static bool find_variable(const char *configpath, const char *varname, char **varvalue) { FILE *stream; char *tmpname; char *tmpvalue; char *ptr; stream = fopen(configpath, "r"); if (!stream) { fprintf(stderr, "ERROR: failed to open %s for reading: %s\n", configpath, strerror(errno)); exit(EXIT_FAILURE); } /* Loop until the entire file has been parsed. */ do { /* Read the next line from the file */ ptr = read_line(stream); if (ptr) { /* Parse the line into a variable and a value field */ tmpname = NULL; tmpvalue = NULL; parse_line(ptr, &tmpname, &tmpvalue); /* Make sure that both a variable name and value name were found. */ if (tmpname == NULL || tmpvalue == NULL) { continue; } /* Check if this the variable name and value we are looking for */ if (strcmp(varname, tmpname) == 0) { /* Yes.. return the value of the variable */ *varvalue = tmpvalue; fclose(stream); return true; } } } while (ptr); /* Return failure */ fclose(stream); return false; } /**************************************************************************** * Name: check_variable * * Description: * Return true if the selected variable exists in the configuration file * and has the specified value. * ****************************************************************************/ static bool check_variable(const char *configpath, const char *varname, const char *varvalue) { char *tmpvalue; if (find_variable(configpath, varname, &tmpvalue)) { /* The variable name exists. Does it have a value? Does the value * match varvalue? */ if (tmpvalue != NULL && strcmp(varvalue, tmpvalue) == 0) { /* Yes.. return success */ return true; } } /* Return failure */ return false; } /**************************************************************************** * Name: test_filepath * * Description: * Test if a regular file exists at this path. * ****************************************************************************/ static bool test_filepath(const char *filepath) { struct stat statbuf; int ret; ret = stat(filepath, &statbuf); if (ret < 0) { return false; } return S_ISREG(statbuf.st_mode); } /**************************************************************************** * Name: test_dirpath * * Description: * Test if a regular file exists at this path. * ****************************************************************************/ static bool test_dirpath(const char *filepath) { struct stat statbuf; int ret; ret = stat(filepath, &statbuf); if (ret < 0) { return false; } return S_ISDIR(statbuf.st_mode); } /**************************************************************************** * Name: foreach_dirent * * Description: * Given a directory path, call the provided function for each entry in * the directory. * ****************************************************************************/ static int foreach_dirent(const char *dirpath, direntcb_t cb, void *arg) { DIR *dirp; struct dirent *result; struct dirent entry; int ret; dirp = opendir(dirpath); if (dirp == NULL) { fprintf(stderr, "ERROR: Failed to open directory '%s': %s\n", dirpath, strerror(errno)); exit(EXIT_FAILURE); } for (; ; ) { ret = readdir_r(dirp, &entry, &result); if (ret != 0) { fprintf(stderr, "ERROR: Failed to reed directory '%s' entry: %s\n", dirpath, strerror(ret)); closedir(dirp); exit(EXIT_FAILURE); } if (result == NULL) { break; } /* Skip over the . and .. hard links */ if (strcmp(entry.d_name, ".") == 0 || strcmp(entry.d_name, "..") == 0) { continue; } ret = cb(dirpath, &entry, arg); if (ret != 0) { break; } } closedir(dirp); return ret; } /**************************************************************************** * Name: enum_architectures * * Description: * Enumerate all architecture directory names. * ****************************************************************************/ static int enum_architectures(const char *dirpath, struct dirent *entry, void *arg) { char *archpath; char *testpath; /* All architecture directories should contain a Kconfig file, an include/ * directory, and a src/ directory. */ asprintf(&archpath, "%s%c%s", dirpath, g_delim, entry->d_name); asprintf(&testpath, "%s%cKconfig", archpath, g_delim); if (test_filepath(testpath)) { free(testpath); asprintf(&testpath, "%s%cinclude", archpath, g_delim); if (test_dirpath(testpath)) { free(testpath); asprintf(&testpath, "%s%csrc", archpath, g_delim); if (test_dirpath(testpath)) { if (g_narch >= MAX_ARCHITECTURES) { fprintf(stderr, "ERROR: Too many architecture directories found\n"); exit(EXIT_FAILURE); } g_arch[g_narch] = strdup(entry->d_name); g_narch++; } } } free(testpath); free(archpath); return 0; } /**************************************************************************** * Name: enum_mcus * * Description: * Enumerate all MCU directory names. * ****************************************************************************/ static int enum_mcus(const char *dirpath, struct dirent *entry, void *arg) { char *mcupath; char *testpath; /* All MCU directories should contain a Kconfig and a Make.defs file. */ asprintf(&mcupath, "%s%c%s", dirpath, g_delim, entry->d_name); asprintf(&testpath, "%s%cKconfig", mcupath, g_delim); if (test_filepath(testpath)) { free(testpath); asprintf(&testpath, "%s%cMake.defs", mcupath, g_delim); if (test_filepath(testpath)) { if (g_nmcu >= MAX_MCUS) { fprintf(stderr, "ERROR: Too many MCU directories found\n"); exit(EXIT_FAILURE); } g_mcu[g_nmcu] = strdup(entry->d_name); g_nmcu++; } } free(testpath); free(mcupath); return 0; } /**************************************************************************** * Name: enum_board_configurations * * Description: * Enumerate all configurations for boards find the configuration * directory for the selected MCU. * ****************************************************************************/ static int enum_board_configurations(const char *dirpath, struct dirent *entry, void *arg) { char *configpath; char *varvalue; int ret = 0; /* All board directories should contain a defconfig file. */ asprintf(&configpath, "%s%c%s%cdefconfig", dirpath, g_delim, entry->d_name, g_delim); if (test_filepath(configpath)) { /* We don't want all board configurations, we only want the name of * the board that includes a configuration with: * * CONFIG_ARCH_CHIP="xxxx" * * Where xxxx is the selected MCU name. */ asprintf(&varvalue, "\"%s\"", g_selected_mcu); if (check_variable(configpath, "CONFIG_ARCH_CHIP", varvalue)) { /* Found it... add the board name to the list of boards for the * selected MCU. */ if (g_nboard >= MAX_BOARDS) { fprintf(stderr, "ERROR: Too many board configurations found\n"); exit(EXIT_FAILURE); } g_board[g_nboard] = strdup(arg); g_nboard++; /* If we have not yet extracted the architecture family, then do * that here. */ if (g_selected_family == NULL) { char *family; if (find_variable(configpath, "CONFIG_ARCH_FAMILY", &family)) { g_selected_family = strdup(family); } } /* Stop the enumeration if we find a match. Continue if not... * that is because one board might possible support multiple * architectures. */ ret = 1; } free(varvalue); } free(configpath); return ret; } /**************************************************************************** * Name: enum_boards * * Description: * Enumerate all boards find the configuration directory for the selected * MCU. * ****************************************************************************/ static int enum_boards(const char *dirpath, struct dirent *entry, void *arg) { char *boardpath; char *testpath; /* All board directories should contain a Kconfig file, an include/ * directory, and a src/ directory. */ asprintf(&boardpath, "%s%c%s", dirpath, g_delim, entry->d_name); asprintf(&testpath, "%s%cKconfig", boardpath, g_delim); if (test_filepath(testpath)) { free(testpath); asprintf(&testpath, "%s%cinclude", boardpath, g_delim); if (test_dirpath(testpath)) { free(testpath); asprintf(&testpath, "%s%csrc", boardpath, g_delim); if (test_dirpath(testpath)) { /* Enumerate the board configurations */ foreach_dirent(boardpath, enum_board_configurations, entry->d_name); } } } free(testpath); free(boardpath); return 0; } /**************************************************************************** * Name: list_select * * Description: * Select one value from a list. * ****************************************************************************/ char *list_select(char **list, unsigned nitems) { char ch; int ndx; int i; /* Show the list */ for (i = 0, ch = '1'; i < nitems; i++) { printf(" %c. %s\n", ch, list[i]); if (ch == '9') { ch = 'a'; } else if (ch == 'z') { ch = 'A'; } else { ch++; } } for (; ; ) { bool input = false; printf("Enter [1"); if (nitems > 1) { printf("-%c", nitems >= 9 ? '9' : '0' + nitems); if (nitems > 9) { printf(",a"); if (nitems > 10) { printf("-%c", 'a' + nitems - 10); if (nitems > 35) { printf(",A"); if (nitems > 36) { printf("-%c", 'A' + nitems - 36); } } } } } printf("]: "); do { ch = getchar(); if (ch >= '1' && ch <= '9') { ndx = ch - '1'; } else if (ch >= 'a' && ch <= 'z') { ndx = ch - 'a' + 9; } else if (ch >= 'A' && ch <= 'Z') { ndx = ch - 'A' + 35; } else if (ch == '\n') { continue; } else { printf("Invalid selection: %c -- Try again\n", ch); input = true; continue; } if (ndx < nitems) { return list[ndx]; } else { printf("Invalid selection: %c -- Try again\n", ch); input = true; } } while (!input); } } /**************************************************************************** * Name: create_config * * Description: * Generate a bogus .config file. There is only sufficient information * in this bogus .config to estable the correct symbolic links. * ****************************************************************************/ static void create_config(void) { FILE *stream; stream = fopen(".config", "w"); if (!stream) { fprintf(stderr, "ERROR: failed to open .config for writing: %s\n", strerror(errno)); exit(EXIT_FAILURE); } fprintf(stream, "CONFIG_ARCH=\"%s\"\n", g_selected_arch); if (g_selected_family != NULL) { fprintf(stream, "CONFIG_ARCH_FAMILY=%s\n", g_selected_family); } fprintf(stream, "CONFIG_ARCH_CHIP=\"%s\"\n", g_selected_mcu); fprintf(stream, "CONFIG_ARCH_BOARD=\"%s\"\n", g_selected_board); fclose(stream); } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: main * * Description: * Program entry point. * ****************************************************************************/ int main(int argc, char **argv) { char *archpath; /* Enumerate all of the architectures */ g_narch = 0; foreach_dirent(g_archdir, enum_architectures, NULL); /* Select an architecture */ printf("Select an architecture:\n"); g_selected_arch = list_select(g_arch, g_narch); /* Enumerate the MCUs for the selected architecture */ g_nmcu = 0; asprintf(&archpath, "%s%c%s%csrc", g_archdir, g_delim, g_selected_arch, g_delim); foreach_dirent(archpath, enum_mcus, NULL); /* Select an MCU */ printf("Select an MCU for architecture=%s:\n", g_selected_arch); g_selected_mcu = list_select(g_mcu, g_nmcu); /* Enumerate the boards for the selected MCU */ g_nboard = 0; foreach_dirent(g_configdir, enum_boards, NULL); /* Select an board */ printf("Select a board for MCU=%s:\n", g_selected_mcu); g_selected_board = list_select(g_board, g_nboard); /* Then output a bogus .config file with enough information to establish * the correct symbolic links */ create_config(); return 0; }