xref: /openbmc/u-boot/cmd/ini.c (revision fd0bc623)
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 <environment.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. */
39 static 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. */
49 static 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. */
59 static 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. */
71 static 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 */
79 static 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((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 */
116 static 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 
202 static 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 
229 static int do_ini(cmd_tbl_t *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 *)simple_strtoul(
240 		argc < 3 ? env_get("loadaddr") : argv[2], NULL, 16);
241 	file_size = (size_t)simple_strtoul(
242 		argc < 4 ? env_get("filesize") : argv[3], NULL, 16);
243 
244 	return ini_parse(file_address, file_size, ini_handler, (void *)section);
245 }
246 
247 U_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