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