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 <environment.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(cmd_tbl_t *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 ); 252