uboot/cmd/ini.c
<<
>>
Prefs
   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. */
  39static 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. */
  49static 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. */
  59static 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. */
  71static 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 */
  79static 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*/
 116static 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
 202static 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
 229static 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 *)hextoul(argc < 3 ? env_get("loadaddr") : argv[2],
 240                                        NULL);
 241        file_size = (size_t)hextoul(argc < 4 ? env_get("filesize") : argv[3],
 242                                     NULL);
 243
 244        return ini_parse(file_address, file_size, ini_handler, (void *)section);
 245}
 246
 247U_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