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