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