|  | /* | 
|  | * inih -- simple .INI file parser | 
|  | * | 
|  | * Copyright (c) 2009, Brush Technology | 
|  | * Copyright (c) 2012: | 
|  | *              Joe Hershberger, National Instruments, joe.hershberger@ni.com | 
|  | * All rights reserved. | 
|  | * | 
|  | * SPDX-License-Identifier:	BSD-3-Clause | 
|  | * | 
|  | * Go to the project home page for more info: | 
|  | * http://code.google.com/p/inih/ | 
|  | */ | 
|  |  | 
|  | #include <common.h> | 
|  | #include <command.h> | 
|  | #include <environment.h> | 
|  | #include <linux/ctype.h> | 
|  | #include <linux/string.h> | 
|  |  | 
|  | #ifdef CONFIG_INI_MAX_LINE | 
|  | #define MAX_LINE CONFIG_INI_MAX_LINE | 
|  | #else | 
|  | #define MAX_LINE 200 | 
|  | #endif | 
|  |  | 
|  | #ifdef CONFIG_INI_MAX_SECTION | 
|  | #define MAX_SECTION CONFIG_INI_MAX_SECTION | 
|  | #else | 
|  | #define MAX_SECTION 50 | 
|  | #endif | 
|  |  | 
|  | #ifdef CONFIG_INI_MAX_NAME | 
|  | #define MAX_NAME CONFIG_INI_MAX_NAME | 
|  | #else | 
|  | #define MAX_NAME 50 | 
|  | #endif | 
|  |  | 
|  | /* Strip whitespace chars off end of given string, in place. Return s. */ | 
|  | static char *rstrip(char *s) | 
|  | { | 
|  | char *p = s + strlen(s); | 
|  |  | 
|  | while (p > s && isspace(*--p)) | 
|  | *p = '\0'; | 
|  | return s; | 
|  | } | 
|  |  | 
|  | /* Return pointer to first non-whitespace char in given string. */ | 
|  | static char *lskip(const char *s) | 
|  | { | 
|  | while (*s && isspace(*s)) | 
|  | s++; | 
|  | return (char *)s; | 
|  | } | 
|  |  | 
|  | /* Return pointer to first char c or ';' comment in given string, or pointer to | 
|  | null at end of string if neither found. ';' must be prefixed by a whitespace | 
|  | character to register as a comment. */ | 
|  | static char *find_char_or_comment(const char *s, char c) | 
|  | { | 
|  | int was_whitespace = 0; | 
|  |  | 
|  | while (*s && *s != c && !(was_whitespace && *s == ';')) { | 
|  | was_whitespace = isspace(*s); | 
|  | s++; | 
|  | } | 
|  | return (char *)s; | 
|  | } | 
|  |  | 
|  | /* Version of strncpy that ensures dest (size bytes) is null-terminated. */ | 
|  | static char *strncpy0(char *dest, const char *src, size_t size) | 
|  | { | 
|  | strncpy(dest, src, size); | 
|  | dest[size - 1] = '\0'; | 
|  | return dest; | 
|  | } | 
|  |  | 
|  | /* Emulate the behavior of fgets but on memory */ | 
|  | static char *memgets(char *str, int num, char **mem, size_t *memsize) | 
|  | { | 
|  | char *end; | 
|  | int len; | 
|  | int newline = 1; | 
|  |  | 
|  | end = memchr(*mem, '\n', *memsize); | 
|  | if (end == NULL) { | 
|  | if (*memsize == 0) | 
|  | return NULL; | 
|  | end = *mem + *memsize; | 
|  | newline = 0; | 
|  | } | 
|  | len = min((end - *mem) + newline, num); | 
|  | memcpy(str, *mem, len); | 
|  | if (len < num) | 
|  | str[len] = '\0'; | 
|  |  | 
|  | /* prepare the mem vars for the next call */ | 
|  | *memsize -= (end - *mem) + newline; | 
|  | *mem += (end - *mem) + newline; | 
|  |  | 
|  | return str; | 
|  | } | 
|  |  | 
|  | /* Parse given INI-style file. May have [section]s, name=value pairs | 
|  | (whitespace stripped), and comments starting with ';' (semicolon). Section | 
|  | is "" if name=value pair parsed before any section heading. name:value | 
|  | pairs are also supported as a concession to Python's ConfigParser. | 
|  |  | 
|  | For each name=value pair parsed, call handler function with given user | 
|  | pointer as well as section, name, and value (data only valid for duration | 
|  | of handler call). Handler should return nonzero on success, zero on error. | 
|  |  | 
|  | Returns 0 on success, line number of first error on parse error (doesn't | 
|  | stop on first error). | 
|  | */ | 
|  | static int ini_parse(char *filestart, size_t filelen, | 
|  | int (*handler)(void *, char *, char *, char *),	void *user) | 
|  | { | 
|  | /* Uses a fair bit of stack (use heap instead if you need to) */ | 
|  | char line[MAX_LINE]; | 
|  | char section[MAX_SECTION] = ""; | 
|  | char prev_name[MAX_NAME] = ""; | 
|  |  | 
|  | char *curmem = filestart; | 
|  | char *start; | 
|  | char *end; | 
|  | char *name; | 
|  | char *value; | 
|  | size_t memleft = filelen; | 
|  | int lineno = 0; | 
|  | int error = 0; | 
|  |  | 
|  | /* Scan through file line by line */ | 
|  | while (memgets(line, sizeof(line), &curmem, &memleft) != NULL) { | 
|  | lineno++; | 
|  | start = lskip(rstrip(line)); | 
|  |  | 
|  | if (*start == ';' || *start == '#') { | 
|  | /* | 
|  | * Per Python ConfigParser, allow '#' comments at start | 
|  | * of line | 
|  | */ | 
|  | } | 
|  | #if CONFIG_INI_ALLOW_MULTILINE | 
|  | else if (*prev_name && *start && start > line) { | 
|  | /* | 
|  | * Non-blank line with leading whitespace, treat as | 
|  | * continuation of previous name's value (as per Python | 
|  | * ConfigParser). | 
|  | */ | 
|  | if (!handler(user, section, prev_name, start) && !error) | 
|  | error = lineno; | 
|  | } | 
|  | #endif | 
|  | else if (*start == '[') { | 
|  | /* A "[section]" line */ | 
|  | end = find_char_or_comment(start + 1, ']'); | 
|  | if (*end == ']') { | 
|  | *end = '\0'; | 
|  | strncpy0(section, start + 1, sizeof(section)); | 
|  | *prev_name = '\0'; | 
|  | } else if (!error) { | 
|  | /* No ']' found on section line */ | 
|  | error = lineno; | 
|  | } | 
|  | } else if (*start && *start != ';') { | 
|  | /* Not a comment, must be a name[=:]value pair */ | 
|  | end = find_char_or_comment(start, '='); | 
|  | if (*end != '=') | 
|  | end = find_char_or_comment(start, ':'); | 
|  | if (*end == '=' || *end == ':') { | 
|  | *end = '\0'; | 
|  | name = rstrip(start); | 
|  | value = lskip(end + 1); | 
|  | end = find_char_or_comment(value, '\0'); | 
|  | if (*end == ';') | 
|  | *end = '\0'; | 
|  | rstrip(value); | 
|  | /* Strip double-quotes */ | 
|  | if (value[0] == '"' && | 
|  | value[strlen(value)-1] == '"') { | 
|  | value[strlen(value)-1] = '\0'; | 
|  | value += 1; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Valid name[=:]value pair found, call handler | 
|  | */ | 
|  | strncpy0(prev_name, name, sizeof(prev_name)); | 
|  | if (!handler(user, section, name, value) && | 
|  | !error) | 
|  | error = lineno; | 
|  | } else if (!error) | 
|  | /* No '=' or ':' found on name[=:]value line */ | 
|  | error = lineno; | 
|  | } | 
|  | } | 
|  |  | 
|  | return error; | 
|  | } | 
|  |  | 
|  | static int ini_handler(void *user, char *section, char *name, char *value) | 
|  | { | 
|  | char *requested_section = (char *)user; | 
|  | #ifdef CONFIG_INI_CASE_INSENSITIVE | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < strlen(requested_section); i++) | 
|  | requested_section[i] = tolower(requested_section[i]); | 
|  | for (i = 0; i < strlen(section); i++) | 
|  | section[i] = tolower(section[i]); | 
|  | #endif | 
|  |  | 
|  | if (!strcmp(section, requested_section)) { | 
|  | #ifdef CONFIG_INI_CASE_INSENSITIVE | 
|  | for (i = 0; i < strlen(name); i++) | 
|  | name[i] = tolower(name[i]); | 
|  | for (i = 0; i < strlen(value); i++) | 
|  | value[i] = tolower(value[i]); | 
|  | #endif | 
|  | setenv(name, value); | 
|  | printf("ini: Imported %s as %s\n", name, value); | 
|  | } | 
|  |  | 
|  | /* success */ | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | static int do_ini(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) | 
|  | { | 
|  | const char *section; | 
|  | char *file_address; | 
|  | size_t file_size; | 
|  |  | 
|  | if (argc == 1) | 
|  | return CMD_RET_USAGE; | 
|  |  | 
|  | section = argv[1]; | 
|  | file_address = (char *)simple_strtoul( | 
|  | argc < 3 ? getenv("loadaddr") : argv[2], NULL, 16); | 
|  | file_size = (size_t)simple_strtoul( | 
|  | argc < 4 ? getenv("filesize") : argv[3], NULL, 16); | 
|  |  | 
|  | return ini_parse(file_address, file_size, ini_handler, (void *)section); | 
|  | } | 
|  |  | 
|  | U_BOOT_CMD( | 
|  | ini, 4, 0, do_ini, | 
|  | "parse an ini file in memory and merge the specified section into the env", | 
|  | "section [[file-address] file-size]" | 
|  | ); |