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 <stdio.h> 25 #include <stdlib.h> 26 #include <string.h> 27 #include <sys/stat.h> 28 29 #include "osi/include/allocator.h" 30 #include "osi/include/config.h" 31 #include "osi/include/list.h" 32 #include "osi/include/log.h" 33 34 typedef struct { 35 char *key; 36 char *value; 37 } entry_t; 38 39 typedef struct { 40 char *name; 41 list_t *entries; 42 } section_t; 43 44 struct config_t { 45 list_t *sections; 46 }; 47 48 // Empty definition; this type is aliased to list_node_t. 49 struct config_section_iter_t {}; 50 51 static void config_parse(FILE *fp, config_t *config); 52 53 static section_t *section_new(const char *name); 54 static void section_free(void *ptr); 55 static section_t *section_find(const config_t *config, const char *section); 56 57 static entry_t *entry_new(const char *key, const char *value); 58 static void entry_free(void *ptr); 59 static entry_t *entry_find(const config_t *config, const char *section, const char *key); 60 61 config_t *config_new_empty(void) { 62 config_t *config = osi_calloc(sizeof(config_t)); 63 if (!config) { 64 LOG_ERROR("%s unable to allocate memory for config_t.", __func__); 65 goto error; 66 } 67 68 config->sections = list_new(section_free); 69 if (!config->sections) { 70 LOG_ERROR("%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 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("%s unable to open file '%s': %s", __func__, filename, strerror(errno)); 91 config_free(config); 92 return NULL; 93 } 94 config_parse(fp, config); 95 fclose(fp); 96 return config; 97 } 98 99 void config_free(config_t *config) { 100 if (!config) 101 return; 102 103 list_free(config->sections); 104 osi_free(config); 105 } 106 107 bool config_has_section(const config_t *config, const char *section) { 108 assert(config != NULL); 109 assert(section != NULL); 110 111 return (section_find(config, section) != NULL); 112 } 113 114 bool config_has_key(const config_t *config, const char *section, const char *key) { 115 assert(config != NULL); 116 assert(section != NULL); 117 assert(key != NULL); 118 119 return (entry_find(config, section, key) != NULL); 120 } 121 122 int config_get_int(const config_t *config, const char *section, const char *key, int def_value) { 123 assert(config != NULL); 124 assert(section != NULL); 125 assert(key != NULL); 126 127 entry_t *entry = entry_find(config, section, key); 128 if (!entry) 129 return def_value; 130 131 char *endptr; 132 int ret = strtol(entry->value, &endptr, 0); 133 return (*endptr == '\0') ? ret : def_value; 134 } 135 136 bool config_get_bool(const config_t *config, const char *section, const char *key, bool def_value) { 137 assert(config != NULL); 138 assert(section != NULL); 139 assert(key != NULL); 140 141 entry_t *entry = entry_find(config, section, key); 142 if (!entry) 143 return def_value; 144 145 if (!strcmp(entry->value, "true")) 146 return true; 147 if (!strcmp(entry->value, "false")) 148 return false; 149 150 return def_value; 151 } 152 153 const char *config_get_string(const config_t *config, const char *section, const char *key, const char *def_value) { 154 assert(config != NULL); 155 assert(section != NULL); 156 assert(key != NULL); 157 158 entry_t *entry = entry_find(config, section, key); 159 if (!entry) 160 return def_value; 161 162 return entry->value; 163 } 164 165 void config_set_int(config_t *config, const char *section, const char *key, int value) { 166 assert(config != NULL); 167 assert(section != NULL); 168 assert(key != NULL); 169 170 char value_str[32] = { 0 }; 171 sprintf(value_str, "%d", value); 172 config_set_string(config, section, key, value_str); 173 } 174 175 void config_set_bool(config_t *config, const char *section, const char *key, bool value) { 176 assert(config != NULL); 177 assert(section != NULL); 178 assert(key != NULL); 179 180 config_set_string(config, section, key, value ? "true" : "false"); 181 } 182 183 void config_set_string(config_t *config, const char *section, const char *key, const char *value) { 184 section_t *sec = section_find(config, section); 185 if (!sec) { 186 sec = section_new(section); 187 list_append(config->sections, sec); 188 } 189 190 for (const list_node_t *node = list_begin(sec->entries); node != list_end(sec->entries); node = list_next(node)) { 191 entry_t *entry = list_node(node); 192 if (!strcmp(entry->key, key)) { 193 osi_free(entry->value); 194 entry->value = osi_strdup(value); 195 return; 196 } 197 } 198 199 entry_t *entry = entry_new(key, value); 200 list_append(sec->entries, entry); 201 } 202 203 bool config_remove_section(config_t *config, const char *section) { 204 assert(config != NULL); 205 assert(section != NULL); 206 207 section_t *sec = section_find(config, section); 208 if (!sec) 209 return false; 210 211 return list_remove(config->sections, sec); 212 } 213 214 bool config_remove_key(config_t *config, const char *section, const char *key) { 215 assert(config != NULL); 216 assert(section != NULL); 217 assert(key != NULL); 218 219 section_t *sec = section_find(config, section); 220 entry_t *entry = entry_find(config, section, key); 221 if (!sec || !entry) 222 return false; 223 224 return list_remove(sec->entries, entry); 225 } 226 227 const config_section_node_t *config_section_begin(const config_t *config) { 228 assert(config != NULL); 229 return (const config_section_node_t *)list_begin(config->sections); 230 } 231 232 const config_section_node_t *config_section_end(const config_t *config) { 233 assert(config != NULL); 234 return (const config_section_node_t *)list_end(config->sections); 235 } 236 237 const config_section_node_t *config_section_next(const config_section_node_t *node) { 238 assert(node != NULL); 239 return (const config_section_node_t *)list_next((const list_node_t *)node); 240 } 241 242 const char *config_section_name(const config_section_node_t *node) { 243 assert(node != NULL); 244 const list_node_t *lnode = (const list_node_t *)node; 245 const section_t *section = (const section_t *)list_node(lnode); 246 return section->name; 247 } 248 249 bool config_save(const config_t *config, const char *filename) { 250 assert(config != NULL); 251 assert(filename != NULL); 252 assert(*filename != '\0'); 253 254 char *temp_filename = osi_calloc(strlen(filename) + 5); 255 if (!temp_filename) { 256 LOG_ERROR("%s unable to allocate memory for filename.", __func__); 257 return false; 258 } 259 260 strcpy(temp_filename, filename); 261 strcat(temp_filename, ".new"); 262 263 FILE *fp = fopen(temp_filename, "wt"); 264 if (!fp) { 265 LOG_ERROR("%s unable to write file '%s': %s", __func__, temp_filename, strerror(errno)); 266 goto error; 267 } 268 269 for (const list_node_t *node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) { 270 const section_t *section = (const section_t *)list_node(node); 271 fprintf(fp, "[%s]\n", section->name); 272 273 for (const list_node_t *enode = list_begin(section->entries); enode != list_end(section->entries); enode = list_next(enode)) { 274 const entry_t *entry = (const entry_t *)list_node(enode); 275 fprintf(fp, "%s = %s\n", entry->key, entry->value); 276 } 277 278 // Only add a separating newline if there are more sections. 279 if (list_next(node) != list_end(config->sections)) 280 fputc('\n', fp); 281 } 282 283 fflush(fp); 284 fclose(fp); 285 286 // Change the file's permissions to Read/Write by User and Group 287 if (chmod(temp_filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) == -1) { 288 LOG_ERROR("%s unable to change file permissions '%s': %s", __func__, filename, strerror(errno)); 289 goto error; 290 } 291 292 if (rename(temp_filename, filename) == -1) { 293 LOG_ERROR("%s unable to commit file '%s': %s", __func__, filename, strerror(errno)); 294 goto error; 295 } 296 297 osi_free(temp_filename); 298 return true; 299 300 error:; 301 unlink(temp_filename); 302 osi_free(temp_filename); 303 return false; 304 } 305 306 static char *trim(char *str) { 307 while (isspace(*str)) 308 ++str; 309 310 if (!*str) 311 return str; 312 313 char *end_str = str + strlen(str) - 1; 314 while (end_str > str && isspace(*end_str)) 315 --end_str; 316 317 end_str[1] = '\0'; 318 return str; 319 } 320 321 static void config_parse(FILE *fp, config_t *config) { 322 assert(fp != NULL); 323 assert(config != NULL); 324 325 int line_num = 0; 326 char line[1024]; 327 char section[1024]; 328 strcpy(section, CONFIG_DEFAULT_SECTION); 329 330 while (fgets(line, sizeof(line), fp)) { 331 char *line_ptr = trim(line); 332 ++line_num; 333 334 // Skip blank and comment lines. 335 if (*line_ptr == '\0' || *line_ptr == '#') 336 continue; 337 338 if (*line_ptr == '[') { 339 size_t len = strlen(line_ptr); 340 if (line_ptr[len - 1] != ']') { 341 LOG_DEBUG("%s unterminated section name on line %d.", __func__, line_num); 342 continue; 343 } 344 strncpy(section, line_ptr + 1, len - 2); 345 section[len - 2] = '\0'; 346 } else { 347 char *split = strchr(line_ptr, '='); 348 if (!split) { 349 LOG_DEBUG("%s no key/value separator found on line %d.", __func__, line_num); 350 continue; 351 } 352 353 *split = '\0'; 354 config_set_string(config, section, trim(line_ptr), trim(split + 1)); 355 } 356 } 357 } 358 359 static section_t *section_new(const char *name) { 360 section_t *section = osi_calloc(sizeof(section_t)); 361 if (!section) 362 return NULL; 363 364 section->name = osi_strdup(name); 365 section->entries = list_new(entry_free); 366 return section; 367 } 368 369 static void section_free(void *ptr) { 370 if (!ptr) 371 return; 372 373 section_t *section = ptr; 374 osi_free(section->name); 375 list_free(section->entries); 376 osi_free(section); 377 } 378 379 static section_t *section_find(const config_t *config, const char *section) { 380 for (const list_node_t *node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) { 381 section_t *sec = list_node(node); 382 if (!strcmp(sec->name, section)) 383 return sec; 384 } 385 386 return NULL; 387 } 388 389 static entry_t *entry_new(const char *key, const char *value) { 390 entry_t *entry = osi_calloc(sizeof(entry_t)); 391 if (!entry) 392 return NULL; 393 394 entry->key = osi_strdup(key); 395 entry->value = osi_strdup(value); 396 return entry; 397 } 398 399 static void entry_free(void *ptr) { 400 if (!ptr) 401 return; 402 403 entry_t *entry = ptr; 404 osi_free(entry->key); 405 osi_free(entry->value); 406 osi_free(entry); 407 } 408 409 static entry_t *entry_find(const config_t *config, const char *section, const char *key) { 410 section_t *sec = section_find(config, section); 411 if (!sec) 412 return NULL; 413 414 for (const list_node_t *node = list_begin(sec->entries); node != list_end(sec->entries); node = list_next(node)) { 415 entry_t *entry = list_node(node); 416 if (!strcmp(entry->key, key)) 417 return entry; 418 } 419 420 return NULL; 421 } 422