| /**************************************************************************** |
| * tools/initialconfig.c |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #define _GNU_SOURCE 1 |
| #include <sys/stat.h> |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <dirent.h> |
| #include <errno.h> |
| |
| /**************************************************************************** |
| * 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 *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 (; ; ) |
| { |
| /* To distinguish between end of stream and error, set |
| * errno to 0 and verify whether its value changed if |
| * readdir returned NULL. |
| */ |
| |
| errno = 0; |
| |
| entry = readdir(dirp); |
| if (entry == NULL && errno != 0) |
| { |
| fprintf(stderr, |
| "ERROR: Failed to read directory '%s' entry: %s\n", |
| dirpath, strerror(errno)); |
| closedir(dirp); |
| exit(EXIT_FAILURE); |
| } |
| |
| if (entry == 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) |
| { |
| int ret; |
| char *archpath; |
| char *testpath; |
| |
| /* All architecture directories should contain a Kconfig file, an include/ |
| * directory, and a src/ directory. |
| */ |
| |
| ret = asprintf(&archpath, "%s%c%s", dirpath, g_delim, entry->d_name); |
| if (ret < 0) |
| { |
| fprintf(stderr, |
| "ERROR: asprintf() failed to archpath\n"); |
| return ret; |
| } |
| |
| ret = asprintf(&testpath, "%s%cKconfig", archpath, g_delim); |
| if (ret < 0) |
| { |
| fprintf(stderr, |
| "ERROR: asprintf() failed to testpath\n"); |
| return ret; |
| } |
| |
| if (test_filepath(testpath)) |
| { |
| free(testpath); |
| |
| ret = asprintf(&testpath, "%s%cinclude", archpath, g_delim); |
| if (ret < 0) |
| { |
| fprintf(stderr, |
| "ERROR: asprintf() failed to testpath/include\n"); |
| return ret; |
| } |
| |
| if (test_dirpath(testpath)) |
| { |
| free(testpath); |
| |
| ret = asprintf(&testpath, "%s%csrc", archpath, g_delim); |
| if (ret < 0) |
| { |
| fprintf(stderr, |
| "ERROR: asprintf() failed to testpath/src\n"); |
| return ret; |
| } |
| |
| 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) |
| { |
| int ret; |
| char *mcupath; |
| char *testpath; |
| |
| /* All MCU directories should contain a Kconfig and a Make.defs file. */ |
| |
| ret = asprintf(&mcupath, "%s%c%s", dirpath, g_delim, entry->d_name); |
| if (ret < 0) |
| { |
| fprintf(stderr, |
| "ERROR: asprintf() failed to mcupath\n"); |
| return ret; |
| } |
| |
| ret = asprintf(&testpath, "%s%cKconfig", mcupath, g_delim); |
| if (ret < 0) |
| { |
| fprintf(stderr, |
| "ERROR: asprintf() failed to archpath/Kconfig\n"); |
| return ret; |
| } |
| |
| if (test_filepath(testpath)) |
| { |
| free(testpath); |
| ret = asprintf(&testpath, "%s%cMake.defs", mcupath, g_delim); |
| if (ret < 0) |
| { |
| fprintf(stderr, |
| "ERROR: asprintf() failed to testpath/Make.defs\n"); |
| return ret; |
| } |
| |
| 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. */ |
| |
| ret = asprintf(&configpath, "%s%c%s%cdefconfig", |
| dirpath, g_delim, entry->d_name, g_delim); |
| if (ret < 0) |
| { |
| fprintf(stderr, |
| "ERROR: asprintf() failed to configpath\n"); |
| return ret; |
| } |
| |
| 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. |
| */ |
| |
| ret = asprintf(&varvalue, "\"%s\"", g_selected_mcu); |
| if (ret < 0) |
| { |
| fprintf(stderr, |
| "ERROR: asprintf() failed to varvalue\n"); |
| return ret; |
| } |
| |
| 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) |
| { |
| int ret = 0; |
| char *boardpath; |
| char *testpath; |
| |
| /* All board directories should contain a Kconfig file, an include/ |
| * directory, and a src/ directory. |
| */ |
| |
| ret = asprintf(&boardpath, "%s%c%s", dirpath, g_delim, entry->d_name); |
| if (ret < 0) |
| { |
| fprintf(stderr, |
| "ERROR: asprintf() failed to boardpath\n"); |
| return ret; |
| } |
| |
| ret = asprintf(&testpath, "%s%cKconfig", boardpath, g_delim); |
| if (ret < 0) |
| { |
| fprintf(stderr, |
| "ERROR: asprintf() failed to testpath\n"); |
| return ret; |
| } |
| |
| if (test_filepath(testpath)) |
| { |
| free(testpath); |
| ret = asprintf(&testpath, "%s%cinclude", boardpath, g_delim); |
| if (ret < 0) |
| { |
| fprintf(stderr, |
| "ERROR: asprintf() failed to testpath\n"); |
| return ret; |
| } |
| |
| if (test_dirpath(testpath)) |
| { |
| free(testpath); |
| ret = asprintf(&testpath, "%s%csrc", boardpath, g_delim); |
| if (ret < 0) |
| { |
| fprintf(stderr, |
| "ERROR: asprintf() failed to archpath\n"); |
| return ret; |
| } |
| |
| 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) |
| { |
| int ret; |
| 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; |
| ret = asprintf(&archpath, "%s%c%s%csrc", |
| g_archdir, g_delim, g_selected_arch, g_delim); |
| if (ret < 0) |
| { |
| fprintf(stderr, |
| "ERROR: asprintf() failed to archpath/src\n"); |
| return ret; |
| } |
| |
| 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; |
| } |