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(&timestamp, 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