1 /*-*- Mode: C; c-basic-offset: 8 -*-*/
2
3 /***
4 This file is part of libcanberra.
5
6 Copyright 2008 Lennart Poettering
7
8 libcanberra is free software; you can redistribute it and/or modify
9 it under the terms of the GNU Lesser General Public License as
10 published by the Free Software Foundation, either version 2.1 of the
11 License, or (at your option) any later version.
12
13 libcanberra is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public
19 License along with libcanberra. If not, see
20 <http://www.gnu.org/licenses/>.
21 ***/
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #include <signal.h>
30 #include <fcntl.h>
31 #include <unistd.h>
32 #include <pthread.h>
33 #include <errno.h>
34
35 #include <tdb.h>
36
37 #include "malloc.h"
38 #include "macro.h"
39 #include "mutex.h"
40 #include "canberra.h"
41 #include "sound-theme-spec.h"
42 #include "cache.h"
43
44 #define FILENAME "event-sound-cache.tdb"
45 #define UPDATE_INTERVAL 10
46
47 /* This part is not portable due to pthread_once usage, should be abstracted
48 * when we port this to platforms that do not have POSIX threading */
49
50 static ca_mutex *mutex = NULL;
51 static struct tdb_context *database = NULL;
52
allocate_mutex_once(void)53 static void allocate_mutex_once(void) {
54 mutex = ca_mutex_new();
55 }
56
allocate_mutex(void)57 static int allocate_mutex(void) {
58 static pthread_once_t once = PTHREAD_ONCE_INIT;
59
60 if (pthread_once(&once, allocate_mutex_once) != 0)
61 return CA_ERROR_OOM;
62
63 if (!mutex)
64 return CA_ERROR_OOM;
65
66 return 0;
67 }
68
get_cache_home(char ** e)69 static int get_cache_home(char **e) {
70 const char *env, *subdir;
71 char *r;
72
73 ca_return_val_if_fail(e, CA_ERROR_INVALID);
74
75 if ((env = getenv("XDG_CACHE_HOME")) && *env == '/')
76 subdir = "";
77 else if ((env = getenv("HOME")) && *env == '/')
78 subdir = "/.cache";
79 else {
80 *e = NULL;
81 return CA_SUCCESS;
82 }
83
84 if (!(r = ca_new(char, strlen(env) + strlen(subdir) + 1)))
85 return CA_ERROR_OOM;
86
87 sprintf(r, "%s%s", env, subdir);
88 *e = r;
89
90 return CA_SUCCESS;
91 }
92
sensible_gethostbyname(char * n,size_t l)93 static int sensible_gethostbyname(char *n, size_t l) {
94
95 if (gethostname(n, l) < 0)
96 return -1;
97
98 n[l-1] = 0;
99
100 if (strlen(n) >= l-1) {
101 errno = ENAMETOOLONG;
102 return -1;
103 }
104
105 if (!n[0]) {
106 errno = ENOENT;
107 return -1;
108 }
109
110 return 0;
111 }
112
get_machine_id(char ** id)113 static int get_machine_id(char **id) {
114 FILE *f;
115 size_t l;
116
117 ca_return_val_if_fail(id, CA_ERROR_INVALID);
118
119 /* First we try the D-Bus machine id */
120
121 if ((f = fopen(CA_MACHINE_ID, "r"))) {
122 char ln[34] = "", *r;
123
124 r = fgets(ln, sizeof(ln)-1, f);
125 fclose(f);
126
127 if (r) {
128 ln[strcspn(ln, " \n\r\t")] = 0;
129
130 if (!(*id = ca_strdup(ln)))
131 return CA_ERROR_OOM;
132
133 return CA_SUCCESS;
134 }
135 }
136
137 /* Then we try the host name */
138
139 l = 100;
140
141 for (;;) {
142 if (!(*id = ca_new(char, l)))
143 return CA_ERROR_OOM;
144
145 if (sensible_gethostbyname(*id, l) >= 0)
146 return CA_SUCCESS;
147
148 ca_free(*id);
149
150 if (errno != EINVAL && errno != ENAMETOOLONG)
151 break;
152
153 l *= 2;
154 }
155
156 /* Then we use the POSIX host id */
157
158 *id = ca_sprintf_malloc("%08lx", (unsigned long) gethostid());
159 return CA_SUCCESS;
160 }
161
db_open(void)162 static int db_open(void) {
163 int ret;
164 char *c, *id, *pn;
165
166 if ((ret = allocate_mutex()) < 0)
167 return ret;
168
169 ca_mutex_lock(mutex);
170
171 if (database) {
172 ret = CA_SUCCESS;
173 goto finish;
174 }
175
176 if ((ret = get_cache_home(&c)) < 0)
177 goto finish;
178
179 if (!c) {
180 ret = CA_ERROR_NOTFOUND;
181 goto finish;
182 }
183
184 /* Try to create, just in case it doesn't exist yet. We don't do
185 * this recursively however. */
186 mkdir(c, 0755);
187
188 if ((ret = get_machine_id(&id)) < 0) {
189 ca_free(c);
190 goto finish;
191 }
192
193 /* This data is machine specific, hence we include some kind of
194 * stable machine id here in the name. Also, we don't want to care
195 * abouth endianess/packing issues, hence we include the compiler
196 * target in the name, too. */
197
198 pn = ca_sprintf_malloc("%s/" FILENAME ".%s." CANONICAL_HOST, c, id);
199 ca_free(c);
200 ca_free(id);
201
202 if (!pn) {
203 ret = CA_ERROR_OOM;
204 goto finish;
205 }
206
207 /* We pass TDB_NOMMAP here as long as rhbz 460851 is not fixed in
208 * tdb. */
209 database = tdb_open(pn, 0, TDB_NOMMAP, O_RDWR|O_CREAT|O_NOCTTY
210 #ifdef O_CLOEXEC
211 | O_CLOEXEC
212 #endif
213 , 0644);
214 ca_free(pn);
215
216 if (!database) {
217 ret = CA_ERROR_CORRUPT;
218 goto finish;
219 }
220
221 ret = CA_SUCCESS;
222
223 finish:
224 ca_mutex_unlock(mutex);
225
226 return ret;
227 }
228
229 #ifdef CA_GCC_DESTRUCTOR
230
231 static void db_close(void) CA_GCC_DESTRUCTOR;
232
db_close(void)233 static void db_close(void) {
234 /* Only here to make this valgrind clean */
235
236 if (!getenv("VALGRIND"))
237 return;
238
239 if (mutex) {
240 ca_mutex_free(mutex);
241 mutex = NULL;
242 }
243
244 if (database) {
245 tdb_close(database);
246 database = NULL;
247 }
248 }
249
250 #endif
251
db_lookup(const void * key,size_t klen,void ** data,size_t * dlen)252 static int db_lookup(const void *key, size_t klen, void **data, size_t *dlen) {
253 int ret;
254 TDB_DATA k, d;
255
256 ca_return_val_if_fail(key, CA_ERROR_INVALID);
257 ca_return_val_if_fail(klen > 0, CA_ERROR_INVALID);
258 ca_return_val_if_fail(data, CA_ERROR_INVALID);
259 ca_return_val_if_fail(dlen, CA_ERROR_INVALID);
260
261 if ((ret = db_open()) < 0)
262 return ret;
263
264 k.dptr = (void*) key;
265 k.dsize = klen;
266
267 ca_mutex_lock(mutex);
268
269 ca_assert(database);
270 d = tdb_fetch(database, k);
271 if (!d.dptr) {
272 ret = CA_ERROR_NOTFOUND;
273 goto finish;
274 }
275
276 *data = d.dptr;
277 *dlen = d.dsize;
278
279 finish:
280 ca_mutex_unlock(mutex);
281
282 return ret;
283 }
284
db_store(const void * key,size_t klen,const void * data,size_t dlen)285 static int db_store(const void *key, size_t klen, const void *data, size_t dlen) {
286 int ret;
287 TDB_DATA k, d;
288
289 ca_return_val_if_fail(key, CA_ERROR_INVALID);
290 ca_return_val_if_fail(klen > 0, CA_ERROR_INVALID);
291 ca_return_val_if_fail(data || dlen == 0, CA_ERROR_INVALID);
292
293 if ((ret = db_open()) < 0)
294 return ret;
295
296 k.dptr = (void*) key;
297 k.dsize = klen;
298
299 d.dptr = (void*) data;
300 d.dsize = dlen;
301
302 ca_mutex_lock(mutex);
303
304 ca_assert(database);
305 if (tdb_store(database, k, d, TDB_REPLACE) < 0) {
306 ret = CA_ERROR_CORRUPT;
307 goto finish;
308 }
309
310 ret = CA_SUCCESS;
311
312 finish:
313 ca_mutex_unlock(mutex);
314
315 return ret;
316 }
317
db_remove(const void * key,size_t klen)318 static int db_remove(const void *key, size_t klen) {
319 int ret;
320 TDB_DATA k;
321
322 ca_return_val_if_fail(key, CA_ERROR_INVALID);
323 ca_return_val_if_fail(klen > 0, CA_ERROR_INVALID);
324
325 if ((ret = db_open()) < 0)
326 return ret;
327
328 k.dptr = (void*) key;
329 k.dsize = klen;
330
331 ca_mutex_lock(mutex);
332
333 ca_assert(database);
334 if (tdb_delete(database, k) < 0) {
335 ret = CA_ERROR_CORRUPT;
336 goto finish;
337 }
338
339 ret = CA_SUCCESS;
340
341 finish:
342 ca_mutex_unlock(mutex);
343
344 return ret;
345 }
346
build_key(const char * theme,const char * name,const char * locale,const char * profile,size_t * klen)347 static char *build_key(
348 const char *theme,
349 const char *name,
350 const char *locale,
351 const char *profile,
352 size_t *klen) {
353
354 char *key, *k;
355 size_t tl, nl, ll, pl;
356
357 tl = strlen(theme);
358 nl = strlen(name);
359 ll = strlen(locale);
360 pl = strlen(profile);
361 *klen = tl+1+nl+1+ll+1+pl+1;
362
363 if (!(key = ca_new(char, *klen)))
364 return NULL;
365
366 k = key;
367 strcpy(k, theme);
368 k += tl+1;
369 strcpy(k, name);
370 k += nl+1;
371 strcpy(k, locale);
372 k += ll+1;
373 strcpy(k, profile);
374
375 return key;
376 }
377
get_last_change(time_t * t)378 static int get_last_change(time_t *t) {
379 int ret;
380 char *e, *k;
381 struct stat st;
382 static time_t last_check = 0, last_change = 0;
383 time_t now;
384 const char *g;
385
386 ca_return_val_if_fail(t, CA_ERROR_INVALID);
387
388 if ((ret = allocate_mutex()) < 0)
389 return ret;
390
391 ca_mutex_lock(mutex);
392
393 ca_assert_se(time(&now) != (time_t) -1);
394
395 if (now < last_check + UPDATE_INTERVAL) {
396 *t = last_change;
397 ret = CA_SUCCESS;
398 goto finish;
399 }
400
401 if ((ret = ca_get_data_home(&e)) < 0)
402 goto finish;
403
404 *t = 0;
405
406 if (e) {
407 if (!(k = ca_new(char, strlen(e) + sizeof("/sounds")))) {
408 ca_free(e);
409 ret = CA_ERROR_OOM;
410 goto finish;
411 }
412
413 sprintf(k, "%s/sounds", e);
414 ca_free(e);
415
416 if (stat(k, &st) >= 0)
417 *t = st.st_mtime;
418
419 ca_free(k);
420 }
421
422 g = ca_get_data_dirs();
423
424 for (;;) {
425 size_t j = strcspn(g, ":");
426
427 if (g[0] == '/' && j > 0) {
428
429 if (!(k = ca_new(char, j + sizeof("/sounds")))) {
430 ret = CA_ERROR_OOM;
431 goto finish;
432 }
433
434 memcpy(k, g, j);
435 strcpy(k+j, "/sounds");
436
437 if (stat(k, &st) >= 0)
438 if (st.st_mtime >= *t)
439 *t = st.st_mtime;
440
441 ca_free(k);
442 }
443
444 if (g[j] == 0)
445 break;
446
447 g += j+1;
448 }
449
450 last_change = *t;
451 last_check = now;
452
453 ret = 0;
454
455 finish:
456
457 ca_mutex_unlock(mutex);
458
459 return ret;
460 }
461
ca_cache_lookup_sound(ca_sound_file ** f,ca_sound_file_open_callback_t sfopen,char ** sound_path,const char * theme,const char * name,const char * locale,const char * profile)462 int ca_cache_lookup_sound(
463 ca_sound_file **f,
464 ca_sound_file_open_callback_t sfopen,
465 char **sound_path,
466 const char *theme,
467 const char *name,
468 const char *locale,
469 const char *profile) {
470
471 char *key = NULL;
472 void *data = NULL;
473 size_t klen, dlen;
474 int ret;
475 uint32_t timestamp;
476 time_t last_change, now;
477 ca_bool_t remove_entry = FALSE;
478
479 ca_return_val_if_fail(f, CA_ERROR_INVALID);
480 ca_return_val_if_fail(sfopen, CA_ERROR_INVALID);
481 ca_return_val_if_fail(theme, CA_ERROR_INVALID);
482 ca_return_val_if_fail(name && *name, CA_ERROR_INVALID);
483 ca_return_val_if_fail(locale, CA_ERROR_INVALID);
484 ca_return_val_if_fail(profile, CA_ERROR_INVALID);
485
486 if (sound_path)
487 *sound_path = NULL;
488
489 if (!(key = build_key(theme, name, locale, profile, &klen)))
490 return CA_ERROR_OOM;
491
492 ret = db_lookup(key, klen, &data, &dlen);
493
494 if (ret < 0)
495 goto finish;
496
497 ca_assert(data);
498
499 if (dlen < sizeof(uint32_t) ||
500 (dlen > sizeof(uint32_t) && ((char*) data)[dlen-1] != 0)) {
501
502 /* Corrupt entry */
503 ret = CA_ERROR_NOTFOUND;
504 remove_entry = TRUE;
505 goto finish;
506 }
507
508 memcpy(×tamp, data, sizeof(timestamp));
509
510 if ((ret = get_last_change(&last_change)) < 0)
511 goto finish;
512
513 ca_assert_se(time(&now) != (time_t) -1);
514
515 /* Hmm, is the entry older than the last change to our sound theme
516 * dirs? Also, check for clock skews */
517 if ((time_t) timestamp < last_change || ((time_t) timestamp > now)) {
518 remove_entry = TRUE;
519 ret = CA_ERROR_NOTFOUND;
520 goto finish;
521 }
522
523 if (dlen <= sizeof(uint32_t)) {
524 /* Negative caching entry. */
525 *f = NULL;
526 ret = CA_SUCCESS;
527 goto finish;
528 }
529
530 if (sound_path) {
531 if (!(*sound_path = ca_strdup((const char*) data + sizeof(uint32_t)))) {
532 ret = CA_ERROR_OOM;
533 goto finish;
534 }
535 }
536
537 if ((ret = sfopen(f, (const char*) data + sizeof(uint32_t))) < 0)
538 remove_entry = TRUE;
539
540 finish:
541
542 if (remove_entry)
543 db_remove(key, klen);
544
545 if (sound_path && ret < 0)
546 ca_free(*sound_path);
547
548 ca_free(key);
549 ca_free(data);
550
551 return ret;
552 }
553
ca_cache_store_sound(const char * theme,const char * name,const char * locale,const char * profile,const char * fname)554 int ca_cache_store_sound(
555 const char *theme,
556 const char *name,
557 const char *locale,
558 const char *profile,
559 const char *fname) {
560
561 char *key;
562 void *data;
563 size_t klen, dlen;
564 int ret;
565 time_t now;
566
567 ca_return_val_if_fail(theme, CA_ERROR_INVALID);
568 ca_return_val_if_fail(name && *name, CA_ERROR_INVALID);
569 ca_return_val_if_fail(locale, CA_ERROR_INVALID);
570 ca_return_val_if_fail(profile, CA_ERROR_INVALID);
571
572 if (!(key = build_key(theme, name, locale, profile, &klen)))
573 return CA_ERROR_OOM;
574
575 dlen = sizeof(uint32_t) + (fname ? strlen(fname) + 1 : 0);
576
577 if (!(data = ca_malloc(dlen))) {
578 ca_free(key);
579 return CA_ERROR_OOM;
580 }
581
582 ca_assert_se(time(&now) != (time_t) -1);
583 *(uint32_t*) data = (uint32_t) now;
584
585 if (fname)
586 strcpy((char*) data + sizeof(uint32_t), fname);
587
588 ret = db_store(key, klen, data, dlen);
589
590 ca_free(key);
591 ca_free(data);
592
593 return ret;
594 }
595