1 /******************************************************************************
2 *
3 * Copyright (C) 2014 Google, Inc.
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 #define LOG_TAG "bt_osi_config"
20
21 #include "osi/include/config.h"
22
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
34 #include "osi/include/allocator.h"
35 #include "osi/include/list.h"
36 #include "osi/include/log.h"
37
38 typedef struct {
39 char* key;
40 char* value;
41 } entry_t;
42
43 typedef struct {
44 char* name;
45 list_t* entries;
46 } section_t;
47
48 struct config_t {
49 list_t* sections;
50 };
51
52 // Empty definition; this type is aliased to list_node_t.
53 struct config_section_iter_t {};
54
55 static bool config_parse(FILE* fp, config_t* config);
56
57 static section_t* section_new(const char* name);
58 static void section_free(void* ptr);
59 static section_t* section_find(const config_t* config, const char* section);
60
61 static entry_t* entry_new(const char* key, const char* value);
62 static void entry_free(void* ptr);
63 static entry_t* entry_find(const config_t* config, const char* section,
64 const char* key);
65
config_new_empty(void)66 config_t* config_new_empty(void) {
67 config_t* config = static_cast<config_t*>(osi_calloc(sizeof(config_t)));
68
69 config->sections = list_new(section_free);
70 if (!config->sections) {
71 LOG_ERROR(LOG_TAG, "%s unable to allocate list for sections.", __func__);
72 goto error;
73 }
74
75 return config;
76
77 error:;
78 config_free(config);
79 return NULL;
80 }
81
config_new(const char * filename)82 config_t* config_new(const char* filename) {
83 CHECK(filename != NULL);
84
85 config_t* config = config_new_empty();
86 if (!config) return NULL;
87
88 FILE* fp = fopen(filename, "rt");
89 if (!fp) {
90 LOG_ERROR(LOG_TAG, "%s unable to open file '%s': %s", __func__, filename,
91 strerror(errno));
92 config_free(config);
93 return NULL;
94 }
95
96 if (!config_parse(fp, config)) {
97 config_free(config);
98 config = NULL;
99 }
100
101 fclose(fp);
102 return config;
103 }
104
config_new_clone(const config_t * src)105 config_t* config_new_clone(const config_t* src) {
106 CHECK(src != NULL);
107
108 config_t* ret = config_new_empty();
109
110 CHECK(ret != NULL);
111
112 for (const list_node_t* node = list_begin(src->sections);
113 node != list_end(src->sections); node = list_next(node)) {
114 section_t* sec = static_cast<section_t*>(list_node(node));
115
116 for (const list_node_t* node_entry = list_begin(sec->entries);
117 node_entry != list_end(sec->entries);
118 node_entry = list_next(node_entry)) {
119 entry_t* entry = static_cast<entry_t*>(list_node(node_entry));
120
121 config_set_string(ret, sec->name, entry->key, entry->value);
122 }
123 }
124
125 return ret;
126 }
127
config_free(config_t * config)128 void config_free(config_t* config) {
129 if (!config) return;
130
131 list_free(config->sections);
132 osi_free(config);
133 }
134
config_has_section(const config_t * config,const char * section)135 bool config_has_section(const config_t* config, const char* section) {
136 CHECK(config != NULL);
137 CHECK(section != NULL);
138
139 return (section_find(config, section) != NULL);
140 }
141
config_has_key(const config_t * config,const char * section,const char * key)142 bool config_has_key(const config_t* config, const char* section,
143 const char* key) {
144 CHECK(config != NULL);
145 CHECK(section != NULL);
146 CHECK(key != NULL);
147
148 return (entry_find(config, section, key) != NULL);
149 }
150
config_get_int(const config_t * config,const char * section,const char * key,int def_value)151 int config_get_int(const config_t* config, const char* section, const char* key,
152 int def_value) {
153 CHECK(config != NULL);
154 CHECK(section != NULL);
155 CHECK(key != NULL);
156
157 entry_t* entry = entry_find(config, section, key);
158 if (!entry) return def_value;
159
160 char* endptr;
161 int ret = strtol(entry->value, &endptr, 0);
162 return (*endptr == '\0') ? ret : def_value;
163 }
164
config_get_bool(const config_t * config,const char * section,const char * key,bool def_value)165 bool config_get_bool(const config_t* config, const char* section,
166 const char* key, bool def_value) {
167 CHECK(config != NULL);
168 CHECK(section != NULL);
169 CHECK(key != NULL);
170
171 entry_t* entry = entry_find(config, section, key);
172 if (!entry) return def_value;
173
174 if (!strcmp(entry->value, "true")) return true;
175 if (!strcmp(entry->value, "false")) return false;
176
177 return def_value;
178 }
179
config_get_string(const config_t * config,const char * section,const char * key,const char * def_value)180 const char* config_get_string(const config_t* config, const char* section,
181 const char* key, const char* def_value) {
182 CHECK(config != NULL);
183 CHECK(section != NULL);
184 CHECK(key != NULL);
185
186 entry_t* entry = entry_find(config, section, key);
187 if (!entry) return def_value;
188
189 return entry->value;
190 }
191
config_set_int(config_t * config,const char * section,const char * key,int value)192 void config_set_int(config_t* config, const char* section, const char* key,
193 int value) {
194 CHECK(config != NULL);
195 CHECK(section != NULL);
196 CHECK(key != NULL);
197
198 char value_str[32] = {0};
199 snprintf(value_str, sizeof(value_str), "%d", value);
200 config_set_string(config, section, key, value_str);
201 }
202
config_set_bool(config_t * config,const char * section,const char * key,bool value)203 void config_set_bool(config_t* config, const char* section, const char* key,
204 bool value) {
205 CHECK(config != NULL);
206 CHECK(section != NULL);
207 CHECK(key != NULL);
208
209 config_set_string(config, section, key, value ? "true" : "false");
210 }
211
config_set_string(config_t * config,const char * section,const char * key,const char * value)212 void config_set_string(config_t* config, const char* section, const char* key,
213 const char* value) {
214 section_t* sec = section_find(config, section);
215 if (!sec) {
216 sec = section_new(section);
217 list_append(config->sections, sec);
218 }
219
220 for (const list_node_t* node = list_begin(sec->entries);
221 node != list_end(sec->entries); node = list_next(node)) {
222 entry_t* entry = static_cast<entry_t*>(list_node(node));
223 if (!strcmp(entry->key, key)) {
224 osi_free(entry->value);
225 entry->value = osi_strdup(value);
226 return;
227 }
228 }
229
230 entry_t* entry = entry_new(key, value);
231 list_append(sec->entries, entry);
232 }
233
config_remove_section(config_t * config,const char * section)234 bool config_remove_section(config_t* config, const char* section) {
235 CHECK(config != NULL);
236 CHECK(section != NULL);
237
238 section_t* sec = section_find(config, section);
239 if (!sec) return false;
240
241 return list_remove(config->sections, sec);
242 }
243
config_remove_key(config_t * config,const char * section,const char * key)244 bool config_remove_key(config_t* config, const char* section, const char* key) {
245 CHECK(config != NULL);
246 CHECK(section != NULL);
247 CHECK(key != NULL);
248
249 section_t* sec = section_find(config, section);
250 entry_t* entry = entry_find(config, section, key);
251 if (!sec || !entry) return false;
252
253 return list_remove(sec->entries, entry);
254 }
255
config_section_begin(const config_t * config)256 const config_section_node_t* config_section_begin(const config_t* config) {
257 CHECK(config != NULL);
258 return (const config_section_node_t*)list_begin(config->sections);
259 }
260
config_section_end(const config_t * config)261 const config_section_node_t* config_section_end(const config_t* config) {
262 CHECK(config != NULL);
263 return (const config_section_node_t*)list_end(config->sections);
264 }
265
config_section_next(const config_section_node_t * node)266 const config_section_node_t* config_section_next(
267 const config_section_node_t* node) {
268 CHECK(node != NULL);
269 return (const config_section_node_t*)list_next((const list_node_t*)node);
270 }
271
config_section_name(const config_section_node_t * node)272 const char* config_section_name(const config_section_node_t* node) {
273 CHECK(node != NULL);
274 const list_node_t* lnode = (const list_node_t*)node;
275 const section_t* section = (const section_t*)list_node(lnode);
276 return section->name;
277 }
278
config_save(const config_t * config,const char * filename)279 bool config_save(const config_t* config, const char* filename) {
280 CHECK(config != NULL);
281 CHECK(filename != NULL);
282 CHECK(*filename != '\0');
283
284 // Steps to ensure content of config file gets to disk:
285 //
286 // 1) Open and write to temp file (e.g. bt_config.conf.new).
287 // 2) Sync the temp file to disk with fsync().
288 // 3) Rename temp file to actual config file (e.g. bt_config.conf).
289 // This ensures atomic update.
290 // 4) Sync directory that has the conf file with fsync().
291 // This ensures directory entries are up-to-date.
292 int dir_fd = -1;
293 FILE* fp = NULL;
294
295 // Build temp config file based on config file (e.g. bt_config.conf.new).
296 static const char* temp_file_ext = ".new";
297 const int filename_len = strlen(filename);
298 const int temp_filename_len = filename_len + strlen(temp_file_ext) + 1;
299 char* temp_filename = static_cast<char*>(osi_calloc(temp_filename_len));
300 snprintf(temp_filename, temp_filename_len, "%s%s", filename, temp_file_ext);
301
302 // Extract directory from file path (e.g. /data/misc/bluedroid).
303 char* temp_dirname = osi_strdup(filename);
304 const char* directoryname = dirname(temp_dirname);
305 if (!directoryname) {
306 LOG_ERROR(LOG_TAG, "%s error extracting directory from '%s': %s", __func__,
307 filename, strerror(errno));
308 goto error;
309 }
310
311 dir_fd = open(directoryname, O_RDONLY);
312 if (dir_fd < 0) {
313 LOG_ERROR(LOG_TAG, "%s unable to open dir '%s': %s", __func__,
314 directoryname, strerror(errno));
315 goto error;
316 }
317
318 fp = fopen(temp_filename, "wt");
319 if (!fp) {
320 LOG_ERROR(LOG_TAG, "%s unable to write file '%s': %s", __func__,
321 temp_filename, strerror(errno));
322 goto error;
323 }
324
325 for (const list_node_t* node = list_begin(config->sections);
326 node != list_end(config->sections); node = list_next(node)) {
327 const section_t* section = (const section_t*)list_node(node);
328 if (fprintf(fp, "[%s]\n", section->name) < 0) {
329 LOG_ERROR(LOG_TAG, "%s unable to write to file '%s': %s", __func__,
330 temp_filename, strerror(errno));
331 goto error;
332 }
333
334 for (const list_node_t* enode = list_begin(section->entries);
335 enode != list_end(section->entries); enode = list_next(enode)) {
336 const entry_t* entry = (const entry_t*)list_node(enode);
337 if (fprintf(fp, "%s = %s\n", entry->key, entry->value) < 0) {
338 LOG_ERROR(LOG_TAG, "%s unable to write to file '%s': %s", __func__,
339 temp_filename, strerror(errno));
340 goto error;
341 }
342 }
343
344 // Only add a separating newline if there are more sections.
345 if (list_next(node) != list_end(config->sections)) {
346 if (fputc('\n', fp) == EOF) {
347 LOG_ERROR(LOG_TAG, "%s unable to write to file '%s': %s", __func__,
348 temp_filename, strerror(errno));
349 goto error;
350 }
351 }
352 }
353
354 // Sync written temp file out to disk. fsync() is blocking until data makes it
355 // to disk.
356 if (fsync(fileno(fp)) < 0) {
357 LOG_WARN(LOG_TAG, "%s unable to fsync file '%s': %s", __func__,
358 temp_filename, strerror(errno));
359 }
360
361 if (fclose(fp) == EOF) {
362 LOG_ERROR(LOG_TAG, "%s unable to close file '%s': %s", __func__,
363 temp_filename, strerror(errno));
364 goto error;
365 }
366 fp = NULL;
367
368 // Change the file's permissions to Read/Write by User and Group
369 if (chmod(temp_filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) == -1) {
370 LOG_ERROR(LOG_TAG, "%s unable to change file permissions '%s': %s",
371 __func__, filename, strerror(errno));
372 goto error;
373 }
374
375 // Rename written temp file to the actual config file.
376 if (rename(temp_filename, filename) == -1) {
377 LOG_ERROR(LOG_TAG, "%s unable to commit file '%s': %s", __func__, filename,
378 strerror(errno));
379 goto error;
380 }
381
382 // This should ensure the directory is updated as well.
383 if (fsync(dir_fd) < 0) {
384 LOG_WARN(LOG_TAG, "%s unable to fsync dir '%s': %s", __func__,
385 directoryname, strerror(errno));
386 }
387
388 if (close(dir_fd) < 0) {
389 LOG_ERROR(LOG_TAG, "%s unable to close dir '%s': %s", __func__,
390 directoryname, strerror(errno));
391 goto error;
392 }
393
394 osi_free(temp_filename);
395 osi_free(temp_dirname);
396 return true;
397
398 error:
399 // This indicates there is a write issue. Unlink as partial data is not
400 // acceptable.
401 unlink(temp_filename);
402 if (fp) fclose(fp);
403 if (dir_fd != -1) close(dir_fd);
404 osi_free(temp_filename);
405 osi_free(temp_dirname);
406 return false;
407 }
408
trim(char * str)409 static char* trim(char* str) {
410 while (isspace(*str)) ++str;
411
412 if (!*str) return str;
413
414 char* end_str = str + strlen(str) - 1;
415 while (end_str > str && isspace(*end_str)) --end_str;
416
417 end_str[1] = '\0';
418 return str;
419 }
420
config_parse(FILE * fp,config_t * config)421 static bool config_parse(FILE* fp, config_t* config) {
422 CHECK(fp != NULL);
423 CHECK(config != NULL);
424
425 int line_num = 0;
426 char line[1024];
427 char section[1024];
428 strcpy(section, CONFIG_DEFAULT_SECTION);
429
430 while (fgets(line, sizeof(line), fp)) {
431 char* line_ptr = trim(line);
432 ++line_num;
433
434 // Skip blank and comment lines.
435 if (*line_ptr == '\0' || *line_ptr == '#') continue;
436
437 if (*line_ptr == '[') {
438 size_t len = strlen(line_ptr);
439 if (line_ptr[len - 1] != ']') {
440 LOG_DEBUG(LOG_TAG, "%s unterminated section name on line %d.", __func__,
441 line_num);
442 return false;
443 }
444 strncpy(section, line_ptr + 1, len - 2);
445 section[len - 2] = '\0';
446 } else {
447 char* split = strchr(line_ptr, '=');
448 if (!split) {
449 LOG_DEBUG(LOG_TAG, "%s no key/value separator found on line %d.",
450 __func__, line_num);
451 return false;
452 }
453
454 *split = '\0';
455 config_set_string(config, section, trim(line_ptr), trim(split + 1));
456 }
457 }
458 return true;
459 }
460
section_new(const char * name)461 static section_t* section_new(const char* name) {
462 section_t* section = static_cast<section_t*>(osi_calloc(sizeof(section_t)));
463
464 section->name = osi_strdup(name);
465 section->entries = list_new(entry_free);
466 return section;
467 }
468
section_free(void * ptr)469 static void section_free(void* ptr) {
470 if (!ptr) return;
471
472 section_t* section = static_cast<section_t*>(ptr);
473 osi_free(section->name);
474 list_free(section->entries);
475 osi_free(section);
476 }
477
section_find(const config_t * config,const char * section)478 static section_t* section_find(const config_t* config, const char* section) {
479 for (const list_node_t* node = list_begin(config->sections);
480 node != list_end(config->sections); node = list_next(node)) {
481 section_t* sec = static_cast<section_t*>(list_node(node));
482 if (!strcmp(sec->name, section)) return sec;
483 }
484
485 return NULL;
486 }
487
entry_new(const char * key,const char * value)488 static entry_t* entry_new(const char* key, const char* value) {
489 entry_t* entry = static_cast<entry_t*>(osi_calloc(sizeof(entry_t)));
490
491 entry->key = osi_strdup(key);
492 entry->value = osi_strdup(value);
493 return entry;
494 }
495
entry_free(void * ptr)496 static void entry_free(void* ptr) {
497 if (!ptr) return;
498
499 entry_t* entry = static_cast<entry_t*>(ptr);
500 osi_free(entry->key);
501 osi_free(entry->value);
502 osi_free(entry);
503 }
504
entry_find(const config_t * config,const char * section,const char * key)505 static entry_t* entry_find(const config_t* config, const char* section,
506 const char* key) {
507 section_t* sec = section_find(config, section);
508 if (!sec) return NULL;
509
510 for (const list_node_t* node = list_begin(sec->entries);
511 node != list_end(sec->entries); node = list_next(node)) {
512 entry_t* entry = static_cast<entry_t*>(list_node(node));
513 if (!strcmp(entry->key, key)) return entry;
514 }
515
516 return NULL;
517 }
518