Home | History | Annotate | Download | only in src
      1 /******************************************************************************
      2  *
      3  *  Copyright 2017 The Android Open Source Project
      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 #include "osi/include/config.h"
     20 #include "log/log.h"
     21 
     22 #include <base/files/file_path.h>
     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 #include <sstream>
     34 #include <type_traits>
     35 
     36 // Empty definition; this type is aliased to list_node_t.
     37 struct config_section_iter_t {};
     38 
     39 static bool config_parse(FILE* fp, config_t* config);
     40 
     41 template <typename T,
     42           class = typename std::enable_if<std::is_same<
     43               config_t, typename std::remove_const<T>::type>::value>>
     44 static auto section_find(T& config, const std::string& section) {
     45   return std::find_if(
     46       config.sections.begin(), config.sections.end(),
     47       [&section](const section_t& sec) { return sec.name == section; });
     48 }
     49 
     50 static const entry_t* entry_find(const config_t& config,
     51                                  const std::string& section,
     52                                  const std::string& key) {
     53   auto sec = section_find(config, section);
     54   if (sec == config.sections.end()) return nullptr;
     55 
     56   for (const entry_t& entry : sec->entries) {
     57     if (entry.key == key) return &entry;
     58   }
     59 
     60   return nullptr;
     61 }
     62 
     63 std::unique_ptr<config_t> config_new_empty(void) {
     64   return std::make_unique<config_t>();
     65 }
     66 
     67 std::unique_ptr<config_t> config_new(const char* filename) {
     68   CHECK(filename != nullptr);
     69 
     70   std::unique_ptr<config_t> config = config_new_empty();
     71 
     72   FILE* fp = fopen(filename, "rt");
     73   if (!fp) {
     74     LOG(ERROR) << __func__ << ": unable to open file '" << filename
     75                << "': " << strerror(errno);
     76     return nullptr;
     77   }
     78 
     79   if (!config_parse(fp, config.get())) {
     80     config.reset();
     81   }
     82 
     83   fclose(fp);
     84   return config;
     85 }
     86 
     87 std::unique_ptr<config_t> config_new_clone(const config_t& src) {
     88   std::unique_ptr<config_t> ret = config_new_empty();
     89 
     90   for (const section_t& sec : src.sections) {
     91     for (const entry_t& entry : sec.entries) {
     92       config_set_string(ret.get(), sec.name, entry.key, entry.value);
     93     }
     94   }
     95 
     96   return ret;
     97 }
     98 
     99 bool config_has_section(const config_t& config, const std::string& section) {
    100   return (section_find(config, section) != config.sections.end());
    101 }
    102 
    103 bool config_has_key(const config_t& config, const std::string& section,
    104                     const std::string& key) {
    105   return (entry_find(config, section, key) != nullptr);
    106 }
    107 
    108 int config_get_int(const config_t& config, const std::string& section,
    109                    const std::string& key, int def_value) {
    110   const entry_t* entry = entry_find(config, section, key);
    111   if (!entry) return def_value;
    112 
    113   char* endptr;
    114   int ret = strtol(entry->value.c_str(), &endptr, 0);
    115   return (*endptr == '\0') ? ret : def_value;
    116 }
    117 
    118 uint64_t config_get_uint64(const config_t& config, const std::string& section,
    119                            const std::string& key, uint64_t def_value) {
    120   const entry_t* entry = entry_find(config, section, key);
    121   if (!entry) return def_value;
    122 
    123   char* endptr;
    124   uint64_t ret = strtoull(entry->value.c_str(), &endptr, 0);
    125   return (*endptr == '\0') ? ret : def_value;
    126 }
    127 
    128 bool config_get_bool(const config_t& config, const std::string& section,
    129                      const std::string& key, bool def_value) {
    130   const entry_t* entry = entry_find(config, section, key);
    131   if (!entry) return def_value;
    132 
    133   if (entry->value == "true") return true;
    134   if (entry->value == "false") return false;
    135 
    136   return def_value;
    137 }
    138 
    139 const std::string* config_get_string(const config_t& config,
    140                                      const std::string& section,
    141                                      const std::string& key,
    142                                      const std::string* def_value) {
    143   const entry_t* entry = entry_find(config, section, key);
    144   if (!entry) return def_value;
    145 
    146   return &entry->value;
    147 }
    148 
    149 void config_set_int(config_t* config, const std::string& section,
    150                     const std::string& key, int value) {
    151   config_set_string(config, section, key, std::to_string(value));
    152 }
    153 
    154 void config_set_uint64(config_t* config, const std::string& section,
    155                        const std::string& key, uint64_t value) {
    156   config_set_string(config, section, key, std::to_string(value));
    157 }
    158 
    159 void config_set_bool(config_t* config, const std::string& section,
    160                      const std::string& key, bool value) {
    161   config_set_string(config, section, key, value ? "true" : "false");
    162 }
    163 
    164 void config_set_string(config_t* config, const std::string& section,
    165                        const std::string& key, const std::string& value) {
    166   CHECK(config);
    167 
    168   auto sec = section_find(*config, section);
    169   if (sec == config->sections.end()) {
    170     config->sections.emplace_back(section_t{.name = section});
    171     sec = std::prev(config->sections.end());
    172   }
    173 
    174   std::string value_no_newline;
    175   size_t newline_position = value.find("\n");
    176   if (newline_position != std::string::npos) {
    177     android_errorWriteLog(0x534e4554, "70808273");
    178     value_no_newline = value.substr(0, newline_position);
    179   } else {
    180     value_no_newline = value;
    181   }
    182 
    183   for (entry_t& entry : sec->entries) {
    184     if (entry.key == key) {
    185       entry.value = value_no_newline;
    186       return;
    187     }
    188   }
    189 
    190   sec->entries.emplace_back(entry_t{.key = key, .value = value_no_newline});
    191 }
    192 
    193 bool config_remove_section(config_t* config, const std::string& section) {
    194   CHECK(config);
    195 
    196   auto sec = section_find(*config, section);
    197   if (sec == config->sections.end()) return false;
    198 
    199   config->sections.erase(sec);
    200   return true;
    201 }
    202 
    203 bool config_remove_key(config_t* config, const std::string& section,
    204                        const std::string& key) {
    205   CHECK(config);
    206   auto sec = section_find(*config, section);
    207   if (sec == config->sections.end()) return false;
    208 
    209   for (auto entry = sec->entries.begin(); entry != sec->entries.end();
    210        ++entry) {
    211     if (entry->key == key) {
    212       sec->entries.erase(entry);
    213       return true;
    214     }
    215   }
    216 
    217   return false;
    218 }
    219 
    220 bool config_save(const config_t& config, const std::string& filename) {
    221   CHECK(!filename.empty());
    222 
    223   // Steps to ensure content of config file gets to disk:
    224   //
    225   // 1) Open and write to temp file (e.g. bt_config.conf.new).
    226   // 2) Sync the temp file to disk with fsync().
    227   // 3) Rename temp file to actual config file (e.g. bt_config.conf).
    228   //    This ensures atomic update.
    229   // 4) Sync directory that has the conf file with fsync().
    230   //    This ensures directory entries are up-to-date.
    231   int dir_fd = -1;
    232   FILE* fp = nullptr;
    233   std::stringstream serialized;
    234 
    235   // Build temp config file based on config file (e.g. bt_config.conf.new).
    236   const std::string temp_filename = filename + ".new";
    237 
    238   // Extract directory from file path (e.g. /data/misc/bluedroid).
    239   const std::string directoryname = base::FilePath(filename).DirName().value();
    240   if (directoryname.empty()) {
    241     LOG(ERROR) << __func__ << ": error extracting directory from '" << filename
    242                << "': " << strerror(errno);
    243     goto error;
    244   }
    245 
    246   dir_fd = open(directoryname.c_str(), O_RDONLY);
    247   if (dir_fd < 0) {
    248     LOG(ERROR) << __func__ << ": unable to open dir '" << directoryname
    249                << "': " << strerror(errno);
    250     goto error;
    251   }
    252 
    253   fp = fopen(temp_filename.c_str(), "wt");
    254   if (!fp) {
    255     LOG(ERROR) << __func__ << ": unable to write to file '" << temp_filename
    256                << "': " << strerror(errno);
    257     goto error;
    258   }
    259 
    260   for (const section_t& section : config.sections) {
    261     serialized << "[" << section.name << "]" << std::endl;
    262 
    263     for (const entry_t& entry : section.entries)
    264       serialized << entry.key << " = " << entry.value << std::endl;
    265 
    266     serialized << std::endl;
    267   }
    268 
    269   if (fprintf(fp, "%s", serialized.str().c_str()) < 0) {
    270     LOG(ERROR) << __func__ << ": unable to write to file '" << temp_filename
    271                << "': " << strerror(errno);
    272     goto error;
    273   }
    274 
    275   // Sync written temp file out to disk. fsync() is blocking until data makes it
    276   // to disk.
    277   if (fsync(fileno(fp)) < 0) {
    278     LOG(WARNING) << __func__ << ": unable to fsync file '" << temp_filename
    279                  << "': " << strerror(errno);
    280   }
    281 
    282   if (fclose(fp) == EOF) {
    283     LOG(ERROR) << __func__ << ": unable to close file '" << temp_filename
    284                << "': " << strerror(errno);
    285     goto error;
    286   }
    287   fp = nullptr;
    288 
    289   // Change the file's permissions to Read/Write by User and Group
    290   if (chmod(temp_filename.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) ==
    291       -1) {
    292     LOG(ERROR) << __func__ << ": unable to change file permissions '"
    293                << filename << "': " << strerror(errno);
    294     goto error;
    295   }
    296 
    297   // Rename written temp file to the actual config file.
    298   if (rename(temp_filename.c_str(), filename.c_str()) == -1) {
    299     LOG(ERROR) << __func__ << ": unable to commit file '" << filename
    300                << "': " << strerror(errno);
    301     goto error;
    302   }
    303 
    304   // This should ensure the directory is updated as well.
    305   if (fsync(dir_fd) < 0) {
    306     LOG(WARNING) << __func__ << ": unable to fsync dir '" << directoryname
    307                  << "': " << strerror(errno);
    308   }
    309 
    310   if (close(dir_fd) < 0) {
    311     LOG(ERROR) << __func__ << ": unable to close dir '" << directoryname
    312                << "': " << strerror(errno);
    313     goto error;
    314   }
    315 
    316   return true;
    317 
    318 error:
    319   // This indicates there is a write issue.  Unlink as partial data is not
    320   // acceptable.
    321   unlink(temp_filename.c_str());
    322   if (fp) fclose(fp);
    323   if (dir_fd != -1) close(dir_fd);
    324   return false;
    325 }
    326 
    327 static char* trim(char* str) {
    328   while (isspace(*str)) ++str;
    329 
    330   if (!*str) return str;
    331 
    332   char* end_str = str + strlen(str) - 1;
    333   while (end_str > str && isspace(*end_str)) --end_str;
    334 
    335   end_str[1] = '\0';
    336   return str;
    337 }
    338 
    339 static bool config_parse(FILE* fp, config_t* config) {
    340   CHECK(fp != nullptr);
    341   CHECK(config != nullptr);
    342 
    343   int line_num = 0;
    344   char line[1024];
    345   char section[1024];
    346   strcpy(section, CONFIG_DEFAULT_SECTION);
    347 
    348   while (fgets(line, sizeof(line), fp)) {
    349     char* line_ptr = trim(line);
    350     ++line_num;
    351 
    352     // Skip blank and comment lines.
    353     if (*line_ptr == '\0' || *line_ptr == '#') continue;
    354 
    355     if (*line_ptr == '[') {
    356       size_t len = strlen(line_ptr);
    357       if (line_ptr[len - 1] != ']') {
    358         VLOG(1) << __func__ << ": unterminated section name on line "
    359                 << line_num;
    360         return false;
    361       }
    362       strncpy(section, line_ptr + 1, len - 2);  // NOLINT (len < 1024)
    363       section[len - 2] = '\0';
    364     } else {
    365       char* split = strchr(line_ptr, '=');
    366       if (!split) {
    367         VLOG(1) << __func__ << ": no key/value separator found on line "
    368                 << line_num;
    369         return false;
    370       }
    371 
    372       *split = '\0';
    373       config_set_string(config, section, trim(line_ptr), trim(split + 1));
    374     }
    375   }
    376   return true;
    377 }
    378