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 [§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
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