383 lines
8.8 KiB
C
383 lines
8.8 KiB
C
/*
|
|
* Copyright (C) 2011 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 <android/log.h>
|
|
#include <cutils/hashmap.h>
|
|
#include <cutils/memory.h>
|
|
#include <cutils/str_parms.h>
|
|
|
|
#define UNUSED __attribute__((unused))
|
|
|
|
/* When an object is allocated but not freed in a function,
|
|
* because its ownership is released to other object like a hashmap,
|
|
* call RELEASE_OWNERSHIP to tell the clang analyzer and avoid
|
|
* false warnings about potential memory leak.
|
|
* For now, a "temporary" assignment to global variables
|
|
* is enough to confuse the clang static analyzer.
|
|
*/
|
|
#ifdef __clang_analyzer__
|
|
static void *released_pointer;
|
|
#define RELEASE_OWNERSHIP(x) { released_pointer = x; released_pointer = 0; }
|
|
#else
|
|
#define RELEASE_OWNERSHIP(x)
|
|
#endif
|
|
|
|
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 */
|
|
#ifdef __clang__
|
|
__attribute__((no_sanitize("integer")))
|
|
#endif
|
|
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);
|
|
RELEASE_OWNERSHIP(value);
|
|
if (old_val) {
|
|
free(old_val);
|
|
free(key);
|
|
} else {
|
|
RELEASE_OWNERSHIP(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.
|
|
RELEASE_OWNERSHIP(tmp_key);
|
|
RELEASE_OWNERSHIP(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.)
|
|
RELEASE_OWNERSHIP(tmp_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);
|
|
}
|