xref: /openbmc/u-boot/cmd/ini.c (revision e8f80a5a)
1*83d290c5STom Rini // SPDX-License-Identifier: BSD-3-Clause
22e192b24SSimon Glass /*
32e192b24SSimon Glass  * inih -- simple .INI file parser
42e192b24SSimon Glass  *
52e192b24SSimon Glass  * Copyright (c) 2009, Brush Technology
62e192b24SSimon Glass  * Copyright (c) 2012:
72e192b24SSimon Glass  *              Joe Hershberger, National Instruments, joe.hershberger@ni.com
82e192b24SSimon Glass  * All rights reserved.
92e192b24SSimon Glass  *
102e192b24SSimon Glass  * Go to the project home page for more info:
112e192b24SSimon Glass  * http://code.google.com/p/inih/
122e192b24SSimon Glass  */
132e192b24SSimon Glass 
142e192b24SSimon Glass #include <common.h>
152e192b24SSimon Glass #include <command.h>
162e192b24SSimon Glass #include <environment.h>
172e192b24SSimon Glass #include <linux/ctype.h>
182e192b24SSimon Glass #include <linux/string.h>
192e192b24SSimon Glass 
202e192b24SSimon Glass #ifdef CONFIG_INI_MAX_LINE
212e192b24SSimon Glass #define MAX_LINE CONFIG_INI_MAX_LINE
222e192b24SSimon Glass #else
232e192b24SSimon Glass #define MAX_LINE 200
242e192b24SSimon Glass #endif
252e192b24SSimon Glass 
262e192b24SSimon Glass #ifdef CONFIG_INI_MAX_SECTION
272e192b24SSimon Glass #define MAX_SECTION CONFIG_INI_MAX_SECTION
282e192b24SSimon Glass #else
292e192b24SSimon Glass #define MAX_SECTION 50
302e192b24SSimon Glass #endif
312e192b24SSimon Glass 
322e192b24SSimon Glass #ifdef CONFIG_INI_MAX_NAME
332e192b24SSimon Glass #define MAX_NAME CONFIG_INI_MAX_NAME
342e192b24SSimon Glass #else
352e192b24SSimon Glass #define MAX_NAME 50
362e192b24SSimon Glass #endif
372e192b24SSimon Glass 
382e192b24SSimon Glass /* Strip whitespace chars off end of given string, in place. Return s. */
rstrip(char * s)392e192b24SSimon Glass static char *rstrip(char *s)
402e192b24SSimon Glass {
412e192b24SSimon Glass 	char *p = s + strlen(s);
422e192b24SSimon Glass 
432e192b24SSimon Glass 	while (p > s && isspace(*--p))
442e192b24SSimon Glass 		*p = '\0';
452e192b24SSimon Glass 	return s;
462e192b24SSimon Glass }
472e192b24SSimon Glass 
482e192b24SSimon Glass /* Return pointer to first non-whitespace char in given string. */
lskip(const char * s)492e192b24SSimon Glass static char *lskip(const char *s)
502e192b24SSimon Glass {
512e192b24SSimon Glass 	while (*s && isspace(*s))
522e192b24SSimon Glass 		s++;
532e192b24SSimon Glass 	return (char *)s;
542e192b24SSimon Glass }
552e192b24SSimon Glass 
562e192b24SSimon Glass /* Return pointer to first char c or ';' comment in given string, or pointer to
572e192b24SSimon Glass    null at end of string if neither found. ';' must be prefixed by a whitespace
582e192b24SSimon Glass    character to register as a comment. */
find_char_or_comment(const char * s,char c)592e192b24SSimon Glass static char *find_char_or_comment(const char *s, char c)
602e192b24SSimon Glass {
612e192b24SSimon Glass 	int was_whitespace = 0;
622e192b24SSimon Glass 
632e192b24SSimon Glass 	while (*s && *s != c && !(was_whitespace && *s == ';')) {
642e192b24SSimon Glass 		was_whitespace = isspace(*s);
652e192b24SSimon Glass 		s++;
662e192b24SSimon Glass 	}
672e192b24SSimon Glass 	return (char *)s;
682e192b24SSimon Glass }
692e192b24SSimon Glass 
702e192b24SSimon Glass /* Version of strncpy that ensures dest (size bytes) is null-terminated. */
strncpy0(char * dest,const char * src,size_t size)712e192b24SSimon Glass static char *strncpy0(char *dest, const char *src, size_t size)
722e192b24SSimon Glass {
732e192b24SSimon Glass 	strncpy(dest, src, size);
742e192b24SSimon Glass 	dest[size - 1] = '\0';
752e192b24SSimon Glass 	return dest;
762e192b24SSimon Glass }
772e192b24SSimon Glass 
782e192b24SSimon Glass /* Emulate the behavior of fgets but on memory */
memgets(char * str,int num,char ** mem,size_t * memsize)792e192b24SSimon Glass static char *memgets(char *str, int num, char **mem, size_t *memsize)
802e192b24SSimon Glass {
812e192b24SSimon Glass 	char *end;
822e192b24SSimon Glass 	int len;
832e192b24SSimon Glass 	int newline = 1;
842e192b24SSimon Glass 
852e192b24SSimon Glass 	end = memchr(*mem, '\n', *memsize);
862e192b24SSimon Glass 	if (end == NULL) {
872e192b24SSimon Glass 		if (*memsize == 0)
882e192b24SSimon Glass 			return NULL;
892e192b24SSimon Glass 		end = *mem + *memsize;
902e192b24SSimon Glass 		newline = 0;
912e192b24SSimon Glass 	}
922e192b24SSimon Glass 	len = min((end - *mem) + newline, num);
932e192b24SSimon Glass 	memcpy(str, *mem, len);
942e192b24SSimon Glass 	if (len < num)
952e192b24SSimon Glass 		str[len] = '\0';
962e192b24SSimon Glass 
972e192b24SSimon Glass 	/* prepare the mem vars for the next call */
982e192b24SSimon Glass 	*memsize -= (end - *mem) + newline;
992e192b24SSimon Glass 	*mem += (end - *mem) + newline;
1002e192b24SSimon Glass 
1012e192b24SSimon Glass 	return str;
1022e192b24SSimon Glass }
1032e192b24SSimon Glass 
1042e192b24SSimon Glass /* Parse given INI-style file. May have [section]s, name=value pairs
1052e192b24SSimon Glass    (whitespace stripped), and comments starting with ';' (semicolon). Section
1062e192b24SSimon Glass    is "" if name=value pair parsed before any section heading. name:value
1072e192b24SSimon Glass    pairs are also supported as a concession to Python's ConfigParser.
1082e192b24SSimon Glass 
1092e192b24SSimon Glass    For each name=value pair parsed, call handler function with given user
1102e192b24SSimon Glass    pointer as well as section, name, and value (data only valid for duration
1112e192b24SSimon Glass    of handler call). Handler should return nonzero on success, zero on error.
1122e192b24SSimon Glass 
1132e192b24SSimon Glass    Returns 0 on success, line number of first error on parse error (doesn't
1142e192b24SSimon Glass    stop on first error).
1152e192b24SSimon Glass */
ini_parse(char * filestart,size_t filelen,int (* handler)(void *,char *,char *,char *),void * user)1162e192b24SSimon Glass static int ini_parse(char *filestart, size_t filelen,
1172e192b24SSimon Glass 	int (*handler)(void *, char *, char *, char *),	void *user)
1182e192b24SSimon Glass {
1192e192b24SSimon Glass 	/* Uses a fair bit of stack (use heap instead if you need to) */
1202e192b24SSimon Glass 	char line[MAX_LINE];
1212e192b24SSimon Glass 	char section[MAX_SECTION] = "";
1222e192b24SSimon Glass 	char prev_name[MAX_NAME] = "";
1232e192b24SSimon Glass 
1242e192b24SSimon Glass 	char *curmem = filestart;
1252e192b24SSimon Glass 	char *start;
1262e192b24SSimon Glass 	char *end;
1272e192b24SSimon Glass 	char *name;
1282e192b24SSimon Glass 	char *value;
1292e192b24SSimon Glass 	size_t memleft = filelen;
1302e192b24SSimon Glass 	int lineno = 0;
1312e192b24SSimon Glass 	int error = 0;
1322e192b24SSimon Glass 
1332e192b24SSimon Glass 	/* Scan through file line by line */
1342e192b24SSimon Glass 	while (memgets(line, sizeof(line), &curmem, &memleft) != NULL) {
1352e192b24SSimon Glass 		lineno++;
1362e192b24SSimon Glass 		start = lskip(rstrip(line));
1372e192b24SSimon Glass 
1382e192b24SSimon Glass 		if (*start == ';' || *start == '#') {
1392e192b24SSimon Glass 			/*
1402e192b24SSimon Glass 			 * Per Python ConfigParser, allow '#' comments at start
1412e192b24SSimon Glass 			 * of line
1422e192b24SSimon Glass 			 */
1432e192b24SSimon Glass 		}
1442e192b24SSimon Glass #if CONFIG_INI_ALLOW_MULTILINE
1452e192b24SSimon Glass 		else if (*prev_name && *start && start > line) {
1462e192b24SSimon Glass 			/*
1472e192b24SSimon Glass 			 * Non-blank line with leading whitespace, treat as
1482e192b24SSimon Glass 			 * continuation of previous name's value (as per Python
1492e192b24SSimon Glass 			 * ConfigParser).
1502e192b24SSimon Glass 			 */
1512e192b24SSimon Glass 			if (!handler(user, section, prev_name, start) && !error)
1522e192b24SSimon Glass 				error = lineno;
1532e192b24SSimon Glass 		}
1542e192b24SSimon Glass #endif
1552e192b24SSimon Glass 		else if (*start == '[') {
1562e192b24SSimon Glass 			/* A "[section]" line */
1572e192b24SSimon Glass 			end = find_char_or_comment(start + 1, ']');
1582e192b24SSimon Glass 			if (*end == ']') {
1592e192b24SSimon Glass 				*end = '\0';
1602e192b24SSimon Glass 				strncpy0(section, start + 1, sizeof(section));
1612e192b24SSimon Glass 				*prev_name = '\0';
1622e192b24SSimon Glass 			} else if (!error) {
1632e192b24SSimon Glass 				/* No ']' found on section line */
1642e192b24SSimon Glass 				error = lineno;
1652e192b24SSimon Glass 			}
1662e192b24SSimon Glass 		} else if (*start && *start != ';') {
1672e192b24SSimon Glass 			/* Not a comment, must be a name[=:]value pair */
1682e192b24SSimon Glass 			end = find_char_or_comment(start, '=');
1692e192b24SSimon Glass 			if (*end != '=')
1702e192b24SSimon Glass 				end = find_char_or_comment(start, ':');
1712e192b24SSimon Glass 			if (*end == '=' || *end == ':') {
1722e192b24SSimon Glass 				*end = '\0';
1732e192b24SSimon Glass 				name = rstrip(start);
1742e192b24SSimon Glass 				value = lskip(end + 1);
1752e192b24SSimon Glass 				end = find_char_or_comment(value, '\0');
1762e192b24SSimon Glass 				if (*end == ';')
1772e192b24SSimon Glass 					*end = '\0';
1782e192b24SSimon Glass 				rstrip(value);
1792e192b24SSimon Glass 				/* Strip double-quotes */
1802e192b24SSimon Glass 				if (value[0] == '"' &&
1812e192b24SSimon Glass 				    value[strlen(value)-1] == '"') {
1822e192b24SSimon Glass 					value[strlen(value)-1] = '\0';
1832e192b24SSimon Glass 					value += 1;
1842e192b24SSimon Glass 				}
1852e192b24SSimon Glass 
1862e192b24SSimon Glass 				/*
1872e192b24SSimon Glass 				 * Valid name[=:]value pair found, call handler
1882e192b24SSimon Glass 				 */
1892e192b24SSimon Glass 				strncpy0(prev_name, name, sizeof(prev_name));
1902e192b24SSimon Glass 				if (!handler(user, section, name, value) &&
1912e192b24SSimon Glass 				     !error)
1922e192b24SSimon Glass 					error = lineno;
1932e192b24SSimon Glass 			} else if (!error)
1942e192b24SSimon Glass 				/* No '=' or ':' found on name[=:]value line */
1952e192b24SSimon Glass 				error = lineno;
1962e192b24SSimon Glass 		}
1972e192b24SSimon Glass 	}
1982e192b24SSimon Glass 
1992e192b24SSimon Glass 	return error;
2002e192b24SSimon Glass }
2012e192b24SSimon Glass 
ini_handler(void * user,char * section,char * name,char * value)2022e192b24SSimon Glass static int ini_handler(void *user, char *section, char *name, char *value)
2032e192b24SSimon Glass {
2042e192b24SSimon Glass 	char *requested_section = (char *)user;
2052e192b24SSimon Glass #ifdef CONFIG_INI_CASE_INSENSITIVE
2062e192b24SSimon Glass 	int i;
2072e192b24SSimon Glass 
2082e192b24SSimon Glass 	for (i = 0; i < strlen(requested_section); i++)
2092e192b24SSimon Glass 		requested_section[i] = tolower(requested_section[i]);
2102e192b24SSimon Glass 	for (i = 0; i < strlen(section); i++)
2112e192b24SSimon Glass 		section[i] = tolower(section[i]);
2122e192b24SSimon Glass #endif
2132e192b24SSimon Glass 
2142e192b24SSimon Glass 	if (!strcmp(section, requested_section)) {
2152e192b24SSimon Glass #ifdef CONFIG_INI_CASE_INSENSITIVE
2162e192b24SSimon Glass 		for (i = 0; i < strlen(name); i++)
2172e192b24SSimon Glass 			name[i] = tolower(name[i]);
2182e192b24SSimon Glass 		for (i = 0; i < strlen(value); i++)
2192e192b24SSimon Glass 			value[i] = tolower(value[i]);
2202e192b24SSimon Glass #endif
221382bee57SSimon Glass 		env_set(name, value);
2222e192b24SSimon Glass 		printf("ini: Imported %s as %s\n", name, value);
2232e192b24SSimon Glass 	}
2242e192b24SSimon Glass 
2252e192b24SSimon Glass 	/* success */
2262e192b24SSimon Glass 	return 1;
2272e192b24SSimon Glass }
2282e192b24SSimon Glass 
do_ini(cmd_tbl_t * cmdtp,int flag,int argc,char * const argv[])2292e192b24SSimon Glass static int do_ini(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
2302e192b24SSimon Glass {
2312e192b24SSimon Glass 	const char *section;
2322e192b24SSimon Glass 	char *file_address;
2332e192b24SSimon Glass 	size_t file_size;
2342e192b24SSimon Glass 
2352e192b24SSimon Glass 	if (argc == 1)
2362e192b24SSimon Glass 		return CMD_RET_USAGE;
2372e192b24SSimon Glass 
2382e192b24SSimon Glass 	section = argv[1];
2392e192b24SSimon Glass 	file_address = (char *)simple_strtoul(
24000caae6dSSimon Glass 		argc < 3 ? env_get("loadaddr") : argv[2], NULL, 16);
2412e192b24SSimon Glass 	file_size = (size_t)simple_strtoul(
24200caae6dSSimon Glass 		argc < 4 ? env_get("filesize") : argv[3], NULL, 16);
2432e192b24SSimon Glass 
2442e192b24SSimon Glass 	return ini_parse(file_address, file_size, ini_handler, (void *)section);
2452e192b24SSimon Glass }
2462e192b24SSimon Glass 
2472e192b24SSimon Glass U_BOOT_CMD(
2482e192b24SSimon Glass 	ini, 4, 0, do_ini,
2492e192b24SSimon Glass 	"parse an ini file in memory and merge the specified section into the env",
2502e192b24SSimon Glass 	"section [[file-address] file-size]"
2512e192b24SSimon Glass );
252