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