Home | History | Annotate | Download | only in src
      1 /******************************************************************************
      2  *
      3  *  Copyright (C) 2014 Google, Inc.
      4  *
      5  *  Licensed under the Apache License, Version 2.0 (the "License");
      6  *  you may not use this file except in compliance with the License.
      7  *  You may obtain a copy of the License at:
      8  *
      9  *  http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  *
     17  ******************************************************************************/
     18 
     19 #define LOG_TAG "bt_osi_config"
     20 
     21 #include <assert.h>
     22 #include <ctype.h>
     23 #include <errno.h>
     24 #include <stdio.h>
     25 #include <stdlib.h>
     26 #include <string.h>
     27 #include <sys/stat.h>
     28 
     29 #include "osi/include/allocator.h"
     30 #include "osi/include/config.h"
     31 #include "osi/include/list.h"
     32 #include "osi/include/log.h"
     33 
     34 typedef struct {
     35   char *key;
     36   char *value;
     37 } entry_t;
     38 
     39 typedef struct {
     40   char *name;
     41   list_t *entries;
     42 } section_t;
     43 
     44 struct config_t {
     45   list_t *sections;
     46 };
     47 
     48 // Empty definition; this type is aliased to list_node_t.
     49 struct config_section_iter_t {};
     50 
     51 static void config_parse(FILE *fp, config_t *config);
     52 
     53 static section_t *section_new(const char *name);
     54 static void section_free(void *ptr);
     55 static section_t *section_find(const config_t *config, const char *section);
     56 
     57 static entry_t *entry_new(const char *key, const char *value);
     58 static void entry_free(void *ptr);
     59 static entry_t *entry_find(const config_t *config, const char *section, const char *key);
     60 
     61 config_t *config_new_empty(void) {
     62   config_t *config = osi_calloc(sizeof(config_t));
     63   if (!config) {
     64     LOG_ERROR("%s unable to allocate memory for config_t.", __func__);
     65     goto error;
     66   }
     67 
     68   config->sections = list_new(section_free);
     69   if (!config->sections) {
     70     LOG_ERROR("%s unable to allocate list for sections.", __func__);
     71     goto error;
     72   }
     73 
     74   return config;
     75 
     76 error:;
     77   config_free(config);
     78   return NULL;
     79 }
     80 
     81 config_t *config_new(const char *filename) {
     82   assert(filename != NULL);
     83 
     84   config_t *config = config_new_empty();
     85   if (!config)
     86     return NULL;
     87 
     88   FILE *fp = fopen(filename, "rt");
     89   if (!fp) {
     90     LOG_ERROR("%s unable to open file '%s': %s", __func__, filename, strerror(errno));
     91     config_free(config);
     92     return NULL;
     93   }
     94   config_parse(fp, config);
     95   fclose(fp);
     96   return config;
     97 }
     98 
     99 void config_free(config_t *config) {
    100   if (!config)
    101     return;
    102 
    103   list_free(config->sections);
    104   osi_free(config);
    105 }
    106 
    107 bool config_has_section(const config_t *config, const char *section) {
    108   assert(config != NULL);
    109   assert(section != NULL);
    110 
    111   return (section_find(config, section) != NULL);
    112 }
    113 
    114 bool config_has_key(const config_t *config, const char *section, const char *key) {
    115   assert(config != NULL);
    116   assert(section != NULL);
    117   assert(key != NULL);
    118 
    119   return (entry_find(config, section, key) != NULL);
    120 }
    121 
    122 int config_get_int(const config_t *config, const char *section, const char *key, int def_value) {
    123   assert(config != NULL);
    124   assert(section != NULL);
    125   assert(key != NULL);
    126 
    127   entry_t *entry = entry_find(config, section, key);
    128   if (!entry)
    129     return def_value;
    130 
    131   char *endptr;
    132   int ret = strtol(entry->value, &endptr, 0);
    133   return (*endptr == '\0') ? ret : def_value;
    134 }
    135 
    136 bool config_get_bool(const config_t *config, const char *section, const char *key, bool def_value) {
    137   assert(config != NULL);
    138   assert(section != NULL);
    139   assert(key != NULL);
    140 
    141   entry_t *entry = entry_find(config, section, key);
    142   if (!entry)
    143     return def_value;
    144 
    145   if (!strcmp(entry->value, "true"))
    146     return true;
    147   if (!strcmp(entry->value, "false"))
    148     return false;
    149 
    150   return def_value;
    151 }
    152 
    153 const char *config_get_string(const config_t *config, const char *section, const char *key, const char *def_value) {
    154   assert(config != NULL);
    155   assert(section != NULL);
    156   assert(key != NULL);
    157 
    158   entry_t *entry = entry_find(config, section, key);
    159   if (!entry)
    160     return def_value;
    161 
    162   return entry->value;
    163 }
    164 
    165 void config_set_int(config_t *config, const char *section, const char *key, int value) {
    166   assert(config != NULL);
    167   assert(section != NULL);
    168   assert(key != NULL);
    169 
    170   char value_str[32] = { 0 };
    171   sprintf(value_str, "%d", value);
    172   config_set_string(config, section, key, value_str);
    173 }
    174 
    175 void config_set_bool(config_t *config, const char *section, const char *key, bool value) {
    176   assert(config != NULL);
    177   assert(section != NULL);
    178   assert(key != NULL);
    179 
    180   config_set_string(config, section, key, value ? "true" : "false");
    181 }
    182 
    183 void config_set_string(config_t *config, const char *section, const char *key, const char *value) {
    184   section_t *sec = section_find(config, section);
    185   if (!sec) {
    186     sec = section_new(section);
    187     list_append(config->sections, sec);
    188   }
    189 
    190   for (const list_node_t *node = list_begin(sec->entries); node != list_end(sec->entries); node = list_next(node)) {
    191     entry_t *entry = list_node(node);
    192     if (!strcmp(entry->key, key)) {
    193       osi_free(entry->value);
    194       entry->value = osi_strdup(value);
    195       return;
    196     }
    197   }
    198 
    199   entry_t *entry = entry_new(key, value);
    200   list_append(sec->entries, entry);
    201 }
    202 
    203 bool config_remove_section(config_t *config, const char *section) {
    204   assert(config != NULL);
    205   assert(section != NULL);
    206 
    207   section_t *sec = section_find(config, section);
    208   if (!sec)
    209     return false;
    210 
    211   return list_remove(config->sections, sec);
    212 }
    213 
    214 bool config_remove_key(config_t *config, const char *section, const char *key) {
    215   assert(config != NULL);
    216   assert(section != NULL);
    217   assert(key != NULL);
    218 
    219   section_t *sec = section_find(config, section);
    220   entry_t *entry = entry_find(config, section, key);
    221   if (!sec || !entry)
    222     return false;
    223 
    224   return list_remove(sec->entries, entry);
    225 }
    226 
    227 const config_section_node_t *config_section_begin(const config_t *config) {
    228   assert(config != NULL);
    229   return (const config_section_node_t *)list_begin(config->sections);
    230 }
    231 
    232 const config_section_node_t *config_section_end(const config_t *config) {
    233   assert(config != NULL);
    234   return (const config_section_node_t *)list_end(config->sections);
    235 }
    236 
    237 const config_section_node_t *config_section_next(const config_section_node_t *node) {
    238   assert(node != NULL);
    239   return (const config_section_node_t *)list_next((const list_node_t *)node);
    240 }
    241 
    242 const char *config_section_name(const config_section_node_t *node) {
    243   assert(node != NULL);
    244   const list_node_t *lnode = (const list_node_t *)node;
    245   const section_t *section = (const section_t *)list_node(lnode);
    246   return section->name;
    247 }
    248 
    249 bool config_save(const config_t *config, const char *filename) {
    250   assert(config != NULL);
    251   assert(filename != NULL);
    252   assert(*filename != '\0');
    253 
    254   char *temp_filename = osi_calloc(strlen(filename) + 5);
    255   if (!temp_filename) {
    256     LOG_ERROR("%s unable to allocate memory for filename.", __func__);
    257     return false;
    258   }
    259 
    260   strcpy(temp_filename, filename);
    261   strcat(temp_filename, ".new");
    262 
    263   FILE *fp = fopen(temp_filename, "wt");
    264   if (!fp) {
    265     LOG_ERROR("%s unable to write file '%s': %s", __func__, temp_filename, strerror(errno));
    266     goto error;
    267   }
    268 
    269   for (const list_node_t *node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) {
    270     const section_t *section = (const section_t *)list_node(node);
    271     fprintf(fp, "[%s]\n", section->name);
    272 
    273     for (const list_node_t *enode = list_begin(section->entries); enode != list_end(section->entries); enode = list_next(enode)) {
    274       const entry_t *entry = (const entry_t *)list_node(enode);
    275       fprintf(fp, "%s = %s\n", entry->key, entry->value);
    276     }
    277 
    278     // Only add a separating newline if there are more sections.
    279     if (list_next(node) != list_end(config->sections))
    280       fputc('\n', fp);
    281   }
    282 
    283   fflush(fp);
    284   fclose(fp);
    285 
    286   // Change the file's permissions to Read/Write by User and Group
    287   if (chmod(temp_filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) == -1) {
    288     LOG_ERROR("%s unable to change file permissions '%s': %s", __func__, filename, strerror(errno));
    289     goto error;
    290   }
    291 
    292   if (rename(temp_filename, filename) == -1) {
    293     LOG_ERROR("%s unable to commit file '%s': %s", __func__, filename, strerror(errno));
    294     goto error;
    295   }
    296 
    297   osi_free(temp_filename);
    298   return true;
    299 
    300 error:;
    301   unlink(temp_filename);
    302   osi_free(temp_filename);
    303   return false;
    304 }
    305 
    306 static char *trim(char *str) {
    307   while (isspace(*str))
    308     ++str;
    309 
    310   if (!*str)
    311     return str;
    312 
    313   char *end_str = str + strlen(str) - 1;
    314   while (end_str > str && isspace(*end_str))
    315     --end_str;
    316 
    317   end_str[1] = '\0';
    318   return str;
    319 }
    320 
    321 static void config_parse(FILE *fp, config_t *config) {
    322   assert(fp != NULL);
    323   assert(config != NULL);
    324 
    325   int line_num = 0;
    326   char line[1024];
    327   char section[1024];
    328   strcpy(section, CONFIG_DEFAULT_SECTION);
    329 
    330   while (fgets(line, sizeof(line), fp)) {
    331     char *line_ptr = trim(line);
    332     ++line_num;
    333 
    334     // Skip blank and comment lines.
    335     if (*line_ptr == '\0' || *line_ptr == '#')
    336       continue;
    337 
    338     if (*line_ptr == '[') {
    339       size_t len = strlen(line_ptr);
    340       if (line_ptr[len - 1] != ']') {
    341         LOG_DEBUG("%s unterminated section name on line %d.", __func__, line_num);
    342         continue;
    343       }
    344       strncpy(section, line_ptr + 1, len - 2);
    345       section[len - 2] = '\0';
    346     } else {
    347       char *split = strchr(line_ptr, '=');
    348       if (!split) {
    349         LOG_DEBUG("%s no key/value separator found on line %d.", __func__, line_num);
    350         continue;
    351       }
    352 
    353       *split = '\0';
    354       config_set_string(config, section, trim(line_ptr), trim(split + 1));
    355     }
    356   }
    357 }
    358 
    359 static section_t *section_new(const char *name) {
    360   section_t *section = osi_calloc(sizeof(section_t));
    361   if (!section)
    362     return NULL;
    363 
    364   section->name = osi_strdup(name);
    365   section->entries = list_new(entry_free);
    366   return section;
    367 }
    368 
    369 static void section_free(void *ptr) {
    370   if (!ptr)
    371     return;
    372 
    373   section_t *section = ptr;
    374   osi_free(section->name);
    375   list_free(section->entries);
    376   osi_free(section);
    377 }
    378 
    379 static section_t *section_find(const config_t *config, const char *section) {
    380   for (const list_node_t *node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) {
    381     section_t *sec = list_node(node);
    382     if (!strcmp(sec->name, section))
    383       return sec;
    384   }
    385 
    386   return NULL;
    387 }
    388 
    389 static entry_t *entry_new(const char *key, const char *value) {
    390   entry_t *entry = osi_calloc(sizeof(entry_t));
    391   if (!entry)
    392     return NULL;
    393 
    394   entry->key = osi_strdup(key);
    395   entry->value = osi_strdup(value);
    396   return entry;
    397 }
    398 
    399 static void entry_free(void *ptr) {
    400   if (!ptr)
    401     return;
    402 
    403   entry_t *entry = ptr;
    404   osi_free(entry->key);
    405   osi_free(entry->value);
    406   osi_free(entry);
    407 }
    408 
    409 static entry_t *entry_find(const config_t *config, const char *section, const char *key) {
    410   section_t *sec = section_find(config, section);
    411   if (!sec)
    412     return NULL;
    413 
    414   for (const list_node_t *node = list_begin(sec->entries); node != list_end(sec->entries); node = list_next(node)) {
    415     entry_t *entry = list_node(node);
    416     if (!strcmp(entry->key, key))
    417       return entry;
    418   }
    419 
    420   return NULL;
    421 }
    422