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