blob: dfe8c4ba717ae6ae97889058eddac49bae312446 [file] [log] [blame]
/*
* Copyright (C) 2011-2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "str_params"
//#define LOG_NDEBUG 0
#define _GNU_SOURCE 1
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cutils/hashmap.h>
#include <cutils/memory.h>
#include <cutils/str_parms.h>
#include <log/log.h>
#define UNUSED __attribute__((unused))
struct str_parms {
Hashmap *map;
};
static bool str_eq(void *key_a, void *key_b)
{
return !strcmp((const char *)key_a, (const char *)key_b);
}
/* use djb hash unless we find it inadequate */
static int str_hash_fn(void *str)
{
uint32_t hash = 5381;
char *p;
for (p = str; p && *p; p++)
hash = ((hash << 5) + hash) + *p;
return (int)hash;
}
struct str_parms *str_parms_create(void)
{
struct str_parms *str_parms;
str_parms = calloc(1, sizeof(struct str_parms));
if (!str_parms)
return NULL;
str_parms->map = hashmapCreate(5, str_hash_fn, str_eq);
if (!str_parms->map)
goto err;
return str_parms;
err:
free(str_parms);
return NULL;
}
struct remove_ctxt {
struct str_parms *str_parms;
const char *key;
};
static bool remove_pair(void *key, void *value, void *context)
{
struct remove_ctxt *ctxt = context;
bool should_continue;
/*
* - if key is not supplied, then we are removing all entries,
* so remove key and continue (i.e. return true)
* - if key is supplied and matches, then remove it and don't
* continue (return false). Otherwise, return true and keep searching
* for key.
*
*/
if (!ctxt->key) {
should_continue = true;
goto do_remove;
} else if (!strcmp(ctxt->key, key)) {
should_continue = false;
goto do_remove;
}
return true;
do_remove:
hashmapRemove(ctxt->str_parms->map, key);
free(key);
free(value);
return should_continue;
}
void str_parms_del(struct str_parms *str_parms, const char *key)
{
struct remove_ctxt ctxt = {
.str_parms = str_parms,
.key = key,
};
hashmapForEach(str_parms->map, remove_pair, &ctxt);
}
void str_parms_destroy(struct str_parms *str_parms)
{
struct remove_ctxt ctxt = {
.str_parms = str_parms,
};
hashmapForEach(str_parms->map, remove_pair, &ctxt);
hashmapFree(str_parms->map);
free(str_parms);
}
struct str_parms *str_parms_create_str(const char *_string)
{
struct str_parms *str_parms;
char *str;
char *kvpair;
char *tmpstr;
int items = 0;
str_parms = str_parms_create();
if (!str_parms)
goto err_create_str_parms;
str = strdup(_string);
if (!str)
goto err_strdup;
ALOGV("%s: source string == '%s'\n", __func__, _string);
kvpair = strtok_r(str, ";", &tmpstr);
while (kvpair && *kvpair) {
char *eq = strchr(kvpair, '='); /* would love strchrnul */
char *value;
char *key;
void *old_val;
if (eq == kvpair)
goto next_pair;
if (eq) {
key = strndup(kvpair, eq - kvpair);
if (*(++eq))
value = strdup(eq);
else
value = strdup("");
} else {
key = strdup(kvpair);
value = strdup("");
}
/* if we replaced a value, free it */
old_val = hashmapPut(str_parms->map, key, value);
if (old_val) {
free(old_val);
free(key);
}
items++;
next_pair:
kvpair = strtok_r(NULL, ";", &tmpstr);
}
if (!items)
ALOGV("%s: no items found in string\n", __func__);
free(str);
return str_parms;
err_strdup:
str_parms_destroy(str_parms);
err_create_str_parms:
return NULL;
}
int str_parms_add_str(struct str_parms *str_parms, const char *key,
const char *value)
{
void *tmp_key = NULL;
void *tmp_val = NULL;
void *old_val = NULL;
// strdup and hashmapPut both set errno on failure.
// Set errno to 0 so we can recognize whether anything went wrong.
int saved_errno = errno;
errno = 0;
tmp_key = strdup(key);
if (tmp_key == NULL) {
goto clean_up;
}
tmp_val = strdup(value);
if (tmp_val == NULL) {
goto clean_up;
}
old_val = hashmapPut(str_parms->map, tmp_key, tmp_val);
if (old_val == NULL) {
// Did hashmapPut fail?
if (errno == ENOMEM) {
goto clean_up;
}
// For new keys, hashmap takes ownership of tmp_key and tmp_val.
tmp_key = tmp_val = NULL;
} else {
// For existing keys, hashmap takes ownership of tmp_val.
// (It also gives up ownership of old_val.)
tmp_val = NULL;
}
clean_up:
free(tmp_key);
free(tmp_val);
free(old_val);
int result = -errno;
errno = saved_errno;
return result;
}
int str_parms_add_int(struct str_parms *str_parms, const char *key, int value)
{
char val_str[12];
int ret;
ret = snprintf(val_str, sizeof(val_str), "%d", value);
if (ret < 0)
return -EINVAL;
ret = str_parms_add_str(str_parms, key, val_str);
return ret;
}
int str_parms_add_float(struct str_parms *str_parms, const char *key,
float value)
{
char val_str[23];
int ret;
ret = snprintf(val_str, sizeof(val_str), "%.10f", value);
if (ret < 0)
return -EINVAL;
ret = str_parms_add_str(str_parms, key, val_str);
return ret;
}
int str_parms_has_key(struct str_parms *str_parms, const char *key) {
return hashmapGet(str_parms->map, (void *)key) != NULL;
}
int str_parms_get_str(struct str_parms *str_parms, const char *key, char *val,
int len)
{
char *value;
value = hashmapGet(str_parms->map, (void *)key);
if (value)
return strlcpy(val, value, len);
return -ENOENT;
}
int str_parms_get_int(struct str_parms *str_parms, const char *key, int *val)
{
char *value;
char *end;
value = hashmapGet(str_parms->map, (void *)key);
if (!value)
return -ENOENT;
*val = (int)strtol(value, &end, 0);
if (*value != '\0' && *end == '\0')
return 0;
return -EINVAL;
}
int str_parms_get_float(struct str_parms *str_parms, const char *key,
float *val)
{
float out;
char *value;
char *end;
value = hashmapGet(str_parms->map, (void *)key);
if (!value)
return -ENOENT;
out = strtof(value, &end);
if (*value == '\0' || *end != '\0')
return -EINVAL;
*val = out;
return 0;
}
static bool combine_strings(void *key, void *value, void *context)
{
char **old_str = context;
char *new_str;
int ret;
ret = asprintf(&new_str, "%s%s%s=%s",
*old_str ? *old_str : "",
*old_str ? ";" : "",
(char *)key,
(char *)value);
if (*old_str)
free(*old_str);
if (ret >= 0) {
*old_str = new_str;
return true;
}
*old_str = NULL;
return false;
}
char *str_parms_to_str(struct str_parms *str_parms)
{
char *str = NULL;
if (hashmapSize(str_parms->map) > 0)
hashmapForEach(str_parms->map, combine_strings, &str);
else
str = strdup("");
return str;
}
static bool dump_entry(void *key, void *value, void *context UNUSED)
{
ALOGI("key: '%s' value: '%s'\n", (char *)key, (char *)value);
return true;
}
void str_parms_dump(struct str_parms *str_parms)
{
hashmapForEach(str_parms->map, dump_entry, str_parms);
}
#ifdef TEST_STR_PARMS
static void test_str_parms_str(const char *str)
{
struct str_parms *str_parms;
char *out_str;
str_parms = str_parms_create_str(str);
str_parms_add_str(str_parms, "dude", "woah");
str_parms_add_str(str_parms, "dude", "woah");
str_parms_del(str_parms, "dude");
str_parms_dump(str_parms);
out_str = str_parms_to_str(str_parms);
str_parms_destroy(str_parms);
ALOGI("%s: '%s' stringified is '%s'", __func__, str, out_str);
free(out_str);
}
int main(void)
{
test_str_parms_str("");
test_str_parms_str(";");
test_str_parms_str("=");
test_str_parms_str("=;");
test_str_parms_str("=bar");
test_str_parms_str("=bar;");
test_str_parms_str("foo=");
test_str_parms_str("foo=;");
test_str_parms_str("foo=bar");
test_str_parms_str("foo=bar;");
test_str_parms_str("foo=bar;baz");
test_str_parms_str("foo=bar;baz=");
test_str_parms_str("foo=bar;baz=bat");
test_str_parms_str("foo=bar;baz=bat;");
test_str_parms_str("foo=bar;baz=bat;foo=bar");
// hashmapPut reports errors by setting errno to ENOMEM.
// Test that we're not confused by running in an environment where this is already true.
errno = ENOMEM;
test_str_parms_str("foo=bar;baz=");
if (errno != ENOMEM) {
abort();
}
test_str_parms_str("foo=bar;baz=");
return 0;
}
#endif