]> git.kernelconcepts.de Git - karo-tx-uboot.git/blob - common/cmd_ini.c
Add a new "ini" command
[karo-tx-uboot.git] / common / cmd_ini.c
1 /*
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. */
35 static 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. */
45 static 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. */
55 static 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. */
67 static 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 */
75 static 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 */
112 static 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
198 static 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
225 static 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
243 U_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 );