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