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 <base/logging.h>
23 #include <ctype.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <libgen.h>
27 #include <log/log.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 <sstream>
35 #include <type_traits>
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 CHECK(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) << __func__ << ": unable to open file '" << filename
105 << "': " << strerror(errno);
106 return nullptr;
107 }
108
109 if (!config_parse(fp, config.get())) {
110 config.reset();
111 }
112
113 fclose(fp);
114 return config;
115 }
116
checksum_read(const char * filename)117 std::string checksum_read(const char* filename) {
118 base::FilePath path(filename);
119 if (!base::PathExists(path)) {
120 LOG(ERROR) << __func__ << ": unable to locate file '" << filename << "'";
121 return "";
122 }
123 std::string encrypted_hash;
124 if (!base::ReadFileToString(path, &encrypted_hash)) {
125 LOG(ERROR) << __func__ << ": unable to read file '" << filename << "'";
126 }
127 return encrypted_hash;
128 }
129
config_new_clone(const config_t & src)130 std::unique_ptr<config_t> config_new_clone(const config_t& src) {
131 std::unique_ptr<config_t> ret = config_new_empty();
132
133 for (const section_t& sec : src.sections) {
134 for (const entry_t& entry : sec.entries) {
135 config_set_string(ret.get(), sec.name, entry.key, entry.value);
136 }
137 }
138
139 return ret;
140 }
141
config_has_section(const config_t & config,const std::string & section)142 bool config_has_section(const config_t& config, const std::string& section) {
143 return (section_find(config, section) != config.sections.end());
144 }
145
config_has_key(const config_t & config,const std::string & section,const std::string & key)146 bool config_has_key(const config_t& config, const std::string& section,
147 const std::string& key) {
148 return (entry_find(config, section, key) != nullptr);
149 }
150
config_get_int(const config_t & config,const std::string & section,const std::string & key,int def_value)151 int config_get_int(const config_t& config, const std::string& section,
152 const std::string& key, int def_value) {
153 const entry_t* entry = entry_find(config, section, key);
154 if (!entry) return def_value;
155
156 char* endptr;
157 int ret = strtol(entry->value.c_str(), &endptr, 0);
158 return (*endptr == '\0') ? ret : def_value;
159 }
160
config_get_uint64(const config_t & config,const std::string & section,const std::string & key,uint64_t def_value)161 uint64_t config_get_uint64(const config_t& config, const std::string& section,
162 const std::string& key, uint64_t def_value) {
163 const entry_t* entry = entry_find(config, section, key);
164 if (!entry) return def_value;
165
166 char* endptr;
167 uint64_t ret = strtoull(entry->value.c_str(), &endptr, 0);
168 return (*endptr == '\0') ? ret : def_value;
169 }
170
config_get_bool(const config_t & config,const std::string & section,const std::string & key,bool def_value)171 bool config_get_bool(const config_t& config, const std::string& section,
172 const std::string& key, bool def_value) {
173 const entry_t* entry = entry_find(config, section, key);
174 if (!entry) return def_value;
175
176 if (entry->value == "true") return true;
177 if (entry->value == "false") return false;
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,
183 const std::string& section,
184 const std::string& key,
185 const std::string* def_value) {
186 const entry_t* entry = entry_find(config, section, key);
187 if (!entry) return def_value;
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,
193 const std::string& key, 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,
198 const std::string& key, 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,
203 const std::string& key, 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,
208 const std::string& key, const std::string& value) {
209 CHECK(config);
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 android_errorWriteLog(0x534e4554, "70808273");
221 value_no_newline = value.substr(0, newline_position);
222 } else {
223 value_no_newline = value;
224 }
225
226 for (entry_t& entry : sec->entries) {
227 if (entry.key == key) {
228 entry.value = value_no_newline;
229 return;
230 }
231 }
232
233 sec->entries.emplace_back(entry_t{.key = key, .value = value_no_newline});
234 }
235
config_remove_section(config_t * config,const std::string & section)236 bool config_remove_section(config_t* config, const std::string& section) {
237 CHECK(config);
238
239 auto sec = section_find(*config, section);
240 if (sec == config->sections.end()) return false;
241
242 config->sections.erase(sec);
243 return true;
244 }
245
config_remove_key(config_t * config,const std::string & section,const std::string & key)246 bool config_remove_key(config_t* config, const std::string& section,
247 const std::string& key) {
248 CHECK(config);
249 auto sec = section_find(*config, section);
250 if (sec == config->sections.end()) return false;
251
252 for (auto entry = sec->entries.begin(); entry != sec->entries.end();
253 ++entry) {
254 if (entry->key == key) {
255 sec->entries.erase(entry);
256 return true;
257 }
258 }
259
260 return false;
261 }
262
config_save(const config_t & config,const std::string & filename)263 bool config_save(const config_t& config, const std::string& filename) {
264 CHECK(!filename.empty());
265
266 // Steps to ensure content of config file gets to disk:
267 //
268 // 1) Open and write to temp file (e.g. bt_config.conf.new).
269 // 2) Flush the stream buffer to the temp file.
270 // 3) Sync the temp file to disk with fsync().
271 // 4) Rename temp file to actual config file (e.g. bt_config.conf).
272 // This ensures atomic update.
273 // 5) Sync directory that has the conf file with fsync().
274 // This ensures directory entries are up-to-date.
275 int dir_fd = -1;
276 FILE* fp = nullptr;
277 std::stringstream serialized;
278
279 // Build temp config file based on config file (e.g. bt_config.conf.new).
280 const std::string temp_filename = filename + ".new";
281
282 // Extract directory from file path (e.g. /data/misc/bluedroid).
283 const std::string directoryname = base::FilePath(filename).DirName().value();
284 if (directoryname.empty()) {
285 LOG(ERROR) << __func__ << ": error extracting directory from '" << filename
286 << "': " << strerror(errno);
287 goto error;
288 }
289
290 dir_fd = open(directoryname.c_str(), O_RDONLY);
291 if (dir_fd < 0) {
292 LOG(ERROR) << __func__ << ": unable to open dir '" << directoryname
293 << "': " << strerror(errno);
294 goto error;
295 }
296
297 fp = fopen(temp_filename.c_str(), "wt");
298 if (!fp) {
299 LOG(ERROR) << __func__ << ": unable to write to file '" << temp_filename
300 << "': " << strerror(errno);
301 goto error;
302 }
303
304 for (const section_t& section : config.sections) {
305 serialized << "[" << section.name << "]" << std::endl;
306
307 for (const entry_t& entry : section.entries)
308 serialized << entry.key << " = " << entry.value << std::endl;
309
310 serialized << std::endl;
311 }
312
313 if (fprintf(fp, "%s", serialized.str().c_str()) < 0) {
314 LOG(ERROR) << __func__ << ": unable to write to file '" << temp_filename
315 << "': " << strerror(errno);
316 goto error;
317 }
318
319 // Flush the stream buffer to the temp file.
320 if (fflush(fp) < 0) {
321 LOG(ERROR) << __func__ << ": unable to write flush buffer to file '"
322 << temp_filename << "': " << strerror(errno);
323 goto error;
324 }
325
326 // Sync written temp file out to disk. fsync() is blocking until data makes it
327 // to disk.
328 if (fsync(fileno(fp)) < 0) {
329 LOG(WARNING) << __func__ << ": unable to fsync file '" << temp_filename
330 << "': " << strerror(errno);
331 }
332
333 if (fclose(fp) == EOF) {
334 LOG(ERROR) << __func__ << ": unable to close file '" << temp_filename
335 << "': " << strerror(errno);
336 goto error;
337 }
338 fp = nullptr;
339
340 // Change the file's permissions to Read/Write by User and Group
341 if (chmod(temp_filename.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) ==
342 -1) {
343 LOG(ERROR) << __func__ << ": unable to change file permissions '"
344 << filename << "': " << strerror(errno);
345 goto error;
346 }
347
348 // Rename written temp file to the actual config file.
349 if (rename(temp_filename.c_str(), filename.c_str()) == -1) {
350 LOG(ERROR) << __func__ << ": unable to commit file '" << filename
351 << "': " << strerror(errno);
352 goto error;
353 }
354
355 // This should ensure the directory is updated as well.
356 if (fsync(dir_fd) < 0) {
357 LOG(WARNING) << __func__ << ": unable to fsync dir '" << directoryname
358 << "': " << strerror(errno);
359 }
360
361 if (close(dir_fd) < 0) {
362 LOG(ERROR) << __func__ << ": unable to close dir '" << directoryname
363 << "': " << strerror(errno);
364 goto error;
365 }
366
367 return true;
368
369 error:
370 // This indicates there is a write issue. Unlink as partial data is not
371 // acceptable.
372 unlink(temp_filename.c_str());
373 if (fp) fclose(fp);
374 if (dir_fd != -1) close(dir_fd);
375 return false;
376 }
377
checksum_save(const std::string & checksum,const std::string & filename)378 bool checksum_save(const std::string& checksum, const std::string& filename) {
379 CHECK(!checksum.empty()) << __func__ << ": checksum cannot be empty";
380 CHECK(!filename.empty()) << __func__ << ": filename cannot be empty";
381
382 // Steps to ensure content of config checksum file gets to disk:
383 //
384 // 1) Open and write to temp file (e.g.
385 // bt_config.conf.encrypted-checksum.new). 2) Sync the temp file to disk with
386 // fsync(). 3) Rename temp file to actual config checksum file (e.g.
387 // bt_config.conf.encrypted-checksum).
388 // This ensures atomic update.
389 // 4) Sync directory that has the conf file with fsync().
390 // This ensures directory entries are up-to-date.
391 FILE* fp = nullptr;
392 int dir_fd = -1;
393
394 // Build temp config checksum file based on config checksum file (e.g.
395 // bt_config.conf.encrypted-checksum.new).
396 const std::string temp_filename = filename + ".new";
397 base::FilePath path(temp_filename);
398
399 // Extract directory from file path (e.g. /data/misc/bluedroid).
400 const std::string directoryname = base::FilePath(filename).DirName().value();
401 if (directoryname.empty()) {
402 LOG(ERROR) << __func__ << ": error extracting directory from '" << filename
403 << "': " << strerror(errno);
404 goto error2;
405 }
406
407 dir_fd = open(directoryname.c_str(), O_RDONLY);
408 if (dir_fd < 0) {
409 LOG(ERROR) << __func__ << ": unable to open dir '" << directoryname
410 << "': " << strerror(errno);
411 goto error2;
412 }
413
414 if (base::WriteFile(path, checksum.data(), checksum.size()) !=
415 (int)checksum.size()) {
416 LOG(ERROR) << __func__ << ": unable to write file '" << filename.c_str();
417 goto error2;
418 }
419
420 fp = fopen(temp_filename.c_str(), "rb");
421 if (!fp) {
422 LOG(ERROR) << __func__ << ": unable to write to file '" << temp_filename
423 << "': " << strerror(errno);
424 goto error2;
425 }
426
427 // Sync written temp file out to disk. fsync() is blocking until data makes it
428 // to disk.
429 if (fsync(fileno(fp)) < 0) {
430 LOG(WARNING) << __func__ << ": unable to fsync file '" << temp_filename
431 << "': " << strerror(errno);
432 }
433
434 if (fclose(fp) == EOF) {
435 LOG(ERROR) << __func__ << ": unable to close file '" << temp_filename
436 << "': " << strerror(errno);
437 goto error2;
438 }
439 fp = nullptr;
440
441 // Change the file's permissions to Read/Write by User and Group
442 if (chmod(temp_filename.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) ==
443 -1) {
444 LOG(ERROR) << __func__ << ": unable to change file permissions '"
445 << filename << "': " << strerror(errno);
446 goto error2;
447 }
448
449 // Rename written temp file to the actual config file.
450 if (rename(temp_filename.c_str(), filename.c_str()) == -1) {
451 LOG(ERROR) << __func__ << ": unable to commit file '" << filename
452 << "': " << strerror(errno);
453 goto error2;
454 }
455
456 // This should ensure the directory is updated as well.
457 if (fsync(dir_fd) < 0) {
458 LOG(WARNING) << __func__ << ": unable to fsync dir '" << directoryname
459 << "': " << strerror(errno);
460 }
461
462 if (close(dir_fd) < 0) {
463 LOG(ERROR) << __func__ << ": unable to close dir '" << directoryname
464 << "': " << strerror(errno);
465 goto error2;
466 }
467
468 return true;
469
470 error2:
471 // This indicates there is a write issue. Unlink as partial data is not
472 // acceptable.
473 unlink(temp_filename.c_str());
474 if (fp) fclose(fp);
475 if (dir_fd != -1) close(dir_fd);
476 return false;
477 }
478
trim(char * str)479 static char* trim(char* str) {
480 while (isspace(*str)) ++str;
481
482 if (!*str) return str;
483
484 char* end_str = str + strlen(str) - 1;
485 while (end_str > str && isspace(*end_str)) --end_str;
486
487 end_str[1] = '\0';
488 return str;
489 }
490
config_parse(FILE * fp,config_t * config)491 static bool config_parse(FILE* fp, config_t* config) {
492 CHECK(fp != nullptr);
493 CHECK(config != nullptr);
494
495 int line_num = 0;
496 char line[1024];
497 char section[1024];
498 strcpy(section, CONFIG_DEFAULT_SECTION);
499
500 while (fgets(line, sizeof(line), fp)) {
501 char* line_ptr = trim(line);
502 ++line_num;
503
504 // Skip blank and comment lines.
505 if (*line_ptr == '\0' || *line_ptr == '#') continue;
506
507 if (*line_ptr == '[') {
508 size_t len = strlen(line_ptr);
509 if (line_ptr[len - 1] != ']') {
510 VLOG(1) << __func__ << ": unterminated section name on line "
511 << line_num;
512 return false;
513 }
514 strncpy(section, line_ptr + 1, len - 2); // NOLINT (len < 1024)
515 section[len - 2] = '\0';
516 } else {
517 char* split = strchr(line_ptr, '=');
518 if (!split) {
519 VLOG(1) << __func__ << ": no key/value separator found on line "
520 << line_num;
521 return false;
522 }
523
524 *split = '\0';
525 config_set_string(config, section, trim(line_ptr), trim(split + 1));
526 }
527 }
528 return true;
529 }
530