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 [§ion](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