• 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 <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       [&section](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       [&section](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