• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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       [&section](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       [&section](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