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