blob: 74481cbf6f2f6cff8faa1a87837f43206bd4423c [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 *
Joe Hershberger961c4372012-10-04 09:54:07 +00009 * The "inih" library is distributed under the following license, which is
10 * derived from and very similar to the 3-clause BSD license:
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions are met:
14 * * Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * * Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * * Neither the name of Brush Technology nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY BRUSH TECHNOLOGY ''AS IS'' AND ANY
24 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26 * DISCLAIMED. IN NO EVENT SHALL BRUSH TECHNOLOGY BE LIABLE FOR ANY
27 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
30 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
32 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 *
34 * Go to the project home page for more info:
Joe Hershbergerc167cc02012-10-03 11:15:51 +000035 * http://code.google.com/p/inih/
36 */
37
38#include <common.h>
39#include <command.h>
40#include <environment.h>
41#include <linux/ctype.h>
42#include <linux/string.h>
43
44#ifdef CONFIG_INI_MAX_LINE
45#define MAX_LINE CONFIG_INI_MAX_LINE
46#else
47#define MAX_LINE 200
48#endif
49
50#ifdef CONFIG_INI_MAX_SECTION
51#define MAX_SECTION CONFIG_INI_MAX_SECTION
52#else
53#define MAX_SECTION 50
54#endif
55
56#ifdef CONFIG_INI_MAX_NAME
57#define MAX_NAME CONFIG_INI_MAX_NAME
58#else
59#define MAX_NAME 50
60#endif
61
62/* Strip whitespace chars off end of given string, in place. Return s. */
63static char *rstrip(char *s)
64{
65 char *p = s + strlen(s);
66
67 while (p > s && isspace(*--p))
68 *p = '\0';
69 return s;
70}
71
72/* Return pointer to first non-whitespace char in given string. */
73static char *lskip(const char *s)
74{
75 while (*s && isspace(*s))
76 s++;
77 return (char *)s;
78}
79
80/* Return pointer to first char c or ';' comment in given string, or pointer to
81 null at end of string if neither found. ';' must be prefixed by a whitespace
82 character to register as a comment. */
83static char *find_char_or_comment(const char *s, char c)
84{
85 int was_whitespace = 0;
86
87 while (*s && *s != c && !(was_whitespace && *s == ';')) {
88 was_whitespace = isspace(*s);
89 s++;
90 }
91 return (char *)s;
92}
93
94/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
95static char *strncpy0(char *dest, const char *src, size_t size)
96{
97 strncpy(dest, src, size);
98 dest[size - 1] = '\0';
99 return dest;
100}
101
102/* Emulate the behavior of fgets but on memory */
103static char *memgets(char *str, int num, char **mem, size_t *memsize)
104{
105 char *end;
106 int len;
107 int newline = 1;
108
109 end = memchr(*mem, '\n', *memsize);
110 if (end == NULL) {
111 if (*memsize == 0)
112 return NULL;
113 end = *mem + *memsize;
114 newline = 0;
115 }
116 len = min((end - *mem) + newline, num);
117 memcpy(str, *mem, len);
118 if (len < num)
119 str[len] = '\0';
120
121 /* prepare the mem vars for the next call */
122 *memsize -= (end - *mem) + newline;
123 *mem += (end - *mem) + newline;
124
125 return str;
126}
127
128/* Parse given INI-style file. May have [section]s, name=value pairs
129 (whitespace stripped), and comments starting with ';' (semicolon). Section
130 is "" if name=value pair parsed before any section heading. name:value
131 pairs are also supported as a concession to Python's ConfigParser.
132
133 For each name=value pair parsed, call handler function with given user
134 pointer as well as section, name, and value (data only valid for duration
135 of handler call). Handler should return nonzero on success, zero on error.
136
137 Returns 0 on success, line number of first error on parse error (doesn't
138 stop on first error).
139*/
140static int ini_parse(char *filestart, size_t filelen,
141 int (*handler)(void *, char *, char *, char *), void *user)
142{
143 /* Uses a fair bit of stack (use heap instead if you need to) */
144 char line[MAX_LINE];
145 char section[MAX_SECTION] = "";
146 char prev_name[MAX_NAME] = "";
147
148 char *curmem = filestart;
149 char *start;
150 char *end;
151 char *name;
152 char *value;
153 size_t memleft = filelen;
154 int lineno = 0;
155 int error = 0;
156
157 /* Scan through file line by line */
158 while (memgets(line, sizeof(line), &curmem, &memleft) != NULL) {
159 lineno++;
160 start = lskip(rstrip(line));
161
162 if (*start == ';' || *start == '#') {
163 /*
164 * Per Python ConfigParser, allow '#' comments at start
165 * of line
166 */
167 }
168#if CONFIG_INI_ALLOW_MULTILINE
169 else if (*prev_name && *start && start > line) {
170 /*
171 * Non-blank line with leading whitespace, treat as
172 * continuation of previous name's value (as per Python
173 * ConfigParser).
174 */
175 if (!handler(user, section, prev_name, start) && !error)
176 error = lineno;
177 }
178#endif
179 else if (*start == '[') {
180 /* A "[section]" line */
181 end = find_char_or_comment(start + 1, ']');
182 if (*end == ']') {
183 *end = '\0';
184 strncpy0(section, start + 1, sizeof(section));
185 *prev_name = '\0';
186 } else if (!error) {
187 /* No ']' found on section line */
188 error = lineno;
189 }
190 } else if (*start && *start != ';') {
191 /* Not a comment, must be a name[=:]value pair */
192 end = find_char_or_comment(start, '=');
193 if (*end != '=')
194 end = find_char_or_comment(start, ':');
195 if (*end == '=' || *end == ':') {
196 *end = '\0';
197 name = rstrip(start);
198 value = lskip(end + 1);
199 end = find_char_or_comment(value, '\0');
200 if (*end == ';')
201 *end = '\0';
202 rstrip(value);
203 /* Strip double-quotes */
204 if (value[0] == '"' &&
205 value[strlen(value)-1] == '"') {
206 value[strlen(value)-1] = '\0';
207 value += 1;
208 }
209
210 /*
211 * Valid name[=:]value pair found, call handler
212 */
213 strncpy0(prev_name, name, sizeof(prev_name));
214 if (!handler(user, section, name, value) &&
215 !error)
216 error = lineno;
217 } else if (!error)
218 /* No '=' or ':' found on name[=:]value line */
219 error = lineno;
220 }
221 }
222
223 return error;
224}
225
226static int ini_handler(void *user, char *section, char *name, char *value)
227{
228 char *requested_section = (char *)user;
229#ifdef CONFIG_INI_CASE_INSENSITIVE
230 int i;
231
232 for (i = 0; i < strlen(requested_section); i++)
233 requested_section[i] = tolower(requested_section[i]);
234 for (i = 0; i < strlen(section); i++)
235 section[i] = tolower(section[i]);
236#endif
237
238 if (!strcmp(section, requested_section)) {
239#ifdef CONFIG_INI_CASE_INSENSITIVE
240 for (i = 0; i < strlen(name); i++)
241 name[i] = tolower(name[i]);
242 for (i = 0; i < strlen(value); i++)
243 value[i] = tolower(value[i]);
244#endif
245 setenv(name, value);
246 printf("ini: Imported %s as %s\n", name, value);
247 }
248
249 /* success */
250 return 1;
251}
252
253static int do_ini(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
254{
255 const char *section;
256 char *file_address;
257 size_t file_size;
258
259 if (argc == 1)
260 return CMD_RET_USAGE;
261
262 section = argv[1];
263 file_address = (char *)simple_strtoul(
264 argc < 3 ? getenv("loadaddr") : argv[2], NULL, 16);
265 file_size = (size_t)simple_strtoul(
266 argc < 4 ? getenv("filesize") : argv[3], NULL, 16);
267
268 return ini_parse(file_address, file_size, ini_handler, (void *)section);
269}
270
271U_BOOT_CMD(
272 ini, 4, 0, do_ini,
273 "parse an ini file in memory and merge the specified section into the env",
274 "section [[file-address] file-size]"
275);