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