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 "osi/include/config.h"
     22 
     23 #include <assert.h>
     24 #include <ctype.h>
     25 #include <errno.h>
     26 #include <fcntl.h>
     27 #include <libgen.h>
     28 #include <stdio.h>
     29 #include <stdlib.h>
     30 #include <string.h>
     31 #include <unistd.h>
     32 #include <sys/stat.h>
     33 
     34 #include "osi/include/allocator.h"
     35 #include "osi/include/list.h"
     36 #include "osi/include/log.h"
     37 
     38 typedef struct {
     39   char *key;
     40   char *value;
     41 } entry_t;
     42 
     43 typedef struct {
     44   char *name;
     45   list_t *entries;
     46 } section_t;
     47 
     48 struct config_t {
     49   list_t *sections;
     50 };
     51 
     52 // Empty definition; this type is aliased to list_node_t.
     53 struct config_section_iter_t {};
     54 
     55 static bool config_parse(FILE *fp, config_t *config);
     56 
     57 static section_t *section_new(const char *name);
     58 static void section_free(void *ptr);
     59 static section_t *section_find(const config_t *config, const char *section);
     60 
     61 static entry_t *entry_new(const char *key, const char *value);
     62 static void entry_free(void *ptr);
     63 static entry_t *entry_find(const config_t *config, const char *section, const char *key);
     64 
     65 config_t *config_new_empty(void) {
     66   config_t *config = osi_calloc(sizeof(config_t));
     67 
     68   config->sections = list_new(section_free);
     69   if (!config->sections) {
     70     LOG_ERROR(LOG_TAG, "%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(LOG_TAG, "%s unable to open file '%s': %s", __func__, filename, strerror(errno));
     91     config_free(config);
     92     return NULL;
     93   }
     94 
     95   if (!config_parse(fp, config)) {
     96     config_free(config);
     97     config = NULL;
     98   }
     99 
    100   fclose(fp);
    101   return config;
    102 }
    103 
    104 config_t *config_new_clone(const config_t *src) {
    105   assert(src != NULL);
    106 
    107   config_t *ret = config_new_empty();
    108 
    109   assert(ret != NULL);
    110 
    111   for (const list_node_t *node = list_begin(src->sections);
    112        node != list_end(src->sections);
    113        node = list_next(node)) {
    114     section_t *sec = list_node(node);
    115 
    116     for (const list_node_t *node_entry = list_begin(sec->entries);
    117          node_entry != list_end(sec->entries);
    118          node_entry = list_next(node_entry)) {
    119       entry_t *entry = list_node(node_entry);
    120 
    121       config_set_string(ret, sec->name, entry->key, entry->value);
    122     }
    123   }
    124 
    125   return ret;
    126 }
    127 
    128 void config_free(config_t *config) {
    129   if (!config)
    130     return;
    131 
    132   list_free(config->sections);
    133   osi_free(config);
    134 }
    135 
    136 bool config_has_section(const config_t *config, const char *section) {
    137   assert(config != NULL);
    138   assert(section != NULL);
    139 
    140   return (section_find(config, section) != NULL);
    141 }
    142 
    143 bool config_has_key(const config_t *config, const char *section, const char *key) {
    144   assert(config != NULL);
    145   assert(section != NULL);
    146   assert(key != NULL);
    147 
    148   return (entry_find(config, section, key) != NULL);
    149 }
    150 
    151 int config_get_int(const config_t *config, const char *section, const char *key, int def_value) {
    152   assert(config != NULL);
    153   assert(section != NULL);
    154   assert(key != NULL);
    155 
    156   entry_t *entry = entry_find(config, section, key);
    157   if (!entry)
    158     return def_value;
    159 
    160   char *endptr;
    161   int ret = strtol(entry->value, &endptr, 0);
    162   return (*endptr == '\0') ? ret : def_value;
    163 }
    164 
    165 bool config_get_bool(const config_t *config, const char *section, const char *key, bool def_value) {
    166   assert(config != NULL);
    167   assert(section != NULL);
    168   assert(key != NULL);
    169 
    170   entry_t *entry = entry_find(config, section, key);
    171   if (!entry)
    172     return def_value;
    173 
    174   if (!strcmp(entry->value, "true"))
    175     return true;
    176   if (!strcmp(entry->value, "false"))
    177     return false;
    178 
    179   return def_value;
    180 }
    181 
    182 const char *config_get_string(const config_t *config, const char *section, const char *key, const char *def_value) {
    183   assert(config != NULL);
    184   assert(section != NULL);
    185   assert(key != NULL);
    186 
    187   entry_t *entry = entry_find(config, section, key);
    188   if (!entry)
    189     return def_value;
    190 
    191   return entry->value;
    192 }
    193 
    194 void config_set_int(config_t *config, const char *section, const char *key, int value) {
    195   assert(config != NULL);
    196   assert(section != NULL);
    197   assert(key != NULL);
    198 
    199   char value_str[32] = { 0 };
    200   sprintf(value_str, "%d", value);
    201   config_set_string(config, section, key, value_str);
    202 }
    203 
    204 void config_set_bool(config_t *config, const char *section, const char *key, bool value) {
    205   assert(config != NULL);
    206   assert(section != NULL);
    207   assert(key != NULL);
    208 
    209   config_set_string(config, section, key, value ? "true" : "false");
    210 }
    211 
    212 void config_set_string(config_t *config, const char *section, const char *key, const char *value) {
    213   section_t *sec = section_find(config, section);
    214   if (!sec) {
    215     sec = section_new(section);
    216     list_append(config->sections, sec);
    217   }
    218 
    219   for (const list_node_t *node = list_begin(sec->entries); node != list_end(sec->entries); node = list_next(node)) {
    220     entry_t *entry = list_node(node);
    221     if (!strcmp(entry->key, key)) {
    222       osi_free(entry->value);
    223       entry->value = osi_strdup(value);
    224       return;
    225     }
    226   }
    227 
    228   entry_t *entry = entry_new(key, value);
    229   list_append(sec->entries, entry);
    230 }
    231 
    232 bool config_remove_section(config_t *config, const char *section) {
    233   assert(config != NULL);
    234   assert(section != NULL);
    235 
    236   section_t *sec = section_find(config, section);
    237   if (!sec)
    238     return false;
    239 
    240   return list_remove(config->sections, sec);
    241 }
    242 
    243 bool config_remove_key(config_t *config, const char *section, const char *key) {
    244   assert(config != NULL);
    245   assert(section != NULL);
    246   assert(key != NULL);
    247 
    248   section_t *sec = section_find(config, section);
    249   entry_t *entry = entry_find(config, section, key);
    250   if (!sec || !entry)
    251     return false;
    252 
    253   return list_remove(sec->entries, entry);
    254 }
    255 
    256 const config_section_node_t *config_section_begin(const config_t *config) {
    257   assert(config != NULL);
    258   return (const config_section_node_t *)list_begin(config->sections);
    259 }
    260 
    261 const config_section_node_t *config_section_end(const config_t *config) {
    262   assert(config != NULL);
    263   return (const config_section_node_t *)list_end(config->sections);
    264 }
    265 
    266 const config_section_node_t *config_section_next(const config_section_node_t *node) {
    267   assert(node != NULL);
    268   return (const config_section_node_t *)list_next((const list_node_t *)node);
    269 }
    270 
    271 const char *config_section_name(const config_section_node_t *node) {
    272   assert(node != NULL);
    273   const list_node_t *lnode = (const list_node_t *)node;
    274   const section_t *section = (const section_t *)list_node(lnode);
    275   return section->name;
    276 }
    277 
    278 bool config_save(const config_t *config, const char *filename) {
    279   assert(config != NULL);
    280   assert(filename != NULL);
    281   assert(*filename != '\0');
    282 
    283   // Steps to ensure content of config file gets to disk:
    284   //
    285   // 1) Open and write to temp file (e.g. bt_config.conf.new).
    286   // 2) Sync the temp file to disk with fsync().
    287   // 3) Rename temp file to actual config file (e.g. bt_config.conf).
    288   //    This ensures atomic update.
    289   // 4) Sync directory that has the conf file with fsync().
    290   //    This ensures directory entries are up-to-date.
    291   int dir_fd = -1;
    292   FILE *fp = NULL;
    293 
    294   // Build temp config file based on config file (e.g. bt_config.conf.new).
    295   static const char *temp_file_ext = ".new";
    296   const int filename_len = strlen(filename);
    297   const int temp_filename_len = filename_len + strlen(temp_file_ext) + 1;
    298   char *temp_filename = osi_calloc(temp_filename_len);
    299   snprintf(temp_filename, temp_filename_len, "%s%s", filename, temp_file_ext);
    300 
    301   // Extract directory from file path (e.g. /data/misc/bluedroid).
    302   char *temp_dirname = osi_strdup(filename);
    303   const char *directoryname = dirname(temp_dirname);
    304   if (!directoryname) {
    305     LOG_ERROR(LOG_TAG, "%s error extracting directory from '%s': %s", __func__, filename, strerror(errno));
    306     goto error;
    307   }
    308 
    309   dir_fd = open(directoryname, O_RDONLY);
    310   if (dir_fd < 0) {
    311     LOG_ERROR(LOG_TAG, "%s unable to open dir '%s': %s", __func__, directoryname, strerror(errno));
    312     goto error;
    313   }
    314 
    315   fp = fopen(temp_filename, "wt");
    316   if (!fp) {
    317     LOG_ERROR(LOG_TAG, "%s unable to write file '%s': %s", __func__, temp_filename, strerror(errno));
    318     goto error;
    319   }
    320 
    321   for (const list_node_t *node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) {
    322     const section_t *section = (const section_t *)list_node(node);
    323     if (fprintf(fp, "[%s]\n", section->name) < 0) {
    324       LOG_ERROR(LOG_TAG, "%s unable to write to file '%s': %s", __func__, temp_filename, strerror(errno));
    325       goto error;
    326     }
    327 
    328     for (const list_node_t *enode = list_begin(section->entries); enode != list_end(section->entries); enode = list_next(enode)) {
    329       const entry_t *entry = (const entry_t *)list_node(enode);
    330       if (fprintf(fp, "%s = %s\n", entry->key, entry->value) < 0) {
    331         LOG_ERROR(LOG_TAG, "%s unable to write to file '%s': %s", __func__, temp_filename, strerror(errno));
    332         goto error;
    333       }
    334     }
    335 
    336     // Only add a separating newline if there are more sections.
    337     if (list_next(node) != list_end(config->sections)) {
    338       if (fputc('\n', fp) == EOF) {
    339         LOG_ERROR(LOG_TAG, "%s unable to write to file '%s': %s", __func__, temp_filename, strerror(errno));
    340         goto error;
    341       }
    342     }
    343   }
    344 
    345   // Sync written temp file out to disk. fsync() is blocking until data makes it to disk.
    346   if (fsync(fileno(fp)) < 0) {
    347     LOG_WARN(LOG_TAG, "%s unable to fsync file '%s': %s", __func__, temp_filename, strerror(errno));
    348   }
    349 
    350   if (fclose(fp) == EOF) {
    351     LOG_ERROR(LOG_TAG, "%s unable to close file '%s': %s", __func__, temp_filename, strerror(errno));
    352     goto error;
    353   }
    354   fp = NULL;
    355 
    356   // Change the file's permissions to Read/Write by User and Group
    357   if (chmod(temp_filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) == -1) {
    358     LOG_ERROR(LOG_TAG, "%s unable to change file permissions '%s': %s", __func__, filename, strerror(errno));
    359     goto error;
    360   }
    361 
    362   // Rename written temp file to the actual config file.
    363   if (rename(temp_filename, filename) == -1) {
    364     LOG_ERROR(LOG_TAG, "%s unable to commit file '%s': %s", __func__, filename, strerror(errno));
    365     goto error;
    366   }
    367 
    368   // This should ensure the directory is updated as well.
    369   if (fsync(dir_fd) < 0) {
    370     LOG_WARN(LOG_TAG, "%s unable to fsync dir '%s': %s", __func__, directoryname, strerror(errno));
    371   }
    372 
    373   if (close(dir_fd) < 0) {
    374     LOG_ERROR(LOG_TAG, "%s unable to close dir '%s': %s", __func__, directoryname, strerror(errno));
    375     goto error;
    376   }
    377 
    378   osi_free(temp_filename);
    379   osi_free(temp_dirname);
    380   return true;
    381 
    382 error:
    383   // This indicates there is a write issue.  Unlink as partial data is not acceptable.
    384   unlink(temp_filename);
    385   if (fp)
    386     fclose(fp);
    387   if (dir_fd != -1)
    388     close(dir_fd);
    389   osi_free(temp_filename);
    390   osi_free(temp_dirname);
    391   return false;
    392 }
    393 
    394 static char *trim(char *str) {
    395   while (isspace(*str))
    396     ++str;
    397 
    398   if (!*str)
    399     return str;
    400 
    401   char *end_str = str + strlen(str) - 1;
    402   while (end_str > str && isspace(*end_str))
    403     --end_str;
    404 
    405   end_str[1] = '\0';
    406   return str;
    407 }
    408 
    409 static bool config_parse(FILE *fp, config_t *config) {
    410   assert(fp != NULL);
    411   assert(config != NULL);
    412 
    413   int line_num = 0;
    414   char line[1024];
    415   char section[1024];
    416   strcpy(section, CONFIG_DEFAULT_SECTION);
    417 
    418   while (fgets(line, sizeof(line), fp)) {
    419     char *line_ptr = trim(line);
    420     ++line_num;
    421 
    422     // Skip blank and comment lines.
    423     if (*line_ptr == '\0' || *line_ptr == '#')
    424       continue;
    425 
    426     if (*line_ptr == '[') {
    427       size_t len = strlen(line_ptr);
    428       if (line_ptr[len - 1] != ']') {
    429         LOG_DEBUG(LOG_TAG, "%s unterminated section name on line %d.", __func__, line_num);
    430         return false;
    431       }
    432       strncpy(section, line_ptr + 1, len - 2);
    433       section[len - 2] = '\0';
    434     } else {
    435       char *split = strchr(line_ptr, '=');
    436       if (!split) {
    437         LOG_DEBUG(LOG_TAG, "%s no key/value separator found on line %d.", __func__, line_num);
    438         return false;
    439       }
    440 
    441       *split = '\0';
    442       config_set_string(config, section, trim(line_ptr), trim(split + 1));
    443     }
    444   }
    445   return true;
    446 }
    447 
    448 static section_t *section_new(const char *name) {
    449   section_t *section = osi_calloc(sizeof(section_t));
    450 
    451   section->name = osi_strdup(name);
    452   section->entries = list_new(entry_free);
    453   return section;
    454 }
    455 
    456 static void section_free(void *ptr) {
    457   if (!ptr)
    458     return;
    459 
    460   section_t *section = ptr;
    461   osi_free(section->name);
    462   list_free(section->entries);
    463   osi_free(section);
    464 }
    465 
    466 static section_t *section_find(const config_t *config, const char *section) {
    467   for (const list_node_t *node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) {
    468     section_t *sec = list_node(node);
    469     if (!strcmp(sec->name, section))
    470       return sec;
    471   }
    472 
    473   return NULL;
    474 }
    475 
    476 static entry_t *entry_new(const char *key, const char *value) {
    477   entry_t *entry = osi_calloc(sizeof(entry_t));
    478 
    479   entry->key = osi_strdup(key);
    480   entry->value = osi_strdup(value);
    481   return entry;
    482 }
    483 
    484 static void entry_free(void *ptr) {
    485   if (!ptr)
    486     return;
    487 
    488   entry_t *entry = ptr;
    489   osi_free(entry->key);
    490   osi_free(entry->value);
    491   osi_free(entry);
    492 }
    493 
    494 static entry_t *entry_find(const config_t *config, const char *section, const char *key) {
    495   section_t *sec = section_find(config, section);
    496   if (!sec)
    497     return NULL;
    498 
    499   for (const list_node_t *node = list_begin(sec->entries); node != list_end(sec->entries); node = list_next(node)) {
    500     entry_t *entry = list_node(node);
    501     if (!strcmp(entry->key, key))
    502       return entry;
    503   }
    504 
    505   return NULL;
    506 }
    507