• 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 #include "log/log.h"
21 
22 #include <base/files/file_path.h>
23 #include <base/logging.h>
24 #include <ctype.h>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <libgen.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 
config_new_clone(const config_t & src)87 std::unique_ptr<config_t> config_new_clone(const config_t& src) {
88   std::unique_ptr<config_t> ret = config_new_empty();
89 
90   for (const section_t& sec : src.sections) {
91     for (const entry_t& entry : sec.entries) {
92       config_set_string(ret.get(), sec.name, entry.key, entry.value);
93     }
94   }
95 
96   return ret;
97 }
98 
config_has_section(const config_t & config,const std::string & section)99 bool config_has_section(const config_t& config, const std::string& section) {
100   return (section_find(config, section) != config.sections.end());
101 }
102 
config_has_key(const config_t & config,const std::string & section,const std::string & key)103 bool config_has_key(const config_t& config, const std::string& section,
104                     const std::string& key) {
105   return (entry_find(config, section, key) != nullptr);
106 }
107 
config_get_int(const config_t & config,const std::string & section,const std::string & key,int def_value)108 int config_get_int(const config_t& config, const std::string& section,
109                    const std::string& key, int def_value) {
110   const entry_t* entry = entry_find(config, section, key);
111   if (!entry) return def_value;
112 
113   char* endptr;
114   int ret = strtol(entry->value.c_str(), &endptr, 0);
115   return (*endptr == '\0') ? ret : def_value;
116 }
117 
config_get_uint64(const config_t & config,const std::string & section,const std::string & key,uint64_t def_value)118 uint64_t config_get_uint64(const config_t& config, const std::string& section,
119                            const std::string& key, uint64_t def_value) {
120   const entry_t* entry = entry_find(config, section, key);
121   if (!entry) return def_value;
122 
123   char* endptr;
124   uint64_t ret = strtoull(entry->value.c_str(), &endptr, 0);
125   return (*endptr == '\0') ? ret : def_value;
126 }
127 
config_get_bool(const config_t & config,const std::string & section,const std::string & key,bool def_value)128 bool config_get_bool(const config_t& config, const std::string& section,
129                      const std::string& key, bool def_value) {
130   const entry_t* entry = entry_find(config, section, key);
131   if (!entry) return def_value;
132 
133   if (entry->value == "true") return true;
134   if (entry->value == "false") return false;
135 
136   return def_value;
137 }
138 
config_get_string(const config_t & config,const std::string & section,const std::string & key,const std::string * def_value)139 const std::string* config_get_string(const config_t& config,
140                                      const std::string& section,
141                                      const std::string& key,
142                                      const std::string* def_value) {
143   const entry_t* entry = entry_find(config, section, key);
144   if (!entry) return def_value;
145 
146   return &entry->value;
147 }
148 
config_set_int(config_t * config,const std::string & section,const std::string & key,int value)149 void config_set_int(config_t* config, const std::string& section,
150                     const std::string& key, int value) {
151   config_set_string(config, section, key, std::to_string(value));
152 }
153 
config_set_uint64(config_t * config,const std::string & section,const std::string & key,uint64_t value)154 void config_set_uint64(config_t* config, const std::string& section,
155                        const std::string& key, uint64_t value) {
156   config_set_string(config, section, key, std::to_string(value));
157 }
158 
config_set_bool(config_t * config,const std::string & section,const std::string & key,bool value)159 void config_set_bool(config_t* config, const std::string& section,
160                      const std::string& key, bool value) {
161   config_set_string(config, section, key, value ? "true" : "false");
162 }
163 
config_set_string(config_t * config,const std::string & section,const std::string & key,const std::string & value)164 void config_set_string(config_t* config, const std::string& section,
165                        const std::string& key, const std::string& value) {
166   CHECK(config);
167 
168   auto sec = section_find(*config, section);
169   if (sec == config->sections.end()) {
170     config->sections.emplace_back(section_t{.name = section});
171     sec = std::prev(config->sections.end());
172   }
173 
174   std::string value_no_newline;
175   size_t newline_position = value.find("\n");
176   if (newline_position != std::string::npos) {
177     android_errorWriteLog(0x534e4554, "70808273");
178     value_no_newline = value.substr(0, newline_position);
179   } else {
180     value_no_newline = value;
181   }
182 
183   for (entry_t& entry : sec->entries) {
184     if (entry.key == key) {
185       entry.value = value_no_newline;
186       return;
187     }
188   }
189 
190   sec->entries.emplace_back(entry_t{.key = key, .value = value_no_newline});
191 }
192 
config_remove_section(config_t * config,const std::string & section)193 bool config_remove_section(config_t* config, const std::string& section) {
194   CHECK(config);
195 
196   auto sec = section_find(*config, section);
197   if (sec == config->sections.end()) return false;
198 
199   config->sections.erase(sec);
200   return true;
201 }
202 
config_remove_key(config_t * config,const std::string & section,const std::string & key)203 bool config_remove_key(config_t* config, const std::string& section,
204                        const std::string& key) {
205   CHECK(config);
206   auto sec = section_find(*config, section);
207   if (sec == config->sections.end()) return false;
208 
209   for (auto entry = sec->entries.begin(); entry != sec->entries.end();
210        ++entry) {
211     if (entry->key == key) {
212       sec->entries.erase(entry);
213       return true;
214     }
215   }
216 
217   return false;
218 }
219 
config_save(const config_t & config,const std::string & filename)220 bool config_save(const config_t& config, const std::string& filename) {
221   CHECK(!filename.empty());
222 
223   // Steps to ensure content of config file gets to disk:
224   //
225   // 1) Open and write to temp file (e.g. bt_config.conf.new).
226   // 2) Sync the temp file to disk with fsync().
227   // 3) Rename temp file to actual config file (e.g. bt_config.conf).
228   //    This ensures atomic update.
229   // 4) Sync directory that has the conf file with fsync().
230   //    This ensures directory entries are up-to-date.
231   int dir_fd = -1;
232   FILE* fp = nullptr;
233   std::stringstream serialized;
234 
235   // Build temp config file based on config file (e.g. bt_config.conf.new).
236   const std::string temp_filename = filename + ".new";
237 
238   // Extract directory from file path (e.g. /data/misc/bluedroid).
239   const std::string directoryname = base::FilePath(filename).DirName().value();
240   if (directoryname.empty()) {
241     LOG(ERROR) << __func__ << ": error extracting directory from '" << filename
242                << "': " << strerror(errno);
243     goto error;
244   }
245 
246   dir_fd = open(directoryname.c_str(), O_RDONLY);
247   if (dir_fd < 0) {
248     LOG(ERROR) << __func__ << ": unable to open dir '" << directoryname
249                << "': " << strerror(errno);
250     goto error;
251   }
252 
253   fp = fopen(temp_filename.c_str(), "wt");
254   if (!fp) {
255     LOG(ERROR) << __func__ << ": unable to write to file '" << temp_filename
256                << "': " << strerror(errno);
257     goto error;
258   }
259 
260   for (const section_t& section : config.sections) {
261     serialized << "[" << section.name << "]" << std::endl;
262 
263     for (const entry_t& entry : section.entries)
264       serialized << entry.key << " = " << entry.value << std::endl;
265 
266     serialized << std::endl;
267   }
268 
269   if (fprintf(fp, "%s", serialized.str().c_str()) < 0) {
270     LOG(ERROR) << __func__ << ": unable to write to file '" << temp_filename
271                << "': " << strerror(errno);
272     goto error;
273   }
274 
275   // Sync written temp file out to disk. fsync() is blocking until data makes it
276   // to disk.
277   if (fsync(fileno(fp)) < 0) {
278     LOG(WARNING) << __func__ << ": unable to fsync file '" << temp_filename
279                  << "': " << strerror(errno);
280   }
281 
282   if (fclose(fp) == EOF) {
283     LOG(ERROR) << __func__ << ": unable to close file '" << temp_filename
284                << "': " << strerror(errno);
285     goto error;
286   }
287   fp = nullptr;
288 
289   // Change the file's permissions to Read/Write by User and Group
290   if (chmod(temp_filename.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) ==
291       -1) {
292     LOG(ERROR) << __func__ << ": unable to change file permissions '"
293                << filename << "': " << strerror(errno);
294     goto error;
295   }
296 
297   // Rename written temp file to the actual config file.
298   if (rename(temp_filename.c_str(), filename.c_str()) == -1) {
299     LOG(ERROR) << __func__ << ": unable to commit file '" << filename
300                << "': " << strerror(errno);
301     goto error;
302   }
303 
304   // This should ensure the directory is updated as well.
305   if (fsync(dir_fd) < 0) {
306     LOG(WARNING) << __func__ << ": unable to fsync dir '" << directoryname
307                  << "': " << strerror(errno);
308   }
309 
310   if (close(dir_fd) < 0) {
311     LOG(ERROR) << __func__ << ": unable to close dir '" << directoryname
312                << "': " << strerror(errno);
313     goto error;
314   }
315 
316   return true;
317 
318 error:
319   // This indicates there is a write issue.  Unlink as partial data is not
320   // acceptable.
321   unlink(temp_filename.c_str());
322   if (fp) fclose(fp);
323   if (dir_fd != -1) close(dir_fd);
324   return false;
325 }
326 
trim(char * str)327 static char* trim(char* str) {
328   while (isspace(*str)) ++str;
329 
330   if (!*str) return str;
331 
332   char* end_str = str + strlen(str) - 1;
333   while (end_str > str && isspace(*end_str)) --end_str;
334 
335   end_str[1] = '\0';
336   return str;
337 }
338 
config_parse(FILE * fp,config_t * config)339 static bool config_parse(FILE* fp, config_t* config) {
340   CHECK(fp != nullptr);
341   CHECK(config != nullptr);
342 
343   int line_num = 0;
344   char line[1024];
345   char section[1024];
346   strcpy(section, CONFIG_DEFAULT_SECTION);
347 
348   while (fgets(line, sizeof(line), fp)) {
349     char* line_ptr = trim(line);
350     ++line_num;
351 
352     // Skip blank and comment lines.
353     if (*line_ptr == '\0' || *line_ptr == '#') continue;
354 
355     if (*line_ptr == '[') {
356       size_t len = strlen(line_ptr);
357       if (line_ptr[len - 1] != ']') {
358         VLOG(1) << __func__ << ": unterminated section name on line "
359                 << line_num;
360         return false;
361       }
362       strncpy(section, line_ptr + 1, len - 2);  // NOLINT (len < 1024)
363       section[len - 2] = '\0';
364     } else {
365       char* split = strchr(line_ptr, '=');
366       if (!split) {
367         VLOG(1) << __func__ << ": no key/value separator found on line "
368                 << line_num;
369         return false;
370       }
371 
372       *split = '\0';
373       config_set_string(config, section, trim(line_ptr), trim(split + 1));
374     }
375   }
376   return true;
377 }
378