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