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