1 /*
2  * MOC - music on console
3  * Copyright (C) 2005, 2006 Damian Pietras <daper@daper.net>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  */
11 
12 #ifdef HAVE_CONFIG_H
13 # include "config.h"
14 #endif
15 
16 #include <pthread.h>
17 #include <assert.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <stdio.h>
21 #include <errno.h>
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <time.h>
25 #include <unistd.h>
26 
27 #ifdef HAVE_DB_H
28 #include <db.h>
29 #endif
30 
31 /* Include dirent for various systems */
32 #ifdef HAVE_DIRENT_H
33 # include <dirent.h>
34 #else
35 # define dirent direct
36 # if HAVE_SYS_NDIR_H
37 #  include <sys/ndir.h>
38 # endif
39 #endif
40 
41 #define DEBUG
42 
43 #include "common.h"
44 #include "server.h"
45 #include "playlist.h"
46 #include "rbtree.h"
47 #include "files.h"
48 #include "tags_cache.h"
49 #include "log.h"
50 #include "audio.h"
51 
52 /* The name of the tags database in the cache directory. */
53 #define TAGS_DB "tags.db"
54 
55 /* The name of the version tag file in the cache directory. */
56 #define MOC_VERSION_TAG "moc_version_tag"
57 
58 /* The maximum length of the version tag (including trailing NULL). */
59 #define VERSION_TAG_MAX 64
60 
61 /* Number used to create cache version tag to detect incompatibilities
62  * between cache version stored on the disk and MOC/BerkeleyDB environment.
63  *
64  * If you modify the DB structure, increase this number.  You can also
65  * temporarily set it to zero to disable cache activity during structural
66  * changes which require multiple commits.
67  */
68 #define CACHE_DB_FORMAT_VERSION	1
69 
70 /* How frequently to flush the tags database to disk.  A value of zero
71  * disables flushing. */
72 #define DB_SYNC_COUNT 5
73 
74 /* Element of a requests queue. */
75 struct request_queue_node
76 {
77 	struct request_queue_node *next;
78 	char *file; /* file that this request is for (malloc()ed) */
79 	int tags_sel; /* which tags to read (TAGS_*) */
80 };
81 
82 struct cache_record
83 {
84 	time_t mod_time;		/* last modification time of the file */
85 	time_t atime;			/* Time of last access. */
86 	struct file_tags *tags;
87 };
88 
request_queue_init(struct request_queue * q)89 static void request_queue_init (struct request_queue *q)
90 {
91 	assert (q != NULL);
92 
93 	q->head = NULL;
94 	q->tail = NULL;
95 }
96 
request_queue_clear(struct request_queue * q)97 static void request_queue_clear (struct request_queue *q)
98 {
99 	assert (q != NULL);
100 
101 	while (q->head) {
102 		struct request_queue_node *o = q->head;
103 
104 		q->head = q->head->next;
105 
106 		free (o->file);
107 		free (o);
108 	}
109 
110 	q->tail = NULL;
111 }
112 
113 /* Remove items from the queue from the beginning to the specified file. */
request_queue_clear_up_to(struct request_queue * q,const char * file)114 static void request_queue_clear_up_to (struct request_queue *q,
115                                               const char *file)
116 {
117 	int stop = 0;
118 
119 	assert (q != NULL);
120 
121 	while (q->head && !stop) {
122 		struct request_queue_node *o = q->head;
123 
124 		q->head = q->head->next;
125 
126 		if (!strcmp (o->file, file))
127 			stop = 1;
128 
129 		free (o->file);
130 		free (o);
131 	}
132 
133 	if (!q->head)
134 		q->tail = NULL;
135 }
136 
request_queue_add(struct request_queue * q,const char * file,int tags_sel)137 static void request_queue_add (struct request_queue *q, const char *file,
138                                                             int tags_sel)
139 {
140 	assert (q != NULL);
141 
142 	if (!q->head) {
143 		q->head = (struct request_queue_node *)xmalloc (
144 				sizeof(struct request_queue_node));
145 		q->tail = q->head;
146 	}
147 	else {
148 		assert (q->tail != NULL);
149 		assert (q->tail->next == NULL);
150 
151 		q->tail->next = (struct request_queue_node *)xmalloc (
152 				sizeof(struct request_queue_node));
153 		q->tail = q->tail->next;
154 	}
155 
156 	q->tail->file = xstrdup (file);
157 	q->tail->tags_sel = tags_sel;
158 	q->tail->next = NULL;
159 }
160 
request_queue_empty(const struct request_queue * q)161 static int request_queue_empty (const struct request_queue *q)
162 {
163 	assert (q != NULL);
164 
165 	return q->head == NULL;
166 }
167 
168 /* Get the file name of the first element in the queue or NULL if the queue is
169  * empty. Put tags to be read in *tags_sel. Returned memory is malloc()ed. */
request_queue_pop(struct request_queue * q,int * tags_sel)170 static char *request_queue_pop (struct request_queue *q, int *tags_sel)
171 {
172 	struct request_queue_node *n;
173 	char *file;
174 
175 	assert (q != NULL);
176 
177 	if (q->head == NULL)
178 		return NULL;
179 
180 	n = q->head;
181 	q->head = n->next;
182 	file = n->file;
183 	*tags_sel = n->tags_sel;
184 	free (n);
185 
186 	if (q->tail == n)
187 		q->tail = NULL; /* the queue is empty */
188 
189 	return file;
190 }
191 
192 #ifdef HAVE_DB_H
strlen_null(const char * s)193 static size_t strlen_null (const char *s)
194 {
195 	return s ? strlen (s) : 0;
196 }
197 #endif
198 
199 #ifdef HAVE_DB_H
cache_record_serialize(const struct cache_record * rec,int * len)200 static char *cache_record_serialize (const struct cache_record *rec, int *len)
201 {
202 	char *buf;
203 	char *p;
204 	size_t artist_len;
205 	size_t album_len;
206 	size_t title_len;
207 
208 	artist_len = strlen_null (rec->tags->artist);
209 	album_len = strlen_null (rec->tags->album);
210 	title_len = strlen_null (rec->tags->title);
211 
212 	*len = sizeof(rec->mod_time)
213 		+ sizeof(rec->atime)
214 		+ sizeof(size_t) * 3 /* lengths of title, artist, time. */
215 		+ artist_len
216 		+ album_len
217 		+ title_len
218 		+ sizeof(rec->tags->track)
219 		+ sizeof(rec->tags->time);
220 
221 	buf = p = (char *)xmalloc (*len);
222 
223 	memcpy (p, &rec->mod_time, sizeof(rec->mod_time));
224 	p += sizeof(rec->mod_time);
225 
226 	memcpy (p, &rec->atime, sizeof(rec->atime));
227 	p += sizeof(rec->atime);
228 
229 	memcpy (p, &artist_len, sizeof(artist_len));
230 	p += sizeof(artist_len);
231 	if (artist_len) {
232 		memcpy (p, rec->tags->artist, artist_len);
233 		p += artist_len;
234 	}
235 
236 	memcpy (p, &album_len, sizeof(album_len));
237 	p += sizeof(album_len);
238 	if (album_len) {
239 		memcpy (p, rec->tags->album, album_len);
240 		p += album_len;
241 	}
242 
243 	memcpy (p, &title_len, sizeof(title_len));
244 	p += sizeof(title_len);
245 	if (title_len) {
246 		memcpy (p, rec->tags->title, title_len);
247 		p += title_len;
248 	}
249 
250 	memcpy (p, &rec->tags->track, sizeof(rec->tags->track));
251 	p += sizeof(rec->tags->track);
252 
253 	memcpy (p, &rec->tags->time, sizeof(rec->tags->time));
254 	p += sizeof(rec->tags->time);
255 
256 	return buf;
257 }
258 #endif
259 
260 #ifdef HAVE_DB_H
cache_record_deserialize(struct cache_record * rec,const char * serialized,size_t size,int skip_tags)261 static int cache_record_deserialize (struct cache_record *rec,
262            const char *serialized, size_t size, int skip_tags)
263 {
264 	const char *p = serialized;
265 	size_t bytes_left = size;
266 	size_t str_len;
267 
268 	assert (rec != NULL);
269 	assert (serialized != NULL);
270 
271 	if (!skip_tags)
272 		rec->tags = tags_new ();
273 	else
274 		rec->tags = NULL;
275 
276 #define extract_num(var)			\
277 	if (bytes_left < sizeof(var))		\
278 		goto err;			\
279 	memcpy (&var, p, sizeof(var));		\
280 	bytes_left -= sizeof(var);		\
281 	p += sizeof(var);
282 
283 #define extract_str(var)			\
284 	if (bytes_left < sizeof(str_len))	\
285 		goto err;			\
286 	memcpy (&str_len, p, sizeof(str_len));	\
287 	p += sizeof(str_len);			\
288 	if (bytes_left < str_len)		\
289 		goto err;			\
290 	var = xmalloc (str_len + 1);		\
291 	memcpy (var, p, str_len);		\
292 	var[str_len] = '\0';			\
293 	p += str_len;
294 
295 	extract_num (rec->mod_time);
296 	extract_num (rec->atime);
297 
298 	if (!skip_tags) {
299 		extract_str (rec->tags->artist);
300 		extract_str (rec->tags->album);
301 		extract_str (rec->tags->title);
302 		extract_num (rec->tags->track);
303 		extract_num (rec->tags->time);
304 
305 		if (rec->tags->title)
306 			rec->tags->filled |= TAGS_COMMENTS;
307 		else {
308 			if (rec->tags->artist)
309 				free (rec->tags->artist);
310 			rec->tags->artist = NULL;
311 
312 			if (rec->tags->album)
313 				free (rec->tags->album);
314 			rec->tags->album = NULL;
315 		}
316 
317 		if (rec->tags->time >= 0)
318 			rec->tags->filled |= TAGS_TIME;
319 	}
320 
321 	return 1;
322 
323 err:
324 	logit ("Cache record deserialization error at %tdB", p - serialized);
325 	tags_free (rec->tags);
326 	rec->tags = NULL;
327 	return 0;
328 }
329 #endif
330 
331 /* Locked DB function prototype.
332  * The function must not acquire or release DB locks. */
333 #ifdef HAVE_DB_H
334 typedef void *t_locked_fn (struct tags_cache *, const char *,
335                                       int, int, DBT *, DBT *);
336 #endif
337 
338 /* This function ensures that a DB function takes place while holding a
339  * database record lock.  It also provides an initialised database thang
340  * for the key and record. */
341 #ifdef HAVE_DB_H
with_db_lock(t_locked_fn fn,struct tags_cache * c,const char * file,int tags_sel,int client_id)342 static void *with_db_lock (t_locked_fn fn, struct tags_cache *c,
343                            const char *file, int tags_sel, int client_id)
344 {
345 	int rc;
346 	void *result;
347 	DB_LOCK lock;
348 	DBT key, record;
349 
350 	assert (c->db_env != NULL);
351 
352 	memset (&key, 0, sizeof (key));
353 	key.data = (void *) file;
354 	key.size = strlen (file);
355 
356 	memset (&record, 0, sizeof (record));
357 	record.flags = DB_DBT_MALLOC;
358 
359 	rc = c->db_env->lock_get (c->db_env, c->locker, 0,
360 			&key, DB_LOCK_WRITE, &lock);
361 	if (rc)
362 		fatal ("Can't get DB lock: %s", db_strerror (rc));
363 
364 	result = fn (c, file, tags_sel, client_id, &key, &record);
365 
366 	rc = c->db_env->lock_put (c->db_env, &lock);
367 	if (rc)
368 		fatal ("Can't release DB lock: %s", db_strerror (rc));
369 
370 	if (record.data)
371 		free (record.data);
372 
373 	return result;
374 }
375 #endif
376 
377 #ifdef HAVE_DB_H
tags_cache_remove_rec(struct tags_cache * c,const char * fname)378 static void tags_cache_remove_rec (struct tags_cache *c, const char *fname)
379 {
380 	DBT key;
381 	int ret;
382 
383 	assert (fname != NULL);
384 
385 	debug ("Removing %s from the cache...", fname);
386 
387 	memset (&key, 0, sizeof(key));
388 	key.data = (void *)fname;
389 	key.size = strlen (fname);
390 
391 	ret = c->db->del (c->db, NULL, &key, 0);
392 	if (ret)
393 		logit ("Can't remove item for %s from the cache: %s", fname,
394 				db_strerror (ret));
395 }
396 #endif
397 
398 /* Remove the one element of the cache based on it's access time. */
399 #ifdef HAVE_DB_H
tags_cache_gc(struct tags_cache * c)400 static void tags_cache_gc (struct tags_cache *c)
401 {
402 	DBC *cur;
403 	DBT key;
404 	DBT serialized_cache_rec;
405 	int ret;
406 	char *last_referenced = NULL;
407 	time_t last_referenced_atime = time (NULL) + 1;
408 	int nitems = 0;
409 
410 	c->db->cursor (c->db, NULL, &cur, 0);
411 
412 	memset (&key, 0, sizeof(key));
413 	memset (&serialized_cache_rec, 0, sizeof(serialized_cache_rec));
414 
415 	key.flags = DB_DBT_MALLOC;
416 	serialized_cache_rec.flags = DB_DBT_MALLOC;
417 
418 	while (true) {
419 		struct cache_record rec;
420 
421 #if DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR < 6
422 		ret = cur->c_get (cur, &key, &serialized_cache_rec, DB_NEXT);
423 #else
424 		ret = cur->get (cur, &key, &serialized_cache_rec, DB_NEXT);
425 #endif
426 
427 		if (ret != 0)
428 			break;
429 
430 		if (cache_record_deserialize (&rec, serialized_cache_rec.data,
431 					serialized_cache_rec.size, 1)
432 				&& rec.atime < last_referenced_atime) {
433 			last_referenced_atime = rec.atime;
434 
435 			if (last_referenced)
436 				free (last_referenced);
437 			last_referenced = (char *)xmalloc (key.size + 1);
438 			memcpy (last_referenced, key.data, key.size);
439 			last_referenced[key.size] = '\0';
440 		}
441 
442 		// TODO: remove objects with serialization error.
443 
444 		nitems++;
445 
446 		free (key.data);
447 		free (serialized_cache_rec.data);
448 	}
449 
450 	if (ret != DB_NOTFOUND)
451 		logit ("Searching for element to remove failed (cursor): %s",
452 				db_strerror (ret));
453 
454 #if DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR < 6
455 	cur->c_close (cur);
456 #else
457 	cur->close (cur);
458 #endif
459 
460 	debug ("Elements in cache: %d (limit %d)", nitems, c->max_items);
461 
462 	if (last_referenced) {
463 		if (nitems >= c->max_items)
464 			tags_cache_remove_rec (c, last_referenced);
465 		free (last_referenced);
466 	}
467 	else
468 		debug ("Cache empty");
469 }
470 #endif
471 
472 /* Synchronize cache every DB_SYNC_COUNT updates. */
473 #ifdef HAVE_DB_H
tags_cache_sync(struct tags_cache * c)474 static void tags_cache_sync (struct tags_cache *c)
475 {
476 	static int sync_count = 0;
477 
478 	if (DB_SYNC_COUNT == 0)
479 		return;
480 
481 	sync_count += 1;
482 	if (sync_count >= DB_SYNC_COUNT) {
483 		sync_count = 0;
484 		c->db->sync (c->db, 0);
485 	}
486 }
487 #endif
488 
489 /* Add this tags object for the file to the cache. */
490 #ifdef HAVE_DB_H
tags_cache_add(struct tags_cache * c,const char * file,DBT * key,struct file_tags * tags)491 static void tags_cache_add (struct tags_cache *c, const char *file,
492                                   DBT *key, struct file_tags *tags)
493 {
494 	char *serialized_cache_rec;
495 	int serial_len;
496 	struct cache_record rec;
497 	DBT data;
498 	int ret;
499 
500 	assert (tags != NULL);
501 
502 	debug ("Adding/updating cache object");
503 
504 	rec.mod_time = get_mtime (file);
505 	rec.atime = time (NULL);
506 	rec.tags = tags;
507 
508 	serialized_cache_rec = cache_record_serialize (&rec, &serial_len);
509 	if (!serialized_cache_rec)
510 		return;
511 
512 	memset (&data, 0, sizeof(data));
513 	data.data = serialized_cache_rec;
514 	data.size = serial_len;
515 
516 	tags_cache_gc (c);
517 
518 	ret = c->db->put (c->db, NULL, key, &data, 0);
519 	if (ret)
520 		error ("DB put error: %s", db_strerror (ret));
521 
522 	tags_cache_sync (c);
523 
524 	free (serialized_cache_rec);
525 }
526 #endif
527 
528 /* Read time tags for a file into tags structure (or create it if NULL). */
read_missing_tags(const char * file,struct file_tags * tags,int tags_sel)529 struct file_tags *read_missing_tags (const char *file,
530                  struct file_tags *tags, int tags_sel)
531 {
532 	if (tags == NULL)
533 		tags = tags_new ();
534 
535 	if (tags_sel & TAGS_TIME) {
536 		int time;
537 
538 		/* Try to get it from the server's playlist first. */
539 		time = audio_get_ftime (file);
540 
541 		if (time != -1) {
542 			tags->time = time;
543 			tags->filled |= TAGS_TIME;
544 			tags_sel &= ~TAGS_TIME;
545 		}
546 	}
547 
548 	tags = read_file_tags (file, tags, tags_sel);
549 
550 	return tags;
551 }
552 
553 /* Read the selected tags for this file and add it to the cache. */
554 #ifdef HAVE_DB_H
locked_read_add(struct tags_cache * c,const char * file,const int tags_sel,const int client_id,DBT * key,DBT * serialized_cache_rec)555 static void *locked_read_add (struct tags_cache *c, const char *file,
556                               const int tags_sel, const int client_id,
557                               DBT *key, DBT *serialized_cache_rec)
558 {
559 	int ret;
560 	struct file_tags *tags = NULL;
561 
562 	assert (c->db != NULL);
563 
564 	ret = c->db->get (c->db, NULL, key, serialized_cache_rec, 0);
565 	if (ret && ret != DB_NOTFOUND)
566 		logit ("Cache DB get error: %s", db_strerror (ret));
567 
568 	/* If this entry is already present in the cache, we have 3 options:
569 	 * we must read different tags (TAGS_*) or the tags are outdated
570 	 * or this is an immediate tags read (client_id == -1) */
571 	if (ret == 0) {
572 		struct cache_record rec;
573 
574 		if (cache_record_deserialize (&rec, serialized_cache_rec->data,
575 		                              serialized_cache_rec->size, 0)) {
576 			time_t curr_mtime = get_mtime (file);
577 
578 			if (rec.mod_time != curr_mtime) {
579 				debug ("Tags in the cache are outdated");
580 				tags_free (rec.tags);  /* remove them and reread tags */
581 			}
582 			else if ((rec.tags->filled & tags_sel) == tags_sel
583 					&& client_id == -1) {
584 				debug ("Tags are in the cache.");
585 				return rec.tags;
586 			}
587 			else {
588 				debug ("Tags in the cache are not what we want");
589 				tags = rec.tags;  /* read additional tags */
590 			}
591 		}
592 	}
593 
594 	tags = read_missing_tags (file, tags, tags_sel);
595 	tags_cache_add (c, file, key, tags);
596 
597 	return tags;
598 }
599 #endif
600 
601 /* Read the selected tags for this file and add it to the cache.
602  * If client_id != -1, the server is notified using tags_response().
603  * If client_id == -1, copy of file_tags is returned. */
tags_cache_read_add(struct tags_cache * c ATTR_UNUSED,const char * file,int tags_sel,int client_id)604 static struct file_tags *tags_cache_read_add (struct tags_cache *c ATTR_UNUSED,
605                      const char *file, int tags_sel, int client_id)
606 {
607 	struct file_tags *tags = NULL;
608 
609 	assert (file != NULL);
610 
611 	debug ("Getting tags for %s", file);
612 
613 #ifdef HAVE_DB_H
614 	if (c->max_items)
615 		tags = (struct file_tags *)with_db_lock (locked_read_add, c, file,
616 		                                         tags_sel, client_id);
617 	else
618 #endif
619 		tags = read_missing_tags (file, tags, tags_sel);
620 
621 	if (client_id != -1) {
622 		tags_response (client_id, file, tags);
623 		tags_free (tags);
624 		tags = NULL;
625 	}
626 
627 	/* TODO: Remove the oldest items from the cache if we exceeded the maximum
628 	 * cache size */
629 
630 	return tags;
631 }
632 
reader_thread(void * cache_ptr)633 static void *reader_thread (void *cache_ptr)
634 {
635 	struct tags_cache *c;
636 	int curr_queue = 0; /* index of the queue from where
637 	                       we will get the next request */
638 
639 	logit ("Tags reader thread started");
640 
641 	assert (cache_ptr != NULL);
642 
643 	c = (struct tags_cache *)cache_ptr;
644 
645 	LOCK (c->mutex);
646 
647 	while (!c->stop_reader_thread) {
648 		int i;
649 		char *request_file;
650 		int tags_sel = 0;
651 
652 		/* Find the queue with a request waiting.  Begin searching at
653 		 * curr_queue: we want to get one request from each queue,
654 		 * and then move to the next non-empty queue. */
655 		i = curr_queue;
656 		while (i < CLIENTS_MAX && request_queue_empty (&c->queues[i]))
657 			i++;
658 		if (i == CLIENTS_MAX) {
659 			i = 0;
660 			while (i < curr_queue && request_queue_empty (&c->queues[i]))
661 				i++;
662 
663 			if (i == curr_queue) {
664 				debug ("All queues empty, waiting");
665 				pthread_cond_wait (&c->request_cond, &c->mutex);
666 				continue;
667 			}
668 		}
669 		curr_queue = i;
670 
671 		request_file = request_queue_pop (&c->queues[curr_queue], &tags_sel);
672 		UNLOCK (c->mutex);
673 
674 		tags_cache_read_add (c, request_file, tags_sel, curr_queue);
675 		free (request_file);
676 
677 		LOCK (c->mutex);
678 		curr_queue = (curr_queue + 1) % CLIENTS_MAX;
679 	}
680 
681 	UNLOCK (c->mutex);
682 
683 	logit ("Exiting tags reader thread");
684 
685 	return NULL;
686 }
687 
tags_cache_init(struct tags_cache * c,size_t max_size)688 void tags_cache_init (struct tags_cache *c, size_t max_size)
689 {
690 	int i, rc;
691 
692 	assert (c != NULL);
693 
694 #ifdef HAVE_DB_H
695 	c->db_env = NULL;
696 	c->db = NULL;
697 #endif
698 
699 	for (i = 0; i < CLIENTS_MAX; i++)
700 		request_queue_init (&c->queues[i]);
701 
702 #if CACHE_DB_FORMAT_VERSION
703 	c->max_items = max_size;
704 #else
705 	c->max_items = 0;
706 #endif
707 	c->stop_reader_thread = 0;
708 	pthread_mutex_init (&c->mutex, NULL);
709 
710 	rc = pthread_cond_init (&c->request_cond, NULL);
711 	if (rc != 0)
712 		fatal ("Can't create request_cond: %s", strerror (rc));
713 
714 	rc = pthread_create (&c->reader_thread, NULL, reader_thread, c);
715 	if (rc != 0)
716 		fatal ("Can't create tags cache thread: %s", strerror (rc));
717 }
718 
tags_cache_destroy(struct tags_cache * c)719 void tags_cache_destroy (struct tags_cache *c)
720 {
721 	int i, rc;
722 
723 	assert (c != NULL);
724 
725 	LOCK (c->mutex);
726 	c->stop_reader_thread = 1;
727 	pthread_cond_signal (&c->request_cond);
728 	UNLOCK (c->mutex);
729 
730 #ifdef HAVE_DB_H
731 	if (c->db) {
732 		c->db->set_errcall (c->db, NULL);
733 		c->db->set_msgcall (c->db, NULL);
734 		c->db->set_paniccall (c->db, NULL);
735 		c->db->close (c->db, 0);
736 		c->db = NULL;
737 	}
738 #endif
739 
740 #ifdef HAVE_DB_H
741 	if (c->db_env) {
742 		c->db_env->lock_id_free (c->db_env, c->locker);
743 		c->db_env->set_errcall (c->db_env, NULL);
744 		c->db_env->set_msgcall (c->db_env, NULL);
745 		c->db_env->set_paniccall (c->db_env, NULL);
746 		c->db_env->close (c->db_env, 0);
747 		c->db_env = NULL;
748 	}
749 #endif
750 
751 	rc = pthread_join (c->reader_thread, NULL);
752 	if (rc != 0)
753 		fatal ("pthread_join() on cache reader thread failed: %s",
754 		        strerror (rc));
755 
756 	for (i = 0; i < CLIENTS_MAX; i++)
757 		request_queue_clear (&c->queues[i]);
758 
759 	rc = pthread_mutex_destroy (&c->mutex);
760 	if (rc != 0)
761 		logit ("Can't destroy mutex: %s", strerror (rc));
762 	rc = pthread_cond_destroy (&c->request_cond);
763 	if (rc != 0)
764 		logit ("Can't destroy request_cond: %s", strerror (rc));
765 }
766 
767 #ifdef HAVE_DB_H
locked_add_request(struct tags_cache * c,const char * file,int tags_sel,int client_id,DBT * key,DBT * serialized_cache_rec)768 static void *locked_add_request (struct tags_cache *c, const char *file,
769                                  int tags_sel, int client_id,
770                                  DBT *key, DBT *serialized_cache_rec)
771 {
772 	int db_ret;
773 	struct cache_record rec;
774 
775 	assert (c->db);
776 
777 	db_ret = c->db->get (c->db, NULL, key, serialized_cache_rec, 0);
778 
779 	if (db_ret == DB_NOTFOUND)
780 		return NULL;
781 
782 	if (db_ret) {
783 		error ("Cache DB search error: %s", db_strerror (db_ret));
784 		return NULL;
785 	}
786 
787 	if (cache_record_deserialize (&rec, serialized_cache_rec->data,
788 				serialized_cache_rec->size, 0)) {
789 		if (rec.mod_time == get_mtime (file)
790 				&& (rec.tags->filled & tags_sel) == tags_sel) {
791 			tags_response (client_id, file, rec.tags);
792 			tags_free (rec.tags);
793 			debug ("Tags are present in the cache");
794 			return (void *)1;
795 		}
796 
797 		tags_free (rec.tags);
798 		debug ("Found outdated or incomplete tags in the cache");
799 	}
800 
801 	return NULL;
802 }
803 #endif
804 
tags_cache_add_request(struct tags_cache * c,const char * file,int tags_sel,int client_id)805 void tags_cache_add_request (struct tags_cache *c, const char *file,
806                                         int tags_sel, int client_id)
807 {
808 	void *rc = NULL;
809 
810 	assert (c != NULL);
811 	assert (file != NULL);
812 	assert (LIMIT(client_id, CLIENTS_MAX));
813 
814 	debug ("Request for tags for '%s' from client %d", file, client_id);
815 
816 #ifdef HAVE_DB_H
817 	if (c->max_items)
818 		rc = with_db_lock (locked_add_request, c, file, tags_sel, client_id);
819 #endif
820 
821 	if (!rc) {
822 		LOCK (c->mutex);
823 		request_queue_add (&c->queues[client_id], file, tags_sel);
824 		pthread_cond_signal (&c->request_cond);
825 		UNLOCK (c->mutex);
826 	}
827 }
828 
tags_cache_clear_queue(struct tags_cache * c,int client_id)829 void tags_cache_clear_queue (struct tags_cache *c, int client_id)
830 {
831 	assert (c != NULL);
832 	assert (LIMIT(client_id, CLIENTS_MAX));
833 
834 	LOCK (c->mutex);
835 	request_queue_clear (&c->queues[client_id]);
836 	debug ("Cleared requests queue for client %d", client_id);
837 	UNLOCK (c->mutex);
838 }
839 
840 /* Remove all pending requests from the queue for the given client up to
841  * the request associated with the given file. */
tags_cache_clear_up_to(struct tags_cache * c,const char * file,int client_id)842 void tags_cache_clear_up_to (struct tags_cache *c, const char *file,
843                                                       int client_id)
844 {
845 	assert (c != NULL);
846 	assert (LIMIT(client_id, CLIENTS_MAX));
847 	assert (file != NULL);
848 
849 	LOCK (c->mutex);
850 	debug ("Removing requests for client %d up to file %s", client_id,
851 			file);
852 	request_queue_clear_up_to (&c->queues[client_id], file);
853 	UNLOCK (c->mutex);
854 }
855 
tags_cache_save(struct tags_cache * c ATTR_UNUSED,const char * cache_dir ATTR_UNUSED)856 void tags_cache_save (struct tags_cache *c ATTR_UNUSED,
857                       const char *cache_dir ATTR_UNUSED)
858 {
859 	//TODO: to remove
860 
861 	assert (c != NULL);
862 	assert (cache_dir != NULL);
863 }
864 
865 #ifdef HAVE_DB_H
db_err_cb(const DB_ENV * dbenv ATTR_UNUSED,const char * errpfx,const char * msg)866 static void db_err_cb (const DB_ENV *dbenv ATTR_UNUSED, const char *errpfx,
867                                                         const char *msg)
868 {
869 	assert (msg);
870 
871 	if (errpfx && errpfx[0])
872 		logit ("BDB said: %s: %s", errpfx, msg);
873 	else
874 		logit ("BDB said: %s", msg);
875 }
876 #endif
877 
878 #ifdef HAVE_DB_H
db_msg_cb(const DB_ENV * dbenv ATTR_UNUSED,const char * msg)879 static void db_msg_cb (const DB_ENV *dbenv ATTR_UNUSED, const char *msg)
880 {
881 	assert (msg);
882 
883 	logit ("BDB said: %s", msg);
884 }
885 #endif
886 
887 #ifdef HAVE_DB_H
db_panic_cb(DB_ENV * dbenv ATTR_UNUSED,int errval)888 static void db_panic_cb (DB_ENV *dbenv ATTR_UNUSED, int errval)
889 {
890 	logit ("BDB said: %s", db_strerror (errval));
891 }
892 #endif
893 
894 /* Purge content of a directory. */
895 #ifdef HAVE_DB_H
purge_directory(const char * dir_path)896 static int purge_directory (const char *dir_path)
897 {
898 	DIR *dir;
899 	struct dirent *d;
900 
901 	logit ("Purging %s...", dir_path);
902 
903 	dir = opendir (dir_path);
904 	if (!dir) {
905 		logit ("Can't open directory %s: %s", dir_path, strerror (errno));
906 		return 0;
907 	}
908 
909 	while ((d = readdir (dir))) {
910 		struct stat st;
911 		char *fpath;
912 		int len;
913 
914 		if (!strcmp (d->d_name, ".") || !strcmp (d->d_name, ".."))
915 			continue;
916 
917 		len = strlen (dir_path) + strlen (d->d_name) + 2;
918 		fpath = (char *)xmalloc (len);
919 		snprintf (fpath, len, "%s/%s", dir_path, d->d_name);
920 
921 		if (stat (fpath, &st) < 0) {
922 			logit ("Can't stat %s: %s", fpath, strerror (errno));
923 			free (fpath);
924 			closedir (dir);
925 			return 0;
926 		}
927 
928 		if (S_ISDIR(st.st_mode)) {
929 			if (!purge_directory (fpath)) {
930 				free (fpath);
931 				closedir (dir);
932 				return 0;
933 			}
934 
935 			logit ("Removing directory %s...", fpath);
936 			if (rmdir (fpath) < 0) {
937 				logit ("Can't remove %s: %s", fpath, strerror (errno));
938 				free (fpath);
939 				closedir (dir);
940 				return 0;
941 			}
942 		}
943 		else {
944 			logit ("Removing file %s...", fpath);
945 
946 			if (unlink (fpath) < 0) {
947 				logit ("Can't remove %s: %s", fpath, strerror (errno));
948 				free (fpath);
949 				closedir (dir);
950 				return 0;
951 			}
952 		}
953 
954 		free (fpath);
955 	}
956 
957 	closedir (dir);
958 	return 1;
959 }
960 #endif
961 
962 /* Remove the old Berkley DB backing files from the cache directory. */
963 #ifdef HAVE_DB_H
vacuum_old_db_files(const char * dir_path)964 static void vacuum_old_db_files (const char *dir_path)
965 {
966 	DIR *dir;
967 	struct dirent *d;
968 
969 	dir = opendir (dir_path);
970 	if (!dir) {
971 		logit ("Can't open directory %s: %s", dir_path, strerror (errno));
972 		return;
973 	}
974 
975 	while ((d = readdir (dir))) {
976 		if (!strncmp (d->d_name, "__db.", 5)) {
977 			char *fpath;
978 			int len;
979 
980 			len = strlen (dir_path) + strlen (d->d_name) + 2;
981 			fpath = (char *)xmalloc (len);
982 			snprintf (fpath, len, "%s/%s", dir_path, d->d_name);
983 
984 			logit ("Vacuuming file: %s", fpath);
985 
986 			if (unlink (fpath) < 0)
987 				logit ("Can't remove %s: %s", fpath, strerror (errno));
988 
989 			free (fpath);
990 		}
991 	}
992 
993 	closedir (dir);
994 }
995 #endif
996 
997 /* Create a MOC/db version string.
998  *
999  * @param buf Output buffer (at least VERSION_TAG_MAX chars long)
1000  */
1001 #ifdef HAVE_DB_H
create_version_tag(char * buf)1002 static const char *create_version_tag (char *buf)
1003 {
1004 	int db_major;
1005 	int db_minor;
1006 
1007 	db_version (&db_major, &db_minor, NULL);
1008 
1009 #ifdef PACKAGE_REVISION
1010 	snprintf (buf, VERSION_TAG_MAX, "%d %d %d r%s",
1011 	          CACHE_DB_FORMAT_VERSION, db_major, db_minor, PACKAGE_REVISION);
1012 #else
1013 	snprintf (buf, VERSION_TAG_MAX, "%d %d %d",
1014 	          CACHE_DB_FORMAT_VERSION, db_major, db_minor);
1015 #endif
1016 
1017 	return buf;
1018 }
1019 #endif
1020 
1021 /* Check version of the cache directory.  If it was created
1022  * using format not handled by this version of MOC, return 0. */
1023 #ifdef HAVE_DB_H
cache_version_matches(const char * cache_dir)1024 static int cache_version_matches (const char *cache_dir)
1025 {
1026 	char *fname = NULL;
1027 	char disk_version_tag[VERSION_TAG_MAX];
1028 	ssize_t rres;
1029 	FILE *f;
1030 	int compare_result = 0;
1031 
1032 	fname = (char *)xmalloc (strlen (cache_dir) + sizeof (MOC_VERSION_TAG) + 1);
1033 	sprintf (fname, "%s/%s", cache_dir, MOC_VERSION_TAG);
1034 
1035 	f = fopen (fname, "r");
1036 	if (!f) {
1037 		logit ("No %s in cache directory", MOC_VERSION_TAG);
1038 		free (fname);
1039 		return 0;
1040 	}
1041 
1042 	rres = fread (disk_version_tag, 1, sizeof (disk_version_tag) - 1, f);
1043 	if (rres == sizeof (disk_version_tag) - 1) {
1044 		logit ("On-disk version tag too long");
1045 	}
1046 	else {
1047 		char *ptr, cur_version_tag[VERSION_TAG_MAX];
1048 
1049 		disk_version_tag[rres] = '\0';
1050 		ptr = strrchr (disk_version_tag, '\n');
1051 		if (ptr)
1052 			*ptr = '\0';
1053 		ptr = strrchr (disk_version_tag, ' ');
1054 		if (ptr && ptr[1] == 'r')
1055 			*ptr = '\0';
1056 
1057 		create_version_tag (cur_version_tag);
1058 		ptr = strrchr (cur_version_tag, '\n');
1059 		if (ptr)
1060 			*ptr = '\0';
1061 		ptr = strrchr (cur_version_tag, ' ');
1062 		if (ptr && ptr[1] == 'r')
1063 			*ptr = '\0';
1064 
1065 		compare_result = !strcmp (disk_version_tag, cur_version_tag);
1066 	}
1067 
1068 	fclose (f);
1069 	free (fname);
1070 
1071 	return compare_result;
1072 }
1073 #endif
1074 
1075 #ifdef HAVE_DB_H
write_cache_version(const char * cache_dir)1076 static void write_cache_version (const char *cache_dir)
1077 {
1078 	char cur_version_tag[VERSION_TAG_MAX];
1079 	char *fname = NULL;
1080 	FILE *f;
1081 	int rc;
1082 
1083 	fname = (char *)xmalloc (strlen (cache_dir) + sizeof (MOC_VERSION_TAG) + 1);
1084 	sprintf (fname, "%s/%s", cache_dir, MOC_VERSION_TAG);
1085 
1086 	f = fopen (fname, "w");
1087 	if (!f) {
1088 		logit ("Error opening cache: %s", strerror (errno));
1089 		free (fname);
1090 		return;
1091 	}
1092 
1093 	create_version_tag (cur_version_tag);
1094 	rc = fwrite (cur_version_tag, strlen (cur_version_tag), 1, f);
1095 	if (rc != 1)
1096 		logit ("Error writing cache version tag: %d", rc);
1097 
1098 	free (fname);
1099 	fclose (f);
1100 }
1101 #endif
1102 
1103 /* Make sure that the cache directory exists and clear it if necessary. */
1104 #ifdef HAVE_DB_H
prepare_cache_dir(const char * cache_dir)1105 static int prepare_cache_dir (const char *cache_dir)
1106 {
1107 	if (mkdir (cache_dir, 0700) == 0) {
1108 		write_cache_version (cache_dir);
1109 		return 1;
1110 	}
1111 
1112 	if (errno != EEXIST) {
1113 		error ("Failed to create directory for tags cache: %s",
1114 				strerror (errno));
1115 		return 0;
1116 	}
1117 
1118 	if (!cache_version_matches (cache_dir)) {
1119 		logit ("Tags cache directory is the wrong version, purging....");
1120 
1121 		if (!purge_directory (cache_dir))
1122 			return 0;
1123 		write_cache_version (cache_dir);
1124 	}
1125 	else
1126 		vacuum_old_db_files (cache_dir);
1127 
1128 	return 1;
1129 }
1130 #endif
1131 
tags_cache_load(struct tags_cache * c,const char * cache_dir)1132 void tags_cache_load (struct tags_cache *c, const char *cache_dir)
1133 {
1134 	assert (c != NULL);
1135 	assert (cache_dir != NULL);
1136 
1137 #ifdef HAVE_DB_H
1138 	int ret;
1139 
1140 	if (!c->max_items)
1141 		return;
1142 
1143 	if (!prepare_cache_dir (cache_dir)) {
1144 		error ("Can't prepare cache directory!");
1145 		goto err;
1146 	}
1147 
1148 	ret = db_env_create (&c->db_env, 0);
1149 	if (ret) {
1150 		error ("Can't create DB environment: %s", db_strerror (ret));
1151 		goto err;
1152 	}
1153 
1154 	c->db_env->set_errcall (c->db_env, db_err_cb);
1155 	c->db_env->set_msgcall (c->db_env, db_msg_cb);
1156 	ret = c->db_env->set_paniccall (c->db_env, db_panic_cb);
1157 	if (ret)
1158 		logit ("Could not set DB panic callback");
1159 
1160 	ret = c->db_env->open (c->db_env, cache_dir,
1161 	                       DB_CREATE | DB_PRIVATE | DB_INIT_MPOOL |
1162 	                       DB_THREAD | DB_INIT_LOCK, 0);
1163 	if (ret) {
1164 		error ("Can't open DB environment (%s): %s",
1165 				cache_dir, db_strerror (ret));
1166 		goto err;
1167 	}
1168 
1169 	ret = c->db_env->lock_id (c->db_env, &c->locker);
1170 	if (ret) {
1171 		error ("Failed to get DB locker: %s", db_strerror (ret));
1172 		goto err;
1173 	}
1174 
1175 	ret = db_create (&c->db, c->db_env, 0);
1176 	if (ret) {
1177 		error ("Failed to create cache db: %s", db_strerror (ret));
1178 		goto err;
1179 	}
1180 
1181 	c->db->set_errcall (c->db, db_err_cb);
1182 	c->db->set_msgcall (c->db, db_msg_cb);
1183 	ret = c->db->set_paniccall (c->db, db_panic_cb);
1184 	if (ret)
1185 		logit ("Could not set DB panic callback");
1186 
1187 	ret = c->db->open (c->db, NULL, TAGS_DB, NULL, DB_BTREE,
1188 	                                DB_CREATE | DB_THREAD, 0);
1189 	if (ret) {
1190 		error ("Failed to open (or create) tags cache db: %s",
1191 		        db_strerror (ret));
1192 		goto err;
1193 	}
1194 
1195 	return;
1196 
1197 err:
1198 	if (c->db) {
1199 		c->db->set_errcall (c->db, NULL);
1200 		c->db->set_msgcall (c->db, NULL);
1201 		c->db->set_paniccall (c->db, NULL);
1202 		c->db->close (c->db, 0);
1203 		c->db = NULL;
1204 	}
1205 	if (c->db_env) {
1206 		c->db_env->set_errcall (c->db_env, NULL);
1207 		c->db_env->set_msgcall (c->db_env, NULL);
1208 		c->db_env->set_paniccall (c->db_env, NULL);
1209 		c->db_env->close (c->db_env, 0);
1210 		c->db_env = NULL;
1211 	}
1212 	c->max_items = 0;
1213 	error ("Failed to initialise tags cache: caching disabled");
1214 #endif
1215 }
1216 
1217 /* Immediately read tags for a file bypassing the request queue. */
tags_cache_get_immediate(struct tags_cache * c,const char * file,int tags_sel)1218 struct file_tags *tags_cache_get_immediate (struct tags_cache *c,
1219                                   const char *file, int tags_sel)
1220 {
1221 	struct file_tags *tags;
1222 
1223 	assert (c != NULL);
1224 	assert (file != NULL);
1225 
1226 	debug ("Immediate tags read for %s", file);
1227 
1228 	if (!is_url (file))
1229 		tags = tags_cache_read_add (c, file, tags_sel, -1);
1230 	else
1231 		tags = tags_new ();
1232 
1233 	return tags;
1234 }
1235