blob: a5561442250a517821f91e19c9d2eeed6bae9fb3 [file] [log] [blame]
/*
*
* 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
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include "util.h"
#include "string.h"
#include "queue.h"
#include "settings.h"
#include "private.h"
struct setting_data {
char *key;
char *value;
};
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;
};
static void setting_destroy(void *data)
{
struct setting_data *pair = data;
l_free(pair->key);
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);
}
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();
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_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:
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 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 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 (l_ascii_isalnum(data[i]))
continue;
if (data[i] == '_' || 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) == false)
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;
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 (data[pos] == '[')
r = parse_group(settings, data + pos, line_len, line);
else if (data[pos] != '#')
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_queue_get_entries(group->settings);
l_string_append_printf(buf, "[%s]\n", group->name);
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;
}
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;
}
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 (l_ascii_isalnum(key[i]))
continue;
if (key[i] == '_' || key[i] == '-')
continue;
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);
l_free(value);
return false;
}
if (!validate_key(key)) {
l_util_debug(settings->debug_handler, settings->debug_data,
"Invalid key %s", key);
l_free(value);
return false;
}
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;
}
l_free(pair->value);
pair->value = value;
return true;
}
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, 10);
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, 10);
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, 10);
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, 10);
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("%d", 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;
}