[09a8006] | 1 | /*
|
---|
| 2 | * Copyright (c) 2015 Michal Koutny
|
---|
| 3 | * All rights reserved.
|
---|
| 4 | *
|
---|
| 5 | * Redistribution and use in source and binary forms, with or without
|
---|
| 6 | * modification, are permitted provided that the following conditions
|
---|
| 7 | * are met:
|
---|
| 8 | *
|
---|
| 9 | * - Redistributions of source code must retain the above copyright
|
---|
| 10 | * notice, this list of conditions and the following disclaimer.
|
---|
| 11 | * - Redistributions in binary form must reproduce the above copyright
|
---|
| 12 | * notice, this list of conditions and the following disclaimer in the
|
---|
| 13 | * documentation and/or other materials provided with the distribution.
|
---|
| 14 | * - The name of the author may not be used to endorse or promote products
|
---|
| 15 | * derived from this software without specific prior written permission.
|
---|
| 16 | *
|
---|
| 17 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AS IS'' AND ANY EXPRESS OR
|
---|
| 18 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
---|
| 19 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
---|
| 20 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
---|
| 21 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
---|
| 22 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
---|
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
---|
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
---|
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
---|
| 26 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
---|
| 27 | */
|
---|
| 28 |
|
---|
[6006f35] | 29 | #include <adt/hash.h>
|
---|
| 30 | #include <adt/hash_table.h>
|
---|
| 31 | #include <assert.h>
|
---|
| 32 | #include <errno.h>
|
---|
| 33 | #include <stdio.h>
|
---|
| 34 | #include <stdlib.h>
|
---|
| 35 | #include <str.h>
|
---|
| 36 |
|
---|
| 37 | #include "conf/ini.h"
|
---|
| 38 |
|
---|
| 39 | #define LINE_BUFFER 256
|
---|
| 40 |
|
---|
| 41 | /** Representation of key-value pair from INI file
|
---|
| 42 | *
|
---|
| 43 | * @note Structure is owner of its string data.
|
---|
| 44 | */
|
---|
| 45 | typedef struct {
|
---|
| 46 | ht_link_t ht_link;
|
---|
| 47 |
|
---|
| 48 | /** First line from where the item was extracted. */
|
---|
| 49 | size_t lineno;
|
---|
| 50 |
|
---|
| 51 | char *key;
|
---|
| 52 | char *value;
|
---|
| 53 | } ini_item_t;
|
---|
| 54 |
|
---|
| 55 |
|
---|
| 56 | /* Necessary forward declarations */
|
---|
| 57 | static void ini_section_destroy(ini_section_t **);
|
---|
| 58 | static void ini_item_destroy(ini_item_t **);
|
---|
| 59 |
|
---|
| 60 | /* Hash table functions */
|
---|
| 61 | static size_t ini_section_ht_hash(const ht_link_t *item)
|
---|
| 62 | {
|
---|
| 63 | ini_section_t *section =
|
---|
| 64 | hash_table_get_inst(item, ini_section_t, ht_link);
|
---|
| 65 | /* Nameless default section */
|
---|
| 66 | if (section->name == NULL) {
|
---|
| 67 | return 0;
|
---|
| 68 | }
|
---|
| 69 |
|
---|
| 70 | return hash_string(section->name);
|
---|
| 71 | }
|
---|
| 72 |
|
---|
| 73 | static size_t ini_section_ht_key_hash(void *key)
|
---|
| 74 | {
|
---|
| 75 | return hash_string((const char *)key);
|
---|
| 76 | }
|
---|
| 77 |
|
---|
| 78 | static bool ini_section_ht_equal(const ht_link_t *item1, const ht_link_t *item2)
|
---|
| 79 | {
|
---|
| 80 | return str_cmp(
|
---|
| 81 | hash_table_get_inst(item1, ini_section_t, ht_link)->name,
|
---|
| 82 | hash_table_get_inst(item2, ini_section_t, ht_link)->name) == 0;
|
---|
| 83 | }
|
---|
| 84 |
|
---|
| 85 | static bool ini_section_ht_key_equal(void *key, const ht_link_t *item)
|
---|
| 86 | {
|
---|
| 87 | return str_cmp((const char *)key,
|
---|
| 88 | hash_table_get_inst(item, ini_section_t, ht_link)->name) == 0;
|
---|
| 89 | }
|
---|
| 90 |
|
---|
| 91 | static void ini_section_ht_remove(ht_link_t *item)
|
---|
| 92 | {
|
---|
| 93 | ini_section_t *section = hash_table_get_inst(item, ini_section_t, ht_link);
|
---|
| 94 | ini_section_destroy(§ion);
|
---|
| 95 | }
|
---|
| 96 |
|
---|
| 97 |
|
---|
| 98 | static size_t ini_item_ht_hash(const ht_link_t *item)
|
---|
| 99 | {
|
---|
| 100 | ini_item_t *ini_item =
|
---|
| 101 | hash_table_get_inst(item, ini_item_t, ht_link);
|
---|
| 102 | assert(ini_item->key);
|
---|
| 103 | return hash_string(ini_item->key);
|
---|
| 104 | }
|
---|
| 105 |
|
---|
| 106 | static size_t ini_item_ht_key_hash(void *key)
|
---|
| 107 | {
|
---|
| 108 | return hash_string((const char *)key);
|
---|
| 109 | }
|
---|
| 110 |
|
---|
| 111 | static bool ini_item_ht_equal(const ht_link_t *item1, const ht_link_t *item2)
|
---|
| 112 | {
|
---|
| 113 | return str_cmp(
|
---|
| 114 | hash_table_get_inst(item1, ini_item_t, ht_link)->key,
|
---|
| 115 | hash_table_get_inst(item2, ini_item_t, ht_link)->key) == 0;
|
---|
| 116 | }
|
---|
| 117 |
|
---|
| 118 | static bool ini_item_ht_key_equal(void *key, const ht_link_t *item)
|
---|
| 119 | {
|
---|
| 120 | return str_cmp((const char *)key,
|
---|
| 121 | hash_table_get_inst(item, ini_item_t, ht_link)->key) == 0;
|
---|
| 122 | }
|
---|
| 123 |
|
---|
| 124 | static void ini_item_ht_remove(ht_link_t *item)
|
---|
| 125 | {
|
---|
| 126 | ini_item_t *ini_item = hash_table_get_inst(item, ini_item_t, ht_link);
|
---|
| 127 | ini_item_destroy(&ini_item);
|
---|
| 128 | }
|
---|
| 129 |
|
---|
| 130 |
|
---|
| 131 | static hash_table_ops_t configuration_ht_ops = {
|
---|
| 132 | .hash = &ini_section_ht_hash,
|
---|
| 133 | .key_hash = &ini_section_ht_key_hash,
|
---|
| 134 | .equal = &ini_section_ht_equal,
|
---|
| 135 | .key_equal = &ini_section_ht_key_equal,
|
---|
| 136 | .remove_callback = &ini_section_ht_remove
|
---|
| 137 | };
|
---|
| 138 |
|
---|
| 139 | static hash_table_ops_t section_ht_ops = {
|
---|
| 140 | .hash = &ini_item_ht_hash,
|
---|
| 141 | .key_hash = &ini_item_ht_key_hash,
|
---|
| 142 | .equal = &ini_item_ht_equal,
|
---|
| 143 | .key_equal = &ini_item_ht_key_equal,
|
---|
| 144 | .remove_callback = &ini_item_ht_remove
|
---|
| 145 | };
|
---|
| 146 |
|
---|
| 147 | /* Actual INI functions */
|
---|
| 148 |
|
---|
| 149 | void ini_configuration_init(ini_configuration_t *conf)
|
---|
| 150 | {
|
---|
| 151 | hash_table_create(&conf->sections, 0, 0, &configuration_ht_ops);
|
---|
| 152 | }
|
---|
| 153 |
|
---|
| 154 | /** INI configuration destructor
|
---|
| 155 | *
|
---|
| 156 | * Release all resources of INI structure but the structure itself.
|
---|
| 157 | */
|
---|
| 158 | void ini_configuration_deinit(ini_configuration_t *conf)
|
---|
| 159 | {
|
---|
| 160 | hash_table_destroy(&conf->sections);
|
---|
| 161 | }
|
---|
| 162 |
|
---|
| 163 | static void ini_section_init(ini_section_t *section)
|
---|
| 164 | {
|
---|
| 165 | hash_table_create(§ion->items, 0, 0, §ion_ht_ops);
|
---|
| 166 | section->name = NULL;
|
---|
| 167 | }
|
---|
| 168 |
|
---|
| 169 | static ini_section_t* ini_section_create(void)
|
---|
| 170 | {
|
---|
| 171 | ini_section_t *section = malloc(sizeof(ini_section_t));
|
---|
| 172 | if (section != NULL) {
|
---|
| 173 | ini_section_init(section);
|
---|
| 174 | }
|
---|
| 175 | return section;
|
---|
| 176 | }
|
---|
| 177 |
|
---|
| 178 | static void ini_section_destroy(ini_section_t **section_ptr)
|
---|
| 179 | {
|
---|
| 180 | ini_section_t *section = *section_ptr;
|
---|
| 181 | if (section == NULL) {
|
---|
| 182 | return;
|
---|
| 183 | }
|
---|
| 184 | hash_table_destroy(§ion->items);
|
---|
| 185 | free(section->name);
|
---|
| 186 | free(section);
|
---|
| 187 | *section_ptr = NULL;
|
---|
| 188 | }
|
---|
| 189 |
|
---|
| 190 | static void ini_item_init(ini_item_t *item)
|
---|
| 191 | {
|
---|
| 192 | item->key = NULL;
|
---|
| 193 | item->value = NULL;
|
---|
| 194 | }
|
---|
| 195 |
|
---|
| 196 | static ini_item_t *ini_item_create(void)
|
---|
| 197 | {
|
---|
| 198 | ini_item_t *item = malloc(sizeof(ini_item_t));
|
---|
| 199 | if (item != NULL) {
|
---|
| 200 | ini_item_init(item);
|
---|
| 201 | }
|
---|
| 202 | return item;
|
---|
| 203 | }
|
---|
| 204 |
|
---|
| 205 | static void ini_item_destroy(ini_item_t **item_ptr)
|
---|
| 206 | {
|
---|
| 207 | ini_item_t *item = *item_ptr;
|
---|
| 208 | if (item == NULL) {
|
---|
| 209 | return;
|
---|
| 210 | }
|
---|
| 211 | free(item->key);
|
---|
| 212 | free(item->value);
|
---|
| 213 | free(item);
|
---|
| 214 | *item_ptr = NULL;
|
---|
| 215 | }
|
---|
| 216 |
|
---|
| 217 | /** Parse file contents to INI structure
|
---|
| 218 | *
|
---|
| 219 | * @param[in] filename
|
---|
| 220 | * @param[out] conf initialized structure for configuration
|
---|
| 221 | * @param[out] parse initialized structure to keep parsing errors
|
---|
| 222 | *
|
---|
| 223 | * @return EOK on success
|
---|
| 224 | * @return EIO when file cannot be opened
|
---|
| 225 | * @return ENOMEM
|
---|
| 226 | * @return EINVAL on parse error (details in parse structure)
|
---|
| 227 | */
|
---|
| 228 | int ini_parse_file(const char *filename, ini_configuration_t *conf,
|
---|
| 229 | text_parse_t *parse)
|
---|
| 230 | {
|
---|
| 231 | int rc = EOK;
|
---|
| 232 | FILE *f = NULL;
|
---|
| 233 | char *line_buffer = NULL;
|
---|
| 234 |
|
---|
| 235 | f = fopen(filename, "r");
|
---|
| 236 | if (f == NULL) {
|
---|
| 237 | rc = EIO;
|
---|
| 238 | goto finish;
|
---|
| 239 | }
|
---|
| 240 |
|
---|
| 241 | line_buffer = malloc(LINE_BUFFER);
|
---|
| 242 | if (line_buffer == NULL) {
|
---|
| 243 | rc = ENOMEM;
|
---|
| 244 | goto finish;
|
---|
| 245 | }
|
---|
| 246 |
|
---|
| 247 | char *line = NULL;
|
---|
| 248 | ini_section_t *cur_section = NULL;
|
---|
| 249 | size_t lineno = 0;
|
---|
| 250 |
|
---|
| 251 | while ((line = fgets(line_buffer, LINE_BUFFER - 1, f))) {
|
---|
| 252 | ++lineno;
|
---|
| 253 | size_t line_len = str_size(line);
|
---|
| 254 | if (line[line_len - 1] != '\n') {
|
---|
| 255 | text_parse_raise_error(parse, lineno, INI_ETOO_LONG);
|
---|
| 256 | rc = EINVAL;
|
---|
| 257 | /* Cannot recover terminate parsing */
|
---|
| 258 | goto finish;
|
---|
| 259 | }
|
---|
| 260 | /* Ingore leading/trailing whitespace */
|
---|
| 261 | str_rtrim(line, '\n');
|
---|
| 262 | str_ltrim(line, ' ');
|
---|
| 263 | str_rtrim(line, ' ');
|
---|
| 264 |
|
---|
| 265 | /* Empty line */
|
---|
| 266 | if (str_size(line) == 0) {
|
---|
| 267 | continue;
|
---|
| 268 | }
|
---|
| 269 |
|
---|
| 270 | /* Comment line */
|
---|
| 271 | if (line[0] == ';' || line[0] == '#') {
|
---|
| 272 | continue;
|
---|
| 273 | }
|
---|
| 274 |
|
---|
| 275 | /* Start new section */
|
---|
| 276 | if (line[0] == '[') {
|
---|
| 277 | cur_section = ini_section_create();
|
---|
| 278 | if (cur_section == NULL) {
|
---|
| 279 | rc = ENOMEM;
|
---|
| 280 | goto finish;
|
---|
| 281 | }
|
---|
| 282 |
|
---|
| 283 | char *close_bracket = str_chr(line, ']');
|
---|
| 284 | if (close_bracket == NULL) {
|
---|
| 285 | ini_section_destroy(&cur_section);
|
---|
| 286 | text_parse_raise_error(parse, lineno,
|
---|
| 287 | INI_EBRACKET_EXPECTED);
|
---|
| 288 | rc = EINVAL;
|
---|
| 289 | goto finish;
|
---|
| 290 | }
|
---|
| 291 |
|
---|
| 292 | cur_section->lineno = lineno;
|
---|
| 293 | *close_bracket = '\0';
|
---|
| 294 | cur_section->name = str_dup(line + 1);
|
---|
| 295 |
|
---|
| 296 | if (!hash_table_insert_unique(&conf->sections,
|
---|
| 297 | &cur_section->ht_link)) {
|
---|
| 298 | ini_section_destroy(&cur_section);
|
---|
| 299 | text_parse_raise_error(parse, lineno,
|
---|
| 300 | INI_EDUP_SECTION);
|
---|
| 301 | rc = EINVAL;
|
---|
| 302 | goto finish;
|
---|
| 303 | }
|
---|
| 304 |
|
---|
| 305 | continue;
|
---|
| 306 | }
|
---|
| 307 |
|
---|
| 308 | /* Create a default section if none was specified */
|
---|
| 309 | if (cur_section == NULL) {
|
---|
| 310 | cur_section = ini_section_create();
|
---|
| 311 | if (cur_section == NULL) {
|
---|
| 312 | rc = ENOMEM;
|
---|
| 313 | goto finish;
|
---|
| 314 | }
|
---|
| 315 | cur_section->lineno = lineno;
|
---|
| 316 |
|
---|
| 317 | bool inserted = hash_table_insert_unique(&conf->sections,
|
---|
| 318 | &cur_section->ht_link);
|
---|
| 319 | assert(inserted);
|
---|
| 320 | }
|
---|
| 321 |
|
---|
| 322 | /* Parse key-value pairs */
|
---|
| 323 | ini_item_t *item = ini_item_create();
|
---|
| 324 | if (item == NULL) {
|
---|
| 325 | rc = ENOMEM;
|
---|
| 326 | goto finish;
|
---|
| 327 | }
|
---|
| 328 | item->lineno = lineno;
|
---|
| 329 |
|
---|
| 330 | char *assign_char = str_chr(line, '=');
|
---|
| 331 | if (assign_char == NULL) {
|
---|
| 332 | rc = EINVAL;
|
---|
| 333 | text_parse_raise_error(parse, lineno,
|
---|
| 334 | INI_EASSIGN_EXPECTED);
|
---|
| 335 | goto finish;
|
---|
| 336 | }
|
---|
| 337 |
|
---|
| 338 | *assign_char = '\0';
|
---|
| 339 | char *key = line;
|
---|
| 340 | str_ltrim(key, ' ');
|
---|
| 341 | str_rtrim(key, ' ');
|
---|
| 342 | item->key = str_dup(key);
|
---|
| 343 | if (item->key == NULL) {
|
---|
| 344 | ini_item_destroy(&item);
|
---|
| 345 | rc = ENOMEM;
|
---|
| 346 | goto finish;
|
---|
| 347 | }
|
---|
| 348 |
|
---|
| 349 | char *value = assign_char + 1;
|
---|
| 350 | str_ltrim(value, ' ');
|
---|
| 351 | str_rtrim(value, ' ');
|
---|
| 352 | item->value = str_dup(value);
|
---|
| 353 | if (item->value == NULL) {
|
---|
| 354 | ini_item_destroy(&item);
|
---|
| 355 | rc = ENOMEM;
|
---|
| 356 | goto finish;
|
---|
| 357 | }
|
---|
| 358 |
|
---|
| 359 | hash_table_insert(&cur_section->items, &item->ht_link);
|
---|
| 360 | }
|
---|
| 361 |
|
---|
| 362 | finish:
|
---|
| 363 | if (f) {
|
---|
| 364 | fclose(f);
|
---|
| 365 | }
|
---|
| 366 | free(line_buffer);
|
---|
| 367 |
|
---|
| 368 | return rc;
|
---|
| 369 | }
|
---|
| 370 |
|
---|
| 371 |
|
---|
| 372 | /** Get a section from configuration
|
---|
| 373 | *
|
---|
| 374 | * @param[in] ini_configuration
|
---|
| 375 | * @param[in] section_name name of section or NULL for default section
|
---|
| 376 | *
|
---|
| 377 | * @return Section with given name
|
---|
| 378 | * @return NULL when no such section exits
|
---|
| 379 | */
|
---|
| 380 | ini_section_t *ini_get_section(ini_configuration_t *ini_conf,
|
---|
| 381 | const char *section_name)
|
---|
| 382 | {
|
---|
| 383 | ht_link_t *item = hash_table_find(&ini_conf->sections,
|
---|
| 384 | (void *)section_name);
|
---|
| 385 | if (item == NULL) {
|
---|
| 386 | return NULL;
|
---|
| 387 | }
|
---|
| 388 |
|
---|
| 389 | return hash_table_get_inst(item, ini_section_t, ht_link);
|
---|
| 390 | }
|
---|
| 391 |
|
---|
| 392 | /** Get item iterator to items with given key in the section
|
---|
| 393 | *
|
---|
| 394 | * @param[in] section
|
---|
| 395 | * @param[in] key
|
---|
| 396 | *
|
---|
| 397 | * @return Always return iterator (even when there's no item with given key)
|
---|
| 398 | */
|
---|
| 399 | ini_item_iterator_t ini_section_get_iterator(ini_section_t *section,
|
---|
| 400 | const char *key)
|
---|
| 401 | {
|
---|
| 402 | ini_item_iterator_t result;
|
---|
| 403 | result.first_item = hash_table_find(§ion->items, (void *)key);
|
---|
| 404 | result.cur_item = result.first_item;
|
---|
| 405 | result.table = §ion->items;
|
---|
| 406 | result.incremented = false;
|
---|
| 407 |
|
---|
| 408 | return result;
|
---|
| 409 | }
|
---|
| 410 |
|
---|
| 411 | bool ini_item_iterator_valid(ini_item_iterator_t *iterator)
|
---|
| 412 | {
|
---|
| 413 | bool empty = (iterator->cur_item != NULL);
|
---|
| 414 | bool looped = (iterator->cur_item == iterator->first_item);
|
---|
| 415 | return empty || (looped && iterator->incremented);
|
---|
| 416 | }
|
---|
| 417 |
|
---|
| 418 | /** Move iterator to next item (of the same key)
|
---|
| 419 | *
|
---|
| 420 | * @param[in] iterator valid iterator
|
---|
| 421 | */
|
---|
| 422 | void ini_item_iterator_inc(ini_item_iterator_t *iterator)
|
---|
| 423 | {
|
---|
| 424 | iterator->cur_item =
|
---|
| 425 | hash_table_find_next(iterator->table, iterator->cur_item);
|
---|
| 426 | iterator->incremented = true;
|
---|
| 427 | }
|
---|
| 428 |
|
---|
| 429 | /** Get item value for current iterator
|
---|
| 430 | *
|
---|
| 431 | * @param[in] iterator valid iterator
|
---|
| 432 | */
|
---|
| 433 | const char *ini_item_iterator_value(ini_item_iterator_t *iterator)
|
---|
| 434 | {
|
---|
| 435 | ini_item_t *ini_item =
|
---|
| 436 | hash_table_get_inst(iterator->cur_item, ini_item_t, ht_link);
|
---|
| 437 | return ini_item->value;
|
---|
| 438 | }
|
---|
| 439 |
|
---|
| 440 | /** Get item line number for current iterator
|
---|
| 441 | *
|
---|
| 442 | * @param[in] iterator valid iterator
|
---|
| 443 | *
|
---|
| 444 | * @return Line number of input where item was originally defined.
|
---|
| 445 | */
|
---|
| 446 | size_t ini_item_iterator_lineno(ini_item_iterator_t *iterator)
|
---|
| 447 | {
|
---|
| 448 | ini_item_t *ini_item =
|
---|
| 449 | hash_table_get_inst(iterator->cur_item, ini_item_t, ht_link);
|
---|
| 450 | return ini_item->lineno;
|
---|
| 451 | }
|
---|