1 /*
2  * Copyright 2008-2013 Various Authors
3  * Copyright 2004 Timo Hirvonen
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "cache.h"
20 #include "misc.h"
21 #include "file.h"
22 #include "input.h"
23 #include "track_info.h"
24 #include "utils.h"
25 #include "xmalloc.h"
26 #include "xstrjoin.h"
27 #include "gbuf.h"
28 #include "options.h"
29 
30 #include <stdlib.h>
31 #include <stdio.h>
32 #include <stdint.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <stdbool.h>
36 #include <unistd.h>
37 #include <fcntl.h>
38 #include <errno.h>
39 #include <sys/mman.h>
40 
41 #define CACHE_VERSION   0x0d
42 
43 #define CACHE_64_BIT	0x01
44 #define CACHE_BE	0x02
45 
46 #define CACHE_RESERVED_PATTERN  	0xff
47 
48 #define CACHE_ENTRY_USED_SIZE		28
49 #define CACHE_ENTRY_RESERVED_SIZE	52
50 #define CACHE_ENTRY_TOTAL_SIZE	(CACHE_ENTRY_RESERVED_SIZE + CACHE_ENTRY_USED_SIZE)
51 
52 // Cmus Track Cache version X + 4 bytes flags
53 static char cache_header[8] = "CTC\0\0\0\0\0";
54 
55 // host byte order
56 // mtime is either 32 or 64 bits
57 struct cache_entry {
58 	// size of this struct including size itself
59 	uint32_t size;
60 
61 	int32_t play_count;
62 	int64_t mtime;
63 	int32_t duration;
64 	int32_t bitrate;
65 	int32_t bpm;
66 
67 	// when introducing new fields decrease the reserved space accordingly
68 	uint8_t _reserved[CACHE_ENTRY_RESERVED_SIZE];
69 
70 	// filename, codec, codec_profile and N * (key, val)
71 	char strings[];
72 };
73 
74 // make sure our mmap/sizeof-based code works
75 STATIC_ASSERT(CACHE_ENTRY_TOTAL_SIZE == sizeof(struct cache_entry));
76 STATIC_ASSERT(CACHE_ENTRY_TOTAL_SIZE == offsetof(struct cache_entry, strings));
77 
78 
79 #define ALIGN(size) (((size) + sizeof(long) - 1) & ~(sizeof(long) - 1))
80 #define HASH_SIZE 1023
81 
82 static struct track_info *hash_table[HASH_SIZE];
83 static char *cache_filename;
84 static int total;
85 
86 struct fifo_mutex cache_mutex = FIFO_MUTEX_INITIALIZER;
87 
88 
add_ti(struct track_info * ti,unsigned int hash)89 static void add_ti(struct track_info *ti, unsigned int hash)
90 {
91 	unsigned int pos = hash % HASH_SIZE;
92 	struct track_info *next = hash_table[pos];
93 
94 	ti->next = next;
95 	hash_table[pos] = ti;
96 	total++;
97 }
98 
valid_cache_entry(const struct cache_entry * e,unsigned int avail)99 static int valid_cache_entry(const struct cache_entry *e, unsigned int avail)
100 {
101 	unsigned int min_size = sizeof(*e);
102 	unsigned int str_size;
103 	int i, count;
104 
105 	if (avail < min_size)
106 		return 0;
107 
108 	if (e->size < min_size || e->size > avail)
109 		return 0;
110 
111 	str_size = e->size - min_size;
112 	count = 0;
113 	for (i = 0; i < str_size; i++) {
114 		if (!e->strings[i])
115 			count++;
116 	}
117 	if (count % 2 == 0)
118 		return 0;
119 	if (e->strings[str_size - 1])
120 		return 0;
121 	return 1;
122 }
123 
cache_entry_to_ti(struct cache_entry * e)124 static struct track_info *cache_entry_to_ti(struct cache_entry *e)
125 {
126 	const char *strings = e->strings;
127 	struct track_info *ti = track_info_new(strings);
128 	struct keyval *kv;
129 	int str_size = e->size - sizeof(*e);
130 	int pos, i, count;
131 
132 	ti->duration = e->duration;
133 	ti->bitrate = e->bitrate;
134 	ti->mtime = e->mtime;
135 	ti->play_count = e->play_count;
136 	ti->bpm = e->bpm;
137 
138 	// count strings (filename + codec + codec_profile + key/val pairs)
139 	count = 0;
140 	for (i = 0; i < str_size; i++) {
141 		if (!strings[i])
142 			count++;
143 	}
144 	count = (count - 3) / 2;
145 
146 	// NOTE: filename already copied by track_info_new()
147 	pos = strlen(strings) + 1;
148 	ti->codec = strings[pos] ? xstrdup(strings + pos) : NULL;
149 	pos += strlen(strings + pos) + 1;
150 	ti->codec_profile = strings[pos] ? xstrdup(strings + pos) : NULL;
151 	pos += strlen(strings + pos) + 1;
152 	kv = xnew(struct keyval, count + 1);
153 	for (i = 0; i < count; i++) {
154 		int size;
155 
156 		size = strlen(strings + pos) + 1;
157 		kv[i].key = xstrdup(strings + pos);
158 		pos += size;
159 
160 		size = strlen(strings + pos) + 1;
161 		kv[i].val = xstrdup(strings + pos);
162 		pos += size;
163 	}
164 	kv[i].key = NULL;
165 	kv[i].val = NULL;
166 	track_info_set_comments(ti, kv);
167 	return ti;
168 }
169 
lookup_cache_entry(const char * filename,unsigned int hash)170 struct track_info *lookup_cache_entry(const char *filename, unsigned int hash)
171 {
172 	struct track_info *ti = hash_table[hash % HASH_SIZE];
173 
174 	while (ti) {
175 		if (!strcmp(filename, ti->filename))
176 			return ti;
177 		ti = ti->next;
178 	}
179 	return NULL;
180 }
181 
do_cache_remove_ti(struct track_info * ti,unsigned int hash)182 static void do_cache_remove_ti(struct track_info *ti, unsigned int hash)
183 {
184 	unsigned int pos = hash % HASH_SIZE;
185 	struct track_info *t = hash_table[pos];
186 	struct track_info *next, *prev = NULL;
187 
188 	while (t) {
189 		next = t->next;
190 		if (t == ti) {
191 			if (prev) {
192 				prev->next = next;
193 			} else {
194 				hash_table[pos] = next;
195 			}
196 			total--;
197 			track_info_unref(ti);
198 			return;
199 		}
200 		prev = t;
201 		t = next;
202 	}
203 }
204 
cache_remove_ti(struct track_info * ti)205 void cache_remove_ti(struct track_info *ti)
206 {
207 	do_cache_remove_ti(ti, hash_str(ti->filename));
208 }
209 
read_cache(void)210 static int read_cache(void)
211 {
212 	unsigned int size, offset = 0;
213 	struct stat st = {};
214 	char *buf;
215 	int fd;
216 
217 	fd = open(cache_filename, O_RDONLY);
218 	if (fd < 0) {
219 		if (errno == ENOENT)
220 			return 0;
221 		return -1;
222 	}
223 	fstat(fd, &st);
224 	if (st.st_size < sizeof(cache_header))
225 		goto close;
226 	size = st.st_size;
227 
228 	buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
229 	if (buf == MAP_FAILED) {
230 		close(fd);
231 		return -1;
232 	}
233 
234 	if (memcmp(buf, cache_header, sizeof(cache_header)))
235 		goto corrupt;
236 
237 	offset = sizeof(cache_header);
238 	while (offset < size) {
239 		struct cache_entry *e = (void *)(buf + offset);
240 		struct track_info *ti;
241 
242 		if (!valid_cache_entry(e, size - offset))
243 			goto corrupt;
244 
245 		ti = cache_entry_to_ti(e);
246 		add_ti(ti, hash_str(ti->filename));
247 		offset += ALIGN(e->size);
248 	}
249 	munmap(buf, size);
250 	close(fd);
251 	return 0;
252 corrupt:
253 	munmap(buf, size);
254 close:
255 	close(fd);
256 	// corrupt
257 	return -2;
258 }
259 
cache_init(void)260 int cache_init(void)
261 {
262 	unsigned int flags = 0;
263 
264 #ifdef WORDS_BIGENDIAN
265 	flags |= CACHE_BE;
266 #endif
267 	if (sizeof(long) == 8)
268 		flags |= CACHE_64_BIT;
269 
270 	cache_header[7] = flags & 0xff; flags >>= 8;
271 	cache_header[6] = flags & 0xff; flags >>= 8;
272 	cache_header[5] = flags & 0xff; flags >>= 8;
273 	cache_header[4] = flags & 0xff;
274 
275 	/* assumed version */
276 	cache_header[3] = CACHE_VERSION;
277 
278 	cache_filename = xstrjoin(cmus_config_dir, "/cache");
279 	return read_cache();
280 }
281 
ti_filename_cmp(const void * a,const void * b)282 static int ti_filename_cmp(const void *a, const void *b)
283 {
284 	const struct track_info *ai = *(const struct track_info **)a;
285 	const struct track_info *bi = *(const struct track_info **)b;
286 
287 	return strcmp(ai->filename, bi->filename);
288 }
289 
get_track_infos(bool reference)290 static struct track_info **get_track_infos(bool reference)
291 {
292 	struct track_info **tis;
293 	int i, c;
294 
295 	tis = xnew(struct track_info *, total);
296 	c = 0;
297 	for (i = 0; i < HASH_SIZE; i++) {
298 		struct track_info *ti = hash_table[i];
299 
300 		while (ti) {
301 			if (reference)
302 				track_info_ref(ti);
303 			tis[c++] = ti;
304 			ti = ti->next;
305 		}
306 	}
307 	qsort(tis, total, sizeof(struct track_info *), ti_filename_cmp);
308 	return tis;
309 }
310 
flush_buffer(int fd,struct gbuf * buf)311 static void flush_buffer(int fd, struct gbuf *buf)
312 {
313 	if (buf->len) {
314 		write_all(fd, buf->buffer, buf->len);
315 		gbuf_clear(buf);
316 	}
317 }
318 
write_ti(int fd,struct gbuf * buf,struct track_info * ti,unsigned int * offsetp)319 static void write_ti(int fd, struct gbuf *buf, struct track_info *ti, unsigned int *offsetp)
320 {
321 	const struct keyval *kv = ti->comments;
322 	unsigned int offset = *offsetp;
323 	unsigned int pad;
324 	struct cache_entry e;
325 	int *len, alloc = 64, count, i;
326 
327 	memset(e._reserved, CACHE_RESERVED_PATTERN, sizeof(e._reserved));
328 
329 	count = 0;
330 	len = xnew(int, alloc);
331 	e.size = sizeof(e);
332 	e.duration = ti->duration;
333 	e.bitrate = ti->bitrate;
334 	e.mtime = ti->mtime;
335 	e.play_count = ti->play_count;
336 	e.bpm = ti->bpm;
337 	len[count] = strlen(ti->filename) + 1;
338 	e.size += len[count++];
339 	len[count] = (ti->codec ? strlen(ti->codec) : 0) + 1;
340 	e.size += len[count++];
341 	len[count] = (ti->codec_profile ? strlen(ti->codec_profile) : 0) + 1;
342 	e.size += len[count++];
343 	for (i = 0; kv[i].key; i++) {
344 		if (count + 2 > alloc) {
345 			alloc *= 2;
346 			len = xrenew(int, len, alloc);
347 		}
348 		len[count] = strlen(kv[i].key) + 1;
349 		e.size += len[count++];
350 		len[count] = strlen(kv[i].val) + 1;
351 		e.size += len[count++];
352 	}
353 
354 	pad = ALIGN(offset) - offset;
355 	if (gbuf_avail(buf) < pad + e.size)
356 		flush_buffer(fd, buf);
357 
358 	count = 0;
359 	if (pad)
360 		gbuf_set(buf, 0, pad);
361 	gbuf_add_bytes(buf, &e, sizeof(e));
362 	gbuf_add_bytes(buf, ti->filename, len[count++]);
363 	gbuf_add_bytes(buf, ti->codec ? ti->codec : "", len[count++]);
364 	gbuf_add_bytes(buf, ti->codec_profile ? ti->codec_profile : "", len[count++]);
365 	for (i = 0; kv[i].key; i++) {
366 		gbuf_add_bytes(buf, kv[i].key, len[count++]);
367 		gbuf_add_bytes(buf, kv[i].val, len[count++]);
368 	}
369 
370 	free(len);
371 	*offsetp = offset + pad + e.size;
372 }
373 
cache_close(void)374 int cache_close(void)
375 {
376 	GBUF(buf);
377 	struct track_info **tis;
378 	unsigned int offset;
379 	int i, fd, rc;
380 	char *tmp;
381 
382 	tmp = xstrjoin(cmus_config_dir, "/cache.tmp");
383 	fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0666);
384 	if (fd < 0) {
385 		free(tmp);
386 		return -1;
387 	}
388 
389 	tis = get_track_infos(false);
390 
391 	gbuf_grow(&buf, 64 * 1024 - 1);
392 	gbuf_add_bytes(&buf, cache_header, sizeof(cache_header));
393 	offset = sizeof(cache_header);
394 	for (i = 0; i < total; i++)
395 		write_ti(fd, &buf, tis[i], &offset);
396 	flush_buffer(fd, &buf);
397 	gbuf_free(&buf);
398 	free(tis);
399 
400 	close(fd);
401 	rc = rename(tmp, cache_filename);
402 	free(tmp);
403 	return rc;
404 }
405 
ip_get_ti(const char * filename)406 static struct track_info *ip_get_ti(const char *filename)
407 {
408 	struct track_info *ti = NULL;
409 	struct input_plugin *ip;
410 	struct keyval *comments;
411 	int rc;
412 
413 	ip = ip_new(filename);
414 	rc = ip_open(ip);
415 	if (rc) {
416 		ip_delete(ip);
417 		return NULL;
418 	}
419 
420 	rc = ip_read_comments(ip, &comments);
421 	if (!rc) {
422 		ti = track_info_new(filename);
423 		track_info_set_comments(ti, comments);
424 		ti->duration = ip_duration(ip);
425 		ti->bitrate = ip_bitrate(ip);
426 		ti->codec = ip_codec(ip);
427 		ti->codec_profile = ip_codec_profile(ip);
428 		ti->mtime = ip_is_remote(ip) ? -1 : file_get_mtime(filename);
429 	}
430 	ip_delete(ip);
431 	return ti;
432 }
433 
cache_get_ti(const char * filename,int force)434 struct track_info *cache_get_ti(const char *filename, int force)
435 {
436 	unsigned int hash = hash_str(filename);
437 	struct track_info *ti;
438 	int reload = 0;
439 
440 	ti = lookup_cache_entry(filename, hash);
441 	if (ti) {
442 		if ((!skip_track_info && ti->duration == 0 && !is_http_url(filename)) || force){
443 			do_cache_remove_ti(ti, hash);
444 			ti = NULL;
445 			reload = 1;
446 		}
447 	}
448 	if (!ti) {
449 		if (skip_track_info && !reload && !force) {
450 			struct growing_keyvals c = {NULL, 0, 0};
451 
452 			ti = track_info_new(filename);
453 
454 			keyvals_terminate(&c);
455 			track_info_set_comments(ti, c.keyvals);
456 
457 			ti->duration = 0;
458 		} else {
459 		       	ti = ip_get_ti(filename);
460 		}
461 		if (!ti)
462 			return NULL;
463 		add_ti(ti, hash);
464 	}
465 	track_info_ref(ti);
466 	return ti;
467 }
468 
cache_refresh(int * count,int force)469 struct track_info **cache_refresh(int *count, int force)
470 {
471 	struct track_info **tis = get_track_infos(true);
472 	int i, n = total;
473 
474 	for (i = 0; i < n; i++) {
475 		unsigned int hash;
476 		struct track_info *ti = tis[i];
477 		struct stat st;
478 		int rc = 0;
479 
480 		cache_yield();
481 
482 		/*
483 		 * If no-one else has reference to tis[i] then it is set to NULL
484 		 * otherwise:
485 		 *
486 		 * unchanged: tis[i] = NULL
487 		 * deleted:   tis[i]->next = NULL
488 		 * changed:   tis[i]->next = new
489 		 */
490 
491 		if (!is_url(ti->filename)) {
492 			rc = stat(ti->filename, &st);
493 			if (!rc && !force && ti->mtime == st.st_mtime) {
494 				// unchanged
495 				track_info_unref(ti);
496 				tis[i] = NULL;
497 				continue;
498 			}
499 		}
500 
501 		hash = hash_str(ti->filename);
502 		do_cache_remove_ti(ti, hash);
503 
504 		if (!rc) {
505 			// changed
506 			struct track_info *new_ti;
507 
508 			// clear cache-only entries
509 			if (force && track_info_unique_ref(ti)) {
510 				track_info_unref(ti);
511 				tis[i] = NULL;
512 				continue;
513 			}
514 
515 			new_ti = ip_get_ti(ti->filename);
516 			if (new_ti) {
517 				add_ti(new_ti, hash);
518 
519 				if (track_info_unique_ref(ti)) {
520 					track_info_unref(ti);
521 					tis[i] = NULL;
522 				} else {
523 					track_info_ref(new_ti);
524 					ti->next = new_ti;
525 				}
526 				continue;
527 			}
528 			// treat as deleted
529 		}
530 
531 		// deleted
532 		if (track_info_unique_ref(ti)) {
533 			track_info_unref(ti);
534 			tis[i] = NULL;
535 		} else {
536 			ti->next = NULL;
537 		}
538 	}
539 	*count = n;
540 	return tis;
541 }
542