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