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