1 /*
2 * MOC - music on console
3 * Copyright (C) 2004 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 <stdio.h>
17 #include <assert.h>
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <unistd.h>
21 #include <string.h>
22 #include <errno.h>
23 #include <stdlib.h>
24
25 #ifdef HAVE_LIBMAGIC
26 #include <magic.h>
27 #include <pthread.h>
28 #endif
29
30 /* Include dirent for various systems */
31 #ifdef HAVE_DIRENT_H
32 # include <dirent.h>
33 #else
34 # define dirent direct
35 # if HAVE_SYS_NDIR_H
36 # include <sys/ndir.h>
37 # endif
38 #endif
39
40 #define DEBUG
41
42 #include "common.h"
43 #include "playlist.h"
44 #include "lists.h"
45 #include "interface.h"
46 #include "decoder.h"
47 #include "options.h"
48 #include "files.h"
49 #include "playlist_file.h"
50 #include "log.h"
51 #include "utf8.h"
52
53 #define READ_LINE_INIT_SIZE 256
54
55 #ifdef HAVE_LIBMAGIC
56 static magic_t cookie = NULL;
57 static char *cached_file = NULL;
58 static char *cached_result = NULL;
59 #endif
60
files_init()61 void files_init ()
62 {
63 #ifdef HAVE_LIBMAGIC
64 assert (cookie == NULL);
65
66 cookie = magic_open (MAGIC_SYMLINK | MAGIC_MIME | MAGIC_ERROR |
67 MAGIC_NO_CHECK_COMPRESS | MAGIC_NO_CHECK_ELF |
68 MAGIC_NO_CHECK_TAR | MAGIC_NO_CHECK_TOKENS |
69 MAGIC_NO_CHECK_FORTRAN | MAGIC_NO_CHECK_TROFF);
70 if (cookie == NULL)
71 logit ("Error allocating magic cookie: %s", strerror (errno));
72 else if (magic_load (cookie, NULL) != 0) {
73 logit ("Error loading magic database: %s", magic_error (cookie));
74 magic_close (cookie);
75 cookie = NULL;
76 }
77 #endif
78 }
79
files_cleanup()80 void files_cleanup ()
81 {
82 #ifdef HAVE_LIBMAGIC
83 free (cached_file);
84 cached_file = NULL;
85 free (cached_result);
86 cached_result = NULL;
87 magic_close (cookie);
88 cookie = NULL;
89 #endif
90 }
91
92 /* Is the string a URL? */
is_url(const char * str)93 inline int is_url (const char *str)
94 {
95 return !strncasecmp (str, "http://", sizeof ("http://") - 1)
96 || !strncasecmp (str, "ftp://", sizeof ("ftp://") - 1);
97 }
98
99 /* Return 1 if the file is a directory, 0 if not, -1 on error. */
is_dir(const char * file)100 int is_dir (const char *file)
101 {
102 struct stat file_stat;
103
104 if (is_url (file))
105 return 0;
106
107 if (stat(file, &file_stat) == -1) {
108 error ("Can't stat %s: %s", file, strerror(errno));
109 return -1;
110 }
111 return S_ISDIR(file_stat.st_mode) ? 1 : 0;
112 }
113
114 /* Return 1 if the file can be read by this user, 0 if not */
can_read_file(const char * file)115 int can_read_file (const char *file)
116 {
117 return access(file, R_OK) == 0;
118 }
119
file_type(const char * file)120 enum file_type file_type (const char *file)
121 {
122 struct stat file_stat;
123
124 assert (file != NULL);
125
126 if (is_url(file))
127 return F_URL;
128 if (stat(file, &file_stat) == -1)
129 return F_OTHER; /* Ignore the file if stat() failed */
130 if (S_ISDIR(file_stat.st_mode))
131 return F_DIR;
132 if (is_sound_file(file))
133 return F_SOUND;
134 if (is_plist_file(file))
135 return F_PLAYLIST;
136 return F_OTHER;
137 }
138
139 /* Given a file name, return the mime type or NULL. */
file_mime_type(const char * file ATTR_UNUSED)140 char *file_mime_type (const char *file ATTR_UNUSED)
141 {
142 char *result = NULL;
143
144 assert (file != NULL);
145
146 #ifdef HAVE_LIBMAGIC
147 static pthread_mutex_t magic_mutex = PTHREAD_MUTEX_INITIALIZER;
148
149 if (cookie != NULL) {
150 LOCK(magic_mutex);
151 if (cached_file && !strcmp (cached_file, file))
152 result = xstrdup (cached_result);
153 else {
154 free (cached_file);
155 free (cached_result);
156 cached_file = cached_result = NULL;
157 result = xstrdup (magic_file (cookie, file));
158 if (result == NULL)
159 logit ("Error interrogating file: %s", magic_error (cookie));
160 else {
161 cached_file = xstrdup (file);
162 cached_result = xstrdup (result);
163 }
164 }
165 UNLOCK(magic_mutex);
166 }
167 #endif
168
169 return result;
170 }
171
172 /* Make a title from the file name for the item. If hide_extn != 0,
173 * strip the file name from extension. */
make_file_title(struct plist * plist,const int num,const int hide_extension)174 void make_file_title (struct plist *plist, const int num,
175 const int hide_extension)
176 {
177 assert (plist != NULL);
178 assert (LIMIT(num, plist->num));
179 assert (!plist_deleted (plist, num));
180
181 if (file_type (plist->items[num].file) != F_URL) {
182 char *file = xstrdup (plist->items[num].file);
183
184 if (hide_extension) {
185 char *extn;
186
187 extn = ext_pos (file);
188 if (extn)
189 *(extn - 1) = 0;
190 }
191
192 if (options_get_int ("FileNamesIconv"))
193 {
194 char *old_title = file;
195 file = files_iconv_str (file);
196 free (old_title);
197 }
198
199 plist_set_title_file (plist, num, file);
200 free (file);
201 }
202 else
203 plist_set_title_file (plist, num, plist->items[num].file);
204 }
205
206 /* Make a title from the tags for the item. */
make_tags_title(struct plist * plist,const int num)207 void make_tags_title (struct plist *plist, const int num)
208 {
209 int hide_extn;
210 char *title;
211
212 assert (plist != NULL);
213 assert (LIMIT(num, plist->num));
214 assert (!plist_deleted (plist, num));
215
216 if (file_type (plist->items[num].file) == F_URL) {
217 make_file_title (plist, num, 0);
218 return;
219 }
220
221 if (plist->items[num].title_tags)
222 return;
223
224 assert (plist->items[num].file != NULL);
225
226 if (plist->items[num].tags->title) {
227 title = build_title (plist->items[num].tags);
228 plist_set_title_tags (plist, num, title);
229 free (title);
230 return;
231 }
232
233 hide_extn = options_get_int ("HideFileExtension");
234 make_file_title (plist, num, hide_extn);
235 }
236
237 /* Switch playlist titles to title_file */
switch_titles_file(struct plist * plist)238 void switch_titles_file (struct plist *plist)
239 {
240 int i, hide_extn;
241
242 hide_extn = options_get_int ("HideFileExtension");
243
244 for (i = 0; i < plist->num; i++) {
245 if (plist_deleted (plist, i))
246 continue;
247
248 if (!plist->items[i].title_file)
249 make_file_title (plist, i, hide_extn);
250
251 assert (plist->items[i].title_file != NULL);
252 }
253 }
254
255 /* Switch playlist titles to title_tags */
switch_titles_tags(struct plist * plist)256 void switch_titles_tags (struct plist *plist)
257 {
258 int i, hide_extn;
259
260 hide_extn = options_get_int ("HideFileExtension");
261
262 for (i = 0; i < plist->num; i++) {
263 if (plist_deleted (plist, i))
264 continue;
265
266 if (!plist->items[i].title_tags && !plist->items[i].title_file)
267 make_file_title (plist, i, hide_extn);
268 }
269 }
270
271 /* Add file to the directory path in buf resolving '../' and removing './'. */
272 /* buf must be absolute path. */
resolve_path(char * buf,const int size,const char * file)273 void resolve_path (char *buf, const int size, const char *file)
274 {
275 char *f; /* points to the char in *file we process */
276 char path[2*PATH_MAX]; /* temporary path */
277 int len = 0; /* number of characters in the buffer */
278
279 assert (buf[0] == '/');
280
281 if (snprintf(path, sizeof(path), "%s/%s/", buf, file)
282 >= (int)sizeof(path))
283 fatal ("Path too long!");
284
285 f = path;
286 while (*f) {
287 if (!strncmp(f, "/../", 4)) {
288 char *slash = strrchr (buf, '/');
289
290 assert (slash != NULL);
291
292 if (slash == buf) {
293
294 /* make '/' from '/directory' */
295 buf[1] = 0;
296 len = 1;
297 }
298 else {
299
300 /* strip one element */
301 *(slash) = 0;
302 len -= len - (slash - buf);
303 buf[len] = 0;
304 }
305
306 f+= 3;
307 }
308 else if (!strncmp(f, "/./", 3))
309
310 /* skip '/.' */
311 f += 2;
312 else if (!strncmp(f, "//", 2))
313
314 /* remove double slash */
315 f++;
316 else if (len == size - 1)
317 fatal ("Path too long!");
318 else {
319 buf[len++] = *(f++);
320 buf[len] = 0;
321 }
322 }
323
324 /* remove dot from '/dir/.' */
325 if (len >= 2 && buf[len-1] == '.' && buf[len-2] == '/')
326 buf[--len] = 0;
327
328 /* strip trailing slash */
329 if (len > 1 && buf[len-1] == '/')
330 buf[--len] = 0;
331 }
332
333 /* Read selected tags for a file into tags structure (or create it if NULL).
334 * If some tags are already present, don't read them.
335 * If present_tags is NULL, allocate new tags. */
read_file_tags(const char * file,struct file_tags * tags,const int tags_sel)336 struct file_tags *read_file_tags (const char *file,
337 struct file_tags *tags, const int tags_sel)
338 {
339 struct decoder *df;
340 int needed_tags;
341
342 assert (file != NULL);
343
344 if (tags == NULL)
345 tags = tags_new ();
346
347 if (file_type (file) == F_URL)
348 return tags;
349
350 needed_tags = ~tags->filled & tags_sel;
351 if (!needed_tags) {
352 debug ("No need to read any tags");
353 return tags;
354 }
355
356 df = get_decoder (file);
357 if (!df) {
358 logit ("Can't find decoder functions for %s", file);
359 return tags;
360 }
361
362 /* This makes sure that we don't cause a memory leak */
363 assert (!((needed_tags & TAGS_COMMENTS) &&
364 (tags->title || tags->artist || tags->album)));
365
366 df->info (file, tags, needed_tags);
367 tags->filled |= tags_sel;
368
369 return tags;
370 }
371
372 /* Read the content of the directory, make an array of absolute paths for
373 * all recognized files. Put directories, playlists and sound files
374 * in proper structures. Return 0 on error.*/
read_directory(const char * directory,lists_t_strs * dirs,lists_t_strs * playlists,struct plist * plist)375 int read_directory (const char *directory, lists_t_strs *dirs,
376 lists_t_strs *playlists, struct plist *plist)
377 {
378 DIR *dir;
379 struct dirent *entry;
380 int show_hidden = options_get_int ("ShowHiddenFiles");
381 int dir_is_root;
382
383 assert (directory != NULL);
384 assert (*directory == '/');
385 assert (dirs != NULL);
386 assert (playlists != NULL);
387 assert (plist != NULL);
388
389 if (!(dir = opendir(directory))) {
390 error ("Can't read directory: %s", strerror(errno));
391 return 0;
392 }
393
394 if (!strcmp(directory, "/"))
395 dir_is_root = 1;
396 else
397 dir_is_root = 0;
398
399 while ((entry = readdir(dir))) {
400 char file[PATH_MAX];
401 enum file_type type;
402
403 if (user_wants_interrupt()) {
404 error ("Interrupted! Not all files read!");
405 break;
406 }
407
408 if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
409 continue;
410 if (!show_hidden && entry->d_name[0] == '.')
411 continue;
412 if (snprintf(file, sizeof(file), "%s/%s", dir_is_root ?
413 "" : directory, entry->d_name)
414 >= (int)sizeof(file)) {
415 error ("Path too long!");
416 closedir (dir);
417 return 0;
418 }
419 type = file_type (file);
420 if (type == F_SOUND)
421 plist_add (plist, file);
422 else if (type == F_DIR)
423 lists_strs_append (dirs, file);
424 else if (type == F_PLAYLIST)
425 lists_strs_append (playlists, file);
426 }
427
428 closedir (dir);
429
430 return 1;
431 }
432
dir_symlink_loop(const ino_t inode_no,const ino_t * dir_stack,const int depth)433 static int dir_symlink_loop (const ino_t inode_no, const ino_t *dir_stack,
434 const int depth)
435 {
436 int i;
437
438 for (i = 0; i < depth; i++)
439 if (dir_stack[i] == inode_no)
440 return 1;
441
442 return 0;
443 }
444
445 /* Recursively add files from the directory to the playlist.
446 * Return 1 if OK (and even some errors), 0 if the user interrupted. */
read_directory_recurr_internal(const char * directory,struct plist * plist,ino_t ** dir_stack,int * depth)447 static int read_directory_recurr_internal (const char *directory, struct plist *plist,
448 ino_t **dir_stack, int *depth)
449 {
450 DIR *dir;
451 struct dirent *entry;
452 struct stat st;
453
454 if (stat(directory, &st)) {
455 error ("Can't stat %s: %s", directory, strerror(errno));
456 return 0;
457 }
458
459 assert (plist != NULL);
460 assert (directory != NULL);
461
462 if (*dir_stack && dir_symlink_loop(st.st_ino, *dir_stack, *depth)) {
463 logit ("Detected symlink loop on %s", directory);
464 return 1;
465 }
466
467 if (!(dir = opendir(directory))) {
468 error ("Can't read directory: %s", strerror(errno));
469 return 1;
470 }
471
472 (*depth)++;
473 *dir_stack = (ino_t *)xrealloc (*dir_stack, sizeof(ino_t) * (*depth));
474 (*dir_stack)[*depth - 1] = st.st_ino;
475
476 while ((entry = readdir(dir))) {
477 char file[PATH_MAX];
478 enum file_type type;
479
480 if (user_wants_interrupt()) {
481 error ("Interrupted! Not all files read!");
482 break;
483 }
484
485 if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
486 continue;
487 if (snprintf(file, sizeof(file), "%s/%s", directory, entry->d_name)
488 >= (int)sizeof(file)) {
489 error ("Path too long!");
490 continue;
491 }
492 type = file_type (file);
493 if (type == F_DIR)
494 read_directory_recurr_internal(file, plist, dir_stack, depth);
495 else if (type == F_SOUND && plist_find_fname(plist, file) == -1)
496 plist_add (plist, file);
497 }
498
499 (*depth)--;
500 *dir_stack = (ino_t *)xrealloc (*dir_stack, sizeof(ino_t) * (*depth));
501
502 closedir (dir);
503 return 1;
504 }
505
read_directory_recurr(const char * directory,struct plist * plist)506 int read_directory_recurr (const char *directory, struct plist *plist)
507 {
508 int ret;
509 int depth = 0;
510 ino_t *dir_stack = NULL;
511
512 ret = read_directory_recurr_internal (directory, plist, &dir_stack,
513 &depth);
514
515 if (dir_stack)
516 free (dir_stack);
517
518 return ret;
519 }
520
521 /* Return the file extension position or NULL if the file has no extension. */
ext_pos(const char * file)522 char *ext_pos (const char *file)
523 {
524 char *ext = strrchr (file, '.');
525 char *slash = strrchr (file, '/');
526
527 /* don't treat dot in ./file or /.file as a dot before extension */
528 if (ext && (!slash || slash < ext) && ext != file && *(ext-1) != '/')
529 ext++;
530 else
531 ext = NULL;
532
533 return ext;
534 }
535
536 /* Read one line from a file, strip trailing end of line chars.
537 * Returned memory is malloc()ed. Return NULL on error or EOF. */
read_line(FILE * file)538 char *read_line (FILE *file)
539 {
540 int line_alloc = READ_LINE_INIT_SIZE;
541 int len = 0;
542 char *line = (char *)xmalloc (sizeof(char) * line_alloc);
543
544 while (1) {
545 if (!fgets(line + len, line_alloc - len, file))
546 break;
547 len = strlen(line);
548
549 if (line[len-1] == '\n')
550 break;
551
552 /* If we are here, it means that line is longer than the buffer. */
553 line_alloc *= 2;
554 line = (char *)xrealloc (line, sizeof(char) * line_alloc);
555 }
556
557 if (len == 0) {
558 free (line);
559 return NULL;
560 }
561
562 if (line[len-1] == '\n')
563 line[--len] = 0;
564 if (len > 0 && line[len-1] == '\r')
565 line[--len] = 0;
566
567 return line;
568 }
569
570 /* Return malloc()ed string in form "base/name". */
add_dir_file(const char * base,const char * name)571 static char *add_dir_file (const char *base, const char *name)
572 {
573 char *path;
574 int base_is_root;
575
576 base_is_root = !strcmp (base, "/") ? 1 : 0;
577 path = (char *)xmalloc (sizeof(char) *
578 (strlen(base) + strlen(name) + 2));
579
580 sprintf (path, "%s/%s", base_is_root ? "" : base, name);
581
582 return path;
583 }
584
585 /* Find directories having a prefix of 'pattern'.
586 * - If there are no matches, NULL is returned.
587 * - If there is one such directory, it is returned with a trailing '/'.
588 * - Otherwise the longest common prefix is returned (with no trailing '/').
589 * (This is used for directory auto-completion.)
590 * Returned memory is malloc()ed.
591 * 'pattern' is temporarily modified! */
find_match_dir(char * pattern)592 char *find_match_dir (char *pattern)
593 {
594 char *slash;
595 DIR *dir;
596 struct dirent *entry;
597 int name_len;
598 char *name;
599 char *matching_dir = NULL;
600 char *search_dir;
601 int unambiguous = 1;
602
603 if (!pattern[0])
604 return NULL;
605
606 /* strip the last directory */
607 slash = strrchr (pattern, '/');
608 if (!slash)
609 return NULL;
610 if (slash == pattern) {
611 /* only '/dir' */
612 search_dir = xstrdup ("/");
613 }
614 else {
615 *slash = 0;
616 search_dir = xstrdup (pattern);
617 *slash = '/';
618 }
619
620 name = slash + 1;
621 name_len = strlen (name);
622
623 if (!(dir = opendir(search_dir)))
624 return NULL;
625
626 while ((entry = readdir(dir))) {
627 if (strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..")
628 && !strncmp(entry->d_name, name, name_len)) {
629 char *path = add_dir_file (search_dir, entry->d_name);
630
631 if (is_dir(path) == 1) {
632 if (matching_dir) {
633
634 /* More matching directories - strip
635 * matching_dir to the part that is
636 * common to both paths */
637 int i = 0;
638
639 while (matching_dir[i] == path[i]
640 && path[i])
641 i++;
642 matching_dir[i] = 0;
643 free (path);
644 unambiguous = 0;
645 }
646 else
647 matching_dir = path;
648 }
649 else
650 free (path);
651 }
652 }
653
654 closedir (dir);
655 free (search_dir);
656
657 if (matching_dir && unambiguous) {
658 matching_dir = (char *)xrealloc (matching_dir,
659 sizeof(char) * (strlen(matching_dir) + 2));
660 strcat (matching_dir, "/");
661 }
662
663 return matching_dir;
664 }
665
666 /* Return != 0 if the file exists. */
file_exists(const char * file)667 int file_exists (const char *file)
668 {
669 struct stat file_stat;
670
671 if (!stat(file, &file_stat))
672 return 1;
673
674 /* Log any error other than non-existence. */
675 if (errno != ENOENT)
676 logit ("Error : %s", strerror (errno));
677
678 return 0;
679 }
680
681 /* Get the modification time of a file. Return (time_t)-1 on error */
get_mtime(const char * file)682 time_t get_mtime (const char *file)
683 {
684 struct stat stat_buf;
685
686 if (stat(file, &stat_buf) != -1)
687 return stat_buf.st_mtime;
688
689 return (time_t)-1;
690 }
691
692 /* Convert file path to absolute path;
693 * resulting string is allocated and must be freed afterwards. */
absolute_path(const char * path,const char * cwd)694 char *absolute_path (const char *path, const char *cwd)
695 {
696 char tmp[2*PATH_MAX];
697 char *result;
698
699 assert (path);
700 assert (cwd);
701
702 if(path[0] != '/' && !is_url(path)) {
703 strncpy (tmp, cwd, sizeof(tmp));
704 tmp[sizeof(tmp)-1] = 0;
705
706 resolve_path (tmp, sizeof(tmp), path);
707
708 result = (char *)xmalloc (sizeof(char) * (strlen(tmp)+1));
709 strcpy (result, tmp);
710 }
711 else {
712 result = (char *)xmalloc (sizeof(char) * (strlen(path)+1));
713 strcpy (result, path);
714 }
715
716 return result;
717 }
718
719 /* Check that a file which may cause other applications to be invoked
720 * is secure against tampering. */
is_secure(const char * file)721 bool is_secure (const char *file)
722 {
723 struct stat sb;
724
725 assert (file && file[0]);
726
727 if (stat (file, &sb) == -1)
728 return true;
729 if (!S_ISREG(sb.st_mode))
730 return false;
731 if (sb.st_mode & (S_IWGRP|S_IWOTH))
732 return false;
733 if (sb.st_uid != 0 && sb.st_uid != geteuid ())
734 return false;
735
736 return true;
737 }
738