You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
382 lines
8.8 KiB
382 lines
8.8 KiB
/* |
|
* 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 <cutils/hashmap.h> |
|
#include <cutils/memory.h> |
|
#include <cutils/str_parms.h> |
|
#include <log/log.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); |
|
}
|
|
|