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