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