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