1 /* filelist.c
2 
3 Copyright (C) 1999-2003 Tom Gilbert.
4 Copyright (C) 2010-2020 Daniel Friesel.
5 
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to
8 deal in the Software without restriction, including without limitation the
9 rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 sell copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
12 
13 The above copyright notice and this permission notice shall be included in
14 all copies of the Software and its documentation and acknowledgment shall be
15 given in the documentation and software packages that this Software was
16 used.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
22 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 
25 */
26 
27 #ifdef HAVE_LIBEXIF
28 #include <libexif/exif-data.h>
29 #endif
30 
31 #include "feh.h"
32 #include "filelist.h"
33 #include "signals.h"
34 #include "options.h"
35 
36 gib_list *filelist = NULL;
37 gib_list *original_file_items = NULL; /* original file items from argv */
38 int filelist_len = 0;
39 gib_list *current_file = NULL;
40 extern int errno;
41 
42 static gib_list *rm_filelist = NULL;
43 
feh_file_new(char * filename)44 feh_file *feh_file_new(char *filename)
45 {
46 	feh_file *newfile;
47 	char *s;
48 
49 	newfile = (feh_file *) emalloc(sizeof(feh_file));
50 	newfile->caption = NULL;
51 	newfile->filename = estrdup(filename);
52 	s = strrchr(filename, '/');
53 	if (s)
54 		newfile->name = estrdup(s + 1);
55 	else
56 		newfile->name = estrdup(filename);
57 	newfile->info = NULL;
58 #ifdef HAVE_LIBEXIF
59 	newfile->ed = NULL;
60 #endif
61 	return(newfile);
62 }
63 
feh_file_free(feh_file * file)64 void feh_file_free(feh_file * file)
65 {
66 	if (!file)
67 		return;
68 	if (file->filename)
69 		free(file->filename);
70 	if (file->name)
71 		free(file->name);
72 	if (file->caption)
73 		free(file->caption);
74 	if (file->info)
75 		feh_file_info_free(file->info);
76 #ifdef HAVE_LIBEXIF
77 	if (file->ed)
78 		exif_data_unref(file->ed);
79 #endif
80 	free(file);
81 	return;
82 }
83 
feh_file_info_new(void)84 feh_file_info *feh_file_info_new(void)
85 {
86 	feh_file_info *info;
87 
88 
89 	info = (feh_file_info *) emalloc(sizeof(feh_file_info));
90 
91 	info->width = 0;
92 	info->height = 0;
93 	info->size = 0;
94 	info->pixels = 0;
95 	info->has_alpha = 0;
96 	info->format = NULL;
97 	info->extension = NULL;
98 
99 	return(info);
100 }
101 
feh_file_info_free(feh_file_info * info)102 void feh_file_info_free(feh_file_info * info)
103 {
104 	if (!info)
105 		return;
106 	if (info->format)
107 		free(info->format);
108 	if (info->extension)
109 		free(info->extension);
110 	free(info);
111 	return;
112 }
113 
feh_file_rm_and_free(gib_list * list,gib_list * l)114 gib_list *feh_file_rm_and_free(gib_list * list, gib_list * l)
115 {
116 	unlink(FEH_FILE(l->data)->filename);
117 	return(feh_file_remove_from_list(list, l));
118 }
119 
feh_file_remove_from_list(gib_list * list,gib_list * l)120 gib_list *feh_file_remove_from_list(gib_list * list, gib_list * l)
121 {
122 	feh_file_free(FEH_FILE(l->data));
123 	D(("filelist_len %d -> %d\n", filelist_len, filelist_len - 1));
124 	filelist_len--;
125 	return(gib_list_remove(list, l));
126 }
127 
file_selector_all(const struct dirent * unused)128 int file_selector_all(const struct dirent *unused __attribute__((unused)))
129 {
130   return 1;
131 }
132 
feh_print_stat_error(char * path)133 static void feh_print_stat_error(char *path)
134 {
135 	if (opt.quiet)
136 		return;
137 
138 	switch (errno) {
139 	case ENOENT:
140 	case ENOTDIR:
141 		weprintf("%s does not exist - skipping", path);
142 		break;
143 	case ELOOP:
144 		weprintf("%s - too many levels of symbolic links - skipping", path);
145 		break;
146 	case EACCES:
147 		weprintf("you don't have permission to open %s - skipping", path);
148 		break;
149 	case EOVERFLOW:
150 		weprintf("Cannot open %s - EOVERFLOW.\n"
151 			"Recompile with stat64=1 to fix this", path);
152 		break;
153 	default:
154 		weprintf("couldn't open %s", path);
155 		break;
156 	}
157 }
158 
add_stdin_to_filelist()159 static void add_stdin_to_filelist()
160 {
161 	char buf[1024];
162 	size_t readsize;
163 	char *sfn = estrjoin("_", "/tmp/feh_stdin", "XXXXXX", NULL);
164 	int fd = mkstemp(sfn);
165 	FILE *outfile;
166 
167 	if (fd == -1) {
168 		free(sfn);
169 		weprintf("cannot read from stdin: mktemp:");
170 		return;
171 	}
172 
173 	outfile = fdopen(fd, "w");
174 
175 	if (outfile == NULL) {
176 		free(sfn);
177 		weprintf("cannot read from stdin: fdopen:");
178 		return;
179 	}
180 
181 	while ((readsize = fread(buf, sizeof(char), sizeof(buf), stdin)) > 0) {
182 		if (fwrite(buf, sizeof(char), readsize, outfile) < readsize) {
183 			free(sfn);
184 			return;
185 		}
186 	}
187 	fclose(outfile);
188 
189 	filelist = gib_list_add_front(filelist, feh_file_new(sfn));
190 	add_file_to_rm_filelist(sfn);
191 	free(sfn);
192 }
193 
194 
195 /* Recursive */
add_file_to_filelist_recursively(char * origpath,unsigned char level)196 void add_file_to_filelist_recursively(char *origpath, unsigned char level)
197 {
198 	struct stat st;
199 	char *path;
200 
201 	if (!origpath)
202 		return;
203 
204 	path = estrdup(origpath);
205 	D(("file is %s\n", path));
206 
207 	if (level == FILELIST_FIRST) {
208 		/* First time through, sort out pathname */
209 		int len = 0;
210 
211 		len = strlen(path);
212 		if (path[len - 1] == '/')
213 			path[len - 1] = '\0';
214 
215 		if (path_is_url(path)) {
216 			D(("Adding url %s to filelist\n", path));
217 			filelist = gib_list_add_front(filelist, feh_file_new(path));
218 			/* We'll download it later... */
219 			free(path);
220 			return;
221 		} else if ((len == 1) && (path[0] == '-')) {
222 			D(("Adding temporary file for stdin (-) to filelist\n"));
223 			add_stdin_to_filelist();
224 			free(path);
225 			return;
226 		} else if (opt.filelistfile) {
227 			char *newpath = feh_absolute_path(path);
228 
229 			free(path);
230 			path = newpath;
231 		}
232 	}
233 
234 	errno = 0;
235 	if (stat(path, &st)) {
236 		feh_print_stat_error(path);
237 		free(path);
238 		return;
239 	}
240 
241 	if ((S_ISDIR(st.st_mode)) && (level != FILELIST_LAST)) {
242 		struct dirent **de;
243 		DIR *dir;
244 		int cnt, n;
245 
246 		D(("It is a directory\n"));
247 
248 		if ((dir = opendir(path)) == NULL) {
249 			if (!opt.quiet)
250 				weprintf("couldn't open directory %s:", path);
251 			free(path);
252 			return;
253 		}
254 		n = scandir(path, &de, file_selector_all, alphasort);
255 		if (n < 0) {
256 			switch (errno) {
257 			case ENOMEM:
258 				weprintf("Insufficient memory to scan directory %s:", path);
259 				break;
260 			default:
261 				weprintf("Failed to scan directory %s:", path);
262 			}
263 		} else {
264 			for (cnt = 0; cnt < n; cnt++) {
265 				if (strcmp(de[cnt]->d_name, ".")
266 						&& strcmp(de[cnt]->d_name, "..")) {
267 					char *newfile;
268 
269 					newfile = estrjoin("", path, "/", de[cnt]->d_name, NULL);
270 
271 					/* This ensures we go down one level even if not fully recursive
272 					   - this way "feh some_dir" expands to some_dir's contents */
273 					if (opt.recursive)
274 						add_file_to_filelist_recursively(newfile, FILELIST_CONTINUE);
275 					else
276 						add_file_to_filelist_recursively(newfile, FILELIST_LAST);
277 
278 					free(newfile);
279 				}
280 				free(de[cnt]);
281 			}
282 			free(de);
283 		}
284 		closedir(dir);
285 	} else if (S_ISREG(st.st_mode)) {
286 		D(("Adding regular file %s to filelist\n", path));
287 		filelist = gib_list_add_front(filelist, feh_file_new(path));
288 	}
289 	free(path);
290 	return;
291 }
292 
add_file_to_rm_filelist(char * file)293 void add_file_to_rm_filelist(char *file)
294 {
295 	rm_filelist = gib_list_add_front(rm_filelist, feh_file_new(file));
296 	return;
297 }
298 
delete_rm_files(void)299 void delete_rm_files(void)
300 {
301 	gib_list *l;
302 
303 	for (l = rm_filelist; l; l = l->next)
304 		unlink(FEH_FILE(l->data)->filename);
305 	return;
306 }
307 
feh_file_info_preload(gib_list * list)308 gib_list *feh_file_info_preload(gib_list * list)
309 {
310 	gib_list *l;
311 	feh_file *file = NULL;
312 	gib_list *remove_list = NULL;
313 
314 	for (l = list; l; l = l->next) {
315 		file = FEH_FILE(l->data);
316 		D(("file %p, file->next %p, file->name %s\n", l, l->next, file->name));
317 		if (feh_file_info_load(file, NULL)) {
318 			D(("Failed to load file %p\n", file));
319 			remove_list = gib_list_add_front(remove_list, l);
320 			if (opt.verbose)
321 				feh_display_status('x');
322 		} else if (((unsigned int)file->info->width < opt.min_width)
323 				|| ((unsigned int)file->info->width > opt.max_width)
324 				|| ((unsigned int)file->info->height < opt.min_height)
325 				|| ((unsigned int)file->info->height > opt.max_height)) {
326 			remove_list = gib_list_add_front(remove_list, l);
327 			if (opt.verbose)
328 				feh_display_status('s');
329 		} else if (opt.verbose)
330 			feh_display_status('.');
331 		if (sig_exit) {
332 			feh_display_status(0);
333 			exit(sig_exit);
334 		}
335 	}
336 	if (opt.verbose)
337 		feh_display_status(0);
338 
339 	if (remove_list) {
340 		for (l = remove_list; l; l = l->next) {
341 			feh_file_free(FEH_FILE(((gib_list *) l->data)->data));
342 			filelist = list = gib_list_remove(list, (gib_list *) l->data);
343 		}
344 
345 		gib_list_free(remove_list);
346 	}
347 
348 	return(list);
349 }
350 
feh_file_info_load(feh_file * file,Imlib_Image im)351 int feh_file_info_load(feh_file * file, Imlib_Image im)
352 {
353 	struct stat st;
354 	int need_free = 1;
355 	Imlib_Image im1;
356 
357 	D(("im is %p\n", im));
358 
359 	if (im)
360 		need_free = 0;
361 
362 	errno = 0;
363 	if (stat(file->filename, &st)) {
364 		feh_print_stat_error(file->filename);
365 		return(1);
366 	}
367 
368 	if (im)
369 		im1 = im;
370 	else if (!feh_load_image(&im1, file) || !im1)
371 		return(1);
372 
373 	file->info = feh_file_info_new();
374 
375 	file->info->width = gib_imlib_image_get_width(im1);
376 	file->info->height = gib_imlib_image_get_height(im1);
377 
378 	file->info->has_alpha = gib_imlib_image_has_alpha(im1);
379 
380 	file->info->pixels = file->info->width * file->info->height;
381 
382 	file->info->format = estrdup(gib_imlib_image_format(im1));
383 
384 	file->info->size = st.st_size;
385 
386 	if (need_free)
387 		gib_imlib_free_image_and_decache(im1);
388 	return(0);
389 }
390 
feh_file_dirname(char * dst,feh_file * f,int maxlen)391 void feh_file_dirname(char *dst, feh_file * f, int maxlen)
392 {
393 	int n = strlen(f->filename) - strlen(f->name);
394 
395 	/* Give up on long dirnames */
396 	if (n <= 0 || n >= maxlen) {
397 		dst[0] = '\0';
398 		return;
399 	}
400 
401 	strncpy(dst, f->filename, n);
402 	dst[n] = '\0';
403 }
404 
strcmp_or_strverscmp(const char * s1,const char * s2)405 static inline int strcmp_or_strverscmp(const char *s1, const char *s2)
406 {
407 	if (!opt.version_sort)
408 		return(strcmp(s1, s2));
409 	else
410 		return(strverscmp(s1, s2));
411 }
412 
feh_cmp_filename(void * file1,void * file2)413 int feh_cmp_filename(void *file1, void *file2)
414 {
415 	return(strcmp_or_strverscmp(FEH_FILE(file1)->filename, FEH_FILE(file2)->filename));
416 }
417 
feh_cmp_name(void * file1,void * file2)418 int feh_cmp_name(void *file1, void *file2)
419 {
420 	return(strcmp_or_strverscmp(FEH_FILE(file1)->name, FEH_FILE(file2)->name));
421 }
422 
feh_cmp_dirname(void * file1,void * file2)423 int feh_cmp_dirname(void *file1, void *file2)
424 {
425 	char dir1[PATH_MAX], dir2[PATH_MAX];
426 	int cmp;
427 	feh_file_dirname(dir1, FEH_FILE(file1), PATH_MAX);
428 	feh_file_dirname(dir2, FEH_FILE(file2), PATH_MAX);
429 	if ((cmp = strcmp_or_strverscmp(dir1, dir2)) != 0)
430 		return(cmp);
431 	return(feh_cmp_name(file1, file2));
432 }
433 
434 /* Return -1 if file1 is _newer_ than file2 */
feh_cmp_mtime(void * file1,void * file2)435 int feh_cmp_mtime(void *file1, void *file2)
436 {
437 	struct stat s1, s2;
438 
439 	if (stat(FEH_FILE(file1)->filename, &s1)) {
440 		feh_print_stat_error(FEH_FILE(file1)->filename);
441 		return(-1);
442 	}
443 
444 	if (stat(FEH_FILE(file2)->filename, &s2)) {
445 		feh_print_stat_error(FEH_FILE(file2)->filename);
446 		return(-1);
447 	}
448 
449 	/* gib_list_sort is not stable, so explicitly return 0 as -1 */
450 	return(s1.st_mtime >= s2.st_mtime ? -1 : 1);
451 }
452 
feh_cmp_width(void * file1,void * file2)453 int feh_cmp_width(void *file1, void *file2)
454 {
455 	return((FEH_FILE(file1)->info->width - FEH_FILE(file2)->info->width));
456 }
457 
feh_cmp_height(void * file1,void * file2)458 int feh_cmp_height(void *file1, void *file2)
459 {
460 	return((FEH_FILE(file1)->info->height - FEH_FILE(file2)->info->height));
461 }
462 
feh_cmp_pixels(void * file1,void * file2)463 int feh_cmp_pixels(void *file1, void *file2)
464 {
465 	return((FEH_FILE(file1)->info->pixels - FEH_FILE(file2)->info->pixels));
466 }
467 
feh_cmp_size(void * file1,void * file2)468 int feh_cmp_size(void *file1, void *file2)
469 {
470 	return((FEH_FILE(file1)->info->size - FEH_FILE(file2)->info->size));
471 }
472 
feh_cmp_format(void * file1,void * file2)473 int feh_cmp_format(void *file1, void *file2)
474 {
475 	return(strcmp(FEH_FILE(file1)->info->format, FEH_FILE(file2)->info->format));
476 }
477 
feh_prepare_filelist(void)478 void feh_prepare_filelist(void)
479 {
480 	/*
481 	 * list and customlist mode as well as the somewhat more fancy sort modes
482 	 * need access to file infos. Preloading them is also useful for
483 	 * list/customlist as --min-dimension/--max-dimension may filter images
484 	 * which should not be processed.
485 	 * Finally, if --min-dimension/--max-dimension (-> opt.filter_by_dimensions)
486 	 * is set and we're in thumbnail mode, we need to filter images first so
487 	 * we can create a properly sized thumbnail list.
488 	 */
489 	if (opt.list || opt.preload || opt.customlist || (opt.sort > SORT_MTIME)
490 			|| (opt.filter_by_dimensions && (opt.index || opt.thumbs || opt.bgmode))) {
491 		/* For these sort options, we have to preload images */
492 		filelist = feh_file_info_preload(filelist);
493 		if (!gib_list_length(filelist))
494 			show_mini_usage();
495 	}
496 
497 	D(("sort mode requested is: %d\n", opt.sort));
498 	switch (opt.sort) {
499 	case SORT_NONE:
500 		if (opt.randomize) {
501 			/* Randomize the filename order */
502 			filelist = gib_list_randomize(filelist);
503 		} else if (!opt.reverse) {
504 			/* Let's reverse the list. Its back-to-front right now ;) */
505 			filelist = gib_list_reverse(filelist);
506 		}
507 		break;
508 	case SORT_NAME:
509 		filelist = gib_list_sort(filelist, feh_cmp_name);
510 		break;
511 	case SORT_FILENAME:
512 		filelist = gib_list_sort(filelist, feh_cmp_filename);
513 		break;
514 	case SORT_DIRNAME:
515 		filelist = gib_list_sort(filelist, feh_cmp_dirname);
516 		break;
517 	case SORT_MTIME:
518 		filelist = gib_list_sort(filelist, feh_cmp_mtime);
519 		break;
520 	case SORT_WIDTH:
521 		filelist = gib_list_sort(filelist, feh_cmp_width);
522 		break;
523 	case SORT_HEIGHT:
524 		filelist = gib_list_sort(filelist, feh_cmp_height);
525 		break;
526 	case SORT_PIXELS:
527 		filelist = gib_list_sort(filelist, feh_cmp_pixels);
528 		break;
529 	case SORT_SIZE:
530 		filelist = gib_list_sort(filelist, feh_cmp_size);
531 		break;
532 	case SORT_FORMAT:
533 		filelist = gib_list_sort(filelist, feh_cmp_format);
534 		break;
535 	default:
536 		break;
537 	}
538 
539 	/* no point reversing a random list */
540 	if (opt.reverse && (opt.sort != SORT_NONE)) {
541 		D(("Reversing filelist as requested\n"));
542 		filelist = gib_list_reverse(filelist);
543 	}
544 
545 	return;
546 }
547 
feh_write_filelist(gib_list * list,char * filename)548 int feh_write_filelist(gib_list * list, char *filename)
549 {
550 	FILE *fp;
551 	gib_list *l;
552 
553 	if (!list || !filename || !strcmp(filename, "/dev/stdin"))
554 		return(0);
555 
556 	errno = 0;
557 	if ((fp = fopen(filename, "w")) == NULL) {
558 		weprintf("can't write filelist %s:", filename);
559 		return(0);
560 	}
561 
562 	for (l = list; l; l = l->next)
563 		fprintf(fp, "%s\n", (FEH_FILE(l->data)->filename));
564 
565 	fclose(fp);
566 
567 	return(1);
568 }
569 
feh_read_filelist(char * filename)570 gib_list *feh_read_filelist(char *filename)
571 {
572 	FILE *fp;
573 	gib_list *list = NULL;
574 	char s[1024], s1[1024];
575 	Imlib_Load_Error err = IMLIB_LOAD_ERROR_NONE;
576 	Imlib_Image tmp_im;
577 	struct stat st;
578 	signed short tmp_conversion_timeout;
579 
580 	if (!filename)
581 		return(NULL);
582 
583 	/*
584 	 * feh_load_image will fail horribly if filename is not seekable
585 	 */
586 	tmp_conversion_timeout = opt.conversion_timeout;
587 	opt.conversion_timeout = -1;
588 	if (!stat(filename, &st) && S_ISREG(st.st_mode)) {
589 		tmp_im = imlib_load_image_with_error_return(filename, &err);
590 		if (err == IMLIB_LOAD_ERROR_NONE) {
591 			gib_imlib_free_image_and_decache(tmp_im);
592 			weprintf("Filelist file %s is an image, refusing to use it.\n"
593 				"Did you mix up -f and -F?", filename);
594 			opt.filelistfile = NULL;
595 			return NULL;
596 		}
597 	}
598 	opt.conversion_timeout = tmp_conversion_timeout;
599 
600 	errno = 0;
601 
602 	if (!strcmp(filename, "/dev/stdin"))
603 		fp = stdin;
604 	else
605 		fp = fopen(filename, "r");
606 
607 	if (fp == NULL) {
608 		/* return quietly, as it's okay to specify a filelist file that doesn't
609 		   exist. In that case we create it on exit. */
610 		return(NULL);
611 	}
612 
613 	for (; fgets(s, sizeof(s), fp);) {
614 		D(("Got line '%s'\n", s));
615 		s1[0] = '\0';
616 		sscanf(s, "%[^\n]", (char *) &s1);
617 		if (!(*s1) || (*s1 == '\n'))
618 			continue;
619 		D(("Got filename %s from filelist file\n", s1));
620 		/* Add it to the new list */
621 		list = gib_list_add_front(list, feh_file_new(s1));
622 	}
623 	if (strcmp(filename, "/dev/stdin"))
624 		fclose(fp);
625 
626 	return(list);
627 }
628 
feh_absolute_path(char * path)629 char *feh_absolute_path(char *path)
630 {
631 	char cwd[PATH_MAX];
632 	char fullpath[PATH_MAX];
633 	char temp[PATH_MAX];
634 	char *ret;
635 
636 	if (!path)
637 		return(NULL);
638 	if (path[0] == '/' || path_is_url(path))
639 		return(estrdup(path));
640 	/* This path is not relative. We're gonna convert it, so that a
641 	   filelist file can be saved anywhere and feh will still find the
642 	   images */
643 	D(("Need to convert %s to an absolute form\n", path));
644 	/* I SHOULD be able to just use a simple realpath() here, but dumb *
645 	   old Solaris's realpath doesn't return an absolute path if the
646 	   path you give it is relative. Linux and BSD get this right... */
647 	if (getcwd(cwd, sizeof(cwd)) == NULL)
648 		eprintf("Cannot determine working directory:");
649 	snprintf(temp, sizeof(temp), "%s/%s", cwd, path);
650 	if (realpath(temp, fullpath) != NULL) {
651 		ret = estrdup(fullpath);
652 	} else {
653 		ret = estrdup(temp);
654 	}
655 	D(("Converted path to %s\n", ret));
656 	return(ret);
657 }
658 
feh_save_filelist()659 void feh_save_filelist()
660 {
661 	char *tmpname;
662 
663 	tmpname = feh_unique_filename("", "filelist");
664 
665 	if (opt.verbose)
666 		fprintf(stderr, "saving filelist to filename '%s'\n", tmpname);
667 
668 	feh_write_filelist(filelist, tmpname);
669 	free(tmpname);
670 	return;
671 }
672