1// SPDX-License-Identifier: BSD-3-Clause
2/*
3 * inih -- simple .INI file parser
4 *
5 * Copyright (c) 2009, Brush Technology
6 * Copyright (c) 2012:
7 *              Joe Hershberger, National Instruments, joe.hershberger@ni.com
8 * All rights reserved.
9 *
10 * Go to the project home page for more info:
11 * http://code.google.com/p/inih/
12 */
13
14#include <common.h>
15#include <command.h>
16#include <env.h>
17#include <linux/ctype.h>
18#include <linux/string.h>
19
20#ifdef CONFIG_INI_MAX_LINE
21#define MAX_LINE CONFIG_INI_MAX_LINE
22#else
23#define MAX_LINE 200
24#endif
25
26#ifdef CONFIG_INI_MAX_SECTION
27#define MAX_SECTION CONFIG_INI_MAX_SECTION
28#else
29#define MAX_SECTION 50
30#endif
31
32#ifdef CONFIG_INI_MAX_NAME
33#define MAX_NAME CONFIG_INI_MAX_NAME
34#else
35#define MAX_NAME 50
36#endif
37
38/* Strip whitespace chars off end of given string, in place. Return s. */
39static char *rstrip(char *s)
40{
41	char *p = s + strlen(s);
42
43	while (p > s && isspace(*--p))
44		*p = '\0';
45	return s;
46}
47
48/* Return pointer to first non-whitespace char in given string. */
49static char *lskip(const char *s)
50{
51	while (*s && isspace(*s))
52		s++;
53	return (char *)s;
54}
55
56/* Return pointer to first char c or ';' comment in given string, or pointer to
57   null at end of string if neither found. ';' must be prefixed by a whitespace
58   character to register as a comment. */
59static char *find_char_or_comment(const char *s, char c)
60{
61	int was_whitespace = 0;
62
63	while (*s && *s != c && !(was_whitespace && *s == ';')) {
64		was_whitespace = isspace(*s);
65		s++;
66	}
67	return (char *)s;
68}
69
70/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
71static char *strncpy0(char *dest, const char *src, size_t size)
72{
73	strncpy(dest, src, size);
74	dest[size - 1] = '\0';
75	return dest;
76}
77
78/* Emulate the behavior of fgets but on memory */
79static char *memgets(char *str, int num, char **mem, size_t *memsize)
80{
81	char *end;
82	int len;
83	int newline = 1;
84
85	end = memchr(*mem, '\n', *memsize);
86	if (end == NULL) {
87		if (*memsize == 0)
88			return NULL;
89		end = *mem + *memsize;
90		newline = 0;
91	}
92	len = min((int)(end - *mem) + newline, num);
93	memcpy(str, *mem, len);
94	if (len < num)
95		str[len] = '\0';
96
97	/* prepare the mem vars for the next call */
98	*memsize -= (end - *mem) + newline;
99	*mem += (end - *mem) + newline;
100
101	return str;
102}
103
104/* Parse given INI-style file. May have [section]s, name=value pairs
105   (whitespace stripped), and comments starting with ';' (semicolon). Section
106   is "" if name=value pair parsed before any section heading. name:value
107   pairs are also supported as a concession to Python's ConfigParser.
108
109   For each name=value pair parsed, call handler function with given user
110   pointer as well as section, name, and value (data only valid for duration
111   of handler call). Handler should return nonzero on success, zero on error.
112
113   Returns 0 on success, line number of first error on parse error (doesn't
114   stop on first error).
115*/
116static int ini_parse(char *filestart, size_t filelen,
117	int (*handler)(void *, char *, char *, char *),	void *user)
118{
119	/* Uses a fair bit of stack (use heap instead if you need to) */
120	char line[MAX_LINE];
121	char section[MAX_SECTION] = "";
122	char prev_name[MAX_NAME] = "";
123
124	char *curmem = filestart;
125	char *start;
126	char *end;
127	char *name;
128	char *value;
129	size_t memleft = filelen;
130	int lineno = 0;
131	int error = 0;
132
133	/* Scan through file line by line */
134	while (memgets(line, sizeof(line), &curmem, &memleft) != NULL) {
135		lineno++;
136		start = lskip(rstrip(line));
137
138		if (*start == ';' || *start == '#') {
139			/*
140			 * Per Python ConfigParser, allow '#' comments at start
141			 * of line
142			 */
143		}
144#if CONFIG_INI_ALLOW_MULTILINE
145		else if (*prev_name && *start && start > line) {
146			/*
147			 * Non-blank line with leading whitespace, treat as
148			 * continuation of previous name's value (as per Python
149			 * ConfigParser).
150			 */
151			if (!handler(user, section, prev_name, start) && !error)
152				error = lineno;
153		}
154#endif
155		else if (*start == '[') {
156			/* A "[section]" line */
157			end = find_char_or_comment(start + 1, ']');
158			if (*end == ']') {
159				*end = '\0';
160				strncpy0(section, start + 1, sizeof(section));
161				*prev_name = '\0';
162			} else if (!error) {
163				/* No ']' found on section line */
164				error = lineno;
165			}
166		} else if (*start && *start != ';') {
167			/* Not a comment, must be a name[=:]value pair */
168			end = find_char_or_comment(start, '=');
169			if (*end != '=')
170				end = find_char_or_comment(start, ':');
171			if (*end == '=' || *end == ':') {
172				*end = '\0';
173				name = rstrip(start);
174				value = lskip(end + 1);
175				end = find_char_or_comment(value, '\0');
176				if (*end == ';')
177					*end = '\0';
178				rstrip(value);
179				/* Strip double-quotes */
180				if (value[0] == '"' &&
181				    value[strlen(value)-1] == '"') {
182					value[strlen(value)-1] = '\0';
183					value += 1;
184				}
185
186				/*
187				 * Valid name[=:]value pair found, call handler
188				 */
189				strncpy0(prev_name, name, sizeof(prev_name));
190				if (!handler(user, section, name, value) &&
191				     !error)
192					error = lineno;
193			} else if (!error)
194				/* No '=' or ':' found on name[=:]value line */
195				error = lineno;
196		}
197	}
198
199	return error;
200}
201
202static int ini_handler(void *user, char *section, char *name, char *value)
203{
204	char *requested_section = (char *)user;
205#ifdef CONFIG_INI_CASE_INSENSITIVE
206	int i;
207
208	for (i = 0; i < strlen(requested_section); i++)
209		requested_section[i] = tolower(requested_section[i]);
210	for (i = 0; i < strlen(section); i++)
211		section[i] = tolower(section[i]);
212#endif
213
214	if (!strcmp(section, requested_section)) {
215#ifdef CONFIG_INI_CASE_INSENSITIVE
216		for (i = 0; i < strlen(name); i++)
217			name[i] = tolower(name[i]);
218		for (i = 0; i < strlen(value); i++)
219			value[i] = tolower(value[i]);
220#endif
221		env_set(name, value);
222		printf("ini: Imported %s as %s\n", name, value);
223	}
224
225	/* success */
226	return 1;
227}
228
229static int do_ini(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
230{
231	const char *section;
232	char *file_address;
233	size_t file_size;
234
235	if (argc == 1)
236		return CMD_RET_USAGE;
237
238	section = argv[1];
239	file_address = (char *)hextoul(argc < 3 ? env_get("loadaddr") : argv[2],
240					NULL);
241	file_size = (size_t)hextoul(argc < 4 ? env_get("filesize") : argv[3],
242				     NULL);
243
244	return ini_parse(file_address, file_size, ini_handler, (void *)section);
245}
246
247U_BOOT_CMD(
248	ini, 4, 0, do_ini,
249	"parse an ini file in memory and merge the specified section into the env",
250	"section [[file-address] file-size]"
251);
252