blob: 8656299447b6b6b35ba53b8ccb3cdbaf23e4ac73 [file] [log] [blame]
Joe Hershbergerc167cc02012-10-03 11:15:51 +00001/*
2 * inih -- simple .INI file parser
3 *
Joe Hershberger961c4372012-10-04 09:54:07 +00004 * Copyright (c) 2009, Brush Technology
5 * Copyright (c) 2012:
6 * Joe Hershberger, National Instruments, joe.hershberger@ni.com
7 * All rights reserved.
Joe Hershbergerc167cc02012-10-03 11:15:51 +00008 *
Wolfgang Denkcb3761e2013-07-28 22:12:47 +02009 * SPDX-License-Identifier: BSD-3-Clause
Joe Hershberger961c4372012-10-04 09:54:07 +000010 *
11 * Go to the project home page for more info:
Joe Hershbergerc167cc02012-10-03 11:15:51 +000012 * 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. */
40static 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. */
50static 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. */
60static 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. */
72static 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 */
80static 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*/
117static 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
203static 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
Simon Glass382bee52017-08-03 12:22:09 -0600222 env_set(name, value);
Joe Hershbergerc167cc02012-10-03 11:15:51 +0000223 printf("ini: Imported %s as %s\n", name, value);
224 }
225
226 /* success */
227 return 1;
228}
229
230static 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(
Simon Glass00caae62017-08-03 12:22:12 -0600241 argc < 3 ? env_get("loadaddr") : argv[2], NULL, 16);
Joe Hershbergerc167cc02012-10-03 11:15:51 +0000242 file_size = (size_t)simple_strtoul(
Simon Glass00caae62017-08-03 12:22:12 -0600243 argc < 4 ? env_get("filesize") : argv[3], NULL, 16);
Joe Hershbergerc167cc02012-10-03 11:15:51 +0000244
245 return ini_parse(file_address, file_size, ini_handler, (void *)section);
246}
247
248U_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);