1 // SPDX-License-Identifier: BSD-3-Clause
3 * inih -- simple .INI file parser
5 * Copyright (c) 2009, Brush Technology
7 * Joe Hershberger, National Instruments, joe.hershberger@ni.com
10 * Go to the project home page for more info:
11 * http://code.google.com/p/inih/
17 #include <linux/ctype.h>
18 #include <linux/string.h>
20 #ifdef CONFIG_INI_MAX_LINE
21 #define MAX_LINE CONFIG_INI_MAX_LINE
26 #ifdef CONFIG_INI_MAX_SECTION
27 #define MAX_SECTION CONFIG_INI_MAX_SECTION
29 #define MAX_SECTION 50
32 #ifdef CONFIG_INI_MAX_NAME
33 #define MAX_NAME CONFIG_INI_MAX_NAME
38 /* Strip whitespace chars off end of given string, in place. Return s. */
39 static char *rstrip(char *s)
41 char *p = s + strlen(s);
43 while (p > s && isspace(*--p))
48 /* Return pointer to first non-whitespace char in given string. */
49 static char *lskip(const char *s)
51 while (*s && isspace(*s))
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)
61 int was_whitespace = 0;
63 while (*s && *s != c && !(was_whitespace && *s == ';')) {
64 was_whitespace = isspace(*s);
70 /* Version of strncpy that ensures dest (size bytes) is null-terminated. */
71 static char *strncpy0(char *dest, const char *src, size_t size)
73 strncpy(dest, src, size);
74 dest[size - 1] = '\0';
78 /* Emulate the behavior of fgets but on memory */
79 static char *memgets(char *str, int num, char **mem, size_t *memsize)
85 end = memchr(*mem, '\n', *memsize);
89 end = *mem + *memsize;
92 len = min((end - *mem) + newline, num);
93 memcpy(str, *mem, len);
97 /* prepare the mem vars for the next call */
98 *memsize -= (end - *mem) + newline;
99 *mem += (end - *mem) + newline;
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.
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.
113 Returns 0 on success, line number of first error on parse error (doesn't
114 stop on first error).
116 static int ini_parse(char *filestart, size_t filelen,
117 int (*handler)(void *, char *, char *, char *), void *user)
119 /* Uses a fair bit of stack (use heap instead if you need to) */
121 char section[MAX_SECTION] = "";
122 char prev_name[MAX_NAME] = "";
124 char *curmem = filestart;
129 size_t memleft = filelen;
133 /* Scan through file line by line */
134 while (memgets(line, sizeof(line), &curmem, &memleft) != NULL) {
136 start = lskip(rstrip(line));
138 if (*start == ';' || *start == '#') {
140 * Per Python ConfigParser, allow '#' comments at start
144 #if CONFIG_INI_ALLOW_MULTILINE
145 else if (*prev_name && *start && start > line) {
147 * Non-blank line with leading whitespace, treat as
148 * continuation of previous name's value (as per Python
151 if (!handler(user, section, prev_name, start) && !error)
155 else if (*start == '[') {
156 /* A "[section]" line */
157 end = find_char_or_comment(start + 1, ']');
160 strncpy0(section, start + 1, sizeof(section));
163 /* No ']' found on section line */
166 } else if (*start && *start != ';') {
167 /* Not a comment, must be a name[=:]value pair */
168 end = find_char_or_comment(start, '=');
170 end = find_char_or_comment(start, ':');
171 if (*end == '=' || *end == ':') {
173 name = rstrip(start);
174 value = lskip(end + 1);
175 end = find_char_or_comment(value, '\0');
179 /* Strip double-quotes */
180 if (value[0] == '"' &&
181 value[strlen(value)-1] == '"') {
182 value[strlen(value)-1] = '\0';
187 * Valid name[=:]value pair found, call handler
189 strncpy0(prev_name, name, sizeof(prev_name));
190 if (!handler(user, section, name, value) &&
194 /* No '=' or ':' found on name[=:]value line */
202 static int ini_handler(void *user, char *section, char *name, char *value)
204 char *requested_section = (char *)user;
205 #ifdef CONFIG_INI_CASE_INSENSITIVE
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]);
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]);
221 env_set(name, value);
222 printf("ini: Imported %s as %s\n", name, value);
229 static int do_ini(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
236 return CMD_RET_USAGE;
239 file_address = (char *)hextoul(argc < 3 ? env_get("loadaddr") : argv[2],
241 file_size = (size_t)hextoul(argc < 4 ? env_get("filesize") : argv[3],
244 return ini_parse(file_address, file_size, ini_handler, (void *)section);
249 "parse an ini file in memory and merge the specified section into the env",
250 "section [[file-address] file-size]"