/* * 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); }