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