• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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