nuttx/tools/initialconfig.c
Alin Jerpelea c9eef2d697 tools: migrate to SPDX identifier
Most tools used for compliance and SBOM generation use SPDX identifiers
This change brings us a step closer to an easy SBOM generation.

Signed-off-by: Alin Jerpelea <alin.jerpelea@sony.com>
2024-09-10 23:11:11 +08:00

948 lines
24 KiB
C

/****************************************************************************
* tools/initialconfig.c
*
* SPDX-License-Identifier: Apache-2.0
*
* 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;
}