str_parms.c revision d3c8d5b8d4ade074129b65199a8048c81089ee0e
1/* 2 * Copyright (C) 2011-2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#define LOG_TAG "str_params" 18//#define LOG_NDEBUG 0 19 20#define _GNU_SOURCE 1 21#include <errno.h> 22#include <stdint.h> 23#include <stdio.h> 24#include <stdlib.h> 25#include <string.h> 26 27#include <cutils/hashmap.h> 28#include <cutils/log.h> 29#include <cutils/memory.h> 30 31#include <cutils/str_parms.h> 32 33#define UNUSED __attribute__((unused)) 34 35struct str_parms { 36 Hashmap *map; 37}; 38 39 40static bool str_eq(void *key_a, void *key_b) 41{ 42 return !strcmp((const char *)key_a, (const char *)key_b); 43} 44 45/* use djb hash unless we find it inadequate */ 46static int str_hash_fn(void *str) 47{ 48 uint32_t hash = 5381; 49 char *p; 50 51 for (p = str; p && *p; p++) 52 hash = ((hash << 5) + hash) + *p; 53 return (int)hash; 54} 55 56struct str_parms *str_parms_create(void) 57{ 58 struct str_parms *str_parms; 59 60 str_parms = calloc(1, sizeof(struct str_parms)); 61 if (!str_parms) 62 return NULL; 63 64 str_parms->map = hashmapCreate(5, str_hash_fn, str_eq); 65 if (!str_parms->map) 66 goto err; 67 68 return str_parms; 69 70err: 71 free(str_parms); 72 return NULL; 73} 74 75struct remove_ctxt { 76 struct str_parms *str_parms; 77 const char *key; 78}; 79 80static bool remove_pair(void *key, void *value, void *context) 81{ 82 struct remove_ctxt *ctxt = context; 83 bool should_continue; 84 85 /* 86 * - if key is not supplied, then we are removing all entries, 87 * so remove key and continue (i.e. return true) 88 * - if key is supplied and matches, then remove it and don't 89 * continue (return false). Otherwise, return true and keep searching 90 * for key. 91 * 92 */ 93 if (!ctxt->key) { 94 should_continue = true; 95 goto do_remove; 96 } else if (!strcmp(ctxt->key, key)) { 97 should_continue = false; 98 goto do_remove; 99 } 100 101 return true; 102 103do_remove: 104 hashmapRemove(ctxt->str_parms->map, key); 105 free(key); 106 free(value); 107 return should_continue; 108} 109 110void str_parms_del(struct str_parms *str_parms, const char *key) 111{ 112 struct remove_ctxt ctxt = { 113 .str_parms = str_parms, 114 .key = key, 115 }; 116 hashmapForEach(str_parms->map, remove_pair, &ctxt); 117} 118 119void str_parms_destroy(struct str_parms *str_parms) 120{ 121 struct remove_ctxt ctxt = { 122 .str_parms = str_parms, 123 }; 124 125 hashmapForEach(str_parms->map, remove_pair, &ctxt); 126 hashmapFree(str_parms->map); 127 free(str_parms); 128} 129 130struct str_parms *str_parms_create_str(const char *_string) 131{ 132 struct str_parms *str_parms; 133 char *str; 134 char *kvpair; 135 char *tmpstr; 136 int items = 0; 137 138 str_parms = str_parms_create(); 139 if (!str_parms) 140 goto err_create_str_parms; 141 142 str = strdup(_string); 143 if (!str) 144 goto err_strdup; 145 146 ALOGV("%s: source string == '%s'\n", __func__, _string); 147 148 kvpair = strtok_r(str, ";", &tmpstr); 149 while (kvpair && *kvpair) { 150 char *eq = strchr(kvpair, '='); /* would love strchrnul */ 151 char *value; 152 char *key; 153 void *old_val; 154 155 if (eq == kvpair) 156 goto next_pair; 157 158 if (eq) { 159 key = strndup(kvpair, eq - kvpair); 160 if (*(++eq)) 161 value = strdup(eq); 162 else 163 value = strdup(""); 164 } else { 165 key = strdup(kvpair); 166 value = strdup(""); 167 } 168 169 /* if we replaced a value, free it */ 170 old_val = hashmapPut(str_parms->map, key, value); 171 if (old_val) { 172 free(old_val); 173 free(key); 174 } 175 176 items++; 177next_pair: 178 kvpair = strtok_r(NULL, ";", &tmpstr); 179 } 180 181 if (!items) 182 ALOGV("%s: no items found in string\n", __func__); 183 184 free(str); 185 186 return str_parms; 187 188err_strdup: 189 str_parms_destroy(str_parms); 190err_create_str_parms: 191 return NULL; 192} 193 194int str_parms_add_str(struct str_parms *str_parms, const char *key, 195 const char *value) 196{ 197 void *tmp_key = NULL; 198 void *tmp_val = NULL; 199 void *old_val = NULL; 200 201 // strdup and hashmapPut both set errno on failure. 202 // Set errno to 0 so we can recognize whether anything went wrong. 203 int saved_errno = errno; 204 errno = 0; 205 206 tmp_key = strdup(key); 207 if (tmp_key == NULL) { 208 goto clean_up; 209 } 210 211 tmp_val = strdup(value); 212 if (tmp_val == NULL) { 213 goto clean_up; 214 } 215 216 old_val = hashmapPut(str_parms->map, tmp_key, tmp_val); 217 if (old_val == NULL) { 218 // Did hashmapPut fail? 219 if (errno == ENOMEM) { 220 goto clean_up; 221 } 222 // For new keys, hashmap takes ownership of tmp_key and tmp_val. 223 tmp_key = tmp_val = NULL; 224 } else { 225 // For existing keys, hashmap takes ownership of tmp_val. 226 // (It also gives up ownership of old_val.) 227 tmp_val = NULL; 228 } 229 230clean_up: 231 free(tmp_key); 232 free(tmp_val); 233 free(old_val); 234 int result = -errno; 235 errno = saved_errno; 236 return result; 237} 238 239int str_parms_add_int(struct str_parms *str_parms, const char *key, int value) 240{ 241 char val_str[12]; 242 int ret; 243 244 ret = snprintf(val_str, sizeof(val_str), "%d", value); 245 if (ret < 0) 246 return -EINVAL; 247 248 ret = str_parms_add_str(str_parms, key, val_str); 249 return ret; 250} 251 252int str_parms_add_float(struct str_parms *str_parms, const char *key, 253 float value) 254{ 255 char val_str[23]; 256 int ret; 257 258 ret = snprintf(val_str, sizeof(val_str), "%.10f", value); 259 if (ret < 0) 260 return -EINVAL; 261 262 ret = str_parms_add_str(str_parms, key, val_str); 263 return ret; 264} 265 266int str_parms_get_str(struct str_parms *str_parms, const char *key, char *val, 267 int len) 268{ 269 char *value; 270 271 value = hashmapGet(str_parms->map, (void *)key); 272 if (value) 273 return strlcpy(val, value, len); 274 275 return -ENOENT; 276} 277 278int str_parms_get_int(struct str_parms *str_parms, const char *key, int *val) 279{ 280 char *value; 281 char *end; 282 283 value = hashmapGet(str_parms->map, (void *)key); 284 if (!value) 285 return -ENOENT; 286 287 *val = (int)strtol(value, &end, 0); 288 if (*value != '\0' && *end == '\0') 289 return 0; 290 291 return -EINVAL; 292} 293 294int str_parms_get_float(struct str_parms *str_parms, const char *key, 295 float *val) 296{ 297 float out; 298 char *value; 299 char *end; 300 301 value = hashmapGet(str_parms->map, (void *)key); 302 if (!value) 303 return -ENOENT; 304 305 out = strtof(value, &end); 306 if (*value == '\0' || *end != '\0') 307 return -EINVAL; 308 309 *val = out; 310 return 0; 311} 312 313static bool combine_strings(void *key, void *value, void *context) 314{ 315 char **old_str = context; 316 char *new_str; 317 int ret; 318 319 ret = asprintf(&new_str, "%s%s%s=%s", 320 *old_str ? *old_str : "", 321 *old_str ? ";" : "", 322 (char *)key, 323 (char *)value); 324 if (*old_str) 325 free(*old_str); 326 327 if (ret >= 0) { 328 *old_str = new_str; 329 return true; 330 } 331 332 *old_str = NULL; 333 return false; 334} 335 336char *str_parms_to_str(struct str_parms *str_parms) 337{ 338 char *str = NULL; 339 340 if (hashmapSize(str_parms->map) > 0) 341 hashmapForEach(str_parms->map, combine_strings, &str); 342 else 343 str = strdup(""); 344 return str; 345} 346 347static bool dump_entry(void *key, void *value, void *context UNUSED) 348{ 349 ALOGI("key: '%s' value: '%s'\n", (char *)key, (char *)value); 350 return true; 351} 352 353void str_parms_dump(struct str_parms *str_parms) 354{ 355 hashmapForEach(str_parms->map, dump_entry, str_parms); 356} 357 358#ifdef TEST_STR_PARMS 359static void test_str_parms_str(const char *str) 360{ 361 struct str_parms *str_parms; 362 char *out_str; 363 364 str_parms = str_parms_create_str(str); 365 str_parms_add_str(str_parms, "dude", "woah"); 366 str_parms_add_str(str_parms, "dude", "woah"); 367 str_parms_del(str_parms, "dude"); 368 str_parms_dump(str_parms); 369 out_str = str_parms_to_str(str_parms); 370 str_parms_destroy(str_parms); 371 ALOGI("%s: '%s' stringified is '%s'", __func__, str, out_str); 372 free(out_str); 373} 374 375int main(void) 376{ 377 struct str_parms *str_parms; 378 379 test_str_parms_str(""); 380 test_str_parms_str(";"); 381 test_str_parms_str("="); 382 test_str_parms_str("=;"); 383 test_str_parms_str("=bar"); 384 test_str_parms_str("=bar;"); 385 test_str_parms_str("foo="); 386 test_str_parms_str("foo=;"); 387 test_str_parms_str("foo=bar"); 388 test_str_parms_str("foo=bar;"); 389 test_str_parms_str("foo=bar;baz"); 390 test_str_parms_str("foo=bar;baz="); 391 test_str_parms_str("foo=bar;baz=bat"); 392 test_str_parms_str("foo=bar;baz=bat;"); 393 test_str_parms_str("foo=bar;baz=bat;foo=bar"); 394 395 // hashmapPut reports errors by setting errno to ENOMEM. 396 // Test that we're not confused by running in an environment where this is already true. 397 errno = ENOMEM; 398 test_str_parms_str("foo=bar;baz="); 399 if (errno != ENOMEM) { 400 abort(); 401 } 402 test_str_parms_str("foo=bar;baz="); 403 404 return 0; 405} 406#endif 407