Merge branch 'patman' of git://git.denx.de/u-boot-x86
[platform/kernel/u-boot.git] / common / cmd_ini.c
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  * 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:
35  * 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. */
63 static 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. */
73 static 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. */
83 static 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. */
95 static 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 */
103 static 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 */
140 static 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
226 static 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
253 static 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
271 U_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 );