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