1/***
2  This file is part of PulseAudio.
3
4  Copyright 2009 Nokia Corporation
5  Contact: Maemo Multimedia <multimedia@maemo.org>
6
7  PulseAudio is free software; you can redistribute it and/or modify
8  it under the terms of the GNU Lesser General Public License as
9  published by the Free Software Foundation; either version 2.1 of the
10  License, or (at your option) any later version.
11
12  PulseAudio is distributed in the hope that it will be useful, but
13  WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  Lesser General Public License for more details.
16
17  You should have received a copy of the GNU Lesser General Public
18  License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
19***/
20
21#ifdef HAVE_CONFIG_H
22#include <config.h>
23#endif
24
25#include <errno.h>
26#include <sys/types.h>
27#include <unistd.h>
28#include <stdio.h>
29
30#include <pulse/xmalloc.h>
31#include <pulsecore/core-util.h>
32#include <pulsecore/log.h>
33#include <pulsecore/core-error.h>
34#include <pulsecore/hashmap.h>
35
36#include "database.h"
37
38typedef struct simple_data {
39    char *filename;
40    char *tmp_filename;
41    pa_hashmap *map;
42    bool read_only;
43} simple_data;
44
45typedef struct entry {
46    pa_datum key;
47    pa_datum data;
48} entry;
49
50void pa_datum_free(pa_datum *d) {
51    pa_assert(d);
52
53    pa_xfree(d->data);
54    d->data = NULL;
55    d->size = 0;
56}
57
58static int compare_func(const void *a, const void *b) {
59    const pa_datum *aa, *bb;
60
61    aa = (const pa_datum*)a;
62    bb = (const pa_datum*)b;
63
64    if (aa->size != bb->size)
65        return aa->size > bb->size ? 1 : -1;
66
67    return memcmp(aa->data, bb->data, aa->size);
68}
69
70/* pa_idxset_string_hash_func modified for our use */
71static unsigned hash_func(const void *p) {
72    const pa_datum *d;
73    unsigned hash = 0;
74    const char *c;
75    unsigned i;
76
77    d = (const pa_datum*)p;
78    c = d->data;
79
80    for (i = 0; i < d->size; i++) {
81        hash = 31 * hash + (unsigned) *c;
82        c++;
83    }
84
85    return hash;
86}
87
88static entry* new_entry(const pa_datum *key, const pa_datum *data) {
89    entry *e;
90
91    pa_assert(key);
92    pa_assert(data);
93
94    e = pa_xnew0(entry, 1);
95    e->key.data = key->size > 0 ? pa_xmemdup(key->data, key->size) : NULL;
96    e->key.size = key->size;
97    e->data.data = data->size > 0 ? pa_xmemdup(data->data, data->size) : NULL;
98    e->data.size = data->size;
99    return e;
100}
101
102static void free_entry(entry *e) {
103    if (e) {
104        if (e->key.data)
105            pa_xfree(e->key.data);
106        if (e->data.data)
107            pa_xfree(e->data.data);
108        pa_xfree(e);
109    }
110}
111
112static int read_uint(FILE *f, uint32_t *res) {
113    size_t items = 0;
114    uint8_t values[4];
115    uint32_t tmp;
116    int i;
117
118    items = fread(&values, sizeof(values), sizeof(uint8_t), f);
119
120    if (feof(f)) /* EOF */
121        return 0;
122
123    if (ferror(f))
124        return -1;
125
126    for (i = 0; i < 4; ++i) {
127        tmp = values[i];
128        *res += (tmp << (i*8));
129    }
130
131    return items;
132}
133
134static int read_data(FILE *f, void **data, ssize_t *length) {
135    size_t items = 0;
136    uint32_t data_len = 0;
137
138    pa_assert(f);
139
140    *data = NULL;
141    *length = 0;
142
143    if ((items = read_uint(f, &data_len)) <= 0)
144        return -1;
145
146    if (data_len > 0) {
147        *data = pa_xmalloc0(data_len);
148        items = fread(*data, data_len, 1, f);
149
150        if (feof(f)) /* EOF */
151            goto reset;
152
153        if (ferror(f))
154            goto reset;
155
156        *length = data_len;
157
158    } else { /* no data? */
159        return -1;
160    }
161
162    return 0;
163
164reset:
165    pa_xfree(*data);
166    *data = NULL;
167    *length = 0;
168    return -1;
169}
170
171static int fill_data(simple_data *db, FILE *f) {
172    pa_datum key;
173    pa_datum data;
174    void *d = NULL;
175    ssize_t l = 0;
176    bool append = false;
177    enum { FIELD_KEY = 0, FIELD_DATA } field = FIELD_KEY;
178
179    pa_assert(db);
180    pa_assert(db->map);
181
182    errno = 0;
183
184    key.size = 0;
185    key.data = NULL;
186
187    while (!read_data(f, &d, &l)) {
188
189        switch (field) {
190            case FIELD_KEY:
191                key.data = d;
192                key.size = l;
193                field = FIELD_DATA;
194                break;
195            case FIELD_DATA:
196                data.data = d;
197                data.size = l;
198                append = true;
199                break;
200        }
201
202        if (append) {
203            entry *e = pa_xnew0(entry, 1);
204            e->key.data = key.data;
205            e->key.size = key.size;
206            e->data.data = data.data;
207            e->data.size = data.size;
208            pa_hashmap_put(db->map, &e->key, e);
209            append = false;
210            field = FIELD_KEY;
211        }
212    }
213
214    if (ferror(f)) {
215        pa_log_warn("read error. %s", pa_cstrerror(errno));
216        pa_database_clear((pa_database*)db);
217    }
218
219    if (field == FIELD_DATA && d)
220        pa_xfree(d);
221
222    return pa_hashmap_size(db->map);
223}
224
225const char* pa_database_get_filename_suffix(void) {
226    return ".simple";
227}
228
229pa_database* pa_database_open_internal(const char *path, bool for_write) {
230    FILE *f;
231    simple_data *db;
232
233    pa_assert(path);
234
235    errno = 0;
236
237    f = pa_fopen_cloexec(path, "r");
238
239    if (f || errno == ENOENT) { /* file not found is ok */
240        db = pa_xnew0(simple_data, 1);
241        db->map = pa_hashmap_new_full(hash_func, compare_func, NULL, (pa_free_cb_t) free_entry);
242        db->filename = pa_xstrdup(path);
243        db->tmp_filename = pa_sprintf_malloc(".%s.tmp", db->filename);
244        db->read_only = !for_write;
245
246        if (f) {
247            fill_data(db, f);
248            fclose(f);
249        }
250    } else {
251        if (errno == 0)
252            errno = EIO;
253        db = NULL;
254    }
255
256    return (pa_database*) db;
257}
258
259void pa_database_close(pa_database *database) {
260    simple_data *db = (simple_data*)database;
261    pa_assert(db);
262
263    pa_database_sync(database);
264    pa_xfree(db->filename);
265    pa_xfree(db->tmp_filename);
266    pa_hashmap_free(db->map);
267    pa_xfree(db);
268}
269
270pa_datum* pa_database_get(pa_database *database, const pa_datum *key, pa_datum* data) {
271    simple_data *db = (simple_data*)database;
272    entry *e;
273
274    pa_assert(db);
275    pa_assert(key);
276    pa_assert(data);
277
278    e = pa_hashmap_get(db->map, key);
279
280    if (!e)
281        return NULL;
282
283    data->data = e->data.size > 0 ? pa_xmemdup(e->data.data, e->data.size) : NULL;
284    data->size = e->data.size;
285
286    return data;
287}
288
289int pa_database_set(pa_database *database, const pa_datum *key, const pa_datum* data, bool overwrite) {
290    simple_data *db = (simple_data*)database;
291    entry *e;
292    int ret = 0;
293
294    pa_assert(db);
295    pa_assert(key);
296    pa_assert(data);
297
298    if (db->read_only)
299        return -1;
300
301    e = new_entry(key, data);
302
303    if (pa_hashmap_put(db->map, &e->key, e) < 0) {
304        /* entry with same key exists in hashmap */
305        entry *r;
306        if (overwrite) {
307            r = pa_hashmap_remove(db->map, key);
308            pa_hashmap_put(db->map, &e->key, e);
309        } else {
310            /* won't overwrite, so clean new entry */
311            r = e;
312            ret = -1;
313        }
314
315        free_entry(r);
316    }
317
318    return ret;
319}
320
321int pa_database_unset(pa_database *database, const pa_datum *key) {
322    simple_data *db = (simple_data*)database;
323
324    pa_assert(db);
325    pa_assert(key);
326
327    return pa_hashmap_remove_and_free(db->map, key);
328}
329
330int pa_database_clear(pa_database *database) {
331    simple_data *db = (simple_data*)database;
332
333    pa_assert(db);
334
335    pa_hashmap_remove_all(db->map);
336
337    return 0;
338}
339
340signed pa_database_size(pa_database *database) {
341    simple_data *db = (simple_data*)database;
342    pa_assert(db);
343
344    return (signed) pa_hashmap_size(db->map);
345}
346
347pa_datum* pa_database_first(pa_database *database, pa_datum *key, pa_datum *data) {
348    simple_data *db = (simple_data*)database;
349    entry *e;
350
351    pa_assert(db);
352    pa_assert(key);
353
354    e = pa_hashmap_first(db->map);
355
356    if (!e)
357        return NULL;
358
359    key->data = e->key.size > 0 ? pa_xmemdup(e->key.data, e->key.size) : NULL;
360    key->size = e->key.size;
361
362    if (data) {
363        data->data = e->data.size > 0 ? pa_xmemdup(e->data.data, e->data.size) : NULL;
364        data->size = e->data.size;
365    }
366
367    return key;
368}
369
370pa_datum* pa_database_next(pa_database *database, const pa_datum *key, pa_datum *next, pa_datum *data) {
371    simple_data *db = (simple_data*)database;
372    entry *e;
373    entry *search;
374    void *state;
375    bool pick_now;
376
377    pa_assert(db);
378    pa_assert(next);
379
380    if (!key)
381        return pa_database_first(database, next, data);
382
383    search = pa_hashmap_get(db->map, key);
384
385    state = NULL;
386    pick_now = false;
387
388    while ((e = pa_hashmap_iterate(db->map, &state, NULL))) {
389        if (pick_now)
390            break;
391
392        if (search == e)
393            pick_now = true;
394    }
395
396    if (!pick_now || !e)
397        return NULL;
398
399    next->data = e->key.size > 0 ? pa_xmemdup(e->key.data, e->key.size) : NULL;
400    next->size = e->key.size;
401
402    if (data) {
403        data->data = e->data.size > 0 ? pa_xmemdup(e->data.data, e->data.size) : NULL;
404        data->size = e->data.size;
405    }
406
407    return next;
408}
409
410static int write_uint(FILE *f, const uint32_t num) {
411    size_t items;
412    uint8_t values[4];
413    int i;
414    errno = 0;
415
416    for (i = 0; i < 4; i++)
417        values[i] = (num >> (i*8)) & 0xFF;
418
419    items = fwrite(&values, sizeof(values), sizeof(uint8_t), f);
420
421    if (ferror(f))
422        return -1;
423
424    return items;
425}
426
427static int write_data(FILE *f, void *data, const size_t length) {
428    size_t items;
429    uint32_t len;
430
431    len = length;
432    if ((items = write_uint(f, len)) <= 0)
433        return -1;
434
435    items = fwrite(data, length, 1, f);
436
437    if (ferror(f) || items != 1)
438        return -1;
439
440    return 0;
441}
442
443static int write_entry(FILE *f, const entry *e) {
444    pa_assert(f);
445    pa_assert(e);
446
447    if (write_data(f, e->key.data, e->key.size) < 0)
448        return -1;
449    if (write_data(f, e->data.data, e->data.size) < 0)
450        return -1;
451
452    return 0;
453}
454
455int pa_database_sync(pa_database *database) {
456    simple_data *db = (simple_data*)database;
457    FILE *f;
458    void *state;
459    entry *e;
460
461    pa_assert(db);
462
463    if (db->read_only)
464        return 0;
465
466    errno = 0;
467
468    f = pa_fopen_cloexec(db->tmp_filename, "w");
469
470    if (!f)
471        goto fail;
472
473    state = NULL;
474    while((e = pa_hashmap_iterate(db->map, &state, NULL))) {
475        if (write_entry(f, e) < 0) {
476            pa_log_warn("error while writing to file. %s", pa_cstrerror(errno));
477            goto fail;
478        }
479    }
480
481    fclose(f);
482    f = NULL;
483
484    if (rename(db->tmp_filename, db->filename) < 0) {
485        pa_log_warn("error while renaming file. %s", pa_cstrerror(errno));
486        goto fail;
487    }
488
489    return 0;
490
491fail:
492    if (f)
493        fclose(f);
494    return -1;
495}
496