| /* | |
| * 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. | |
| */ | |
| #include <cutils/str_parms.h> | |
| #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 <log/log.h> | |
| /* 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; | |
| for (char* p = static_cast<char*>(str); p && *p; p++) | |
| hash = ((hash << 5) + hash) + *p; | |
| return (int)hash; | |
| } | |
| struct str_parms *str_parms_create(void) | |
| { | |
| str_parms* s = static_cast<str_parms*>(calloc(1, sizeof(str_parms))); | |
| if (!s) return NULL; | |
| s->map = hashmapCreate(5, str_hash_fn, str_eq); | |
| if (!s->map) { | |
| free(s); | |
| return NULL; | |
| } | |
| return s; | |
| } | |
| struct remove_ctxt { | |
| struct str_parms *str_parms; | |
| const char *key; | |
| }; | |
| static bool remove_pair(void *key, void *value, void *context) | |
| { | |
| remove_ctxt* ctxt = static_cast<remove_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, static_cast<const char*>(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) | |
| { | |
| // TODO: hashmapGet should take a const* key. | |
| char* value = static_cast<char*>(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 *end; | |
| // TODO: hashmapGet should take a const* key. | |
| char* value = static_cast<char*>(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 *end; | |
| // TODO: hashmapGet should take a const* key. | |
| char* value = static_cast<char*>(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 = static_cast<char**>(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; | |
| hashmapForEach(str_parms->map, combine_strings, &str); | |
| return (str != NULL) ? str : strdup(""); | |
| } | |
| static bool dump_entry(void* key, void* value, void* /*context*/) { | |
| 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); | |
| } |