1 /*
2  * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define LOG_TAG "bt_osi_config"
8 #include "esp_system.h"
9 #include "nvs_flash.h"
10 #include "nvs.h"
11 
12 #include <ctype.h>
13 #include <errno.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 
18 #include "bt_common.h"
19 #include "osi/allocator.h"
20 #include "osi/config.h"
21 #include "osi/list.h"
22 
23 #define CONFIG_FILE_MAX_SIZE             (1536)//1.5k
24 #define CONFIG_FILE_DEFAULE_LENGTH       (2048)
25 #define CONFIG_KEY                       "bt_cfg_key"
26 typedef struct {
27     char *key;
28     char *value;
29 } entry_t;
30 
31 typedef struct {
32     char *name;
33     list_t *entries;
34 } section_t;
35 
36 struct config_t {
37     list_t *sections;
38 };
39 
40 // Empty definition; this type is aliased to list_node_t.
41 struct config_section_iter_t {};
42 
43 static void config_parse(nvs_handle_t fp, config_t *config);
44 
45 static section_t *section_new(const char *name);
46 static void section_free(void *ptr);
47 static section_t *section_find(const config_t *config, const char *section);
48 
49 static entry_t *entry_new(const char *key, const char *value);
50 static void entry_free(void *ptr);
51 static entry_t *entry_find(const config_t *config, const char *section, const char *key);
52 
config_new_empty(void)53 config_t *config_new_empty(void)
54 {
55     config_t *config = osi_calloc(sizeof(config_t));
56     if (!config) {
57         OSI_TRACE_ERROR("%s unable to allocate memory for config_t.\n", __func__);
58         goto error;
59     }
60 
61     config->sections = list_new(section_free);
62     if (!config->sections) {
63         OSI_TRACE_ERROR("%s unable to allocate list for sections.\n", __func__);
64         goto error;
65     }
66 
67     return config;
68 
69 error:;
70     config_free(config);
71     return NULL;
72 }
73 
config_new(const char * filename)74 config_t *config_new(const char *filename)
75 {
76     assert(filename != NULL);
77 
78     config_t *config = config_new_empty();
79     if (!config) {
80         return NULL;
81     }
82 
83     esp_err_t err;
84     nvs_handle_t fp;
85     err = nvs_open(filename, NVS_READWRITE, &fp);
86     if (err != ESP_OK) {
87         if (err == ESP_ERR_NVS_NOT_INITIALIZED) {
88             OSI_TRACE_ERROR("%s: NVS not initialized. "
89                       "Call nvs_flash_init before initializing bluetooth.", __func__);
90         } else {
91             OSI_TRACE_ERROR("%s unable to open NVS namespace '%s'\n", __func__, filename);
92         }
93         config_free(config);
94         return NULL;
95     }
96 
97     config_parse(fp, config);
98     nvs_close(fp);
99     return config;
100 }
101 
config_free(config_t * config)102 void config_free(config_t *config)
103 {
104     if (!config) {
105         return;
106     }
107 
108     list_free(config->sections);
109     osi_free(config);
110 }
111 
config_has_section(const config_t * config,const char * section)112 bool config_has_section(const config_t *config, const char *section)
113 {
114     assert(config != NULL);
115     assert(section != NULL);
116 
117     return (section_find(config, section) != NULL);
118 }
119 
config_has_key(const config_t * config,const char * section,const char * key)120 bool config_has_key(const config_t *config, const char *section, const char *key)
121 {
122     assert(config != NULL);
123     assert(section != NULL);
124     assert(key != NULL);
125 
126     return (entry_find(config, section, key) != NULL);
127 }
128 
config_has_key_in_section(config_t * config,const char * key,char * key_value)129 bool config_has_key_in_section(config_t *config, const char *key, char *key_value)
130 {
131     OSI_TRACE_DEBUG("key = %s, value = %s", key, key_value);
132     for (const list_node_t *node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) {
133         const section_t *section = (const section_t *)list_node(node);
134 
135         for (const list_node_t *node = list_begin(section->entries); node != list_end(section->entries); node = list_next(node)) {
136             entry_t *entry = list_node(node);
137             OSI_TRACE_DEBUG("entry->key = %s, entry->value = %s", entry->key, entry->value);
138             if (!strcmp(entry->key, key) && !strcmp(entry->value, key_value)) {
139                 OSI_TRACE_DEBUG("%s, the irk aready in the flash.", __func__);
140                 return true;
141             }
142         }
143     }
144 
145     return false;
146 }
147 
config_get_int(const config_t * config,const char * section,const char * key,int def_value)148 int config_get_int(const config_t *config, const char *section, const char *key, int def_value)
149 {
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 
159     char *endptr;
160     int ret = strtol(entry->value, &endptr, 0);
161     return (*endptr == '\0') ? ret : def_value;
162 }
163 
config_get_bool(const config_t * config,const char * section,const char * key,bool def_value)164 bool config_get_bool(const config_t *config, const char *section, const char *key, bool def_value)
165 {
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 
175     if (!strcmp(entry->value, "true")) {
176         return true;
177     }
178     if (!strcmp(entry->value, "false")) {
179         return false;
180     }
181 
182     return def_value;
183 }
184 
config_get_string(const config_t * config,const char * section,const char * key,const char * def_value)185 const char *config_get_string(const config_t *config, const char *section, const char *key, const char *def_value)
186 {
187     assert(config != NULL);
188     assert(section != NULL);
189     assert(key != NULL);
190 
191     entry_t *entry = entry_find(config, section, key);
192     if (!entry) {
193         return def_value;
194     }
195 
196     return entry->value;
197 }
198 
config_set_int(config_t * config,const char * section,const char * key,int value)199 void config_set_int(config_t *config, const char *section, const char *key, int value)
200 {
201     assert(config != NULL);
202     assert(section != NULL);
203     assert(key != NULL);
204 
205     char value_str[32] = { 0 };
206     sprintf(value_str, "%d", value);
207     config_set_string(config, section, key, value_str, false);
208 }
209 
config_set_bool(config_t * config,const char * section,const char * key,bool value)210 void config_set_bool(config_t *config, const char *section, const char *key, bool value)
211 {
212     assert(config != NULL);
213     assert(section != NULL);
214     assert(key != NULL);
215 
216     config_set_string(config, section, key, value ? "true" : "false", false);
217 }
218 
config_set_string(config_t * config,const char * section,const char * key,const char * value,bool insert_back)219 void config_set_string(config_t *config, const char *section, const char *key, const char *value, bool insert_back)
220 {
221     section_t *sec = section_find(config, section);
222     if (!sec) {
223         sec = section_new(section);
224         if (insert_back) {
225             list_append(config->sections, sec);
226         } else {
227             list_prepend(config->sections, sec);
228         }
229     }
230 
231     for (const list_node_t *node = list_begin(sec->entries); node != list_end(sec->entries); node = list_next(node)) {
232         entry_t *entry = list_node(node);
233         if (!strcmp(entry->key, key)) {
234             osi_free(entry->value);
235             entry->value = osi_strdup(value);
236             return;
237         }
238     }
239 
240     entry_t *entry = entry_new(key, value);
241     list_append(sec->entries, entry);
242 }
243 
config_remove_section(config_t * config,const char * section)244 bool config_remove_section(config_t *config, const char *section)
245 {
246     assert(config != NULL);
247     assert(section != NULL);
248 
249     section_t *sec = section_find(config, section);
250     if (!sec) {
251         return false;
252     }
253 
254     return list_remove(config->sections, sec);
255 }
256 
config_update_newest_section(config_t * config,const char * section)257 bool config_update_newest_section(config_t *config, const char *section)
258 {
259     assert(config != NULL);
260     assert(section != NULL);
261 
262     list_node_t *first_node = list_begin(config->sections);
263     if (first_node == NULL) {
264         return false;
265     }
266     section_t *first_sec = list_node(first_node);
267     if (strcmp(first_sec->name, section) == 0) {
268         return true;
269     }
270 
271     for (const list_node_t *node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) {
272         section_t *sec = list_node(node);
273         if (strcmp(sec->name, section) == 0) {
274             list_delete(config->sections, sec);
275             list_prepend(config->sections, sec);
276             return true;
277         }
278     }
279 
280     return false;
281 }
282 
config_remove_key(config_t * config,const char * section,const char * key)283 bool config_remove_key(config_t *config, const char *section, const char *key)
284 {
285     assert(config != NULL);
286     assert(section != NULL);
287     assert(key != NULL);
288     bool ret;
289 
290     section_t *sec = section_find(config, section);
291     entry_t *entry = entry_find(config, section, key);
292     if (!sec || !entry) {
293         return false;
294     }
295 
296     ret = list_remove(sec->entries, entry);
297     if (list_length(sec->entries) == 0) {
298         OSI_TRACE_DEBUG("%s remove section name:%s",__func__, section);
299         ret &= config_remove_section(config, section);
300     }
301     return ret;
302 }
303 
config_section_begin(const config_t * config)304 const config_section_node_t *config_section_begin(const config_t *config)
305 {
306     assert(config != NULL);
307     return (const config_section_node_t *)list_begin(config->sections);
308 }
309 
config_section_end(const config_t * config)310 const config_section_node_t *config_section_end(const config_t *config)
311 {
312     assert(config != NULL);
313     return (const config_section_node_t *)list_end(config->sections);
314 }
315 
config_section_next(const config_section_node_t * node)316 const config_section_node_t *config_section_next(const config_section_node_t *node)
317 {
318     assert(node != NULL);
319     return (const config_section_node_t *)list_next((const list_node_t *)node);
320 }
321 
config_section_name(const config_section_node_t * node)322 const char *config_section_name(const config_section_node_t *node)
323 {
324     assert(node != NULL);
325     const list_node_t *lnode = (const list_node_t *)node;
326     const section_t *section = (const section_t *)list_node(lnode);
327     return section->name;
328 }
329 
get_config_size(const config_t * config)330 static int get_config_size(const config_t *config)
331 {
332     assert(config != NULL);
333 
334     int w_len = 0, total_size = 0;
335 
336     for (const list_node_t *node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) {
337         const section_t *section = (const section_t *)list_node(node);
338         w_len = strlen(section->name) + strlen("[]\n");// format "[section->name]\n"
339         total_size += w_len;
340 
341         for (const list_node_t *enode = list_begin(section->entries); enode != list_end(section->entries); enode = list_next(enode)) {
342             const entry_t *entry = (const entry_t *)list_node(enode);
343             w_len = strlen(entry->key) + strlen(entry->value) + strlen(" = \n");// format "entry->key = entry->value\n"
344             total_size += w_len;
345         }
346 
347         // Only add a separating newline if there are more sections.
348         if (list_next(node) != list_end(config->sections)) {
349                 total_size ++;  //'\n'
350         } else {
351             break;
352         }
353     }
354     total_size ++; //'\0'
355     return total_size;
356 }
357 
get_config_size_from_flash(nvs_handle_t fp)358 static int get_config_size_from_flash(nvs_handle_t fp)
359 {
360     assert(fp != 0);
361 
362     esp_err_t err;
363     const size_t keyname_bufsz = sizeof(CONFIG_KEY) + 5 + 1; // including log10(sizeof(i))
364     char *keyname = osi_calloc(keyname_bufsz);
365     if (!keyname){
366         OSI_TRACE_ERROR("%s, malloc error\n", __func__);
367         return 0;
368     }
369     size_t length = CONFIG_FILE_DEFAULE_LENGTH;
370     size_t total_length = 0;
371     uint16_t i = 0;
372     snprintf(keyname, keyname_bufsz, "%s%d", CONFIG_KEY, 0);
373     err = nvs_get_blob(fp, keyname, NULL, &length);
374     if (err == ESP_ERR_NVS_NOT_FOUND) {
375         osi_free(keyname);
376         return 0;
377     }
378     if (err != ESP_OK) {
379         OSI_TRACE_ERROR("%s, error %d\n", __func__, err);
380         osi_free(keyname);
381         return 0;
382     }
383     total_length += length;
384     while (length == CONFIG_FILE_MAX_SIZE) {
385         length = CONFIG_FILE_DEFAULE_LENGTH;
386         snprintf(keyname, keyname_bufsz, "%s%d", CONFIG_KEY, ++i);
387         err = nvs_get_blob(fp, keyname, NULL, &length);
388 
389         if (err == ESP_ERR_NVS_NOT_FOUND) {
390             break;
391         }
392         if (err != ESP_OK) {
393             OSI_TRACE_ERROR("%s, error %d\n", __func__, err);
394             osi_free(keyname);
395             return 0;
396         }
397         total_length += length;
398     }
399     osi_free(keyname);
400     return total_length;
401 }
402 
config_save(const config_t * config,const char * filename)403 bool config_save(const config_t *config, const char *filename)
404 {
405     assert(config != NULL);
406     assert(filename != NULL);
407     assert(*filename != '\0');
408 
409     esp_err_t err;
410     int err_code = 0;
411     nvs_handle_t fp;
412     char *line = osi_calloc(1024);
413     const size_t keyname_bufsz = sizeof(CONFIG_KEY) + 5 + 1; // including log10(sizeof(i))
414     char *keyname = osi_calloc(keyname_bufsz);
415     int config_size = get_config_size(config);
416     char *buf = osi_calloc(config_size);
417     if (!line || !buf || !keyname) {
418         err_code |= 0x01;
419         goto error;
420     }
421 
422     err = nvs_open(filename, NVS_READWRITE, &fp);
423     if (err != ESP_OK) {
424         if (err == ESP_ERR_NVS_NOT_INITIALIZED) {
425             OSI_TRACE_ERROR("%s: NVS not initialized. "
426                       "Call nvs_flash_init before initializing bluetooth.", __func__);
427         }
428         err_code |= 0x02;
429         goto error;
430     }
431 
432     int w_cnt, w_cnt_total = 0;
433     for (const list_node_t *node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) {
434         const section_t *section = (const section_t *)list_node(node);
435         w_cnt = snprintf(line, 1024, "[%s]\n", section->name);
436         if(w_cnt < 0) {
437             OSI_TRACE_ERROR("snprintf error w_cnt %d.",w_cnt);
438             err_code |= 0x10;
439             goto error;
440         }
441         if(w_cnt_total + w_cnt > config_size) {
442             OSI_TRACE_ERROR("%s, memcpy size (w_cnt + w_cnt_total = %d) is larger than buffer size (config_size = %d).", __func__, (w_cnt + w_cnt_total), config_size);
443             err_code |= 0x20;
444             goto error;
445         }
446         OSI_TRACE_DEBUG("section name: %s, w_cnt + w_cnt_total = %d\n", section->name, w_cnt + w_cnt_total);
447         memcpy(buf + w_cnt_total, line, w_cnt);
448         w_cnt_total += w_cnt;
449 
450         for (const list_node_t *enode = list_begin(section->entries); enode != list_end(section->entries); enode = list_next(enode)) {
451             const entry_t *entry = (const entry_t *)list_node(enode);
452             OSI_TRACE_DEBUG("(key, val): (%s, %s)\n", entry->key, entry->value);
453             w_cnt = snprintf(line, 1024, "%s = %s\n", entry->key, entry->value);
454             if(w_cnt < 0) {
455                 OSI_TRACE_ERROR("snprintf error w_cnt %d.",w_cnt);
456                 err_code |= 0x10;
457                 goto error;
458             }
459             if(w_cnt_total + w_cnt > config_size) {
460                 OSI_TRACE_ERROR("%s, memcpy size (w_cnt + w_cnt_total = %d) is larger than buffer size.(config_size = %d)", __func__, (w_cnt + w_cnt_total), config_size);
461                 err_code |= 0x20;
462                 goto error;
463             }
464             OSI_TRACE_DEBUG("%s, w_cnt + w_cnt_total = %d", __func__, w_cnt + w_cnt_total);
465             memcpy(buf + w_cnt_total, line, w_cnt);
466             w_cnt_total += w_cnt;
467         }
468 
469         // Only add a separating newline if there are more sections.
470         if (list_next(node) != list_end(config->sections)) {
471             buf[w_cnt_total] = '\n';
472             w_cnt_total += 1;
473         } else {
474             break;
475         }
476     }
477     buf[w_cnt_total] = '\0';
478     if (w_cnt_total < CONFIG_FILE_MAX_SIZE) {
479         snprintf(keyname, keyname_bufsz, "%s%d", CONFIG_KEY, 0);
480         err = nvs_set_blob(fp, keyname, buf, w_cnt_total);
481         if (err != ESP_OK) {
482             nvs_close(fp);
483             err_code |= 0x04;
484             goto error;
485         }
486     }else {
487         int count = (w_cnt_total / CONFIG_FILE_MAX_SIZE);
488         assert(count <= 0xFF);
489         for (uint8_t i = 0; i <= count; i++)
490         {
491             snprintf(keyname, keyname_bufsz, "%s%d", CONFIG_KEY, i);
492             if (i == count) {
493                 err = nvs_set_blob(fp, keyname, buf + i*CONFIG_FILE_MAX_SIZE, w_cnt_total - i*CONFIG_FILE_MAX_SIZE);
494                 OSI_TRACE_DEBUG("save keyname = %s, i = %d, %d\n", keyname, i, w_cnt_total - i*CONFIG_FILE_MAX_SIZE);
495             }else {
496                 err = nvs_set_blob(fp, keyname, buf + i*CONFIG_FILE_MAX_SIZE, CONFIG_FILE_MAX_SIZE);
497                 OSI_TRACE_DEBUG("save keyname = %s, i = %d, %d\n", keyname, i, CONFIG_FILE_MAX_SIZE);
498             }
499             if (err != ESP_OK) {
500                 nvs_close(fp);
501                 err_code |= 0x04;
502                 goto error;
503             }
504         }
505     }
506 
507     err = nvs_commit(fp);
508     if (err != ESP_OK) {
509         nvs_close(fp);
510         err_code |= 0x08;
511         goto error;
512     }
513 
514     nvs_close(fp);
515     osi_free(line);
516     osi_free(buf);
517     osi_free(keyname);
518     return true;
519 
520 error:
521     if (buf) {
522         osi_free(buf);
523     }
524     if (line) {
525         osi_free(line);
526     }
527     if (keyname) {
528         osi_free(keyname);
529     }
530     if (err_code) {
531         OSI_TRACE_ERROR("%s, err_code: 0x%x\n", __func__, err_code);
532     }
533     return false;
534 }
535 
trim(char * str)536 static char *trim(char *str)
537 {
538     while (isspace((unsigned char)(*str))) {
539         ++str;
540     }
541 
542     if (!*str) {
543         return str;
544     }
545 
546     char *end_str = str + strlen(str) - 1;
547     while (end_str > str && isspace((unsigned char)(*end_str))) {
548         --end_str;
549     }
550 
551     end_str[1] = '\0';
552     return str;
553 }
554 
config_parse(nvs_handle_t fp,config_t * config)555 static void config_parse(nvs_handle_t fp, config_t *config)
556 {
557     assert(fp != 0);
558     assert(config != NULL);
559 
560     esp_err_t err;
561     int line_num = 0;
562     int err_code = 0;
563     uint16_t i = 0;
564     size_t length = CONFIG_FILE_DEFAULE_LENGTH;
565     size_t total_length = 0;
566     char *line = osi_calloc(1024);
567     char *section = osi_calloc(1024);
568     const size_t keyname_bufsz = sizeof(CONFIG_KEY) + 5 + 1; // including log10(sizeof(i))
569     char *keyname = osi_calloc(keyname_bufsz);
570     int buf_size = get_config_size_from_flash(fp);
571     char *buf = NULL;
572 
573     if(buf_size == 0) { //First use nvs
574         goto error;
575     }
576     buf = osi_calloc(buf_size);
577     if (!line || !section || !buf || !keyname) {
578         err_code |= 0x01;
579         goto error;
580     }
581     snprintf(keyname, keyname_bufsz, "%s%d", CONFIG_KEY, 0);
582     err = nvs_get_blob(fp, keyname, buf, &length);
583     if (err == ESP_ERR_NVS_NOT_FOUND) {
584         goto error;
585     }
586     if (err != ESP_OK) {
587         err_code |= 0x02;
588         goto error;
589     }
590     total_length += length;
591     while (length == CONFIG_FILE_MAX_SIZE) {
592         length = CONFIG_FILE_DEFAULE_LENGTH;
593         snprintf(keyname, keyname_bufsz, "%s%d", CONFIG_KEY, ++i);
594         err = nvs_get_blob(fp, keyname, buf + CONFIG_FILE_MAX_SIZE * i, &length);
595 
596         if (err == ESP_ERR_NVS_NOT_FOUND) {
597             break;
598         }
599         if (err != ESP_OK) {
600             err_code |= 0x02;
601             goto error;
602         }
603         total_length += length;
604     }
605     char *p_line_end;
606     char *p_line_bgn = buf;
607     strcpy(section, CONFIG_DEFAULT_SECTION);
608 
609     while ( (p_line_bgn < buf + total_length - 1) && (p_line_end = strchr(p_line_bgn, '\n'))) {
610 
611         // get one line
612         int line_len = p_line_end - p_line_bgn;
613         if (line_len > 1023) {
614             OSI_TRACE_WARNING("%s exceed max line length on line %d.\n", __func__, line_num);
615             break;
616         }
617         memcpy(line, p_line_bgn, line_len);
618         line[line_len] = '\0';
619         p_line_bgn = p_line_end + 1;
620         char *line_ptr = trim(line);
621         ++line_num;
622 
623         // Skip blank and comment lines.
624         if (*line_ptr == '\0' || *line_ptr == '#') {
625             continue;
626         }
627 
628         if (*line_ptr == '[') {
629             size_t len = strlen(line_ptr);
630             if (line_ptr[len - 1] != ']') {
631                 OSI_TRACE_WARNING("%s unterminated section name on line %d.\n", __func__, line_num);
632                 continue;
633             }
634             strncpy(section, line_ptr + 1, len - 2);
635             section[len - 2] = '\0';
636         } else {
637             char *split = strchr(line_ptr, '=');
638             if (!split) {
639                 OSI_TRACE_DEBUG("%s no key/value separator found on line %d.\n", __func__, line_num);
640                 continue;
641             }
642             *split = '\0';
643             config_set_string(config, section, trim(line_ptr), trim(split + 1), true);
644         }
645     }
646 
647 error:
648     if (buf) {
649         osi_free(buf);
650     }
651     if (line) {
652         osi_free(line);
653     }
654     if (section) {
655         osi_free(section);
656     }
657     if (keyname) {
658         osi_free(keyname);
659     }
660     if (err_code) {
661         OSI_TRACE_ERROR("%s returned with err code: %d\n", __func__, err_code);
662     }
663 }
664 
section_new(const char * name)665 static section_t *section_new(const char *name)
666 {
667     section_t *section = osi_calloc(sizeof(section_t));
668     if (!section) {
669         return NULL;
670     }
671 
672     section->name = osi_strdup(name);
673     section->entries = list_new(entry_free);
674     return section;
675 }
676 
section_free(void * ptr)677 static void section_free(void *ptr)
678 {
679     if (!ptr) {
680         return;
681     }
682 
683     section_t *section = ptr;
684     osi_free(section->name);
685     list_free(section->entries);
686     osi_free(section);
687 }
688 
section_find(const config_t * config,const char * section)689 static section_t *section_find(const config_t *config, const char *section)
690 {
691     for (const list_node_t *node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) {
692         section_t *sec = list_node(node);
693         if (!strcmp(sec->name, section)) {
694             return sec;
695         }
696     }
697 
698     return NULL;
699 }
700 
entry_new(const char * key,const char * value)701 static entry_t *entry_new(const char *key, const char *value)
702 {
703     entry_t *entry = osi_calloc(sizeof(entry_t));
704     if (!entry) {
705         return NULL;
706     }
707 
708     entry->key = osi_strdup(key);
709     entry->value = osi_strdup(value);
710     return entry;
711 }
712 
entry_free(void * ptr)713 static void entry_free(void *ptr)
714 {
715     if (!ptr) {
716         return;
717     }
718 
719     entry_t *entry = ptr;
720     osi_free(entry->key);
721     osi_free(entry->value);
722     osi_free(entry);
723 }
724 
entry_find(const config_t * config,const char * section,const char * key)725 static entry_t *entry_find(const config_t *config, const char *section, const char *key)
726 {
727     section_t *sec = section_find(config, section);
728     if (!sec) {
729         return NULL;
730     }
731 
732     for (const list_node_t *node = list_begin(sec->entries); node != list_end(sec->entries); node = list_next(node)) {
733         entry_t *entry = list_node(node);
734         if (!strcmp(entry->key, key)) {
735             return entry;
736         }
737     }
738 
739     return NULL;
740 }
741