| /* |
| * |
| * Embedded Linux library |
| * |
| * Copyright (C) 2011-2014 Intel Corporation. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #if __STDC_VERSION__ <= 199409L |
| #define _DEFAULT_SOURCE /* for strto{u}ll() */ |
| #endif |
| |
| #define _GNU_SOURCE |
| #include <stdio.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <strings.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/mman.h> |
| |
| #include "util.h" |
| #include "strv.h" |
| #include "utf8.h" |
| #include "string.h" |
| #include "queue.h" |
| #include "settings.h" |
| #include "private.h" |
| #include "missing.h" |
| #include "pem-private.h" |
| |
| struct setting_data { |
| char *key; |
| char *value; |
| }; |
| |
| struct embedded_group_data { |
| char *name; |
| char type[32]; |
| size_t len; |
| char data[0]; |
| }; |
| |
| struct group_data { |
| char *name; |
| struct l_queue *settings; |
| }; |
| |
| struct l_settings { |
| l_settings_debug_cb_t debug_handler; |
| l_settings_destroy_cb_t debug_destroy; |
| void *debug_data; |
| struct l_queue *groups; |
| struct l_queue *embedded_groups; |
| }; |
| |
| static void setting_destroy(void *data) |
| { |
| struct setting_data *pair = data; |
| |
| l_free(pair->key); |
| explicit_bzero(pair->value, strlen(pair->value)); |
| l_free(pair->value); |
| l_free(pair); |
| } |
| |
| static void group_destroy(void *data) |
| { |
| struct group_data *group = data; |
| |
| l_free(group->name); |
| l_queue_destroy(group->settings, setting_destroy); |
| |
| l_free(group); |
| } |
| |
| static void embedded_group_destroy(void *data) |
| { |
| struct embedded_group_data *group = data; |
| |
| l_free(group->name); |
| l_free(group); |
| } |
| |
| LIB_EXPORT struct l_settings *l_settings_new(void) |
| { |
| struct l_settings *settings; |
| |
| settings = l_new(struct l_settings, 1); |
| settings->groups = l_queue_new(); |
| settings->embedded_groups = l_queue_new(); |
| |
| return settings; |
| } |
| |
| LIB_EXPORT void l_settings_free(struct l_settings *settings) |
| { |
| if (unlikely(!settings)) |
| return; |
| |
| if (settings->debug_destroy) |
| settings->debug_destroy(settings->debug_data); |
| |
| l_queue_destroy(settings->groups, group_destroy); |
| l_queue_destroy(settings->embedded_groups, embedded_group_destroy); |
| |
| l_free(settings); |
| } |
| |
| static char *unescape_value(const char *value) |
| { |
| char *ret; |
| char *n; |
| const char *o; |
| |
| ret = l_new(char, strlen(value) + 1); |
| |
| for (n = ret, o = value; *o; o++, n++) { |
| if (*o != '\\') { |
| *n = *o; |
| continue; |
| } |
| |
| o += 1; |
| |
| switch (*o) { |
| case 's': |
| *n = ' '; |
| break; |
| case 'n': |
| *n = '\n'; |
| break; |
| case 't': |
| *n = '\t'; |
| break; |
| case 'r': |
| *n = '\r'; |
| break; |
| case '\\': |
| *n = '\\'; |
| break; |
| default: |
| explicit_bzero(ret, n - ret); |
| l_free(ret); |
| return NULL; |
| } |
| } |
| |
| return ret; |
| } |
| |
| static char *escape_value(const char *value) |
| { |
| size_t i; |
| size_t j; |
| char *ret; |
| bool lead_whitespace; |
| |
| for (i = 0, j = 0, lead_whitespace = true; value[i]; i++) { |
| switch (value[i]) { |
| case ' ': |
| case '\t': |
| if (lead_whitespace) |
| j += 1; |
| |
| break; |
| case '\n': |
| case '\r': |
| case '\\': |
| j += 1; |
| /* fall through */ |
| default: |
| lead_whitespace = false; |
| } |
| } |
| |
| ret = l_malloc(i + j + 1); |
| |
| for (i = 0, j = 0, lead_whitespace = true; value[i]; i++) { |
| switch (value[i]) { |
| case ' ': |
| if (lead_whitespace) { |
| ret[j++] = '\\'; |
| ret[j++] = 's'; |
| } else |
| ret[j++] = value[i]; |
| |
| break; |
| case '\t': |
| if (lead_whitespace) { |
| ret[j++] = '\\'; |
| ret[j++] = 't'; |
| } else |
| ret[j++] = value[i]; |
| |
| break; |
| case '\n': |
| ret[j++] = '\\'; |
| ret[j++] = 'n'; |
| lead_whitespace = false; |
| break; |
| case '\r': |
| ret[j++] = '\\'; |
| ret[j++] = 'r'; |
| lead_whitespace = false; |
| break; |
| case '\\': |
| ret[j++] = '\\'; |
| ret[j++] = '\\'; |
| lead_whitespace = false; |
| break; |
| default: |
| ret[j++] = value[i]; |
| lead_whitespace = false; |
| } |
| } |
| |
| ret[j] = '\0'; |
| |
| return ret; |
| } |
| |
| static ssize_t parse_pem(const char *data, size_t len) |
| { |
| const char *ptr; |
| const char *end; |
| size_t count = 0; |
| |
| ptr = data; |
| end = data + len; |
| |
| while (ptr && ptr < end) { |
| const char *pem_start = ptr; |
| |
| if (!pem_next(ptr, len, NULL, NULL, &ptr, true)) { |
| if (ptr) |
| return -EINVAL; |
| |
| break; |
| } |
| |
| len -= ptr - pem_start; |
| count += ptr - pem_start; |
| } |
| |
| return count; |
| } |
| |
| struct group_extension { |
| char *name; |
| ssize_t (*parse)(const char *data, size_t len); |
| }; |
| |
| static const struct group_extension pem_extension = { |
| .name = "pem", |
| .parse = parse_pem, |
| }; |
| |
| static const struct group_extension *extensions[] = { |
| &pem_extension, |
| NULL |
| }; |
| |
| static const struct group_extension *find_group_extension(const char *type, |
| size_t len) |
| { |
| unsigned int i; |
| |
| for (i = 0; extensions[i]; i++) { |
| if (!strncmp(type, extensions[i]->name, len)) |
| return extensions[i]; |
| } |
| |
| return NULL; |
| } |
| |
| static ssize_t parse_embedded_group(struct l_settings *setting, |
| const char *data, |
| size_t line_len, size_t len, |
| size_t line) |
| { |
| struct embedded_group_data *group; |
| const struct group_extension *ext; |
| const char *ptr; |
| const char *type; |
| size_t type_len; |
| const char *name; |
| size_t name_len; |
| ssize_t bytes; |
| |
| /* Must be at least [@a@b] */ |
| if (line_len < 6) |
| goto invalid_group; |
| |
| /* caller checked data[1] == '@', next char is type */ |
| type = data + 2; |
| |
| ptr = memchr(type, '@', line_len - 2); |
| |
| type_len = ptr - type; |
| |
| if (!ptr || type_len > 31 || type_len < 1) |
| goto invalid_group; |
| |
| if (ptr + 1 > data + line_len) |
| goto invalid_group; |
| |
| name = ptr + 1; |
| |
| /* subtract [@@ + type */ |
| ptr = memchr(name, ']', line_len - 3 - type_len); |
| |
| name_len = ptr - name; |
| |
| if (!ptr || name_len < 1) |
| goto invalid_group; |
| |
| ext = find_group_extension(type, type_len); |
| if (!ext) |
| goto invalid_group; |
| |
| if (ptr + 2 > data + len) { |
| l_util_debug(setting->debug_handler, setting->debug_data, |
| "Embedded group had no payload"); |
| return -EINVAL; |
| } |
| |
| bytes = ext->parse(ptr + 2, len - line_len); |
| if (bytes < 0) { |
| l_util_debug(setting->debug_handler, setting->debug_data, |
| "Failed to parse embedded group data"); |
| return -EINVAL; |
| } |
| |
| group = l_malloc(sizeof(struct embedded_group_data) + bytes + 1); |
| |
| group->name = l_strndup(name, name_len); |
| |
| memcpy(group->type, type, type_len); |
| group->type[type_len] = '\0'; |
| |
| group->len = bytes; |
| memcpy(group->data, ptr + 2, bytes); |
| group->data[bytes] = '\0'; |
| |
| l_queue_push_tail(setting->embedded_groups, group); |
| |
| return bytes; |
| |
| invalid_group: |
| l_util_debug(setting->debug_handler, setting->debug_data, |
| "Invalid embedded group at line %zd", line); |
| |
| return -EINVAL; |
| } |
| |
| static bool parse_group(struct l_settings *settings, const char *data, |
| size_t len, size_t line) |
| { |
| size_t i = 1; |
| size_t end; |
| struct group_data *group; |
| |
| while (i < len && data[i] != ']') { |
| if (l_ascii_isprint(data[i]) == false || data[i] == '[') { |
| l_util_debug(settings->debug_handler, |
| settings->debug_data, |
| "Invalid group name at line %zd", line); |
| return false; |
| } |
| |
| i += 1; |
| } |
| |
| if (i >= len) { |
| l_util_debug(settings->debug_handler, settings->debug_data, |
| "Unterminated group name at line %zd", line); |
| return false; |
| } |
| |
| end = i; |
| i += 1; |
| |
| while (i < len && l_ascii_isblank(data[i])) |
| i += 1; |
| |
| if (i != len) { |
| l_util_debug(settings->debug_handler, settings->debug_data, |
| "Junk characters at the end of line %zd", line); |
| return false; |
| } |
| |
| group = l_new(struct group_data, 1); |
| group->name = l_strndup(data + 1, end - 1); |
| group->settings = l_queue_new(); |
| |
| l_queue_push_tail(settings->groups, group); |
| |
| return true; |
| } |
| |
| static bool validate_key_character(char c) |
| { |
| if (l_ascii_isalnum(c)) |
| return true; |
| |
| if (c == '_' || c == '-' || c == '.') |
| return true; |
| |
| return false; |
| } |
| |
| static unsigned int parse_key(struct l_settings *settings, const char *data, |
| size_t len, size_t line) |
| { |
| unsigned int i; |
| unsigned int end; |
| struct group_data *group; |
| struct setting_data *pair; |
| |
| for (i = 0; i < len; i++) { |
| if (validate_key_character(data[i])) |
| continue; |
| |
| if (l_ascii_isblank(data[i])) |
| break; |
| |
| l_util_debug(settings->debug_handler, settings->debug_data, |
| "Invalid character in Key on line %zd", line); |
| |
| return 0; |
| } |
| |
| end = i; |
| |
| /* Make sure the rest of the characters are blanks */ |
| while (i < len) { |
| if (l_ascii_isblank(data[i++])) |
| continue; |
| |
| l_util_debug(settings->debug_handler, settings->debug_data, |
| "Garbage after Key on line %zd", line); |
| |
| return 0; |
| } |
| |
| group = l_queue_peek_tail(settings->groups); |
| pair = l_new(struct setting_data, 1); |
| pair->key = l_strndup(data, end); |
| l_queue_push_head(group->settings, pair); |
| |
| return end; |
| } |
| |
| static bool parse_value(struct l_settings *settings, const char *data, |
| size_t len, size_t line) |
| { |
| unsigned int end = len; |
| struct group_data *group; |
| struct setting_data *pair; |
| |
| group = l_queue_peek_tail(settings->groups); |
| pair = l_queue_pop_head(group->settings); |
| |
| if (!l_utf8_validate(data, len, NULL)) { |
| l_util_debug(settings->debug_handler, settings->debug_data, |
| "Invalid UTF8 in value on line: %zd", line); |
| |
| l_free(pair->key); |
| l_free(pair); |
| |
| return false; |
| } |
| |
| pair->value = l_strndup(data, end); |
| l_queue_push_tail(group->settings, pair); |
| |
| return true; |
| } |
| |
| static bool parse_keyvalue(struct l_settings *settings, const char *data, |
| size_t len, size_t line) |
| { |
| const char *equal = memchr(data, '=', len); |
| |
| if (!equal) { |
| l_util_debug(settings->debug_handler, settings->debug_data, |
| "Delimiter '=' not found on line: %zd", line); |
| return false; |
| } |
| |
| if (equal == data) { |
| l_util_debug(settings->debug_handler, settings->debug_data, |
| "Empty key on line: %zd", line); |
| return false; |
| } |
| |
| if (!parse_key(settings, data, equal - data, line)) |
| return false; |
| |
| equal += 1; |
| while (equal < data + len && l_ascii_isblank(*equal)) |
| equal += 1; |
| |
| return parse_value(settings, equal, len - (equal - data), line); |
| } |
| |
| LIB_EXPORT bool l_settings_load_from_data(struct l_settings *settings, |
| const char *data, size_t len) |
| { |
| size_t pos = 0; |
| bool r = true; |
| bool has_group = false; |
| const char *eol; |
| size_t line = 1; |
| size_t line_len; |
| |
| if (unlikely(!settings || !data || !len)) |
| return false; |
| |
| while (pos < len && r) { |
| if (l_ascii_isblank(data[pos])) { |
| pos += 1; |
| continue; |
| } |
| |
| if (data[pos] == '\n') { |
| line += 1; |
| pos += 1; |
| continue; |
| } |
| |
| eol = memchr(data + pos, '\n', len - pos); |
| if (!eol) |
| eol = data + len; |
| |
| line_len = eol - data - pos; |
| |
| if (line_len > 1 && data[pos] == '[' && data[pos + 1] == '@') { |
| ssize_t ret; |
| |
| ret = parse_embedded_group(settings, data + pos, |
| line_len, len - pos, |
| line); |
| if (ret < 0) |
| return false; |
| |
| /* |
| * This is the offset for the actual raw data, the |
| * group line will be offset below |
| */ |
| pos += ret; |
| } else if (data[pos] == '[') { |
| r = parse_group(settings, data + pos, line_len, line); |
| if (r) |
| has_group = true; |
| } else if (data[pos] != '#') { |
| if (!has_group) |
| return false; |
| |
| r = parse_keyvalue(settings, data + pos, line_len, |
| line); |
| } |
| |
| pos += line_len; |
| } |
| |
| return r; |
| } |
| |
| LIB_EXPORT char *l_settings_to_data(const struct l_settings *settings, |
| size_t *len) |
| { |
| struct l_string *buf; |
| char *ret; |
| const struct l_queue_entry *group_entry; |
| |
| if (unlikely(!settings)) |
| return NULL; |
| |
| buf = l_string_new(255); |
| |
| group_entry = l_queue_get_entries(settings->groups); |
| while (group_entry) { |
| struct group_data *group = group_entry->data; |
| const struct l_queue_entry *setting_entry; |
| |
| l_string_append_printf(buf, "[%s]\n", group->name); |
| |
| setting_entry = l_queue_get_entries(group->settings); |
| |
| while (setting_entry) { |
| struct setting_data *setting = setting_entry->data; |
| |
| l_string_append_printf(buf, "%s=%s\n", |
| setting->key, setting->value); |
| setting_entry = setting_entry->next; |
| } |
| |
| if (group_entry->next) |
| l_string_append_c(buf, '\n'); |
| |
| group_entry = group_entry->next; |
| } |
| |
| group_entry = l_queue_get_entries(settings->embedded_groups); |
| |
| if (group_entry && l_queue_length(settings->groups) > 0) |
| l_string_append_c(buf, '\n'); |
| |
| while (group_entry) { |
| struct embedded_group_data *group = group_entry->data; |
| |
| l_string_append_printf(buf, "[@%s@%s]\n%s", |
| group->type, |
| group->name, |
| group->data); |
| if (group_entry->next) |
| l_string_append_c(buf, '\n'); |
| |
| group_entry = group_entry->next; |
| } |
| |
| ret = l_string_unwrap(buf); |
| |
| if (len) |
| *len = strlen(ret); |
| |
| return ret; |
| } |
| |
| LIB_EXPORT bool l_settings_load_from_file(struct l_settings *settings, |
| const char *filename) |
| { |
| int fd; |
| struct stat st; |
| char *data; |
| bool r; |
| |
| if (unlikely(!settings || !filename)) |
| return false; |
| |
| fd = open(filename, O_RDONLY); |
| if (fd < 0) { |
| l_util_debug(settings->debug_handler, settings->debug_data, |
| "Could not open %s (%s)", filename, |
| strerror(errno)); |
| return false; |
| } |
| |
| if (fstat(fd, &st) < 0) { |
| l_util_debug(settings->debug_handler, settings->debug_data, |
| "Could not stat %s (%s)", filename, |
| strerror(errno)); |
| close(fd); |
| |
| return false; |
| } |
| |
| /* Nothing to do, assume success */ |
| if (st.st_size == 0) { |
| close(fd); |
| return true; |
| } |
| |
| data = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); |
| if (data == MAP_FAILED) { |
| l_util_debug(settings->debug_handler, settings->debug_data, |
| "Could not mmap %s (%s)", filename, |
| strerror(errno)); |
| close(fd); |
| |
| return false; |
| } |
| |
| r = l_settings_load_from_data(settings, data, st.st_size); |
| |
| munmap(data, st.st_size); |
| close(fd); |
| |
| return r; |
| } |
| |
| LIB_EXPORT bool l_settings_set_debug(struct l_settings *settings, |
| l_settings_debug_cb_t callback, |
| void *user_data, |
| l_settings_destroy_cb_t destroy) |
| { |
| if (unlikely(!settings)) |
| return false; |
| |
| if (settings->debug_destroy) |
| settings->debug_destroy(settings->debug_data); |
| |
| settings->debug_handler = callback; |
| settings->debug_destroy = destroy; |
| settings->debug_data = user_data; |
| |
| return true; |
| } |
| |
| static bool group_match(const void *a, const void *b) |
| { |
| const struct group_data *group = a; |
| const char *name = b; |
| |
| return !strcmp(group->name, name); |
| } |
| |
| struct gather_data { |
| int cur; |
| char **v; |
| }; |
| |
| static void gather_groups(void *data, void *user_data) |
| { |
| struct group_data *group_data = data; |
| struct gather_data *gather = user_data; |
| |
| gather->v[gather->cur++] = l_strdup(group_data->name); |
| } |
| |
| LIB_EXPORT char **l_settings_get_groups(const struct l_settings *settings) |
| { |
| char **ret; |
| struct gather_data gather; |
| |
| if (unlikely(!settings)) |
| return NULL; |
| |
| ret = l_new(char *, l_queue_length(settings->groups) + 1); |
| gather.v = ret; |
| gather.cur = 0; |
| |
| l_queue_foreach(settings->groups, gather_groups, &gather); |
| |
| return ret; |
| } |
| |
| LIB_EXPORT bool l_settings_has_group(const struct l_settings *settings, |
| const char *group_name) |
| { |
| struct group_data *group; |
| |
| if (unlikely(!settings)) |
| return false; |
| |
| group = l_queue_find(settings->groups, group_match, group_name); |
| |
| return !!group; |
| } |
| |
| static bool key_match(const void *a, const void *b) |
| { |
| const struct setting_data *setting = a; |
| const char *key = b; |
| |
| return !strcmp(setting->key, key); |
| } |
| |
| static void gather_keys(void *data, void *user_data) |
| { |
| struct setting_data *setting_data = data; |
| struct gather_data *gather = user_data; |
| |
| gather->v[gather->cur++] = l_strdup(setting_data->key); |
| } |
| |
| LIB_EXPORT char **l_settings_get_keys(const struct l_settings *settings, |
| const char *group_name) |
| { |
| char **ret; |
| struct group_data *group_data; |
| struct gather_data gather; |
| |
| if (unlikely(!settings)) |
| return NULL; |
| |
| group_data = l_queue_find(settings->groups, group_match, group_name); |
| if (!group_data) |
| return NULL; |
| |
| ret = l_new(char *, l_queue_length(group_data->settings) + 1); |
| gather.v = ret; |
| gather.cur = 0; |
| |
| l_queue_foreach(group_data->settings, gather_keys, &gather); |
| |
| return ret; |
| } |
| |
| LIB_EXPORT bool l_settings_has_key(const struct l_settings *settings, |
| const char *group_name, const char *key) |
| { |
| struct group_data *group; |
| struct setting_data *setting; |
| |
| if (unlikely(!settings)) |
| return false; |
| |
| group = l_queue_find(settings->groups, group_match, group_name); |
| if (!group) |
| return false; |
| |
| setting = l_queue_find(group->settings, key_match, key); |
| |
| return !!setting; |
| } |
| |
| LIB_EXPORT const char *l_settings_get_value(const struct l_settings *settings, |
| const char *group_name, |
| const char *key) |
| { |
| struct group_data *group; |
| struct setting_data *setting; |
| |
| if (unlikely(!settings)) |
| return NULL; |
| |
| group = l_queue_find(settings->groups, group_match, group_name); |
| if (!group) |
| return NULL; |
| |
| setting = l_queue_find(group->settings, key_match, key); |
| if (!setting) |
| return NULL; |
| |
| return setting->value; |
| } |
| |
| static bool validate_group_name(const char *group_name) |
| { |
| int i; |
| |
| for (i = 0; group_name[i]; i++) { |
| if (!l_ascii_isprint(group_name[i])) |
| return false; |
| |
| if (group_name[i] == ']' || group_name[i] == '[') |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool validate_key(const char *key) |
| { |
| int i; |
| |
| for (i = 0; key[i]; i++) { |
| if (!validate_key_character(key[i])) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool set_value(struct l_settings *settings, const char *group_name, |
| const char *key, char *value) |
| { |
| struct group_data *group; |
| struct setting_data *pair; |
| |
| if (!validate_group_name(group_name)) { |
| l_util_debug(settings->debug_handler, settings->debug_data, |
| "Invalid group name %s", group_name); |
| goto error; |
| } |
| |
| if (!validate_key(key)) { |
| l_util_debug(settings->debug_handler, settings->debug_data, |
| "Invalid key %s", key); |
| goto error; |
| } |
| |
| group = l_queue_find(settings->groups, group_match, group_name); |
| if (!group) { |
| group = l_new(struct group_data, 1); |
| group->name = l_strdup(group_name); |
| group->settings = l_queue_new(); |
| |
| l_queue_push_tail(settings->groups, group); |
| goto add_pair; |
| } |
| |
| pair = l_queue_find(group->settings, key_match, key); |
| if (!pair) { |
| add_pair: |
| pair = l_new(struct setting_data, 1); |
| pair->key = l_strdup(key); |
| pair->value = value; |
| l_queue_push_tail(group->settings, pair); |
| |
| return true; |
| } |
| |
| explicit_bzero(pair->value, strlen(pair->value)); |
| l_free(pair->value); |
| pair->value = value; |
| |
| return true; |
| |
| error: |
| explicit_bzero(value, strlen(value)); |
| l_free(value); |
| return false; |
| } |
| |
| LIB_EXPORT bool l_settings_set_value(struct l_settings *settings, |
| const char *group_name, const char *key, |
| const char *value) |
| { |
| if (unlikely(!settings || !value)) |
| return false; |
| |
| return set_value(settings, group_name, key, l_strdup(value)); |
| } |
| |
| LIB_EXPORT bool l_settings_get_bool(const struct l_settings *settings, |
| const char *group_name, const char *key, |
| bool *out) |
| { |
| const char *value; |
| |
| value = l_settings_get_value(settings, group_name, key); |
| if (!value) |
| return false; |
| |
| if (!strcasecmp(value, "true") || !strcmp(value, "1")) { |
| if (out) |
| *out = true; |
| |
| return true; |
| } |
| |
| if (!strcasecmp(value, "false") || !strcmp(value, "0")) { |
| if (out) |
| *out = false; |
| |
| return true; |
| } |
| |
| l_util_debug(settings->debug_handler, settings->debug_data, |
| "Could not interpret %s as a bool", value); |
| |
| return false; |
| } |
| |
| LIB_EXPORT bool l_settings_set_bool(struct l_settings *settings, |
| const char *group_name, const char *key, |
| bool in) |
| { |
| static const char *true_str = "true"; |
| static const char *false_str = "false"; |
| const char *v; |
| |
| if (in == false) |
| v = false_str; |
| else |
| v = true_str; |
| |
| return l_settings_set_value(settings, group_name, key, v); |
| } |
| |
| LIB_EXPORT bool l_settings_get_int(const struct l_settings *settings, |
| const char *group_name, |
| const char *key, int *out) |
| { |
| const char *value = l_settings_get_value(settings, group_name, key); |
| long int r; |
| int t; |
| char *endp; |
| |
| if (!value) |
| return false; |
| |
| if (*value == '\0') |
| goto error; |
| |
| errno = 0; |
| |
| t = r = strtol(value, &endp, 0); |
| if (*endp != '\0') |
| goto error; |
| |
| if (unlikely(errno == ERANGE || r != t)) |
| goto error; |
| |
| if (out) |
| *out = r; |
| |
| return true; |
| |
| error: |
| l_util_debug(settings->debug_handler, settings->debug_data, |
| "Could not interpret %s as an int", value); |
| |
| return false; |
| } |
| |
| LIB_EXPORT bool l_settings_set_int(struct l_settings *settings, |
| const char *group_name, const char *key, |
| int in) |
| { |
| char buf[64]; |
| |
| snprintf(buf, sizeof(buf), "%d", in); |
| |
| return l_settings_set_value(settings, group_name, key, buf); |
| } |
| |
| LIB_EXPORT bool l_settings_get_uint(const struct l_settings *settings, |
| const char *group_name, const char *key, |
| unsigned int *out) |
| { |
| const char *value = l_settings_get_value(settings, group_name, key); |
| unsigned long int r; |
| unsigned int t; |
| char *endp; |
| |
| if (!value) |
| return false; |
| |
| if (*value == '\0') |
| goto error; |
| |
| errno = 0; |
| |
| t = r = strtoul(value, &endp, 0); |
| if (*endp != '\0') |
| goto error; |
| |
| if (unlikely(errno == ERANGE || r != t)) |
| goto error; |
| |
| if (out) |
| *out = r; |
| |
| return true; |
| |
| error: |
| l_util_debug(settings->debug_handler, settings->debug_data, |
| "Could not interpret %s as a uint", value); |
| |
| return false; |
| } |
| |
| LIB_EXPORT bool l_settings_set_uint(struct l_settings *settings, |
| const char *group_name, const char *key, |
| unsigned int in) |
| { |
| char buf[64]; |
| |
| snprintf(buf, sizeof(buf), "%u", in); |
| |
| return l_settings_set_value(settings, group_name, key, buf); |
| } |
| |
| LIB_EXPORT bool l_settings_get_int64(const struct l_settings *settings, |
| const char *group_name, const char *key, |
| int64_t *out) |
| { |
| const char *value = l_settings_get_value(settings, group_name, key); |
| int64_t r; |
| char *endp; |
| |
| if (!value) |
| return false; |
| |
| if (*value == '\0') |
| goto error; |
| |
| errno = 0; |
| |
| r = strtoll(value, &endp, 0); |
| if (*endp != '\0') |
| goto error; |
| |
| if (unlikely(errno == ERANGE)) |
| goto error; |
| |
| if (out) |
| *out = r; |
| |
| return true; |
| |
| error: |
| l_util_debug(settings->debug_handler, settings->debug_data, |
| "Could not interpret %s as an int64", value); |
| |
| return false; |
| } |
| |
| LIB_EXPORT bool l_settings_set_int64(struct l_settings *settings, |
| const char *group_name, const char *key, |
| int64_t in) |
| { |
| char buf[64]; |
| |
| snprintf(buf, sizeof(buf), "%" PRId64, in); |
| |
| return l_settings_set_value(settings, group_name, key, buf); |
| } |
| |
| LIB_EXPORT bool l_settings_get_uint64(const struct l_settings *settings, |
| const char *group_name, const char *key, |
| uint64_t *out) |
| { |
| const char *value = l_settings_get_value(settings, group_name, key); |
| uint64_t r; |
| char *endp; |
| |
| if (!value) |
| return false; |
| |
| if (*value == '\0') |
| goto error; |
| |
| errno = 0; |
| |
| r = strtoull(value, &endp, 0); |
| if (*endp != '\0') |
| goto error; |
| |
| if (unlikely(errno == ERANGE)) |
| goto error; |
| |
| if (out) |
| *out = r; |
| |
| return true; |
| |
| error: |
| l_util_debug(settings->debug_handler, settings->debug_data, |
| "Could not interpret %s as a uint64", value); |
| |
| return false; |
| } |
| |
| LIB_EXPORT bool l_settings_set_uint64(struct l_settings *settings, |
| const char *group_name, const char *key, |
| uint64_t in) |
| { |
| char buf[64]; |
| |
| snprintf(buf, sizeof(buf), "%" PRIu64, in); |
| |
| return l_settings_set_value(settings, group_name, key, buf); |
| } |
| |
| LIB_EXPORT char *l_settings_get_string(const struct l_settings *settings, |
| const char *group_name, const char *key) |
| { |
| const char *value = l_settings_get_value(settings, group_name, key); |
| |
| if (!value) |
| return NULL; |
| |
| return unescape_value(value); |
| } |
| |
| LIB_EXPORT bool l_settings_set_string(struct l_settings *settings, |
| const char *group_name, const char *key, |
| const char *value) |
| { |
| char *buf; |
| |
| if (unlikely(!settings || !value)) |
| return false; |
| |
| buf = escape_value(value); |
| |
| return set_value(settings, group_name, key, buf); |
| } |
| |
| LIB_EXPORT char **l_settings_get_string_list(const struct l_settings *settings, |
| const char *group_name, |
| const char *key, |
| const char delimiter) |
| { |
| const char *value = l_settings_get_value(settings, group_name, key); |
| char *str; |
| char **ret; |
| |
| if (!value) |
| return NULL; |
| |
| str = unescape_value(value); |
| if (str == NULL) |
| return NULL; |
| |
| ret = l_strsplit(str, delimiter); |
| l_free(str); |
| |
| return ret; |
| } |
| |
| LIB_EXPORT bool l_settings_set_string_list(struct l_settings *settings, |
| const char *group_name, const char *key, |
| char **value, char delimiter) |
| { |
| char *buf; |
| char *tmp; |
| |
| if (unlikely(!settings || !value)) |
| return false; |
| |
| tmp = l_strjoinv(value, delimiter); |
| buf = escape_value(tmp); |
| l_free(tmp); |
| |
| return set_value(settings, group_name, key, buf); |
| } |
| |
| LIB_EXPORT bool l_settings_get_double(const struct l_settings *settings, |
| const char *group_name, const char *key, |
| double *out) |
| { |
| const char *value = l_settings_get_value(settings, group_name, key); |
| char *endp; |
| double r; |
| |
| if (!value) |
| return NULL; |
| |
| if (*value == '\0') |
| goto error; |
| |
| errno = 0; |
| |
| r = strtod(value, &endp); |
| if (*endp != '\0') |
| goto error; |
| |
| if (unlikely(errno == ERANGE)) |
| goto error; |
| |
| if (out) |
| *out = r; |
| |
| return true; |
| |
| error: |
| l_util_debug(settings->debug_handler, settings->debug_data, |
| "Could not interpret %s as a double", value); |
| |
| return false; |
| } |
| |
| LIB_EXPORT bool l_settings_set_double(struct l_settings *settings, |
| const char *group_name, const char *key, |
| double in) |
| { |
| L_AUTO_FREE_VAR(char *, buf); |
| |
| buf = l_strdup_printf("%f", in); |
| |
| return l_settings_set_value(settings, group_name, key, buf); |
| } |
| |
| LIB_EXPORT bool l_settings_get_float(const struct l_settings *settings, |
| const char *group_name, const char *key, |
| float *out) |
| { |
| const char *value = l_settings_get_value(settings, group_name, key); |
| char *endp; |
| float r; |
| |
| if (!value) |
| return NULL; |
| |
| if (*value == '\0') |
| goto error; |
| |
| errno = 0; |
| |
| r = strtof(value, &endp); |
| if (*endp != '\0') |
| goto error; |
| |
| if (unlikely(errno == ERANGE)) |
| goto error; |
| |
| if (out) |
| *out = r; |
| |
| return true; |
| |
| error: |
| l_util_debug(settings->debug_handler, settings->debug_data, |
| "Could not interpret %s as a float", value); |
| |
| return false; |
| } |
| |
| LIB_EXPORT bool l_settings_set_float(struct l_settings *settings, |
| const char *group_name, const char *key, |
| float in) |
| { |
| L_AUTO_FREE_VAR(char *, buf); |
| |
| buf = l_strdup_printf("%f", in); |
| |
| return l_settings_set_value(settings, group_name, key, buf); |
| } |
| |
| LIB_EXPORT bool l_settings_remove_group(struct l_settings *settings, |
| const char *group_name) |
| { |
| struct group_data *group; |
| |
| if (unlikely(!settings)) |
| return false; |
| |
| group = l_queue_remove_if(settings->groups, group_match, group_name); |
| if (!group) |
| return false; |
| |
| group_destroy(group); |
| |
| return true; |
| } |
| |
| LIB_EXPORT bool l_settings_remove_key(struct l_settings *settings, |
| const char *group_name, |
| const char *key) |
| { |
| struct group_data *group; |
| struct setting_data *setting; |
| |
| if (unlikely(!settings)) |
| return false; |
| |
| group = l_queue_find(settings->groups, group_match, group_name); |
| if (!group) |
| return false; |
| |
| setting = l_queue_remove_if(group->settings, key_match, key); |
| if (!setting) |
| return false; |
| |
| setting_destroy(setting); |
| |
| return true; |
| } |
| |
| static void gather_embedded_groups(void *data, void *user_data) |
| { |
| struct embedded_group_data *group_data = data; |
| struct gather_data *gather = user_data; |
| |
| gather->v[gather->cur++] = l_strdup(group_data->name); |
| } |
| |
| LIB_EXPORT char **l_settings_get_embedded_groups(struct l_settings *settings) |
| { |
| char **ret; |
| struct gather_data gather; |
| |
| if (unlikely(!settings)) |
| return NULL; |
| |
| ret = l_new(char *, l_queue_length(settings->groups) + 1); |
| gather.v = ret; |
| gather.cur = 0; |
| |
| l_queue_foreach(settings->embedded_groups, gather_embedded_groups, |
| &gather); |
| |
| return ret; |
| } |
| |
| static bool embedded_group_match(const void *a, const void *b) |
| { |
| const struct embedded_group_data *group = a; |
| const char *name = b; |
| |
| return !strcmp(group->name, name); |
| } |
| |
| LIB_EXPORT bool l_settings_has_embedded_group(struct l_settings *settings, |
| const char *group) |
| { |
| struct embedded_group_data *group_data; |
| |
| if (unlikely(!settings)) |
| return false; |
| |
| group_data = l_queue_find(settings->embedded_groups, |
| embedded_group_match, group); |
| |
| return group_data != NULL; |
| } |
| |
| LIB_EXPORT const char *l_settings_get_embedded_value( |
| struct l_settings *settings, |
| const char *group_name, |
| const char **out_type) |
| { |
| struct embedded_group_data *group; |
| |
| if (unlikely(!settings)) |
| return false; |
| |
| group = l_queue_find(settings->embedded_groups, |
| embedded_group_match, group_name); |
| if (!group) |
| return NULL; |
| |
| if (out_type) |
| *out_type = group->type; |
| |
| return group->data; |
| } |