1 /*
2  * Copyright 2008-2013 Various Authors
3  * Copyright 2008 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 "utils.h"
20 #include "job.h"
21 #include "worker.h"
22 #include "cache.h"
23 #include "xmalloc.h"
24 #include "debug.h"
25 #include "load_dir.h"
26 #include "path.h"
27 #include "editable.h"
28 #include "pl.h"
29 #include "play_queue.h"
30 #include "lib.h"
31 #include "utils.h"
32 #include "file.h"
33 #include "cache.h"
34 #include "player.h"
35 #include "discid.h"
36 #include "xstrjoin.h"
37 #include "ui_curses.h"
38 #include "cue_utils.h"
39 
40 #include <string.h>
41 #include <unistd.h>
42 #include <errno.h>
43 #include <sys/mman.h>
44 
45 enum job_result_var {
46 	JOB_RES_ADD,
47 	JOB_RES_UPDATE,
48 	JOB_RES_UPDATE_CACHE,
49 	JOB_RES_PL_DELETE,
50 };
51 
52 enum update_kind {
53 	UPDATE_NONE = 0,
54 	UPDATE_REMOVE = 1,
55 	UPDATE_MTIME_CHANGED = 2,
56 };
57 
58 struct job_result {
59 	struct list_head node;
60 
61 	enum job_result_var var;
62 	union {
63 		struct {
64 			add_ti_cb add_cb;
65 			size_t add_num;
66 			struct track_info **add_ti;
67 			void *add_opaque;
68 		};
69 		struct {
70 			size_t update_num;
71 			struct track_info **update_ti;
72 			enum update_kind *update_kind;
73 		};
74 		struct {
75 			size_t update_cache_num;
76 			struct track_info **update_cache_ti;
77 		};
78 		struct {
79 			void (*pl_delete_cb)(struct playlist *);
80 			struct playlist *pl_delete_pl;
81 		};
82 	};
83 };
84 
85 int job_fd;
86 static int job_fd_priv;
87 
88 static LIST_HEAD(job_result_head);
89 static pthread_mutex_t job_mutex = CMUS_MUTEX_INITIALIZER;
90 
91 #define TI_CAP 32
92 static struct track_info **ti_buffer;
93 static size_t ti_buffer_fill;
94 static struct add_data *jd;
95 
96 #define job_lock() cmus_mutex_lock(&job_mutex)
97 #define job_unlock() cmus_mutex_unlock(&job_mutex)
98 
job_init(void)99 void job_init(void)
100 {
101 	init_pipes(&job_fd, &job_fd_priv);
102 
103 	worker_init();
104 }
105 
job_exit(void)106 void job_exit(void)
107 {
108 	worker_remove_jobs_by_type(JOB_TYPE_ANY);
109 	worker_exit();
110 
111 	close(job_fd);
112 	close(job_fd_priv);
113 }
114 
job_push_result(struct job_result * res)115 static void job_push_result(struct job_result *res)
116 {
117 	job_lock();
118 	list_add_tail(&res->node, &job_result_head);
119 	job_unlock();
120 
121 	notify_via_pipe(job_fd_priv);
122 }
123 
job_pop_result(void)124 static struct job_result *job_pop_result(void)
125 {
126 	struct job_result *res = NULL;
127 
128 	job_lock();
129 	if (!list_empty(&job_result_head)) {
130 		struct list_head *item = job_result_head.next;
131 		list_del(item);
132 		res = container_of(item, struct job_result, node);
133 	}
134 	job_unlock();
135 
136 	return res;
137 }
138 
flush_ti_buffer(void)139 static void flush_ti_buffer(void)
140 {
141 	struct job_result *res = xnew(struct job_result, 1);
142 
143 	res->var = JOB_RES_ADD;
144 	res->add_cb = jd->add;
145 	res->add_num = ti_buffer_fill;
146 	res->add_ti = ti_buffer;
147 	res->add_opaque = jd->opaque;
148 
149 	job_push_result(res);
150 
151 	ti_buffer_fill = 0;
152 	ti_buffer = NULL;
153 }
154 
add_ti(struct track_info * ti)155 static void add_ti(struct track_info *ti)
156 {
157 	if (ti_buffer_fill == TI_CAP)
158 		flush_ti_buffer();
159 	if (!ti_buffer)
160 		ti_buffer = xnew(struct track_info *, TI_CAP);
161 	ti_buffer[ti_buffer_fill++] = ti;
162 }
163 
164 static int add_file_cue(const char *filename);
165 
add_file(const char * filename,int force)166 static void add_file(const char *filename, int force)
167 {
168 	struct track_info *ti;
169 
170 	if (!is_cue_url(filename)) {
171 		if (force || lookup_cache_entry(filename, hash_str(filename)) == NULL) {
172 			int done = add_file_cue(filename);
173 			if (done)
174 				return;
175 		}
176 	}
177 
178 	cache_lock();
179 	ti = cache_get_ti(filename, force);
180 	cache_unlock();
181 
182 	if (ti)
183 		add_ti(ti);
184 }
185 
add_file_cue(const char * filename)186 static int add_file_cue(const char *filename)
187 {
188 	int n_tracks;
189 	char *url;
190 	char *cue_filename;
191 
192 	cue_filename = associated_cue(filename);
193 	if (cue_filename == NULL)
194 		return 0;
195 
196 	n_tracks = cue_get_ntracks(cue_filename);
197 	if (n_tracks <= 0) {
198 		free(cue_filename);
199 		return 0;
200 	}
201 
202 	for (int i = 1; i <= n_tracks; ++i) {
203 		url = construct_cue_url(cue_filename, i);
204 		add_file(url, 0);
205 		free(url);
206 	}
207 
208 	free(cue_filename);
209 	return 1;
210 }
211 
add_url(const char * url)212 static void add_url(const char *url)
213 {
214 	add_file(url, 0);
215 }
216 
add_cdda(const char * url)217 static void add_cdda(const char *url)
218 {
219 	char *disc_id = NULL;
220 	int start_track = 1, end_track = -1;
221 
222 	parse_cdda_url(url, &disc_id, &start_track, &end_track);
223 
224 	if (end_track != -1) {
225 		int i;
226 		for (i = start_track; i <= end_track; i++) {
227 			char *new_url = gen_cdda_url(disc_id, i, -1);
228 			add_file(new_url, 0);
229 			free(new_url);
230 		}
231 	} else
232 		add_file(url, 0);
233 	free(disc_id);
234 }
235 
dir_entry_cmp(const void * ap,const void * bp)236 static int dir_entry_cmp(const void *ap, const void *bp)
237 {
238 	struct dir_entry *a = *(struct dir_entry **)ap;
239 	struct dir_entry *b = *(struct dir_entry **)bp;
240 
241 	return strcmp(a->name, b->name);
242 }
243 
dir_entry_cmp_reverse(const void * ap,const void * bp)244 static int dir_entry_cmp_reverse(const void *ap, const void *bp)
245 {
246 	struct dir_entry *a = *(struct dir_entry **)ap;
247 	struct dir_entry *b = *(struct dir_entry **)bp;
248 
249 	return strcmp(b->name, a->name);
250 }
251 
points_within_and_visible(const char * target,const char * root)252 static int points_within_and_visible(const char *target, const char *root)
253 {
254 	int tlen = strlen(target);
255 	int rlen = strlen(root);
256 
257 	if (rlen > tlen)
258 		return 0;
259 	if (strncmp(target, root, rlen))
260 		return 0;
261 	if (target[rlen] != '/' && target[rlen] != '\0')
262 		return 0;
263 	/* assume the path is normalized */
264 	if (strstr(target + rlen, "/."))
265 		return 0;
266 
267 	return 1;
268 }
269 
add_dir(const char * dirname,const char * root)270 static void add_dir(const char *dirname, const char *root)
271 {
272 	struct directory dir;
273 	struct dir_entry **ents;
274 	const char *name;
275 	PTR_ARRAY(array);
276 	int i;
277 
278 	if (dir_open(&dir, dirname)) {
279 		d_print("error: opening %s: %s\n", dirname, strerror(errno));
280 		return;
281 	}
282 	while ((name = dir_read(&dir))) {
283 		struct dir_entry *ent;
284 		int size;
285 
286 		if (name[0] == '.')
287 			continue;
288 
289 		if (dir.is_link) {
290 			char buf[1024];
291 			char *target;
292 			int rc = readlink(dir.path, buf, sizeof(buf));
293 
294 			if (rc < 0 || rc == sizeof(buf))
295 				continue;
296 			buf[rc] = 0;
297 			target = path_absolute_cwd(buf, dirname);
298 			if (points_within_and_visible(target, root)) {
299 				d_print("%s -> %s points within %s. ignoring\n",
300 						dir.path, target, root);
301 				free(target);
302 				continue;
303 			}
304 			free(target);
305 		}
306 
307 		size = strlen(name) + 1;
308 		ent = xmalloc(sizeof(struct dir_entry) + size);
309 		ent->mode = dir.st.st_mode;
310 		memcpy(ent->name, name, size);
311 		ptr_array_add(&array, ent);
312 	}
313 	dir_close(&dir);
314 
315 	if (jd->add == play_queue_prepend) {
316 		ptr_array_sort(&array, dir_entry_cmp_reverse);
317 	} else {
318 		ptr_array_sort(&array, dir_entry_cmp);
319 	}
320 	ents = array.ptrs;
321 	for (i = 0; i < array.count; i++) {
322 		if (!worker_cancelling()) {
323 			/* abuse dir.path because
324 			 *  - it already contains dirname + '/'
325 			 *  - it is guaranteed to be large enough
326 			 */
327 			int len = strlen(ents[i]->name);
328 
329 			memcpy(dir.path + dir.len, ents[i]->name, len + 1);
330 			if (S_ISDIR(ents[i]->mode)) {
331 				add_dir(dir.path, root);
332 			} else {
333 				add_file(dir.path, 0);
334 			}
335 		}
336 		free(ents[i]);
337 	}
338 	free(ents);
339 }
340 
handle_line(void * data,const char * line)341 static int handle_line(void *data, const char *line)
342 {
343 	if (worker_cancelling())
344 		return 1;
345 
346 	if (is_http_url(line) || is_cue_url(line)) {
347 		add_url(line);
348 	} else {
349 		char *absolute = path_absolute_cwd(line, data);
350 		add_file(absolute, 0);
351 		free(absolute);
352 	}
353 
354 	return 0;
355 }
356 
add_pl(const char * filename)357 static void add_pl(const char *filename)
358 {
359 	char *buf;
360 	ssize_t size;
361 	int reverse;
362 
363 	buf = mmap_file(filename, &size);
364 	if (size == -1)
365 		return;
366 
367 	if (buf) {
368 		char *cwd = xstrjoin(filename, "/..");
369 		/* beautiful hack */
370 		reverse = jd->add == play_queue_prepend;
371 
372 		cmus_playlist_for_each(buf, size, reverse, handle_line, cwd);
373 		free(cwd);
374 		munmap(buf, size);
375 	}
376 }
377 
do_add_job(void * data)378 static void do_add_job(void *data)
379 {
380 	jd = data;
381 	switch (jd->type) {
382 	case FILE_TYPE_URL:
383 		add_url(jd->name);
384 		break;
385 	case FILE_TYPE_CDDA:
386 		add_cdda(jd->name);
387 		break;
388 	case FILE_TYPE_PL:
389 		add_pl(jd->name);
390 		break;
391 	case FILE_TYPE_DIR:
392 		add_dir(jd->name, jd->name);
393 		break;
394 	case FILE_TYPE_FILE:
395 		add_file(jd->name, jd->force);
396 		break;
397 	case FILE_TYPE_INVALID:
398 		break;
399 	}
400 	if (ti_buffer)
401 		flush_ti_buffer();
402 	jd = NULL;
403 }
404 
free_add_job(void * data)405 static void free_add_job(void *data)
406 {
407 	struct add_data *d = data;
408 	free(d->name);
409 	free(d);
410 }
411 
job_handle_add_result(struct job_result * res)412 static void job_handle_add_result(struct job_result *res)
413 {
414 	for (size_t i = 0; i < res->add_num; i++) {
415 		res->add_cb(res->add_ti[i], res->add_opaque);
416 		track_info_unref(res->add_ti[i]);
417 	}
418 
419 	free(res->add_ti);
420 }
421 
job_schedule_add(int type,struct add_data * data)422 void job_schedule_add(int type, struct add_data *data)
423 {
424 	worker_add_job(type | JOB_TYPE_ADD, do_add_job, free_add_job, data);
425 }
426 
do_update_job(void * data)427 static void do_update_job(void *data)
428 {
429 	struct update_data *d = data;
430 	int i;
431 	enum update_kind *kind = xnew(enum update_kind, d->used);
432 	struct job_result *res;
433 
434 	for (i = 0; i < d->used; i++) {
435 		struct track_info *ti = d->ti[i];
436 		struct stat s;
437 		int rc;
438 
439 		rc = stat(ti->filename, &s);
440 		if (rc || d->force || ti->mtime != s.st_mtime || ti->duration == 0) {
441 			kind[i] = UPDATE_NONE;
442 			if (!is_cue_url(ti->filename) && !is_http_url(ti->filename) && rc)
443 				kind[i] |= UPDATE_REMOVE;
444 			else if (ti->mtime != s.st_mtime)
445 				kind[i] |= UPDATE_MTIME_CHANGED;
446 		} else {
447 			track_info_unref(ti);
448 			d->ti[i] = NULL;
449 		}
450 	}
451 
452 	res = xnew(struct job_result, 1);
453 
454 	res->var = JOB_RES_UPDATE;
455 	res->update_num = d->used;
456 	res->update_ti = d->ti;
457 	res->update_kind = kind;
458 
459 	job_push_result(res);
460 
461 	d->ti = NULL;
462 }
463 
free_update_job(void * data)464 static void free_update_job(void *data)
465 {
466 	struct update_data *d = data;
467 
468 	if (d->ti) {
469 		for (size_t i = 0; i < d->used; i++)
470 			track_info_unref(d->ti[i]);
471 		free(d->ti);
472 	}
473 	free(d);
474 }
475 
job_handle_update_result(struct job_result * res)476 static void job_handle_update_result(struct job_result *res)
477 {
478 	for (size_t i = 0; i < res->update_num; i++) {
479 		struct track_info *ti = res->update_ti[i];
480 		int force;
481 
482 		if (!ti)
483 			continue;
484 
485 		lib_remove(ti);
486 
487 		cache_lock();
488 		cache_remove_ti(ti);
489 		cache_unlock();
490 
491 		if (res->update_kind[i] & UPDATE_REMOVE) {
492 			d_print("removing dead file %s\n", ti->filename);
493 		} else {
494 			if (res->update_kind[i] & UPDATE_MTIME_CHANGED)
495 				d_print("mtime changed: %s\n", ti->filename);
496 			force = ti->duration == 0;
497 			cmus_add(lib_add_track, ti->filename, FILE_TYPE_FILE,
498 					JOB_TYPE_LIB, force, NULL);
499 		}
500 
501 		track_info_unref(ti);
502 	}
503 
504 	free(res->update_kind);
505 	free(res->update_ti);
506 }
507 
job_schedule_update(struct update_data * data)508 void job_schedule_update(struct update_data *data)
509 {
510 	worker_add_job(JOB_TYPE_LIB | JOB_TYPE_UPDATE, do_update_job,
511 			free_update_job, data);
512 }
513 
do_update_cache_job(void * data)514 static void do_update_cache_job(void *data)
515 {
516 	struct update_cache_data *d = data;
517 	int count;
518 	struct track_info **tis;
519 	struct job_result *res;
520 
521 	cache_lock();
522 	tis = cache_refresh(&count, d->force);
523 	cache_unlock();
524 
525 	res = xnew(struct job_result, 1);
526 	res->var = JOB_RES_UPDATE_CACHE;
527 	res->update_cache_ti = tis;
528 	res->update_cache_num = count;
529 	job_push_result(res);
530 }
531 
free_update_cache_job(void * data)532 static void free_update_cache_job(void *data)
533 {
534 	free(data);
535 }
536 
job_handle_update_cache_result(struct job_result * res)537 static void job_handle_update_cache_result(struct job_result *res)
538 {
539 	for (size_t i = 0; i < res->update_cache_num; i++) {
540 		struct track_info *new, *old = res->update_cache_ti[i];
541 
542 		if (!old)
543 			continue;
544 
545 		new = old->next;
546 		if (lib_remove(old) && new)
547 			lib_add_track(new, NULL);
548 		pl_update_track(old, new);
549 		editable_update_track(&pq_editable, old, new);
550 		if (player_info.ti == old && new) {
551 			track_info_ref(new);
552 			player_file_changed(new);
553 		}
554 
555 		track_info_unref(old);
556 		if (new)
557 			track_info_unref(new);
558 	}
559 	free(res->update_cache_ti);
560 }
561 
job_schedule_update_cache(int type,struct update_cache_data * data)562 void job_schedule_update_cache(int type, struct update_cache_data *data)
563 {
564 	worker_add_job(type | JOB_TYPE_UPDATE_CACHE, do_update_cache_job,
565 			free_update_cache_job, data);
566 }
567 
do_pl_delete_job(void * data)568 static void do_pl_delete_job(void *data)
569 {
570 	/*
571 	 * If PL jobs are canceled this function won't run. Hence we push the
572 	 * result in the free function.
573 	 */
574 }
575 
free_pl_delete_job(void * data)576 static void free_pl_delete_job(void *data)
577 {
578 	struct pl_delete_data *pdd = data;
579 	struct job_result *res;
580 
581 	res = xnew(struct job_result, 1);
582 	res->var = JOB_RES_PL_DELETE;
583 	res->pl_delete_cb = pdd->cb;
584 	res->pl_delete_pl = pdd->pl;
585 	job_push_result(res);
586 
587 	free(pdd);
588 }
589 
job_handle_pl_delete_result(struct job_result * res)590 static void job_handle_pl_delete_result(struct job_result *res)
591 {
592 	res->pl_delete_cb(res->pl_delete_pl);
593 }
594 
job_schedule_pl_delete(struct pl_delete_data * data)595 void job_schedule_pl_delete(struct pl_delete_data *data)
596 {
597 	worker_add_job(JOB_TYPE_PL | JOB_TYPE_DELETE, do_pl_delete_job,
598 			free_pl_delete_job, data);
599 }
600 
job_handle_result(struct job_result * res)601 static void job_handle_result(struct job_result *res)
602 {
603 	switch (res->var) {
604 	case JOB_RES_ADD:
605 		job_handle_add_result(res);
606 		break;
607 	case JOB_RES_UPDATE:
608 		job_handle_update_result(res);
609 		break;
610 	case JOB_RES_UPDATE_CACHE:
611 		job_handle_update_cache_result(res);
612 		break;
613 	case JOB_RES_PL_DELETE:
614 		job_handle_pl_delete_result(res);
615 		break;
616 	}
617 	free(res);
618 }
619 
job_handle(void)620 void job_handle(void)
621 {
622 	clear_pipe(job_fd, -1);
623 
624 	struct job_result *res;
625 	while ((res = job_pop_result()))
626 		job_handle_result(res);
627 }
628