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