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